# 员工个人 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. 自动化测试必须使用两个用户交叉访问,验证列表、详情、检索、向量和删除均返回不可见。 ## 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 所有权。 ## 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`。 ### 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` | 本人申请进度 | | POST | `/exports/outline` | 生成可编辑汇报大纲 | | POST | `/exports/pptx` | 根据已确认大纲和模板异步生成 PPTX | | GET | `/exports/{id}` | 生成状态与下载地址 | ## 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` 新增: ```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 关键交互 - 上传后立即返回列表并显示解析状态,不阻塞页面等待解析。 - 每条答案引用显示域徽标:我的资料、企业 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. 北森、考勤等外部个人数据属于阶段三。