fix(aihr): preserve case contributor attribution

This commit is contained in:
2026-07-14 10:45:22 +08:00
parent 6f0854803d
commit 92afb51902
4 changed files with 33 additions and 8 deletions
@@ -69,7 +69,7 @@ public class AihrCaseService {
MediaRef media = storeSourceAudio(file);
String caseId = "case-" + UUID.randomUUID();
CaseState state = new CaseState(caseId, fileName, scopedProject, transcript, tagsFromText(transcript), null,
media.ossId(), media.url());
media.ossId(), media.url(), currentCaseOwner());
cases.put(caseId, state);
saveCase(state, "已转写", caseTitle(List.of(), fileName), transcript);
return new UploadResponse(caseId, fileName, transcript, state.tags(), "real-asr", media.url(), media.ossId());
@@ -85,7 +85,7 @@ public class AihrCaseService {
CaseState state = requireCase(request == null ? null : request.caseId(), projectScopes);
CaseSummary summary = organizeWithModel(state).orElseGet(() -> localSummary(state));
CaseState next = new CaseState(state.id(), state.fileName(), state.projectExtOrgId(), state.transcript(), summary.tags(), summary.summary(),
state.mediaOssId(), state.mediaUrl());
state.mediaOssId(), state.mediaUrl(), state.owner());
cases.put(state.id(), next);
saveCase(next, "已整理", caseTitle(summary.summary(), state.fileName()), summary.aiSummary());
return new OrganizeResponse(state.id(), summary.summary(), summary.tags(), summary.aiSummary(), summary.source());
@@ -104,7 +104,7 @@ public class AihrCaseService {
primaryTag(state.tags()),
"已入库",
LocalDateTime.now().format(TIME_FORMAT),
"培训组",
firstNonBlank(state.owner(), "培训组"),
detailSummary.isBlank() ? state.transcript() : detailSummary
);
saveCase(state, "已入库", record.title(), record.summary());
@@ -233,7 +233,7 @@ public class AihrCaseService {
jdbcTemplate.update("""
INSERT INTO aihr_case_record
(tenant_id, case_id, file_name, project_ext_org_id, title, type, status, transcript, summary, media_oss_id, media_url, owner, create_time, update_time)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, '培训组', now(), now())
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, now(), now())
ON DUPLICATE KEY UPDATE
file_name = VALUES(file_name),
project_ext_org_id = VALUES(project_ext_org_id),
@@ -256,7 +256,8 @@ public class AihrCaseService {
truncate(maskSensitiveText(state.transcript()), 4000),
truncate(maskSensitiveText(summary), 1000),
state.mediaOssId(),
truncate(state.mediaUrl(), 500)
truncate(state.mediaUrl(), 500),
firstNonBlank(state.owner(), "培训组")
);
}
@@ -403,7 +404,7 @@ public class AihrCaseService {
ensureCaseTable();
List<CaseState> persisted = jdbcTemplate.query("""
SELECT case_id, file_name, project_ext_org_id, transcript, type
, media_oss_id, media_url
, media_oss_id, media_url, owner
FROM aihr_case_record
WHERE tenant_id = ? AND case_id = ?
LIMIT 1
@@ -415,7 +416,8 @@ public class AihrCaseService {
tagsFromText(firstNonBlank(rs.getString("type"), "现场案例")),
null,
rs.getObject("media_oss_id", Long.class),
firstNonBlank(rs.getString("media_url"), "")
firstNonBlank(rs.getString("media_url"), ""),
firstNonBlank(rs.getString("owner"), "培训组")
), tenantId(), normalizedCaseId);
state = persisted.stream().findFirst().orElse(null);
if (state != null) {
@@ -535,6 +537,18 @@ public class AihrCaseService {
return clean(AihrSensitiveText.forModel(text));
}
private String currentCaseOwner() {
return caseOwner(LoginHelper.getUsername());
}
static String caseOwner(String identity) {
String value = clean(identity);
if (value.matches("1[3-9]\\d{9}")) {
return "员工" + value.substring(value.length() - 4);
}
return firstNonBlank(value, "培训组");
}
static boolean isSupportedAudioFile(String fileName) {
String lower = clean(fileName).toLowerCase(Locale.ROOT);
return SUPPORTED_AUDIO_EXTENSIONS.stream().anyMatch(lower::endsWith);
@@ -557,7 +571,8 @@ public class AihrCaseService {
List<TagResponse> tags,
List<SummaryResponse> summary,
Long mediaOssId,
String mediaUrl
String mediaUrl,
String owner
) {
}
@@ -163,6 +163,14 @@ class AihrCaseServiceTest {
assertFalse(AihrCaseService.isCaseContributorPosition("普通员工", "一线"));
}
@Test
@Tag("dev")
void caseOwnerKeepsAttributionWithoutPersistingRawMobileNumber() {
assertEquals("员工1111", AihrCaseService.caseOwner("13900001111"));
assertEquals("hr_operator", AihrCaseService.caseOwner("hr_operator"));
assertEquals("培训组", AihrCaseService.caseOwner(" "));
}
private static class CaseJdbcTemplate extends JdbcTemplate {
private Object[] updateArgs = new Object[0];
+1
View File
@@ -176,3 +176,4 @@
- 2026-07-14 BRD G3 音频读取权限复核:发现移动端音频下载接口对已登录 SYS 用户未校验角色,普通后台账号理论上可枚举 `ossId` 读取训练录音;现复用 `superadmin/hr_operator` 门禁,APP 用户继续按本人/主管项目范围校验,其他身份返回 403。AIHR 全量测试 `70/70` 通过,未修改生产环境。
- 2026-07-14 BRD 4.4 每日三题资格补强:每日派题现在在组织快照存在 `hire_date` 列时按“入职日期至今不超过 3 个月”判断,边界按自然月且包含当天;旧 Demo 快照没有该列时继续使用“移动端训练记录少于 3 次”的兼容回退。正式试点仍需在外部组织同步落地并校验 `hire_date`,不能把回退口径当成正式新员工资格。
- 2026-07-14 BRD G3 候选资料审核状态复核:候选人资料审核接口此前在空请求体时默认写回“待审核”,可能覆盖已有“已通过/已驳回”状态;现要求审核动作显式提交三态之一,空值和未知状态直接拒绝,避免后台误操作回退审核结论。
- 2026-07-14 BRD 案例署名复核:案例库已有负责人字段,但此前上传、整理和入库记录都硬编码为“培训组”,无法追溯案例贡献者;现复用既有 `owner` 字段保存首次上传身份,整理和入库阶段保留原署名,手机号身份仅显示员工末四位,避免把原始手机号写入案例展示。
+1
View File
@@ -514,6 +514,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/AihrCaseService.java "position_level IN ('主管', '项目经理')"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrCaseService.java "media_oss_id"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrCaseService.java "storeSourceAudio"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrCaseService.java "currentCaseOwner"
contains mobile-uni/src/pages/user/cases/index.vue "case-audio"
contains frontend/src/views/knowledge/cases.vue "case-audio"
contains backend/script/sql/aihr_practice_mysql8.sql "aihr_case_record"