PocketBase的自定义任务【专供LLM请求耗时任务】

在开发客服系统,中间有段实现需要频繁对知识库和提示词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");
});
7. 官方参考
相关推荐
1点东西2 天前
做了近两年的Agent开发,其实真正要学的就是这五件事
llm·agent·ai编程
程序猿编码3 天前
告别改源码适配模型:纯 C++ 可配置 LLM 推理引擎,全格式全结构兼容
c++·大模型·llm·推理引擎
小林ixn3 天前
从 LangChain 到 LangGraph:用「网状工作流」解锁多 Agent 协作的正确姿势
langchain·llm·agent
武子康3 天前
自己做一个 Mini Reviewer:让 AI 审到本次准备提交的代码
人工智能·llm·agent
武子康3 天前
GPU Pod 已经 Running,为什么扩容还没变成推理容量?
人工智能·llm·agent
BlackStar_L3 天前
第二章 上下文工程
大模型·llm·agent
Together_CZ3 天前
DeepSeek-V4.1-Flash:Pushing the Limits of KV Cache Compression——推动 KV 缓存压缩的极限
缓存·llm·compression·kv cache·deepseek·v4.1-flash·推动 kv 缓存压缩的极限
桃西西呀3 天前
你拍的一堆硬币,手机怎么一眼数出有几枚?聊聊边缘、轮廓和模板匹配
人工智能·llm·图像识别
slacker-kian4 天前
[实践]-让 SAP 工程 Skill 脱离 opencode跑在自定义Agent 上
ai·llm·sap·agent·abap·adt·opencode
武子康4 天前
CLAUDE.md 越写越长,哪些规则该放到子目录?
人工智能·llm·agent