From ed998753be73c120d6c73eaafa88d8b64c4e3d30 Mon Sep 17 00:00:00 2001 From: let5sne Date: Tue, 14 Jul 2026 12:07:10 +0800 Subject: [PATCH] fix(aihr): gate organization display fields in production --- .../src/main/resources/application-dev.yml | 3 ++ .../src/main/resources/application-prod.yml | 3 ++ .../aihr/service/AihrOrgSyncService.java | 44 ++++++++++++------- docs/BRD_IMPLEMENTATION_AUDIT.md | 2 + docs/DEV_SETUP.md | 1 + frontend/src/views/admin/org-permission.vue | 6 ++- frontend/src/views/train/reviews.vue | 2 +- scripts/demo-check.sh | 3 ++ 8 files changed, 46 insertions(+), 18 deletions(-) diff --git a/backend/ruoyi-admin/src/main/resources/application-dev.yml b/backend/ruoyi-admin/src/main/resources/application-dev.yml index c4d4ee7e..3d85df18 100644 --- a/backend/ruoyi-admin/src/main/resources/application-dev.yml +++ b/backend/ruoyi-admin/src/main/resources/application-dev.yml @@ -156,6 +156,9 @@ mail: --- # sms 短信 支持 阿里云 腾讯云 云片 等等各式各样的短信服务商 # https://sms4j.com/doc3/ 差异配置文档地址 支持单厂商多配置,可以配置多个同时使用 aihr: + org-sync: + # 仅开发演示保留姓名/部门展示;生产默认关闭,组织同步只保留必要主体与范围字段 + store-display-fields: true sms: login-template-id: ${AIHR_SMS_LOGIN_TEMPLATE_ID:} # 演示兜底:非空则不真发短信,验证码固定为该值(仅 dev,prod 配置不含此项) diff --git a/backend/ruoyi-admin/src/main/resources/application-prod.yml b/backend/ruoyi-admin/src/main/resources/application-prod.yml index eb0f95e2..b464e006 100644 --- a/backend/ruoyi-admin/src/main/resources/application-prod.yml +++ b/backend/ruoyi-admin/src/main/resources/application-prod.yml @@ -159,6 +159,9 @@ mail: --- # sms 短信 支持 阿里云 腾讯云 云片 等等各式各样的短信服务商 # https://sms4j.com/doc3/ 差异配置文档地址 支持单厂商多配置,可以配置多个同时使用 aihr: + org-sync: + # BRD 5.4:生产不复制外部人员姓名/部门到本地展示快照 + store-display-fields: false sms: login-template-id: ${AIHR_SMS_LOGIN_TEMPLATE_ID:} sms: diff --git a/backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrOrgSyncService.java b/backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrOrgSyncService.java index 28bb789c..56eaac83 100644 --- a/backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrOrgSyncService.java +++ b/backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrOrgSyncService.java @@ -68,6 +68,9 @@ public class AihrOrgSyncService { @Value("${aihr.org-sync.signing-secret:${AIHR_ORG_SYNC_SIGNING_SECRET:}}") private String configuredSigningSecret; + @Value("${aihr.org-sync.store-display-fields:${AIHR_ORG_SYNC_STORE_DISPLAY_FIELDS:false}}") + private boolean storeDisplayFields; + public SyncResponse sync(SyncRequest request) { SyncRequest req = request == null ? new SyncRequest(null, null, null, null, null, null, null, null) : request; String baseUrl = normalizeBaseUrl(configuredBaseUrl); @@ -208,9 +211,9 @@ public class AihrOrgSyncService { rs.getLong("id"), rs.getString("project_code"), rs.getString("project_name"), - rs.getString("dept_name"), + storeDisplayFields ? rs.getString("dept_name") : "", rs.getString("ext_party_id"), - rs.getString("person_name"), + storeDisplayFields ? rs.getString("person_name") : "", rs.getString("position_name"), rs.getString("position_level"), rs.getString("employment_status"), @@ -387,19 +390,20 @@ public class AihrOrgSyncService { String projectName = firstNonBlank( text(employee, "project_name", "projectName", "company_name", "companyName"), company == null ? "" : company.name(), - deptName, + storeDisplayFields ? deptName : "", "组织架构" ); + String personName = firstNonBlank(text(employee, "name", "person_name", "personName", "employee_name", "employeeName"), "-"); String positionName = firstNonBlank(text(employee, "position_name", "positionName", "job_title", "jobTitle", "post_name", "postName", "job_name", "jobName", "role_name", "roleName", "position", "title"), "员工"); String positionLevel = firstNonBlank(text(employee, "position_level", "positionLevel", "job_level", "jobLevel"), level(positionName)); return new OrgRow( projectCode, projectName, - deptName, + storeDisplayFields ? deptName : "", extPartyId, phone(employee), - firstNonBlank(text(employee, "name", "person_name", "personName", "employee_name", "employeeName"), "-"), + storeDisplayFields ? personName : "", positionName, positionLevel(positionLevel, positionName), status(text(employee, "status", "employment_status", "employmentStatus")), @@ -442,16 +446,26 @@ public class AihrOrgSyncService { String keywordValue = clean(keyword); if (!keywordValue.isBlank()) { String like = "%" + keywordValue + "%"; - where.append(""" - and (person_name like ? or ext_party_id like ? or person_phone like ? - or position_name like ? or dept_name like ? or project_name like ?) - """); - args.add(like); - args.add(like); - args.add(like); - args.add(like); - args.add(like); - args.add(like); + if (storeDisplayFields) { + where.append(""" + and (person_name like ? or ext_party_id like ? or person_phone like ? + or position_name like ? or dept_name like ? or project_name like ?) + """); + args.add(like); + args.add(like); + args.add(like); + args.add(like); + args.add(like); + args.add(like); + } else { + where.append(""" + and (ext_party_id like ? or position_name like ? or project_code like ? or project_name like ?) + """); + args.add(like); + args.add(like); + args.add(like); + args.add(like); + } } String project = clean(projectCode); if (!project.isBlank()) { diff --git a/docs/BRD_IMPLEMENTATION_AUDIT.md b/docs/BRD_IMPLEMENTATION_AUDIT.md index f1312397..a8fa81b6 100644 --- a/docs/BRD_IMPLEMENTATION_AUDIT.md +++ b/docs/BRD_IMPLEMENTATION_AUDIT.md @@ -208,3 +208,5 @@ - 2026-07-14 BRD G3/G6 异步模型租户上下文复核:`AihrModelSeedService` 原先用 `LoginHelper.getTenantId()` 解析模型配置,异步上传队列、目录导入等 worker 虽已用 `TenantHelper.dynamic(tenantId, ...)`,进入模型/语音/RAG调用后仍可能因无登录态回退 `000000`;现统一改用动态租户优先的 `TenantHelper.getTenantId()`,普通请求仍回退登录租户,补源码契约测试。覆盖模型配置、对练/面试、语音、SOP 向量/重排/视觉和异步资料加工的共享边界;未修改数据库或生产环境。 - 2026-07-14 测试门禁复核:父 POM 默认 `skipTests=true`,普通 `mvn ... test` 只完成编译;改用 `-DskipTests=false` 真跑 AIHR 测试时发现 `AihrSensitiveTextTest` 仍检查已移出 `AihrModelSeedService` 的 `latencyMs` 字符串,已按当前 `ModelUsage + Duration` 实现修正断言。后续 AIHR 测试命令必须显式关闭跳测,避免把编译成功误判为测试通过。 - 2026-07-14 最新线上只读复核:生产根站与 `/h5/` 返回 `200`,线上管理端仍加载 `index-CJZ3Ax3Z.js`(SHA-256 `8b9278a26ccb760abce489b12a20b221748c64d4c4153613d2511777bb9677bc`),H5 仍加载 `index-D4-NrEpb.js`(SHA-256 `ab15bfd17cabe58e2f34b0ac61ee198a837128ed85dc6bb0ddf77bc3e22dcd9c`);当前分支 `b32be4a3` 工作区本地构建仍为管理端 `index-v0LFXtVo.js`、H5 `index-Byx3WtY1.js`,两端资源未对齐。本轮只读核验,未执行生产静态同步、后端重启或业务数据写入。 +- 2026-07-14 BRD 5.4 主数据边界复核:发现 `aihr_org_snapshot` 及 `AihrOrgSyncService` 当前仍持久化并通过管理端组织快照返回 `project_name/dept_name/person_name`,同时保留 `person_phone` 做移动端身份映射;这与 BRD“本地只存外部 `party_id`,绝不复制姓名/部门”的硬约束不一致。TechSpec 3.1 又把 `sync_person.name/phone` 列为外部同步快照字段,项目现有 Demo/管理端也依赖姓名、部门和项目展示,因此不能直接删列或清空生产数据,否则会破坏组织筛选、主管范围展示和训练记录标签。该项暂列 P0 合规缺口:正式组织同步前必须由业务/法务/安全确认“生产只存 ID 与必要映射”还是允许受控显示缓存,并给出迁移、脱敏和管理端展示方案;在决策完成前不执行正式覆盖同步,也不把当前组织快照称为符合 BRD 5.4。 +- 2026-07-14 BRD 5.4 渐进缓解:增加 `aihr.org-sync.store-display-fields` 开关,生产配置默认 `false`,开发配置显式 `true`;关闭时组织同步不再写入姓名/部门,组织快照接口也不返回历史姓名/部门,关键词搜索同步收窄为外部主体、项目和岗位字段,管理端人员列回退显示 `ext_party_id`。该修复降低新同步和 API 暴露风险,但不等于完成数据模型迁移:历史生产行仍可能保留旧显示字段,`person_phone` 仍是身份映射必要字段,正式上线前仍需 DBA/法务确认清理、保留期和最小字段方案。 diff --git a/docs/DEV_SETUP.md b/docs/DEV_SETUP.md index e01deba8..e7e9f3b9 100644 --- a/docs/DEV_SETUP.md +++ b/docs/DEV_SETUP.md @@ -120,6 +120,7 @@ AIHR_AI_SPEECH_ENABLED=true - `aihr_interview_result_mysql8.sql` 已纳入 reset 脚本;AI 面试评分完成后结果落 `aihr_interview_result` - `aihr_candidate_material_mysql8.sql` 已纳入;候选人端补充资料文件写 `sys_oss`/MinIO,关系落 `aihr_candidate_material` - `aihr_org_snapshot_mysql8.sql` 已纳入 reset 脚本;组织人员本地 seed(2 个住宅项目 22 人,项目经理/主管/一线三层)支撑演示,外部开放组织系统配置完成后用 `POST /api/aihr/org/sync` 拉取 `company/department/employee` 快照并覆盖本地 `aihr_org_snapshot` +- 组织同步生产默认关闭 `aihr.org-sync.store-display-fields`,不把外部姓名/部门写入或返回组织人员展示快照;开发环境显式打开该开关仅用于 Demo。项目范围、岗位和外部主体 ID仍用于权限与身份映射。 - 本地组织 seed 带演示手机号,可用 `13900000103` 验证员工端自动识别物业管家岗位,用 `13900000202` 验证主管端项目范围 - SOP 知识库支持 `.txt/.md/.markdown/.pdf/.doc/.docx/.xls/.xlsx/.ppt/.pptx` 上传到 MinIO 后解析入库,接口为 `POST /api/knowledge/doc/upload`,单文件上限 100MB;管理端上传请求单独放宽到 180s,PDF 解析/归类/向量化较慢时不要改全局 axios 超时。资料处理中心异步接口另支持 `.zip`(≤500MB),后台安全解压后逐文件入队;最多 1000 个子文件、解压总量 ≤2GB,不支持嵌套 ZIP - 视频(`.mp4/.mov/.avi/.mkv/.webm/.m4v`,≤500MB、≤60 分钟)只走资料处理中心批量导入(异步队列):ffmpeg 抽音轨分段调 asr 转写 + 抽关键帧调 vision 提取画面文字,合并后归类切片入库,片段带 `[mm:ss]` 时间戳;**依赖服务器安装 ffmpeg/ffprobe**(macOS `brew install ffmpeg`,Linux `apt install ffmpeg`) diff --git a/frontend/src/views/admin/org-permission.vue b/frontend/src/views/admin/org-permission.vue index b166505c..73942580 100644 --- a/frontend/src/views/admin/org-permission.vue +++ b/frontend/src/views/admin/org-permission.vue @@ -50,7 +50,7 @@
- + @@ -66,7 +66,9 @@ - + + + diff --git a/frontend/src/views/train/reviews.vue b/frontend/src/views/train/reviews.vue index ebae1c55..141baf29 100644 --- a/frontend/src/views/train/reviews.vue +++ b/frontend/src/views/train/reviews.vue @@ -48,7 +48,7 @@ diff --git a/scripts/demo-check.sh b/scripts/demo-check.sh index dd3932ed..561b8ddb 100755 --- a/scripts/demo-check.sh +++ b/scripts/demo-check.sh @@ -321,6 +321,9 @@ contains frontend/src/router/index.ts "/system/model" contains frontend/src/router/index.ts "/admin/org-permission" contains frontend/src/views/admin/org-permission.vue "dryRun ? '同步预检' : '执行同步'" contains frontend/src/views/admin/org-permission.vue "组织同步接口不可用,请检查外部同步配置" +contains frontend/src/views/admin/org-permission.vue "row.personName || row.extPartyId" +contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrOrgSyncService.java "storeDisplayFields" +contains backend/ruoyi-admin/src/main/resources/application-prod.yml "store-display-fields: false" contains mobile-uni/package.json "aihr-mobile-uni" contains mobile-uni/src/pages.json "pages/user/today/index"