fix(aihr): namespace external organization directory ids

This commit is contained in:
2026-07-24 09:24:45 +08:00
parent faa2a77e9a
commit e45425f4d3
4 changed files with 52 additions and 17 deletions
@@ -302,21 +302,34 @@ public class AihrOrgSyncService {
}
private static ExternalDirectoryNode directoryNode(JsonNode item, String nodeType) {
String externalId = switch (nodeType) {
String rawExternalId = switch (nodeType) {
case "GROUP" -> firstNonBlank(text(item, "id", "group_id", "groupId"));
case "COMPANY" -> firstNonBlank(text(item, "id", "company_id", "companyId"));
case "PROJECT" -> firstNonBlank(text(item, "id", "project_id", "projectId"));
case "DEPARTMENT" -> firstNonBlank(text(item, "id", "department_id", "departmentId", "dept_id", "deptId"));
default -> "";
};
if (externalId.isBlank()) {
if (rawExternalId.isBlank()) {
return null;
}
String externalId = stableDirectoryId(nodeType, rawExternalId);
String parentExternalId = switch (nodeType) {
case "GROUP" -> firstNonBlank(text(item, "parent_group_id", "parentGroupId", "parent_id", "parentId"));
case "COMPANY" -> firstNonBlank(text(item, "group_id", "groupId", "parent_group_id", "parentGroupId", "parent_id", "parentId"));
case "PROJECT" -> firstNonBlank(text(item, "company_id", "companyId", "group_id", "groupId", "parent_id", "parentId"));
case "DEPARTMENT" -> firstNonBlank(text(item, "project_id", "projectId", "company_id", "companyId", "group_id", "groupId", "parent_id", "parentId"));
case "GROUP" -> stableDirectoryId("GROUP", firstNonBlank(text(item, "parent_group_id", "parentGroupId", "parent_id", "parentId")));
case "COMPANY" -> firstNonBlank(
stableDirectoryId("GROUP", text(item, "group_id", "groupId", "parent_group_id", "parentGroupId")),
stableDirectoryId("GROUP", text(item, "parent_id", "parentId"))
);
case "PROJECT" -> firstNonBlank(
stableDirectoryId("COMPANY", text(item, "company_id", "companyId")),
stableDirectoryId("GROUP", text(item, "group_id", "groupId")),
stableDirectoryId("COMPANY", text(item, "parent_id", "parentId"))
);
case "DEPARTMENT" -> firstNonBlank(
stableDirectoryId("PROJECT", text(item, "project_id", "projectId")),
stableDirectoryId("COMPANY", text(item, "company_id", "companyId")),
stableDirectoryId("GROUP", text(item, "group_id", "groupId")),
stableDirectoryId("DEPARTMENT", text(item, "parent_id", "parentId"))
);
default -> "";
};
if (externalId.length() > 128 || parentExternalId.length() > 128) {
@@ -332,6 +345,26 @@ public class AihrOrgSyncService {
return new ExternalDirectoryNode(externalId, parentExternalId, nodeType, truncate(name, 200));
}
static String rawDirectoryId(String stableId, String nodeType) {
String prefix = directoryIdPrefix(nodeType);
return stableId != null && stableId.startsWith(prefix) ? stableId.substring(prefix.length()) : clean(stableId);
}
private static String stableDirectoryId(String nodeType, String rawId) {
String value = clean(rawId);
return value.isBlank() ? "" : directoryIdPrefix(nodeType) + value;
}
private static String directoryIdPrefix(String nodeType) {
return switch (nodeType) {
case "GROUP" -> "G:";
case "COMPANY" -> "C:";
case "PROJECT" -> "P:";
case "DEPARTMENT" -> "D:";
default -> throw new IllegalArgumentException("不支持的组织节点类型:" + nodeType);
};
}
private List<JsonNode> fetchSnapshot(String baseUrl, String token, SyncRequest req, String resourceType,
int pageSize, int maxPages) {
List<JsonNode> items = new ArrayList<>();
@@ -227,10 +227,11 @@ public class AihrTenantOrgGovernanceService {
String groupId = null;
String companyId = null;
String departmentId = null;
String sourceExternalId = AihrOrgSyncService.rawDirectoryId(binding.rootExternalId(), binding.rootNodeType());
switch (binding.rootNodeType()) {
case "GROUP" -> groupId = binding.rootExternalId();
case "COMPANY" -> companyId = binding.rootExternalId();
case "DEPARTMENT" -> departmentId = binding.rootExternalId();
case "GROUP" -> groupId = sourceExternalId;
case "COMPANY" -> companyId = sourceExternalId;
case "DEPARTMENT" -> departmentId = sourceExternalId;
default -> throw new ServiceException("ORG_SCOPE_TYPE_NOT_SYNCABLE", HttpStatus.CONFLICT);
}
SyncRequest source = requested == null
@@ -296,10 +296,10 @@ public class AihrOrgSyncServiceTest {
HttpServer server = HttpServer.create(new InetSocketAddress("127.0.0.1", 0), 0);
server.createContext("/api/open/v1/sync/snapshot", exchange -> {
String query = exchange.getRequestURI().getRawQuery();
String items = query.contains("resource_type=group") ? "[{\"id\":\"G-1\",\"name\":\"银城集团\"}]"
: query.contains("resource_type=company") ? "[{\"id\":\"C-1\",\"group_id\":\"G-1\",\"name\":\"南京公司\"}]"
: query.contains("resource_type=project") ? "[{\"id\":\"P-1\",\"company_id\":\"C-1\",\"name\":\"翡翠湾项目\"}]"
: query.contains("resource_type=department") ? "[{\"id\":\"D-1\",\"project_id\":\"P-1\",\"name\":\"客服部\"}]" : "[]";
String items = query.contains("resource_type=group") ? "[{\"id\":\"1\",\"name\":\"银城集团\"}]"
: query.contains("resource_type=company") ? "[{\"id\":\"1\",\"group_id\":\"1\",\"name\":\"南京公司\"}]"
: query.contains("resource_type=project") ? "[{\"id\":\"1\",\"company_id\":\"1\",\"name\":\"翡翠湾项目\"}]"
: query.contains("resource_type=department") ? "[{\"id\":\"1\",\"project_id\":\"1\",\"name\":\"客服部\"}]" : "[]";
byte[] body = ("{\"data\":{\"items\":" + items + ",\"total\":1,\"has_more\":false}}").getBytes(StandardCharsets.UTF_8);
exchange.getResponseHeaders().set("Content-Type", "application/json; charset=utf-8");
exchange.sendResponseHeaders(200, body.length);
@@ -319,9 +319,10 @@ public class AihrOrgSyncServiceTest {
var snapshot = service.directorySnapshot();
assertEquals(4, snapshot.nodes().size());
assertTrue(snapshot.nodes().stream().anyMatch(node -> "GROUP".equals(node.nodeType()) && "G-1".equals(node.externalId())));
assertEquals(4, snapshot.nodes().stream().map(node -> node.externalId()).distinct().count());
assertTrue(snapshot.nodes().stream().anyMatch(node -> "GROUP".equals(node.nodeType()) && "G:1".equals(node.externalId())));
assertTrue(snapshot.nodes().stream().anyMatch(node -> "DEPARTMENT".equals(node.nodeType())
&& "P-1".equals(node.parentExternalId())));
&& "P:1".equals(node.parentExternalId())));
} finally {
server.stop(0);
}
@@ -104,10 +104,10 @@ class AihrTenantOrgGovernanceServiceTest {
when(resultSet.getString("tenant_id")).thenReturn("000000");
when(resultSet.getString("tenant_name")).thenReturn("平台租户");
when(resultSet.getString("source_code")).thenReturn(AihrTenantOrgGovernanceService.SOURCE_CODE);
when(resultSet.getString("root_external_id")).thenReturn("C-1");
when(resultSet.getString("root_external_id")).thenReturn("C:C-1");
when(resultSet.getString("root_name")).thenReturn("南京公司");
when(resultSet.getString("root_node_type")).thenReturn("COMPANY");
when(resultSet.getString("root_path")).thenReturn("G-1/C-1");
when(resultSet.getString("root_path")).thenReturn("G:G-1/C:C-1");
when(resultSet.getString("mode")).thenReturn(mode);
when(resultSet.getString("status")).thenReturn(AihrTenantOrgGovernanceService.ACTIVE);
when(resultSet.getString("directory_version")).thenReturn("v1");