fix(aihr): scope case records to tenant

This commit is contained in:
2026-07-14 06:34:33 +08:00
parent c7f4ce4a48
commit a67ccd455d
3 changed files with 24 additions and 15 deletions
@@ -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<CaseSummary> organizeWithModel(CaseState state) {
String system = """
你是物业培训案例编辑。请把转写内容整理成可复用案例。
@@ -388,8 +396,8 @@ public class AihrCaseService {
List<CaseState> 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);
@@ -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 <T> List<T> query(String sql, RowMapper<T> rowMapper, Object... args) {
this.lastCaseId = String.valueOf(args[0]);
this.lastCaseId = String.valueOf(args[1]);
this.lastArgs = args;
try {
ResultSet resultSet = mock(ResultSet.class);
+1
View File
@@ -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` 回退。该修复已覆盖案例服务的列表、详情、项目范围、贡献者校验、保存和持久化回读路径,仍需在正式多租户环境做隔离回归。