# 大喇叭公司文件多岗位解读 Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** 在现有“大喇叭消息 + 单个公司文件”链路中生成有原文依据的多岗位解读,并让员工默认看到自己岗位视角、可切换查看全部相关视角。 **Architecture:** 复用现有附件解析、摘要、鉴权下载和消息追问链路。新增一个职责单一的 `AihrBroadcastInsightService`,使用现有模型调用边界按自然段分块生成结构化岗位解读,服务端只保留引用能在原文块中精确命中的内容;附件文件状态与解读状态分离,解读失败不阻塞文件发布和阅读。 **Tech Stack:** Java 17、Spring Boot、JdbcTemplate、Jackson、MySQL 8、Vue 3、uni-app、TypeScript、Node test、JUnit 5/Mockito。 --- ## 文件职责 - Create `backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/broadcast/AihrBroadcastInsightService.java` - 固定岗位目录、岗位映射、自然段分块、模型调用、JSON 清洗、原文引用校验、跨块合并和响应反序列化。 - Modify `backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/broadcast/AihrBroadcastAttachmentService.java` - 文件提取完成后先保存 `READY`,再独立领取和处理岗位解读任务;恢复卡死任务。 - Modify `backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/broadcast/AihrBroadcastService.java` - 员工详情查询解读字段,基于认证手机号对应的在职组织岗位计算默认视角,不返回 `extracted_text`。 - Modify `backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/broadcast/AihrBroadcastDto.java` - 增加岗位解读、来源依据和解读状态字段。 - Create `backend/script/sql/update/aihr_20260725_broadcast_multi_role_insight_mysql8.sql` - 为附件表增加 `insight_status`、`insights_json`、`insight_version` 及解读队列索引。 - Create `backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/broadcast/AihrBroadcastInsightServiceTest.java` - 覆盖岗位映射、自然段分块、伪造引用剔除、跨块合并和部分成功状态。 - Modify existing broadcast backend tests - 覆盖文件与解读状态解耦、DTO 不泄漏全文、租户/撤回/在职身份约束不回退。 - Modify `mobile-uni/src/services/broadcast.ts` - 声明岗位解读响应类型。 - Modify `mobile-uni/src/pages/user/broadcast/detail.vue` - 在文件摘要之后展示默认岗位、岗位切换、五类要点和折叠的原文依据。 - Modify `mobile-uni/src/pages/user/broadcast/index.vue` - 将通知命中标签“与我有关”改为“定向提醒”。 - Create `mobile-uni/tests/broadcast-multi-role-insight.test.mjs` - 覆盖类型契约、默认视角、切换、依据折叠、降级状态及术语改名。 - Modify `scripts/reset-dev-db.sh`, `scripts/tests/aihr-schema-migrations.test.sh`, `scripts/release-preflight.sh`, `scripts/demo-check.sh` - 纳入正式迁移与远端只读 schema 合同。 - Modify `docs/API_INTEGRATION.md`, `docs/BRD_IMPLEMENTATION_AUDIT.md` - 记录接口字段、生成状态、失败降级和验收边界。 ### Task 1: 数据库合同与 DTO **Files:** - Create: `backend/script/sql/update/aihr_20260725_broadcast_multi_role_insight_mysql8.sql` - Modify: `backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/broadcast/AihrBroadcastDto.java` - Modify: `scripts/tests/aihr-schema-migrations.test.sh` - Test: `backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/broadcast/AihrBroadcastAttachmentContractTest.java` - [ ] **Step 1: 写失败的 schema 与 DTO 合同测试** 在迁移测试中要求以下三列存在: ```bash SELECT COUNT(*) FROM information_schema.columns WHERE table_schema='$DB' AND table_name='aihr_broadcast_attachment' AND column_name IN ('insight_status','insights_json','insight_version'); ``` 在 `AihrBroadcastAttachmentContractTest` 中断言响应包含 `insightStatus`、`defaultPerspectiveCode`、`perspectives`,同时不包含 `extractedText` 和 `insightsJson`。 - [ ] **Step 2: 运行测试并确认失败** Run: ```bash bash scripts/tests/aihr-schema-migrations.test.sh mvn -f backend/pom.xml -pl ruoyi-modules/ruoyi-aihr -am \ -Dtest=AihrBroadcastAttachmentContractTest \ -Dsurefire.failIfNoSpecifiedTests=false test ``` Expected: schema 测试缺少 3 列,Java 测试缺少新的 record component。 - [ ] **Step 3: 写幂等迁移和最小 DTO** 迁移增加: ```sql ALTER TABLE aihr_broadcast_attachment ADD COLUMN insight_status varchar(20) NOT NULL DEFAULT 'NOT_PROCESSED' AFTER summary, ADD COLUMN insights_json longtext NULL AFTER insight_status, ADD COLUMN insight_version varchar(30) NULL AFTER insights_json; ``` 迁移必须通过 `information_schema` + prepared statement 分列幂等执行,并增加: ```sql KEY idx_aihr_broadcast_attachment_insight_queue (insight_status, status, update_time) ``` DTO 增加: ```java public record BroadcastEvidence(int paragraphIndex, String quote) {} public record BroadcastPerspective( String code, String label, String summary, List concerns, List impacts, List actions, List risks, List evidence ) {} ``` 附件响应追加: ```java String insightStatus, String defaultPerspectiveCode, List perspectiveLabels, List perspectives ``` - [ ] **Step 4: 运行测试并确认通过** Run the two Task 1 commands. Expected: PASS,迁移可重复执行,DTO 没有原文和 JSON 存储字段。 - [ ] **Step 5: 提交** ```bash git add backend/script/sql/update/aihr_20260725_broadcast_multi_role_insight_mysql8.sql \ backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/broadcast/AihrBroadcastDto.java \ backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/broadcast/AihrBroadcastAttachmentContractTest.java \ scripts/tests/aihr-schema-migrations.test.sh git commit -m "feat(broadcast): add multi-role insight contract" ``` ### Task 2: 岗位解读生成器 **Files:** - Create: `backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/broadcast/AihrBroadcastInsightService.java` - Create: `backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/broadcast/AihrBroadcastInsightServiceTest.java` - [ ] **Step 1: 写失败的核心逻辑测试** 使用 Mockito 模拟 `AihrModelSeedService.chat` 返回 OpenAI-compatible JSON,覆盖: ```java assertEquals("finance", AihrBroadcastInsightService.defaultPerspectiveCode(List.of("财务经理"))); assertEquals("living_advisor", AihrBroadcastInsightService.defaultPerspectiveCode(List.of("物业管家"))); assertNull(AihrBroadcastInsightService.defaultPerspectiveCode(List.of("未知岗位"))); assertTrue(AihrBroadcastInsightService.chunks(longParagraphText).size() > 1); assertTrue(result.perspectives().getFirst().evidence().stream() .allMatch(item -> source.contains(item.quote()))); assertFalse(resultJson.contains("模型编造但原文不存在")); ``` 另测第二块模型失败时保留第一块结果并返回 `PARTIAL`,所有块失败时返回 `FAILED`。 - [ ] **Step 2: 运行测试并确认失败** Run: ```bash mvn -f backend/pom.xml -pl ruoyi-modules/ruoyi-aihr -am \ -Dtest=AihrBroadcastInsightServiceTest \ -Dsurefire.failIfNoSpecifiedTests=false test ``` Expected: FAIL because the service does not exist. - [ ] **Step 3: 实现最小生成器** 固定九个角色: ```java private static final Map ROLE_LABELS = Map.ofEntries( entry("living_advisor", "生活顾问/客服"), entry("cleaning", "保洁"), entry("security", "保安"), entry("engineering", "保修/工程"), entry("finance", "财务"), entry("hr", "人力"), entry("operations", "业务运营"), entry("audit_risk", "审计风控"), entry("management", "管理层") ); ``` 自然段块目标 4,000 字、相邻重叠 300 字,最多 80 块。每块调用: ```java modelService.chat(new ChatRequest( prompt(fileName, chunk, paragraphOffset), null, "只根据给定原文返回 JSON;未提及的岗位不要输出;evidence.quote 必须逐字复制原文。" )); ``` 只接受 `mode == "openai-compatible"` 且 `error` 为空的响应。解析后执行: ```java if (!chunk.text().contains(evidence.quote())) { continue; } ``` 每岗位最多 5 条 concerns/impacts/actions/risks、3 条 evidence;文本去空白、去重、限制长度;仅保留固定角色代码。成功块少于总块数或正文超过 80 块时为 `PARTIAL`,全部失败为 `FAILED`。 - [ ] **Step 4: 运行测试并确认通过** Run the Task 2 Maven command. Expected: PASS. - [ ] **Step 5: 提交** ```bash git add backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/broadcast/AihrBroadcastInsightService.java \ backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/broadcast/AihrBroadcastInsightServiceTest.java git commit -m "feat(broadcast): generate grounded role insights" ``` ### Task 3: 附件异步链路与员工详情 **Files:** - Modify: `backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/broadcast/AihrBroadcastAttachmentService.java` - Modify: `backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/broadcast/AihrBroadcastService.java` - Modify: `backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/broadcast/AihrBroadcastServiceTest.java` - Modify: `backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/broadcast/AihrBroadcastAttachmentContractTest.java` - [ ] **Step 1: 写失败的链路测试** 测试源代码和 JDBC 参数,要求: ```java assertTrue(attachmentSource.contains("set status = 'READY', extracted_text = ?, summary = ?")); assertTrue(attachmentSource.contains("insight_status = 'PENDING'")); assertTrue(attachmentSource.contains("insight_status = 'FAILED'")); assertTrue(detailSql.contains("a.insights_json as attachment_insights_json")); assertTrue(detailSql.contains("m.status = 'PUBLISHED'")); assertFalse(responseFields.contains("extractedText")); ``` 新增详情测试:岗位 `财务主管` 默认代码为 `finance`;同一响应仍返回其他全部岗位;撤回、跨租户、非在职员工继续在查询前失败关闭。 - [ ] **Step 2: 运行测试并确认失败** Run: ```bash mvn -f backend/pom.xml -pl ruoyi-modules/ruoyi-aihr -am \ -Dtest=AihrBroadcastServiceTest,AihrBroadcastAttachmentContractTest \ -Dsurefire.failIfNoSpecifiedTests=false test ``` Expected: FAIL on missing insight queue and response mapping. - [ ] **Step 3: 分离文件状态和解读状态** 附件解析成功后立即写: ```sql status = 'READY', extracted_text = ?, summary = ?, insight_status = 'PENDING', insights_json = NULL, insight_version = NULL ``` 解读任务领取时把 `PENDING` 原子更新为内部 `PROCESSING`;API 将内部 `PROCESSING` 映射为 `PENDING`。成功保存 `READY/PARTIAL` 与 JSON、版本;异常只写 `insight_status='FAILED'`,不得把附件 `status` 改回 `FAILED`。启动恢复将超过 30 分钟的内部 `PROCESSING` 重置为 `PENDING`。 - [ ] **Step 4: 安全映射员工详情** 详情 SQL 只在既有租户、`PUBLISHED`、在职员工门禁通过后读取存储 JSON。服务端用当前 APP 用户手机号和 `aihr_org_snapshot` 的精确在职匹配读取 `position_name`,再计算 `defaultPerspectiveCode`;客户端不得提交岗位。返回全部已生成岗位,但绝不返回 `extracted_text` 或 `insights_json`。 - [ ] **Step 5: 运行测试并确认通过** Run the Task 3 Maven command. Expected: PASS. - [ ] **Step 6: 提交** ```bash git add backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/broadcast/AihrBroadcastAttachmentService.java \ backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/broadcast/AihrBroadcastService.java \ backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/broadcast/AihrBroadcastServiceTest.java \ backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/broadcast/AihrBroadcastAttachmentContractTest.java git commit -m "feat(broadcast): expose employee role insights" ``` ### Task 4: 员工端多岗位阅读 **Files:** - Modify: `mobile-uni/src/services/broadcast.ts` - Modify: `mobile-uni/src/pages/user/broadcast/detail.vue` - Modify: `mobile-uni/src/pages/user/broadcast/index.vue` - Create: `mobile-uni/tests/broadcast-multi-role-insight.test.mjs` - Modify: `mobile-uni/tests/broadcast-attachment.test.mjs` - [ ] **Step 1: 写失败的移动端合同测试** 断言源码包含: ```js assert.match(service, /interface BroadcastPerspective/); assert.match(detail, /多岗位解读/); assert.match(detail, /defaultPerspectiveCode/); assert.match(detail, /原文依据/); assert.match(detail, /insightStatus === 'FAILED'/); assert.doesNotMatch(detail, /与我有关/); assert.match(index, /定向提醒/); ``` - [ ] **Step 2: 运行测试并确认失败** Run: ```bash npm --prefix mobile-uni run test:unit ``` Expected: new multi-role test FAIL. - [ ] **Step 3: 增加类型和默认选择** 类型与后端一致。详情加载后: ```ts const perspectives = computed(() => item.value?.attachment?.perspectives || []); const selectedCode = ref(''); const selectInitialPerspective = (attachment?: BroadcastAttachment) => { selectedCode.value = attachment?.perspectives.some( perspective => perspective.code === attachment.defaultPerspectiveCode ) ? attachment.defaultPerspectiveCode || '' : attachment?.perspectives[0]?.code || ''; }; ``` 显示“我的岗位视角”仅当默认代码实际命中;否则显示“多岗位解读”。 - [ ] **Step 4: 实现移动端布局和降级状态** 摘要保持在前。其后依次展示: ```text 多岗位解读 / 我的岗位视角 [生活顾问] [财务] [人力] [业务运营] … 本岗位摘要 关注点 / 影响 / 建议行动 / 风险 原文依据(默认折叠) ``` `PENDING` 显示“AI 正在从不同岗位解读”;`PARTIAL` 显示“已生成部分岗位解读”;`FAILED` 或 `NOT_PROCESSED` 只显示轻量说明,不隐藏摘要、原文件和“问这条消息”。岗位 chips 允许横向滚动,按钮具备可识别选中态和可读文本。 - [ ] **Step 5: 运行移动端测试、类型检查和构建** Run: ```bash npm --prefix mobile-uni run test:unit npm --prefix mobile-uni run typecheck npm --prefix mobile-uni run build:h5 ``` Expected: all PASS. - [ ] **Step 6: 提交** ```bash git add mobile-uni/src/services/broadcast.ts \ mobile-uni/src/pages/user/broadcast/detail.vue \ mobile-uni/src/pages/user/broadcast/index.vue \ mobile-uni/tests/broadcast-multi-role-insight.test.mjs \ mobile-uni/tests/broadcast-attachment.test.mjs git commit -m "feat(mobile): show broadcast role perspectives" ``` ### Task 5: 开发重置、发布预检与文档 **Files:** - Modify: `scripts/reset-dev-db.sh` - Modify: `scripts/release-preflight.sh` - Modify: `scripts/demo-check.sh` - Modify: `docs/API_INTEGRATION.md` - Modify: `docs/BRD_IMPLEMENTATION_AUDIT.md` - [ ] **Step 1: 写失败的发布合同** 将远端附件合同从 11 个业务列扩展为 14 个,并明确要求: ```text insight_status insights_json insight_version ``` `demo-check.sh` 要求新迁移、解读服务、移动端“多岗位解读”和“定向提醒”存在。 - [ ] **Step 2: 运行静态预检并确认失败** Run: ```bash bash scripts/demo-check.sh ``` Expected: FAIL on missing preflight/docs markers before implementation. - [ ] **Step 3: 接入迁移与更新文档** `reset-dev-db.sh` 在附件基础迁移后执行新迁移。API 文档记录: ```text insightStatus: NOT_PROCESSED | PENDING | READY | PARTIAL | FAILED defaultPerspectiveCode: 服务端按认证手机号对应在职岗位计算 perspectives[]: code/label/summary/concerns/impacts/actions/risks/evidence ``` 同时记录:所有有原消息阅读权限的员工可切换全部生成岗位;引用必须逐字命中原文;失败不影响摘要、下载和追问;旧“与我有关”改名“定向提醒”且语义仍为通知定向。 - [ ] **Step 4: 运行静态预检** Run: ```bash bash scripts/demo-check.sh git diff --check ``` Expected: PASS. - [ ] **Step 5: 提交** ```bash git add scripts/reset-dev-db.sh scripts/release-preflight.sh scripts/demo-check.sh \ docs/API_INTEGRATION.md docs/BRD_IMPLEMENTATION_AUDIT.md git commit -m "docs(broadcast): define role insight release contract" ``` ### Task 6: 集成、视觉和真实交互验收 **Files:** - Verify all modified files - Evidence: local browser screenshots at 390 × 844 and desktop responsive view - [ ] **Step 1: 运行后端与移动端全量相关检查** Run: ```bash mvn -f backend/pom.xml -pl ruoyi-modules/ruoyi-aihr -am \ -DskipITs -Dsurefire.failIfNoSpecifiedTests=false test npm --prefix mobile-uni run test:unit npm --prefix mobile-uni run typecheck npm --prefix mobile-uni run build:h5 bash scripts/demo-check.sh git diff --check ``` Expected: PASS. - [ ] **Step 2: 启动本地服务并造两份可识别测试文件** Run: ```bash docker compose -f docker-compose.dev.yml up -d portless run --name wygj-api ./scripts/dev-backend.sh portless run --name wygj-mobile-uni npm --prefix mobile-uni run dev:h5 ``` 通过现有管理端上传并发布: 1. 同时涉及财务、人力、业务运营的短文件; 2. 超过 4,000 字且关键内容在尾部的长文件。 使用数据库中实际在职手机号和开发固定验证码登录,不在日志、截图或报告中保留手机号。 - [ ] **Step 3: 在 390 × 844 浏览器真实验证** 逐项操作: 1. 打开大喇叭详情,确认摘要位于多岗位解读之前; 2. 确认默认选中当前员工岗位对应视角; 3. 横向切换财务、人力、业务运营等全部返回岗位; 4. 展开原文依据并核对引文存在于文件; 5. 打开原文件; 6. 点击“问这条消息”; 7. 验证 `PENDING/PARTIAL/FAILED` 时主链路仍可用; 8. 检查文字换行、chip 溢出、底部按钮、滚动与点击区域。 保存并检查截图,不以页面可加载替代视觉验收。 - [ ] **Step 4: 安全回归** 用自动化或直接 HTTP 证据确认: ```text 撤回消息 -> 404 跨租户消息 -> 404 非在职 APP 身份 -> 403 员工详情 JSON -> 不含 extracted_text / insights_json ``` - [ ] **Step 5: 代码审查与修复** 分别进行 Java、TypeScript 和整体代码审查;修复所有高/中优先级问题后重跑 Task 6 Step 1。 - [ ] **Step 6: 提交最终修复** ```bash git add -u git commit -m "fix(broadcast): close multi-role insight review findings" ``` - [ ] **Step 7: 交付分支** 确认工作区干净,报告 `implemented / locally verified / visually verified`;生产部署与 `production-verified` 只有在执行正式迁移、静态发布、后端发布、完整 `release-preflight` 和线上真实账号验证后才能声明。