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

22 KiB
Raw Blame History

员工个人 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 检索流程

用户问题 + 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:

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

解析器抽为共享、无状态能力:

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. 自动化测试必须使用两个用户交叉访问,验证列表、详情、检索、向量和删除均返回不可见。

4. 数据模型

初始化 SQL:backend/script/sql/aihr_personal_knowledge_mysql8.sql。

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

创建响应:

{
  "itemId": 1201,
  "status": "QUEUED",
  "duplicateOf": null
}

6.2 搜索与问答

POST /search

{
  "queryText": "7月1日至7月10日我收藏了哪些保洁管理资料",
  "scope": ["PERSONAL"],
  "dateFrom": "2026-07-01",
  "dateTo": "2026-07-10",
  "itemIds": [],
  "limit": 10
}

POST /ask

{
  "sessionId": null,
  "queryText": "结合我的资料和企业制度,整理本周保洁管理改进建议",
  "scope": ["PERSONAL", "ENTERPRISE"],
  "dateFrom": "2026-07-01",
  "dateTo": "2026-07-10",
  "itemIds": [],
  "outputFormat": "ACTION_PLAN"
}

响应:

{
  "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.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 新增:

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 关键交互

  • 上传后立即返回列表并显示解析状态,不阻塞页面等待解析。
  • 每条答案引用显示域徽标:我的资料、企业 SOP、外部网页。
  • 混合问答默认关闭,用户主动选择后才同时检索两域。
  • 删除前明确提示会删除附件、解析正文和搜索索引。
  • 资料详情允许修正标题、时间和标签,不允许直接编辑解析正文。
  • P1 PPT 生成必须先展示可编辑大纲和模板选择,再提交异步任务。

11. 错误码与降级

业务码 含义 前端动作
PERSONAL_SPACE_QUOTA_EXCEEDED 空间或条目配额超限 显示用量并引导删除
PERSONAL_ITEM_NOT_FOUND 不存在或无权访问 统一显示不存在,不泄露归属
PERSONAL_ITEM_NOT_READY 仍在解析 展示状态并允许刷新
PERSONAL_PARSE_FAILED 解析失败 展示可公开原因和重试
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:企业知识授权融合

  • 企业知识权限过滤。
  • 个人/企业/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. 北森、考勤等外部个人数据属于阶段三。