fix(aihr): mask sensitive case transcripts
This commit is contained in:
+17
-4
@@ -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("(?<!\\d)(1[3-9]\\d{2})\\d{3}(\\d{4})(?!\\d)", "$1****$2");
|
||||
masked = masked.replaceAll("(?<!\\d)(\\d{1,2})(栋|幢|号楼|单元)(\\d{3,4})(?!\\d)", "$1$2****");
|
||||
masked = masked.replaceAll("(?<!\\d)(\\d{1,2})[--](\\d{3,4})(?!\\d)", "$1-****");
|
||||
masked = masked.replaceAll("([\\u4e00-\\u9fa5])([\\u4e00-\\u9fa5])(先生|女士|经理)", "$1*$3");
|
||||
return clean(masked);
|
||||
}
|
||||
|
||||
private record CaseState(
|
||||
String id,
|
||||
String fileName,
|
||||
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
package org.dromara.aihr.service;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.dromara.aihr.domain.AihrCaseDto.UploadResponse;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.mock.web.MockMultipartFile;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
|
||||
class AihrCaseServiceTest {
|
||||
|
||||
@Test
|
||||
@Tag("dev")
|
||||
void uploadMasksSensitiveTranscriptBeforeReturningAndSaving() {
|
||||
AihrSpeechService speechService = new AihrSpeechService(null, new ObjectMapper()) {
|
||||
@Override
|
||||
public Optional<String> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user