PocketBase 保留目录说明 & 钩子(Hook)调用方式与示例
一、核心需求确认
你希望了解 PocketBase 运行过程中自动生成/识别的保留目录的名称和具体作用,同时掌握 PocketBase 钩子(Hook)的调用方式,并通过实际示例理解钩子在业务场景中的应用(如数据校验、事件触发、自定义逻辑等)。
二、PocketBase 保留目录详解
PocketBase 运行时会在其工作目录(如 /opt/pocketbase)自动生成/识别多个保留目录(文件名固定,PocketBase 会优先读取),所有核心数据、配置、自定义逻辑都集中在这些目录中。以下是所有保留目录的名称、作用及关键细节:
| 目录名称 |
核心作用 |
关键文件/子目录 |
适用场景 |
pb_data/ |
最核心目录,存储所有持久化数据(数据库、配置、文件、日志等) |
- pb.db:SQLite 核心数据库(存储集合、记录、用户等)- pb_config.json:全局配置文件(应用名称、跨域、邮件等)- storage/:文件存储目录(上传的图片/文件)- logs/:运行日志(可选)- pb_auth.json:认证相关配置 |
数据备份、配置修改、文件存储管理 |
pb_hooks/ |
存放 JS/TS 钩子脚本,PocketBase 启动时自动加载,用于扩展业务逻辑 |
*.js/*.ts(如 users.hooks.js) |
数据校验、事件触发、自定义认证、请求拦截 |
pb_migrations/ |
数据库迁移脚本目录,用于管理集合结构变更(版本升级/手动修改表结构) |
*.js/*.ts(如 20240104120000_init.js) |
生产环境集合结构变更、版本迭代 |
pb_public/ |
静态文件托管目录,可通过 URL 直接访问(无需认证) |
任意静态文件(如 index.html、logo.png) |
托管前端静态页面、公共资源(如图片、JS) |
各目录详细说明
1. pb_data/(核心数据目录)
- 不可删除:删除该目录会丢失所有数据(数据库、配置、上传文件);
- 备份优先级:日常运维只需备份该目录即可完整恢复 PocketBase 所有数据;
- 关键文件说明:
pb.db:SQLite 数据库文件,存储集合定义、记录数据、管理员/用户账号等核心数据;
pb_config.json:自动生成的配置文件,可手动修改(如跨域允许的域名、邮件服务器配置),修改后需重启 PocketBase;
storage/:默认存储用户上传的文件(如集合中「File」类型字段的文件),路径对应 API 返回的 fileUrl;
- 权限要求:运行 PocketBase 的用户需有该目录的读写权限(如
chmod -R 755 pb_data/)。
2. pb_hooks/(钩子脚本目录)
- 自动加载:PocketBase 启动时会扫描该目录下所有
.js/.ts 文件并执行,无需手动配置;
- 运行环境:基于 Go 内置的 ESModule 运行时,支持大部分 ES6+ 语法,可直接使用
console.log 调试;
- 无依赖:暂不支持 npm 包,需用原生 JS/TS 编写逻辑。
3. pb_migrations/(数据库迁移目录)
- 自动生成:在管理后台修改集合结构(如添加字段)时,会自动生成迁移脚本;
- 手动执行:可通过命令
./pocketbase migrate up 执行未应用的迁移脚本;
- 回滚:支持
./pocketbase migrate down 回滚最近一次迁移(需脚本支持)。
4. pb_public/(静态文件目录)
- 访问方式:文件路径对应 URL 路径,如
pb_public/logo.png 可通过 http://域名/public/logo.png 访问;
- 权限:所有文件无需认证即可访问,适合存放公共静态资源(不建议存放敏感文件)。
三、PocketBase 钩子(Hook)调用方式与示例
1. 钩子核心概念
PocketBase 钩子是服务端事件驱动脚本,可拦截/处理系统核心事件(如数据创建/更新、用户认证、服务启动等),实现自定义业务逻辑(如数据校验、自动填充字段、日志记录、权限控制)。
钩子分为两类:
- 集合钩子(Collection Hooks):针对特定集合的操作(如
users 集合的创建/更新),最常用;
- 全局钩子(App Hooks):针对全局事件(如服务启动、HTTP 请求拦截、用户登录)。
2. 钩子基本使用步骤
- 在 PocketBase 工作目录(如
/opt/pocketbase)创建 pb_hooks 目录:
mkdir -p /opt/pocketbase/pb_hooks
- 在
pb_hooks 目录下创建 .js 文件(如 users.hooks.js);
- 编写钩子逻辑,保存后重启 PocketBase 守护进程(堡塔面板「进程守护」→ 重启);
- 测试触发条件(如创建一条用户记录),验证钩子是否生效。
3. 常用钩子示例
示例 1:集合钩子 - 数据创建前校验(必填字段+格式校验)
场景:users 集合创建记录时,强制校验「email」字段格式,且「username」字段不能为空。
创建 pb_hooks/users.hooks.js:
// 监听 users 集合的 beforeCreate 事件(创建记录前触发)
onRecordBeforeCreate((e) => {
// 仅针对 users 集合生效
if (e.record.collectionName !== "users") {
return;
}
// 1. 校验 username 不能为空
const username = e.record.get("username");
if (!username || username.trim() === "") {
throw new Error("用户名不能为空!"); // 抛出错误会终止创建操作,前端会收到该错误信息
}
// 2. 校验 email 格式
const email = e.record.get("email");
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) {
throw new Error("邮箱格式不正确!");
}
// 3. 自动填充创建时间(无需前端传参)
e.record.set("created_at", new Date().toISOString());
});
触发方式:调用 users 集合的创建 API 时自动触发:
# 测试:传入错误邮箱,会返回「邮箱格式不正确」
curl -X POST http://域名/api/collections/users/records \
-H "Content-Type: application/json" \
-d '{"username":"test","email":"invalid-email","age":25}'
示例 2:集合钩子 - 数据更新后记录日志
场景:users 集合记录更新后,自动将操作日志写入 user_logs 集合(需先创建该集合,包含 user_id、operation、update_time 字段)。
创建 pb_hooks/users.hooks.js:
// 监听 users 集合的 afterUpdate 事件(更新记录后触发)
onRecordAfterUpdate(async (e) => {
if (e.record.collectionName !== "users") {
return;
}
// 获取更新的用户ID和操作时间
const userId = e.record.id;
const updateTime = new Date().toISOString();
// 写入日志到 user_logs 集合
await $app.dao().createRecord(
$app.dao().findCollectionByNameOrId("user_logs"),
{
user_id: userId,
operation: "用户信息更新",
update_time: updateTime,
}
);
console.log(`用户 ${userId} 信息已更新,日志已记录`); // 日志会输出到 PocketBase 运行日志
});
示例 3:全局钩子 - 服务启动时初始化
场景:PocketBase 启动时,自动检查 settings 集合是否存在默认配置,不存在则创建。
创建 pb_hooks/app.hooks.js:
// 监听服务启动后事件
onAppAfterServe((e) => {
console.log("PocketBase 服务已启动,开始初始化默认配置");
// 获取 settings 集合
const settingsCollection = $app.dao().findCollectionByNameOrId("settings");
if (!settingsCollection) {
console.log("settings 集合不存在,跳过初始化");
return;
}
// 检查是否已有默认配置
const defaultSettings = $app.dao().findFirstRecordByFilter(
"settings",
"key = 'site_name'",
""
);
// 无默认配置则创建
if (!defaultSettings) {
const record = new Record(settingsCollection);
record.set("key", "site_name");
record.set("value", "我的 PocketBase 应用");
$app.dao().saveRecord(record);
console.log("默认站点名称已创建");
}
});
示例 4:全局钩子 - 拦截 HTTP 请求添加跨域头
场景:自定义跨域配置(替代 pb_config.json 中的跨域设置),允许指定域名的跨域请求。
创建 pb_hooks/app.hooks.js:
// 监听所有 HTTP 请求
onHttpRequest((e) => {
// 获取请求的 Origin 头
const origin = e.request.headers.get("Origin");
// 允许的跨域域名列表
const allowedOrigins = ["https://your-domain.com", "http://localhost:3000"];
// 若 Origin 在允许列表中,添加跨域头
if (allowedOrigins.includes(origin)) {
e.response.headers.set("Access-Control-Allow-Origin", origin);
e.response.headers.set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
e.response.headers.set("Access-Control-Allow-Headers", "Content-Type, Authorization");
}
// 处理 OPTIONS 预检请求
if (e.request.method === "OPTIONS") {
e.response.status = 204;
e.response.body = "";
}
});
示例 5:认证钩子 - 自定义用户登录逻辑
场景:用户登录时,额外校验「账号是否禁用」(users 集合需添加 is_disabled 布尔字段)。
创建 pb_hooks/auth.hooks.js:
// 监听用户密码登录事件
onRecordAuthWithPassword((e) => {
// 仅针对 users 集合的登录
if (e.record.collectionName !== "users") {
return;
}
// 检查用户是否被禁用
const isDisabled = e.record.get("is_disabled");
if (isDisabled) {
throw new Error("该账号已被禁用,请联系管理员!"); // 终止登录
}
// 登录成功后,记录最后登录时间
e.record.set("last_login_at", new Date().toISOString());
await $app.dao().saveRecord(e.record);
console.log(`用户 ${e.record.id} 登录成功,最后登录时间已更新`);
});
4. 钩子调试技巧
- 日志输出:使用
console.log(xxx) 输出调试信息,日志会写入 PocketBase 运行日志(堡塔面板「进程守护」→「日志」可查看);
- 错误排查:钩子脚本语法错误会导致 PocketBase 启动失败,可在终端手动运行
./pocketbase serve 查看具体错误信息;
- 参数打印:触发钩子时,打印事件对象
e 查看可用参数(如 console.log(JSON.stringify(e.record)))。
四、总结
- 保留目录核心要点:
pb_data/ 是核心数据仓,备份该目录即可恢复所有数据;
pb_hooks/ 存放自定义钩子脚本,启动时自动加载;
pb_public/ 托管无需认证的静态文件,pb_migrations/ 管理数据库结构变更。
- 钩子使用核心要点:
- 钩子分「集合钩子」(针对数据操作)和「全局钩子」(针对系统事件),放在
pb_hooks/ 目录自动加载;
- 常用钩子:
onRecordBeforeCreate(数据校验)、onRecordAfterUpdate(日志记录)、onHttpRequest(跨域配置);
- 钩子中抛出错误会终止原操作,适合做数据校验;可通过
$app.dao() 操作数据库(如创建/更新记录)。
- 实战建议:
- 钩子脚本优先实现核心业务逻辑(校验、自动填充),避免复杂计算;
- 修改钩子后需重启 PocketBase 才能生效;
- 生产环境需备份
pb_hooks/ 目录,避免钩子脚本丢失。