Files
prop-ai-hr/docs/个人AI助理阶段二专项TechSpec.md
T

594 lines
25 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 员工个人 AI 助理 / 个人知识空间专项 TechSpec
> 版本:v1.0 | 日期:2026-07-11
>
> 归属:《银城员工端 APP 分阶段实施总纲》阶段二
>
> 需求基线:《物业 AI 人力资源系统业务需求文档 BRD》4.8
>
> 实施边界:首批交付个人资料收藏、检索、带引用问答和工作整理;PPT 生成为 P1;北森、考勤等外部数据归阶段三。
## 1. 目标与非目标
### 1.1 目标
为每名已登录员工提供与企业知识库隔离的个人知识空间,支持:
1. 收藏个人文字、文件、图片和网页链接。
2. 自动解析正文、生成摘要与主题标签。
3. 按日期、主题、来源和关键词检索个人资料。
4. 在授权范围内选择“个人资料 / 企业知识 / 混合”进行问答。
5. 输出结论、行动项和待确认事项,并逐条展示引用来源。
6. 删除个人资料时同步清理关系库、对象存储和向量索引。
### 1.2 非目标
- 阶段二首批不生成 PPT 文件,只生成可编辑汇报提纲。
- 不读取北森、考勤、请假、任职等阶段三外部数据。
- 不做通用网盘、多人协作文档、在线 Office 编辑和自动对外发送。
- 不允许个人资料自动进入企业知识库。
- 不通过“收藏网页”绕过登录、付费墙、企业内网或版权限制。
- 不复用企业知识表增加 `scope_type` 来承载个人数据。
## 2. 核心架构决策
### 2.1 处理能力复用,数据物理分域
| 能力 | 复用方式 | 隔离要求 |
|---|---|---|
| 文件上传与 OSS | 复用 `ISysOssService` | 使用私有前缀 `personal/{tenantId}/{userId}/{itemId}/` |
| 文档解析 | 从 `AihrSopSeedService` 抽取无状态解析组件 | 解析结果只能写个人表 |
| Embedding / Rerank / LLM | 复用现有模型配置与成本闸门 | 调用前按个人资料规则脱敏,不写企业片段表 |
| 全文检索 | 使用个人片段表 MySQL Fulltext | 查询必须包含 `tenant_id + owner_user_id` |
| 向量检索 | 新建 `aihr_personal_knowledge` collection | Qdrant filter 必须包含 `tenant_id + owner_user_id` |
| 企业知识检索 | 调用现有企业 RAG 服务 | 继续执行企业知识密级、项目和角色权限 |
禁止将个人资料写入:
- `aihr_knowledge_info`
- `aihr_knowledge_attach`
- `aihr_knowledge_fragment`
- 企业 Qdrant collection `aihr_knowledge`
### 2.2 检索流程
```text
用户问题 + scope(personal / enterprise / mixed)
→ 从移动端 token 取得 tenantId、userId、extPartyId
→ 校验日期、itemIds、知识范围和配额
→ personal: 个人 Fulltext + 个人 Qdrant(强制 owner filter)
→ enterprise: 企业 Fulltext + 企业 Qdrant(强制企业权限)
→ mixed: 两域分别检索并授权后融合,不做跨域原始结果直连
→ Rerank
→ LLM 生成答案
→ 返回 citations,每条标记 PERSONAL / ENTERPRISE / WEB
→ 保存会话、答案、引用和 prompt/model 版本
```
### 2.3 模块边界
后端新增独立包,避免继续扩大 `AihrSopSeedService`:
```text
org.dromara.aihr.personal
├── controller/PersonalAssistantController.java
├── domain/PersonalAssistantDto.java
├── service/PersonalSpaceService.java
├── service/PersonalIngestionService.java
├── service/PersonalRetrievalService.java
├── service/PersonalAnswerService.java
├── service/PersonalCleanupService.java
├── service/PersonalUrlFetchService.java
└── support/PersonalKnowledgeProperties.java
```
解析器抽为共享、无状态能力:
```text
org.dromara.aihr.knowledge.parse
├── KnowledgeDocumentParser.java
├── ParsedDocument.java
└── KnowledgeChunker.java
```
共享解析器只接受文件/字节和解析参数,只返回内存对象,不知道企业表、个人表或 Qdrant collection。
## 3. 身份、权限与数据隔离
### 3.1 所有者身份
- `owner_user_id`:本地 `sys_user.user_id`,作为阶段二强制所有权键。
- `owner_ext_party_id`:可空,保存组织快照外部主体 ID,供阶段三身份迁移使用。
- `tenant_id`:沿用 RuoYi 多租户上下文。
- Controller 不接受客户端提供的 `ownerUserId`、`tenantId`。
- Service 每次查询都从当前登录态取得 `tenant_id + user_id`,不得只按主键查询。
### 3.2 访问矩阵
| 操作 | 本人 | 普通主管 | HR/运营 | 系统管理员 |
|---|---:|---:|---:|---:|
| 查看个人资料正文 | ✅ | ❌ | ❌ | ❌(默认) |
| 查看个人资料数量/处理状态 | ✅ | ❌ | 聚合且脱敏 | 聚合且脱敏 |
| 删除本人资料 | ✅ | ❌ | ❌ | ❌(默认) |
| 申请分享/企业入库 | ✅ | ❌ | 审核申请 | 审核申请 |
| 紧急审计查看 | ❌ | ❌ | 需 `aihr:personal:audit`、工单号和原因 | 同左 |
紧急审计必须记录审计人、目标用户、itemId、原因、工单号、时间和结果;正文不写入审计日志。
### 3.3 防越权规则
1. 所有 `/api/aihr/personal-assistant/**` 接口必须登录,不使用 `@SaIgnore`。
2. 单项读取、删除、重试必须使用 `WHERE id=? AND tenant_id=? AND owner_user_id=?`。
3. Qdrant 查询必须同时过滤 `tenant_id` 和 `owner_user_id`。
4. 企业知识结果进入融合前必须经过企业权限过滤;客户端传来的 fragmentId 不能直接作为引用。
5. 个人资料转企业知识必须复制经审核后的脱敏版本,不能把个人 item 直接改成企业 scope。
6. 自动化测试必须使用两个用户交叉访问,验证列表、详情、检索、向量和删除均返回不可见。
企业知识授权由唯一生产策略 `OrgSnapshotEnterpriseKnowledgeAccessPolicy` 负责:只用登录态
`tenant_id + user_id` 查询本人 `sys_user.phonenumber`,再匹配同租户、在职的 `aihr_org_snapshot`,
最后按 `aihr_knowledge_acl` 的 `TENANT / PROJECT / POSITION` 规则生成 fragment allowlist。岗位别名
`生活顾问 / 物业管家 / 客服管家` 只在服务端归一化;无组织快照、离职、无启用 ACL、表缺失或数据库故障均默认拒绝。
这里消费的是阶段二本地/定时同步快照,不代表阶段三北森实时对接已经完成。
## 4. 数据模型
初始化 SQL:`backend/script/sql/aihr_personal_knowledge_mysql8.sql`。
企业 ACL 独立位于 `backend/script/sql/aihr_knowledge_mysql8.sql` 的 `aihr_knowledge_acl`,不向个人表添加
企业 scope。ACL 以租户和 knowledge 为边界,可按租户、项目或岗位授权,并保留 `INTERNAL / RESTRICTED`
密级字段;没有启用 ACL 的企业知识默认不可见。当前 seed 仅将 SOP `1001/1002/1003` 明确授权给一线
生活顾问岗位,没有全租户放开。阶段二尚无账号密级映射,因此只允许 `INTERNAL`;`RESTRICTED` 和未知密级
继续默认拒绝,待显式账号密级授权映射完成后再开放。
### 4.1 `aihr_personal_space`
每个租户内每名员工一条。
| 字段 | 类型 | 说明 |
|---|---|---|
| id | bigint PK | 主键 |
| tenant_id | varchar(20) | 租户 |
| owner_user_id | bigint | 本地用户 ID |
| owner_ext_party_id | varchar(100) null | 外部主体 ID |
| status | varchar(20) | ACTIVE / FROZEN / DELETING |
| quota_bytes | bigint | 默认 524288000(500MB) |
| used_bytes | bigint | 已用空间,事务内维护 |
| item_count | int | 未删除资料数 |
| create_time/update_time | datetime | 审计时间 |
约束:`UNIQUE(tenant_id, owner_user_id)`。
### 4.2 `aihr_personal_item`
| 字段 | 类型 | 说明 |
|---|---|---|
| id | bigint PK | 资料 ID |
| tenant_id/space_id/owner_user_id | bigint/varchar | 冗余所有权键,便于强制过滤 |
| source_type | varchar(20) | TEXT / FILE / IMAGE / URL |
| title | varchar(500) | 标题 |
| original_url | varchar(2000) null | 原网页地址 |
| oss_id | bigint null | 原文件或网页快照 OSS ID |
| mime_type | varchar(100) | MIME |
| size_bytes | bigint | 原始大小 |
| content_hash | varchar(64) | SHA-256 去重 |
| status | varchar(20) | QUEUED / PARSING / READY / FAILED / DELETING / DELETED |
| error_code/error_message | varchar | 可公开的失败信息,不存堆栈 |
| attempt_count | int | 解析尝试次数,默认 0,每次执行前加 1 |
| summary | text | 自动摘要 |
| tags_json | json | 主题标签数组 |
| captured_at | datetime | 内容产生/网页抓取时间 |
| parsed_at | datetime null | 解析完成时间 |
| deleted_at | datetime null | 逻辑删除时间 |
| create_time/update_time | datetime | 审计时间 |
索引:
- `(tenant_id, owner_user_id, status, create_time)`
- `(tenant_id, owner_user_id, captured_at)`
- `(space_id, content_hash)`
### 4.3 `aihr_personal_fragment`
| 字段 | 类型 | 说明 |
|---|---|---|
| id | bigint PK | 片段 ID |
| tenant_id/space_id/owner_user_id/item_id | bigint/varchar | 所有权与父项 |
| idx | int | 片段序号 |
| content | text | 正文片段 |
| token_count | int | token 数 |
| embedding_json | longtext null | Qdrant 不可用时的本地兜底 |
| embedding_model | varchar(100) null | 模型 |
| embedding_time | datetime null | 向量时间 |
| create_time | datetime | 创建时间 |
约束:`UNIQUE(item_id, idx)`;Fulltext 索引仅覆盖个人表。
### 4.4 `aihr_personal_chat_session` 与 `aihr_personal_chat_message`
Session 保存 `id/tenant_id/owner_user_id/title/default_scope/create_time/update_time`。
Message 保存:
- `session_id/owner_user_id/role`
- `content`
- `scope_json`
- `citations_json`
- `model_name/prompt_version`
- `input_tokens/output_tokens/latency_ms`
- `create_time`
个人消息只允许本人读取和删除;运营报表仅统计数量、token 和延迟,不读取 content。
### 4.5 `aihr_personal_publish_request`(P1)
个人资料申请进入团队或企业知识库的审核记录:`item_id/applicant_user_id/target_scope/reason/status/reviewer_user_id/review_comment/review_time/published_knowledge_id`。
状态固定为 `PENDING / APPROVED / REJECTED / CANCELLED`。批准后生成新的企业知识附件和片段,保留来源链,不改变个人 item 所有权。
### 4.6 `aihr_personal_audit_log`
紧急审计专用不可变日志:`tenant_id/auditor_user_id/target_owner_user_id/item_id/action/reason/ticket_no/result/create_time`。日志不保存个人正文、附件 URL、问题或答案内容;业务接口不提供更新和删除操作。
## 5. Qdrant 与 OSS 设计
### 5.1 Qdrant
- collection:`aihr_personal_knowledge`
- 向量维度:跟随当前启用的 vector 模型;模型或维度变化使用独立重建任务。
- payload:`tenant_id`、`owner_user_id`、`space_id`、`item_id`、`fragment_id`、`source_type`、`captured_at`。
- 必建 payload index:`tenant_id`、`owner_user_id`、`item_id`。
- 查询无 owner filter 时,`PersonalRetrievalService` 直接拒绝执行并记录安全日志。
### 5.2 OSS
- 路径:`personal/{tenantId}/{userId}/{itemId}/{safeFileName}`。
- bucket 保持私有;下载只能通过鉴权接口返回短时签名 URL。
- 原文件、网页正文快照和生成导出文件使用不同子目录。
- 文件名、Content-Type 和 multipart header 继续执行 CRLF 清洗。
- `sys_oss.ext1` 只保存解析状态和 personal itemId,不保存个人正文或手机号。
## 6. API 契约
统一前缀:`/api/aihr/personal-assistant`。响应沿用 `R<T>`。
### 6.1 空间与资料
| 方法 | 路径 | 请求/说明 |
|---|---|---|
| GET | `/space` | 当前用户空间、配额、已用量、资料数 |
| GET | `/items` | `pageNum/pageSize/status/sourceType/dateFrom/dateTo/keyword` |
| POST | `/items/text` | `{title, content, capturedAt?, tags?}` |
| POST | `/items/file` | multipart `file`, `title?`, `capturedAt?` |
| POST | `/items/url` | `{url, title?, capturedAt?}`,异步抓取 |
| GET | `/items/{id}` | 详情、解析状态、摘要、标签;所有权校验 |
| POST | `/items/{id}/retry` | 仅 FAILED 可重试 |
| DELETE | `/items/{id}` | 返回 cleanupJobId,进入 DELETING |
| GET | `/items/{id}/download-url` | 返回 5 分钟私有签名 URL |
创建响应:
```json
{
"itemId": 1201,
"status": "QUEUED",
"duplicateOf": null
}
```
### 6.2 搜索与问答
`POST /search`
```json
{
"queryText": "7月1日至7月10日我收藏了哪些保洁管理资料",
"scope": ["PERSONAL"],
"dateFrom": "2026-07-01",
"dateTo": "2026-07-10",
"itemIds": [],
"limit": 10
}
```
`POST /ask`
```json
{
"sessionId": null,
"queryText": "结合我的资料和企业制度,整理本周保洁管理改进建议",
"scope": ["PERSONAL", "ENTERPRISE"],
"dateFrom": "2026-07-01",
"dateTo": "2026-07-10",
"itemIds": [],
"outputFormat": "ACTION_PLAN"
}
```
响应:
```json
{
"sessionId": 301,
"answer": "结论……\n行动项……\n待确认事项……",
"citations": [
{
"domain": "PERSONAL",
"sourceId": "item:1201:fragment:3",
"title": "保洁班组周记录.xlsx",
"excerpt": "……",
"capturedAt": "2026-07-08T09:30:00"
},
{
"domain": "ENTERPRISE",
"sourceId": "knowledge:1:fragment:88",
"title": "住宅保洁作业标准",
"excerpt": "……",
"capturedAt": null
}
],
"model": "configured-chat-model",
"promptVersion": "personal_assistant_v1"
}
```
若无可用引用,返回明确的“当前资料中没有足够依据”,不得生成无引用的业务结论。
### 6.3 会话
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | `/sessions` | 当前用户会话列表 |
| GET | `/sessions/{id}` | 消息与引用 |
| DELETE | `/sessions/{id}` | 删除本人会话与消息 |
### 6.4 分享与导出(P1)
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | `/items/{id}/publish-requests` | 申请进入团队/企业知识库 |
| GET | `/publish-requests` | 本人申请进度 |
| GET | `/admin/publish-requests` | HR/运营按 PENDING/APPROVED/REJECTED 查询审核列表 |
| POST | `/admin/publish-requests/{id}/review` | HR/运营批准或驳回;批准后生成脱敏企业知识副本 |
| POST | `/exports/outline` | 生成可编辑汇报大纲 |
| POST | `/exports/pptx` | 根据已确认大纲和模板异步生成 PPTX |
| GET | `/exports/{id}` | 生成状态与下载地址 |
### 6.5 运营聚合与紧急审计
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | `/admin/metrics` | 仅返回用户数、资料数、状态、容量、失败率等聚合信息,不返回标题和正文 |
| POST | `/admin/items/{id}/audit-view` | 需要 `aihr:personal:audit` 权限、非空工单号和原因;先写审计日志,再返回一次性详情 |
| GET | `/admin/audit-logs` | 按审计人、目标用户、工单号和时间查询不可变日志 |
紧急审计接口与员工接口使用不同 Controller;超级管理员也不能绕过权限、工单号和原因校验。
## 7. 网页采集安全
`PersonalUrlFetchService` 必须执行:
1. 仅允许 `http/https`,拒绝用户名密码 URL、`file:`、`ftp:`、`data:`。
2. DNS 解析后拒绝 loopback、private、link-local、multicast、保留地址和云元数据地址。
3. 最多 3 次重定向,每次重定向重新解析和校验目标 IP。
4. 连接超时 5 秒、总超时 15 秒、响应正文上限 10MB。
5. 只接收 HTML、纯文本及明确允许的文档 MIME;下载文件仍走文件校验链。
6. 不携带用户浏览器 Cookie、Authorization、Referer 或企业内部代理凭证。
7. HTML 清洗脚本、样式、iframe、表单和隐藏元素,只保留正文与可追溯链接。
8. 记录最终 URL、HTTP 状态、抓取时间、内容哈希和 robots/版权提示;失败可重试但不绕过限制。
## 8. 解析、模型与提示词
### 8.1 解析
- 支持格式与现有知识库一致:txt/md/PDF/Word/Excel/PPT、常用图片。
- 视频、音频首批不进入个人空间;待语音转写和视频解析成本评估后扩展。
- item 状态由 `QUEUED → PARSING → READY/FAILED` 单向推进;重试将状态恢复为 QUEUED、增加 `attempt_count`,并覆盖 `error_code/error_message`。
- 文本块默认 800 字、重叠 120 字;参数由 `PersonalKnowledgeProperties` 管理。
#### 8.1.1 扫描 PDF OCR
- 普通 PDF 先走文本解析;仅在解析器返回 `EMPTY` 时转入视觉 OCR,不重复处理已有文本层的 PDF。
- 每批最多识别 20 页,单文件硬上限 200 页;页面渲染限制最长边 2200px、单页图片 4MB,避免大图耗尽内存。
- `aihr_personal_ocr_job` 保存文件级进度,`aihr_personal_ocr_page` 保存页级状态、识别正文、尝试次数和公开错误码;两表都必须带 `tenant_id + owner_user_id + item_id`。
- 失败页最多自动尝试 3 次;已有成功页不重复调用模型。用户可在资料详情只重试失败页。
- 至少一页成功时允许 item 进入 `READY`,同时展示失败页码;全部页面失败时 item 进入 `FAILED`,不得生成空片段或伪造正文。
- OCR 复用 `category=vision` 的 OpenAI-compatible 模型配置;没有 vision 时不把普通 chat 模型的失败伪装成“文件为空”。
### 8.2 提示词模板
新增模板代码:
- `personal_assistant_answer_v1`
- `personal_assistant_summary_v1`
- `personal_assistant_action_plan_v1`
- `personal_assistant_outline_v1`
- `personal_assistant_ppt_v1`(P1)
系统提示词必须包含:只基于授权片段回答、区分来源域、忽略资料中的指令注入、没有依据时拒答、不得代表用户对外作决定。
### 8.3 成本闸门
- 复用 `AIHR_AI_RUNTIME_ENABLED`、chat/vector/rerank 开关。
- 解析和向量化按内容哈希去重。
- 摘要按需生成;首屏列表不批量触发 LLM。
- 保存 token、模型和延迟,不保存供应商原始请求日志中的个人正文。
## 9. 删除、配额与生命周期
### 9.1 配额默认值
| 配置 | 默认值 |
|---|---:|
| `aihr.personal.max-file-size-mb` | 20 |
| `aihr.personal.max-url-body-mb` | 10 |
| `aihr.personal.max-space-mb` | 500 |
| `aihr.personal.max-items` | 1000 |
| `aihr.personal.download-url-minutes` | 5 |
### 9.2 删除流程
1. API 将 item 标记为 DELETING,立即从列表和检索隐藏。
2. 清理任务删除 Qdrant points。
3. 删除个人 fragment。
4. 删除或解绑 `sys_oss` 对象。
5. 将 item 标记为 DELETED,仅保留无正文的审计元数据。
6. 清理任务幂等重试,24 小时内完成物理内容清理。
用户账号冻结后禁止访问个人空间;阶段三接入权威离职状态后,默认冻结 30 天,允许用户或经授权管理员导出,期满清理。该天数必须可配置。
## 10. 前端设计
### 10.1 页面与入口
`mobile-uni` 新增:
```text
src/pages/user/assistant/index.vue 个人助理首页/提问
src/pages/user/assistant/library.vue 个人资料列表
src/pages/user/assistant/item.vue 资料详情与解析状态
src/pages/user/assistant/capture.vue 文字、文件、链接收藏
src/pages/user/assistant/sessions.vue 历史会话
src/services/personal-assistant.ts API 客户端
```
入口策略:员工端「问」页增加“企业知识 / 我的资料”范围切换和“收藏资料”入口;不新增第五个底部 Tab。
### 10.2 关键交互
- 上传后立即返回列表并显示解析状态,不阻塞页面等待解析。
- 扫描 PDF 详情显示 `已处理页数/总页数`、进度条和失败页码;处理期间每 2 秒轮询,终态停止轮询。
- 每条答案引用显示域徽标:我的资料、企业 SOP、外部网页。
- 混合问答默认关闭,用户主动选择后才同时检索两域。
- 删除前明确提示会删除附件、解析正文和搜索索引。
- 资料详情允许修正标题、时间和标签,不允许直接编辑解析正文。
- P1 PPT 生成必须先展示可编辑大纲和模板选择,再提交异步任务。
## 11. 错误码与降级
| 业务码 | 含义 | 前端动作 |
|---|---|---|
| `PERSONAL_SPACE_QUOTA_EXCEEDED` | 空间或条目配额超限 | 显示用量并引导删除 |
| `PERSONAL_ITEM_NOT_FOUND` | 不存在或无权访问 | 统一显示不存在,不泄露归属 |
| `PERSONAL_ITEM_NOT_READY` | 仍在解析 | 展示状态并允许刷新 |
| `PERSONAL_PARSE_FAILED` | 解析失败 | 展示可公开原因和重试 |
| `PERSONAL_OCR_MODEL_UNAVAILABLE` | 未配置可识图模型 | 提示配置视觉模型后重试失败页 |
| `PERSONAL_OCR_PROVIDER_FAILED` | 视觉模型调用失败 | 保留失败页,允许稍后仅重试失败页 |
| `PERSONAL_OCR_EMPTY` | 页面可访问但没有识别到文字 | 展示失败页和文件检查提示 |
| `PERSONAL_PDF_PAGE_LIMIT` | PDF 超过 200 页 | 拒绝进入 OCR,提示拆分文件 |
| `PERSONAL_URL_BLOCKED` | URL 被安全策略拦截 | 明确不能访问该地址 |
| `PERSONAL_NO_CITATION` | 无足够资料支撑 | 不生成业务结论 |
| `ENTERPRISE_SCOPE_FORBIDDEN` | 无企业知识范围权限 | 保留个人结果,提示企业范围不可用 |
| `PERSONAL_AI_DISABLED` | AI 成本闸门关闭 | 仍允许收藏和关键词检索,暂停摘要/问答 |
企业检索失败时,mixed 查询可只返回个人结果并标记降级;个人权限校验失败时不得降级为企业或公开检索。
## 12. 测试策略
### 12.1 单元测试
- owner/tenant 条件生成与空 owner 拒绝。
- URL IP 分类、重定向二次校验、正文上限。
- 文件哈希去重与配额计算。
- 引用域序列化和无引用拒答。
- 删除任务幂等性。
- prompt injection 文本不得改变系统指令。
### 12.2 集成测试
- MySQL:两个用户创建、列表、详情、搜索、删除互不可见。
- Qdrant:未带 owner filter 的调用被拒绝;用户 A 不返回用户 B point。
- OSS:签名 URL 只能由本人生成;删除后对象不可访问。
- mixed:个人和企业结果分别授权、融合、引用域正确。
- 成本闸门关闭:收藏与关键词检索可用,LLM 能力明确降级。
### 12.3 浏览器 E2E
1. 手机号登录并确认岗位。
2. 收藏一段文字、一个 PDF 和一个公开网页。
3. 查看 QUEUED/PARSING/READY 状态变化。
4. 按日期查询资料。
5. 分别执行个人、企业、混合问答。
6. 检查引用徽标和原始来源。
7. 删除资料并确认列表、问答和下载均不可再访问。
8. 切换另一手机号,确认看不到前一用户的资料、标题和会话。
## 13. 非功能指标
| 指标 | 验收线 |
|---|---|
| 跨用户数据泄漏 | 0;自动越权矩阵 100% 通过 |
| 支持样本解析成功率 | ≥95%(排除加密/损坏文件) |
| 关键词/向量检索 P95 | ≤2.5 秒 |
| AI 回答 P95 | ≤8 秒;超时明确提示可重试 |
| 引用可访问率 | 100%,且当前用户有权访问 |
| 删除可见性 | API 成功后立即隐藏 |
| 物理内容清理 | 24 小时内完成 |
| URL 私网/元数据地址拦截 | 100% |
## 14. 里程碑与发布闸门
### M0:安全与数据地基
- 独立表、独立 Qdrant collection、私有 OSS 前缀。
- 两用户越权测试、URL SSRF 测试、删除测试先行。
### M1:资料收藏与解析
- 文字、文件、图片、网页链接收藏。
- 异步解析状态、失败重试、配额和列表。
### M2:个人检索与问答
- 日期/主题/来源检索。
- 个人问答、引用、会话和成本统计。
### M3:企业知识授权融合
- 企业知识权限过滤:组织快照 + 独立 ACL + SQL fragment allowlist 下推,默认拒绝。
- 个人/企业/mixed 三范围切换和引用域标记。
### M4:移动端闭环与试点
- 员工端页面、真实浏览器 E2E、20 名试点员工。
- 试点期间不开放分享/企业入库和 PPT 文件生成。
### M5:P1 增强
- 分享/企业入库审核。
- 周报月报、汇报提纲、PPTX 异步生成。
发布硬闸门:跨用户越权测试、SSRF 测试、删除链路、引用权限和真实浏览器闭环任一未通过,不得进入试点。
## 15. 实施文件清单
预计新增:
- `backend/script/sql/aihr_personal_knowledge_mysql8.sql`
- `backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/personal/**`
- `backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/personal/**`
- `mobile-uni/src/pages/user/assistant/*.vue`
- `mobile-uni/src/services/personal-assistant.ts`
- `mobile-uni/tests/personal-assistant.test.mjs`
预计修改:
- `backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrSopSeedService.java`(仅抽取解析能力,不加入个人业务)
- `backend/script/sql/aihr_knowledge_mysql8.sql`(仅共享解析迁移兼容时调整)
- `mobile-uni/src/pages.json`
- `mobile-uni/src/pages/user/sop/index.vue`
- `mobile-uni/src/types/api.ts`
- `scripts/reset-dev-db.sh`
- `docs/API_INTEGRATION.md`
- `docs/DEV_SETUP.md`
- `docs/DEMO_ACCEPTANCE.md`
## 16. 决策摘要
1. 个人知识和企业知识物理分域,不采用共享表 `scope_type`。
2. 文件解析、模型和成本闸门复用,存储和检索索引不复用。
3. 本地 `user_id` 是阶段二所有权键,`ext_party_id` 只作阶段三迁移锚点。
4. Mixed 检索先分域授权,再融合结果。
5. 个人资料默认私有,分享和企业入库走 P1 审核链。
6. PPT 是 P1,不阻塞阶段二首批。
7. 北森、考勤等外部个人数据属于阶段三。