Files
Web_BAI_Manage_ApiServer/pocket-base/spec/changes.2026-03-23-pocketbase-hooks-auth-hardening.md

432 lines
13 KiB
Markdown
Raw Normal View History

# OpenSpec 变更记录PocketBase Hooks 认证链路加固
## 日期
- 2026-03-23
## 范围
本次变更覆盖 `pocket-base/` 下 PocketBase hooks 项目的微信登录、平台注册/登录、资料更新、token 刷新、认证落库、错误可观测性、索引策略、字典管理、附件管理、文档管理、文档操作历史、页面辅助操作、健康检查版本探针、OpenAPI 鉴权与统一响应规范。
---
## 一、认证模型调整
### 1. 认证体系
- 保持 PocketBase 原生 auth token 作为唯一正式认证令牌。
- 登录与刷新响应统一为项目标准结构:`code``msg``data`,认证成功时额外返回顶层 `token`
- `authMethod` 统一使用空字符串 `''`,避免触发不必要的 MFA / login alerts 校验。
### 2. Header 规则
- 正式认证 Header 为:`Authorization: Bearer <token>`
- 非标准 Header `Open-Authorization` 不属于本项目接口定义。
- `users_wx_openid` Header 已从 active hooks 鉴权链路移除。
---
## 二、身份字段与数据模型约束
### 1. openid 作为唯一业务身份锚点
- `tbl_auth_users` 统一保留 `openid` 作为全平台身份锚点。
- 微信用户:`openid = 微信 openid`
- 平台用户:`openid = 服务端生成的 GUID`
- 业务逻辑中不再使用 `users_wx_openid`
- 用户查询、token 刷新、资料更新均基于 auth record 的 `openid`
### 2. auth 集合兼容字段
由于 `tbl_auth_users` 当前为 PocketBase `auth` 集合,登录注册时为兼容 PocketBase 原生 auth 校验,新增以下兼容策略:
- `email` 使用占位格式:
- 微信用户:`<openid>@wechat.local`
- 平台用户:`<openid>@manage.local`
- 自动生成随机密码
- 自动补齐 `passwordConfirm`
说明:
- 占位 `email` 仅用于满足 auth 集合保存条件,不代表用户真实邮箱。
- 业务主身份仍然是 `openid`
### 3. 自定义字段可空策略
- `tbl_auth_users` 的自定义字段目标约束为:除 `openid` 外,其余业务字段均允许为空。
- 已将 schema 脚本中的 `user_id` 改为非必填。
- 其余业务字段保持非必填。
---
## 三、查询与排序修复
### 1. 移除无意义的 `created` 排序
在 hooks 查询中,以下查询原先使用 `'-created'` 排序:
-`openid` 查询用户
-`company_id` 查询公司
-`users_phone` 查询重复手机号
该写法在 PocketBase 当前运行场景下触发:
- `invalid sort field "created"`
现已统一移除排序参数,改为空排序字符串,因为这些查询本质上均为精确匹配或去重检查,不依赖排序。
---
## 四、错误可观测性增强
### 1. 登录路由显式错误响应
`POST /api/wechat/login` 新增局部 try/catch
- 保留业务状态码
- 返回 `{ code, msg, data }`
- 写入 `logger.error('微信登录失败', ...)`
### 2. 全局错误包装顺序修正
- `routerUse(...)` 全局错误包装提前到路由注册前。
- 统一兼容 `err.statusCode` / `err.status`
### 3. auth 保存失败透传
新增 `saveAuthUserRecord(record)` 包装 `$app.save(record)`
- 失败时统一抛出 `保存认证用户失败`
- 附带 `originalMessage``originalData`
目的:
- 避免 PocketBase 默认 `Something went wrong while processing your request.` 吞掉具体原因。
---
## 五、数据库索引策略修复
### 1. users_phone 索引调整
原设计:
- `users_phone` 唯一索引
问题:
- 新用户注册阶段手机号为空,多个空值会触发唯一约束冲突,导致注册失败。
现调整为:
- `users_phone` 普通索引
说明:
- 手机号唯一性改由业务逻辑在资料完善阶段校验。
- 允许多个未完善资料用户以空手机号存在。
---
## 六、接口契约同步结果
当前 active PocketBase hooks 契约如下:
- `POST /api/system/test-helloworld`
- `POST /api/system/health`
- `POST /api/system/refresh-token`
- `POST /api/platform/register`
- `POST /api/platform/login`
- `POST /api/wechat/login`
- `POST /api/wechat/profile`
- `POST /api/dictionary/list`
- `POST /api/dictionary/detail`
- `POST /api/dictionary/create`
- `POST /api/dictionary/update`
- `POST /api/dictionary/delete`
- `POST /api/attachment/list`
- `POST /api/attachment/detail`
- `POST /api/attachment/upload`
- `POST /api/attachment/delete`
- `POST /api/document/list`
- `POST /api/document/detail`
- `POST /api/document/create`
- `POST /api/document/update`
- `POST /api/document/delete`
- `POST /api/document-history/list`
其中平台用户链路补充为:
### `POST /api/platform/register`
- body 必填:`users_name``users_phone``password``passwordConfirm``users_picture`
- 自动生成 GUID 并写入统一身份字段 `openid`
- 写入 `users_idtype = ManagePlatform`
- 成功时返回统一结构:`code``msg``data`,并在顶层额外返回 `token`
### `POST /api/platform/login`
- body 必填:`login_account``password`
- 仅允许 `users_idtype = ManagePlatform`
- 前端使用邮箱或手机号 + 密码提交
- 服务端先通过 PocketBase `auth-with-password` 校验身份,再由当前 hooks 进程签发正式 token
- 成功时返回统一结构:`code``msg``data`,并在顶层额外返回 `token`
其中:
### `POST /api/wechat/login`
- body 必填:`users_wx_code`
- 自动以微信 code 换取微信侧 `openid` 并写入统一身份字段
- 若不存在 auth 用户则尝试创建 `tbl_auth_users` 记录
- 写入 `users_idtype = WeChat`
- 成功时返回统一结构:`code``msg``data`,并在顶层额外返回 `token`
### `POST /api/wechat/profile`
-`Authorization`
- 基于当前 auth record 的 `openid` 定位用户
- 服务端用 `users_phone_code` 换取手机号后保存
### `POST /api/system/refresh-token`
- body 可选:`users_wx_code`(允许为空)
- `Authorization` 可选:
- 若 token 仍有效:基于当前 auth record 续签
- 若 token 已过期:回退到微信 code 重签流程
- 若 token 已过期且未提供 `users_wx_code`,返回:`token已过期请上传users_wx_code`
- 返回统一结构:`code``msg``data`,并在顶层额外返回新 `token`
- 属于系统级通用认证能力,不限定为微信专属接口
### 字典管理接口
新增 `dictionary` 分类接口,统一要求平台管理用户访问:
- `POST /api/dictionary/list`
- 支持按 `dict_name` 模糊搜索
- 返回字典全量信息,并将 `dict_word_enum``dict_word_description``dict_word_sort_order` 组装为 `items`
- `POST /api/dictionary/detail`
-`dict_name` 查询单条字典
- `POST /api/dictionary/create`
- 新增字典,`system_dict_id` 自动生成,`dict_name` 唯一
- `POST /api/dictionary/update`
-`original_dict_name` / `dict_name` 更新字典
- `POST /api/dictionary/delete`
-`dict_name` 真删除字典
说明:
- `dict_word_enum``dict_word_description``dict_word_sort_order` 已统一改为 JSON 字符串持久化,其中 `dict_word_sort_order` 已改为 `text` 类型。
- 查询时统一聚合为:`items: [{ enum, description, sortOrder }]`
### 附件管理接口
新增 `attachment` 分类接口,统一要求平台管理用户访问:
- `POST /api/attachment/list`
- 支持按 `attachments_id``attachments_filename` 模糊搜索
- 支持按 `attachments_status` 过滤
- 返回附件元数据以及 PocketBase 文件流链接 `attachments_url`
- `POST /api/attachment/detail`
-`attachments_id` 查询单个附件
- 返回文件流链接与下载链接
- `POST /api/attachment/upload`
- 使用 `multipart/form-data`
- 文件字段固定为 `attachments_link`
- 上传成功后自动生成 `attachments_id`
- 自动写入 `attachments_owner = 当前用户 openid`
- `POST /api/attachment/delete`
-`attachments_id` 真删除附件
- 若该附件已被 `tbl_document.document_image``document_video` 引用,则拒绝删除
说明:
- `tbl_attachments.attachments_link` 为 PocketBase `file` 字段,保存实际文件本体。
- 对外查询时会额外补充:
- `attachments_url`
- `attachments_download_url`
### 文档管理接口
新增 `document` 分类接口,统一要求平台管理用户访问:
- `POST /api/document/list`
- 支持按 `document_id``document_title``document_subtitle``document_summary``document_keywords` 模糊搜索
- 支持按 `document_status``document_type` 过滤
- 返回时会自动联查 `tbl_attachments`
- 额外补充:
- `document_image_url`
- `document_video_url`
- `document_image_attachment`
- `document_video_attachment`
- `POST /api/document/detail`
-`document_id` 查询单条文档
- 返回与附件表联动解析后的文件流链接
- `POST /api/document/create`
- 新增文档
- `document_id` 可不传,由服务端自动生成
- `document_image``document_video` 必须传入已存在的 `attachments_id`
- 成功后会写入一条文档操作历史,类型为 `create`
- `POST /api/document/update`
-`document_id` 更新文档
- 若传入附件字段,则会校验对应 `attachments_id` 是否存在
- 成功后会写入一条文档操作历史,类型为 `update`
- `POST /api/document/delete`
-`document_id` 真删除文档
- 删除前会写入一条文档操作历史,类型为 `delete`
说明:
- `document_image``document_video` 当前保存的是 `attachments_id`,不是 PocketBase 文件字段。
- 文档查询时通过 `attachments_id -> tbl_attachments` 反查实际文件,并返回可直接访问的数据流链接。
- `document_owner` 语义为“上传者 openid”。
### 文档操作历史接口
新增 `document-history` 分类接口,统一要求平台管理用户访问:
- `POST /api/document-history/list`
- 不传 `document_id` 时返回全部文档历史
- 传入 `document_id` 时仅返回该文档历史
- 结果按创建时间倒序排列
说明:
- 操作历史表为 `tbl_document_operation_history`
- 当前由文档新增、修改、删除接口自动写入
- 主要字段为:
- `doh_document_id`
- `doh_operation_type`
- `doh_user_id`
- `doh_current_count`
- `doh_remark`
---
## 七、页面与运维辅助能力新增
### 1. PocketBase 页面
当前页面入口:
- `/pb/manage`
- `/pb/manage/login`
- `/pb/manage/dictionary-manage`
- `/pb/manage/document-manage`
页面能力:
- 首页支持跳转到子页面
- 字典管理页支持:
- Bearer Token 粘贴与本地保存
- `dict_name` 模糊搜索
- 指定字典查询
- 行内编辑基础字段
- 弹窗编辑枚举项
- 新增 / 删除字典
- 返回主页
- 文档管理页支持:
- 先上传附件到 `tbl_attachments`
- 再把返回的 `attachments_id` 写入 `tbl_document.document_image` / `document_video`
- 新增文档
- 查询文档列表
- 直接展示 PocketBase 文件流链接
- 删除文档
说明:
- 原页面 `page-b.js` 已替换为 `document-manage.js`
- 页面实际走的接口链路为:
- `/api/attachment/upload`
- `/api/document/create`
- `/api/document/list`
- `/api/document/delete`
### 2. 健康检查版本探针
`POST /api/system/health` 新增:
- `data.version`
用途:
- 通过修改 `APP_VERSION` 判断 hooks 是否已成功部署并生效
配置来源:
- 进程环境变量 `APP_VERSION`
-`runtime.js`
---
## 八、OpenAPI 与 Apifox 调试策略调整
### 1. 统一返回结构
所有对外接口统一返回:
- `code`
- `msg`
- `data`
认证成功类接口额外返回:
- `token`
不再返回以下顶层字段:
- `record`
- `meta`
### 2. 鉴权文档策略
OpenAPI 文档中已取消 `bearerAuth` 鉴权组件与接口级重复 `Authorization` 参数。
统一约定:
- 在 Apifox 环境中配置全局 Header`Authorization: Bearer {{token}}`
- 不再依赖文档中的 Bearer 组件自动注入
此举目的是:
- 避免接口页重复出现局部 `Authorization`
- 统一依赖环境变量完成鉴权注入
---
## 九、当前已知边界
1. `tbl_auth_users` 为 PocketBase `auth` 集合,因此仍受 PocketBase auth 内置规则影响。
2. 自定义字段“除 openid 外均可空”已在脚本层按目标放宽,但 auth 集合结构更新仍可能触发 PocketBase 服务端限制。
3. 若线上仍返回 PocketBase 默认 400需要确保最新 hooks 已部署并重启生效。
4. 平台登录通过回源 PocketBase REST 完成密码校验,因此 `POCKETBASE_API_URL` 必须配置为 PocketBase 进程/容器内部可达地址,不应使用外部 HTTPS 域名。
5. Apifox 环境中需自行维护全局 Header`Authorization: Bearer {{token}}`,否则鉴权接口不会自动携带 token。
---
## 十、归档建议
部署时至少同步以下文件:
- `pocket-base/bai-api-main.pb.js`
- `pocket-base/bai-web-main.pb.js`
- `pocket-base/bai_api_pb_hooks/`
- `pocket-base/bai_web_pb_hooks/`
- `pocket-base/spec/openapi.yaml`
- `script/pocketbase.js`
并在 PocketBase 环境中执行 schema 同步后重启服务,再进行接口验证。
建议归档后的发布核验顺序:
1. `POST /api/system/health`:确认 `data.version`
2. `POST /api/platform/login`:确认返回统一结构与顶层 `token`
3. `POST /api/dictionary/list`:确认鉴权与字典接口可用
---
## 十一、归档状态
- 已将本轮字典管理、PocketBase 页面、统一响应、平台登录兼容修复、健康检查版本探针、OpenAPI/Apifox 鉴权策略调整并入本变更记录。
- 本记录可作为当前 PocketBase hooks 阶段性归档基线继续维护。