feat(aihr): flag masked org phones in precheck
This commit is contained in:
+1
@@ -28,6 +28,7 @@ public final class AihrOrgSyncDto {
|
|||||||
int syncedCount,
|
int syncedCount,
|
||||||
int skippedCount,
|
int skippedCount,
|
||||||
int phoneLinked,
|
int phoneLinked,
|
||||||
|
int maskedPhone,
|
||||||
int suspectText,
|
int suspectText,
|
||||||
List<String> warnings
|
List<String> warnings
|
||||||
) {
|
) {
|
||||||
|
|||||||
+22
-4
@@ -42,6 +42,9 @@ public class AihrOrgSyncService {
|
|||||||
private static final int MAX_PAGE_SIZE = 500;
|
private static final int MAX_PAGE_SIZE = 500;
|
||||||
private static final int DEFAULT_MAX_PAGES = 200;
|
private static final int DEFAULT_MAX_PAGES = 200;
|
||||||
private static final Duration HTTP_TIMEOUT = Duration.ofSeconds(30);
|
private static final Duration HTTP_TIMEOUT = Duration.ofSeconds(30);
|
||||||
|
private static final String[] PHONE_FIELDS = {"phone", "phone_number", "phoneNumber", "mobile", "mobile_phone",
|
||||||
|
"mobilePhone", "mobile_tel", "mobileTel", "cellphone", "cellPhone", "contact_phone",
|
||||||
|
"contactPhone", "contact_mobile", "contactMobile", "phonenumber", "telephone", "tel", "手机号", "手机", "联系电话"};
|
||||||
|
|
||||||
private final ObjectMapper objectMapper;
|
private final ObjectMapper objectMapper;
|
||||||
private final JdbcTemplate jdbcTemplate;
|
private final JdbcTemplate jdbcTemplate;
|
||||||
@@ -85,11 +88,15 @@ public class AihrOrgSyncService {
|
|||||||
|
|
||||||
List<OrgRow> rows = new ArrayList<>();
|
List<OrgRow> rows = new ArrayList<>();
|
||||||
int skipped = 0;
|
int skipped = 0;
|
||||||
|
int maskedPhone = 0;
|
||||||
for (JsonNode employee : employeeItems) {
|
for (JsonNode employee : employeeItems) {
|
||||||
OrgRow row = orgRow(employee, companies, departments);
|
OrgRow row = orgRow(employee, companies, departments);
|
||||||
if (row == null) {
|
if (row == null) {
|
||||||
skipped++;
|
skipped++;
|
||||||
} else {
|
} else {
|
||||||
|
if (row.personPhone().isBlank() && hasUnusablePhoneCandidate(employee)) {
|
||||||
|
maskedPhone++;
|
||||||
|
}
|
||||||
rows.add(row);
|
rows.add(row);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -101,6 +108,9 @@ public class AihrOrgSyncService {
|
|||||||
if (suspectText > 0) {
|
if (suspectText > 0) {
|
||||||
warnings.add("外部员工快照存在 " + suspectText + " 条疑似乱码字段,请检查开放接口编码或导入链路");
|
warnings.add("外部员工快照存在 " + suspectText + " 条疑似乱码字段,请检查开放接口编码或导入链路");
|
||||||
}
|
}
|
||||||
|
if (maskedPhone > 0) {
|
||||||
|
warnings.add("外部员工快照存在 " + maskedPhone + " 条脱敏/不可用手机号,不能用于移动端正式登录映射");
|
||||||
|
}
|
||||||
if (!dryRun && replaceExisting && rows.isEmpty()) {
|
if (!dryRun && replaceExisting && rows.isEmpty()) {
|
||||||
throw new IllegalArgumentException("外部员工快照为空,已阻止覆盖本地组织人员快照");
|
throw new IllegalArgumentException("外部员工快照为空,已阻止覆盖本地组织人员快照");
|
||||||
}
|
}
|
||||||
@@ -118,6 +128,7 @@ public class AihrOrgSyncService {
|
|||||||
dryRun ? 0 : rows.size(),
|
dryRun ? 0 : rows.size(),
|
||||||
skipped,
|
skipped,
|
||||||
phoneLinked,
|
phoneLinked,
|
||||||
|
maskedPhone,
|
||||||
suspectText,
|
suspectText,
|
||||||
List.copyOf(warnings)
|
List.copyOf(warnings)
|
||||||
);
|
);
|
||||||
@@ -525,10 +536,7 @@ public class AihrOrgSyncService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static String phone(JsonNode employee) {
|
private static String phone(JsonNode employee) {
|
||||||
String[] fields = {"phone", "phone_number", "phoneNumber", "mobile", "mobile_phone",
|
for (String field : PHONE_FIELDS) {
|
||||||
"mobilePhone", "mobile_tel", "mobileTel", "cellphone", "cellPhone", "contact_phone",
|
|
||||||
"contactPhone", "contact_mobile", "contactMobile", "phonenumber", "telephone", "tel", "手机号", "手机", "联系电话"};
|
|
||||||
for (String field : fields) {
|
|
||||||
String normalized = normalizeMobilePhone(text(employee, field));
|
String normalized = normalizeMobilePhone(text(employee, field));
|
||||||
if (!normalized.isBlank()) {
|
if (!normalized.isBlank()) {
|
||||||
return normalized;
|
return normalized;
|
||||||
@@ -545,6 +553,16 @@ public class AihrOrgSyncService {
|
|||||||
return digits.matches("1[3-9]\\d{9}") ? digits : "";
|
return digits.matches("1[3-9]\\d{9}") ? digits : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static boolean hasUnusablePhoneCandidate(JsonNode employee) {
|
||||||
|
for (String field : PHONE_FIELDS) {
|
||||||
|
String value = text(employee, field);
|
||||||
|
if (!value.isBlank() && normalizeMobilePhone(value).isBlank()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private static boolean hasSuspectText(OrgRow row) {
|
private static boolean hasSuspectText(OrgRow row) {
|
||||||
return hasQuestionMark(row.projectName())
|
return hasQuestionMark(row.projectName())
|
||||||
|| hasQuestionMark(row.deptName())
|
|| hasQuestionMark(row.deptName())
|
||||||
|
|||||||
+12
@@ -1,9 +1,12 @@
|
|||||||
package org.dromara.aihr.service;
|
package org.dromara.aihr.service;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.Tag;
|
import org.junit.jupiter.api.Tag;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
@Tag("dev")
|
@Tag("dev")
|
||||||
public class AihrOrgSyncServiceTest {
|
public class AihrOrgSyncServiceTest {
|
||||||
@@ -14,4 +17,13 @@ public class AihrOrgSyncServiceTest {
|
|||||||
assertEquals("", AihrOrgSyncService.normalizeMobilePhone("139****1111"));
|
assertEquals("", AihrOrgSyncService.normalizeMobilePhone("139****1111"));
|
||||||
assertEquals("", AihrOrgSyncService.normalizeMobilePhone("025-88888888"));
|
assertEquals("", AihrOrgSyncService.normalizeMobilePhone("025-88888888"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void unusablePhoneCandidateDetectsMaskedPhone() throws Exception {
|
||||||
|
ObjectMapper mapper = new ObjectMapper();
|
||||||
|
|
||||||
|
assertTrue(AihrOrgSyncService.hasUnusablePhoneCandidate(mapper.readTree("{\"phone\":\"139****1111\"}")));
|
||||||
|
assertFalse(AihrOrgSyncService.hasUnusablePhoneCandidate(mapper.readTree("{\"phone\":\"13900001111\"}")));
|
||||||
|
assertFalse(AihrOrgSyncService.hasUnusablePhoneCandidate(mapper.readTree("{\"name\":\"张三\"}")));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,5 +21,5 @@
|
|||||||
- 优先复用已有 `/api/aihr/mobile/practice/*` 与 `/api/train/practice/*` 接口。
|
- 优先复用已有 `/api/aihr/mobile/practice/*` 与 `/api/train/practice/*` 接口。
|
||||||
- 组织人员快照在 `组织与权限` 页分页展示;业务页只取下拉候选,真实复盘范围由接口参数落到后端过滤。
|
- 组织人员快照在 `组织与权限` 页分页展示;业务页只取下拉候选,真实复盘范围由接口参数落到后端过滤。
|
||||||
- `组织与权限` 页暴露手机号覆盖数和疑似乱码数,只做质量提示,不展示手机号明细。
|
- `组织与权限` 页暴露手机号覆盖数和疑似乱码数,只做质量提示,不展示手机号明细。
|
||||||
- 组织同步预检同步返回手机号覆盖数和疑似乱码数;手机号只统计可用于移动端登录的 11 位手机号。
|
- 组织同步预检同步返回手机号覆盖数、脱敏手机号数和疑似乱码数;手机号只统计可用于移动端登录的 11 位手机号。
|
||||||
- 规则配置中心先做运营口径展示;需要真实持久化时再补 `incentive_rule` 等表。
|
- 规则配置中心先做运营口径展示;需要真实持久化时再补 `incentive_rule` 等表。
|
||||||
|
|||||||
@@ -114,6 +114,7 @@ export type OrgSyncResponse = {
|
|||||||
syncedCount: number;
|
syncedCount: number;
|
||||||
skippedCount: number;
|
skippedCount: number;
|
||||||
phoneLinked: number;
|
phoneLinked: number;
|
||||||
|
maskedPhone: number;
|
||||||
suspectText: number;
|
suspectText: number;
|
||||||
warnings: string[];
|
warnings: string[];
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -156,6 +156,9 @@
|
|||||||
<div>
|
<div>
|
||||||
<span>手机号/疑似乱码</span><strong>{{ syncResult.phoneLinked }} / {{ syncResult.suspectText }}</strong>
|
<span>手机号/疑似乱码</span><strong>{{ syncResult.phoneLinked }} / {{ syncResult.suspectText }}</strong>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>脱敏手机号</span><strong>{{ syncResult.maskedPhone }}</strong>
|
||||||
|
</div>
|
||||||
<p v-if="syncResult.warnings?.length">{{ syncResult.warnings.join(';') }}</p>
|
<p v-if="syncResult.warnings?.length">{{ syncResult.warnings.join(';') }}</p>
|
||||||
<p v-else>同步接口已返回结果。</p>
|
<p v-else>同步接口已返回结果。</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user