From 116fb320c8fa7911d3885a7b521f6ee73d5a9726 Mon Sep 17 00:00:00 2001 From: let5sne Date: Tue, 14 Jul 2026 10:53:50 +0800 Subject: [PATCH] fix(aihr): scope knowledge feedback fragments --- .../aihr/service/AihrSopSeedService.java | 19 +++++++++++++++++++ .../aihr/service/AihrSopSeedServiceTest.java | 13 +++++++++++++ docs/BRD_IMPLEMENTATION_AUDIT.md | 1 + scripts/demo-check.sh | 1 + 4 files changed, 34 insertions(+) diff --git a/backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrSopSeedService.java b/backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrSopSeedService.java index bdd37012..d5760dc7 100644 --- a/backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrSopSeedService.java +++ b/backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrSopSeedService.java @@ -230,6 +230,7 @@ public class AihrSopSeedService { if (fragmentIds.isEmpty()) { throw new ServiceException("缺少片段ID"); } + validateFeedbackFragmentOwnership(fragmentIds); ensureAnswerFeedbackTable(); String reviewStatus = "down".equals(verdict) ? "待复核" : "已复核"; Timestamp now = Timestamp.valueOf(java.time.LocalDateTime.now()); @@ -2090,6 +2091,24 @@ public class AihrSopSeedService { return ids.stream().toList(); } + private void validateFeedbackFragmentOwnership(List fragmentIds) { + if (fragmentIds.size() > 20) { + throw new ServiceException("单次最多反馈 20 个知识片段"); + } + String placeholders = String.join(",", fragmentIds.stream().map(id -> "?").toList()); + List args = new ArrayList<>(); + args.add(tenantId()); + args.addAll(fragmentIds); + List ownedIds = jdbcTemplate.queryForList(""" + SELECT id + FROM aihr_knowledge_fragment + WHERE tenant_id = ? AND id IN (%s) + """.formatted(placeholders), Long.class, args.toArray()); + if (ownedIds.size() != fragmentIds.size()) { + throw new ServiceException("知识片段不存在或不属于当前租户"); + } + } + private static String formatTimestamp(Timestamp timestamp) { return timestamp == null ? "" : timestamp.toLocalDateTime().format(TIME_FORMATTER); } diff --git a/backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/service/AihrSopSeedServiceTest.java b/backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/service/AihrSopSeedServiceTest.java index bcec7e64..b4347662 100644 --- a/backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/service/AihrSopSeedServiceTest.java +++ b/backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/service/AihrSopSeedServiceTest.java @@ -129,6 +129,19 @@ public class AihrSopSeedServiceTest { assertTrue(code.contains("reviewAnswerFeedback(id, operator)")); } + @Test + @Tag("dev") + public void answerFeedbackValidatesFragmentOwnership() throws Exception { + Path source = Path.of("src/main/java/org/dromara/aihr/service/AihrSopSeedService.java"); + if (!Files.exists(source)) { + source = Path.of("ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrSopSeedService.java"); + } + String code = Files.readString(source); + + assertTrue(code.contains("validateFeedbackFragmentOwnership(fragmentIds)")); + assertTrue(code.contains("WHERE tenant_id = ? AND id IN (%s)")); + } + private static AihrSopSeedService.KnowledgeHit hit(Long fragmentId, String title) { return new AihrSopSeedService.KnowledgeHit(fragmentId, title, "sop", "", "doc-" + fragmentId, "片段内容", 1, 1.0); } diff --git a/docs/BRD_IMPLEMENTATION_AUDIT.md b/docs/BRD_IMPLEMENTATION_AUDIT.md index 13f023d1..52899b0a 100644 --- a/docs/BRD_IMPLEMENTATION_AUDIT.md +++ b/docs/BRD_IMPLEMENTATION_AUDIT.md @@ -180,3 +180,4 @@ - 2026-07-14 BRD 候选人关联完整性复核:候选人—员工关联接口此前只校验员工主体在职,允许给不存在的任意 `candidateId` 建立入职关联;现要求候选人必须来自内置候选档案、活动面试会话或当前租户已持久化面试记录,保留已有幂等关联和历史数据兼容。 - 2026-07-14 BRD SOP 审核留痕复核:SOP 问答评审接口此前接受请求体中的 `reviewer` 并直接入库,登录用户可伪造审核人;现由控制器从当前登录用户传入服务端操作人,请求体仅保留评审结论和备注,保留旧服务调用的默认“人工评审”兼容。 - 2026-07-14 BRD 知识答案反馈复核:原先只更新 `review_status`,没有复核人和复核时间;现由服务端记录登录操作人到 `reviewer/reviewed_time`,管理端待复核表同步展示留痕。 +- 2026-07-14 BRD 知识反馈归属复核:答案反馈原先信任客户端传入的片段 ID,未校验知识片段是否属于当前租户;现入库前按当前租户校验片段归属并限制单次最多 20 个片段。 diff --git a/scripts/demo-check.sh b/scripts/demo-check.sh index 89b0313c..43c29444 100755 --- a/scripts/demo-check.sh +++ b/scripts/demo-check.sh @@ -495,6 +495,7 @@ contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/control contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrSopSeedService.java "仅支持 txt/md/markdown/pdf/doc/docx/xls/xlsx/ppt/pptx/图片文件" contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrSopSeedService.java "/embeddings" contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrSopSeedService.java "aihr_knowledge_gap" +contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrSopSeedService.java "validateFeedbackFragmentOwnership" contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrVideoService.java "处理输出已隐藏" contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrUploadQueueService.java "资料加工失败,请重试或联系管理员" contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrSopSeedService.java "目录导入失败,请重试或联系管理员"