19 KiB
大喇叭公司文件多岗位解读 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 合同测试
在迁移测试中要求以下三列存在:
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 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
迁移增加:
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 分列幂等执行,并增加:
KEY idx_aihr_broadcast_attachment_insight_queue
(insight_status, status, update_time)
DTO 增加:
public record BroadcastEvidence(int paragraphIndex, String quote) {}
public record BroadcastPerspective(
String code,
String label,
String summary,
List<String> concerns,
List<String> impacts,
List<String> actions,
List<String> risks,
List<BroadcastEvidence> evidence
) {}
附件响应追加:
String insightStatus,
String defaultPerspectiveCode,
List<String> perspectiveLabels,
List<BroadcastPerspective> perspectives
- Step 4: 运行测试并确认通过
Run the two Task 1 commands.
Expected: PASS,迁移可重复执行,DTO 没有原文和 JSON 存储字段。
- Step 5: 提交
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,覆盖:
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:
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: 实现最小生成器
固定九个角色:
private static final Map<String, String> 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 块。每块调用:
modelService.chat(new ChatRequest(
prompt(fileName, chunk, paragraphOffset),
null,
"只根据给定原文返回 JSON;未提及的岗位不要输出;evidence.quote 必须逐字复制原文。"
));
只接受 mode == "openai-compatible" 且 error 为空的响应。解析后执行:
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: 提交
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 参数,要求:
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:
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: 分离文件状态和解读状态
附件解析成功后立即写:
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: 提交
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: 写失败的移动端合同测试
断言源码包含:
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:
npm --prefix mobile-uni run test:unit
Expected: new multi-role test FAIL.
- Step 3: 增加类型和默认选择
类型与后端一致。详情加载后:
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: 实现移动端布局和降级状态
摘要保持在前。其后依次展示:
多岗位解读 / 我的岗位视角
[生活顾问] [财务] [人力] [业务运营] …
本岗位摘要
关注点 / 影响 / 建议行动 / 风险
原文依据(默认折叠)
PENDING 显示“AI 正在从不同岗位解读”;PARTIAL 显示“已生成部分岗位解读”;FAILED 或 NOT_PROCESSED 只显示轻量说明,不隐藏摘要、原文件和“问这条消息”。岗位 chips 允许横向滚动,按钮具备可识别选中态和可读文本。
- Step 5: 运行移动端测试、类型检查和构建
Run:
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: 提交
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 个,并明确要求:
insight_status
insights_json
insight_version
demo-check.sh 要求新迁移、解读服务、移动端“多岗位解读”和“定向提醒”存在。
- Step 2: 运行静态预检并确认失败
Run:
bash scripts/demo-check.sh
Expected: FAIL on missing preflight/docs markers before implementation.
- Step 3: 接入迁移与更新文档
reset-dev-db.sh 在附件基础迁移后执行新迁移。API 文档记录:
insightStatus: NOT_PROCESSED | PENDING | READY | PARTIAL | FAILED
defaultPerspectiveCode: 服务端按认证手机号对应在职岗位计算
perspectives[]: code/label/summary/concerns/impacts/actions/risks/evidence
同时记录:所有有原消息阅读权限的员工可切换全部生成岗位;引用必须逐字命中原文;失败不影响摘要、下载和追问;旧“与我有关”改名“定向提醒”且语义仍为通知定向。
- Step 4: 运行静态预检
Run:
bash scripts/demo-check.sh
git diff --check
Expected: PASS.
- Step 5: 提交
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:
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:
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
通过现有管理端上传并发布:
- 同时涉及财务、人力、业务运营的短文件;
- 超过 4,000 字且关键内容在尾部的长文件。
使用数据库中实际在职手机号和开发固定验证码登录,不在日志、截图或报告中保留手机号。
- Step 3: 在 390 × 844 浏览器真实验证
逐项操作:
- 打开大喇叭详情,确认摘要位于多岗位解读之前;
- 确认默认选中当前员工岗位对应视角;
- 横向切换财务、人力、业务运营等全部返回岗位;
- 展开原文依据并核对引文存在于文件;
- 打开原文件;
- 点击“问这条消息”;
- 验证
PENDING/PARTIAL/FAILED时主链路仍可用; - 检查文字换行、chip 溢出、底部按钮、滚动与点击区域。
保存并检查截图,不以页面可加载替代视觉验收。
- Step 4: 安全回归
用自动化或直接 HTTP 证据确认:
撤回消息 -> 404
跨租户消息 -> 404
非在职 APP 身份 -> 403
员工详情 JSON -> 不含 extracted_text / insights_json
- Step 5: 代码审查与修复
分别进行 Java、TypeScript 和整体代码审查;修复所有高/中优先级问题后重跑 Task 6 Step 1。
- Step 6: 提交最终修复
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 和线上真实账号验证后才能声明。