在开发客服系统,中间有段实现需要频繁对知识库和提示词CRUE,但是没有后端和前端的支持,数据库接口暂时开发一个人工作量有点大,于是就采用了PocketBase,目前我已经编写了go语言的后台接口,但是这个开源项目确实挺好用的,尤其是对于一些公司算法岗位身兼多职的可以采用该方案,快速实现项目原型开发,提供演示算法,后期可以再自行写对应接口或者交给后端和前端再去实现细节接口API。之前整理的资料分享给大家。
1. 快速开始
|-----------------------------|------------------------|---------------------------------------|
| 任务名 | 执行频率 | 作用 |
| __pbDBOptimize__ | 每天凌晨 0:00(UTC) | 优化数据库表结构,提升查询性能(类似 SQLite 的 VACUUM) |
| __pbMFACleanup__ | 每小时 0 分钟 | 清理过期的多因素认证(MFA)令牌 |
| __pbOTPCleanup__ | 每小时 0 分钟 | 清理过期的一次性密码(OTP)验证码 |
| __pbLogsCleanup__ | 每 6 小时(0, 6, 12, 18 点) | 删除旧的日志记录(防止日志膨胀) |
| __pbRateLimitersCleanup__ | 每天 2:00(UTC) | 清理过期的速率限制(rate limiter)数据 |
2. 添加自定义任务
vim pu_public/pb_hooks.js
import { syncChatHistory } from "./sync_chat_history.js";
// 注册定时任务
cronAdd("sync_chat_history", "0 2 * * *", syncChatHistory);
// 你还可以注册其他钩子,例如:
// onRecordBeforeCreate(e => { ... })
PocketBase 的 pb_hooks.js(JavaScript 扩展)支持多种 钩子(Hooks) 和 调度方法(Cron),用于在特定事件发生时执行自定义逻辑。
这些钩子运行在 Deno 环境 中,拥有对 $app(核心应用实例)、数据库、HTTP 客户端等的完全访问权限。
3. 支持的钩子类型总览
|------------------|----------------------------------------------------------------------------------------------------------------------------------------|--------------------------------|
| 钩子类别 | 方法名 | 触发时机 |
| 服务启动 | onBeforeServe | PocketBase 启动前(可注册自定义路由) |
| 记录(Record)操作 | onRecordBeforeCreate,onRecordAfterCreate,onRecordBeforeUpdate,onRecordAfterUpdate,onRecordBeforeDelete,onRecordAfterDelete | 对 collections 中的记录进行 CRUD 操作前后 |
| 认证/用户 | onModelBeforeCreate,onModelAfterCreate(适用于 _users表) | 用户注册、登录等(较少用,通常用 Record 钩子) |
| 文件上传 | onFileBeforeUpload onFileAfterUpload | 上传文件前后 |
| 定时任务 | cronAdd(id, expr, handler) | 注册周期性后台任务 |
📌 注意:所有钩子函数必须在 pb_hooks.js顶层直接调用(不能嵌套在其他函数内)。
4. 详细说明 + 实战案例
4.1. onBeforeServe ------ 启动前注册自定义 API 路由
// pb_public/pb_hooks.js
onBeforeServe(() => {
// 注册 GET /api/hello
$app.on("GET", "/api/hello", (c) => {
return c.json({ message: "Hello from custom route!" });
});
// 注册 POST /api/webhook
$app.on("POST", "/api/webhook", async (c) => {
const body = await c.req.parseBody();
console.log("Webhook received:", body);
return c.json({ ok: true });
});
});
用途:创建 Webhook、外部回调、自定义 API 接口。
4.2. 记录操作钩子(最常用)
4.2.1. onRecordBeforeCreate ------ 创建前校验或修改数据
onRecordBeforeCreate((e) => {
// 只对 'orders' collection 生效
if (e.collection.name !== "orders") return;
// 自动设置创建时间(如果前端没传)
if (!e.record.get("created_at")) {
e.record.set("created_at", new Date().toISOString());
}
// 校验金额 > 0
if (e.record.get("amount") <= 0) {
throw new Error("Amount must be greater than 0");
}
});
4.2.2. onRecordAfterCreate ------ 创建后触发通知
onRecordAfterCreate(async (e) => {
if (e.collection.name === "support_tickets") {
// 发送邮件或企业微信通知
await fetch("https://qyapi.weixin.qq.com/...", {
method: "POST",
body: JSON.stringify({
msgtype: "text",
text: { content: `新工单 #${e.record.id} 已创建` }
})
});
}
});
4.2.3. onRecordBeforeUpdate ------ 禁止修改某些字段
onRecordBeforeUpdate((e) => {
if (e.collection.name === "users") {
// 禁止修改邮箱
if (e.record.get("email") !== e.oldRecord.get("email")) {
throw new Error("Email cannot be changed");
}
}
});
4.2.4. onRecordAfterDelete ------ 清理关联数据
onRecordAfterDelete(async (e) => {
if (e.collection.name === "projects") {
// 删除该项目下的所有任务
await $app.dao().deleteRecordsByExpr("tasks", "project = {:id}", { id: e.record.id });
}
});
4.3. 文件上传钩子
onFileBeforeUpload((e) => {
// 限制文件类型
if (!e.file.name.endsWith(".pdf")) {
throw new Error("Only PDF files are allowed");
}
// 限制大小(10MB)
if (e.file.size > 10 * 1024 * 1024) {
throw new Error("File too large");
}
});
onFileAfterUpload((e) => {
console.log("File uploaded:", e.file.key);
// 可触发 OCR、转码等
});
4.4. 定时任务(Cron)
// 每天凌晨 2 点清理过期 token
cronAdd("cleanup_tokens", "0 2 * * *", async () => {
const now = new Date().toISOString();
const expired = await $app.dao().findRecordsByExpr(
"auth_tokens",
"expires <= {:now}",
{ now }
);
for (const rec of expired) {
await $app.dao().deleteRecord(rec);
}
console.log(`Cleaned ${expired.length} expired tokens`);
});
4.5. 其他实用技巧
4.5.1. 获取当前用户(在 Record 钩子中)
onRecordBeforeCreate((e) => {
const userId = e.context.authRecord?.id; // 当前操作用户 ID
if (userId) {
e.record.set("created_by", userId);
}
});
4.5.2. 调试日志
console.log("Hook triggered:", e.collection.name);
console.error("Validation failed");
日志会输出到终端(pocketbase serve 的控制台)。
5. 注意事项
|----------|-----------------------------|
| 事项 | 说明 |
| 文件位置 | 必须是 pb_public/pb_hooks.js |
| 重启生效 | 修改后需重启 pocketbase serve |
| 异步支持 | 所有钩子都支持 async/await |
| 错误处理 | 抛出 Error 会中断操作并返回 400 |
| 权限 | 钩子以系统权限运行(可访问所有数据) |
| 性能 | 避免在钩子中做耗时操作(会阻塞请求) |
6. 完整示例模板
// pb_public/pb_hooks.js
// 1. 自定义路由
onBeforeServe(() => {
$app.on("GET", "/api/status", (c) => c.json({ alive: true }));
});
// 2. 自动填充创建者
onRecordBeforeCreate((e) => {
if (e.context.authRecord) {
e.record.set("created_by", e.context.authRecord.id);
}
});
// 3. 定时清理
cronAdd("daily_cleanup", "0 2 * * *", async () => {
// ...清理逻辑
});
// 4. 文件校验
onFileBeforeUpload((e) => {
if (e.file.size > 5e6) throw new Error("Max 5MB");
});