From a67ccd455dc9692ae808f5c85dcd3d63865ece68 Mon Sep 17 00:00:00 2001 From: let5sne Date: Tue, 14 Jul 2026 06:34:33 +0800 Subject: [PATCH] fix(aihr): scope case records to tenant --- .../dromara/aihr/service/AihrCaseService.java | 28 ++++++++++++------- .../aihr/service/AihrCaseServiceTest.java | 10 +++---- docs/BRD_IMPLEMENTATION_AUDIT.md | 1 + 3 files changed, 24 insertions(+), 15 deletions(-) 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 0cf6edee..6b0e1c19 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 @@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.dromara.common.satoken.utils.LoginHelper; import org.dromara.aihr.domain.AihrCaseDto.CurateRequest; import org.dromara.aihr.domain.AihrCaseDto.CurateResponse; import org.dromara.aihr.domain.AihrCaseDto.DetailResponse; @@ -118,11 +119,12 @@ public class AihrCaseService { String sql = """ SELECT case_id, title, type, status, update_time, owner, summary FROM aihr_case_record - WHERE tenant_id = '000000' + WHERE tenant_id = ? """ + projectWhere + """ ORDER BY update_time DESC, id DESC LIMIT ? """; + args.add(0, tenantId()); return jdbcTemplate.query(sql, (rs, rowNum) -> new RecordResponse( firstNonBlank(rs.getString("case_id"), ""), firstNonBlank(rs.getString("title"), "现场案例"), @@ -150,8 +152,9 @@ public class AihrCaseService { String sql = """ SELECT case_id, file_name, title, type, status, update_time, owner, transcript, summary, media_url, media_oss_id FROM aihr_case_record - WHERE tenant_id = '000000' AND case_id = ? + WHERE tenant_id = ? AND case_id = ? """ + projectWhere + " LIMIT 1"; + args.add(0, tenantId()); return jdbcTemplate.query(sql, (rs, rowNum) -> { Long mediaOssId = rs.getObject("media_oss_id", Long.class); return new DetailResponse( @@ -179,13 +182,13 @@ public class AihrCaseService { return jdbcTemplate.query(""" SELECT DISTINCT project_ext_org_id FROM aihr_org_snapshot - WHERE tenant_id = '000000' + WHERE tenant_id = ? AND employment_status = 'active' AND project_ext_org_id IS NOT NULL AND project_ext_org_id <> '' AND (person_phone = ? OR ext_party_id = ?) ORDER BY project_ext_org_id - """, (rs, rowNum) -> rs.getString("project_ext_org_id"), username, username); + """, (rs, rowNum) -> rs.getString("project_ext_org_id"), tenantId(), username, username); } catch (DataAccessException e) { log.warn("resolve app user project scope failed: {}", e.getMessage()); return List.of(); @@ -201,7 +204,7 @@ public class AihrCaseService { Integer count = jdbcTemplate.queryForObject(""" SELECT COUNT(*) FROM aihr_org_snapshot - WHERE tenant_id = '000000' + WHERE tenant_id = ? AND employment_status = 'active' AND (person_phone = ? OR ext_party_id = ?) AND (position_level IN ('主管', '项目经理') @@ -209,7 +212,7 @@ public class AihrCaseService { OR position_name LIKE '%经理%' OR position_name LIKE '%负责人%' OR position_name LIKE '%组长%') - """, Integer.class, username, username); + """, Integer.class, tenantId(), username, username); return count != null && count > 0; } catch (DataAccessException e) { log.warn("resolve case contributor failed: {}", e.getMessage()); @@ -222,7 +225,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 ('000000', ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, '培训组', now(), now()) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, '培训组', now(), now()) ON DUPLICATE KEY UPDATE file_name = VALUES(file_name), project_ext_org_id = VALUES(project_ext_org_id), @@ -235,6 +238,7 @@ public class AihrCaseService { media_url = VALUES(media_url), update_time = now() """, + tenantId(), state.id(), state.fileName(), firstNonBlank(state.projectExtOrgId(), ""), @@ -284,6 +288,10 @@ public class AihrCaseService { } } + private String tenantId() { + return firstNonBlank(LoginHelper.getTenantId(), "000000"); + } + private Optional organizeWithModel(CaseState state) { String system = """ 你是物业培训案例编辑。请把转写内容整理成可复用案例。 @@ -388,8 +396,8 @@ public class AihrCaseService { List persisted = jdbcTemplate.query(""" SELECT case_id, file_name, project_ext_org_id, transcript, type , media_oss_id, media_url - FROM aihr_case_record - WHERE tenant_id = '000000' AND case_id = ? + FROM aihr_case_record + WHERE tenant_id = ? AND case_id = ? LIMIT 1 """, (rs, rowNum) -> new CaseState( rs.getString("case_id"), @@ -400,7 +408,7 @@ public class AihrCaseService { null, rs.getObject("media_oss_id", Long.class), firstNonBlank(rs.getString("media_url"), "") - ), normalizedCaseId); + ), tenantId(), normalizedCaseId); state = persisted.stream().findFirst().orElse(null); if (state != null) { cases.put(normalizedCaseId, state); diff --git a/backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/service/AihrCaseServiceTest.java b/backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/service/AihrCaseServiceTest.java index 784308a3..45fe05fd 100644 --- a/backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/service/AihrCaseServiceTest.java +++ b/backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/service/AihrCaseServiceTest.java @@ -39,8 +39,8 @@ class AihrCaseServiceTest { UploadResponse response = service.upload(new MockMultipartFile("file", "case.webm", "audio/webm", new byte[]{1}), "P1", null); assertEquals("张*先生住在12栋****,手机号1390****1111,说2-****漏水", response.transcript()); - assertEquals(response.transcript(), jdbcTemplate.updateArgs[6]); assertEquals(response.transcript(), jdbcTemplate.updateArgs[7]); + assertEquals(response.transcript(), jdbcTemplate.updateArgs[8]); assertFalse(response.transcript().contains("13900001111")); assertFalse(response.transcript().contains("1201")); assertFalse(response.transcript().contains("2-304")); @@ -67,8 +67,8 @@ class AihrCaseServiceTest { assertEquals(123L, response.mediaOssId()); assertEquals("https://oss.local/cases/case.webm", response.mediaUrl()); - assertEquals(123L, jdbcTemplate.updateArgs[8]); - assertEquals("https://oss.local/cases/case.webm", jdbcTemplate.updateArgs[9]); + assertEquals(123L, jdbcTemplate.updateArgs[9]); + assertEquals("https://oss.local/cases/case.webm", jdbcTemplate.updateArgs[10]); } @Test @@ -105,7 +105,7 @@ class AihrCaseServiceTest { assertEquals("业主投诉后完成跟进", detail.transcript()); assertEquals("https://oss.local/cases/refreshed-case.webm", detail.mediaUrl()); assertEquals(456L, detail.mediaOssId()); - assertEquals("P1", jdbcTemplate.lastArgs[1]); + assertEquals("P1", jdbcTemplate.lastArgs[2]); } @Test @@ -158,7 +158,7 @@ class AihrCaseServiceTest { @Override public List query(String sql, RowMapper rowMapper, Object... args) { - this.lastCaseId = String.valueOf(args[0]); + this.lastCaseId = String.valueOf(args[1]); this.lastArgs = args; try { ResultSet resultSet = mock(ResultSet.class); diff --git a/docs/BRD_IMPLEMENTATION_AUDIT.md b/docs/BRD_IMPLEMENTATION_AUDIT.md index 88985c5b..f762e53d 100644 --- a/docs/BRD_IMPLEMENTATION_AUDIT.md +++ b/docs/BRD_IMPLEMENTATION_AUDIT.md @@ -92,3 +92,4 @@ - 2026-07-14 生产 schema 修复:使用生产 `/opt/wygj/config/wygj.env` 中的数据库连接配置核对后,按仓库现有 `aihr_practice_mysql8.sql`/运行时建表定义执行 `CREATE TABLE IF NOT EXISTS`,补齐缺失的 `aihr_practice_audio`(9 列)和 `aihr_practice_calibration`(11 列);未写入任何业务行、未新增自定义字段。复核结果为 `org_active=22`、`org_phone_linked=0`、`calibration=0`、`sop_review=13`、`audio=0`,租户接口仍返回 200。该操作只解除 schema 漂移阻塞,不代表正式组织映射或 M5 试点完成。 - 2026-07-14 候选人关联发布边界核对:生产根站点与 `/h5/` 返回 200,`wygj-aihr.service` active,线上后端仍为上一版 jar;远端管理端主 JS 未包含本批 `入职主体关联/candidate-links`,本批源码已提交但尚未发布。后续发布需按现有备份、静态资源同步、后端重启和浏览器回归流程执行。 - 2026-07-14 门禁合同复核:`scripts/tests/demo-check-pilot-gates.test.sh` 通过,确认严格预检覆盖批次日期、唯一在职身份、1–2 个项目、每人至少 10 次训练、80% 完训、20 条校准、AI/人工分档一致率、SOP 可用率、满意度和音频/RAG schema;`scripts/tests/pilot-export-contract.test.mjs` 5/5 通过,AIHR 案例/对练定向测试 31/31 通过,`mobile-uni` 类型检查与 49 个单测通过。当前没有新增可安全实现的门禁缺口,剩余阻塞仍是正式组织映射、正式试点数据、视频样片和方言样本等外部输入。 +- 2026-07-14 租户边界复核:AI 面试结果、候选人入职主体关联和案例记录的查询、写入、组织身份解析均改为使用当前登录租户;无登录上下文时仅保留本地开发默认租户 `000000` 回退。该修复已覆盖案例服务的列表、详情、项目范围、贡献者校验、保存和持久化回读路径,仍需在正式多租户环境做隔离回归。