From 0acbea705872b34d5ec7e7035f7502550d451260 Mon Sep 17 00:00:00 2001 From: let5sne Date: Thu, 9 Jul 2026 23:11:02 +0800 Subject: [PATCH] fix(aihr): mask sensitive case transcripts --- .../dromara/aihr/service/AihrCaseService.java | 21 ++++++-- .../aihr/service/AihrCaseServiceTest.java | 53 +++++++++++++++++++ 2 files changed, 70 insertions(+), 4 deletions(-) create mode 100644 backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/service/AihrCaseServiceTest.java diff --git a/backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrCaseService.java b/backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrCaseService.java index 57cbbf23..2a79bbb1 100644 --- a/backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrCaseService.java +++ b/backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrCaseService.java @@ -48,6 +48,7 @@ public class AihrCaseService { try { String transcript = speechService.transcribe(file.getBytes(), fileName, contentType) .orElseThrow(() -> new IllegalStateException("ASR 未配置或转写失败")); + transcript = maskSensitiveText(transcript); String caseId = "case-" + UUID.randomUUID(); CaseState state = new CaseState(caseId, fileName, projectExtOrgId, transcript, tagsFromText(transcript), null); cases.put(caseId, state); @@ -127,8 +128,8 @@ public class AihrCaseService { truncate(title, 120), primaryTag(state.tags()), status, - truncate(state.transcript(), 4000), - truncate(summary, 1000) + truncate(maskSensitiveText(state.transcript()), 4000), + truncate(maskSensitiveText(summary), 1000) ); } @@ -183,7 +184,7 @@ public class AihrCaseService { if (summaryNode.isArray()) { for (JsonNode item : summaryNode) { String label = clean(item.path("label").asText("")); - String text = clean(item.path("text").asText("")); + String text = maskSensitiveText(item.path("text").asText("")); if (!label.isBlank() && !text.isBlank()) { summary.add(new SummaryResponse(label, truncate(text, 120))); } @@ -205,7 +206,7 @@ public class AihrCaseService { if (tags.isEmpty()) { tags = tagsFromText(root.path("aiSummary").asText("")); } - return Optional.of(new CaseSummary(summary, tags, truncate(root.path("aiSummary").asText(""), 180), "real-llm")); + return Optional.of(new CaseSummary(summary, tags, truncate(maskSensitiveText(root.path("aiSummary").asText("")), 180), "real-llm")); } catch (Exception e) { log.warn("case summary parse failed, uses local transcript summary: {}", e.getMessage()); return Optional.empty(); @@ -294,6 +295,18 @@ public class AihrCaseService { return value == null ? "" : value.trim(); } + private static String maskSensitiveText(String text) { + if (text == null || text.isBlank()) { + return clean(text); + } + String masked = text; + masked = masked.replaceAll("(? transcribe(byte[] audio, String filename, String contentType) { + return Optional.of("张三先生住在12栋1201,手机号13900001111,说2-304漏水"); + } + }; + CaseJdbcTemplate jdbcTemplate = new CaseJdbcTemplate(); + AihrCaseService service = new AihrCaseService(speechService, null, new ObjectMapper(), jdbcTemplate); + + UploadResponse response = service.upload(new MockMultipartFile("file", "case.webm", "audio/webm", new byte[]{1}), "P1"); + + assertEquals("张*先生住在12栋****,手机号1390****1111,说2-****漏水", response.transcript()); + assertEquals(response.transcript(), jdbcTemplate.updateArgs[6]); + assertEquals(response.transcript(), jdbcTemplate.updateArgs[7]); + assertFalse(response.transcript().contains("13900001111")); + assertFalse(response.transcript().contains("1201")); + assertFalse(response.transcript().contains("2-304")); + } + + private static final class CaseJdbcTemplate extends JdbcTemplate { + private Object[] updateArgs = new Object[0]; + + @Override + public void execute(String sql) { + // Table DDL is not relevant for this unit test. + } + + @Override + public int update(String sql, Object... args) { + this.updateArgs = args; + return 1; + } + } +}