feat(personal-assistant): add confirmed project memory

This commit is contained in:
2026-07-19 08:19:24 +08:00
parent 151cc26caf
commit 24da833fa6
31 changed files with 4037 additions and 60 deletions
+53
View File
@@ -14,6 +14,7 @@
| 案例沉淀 `/knowledge/cases` | `GET /api/knowledge/case/capabilities`、`POST /api/knowledge/case/upload`、`/organize`、`/curate`、`GET /records`、`GET /records/{caseId}`、`POST /records/{caseId}/review`、`GET /records/{caseId}/media` | `/capabilities` 返回服务端判定的案例提交/查看能力,移动端不再向普通员工展示无权提交的素材表单;`/upload` 改为 multipart 真实语音上传并走 ASR,服务端只接受 MP3/WAV/M4A/WebM/OGG/AAC/FLAC,成功后原始音频写入 `sys_oss`,案例记录只保存 `mediaOssId` 供受保护媒体接口读取,不向客户端回传原始 `mediaUrl`;`/organize` 用真实转写调 chat 模型整理案例,未配置模型时按真实 transcript 本地结构化,并从背景外的真实摘要项提取学习点;APP 用户的项目范围从 `aihr_org_snapshot` 登录身份解析,上传、整理、入库、列表和详情均按项目范围校验,未完成正式组织映射时安全拒绝,不接受前端伪造项目范围;员工列表/详情只返回 `已入库` 案例,管理端系统用户保留全局运营视图;移动端和管理端案例详情通过受保护媒体接口回放原始音频,主管/项目负责人可提交脱敏点评;预渲染视频样片仍待正式媒体资产接入 |
| 案例媒体安全 | `GET /api/knowledge/case/records/{caseId}/media` | 案例详情只返回 `mediaOssId`,不返回原始 `sys_oss.url`;媒体下载会重复执行登录、后台角色或 APP 项目范围校验,再由服务端流式读取 OSS。管理端与 `mobile-uni` 通过鉴权 blob/temp 文件播放,关闭详情页时释放本地对象 URL |
| 问师傅与 SOP 知识库 `/pages/user/sop/index`、`/knowledge/sop` | `POST /api/knowledge/query`、`POST /api/knowledge/query-media`、`GET /api/knowledge/resources/{attachmentId}/content`、`POST /api/knowledge/answer-feedback`、`GET /api/knowledge/position-sop`、`GET /api/aihr/mobile/onboard/tasks`、`POST /api/aihr/mobile/onboard/tasks/{id}/complete`、`GET /api/aihr/mobile/qualification`、`POST /api/knowledge/doc/upload` | 文字/现场附件查询走统一知识空间授权和 MySQL Fulltext + Qdrant 混合召回;内部登录端可选传 `conversationId/contextVersion`,服务端保留 30 分钟不活跃过期、最近 6 轮脱敏截断上下文,用现有 chat 模型或保守规则输出 `QA/FILE/VIDEO/DATA_TOOL` 和 `rewrittenQuery`,旧版本冲突返回 `409`。原文件/视频仅从当前命中且仍有授权的附件返回,内容接口每次重新计算租户、应用和主体权限;无资源时明确返回空列表,不生成假链接。旧客户端和外部 API 保持单轮无状态。搜索继续返回 `reviewId/promptVersion`,员工反馈保存评审批次;详细边界见 [专项设计](20260718/问师傅多轮会话与原始资料交付设计.md)。员工学习页按当前 APP 身份读取正式岗前/入职任务,资格证据无正式数据时明确返回 `NOT_CONFIGURED` |
| 工作助手确认式项目记忆 `/pages/user/sop/index`、`/pages/user/assistant/memories` | 既有 `POST /api/knowledge/query` 可选返回 `memoryCandidate`;`GET /api/aihr/personal-assistant/memory-candidates?status=DRAFT`、`POST /api/aihr/personal-assistant/memory-candidates/{id}/confirm`、`POST .../{id}/dismiss`、`GET /api/aihr/service-memories`、`GET /api/aihr/service-memories/{id}` | 2026-07-19 最小切片只识别 `RESIDENT_NEED/FOLLOW_UP` 并保存到 `PROJECT_SERVICE`。用户文字或 ASR 转写进入同一候选检测;候选草稿不是长期记忆,只有当前员工显式确认后才按服务端解析出的 `tenant_id + project_code` 写入项目服务记录和版本表。多项目无法唯一判断时返回 `NEEDS_INPUT`,不能显示确认按钮;确认携带 `expectedVersion + idempotencyKey`,同键同内容重放返回原结果,同键不同内容或旧版本返回冲突。提醒默认关闭;首批不创建个人知识库、Qdrant collection,也不把个人想法写入项目域 |
| 资料处理 `/knowledge/processing` | `GET /api/knowledge/processing/overview`、`POST /api/knowledge/doc/upload-async`、`GET /api/knowledge/doc/upload-items`、`POST /api/knowledge/doc/upload-items/{id}/retry` | 已接入解析任务状态聚合;页面只保留“批量导入”,接口暂存+入队即秒回,后台 worker(并发 2)逐条解析/归类/向量化;ZIP 在 worker 内安全解压后把支持的子文件继续入同一批次队列,页面按批次轮询进度、失败可单文件重试;不提供浏览器目录选择或服务端目录导入入口 |
| 组织人员同步 | `POST /api/aihr/org/sync` | 从开放组织同步系统的 `/api/open/v1/sync/snapshot` 拉取 `company/department/employee` 快照,分页参数使用 `limit`;员工手机号只落 `person_phone` 用于移动端身份映射,不在组织列表响应暴露;岗位识别 `position/job_title/post/job_name/role/title` 等字段。`dryRun` 必须显式传入 `true`(预检)或 `false`(写入),省略或传 `null` 直接拒绝,避免空请求意外写库;默认 `replaceExisting=true`,写入前必须先用 `{"dryRun":true}`;dry-run 不访问本地快照表、不执行 DDL/写库,返回 `phoneLinked/maskedPhone/suspectText/warnings` 且 `syncedCount=0`。非 dry-run 覆盖写入遇到员工被跳过、手机号不完整、脱敏手机号或疑似乱码时默认拒绝,只有确认 dry-run 结果后显式传 `allowPartialReplace=true` 才允许覆盖;重复 `ext_party_id` 始终拒绝写入,因为数据库唯一键会折叠重复身份;`replaceExisting=false` 不触发不完整快照覆盖闸门,但仍拒绝重复身份。2026-07-15 线上已用 `/api/open/v1` 前缀完成配置、dry-run 和覆盖同步:开放平台返回公司 17、部门 963、员工 3417;生产 `aihr_org_snapshot` 为 3417 行,`phoneLinked=3392`、`maskedPhone=25`、`suspectText=0`,在职 2943、离职 474。当前剩余 25 人手机号不可用属于上游数据质量问题;功能发布不再被组织同步能力阻塞 |
| 移动端手机号登录 | `GET /resource/sms/code`、`POST /auth/mobile/sms-login` | 已复用 sms4j 阿里云配置 `config1` 和 RuoYi `sms` 授权策略;短信发送成功后才写 Redis 验证码;手机号不存在时自动注册 `app_user`;`aihr.sms.dev-fixed-code` 非空时不真发短信、验证码固定(dev 默认 `123456`)。prod 默认关闭,试点期只有同时设置 `AIHR_SMS_DEV_FIXED_CODE` 与 `AIHR_SMS_PROD_FIXED_CODE_ENABLED=true` 才启用固定码。 |
@@ -220,3 +221,55 @@ Content-Type: application/json
成功响应包含 `requestId`、`answer`、`citations`、`usedSpaceCodes`、`noEvidence` 和兼容 `legacy` 数据。引用携带 `spaceCode/sourceType/docId/title/snippet/fragmentId`;客户端不得把无引用回答包装成有知识依据的正式答案。
空间、授权和应用管理接口统一位于 `/api/knowledge/admin`:`spaces`、`spaces/{id}/grants`、`spaces/{id}/documents`、`apps`、`apps/{id}/spaces`、`apps/{id}/rotate-token`。`DELETE /spaces/{id}/documents/{attachId}` 只解绑当前空间成员;其他空间仍引用同一 OSS 时原文件不会删除。仅知识平台管理角色可调用,API_TOKEN 创建或轮换后只返回一次明文。完整初始化、令牌保管、legacy 迁移与回滚步骤见 [KNOWLEDGE_PLATFORM_RUNBOOK.md](KNOWLEDGE_PLATFORM_RUNBOOK.md)。
## 工作助手项目服务记忆最小切片
该切片复用上面的短会话请求,不新增另一套问答 session。登录员工说“记一下:3 栋 2 单元 1201 室住户想了解养老服务,十天后回访”时,`POST /api/knowledge/query` 在正常回答字段之外可返回:
```json
{
"memoryCandidate": {
"id": 301,
"version": 1,
"status": "DRAFT",
"memoryType": "RESIDENT_NEED",
"targetDomain": "PROJECT_SERVICE",
"draft": {
"buildingName": "3栋",
"unitName": "2单元",
"roomNo": "1201",
"category": "养老服务咨询",
"summary": "住户希望了解社区养老服务",
"occurredAt": "2026-07-19T14:00:00+08:00",
"followUpAt": null
},
"missingFields": [],
"expiresAt": "2026-07-20T14:00:00+08:00"
}
}
```
确认请求只提交用户在卡片上确认或修改的草稿;`projectCode` 即使传入也不能作为授权依据:
```http
POST /api/aihr/personal-assistant/memory-candidates/301/confirm
Authorization: Bearer <session-token>
Content-Type: application/json
{
"expectedVersion": 1,
"idempotencyKey": "memory-confirm-301-<stable-uuid>",
"enableReminder": false,
"draft": {
"buildingName": "3栋",
"unitName": "2单元",
"roomNo": "1201",
"category": "养老服务咨询",
"summary": "住户希望了解社区养老服务",
"occurredAt": "2026-07-19T14:00:00+08:00",
"followUpAt": null
}
}
```
服务端必须在事务中锁定候选,重新解析当前员工有效项目,写入 `aihr_service_memory` 与 `aihr_service_memory_version` 后再把候选改为 `CONFIRMED`。`DRAFT` 默认 24 小时过期;`NEEDS_INPUT` 只用于继续追问。列表、详情和后续问答召回必须同时过滤当前租户和授权 `project_code`,不得因为记录由本人创建而绕过项目权限。
+10 -4
View File
@@ -14,6 +14,8 @@
上述事实只证明本批版本和 schema 已发布。正式 OTP、真机能力、角色权限正反例和严格试点窗口仍需按发布后回归及 `DEMO_ACCEPTANCE.md` 验收。
2026-07-19 增量在上述基线上新增“项目服务记忆最小切片”:候选信息只有在当前员工明确确认后,才会写入其已授权项目的服务记录;本批不包含个人想法/经验的长期知识库、跨项目共享、自动提醒或向量化个人资料。迁移后结构门禁由 `35/35` 增至 `38/38`。
## 发布前置条件
1. 确认发布窗口、DB 备份和回滚负责人;迁移脚本包含 `ALTER TABLE`,不能在无备份状态执行。
@@ -48,9 +50,11 @@ mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aih
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260717_web_ai_question_reward_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260718_knowledge_conversation_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260718_release_collation_compat_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260718_unify_all_collations_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260719_service_memory_mysql8.sql
```
顺序原因:场景补充依赖三张场景/Rubric 基础表;五维迁移依赖场景和 Rubric;岗位/SOP/任务/资格迁移只补正式数据契约,不写业务行,也不代表岗位适用范围、任务规则或资格标准已经获得 HR 确认。住宅 SOP 和 Prompt 迁移只补内置内容,不代表内容已完成业务复核。知识会话迁移只新增短期上下文表,不生成业务对话。排序规则兼容迁移必须最后执行,它会读取目标库 `aihr_knowledge_info.tenant_id` 的实际排序规则,并将 19 张发布表统一到同一规则,兼容历史生产库与全新 MySQL 8 数据库。
顺序原因:场景补充依赖三张场景/Rubric 基础表;五维迁移依赖场景和 Rubric;岗位/SOP/任务/资格迁移只补正式数据契约,不写业务行,也不代表岗位适用范围、任务规则或资格标准已经获得 HR 确认。住宅 SOP 和 Prompt 迁移只补内置内容,不代表内容已完成业务复核。知识会话迁移只新增短期上下文表,不生成业务对话。排序规则兼容迁移先对齐本批发布表,全量排序规则迁移再统一历史 `aihr_*` 表;两者都读取目标库 `aihr_knowledge_info.tenant_id` 的实际排序规则。服务记忆迁移最后创建候选、正式记录和版本留痕三张表,同样对齐该规则,兼容历史生产库与全新 MySQL 8 数据库。
## 迁移后只读核验
@@ -74,7 +78,8 @@ WHERE table_schema = DATABASE()
'aihr_incentive_rule', 'aihr_points_ledger',
'aihr_candidate_material', 'aihr_interview_result', 'aihr_candidate_employee_link',
'aihr_position_responsibility', 'aihr_sop_applicability',
'aihr_onboard_task', 'aihr_qualification_gate'
'aihr_onboard_task', 'aihr_qualification_gate',
'aihr_memory_candidate', 'aihr_service_memory', 'aihr_service_memory_version'
)
ORDER BY table_name;
@@ -117,14 +122,15 @@ GROUP BY question_type
ORDER BY question_type;
```
预期是三张场景/Rubric 表、岗位题库、训练反馈表、五张考试表、两张全网检索表、问题榜/激励六张表、三张证据表、知识平台治理表、三张候选人/面试表和四张岗位/SOP/任务/资格契约表均存在;会话证据字段为 `8/8`,专项批量/内容快照字段为 `8/8`,候选资料审核字段为 `2/2`,面试复核字段为 `6/6`,字段查询覆盖所有列,内置 Prompt 数量为 `6`。五个岗位各有 `20` 个场景,`daily/special` 各有 `100` 道题;其中 `14` 个未完成正式审核的高风险场景及其 `28` 道题保持禁用。岗位/SOP/任务/资格表为空是允许的;这只证明 schema/seed 迁移完成,不证明正式岗位规则、资格 gating 或试点完成。
预期是三张场景/Rubric 表、岗位题库、训练反馈表、五张考试表、两张全网检索表、问题榜/激励六张表、三张证据表、知识平台治理表、三张候选人/面试表、四张岗位/SOP/任务/资格契约表,以及三张项目服务记忆表均存在;会话证据字段为 `8/8`,专项批量/内容快照字段为 `8/8`,候选资料审核字段为 `2/2`,面试复核字段为 `6/6`,字段查询覆盖所有列,内置 Prompt 数量为 `6`。五个岗位各有 `20` 个场景,`daily/special` 各有 `100` 道题;其中 `14` 个未完成正式审核的高风险场景及其 `28` 道题保持禁用。岗位/SOP/任务/资格表及新服务记忆表为空是允许的;这只证明 schema/seed 迁移完成,不证明已有员工确认过记忆、完整个人知识库或严格试点完成。
## 迁移后应用回归
1. 重启 `wygj-aihr.service`,确认状态为 `active`。
2. 检查根站、`/h5/`、租户接口和员工首页 API 的 HTTP 与业务码均为 `200`。
3. 用正式测试账号完成员工端训练、主管复盘和管理端场景/Prompt 只读检查;不要使用开发固定验证码验证生产安全策略。
4. 迁移完成后再按同一正式时间窗运行:
4. 用具备单一项目授权的正式测试账号,在“工作助手”说出一条包含房号、住户需求和跟进时间的测试信息;确认候选卡前数据库不得出现正式服务记录,确认后只能写入该账号已授权的 `project_code`。随后新开对话查询该事项,必须能召回;双击确认不得重复写入。测试结束保留审计证据,不直接删除生产业务记录。
5. 迁移完成后再按同一正式时间窗运行:
```bash
AIHR_PILOT_TENANT_ID=000000 \
+2
View File
@@ -21,6 +21,7 @@
| 5 | 案例沉淀 `/knowledge/cases` | 上传真实语音 → ASR 转写 → AI 整理 → 送审 → 入库 → 查看案例详情/回放原始音频 | 已完成语音转写、原始音频 OSS 留痕、整理、入库、学习点、主管点评和按项目权限查看持久化转写/摘要详情;预渲染视频样片仍未接入,不把音频回放当作“查看样片”完成证据 |
| 6 | SOP知识库 `/knowledge/sop` | 上传 txt/md/PDF/Word/Excel/PPT 文档或使用 seed 文档 → 检索 → 生成训练题 | 已完成闭环、引用 SOP 原文片段、训练题已生成 |
| 7 | 资料处理 `/knowledge/processing` | 打开页面 → 查看解析任务 → 查看处理链路 → 点“批量导入”选择少量文件或 ZIP | 可看到资料总量、等待/解析中/完成/失败、片段数、向量化状态、批次进度和事件列表;页面不出现“选择目录”“服务端导入” |
| 8 | 员工端工作助手 `/h5/#/pages/user/sop/index` | 语音或文字说“记一下:3 栋 2 单元 1201 室住户想了解养老服务,十天后回访” → 查看候选卡 → 先暂不保存,再重新生成并确认 → 打开“我的资料与记忆” → 新对话按房号查询 | 一级只显示“工作助手 / 查全网”;候选卡可修改、暂不保存、确认保存,提醒默认关闭;暂不保存不写正式记录,确认后只写当前授权项目且双击不重复;新对话能按项目权限召回并展示来源。该项只验收项目服务记忆最小切片,不代表完整个人资料库已交付 |
## AI陪练二期增量脚本
@@ -48,5 +49,6 @@
## 不演示
- 不演示 SSO、权限配置、知识图谱、数字人视频生成、绩效挂钩。
- 不把“项目服务记忆最小切片”演示成完整个人助理:个人文档/网页收藏、个人经验知识库、PPT 生成、自动提醒和项目服务记忆 Qdrant 向量化仍不在本批范围。
- 不承诺管理端 AI 面试、案例沉淀已写入正式业务数据库;三角色对练(管理端与移动端)记录写入 `aihr_practice_session`;候选人补充资料写 `sys_oss` + `aihr_candidate_material`,HR 可审核状态,但不做复杂审批流转;SOP 检索与文档上传使用 `aihr_knowledge_*` 本地表,失败时回退 seed。
- 大模型不作为演示硬依赖:模型管理已启用 chat 模型时,三角色对练为真实 LLM 生成与评分(asr/tts 配置后语音输入/播报可用);未配置或现场调用失败时全链路自动回退 seed,演示不中断。
+7
View File
@@ -219,6 +219,7 @@ RELEASE_REMOTE_URL=https://peilian.njzhmj.top RELEASE_VERIFY_REMOTE_MATCH=true R
- SOP知识库:进入 `/knowledge/sop`,可上传 txt/md/PDF/Word/Excel/PPT 文档入库;点击“检索” → “生成训练题”,应看到“已完成闭环”、命中数据库 SOP 原文片段和训练题;数据库不可用时页面回退 seed。
- 资料处理:进入 `/knowledge/processing`,应看到资料总量、解析任务表、处理链路、规则与风险;页面只保留“批量导入”和“刷新”,不应出现“选择目录”“服务端导入”或目录导入任务面板。**批量导入走异步队列**:提交即返回,页面出现“本次批量上传”进度面板(排队/加工中/完成/失败 + 单条重试),后台 worker 并发 2 逐条解析入库;支持多文件和 ZIP,暂存目录默认 `./.data/staging`(`aihr.upload.staging` 覆盖)。
- uni-app 员工端:进入 `https://wygj-mobile-uni.localhost/h5/#/pages/user/today/index`,手机号登录(dev 验证码固定 `123456`)。今日页进入问师傅、练习、案例素材和个人页;练习页应能完成开始练习、提交回应、结束评分、每日三题提交,个人页同步训练历史和成长证据包。每日三题正式按组织快照 `hire_date` 判断入职三个月窗口;本地 Demo 若快照尚无该字段,仅由 `dev/local` profile 且 `application-dev.yml` 的 `aihr.practice.allow-legacy-daily-drill-fallback=true` 启用训练次数回退,生产 profile 即使误传环境变量也强制关闭。
- 工作助手项目服务记忆:`/h5/#/pages/user/sop/index` 一级入口只保留“工作助手 / 查全网”,既有文字、ASR、媒体、数据工具和 30 分钟/最近 6 轮短会话链保持不变;`/h5/#/pages/user/assistant/memories` 展示待确认候选和当前项目可见的服务记录。首批只落 `aihr_memory_candidate`、`aihr_service_memory`、`aihr_service_memory_version` 三张关系表,不依赖新的 Qdrant collection。候选卡未确认前不得查询到正式记录,提醒默认关闭。
- 移动端登录后若组织快照接口返回 `401/403`,只降级为手动岗位确认,不应清除手机号登录态;岗位确认页仍需允许员工选择“生活顾问”后继续进入业务页。
- uni-app 主管端:进入 `https://wygj-mobile-uni.localhost/h5/#/pages/supervisor/index/index`,应看到团队概览、团队画像、团队预警、指派专项、待复盘和复盘详情;从团队画像派专项后,最近专项应立即回读新记录。
- uni-app 候选人端:进入 `https://wygj-mobile-uni.localhost/h5/#/pages/candidate/index/index`,手机号登录(dev 验证码固定 `123456`)→ “开始面试/面试练习”完成答题评分;“补充资料”选择 PDF/Word/图片后上传,应看到资料状态。Codex 内置浏览器不支持本地文件选择时,用真实 HTTP multipart smoke 代替浏览器文件选择。
@@ -228,6 +229,12 @@ RELEASE_REMOTE_URL=https://peilian.njzhmj.top RELEASE_VERIFY_REMOTE_MATCH=true R
```bash
./scripts/demo-check.sh
bash scripts/tests/aihr-schema-migrations.test.sh
node --test mobile-uni/tests/personal-assistant.test.mjs
API_BASE=https://wygj-api.localhost ./scripts/personal-assistant-smoke.sh
# 登录后只读核验:TOKEN=<mobile-access-token> API_BASE=https://wygj-api.localhost ./scripts/personal-assistant-smoke.sh
```
工作助手记忆本地人工回归至少覆盖:以有且只有一个 `project_code` 的员工登录;说出完整房屋定位、需求和时间后看到 `DRAFT` 卡;刷新“我的资料与记忆”仍能恢复;点击“暂不保存”不产生正式记录;重新生成后双击“确认保存”只写一条记录和一个初始版本;新开对话可按房号或事项召回。再用无项目、多个项目无法唯一判断及另一项目账号做反例,分别应为安全拒绝/`NEEDS_INPUT`/不可见。
完整演示脚本与录屏兜底见 [DEMO_ACCEPTANCE.md](DEMO_ACCEPTANCE.md)。
+20 -16
View File
@@ -70,7 +70,7 @@
| 全文检索 | 使用个人片段表 MySQL Fulltext | 查询必须包含 `tenant_id + owner_user_id` |
| 向量检索 | 新建 `aihr_personal_knowledge` collection | Qdrant filter 必须包含 `tenant_id + owner_user_id` |
| 企业知识检索 | 调用现有企业 RAG 服务 | 继续执行企业知识密级、项目和角色权限 |
| 项目业务记忆 | 独立 `aihr_service_memory` 关系表与同名 Qdrant collection | 查询必须包含 `tenant_id + project_id`,不得写入个人或企业知识表 |
| 项目业务记忆 | 独立 `aihr_service_memory` 关系表;关系库检索为 2026-07-19 最小切片,Qdrant 仅列为后续增强 | 查询必须包含 `tenant_id + project_code`,不得写入个人或企业知识表 |
禁止将个人资料写入:
@@ -87,7 +87,7 @@
→ AUTO: 意图路由只在三个内部授权域中选择;MANUAL: 校验用户指定的内部范围
→ 校验日期、itemIds、知识范围和配额
→ personal: 个人 Fulltext + 个人 Qdrant(强制 owner filter)
→ project_service: 项目业务记忆 Fulltext + 独立 Qdrant(强制 project filter)
→ project_service: 首批用关系库检索并强制 project_code filter;后续再叠加独立 Qdrant
→ enterprise: 企业 Fulltext + 企业 Qdrant(强制企业权限)
→ mixed: 所选域分别检索并授权后融合,不做跨域原始结果直连
→ Rerank
@@ -197,8 +197,8 @@ org.dromara.aihr.knowledge.parse
### 3.4 项目业务记忆权限
1. `project_id` 必须由当前登录员工的有效项目任职解析,客户端不得指定越权项目。
2. 项目业务记忆的列表、详情、修改和召回必须同时过滤 `tenant_id + project_id`,创建人不因“本人记录”获得跨项目访问权。
1. `project_code` 必须由当前登录员工的有效项目任职解析,沿用 `aihr_org_snapshot.project_code` 和现有知识授权口径;客户端不得指定越权项目。
2. 项目业务记忆的列表、详情、修改和召回必须同时过滤 `tenant_id + project_code`,创建人不因“本人记录”获得跨项目访问权。
3. 住户姓名、电话、身份证件和健康信息不作为自由文本检索字段;首批优先保存房屋定位、需求摘要、跟进状态和必要说明。
4. 发送外部模型前先由服务端提取并令牌化可识别信息;模型输出不得覆盖服务端保存的主体定位。
5. 员工修改已确认记录必须携带 `expectedVersion`,成功后新增版本快照,不原地抹除历史。
@@ -305,8 +305,8 @@ Message 保存:
| 字段 | 类型 | 说明 |
|---|---|---|
| id | bigint PK | 候选 ID |
| tenant_id/owner_user_id/project_id | varchar/bigint | 当前身份与候选项目范围 |
| source_session_id/source_message_id | bigint | 来源会话与消息 |
| tenant_id/owner_user_id/project_code | varchar | 当前身份与候选项目范围;项目键使用现有组织快照的 `project_code`,不是自造数值 ID |
| source_conversation_id/source_request_id | varchar(64) | 来源短会话与本次问答请求;当前「问」链没有持久化消息 ID |
| candidate_hash | varchar(64) | 来源 + 规范化草稿哈希,用于防重复 |
| memory_type | varchar(30) | PERSONAL_NOTE / WORK_IDEA / RESIDENT_NEED / FOLLOW_UP |
| target_domain | varchar(30) | PERSONAL / PROJECT_SERVICE |
@@ -314,11 +314,13 @@ Message 保存:
| draft_json | json | 可编辑结构化草稿;不得包含未脱敏模型原始响应 |
| missing_fields_json | json | 仍需追问的字段 |
| confidence | decimal(5,4) | 只决定是否建议展示,不决定自动保存 |
| version | int | 乐观锁版本,从 1 开始 |
| confirm_idempotency_key/confirm_request_hash | varchar | 确认写入的稳定幂等键及请求内容哈希 |
| target_id | bigint null | 确认后生成的个人 item 或业务记忆 ID |
| expires_at/confirmed_at | datetime null | 草稿过期与确认时间 |
| create_time/update_time | datetime | 审计时间 |
约束:`UNIQUE(tenant_id, owner_user_id, source_message_id, candidate_hash)`;DRAFT 默认 24 小时过期,DISMISSED/EXPIRED 的 `draft_json` 在 24 小时内物理清理。
约束:`UNIQUE(tenant_id, owner_user_id, source_request_id, candidate_hash)`;DRAFT 默认 24 小时过期,DISMISSED/EXPIRED 的 `draft_json` 在 24 小时内物理清理。
### 4.8 `aihr_service_memory`
@@ -327,7 +329,7 @@ Message 保存:
| 字段 | 类型 | 说明 |
|---|---|---|
| id | bigint PK | 业务记忆 ID |
| tenant_id/project_id | varchar/bigint | 租户与项目数据范围 |
| tenant_id/project_code | varchar | 租户与现有项目授权键 |
| subject_type | varchar(20) | RESIDENT / LOCATION |
| resident_external_id | varchar(100) null | 仅权威住户主数据接入后保存;不得自行生成 |
| building_name/unit_name/room_no | varchar | 员工确认的结构化房屋定位 |
@@ -336,12 +338,12 @@ Message 保存:
| status | varchar(20) | OPEN / IN_PROGRESS / RESOLVED / VOID |
| occurred_at | datetime | 事件发生或获知时间 |
| follow_up_at | datetime null | 用户单独确认的跟进时间;空值不产生提醒 |
| source_session_id/source_message_id | bigint | 可追溯来源 |
| source_conversation_id/source_request_id | varchar(64) | 可追溯来源 |
| created_by/confirmed_by | bigint | 记录人与确认人 |
| version | int | 乐观锁版本,从 1 开始 |
| version | int | 当前乐观锁版本,从 1 开始 |
| create_time/update_time | datetime | 审计时间 |
索引至少包含 `(tenant_id, project_id, status, follow_up_at)`、`(tenant_id, project_id, building_name, unit_name, room_no)`;全文检索只覆盖 `title/summary/detail`,向量 payload 强制包含 `tenant_id + project_id + memory_id + status`。
索引至少包含 `(tenant_id, project_code, status, follow_up_at)`、`(tenant_id, project_code, building_name, unit_name, room_no)`;首批关系库检索覆盖 `title/summary/detail`,后续若启用向量检索,payload 强制包含 `tenant_id + project_code + memory_id + status`。
### 4.9 `aihr_service_memory_version`
@@ -351,11 +353,12 @@ Message 保存:
### 5.1 Qdrant
- collections:个人资料使用 `aihr_personal_knowledge`;项目业务记忆使用 `aihr_service_memory`,两者不混 collection。
- 完整阶段目标:个人资料使用 `aihr_personal_knowledge` collection;项目业务记忆使用独立 `aihr_service_memory` collection,两者不混 collection。
- 2026-07-19 项目服务记忆最小切片只使用 MySQL 关系库,不创建新的 Qdrant collection;不得因未做向量化而回退到无权限过滤的企业 collection。
- 向量维度:跟随当前启用的 vector 模型;模型或维度变化使用独立重建任务。
- 个人 payload:`tenant_id`、`owner_user_id`、`space_id`、`item_id`、`fragment_id`、`source_type`、`captured_at`;必建 index 为 `tenant_id`、`owner_user_id`、`item_id`。
- 项目业务记忆 payload:`tenant_id`、`project_id`、`memory_id`、`status`、`occurred_at`、`updated_at`;必建 index 为 `tenant_id`、`project_id`、`memory_id`、`status`。
- 个人查询无 owner filter、项目业务查询无 project filter 时,检索服务直接拒绝执行并记录安全日志。
- 项目业务记忆后续 payload:`tenant_id`、`project_code`、`memory_id`、`status`、`occurred_at`、`updated_at`;必建 index 为 `tenant_id`、`project_code`、`memory_id`、`status`。
- 个人查询无 owner filter、项目业务查询无 project_code filter 时,检索服务直接拒绝执行并记录安全日志。
- `aihr_memory_candidate` 的任何状态都不写 Qdrant;只有关系库确认事务提交成功后才异步索引目标记录。
### 5.2 OSS
@@ -508,7 +511,7 @@ Message 保存:
"targetDomain": "PROJECT_SERVICE",
"memoryType": "RESIDENT_NEED",
"subject": {
"projectId": 88,
"projectCode": "YC-RESIDENTIAL-03",
"buildingName": "3栋",
"unitName": "2单元",
"roomNo": "1201"
@@ -520,7 +523,7 @@ Message 保存:
}
```
服务端必须忽略请求中的 `projectId` 作为授权依据,仅用它与当前登录态可访问项目做一致性校验。确认成功返回 `targetDomain/targetId/version`;同一 `idempotencyKey` 重放必须返回同一结果,不重复创建记录。
服务端必须忽略请求中的 `projectCode` 作为授权依据,仅用它与当前登录态可访问项目做一致性校验;客户端可省略该字段。确认成功返回 `targetDomain/targetId/version`;同一 `idempotencyKey` 重放必须返回同一结果,不重复创建记录,同一键配不同请求体必须拒绝。
## 7. 网页采集安全
@@ -759,6 +762,7 @@ src/services/personal-assistant.ts API 客户端
- `backend/script/sql/aihr_personal_knowledge_mysql8.sql`
- `backend/script/sql/aihr_service_memory_mysql8.sql`
- `backend/script/sql/update/aihr_20260719_service_memory_mysql8.sql`(项目服务记忆最小切片生产增量)
- `backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/personal/**`
- `backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/memory/**`(候选识别、确认编排与项目业务记忆)
- `backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/personal/**`