fix(aihr): gate organization display fields in production
This commit is contained in:
@@ -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 配置不含此项)
|
||||
|
||||
@@ -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:
|
||||
|
||||
+19
-5
@@ -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,6 +446,7 @@ public class AihrOrgSyncService {
|
||||
String keywordValue = clean(keyword);
|
||||
if (!keywordValue.isBlank()) {
|
||||
String like = "%" + keywordValue + "%";
|
||||
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 ?)
|
||||
@@ -452,6 +457,15 @@ public class AihrOrgSyncService {
|
||||
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()) {
|
||||
|
||||
@@ -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/法务确认清理、保留期和最小字段方案。
|
||||
|
||||
@@ -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`)
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
</template>
|
||||
</el-alert>
|
||||
<div class="org-filters">
|
||||
<el-input v-model="orgFilters.keyword" clearable placeholder="姓名/岗位/部门/外部ID" @keyup.enter="searchOrgSnapshot" />
|
||||
<el-input v-model="orgFilters.keyword" clearable placeholder="岗位/项目/外部ID" @keyup.enter="searchOrgSnapshot" />
|
||||
<el-select v-model="orgFilters.projectCode" clearable placeholder="项目">
|
||||
<el-option v-for="item in orgSnapshot?.projects || []" :key="item.projectCode" :label="item.projectName" :value="item.projectCode" />
|
||||
</el-select>
|
||||
@@ -66,7 +66,9 @@
|
||||
<el-table v-loading="orgLoading" :data="orgSnapshot?.rows || []" size="small" empty-text="暂无组织人员快照">
|
||||
<el-table-column prop="projectName" label="项目" min-width="150" show-overflow-tooltip />
|
||||
<el-table-column prop="deptName" label="部门/条线" min-width="130" show-overflow-tooltip />
|
||||
<el-table-column prop="personName" label="姓名" width="110" />
|
||||
<el-table-column label="人员主体" width="140">
|
||||
<template #default="{ row }">{{ row.personName || row.extPartyId }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="positionName" label="岗位" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column prop="positionLevel" label="层级" width="100" />
|
||||
<el-table-column prop="employmentStatus" label="状态" width="90">
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
<el-option
|
||||
v-for="person in orgPeople"
|
||||
:key="person.extPartyId"
|
||||
:label="`${person.personName} / ${person.positionName}`"
|
||||
:label="`${person.personName || person.extPartyId} / ${person.positionName}`"
|
||||
:value="person.extPartyId"
|
||||
/>
|
||||
</el-select>
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user