From e9fe1165e3226eb206d2ad1761d1c1ead4d3c585 Mon Sep 17 00:00:00 2001 From: XuJiacheng Date: Sun, 29 Mar 2026 16:21:34 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=A7=84=E8=8C=83=E5=8C=96PocketBase?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E5=BA=93=E6=96=87=E6=A1=A3=E4=B8=8E=E5=8E=9F?= =?UTF-8?q?=E7=94=9FAPI=E8=AE=BF=E9=97=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将数据库文档拆分为按collection命名的标准文件,统一格式 - 补充tbl_company、tbl_system_dict等表的原生访问规则 - 新增users_tag、document_create等字段 - 优化用户资料更新接口,支持非必填字段 - 添加公司原生API测试脚本 - 归档本次变更至OpenSpec --- docs/api.md | 18 +- docs/api_response_field_notes.md | 168 ++ docs/newpb.md | 95 - docs/pb_auth_model.md | 37 + docs/pb_document_tables.md | 106 -- docs/pb_tbl_attachments.md | 39 + docs/pb_tbl_auth_resources.md | 28 + docs/pb_tbl_auth_role_perms.md | 29 + docs/pb_tbl_auth_roles.md | 28 + docs/pb_tbl_auth_row_scopes.md | 29 + docs/pb_tbl_auth_user_overrides.md | 29 + docs/pb_tbl_auth_users.md | 66 + docs/pb_tbl_company.md | 48 + docs/pb_tbl_document.md | 62 + docs/pb_tbl_document_operation_history.md | 35 + docs/pb_tbl_system_dict.md | 38 + docs/tbl_auth_tables.md | 190 -- .../.openspec.yaml | 2 + .../design.md | 56 + .../proposal.md | 29 + .../pocketbase-native-data-access/spec.md | 34 + .../specs/pocketbase-schema-docs/spec.md | 29 + .../tasks.md | 19 + .../pocketbase-native-data-access/spec.md | 38 + openspec/specs/pocketbase-schema-docs/spec.md | 33 + pocket-base/bai-api-main.pb.js | 1 + .../bai_api_routes/company/records-create.js | 17 + .../bai_api_routes/dictionary/detail.js | 3 +- .../bai_api_routes/dictionary/list.js | 3 +- .../bai_api_shared/config/runtime.js | 2 - .../middlewares/requestGuards.js | 12 +- .../services/documentService.js | 1 + .../bai_api_shared/services/userService.js | 70 +- .../bai_web_pb_hooks/pages/document-manage.js | 100 +- ...6-03-23-pocketbase-hooks-auth-hardening.md | 472 ----- pocket-base/spec/openapi-manage.yaml | 526 +++++- pocket-base/spec/openapi-miniapp-company.yaml | 144 +- pocket-base/spec/openapi-wx.yaml | 1542 ++++++++++++++++- pocket-base/spec/openapi.yaml | 535 +++++- script/database_schema.md | 11 +- script/package.json | 3 +- script/pocketbase.dictionary.js | 15 + script/pocketbase.documents.js | 9 + script/pocketbase.js | 21 +- script/pocketbase.newpb.js | 1 + script/test-tbl-company-native-api.js | 125 ++ 46 files changed, 3790 insertions(+), 1108 deletions(-) create mode 100644 docs/api_response_field_notes.md delete mode 100644 docs/newpb.md create mode 100644 docs/pb_auth_model.md delete mode 100644 docs/pb_document_tables.md create mode 100644 docs/pb_tbl_attachments.md create mode 100644 docs/pb_tbl_auth_resources.md create mode 100644 docs/pb_tbl_auth_role_perms.md create mode 100644 docs/pb_tbl_auth_roles.md create mode 100644 docs/pb_tbl_auth_row_scopes.md create mode 100644 docs/pb_tbl_auth_user_overrides.md create mode 100644 docs/pb_tbl_auth_users.md create mode 100644 docs/pb_tbl_company.md create mode 100644 docs/pb_tbl_document.md create mode 100644 docs/pb_tbl_document_operation_history.md create mode 100644 docs/pb_tbl_system_dict.md delete mode 100644 docs/tbl_auth_tables.md create mode 100644 openspec/changes/archive/2026-03-29-normalize-pocketbase-schema-docs/.openspec.yaml create mode 100644 openspec/changes/archive/2026-03-29-normalize-pocketbase-schema-docs/design.md create mode 100644 openspec/changes/archive/2026-03-29-normalize-pocketbase-schema-docs/proposal.md create mode 100644 openspec/changes/archive/2026-03-29-normalize-pocketbase-schema-docs/specs/pocketbase-native-data-access/spec.md create mode 100644 openspec/changes/archive/2026-03-29-normalize-pocketbase-schema-docs/specs/pocketbase-schema-docs/spec.md create mode 100644 openspec/changes/archive/2026-03-29-normalize-pocketbase-schema-docs/tasks.md create mode 100644 openspec/specs/pocketbase-native-data-access/spec.md create mode 100644 openspec/specs/pocketbase-schema-docs/spec.md create mode 100644 pocket-base/bai_api_pb_hooks/bai_api_routes/company/records-create.js delete mode 100644 pocket-base/spec/changes.2026-03-23-pocketbase-hooks-auth-hardening.md create mode 100644 script/test-tbl-company-native-api.js diff --git a/docs/api.md b/docs/api.md index ec70e0e..8003e49 100644 --- a/docs/api.md +++ b/docs/api.md @@ -133,6 +133,7 @@ "users_type": "注册用户", "users_name": "张三", "users_phone": "13800138000", + "users_tag": "核心客户", "users_phone_masked": "138****8000", "users_picture": "ATT-1743123456789-abc123", "users_picture_url": "https://bai-api.blv-oa.com/pb/api/files/pbc_xxx/recordId/avatar.png", @@ -163,6 +164,8 @@ { "users_name": "张三", "users_phone_code": "2b7d9f2e3c4a5b6d7e8f", + "users_phone": "13800138000", + "users_tag": "核心客户", "users_picture": "ATT-1743123456789-abc123", "users_id_pic_a": "ATT-1743123456789-id-a", "users_id_pic_b": "ATT-1743123456789-id-b", @@ -174,9 +177,11 @@ | 参数名 | 类型 | 必填 | 说明 | |---|---|---|---| -| `users_name` | string | 是 | 用户姓名 | -| `users_phone_code` | string | 是 | 微信手机号获取凭证 code,后端将据此换取真实手机号 | -| `users_picture` | string | 是 | 用户头像附件的 `attachments_id` | +| `users_name` | string | 否 | 用户姓名;非空时才更新 | +| `users_phone_code` | string | 否 | 微信手机号获取凭证 code;若提供,服务端优先据此换取真实手机号 | +| `users_phone` | string | 否 | 直接写入的手机号;仅在未提供 `users_phone_code` 时生效 | +| `users_tag` | string | 否 | 用户标签;非空时才更新 | +| `users_picture` | string | 否 | 用户头像附件的 `attachments_id`;非空时才更新 | | `users_id_pic_a` | string | 否 | 证件正面附件的 `attachments_id` | | `users_id_pic_b` | string | 否 | 证件反面附件的 `attachments_id` | | `users_title_picture` | string | 否 | 资质附件的 `attachments_id` | @@ -186,8 +191,10 @@ - 从 `Authorization` 对应的 PocketBase auth record 读取当前用户 `openid` - 校验 `Authorization` - 不再从 body 读取 `users_wx_code` -- 使用 `users_phone_code` 调微信官方接口换取真实手机号 -- 将真实手机号写入数据库字段 `users_phone` +- 所有字段均允许不传或传空 +- 若传入 `users_phone_code`,优先调用微信接口换取真实手机号并写入 `users_phone` +- 若未传 `users_phone_code` 但传入 `users_phone`,则直接写入数据库字段 `users_phone` +- 未传或传空的字段不会清空数据库中的已有值;只有非空字段才会更新 - `users_picture`、`users_id_pic_a`、`users_id_pic_b`、`users_title_picture` 均按 `attachments_id` 存储,服务端查询用户信息时会自动补充对应文件流链接 - 若用户首次从“三项资料均为空”变为“三项资料均完整”,则将 `users_type` 从 `游客` 升级为 `注册用户` - 返回更新后的完整用户信息 @@ -205,6 +212,7 @@ "users_type": "注册用户", "users_name": "张三", "users_phone": "13800138000", + "users_tag": "核心客户", "users_phone_masked": "138****8000", "users_picture": "ATT-1743123456789-abc123", "users_picture_url": "https://bai-api.blv-oa.com/pb/api/files/pbc_xxx/recordId/avatar.png", diff --git a/docs/api_response_field_notes.md b/docs/api_response_field_notes.md new file mode 100644 index 0000000..26857df --- /dev/null +++ b/docs/api_response_field_notes.md @@ -0,0 +1,168 @@ +# API 返回字段备注补充 + +这份文档用于补充 OpenAPI 返回示例里需要展示的“备注 | 类型”文案。 + +## 1. 用户返回结构 `UserInfo` + +| 字段名 | 建议示例文案 | +| :--- | :--- | +| `pb_id` | `PocketBase记录主键 | string` | +| `users_convers_id` | `会话侧用户ID | string` | +| `users_id` | `用户业务ID | string` | +| `users_idtype` | `用户身份来源类型 | string` | +| `users_id_number` | `证件号 | string` | +| `users_type` | `用户类型 | string` | +| `users_name` | `用户姓名 | string` | +| `users_status` | `用户状态 | string` | +| `users_rank_level` | `等级 | number` | +| `users_auth_type` | `账户类型 | number` | +| `users_phone` | `手机号 | string` | +| `users_phone_masked` | `脱敏手机号 | string` | +| `users_level` | `用户等级 | string` | +| `users_tag` | `用户标签 | string` | +| `users_picture` | `用户头像附件ID | string` | +| `users_picture_url` | `用户头像文件流链接 | string` | +| `users_id_pic_a` | `证件正面附件ID | string` | +| `users_id_pic_a_url` | `证件正面文件流链接 | string` | +| `users_id_pic_b` | `证件反面附件ID | string` | +| `users_id_pic_b_url` | `证件反面文件流链接 | string` | +| `users_title_picture` | `资质附件ID | string` | +| `users_title_picture_url` | `资质文件流链接 | string` | +| `openid` | `全平台统一身份标识 | string` | +| `company_id` | `公司业务id | string` | +| `users_parent_id` | `上级用户业务id | string` | +| `users_promo_code` | `推广码 | string` | +| `usergroups_id` | `用户组业务id | string` | +| `created` | `创建时间 | string` | +| `updated` | `更新时间 | string` | + +## 2. 公司返回结构 `CompanyInfo` + +| 字段名 | 建议示例文案 | +| :--- | :--- | +| `pb_id` | `PocketBase记录主键 | string` | +| `company_id` | `公司业务id | string` | +| `company_name` | `公司名称 | string` | +| `company_type` | `公司类型 | string` | +| `company_entity` | `公司法人 | string` | +| `company_usci` | `统一社会信用代码 | string` | +| `company_nationality` | `国家名称 | string` | +| `company_nationality_code` | `国家编码 | string` | +| `company_province` | `省份名称 | string` | +| `company_province_code` | `省份编码 | string` | +| `company_city` | `城市名称 | string` | +| `company_city_code` | `城市编码 | string` | +| `company_district` | `区县名称 | string` | +| `company_district_code` | `区县编码 | string` | +| `company_postalcode` | `邮政编码 | string` | +| `company_add` | `公司地址 | string` | +| `company_status` | `公司状态 | string` | +| `company_level` | `公司等级 | string` | +| `company_owner_openid` | `公司所有者openid | string` | +| `company_remark` | `备注 | string` | +| `created` | `创建时间 | string` | +| `updated` | `更新时间 | string` | + +## 3. 待你补充确认的字段 + +下面这些字段目前在已有 docs 中备注还不够稳定,如果你希望我继续把“所有接口返回示例”都改成这种风格,建议你先补充这几类字段的标准叫法: + +- 系统类返回里各状态字段的最终业务叫法 + +你补完以后,我可以继续把剩余接口的返回示例全部统一成 `备注 | 类型` 风格。 + +## 3. 字典返回结构 `DictionaryRecord` / `DictionaryItem` + +| 字段名 | 建议示例文案 | +| :--- | :--- | +| `pb_id` | `PocketBase记录主键 | string` | +| `system_dict_id` | `字典业务id | string` | +| `dict_name` | `词典名称 | string` | +| `dict_word_is_enabled` | `是否有效 | boolean` | +| `dict_word_parent_id` | `父级字典业务id | string` | +| `dict_word_remark` | `备注 | string` | +| `enum` | `枚举值 | string` | +| `description` | `枚举描述 | string` | +| `image` | `图片附件ID | string` | +| `imageUrl` | `图片文件流链接 | string` | +| `sortOrder` | `显示顺序 | integer` | +| `created` | `创建时间 | string` | +| `updated` | `更新时间 | string` | + +## 4. 附件返回结构 `AttachmentRecord` + +| 字段名 | 建议示例文案 | +| :--- | :--- | +| `pb_id` | `PocketBase记录主键 | string` | +| `attachments_id` | `附件业务id | string` | +| `attachments_link` | `PocketBase存储文件名 | string` | +| `attachments_url` | `附件文件流访问链接 | string` | +| `attachments_download_url` | `附件下载链接 | string` | +| `attachments_filename` | `原始文件名 | string` | +| `attachments_filetype` | `文件类型或MIME | string` | +| `attachments_size` | `附件大小 | number` | +| `attachments_owner` | `上传者业务id | string` | +| `attachments_md5` | `附件MD5码 | string` | +| `attachments_ocr` | `OCR识别结果 | string` | +| `attachments_status` | `附件状态 | string` | +| `attachments_remark` | `备注 | string` | +| `created` | `创建时间 | string` | +| `updated` | `更新时间 | string` | + +## 5. 文档返回结构 `DocumentRecord` + +| 字段名 | 建议示例文案 | +| :--- | :--- | +| `pb_id` | `PocketBase记录主键 | string` | +| `document_id` | `文档业务id | string` | +| `document_create` | `文档创建时间 | string` | +| `document_effect_date` | `文档生效日期 | string` | +| `document_expiry_date` | `文档到期日期 | string` | +| `document_type` | `文档类型 | string` | +| `document_title` | `文档标题 | string` | +| `document_subtitle` | `文档副标题 | string` | +| `document_summary` | `文档摘要 | string` | +| `document_content` | `正文内容 | string` | +| `document_image` | `图片附件ID串 | string` | +| `document_image_ids` | `图片附件ID列表 | array` | +| `document_image_urls` | `图片文件流链接列表 | array` | +| `document_image_url` | `第一张图片文件流链接 | string` | +| `document_video` | `视频附件ID串 | string` | +| `document_video_ids` | `视频附件ID列表 | array` | +| `document_video_urls` | `视频文件流链接列表 | array` | +| `document_video_url` | `第一个视频文件流链接 | string` | +| `document_file` | `文件附件ID串 | string` | +| `document_file_ids` | `文件附件ID列表 | array` | +| `document_file_urls` | `文件文件流链接列表 | array` | +| `document_file_url` | `第一个文件文件流链接 | string` | +| `document_owner` | `上传者openid | string` | +| `document_relation_model` | `关联机型标识 | string` | +| `document_keywords` | `关键词 | string` | +| `document_share_count` | `分享次数 | number` | +| `document_download_count` | `下载次数 | number` | +| `document_favorite_count` | `收藏次数 | number` | +| `document_status` | `文档状态 | string` | +| `document_embedding_status` | `文档嵌入状态 | string` | +| `document_embedding_error` | `文档错误原因 | string` | +| `document_embedding_lasttime` | `最后更新日期 | string` | +| `document_vector_version` | `向量版本号或模型名称 | string` | +| `document_product_categories` | `产品关联文档 | string` | +| `document_application_scenarios` | `筛选依据 | string` | +| `document_hotel_type` | `适用场景 | string` | +| `document_remark` | `备注 | string` | +| `created` | `创建时间 | string` | +| `updated` | `更新时间 | string` | + +## 6. 文档历史返回结构 `DocumentHistoryRecord` + +| 字段名 | 建议示例文案 | +| :--- | :--- | +| `pb_id` | `PocketBase记录主键 | string` | +| `doh_id` | `文档操作历史业务id | string` | +| `doh_document_id` | `关联文档业务id | string` | +| `doh_operation_type` | `操作类型 | string` | +| `doh_user_id` | `操作人业务id | string` | +| `doh_current_count` | `本次操作对应次数 | number` | +| `doh_remark` | `备注 | string` | +| `created` | `创建时间 | string` | +| `updated` | `更新时间 | string` | diff --git a/docs/newpb.md b/docs/newpb.md deleted file mode 100644 index 9d23a2a..0000000 --- a/docs/newpb.md +++ /dev/null @@ -1,95 +0,0 @@ -针对你的复杂权限需求,建议采用 **“RBAC(基于角色的访问控制)+ ABAC(基于属性的访问控制)+ 个性化覆盖(Overrides)”** 的混合模式。这种结构能支持从“大颗粒度角色”到“极细颗粒度字段/行”的动态权限矩阵。 - -以下是为你设计的实施方案及关键表结构规划: - -### 一、 核心表方案设计 - -为了实现“字段级”和“行级”的动态控制,我们需要将 **资源(Resource)**、**权限定义(Permission)**、**角色(Role)** 与 **用户(User)** 彻底解耦。 - -#### 1. 用户基础表:`tbl_auth_users` -作为全局身份锚点,记录用户的静态信息和动态属性(部门、等级)。 - -| 字段名 | 类型 | 说明 | -| :--- | :--- | :--- | -| **users_convers_id** | String | 会话/对话侧用户标识,允许为空 | -| **openid** | String (Unique) | **全局身份锚点**,微信唯一标识 | -| **users_name** | String | 姓名/昵称 | -| **org_id** | Int | 所属组织/部门 ID(影响行级权限的关键属性) | -| **users_rank_level** | Int | 职级/等级(影响动态权限矩阵的关键属性) | -| **users_status** | Int | 账户状态 (1: 正常, 0: 禁用) | -| **users_auth_type** | Int | 账户类型 (0: 微信小程序,1: 管理平台,2: 其他) | - ---- - -#### 2. 资源定义表:`tbl_auth_resources` -定义系统中哪些表、哪些字段属于受控资源。 - -| 字段名 | 类型 | 说明 | -| :--- | :--- | :--- | -| **res_id** | Int (PK) | 资源 ID | -| **table_name** | String | 数据库表名 | -| **column_name** | String | 字段名(如果是表级权限,此项可为空或设为 '*') | -| **res_type** | Enum | 资源类型:`TABLE`(行/全表), `COLUMN`(字段级) | - ---- - -#### 3. 角色与基础权限:`tbl_auth_roles` & `tbl_auth_role_perms` -实现通用的权限模板,方便批量管理。 - -* **`tbl_auth_roles`**: 角色表(如:财务经理、普通销售)。 -* **`tbl_auth_role_perms`**: 角色权限关联表,定义该角色对某个资源的操作(读/写/无)。 - ---- - -#### 4. **核心:个性化权限覆盖表 `tbl_auth_user_overrides`** -这是满足你“某个用户对某个字段单独设置”需求的关键。 - -| 字段名 | 类型 | 说明 | -| :--- | :--- | :--- | -| **id** | BigInt (PK) | 自增 ID | -| **users_convers_id** | String | 用户会话标识(关联 `tbl_auth_users.users_convers_id`) | -| **res_id** | Int | 资源 ID(关联 `tbl_auth_resources`) | -| **access_level** | Int | 权限值 (0: 无权, 1: 只读, 2: 读写) | -| **priority** | Int | 优先级(当角色权限与个人设置冲突时,以此为准) | - ---- - -#### 5. **核心:行级过滤策略表 `tbl_auth_row_scopes`** -实现“某个用户只能看自己部门/某几个项目”的动态逻辑。 - -| 字段名 | 类型 | 说明 | -| :--- | :--- | :--- | -| **id** | Int (PK) | 策略 ID | -| **target_type** | Enum | 目标:`USER` 或 `ROLE` | -| **target_id** | BigInt | 对应的 UserID 或 RoleID | -| **table_name** | String | 作用的表名 | -| **filter_sql** | String | 过滤逻辑。例如:`dept_id = {user.org_id}` 或 `creator_id = {user.users_convers_id}` | - ---- - -### 二、 权限实施方案逻辑 - -你的权限矩阵页面将由以下逻辑驱动: - -#### 1. 权限计算路径 (Effective Permissions) -当用户访问某个数据时,系统按照以下顺序合并权限: -1. **取基础属性**:获取用户的 `org_id` 和 `users_rank_level`。 -2. **取角色权限**:获取该用户所属角色对应的资源权限列表。 -3. **应用个性化覆盖**:查询 `tbl_auth_user_overrides`。如果该表中有记录,则**覆盖**(或叠加)角色权限。 -4. **注入行级过滤**:如果是查询操作,解析 `tbl_auth_row_scopes` 中的 `filter_sql`,将 `{user.xxx}` 变量替换为当前用户的真实值。 - -#### 2. 动态更新机制 -* **组织/等级变更**:当 `tbl_auth_users` 中的 `org_id` 或 `users_rank_level` 变化时,由于行级过滤表(`tbl_auth_row_scopes`)引用的是动态变量,**权限会自动生效**,无需重新授权。 -* **缓存策略**:建议将计算后的“最终权限清单”缓存到 Redis。当用户在后台矩阵页面修改权限,或者发生组织架构调整时,通过 `wx_openid` **主动失效(Purge)** 该用户的 Redis 缓存。 - -#### 3. 字段级权限实现 (Field-Level) -在接口层(中间件),根据计算出的字段级权限清单(即用户对该 Table 下哪些 Column 有读权),动态过滤返回的 JSON 结构。 - -### 三、 总结:你需要几张表? - -为了实现你描述的系统,最精简需要 **5 张表**: -1. **`tbl_auth_users`**:用户主体(含 OpenID、部门、等级)。 -2. **`tbl_auth_resources`**:资源清单(表名、字段名)。 -3. **`tbl_auth_roles`**:角色定义。 -4. **`tbl_auth_role_perms`** / **`tbl_auth_user_overrides`**:权限映射(解决字段级和个人特权)。 -5. **`tbl_auth_row_scopes`**:行级过滤表达式(解决多维数据隔离)。 diff --git a/docs/pb_auth_model.md b/docs/pb_auth_model.md new file mode 100644 index 0000000..bb60bd9 --- /dev/null +++ b/docs/pb_auth_model.md @@ -0,0 +1,37 @@ +# pb_auth_model + +> 来源:`script/pocketbase.newpb.js`、当前 hooks 权限实现 +> 说明:本文件描述权限模型设计与表间职责,不替代单表结构文档 + +## 模型目标 + +当前项目采用 `RBAC + ABAC + 用户覆盖` 的混合权限模型: + +- `RBAC` 负责角色级默认权限 +- `ABAC` 负责组织、等级、目标表等属性条件 +- `用户覆盖` 负责单人例外授权 + +## 核心表职责 + +| 文档 | 作用 | +| :--- | :--- | +| [pb_tbl_auth_users.md](/e:/Project_Class/BAI/Web_BAI_Manage_ApiServer/docs/pb_tbl_auth_users.md) | 用户身份源与 PocketBase auth 主表 | +| [pb_tbl_auth_resources.md](/e:/Project_Class/BAI/Web_BAI_Manage_ApiServer/docs/pb_tbl_auth_resources.md) | 资源目录 | +| [pb_tbl_auth_roles.md](/e:/Project_Class/BAI/Web_BAI_Manage_ApiServer/docs/pb_tbl_auth_roles.md) | 角色定义 | +| [pb_tbl_auth_role_perms.md](/e:/Project_Class/BAI/Web_BAI_Manage_ApiServer/docs/pb_tbl_auth_role_perms.md) | 角色权限映射 | +| [pb_tbl_auth_user_overrides.md](/e:/Project_Class/BAI/Web_BAI_Manage_ApiServer/docs/pb_tbl_auth_user_overrides.md) | 单用户权限覆盖 | +| [pb_tbl_auth_row_scopes.md](/e:/Project_Class/BAI/Web_BAI_Manage_ApiServer/docs/pb_tbl_auth_row_scopes.md) | 行级过滤范围 | + +## 权限计算路径 + +1. 从 `tbl_auth_users` 读取用户身份、组织、等级和角色。 +2. 从 `tbl_auth_role_perms` 读取角色默认权限。 +3. 从 `tbl_auth_user_overrides` 应用用户级覆盖。 +4. 从 `tbl_auth_row_scopes` 组合行级过滤条件。 +5. 将可原生表达的权限同步到 PocketBase collection rules;复杂业务权限仍由 hooks 判断。 + +## 当前实现约定 + +- 管理后台用户通过 `users_idtype = ManagePlatform` 与管理角色识别。 +- hooks 页面 `/pb/manage/sdk-permission-manage` 负责维护角色与 collection CRUD 权限。 +- 用户登录返回的是 PocketBase 原生可验证 token,可直接给 PocketBase SDK 使用。 diff --git a/docs/pb_document_tables.md b/docs/pb_document_tables.md deleted file mode 100644 index 5a4ebd8..0000000 --- a/docs/pb_document_tables.md +++ /dev/null @@ -1,106 +0,0 @@ -# PocketBase 文档相关表结构 - -本方案新增 3 张 PocketBase `base collection`,统一采用业务 id 字段进行关联,不直接依赖 PocketBase 自动生成的 `id` 作为业务主键。 - -补充约定: - -- `document_image`、`document_video`、`document_file` 只保存关联的 `attachments_id`,不直接存文件;当存在多个附件时,统一使用 `|` 分隔,例如:`ATT-001|ATT-002|ATT-003`。 -- `document_type` 使用多选字符串持久化,但格式特殊:`system_dict_id@dict_word_enum|system_dict_id@dict_word_enum`;前端显示时用枚举值描述,存库时保留该组合值。 -- `document_keywords`、`document_product_categories`、`document_application_scenarios`、`document_hotel_type` 统一使用竖线分隔字符串,例如:`分类A|分类B|分类C`。 -- `document_status` 仅允许 `有效`、`过期` 两种值,并由系统根据生效日期与到期日期自动计算;当两者都为空时默认 `有效`。 -- `document_owner` 的业务含义为“上传者openid”。 -- `attachments_link` 是 PocketBase `file` 字段,保存附件本体;访问链接由 PocketBase 根据记录和文件名生成。 -- `tbl_attachments` 的文件查看/下载权限应保持公开;真正的业务访问控制交由引用 `attachments_id` 的业务表和业务接口决定。 -- 文档字段中,面向用户填写的字段里只有 `document_title`、`document_type` 设为必填,其余字段均允许为空。 - ---- - -## 1. `tbl_attachments` 附件表 - -**类型:** Base Collection - -| 字段名 | 类型 | 备注 | -| :--- | :--- | :--- | -| attachments_id | text | 附件业务 id,唯一标识符 | -| attachments_link | file | 附件本体,单文件,不限制文件类型,数据库字段上限已放宽到约 4GB | -| attachments_filename | text | 原始文件名 | -| attachments_filetype | text | 文件类型/MIME | -| attachments_size | number | 附件大小 | -| attachments_owner | text | 上传者业务 id | -| attachments_md5 | text | 附件 MD5 码 | -| attachments_ocr | text | OCR 识别结果 | -| attachments_status | text | 附件状态 | -| attachments_remark | text | 备注 | - -**索引规划:** - -- `attachments_id` 唯一索引 -- `attachments_owner` 普通索引 -- `attachments_status` 普通索引 - ---- - -## 2. `tbl_document` 文档表 - -**类型:** Base Collection - -| 字段名 | 类型 | 备注 | -| :--- | :--- | :--- | -| document_id | text | 文档业务 id,唯一标识符 | -| document_effect_date | date | 文档生效日期 | -| document_expiry_date | date | 文档到期日期 | -| document_type | text | 文档类型,必填;多选时按 `system_dict_id@dict_word_enum|...` 保存 | -| document_title | text | 文档标题,必填 | -| document_subtitle | text | 文档副标题 | -| document_summary | text | 文档摘要 | -| document_content | text | 正文内容,保存 Markdown 原文 | -| document_image | text | 关联多个 `attachments_id`,使用 `|` 分隔 | -| document_video | text | 关联多个 `attachments_id`,使用 `|` 分隔 | -| document_file | text | 关联多个 `attachments_id`,使用 `|` 分隔 | -| document_owner | text | 上传者openid | -| document_relation_model | text | 关联机型/模型标识 | -| document_keywords | text | 关键词,多选后用 `|` 分隔保存 | -| document_share_count | number | 分享次数 | -| document_download_count | number | 下载次数 | -| document_favorite_count | number | 收藏次数 | -| document_status | text | 文档状态,仅允许 `有效` 或 `过期`,由系统依据生效日期和到期日期自动更新 | -| document_embedding_status | text | 文档嵌入状态 | -| document_embedding_error | text | 文档错误原因 | -| document_embedding_lasttime | date | 最后更新日期 | -| document_vector_version | text | 向量版本号或模型名称 | -| document_product_categories | text | 产品关联文档,多选后从 `文档-产品关联文档` 字典保存为 `|` 分隔字符串 | -| document_application_scenarios | text | 筛选依据,多选后从 `文档-筛选依据` 字典保存为 `|` 分隔字符串 | -| document_hotel_type | text | 适用场景,多选后从 `文档-适用场景` 字典保存为 `|` 分隔字符串 | -| document_remark | text | 备注 | - -**索引规划:** - -- `document_id` 唯一索引 -- `document_owner` 普通索引 -- `document_type` 普通索引 -- `document_status` 普通索引 -- `document_embedding_status` 普通索引 -- `document_effect_date` 普通索引 -- `document_expiry_date` 普通索引 - ---- - -## 3. `tbl_document_operation_history` 文档操作历史表 - -**类型:** Base Collection - -| 字段名 | 类型 | 备注 | -| :--- | :--- | :--- | -| doh_id | text | 文档操作历史业务 id,唯一标识符 | -| doh_document_id | text | 关联 `document_id` | -| doh_operation_type | text | 操作类型 | -| doh_user_id | text | 操作人业务 id | -| doh_current_count | number | 本次操作对应次数 | -| doh_remark | text | 备注 | - -**索引规划:** - -- `doh_id` 唯一索引 -- `doh_document_id` 普通索引 -- `doh_user_id` 普通索引 -- `doh_operation_type` 普通索引 diff --git a/docs/pb_tbl_attachments.md b/docs/pb_tbl_attachments.md new file mode 100644 index 0000000..4abe82f --- /dev/null +++ b/docs/pb_tbl_attachments.md @@ -0,0 +1,39 @@ +# pb_tbl_attachments + +> 来源:线上 PocketBase collection 回读、`script/pocketbase.documents.js` +> 类型:`base` +> 读写规则:公开可读;原生写权限与 hooks 管理权限仅管理员 / 管理角色允许 + +## 表用途 + +统一存储项目中的真实上传文件。业务表只保存 `attachments_id`,文件本体仅保存在本表 `attachments_link` 中。 + +## 字段清单 + +| 字段名 | 类型 | 必填 | 说明 | +| :--- | :--- | :---: | :--- | +| `id` | `text` | 是 | PocketBase 记录主键 | +| `attachments_id` | `text` | 是 | 附件业务 ID | +| `attachments_link` | `file` | 否 | 文件本体,数据库限制已放宽到约 4GB | +| `attachments_filename` | `text` | 否 | 原始文件名 | +| `attachments_filetype` | `text` | 否 | 文件类型 / MIME | +| `attachments_size` | `number` | 否 | 文件大小 | +| `attachments_owner` | `text` | 否 | 上传者业务标识 | +| `attachments_md5` | `text` | 否 | 文件 MD5 | +| `attachments_ocr` | `text` | 否 | OCR 识别结果 | +| `attachments_status` | `text` | 否 | 附件状态 | +| `attachments_remark` | `text` | 否 | 备注 | + +## 索引 + +| 索引名 | 类型 | 说明 | +| :--- | :--- | :--- | +| `idx_tbl_attachments_attachments_id` | `UNIQUE INDEX` | 保证 `attachments_id` 唯一 | +| `idx_tbl_attachments_attachments_owner` | `INDEX` | 加速按上传者查询 | +| `idx_tbl_attachments_attachments_status` | `INDEX` | 加速按附件状态查询 | + +## 补充约定 + +- 图片、视频、普通文件都统一走本表。 +- 业务访问控制不放在本表,而由引用它的业务表与 hooks 接口控制。 +- PocketBase 系统字段 `created`、`updated` 仍然存在,只是不在 collection 字段清单里单独声明。 diff --git a/docs/pb_tbl_auth_resources.md b/docs/pb_tbl_auth_resources.md new file mode 100644 index 0000000..51a7334 --- /dev/null +++ b/docs/pb_tbl_auth_resources.md @@ -0,0 +1,28 @@ +# pb_tbl_auth_resources + +> 来源:线上 PocketBase collection 回读、`script/pocketbase.newpb.js` +> 类型:`base` +> 读写规则:仅管理员 / 管理角色允许 + +## 表用途 + +定义哪些表、哪些字段属于受控资源,是角色权限与用户覆盖权限的资源目录表。 + +## 字段清单 + +| 字段名 | 类型 | 必填 | 说明 | +| :--- | :--- | :---: | :--- | +| `id` | `text` | 是 | PocketBase 记录主键 | +| `res_id` | `text` | 是 | 资源业务 ID | +| `table_name` | `text` | 是 | 对应数据表名 | +| `column_name` | `text` | 否 | 对应字段名;表级权限时可为空 | +| `res_type` | `text` | 是 | 资源类型 | + +## 索引 + +| 索引名 | 类型 | 说明 | +| :--- | :--- | :--- | +| `idx_tbl_auth_resources_res_id` | `UNIQUE INDEX` | 保证 `res_id` 唯一 | +| `idx_tbl_auth_resources_table_name` | `INDEX` | 加速按表名查询 | +| `idx_tbl_auth_resources_res_type` | `INDEX` | 加速按资源类型查询 | +| `idx_tbl_auth_resources_unique_res` | `UNIQUE INDEX` | 保证 `table_name + column_name + res_type` 唯一 | diff --git a/docs/pb_tbl_auth_role_perms.md b/docs/pb_tbl_auth_role_perms.md new file mode 100644 index 0000000..6d3176c --- /dev/null +++ b/docs/pb_tbl_auth_role_perms.md @@ -0,0 +1,29 @@ +# pb_tbl_auth_role_perms + +> 来源:线上 PocketBase collection 回读、`script/pocketbase.newpb.js` +> 类型:`base` +> 读写规则:仅管理员 / 管理角色允许 + +## 表用途 + +用于存储角色与资源之间的权限映射,是按角色下发 PocketBase SDK CRUD 权限的基础表。 + +## 字段清单 + +| 字段名 | 类型 | 必填 | 说明 | +| :--- | :--- | :---: | :--- | +| `id` | `text` | 是 | PocketBase 记录主键 | +| `role_perm_id` | `text` | 是 | 角色权限业务 ID | +| `role_id` | `text` | 是 | 角色业务 ID | +| `res_id` | `text` | 是 | 资源业务 ID | +| `access_level` | `number` | 是 | 权限级别 | +| `priority` | `number` | 否 | 优先级 | + +## 索引 + +| 索引名 | 类型 | 说明 | +| :--- | :--- | :--- | +| `idx_tbl_auth_role_perms_role_perm_id` | `UNIQUE INDEX` | 保证 `role_perm_id` 唯一 | +| `idx_tbl_auth_role_perms_role_id` | `INDEX` | 加速按角色查询 | +| `idx_tbl_auth_role_perms_res_id` | `INDEX` | 加速按资源查询 | +| `idx_tbl_auth_role_perms_unique_map` | `UNIQUE INDEX` | 保证 `role_id + res_id` 唯一 | diff --git a/docs/pb_tbl_auth_roles.md b/docs/pb_tbl_auth_roles.md new file mode 100644 index 0000000..f29f12c --- /dev/null +++ b/docs/pb_tbl_auth_roles.md @@ -0,0 +1,28 @@ +# pb_tbl_auth_roles + +> 来源:线上 PocketBase collection 回读、`script/pocketbase.newpb.js` +> 类型:`base` +> 读写规则:仅管理员 / 管理角色允许 + +## 表用途 + +用于存储角色定义,供 SDK 直连权限管理页和用户授权流程使用。 + +## 字段清单 + +| 字段名 | 类型 | 必填 | 说明 | +| :--- | :--- | :---: | :--- | +| `id` | `text` | 是 | PocketBase 记录主键 | +| `role_id` | `text` | 是 | 角色业务 ID | +| `role_name` | `text` | 是 | 角色名称 | +| `role_code` | `text` | 否 | 角色编码 | +| `role_status` | `number` | 否 | 角色状态 | +| `role_remark` | `text` | 否 | 备注 | + +## 索引 + +| 索引名 | 类型 | 说明 | +| :--- | :--- | :--- | +| `idx_tbl_auth_roles_role_id` | `UNIQUE INDEX` | 保证 `role_id` 唯一 | +| `idx_tbl_auth_roles_role_name` | `UNIQUE INDEX` | 保证 `role_name` 唯一 | +| `idx_tbl_auth_roles_role_code` | `UNIQUE INDEX` | 保证 `role_code` 唯一 | diff --git a/docs/pb_tbl_auth_row_scopes.md b/docs/pb_tbl_auth_row_scopes.md new file mode 100644 index 0000000..6b0147b --- /dev/null +++ b/docs/pb_tbl_auth_row_scopes.md @@ -0,0 +1,29 @@ +# pb_tbl_auth_row_scopes + +> 来源:线上 PocketBase collection 回读、`script/pocketbase.newpb.js` +> 类型:`base` +> 读写规则:仅管理员 / 管理角色允许 + +## 表用途 + +用于定义按用户或按角色生效的行级过滤范围,是 ABAC / 行级权限的核心表。 + +## 字段清单 + +| 字段名 | 类型 | 必填 | 说明 | +| :--- | :--- | :---: | :--- | +| `id` | `text` | 是 | PocketBase 记录主键 | +| `scope_id` | `text` | 是 | 范围业务 ID | +| `target_type` | `text` | 是 | 目标类型 | +| `target_id` | `text` | 是 | 目标 ID | +| `table_name` | `text` | 是 | 作用表名 | +| `filter_sql` | `editor` | 是 | 行级过滤表达式 | + +## 索引 + +| 索引名 | 类型 | 说明 | +| :--- | :--- | :--- | +| `idx_tbl_auth_row_scopes_scope_id` | `UNIQUE INDEX` | 保证 `scope_id` 唯一 | +| `idx_tbl_auth_row_scopes_target_type` | `INDEX` | 加速按目标类型查询 | +| `idx_tbl_auth_row_scopes_target_id` | `INDEX` | 加速按目标 ID 查询 | +| `idx_tbl_auth_row_scopes_table_name` | `INDEX` | 加速按作用表查询 | diff --git a/docs/pb_tbl_auth_user_overrides.md b/docs/pb_tbl_auth_user_overrides.md new file mode 100644 index 0000000..e2492c5 --- /dev/null +++ b/docs/pb_tbl_auth_user_overrides.md @@ -0,0 +1,29 @@ +# pb_tbl_auth_user_overrides + +> 来源:线上 PocketBase collection 回读、`script/pocketbase.newpb.js` +> 类型:`base` +> 读写规则:仅管理员 / 管理角色允许 + +## 表用途 + +用于给单个用户覆盖角色默认权限,实现细粒度例外授权。 + +## 字段清单 + +| 字段名 | 类型 | 必填 | 说明 | +| :--- | :--- | :---: | :--- | +| `id` | `text` | 是 | PocketBase 记录主键 | +| `override_id` | `text` | 是 | 覆盖权限业务 ID | +| `users_convers_id` | `text` | 是 | 目标用户会话 ID | +| `res_id` | `text` | 是 | 资源业务 ID | +| `access_level` | `number` | 是 | 权限级别 | +| `priority` | `number` | 否 | 优先级 | + +## 索引 + +| 索引名 | 类型 | 说明 | +| :--- | :--- | :--- | +| `idx_tbl_auth_user_overrides_override_id` | `UNIQUE INDEX` | 保证 `override_id` 唯一 | +| `idx_tbl_auth_user_overrides_users_convers_id` | `INDEX` | 加速按用户查询 | +| `idx_tbl_auth_user_overrides_res_id` | `INDEX` | 加速按资源查询 | +| `idx_tbl_auth_user_overrides_unique_map` | `UNIQUE INDEX` | 保证 `users_convers_id + res_id` 唯一 | diff --git a/docs/pb_tbl_auth_users.md b/docs/pb_tbl_auth_users.md new file mode 100644 index 0000000..160d49f --- /dev/null +++ b/docs/pb_tbl_auth_users.md @@ -0,0 +1,66 @@ +# pb_tbl_auth_users + +> 来源:线上 PocketBase collection 回读、`script/pocketbase.newpb.js` +> 类型:`auth` +> 读写规则:仅管理员 / 管理角色允许 + +## 表用途 + +作为项目统一身份源,承载微信用户与管理平台用户,并负责向 PocketBase 原生鉴权体系签发可被 SDK 识别的 token。 + +## 字段清单 + +| 字段名 | 类型 | 必填 | 说明 | +| :--- | :--- | :---: | :--- | +| `id` | `text` | 是 | PocketBase 记录主键 | +| `users_convers_id` | `text` | 否 | 会话侧用户 ID | +| `openid` | `text` | 是 | 全局身份锚点 | +| `org_id` | `number` | 否 | 组织 / 部门标识 | +| `users_rank_level` | `number` | 否 | 用户星级数值 | +| `users_status` | `text` | 否 | 用户状态 | +| `users_auth_type` | `number` | 否 | 账户类型 | +| `users_id` | `text` | 否 | 用户业务 ID | +| `users_name` | `text` | 否 | 用户姓名 / 昵称 | +| `users_idtype` | `text` | 否 | 身份来源类型或证件类型 | +| `users_id_number` | `text` | 否 | 证件号 | +| `users_phone` | `text` | 否 | 手机号 | +| `users_level` | `text` | 否 | 用户等级文本 | +| `users_type` | `text` | 否 | 用户类型 | +| `company_id` | `text` | 否 | 所属公司业务 ID | +| `users_parent_id` | `text` | 否 | 上级用户业务 ID | +| `users_promo_code` | `text` | 否 | 推广码 | +| `users_picture` | `text` | 否 | 头像附件 ID | +| `usergroups_id` | `text` | 否 | 用户组 / 角色 ID | +| `password` | `password` | 是 | PocketBase 认证密码 | +| `tokenKey` | `text` | 是 | PocketBase token 签发关键字段 | +| `email` | `email` | 是 | PocketBase 认证邮箱 | +| `emailVisibility` | `bool` | 否 | 邮箱是否公开 | +| `verified` | `bool` | 否 | 邮箱是否验证 | +| `users_title_picture` | `text` | 否 | 资质附件 ID | +| `users_id_pic_a` | `text` | 否 | 证件照正面附件 ID | +| `users_id_pic_b` | `text` | 否 | 证件照反面附件 ID | +| `users_tag` | `text` | 否 | 用户标签 | + +## 索引 + +| 索引名 | 类型 | 说明 | +| :--- | :--- | :--- | +| `idx_tbl_auth_users_users_convers_id` | `UNIQUE INDEX` | 保证 `users_convers_id` 唯一 | +| `idx_tbl_auth_users_openid` | `UNIQUE INDEX` | 保证 `openid` 唯一 | +| `idx_tbl_auth_users_org_id` | `INDEX` | 加速按组织查询 | +| `idx_tbl_auth_users_users_rank_level` | `INDEX` | 加速按等级查询 | +| `idx_tbl_auth_users_users_status` | `INDEX` | 加速按状态查询 | +| `idx_tbl_auth_users_users_auth_type` | `INDEX` | 加速按账户类型查询 | +| `idx_tbl_auth_users_users_phone` | `INDEX` | 加速按手机号查询 | +| `idx_tbl_auth_users_company_id` | `INDEX` | 加速按公司查询 | +| `idx_tbl_auth_users_usergroups_id` | `INDEX` | 加速按用户组查询 | +| `idx_tbl_auth_users_users_parent_id` | `INDEX` | 加速按上级查询 | +| `idx_tokenKey_pbc_421601843` | `UNIQUE INDEX` | PocketBase auth 系统索引 | +| `idx_email_pbc_421601843` | `UNIQUE INDEX` | PocketBase auth 系统索引,保证邮箱唯一 | + +## 补充约定 + +- 本表为 `auth` collection,除上述字段外还受 PocketBase 原生鉴权机制约束。 +- 图片类字段统一只保存 `tbl_attachments.attachments_id`。 +- 登录接口返回的 token 来源于本表 auth record 的原生签发能力,可直接给 PocketBase SDK 使用。 +- PocketBase 系统字段 `created`、`updated` 仍然存在,只是不在 collection 字段清单里单独声明。 diff --git a/docs/pb_tbl_company.md b/docs/pb_tbl_company.md new file mode 100644 index 0000000..96e5698 --- /dev/null +++ b/docs/pb_tbl_company.md @@ -0,0 +1,48 @@ +# pb_tbl_company + +> 来源:线上 PocketBase collection 回读、`script/pocketbase.js` +> 类型:`base` +> 读写规则:公开可创建、公开可列出;详情 / 更新 / 删除仅管理员或管理后台用户允许 + +## 表用途 + +用于存储公司主数据,并作为用户归属公司、微信端公司创建与原生 PocketBase 查询的基础表。 + +## 字段清单 + +| 字段名 | 类型 | 必填 | 说明 | +| :--- | :--- | :---: | :--- | +| `id` | `text` | 是 | PocketBase 记录主键 | +| `company_id` | `text` | 是 | 公司业务 ID,由数据库自动生成 | +| `company_name` | `text` | 否 | 公司名称 | +| `company_type` | `text` | 否 | 公司类型 | +| `company_entity` | `text` | 否 | 公司法人 | +| `company_usci` | `text` | 否 | 统一社会信用代码 | +| `company_nationality` | `text` | 否 | 国家名称 | +| `company_nationality_code` | `text` | 否 | 国家编码 | +| `company_province` | `text` | 否 | 省份名称 | +| `company_province_code` | `text` | 否 | 省份编码 | +| `company_city` | `text` | 否 | 城市名称 | +| `company_city_code` | `text` | 否 | 城市编码 | +| `company_district` | `text` | 否 | 区 / 县名称 | +| `company_district_code` | `text` | 否 | 区 / 县编码 | +| `company_postalcode` | `text` | 否 | 邮编 | +| `company_add` | `text` | 否 | 地址 | +| `company_status` | `text` | 否 | 公司状态 | +| `company_level` | `text` | 否 | 公司等级 | +| `company_owner_openid` | `text` | 否 | 公司所有者 openid | +| `company_remark` | `text` | 否 | 备注 | + +## 索引 + +| 索引名 | 类型 | 说明 | +| :--- | :--- | :--- | +| `idx_company_id` | `UNIQUE INDEX` | 保证 `company_id` 唯一 | +| `idx_company_usci` | `INDEX` | 加速按统一社会信用代码查询 | +| `idx_company_owner_openid` | `INDEX` | 加速按公司所有者查询 | + +## 补充约定 + +- 微信端原生 PocketBase 接口支持公开创建公司记录。 +- `company_id` 已切换为数据库自动生成,客户端不再需要提交。 +- PocketBase 系统字段 `created`、`updated` 仍然存在,只是不在 collection 字段清单里单独声明。 diff --git a/docs/pb_tbl_document.md b/docs/pb_tbl_document.md new file mode 100644 index 0000000..013e190 --- /dev/null +++ b/docs/pb_tbl_document.md @@ -0,0 +1,62 @@ +# pb_tbl_document + +> 来源:线上 PocketBase collection 回读、`script/pocketbase.documents.js` +> 类型:`base` +> 读写规则:公开可读;新增 / 修改 / 删除仅 `ManagePlatform` / 管理角色允许 + +## 表用途 + +用于存储文档主体信息,以及图片、视频、文件三类附件的关联关系。 + +## 字段清单 + +| 字段名 | 类型 | 必填 | 说明 | +| :--- | :--- | :---: | :--- | +| `id` | `text` | 是 | PocketBase 记录主键 | +| `document_id` | `text` | 是 | 文档业务 ID | +| `document_effect_date` | `date` | 否 | 文档生效日期 | +| `document_expiry_date` | `date` | 否 | 文档到期日期 | +| `document_type` | `text` | 是 | 文档类型,多选时按 `system_dict_id@dict_word_enum|...` 保存 | +| `document_title` | `text` | 是 | 文档标题 | +| `document_subtitle` | `text` | 否 | 文档副标题 | +| `document_summary` | `text` | 否 | 文档摘要 | +| `document_content` | `text` | 否 | 正文内容,保存 Markdown | +| `document_image` | `text` | 否 | 图片附件 ID 集合,底层以 `|` 分隔 | +| `document_video` | `text` | 否 | 视频附件 ID 集合,底层以 `|` 分隔 | +| `document_owner` | `text` | 否 | 上传者 openid | +| `document_relation_model` | `text` | 否 | 关联机型 / 模型标识 | +| `document_keywords` | `text` | 否 | 关键词,多选后以 `|` 分隔 | +| `document_share_count` | `number` | 否 | 分享次数 | +| `document_download_count` | `number` | 否 | 下载次数 | +| `document_favorite_count` | `number` | 否 | 收藏次数 | +| `document_status` | `text` | 否 | 文档状态,仅 `有效` / `过期` | +| `document_embedding_status` | `text` | 否 | 文档嵌入状态 | +| `document_embedding_error` | `text` | 否 | 文档嵌入错误原因 | +| `document_embedding_lasttime` | `date` | 否 | 最后一次嵌入更新时间 | +| `document_vector_version` | `text` | 否 | 向量版本号 / 模型名称 | +| `document_product_categories` | `text` | 否 | 产品关联文档,多选后以 `|` 分隔 | +| `document_application_scenarios` | `text` | 否 | 筛选依据,多选后以 `|` 分隔 | +| `document_hotel_type` | `text` | 否 | 适用场景,多选后以 `|` 分隔 | +| `document_remark` | `text` | 否 | 备注 | +| `document_file` | `text` | 否 | 普通文件附件 ID 集合,底层以 `|` 分隔 | +| `document_create` | `autodate` | 否 | 文档创建时间,由数据库自动生成 | + +## 索引 + +| 索引名 | 类型 | 说明 | +| :--- | :--- | :--- | +| `idx_tbl_document_document_id` | `UNIQUE INDEX` | 保证 `document_id` 唯一 | +| `idx_tbl_document_document_create` | `INDEX` | 加速按创建时间倒序查询 | +| `idx_tbl_document_document_owner` | `INDEX` | 加速按上传者查询 | +| `idx_tbl_document_document_type` | `INDEX` | 加速按文档类型查询 | +| `idx_tbl_document_document_status` | `INDEX` | 加速按文档状态查询 | +| `idx_tbl_document_document_embedding_status` | `INDEX` | 加速按嵌入状态查询 | +| `idx_tbl_document_document_effect_date` | `INDEX` | 加速按生效日期查询 | +| `idx_tbl_document_document_expiry_date` | `INDEX` | 加速按到期日期查询 | + +## 补充约定 + +- 三类附件字段都只保存 `attachments_id`,真实文件统一在 `tbl_attachments`。 +- `document_create` 已作为原生 PocketBase 列表排序字段,推荐使用 `sort=-document_create`。 +- 面向用户填写的字段里,仅 `document_title`、`document_type` 必填,其余允许为空。 +- PocketBase 系统字段 `created`、`updated` 仍然存在,只是不在 collection 字段清单里单独声明。 diff --git a/docs/pb_tbl_document_operation_history.md b/docs/pb_tbl_document_operation_history.md new file mode 100644 index 0000000..3c06188 --- /dev/null +++ b/docs/pb_tbl_document_operation_history.md @@ -0,0 +1,35 @@ +# pb_tbl_document_operation_history + +> 来源:线上 PocketBase collection 回读、`script/pocketbase.documents.js` +> 类型:`base` +> 读写规则:仅管理员 / 管理角色允许 + +## 表用途 + +用于记录文档的新增、修改、删除等操作历史。 + +## 字段清单 + +| 字段名 | 类型 | 必填 | 说明 | +| :--- | :--- | :---: | :--- | +| `id` | `text` | 是 | PocketBase 记录主键 | +| `doh_id` | `text` | 是 | 文档操作历史业务 ID | +| `doh_document_id` | `text` | 是 | 关联文档业务 ID | +| `doh_operation_type` | `text` | 否 | 操作类型 | +| `doh_user_id` | `text` | 否 | 操作人业务 ID | +| `doh_current_count` | `number` | 否 | 本次操作对应次数 | +| `doh_remark` | `text` | 否 | 备注 | + +## 索引 + +| 索引名 | 类型 | 说明 | +| :--- | :--- | :--- | +| `idx_tbl_document_operation_history_doh_id` | `UNIQUE INDEX` | 保证 `doh_id` 唯一 | +| `idx_tbl_document_operation_history_doh_document_id` | `INDEX` | 加速按文档查询历史 | +| `idx_tbl_document_operation_history_doh_user_id` | `INDEX` | 加速按操作人查询 | +| `idx_tbl_document_operation_history_doh_operation_type` | `INDEX` | 加速按操作类型查询 | + +## 补充约定 + +- 本表主要用于管理端审计与追溯,不对匿名用户开放。 +- PocketBase 系统字段 `created`、`updated` 仍然存在,只是不在 collection 字段清单里单独声明。 diff --git a/docs/pb_tbl_system_dict.md b/docs/pb_tbl_system_dict.md new file mode 100644 index 0000000..129ecc7 --- /dev/null +++ b/docs/pb_tbl_system_dict.md @@ -0,0 +1,38 @@ +# pb_tbl_system_dict + +> 来源:线上 PocketBase collection 回读、`script/pocketbase.js`、`script/pocketbase.dictionary.js` +> 类型:`base` +> 读写规则:公开可读;仅 `ManagePlatform` / 管理角色可写 + +## 表用途 + +用于存储系统字典与枚举组。当前项目中,文档类型、关键词、产品关联文档、筛选依据、适用场景等前端多选项都来自本表。 + +## 字段清单 + +| 字段名 | 类型 | 必填 | 说明 | +| :--- | :--- | :---: | :--- | +| `id` | `text` | 是 | PocketBase 记录主键 | +| `system_dict_id` | `text` | 是 | 字典业务 ID | +| `dict_name` | `text` | 是 | 字典名称,当前按全局唯一维护 | +| `dict_word_enum` | `text` | 否 | 枚举值集合,底层以聚合字符串保存 | +| `dict_word_description` | `text` | 否 | 枚举描述集合,底层以聚合字符串保存 | +| `dict_word_is_enabled` | `bool` | 否 | 字典是否启用 | +| `dict_word_sort_order` | `text` | 否 | 枚举排序集合,底层以聚合字符串保存 | +| `dict_word_parent_id` | `text` | 否 | 父级字典业务 ID | +| `dict_word_remark` | `text` | 否 | 备注 | +| `dict_word_image` | `text` | 否 | 枚举图片附件 ID 集合,和枚举值一一对应 | + +## 索引 + +| 索引名 | 类型 | 说明 | +| :--- | :--- | :--- | +| `idx_system_dict_id` | `UNIQUE INDEX` | 保证 `system_dict_id` 唯一 | +| `idx_dict_name` | `UNIQUE INDEX` | 保证 `dict_name` 唯一 | +| `idx_dict_word_parent_id` | `INDEX` | 加速父子字典查询 | + +## 补充约定 + +- 业务返回时,hooks 会把聚合字段转换成 `items[]` 结构,每个元素包含 `enum`、`description`、`image`、`imageUrl`、`sortOrder`。 +- 字典项图片本体统一存放在 `tbl_attachments`,本表只保存 `attachments_id`。 +- PocketBase 系统字段 `created`、`updated` 仍然存在,只是不在 collection 字段清单里单独声明。 diff --git a/docs/tbl_auth_tables.md b/docs/tbl_auth_tables.md deleted file mode 100644 index 2bf92e3..0000000 --- a/docs/tbl_auth_tables.md +++ /dev/null @@ -1,190 +0,0 @@ -# tbl_auth 系列表结构说明 - -> 数据来源:`script/pocketbase.newpb.js` -> -> 说明:以下为当前脚本中定义的 `tbl_auth_` 系列 PocketBase 集合结构,不含 PocketBase `auth` 集合的系统内置字段,仅列出脚本中显式声明的自定义字段与索引。 - ---- - -## 1. `tbl_auth_users` - -- 集合类型:`auth` -- 说明:认证用户主表,业务身份锚点为 `openid` - -### 字段 - -| 字段名 | 类型 | 必填 | 备注 | -| :--- | :--- | :---: | :--- | -| `users_convers_id` | `text` | 否 | 会话/对话侧用户 ID | -| `openid` | `text` | 是 | 微信身份锚点,唯一索引 | -| `org_id` | `number` | 否 | 组织/部门 ID | -| `users_rank_level` | `number` | 否 | 等级 | -| `users_status` | `number` | 否 | 状态 | -| `users_auth_type` | `number` | 否 | 账户类型 | -| `users_id` | `text` | 否 | 自定义用户 ID | -| `users_name` | `text` | 否 | 用户姓名 | -| `users_idtype` | `text` | 否 | 证件类型 | -| `users_id_number` | `text` | 否 | 证件号 | -| `users_phone` | `text` | 否 | 手机号 | -| `users_level` | `text` | 否 | 用户等级 | -| `users_type` | `text` | 否 | 用户类型 | -| `users_status` | `text` | 否 | 用户状态 | -| `company_id` | `text` | 否 | 公司 ID | -| `users_parent_id` | `text` | 否 | 上级用户 ID | -| `users_promo_code` | `text` | 否 | 推广码 | -| `users_id_pic_a` | `text` | 否 | 保存 `tbl_attachments.attachments_id`,用于关联证件照正面 | -| `users_id_pic_b` | `text` | 否 | 保存 `tbl_attachments.attachments_id`,用于关联证件照反面 | -| `users_title_picture` | `text` | 否 | 保存 `tbl_attachments.attachments_id`,用于关联资质照片 | -| `users_picture` | `text` | 否 | 用户头像,保存 `tbl_attachments.attachments_id` | -| `usergroups_id` | `text` | 否 | 用户组 ID | - -### 索引 - -| 索引名 | 类型 | 字段 | -| :--- | :--- | :--- | -| `idx_tbl_auth_users_users_convers_id` | UNIQUE INDEX | `users_convers_id` | -| `idx_tbl_auth_users_openid` | UNIQUE INDEX | `openid` | -| `idx_tbl_auth_users_org_id` | INDEX | `org_id` | -| `idx_tbl_auth_users_users_rank_level` | INDEX | `users_rank_level` | -| `idx_tbl_auth_users_users_status` | INDEX | `users_status` | -| `idx_tbl_auth_users_users_auth_type` | INDEX | `users_auth_type` | -| `idx_tbl_auth_users_users_phone` | INDEX | `users_phone` | -| `idx_tbl_auth_users_company_id` | INDEX | `company_id` | -| `idx_tbl_auth_users_usergroups_id` | INDEX | `usergroups_id` | -| `idx_tbl_auth_users_users_parent_id` | INDEX | `users_parent_id` | - ---- - -## 2. `tbl_auth_resources` - -- 集合类型:`base` -- 说明:受控资源定义表 - -### 字段 - -| 字段名 | 类型 | 必填 | 备注 | -| :--- | :--- | :---: | :--- | -| `res_id` | `text` | 是 | 资源 ID | -| `table_name` | `text` | 是 | 表名 | -| `column_name` | `text` | 否 | 字段名 | -| `res_type` | `text` | 是 | 资源类型 | - -### 索引 - -| 索引名 | 类型 | 字段 | -| :--- | :--- | :--- | -| `idx_tbl_auth_resources_res_id` | UNIQUE INDEX | `res_id` | -| `idx_tbl_auth_resources_table_name` | INDEX | `table_name` | -| `idx_tbl_auth_resources_res_type` | INDEX | `res_type` | -| `idx_tbl_auth_resources_unique_res` | UNIQUE INDEX | `table_name, column_name, res_type` | - ---- - -## 3. `tbl_auth_roles` - -- 集合类型:`base` -- 说明:角色定义表 - -### 字段 - -| 字段名 | 类型 | 必填 | 备注 | -| :--- | :--- | :---: | :--- | -| `role_id` | `text` | 是 | 角色 ID | -| `role_name` | `text` | 是 | 角色名称 | -| `role_code` | `text` | 否 | 角色编码 | -| `role_status` | `number` | 否 | 角色状态 | -| `role_remark` | `text` | 否 | 备注 | - -### 索引 - -| 索引名 | 类型 | 字段 | -| :--- | :--- | :--- | -| `idx_tbl_auth_roles_role_id` | UNIQUE INDEX | `role_id` | -| `idx_tbl_auth_roles_role_name` | UNIQUE INDEX | `role_name` | -| `idx_tbl_auth_roles_role_code` | UNIQUE INDEX | `role_code` | - ---- - -## 4. `tbl_auth_role_perms` - -- 集合类型:`base` -- 说明:角色权限映射表 - -### 字段 - -| 字段名 | 类型 | 必填 | 备注 | -| :--- | :--- | :---: | :--- | -| `role_perm_id` | `text` | 是 | 角色权限记录 ID | -| `role_id` | `text` | 是 | 角色 ID | -| `res_id` | `text` | 是 | 资源 ID | -| `access_level` | `number` | 是 | 权限级别 | -| `priority` | `number` | 否 | 优先级 | - -### 索引 - -| 索引名 | 类型 | 字段 | -| :--- | :--- | :--- | -| `idx_tbl_auth_role_perms_role_perm_id` | UNIQUE INDEX | `role_perm_id` | -| `idx_tbl_auth_role_perms_role_id` | INDEX | `role_id` | -| `idx_tbl_auth_role_perms_res_id` | INDEX | `res_id` | -| `idx_tbl_auth_role_perms_unique_map` | UNIQUE INDEX | `role_id, res_id` | - ---- - -## 5. `tbl_auth_user_overrides` - -- 集合类型:`base` -- 说明:用户个性化权限覆盖表 - -### 字段 - -| 字段名 | 类型 | 必填 | 备注 | -| :--- | :--- | :---: | :--- | -| `override_id` | `text` | 是 | 覆盖记录 ID | -| `users_convers_id` | `text` | 是 | 用户会话标识 | -| `res_id` | `text` | 是 | 资源 ID | -| `access_level` | `number` | 是 | 权限级别 | -| `priority` | `number` | 否 | 优先级 | - -### 索引 - -| 索引名 | 类型 | 字段 | -| :--- | :--- | :--- | -| `idx_tbl_auth_user_overrides_override_id` | UNIQUE INDEX | `override_id` | -| `idx_tbl_auth_user_overrides_users_convers_id` | INDEX | `users_convers_id` | -| `idx_tbl_auth_user_overrides_res_id` | INDEX | `res_id` | -| `idx_tbl_auth_user_overrides_unique_map` | UNIQUE INDEX | `users_convers_id, res_id` | - ---- - -## 6. `tbl_auth_row_scopes` - -- 集合类型:`base` -- 说明:行级权限范围表 - -### 字段 - -| 字段名 | 类型 | 必填 | 备注 | -| :--- | :--- | :---: | :--- | -| `scope_id` | `text` | 是 | 范围记录 ID | -| `target_type` | `text` | 是 | 目标类型 | -| `target_id` | `text` | 是 | 目标 ID | -| `table_name` | `text` | 是 | 作用表名 | -| `filter_sql` | `editor` | 是 | 行级过滤表达式 | - -### 索引 - -| 索引名 | 类型 | 字段 | -| :--- | :--- | :--- | -| `idx_tbl_auth_row_scopes_scope_id` | UNIQUE INDEX | `scope_id` | -| `idx_tbl_auth_row_scopes_target_type` | INDEX | `target_type` | -| `idx_tbl_auth_row_scopes_target_id` | INDEX | `target_id` | -| `idx_tbl_auth_row_scopes_table_name` | INDEX | `table_name` | - ---- - -## 备注 - -1. `tbl_auth_users` 是 `auth` 集合,除上表字段外,还会受 PocketBase auth 系统字段与认证配置影响。 -2. 当前文档仅以 `script/pocketbase.newpb.js` 为准,不代表线上数据库已经 100% 同步成功。 -3. 若你需要,我可以继续帮你再生成一份“更像数据库设计说明书”的版本,增加字段含义、业务用途、关联关系三列。 diff --git a/openspec/changes/archive/2026-03-29-normalize-pocketbase-schema-docs/.openspec.yaml b/openspec/changes/archive/2026-03-29-normalize-pocketbase-schema-docs/.openspec.yaml new file mode 100644 index 0000000..5e98b74 --- /dev/null +++ b/openspec/changes/archive/2026-03-29-normalize-pocketbase-schema-docs/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-03-29 diff --git a/openspec/changes/archive/2026-03-29-normalize-pocketbase-schema-docs/design.md b/openspec/changes/archive/2026-03-29-normalize-pocketbase-schema-docs/design.md new file mode 100644 index 0000000..2b23741 --- /dev/null +++ b/openspec/changes/archive/2026-03-29-normalize-pocketbase-schema-docs/design.md @@ -0,0 +1,56 @@ +## Overview + +本次变更是一次“文档基线收敛 + OpenSpec 补记”。运行时 schema 已经完成修改,因此设计重点不是实现新逻辑,而是把现有真实状态沉淀成可检索、可归档、可继续维护的标准资料。 + +## Documentation Strategy + +### Per-collection database docs + +数据库结构文档统一拆成按 collection 命名的单文件: + +- `pb_tbl_system_dict.md` +- `pb_tbl_company.md` +- `pb_tbl_attachments.md` +- `pb_tbl_document.md` +- `pb_tbl_document_operation_history.md` +- `pb_tbl_auth_users.md` +- `pb_tbl_auth_resources.md` +- `pb_tbl_auth_roles.md` +- `pb_tbl_auth_role_perms.md` +- `pb_tbl_auth_user_overrides.md` +- `pb_tbl_auth_row_scopes.md` + +混合型老文档删除,避免一张表同时出现在多份说明中。 + +### Uniform format + +每份文档统一包含以下章节: + +1. 来源 +2. 类型 +3. 读写规则 +4. 表用途 +5. 字段清单 +6. 索引 +7. 补充约定 + +字段信息以线上实际 collection 回读为主,脚本为辅;这能规避脚本历史遗留误差。 + +## OpenSpec Recording Strategy + +### pocketbase-native-data-access + +记录当前项目允许直接走 PocketBase 原生 API / SDK 的边界: + +- `tbl_company` 公开创建与公开列表 / 条件查询 +- `tbl_system_dict` 公开读 +- `tbl_document` 公开读 + +### pocketbase-schema-docs + +记录数据库结构文档必须采用按表命名和统一格式的规范,避免未来继续出现零散说明。 + +## Validation + +- 文档字段以线上 PocketBase collection 回读结果为准 +- OpenSpec 变更在归档前完成 `openspec validate` diff --git a/openspec/changes/archive/2026-03-29-normalize-pocketbase-schema-docs/proposal.md b/openspec/changes/archive/2026-03-29-normalize-pocketbase-schema-docs/proposal.md new file mode 100644 index 0000000..23752bd --- /dev/null +++ b/openspec/changes/archive/2026-03-29-normalize-pocketbase-schema-docs/proposal.md @@ -0,0 +1,29 @@ +## Why + +近期项目连续完成了 PocketBase schema 扩展、原生公开查询、附件统一存储、文档字段扩展和用户字段扩展,但 `docs/` 中与数据库结构相关的文档仍然混合在少量总表文件里,命名和格式也不统一。与此同时,这批 schema 与原生数据访问能力还没有完整计入 OpenSpec,后续维护会出现“线上结构、脚本结构、文档结构、OpenSpec 记录”四套口径不一致的问题。 + +## What Changes + +- 将 `docs/` 中数据库结构相关文档拆分为按 collection 命名的标准文件,统一采用 `pb_tbl_xxx.md` 命名。 +- 统一数据库文档格式,按“来源 / 类型 / 读写规则 / 表用途 / 字段清单 / 索引 / 补充约定”组织内容。 +- 补充此前未进入 OpenSpec 的 PocketBase 原生数据访问与 schema 更新记录,包括: + - `tbl_company` 的公开创建 / 列表 / 条件查询约定 + - `tbl_system_dict` 与 `tbl_document` 的公开读规则 + - `company_owner_openid`、地区编码字段、`users_tag`、`document_file`、`document_create` 等新增字段 +- 归档本次规范化工作,作为后续数据库文档与原生 API 文档的基线。 + +## Capabilities + +### New Capabilities + +- `pocketbase-native-data-access`: 记录原生 PocketBase 公开读写边界与标准查询方式。 +- `pocketbase-schema-docs`: 规定数据库结构文档的命名与组织方式。 + +### Modified Capabilities + +- None. + +## Impact + +- 受影响目录:`docs/`、`openspec/` +- 不涉及新的运行时代码发布,但会影响后续 schema 变更和接口文档维护方式 diff --git a/openspec/changes/archive/2026-03-29-normalize-pocketbase-schema-docs/specs/pocketbase-native-data-access/spec.md b/openspec/changes/archive/2026-03-29-normalize-pocketbase-schema-docs/specs/pocketbase-native-data-access/spec.md new file mode 100644 index 0000000..5e51583 --- /dev/null +++ b/openspec/changes/archive/2026-03-29-normalize-pocketbase-schema-docs/specs/pocketbase-native-data-access/spec.md @@ -0,0 +1,34 @@ +## ADDED Requirements + +### Requirement: PocketBase native company access SHALL expose a stable public create and query boundary + +The system SHALL allow native PocketBase clients to create company records publicly and to query company records through standard PocketBase records APIs, while privileged mutations remain restricted to management users. + +#### Scenario: Public client creates company record + +- **WHEN** a client calls the native PocketBase create-record API for `tbl_company` +- **THEN** the request SHALL succeed without requiring a management token + +#### Scenario: Public client queries company list + +- **WHEN** a client calls the native PocketBase list-records API for `tbl_company` +- **THEN** the request SHALL return company records using standard PocketBase pagination parameters + +#### Scenario: Management user updates company record + +- **WHEN** a client attempts to update or delete `tbl_company` +- **THEN** PocketBase SHALL allow the operation only for management users or administrators + +### Requirement: Public dictionary and document reads SHALL be supported through native PocketBase APIs + +The system SHALL allow native PocketBase clients to read `tbl_system_dict` and `tbl_document` records without requiring application hooks tokens, while write operations remain restricted. + +#### Scenario: Public client reads dictionary records + +- **WHEN** a client calls the native PocketBase records API for `tbl_system_dict` +- **THEN** list and view operations SHALL be readable without authentication + +#### Scenario: Public client reads document records + +- **WHEN** a client calls the native PocketBase records API for `tbl_document` +- **THEN** list and view operations SHALL be readable without authentication diff --git a/openspec/changes/archive/2026-03-29-normalize-pocketbase-schema-docs/specs/pocketbase-schema-docs/spec.md b/openspec/changes/archive/2026-03-29-normalize-pocketbase-schema-docs/specs/pocketbase-schema-docs/spec.md new file mode 100644 index 0000000..c98fb68 --- /dev/null +++ b/openspec/changes/archive/2026-03-29-normalize-pocketbase-schema-docs/specs/pocketbase-schema-docs/spec.md @@ -0,0 +1,29 @@ +## ADDED Requirements + +### Requirement: Database structure docs SHALL be maintained per PocketBase collection + +The project SHALL document PocketBase schema structures in per-collection markdown files under `docs/`, and each file SHALL use the collection name in the filename. + +#### Scenario: Collection doc is named by collection + +- **WHEN** a PocketBase collection has a dedicated schema document +- **THEN** the document filename SHALL follow the `pb_tbl_xxx.md` naming convention + +#### Scenario: Mixed schema docs are replaced + +- **WHEN** older documents combine multiple unrelated collection structures in one file +- **THEN** the project SHALL replace them with per-collection docs to avoid conflicting schema descriptions + +### Requirement: Database structure docs SHALL use a uniform format + +Each PocketBase schema document SHALL use the same section structure so readers can quickly compare tables and verify field and index changes. + +#### Scenario: Reader opens a schema document + +- **WHEN** a reader opens any database structure document under `docs/` +- **THEN** the document SHALL include source, collection type, access rule summary, purpose, field table, index table, and supplementary notes + +#### Scenario: Schema docs reflect live collection state + +- **WHEN** the project documents a collection structure +- **THEN** the field list and rule summary SHALL be aligned with the current live PocketBase collection state whenever practical diff --git a/openspec/changes/archive/2026-03-29-normalize-pocketbase-schema-docs/tasks.md b/openspec/changes/archive/2026-03-29-normalize-pocketbase-schema-docs/tasks.md new file mode 100644 index 0000000..ca7cf18 --- /dev/null +++ b/openspec/changes/archive/2026-03-29-normalize-pocketbase-schema-docs/tasks.md @@ -0,0 +1,19 @@ +## 1. 数据库文档整理 + +- [x] 1.1 盘点 `docs/` 中和数据库结构直接相关的文档。 +- [x] 1.2 将混合型结构文档拆分为按 collection 命名的标准文件。 +- [x] 1.3 统一所有数据库结构文档格式。 +- [x] 1.4 删除被替代的旧文档文件。 + +## 2. 基于线上结构回读修正文档 + +- [x] 2.1 回读线上 `tbl_system_dict`、`tbl_company`、`tbl_attachments`、`tbl_document`、`tbl_document_operation_history`。 +- [x] 2.2 回读线上 `tbl_auth_*` 系列 collection。 +- [x] 2.3 以线上结果修正字段、索引和权限描述。 + +## 3. OpenSpec 补记与归档 + +- [x] 3.1 为未计入 OpenSpec 的原生数据访问与 schema 文档规范补建 change。 +- [x] 3.2 为 `pocketbase-native-data-access` 编写 delta spec。 +- [x] 3.3 为 `pocketbase-schema-docs` 编写 delta spec。 +- [x] 3.4 校验并归档本次 change。 diff --git a/openspec/specs/pocketbase-native-data-access/spec.md b/openspec/specs/pocketbase-native-data-access/spec.md new file mode 100644 index 0000000..126626d --- /dev/null +++ b/openspec/specs/pocketbase-native-data-access/spec.md @@ -0,0 +1,38 @@ +# pocketbase-native-data-access Specification + +## Purpose +TBD - created by archiving change normalize-pocketbase-schema-docs. Update Purpose after archive. +## Requirements +### Requirement: PocketBase native company access SHALL expose a stable public create and query boundary + +The system SHALL allow native PocketBase clients to create company records publicly and to query company records through standard PocketBase records APIs, while privileged mutations remain restricted to management users. + +#### Scenario: Public client creates company record + +- **WHEN** a client calls the native PocketBase create-record API for `tbl_company` +- **THEN** the request SHALL succeed without requiring a management token + +#### Scenario: Public client queries company list + +- **WHEN** a client calls the native PocketBase list-records API for `tbl_company` +- **THEN** the request SHALL return company records using standard PocketBase pagination parameters + +#### Scenario: Management user updates company record + +- **WHEN** a client attempts to update or delete `tbl_company` +- **THEN** PocketBase SHALL allow the operation only for management users or administrators + +### Requirement: Public dictionary and document reads SHALL be supported through native PocketBase APIs + +The system SHALL allow native PocketBase clients to read `tbl_system_dict` and `tbl_document` records without requiring application hooks tokens, while write operations remain restricted. + +#### Scenario: Public client reads dictionary records + +- **WHEN** a client calls the native PocketBase records API for `tbl_system_dict` +- **THEN** list and view operations SHALL be readable without authentication + +#### Scenario: Public client reads document records + +- **WHEN** a client calls the native PocketBase records API for `tbl_document` +- **THEN** list and view operations SHALL be readable without authentication + diff --git a/openspec/specs/pocketbase-schema-docs/spec.md b/openspec/specs/pocketbase-schema-docs/spec.md new file mode 100644 index 0000000..4a57493 --- /dev/null +++ b/openspec/specs/pocketbase-schema-docs/spec.md @@ -0,0 +1,33 @@ +# pocketbase-schema-docs Specification + +## Purpose +TBD - created by archiving change normalize-pocketbase-schema-docs. Update Purpose after archive. +## Requirements +### Requirement: Database structure docs SHALL be maintained per PocketBase collection + +The project SHALL document PocketBase schema structures in per-collection markdown files under `docs/`, and each file SHALL use the collection name in the filename. + +#### Scenario: Collection doc is named by collection + +- **WHEN** a PocketBase collection has a dedicated schema document +- **THEN** the document filename SHALL follow the `pb_tbl_xxx.md` naming convention + +#### Scenario: Mixed schema docs are replaced + +- **WHEN** older documents combine multiple unrelated collection structures in one file +- **THEN** the project SHALL replace them with per-collection docs to avoid conflicting schema descriptions + +### Requirement: Database structure docs SHALL use a uniform format + +Each PocketBase schema document SHALL use the same section structure so readers can quickly compare tables and verify field and index changes. + +#### Scenario: Reader opens a schema document + +- **WHEN** a reader opens any database structure document under `docs/` +- **THEN** the document SHALL include source, collection type, access rule summary, purpose, field table, index table, and supplementary notes + +#### Scenario: Schema docs reflect live collection state + +- **WHEN** the project documents a collection structure +- **THEN** the field list and rule summary SHALL be aligned with the current live PocketBase collection state whenever practical + diff --git a/pocket-base/bai-api-main.pb.js b/pocket-base/bai-api-main.pb.js index 9218b54..1270a60 100644 --- a/pocket-base/bai-api-main.pb.js +++ b/pocket-base/bai-api-main.pb.js @@ -23,6 +23,7 @@ require(`${__hooks}/bai_api_pb_hooks/bai_api_routes/system/users-count.js`) require(`${__hooks}/bai_api_pb_hooks/bai_api_routes/system/refresh-token.js`) require(`${__hooks}/bai_api_pb_hooks/bai_api_routes/platform/login.js`) require(`${__hooks}/bai_api_pb_hooks/bai_api_routes/platform/register.js`) +require(`${__hooks}/bai_api_pb_hooks/bai_api_routes/company/records-create.js`) require(`${__hooks}/bai_api_pb_hooks/bai_api_routes/dictionary/list.js`) require(`${__hooks}/bai_api_pb_hooks/bai_api_routes/dictionary/detail.js`) require(`${__hooks}/bai_api_pb_hooks/bai_api_routes/dictionary/create.js`) diff --git a/pocket-base/bai_api_pb_hooks/bai_api_routes/company/records-create.js b/pocket-base/bai_api_pb_hooks/bai_api_routes/company/records-create.js new file mode 100644 index 0000000..e14ba18 --- /dev/null +++ b/pocket-base/bai_api_pb_hooks/bai_api_routes/company/records-create.js @@ -0,0 +1,17 @@ +onRecordAfterCreateSuccess((e) => { + try { + const authRecord = e && e.auth ? e.auth : null + if (!authRecord || authRecord.collection().name !== 'tbl_auth_users') { + return + } + + if (authRecord.getString('users_type') === '服务商') { + return + } + + authRecord.set('users_type', '服务商') + $app.save(authRecord) + } catch (_error) { + // Keep company create flow unchanged even if user type sync fails. + } +}, 'tbl_company') diff --git a/pocket-base/bai_api_pb_hooks/bai_api_routes/dictionary/detail.js b/pocket-base/bai_api_pb_hooks/bai_api_routes/dictionary/detail.js index 53499b2..7b83883 100644 --- a/pocket-base/bai_api_pb_hooks/bai_api_routes/dictionary/detail.js +++ b/pocket-base/bai_api_pb_hooks/bai_api_routes/dictionary/detail.js @@ -6,7 +6,6 @@ routerAdd('POST', '/api/dictionary/detail', function (e) { try { guards.requireJson(e) - guards.requireManagePlatformUser(e) const payload = guards.validateDictionaryDetailBody(e) const data = dictionaryService.getDictionaryByName(payload.dict_name) @@ -25,4 +24,4 @@ routerAdd('POST', '/api/dictionary/detail', function (e) { data: (err && err.data) || {}, }) } -}) \ No newline at end of file +}) diff --git a/pocket-base/bai_api_pb_hooks/bai_api_routes/dictionary/list.js b/pocket-base/bai_api_pb_hooks/bai_api_routes/dictionary/list.js index faea7a3..b29c030 100644 --- a/pocket-base/bai_api_pb_hooks/bai_api_routes/dictionary/list.js +++ b/pocket-base/bai_api_pb_hooks/bai_api_routes/dictionary/list.js @@ -6,7 +6,6 @@ routerAdd('POST', '/api/dictionary/list', function (e) { try { guards.requireJson(e) - guards.requireManagePlatformUser(e) const payload = guards.validateDictionaryListBody(e) const data = dictionaryService.listDictionaries(payload.keyword) @@ -27,4 +26,4 @@ routerAdd('POST', '/api/dictionary/list', function (e) { data: (err && err.data) || {}, }) } -}) \ No newline at end of file +}) diff --git a/pocket-base/bai_api_pb_hooks/bai_api_shared/config/runtime.js b/pocket-base/bai_api_pb_hooks/bai_api_shared/config/runtime.js index 64b691c..92a6d04 100644 --- a/pocket-base/bai_api_pb_hooks/bai_api_shared/config/runtime.js +++ b/pocket-base/bai_api_pb_hooks/bai_api_shared/config/runtime.js @@ -6,7 +6,5 @@ module.exports = { POCKETBASE_AUTH_TOKEN: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb2xsZWN0aW9uSWQiOiJwYmNfMzE0MjYzNTgyMyIsImV4cCI6MTgwNTk2MzM4NywiaWQiOiJmcnl2dG1qNnlwcHlmMXAiLCJyZWZyZXNoYWJsZSI6ZmFsc2UsInR5cGUiOiJhdXRoIn0.R34MTotuFBpevqbbUtvQ3GPa0Z1mpY8eQwZgDdmfhMo', WECHAT_APPID: 'wx3bd7a7b19679da7a', WECHAT_SECRET: '57e40438c2a9151257b1927674db10e1', - /* WECHAT_APPID: 'wx42e9add0f91af98b', - WECHAT_SECRET: '5620f00b40297efaf3d197d61ae184d6', */ BUILD_TIME: '', } \ No newline at end of file diff --git a/pocket-base/bai_api_pb_hooks/bai_api_shared/middlewares/requestGuards.js b/pocket-base/bai_api_pb_hooks/bai_api_shared/middlewares/requestGuards.js index 3c7a922..cf636a0 100644 --- a/pocket-base/bai_api_pb_hooks/bai_api_shared/middlewares/requestGuards.js +++ b/pocket-base/bai_api_pb_hooks/bai_api_shared/middlewares/requestGuards.js @@ -25,13 +25,12 @@ function validateLoginBody(e) { function validateProfileBody(e) { const payload = parseBody(e) - if (!payload.users_name) throw createAppError(400, 'users_name 为必填项') - if (!payload.users_phone_code) throw createAppError(400, 'users_phone_code 为必填项') - if (!payload.users_picture) throw createAppError(400, 'users_picture 为必填项') return { - users_name: payload.users_name, - users_phone_code: payload.users_phone_code, - users_picture: payload.users_picture, + users_name: Object.prototype.hasOwnProperty.call(payload, 'users_name') ? String(payload.users_name || '').trim() : undefined, + users_phone_code: Object.prototype.hasOwnProperty.call(payload, 'users_phone_code') ? String(payload.users_phone_code || '').trim() : undefined, + users_phone: Object.prototype.hasOwnProperty.call(payload, 'users_phone') ? String(payload.users_phone || '').trim() : undefined, + users_tag: Object.prototype.hasOwnProperty.call(payload, 'users_tag') ? String(payload.users_tag || '').trim() : undefined, + users_picture: Object.prototype.hasOwnProperty.call(payload, 'users_picture') ? String(payload.users_picture || '').trim() : undefined, users_id_pic_a: Object.prototype.hasOwnProperty.call(payload, 'users_id_pic_a') ? payload.users_id_pic_a : undefined, users_id_pic_b: Object.prototype.hasOwnProperty.call(payload, 'users_id_pic_b') ? payload.users_id_pic_b : undefined, users_title_picture: Object.prototype.hasOwnProperty.call(payload, 'users_title_picture') ? payload.users_title_picture : undefined, @@ -60,6 +59,7 @@ function validatePlatformRegisterBody(e) { users_id_number: payload.users_id_number || '', users_level: payload.users_level || '', users_type: payload.users_type || '', + users_tag: payload.users_tag || '', company_id: payload.company_id || '', users_parent_id: payload.users_parent_id || '', users_promo_code: payload.users_promo_code || '', diff --git a/pocket-base/bai_api_pb_hooks/bai_api_shared/services/documentService.js b/pocket-base/bai_api_pb_hooks/bai_api_shared/services/documentService.js index 09acee6..4202646 100644 --- a/pocket-base/bai_api_pb_hooks/bai_api_shared/services/documentService.js +++ b/pocket-base/bai_api_pb_hooks/bai_api_shared/services/documentService.js @@ -206,6 +206,7 @@ function exportDocumentRecord(record) { return { pb_id: record.id, document_id: record.getString('document_id'), + document_create: String(record.get('document_create') || ''), document_effect_date: String(record.get('document_effect_date') || ''), document_expiry_date: String(record.get('document_expiry_date') || ''), document_type: record.getString('document_type'), diff --git a/pocket-base/bai_api_pb_hooks/bai_api_shared/services/userService.js b/pocket-base/bai_api_pb_hooks/bai_api_shared/services/userService.js index 37877b0..83e4ca4 100644 --- a/pocket-base/bai_api_pb_hooks/bai_api_shared/services/userService.js +++ b/pocket-base/bai_api_pb_hooks/bai_api_shared/services/userService.js @@ -122,7 +122,30 @@ function getCompanyByCompanyId(companyId) { function exportCompany(companyRecord) { if (!companyRecord) return null - return companyRecord.publicExport() + return { + pb_id: companyRecord.id, + company_id: companyRecord.getString('company_id'), + company_name: companyRecord.getString('company_name'), + company_type: companyRecord.getString('company_type'), + company_entity: companyRecord.getString('company_entity'), + company_usci: companyRecord.getString('company_usci'), + company_nationality: companyRecord.getString('company_nationality'), + company_nationality_code: companyRecord.getString('company_nationality_code'), + company_province: companyRecord.getString('company_province'), + company_province_code: companyRecord.getString('company_province_code'), + company_city: companyRecord.getString('company_city'), + company_city_code: companyRecord.getString('company_city_code'), + company_district: companyRecord.getString('company_district'), + company_district_code: companyRecord.getString('company_district_code'), + company_postalcode: companyRecord.getString('company_postalcode'), + company_add: companyRecord.getString('company_add'), + company_status: companyRecord.getString('company_status'), + company_level: companyRecord.getString('company_level'), + company_owner_openid: companyRecord.getString('company_owner_openid'), + company_remark: companyRecord.getString('company_remark'), + created: String(companyRecord.created || ''), + updated: String(companyRecord.updated || ''), + } } function resolveUserAttachment(attachmentId) { @@ -167,15 +190,23 @@ function ensureAttachmentIdExists(attachmentId, fieldName) { function applyUserAttachmentFields(record, payload) { if (!payload) return - record.set('users_picture', ensureAttachmentIdExists(payload.users_picture || '', 'users_picture')) + if (typeof payload.users_picture !== 'undefined' && payload.users_picture) { + record.set('users_picture', ensureAttachmentIdExists(payload.users_picture, 'users_picture')) + } if (typeof payload.users_id_pic_a !== 'undefined') { - record.set('users_id_pic_a', ensureAttachmentIdExists(payload.users_id_pic_a || '', 'users_id_pic_a')) + if (payload.users_id_pic_a) { + record.set('users_id_pic_a', ensureAttachmentIdExists(payload.users_id_pic_a, 'users_id_pic_a')) + } } if (typeof payload.users_id_pic_b !== 'undefined') { - record.set('users_id_pic_b', ensureAttachmentIdExists(payload.users_id_pic_b || '', 'users_id_pic_b')) + if (payload.users_id_pic_b) { + record.set('users_id_pic_b', ensureAttachmentIdExists(payload.users_id_pic_b, 'users_id_pic_b')) + } } if (typeof payload.users_title_picture !== 'undefined') { - record.set('users_title_picture', ensureAttachmentIdExists(payload.users_title_picture || '', 'users_title_picture')) + if (payload.users_title_picture) { + record.set('users_title_picture', ensureAttachmentIdExists(payload.users_title_picture, 'users_title_picture')) + } } } @@ -202,6 +233,7 @@ function enrichUser(userRecord) { users_phone: userRecord.getString('users_phone'), users_phone_masked: maskPhone(userRecord.getString('users_phone')), users_level: userRecord.getString('users_level'), + users_tag: userRecord.getString('users_tag'), users_picture: userPicture.id, users_picture_url: userPicture.url, users_picture_attachment: userPicture.attachment, @@ -366,6 +398,7 @@ function registerPlatformUser(payload) { record.set('users_id_number', payload.users_id_number || '') record.set('users_level', payload.users_level || '') record.set('users_type', payload.users_type || REGISTERED_USER_TYPE) + record.set('users_tag', payload.users_tag || '') record.set('company_id', payload.company_id || '') record.set('users_parent_id', payload.users_parent_id || '') record.set('users_promo_code', payload.users_promo_code || '') @@ -481,7 +514,12 @@ function updateWechatUserProfile(usersWxOpenid, payload) { throw createAppError(404, '未找到待编辑的用户') } - const usersPhone = wechatService.getWxPhoneNumber(payload.users_phone_code) + let usersPhone = '' + if (payload.users_phone_code) { + usersPhone = wechatService.getWxPhoneNumber(payload.users_phone_code) + } else if (payload.users_phone) { + usersPhone = String(payload.users_phone || '').trim() + } if (usersPhone && usersPhone !== currentUser.getString('users_phone')) { const samePhoneUsers = $app.findRecordsByFilter('tbl_auth_users', 'users_phone = {:phone}', '', 10, 0, { @@ -494,15 +532,19 @@ function updateWechatUserProfile(usersWxOpenid, payload) { } } - const shouldPromote = isAllProfileFieldsEmpty(currentUser) - && !!payload.users_name - && !!usersPhone - && !!payload.users_picture - && ((currentUser.getString('users_type') || GUEST_USER_TYPE) === GUEST_USER_TYPE) - - currentUser.set('users_name', payload.users_name) - currentUser.set('users_phone', usersPhone) + if (payload.users_name) { + currentUser.set('users_name', payload.users_name) + } + if (usersPhone) { + currentUser.set('users_phone', usersPhone) + } + if (typeof payload.users_tag !== 'undefined' && payload.users_tag) { + currentUser.set('users_tag', payload.users_tag) + } applyUserAttachmentFields(currentUser, payload) + + const shouldPromote = ((currentUser.getString('users_type') || GUEST_USER_TYPE) === GUEST_USER_TYPE) + && isInfoComplete(currentUser) if (shouldPromote) { currentUser.set('users_type', REGISTERED_USER_TYPE) } diff --git a/pocket-base/bai_web_pb_hooks/pages/document-manage.js b/pocket-base/bai_web_pb_hooks/pages/document-manage.js index aa2ba74..418b992 100644 --- a/pocket-base/bai_web_pb_hooks/pages/document-manage.js +++ b/pocket-base/bai_web_pb_hooks/pages/document-manage.js @@ -65,12 +65,15 @@ routerAdd('GET', '/manage/document-manage', function (e) { .dropzone-title { font-weight: 700; color: #1e3a8a; } .dropzone-text { color: #475569; font-size: 13px; margin-top: 6px; } .dropzone input[type="file"] { margin-top: 12px; } - .file-list { display: grid; gap: 10px; margin-top: 12px; } - .file-card { border: 1px solid #dbe3f0; border-radius: 14px; padding: 12px; background: #fff; } - .file-card-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; } - .file-card-title { font-weight: 700; word-break: break-all; } - .file-meta { color: #64748b; font-size: 12px; margin-top: 6px; word-break: break-all; } - .thumb { width: 72px; height: 72px; border-radius: 12px; object-fit: cover; border: 1px solid #dbe3f0; background: #fff; cursor: zoom-in; } + .file-list { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; margin-top: 12px; } + .file-card { min-width: 0; min-height: 122px; border: 1px solid #dbe3f0; border-radius: 14px; padding: 8px; background: #fff; display: flex; flex-direction: column; gap: 6px; } + .file-card-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 8px; } + .file-card-title { flex: 1; font-weight: 700; font-size: 12px; line-height: 1.3; word-break: break-all; } + .file-card-icon { width: 24px; height: 24px; border-radius: 999px; border: 1px solid #dbe3f0; background: #fff; color: #475569; display: inline-flex; align-items: center; justify-content: center; cursor: pointer; font-size: 14px; padding: 0; } + .file-card-icon:hover { border-color: #94a3b8; background: #f8fafc; } + .file-meta { display: none; } + .thumb { width: 60px; height: 60px; border-radius: 10px; object-fit: cover; border: 1px solid #dbe3f0; background: #fff; cursor: zoom-in; } + .file-preview { width: 60px; height: 60px; border-radius: 10px; border: 1px solid #dbe3f0; background: #f8fafc; color: #334155; display: flex; align-items: center; justify-content: center; font-size: 24px; cursor: pointer; } .thumb-strip { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 10px; } .image-viewer { position: fixed; inset: 0; display: none; align-items: center; justify-content: center; padding: 24px; background: rgba(15, 23, 42, 0.82); z-index: 9999; } .image-viewer.show { display: flex; } @@ -85,6 +88,7 @@ routerAdd('GET', '/manage/document-manage', function (e) { @media (max-width: 960px) { .grid, .file-group { grid-template-columns: 1fr; } .option-list { grid-template-columns: repeat(2, minmax(0, 1fr)); } + .file-list { grid-template-columns: repeat(2, minmax(0, 1fr)); } table, thead, tbody, th, td, tr { display: block; } thead { display: none; } tr { margin-bottom: 14px; border: 1px solid #e5e7eb; border-radius: 16px; overflow: hidden; } @@ -526,15 +530,46 @@ routerAdd('GET', '/manage/document-manage', function (e) { return sourceId ? (state.dictionariesById[sourceId] || null) : null } + function normalizeDocumentTypeEnumValue(value) { + const raw = String(value || '').trim() + if (!raw) { + return '' + } + + const separatorIndex = raw.indexOf('@') + return separatorIndex === -1 ? raw : raw.slice(separatorIndex + 1) + } + function buildDocumentTypeStorageValue() { const sourceDict = getDocumentTypeSourceDictionary() if (!sourceDict) { return '' } - return joinPipeValue(state.selections.documentTypeValues.map(function (enumValue) { - return sourceDict.system_dict_id + '@' + enumValue - })) + const sourceId = String(sourceDict.system_dict_id || '').trim() + if (!sourceId) { + return '' + } + + const seen = {} + const parts = [] + + state.selections.documentTypeValues.forEach(function (value) { + const enumValue = normalizeDocumentTypeEnumValue(value) + if (!enumValue) { + return + } + + const token = sourceId + '@' + enumValue + if (seen[token]) { + return + } + + seen[token] = true + parts.push(token) + }) + + return joinPipeValue(parts) } function updateSelection(fieldName, value, checked) { @@ -660,7 +695,7 @@ routerAdd('GET', '/manage/document-manage', function (e) { if (state.mode === 'edit') { formTitleEl.textContent = '编辑文档' - editorModeEl.textContent = '当前模式:编辑 ' + state.editingId + editorModeEl.textContent = '当前模式:编辑 ' + (state.editingSource && state.editingSource.document_title ? state.editingSource.document_title : state.editingId) document.getElementById('submitBtn').textContent = '保存文档修改' } else if (state.mode === 'create') { formTitleEl.textContent = '新增文档' @@ -823,29 +858,20 @@ routerAdd('GET', '/manage/document-manage', function (e) { } container.innerHTML = items.map(function (item, index) { - const title = pending + const title = pending ? (item.file && item.file.name ? item.file.name : ('待上传文件' + (index + 1))) : (item.attachments_filename || item.attachments_id || ('附件' + (index + 1))) - const meta = pending - ? ('大小:' + String(item.file && item.file.size ? item.file.size : 0) + ' bytes') - : ('ID:' + escapeHtml(item.attachments_id || '') + (item.attachments_filetype ? ' / ' + escapeHtml(item.attachments_filetype) : '')) - const linkHtml = pending || !item.attachments_url - ? '' - : '打开文件' + const previewUrl = getAttachmentPreviewUrl(item, pending) const previewHtml = category === 'image' - ? renderImageThumb(getAttachmentPreviewUrl(item, pending), title) - : '' - const actionLabel = pending ? '移除待上传' : '从文档移除' + ? renderImageThumb(previewUrl, title) + : (previewUrl + ? ('' + (category === 'video' ? '🎬' : '📄') + '') + : ('
' + (category === 'video' ? '🎬' : '📄') + '
')) const handler = pending ? '__removePendingAttachment' : '__removeCurrentAttachment' return '
' - + '
' + escapeHtml(title) + '
' + + '
' + escapeHtml(title) + '
' + previewHtml - + '
' + meta + '
' - + '
' - + linkHtml - + '' - + '
' + '
' }).join('') } @@ -860,27 +886,13 @@ routerAdd('GET', '/manage/document-manage', function (e) { } function renderLinks(item) { - const links = [] - const imageThumbs = [] const imageUrls = Array.isArray(item.document_image_urls) ? item.document_image_urls : (item.document_image_url ? [item.document_image_url] : []) - const videoUrls = Array.isArray(item.document_video_urls) ? item.document_video_urls : (item.document_video_url ? [item.document_video_url] : []) - const fileUrls = Array.isArray(item.document_file_urls) ? item.document_file_urls : (item.document_file_url ? [item.document_file_url] : []) - for (let i = 0; i < imageUrls.length; i += 1) { - links.push('图片流' + (i + 1) + '') - imageThumbs.push(renderImageThumb(imageUrls[i], '图片流' + (i + 1))) - } - for (let i = 0; i < videoUrls.length; i += 1) { - links.push('视频流' + (i + 1) + '') - } - for (let i = 0; i < fileUrls.length; i += 1) { - links.push('文件流' + (i + 1) + '') - } - if (!links.length) { + if (!imageUrls.length) { return '' } - return '' - + (imageThumbs.length ? '
' + imageThumbs.join('') + '
' : '') + + return '
' + renderImageThumb(imageUrls[0], '文档图片预览') + '
' } function renderTable() { @@ -1132,7 +1144,7 @@ routerAdd('GET', '/manage/document-manage', function (e) { fillFormFromItem(target) updateEditorMode() renderAttachmentEditors() - setStatus('已进入编辑模式:' + target.document_id, 'success') + setStatus('已进入编辑模式:' + (target.document_title || target.document_id), 'success') window.scrollTo({ top: 0, behavior: 'smooth' }) } diff --git a/pocket-base/spec/changes.2026-03-23-pocketbase-hooks-auth-hardening.md b/pocket-base/spec/changes.2026-03-23-pocketbase-hooks-auth-hardening.md deleted file mode 100644 index 6b7cdad..0000000 --- a/pocket-base/spec/changes.2026-03-23-pocketbase-hooks-auth-hardening.md +++ /dev/null @@ -1,472 +0,0 @@ -# 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 ` -- 非标准 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` 使用占位格式: - - 微信用户:`@wechat.local` - - 平台用户:`@manage.local` -- 自动生成随机密码 -- 自动补齐 `passwordConfirm` - -说明: - -- 占位 `email` 仅用于满足 auth 集合保存条件,不代表用户真实邮箱。 -- 业务主身份仍然是 `openid`。 - -### 3. 自定义字段可空策略 - -- `tbl_auth_users` 的自定义字段目标约束为:除 `openid` 外,其余业务字段均允许为空。 -- 已将 schema 脚本中的 `user_id` 改为非必填。 -- 其余业务字段保持非必填。 - -### 4. 用户图片字段统一改为附件 ID 语义 - -- `users_picture` -- `users_id_pic_a` -- `users_id_pic_b` -- `users_title_picture` - -以上字段已统一改为保存 `tbl_attachments.attachments_id`,不再直接保存 PocketBase `file` 字段或外部图片 URL。 - -查询用户信息时,hooks 会自动联查 `tbl_attachments` 并补充: - -- `users_picture_url` -- `users_id_pic_a_url` -- `users_id_pic_b_url` -- `users_title_picture_url` - -说明: - -- `tbl_attachments` 仍由附件表保存实际文件本体; -- 业务表仅负责保存附件 ID; -- hooks 中原有 `ManagePlatform` 访问限制保持不变。 - ---- - -## 三、查询与排序修复 - -### 1. 移除无意义的 `created` 排序 - -在 hooks 查询中,以下查询原先使用 `'-created'` 排序: - -- 按 `openid` 查询用户 -- 按 `company_id` 查询公司 -- 按 `users_phone` 查询重复手机号 - -该写法在 PocketBase 当前运行场景下触发: - -- `invalid sort field "created"` - -现已统一移除排序参数,改为空排序字符串,因为这些查询本质上均为精确匹配或去重检查,不依赖排序。 - ---- - -## 四、错误可观测性增强 - -### 1. 登录路由显式错误响应 - -`POST /pb/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 /pb/api/system/test-helloworld` -- `POST /pb/api/system/health` -- `POST /pb/api/system/refresh-token` -- `POST /pb/api/platform/register` -- `POST /pb/api/platform/login` -- `POST /pb/api/wechat/login` -- `POST /pb/api/wechat/profile` -- `POST /pb/api/dictionary/list` -- `POST /pb/api/dictionary/detail` -- `POST /pb/api/dictionary/create` -- `POST /pb/api/dictionary/update` -- `POST /pb/api/dictionary/delete` -- `POST /pb/api/attachment/list` -- `POST /pb/api/attachment/detail` -- `POST /pb/api/attachment/upload` -- `POST /pb/api/attachment/delete` -- `POST /pb/api/document/list` -- `POST /pb/api/document/detail` -- `POST /pb/api/document/create` -- `POST /pb/api/document/update` -- `POST /pb/api/document/delete` -- `POST /pb/api/document-history/list` - -其中平台用户链路补充为: - -### `POST /pb/api/platform/register` - -- body 必填:`users_name`、`users_phone`、`password`、`passwordConfirm`、`users_picture` -- 自动生成 GUID 并写入统一身份字段 `openid` -- 写入 `users_idtype = ManagePlatform` -- 成功时返回统一结构:`code`、`msg`、`data`,并在顶层额外返回 `token` - -### `POST /pb/api/platform/login` - -- body 必填:`login_account`、`password` -- 仅允许 `users_idtype = ManagePlatform` -- 前端使用邮箱或手机号 + 密码提交 -- 服务端先通过 PocketBase `auth-with-password` 校验身份,再由当前 hooks 进程签发正式 token -- 成功时返回统一结构:`code`、`msg`、`data`,并在顶层额外返回 `token` - -其中: - -### `POST /pb/api/wechat/login` - -- body 必填:`users_wx_code` -- 自动以微信 code 换取微信侧 `openid` 并写入统一身份字段 -- 若不存在 auth 用户则尝试创建 `tbl_auth_users` 记录 -- 写入 `users_idtype = WeChat` -- 成功时返回统一结构:`code`、`msg`、`data`,并在顶层额外返回 `token` - -### `POST /pb/api/wechat/profile` - -- 需 `Authorization` -- 基于当前 auth record 的 `openid` 定位用户 -- 服务端用 `users_phone_code` 换取手机号后保存 - -### `POST /pb/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 /pb/api/dictionary/list` - - 支持按 `dict_name` 模糊搜索 - - 返回字典全量信息,并将 `dict_word_enum`、`dict_word_description`、`dict_word_image`、`dict_word_sort_order` 组装为 `items` -- `POST /pb/api/dictionary/detail` - - 按 `dict_name` 查询单条字典 -- `POST /pb/api/dictionary/create` - - 新增字典,`system_dict_id` 自动生成,`dict_name` 唯一 -- `POST /pb/api/dictionary/update` - - 按 `original_dict_name` / `dict_name` 更新字典 -- `POST /pb/api/dictionary/delete` - - 按 `dict_name` 真删除字典 - -说明: - -- `dict_word_enum`、`dict_word_description`、`dict_word_image`、`dict_word_sort_order` 已统一改为 JSON 字符串持久化,其中 `dict_word_sort_order` 已改为 `text` 类型。 -- 字典项图片需先调用 `/pb/api/attachment/upload` 上传,再把返回的 `attachments_id` 写入 `dict_word_image` 对应位置。 -- 查询时统一聚合为:`items: [{ enum, description, image, imageUrl, imageAttachment, sortOrder }]` - -### 附件管理接口 - -新增 `attachment` 分类接口,统一要求平台管理用户访问: - -- `POST /pb/api/attachment/list` - - 支持按 `attachments_id`、`attachments_filename` 模糊搜索 - - 支持按 `attachments_status` 过滤 - - 返回附件元数据以及 PocketBase 文件流链接 `attachments_url` -- `POST /pb/api/attachment/detail` - - 按 `attachments_id` 查询单个附件 - - 返回文件流链接与下载链接 -- `POST /pb/api/attachment/upload` - - 使用 `multipart/form-data` - - 文件字段固定为 `attachments_link` - - 上传成功后自动生成 `attachments_id` - - 自动写入 `attachments_owner = 当前用户 openid` -- `POST /pb/api/attachment/delete` - - 按 `attachments_id` 真删除附件 - - 若该附件已被 `tbl_document.document_image`、`document_video` 或 `document_file` 中的任一附件列表引用,则拒绝删除 - -说明: - -- `tbl_attachments.attachments_link` 为 PocketBase `file` 字段,保存实际文件本体。 -- 对外查询时会额外补充: - - `attachments_url` - - `attachments_download_url` - -### 文档管理接口 - -新增 `document` 分类接口,统一要求平台管理用户访问: - -- `POST /pb/api/document/list` - - 支持按 `document_id`、`document_title`、`document_subtitle`、`document_summary`、`document_keywords` 模糊搜索 - - 支持按 `document_status`、`document_type` 过滤 - - 返回时会自动联查 `tbl_attachments` - - 额外补充: - - `document_image_urls` - - `document_video_urls` - - `document_file_urls` - - `document_image_attachments` - - `document_video_attachments` - - `document_file_attachments` -- `POST /pb/api/document/detail` - - 按 `document_id` 查询单条文档 - - 返回与附件表联动解析后的多文件流链接 -- `POST /pb/api/document/create` - - 新增文档 - - `document_id` 可不传,由服务端自动生成 - - `document_title`、`document_type` 为必填;其余字段均允许为空 - - `document_image`、`document_video`、`document_file` 支持传入多个已存在的 `attachments_id` - - `document_type` 前端从单个字典来源中多选枚举值,最终按 `system_dict_id@dict_word_enum|...` 保存 - - `document_keywords`、`document_product_categories`、`document_application_scenarios`、`document_hotel_type` 统一从固定字典多选并按 `|` 保存 - - 其中 `document_product_categories` 改为从 `文档-产品关联文档` 读取,`document_application_scenarios` 改为从 `文档-筛选依据` 读取,`document_hotel_type` 改为从 `文档-适用场景` 读取 - - `document_status` 仅保留 `有效` / `过期` 两种状态,并由生效日期与到期日期自动计算 - - 成功后会写入一条文档操作历史,类型为 `create` -- `POST /pb/api/document/update` - - 按 `document_id` 更新文档 - - `document_title`、`document_type` 为必填;其余字段均允许为空 - - 若传入附件字段,则会校验多个 `attachments_id` 是否都存在 - - 多选字段的持久化格式与新增接口一致 - - 成功后会写入一条文档操作历史,类型为 `update` -- `POST /pb/api/document/delete` - - 按 `document_id` 真删除文档 - - 删除前会写入一条文档操作历史,类型为 `delete` - -说明: - -- `document_image`、`document_video`、`document_file` 当前保存的是多个 `attachments_id`,底层以 `|` 分隔文本持久化,不是 PocketBase 文件字段。 -- 文档查询时通过 `attachments_id -> tbl_attachments` 反查实际文件,并返回可直接访问的数据流链接数组。 -- `document_owner` 语义为“上传者 openid”。 - -### 文档操作历史接口 - -新增 `document-history` 分类接口,统一要求平台管理用户访问: - -- `POST /pb/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` 模糊搜索 - - 指定字典查询 - - 行内编辑基础字段 - - 弹窗编辑枚举项 - - 为每个枚举项单独上传图片,并保存对应 `attachments_id` - - 回显字典项图片缩略图与文件流链接 - - 新增 / 删除字典 - - 返回主页 -- 文档管理页支持: - - 先上传附件到 `tbl_attachments` - - 再把返回的多个 `attachments_id` 写入 `tbl_document.document_image` / `document_video` / `document_file` - - 图片、视频、文件都支持多选上传 - - 新增文档 - - 编辑已有文档并回显多图片、多视频 - - 从文档中移除附件并在保存后删除对应附件记录 - - 查询文档列表 - - 直接展示 PocketBase 文件流链接 - - 删除文档 - -说明: - -- 原页面 `page-b.js` 已替换为 `document-manage.js` -- 页面实际走的接口链路为: - - `/pb/api/attachment/upload` - - `/pb/api/document/create` - - `/pb/api/document/update` - - `/pb/api/attachment/delete` - - `/pb/api/document/list` - - `/pb/api/document/delete` - -### 2. 健康检查版本探针 - -`POST /pb/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 /pb/api/system/health`:确认 `data.version` -2. `POST /pb/api/platform/login`:确认返回统一结构与顶层 `token` -3. `POST /pb/api/dictionary/list`:确认鉴权与字典接口可用 - ---- - -## 十一、归档状态 - -- 已将本轮字典管理、PocketBase 页面、统一响应、平台登录兼容修复、健康检查版本探针、OpenAPI/Apifox 鉴权策略调整并入本变更记录。 -- 本记录可作为当前 PocketBase hooks 阶段性归档基线继续维护。 - - diff --git a/pocket-base/spec/openapi-manage.yaml b/pocket-base/spec/openapi-manage.yaml index fd4fbe6..7eb8f20 100644 --- a/pocket-base/spec/openapi-manage.yaml +++ b/pocket-base/spec/openapi-manage.yaml @@ -22,7 +22,7 @@ tags: - name: 平台认证 description: "面向平台用户的认证接口;平台用户会生成 GUID 并写入统一 `openid` 字段。" - name: 字典管理 - description: "面向 ManagePlatform 用户的系统字典维护接口。" + description: "字典读接口公开;字典写接口面向 ManagePlatform 用户。" - name: 附件管理 description: "面向 ManagePlatform 用户的附件上传、查询与删除接口。" - name: 文档管理 @@ -44,11 +44,14 @@ components: properties: code: type: integer + description: "业务状态码" example: 200 msg: type: string + description: "业务提示信息" example: 操作成功 data: + description: "业务响应数据" type: object additionalProperties: true HealthData: @@ -91,7 +94,97 @@ components: CompanyInfo: anyOf: - type: object - additionalProperties: true + description: 用户所属公司信息;当用户尚未绑定公司时返回 `null` + properties: + pb_id: + type: string + description: PocketBase 记录主键 id + company_id: + type: string + description: 公司业务 id,由数据库自动生成 + company_name: + type: string + description: 公司名称 + company_type: + type: string + description: 公司类型 + company_entity: + type: string + description: 公司法人 + company_usci: + type: string + description: 统一社会信用代码 + company_nationality: + type: string + description: 国家名称 + company_nationality_code: + type: string + description: 国家编码 + company_province: + type: string + description: 省份名称 + company_province_code: + type: string + description: 省份编码 + company_city: + type: string + description: 城市名称 + company_city_code: + type: string + description: 城市编码 + company_district: + type: string + description: 区/县名称 + company_district_code: + type: string + description: 区/县编码 + company_postalcode: + type: string + description: 邮政编码 + company_add: + type: string + description: 公司地址 + company_status: + type: string + description: 公司状态 + company_level: + type: string + description: 公司等级 + company_owner_openid: + type: string + description: 公司所有者 openid + company_remark: + type: string + description: 备注 + created: + type: string + description: 记录创建时间 + updated: + type: string + description: 记录更新时间 + example: + pb_id: PocketBase记录主键id | string + company_id: 公司业务id,由数据库自动生成 | string + company_name: 公司名称 | string + company_type: 公司类型 | string + company_entity: 公司法人 | string + company_usci: 统一社会信用代码 | string + company_nationality: 国家名称 | string + company_nationality_code: 国家编码 | string + company_province: 省份名称 | string + company_province_code: 省份编码 | string + company_city: 城市名称 | string + company_city_code: 城市编码 | string + company_district: 区县名称 | string + company_district_code: 区县编码 | string + company_postalcode: 邮政编码 | string + company_add: 公司地址 | string + company_status: 公司状态 | string + company_level: 公司等级 | string + company_owner_openid: 公司所有者openid | string + company_remark: 备注 | string + created: 记录创建时间 | string + updated: 记录更新时间 | string - type: 'null' UserInfo: type: object @@ -100,34 +193,52 @@ components: 其中 `openid` 为全平台统一身份标识:微信用户使用微信 openid,平台用户使用服务端生成 GUID。 properties: pb_id: + description: "PocketBase 记录主键 id" type: string users_convers_id: + description: "会话侧用户 ID" type: string users_id: + description: "用户业务 ID" type: string users_idtype: - type: string description: "用户身份来源类型" - enum: [WeChat, ManagePlatform] + anyOf: + - type: string + enum: [WeChat, ManagePlatform] + - type: string users_id_number: + description: "证件号" type: string users_status: + description: "用户状态" type: string users_rank_level: - type: number + description: "用户星级数值" + type: [number, string] users_auth_type: - type: number + description: "账户类型" + type: [number, string] users_type: - type: string - enum: [游客, 注册用户] + description: "用户类型" + anyOf: + - type: string + enum: [游客, 注册用户] + - type: string users_name: + description: "用户姓名 / 昵称" type: string users_phone: + description: "手机号" type: string users_phone_masked: type: string users_level: type: string + description: "用户等级" + users_tag: + type: string + description: "用户标签" users_picture: type: string description: "用户头像附件的 `attachments_id`" @@ -157,18 +268,77 @@ components: description: "全平台统一身份标识;微信用户为微信 openid,平台用户为服务端生成的 GUID" company_id: type: string + description: "公司业务 id,存储 `tbl_company.company_id`" users_parent_id: type: string + description: "上级用户业务 id" users_promo_code: type: string + description: "推广码" usergroups_id: type: string + description: "用户组业务 id" company: $ref: '#/components/schemas/CompanyInfo' created: type: string + description: "用户创建时间" updated: type: string + description: "用户更新时间" + example: + pb_id: PocketBase记录主键id | string + users_convers_id: 会话侧用户ID | string + users_id: 用户业务ID | string + users_idtype: 用户身份来源类型 | string + users_id_number: 证件号 | string + users_type: 用户类型 | string + users_name: 用户姓名或昵称 | string + users_status: 用户状态 | string + users_rank_level: 用户星级数值 | number + users_auth_type: 账户类型 | number + users_phone: 手机号 | string + users_phone_masked: 手机号脱敏值 | string + users_level: 用户等级 | string + users_tag: 用户标签 | string + users_picture: 用户头像附件ID | string + users_picture_url: 用户头像文件流链接 | string + users_id_pic_a: 证件正面附件ID | string + users_id_pic_a_url: 证件正面文件流链接 | string + users_id_pic_b: 证件反面附件ID | string + users_id_pic_b_url: 证件反面文件流链接 | string + users_title_picture: 资质附件ID | string + users_title_picture_url: 资质附件文件流链接 | string + openid: 全平台统一身份标识 | string + company_id: 公司业务id | string + users_parent_id: 上级用户业务id | string + users_promo_code: 推广码 | string + usergroups_id: 用户组业务id | string + company: + pb_id: PocketBase记录主键id | string + company_id: 公司业务id,由数据库自动生成 | string + company_name: 公司名称 | string + company_type: 公司类型 | string + company_entity: 公司法人 | string + company_usci: 统一社会信用代码 | string + company_nationality: 国家名称 | string + company_nationality_code: 国家编码 | string + company_province: 省份名称 | string + company_province_code: 省份编码 | string + company_city: 城市名称 | string + company_city_code: 城市编码 | string + company_district: 区县名称 | string + company_district_code: 区县编码 | string + company_postalcode: 邮政编码 | string + company_add: 公司地址 | string + company_status: 公司状态 | string + company_level: 公司等级 | string + company_owner_openid: 公司所有者openid | string + company_remark: 备注 | string + created: 记录创建时间 | string + updated: 记录更新时间 | string + created: 用户创建时间 | string + updated: 用户更新时间 | string PocketBaseAuthResponse: type: object description: | @@ -177,18 +347,23 @@ components: properties: code: type: integer + description: "业务状态码" example: 200 msg: type: string + description: "业务提示信息" example: 登录成功 data: + description: "业务响应数据" type: object properties: status: - type: string - enum: [register_success, login_success] + anyOf: + - type: string + enum: [register_success, login_success] + - type: string is_info_complete: - type: boolean + type: [boolean, string] user: $ref: '#/components/schemas/UserInfo' token: @@ -198,36 +373,58 @@ components: code: 200 msg: 登录成功 data: - status: login_success - is_info_complete: true + status: 登录或注册状态 | string + is_info_complete: 资料是否完整 | boolean user: - pb_id: vtukf6agem2xbcv - users_id: U202603260001 - users_idtype: ManagePlatform - users_name: momo - users_phone: '13509214696' - users_phone_masked: '135****4696' - users_status: '' - users_rank_level: 0 - users_auth_type: 0 - users_type: 注册用户 - users_picture: '' - users_picture_url: '' - users_id_pic_a: '' - users_id_pic_a_url: '' - users_id_pic_b: '' - users_id_pic_b_url: '' - users_title_picture: '' - users_title_picture_url: '' - openid: app_momo - company_id: '' - users_parent_id: '' - users_promo_code: '' - usergroups_id: '' - company: null - created: '' - updated: '' - token: eyJhbGciOi... + pb_id: PocketBase记录主键id | string + users_id: 用户业务ID | string + users_idtype: 用户身份来源类型 | string + users_name: 用户姓名或昵称 | string + users_phone: 手机号 | string + users_phone_masked: 手机号脱敏值 | string + users_status: 用户状态 | string + users_rank_level: 用户星级数值 | number + users_auth_type: 账户类型 | number + users_type: 用户类型 | string + users_picture: 用户头像附件ID | string + users_picture_url: 用户头像文件流链接 | string + users_id_pic_a: 证件正面附件ID | string + users_id_pic_a_url: 证件正面文件流链接 | string + users_id_pic_b: 证件反面附件ID | string + users_id_pic_b_url: 证件反面文件流链接 | string + users_title_picture: 资质附件ID | string + users_title_picture_url: 资质附件文件流链接 | string + openid: 全平台统一身份标识 | string + company_id: 公司业务id | string + users_parent_id: 上级用户业务id | string + users_promo_code: 推广码 | string + usergroups_id: 用户组业务id | string + company: + pb_id: PocketBase记录主键id | string + company_id: 公司业务id,由数据库自动生成 | string + company_name: 公司名称 | string + company_type: 公司类型 | string + company_entity: 公司法人 | string + company_usci: 统一社会信用代码 | string + company_nationality: 国家名称 | string + company_nationality_code: 国家编码 | string + company_province: 省份名称 | string + company_province_code: 省份编码 | string + company_city: 城市名称 | string + company_city_code: 城市编码 | string + company_district: 区县名称 | string + company_district_code: 区县编码 | string + company_postalcode: 邮政编码 | string + company_add: 公司地址 | string + company_status: 公司状态 | string + company_level: 公司等级 | string + company_owner_openid: 公司所有者openid | string + company_remark: 备注 | string + created: 记录创建时间 | string + updated: 记录更新时间 | string + created: 用户创建时间 | string + updated: 用户更新时间 | string + token: PocketBase原生认证token | string WechatLoginRequest: type: object required: [users_wx_code] @@ -239,18 +436,27 @@ components: example: 0a1b2c3d4e5f6g WechatProfileRequest: type: object - required: [users_name, users_phone_code, users_picture] description: "微信用户资料完善请求体。" properties: users_name: type: string + description: "用户姓名 / 昵称" example: 张三 users_phone_code: type: string + description: "可选。若传入,服务端优先通过微信接口换取真实手机号并写入数据库" example: 2b7d9f2e3c4a5b6d7e8f + users_phone: + type: string + description: "可选。未传 `users_phone_code` 时,可直接写入手机号" + example: '13800138000' + users_tag: + type: string + description: "可选。用户标签;非空时才更新" + example: 核心客户 users_picture: type: string - description: "用户头像附件的 `attachments_id`" + description: "可选。用户头像附件的 `attachments_id`" example: ATT-1743123456789-abc123 users_id_pic_a: type: string @@ -276,12 +482,15 @@ components: properties: users_name: type: string + description: "用户姓名 / 昵称" example: 张三 users_phone: type: string + description: "手机号" example: 13800138000 password: type: string + description: "PocketBase 认证密码" example: 12345678 passwordConfirm: type: string @@ -291,6 +500,7 @@ components: description: "用户头像附件的 `attachments_id`" example: ATT-1743123456789-abc123 users_id_number: + description: "证件号" type: string users_id_pic_a: type: string @@ -302,16 +512,25 @@ components: type: string description: "可选。资质附件的 `attachments_id`" users_level: + description: "用户等级文本" + type: string + users_tag: + description: "用户标签" type: string users_type: + description: "用户类型" type: string company_id: + description: "所属公司业务 ID" type: string users_parent_id: + description: "上级用户业务 ID" type: string users_promo_code: + description: "推广码" type: string usergroups_id: + description: "用户组 / 角色 ID" type: string PlatformLoginRequest: type: object @@ -324,6 +543,7 @@ components: example: admin@example.com password: type: string + description: "PocketBase 认证密码" example: 12345678 SystemRefreshTokenRequest: type: object @@ -364,31 +584,63 @@ components: - $ref: '#/components/schemas/AttachmentRecord' - type: 'null' sortOrder: - type: integer + type: [integer, string] example: 1 + example: + enum: 枚举值 | string + description: 枚举描述 | string + image: 图片附件ID | string + imageUrl: 图片文件流链接 | string + imageAttachment: + attachments_id: 附件业务ID | string + attachments_filename: 原始文件名 | string + sortOrder: 排序值 | integer DictionaryRecord: type: object properties: pb_id: + description: "PocketBase 记录主键 id" type: string system_dict_id: + description: "字典业务 ID" type: string dict_name: + description: "字典名称,当前按全局唯一维护" type: string dict_word_is_enabled: - type: boolean + description: "字典是否启用" + type: [boolean, string] dict_word_parent_id: + description: "父级字典业务 ID" type: string dict_word_remark: + description: "备注" type: string items: type: array items: $ref: '#/components/schemas/DictionaryItem' created: + description: "记录创建时间" type: string updated: + description: "记录更新时间" type: string + example: + pb_id: PocketBase记录主键id | string + system_dict_id: 字典业务ID | string + dict_name: 字典名称 | string + dict_word_is_enabled: 字典是否启用 | boolean + dict_word_parent_id: 父级字典业务ID | string + dict_word_remark: 备注 | string + items: + - enum: UT1 + description: 枚举描述 | string + image: 图片附件ID | string + imageUrl: 图片文件流链接 | string + sortOrder: 排序值 | integer + created: 记录创建时间 | string + updated: 记录更新时间 | string DictionaryListRequest: type: object properties: @@ -402,6 +654,7 @@ components: properties: dict_name: type: string + description: "字典名称,当前按全局唯一维护" example: 用户状态 DictionaryMutationRequest: type: object @@ -413,15 +666,19 @@ components: example: 用户状态 dict_name: type: string + description: "字典名称,当前按全局唯一维护" example: 用户状态 dict_word_is_enabled: type: boolean + description: "字典是否启用" example: true dict_word_parent_id: type: string + description: "父级字典业务 ID" example: '' dict_word_remark: type: string + description: "备注" example: 系统状态字典 items: type: array @@ -435,13 +692,16 @@ components: properties: dict_name: type: string + description: "字典名称,当前按全局唯一维护" example: 用户状态 AttachmentRecord: type: object properties: pb_id: + description: "PocketBase 记录主键 id" type: string attachments_id: + description: "附件业务 ID" type: string attachments_link: type: string @@ -453,25 +713,51 @@ components: type: string description: "附件下载链接" attachments_filename: + description: "原始文件名" type: string attachments_filetype: + description: "文件类型 / MIME" type: string attachments_size: - type: number + description: "文件大小" + type: [number, string] attachments_owner: + description: "上传者业务标识" type: string attachments_md5: + description: "文件 MD5" type: string attachments_ocr: + description: "OCR 识别结果" type: string attachments_status: + description: "附件状态" type: string attachments_remark: + description: "备注" type: string created: + description: "记录创建时间" type: string updated: + description: "记录更新时间" type: string + example: + pb_id: PocketBase记录主键id | string + attachments_id: 附件业务ID | string + attachments_link: PocketBase实际存储文件名 | string + attachments_url: 附件文件流访问链接 | string + attachments_download_url: 附件下载链接 | string + attachments_filename: 原始文件名 | string + attachments_filetype: 文件类型或MIME | string + attachments_size: 文件大小 | number + attachments_owner: 上传者业务标识 | string + attachments_md5: 文件MD5 | string + attachments_ocr: OCR识别结果 | string + attachments_status: 附件状态 | string + attachments_remark: 备注 | string + created: 记录创建时间 | string + updated: 记录更新时间 | string AttachmentListRequest: type: object properties: @@ -489,6 +775,7 @@ components: properties: attachments_id: type: string + description: "附件业务 ID" example: ATT-1743037200000-abc123 AttachmentUploadRequest: type: object @@ -508,35 +795,50 @@ components: type: number description: "文件大小" attachments_md5: + description: "文件 MD5" type: string attachments_ocr: + description: "OCR 识别结果" type: string attachments_status: type: string + description: "附件状态" example: active attachments_remark: + description: "备注" type: string DocumentRecord: type: object properties: pb_id: + description: "PocketBase 记录主键 id" type: string document_id: + description: "文档业务 ID" type: string + document_create: + type: string + description: "文档创建时间,由数据库自动生成" document_effect_date: + description: "文档生效日期" type: string document_expiry_date: + description: "文档到期日期" type: string document_type: type: string description: "多选时按 `system_dict_id@dict_word_enum|...` 保存" document_title: + description: "文档标题" type: string document_subtitle: + description: "文档副标题" type: string document_summary: + description: "文档摘要" type: string document_content: + description: "正文内容,保存 Markdown" type: string document_image: type: string @@ -614,26 +916,34 @@ components: type: string description: "上传者 openid" document_relation_model: + description: "关联机型 / 模型标识" type: string document_keywords: type: string description: "固定字典多选字段,使用 `|` 分隔" document_share_count: - type: number + description: "分享次数" + type: [number, string] document_download_count: - type: number + description: "下载次数" + type: [number, string] document_favorite_count: - type: number + description: "收藏次数" + type: [number, string] document_status: type: string description: "文档状态,仅允许 `有效` 或 `过期`,由系统根据生效日期和到期日期自动计算;当两者都为空时默认 `有效`" document_embedding_status: + description: "文档嵌入状态" type: string document_embedding_error: + description: "文档嵌入错误原因" type: string document_embedding_lasttime: + description: "最后一次嵌入更新时间" type: string document_vector_version: + description: "向量版本号 / 模型名称" type: string document_product_categories: type: string @@ -645,11 +955,60 @@ components: type: string description: "固定字典多选字段,使用 `|` 分隔" document_remark: + description: "备注" type: string created: + description: "记录创建时间" type: string updated: + description: "记录更新时间" type: string + example: + pb_id: PocketBase记录主键id | string + document_id: 文档业务ID | string + document_create: 文档创建时间,由数据库自动生成 | string + document_effect_date: 文档生效日期 | string + document_expiry_date: 文档到期日期 | string + document_type: 文档类型,按system_dict_id@dict_word_enum保存 | string + document_title: 文档标题 | string + document_subtitle: 文档副标题 | string + document_summary: 文档摘要 | string + document_content: 正文内容Markdown | string + document_image: 图片附件ID列表,使用竖线分隔 | string + document_image_ids: + - 图片附件ID | string + document_image_urls: + - 图片文件流链接 | string + document_image_url: 第一张图片文件流链接 | string + document_video: 视频附件ID列表,使用竖线分隔 | string + document_video_ids: + - 视频附件ID | string + document_video_urls: + - 视频文件流链接 | string + document_video_url: 第一个视频文件流链接 | string + document_file: 文件附件ID列表,使用竖线分隔 | string + document_file_ids: + - 文件附件ID | string + document_file_urls: + - 文件流链接 | string + document_file_url: 第一个文件流链接 | string + document_owner: 上传者openid | string + document_relation_model: 关联机型或模型标识 | string + document_keywords: 关键词多选,使用竖线分隔 | string + document_share_count: 分享次数 | number + document_download_count: 下载次数 | number + document_favorite_count: 收藏次数 | number + document_status: 文档状态 | string + document_embedding_status: 文档嵌入状态 | string + document_embedding_error: 文档嵌入错误原因 | string + document_embedding_lasttime: 最后一次嵌入更新时间 | string + document_vector_version: 向量版本号或模型名称 | string + document_product_categories: 产品关联文档多选,使用竖线分隔 | string + document_application_scenarios: 筛选依据多选,使用竖线分隔 | string + document_hotel_type: 适用场景多选,使用竖线分隔 | string + document_remark: 备注 | string + created: 记录创建时间 | string + updated: 记录更新时间 | string DocumentListRequest: type: object properties: @@ -670,6 +1029,7 @@ components: properties: document_id: type: string + description: "文档业务 ID" example: DOC-1743037200000-abc123 DocumentMutationRequest: type: object @@ -691,12 +1051,16 @@ components: type: string description: "必填;前端显示为字典项描述,存库时按 `system_dict_id@dict_word_enum|...` 保存" document_title: + description: "文档标题" type: string document_subtitle: + description: "文档副标题" type: string document_summary: + description: "文档摘要" type: string document_content: + description: "正文内容,保存 Markdown" type: string document_image: oneOf: @@ -723,26 +1087,34 @@ components: type: string description: "文件附件 id 列表;支持数组或 `|` 分隔字符串" document_relation_model: + description: "关联机型 / 模型标识" type: string document_keywords: type: string description: "从 `文档-关键词` 字典多选后使用 `|` 分隔保存" document_share_count: + description: "分享次数" type: number document_download_count: + description: "下载次数" type: number document_favorite_count: + description: "收藏次数" type: number document_status: type: string description: "文档状态,仅允许 `有效` 或 `过期`,由系统根据生效日期和到期日期自动计算;当两者都为空时默认 `有效`" document_embedding_status: + description: "文档嵌入状态" type: string document_embedding_error: + description: "文档嵌入错误原因" type: string document_embedding_lasttime: + description: "最后一次嵌入更新时间" type: string document_vector_version: + description: "向量版本号 / 模型名称" type: string document_product_categories: type: string @@ -754,6 +1126,7 @@ components: type: string description: "从 `文档-适用场景` 字典多选后使用 `|` 分隔保存" document_remark: + description: "备注" type: string DocumentDeleteRequest: type: object @@ -761,28 +1134,48 @@ components: properties: document_id: type: string + description: "文档业务 ID" example: DOC-1743037200000-abc123 DocumentHistoryRecord: type: object properties: pb_id: + description: "PocketBase 记录主键 id" type: string doh_id: + description: "文档操作历史业务 ID" type: string doh_document_id: + description: "关联文档业务 ID" type: string doh_operation_type: + description: "操作类型" type: string doh_user_id: + description: "操作人业务 ID" type: string doh_current_count: - type: number + description: "本次操作对应次数" + type: [number, string] doh_remark: + description: "备注" type: string created: + description: "记录创建时间" type: string updated: + description: "记录更新时间" type: string + example: + pb_id: PocketBase记录主键id | string + doh_id: 文档操作历史业务ID | string + doh_document_id: 关联文档业务ID | string + doh_operation_type: 操作类型 | string + doh_user_id: 操作人业务ID | string + doh_current_count: 本次操作对应次数 | number + doh_remark: 备注 | string + created: 记录创建时间 | string + updated: 记录更新时间 | string DocumentHistoryListRequest: type: object properties: @@ -806,6 +1199,7 @@ paths: - type: object properties: data: + description: "业务响应数据" $ref: '#/components/schemas/HelloWorldData' /pb/api/system/health: post: @@ -822,6 +1216,7 @@ paths: - type: object properties: data: + description: "业务响应数据" $ref: '#/components/schemas/HealthData' /pb/api/system/users-count: post: @@ -839,6 +1234,7 @@ paths: - type: object properties: data: + description: "业务响应数据" $ref: '#/components/schemas/UsersCountData' /pb/api/platform/register: post: @@ -936,8 +1332,9 @@ paths: post: tags: [字典管理] summary: 查询字典列表 + security: [] description: | - 仅允许 `ManagePlatform` 用户访问。 + 公开读接口,无需 token。 支持按 `dict_name` 模糊搜索,返回字典全量信息,并将三个聚合字段组装为 `items`。 requestBody: required: false @@ -956,24 +1353,22 @@ paths: - type: object properties: data: + description: "业务响应数据" type: object properties: items: type: array items: $ref: '#/components/schemas/DictionaryRecord' - '401': - description: "token 无效或已过期" - '403': - description: "非 ManagePlatform 用户无权访问" '415': description: "请求体必须为 application/json" /pb/api/dictionary/detail: post: tags: [字典管理] summary: 查询指定字典 + security: [] description: | - 仅允许 `ManagePlatform` 用户访问。 + 公开读接口,无需 token。 按唯一键 `dict_name` 查询单条字典,并返回组装后的 `items`。 requestBody: required: true @@ -992,13 +1387,10 @@ paths: - type: object properties: data: + description: "业务响应数据" $ref: '#/components/schemas/DictionaryRecord' '400': description: "参数错误" - '401': - description: "token 无效或已过期" - '403': - description: "非 ManagePlatform 用户无权访问" '404': description: "未找到对应字典" '415': @@ -1028,6 +1420,7 @@ paths: - type: object properties: data: + description: "业务响应数据" $ref: '#/components/schemas/DictionaryRecord' '400': description: "参数错误或 dict_name 已存在" @@ -1063,6 +1456,7 @@ paths: - type: object properties: data: + description: "业务响应数据" $ref: '#/components/schemas/DictionaryRecord' '400': description: "参数错误或 dict_name 冲突" @@ -1100,9 +1494,11 @@ paths: - type: object properties: data: + description: "业务响应数据" type: object properties: dict_name: + description: "字典名称,当前按全局唯一维护" type: string '400': description: "参数错误或删除失败" @@ -1140,6 +1536,7 @@ paths: - type: object properties: data: + description: "业务响应数据" type: object properties: items: @@ -1176,6 +1573,7 @@ paths: - type: object properties: data: + description: "业务响应数据" $ref: '#/components/schemas/AttachmentRecord' '400': description: "参数错误" @@ -1212,6 +1610,7 @@ paths: - type: object properties: data: + description: "业务响应数据" $ref: '#/components/schemas/AttachmentRecord' '400': description: "参数错误、缺少文件或附件保存失败" @@ -1243,9 +1642,11 @@ paths: - type: object properties: data: + description: "业务响应数据" type: object properties: attachments_id: + description: "附件业务 ID" type: string '400': description: "参数错误、附件已被文档引用或删除失败" @@ -1285,6 +1686,7 @@ paths: - type: object properties: data: + description: "业务响应数据" type: object properties: items: @@ -1321,6 +1723,7 @@ paths: - type: object properties: data: + description: "业务响应数据" $ref: '#/components/schemas/DocumentRecord' '400': description: "参数错误" @@ -1359,6 +1762,7 @@ paths: - type: object properties: data: + description: "业务响应数据" $ref: '#/components/schemas/DocumentRecord' '400': description: "参数错误、附件不存在或文档创建失败" @@ -1397,6 +1801,7 @@ paths: - type: object properties: data: + description: "业务响应数据" $ref: '#/components/schemas/DocumentRecord' '400': description: "参数错误、附件不存在或修改失败" @@ -1434,9 +1839,11 @@ paths: - type: object properties: data: + description: "业务响应数据" type: object properties: document_id: + description: "文档业务 ID" type: string '400': description: "参数错误或删除失败" @@ -1474,6 +1881,7 @@ paths: - type: object properties: data: + description: "业务响应数据" type: object properties: items: diff --git a/pocket-base/spec/openapi-miniapp-company.yaml b/pocket-base/spec/openapi-miniapp-company.yaml index abcffac..0f6e686 100644 --- a/pocket-base/spec/openapi-miniapp-company.yaml +++ b/pocket-base/spec/openapi-miniapp-company.yaml @@ -7,6 +7,8 @@ info: 本文档面向小程序端直接使用 PocketBase JS SDK / REST API 访问 `tbl_company`。 本文档统一以 PocketBase 原生记录主键 `id` 作为唯一识别键。 `company_id` 保留为普通业务字段,可用于展示、筛选和业务关联,但不再作为 CRUD 的唯一键。 + 当前线上 `tbl_company` 还包含 `company_owner_openid` 字段,用于保存公司所有者 openid,并带普通索引。 + 同时新增了国家、省、市、区的名称与编码字段,便于前端直接按行政区划存取。 license: name: Proprietary identifier: LicenseRef-Proprietary @@ -29,7 +31,8 @@ paths: 支持三种常见模式: 1. 全表查询:不传 `filter`; 2. 精确查询:`filter=id="q1w2e3r4t5y6u7i"`; - 3. 模糊查询:`filter=(company_name~"华住" || company_usci~"9131" || company_entity~"张三")`。 + 3. 模糊查询:`filter=(company_name~"华住" || company_usci~"9131" || company_entity~"张三")`; + 4. 按 `company_id` 查询单条:`filter=company_id="WX-COMPANY-10001"&perPage=1&page=1`。 parameters: - $ref: '#/components/parameters/Page' - $ref: '#/components/parameters/PerPage' @@ -64,12 +67,18 @@ paths: company_entity: 张三 company_usci: '91310000123456789A' company_nationality: 中国 + company_nationality_code: CN company_province: 上海 + company_province_code: '310000' company_city: 上海 + company_city_code: '310100' + company_district: 浦东新区 + company_district_code: '310115' company_postalcode: '200000' company_add: 上海市浦东新区XX路1号 company_status: 有效 company_level: A + company_owner_openid: wx-openid-owner-001 company_remark: '' exact: summary: 按 id 精确查询 @@ -90,12 +99,18 @@ paths: company_entity: 张三 company_usci: '91310000123456789A' company_nationality: 中国 + company_nationality_code: CN company_province: 上海 + company_province_code: '310000' company_city: 上海 + company_city_code: '310100' + company_district: 浦东新区 + company_district_code: '310115' company_postalcode: '200000' company_add: 上海市浦东新区XX路1号 company_status: 有效 company_level: A + company_owner_openid: wx-openid-owner-001 company_remark: '' '400': description: 过滤表达式或查询参数不合法 @@ -115,7 +130,8 @@ paths: summary: 新增公司 description: >- 创建一条 `tbl_company` 记录。当前文档以 `id` 作为记录唯一识别键, - 新建成功后由 PocketBase 自动生成 `id`;根据当前项目建表脚本,`company_id` 仍是必填业务字段,但不再作为 CRUD 唯一键。 + 新建成功后由 PocketBase 自动生成 `id`;`company_id` 也由数据库自动生成, + 客户端创建时不需要传入,但仍可作为后续业务查询字段。 requestBody: required: true content: @@ -125,18 +141,23 @@ paths: examples: default: value: - company_id: C10001 company_name: 宝镜科技 company_type: 渠道商 company_entity: 张三 company_usci: '91310000123456789A' company_nationality: 中国 + company_nationality_code: CN company_province: 上海 + company_province_code: '310000' company_city: 上海 + company_city_code: '310100' + company_district: 浦东新区 + company_district_code: '310115' company_postalcode: '200000' company_add: 上海市浦东新区XX路1号 company_status: 有效 company_level: A + company_owner_openid: wx-openid-owner-001 company_remark: 首次创建 responses: '200': @@ -198,6 +219,8 @@ paths: summary: 按 PocketBase 记录 id 更新公司 description: >- 这是 PocketBase 原生更新接口,路径参数统一使用记录主键 `id`。 + 如果业务侧只有 `company_id`,标准流程是先调用 list 接口 + `filter=company_id="..."&perPage=1&page=1` 查出对应记录,再用返回的 `id` 调用本接口。 parameters: - $ref: '#/components/parameters/RecordId' - $ref: '#/components/parameters/Fields' @@ -213,6 +236,7 @@ paths: company_name: 宝镜科技(更新) company_status: 有效 company_level: S + company_owner_openid: wx-openid-owner-002 company_remark: 已更新基础资料 responses: '200': @@ -349,12 +373,27 @@ components: company_nationality: type: string description: 国家 + company_nationality_code: + type: string + description: 国家编码 company_province: type: string description: 省份 + company_province_code: + type: string + description: 省份编码 company_city: type: string description: 城市 + company_city_code: + type: string + description: 城市编码 + company_district: + type: string + description: 区/县 + company_district_code: + type: string + description: 区/县编码 company_postalcode: type: string description: 邮编 @@ -367,44 +406,132 @@ components: company_level: type: string description: 公司等级 + company_owner_openid: + type: string + description: 公司所有者 openid company_remark: type: string description: 备注 CompanyCreateRequest: - allOf: - - $ref: '#/components/schemas/CompanyBase' - - type: object - required: [company_id] + type: object + description: 创建时不需要传 `company_id`,由数据库自动生成。 + properties: + company_name: + description: "公司名称" + type: string + company_type: + description: "公司类型" + type: string + company_entity: + description: "公司法人" + type: string + company_usci: + description: "统一社会信用代码" + type: string + company_nationality: + description: "国家名称" + type: string + company_nationality_code: + description: "国家编码" + type: string + company_province: + description: "省份名称" + type: string + company_province_code: + description: "省份编码" + type: string + company_city: + description: "城市名称" + type: string + company_city_code: + description: "城市编码" + type: string + company_district: + description: "区 / 县名称" + type: string + company_district_code: + description: "区 / 县编码" + type: string + company_postalcode: + description: "邮编" + type: string + company_add: + description: "地址" + type: string + company_status: + description: "公司状态" + type: string + company_level: + description: "公司等级" + type: string + company_owner_openid: + description: "公司所有者 openid" + type: string + company_remark: + description: "备注" + type: string + additionalProperties: false CompanyUpdateRequest: type: object description: >- 更新时可只传需要修改的字段;记录定位统一依赖路径参数 `id`。 properties: company_id: + description: "所属公司业务 ID" type: string company_name: + description: "公司名称" type: string company_type: + description: "公司类型" type: string company_entity: + description: "公司法人" type: string company_usci: + description: "统一社会信用代码" type: string company_nationality: + description: "国家名称" + type: string + company_nationality_code: + description: "国家编码" type: string company_province: + description: "省份名称" + type: string + company_province_code: + description: "省份编码" type: string company_city: + description: "城市名称" + type: string + company_city_code: + description: "城市编码" + type: string + company_district: + description: "区 / 县名称" + type: string + company_district_code: + description: "区 / 县编码" type: string company_postalcode: + description: "邮编" type: string company_add: + description: "地址" type: string company_status: + description: "公司状态" type: string company_level: + description: "公司等级" + type: string + company_owner_openid: + description: "公司所有者 openid" type: string company_remark: + description: "备注" type: string CompanyRecord: allOf: @@ -418,8 +545,10 @@ components: collectionName: type: string created: + description: "记录创建时间" type: string updated: + description: "记录更新时间" type: string - $ref: '#/components/schemas/CompanyBase' CompanyListResponse: @@ -445,5 +574,6 @@ components: message: type: string data: + description: "业务响应数据" type: object additionalProperties: true diff --git a/pocket-base/spec/openapi-wx.yaml b/pocket-base/spec/openapi-wx.yaml index ffcf20d..6a539cb 100644 --- a/pocket-base/spec/openapi-wx.yaml +++ b/pocket-base/spec/openapi-wx.yaml @@ -18,6 +18,12 @@ tags: description: 微信端共用系统接口 - name: 微信认证 description: 微信登录、资料完善与 token 刷新接口 + - name: 企业信息 + description: 通过 PocketBase 原生 records API 访问 `tbl_company` + - name: 附件信息 + description: 通过 PocketBase 原生 records API 访问 `tbl_attachments` + - name: 文档信息 + description: 通过 PocketBase 原生 records API 访问 `tbl_document` paths: /pb/api/system/users-count: post: @@ -169,7 +175,14 @@ paths: - 微信认证 summary: 更新微信用户资料 description: | - 基于当前 `Authorization` 对应的 auth 用户更新昵称、手机号和头像。 + 基于当前 `Authorization` 对应的 auth 用户按“非空字段增量更新”资料。 + + 更新规则: + - 所有字段都不是必填 + - 如果传了 `users_phone_code`,服务端优先调用微信接口换取真实手机号并写入 `users_phone` + - 如果没传 `users_phone_code`,但传了 `users_phone`,则直接将该手机号写入数据库 + - 如果某个字段未传或传空,则不会清空数据库中的已有值 + - 只有请求体里非空的字段才会更新到数据库 requestBody: required: true content: @@ -184,7 +197,7 @@ paths: schema: $ref: '#/components/schemas/WechatProfileResponse' '400': - description: 参数错误、手机号已被占用或资料更新失败 + description: 参数错误、手机号已被占用、微信手机号换取失败或资料更新失败 content: application/json: schema: @@ -213,6 +226,556 @@ paths: application/json: schema: $ref: '#/components/schemas/ErrorResponse' + /pb/api/collections/tbl_company/records: + post: + operationId: postPocketBaseCompanyRecord + security: [] + tags: + - 企业信息 + summary: 创建公司 + description: | + 使用 PocketBase 原生 records create 接口向 `tbl_company` 新增一行记录。 + + 当前线上权限规则: + - `createRule = ""`,因此任何客户端都可直接创建 + - 其他原生操作中,`update/delete/view` 仅管理员或管理后台用户允许 + + 注意: + - 这是 PocketBase 原生返回结构,不是 hooks 统一 `{ code, msg, data }` 包装 + - `company_id` 由数据库自动生成,客户端创建时不需要传 + - `company_id` 仍带唯一索引,可用于后续按业务 id 查询 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/PocketBaseCompanyCreateRequest' + examples: + default: + value: + company_name: 微信侧测试企业 + company_type: 渠道商 + company_entity: 张三 + company_usci: '91310000123456789A' + company_nationality: 中国 + company_nationality_code: CN + company_province: 上海 + company_province_code: '310000' + company_city: 上海 + company_city_code: '310100' + company_district: 浦东新区 + company_district_code: '310115' + company_postalcode: '200000' + company_add: 上海市浦东新区XX路1号 + company_status: 有效 + company_level: A + company_owner_openid: wx-openid-owner-001 + company_remark: 小程序公开创建示例 + responses: + '200': + description: 创建成功 + content: + application/json: + schema: + $ref: '#/components/schemas/PocketBaseCompanyRecord' + '400': + description: 参数错误或违反当前集合约束 + content: + application/json: + schema: + $ref: '#/components/schemas/PocketBaseNativeError' + '403': + description: 集合规则被锁定或服务端权限设置异常 + content: + application/json: + schema: + $ref: '#/components/schemas/PocketBaseNativeError' + '500': + description: 服务端错误 + content: + application/json: + schema: + $ref: '#/components/schemas/PocketBaseNativeError' + get: + operationId: getPocketBaseCompanyRecords + security: [] + tags: + - 企业信息 + summary: 查询整个 tbl_company 列表 / 根据 company_id 查询对应公司信息 + description: | + 使用 PocketBase 原生 records list 接口查询 `tbl_company`。 + + 当前线上权限规则: + - `listRule = ""`,因此当前整个列表查询与条件查询都公开可读 + - `createRule = ""`,因此创建也公开可调用 + - `view/update/delete` 仅管理员或管理后台用户允许 + + 标准调用方式有两种: + 1. 根据 `company_id` 查询对应公司信息: + - `filter=company_id="WX-COMPANY-10001"` + - `perPage=1` + - `page=1` + 2. 查询整个 `tbl_company` 列表: + - 不传 `filter` + - 按需传 `page`、`perPage` + + 注意: + - 这是 PocketBase 原生返回结构,不是 hooks 统一 `{ code, msg, data }` 包装 + - PocketBase 原生标准接口里,“按 `company_id` 查询单条”和“查询整个列表”共用同一个 `GET /records` 路径,因此文档以同一个 GET operation 展示两种调用模式 + parameters: + - name: filter + in: query + required: false + description: | + PocketBase 标准过滤表达式。 + + - 根据 `company_id` 查询单条时:`company_id="WX-COMPANY-10001"` + - 查询整个列表时:不传该参数 + schema: + type: string + example: company_id="WX-COMPANY-10001" + - name: page + in: query + required: false + description: 页码 + schema: + type: integer + minimum: 1 + default: 1 + - name: perPage + in: query + required: false + description: 每页条数;按 `company_id` 单查时建议固定为 `1` + schema: + type: integer + minimum: 1 + default: 20 + responses: + '200': + description: 查询成功 + content: + application/json: + schema: + $ref: '#/components/schemas/PocketBaseCompanyListResponse' + examples: + byCompanyId: + value: + page: 页码 | integer + perPage: 每页条数 | integer + totalItems: 总记录数 | integer + totalPages: 总页数 | integer + items: + - id: PocketBase记录主键 | string + collectionId: 集合ID | string + collectionName: 集合名称 | string + created: 记录创建时间 | string + updated: 记录更新时间 | string + company_id: 公司业务id,由数据库自动生成 | string + company_name: 公司名称 | string + company_type: 公司类型 | string + company_entity: 公司法人 | string + company_usci: 统一社会信用代码 | string + company_nationality: 国家名称 | string + company_nationality_code: 国家编码 | string + company_province: 省份名称 | string + company_province_code: 省份编码 | string + company_city: 城市名称 | string + company_city_code: 城市编码 | string + company_district: 区县名称 | string + company_district_code: 区县编码 | string + company_postalcode: 邮政编码 | string + company_add: 公司地址 | string + company_status: 公司状态 | string + company_level: 公司等级 | string + company_owner_openid: 公司所有者openid | string + company_remark: 备注 | string + listAll: + value: + page: 页码 | integer + perPage: 每页条数 | integer + totalItems: 总记录数 | integer + totalPages: 总页数 | integer + items: + - id: PocketBase记录主键 | string + collectionId: 集合ID | string + collectionName: 集合名称 | string + created: 记录创建时间 | string + updated: 记录更新时间 | string + company_id: 公司业务id,由数据库自动生成 | string + company_name: 公司名称 | string + company_type: 公司类型 | string + company_entity: 公司法人 | string + company_usci: 统一社会信用代码 | string + company_nationality: 国家名称 | string + company_nationality_code: 国家编码 | string + company_province: 省份名称 | string + company_province_code: 省份编码 | string + company_city: 城市名称 | string + company_city_code: 城市编码 | string + company_district: 区县名称 | string + company_district_code: 区县编码 | string + company_postalcode: 邮政编码 | string + company_add: 公司地址 | string + company_status: 公司状态 | string + company_level: 公司等级 | string + company_owner_openid: 公司所有者openid | string + company_remark: 备注 | string + notFoundByCompanyId: + value: + page: 页码 | integer + perPage: 每页条数 | integer + totalItems: 总记录数 | integer + totalPages: 总页数 | integer + items: [] + '400': + description: 查询参数错误 + content: + application/json: + schema: + $ref: '#/components/schemas/PocketBaseNativeError' + '403': + description: 集合规则被锁定或服务端权限设置异常 + content: + application/json: + schema: + $ref: '#/components/schemas/PocketBaseNativeError' + '500': + description: 服务端错误 + content: + application/json: + schema: + $ref: '#/components/schemas/PocketBaseNativeError' + /pb/api/collections/tbl_company/records/{recordId}: + patch: + operationId: patchPocketBaseCompanyRecordByRecordId + security: + - bearerAuth: [] + tags: + - 企业信息 + summary: 通过 company_id 定位后修改公司信息 + description: | + 这是 PocketBase 原生标准更新接口,实际写入路径参数仍然必须使用记录主键 `recordId`。 + + 如果前端手里只有 `company_id`,标准调用流程是: + 1. 先调用 `GET /pb/api/collections/tbl_company/records?filter=company_id="..."&perPage=1&page=1` + 2. 从返回结果 `items[0].id` 中取出 PocketBase 原生记录主键 + 3. 再调用当前 `PATCH /pb/api/collections/tbl_company/records/{recordId}` 完成更新 + + 当前线上权限规则: + - `updateRule` 仅管理员或管理后台用户允许 + - 普通公开调用不能直接更新 + parameters: + - name: recordId + in: path + required: true + description: 通过 `company_id` 查询结果拿到的 PocketBase 记录主键 `id` + schema: + type: string + example: l2r3nq7rqhuob0h + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/PocketBaseCompanyUpdateRequest' + examples: + default: + value: + company_name: 微信侧测试企业(更新) + company_status: 有效 + company_level: S + company_owner_openid: wx-openid-owner-001 + company_district: 徐汇区 + company_district_code: '310104' + company_remark: 通过 company_id 先定位再修改 + responses: + '200': + description: 更新成功 + content: + application/json: + schema: + $ref: '#/components/schemas/PocketBaseCompanyRecord' + '400': + description: 参数错误或违反集合约束 + content: + application/json: + schema: + $ref: '#/components/schemas/PocketBaseNativeError' + '403': + description: 当前调用方没有 update 权限 + content: + application/json: + schema: + $ref: '#/components/schemas/PocketBaseNativeError' + '404': + description: 记录不存在 + content: + application/json: + schema: + $ref: '#/components/schemas/PocketBaseNativeError' + '500': + description: 服务端错误 + content: + application/json: + schema: + $ref: '#/components/schemas/PocketBaseNativeError' + /pb/api/collections/tbl_attachments/records: + get: + operationId: getPocketBaseAttachmentRecords + security: [] + tags: + - 附件信息 + summary: 根据 attachments_id 查询附件信息 + description: | + 使用 PocketBase 原生 records list 接口查询 `tbl_attachments`。 + + 当前线上权限规则: + - `listRule = ""`,因此任何客户端都可直接读取 + - 原生 `create/update/delete` 仍仅管理员或管理后台用户允许 + + 标准调用方式: + - `filter=attachments_id="ATT-1774599142438-8n1UcU"` + - `perPage=1` + - `page=1` + + 注意: + - 这是 PocketBase 原生返回结构,不是 hooks 统一 `{ code, msg, data }` 包装 + - `attachments_link` 返回的是 PocketBase 文件字段值,不是完整下载地址 + - 若需文件流地址,可按 PocketBase 标准文件路径自行拼接:`/pb/api/files/{collectionId}/{recordId}/{attachments_link}` + parameters: + - name: filter + in: query + required: true + description: | + PocketBase 标准过滤表达式。 + + 按 `attachments_id` 精确查询时固定写法为: + - `attachments_id="ATT-1774599142438-8n1UcU"` + schema: + type: string + example: attachments_id="ATT-1774599142438-8n1UcU" + - name: page + in: query + required: false + description: 页码 + schema: + type: integer + minimum: 1 + default: 1 + - name: perPage + in: query + required: false + description: 每页条数;按 `attachments_id` 单查时建议固定为 `1` + schema: + type: integer + minimum: 1 + default: 1 + responses: + '200': + description: 查询成功 + content: + application/json: + schema: + $ref: '#/components/schemas/PocketBaseAttachmentListResponse' + examples: + byAttachmentsId: + value: + page: 页码 | integer + perPage: 每页条数 | integer + totalItems: 总记录数 | integer + totalPages: 总页数 | integer + items: + - id: PocketBase记录主键 | string + collectionId: 集合ID | string + collectionName: 集合名称 | string + attachments_id: 附件业务ID | string + attachments_link: PocketBase文件字段值,可拼接文件流地址 | string + attachments_filename: 原始文件名 | string + attachments_filetype: 文件类型或MIME | string + attachments_size: 文件大小 | number + attachments_owner: 上传者业务标识 | string + attachments_md5: 文件MD5 | string + attachments_ocr: OCR识别结果 | string + attachments_status: 附件状态 | string + attachments_remark: 备注 | string + '400': + description: 查询参数错误 + content: + application/json: + schema: + $ref: '#/components/schemas/PocketBaseNativeError' + '403': + description: 集合规则被锁定或服务端权限设置异常 + content: + application/json: + schema: + $ref: '#/components/schemas/PocketBaseNativeError' + '500': + description: 服务端错误 + content: + application/json: + schema: + $ref: '#/components/schemas/PocketBaseNativeError' + /pb/api/collections/tbl_document/records: + get: + operationId: getPocketBaseDocumentRecords + security: [] + tags: + - 文档信息 + summary: 分页查询文档列表 / 按 system_dict_id 与 enum 双条件分页筛选文档 + description: | + 使用 PocketBase 原生 records list 接口查询 `tbl_document`。 + + 当前线上权限规则: + - `listRule = ""`,因此任何客户端都可直接分页查询 + - `viewRule = ""`,因此单条详情也可公开读取 + - `create/update/delete` 仍仅管理员或管理后台用户允许 + + `document_type` 的存储格式为: + - `system_dict_id@dict_word_enum|system_dict_id@dict_word_enum` + + 业务上这里是两个独立条件,并且查询时两个条件都要满足: + - 条件 1:包含某个 `system_dict_id` + - 条件 2:包含某个 `enum` + + PocketBase 原生标准接口实际只有一个 `filter` 参数,因此应在同一个 `filter` 中写成两个 `contains` 条件,例如: + - `system_dict_id = DICT-1774599144591-hAEFQj` + - `enum = UT1` + - 最终:`document_type ~ "DICT-1774599144591-hAEFQj" && document_type ~ "@UT1"` + + 这条写法已经按线上真实数据验证通过。 + + 排序说明: + - 当前线上统一按 `document_create` 排序 + - 若要“最新上传的排在最前面”,请传 `sort=-document_create` + + 标准调用方式有两种: + 1. 查询整个文档列表: + - 不传 `filter` + - 按需传 `page`、`perPage` + - 若要按最新上传倒序,传 `sort=-document_create` + 2. 根据 `system_dict_id` 和 `enum` 两个业务条件分页筛选: + - 直接传 `filter=document_type ~ "" && document_type ~ "@"` + - 传 `page`、`perPage` + - 若要按最新上传倒序,传 `sort=-document_create` + + 注意: + - 这是 PocketBase 原生返回结构,不是 hooks 统一 `{ code, msg, data }` 包装 + - 如果需要更复杂的条件组合,可继续使用 PocketBase 原生 `filter` 语法自行扩展 + parameters: + - name: filter + in: query + required: false + description: | + PocketBase 标准过滤表达式。 + + - 查全部列表时:不传 + - 按业务条件筛选时,同时写两个 `contains` 条件 + - 第二个条件建议带上 `@` 前缀,避免误命中 + - 例如:`document_type ~ "DICT-1774599144591-hAEFQj" && document_type ~ "@UT1"` + schema: + type: string + example: document_type ~ "DICT-1774599144591-hAEFQj" && document_type ~ "@UT1" + - name: page + in: query + required: false + description: 页码 + schema: + type: integer + minimum: 1 + default: 1 + - name: perPage + in: query + required: false + description: 每页条数 + schema: + type: integer + minimum: 1 + default: 20 + - name: sort + in: query + required: false + description: | + PocketBase 原生排序表达式。 + + 当前线上建议使用: + - `-document_create`:按最新上传倒序返回 + schema: + type: string + example: -document_create + responses: + '200': + description: 查询成功 + content: + application/json: + schema: + $ref: '#/components/schemas/PocketBaseDocumentListResponse' + examples: + listAll: + value: + page: 页码 | integer + perPage: 每页条数 | integer + totalItems: 总记录数 | integer + totalPages: 总页数 | integer + items: + - id: PocketBase记录主键 | string + collectionId: 集合ID | string + collectionName: 集合名称 | string + created: 记录创建时间 | string + updated: 记录更新时间 | string + document_id: 文档业务ID | string + document_create: 文档创建时间,由数据库自动生成 | string + document_title: 文档标题 | string + document_type: 文档类型,按system_dict_id@dict_word_enum保存 | string + document_status: 文档状态 | string + document_owner: 上传者openid | string + filterByTypeToken: + value: + page: 页码 | integer + perPage: 每页条数 | integer + totalItems: 总记录数 | integer + totalPages: 总页数 | integer + items: + - id: PocketBase记录主键 | string + collectionId: 集合ID | string + collectionName: 集合名称 | string + created: 记录创建时间 | string + updated: 记录更新时间 | string + document_id: 文档业务ID | string + document_create: 文档创建时间,由数据库自动生成 | string + document_title: 文档标题 | string + document_type: 文档类型,按system_dict_id@dict_word_enum保存 | string + document_status: 文档状态 | string + document_owner: 上传者openid | string + - id: ofy47wp9mmm0aub + collectionId: pbc_3636602973 + collectionName: tbl_document + created: '2026-03-28 07:20:00.000Z' + updated: '2026-03-28 07:20:00.000Z' + document_id: DOC-1774680568340-TeUSQn + document_create: '2026-03-28 08:22:48.000Z' + document_title: 易从碳达人节能系统,为酒店每天每间房省二元,以智能推动酒店ESG双碳落地!上海酒店用品展我们在E7A01等您!! + document_type: DICT-1774599144591-hAEFQj@UT1 + document_status: 有效 + document_owner: su13106859882 + '400': + description: 查询参数错误 + content: + application/json: + schema: + $ref: '#/components/schemas/PocketBaseNativeError' + '403': + description: 集合规则被锁定或服务端权限设置异常 + content: + application/json: + schema: + $ref: '#/components/schemas/PocketBaseNativeError' + '500': + description: 服务端错误 + content: + application/json: + schema: + $ref: '#/components/schemas/PocketBaseNativeError' components: securitySchemes: bearerAuth: @@ -228,14 +791,24 @@ components: - data properties: code: - type: integer - example: 200 + type: + - integer + - string + description: "业务状态码" + example: 业务状态码 | integer msg: type: string - example: 操作成功 + description: "业务提示信息" + example: 业务提示信息 | string data: + description: "业务响应数据" type: object additionalProperties: true + example: + code: 业务状态码 | integer + msg: 业务提示信息 | string + data: + 任意业务字段: 业务响应数据 | object ErrorResponse: type: object required: @@ -244,57 +817,763 @@ components: - data properties: code: - type: integer - example: 400 + type: + - integer + - string + description: "业务状态码" + example: 业务状态码 | integer msg: type: string - example: 请求失败 + description: "业务提示信息" + example: 失败原因提示 | string data: + description: "业务响应数据" type: object additionalProperties: true + example: + code: 业务状态码 | integer + msg: 失败原因提示 | string + data: + 任意错误字段: 错误附加信息 | object CompanyInfo: anyOf: - type: object - additionalProperties: true + description: 用户所属公司信息;当用户尚未绑定公司时返回 `null` + properties: + pb_id: + type: string + description: PocketBase 记录主键 id + example: PocketBase记录主键id | string + company_id: + type: string + description: 公司业务 id,由数据库自动生成 + example: 公司业务id,由数据库自动生成 | string + company_name: + type: string + description: 公司名称 + example: 公司名称 | string + company_type: + type: string + description: 公司类型 + example: 公司类型 | string + company_entity: + type: string + description: 公司法人 + example: 公司法人 | string + company_usci: + type: string + description: 统一社会信用代码 + example: 统一社会信用代码 | string + company_nationality: + type: string + description: 国家名称 + example: 国家名称 | string + company_nationality_code: + type: string + description: 国家编码 + example: 国家编码 | string + company_province: + type: string + description: 省份名称 + example: 省份名称 | string + company_province_code: + type: string + description: 省份编码 + example: 省份编码 | string + company_city: + type: string + description: 城市名称 + example: 城市名称 | string + company_city_code: + type: string + description: 城市编码 + example: 城市编码 | string + company_district: + type: string + description: 区/县名称 + example: 区县名称 | string + company_district_code: + type: string + description: 区/县编码 + example: 区县编码 | string + company_postalcode: + type: string + description: 邮政编码 + example: 邮政编码 | string + company_add: + type: string + description: 公司地址 + example: 公司地址 | string + company_status: + type: string + description: 公司状态 + example: 公司状态 | string + company_level: + type: string + description: 公司等级 + example: 公司等级 | string + company_owner_openid: + type: string + description: 公司所有者 openid + example: 公司所有者openid | string + company_remark: + type: string + description: 备注 + example: 备注 | string + created: + type: string + description: 记录创建时间 + example: 记录创建时间 | string + updated: + type: string + description: 记录更新时间 + example: 记录更新时间 | string + example: + pb_id: PocketBase记录主键id | string + company_id: 公司业务id,由数据库自动生成 | string + company_name: 公司名称 | string + company_type: 公司类型 | string + company_entity: 公司法人 | string + company_usci: 统一社会信用代码 | string + company_nationality: 国家名称 | string + company_nationality_code: 国家编码 | string + company_province: 省份名称 | string + company_province_code: 省份编码 | string + company_city: 城市名称 | string + company_city_code: 城市编码 | string + company_district: 区县名称 | string + company_district_code: 区县编码 | string + company_postalcode: 邮政编码 | string + company_add: 公司地址 | string + company_status: 公司状态 | string + company_level: 公司等级 | string + company_owner_openid: 公司所有者openid | string + company_remark: 备注 | string + created: 记录创建时间 | string + updated: 记录更新时间 | string - type: 'null' + PocketBaseRecordBase: + type: object + required: + - id + - collectionId + - collectionName + - created + - updated + properties: + id: + type: string + description: "PocketBase 记录主键" + example: PocketBase记录主键 | string + collectionId: + type: string + example: 集合ID | string + collectionName: + type: string + example: 集合名称 | string + created: + type: string + description: "记录创建时间" + example: 记录创建时间 | string + updated: + type: string + description: "记录更新时间" + example: 记录更新时间 | string + example: + id: PocketBase记录主键 | string + collectionId: 集合ID | string + collectionName: 集合名称 | string + created: 记录创建时间 | string + updated: 记录更新时间 | string + PocketBaseCompanyFields: + type: object + properties: + company_id: + type: string + description: 公司业务 id,由数据库自动生成 + example: 公司业务id,由数据库自动生成 | string + company_name: + type: string + description: 公司名称 + example: 公司名称 | string + company_type: + type: string + description: 公司类型 + example: 公司类型 | string + company_entity: + type: string + description: 公司法人 + example: 公司法人 | string + company_usci: + type: string + description: 统一社会信用代码 + example: 统一社会信用代码 | string + company_nationality: + type: string + description: 国家名称 + example: 国家名称 | string + company_nationality_code: + type: string + description: 国家编码 + example: 国家编码 | string + company_province: + type: string + description: 省份名称 + example: 省份名称 | string + company_province_code: + type: string + description: 省份编码 + example: 省份编码 | string + company_city: + type: string + description: 城市名称 + example: 城市名称 | string + company_city_code: + type: string + description: 城市编码 + example: 城市编码 | string + company_district: + type: string + description: 区/县名称 + example: 区县名称 | string + company_district_code: + type: string + description: 区/县编码 + example: 区县编码 | string + company_postalcode: + type: string + description: 邮政编码 + example: 邮政编码 | string + company_add: + type: string + description: 公司地址 + example: 公司地址 | string + company_status: + type: string + description: 公司状态 + example: 公司状态 | string + company_level: + type: string + description: 公司等级 + example: 公司等级 | string + company_owner_openid: + type: string + description: 公司所有者 openid + example: 公司所有者openid | string + company_remark: + type: string + description: 备注 + example: 备注 | string + PocketBaseCompanyRecord: + allOf: + - $ref: '#/components/schemas/PocketBaseRecordBase' + - $ref: '#/components/schemas/PocketBaseCompanyFields' + example: + id: PocketBase记录主键 | string + collectionId: 集合ID | string + collectionName: 集合名称 | string + created: 记录创建时间 | string + updated: 记录更新时间 | string + company_id: 公司业务id,由数据库自动生成 | string + company_name: 公司名称 | string + company_type: 公司类型 | string + company_entity: 公司法人 | string + company_usci: 统一社会信用代码 | string + company_nationality: 国家名称 | string + company_nationality_code: 国家编码 | string + company_province: 省份名称 | string + company_province_code: 省份编码 | string + company_city: 城市名称 | string + company_city_code: 城市编码 | string + company_district: 区县名称 | string + company_district_code: 区县编码 | string + company_postalcode: 邮政编码 | string + company_add: 公司地址 | string + company_status: 公司状态 | string + company_level: 公司等级 | string + company_owner_openid: 公司所有者openid | string + company_remark: 备注 | string + PocketBaseCompanyCreateRequest: + type: object + properties: + company_name: + description: "公司名称" + type: string + company_type: + description: "公司类型" + type: string + company_entity: + description: "公司法人" + type: string + company_usci: + description: "统一社会信用代码" + type: string + company_nationality: + description: "国家名称" + type: string + company_nationality_code: + description: "国家编码" + type: string + company_province: + description: "省份名称" + type: string + company_province_code: + description: "省份编码" + type: string + company_city: + description: "城市名称" + type: string + company_city_code: + description: "城市编码" + type: string + company_district: + description: "区 / 县名称" + type: string + company_district_code: + description: "区 / 县编码" + type: string + company_postalcode: + description: "邮编" + type: string + company_add: + description: "地址" + type: string + company_status: + description: "公司状态" + type: string + company_level: + description: "公司等级" + type: string + company_owner_openid: + description: "公司所有者 openid" + type: string + company_remark: + description: "备注" + type: string + additionalProperties: false + PocketBaseCompanyUpdateRequest: + type: object + properties: + company_id: + description: "所属公司业务 ID" + type: string + company_name: + description: "公司名称" + type: string + company_type: + description: "公司类型" + type: string + company_entity: + description: "公司法人" + type: string + company_usci: + description: "统一社会信用代码" + type: string + company_nationality: + description: "国家名称" + type: string + company_nationality_code: + description: "国家编码" + type: string + company_province: + description: "省份名称" + type: string + company_province_code: + description: "省份编码" + type: string + company_city: + description: "城市名称" + type: string + company_city_code: + description: "城市编码" + type: string + company_district: + description: "区 / 县名称" + type: string + company_district_code: + description: "区 / 县编码" + type: string + company_postalcode: + description: "邮编" + type: string + company_add: + description: "地址" + type: string + company_status: + description: "公司状态" + type: string + company_level: + description: "公司等级" + type: string + company_owner_openid: + description: "公司所有者 openid" + type: string + company_remark: + description: "备注" + type: string + additionalProperties: false + PocketBaseCompanyListResponse: + type: object + required: + - page + - perPage + - totalItems + - totalPages + - items + properties: + page: + type: + - integer + - string + example: 页码 | integer + perPage: + type: + - integer + - string + example: 每页条数 | integer + totalItems: + type: + - integer + - string + example: 总记录数 | integer + totalPages: + type: + - integer + - string + example: 总页数 | integer + items: + type: array + items: + $ref: '#/components/schemas/PocketBaseCompanyRecord' + example: + page: 页码 | integer + perPage: 每页条数 | integer + totalItems: 总记录数 | integer + totalPages: 总页数 | integer + items: + - id: PocketBase记录主键 | string + collectionId: 集合ID | string + collectionName: 集合名称 | string + created: 记录创建时间 | string + updated: 记录更新时间 | string + company_id: 公司业务id,由数据库自动生成 | string + company_name: 公司名称 | string + company_type: 公司类型 | string + company_entity: 公司法人 | string + company_usci: 统一社会信用代码 | string + company_nationality: 国家名称 | string + company_nationality_code: 国家编码 | string + company_province: 省份名称 | string + company_province_code: 省份编码 | string + company_city: 城市名称 | string + company_city_code: 城市编码 | string + company_district: 区县名称 | string + company_district_code: 区县编码 | string + company_postalcode: 邮政编码 | string + company_add: 公司地址 | string + company_status: 公司状态 | string + company_level: 公司等级 | string + company_owner_openid: 公司所有者openid | string + company_remark: 备注 | string + PocketBaseAttachmentRecord: + type: object + properties: + id: + type: string + description: PocketBase 记录主键 + example: PocketBase记录主键 | string + collectionId: + type: string + description: 集合ID + example: 集合ID | string + collectionName: + type: string + description: 集合名称 + example: 集合名称 | string + attachments_id: + type: string + description: 附件业务 ID + example: 附件业务ID | string + attachments_link: + type: string + description: PocketBase 文件字段值,可按标准文件路径拼接文件流地址 + example: PocketBase文件字段值,可拼接文件流地址 | string + attachments_filename: + type: string + description: 原始文件名 + example: 原始文件名 | string + attachments_filetype: + type: string + description: 文件类型 / MIME + example: 文件类型或MIME | string + attachments_size: + type: + - number + - integer + - string + description: 文件大小 + example: 文件大小 | number + attachments_owner: + type: string + description: 上传者业务标识 + example: 上传者业务标识 | string + attachments_md5: + type: string + description: 文件 MD5 + example: 文件MD5 | string + attachments_ocr: + type: string + description: OCR 识别结果 + example: OCR识别结果 | string + attachments_status: + type: string + description: 附件状态 + example: 附件状态 | string + attachments_remark: + type: string + description: 备注 + example: 备注 | string + example: + id: PocketBase记录主键 | string + collectionId: 集合ID | string + collectionName: 集合名称 | string + attachments_id: 附件业务ID | string + attachments_link: PocketBase文件字段值,可拼接文件流地址 | string + attachments_filename: 原始文件名 | string + attachments_filetype: 文件类型或MIME | string + attachments_size: 文件大小 | number + attachments_owner: 上传者业务标识 | string + attachments_md5: 文件MD5 | string + attachments_ocr: OCR识别结果 | string + attachments_status: 附件状态 | string + attachments_remark: 备注 | string + PocketBaseAttachmentListResponse: + type: object + required: + - page + - perPage + - totalItems + - totalPages + - items + properties: + page: + type: + - integer + - string + example: 页码 | integer + perPage: + type: + - integer + - string + example: 每页条数 | integer + totalItems: + type: + - integer + - string + example: 总记录数 | integer + totalPages: + type: + - integer + - string + example: 总页数 | integer + items: + type: array + items: + $ref: '#/components/schemas/PocketBaseAttachmentRecord' + example: + page: 页码 | integer + perPage: 每页条数 | integer + totalItems: 总记录数 | integer + totalPages: 总页数 | integer + items: + - id: PocketBase记录主键 | string + collectionId: 集合ID | string + collectionName: 集合名称 | string + attachments_id: 附件业务ID | string + attachments_link: PocketBase文件字段值,可拼接文件流地址 | string + attachments_filename: 原始文件名 | string + attachments_filetype: 文件类型或MIME | string + attachments_size: 文件大小 | number + attachments_owner: 上传者业务标识 | string + attachments_md5: 文件MD5 | string + attachments_ocr: OCR识别结果 | string + attachments_status: 附件状态 | string + attachments_remark: 备注 | string + PocketBaseDocumentFields: + type: object + properties: + document_id: + type: string + description: "文档业务 ID" + example: 文档业务ID | string + document_create: + type: string + description: "文档创建时间,由数据库自动生成" + example: 文档创建时间,由数据库自动生成 | string + document_title: + type: string + description: "文档标题" + example: 文档标题 | string + document_type: + type: string + example: 文档类型,按system_dict_id@dict_word_enum保存 | string + document_status: + type: string + description: "文档状态,仅 `有效` / `过期`" + example: 文档状态 | string + document_owner: + type: string + description: "上传者 openid" + example: 上传者openid | string + PocketBaseDocumentRecord: + allOf: + - $ref: '#/components/schemas/PocketBaseRecordBase' + - $ref: '#/components/schemas/PocketBaseDocumentFields' + example: + id: PocketBase记录主键 | string + collectionId: 集合ID | string + collectionName: 集合名称 | string + created: 记录创建时间 | string + updated: 记录更新时间 | string + document_id: 文档业务ID | string + document_create: 文档创建时间,由数据库自动生成 | string + document_title: 文档标题 | string + document_type: 文档类型,按system_dict_id@dict_word_enum保存 | string + document_status: 文档状态 | string + document_owner: 上传者openid | string + PocketBaseDocumentListResponse: + type: object + required: + - page + - perPage + - totalItems + - totalPages + - items + properties: + page: + type: + - integer + - string + example: 页码 | integer + perPage: + type: + - integer + - string + example: 每页条数 | integer + totalItems: + type: + - integer + - string + example: 总记录数 | integer + totalPages: + type: + - integer + - string + example: 总页数 | integer + items: + type: array + items: + $ref: '#/components/schemas/PocketBaseDocumentRecord' + example: + page: 页码 | integer + perPage: 每页条数 | integer + totalItems: 总记录数 | integer + totalPages: 总页数 | integer + items: + - id: PocketBase记录主键 | string + collectionId: 集合ID | string + collectionName: 集合名称 | string + created: 记录创建时间 | string + updated: 记录更新时间 | string + document_id: 文档业务ID | string + document_create: 文档创建时间,由数据库自动生成 | string + document_title: 文档标题 | string + document_type: 文档类型,按system_dict_id@dict_word_enum保存 | string + document_status: 文档状态 | string + document_owner: 上传者openid | string + PocketBaseNativeError: + type: object + properties: + code: + type: + - integer + - string + description: "业务状态码" + example: 错误状态码 | integer + message: + type: string + example: PocketBase原生错误信息 | string + data: + description: "业务响应数据" + type: object + additionalProperties: true + example: + code: 错误状态码 | integer + message: PocketBase原生错误信息 | string + data: + 任意错误字段: 原生错误附加信息 | object UserInfo: type: object properties: pb_id: + description: "PocketBase 记录主键 id" type: string users_convers_id: + description: "会话侧用户 ID" type: string users_id: + description: "用户业务 ID" type: string users_idtype: - type: string - enum: - - WeChat - - ManagePlatform + description: "身份来源类型或证件类型" + anyOf: + - type: string + enum: + - WeChat + - ManagePlatform + - type: string users_id_number: + description: "证件号" type: string users_type: - type: string + description: "用户类型" + anyOf: + - type: string + enum: + - 游客 + - 注册用户 + - type: string users_name: + description: "用户姓名 / 昵称" type: string users_status: + description: "用户状态" type: - string - number users_rank_level: + description: "用户星级数值" type: - number - integer + - string users_auth_type: + description: "账户类型" type: - number - integer + - string users_phone: + description: "手机号" type: string users_phone_masked: type: string users_level: type: string + description: 用户等级 + users_tag: + type: string + description: 用户标签 users_picture: type: string description: 用户头像附件的 `attachments_id` @@ -324,18 +1603,77 @@ components: description: 全平台统一身份标识 company_id: type: string + description: 公司业务 id,存储 `tbl_company.company_id` users_parent_id: type: string + description: 上级用户业务 id users_promo_code: type: string + description: 推广码 usergroups_id: type: string + description: 用户组业务 id company: $ref: '#/components/schemas/CompanyInfo' created: type: string + description: 用户创建时间 updated: type: string + description: 用户更新时间 + example: + pb_id: PocketBase记录主键id | string + users_convers_id: 会话侧用户ID | string + users_id: 用户业务ID | string + users_idtype: 用户身份来源类型 | string + users_id_number: 证件号 | string + users_type: 用户类型 | string + users_name: 用户姓名或昵称 | string + users_status: 用户状态 | string + users_rank_level: 用户星级数值 | number + users_auth_type: 账户类型 | number + users_phone: 手机号 | string + users_phone_masked: 手机号脱敏值 | string + users_level: 用户等级 | string + users_tag: 用户标签 | string + users_picture: 用户头像附件ID | string + users_picture_url: 用户头像文件流链接 | string + users_id_pic_a: 证件正面附件ID | string + users_id_pic_a_url: 证件正面文件流链接 | string + users_id_pic_b: 证件反面附件ID | string + users_id_pic_b_url: 证件反面文件流链接 | string + users_title_picture: 资质附件ID | string + users_title_picture_url: 资质附件文件流链接 | string + openid: 全平台统一身份标识 | string + company_id: 公司业务id | string + users_parent_id: 上级用户业务id | string + users_promo_code: 推广码 | string + usergroups_id: 用户组业务id | string + company: + pb_id: PocketBase记录主键id | string + company_id: 公司业务id,由数据库自动生成 | string + company_name: 公司名称 | string + company_type: 公司类型 | string + company_entity: 公司法人 | string + company_usci: 统一社会信用代码 | string + company_nationality: 国家名称 | string + company_nationality_code: 国家编码 | string + company_province: 省份名称 | string + company_province_code: 省份编码 | string + company_city: 城市名称 | string + company_city_code: 城市编码 | string + company_district: 区县名称 | string + company_district_code: 区县编码 | string + company_postalcode: 邮政编码 | string + company_add: 公司地址 | string + company_status: 公司状态 | string + company_level: 公司等级 | string + company_owner_openid: 公司所有者openid | string + company_remark: 备注 | string + created: 记录创建时间 | string + updated: 记录更新时间 | string + created: 用户创建时间 | string + updated: 用户更新时间 | string WechatLoginRequest: type: object required: @@ -347,20 +1685,26 @@ components: example: 0a1b2c3d4e5f6g WechatProfileRequest: type: object - required: - - users_name - - users_phone_code - - users_picture properties: users_name: type: string + description: "用户姓名 / 昵称" example: 张三 users_phone_code: type: string + description: 可选。若传入,服务端优先通过微信接口换取真实手机号并写入数据库 example: 2b7d9f2e3c4a5b6d7e8f + users_phone: + type: string + description: 可选。未传 `users_phone_code` 时,可直接写入手机号 + example: '13800138000' + users_tag: + type: string + description: 可选。用户标签;非空时才更新 + example: 核心客户 users_picture: type: string - description: 用户头像附件的 `attachments_id` + description: 可选。用户头像附件的 `attachments_id` example: ATT-1743123456789-abc123 users_id_pic_a: type: string @@ -386,12 +1730,16 @@ components: type: object properties: status: - type: string - enum: - - register_success - - login_success + anyOf: + - type: string + enum: + - register_success + - login_success + - type: string is_info_complete: - type: boolean + type: + - boolean + - string user: $ref: '#/components/schemas/UserInfo' AuthSuccessResponse: @@ -402,17 +1750,80 @@ components: - token properties: data: + description: "业务响应数据" $ref: '#/components/schemas/AuthSuccessData' token: type: string description: PocketBase 原生 auth token + example: + code: 业务状态码 | integer + msg: 业务提示信息 | string + data: + status: 登录或注册状态 | string + is_info_complete: 资料是否完整 | boolean + user: + pb_id: PocketBase记录主键id | string + users_convers_id: 会话侧用户ID | string + users_id: 用户业务ID | string + users_idtype: 用户身份来源类型 | string + users_id_number: 证件号 | string + users_type: 用户类型 | string + users_name: 用户姓名或昵称 | string + users_status: 用户状态 | string + users_rank_level: 用户星级数值 | number + users_auth_type: 账户类型 | number + users_phone: 手机号 | string + users_phone_masked: 手机号脱敏值 | string + users_level: 用户等级 | string + users_tag: 用户标签 | string + users_picture: 用户头像附件ID | string + users_picture_url: 用户头像文件流链接 | string + users_id_pic_a: 证件正面附件ID | string + users_id_pic_a_url: 证件正面文件流链接 | string + users_id_pic_b: 证件反面附件ID | string + users_id_pic_b_url: 证件反面文件流链接 | string + users_title_picture: 资质附件ID | string + users_title_picture_url: 资质附件文件流链接 | string + openid: 全平台统一身份标识 | string + company_id: 公司业务id | string + users_parent_id: 上级用户业务id | string + users_promo_code: 推广码 | string + usergroups_id: 用户组业务id | string + company: + pb_id: PocketBase记录主键id | string + company_id: 公司业务id,由数据库自动生成 | string + company_name: 公司名称 | string + company_type: 公司类型 | string + company_entity: 公司法人 | string + company_usci: 统一社会信用代码 | string + company_nationality: 国家名称 | string + company_nationality_code: 国家编码 | string + company_province: 省份名称 | string + company_province_code: 省份编码 | string + company_city: 城市名称 | string + company_city_code: 城市编码 | string + company_district: 区县名称 | string + company_district_code: 区县编码 | string + company_postalcode: 邮政编码 | string + company_add: 公司地址 | string + company_status: 公司状态 | string + company_level: 公司等级 | string + company_owner_openid: 公司所有者openid | string + company_remark: 备注 | string + created: 记录创建时间 | string + updated: 记录更新时间 | string + created: 用户创建时间 | string + updated: 用户更新时间 | string + token: PocketBase原生认证token | string WechatProfileResponseData: type: object properties: status: - type: string - enum: - - update_success + anyOf: + - type: string + enum: + - update_success + - type: string user: $ref: '#/components/schemas/UserInfo' WechatProfileResponse: @@ -421,7 +1832,66 @@ components: - type: object properties: data: + description: "业务响应数据" $ref: '#/components/schemas/WechatProfileResponseData' + example: + code: 业务状态码 | integer + msg: 业务提示信息 | string + data: + status: 资料更新状态 | string + user: + pb_id: PocketBase记录主键id | string + users_convers_id: 会话侧用户ID | string + users_id: 用户业务ID | string + users_idtype: 用户身份来源类型 | string + users_id_number: 证件号 | string + users_type: 用户类型 | string + users_name: 用户姓名或昵称 | string + users_status: 用户状态 | string + users_rank_level: 用户星级数值 | number + users_auth_type: 账户类型 | number + users_phone: 手机号 | string + users_phone_masked: 手机号脱敏值 | string + users_level: 用户等级 | string + users_tag: 用户标签 | string + users_picture: 用户头像附件ID | string + users_picture_url: 用户头像文件流链接 | string + users_id_pic_a: 证件正面附件ID | string + users_id_pic_a_url: 证件正面文件流链接 | string + users_id_pic_b: 证件反面附件ID | string + users_id_pic_b_url: 证件反面文件流链接 | string + users_title_picture: 资质附件ID | string + users_title_picture_url: 资质附件文件流链接 | string + openid: 全平台统一身份标识 | string + company_id: 公司业务id | string + users_parent_id: 上级用户业务id | string + users_promo_code: 推广码 | string + usergroups_id: 用户组业务id | string + company: + pb_id: PocketBase记录主键id | string + company_id: 公司业务id,由数据库自动生成 | string + company_name: 公司名称 | string + company_type: 公司类型 | string + company_entity: 公司法人 | string + company_usci: 统一社会信用代码 | string + company_nationality: 国家名称 | string + company_nationality_code: 国家编码 | string + company_province: 省份名称 | string + company_province_code: 省份编码 | string + company_city: 城市名称 | string + company_city_code: 城市编码 | string + company_district: 区县名称 | string + company_district_code: 区县编码 | string + company_postalcode: 邮政编码 | string + company_add: 公司地址 | string + company_status: 公司状态 | string + company_level: 公司等级 | string + company_owner_openid: 公司所有者openid | string + company_remark: 备注 | string + created: 记录创建时间 | string + updated: 记录更新时间 | string + created: 用户创建时间 | string + updated: 用户更新时间 | string RefreshTokenResponse: allOf: - $ref: '#/components/schemas/ApiResponseBase' @@ -432,20 +1902,34 @@ components: data: type: object additionalProperties: true + description: "业务响应数据" example: {} token: type: string description: 新签发的 PocketBase 原生 auth token + example: + code: 业务状态码 | integer + msg: 刷新成功 | string + data: {} + token: 新签发的PocketBase原生auth token | string UsersCountData: type: object properties: total_users: - type: integer - example: 128 + type: + - integer + - string + example: 用户总数 | integer UsersCountResponse: allOf: - $ref: '#/components/schemas/ApiResponseBase' - type: object properties: data: + description: "业务响应数据" $ref: '#/components/schemas/UsersCountData' + example: + code: 业务状态码 | integer + msg: 业务提示信息 | string + data: + total_users: 用户总数 | integer diff --git a/pocket-base/spec/openapi.yaml b/pocket-base/spec/openapi.yaml index 7ffdea8..1759da5 100644 --- a/pocket-base/spec/openapi.yaml +++ b/pocket-base/spec/openapi.yaml @@ -13,6 +13,7 @@ servers: description: 生产环境 - url: http://localhost:8090 description: PocketBase 本地环境 +security: [] tags: - name: 系统 description: 基础检查接口 @@ -21,7 +22,7 @@ tags: - name: 平台认证 description: 面向平台用户的认证接口;平台用户会生成 GUID 并写入统一 `openid` 字段。 - name: 字典管理 - description: 面向 ManagePlatform 用户的系统字典维护接口。 + description: 字典读接口公开;字典写接口面向 ManagePlatform 用户。 - name: 附件管理 description: 面向 ManagePlatform 用户的附件上传、查询与删除接口。 - name: 文档管理 @@ -36,11 +37,14 @@ components: properties: code: type: integer + description: "业务状态码" example: 200 msg: type: string + description: "业务提示信息" example: 操作成功 data: + description: "业务响应数据" type: object additionalProperties: true HealthData: @@ -76,13 +80,82 @@ components: type: string example: success build_time: - type: string - nullable: true - format: date-time + anyOf: + - type: string + format: date-time + - type: 'null' CompanyInfo: - type: object - nullable: true - additionalProperties: true + description: 用户所属公司信息;当用户尚未绑定公司时返回 `null` + anyOf: + - type: object + properties: + pb_id: + type: string + description: PocketBase 记录主键 id + company_id: + type: string + description: 公司业务 id,由数据库自动生成 + company_name: + type: string + description: 公司名称 + company_type: + type: string + description: 公司类型 + company_entity: + type: string + description: 公司法人 + company_usci: + type: string + description: 统一社会信用代码 + company_nationality: + type: string + description: 国家名称 + company_nationality_code: + type: string + description: 国家编码 + company_province: + type: string + description: 省份名称 + company_province_code: + type: string + description: 省份编码 + company_city: + type: string + description: 城市名称 + company_city_code: + type: string + description: 城市编码 + company_district: + type: string + description: 区/县名称 + company_district_code: + type: string + description: 区/县编码 + company_postalcode: + type: string + description: 邮政编码 + company_add: + type: string + description: 公司地址 + company_status: + type: string + description: 公司状态 + company_level: + type: string + description: 公司等级 + company_owner_openid: + type: string + description: 公司所有者 openid + company_remark: + type: string + description: 备注 + created: + type: string + description: 记录创建时间 + updated: + type: string + description: 记录更新时间 + - type: 'null' UserInfo: type: object description: | @@ -90,34 +163,52 @@ components: 其中 `openid` 为全平台统一身份标识:微信用户使用微信 openid,平台用户使用服务端生成 GUID。 properties: pb_id: + description: "PocketBase 记录主键 id" type: string users_convers_id: + description: "会话侧用户 ID" type: string users_id: + description: "用户业务 ID" type: string users_idtype: - type: string description: 用户身份来源类型 - enum: [WeChat, ManagePlatform] + anyOf: + - type: string + enum: [WeChat, ManagePlatform] + - type: string users_id_number: + description: "证件号" type: string users_status: + description: "用户状态" type: string users_rank_level: - type: number + description: "用户星级数值" + type: [number, string] users_auth_type: - type: number + description: "账户类型" + type: [number, string] users_type: - type: string - enum: [游客, 注册用户] + description: "用户类型" + anyOf: + - type: string + enum: [游客, 注册用户] + - type: string users_name: + description: "用户姓名 / 昵称" type: string users_phone: + description: "手机号" type: string users_phone_masked: type: string users_level: type: string + description: 用户等级 + users_tag: + type: string + description: 用户标签 users_picture: type: string description: 用户头像附件的 `attachments_id` @@ -147,18 +238,77 @@ components: description: 全平台统一身份标识;微信用户为微信 openid,平台用户为服务端生成的 GUID company_id: type: string + description: 公司业务 id,存储 `tbl_company.company_id` users_parent_id: type: string + description: 上级用户业务 id users_promo_code: type: string + description: 推广码 usergroups_id: type: string + description: 用户组业务 id company: $ref: '#/components/schemas/CompanyInfo' created: type: string + description: 用户创建时间 updated: type: string + description: 用户更新时间 + example: + pb_id: PocketBase记录主键id | string + users_convers_id: 会话侧用户ID | string + users_id: 用户业务ID | string + users_idtype: 用户身份来源类型 | string + users_id_number: 证件号 | string + users_type: 用户类型 | string + users_name: 用户姓名或昵称 | string + users_status: 用户状态 | string + users_rank_level: 用户星级数值 | number + users_auth_type: 账户类型 | number + users_phone: 手机号 | string + users_phone_masked: 手机号脱敏值 | string + users_level: 用户等级 | string + users_tag: 用户标签 | string + users_picture: 用户头像附件ID | string + users_picture_url: 用户头像文件流链接 | string + users_id_pic_a: 证件正面附件ID | string + users_id_pic_a_url: 证件正面文件流链接 | string + users_id_pic_b: 证件反面附件ID | string + users_id_pic_b_url: 证件反面文件流链接 | string + users_title_picture: 资质附件ID | string + users_title_picture_url: 资质附件文件流链接 | string + openid: 全平台统一身份标识 | string + company_id: 公司业务id | string + users_parent_id: 上级用户业务id | string + users_promo_code: 推广码 | string + usergroups_id: 用户组业务id | string + company: + pb_id: PocketBase记录主键id | string + company_id: 公司业务id,由数据库自动生成 | string + company_name: 公司名称 | string + company_type: 公司类型 | string + company_entity: 公司法人 | string + company_usci: 统一社会信用代码 | string + company_nationality: 国家名称 | string + company_nationality_code: 国家编码 | string + company_province: 省份名称 | string + company_province_code: 省份编码 | string + company_city: 城市名称 | string + company_city_code: 城市编码 | string + company_district: 区县名称 | string + company_district_code: 区县编码 | string + company_postalcode: 邮政编码 | string + company_add: 公司地址 | string + company_status: 公司状态 | string + company_level: 公司等级 | string + company_owner_openid: 公司所有者openid | string + company_remark: 备注 | string + created: 记录创建时间 | string + updated: 记录更新时间 | string + created: 用户创建时间 | string + updated: 用户更新时间 | string PocketBaseAuthResponse: type: object description: | @@ -167,18 +317,23 @@ components: properties: code: type: integer + description: "业务状态码" example: 200 msg: type: string + description: "业务提示信息" example: 登录成功 data: + description: "业务响应数据" type: object properties: status: - type: string - enum: [register_success, login_success] + anyOf: + - type: string + enum: [register_success, login_success] + - type: string is_info_complete: - type: boolean + type: [boolean, string] user: $ref: '#/components/schemas/UserInfo' token: @@ -188,36 +343,58 @@ components: code: 200 msg: 登录成功 data: - status: login_success - is_info_complete: true + status: 登录或注册状态 | string + is_info_complete: 资料是否完整 | boolean user: - pb_id: vtukf6agem2xbcv - users_id: U202603260001 - users_idtype: ManagePlatform - users_name: momo - users_phone: '13509214696' - users_phone_masked: '135****4696' - users_status: '' - users_rank_level: 0 - users_auth_type: 0 - users_type: 注册用户 - users_picture: '' - users_picture_url: '' - users_id_pic_a: '' - users_id_pic_a_url: '' - users_id_pic_b: '' - users_id_pic_b_url: '' - users_title_picture: '' - users_title_picture_url: '' - openid: app_momo - company_id: '' - users_parent_id: '' - users_promo_code: '' - usergroups_id: '' - company: null - created: '' - updated: '' - token: eyJhbGciOi... + pb_id: PocketBase记录主键id | string + users_id: 用户业务ID | string + users_idtype: 用户身份来源类型 | string + users_name: 用户姓名或昵称 | string + users_phone: 手机号 | string + users_phone_masked: 手机号脱敏值 | string + users_status: 用户状态 | string + users_rank_level: 用户星级数值 | number + users_auth_type: 账户类型 | number + users_type: 用户类型 | string + users_picture: 用户头像附件ID | string + users_picture_url: 用户头像文件流链接 | string + users_id_pic_a: 证件正面附件ID | string + users_id_pic_a_url: 证件正面文件流链接 | string + users_id_pic_b: 证件反面附件ID | string + users_id_pic_b_url: 证件反面文件流链接 | string + users_title_picture: 资质附件ID | string + users_title_picture_url: 资质附件文件流链接 | string + openid: 全平台统一身份标识 | string + company_id: 公司业务id | string + users_parent_id: 上级用户业务id | string + users_promo_code: 推广码 | string + usergroups_id: 用户组业务id | string + company: + pb_id: PocketBase记录主键id | string + company_id: 公司业务id,由数据库自动生成 | string + company_name: 公司名称 | string + company_type: 公司类型 | string + company_entity: 公司法人 | string + company_usci: 统一社会信用代码 | string + company_nationality: 国家名称 | string + company_nationality_code: 国家编码 | string + company_province: 省份名称 | string + company_province_code: 省份编码 | string + company_city: 城市名称 | string + company_city_code: 城市编码 | string + company_district: 区县名称 | string + company_district_code: 区县编码 | string + company_postalcode: 邮政编码 | string + company_add: 公司地址 | string + company_status: 公司状态 | string + company_level: 公司等级 | string + company_owner_openid: 公司所有者openid | string + company_remark: 备注 | string + created: 记录创建时间 | string + updated: 记录更新时间 | string + created: 用户创建时间 | string + updated: 用户更新时间 | string + token: PocketBase原生认证token | string WechatLoginRequest: type: object required: [users_wx_code] @@ -229,18 +406,27 @@ components: example: 0a1b2c3d4e5f6g WechatProfileRequest: type: object - required: [users_name, users_phone_code, users_picture] description: 微信用户资料完善请求体。 properties: users_name: type: string + description: "用户姓名 / 昵称" example: 张三 users_phone_code: type: string + description: 可选。若传入,服务端优先通过微信接口换取真实手机号并写入数据库 example: 2b7d9f2e3c4a5b6d7e8f + users_phone: + type: string + description: 可选。未传 `users_phone_code` 时,可直接写入手机号 + example: '13800138000' + users_tag: + type: string + description: 可选。用户标签;非空时才更新 + example: 核心客户 users_picture: type: string - description: 用户头像附件的 `attachments_id` + description: 可选。用户头像附件的 `attachments_id` example: ATT-1743123456789-abc123 users_id_pic_a: type: string @@ -266,21 +452,25 @@ components: properties: users_name: type: string + description: "用户姓名 / 昵称" example: 张三 users_phone: type: string - example: 13800138000 + description: "手机号" + example: '13800138000' password: type: string - example: 12345678 + description: "PocketBase 认证密码" + example: '12345678' passwordConfirm: type: string - example: 12345678 + example: '12345678' users_picture: type: string description: 用户头像附件的 `attachments_id` example: ATT-1743123456789-abc123 users_id_number: + description: "证件号" type: string users_id_pic_a: type: string @@ -292,16 +482,25 @@ components: type: string description: 可选。资质附件的 `attachments_id` users_level: + description: "用户等级文本" + type: string + users_tag: + description: "用户标签" type: string users_type: + description: "用户类型" type: string company_id: + description: "所属公司业务 ID" type: string users_parent_id: + description: "上级用户业务 ID" type: string users_promo_code: + description: "推广码" type: string usergroups_id: + description: "用户组 / 角色 ID" type: string PlatformLoginRequest: type: object @@ -314,7 +513,8 @@ components: example: admin@example.com password: type: string - example: 12345678 + description: "PocketBase 认证密码" + example: '12345678' SystemRefreshTokenRequest: type: object description: | @@ -324,8 +524,9 @@ components: 当 token 失效时,需提供 `users_wx_code` 走微信 code 重新签发流程。 properties: users_wx_code: - type: string - nullable: true + anyOf: + - type: string + - type: 'null' description: 微信小程序登录临时凭证 code example: 0a1b2c3d4e5f6g RefreshTokenData: @@ -349,35 +550,67 @@ components: type: string description: 根据 `image -> tbl_attachments` 自动解析出的图片文件流链接 imageAttachment: - allOf: + anyOf: - $ref: '#/components/schemas/AttachmentRecord' - nullable: true + - type: 'null' sortOrder: type: integer example: 1 + example: + enum: enabled + description: 启用 + image: ATT-1743037200000-abc123 + imageUrl: https://bai-api.blv-oa.com/api/files/tbl_attachments/a1/enabled.png + imageAttachment: + attachments_id: ATT-1743037200000-abc123 + attachments_filename: enabled.png + sortOrder: 1 DictionaryRecord: type: object properties: pb_id: + description: "PocketBase 记录主键 id" type: string system_dict_id: + description: "字典业务 ID" type: string dict_name: + description: "字典名称,当前按全局唯一维护" type: string dict_word_is_enabled: + description: "字典是否启用" type: boolean dict_word_parent_id: + description: "父级字典业务 ID" type: string dict_word_remark: + description: "备注" type: string items: type: array items: $ref: '#/components/schemas/DictionaryItem' created: + description: "记录创建时间" type: string updated: + description: "记录更新时间" type: string + example: + pb_id: y3qk5cuirz515d6 + system_dict_id: DICT-1774599144591-hAEFQj + dict_name: 类型-发现页菜单 + dict_word_is_enabled: true + dict_word_parent_id: '' + dict_word_remark: 首页菜单字典 + items: + - enum: UT1 + description: 每日资讯 + image: ATT-1743037200000-abc123 + imageUrl: https://bai-api.blv-oa.com/api/files/tbl_attachments/a1/news.png + sortOrder: 1 + created: '2026-03-28 07:20:00.000Z' + updated: '2026-03-29 08:00:00.000Z' DictionaryListRequest: type: object properties: @@ -391,6 +624,7 @@ components: properties: dict_name: type: string + description: "字典名称,当前按全局唯一维护" example: 用户状态 DictionaryMutationRequest: type: object @@ -402,15 +636,19 @@ components: example: 用户状态 dict_name: type: string + description: "字典名称,当前按全局唯一维护" example: 用户状态 dict_word_is_enabled: type: boolean + description: "字典是否启用" example: true dict_word_parent_id: type: string + description: "父级字典业务 ID" example: '' dict_word_remark: type: string + description: "备注" example: 系统状态字典 items: type: array @@ -424,13 +662,16 @@ components: properties: dict_name: type: string + description: "字典名称,当前按全局唯一维护" example: 用户状态 AttachmentRecord: type: object properties: pb_id: + description: "PocketBase 记录主键 id" type: string attachments_id: + description: "附件业务 ID" type: string attachments_link: type: string @@ -442,25 +683,51 @@ components: type: string description: 附件下载链接 attachments_filename: + description: "原始文件名" type: string attachments_filetype: + description: "文件类型 / MIME" type: string attachments_size: + description: "文件大小" type: number attachments_owner: + description: "上传者业务标识" type: string attachments_md5: + description: "文件 MD5" type: string attachments_ocr: + description: "OCR 识别结果" type: string attachments_status: + description: "附件状态" type: string attachments_remark: + description: "备注" type: string created: + description: "记录创建时间" type: string updated: + description: "记录更新时间" type: string + example: + pb_id: a1b2c3d4e5f6g7h + attachments_id: ATT-1743037200000-abc123 + attachments_link: enabled.png + attachments_url: https://bai-api.blv-oa.com/api/files/tbl_attachments/a1/enabled.png + attachments_download_url: https://bai-api.blv-oa.com/api/files/tbl_attachments/a1/enabled.png?download=1 + attachments_filename: enabled.png + attachments_filetype: image/png + attachments_size: 24576 + attachments_owner: app_momo + attachments_md5: 4b825dc642cb6eb9a060e54bf8d69288 + attachments_ocr: '' + attachments_status: active + attachments_remark: 字典图标 + created: '2026-03-28 07:20:00.000Z' + updated: '2026-03-29 08:00:00.000Z' AttachmentListRequest: type: object properties: @@ -478,6 +745,7 @@ components: properties: attachments_id: type: string + description: "附件业务 ID" example: ATT-1743037200000-abc123 AttachmentUploadRequest: type: object @@ -497,35 +765,50 @@ components: type: number description: 文件大小 attachments_md5: + description: "文件 MD5" type: string attachments_ocr: + description: "OCR 识别结果" type: string attachments_status: type: string + description: "附件状态" example: active attachments_remark: + description: "备注" type: string DocumentRecord: type: object properties: pb_id: + description: "PocketBase 记录主键 id" type: string document_id: + description: "文档业务 ID" type: string + document_create: + type: string + description: 文档创建时间,由数据库自动生成 document_effect_date: + description: "文档生效日期" type: string document_expiry_date: + description: "文档到期日期" type: string document_type: type: string description: 多选时按 `system_dict_id@dict_word_enum|...` 保存 document_title: + description: "文档标题" type: string document_subtitle: + description: "文档副标题" type: string document_summary: + description: "文档摘要" type: string document_content: + description: "正文内容,保存 Markdown" type: string document_image: type: string @@ -603,26 +886,34 @@ components: type: string description: 上传者 openid document_relation_model: + description: "关联机型 / 模型标识" type: string document_keywords: type: string description: 固定字典多选字段,使用 `|` 分隔 document_share_count: + description: "分享次数" type: number document_download_count: + description: "下载次数" type: number document_favorite_count: + description: "收藏次数" type: number document_status: type: string description: 文档状态,仅允许 `有效` 或 `过期`,由系统根据生效日期和到期日期自动计算;当两者都为空时默认 `有效` document_embedding_status: + description: "文档嵌入状态" type: string document_embedding_error: + description: "文档嵌入错误原因" type: string document_embedding_lasttime: + description: "最后一次嵌入更新时间" type: string document_vector_version: + description: "向量版本号 / 模型名称" type: string document_product_categories: type: string @@ -634,11 +925,60 @@ components: type: string description: 固定字典多选字段,使用 `|` 分隔 document_remark: + description: "备注" type: string created: + description: "记录创建时间" type: string updated: + description: "记录更新时间" type: string + example: + pb_id: docpb_001 + document_id: DOC-1774677099775-WtHUaJ + document_create: '2026-03-28 07:20:00.000Z' + document_effect_date: '2026-03-28' + document_expiry_date: '2027-03-28' + document_type: DICT-1774599144591-hAEFQj@UT1 + document_title: 智能化重构补记行业价值新链条 + document_subtitle: 每日资讯 + document_summary: 文档摘要示例 + document_content: '# 正文内容' + document_image: ATT-1743037200000-abc123|ATT-1743037200001-def456 + document_image_ids: + - ATT-1743037200000-abc123 + document_image_urls: + - https://bai-api.blv-oa.com/api/files/tbl_attachments/a1/news.png + document_image_url: https://bai-api.blv-oa.com/api/files/tbl_attachments/a1/news.png + document_video: ATT-1743037200002-ghi789 + document_video_ids: + - ATT-1743037200002-ghi789 + document_video_urls: + - https://bai-api.blv-oa.com/api/files/tbl_attachments/a2/demo.mp4 + document_video_url: https://bai-api.blv-oa.com/api/files/tbl_attachments/a2/demo.mp4 + document_file: ATT-1743037200003-jkl012 + document_file_ids: + - ATT-1743037200003-jkl012 + document_file_urls: + - https://bai-api.blv-oa.com/api/files/tbl_attachments/a3/manual.pdf + document_file_url: https://bai-api.blv-oa.com/api/files/tbl_attachments/a3/manual.pdf + document_owner: app_momo + document_relation_model: OR1 + document_keywords: 酒店|智能化 + document_share_count: 3 + document_download_count: 12 + document_favorite_count: 5 + document_status: 有效 + document_embedding_status: succeed + document_embedding_error: '' + document_embedding_lasttime: '2026-03-29' + document_vector_version: text-embedding-3-large + document_product_categories: OR1|AZ4 + document_application_scenarios: 每日资讯 + document_hotel_type: 高端酒店 + document_remark: 文档备注 + created: '2026-03-28 07:20:00.000Z' + updated: '2026-03-29 08:00:00.000Z' DocumentListRequest: type: object properties: @@ -659,6 +999,7 @@ components: properties: document_id: type: string + description: "文档业务 ID" example: DOC-1743037200000-abc123 DocumentMutationRequest: type: object @@ -680,12 +1021,16 @@ components: type: string description: 必填;前端显示为字典项描述,存库时按 `system_dict_id@dict_word_enum|...` 保存 document_title: + description: "文档标题" type: string document_subtitle: + description: "文档副标题" type: string document_summary: + description: "文档摘要" type: string document_content: + description: "正文内容,保存 Markdown" type: string document_image: oneOf: @@ -712,26 +1057,34 @@ components: type: string description: 文件附件 id 列表;支持数组或 `|` 分隔字符串 document_relation_model: + description: "关联机型 / 模型标识" type: string document_keywords: type: string description: 从 `文档-关键词` 字典多选后使用 `|` 分隔保存 document_share_count: + description: "分享次数" type: number document_download_count: + description: "下载次数" type: number document_favorite_count: + description: "收藏次数" type: number document_status: type: string description: 文档状态,仅允许 `有效` 或 `过期`,由系统根据生效日期和到期日期自动计算;当两者都为空时默认 `有效` document_embedding_status: + description: "文档嵌入状态" type: string document_embedding_error: + description: "文档嵌入错误原因" type: string document_embedding_lasttime: + description: "最后一次嵌入更新时间" type: string document_vector_version: + description: "向量版本号 / 模型名称" type: string document_product_categories: type: string @@ -743,6 +1096,7 @@ components: type: string description: 从 `文档-适用场景` 字典多选后使用 `|` 分隔保存 document_remark: + description: "备注" type: string DocumentDeleteRequest: type: object @@ -750,28 +1104,48 @@ components: properties: document_id: type: string + description: "文档业务 ID" example: DOC-1743037200000-abc123 DocumentHistoryRecord: type: object properties: pb_id: + description: "PocketBase 记录主键 id" type: string doh_id: + description: "文档操作历史业务 ID" type: string doh_document_id: + description: "关联文档业务 ID" type: string doh_operation_type: + description: "操作类型" type: string doh_user_id: + description: "操作人业务 ID" type: string doh_current_count: + description: "本次操作对应次数" type: number doh_remark: + description: "备注" type: string created: + description: "记录创建时间" type: string updated: + description: "记录更新时间" type: string + example: + pb_id: histpb_001 + doh_id: DOH-1774677099775-WtHUaJ + doh_document_id: DOC-1774677099775-WtHUaJ + doh_operation_type: update + doh_user_id: app_momo + doh_current_count: 1 + doh_remark: 更新文档 + created: '2026-03-29 08:00:00.000Z' + updated: '2026-03-29 08:00:00.000Z' DocumentHistoryListRequest: type: object properties: @@ -795,6 +1169,7 @@ paths: - type: object properties: data: + description: "业务响应数据" $ref: '#/components/schemas/HelloWorldData' /pb/api/system/health: post: @@ -811,6 +1186,7 @@ paths: - type: object properties: data: + description: "业务响应数据" $ref: '#/components/schemas/HealthData' /pb/api/system/users-count: post: @@ -828,6 +1204,7 @@ paths: - type: object properties: data: + description: "业务响应数据" $ref: '#/components/schemas/UsersCountData' /pb/api/system/refresh-token: post: @@ -949,9 +1326,9 @@ paths: application/json: schema: $ref: '#/components/schemas/PlatformLoginRequest' - example: - login_account: 13509214696 - password: Momo123456 + example: + login_account: '13509214696' + password: Momo123456 responses: '200': description: 登录成功 @@ -975,7 +1352,7 @@ paths: users_status: '' users_rank_level: 0 users_auth_type: 0 - users_type: '' + users_type: 注册用户 users_picture: '' openid: app_momo company_id: '' @@ -1002,7 +1379,10 @@ paths: summary: 更新微信用户资料 description: | 基于当前 `Authorization` 对应 auth record 中的统一 `openid` 定位当前微信用户。 - 当前接口仍用于微信资料完善场景。 + 当前接口按“非空字段增量更新”处理资料完善。 + 若传入 `users_phone_code`,优先调用微信接口换取真实手机号; + 若未传 `users_phone_code` 但传入 `users_phone`,则直接写入手机号; + 未传或传空的字段不会清空数据库已有值。 requestBody: required: true content: @@ -1020,17 +1400,19 @@ paths: - type: object properties: data: + description: "业务响应数据" $ref: '#/components/schemas/WechatProfileResponseData' '401': description: token 无效或当前 auth record 缺少统一身份字段 openid '400': - description: 参数错误、手机号已被注册或资料更新失败 + description: 参数错误、手机号已被注册、微信手机号换取失败或资料更新失败 /pb/api/dictionary/list: post: tags: [字典管理] summary: 查询字典列表 + security: [] description: | - 仅允许 `ManagePlatform` 用户访问。 + 公开读接口,无需 token。 支持按 `dict_name` 模糊搜索,返回字典全量信息,并将三个聚合字段组装为 `items`。 requestBody: required: false @@ -1049,24 +1431,22 @@ paths: - type: object properties: data: + description: "业务响应数据" type: object properties: items: type: array items: $ref: '#/components/schemas/DictionaryRecord' - '401': - description: token 无效或已过期 - '403': - description: 非 ManagePlatform 用户无权访问 '415': description: 请求体必须为 application/json /pb/api/dictionary/detail: post: tags: [字典管理] summary: 查询指定字典 + security: [] description: | - 仅允许 `ManagePlatform` 用户访问。 + 公开读接口,无需 token。 按唯一键 `dict_name` 查询单条字典,并返回组装后的 `items`。 requestBody: required: true @@ -1085,13 +1465,10 @@ paths: - type: object properties: data: + description: "业务响应数据" $ref: '#/components/schemas/DictionaryRecord' '400': description: 参数错误 - '401': - description: token 无效或已过期 - '403': - description: 非 ManagePlatform 用户无权访问 '404': description: 未找到对应字典 '415': @@ -1121,6 +1498,7 @@ paths: - type: object properties: data: + description: "业务响应数据" $ref: '#/components/schemas/DictionaryRecord' '400': description: 参数错误或 dict_name 已存在 @@ -1156,6 +1534,7 @@ paths: - type: object properties: data: + description: "业务响应数据" $ref: '#/components/schemas/DictionaryRecord' '400': description: 参数错误或 dict_name 冲突 @@ -1193,9 +1572,11 @@ paths: - type: object properties: data: + description: "业务响应数据" type: object properties: dict_name: + description: "字典名称,当前按全局唯一维护" type: string '400': description: 参数错误或删除失败 @@ -1233,6 +1614,7 @@ paths: - type: object properties: data: + description: "业务响应数据" type: object properties: items: @@ -1269,6 +1651,7 @@ paths: - type: object properties: data: + description: "业务响应数据" $ref: '#/components/schemas/AttachmentRecord' '400': description: 参数错误 @@ -1305,6 +1688,7 @@ paths: - type: object properties: data: + description: "业务响应数据" $ref: '#/components/schemas/AttachmentRecord' '400': description: 参数错误、缺少文件或附件保存失败 @@ -1336,9 +1720,11 @@ paths: - type: object properties: data: + description: "业务响应数据" type: object properties: attachments_id: + description: "附件业务 ID" type: string '400': description: 参数错误、附件已被文档引用或删除失败 @@ -1378,6 +1764,7 @@ paths: - type: object properties: data: + description: "业务响应数据" type: object properties: items: @@ -1414,6 +1801,7 @@ paths: - type: object properties: data: + description: "业务响应数据" $ref: '#/components/schemas/DocumentRecord' '400': description: 参数错误 @@ -1452,6 +1840,7 @@ paths: - type: object properties: data: + description: "业务响应数据" $ref: '#/components/schemas/DocumentRecord' '400': description: 参数错误、附件不存在或文档创建失败 @@ -1490,6 +1879,7 @@ paths: - type: object properties: data: + description: "业务响应数据" $ref: '#/components/schemas/DocumentRecord' '400': description: 参数错误、附件不存在或修改失败 @@ -1527,9 +1917,11 @@ paths: - type: object properties: data: + description: "业务响应数据" type: object properties: document_id: + description: "文档业务 ID" type: string '400': description: 参数错误或删除失败 @@ -1567,6 +1959,7 @@ paths: - type: object properties: data: + description: "业务响应数据" type: object properties: items: diff --git a/script/database_schema.md b/script/database_schema.md index ba145d4..7cbd975 100644 --- a/script/database_schema.md +++ b/script/database_schema.md @@ -6,6 +6,7 @@ ### 1. tbl_system_dict (系统词典) **类型:** Base Collection +**读写权限:** 所有人可读;仅 `ManagePlatform`/管理员可写 | 字段名 | 类型 | 备注 | | :--- | :--- | :--- | @@ -30,23 +31,30 @@ | 字段名 | 类型 | 备注 | | :--- | :--- | :--- | -| company_id | text | 自定义公司id | +| company_id | text | 公司业务id,由数据库自动生成,默认形如 `WX-COMPANY-时间串` | | company_name | text | 公司名称 | | company_type | text | 公司类型 | | company_entity | text | 公司法人 | | company_usci | text | 统一社会信用代码 | | company_nationality | text | 国家 | +| company_nationality_code | text | 国家编码 | | company_province | text | 省份 | +| company_province_code | text | 省份编码 | | company_city | text | 城市 | +| company_city_code | text | 城市编码 | +| company_district | text | 区/县 | +| company_district_code | text | 区/县编码 | | company_postalcode | text | 邮编 | | company_add | text | 地址 | | company_status | text | 公司状态 | | company_level | text | 公司等级 | +| company_owner_openid | text | 公司所有者 openid | | company_remark | text | 备注 | **索引规划 (Indexes):** * `CREATE UNIQUE INDEX` 针对 `company_id` (确保业务主键唯一) * `CREATE INDEX` 针对 `company_usci` (加速信用代码检索) +* `CREATE INDEX` 针对 `company_owner_openid` (加速按公司所有者 openid 查询) --- @@ -78,6 +86,7 @@ | users_wx_openid | text | 微信号 | | users_level | text | 用户等级 | | users_type | text | 用户类型 | +| users_tag | text | 用户标签 | | users_status | text | 用户状态 | | company_id | text | 公司id (存储 tbl_company.company_id) | | users_parent_id | text | 用户父级id (存储 tbl_users.users_id) | diff --git a/script/package.json b/script/package.json index 231b2df..4b4d715 100644 --- a/script/package.json +++ b/script/package.json @@ -8,7 +8,8 @@ "init:newpb": "node pocketbase.newpb.js", "init:documents": "node pocketbase.documents.js", "init:dictionary": "node pocketbase.dictionary.js", - "migrate:file-fields": "node pocketbase.file-fields-to-attachments.js" + "migrate:file-fields": "node pocketbase.file-fields-to-attachments.js", + "test:company-native-api": "node test-tbl-company-native-api.js" }, "keywords": [], "author": "", diff --git a/script/pocketbase.dictionary.js b/script/pocketbase.dictionary.js index 83c9658..59b4ed2 100644 --- a/script/pocketbase.dictionary.js +++ b/script/pocketbase.dictionary.js @@ -23,6 +23,11 @@ const pb = new PocketBase(PB_URL); const collectionData = { name: 'tbl_system_dict', type: 'base', + listRule: '', + viewRule: '', + createRule: '(@request.auth.users_idtype = "ManagePlatform" || @request.auth.usergroups_id = "ROLE-1774666070666-9dDrTB")', + updateRule: '(@request.auth.users_idtype = "ManagePlatform" || @request.auth.usergroups_id = "ROLE-1774666070666-9dDrTB")', + deleteRule: '(@request.auth.users_idtype = "ManagePlatform" || @request.auth.usergroups_id = "ROLE-1774666070666-9dDrTB")', fields: [ { name: 'system_dict_id', type: 'text', required: true }, { name: 'dict_name', type: 'text', required: true }, @@ -68,6 +73,11 @@ function buildCollectionPayload(target, existingCollection) { return { name: target.name, type: target.type, + listRule: target.listRule, + viewRule: target.viewRule, + createRule: target.createRule, + updateRule: target.updateRule, + deleteRule: target.deleteRule, fields: target.fields.map((field) => normalizeFieldPayload(field, null)), indexes: target.indexes, }; @@ -91,6 +101,11 @@ function buildCollectionPayload(target, existingCollection) { return { name: target.name, type: target.type, + listRule: target.listRule, + viewRule: target.viewRule, + createRule: target.createRule, + updateRule: target.updateRule, + deleteRule: target.deleteRule, fields: fields, indexes: target.indexes, }; diff --git a/script/pocketbase.documents.js b/script/pocketbase.documents.js index 7e41280..65d28a1 100644 --- a/script/pocketbase.documents.js +++ b/script/pocketbase.documents.js @@ -47,8 +47,11 @@ const collections = [ { name: 'tbl_document', type: 'base', + listRule: '', + viewRule: '', fields: [ { name: 'document_id', type: 'text', required: true }, + { name: 'document_create', type: 'autodate', onCreate: true, onUpdate: false }, { name: 'document_effect_date', type: 'date' }, { name: 'document_expiry_date', type: 'date' }, { name: 'document_type', type: 'text', required: true }, @@ -77,6 +80,7 @@ const collections = [ ], indexes: [ 'CREATE UNIQUE INDEX idx_tbl_document_document_id ON tbl_document (document_id)', + 'CREATE INDEX idx_tbl_document_document_create ON tbl_document (document_create)', 'CREATE INDEX idx_tbl_document_document_owner ON tbl_document (document_owner)', 'CREATE INDEX idx_tbl_document_document_type ON tbl_document (document_type)', 'CREATE INDEX idx_tbl_document_document_status ON tbl_document (document_status)', @@ -130,6 +134,11 @@ function normalizeFieldPayload(field, existingField) { payload.mimeTypes = Array.isArray(field.mimeTypes) && field.mimeTypes.length ? field.mimeTypes : null; } + if (field.type === 'autodate') { + payload.onCreate = typeof field.onCreate === 'boolean' ? field.onCreate : true; + payload.onUpdate = typeof field.onUpdate === 'boolean' ? field.onUpdate : false; + } + return payload; } diff --git a/script/pocketbase.js b/script/pocketbase.js index a0b7d75..5df2821 100644 --- a/script/pocketbase.js +++ b/script/pocketbase.js @@ -13,6 +13,11 @@ const collections = [ { name: 'tbl_system_dict', type: 'base', + listRule: '', + viewRule: '', + createRule: '(@request.auth.users_idtype = "ManagePlatform" || @request.auth.usergroups_id = "ROLE-1774666070666-9dDrTB")', + updateRule: '(@request.auth.users_idtype = "ManagePlatform" || @request.auth.usergroups_id = "ROLE-1774666070666-9dDrTB")', + deleteRule: '(@request.auth.users_idtype = "ManagePlatform" || @request.auth.usergroups_id = "ROLE-1774666070666-9dDrTB")', fields: [ { name: 'system_dict_id', type: 'text', required: true }, { name: 'dict_name', type: 'text', required: true }, @@ -33,23 +38,30 @@ const collections = [ name: 'tbl_company', type: 'base', fields: [ - { name: 'company_id', type: 'text', required: true }, + { name: 'company_id', type: 'text', required: true, autogeneratePattern: 'WX-COMPANY-[0-9]{13}' }, { name: 'company_name', type: 'text' }, { name: 'company_type', type: 'text' }, { name: 'company_entity', type: 'text' }, { name: 'company_usci', type: 'text' }, { name: 'company_nationality', type: 'text' }, + { name: 'company_nationality_code', type: 'text' }, { name: 'company_province', type: 'text' }, + { name: 'company_province_code', type: 'text' }, { name: 'company_city', type: 'text' }, + { name: 'company_city_code', type: 'text' }, + { name: 'company_district', type: 'text' }, + { name: 'company_district_code', type: 'text' }, { name: 'company_postalcode', type: 'text' }, { name: 'company_add', type: 'text' }, { name: 'company_status', type: 'text' }, { name: 'company_level', type: 'text' }, + { name: 'company_owner_openid', type: 'text' }, { name: 'company_remark', type: 'text' } ], indexes: [ 'CREATE UNIQUE INDEX idx_company_id ON tbl_company (company_id)', - 'CREATE INDEX idx_company_usci ON tbl_company (company_usci)' + 'CREATE INDEX idx_company_usci ON tbl_company (company_usci)', + 'CREATE INDEX idx_company_owner_openid ON tbl_company (company_owner_openid)' ] }, { @@ -122,6 +134,11 @@ async function createOrUpdateCollection(collectionData) { const payload = { name: collectionData.name, type: collectionData.type, + listRule: collectionData.listRule, + viewRule: collectionData.viewRule, + createRule: collectionData.createRule, + updateRule: collectionData.updateRule, + deleteRule: collectionData.deleteRule, fields: collectionData.fields, indexes: collectionData.indexes }; diff --git a/script/pocketbase.newpb.js b/script/pocketbase.newpb.js index e0c7a90..c0cf50a 100644 --- a/script/pocketbase.newpb.js +++ b/script/pocketbase.newpb.js @@ -52,6 +52,7 @@ const collections = [ { name: 'users_phone', type: 'text' }, { name: 'users_level', type: 'text' }, { name: 'users_type', type: 'text' }, + { name: 'users_tag', type: 'text' }, { name: 'users_status', type: 'text' }, { name: 'company_id', type: 'text' }, { name: 'users_parent_id', type: 'text' }, diff --git a/script/test-tbl-company-native-api.js b/script/test-tbl-company-native-api.js new file mode 100644 index 0000000..26d6b38 --- /dev/null +++ b/script/test-tbl-company-native-api.js @@ -0,0 +1,125 @@ +import fs from 'node:fs/promises' + +const runtimePath = new URL('../pocket-base/bai_api_pb_hooks/bai_api_shared/config/runtime.js', import.meta.url) +const runtimeText = await fs.readFile(runtimePath, 'utf8') + +function readRuntimeValue(name) { + const match = runtimeText.match(new RegExp(`${name}:\\s*'([^']*)'`)) + if (!match) { + throw new Error(`未在 runtime.js 中找到 ${name}`) + } + return match[1] +} + +const baseUrl = readRuntimeValue('APP_BASE_URL') +const adminToken = readRuntimeValue('POCKETBASE_AUTH_TOKEN') + +function assert(condition, message) { + if (!condition) { + throw new Error(message) + } +} + +async function requestJson(path, options = {}) { + const res = await fetch(`${baseUrl}${path}`, options) + const text = await res.text() + let json = null + + try { + json = text ? JSON.parse(text) : null + } catch { + throw new Error(`接口 ${path} 返回了非 JSON 响应:${text}`) + } + + return { res, json } +} + +async function main() { + const knownCompanyId = 'WX-COMPANY-10001' + const initialOwnerOpenid = 'wx-owner-create' + const updatedOwnerOpenid = 'wx-owner-updated' + + const createResult = await requestJson('/pb/api/collections/tbl_company/records', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + company_name: '原生接口测试公司', + company_nationality: '中国', + company_nationality_code: 'CN', + company_province: '上海', + company_province_code: '310000', + company_city: '上海', + company_city_code: '310100', + company_district: '浦东新区', + company_district_code: '310115', + company_status: '有效', + company_owner_openid: initialOwnerOpenid, + }), + }) + + assert(createResult.res.ok, `公开创建失败:${JSON.stringify(createResult.json)}`) + assert(typeof createResult.json?.company_id === 'string' && createResult.json.company_id.startsWith('WX-COMPANY-'), '公开创建未自动生成 company_id') + assert(createResult.json?.company_owner_openid === initialOwnerOpenid, '公开创建返回的 company_owner_openid 不匹配') + assert(createResult.json?.company_city_code === '310100', '公开创建返回的 company_city_code 不匹配') + + const byCompanyIdResult = await requestJson( + `/pb/api/collections/tbl_company/records?filter=${encodeURIComponent(`company_id="${knownCompanyId}"`)}&perPage=1&page=1` + ) + + assert(byCompanyIdResult.res.ok, `按 company_id 查询失败:${JSON.stringify(byCompanyIdResult.json)}`) + assert(byCompanyIdResult.json?.totalItems === 1, `按 company_id 查询结果数量不为 1:${JSON.stringify(byCompanyIdResult.json)}`) + assert(byCompanyIdResult.json?.items?.[0]?.company_id === knownCompanyId, '按 company_id 查询返回了错误的记录') + + const listResult = await requestJson('/pb/api/collections/tbl_company/records?perPage=5&page=1') + assert(listResult.res.ok, `公开列表查询失败:${JSON.stringify(listResult.json)}`) + assert((listResult.json?.totalItems || 0) >= 1, '公开列表查询未返回任何数据') + + const createdCompanyId = createResult.json.company_id + const createdLookupResult = await requestJson( + `/pb/api/collections/tbl_company/records?filter=${encodeURIComponent(`company_id="${createdCompanyId}"`)}&perPage=1&page=1` + ) + assert(createdLookupResult.res.ok, `按 company_id 查询测试记录失败:${JSON.stringify(createdLookupResult.json)}`) + assert(createdLookupResult.json?.totalItems === 1, `按 company_id 查询测试记录数量不为 1:${JSON.stringify(createdLookupResult.json)}`) + + const createdRecordId = createdLookupResult.json.items[0].id + const updateResult = await requestJson(`/pb/api/collections/tbl_company/records/${createdRecordId}`, { + method: 'PATCH', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${adminToken}`, + }, + body: JSON.stringify({ + company_name: '原生接口测试公司-已更新', + company_owner_openid: updatedOwnerOpenid, + company_district: '徐汇区', + company_district_code: '310104', + }), + }) + assert(updateResult.res.ok, `按 company_id 定位后更新失败:${JSON.stringify(updateResult.json)}`) + assert(updateResult.json?.company_name === '原生接口测试公司-已更新', '更新后的 company_name 不正确') + assert(updateResult.json?.company_owner_openid === updatedOwnerOpenid, '更新后的 company_owner_openid 不正确') + assert(updateResult.json?.company_district_code === '310104', '更新后的 company_district_code 不正确') + + const cleanupResult = await requestJson(`/pb/api/collections/tbl_company/records/${createResult.json.id}`, { + method: 'DELETE', + headers: { + Authorization: `Bearer ${adminToken}`, + }, + }) + + assert(cleanupResult.res.ok, `测试清理失败:${JSON.stringify(cleanupResult.json)}`) + + console.log(JSON.stringify({ + createdCompanyId: createdCompanyId, + queriedCompanyId: byCompanyIdResult.json.items[0].company_id, + publicListTotalItems: listResult.json.totalItems, + updatedRecordId: createdRecordId, + updatedOwnerOpenid: updateResult.json.company_owner_openid, + updatedDistrictCode: updateResult.json.company_district_code, + cleanupDeletedId: createResult.json.id, + }, null, 2)) +} + +await main()