Merge remote-tracking branch 'origin/dev' into dev
This commit is contained in:
@@ -13,7 +13,8 @@ import lombok.Getter;
|
||||
public enum DeptSourceEnum {
|
||||
|
||||
EXTERNAL(1, "外部部门"), // 系统创建的部门
|
||||
SYNC(2, "同步部门"); // 通过 OrgSyncService 同步的部门
|
||||
SYNC(2, "同步部门"), // 通过 OrgSyncService 同步的部门
|
||||
IWORK(3, "iWork 同步"); // 通过 iWork 同步的部门
|
||||
|
||||
/**
|
||||
* 类型
|
||||
|
||||
@@ -13,7 +13,8 @@ import lombok.Getter;
|
||||
public enum UserSourceEnum {
|
||||
|
||||
EXTERNAL(1, "外部用户"), // 系统创建、注册等方式产生的用户
|
||||
SYNC(2, "同步用户"); // 通过 UserSyncService 同步的用户
|
||||
SYNC(2, "同步用户"), // 通过 UserSyncService 同步的用户
|
||||
IWORK(3, "iWork 用户"); // 通过 iWork 全量/单条同步产生的用户
|
||||
|
||||
/**
|
||||
* 类型
|
||||
|
||||
@@ -1,23 +1,10 @@
|
||||
package com.zt.plat.module.system.controller.admin.integration.iwork;
|
||||
|
||||
import com.zt.plat.framework.common.pojo.CommonResult;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkAuthRegisterReqVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkAuthRegisterRespVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkAuthTokenReqVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkAuthTokenRespVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkDepartmentQueryReqVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkJobTitleQueryReqVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkOperationRespVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkOrgRespVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkOrgSyncReqVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkSubcompanyQueryReqVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkUserInfoReqVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkUserInfoRespVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkUserQueryReqVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkWorkflowCreateReqVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkWorkflowVoidReqVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.*;
|
||||
import com.zt.plat.module.system.service.integration.iwork.IWorkIntegrationService;
|
||||
import com.zt.plat.module.system.service.integration.iwork.IWorkOrgRestService;
|
||||
import com.zt.plat.module.system.service.integration.iwork.IWorkSyncService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
@@ -42,6 +29,7 @@ public class IWorkIntegrationController {
|
||||
|
||||
private final IWorkIntegrationService integrationService;
|
||||
private final IWorkOrgRestService orgRestService;
|
||||
private final IWorkSyncService syncService;
|
||||
|
||||
@PostMapping("/auth/register")
|
||||
@Operation(summary = "注册 iWork 凭证,获取服务端公钥与 secret")
|
||||
@@ -77,49 +65,63 @@ public class IWorkIntegrationController {
|
||||
|
||||
@PostMapping("/hr/subcompany/page")
|
||||
@Operation(summary = "获取 iWork 分部列表")
|
||||
public CommonResult<IWorkOrgRespVO> listSubcompanies(@Valid @RequestBody IWorkSubcompanyQueryReqVO reqVO) {
|
||||
public CommonResult<IWorkHrSubcompanyPageRespVO> listSubcompanies(@Valid @RequestBody IWorkSubcompanyQueryReqVO reqVO) {
|
||||
return success(orgRestService.listSubcompanies(reqVO));
|
||||
}
|
||||
|
||||
@PostMapping("/hr/department/page")
|
||||
@Operation(summary = "获取 iWork 部门列表")
|
||||
public CommonResult<IWorkOrgRespVO> listDepartments(@Valid @RequestBody IWorkDepartmentQueryReqVO reqVO) {
|
||||
public CommonResult<IWorkHrDepartmentPageRespVO> listDepartments(@Valid @RequestBody IWorkDepartmentQueryReqVO reqVO) {
|
||||
return success(orgRestService.listDepartments(reqVO));
|
||||
}
|
||||
|
||||
@PostMapping("/hr/job-title/page")
|
||||
@Operation(summary = "获取 iWork 岗位列表")
|
||||
public CommonResult<IWorkOrgRespVO> listJobTitles(@Valid @RequestBody IWorkJobTitleQueryReqVO reqVO) {
|
||||
public CommonResult<IWorkHrJobTitlePageRespVO> listJobTitles(@Valid @RequestBody IWorkJobTitleQueryReqVO reqVO) {
|
||||
return success(orgRestService.listJobTitles(reqVO));
|
||||
}
|
||||
|
||||
@PostMapping("/hr/user/page")
|
||||
@Operation(summary = "获取 iWork 人员列表")
|
||||
public CommonResult<IWorkOrgRespVO> listUsers(@Valid @RequestBody IWorkUserQueryReqVO reqVO) {
|
||||
public CommonResult<IWorkHrUserPageRespVO> listUsers(@Valid @RequestBody IWorkUserQueryReqVO reqVO) {
|
||||
return success(orgRestService.listUsers(reqVO));
|
||||
}
|
||||
|
||||
@PostMapping("/hr/subcompany/sync")
|
||||
@Operation(summary = "同步分部信息至 iWork")
|
||||
public CommonResult<IWorkOrgRespVO> syncSubcompanies(@Valid @RequestBody IWorkOrgSyncReqVO reqVO) {
|
||||
return success(orgRestService.syncSubcompanies(reqVO));
|
||||
// @PostMapping("/hr/subcompany/sync")
|
||||
// @Operation(summary = "同步分部信息至 iWork")
|
||||
// public CommonResult<IWorkHrSyncRespVO> syncSubcompanies(@Valid @RequestBody IWorkOrgSyncReqVO reqVO) {
|
||||
// return success(orgRestService.syncSubcompanies(reqVO));
|
||||
// }
|
||||
//
|
||||
// @PostMapping("/hr/department/sync")
|
||||
// @Operation(summary = "同步部门信息至 iWork")
|
||||
// public CommonResult<IWorkHrSyncRespVO> syncDepartments(@Valid @RequestBody IWorkOrgSyncReqVO reqVO) {
|
||||
// return success(orgRestService.syncDepartments(reqVO));
|
||||
// }
|
||||
//
|
||||
// @PostMapping("/hr/job-title/sync")
|
||||
// @Operation(summary = "同步岗位信息至 iWork")
|
||||
// public CommonResult<IWorkHrSyncRespVO> syncJobTitles(@Valid @RequestBody IWorkOrgSyncReqVO reqVO) {
|
||||
// return success(orgRestService.syncJobTitles(reqVO));
|
||||
// }
|
||||
//
|
||||
// @PostMapping("/hr/user/sync")
|
||||
// @Operation(summary = "同步人员信息至 iWork")
|
||||
// public CommonResult<IWorkHrSyncRespVO> syncUsers(@Valid @RequestBody IWorkOrgSyncReqVO reqVO) {
|
||||
// return success(orgRestService.syncUsers(reqVO));
|
||||
// }
|
||||
|
||||
// ----------------- 同步到本地 -----------------
|
||||
|
||||
@PostMapping("/hr/full-sync")
|
||||
@Operation(summary = "手动触发 iWork 组织/人员全量同步")
|
||||
public CommonResult<IWorkFullSyncRespVO> fullSync(@Valid @RequestBody IWorkFullSyncReqVO reqVO) {
|
||||
return success(syncService.fullSync(reqVO));
|
||||
}
|
||||
|
||||
@PostMapping("/hr/department/sync")
|
||||
@Operation(summary = "同步部门信息至 iWork")
|
||||
public CommonResult<IWorkOrgRespVO> syncDepartments(@Valid @RequestBody IWorkOrgSyncReqVO reqVO) {
|
||||
return success(orgRestService.syncDepartments(reqVO));
|
||||
}
|
||||
|
||||
@PostMapping("/hr/job-title/sync")
|
||||
@Operation(summary = "同步岗位信息至 iWork")
|
||||
public CommonResult<IWorkOrgRespVO> syncJobTitles(@Valid @RequestBody IWorkOrgSyncReqVO reqVO) {
|
||||
return success(orgRestService.syncJobTitles(reqVO));
|
||||
}
|
||||
|
||||
@PostMapping("/hr/user/sync")
|
||||
@Operation(summary = "同步人员信息至 iWork")
|
||||
public CommonResult<IWorkOrgRespVO> syncUsers(@Valid @RequestBody IWorkOrgSyncReqVO reqVO) {
|
||||
return success(orgRestService.syncUsers(reqVO));
|
||||
@PostMapping("/hr/single-sync")
|
||||
@Operation(summary = "按 iWork ID 同步单条组织/人员")
|
||||
public CommonResult<IWorkSingleSyncRespVO> singleSync(@Valid @RequestBody IWorkSingleSyncReqVO reqVO) {
|
||||
return success(syncService.syncSingle(reqVO));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.zt.plat.module.system.controller.admin.integration.iwork.vo;
|
||||
|
||||
import com.zt.plat.module.system.enums.integration.IWorkSyncEntityTypeEnum;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.Max;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* iWork 全量同步请求
|
||||
*/
|
||||
@Data
|
||||
public class IWorkFullSyncReqVO {
|
||||
|
||||
@Schema(description = "起始页码,从 1 开始", example = "1")
|
||||
@Min(1)
|
||||
private Integer startPage = 1;
|
||||
|
||||
@Schema(description = "最大处理页数,null 表示处理至 iWork 返回的末页", example = "10")
|
||||
@Min(1)
|
||||
private Integer maxPages;
|
||||
|
||||
@Schema(description = "每次分页从 iWork 拉取的记录数", example = "100")
|
||||
@Min(1)
|
||||
@Max(500)
|
||||
private Integer pageSize = 100;
|
||||
|
||||
@Schema(description = "同步范围列表,默认同步全部。可选:subcompany、department、jobTitle、user")
|
||||
private List<String> scopes;
|
||||
|
||||
@Schema(description = "是否包含已失效(canceled=1)的记录", example = "false")
|
||||
private Boolean includeCanceled = Boolean.FALSE;
|
||||
|
||||
public Set<IWorkSyncEntityTypeEnum> resolveScopes() {
|
||||
EnumSet<IWorkSyncEntityTypeEnum> defaults = EnumSet.allOf(IWorkSyncEntityTypeEnum.class);
|
||||
if (scopes == null || scopes.isEmpty()) {
|
||||
return defaults;
|
||||
}
|
||||
Set<IWorkSyncEntityTypeEnum> resolved = scopes.stream()
|
||||
.map(IWorkSyncEntityTypeEnum::fromCode)
|
||||
.filter(java.util.Objects::nonNull)
|
||||
.collect(Collectors.toCollection(() -> EnumSet.noneOf(IWorkSyncEntityTypeEnum.class)));
|
||||
if (resolved.isEmpty()) {
|
||||
return defaults;
|
||||
}
|
||||
return resolved;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.zt.plat.module.system.controller.admin.integration.iwork.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* iWork 全量同步响应
|
||||
*/
|
||||
@Data
|
||||
public class IWorkFullSyncRespVO {
|
||||
|
||||
@Schema(description = "本次处理的总页数")
|
||||
private Integer processedPages;
|
||||
|
||||
@Schema(description = "每次分页请求的条数")
|
||||
private Integer pageSize;
|
||||
|
||||
@Schema(description = "分部统计信息")
|
||||
private IWorkSyncEntityStatVO subcompanyStat = new IWorkSyncEntityStatVO();
|
||||
|
||||
@Schema(description = "部门统计信息")
|
||||
private IWorkSyncEntityStatVO departmentStat = new IWorkSyncEntityStatVO();
|
||||
|
||||
@Schema(description = "岗位统计信息")
|
||||
private IWorkSyncEntityStatVO jobTitleStat = new IWorkSyncEntityStatVO();
|
||||
|
||||
@Schema(description = "人员统计信息")
|
||||
private IWorkSyncEntityStatVO userStat = new IWorkSyncEntityStatVO();
|
||||
|
||||
@Schema(description = "每个批次的详细统计")
|
||||
private List<IWorkSyncBatchStatVO> batches;
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
package com.zt.plat.module.system.controller.admin.integration.iwork.vo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAnyGetter;
|
||||
import com.fasterxml.jackson.annotation.JsonAnySetter;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import com.zt.plat.module.system.service.integration.iwork.jackson.LenientIntegerDeserializer;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* iWork 部门分页响应。
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "iWork 部门分页响应")
|
||||
public class IWorkHrDepartmentPageRespVO {
|
||||
|
||||
@Schema(description = "响应码")
|
||||
private String code;
|
||||
|
||||
@Schema(description = "提示信息")
|
||||
private String message;
|
||||
|
||||
@Schema(description = "是否成功")
|
||||
private boolean success;
|
||||
|
||||
@Schema(description = "总条数")
|
||||
private Integer totalSize;
|
||||
|
||||
@Schema(description = "总页数")
|
||||
private Integer totalPage;
|
||||
|
||||
@Schema(description = "每页条数")
|
||||
private Integer pageSize;
|
||||
|
||||
@Schema(description = "当前页码")
|
||||
private Integer pageNumber;
|
||||
|
||||
@Schema(description = "部门数据列表")
|
||||
private List<Department> dataList;
|
||||
|
||||
@Data
|
||||
@Schema(description = "部门信息")
|
||||
public static class Department {
|
||||
|
||||
@Schema(description = "部门 ID")
|
||||
@JsonProperty("departmentid")
|
||||
private Integer departmentid;
|
||||
|
||||
@Schema(description = "部门编码")
|
||||
@JsonProperty("departmentcode")
|
||||
private String departmentcode;
|
||||
|
||||
@Schema(description = "部门名称")
|
||||
@JsonProperty("departmentname")
|
||||
private String departmentname;
|
||||
|
||||
@Schema(description = "部门标识")
|
||||
@JsonProperty("departmentmark")
|
||||
private String departmentmark;
|
||||
|
||||
@Schema(description = "所属分部 ID")
|
||||
@JsonProperty("subcompanyid1")
|
||||
private Integer subcompanyid1;
|
||||
|
||||
@Schema(description = "所属分部名称")
|
||||
@JsonProperty("subcompanyname")
|
||||
private String subcompanyname;
|
||||
|
||||
@Schema(description = "上级分部 ID")
|
||||
@JsonProperty("supsubcomid")
|
||||
private Integer supsubcomid;
|
||||
|
||||
@Schema(description = "上级分部名称")
|
||||
@JsonProperty("supsubcomname")
|
||||
private String supsubcomname;
|
||||
|
||||
@Schema(description = "父部门 ID")
|
||||
@JsonProperty("parentdeptid")
|
||||
private Integer parentdeptid;
|
||||
|
||||
@Schema(description = "父部门名称")
|
||||
@JsonProperty("parentdeptname")
|
||||
private String parentdeptname;
|
||||
|
||||
@Schema(description = "层级路径")
|
||||
@JsonProperty("alllevel")
|
||||
private String alllevel;
|
||||
|
||||
@Schema(description = "显示顺序")
|
||||
@JsonProperty("showorder")
|
||||
@JsonDeserialize(using = LenientIntegerDeserializer.class)
|
||||
private Integer showorder;
|
||||
|
||||
@Schema(description = "是否有子部门 (0/1)")
|
||||
@JsonProperty("haschild")
|
||||
private String haschild;
|
||||
|
||||
@Schema(description = "是否已失效 (0/1)")
|
||||
@JsonProperty("canceled")
|
||||
private String canceled;
|
||||
|
||||
@Schema(description = "部门类型")
|
||||
@JsonProperty("departmenttype")
|
||||
private String departmenttype;
|
||||
|
||||
@Schema(description = "负责人 ID")
|
||||
@JsonProperty("managerid")
|
||||
private Integer managerid;
|
||||
|
||||
@Schema(description = "负责人名称")
|
||||
@JsonProperty("manager")
|
||||
private String manager;
|
||||
|
||||
@JsonIgnore
|
||||
private Map<String, Object> attributes;
|
||||
|
||||
@JsonAnySetter
|
||||
public void putAttribute(String key, Object value) {
|
||||
if (attributes == null) {
|
||||
attributes = new LinkedHashMap<>();
|
||||
}
|
||||
attributes.put(key, value);
|
||||
}
|
||||
|
||||
@JsonAnyGetter
|
||||
public Map<String, Object> any() {
|
||||
return attributes == null ? Collections.emptyMap() : attributes;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
package com.zt.plat.module.system.controller.admin.integration.iwork.vo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAnyGetter;
|
||||
import com.fasterxml.jackson.annotation.JsonAnySetter;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import com.zt.plat.module.system.service.integration.iwork.jackson.LenientIntegerDeserializer;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* iWork 岗位分页响应。
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "iWork 岗位分页响应")
|
||||
public class IWorkHrJobTitlePageRespVO {
|
||||
|
||||
@Schema(description = "响应码")
|
||||
private String code;
|
||||
|
||||
@Schema(description = "提示信息")
|
||||
private String message;
|
||||
|
||||
@Schema(description = "是否成功")
|
||||
private boolean success;
|
||||
|
||||
@Schema(description = "总条数")
|
||||
private Integer totalSize;
|
||||
|
||||
@Schema(description = "总页数")
|
||||
private Integer totalPage;
|
||||
|
||||
@Schema(description = "每页条数")
|
||||
private Integer pageSize;
|
||||
|
||||
@Schema(description = "当前页码")
|
||||
private Integer pageNumber;
|
||||
|
||||
@Schema(description = "岗位数据列表")
|
||||
private List<JobTitle> dataList;
|
||||
|
||||
@Data
|
||||
@Schema(description = "岗位信息")
|
||||
public static class JobTitle {
|
||||
|
||||
@Schema(description = "岗位 ID")
|
||||
@JsonProperty("jobtitleid")
|
||||
private Integer jobtitleid;
|
||||
|
||||
@Schema(description = "岗位编码")
|
||||
@JsonProperty("jobtitlecode")
|
||||
private String jobtitlecode;
|
||||
|
||||
@Schema(description = "岗位名称")
|
||||
@JsonProperty("jobtitlename")
|
||||
private String jobtitlename;
|
||||
|
||||
@Schema(description = "岗位类型")
|
||||
@JsonProperty("jobtitletype")
|
||||
private String jobtitletype;
|
||||
|
||||
@Schema(description = "所属岗位组 ID")
|
||||
@JsonProperty("jobgroupid")
|
||||
private Integer jobgroupid;
|
||||
|
||||
@Schema(description = "所属岗位组名称")
|
||||
@JsonProperty("jobgroupname")
|
||||
private String jobgroupname;
|
||||
|
||||
@Schema(description = "岗位层级")
|
||||
@JsonProperty("joblevel")
|
||||
private String joblevel;
|
||||
|
||||
@Schema(description = "岗位职责")
|
||||
@JsonProperty("jobfunction")
|
||||
private String jobfunction;
|
||||
|
||||
@Schema(description = "岗位描述")
|
||||
@JsonProperty("description")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "上级岗位 ID")
|
||||
@JsonProperty("supjobtitleid")
|
||||
private Integer supjobtitleid;
|
||||
|
||||
@Schema(description = "上级岗位名称")
|
||||
@JsonProperty("supjobtitlename")
|
||||
private String supjobtitlename;
|
||||
|
||||
@Schema(description = "显示顺序")
|
||||
@JsonProperty("showorder")
|
||||
@JsonDeserialize(using = LenientIntegerDeserializer.class)
|
||||
private Integer showorder;
|
||||
|
||||
@Schema(description = "是否已失效 (0/1)")
|
||||
@JsonProperty("canceled")
|
||||
private String canceled;
|
||||
|
||||
@JsonIgnore
|
||||
private Map<String, Object> attributes;
|
||||
|
||||
@JsonAnySetter
|
||||
public void putAttribute(String key, Object value) {
|
||||
if (attributes == null) {
|
||||
attributes = new LinkedHashMap<>();
|
||||
}
|
||||
attributes.put(key, value);
|
||||
}
|
||||
|
||||
@JsonAnyGetter
|
||||
public Map<String, Object> any() {
|
||||
return attributes == null ? Collections.emptyMap() : attributes;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
package com.zt.plat.module.system.controller.admin.integration.iwork.vo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAnyGetter;
|
||||
import com.fasterxml.jackson.annotation.JsonAnySetter;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import com.zt.plat.module.system.service.integration.iwork.jackson.LenientIntegerDeserializer;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* iWork 分部分页响应。
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "iWork 分部分页响应")
|
||||
public class IWorkHrSubcompanyPageRespVO {
|
||||
|
||||
@Schema(description = "响应码")
|
||||
private String code;
|
||||
|
||||
@Schema(description = "提示信息")
|
||||
private String message;
|
||||
|
||||
@Schema(description = "是否成功")
|
||||
private boolean success;
|
||||
|
||||
@Schema(description = "总条数")
|
||||
private Integer totalSize;
|
||||
|
||||
@Schema(description = "总页数")
|
||||
private Integer totalPage;
|
||||
|
||||
@Schema(description = "每页条数")
|
||||
private Integer pageSize;
|
||||
|
||||
@Schema(description = "当前页码")
|
||||
private Integer pageNumber;
|
||||
|
||||
@Schema(description = "分部数据列表")
|
||||
private List<Subcompany> dataList;
|
||||
|
||||
@Data
|
||||
@Schema(description = "分部信息")
|
||||
public static class Subcompany {
|
||||
|
||||
@Schema(description = "分部唯一 ID")
|
||||
@JsonProperty("subcompanyid1")
|
||||
private Integer subcompanyid1;
|
||||
|
||||
@Schema(description = "分部编码")
|
||||
@JsonProperty("subcompanycode")
|
||||
private String subcompanycode;
|
||||
|
||||
@Schema(description = "分部名称")
|
||||
@JsonProperty("subcompanyname")
|
||||
private String subcompanyname;
|
||||
|
||||
@Schema(description = "所属总部 ID")
|
||||
@JsonProperty("companyid")
|
||||
private Integer companyid;
|
||||
|
||||
@Schema(description = "所属总部名称")
|
||||
@JsonProperty("companyname")
|
||||
private String companyname;
|
||||
|
||||
@Schema(description = "上级分部 ID")
|
||||
@JsonProperty("supsubcomid")
|
||||
private Integer supsubcomid;
|
||||
|
||||
@Schema(description = "上级分部名称")
|
||||
@JsonProperty("supsubcomname")
|
||||
private String supsubcomname;
|
||||
|
||||
@Schema(description = "显示顺序")
|
||||
@JsonProperty("showorder")
|
||||
@JsonDeserialize(using = LenientIntegerDeserializer.class)
|
||||
private Integer showorder;
|
||||
|
||||
@Schema(description = "分部描述")
|
||||
@JsonProperty("description")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "是否已失效(0/1)")
|
||||
@JsonProperty("canceled")
|
||||
private String canceled;
|
||||
|
||||
@Schema(description = "层级路径")
|
||||
@JsonProperty("alllevel")
|
||||
private String alllevel;
|
||||
|
||||
@JsonIgnore
|
||||
private Map<String, Object> attributes;
|
||||
|
||||
@JsonAnySetter
|
||||
public void putAttribute(String key, Object value) {
|
||||
if (attributes == null) {
|
||||
attributes = new LinkedHashMap<>();
|
||||
}
|
||||
attributes.put(key, value);
|
||||
}
|
||||
|
||||
@JsonAnyGetter
|
||||
public Map<String, Object> any() {
|
||||
return attributes == null ? Collections.emptyMap() : attributes;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package com.zt.plat.module.system.controller.admin.integration.iwork.vo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAnyGetter;
|
||||
import com.fasterxml.jackson.annotation.JsonAnySetter;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* iWork 人力同步响应。
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "iWork 人力同步响应")
|
||||
public class IWorkHrSyncRespVO {
|
||||
|
||||
@Schema(description = "响应码")
|
||||
private String code;
|
||||
|
||||
@Schema(description = "提示信息")
|
||||
private String message;
|
||||
|
||||
@Schema(description = "是否成功")
|
||||
private boolean success;
|
||||
|
||||
@Schema(description = "同步结果明细")
|
||||
private List<SyncResult> result;
|
||||
|
||||
@Data
|
||||
@Schema(description = "同步结果项")
|
||||
public static class SyncResult {
|
||||
|
||||
@Schema(description = "操作动作 add/update/delete")
|
||||
@JsonProperty("@action")
|
||||
private String action;
|
||||
|
||||
@Schema(description = "外部编码")
|
||||
@JsonProperty("code")
|
||||
private String code;
|
||||
|
||||
@Schema(description = "执行结果 success/fail")
|
||||
@JsonProperty("result")
|
||||
private String result;
|
||||
|
||||
@Schema(description = "是否成功")
|
||||
@JsonProperty("success")
|
||||
private Boolean success;
|
||||
|
||||
@Schema(description = "失败描述")
|
||||
@JsonProperty("message")
|
||||
private String message;
|
||||
|
||||
@JsonIgnore
|
||||
private Map<String, Object> attributes;
|
||||
|
||||
@JsonAnySetter
|
||||
public void putAttribute(String key, Object value) {
|
||||
if (attributes == null) {
|
||||
attributes = new LinkedHashMap<>();
|
||||
}
|
||||
attributes.put(key, value);
|
||||
}
|
||||
|
||||
@JsonAnyGetter
|
||||
public Map<String, Object> any() {
|
||||
return attributes == null ? Collections.emptyMap() : attributes;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
package com.zt.plat.module.system.controller.admin.integration.iwork.vo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAnyGetter;
|
||||
import com.fasterxml.jackson.annotation.JsonAnySetter;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import com.zt.plat.module.system.service.integration.iwork.jackson.LenientIntegerDeserializer;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* iWork 人员分页响应。
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "iWork 人员分页响应")
|
||||
public class IWorkHrUserPageRespVO {
|
||||
|
||||
@Schema(description = "响应码")
|
||||
private String code;
|
||||
|
||||
@Schema(description = "提示信息")
|
||||
private String message;
|
||||
|
||||
@Schema(description = "是否成功")
|
||||
private boolean success;
|
||||
|
||||
@Schema(description = "总条数")
|
||||
private Integer totalSize;
|
||||
|
||||
@Schema(description = "总页数")
|
||||
private Integer totalPage;
|
||||
|
||||
@Schema(description = "每页条数")
|
||||
private Integer pageSize;
|
||||
|
||||
@Schema(description = "当前页码")
|
||||
private Integer pageNumber;
|
||||
|
||||
@Schema(description = "人员数据列表")
|
||||
private List<User> dataList;
|
||||
|
||||
@Data
|
||||
@Schema(description = "人员信息")
|
||||
public static class User {
|
||||
|
||||
@Schema(description = "人员 ID")
|
||||
@JsonProperty("id")
|
||||
private Integer id;
|
||||
|
||||
@Schema(description = "人员姓名")
|
||||
@JsonProperty("lastname")
|
||||
private String lastname;
|
||||
|
||||
@Schema(description = "登录账号")
|
||||
@JsonProperty("loginid")
|
||||
private String loginid;
|
||||
|
||||
@Schema(description = "工号")
|
||||
@JsonProperty("workcode")
|
||||
private String workcode;
|
||||
|
||||
@Schema(description = "性别")
|
||||
@JsonProperty("sex")
|
||||
private String sex;
|
||||
|
||||
@Schema(description = "所属分部 ID")
|
||||
@JsonProperty("subcompanyid1")
|
||||
private Integer subcompanyid1;
|
||||
|
||||
@Schema(description = "所属分部名称")
|
||||
@JsonProperty("subcompanyname")
|
||||
private String subcompanyname;
|
||||
|
||||
@Schema(description = "所属部门 ID")
|
||||
@JsonProperty("departmentid")
|
||||
private Integer departmentid;
|
||||
|
||||
@Schema(description = "所属部门名称")
|
||||
@JsonProperty("departmentname")
|
||||
private String departmentname;
|
||||
|
||||
@Schema(description = "所属岗位 ID")
|
||||
@JsonProperty("jobtitleid")
|
||||
private Integer jobtitleid;
|
||||
|
||||
@Schema(description = "所属岗位名称")
|
||||
@JsonProperty("jobtitlename")
|
||||
private String jobtitlename;
|
||||
|
||||
@Schema(description = "手机号码")
|
||||
@JsonProperty("mobile")
|
||||
private String mobile;
|
||||
|
||||
@Schema(description = "办公电话")
|
||||
@JsonProperty("telephone")
|
||||
private String telephone;
|
||||
|
||||
@Schema(description = "邮箱")
|
||||
@JsonProperty("email")
|
||||
private String email;
|
||||
|
||||
@Schema(description = "直属上级 ID")
|
||||
@JsonProperty("managerid")
|
||||
private Integer managerid;
|
||||
|
||||
@Schema(description = "助理 ID")
|
||||
@JsonProperty("assistantid")
|
||||
private Integer assistantid;
|
||||
|
||||
@Schema(description = "安全级别")
|
||||
@JsonProperty("seclevel")
|
||||
private Integer seclevel;
|
||||
|
||||
@Schema(description = "当前状态")
|
||||
@JsonProperty("status")
|
||||
private String status;
|
||||
|
||||
@Schema(description = "入职日期")
|
||||
@JsonProperty("hiredate")
|
||||
private String hiredate;
|
||||
|
||||
@Schema(description = "离职日期")
|
||||
@JsonProperty("leavedate")
|
||||
private String leavedate;
|
||||
|
||||
@Schema(description = "出生日期")
|
||||
@JsonProperty("birthday")
|
||||
private String birthday;
|
||||
|
||||
@Schema(description = "民族")
|
||||
@JsonProperty("folk")
|
||||
private String folk;
|
||||
|
||||
@Schema(description = "婚姻状况")
|
||||
@JsonProperty("maritalstatus")
|
||||
private String maritalstatus;
|
||||
|
||||
@Schema(description = "文化程度")
|
||||
@JsonProperty("educationlevel")
|
||||
private String educationlevel;
|
||||
|
||||
@Schema(description = "籍贯")
|
||||
@JsonProperty("nativeplace")
|
||||
private String nativeplace;
|
||||
|
||||
@Schema(description = "户口所在地")
|
||||
@JsonProperty("nationality")
|
||||
private String nationality;
|
||||
|
||||
@Schema(description = "证件号码")
|
||||
@JsonProperty("certificatenum")
|
||||
private String certificatenum;
|
||||
|
||||
@Schema(description = "显示顺序")
|
||||
@JsonProperty("dsporder")
|
||||
@JsonDeserialize(using = LenientIntegerDeserializer.class)
|
||||
private Integer dsporder;
|
||||
|
||||
@Schema(description = "系统语言")
|
||||
@JsonProperty("systemlanguage")
|
||||
private String systemlanguage;
|
||||
|
||||
@Schema(description = "账号类型")
|
||||
@JsonProperty("accounttype")
|
||||
private String accounttype;
|
||||
|
||||
@JsonIgnore
|
||||
private Map<String, Object> attributes;
|
||||
|
||||
@JsonAnySetter
|
||||
public void putAttribute(String key, Object value) {
|
||||
if (attributes == null) {
|
||||
attributes = new LinkedHashMap<>();
|
||||
}
|
||||
attributes.put(key, value);
|
||||
}
|
||||
|
||||
@JsonAnyGetter
|
||||
public Map<String, Object> any() {
|
||||
return attributes == null ? Collections.emptyMap() : attributes;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,28 +1,15 @@
|
||||
package com.zt.plat.module.system.controller.admin.integration.iwork.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 对 iWork 人力组织 REST 请求的响应封装。
|
||||
* @deprecated 请改用强类型的 IWorkHr*RespVO,避免再引用该占位类。
|
||||
*/
|
||||
@Data
|
||||
public class IWorkOrgRespVO {
|
||||
@Deprecated(forRemoval = true)
|
||||
@Schema(description = "已废弃,占位用")
|
||||
public final class IWorkOrgRespVO {
|
||||
|
||||
@Schema(description = "响应中的业务数据(data 字段或整体映射)")
|
||||
private Map<String, Object> payload;
|
||||
|
||||
@Schema(description = "原始响应字符串")
|
||||
private String rawBody;
|
||||
|
||||
@Schema(description = "是否判断为成功")
|
||||
private boolean success;
|
||||
|
||||
@Schema(description = "提示信息")
|
||||
private String message;
|
||||
|
||||
@Schema(description = "响应码")
|
||||
private String code;
|
||||
private IWorkOrgRespVO() {
|
||||
throw new UnsupportedOperationException("Use IWorkHr*RespVO instead");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.zt.plat.module.system.controller.admin.integration.iwork.vo;
|
||||
|
||||
import com.zt.plat.module.system.enums.integration.IWorkSyncEntityTypeEnum;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* iWork 单条同步请求
|
||||
*/
|
||||
@Data
|
||||
public class IWorkSingleSyncReqVO {
|
||||
|
||||
@Schema(description = "同步的实体类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "user")
|
||||
@NotNull(message = "实体类型不能为空")
|
||||
private IWorkSyncEntityTypeEnum entityType;
|
||||
|
||||
@Schema(description = "iWork 提供的实体主键 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "10001")
|
||||
@NotNull(message = "实体 ID 不能为空")
|
||||
@Min(1)
|
||||
private Long entityId;
|
||||
|
||||
@Schema(description = "缺失时是否自动创建", example = "true")
|
||||
private Boolean createIfMissing = Boolean.TRUE;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.zt.plat.module.system.controller.admin.integration.iwork.vo;
|
||||
|
||||
import com.zt.plat.module.system.enums.integration.IWorkSyncEntityTypeEnum;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* iWork 单条同步响应
|
||||
*/
|
||||
@Data
|
||||
public class IWorkSingleSyncRespVO {
|
||||
|
||||
@Schema(description = "同步的实体类型")
|
||||
private IWorkSyncEntityTypeEnum entityType;
|
||||
|
||||
@Schema(description = "实体 ID")
|
||||
private Long entityId;
|
||||
|
||||
@Schema(description = "是否创建了新的记录")
|
||||
private boolean created;
|
||||
|
||||
@Schema(description = "是否对已有记录进行了更新")
|
||||
private boolean updated;
|
||||
|
||||
@Schema(description = "提示信息")
|
||||
private String message;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.zt.plat.module.system.controller.admin.integration.iwork.vo;
|
||||
|
||||
import com.zt.plat.module.system.enums.integration.IWorkSyncEntityTypeEnum;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 记录一次分页批次执行的统计信息。
|
||||
*/
|
||||
@Data
|
||||
public class IWorkSyncBatchStatVO {
|
||||
|
||||
@Schema(description = "同步的实体类型")
|
||||
private IWorkSyncEntityTypeEnum entityType;
|
||||
|
||||
@Schema(description = "当前批次处理的页码,从 1 开始")
|
||||
private Integer pageNumber;
|
||||
|
||||
@Schema(description = "本批次从 iWork 拉取的记录数量")
|
||||
private Integer pulled;
|
||||
|
||||
@Schema(description = "本批次创建的记录数量")
|
||||
private Integer created;
|
||||
|
||||
@Schema(description = "本批次因已存在而跳过的记录数量")
|
||||
private Integer skippedExisting;
|
||||
|
||||
@Schema(description = "本批次禁用的记录数量")
|
||||
private Integer disabled;
|
||||
|
||||
@Schema(description = "本批次失败的记录数量")
|
||||
private Integer failed;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.zt.plat.module.system.controller.admin.integration.iwork.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* iWork 同步实体统计信息
|
||||
*/
|
||||
@Data
|
||||
public class IWorkSyncEntityStatVO {
|
||||
|
||||
@Schema(description = "从 iWork 拉取的记录数量")
|
||||
private int pulled;
|
||||
|
||||
@Schema(description = "在本系统中新创建的记录数量")
|
||||
private int created;
|
||||
|
||||
@Schema(description = "因已存在而跳过的记录数量")
|
||||
private int skippedExisting;
|
||||
|
||||
@Schema(description = "在本系统中被禁用的记录数量")
|
||||
private int disabled;
|
||||
|
||||
@Schema(description = "同步失败的记录数量")
|
||||
private int failed;
|
||||
|
||||
public void incrementPulled(int delta) {
|
||||
this.pulled += delta;
|
||||
}
|
||||
|
||||
public void incrementCreated(int delta) {
|
||||
this.created += delta;
|
||||
}
|
||||
|
||||
public void incrementSkipped(int delta) {
|
||||
this.skippedExisting += delta;
|
||||
}
|
||||
|
||||
public void incrementDisabled(int delta) {
|
||||
this.disabled += delta;
|
||||
}
|
||||
|
||||
public void incrementFailed(int delta) {
|
||||
this.failed += delta;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.zt.plat.module.system.enums.integration;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* iWork 同步支持的实体类型。
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
public enum IWorkSyncEntityTypeEnum {
|
||||
|
||||
SUBCOMPANY("subcompany", "分部 / 公司"),
|
||||
DEPARTMENT("department", "部门"),
|
||||
JOB_TITLE("jobTitle", "岗位"),
|
||||
USER("user", "人员");
|
||||
|
||||
private final String code;
|
||||
private final String label;
|
||||
|
||||
public static IWorkSyncEntityTypeEnum fromCode(String code) {
|
||||
if (code == null) {
|
||||
return null;
|
||||
}
|
||||
for (IWorkSyncEntityTypeEnum value : values()) {
|
||||
if (value.code.equalsIgnoreCase(code)) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -444,7 +444,9 @@ public class AdminAuthServiceImpl implements AdminAuthService {
|
||||
Integer userSource = user.getUserSource();
|
||||
|
||||
// 同步用户(SYNC = 2)为内部用户,需要使用E办登录
|
||||
if (userSource != null && userSource.equals(UserSourceEnum.SYNC.getSource())) {
|
||||
if (userSource != null &&
|
||||
(userSource.equals(UserSourceEnum.SYNC.getSource()) ||
|
||||
userSource.equals(UserSourceEnum.IWORK.getSource()))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
package com.zt.plat.module.system.service.integration.iwork;
|
||||
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkDepartmentQueryReqVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkHrDepartmentPageRespVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkHrJobTitlePageRespVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkHrSubcompanyPageRespVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkHrSyncRespVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkHrUserPageRespVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkJobTitleQueryReqVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkOrgRespVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkOrgSyncReqVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkSubcompanyQueryReqVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkUserQueryReqVO;
|
||||
@@ -12,19 +16,19 @@ import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkUser
|
||||
*/
|
||||
public interface IWorkOrgRestService {
|
||||
|
||||
IWorkOrgRespVO listSubcompanies(IWorkSubcompanyQueryReqVO reqVO);
|
||||
IWorkHrSubcompanyPageRespVO listSubcompanies(IWorkSubcompanyQueryReqVO reqVO);
|
||||
|
||||
IWorkOrgRespVO listDepartments(IWorkDepartmentQueryReqVO reqVO);
|
||||
IWorkHrDepartmentPageRespVO listDepartments(IWorkDepartmentQueryReqVO reqVO);
|
||||
|
||||
IWorkOrgRespVO listJobTitles(IWorkJobTitleQueryReqVO reqVO);
|
||||
IWorkHrJobTitlePageRespVO listJobTitles(IWorkJobTitleQueryReqVO reqVO);
|
||||
|
||||
IWorkOrgRespVO listUsers(IWorkUserQueryReqVO reqVO);
|
||||
IWorkHrUserPageRespVO listUsers(IWorkUserQueryReqVO reqVO);
|
||||
|
||||
IWorkOrgRespVO syncSubcompanies(IWorkOrgSyncReqVO reqVO);
|
||||
IWorkHrSyncRespVO syncSubcompanies(IWorkOrgSyncReqVO reqVO);
|
||||
|
||||
IWorkOrgRespVO syncDepartments(IWorkOrgSyncReqVO reqVO);
|
||||
IWorkHrSyncRespVO syncDepartments(IWorkOrgSyncReqVO reqVO);
|
||||
|
||||
IWorkOrgRespVO syncJobTitles(IWorkOrgSyncReqVO reqVO);
|
||||
IWorkHrSyncRespVO syncJobTitles(IWorkOrgSyncReqVO reqVO);
|
||||
|
||||
IWorkOrgRespVO syncUsers(IWorkOrgSyncReqVO reqVO);
|
||||
IWorkHrSyncRespVO syncUsers(IWorkOrgSyncReqVO reqVO);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,195 @@
|
||||
package com.zt.plat.module.system.service.integration.iwork;
|
||||
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkHrDepartmentPageRespVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkHrJobTitlePageRespVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkHrSubcompanyPageRespVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkHrUserPageRespVO;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Abstraction for applying iWork entities into local persistence.
|
||||
*/
|
||||
public interface IWorkSyncProcessor {
|
||||
|
||||
BatchResult syncSubcompanies(List<IWorkHrSubcompanyPageRespVO.Subcompany> data, SyncOptions options);
|
||||
|
||||
BatchResult syncDepartments(List<IWorkHrDepartmentPageRespVO.Department> data, SyncOptions options);
|
||||
|
||||
BatchResult syncJobTitles(List<IWorkHrJobTitlePageRespVO.JobTitle> data, SyncOptions options);
|
||||
|
||||
BatchResult syncUsers(List<IWorkHrUserPageRespVO.User> data, SyncOptions options);
|
||||
|
||||
/**
|
||||
* Execution options shared by batch and single sync flows.
|
||||
*/
|
||||
final class SyncOptions {
|
||||
private final boolean includeCanceled;
|
||||
private final boolean allowUpdate;
|
||||
private final boolean createIfMissing;
|
||||
|
||||
private SyncOptions(boolean includeCanceled, boolean allowUpdate, boolean createIfMissing) {
|
||||
this.includeCanceled = includeCanceled;
|
||||
this.allowUpdate = allowUpdate;
|
||||
this.createIfMissing = createIfMissing;
|
||||
}
|
||||
|
||||
public static SyncOptions full(boolean includeCanceled) {
|
||||
return new SyncOptions(includeCanceled, false, true);
|
||||
}
|
||||
|
||||
public static SyncOptions single(boolean createIfMissing) {
|
||||
return new SyncOptions(true, true, Boolean.TRUE.equals(createIfMissing));
|
||||
}
|
||||
|
||||
public static SyncOptions custom(boolean includeCanceled, boolean allowUpdate, boolean createIfMissing) {
|
||||
return new SyncOptions(includeCanceled, allowUpdate, createIfMissing);
|
||||
}
|
||||
|
||||
public boolean isIncludeCanceled() {
|
||||
return includeCanceled;
|
||||
}
|
||||
|
||||
public boolean isAllowUpdate() {
|
||||
return allowUpdate;
|
||||
}
|
||||
|
||||
public boolean isCreateIfMissing() {
|
||||
return createIfMissing;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Aggregated result for a sync batch.
|
||||
*/
|
||||
final class BatchResult {
|
||||
private int pulled;
|
||||
private int created;
|
||||
private int skipped;
|
||||
private int disabled;
|
||||
private int failed;
|
||||
private int updated;
|
||||
private String message;
|
||||
|
||||
public static BatchResult empty() {
|
||||
return new BatchResult();
|
||||
}
|
||||
|
||||
public BatchResult withMessage(String message) {
|
||||
this.message = message;
|
||||
return this;
|
||||
}
|
||||
|
||||
public BatchResult merge(BatchResult other) {
|
||||
if (other == null) {
|
||||
return this;
|
||||
}
|
||||
this.pulled += other.pulled;
|
||||
this.created += other.created;
|
||||
this.skipped += other.skipped;
|
||||
this.disabled += other.disabled;
|
||||
this.failed += other.failed;
|
||||
this.updated += other.updated;
|
||||
if (Objects.nonNull(other.message)) {
|
||||
this.message = other.message;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public static BatchResult fromSingle(BatchResult single) {
|
||||
return empty().merge(single);
|
||||
}
|
||||
|
||||
public static BatchResult singleCreated(String message) {
|
||||
BatchResult result = empty();
|
||||
result.created = 1;
|
||||
result.message = message;
|
||||
return result;
|
||||
}
|
||||
|
||||
public static BatchResult singleSkipped(String message) {
|
||||
BatchResult result = empty();
|
||||
result.skipped = 1;
|
||||
result.message = message;
|
||||
return result;
|
||||
}
|
||||
|
||||
public static BatchResult singleFailed(String message) {
|
||||
BatchResult result = empty();
|
||||
result.failed = 1;
|
||||
result.message = message;
|
||||
return result;
|
||||
}
|
||||
|
||||
public BatchResult increasePulled(int delta) {
|
||||
this.pulled += delta;
|
||||
return this;
|
||||
}
|
||||
|
||||
public void increaseCreated() {
|
||||
this.created++;
|
||||
}
|
||||
|
||||
public void increaseSkipped() {
|
||||
this.skipped++;
|
||||
}
|
||||
|
||||
public void increaseDisabled() {
|
||||
this.disabled++;
|
||||
}
|
||||
|
||||
public void increaseFailed() {
|
||||
this.failed++;
|
||||
}
|
||||
|
||||
public void increaseUpdated() {
|
||||
this.updated++;
|
||||
}
|
||||
|
||||
public int getPulled() {
|
||||
return pulled;
|
||||
}
|
||||
|
||||
public int getCreated() {
|
||||
return created;
|
||||
}
|
||||
|
||||
public int getSkipped() {
|
||||
return skipped;
|
||||
}
|
||||
|
||||
public int getDisabled() {
|
||||
return disabled;
|
||||
}
|
||||
|
||||
public int getFailed() {
|
||||
return failed;
|
||||
}
|
||||
|
||||
public int getUpdated() {
|
||||
return updated;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
|
||||
default BatchResult syncSubcompany(IWorkHrSubcompanyPageRespVO.Subcompany data, SyncOptions options) {
|
||||
return syncSubcompanies(Collections.singletonList(data), options);
|
||||
}
|
||||
|
||||
default BatchResult syncDepartment(IWorkHrDepartmentPageRespVO.Department data, SyncOptions options) {
|
||||
return syncDepartments(Collections.singletonList(data), options);
|
||||
}
|
||||
|
||||
default BatchResult syncJobTitle(IWorkHrJobTitlePageRespVO.JobTitle data, SyncOptions options) {
|
||||
return syncJobTitles(Collections.singletonList(data), options);
|
||||
}
|
||||
|
||||
default BatchResult syncUser(IWorkHrUserPageRespVO.User data, SyncOptions options) {
|
||||
return syncUsers(Collections.singletonList(data), options);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.zt.plat.module.system.service.integration.iwork;
|
||||
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkFullSyncReqVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkFullSyncRespVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkSingleSyncReqVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkSingleSyncRespVO;
|
||||
|
||||
/**
|
||||
* iWork 组织/人员同步服务
|
||||
*/
|
||||
public interface IWorkSyncService {
|
||||
|
||||
/**
|
||||
* 发起全量分批同步
|
||||
*/
|
||||
IWorkFullSyncRespVO fullSync(IWorkFullSyncReqVO reqVO);
|
||||
|
||||
/**
|
||||
* 根据 iWork ID 进行单条同步
|
||||
*/
|
||||
IWorkSingleSyncRespVO syncSingle(IWorkSingleSyncReqVO reqVO);
|
||||
}
|
||||
@@ -6,9 +6,13 @@ import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.zt.plat.framework.common.exception.util.ServiceExceptionUtil;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkDepartmentQueryReqVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkHrDepartmentPageRespVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkHrJobTitlePageRespVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkHrSubcompanyPageRespVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkHrSyncRespVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkHrUserPageRespVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkJobTitleQueryReqVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkOrgBaseQueryReqVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkOrgRespVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkOrgSyncReqVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkSubcompanyQueryReqVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkUserQueryReqVO;
|
||||
@@ -30,8 +34,10 @@ import java.nio.charset.StandardCharsets;
|
||||
import java.time.Clock;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import static com.zt.plat.module.system.service.integration.iwork.IWorkIntegrationErrorCodeConstants.IWORK_BASE_URL_MISSING;
|
||||
import static com.zt.plat.module.system.service.integration.iwork.IWorkIntegrationErrorCodeConstants.IWORK_ORG_IDENTIFIER_MISSING;
|
||||
@@ -44,8 +50,21 @@ import static com.zt.plat.module.system.service.integration.iwork.IWorkIntegrati
|
||||
@Service
|
||||
public class IWorkOrgRestServiceImpl implements IWorkOrgRestService {
|
||||
|
||||
private static final TypeReference<Map<String, Object>> MAP_TYPE = new TypeReference<>() {
|
||||
};
|
||||
private static final TypeReference<List<IWorkHrSubcompanyPageRespVO.Subcompany>> SUBCOMPANY_LIST_TYPE =
|
||||
new TypeReference<>() {
|
||||
};
|
||||
private static final TypeReference<List<IWorkHrDepartmentPageRespVO.Department>> DEPARTMENT_LIST_TYPE =
|
||||
new TypeReference<>() {
|
||||
};
|
||||
private static final TypeReference<List<IWorkHrJobTitlePageRespVO.JobTitle>> JOB_TITLE_LIST_TYPE =
|
||||
new TypeReference<>() {
|
||||
};
|
||||
private static final TypeReference<List<IWorkHrUserPageRespVO.User>> USER_LIST_TYPE =
|
||||
new TypeReference<>() {
|
||||
};
|
||||
private static final TypeReference<List<IWorkHrSyncRespVO.SyncResult>> SYNC_RESULT_LIST_TYPE =
|
||||
new TypeReference<>() {
|
||||
};
|
||||
private static final okhttp3.MediaType JSON_MEDIA_TYPE = okhttp3.MediaType.get("application/json; charset=UTF-8");
|
||||
|
||||
private final IWorkProperties properties;
|
||||
@@ -69,7 +88,7 @@ public class IWorkOrgRestServiceImpl implements IWorkOrgRestService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public IWorkOrgRespVO listSubcompanies(IWorkSubcompanyQueryReqVO reqVO) {
|
||||
public IWorkHrSubcompanyPageRespVO listSubcompanies(IWorkSubcompanyQueryReqVO reqVO) {
|
||||
String path = orgPaths().getSubcompanyPage();
|
||||
Map<String, Object> params = buildBaseParams(reqVO);
|
||||
if (StringUtils.hasText(reqVO.getSubcompanyCode())) {
|
||||
@@ -78,11 +97,12 @@ public class IWorkOrgRestServiceImpl implements IWorkOrgRestService {
|
||||
if (StringUtils.hasText(reqVO.getSubcompanyName())) {
|
||||
params.put("subcompanyname", reqVO.getSubcompanyName());
|
||||
}
|
||||
return invokeParamsEndpoint(path, params);
|
||||
JsonNode node = invokeParamsEndpoint(path, params);
|
||||
return buildSubcompanyPageResp(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IWorkOrgRespVO listDepartments(IWorkDepartmentQueryReqVO reqVO) {
|
||||
public IWorkHrDepartmentPageRespVO listDepartments(IWorkDepartmentQueryReqVO reqVO) {
|
||||
String path = orgPaths().getDepartmentPage();
|
||||
Map<String, Object> params = buildBaseParams(reqVO);
|
||||
if (StringUtils.hasText(reqVO.getDepartmentCode())) {
|
||||
@@ -94,11 +114,12 @@ public class IWorkOrgRestServiceImpl implements IWorkOrgRestService {
|
||||
if (StringUtils.hasText(reqVO.getSubcompanyId())) {
|
||||
params.put("subcompanyid", reqVO.getSubcompanyId());
|
||||
}
|
||||
return invokeParamsEndpoint(path, params);
|
||||
JsonNode node = invokeParamsEndpoint(path, params);
|
||||
return buildDepartmentPageResp(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IWorkOrgRespVO listJobTitles(IWorkJobTitleQueryReqVO reqVO) {
|
||||
public IWorkHrJobTitlePageRespVO listJobTitles(IWorkJobTitleQueryReqVO reqVO) {
|
||||
String path = orgPaths().getJobTitlePage();
|
||||
Map<String, Object> params = buildBaseParams(reqVO);
|
||||
if (StringUtils.hasText(reqVO.getJobTitleCode())) {
|
||||
@@ -107,11 +128,12 @@ public class IWorkOrgRestServiceImpl implements IWorkOrgRestService {
|
||||
if (StringUtils.hasText(reqVO.getJobTitleName())) {
|
||||
params.put("jobtitlename", reqVO.getJobTitleName());
|
||||
}
|
||||
return invokeParamsEndpoint(path, params);
|
||||
JsonNode node = invokeParamsEndpoint(path, params);
|
||||
return buildJobTitlePageResp(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IWorkOrgRespVO listUsers(IWorkUserQueryReqVO reqVO) {
|
||||
public IWorkHrUserPageRespVO listUsers(IWorkUserQueryReqVO reqVO) {
|
||||
String path = orgPaths().getUserPage();
|
||||
Map<String, Object> params = buildBaseParams(reqVO);
|
||||
if (StringUtils.hasText(reqVO.getWorkCode())) {
|
||||
@@ -138,7 +160,8 @@ public class IWorkOrgRestServiceImpl implements IWorkOrgRestService {
|
||||
if (StringUtils.hasText(reqVO.getEmail())) {
|
||||
params.put("email", reqVO.getEmail());
|
||||
}
|
||||
return invokeParamsEndpoint(path, params);
|
||||
JsonNode node = invokeParamsEndpoint(path, params);
|
||||
return buildUserPageResp(node);
|
||||
}
|
||||
|
||||
private Map<String, Object> buildBaseParams(IWorkOrgBaseQueryReqVO reqVO) {
|
||||
@@ -156,47 +179,51 @@ public class IWorkOrgRestServiceImpl implements IWorkOrgRestService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public IWorkOrgRespVO syncSubcompanies(IWorkOrgSyncReqVO reqVO) {
|
||||
public IWorkHrSyncRespVO syncSubcompanies(IWorkOrgSyncReqVO reqVO) {
|
||||
String path = orgPaths().getSyncSubcompany();
|
||||
return invokeDataEndpoint(path, reqVO.getData());
|
||||
JsonNode node = invokeDataEndpoint(path, reqVO.getData());
|
||||
return buildSyncResp(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IWorkOrgRespVO syncDepartments(IWorkOrgSyncReqVO reqVO) {
|
||||
public IWorkHrSyncRespVO syncDepartments(IWorkOrgSyncReqVO reqVO) {
|
||||
String path = orgPaths().getSyncDepartment();
|
||||
return invokeDataEndpoint(path, reqVO.getData());
|
||||
JsonNode node = invokeDataEndpoint(path, reqVO.getData());
|
||||
return buildSyncResp(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IWorkOrgRespVO syncJobTitles(IWorkOrgSyncReqVO reqVO) {
|
||||
public IWorkHrSyncRespVO syncJobTitles(IWorkOrgSyncReqVO reqVO) {
|
||||
String path = orgPaths().getSyncJobTitle();
|
||||
return invokeDataEndpoint(path, reqVO.getData());
|
||||
JsonNode node = invokeDataEndpoint(path, reqVO.getData());
|
||||
return buildSyncResp(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IWorkOrgRespVO syncUsers(IWorkOrgSyncReqVO reqVO) {
|
||||
public IWorkHrSyncRespVO syncUsers(IWorkOrgSyncReqVO reqVO) {
|
||||
String path = orgPaths().getSyncUser();
|
||||
return invokeDataEndpoint(path, reqVO.getData());
|
||||
JsonNode node = invokeDataEndpoint(path, reqVO.getData());
|
||||
return buildSyncResp(node);
|
||||
}
|
||||
|
||||
private IWorkOrgRespVO invokeParamsEndpoint(String path, Map<String, Object> params) {
|
||||
private JsonNode invokeParamsEndpoint(String path, Map<String, Object> params) {
|
||||
Objects.requireNonNull(params, "查询参数不能为空");
|
||||
Map<String, Object> payload = new HashMap<>();
|
||||
payload.put("params", params == null ? Collections.emptyMap() : params);
|
||||
payload.put("params", params);
|
||||
return executeJson(path, payload);
|
||||
}
|
||||
|
||||
private IWorkOrgRespVO invokeDataEndpoint(String path, Object data) {
|
||||
private JsonNode invokeDataEndpoint(String path, Object data) {
|
||||
Objects.requireNonNull(data, "同步数据不能为空");
|
||||
Map<String, Object> payload = new HashMap<>();
|
||||
payload.put("data", data == null ? Collections.emptyMap() : data);
|
||||
payload.put("data", data);
|
||||
return executeJson(path, payload);
|
||||
}
|
||||
|
||||
private IWorkOrgRespVO executeJson(String path, Map<String, Object> payload) {
|
||||
private JsonNode executeJson(String path, Map<String, Object> payload) {
|
||||
// 统一封装请求体并发送 POST 调用
|
||||
assertOrgConfigured(path);
|
||||
Map<String, Object> body = new HashMap<>();
|
||||
if (payload != null && !payload.isEmpty()) {
|
||||
body.putAll(payload);
|
||||
}
|
||||
Map<String, Object> body = new HashMap<>(payload);
|
||||
body.put("token", buildTokenPayload());
|
||||
String jsonBody = toJson(body);
|
||||
|
||||
@@ -214,7 +241,10 @@ public class IWorkOrgRestServiceImpl implements IWorkOrgRestService {
|
||||
log.error("[iWork-Org] 调用 {} 失败,status={} body={}", path, response.code(), responseBody);
|
||||
throw ServiceExceptionUtil.exception(IWORK_ORG_REMOTE_FAILED, response.code(), responseBody);
|
||||
}
|
||||
return buildResponse(responseBody);
|
||||
if (!StringUtils.hasText(responseBody)) {
|
||||
throw ServiceExceptionUtil.exception(IWORK_ORG_REMOTE_FAILED, "iWork 响应为空");
|
||||
}
|
||||
return parseJson(responseBody);
|
||||
} catch (IOException ex) {
|
||||
log.error("[iWork-Org] 调用 {} 失败", path, ex);
|
||||
throw ServiceExceptionUtil.exception(IWORK_ORG_REMOTE_FAILED, ex.getMessage());
|
||||
@@ -222,6 +252,7 @@ public class IWorkOrgRestServiceImpl implements IWorkOrgRestService {
|
||||
}
|
||||
|
||||
private Map<String, String> buildTokenPayload() {
|
||||
// 按 iWork 约定生成 key + ts 结构的鉴权信息
|
||||
String tokenSeed = StringUtils.trimWhitespace(orgConfig().getTokenSeed());
|
||||
if (!StringUtils.hasText(tokenSeed)) {
|
||||
throw ServiceExceptionUtil.exception(IWORK_ORG_IDENTIFIER_MISSING);
|
||||
@@ -242,20 +273,110 @@ public class IWorkOrgRestServiceImpl implements IWorkOrgRestService {
|
||||
return hex.toUpperCase(Locale.ROOT);
|
||||
}
|
||||
|
||||
private IWorkOrgRespVO buildResponse(String responseBody) {
|
||||
// 统一解析 iWork 响应,兼容 data 节点和扁平结构
|
||||
IWorkOrgRespVO respVO = new IWorkOrgRespVO();
|
||||
respVO.setRawBody(responseBody);
|
||||
if (!StringUtils.hasText(responseBody)) {
|
||||
respVO.setPayload(Collections.emptyMap());
|
||||
// 解析并封装分部分页响应
|
||||
private IWorkHrSubcompanyPageRespVO buildSubcompanyPageResp(JsonNode node) {
|
||||
ParsedEnvelope envelope = parseEnvelope(node);
|
||||
IWorkHrSubcompanyPageRespVO respVO = new IWorkHrSubcompanyPageRespVO();
|
||||
respVO.setCode(envelope.code());
|
||||
respVO.setMessage(envelope.message());
|
||||
respVO.setSuccess(envelope.success());
|
||||
JsonNode dataNode = envelope.success() ? readNode(envelope.root(), "data") : null;
|
||||
if (dataNode == null) {
|
||||
respVO.setTotalSize(0);
|
||||
respVO.setTotalPage(0);
|
||||
respVO.setPageSize(0);
|
||||
respVO.setPageNumber(0);
|
||||
respVO.setDataList(Collections.emptyList());
|
||||
return respVO;
|
||||
}
|
||||
JsonNode node = parseJson(responseBody);
|
||||
respVO.setCode(textValue(node, "code"));
|
||||
respVO.setMessage(resolveMessage(node));
|
||||
respVO.setSuccess(isSuccess(node));
|
||||
JsonNode payloadNode = node.has("data") ? node.get("data") : node;
|
||||
respVO.setPayload(objectMapper.convertValue(payloadNode, MAP_TYPE));
|
||||
respVO.setTotalSize(readInt(dataNode, 0, "totalSize"));
|
||||
respVO.setTotalPage(readInt(dataNode, 0, "totalPage", "totalPageCount"));
|
||||
respVO.setPageSize(readInt(dataNode, 0, "pageSize", "pagesize"));
|
||||
respVO.setPageNumber(readInt(dataNode, 0, "pageNumber", "page", "curpage"));
|
||||
respVO.setDataList(readList(dataNode, "dataList", SUBCOMPANY_LIST_TYPE));
|
||||
return respVO;
|
||||
}
|
||||
|
||||
// 解析并封装部门分页响应
|
||||
private IWorkHrDepartmentPageRespVO buildDepartmentPageResp(JsonNode node) {
|
||||
ParsedEnvelope envelope = parseEnvelope(node);
|
||||
IWorkHrDepartmentPageRespVO respVO = new IWorkHrDepartmentPageRespVO();
|
||||
respVO.setCode(envelope.code());
|
||||
respVO.setMessage(envelope.message());
|
||||
respVO.setSuccess(envelope.success());
|
||||
JsonNode dataNode = envelope.success() ? readNode(envelope.root(), "data") : null;
|
||||
if (dataNode == null) {
|
||||
respVO.setTotalSize(0);
|
||||
respVO.setTotalPage(0);
|
||||
respVO.setPageSize(0);
|
||||
respVO.setPageNumber(0);
|
||||
respVO.setDataList(Collections.emptyList());
|
||||
return respVO;
|
||||
}
|
||||
respVO.setTotalSize(readInt(dataNode, 0, "totalSize"));
|
||||
respVO.setTotalPage(readInt(dataNode, 0, "totalPage", "totalPageCount"));
|
||||
respVO.setPageSize(readInt(dataNode, 0, "pageSize", "pagesize"));
|
||||
respVO.setPageNumber(readInt(dataNode, 0, "pageNumber", "page", "curpage"));
|
||||
respVO.setDataList(readList(dataNode, "dataList", DEPARTMENT_LIST_TYPE));
|
||||
return respVO;
|
||||
}
|
||||
|
||||
// 解析并封装岗位分页响应
|
||||
private IWorkHrJobTitlePageRespVO buildJobTitlePageResp(JsonNode node) {
|
||||
ParsedEnvelope envelope = parseEnvelope(node);
|
||||
IWorkHrJobTitlePageRespVO respVO = new IWorkHrJobTitlePageRespVO();
|
||||
respVO.setCode(envelope.code());
|
||||
respVO.setMessage(envelope.message());
|
||||
respVO.setSuccess(envelope.success());
|
||||
JsonNode dataNode = envelope.success() ? readNode(envelope.root(), "data") : null;
|
||||
if (dataNode == null) {
|
||||
respVO.setTotalSize(0);
|
||||
respVO.setTotalPage(0);
|
||||
respVO.setPageSize(0);
|
||||
respVO.setPageNumber(0);
|
||||
respVO.setDataList(Collections.emptyList());
|
||||
return respVO;
|
||||
}
|
||||
respVO.setTotalSize(readInt(dataNode, 0, "totalSize"));
|
||||
respVO.setTotalPage(readInt(dataNode, 0, "totalPage", "totalPageCount"));
|
||||
respVO.setPageSize(readInt(dataNode, 0, "pageSize", "pagesize"));
|
||||
respVO.setPageNumber(readInt(dataNode, 0, "pageNumber", "page", "curpage"));
|
||||
respVO.setDataList(readList(dataNode, "dataList", JOB_TITLE_LIST_TYPE));
|
||||
return respVO;
|
||||
}
|
||||
|
||||
// 解析并封装人员分页响应
|
||||
private IWorkHrUserPageRespVO buildUserPageResp(JsonNode node) {
|
||||
ParsedEnvelope envelope = parseEnvelope(node);
|
||||
IWorkHrUserPageRespVO respVO = new IWorkHrUserPageRespVO();
|
||||
respVO.setCode(envelope.code());
|
||||
respVO.setMessage(envelope.message());
|
||||
respVO.setSuccess(envelope.success());
|
||||
JsonNode dataNode = envelope.success() ? readNode(envelope.root(), "data") : null;
|
||||
if (dataNode == null) {
|
||||
respVO.setTotalSize(0);
|
||||
respVO.setTotalPage(0);
|
||||
respVO.setPageSize(0);
|
||||
respVO.setPageNumber(0);
|
||||
respVO.setDataList(Collections.emptyList());
|
||||
return respVO;
|
||||
}
|
||||
respVO.setTotalSize(readInt(dataNode, 0, "totalSize"));
|
||||
respVO.setTotalPage(readInt(dataNode, 0, "totalPage", "totalPageCount"));
|
||||
respVO.setPageSize(readInt(dataNode, 0, "pageSize", "pagesize"));
|
||||
respVO.setPageNumber(readInt(dataNode, 0, "pageNumber", "page", "curpage"));
|
||||
respVO.setDataList(readList(dataNode, "dataList", USER_LIST_TYPE));
|
||||
return respVO;
|
||||
}
|
||||
|
||||
// 解析并封装同步结果
|
||||
private IWorkHrSyncRespVO buildSyncResp(JsonNode node) {
|
||||
ParsedEnvelope envelope = parseEnvelope(node);
|
||||
IWorkHrSyncRespVO respVO = new IWorkHrSyncRespVO();
|
||||
respVO.setCode(envelope.code());
|
||||
respVO.setMessage(envelope.message());
|
||||
respVO.setSuccess(envelope.success());
|
||||
respVO.setResult(readList(envelope.root(), "result", SYNC_RESULT_LIST_TYPE));
|
||||
return respVO;
|
||||
}
|
||||
|
||||
@@ -268,34 +389,74 @@ public class IWorkOrgRestServiceImpl implements IWorkOrgRestService {
|
||||
}
|
||||
}
|
||||
|
||||
private String resolveMessage(JsonNode node) {
|
||||
private ParsedEnvelope parseEnvelope(JsonNode node) {
|
||||
// 校验 iWork 顶层响应并抽取公共字段
|
||||
if (node == null) {
|
||||
throw ServiceExceptionUtil.exception(IWORK_ORG_REMOTE_FAILED, "iWork 响应缺失");
|
||||
}
|
||||
String code = node.has("code") ? node.get("code").asText() : null;
|
||||
if (!StringUtils.hasText(code)) {
|
||||
throw ServiceExceptionUtil.exception(IWORK_ORG_REMOTE_FAILED, "iWork 响应缺少 code 字段");
|
||||
}
|
||||
String message = null;
|
||||
if (node.has("msg")) {
|
||||
message = node.get("msg").asText();
|
||||
} else if (node.has("message")) {
|
||||
message = node.get("message").asText();
|
||||
}
|
||||
boolean success = "1".equals(code);
|
||||
return new ParsedEnvelope(code, message, success, node);
|
||||
}
|
||||
|
||||
private JsonNode readNode(JsonNode parent, String field) {
|
||||
if (parent == null || !parent.has(field)) {
|
||||
return null;
|
||||
}
|
||||
if (node.has("msg")) {
|
||||
return node.get("msg").asText();
|
||||
}
|
||||
if (node.has("message")) {
|
||||
return node.get("message").asText();
|
||||
}
|
||||
return null;
|
||||
JsonNode value = parent.get(field);
|
||||
return value == null || value.isNull() ? null : value;
|
||||
}
|
||||
|
||||
private boolean isSuccess(JsonNode node) {
|
||||
if (node == null) {
|
||||
return false;
|
||||
private int readInt(JsonNode parent, int defaultValue, String... fieldNames) {
|
||||
if (parent == null || fieldNames == null) {
|
||||
return defaultValue;
|
||||
}
|
||||
if ("1".equals(textValue(node, "code"))) {
|
||||
return true;
|
||||
for (String field : fieldNames) {
|
||||
if (!StringUtils.hasText(field)) {
|
||||
continue;
|
||||
}
|
||||
JsonNode valueNode = readNode(parent, field);
|
||||
if (valueNode == null) {
|
||||
continue;
|
||||
}
|
||||
if (valueNode.isNumber()) {
|
||||
return valueNode.intValue();
|
||||
}
|
||||
if (valueNode.isTextual()) {
|
||||
try {
|
||||
return Integer.parseInt(valueNode.asText());
|
||||
} catch (NumberFormatException ex) {
|
||||
log.warn("[iWork-Org] 字段格式非数值,使用默认值: {}", field);
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
log.warn("[iWork-Org] 字段类型非整数,使用默认值: {}", field);
|
||||
return defaultValue;
|
||||
}
|
||||
if ("1".equals(textValue(node, "status"))) {
|
||||
return true;
|
||||
}
|
||||
return "1".equals(textValue(node, "success"));
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
private String textValue(JsonNode node, String field) {
|
||||
return node != null && node.has(field) ? node.get(field).asText() : null;
|
||||
private <T> List<T> readList(JsonNode parent, String field, TypeReference<List<T>> typeReference) {
|
||||
JsonNode arrayNode = readNode(parent, field);
|
||||
if (arrayNode == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
if (!arrayNode.isArray()) {
|
||||
log.warn("[iWork-Org] 字段应为数组但实际不是,尝试转换: {}", field);
|
||||
}
|
||||
return objectMapper.convertValue(arrayNode, typeReference);
|
||||
}
|
||||
|
||||
private record ParsedEnvelope(String code, String message, boolean success, JsonNode root) {
|
||||
}
|
||||
|
||||
private void assertOrgConfigured(String path) {
|
||||
@@ -318,7 +479,7 @@ public class IWorkOrgRestServiceImpl implements IWorkOrgRestService {
|
||||
|
||||
private String toJson(Object payload) {
|
||||
try {
|
||||
return objectMapper.writeValueAsString(payload == null ? Collections.emptyMap() : payload);
|
||||
return objectMapper.writeValueAsString(payload);
|
||||
} catch (JsonProcessingException ex) {
|
||||
throw ServiceExceptionUtil.exception(IWORK_ORG_REMOTE_FAILED, "序列化 JSON 失败: " + ex.getMessage());
|
||||
}
|
||||
|
||||
@@ -0,0 +1,775 @@
|
||||
package com.zt.plat.module.system.service.integration.iwork.impl;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.zt.plat.framework.common.enums.CommonStatusEnum;
|
||||
import com.zt.plat.framework.tenant.core.context.TenantContextHolder;
|
||||
import com.zt.plat.module.system.controller.admin.dept.vo.depexternalcode.DeptExternalCodeSaveReqVO;
|
||||
import com.zt.plat.module.system.controller.admin.dept.vo.dept.DeptSaveReqVO;
|
||||
import com.zt.plat.module.system.controller.admin.dept.vo.post.PostSaveReqVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkHrDepartmentPageRespVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkHrJobTitlePageRespVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkHrSubcompanyPageRespVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkHrUserPageRespVO;
|
||||
import com.zt.plat.module.system.controller.admin.user.vo.user.UserSaveReqVO;
|
||||
import com.zt.plat.module.system.dal.dataobject.dept.DeptDO;
|
||||
import com.zt.plat.module.system.dal.dataobject.dept.DeptExternalCodeDO;
|
||||
import com.zt.plat.module.system.dal.dataobject.dept.PostDO;
|
||||
import com.zt.plat.module.system.dal.dataobject.user.AdminUserDO;
|
||||
import com.zt.plat.module.system.dal.mysql.dept.PostMapper;
|
||||
import com.zt.plat.module.system.dal.mysql.user.AdminUserMapper;
|
||||
import com.zt.plat.module.system.enums.common.SexEnum;
|
||||
import com.zt.plat.module.system.enums.dept.DeptSourceEnum;
|
||||
import com.zt.plat.module.system.enums.user.UserSourceEnum;
|
||||
import com.zt.plat.module.system.service.dept.DeptExternalCodeService;
|
||||
import com.zt.plat.module.system.service.dept.DeptService;
|
||||
import com.zt.plat.module.system.service.dept.PostService;
|
||||
import com.zt.plat.module.system.service.integration.iwork.IWorkSyncProcessor;
|
||||
import com.zt.plat.module.system.service.user.AdminUserService;
|
||||
import com.zt.plat.module.system.util.sync.SyncVerifyUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class IWorkSyncProcessorImpl implements IWorkSyncProcessor {
|
||||
|
||||
private static final String SYSTEM_CODE_SUBCOMPANY = "IWORK_SUBCOMPANY";
|
||||
private static final String SYSTEM_CODE_DEPARTMENT = "IWORK_DEPARTMENT";
|
||||
private static final String JOB_CODE_PREFIX = "IWORK_JOB_";
|
||||
private static final String DEFAULT_USER_PASSWORD = "Zgty@9527";
|
||||
private static final int DEFAULT_SORT = 999;
|
||||
|
||||
private final DeptService deptService;
|
||||
private final DeptExternalCodeService deptExternalCodeService;
|
||||
private final PostService postService;
|
||||
private final PostMapper postMapper;
|
||||
private final AdminUserService adminUserService;
|
||||
private final AdminUserMapper adminUserMapper;
|
||||
|
||||
private final Map<String, DeptExternalCodeDO> deptExternalCache = new ConcurrentHashMap<>();
|
||||
private final Map<String, PostDO> postCache = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public BatchResult syncSubcompanies(List<IWorkHrSubcompanyPageRespVO.Subcompany> data, SyncOptions options) {
|
||||
List<IWorkHrSubcompanyPageRespVO.Subcompany> records = CollUtil.emptyIfNull(data);
|
||||
BatchResult result = BatchResult.empty();
|
||||
if (records.isEmpty()) {
|
||||
return result;
|
||||
}
|
||||
result.increasePulled(records.size());
|
||||
List<IWorkHrSubcompanyPageRespVO.Subcompany> queue = new ArrayList<>(records);
|
||||
int guard = 0;
|
||||
int maxPasses = Math.max(1, queue.size() * 2);
|
||||
while (!queue.isEmpty() && guard++ < maxPasses) {
|
||||
int processed = 0;
|
||||
Iterator<IWorkHrSubcompanyPageRespVO.Subcompany> iterator = queue.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
IWorkHrSubcompanyPageRespVO.Subcompany sub = iterator.next();
|
||||
if (shouldSkipByCanceled(sub.getCanceled(), options)) {
|
||||
result.increaseSkipped();
|
||||
iterator.remove();
|
||||
continue;
|
||||
}
|
||||
if (sub.getSubcompanyid1() == null) {
|
||||
log.warn("[iWork] 分部缺少标识,跳过:{}", sub.getSubcompanyname());
|
||||
result.increaseFailed();
|
||||
iterator.remove();
|
||||
continue;
|
||||
}
|
||||
ParentHolder parentHolder = resolveSubcompanyParent(sub.getSupsubcomid());
|
||||
if (parentHolder.required() && parentHolder.parentId() == null) {
|
||||
continue;
|
||||
}
|
||||
boolean canceled = isCanceledFlag(sub.getCanceled());
|
||||
DeptSaveReqVO saveReq = buildSubcompanySaveReq(sub, parentHolder.parentId(), canceled);
|
||||
try {
|
||||
DeptSyncOutcome outcome = upsertDept(saveReq,
|
||||
SYSTEM_CODE_SUBCOMPANY,
|
||||
sub.getSubcompanyid1().toString(),
|
||||
sub.getSubcompanyname(),
|
||||
canceled,
|
||||
options);
|
||||
applyDeptOutcome(result, outcome, "分部", sub.getSubcompanyname());
|
||||
} catch (Exception ex) {
|
||||
log.error("[iWork] 同步分部失败: id={} name={}", sub.getSubcompanyid1(), sub.getSubcompanyname(), ex);
|
||||
result.increaseFailed();
|
||||
result.withMessage("同步分部失败: " + ex.getMessage());
|
||||
}
|
||||
iterator.remove();
|
||||
processed++;
|
||||
}
|
||||
if (processed == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!queue.isEmpty()) {
|
||||
for (IWorkHrSubcompanyPageRespVO.Subcompany remaining : queue) {
|
||||
log.warn("[iWork] 分部因父级缺失未同步: id={} name={}", remaining.getSubcompanyid1(), remaining.getSubcompanyname());
|
||||
result.increaseFailed();
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BatchResult syncDepartments(List<IWorkHrDepartmentPageRespVO.Department> data, SyncOptions options) {
|
||||
List<IWorkHrDepartmentPageRespVO.Department> records = CollUtil.emptyIfNull(data);
|
||||
BatchResult result = BatchResult.empty();
|
||||
if (records.isEmpty()) {
|
||||
return result;
|
||||
}
|
||||
result.increasePulled(records.size());
|
||||
List<IWorkHrDepartmentPageRespVO.Department> queue = new ArrayList<>(records);
|
||||
int guard = 0;
|
||||
int maxPasses = Math.max(1, queue.size() * 2);
|
||||
while (!queue.isEmpty() && guard++ < maxPasses) {
|
||||
int processed = 0;
|
||||
Iterator<IWorkHrDepartmentPageRespVO.Department> iterator = queue.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
IWorkHrDepartmentPageRespVO.Department dept = iterator.next();
|
||||
if (shouldSkipByCanceled(dept.getCanceled(), options)) {
|
||||
result.increaseSkipped();
|
||||
iterator.remove();
|
||||
continue;
|
||||
}
|
||||
if (dept.getDepartmentid() == null) {
|
||||
log.warn("[iWork] 部门缺少标识,跳过:{}", dept.getDepartmentname());
|
||||
result.increaseFailed();
|
||||
iterator.remove();
|
||||
continue;
|
||||
}
|
||||
ParentHolder parentHolder = resolveDepartmentParent(dept);
|
||||
if (parentHolder.required() && parentHolder.parentId() == null) {
|
||||
continue;
|
||||
}
|
||||
boolean canceled = isCanceledFlag(dept.getCanceled());
|
||||
DeptSaveReqVO saveReq = buildDepartmentSaveReq(dept, parentHolder.parentId(), canceled);
|
||||
try {
|
||||
DeptSyncOutcome outcome = upsertDept(saveReq,
|
||||
SYSTEM_CODE_DEPARTMENT,
|
||||
dept.getDepartmentid().toString(),
|
||||
dept.getDepartmentname(),
|
||||
canceled,
|
||||
options);
|
||||
applyDeptOutcome(result, outcome, "部门", dept.getDepartmentname());
|
||||
} catch (Exception ex) {
|
||||
log.error("[iWork] 同步部门失败: id={} name={}", dept.getDepartmentid(), dept.getDepartmentname(), ex);
|
||||
result.increaseFailed();
|
||||
result.withMessage("同步部门失败: " + ex.getMessage());
|
||||
}
|
||||
iterator.remove();
|
||||
processed++;
|
||||
}
|
||||
if (processed == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!queue.isEmpty()) {
|
||||
for (IWorkHrDepartmentPageRespVO.Department remaining : queue) {
|
||||
log.warn("[iWork] 部门因父级缺失未同步: id={} name={}", remaining.getDepartmentid(), remaining.getDepartmentname());
|
||||
result.increaseFailed();
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BatchResult syncJobTitles(List<IWorkHrJobTitlePageRespVO.JobTitle> data, SyncOptions options) {
|
||||
List<IWorkHrJobTitlePageRespVO.JobTitle> records = CollUtil.emptyIfNull(data);
|
||||
BatchResult result = BatchResult.empty();
|
||||
if (records.isEmpty()) {
|
||||
return result;
|
||||
}
|
||||
result.increasePulled(records.size());
|
||||
for (IWorkHrJobTitlePageRespVO.JobTitle job : records) {
|
||||
if (job == null) {
|
||||
continue;
|
||||
}
|
||||
if (shouldSkipByCanceled(job.getCanceled(), options)) {
|
||||
result.increaseSkipped();
|
||||
continue;
|
||||
}
|
||||
if (job.getJobtitleid() == null) {
|
||||
log.warn("[iWork] 岗位缺少标识,跳过:{}", job.getJobtitlename());
|
||||
result.increaseFailed();
|
||||
continue;
|
||||
}
|
||||
boolean canceled = isCanceledFlag(job.getCanceled());
|
||||
Integer status = toStatus(canceled);
|
||||
String code = buildJobCode(job.getJobtitleid());
|
||||
String name = limitLength(StrUtil.blankToDefault(job.getJobtitlename(), code), 50);
|
||||
try {
|
||||
JobSyncOutcome outcome = upsertJobTitle(job, code, name, status, options);
|
||||
applyJobOutcome(result, outcome, name);
|
||||
} catch (Exception ex) {
|
||||
log.error("[iWork] 同步岗位失败: id={} name={}", job.getJobtitleid(), job.getJobtitlename(), ex);
|
||||
result.increaseFailed();
|
||||
result.withMessage("同步岗位失败: " + ex.getMessage());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BatchResult syncUsers(List<IWorkHrUserPageRespVO.User> data, SyncOptions options) {
|
||||
List<IWorkHrUserPageRespVO.User> records = CollUtil.emptyIfNull(data);
|
||||
BatchResult result = BatchResult.empty();
|
||||
if (records.isEmpty()) {
|
||||
return result;
|
||||
}
|
||||
result.increasePulled(records.size());
|
||||
for (IWorkHrUserPageRespVO.User user : records) {
|
||||
if (user == null) {
|
||||
continue;
|
||||
}
|
||||
boolean inactive = isInactiveUser(user.getStatus());
|
||||
if (inactive && !options.isIncludeCanceled()) {
|
||||
result.increaseSkipped();
|
||||
continue;
|
||||
}
|
||||
String username = resolveUsername(user);
|
||||
if (StrUtil.isBlank(username)) {
|
||||
log.warn("[iWork] 人员缺少可用账号,跳过:id={} name={}", user.getId(), user.getLastname());
|
||||
result.increaseFailed();
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
Long deptId = resolveUserDeptId(user);
|
||||
if (deptId == null) {
|
||||
log.warn("[iWork] 人员未找到匹配部门,跳过:id={} name={} deptId={} subcompany={}",
|
||||
user.getId(), user.getLastname(), user.getDepartmentid(), user.getSubcompanyid1());
|
||||
result.increaseFailed();
|
||||
continue;
|
||||
}
|
||||
Long postId = resolveUserPostId(user);
|
||||
CommonStatusEnum status = inactive ? CommonStatusEnum.DISABLE : CommonStatusEnum.ENABLE;
|
||||
AdminUserDO existing = adminUserMapper.selectByUsername(username);
|
||||
UserSyncOutcome outcome;
|
||||
if (existing == null) {
|
||||
if (!options.isCreateIfMissing()) {
|
||||
result.increaseSkipped();
|
||||
continue;
|
||||
}
|
||||
outcome = createUser(user, username, deptId, postId, status);
|
||||
} else {
|
||||
if (!Objects.equals(existing.getUserSource(), UserSourceEnum.IWORK.getSource())) {
|
||||
result.increaseSkipped();
|
||||
continue;
|
||||
}
|
||||
if (!options.isAllowUpdate()) {
|
||||
result.increaseSkipped();
|
||||
continue;
|
||||
}
|
||||
outcome = updateUser(existing, user, username, deptId, postId, status);
|
||||
}
|
||||
applyUserOutcome(result, outcome, user.getLastname(), username);
|
||||
} catch (Exception ex) {
|
||||
log.error("[iWork] 同步人员失败: id={} name={}", user.getId(), user.getLastname(), ex);
|
||||
result.increaseFailed();
|
||||
result.withMessage("同步人员失败: " + ex.getMessage());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private DeptSyncOutcome upsertDept(DeptSaveReqVO desired,
|
||||
String systemCode,
|
||||
String externalCode,
|
||||
String externalName,
|
||||
boolean disabled,
|
||||
SyncOptions options) {
|
||||
DeptExternalCodeDO mapping = getDeptMapping(systemCode, externalCode);
|
||||
if (mapping == null) {
|
||||
if (!options.isCreateIfMissing()) {
|
||||
return new DeptSyncOutcome(SyncAction.SKIPPED, false, null);
|
||||
}
|
||||
Long deptId = deptService.createDept(desired);
|
||||
persistDeptExternalCode(null, deptId, systemCode, externalCode, externalName, desired.getStatus());
|
||||
return new DeptSyncOutcome(SyncAction.CREATED, CommonStatusEnum.isDisable(desired.getStatus()), deptId);
|
||||
}
|
||||
Long deptId = mapping.getDeptId();
|
||||
DeptDO existing = deptService.getDept(deptId);
|
||||
if (existing == null) {
|
||||
deptExternalCache.remove(buildDeptCacheKey(systemCode, externalCode));
|
||||
if (!options.isCreateIfMissing()) {
|
||||
return new DeptSyncOutcome(SyncAction.SKIPPED, false, null);
|
||||
}
|
||||
Long recreatedId = deptService.createDept(desired);
|
||||
persistDeptExternalCode(mapping.getId(), recreatedId, systemCode, externalCode, externalName, desired.getStatus());
|
||||
return new DeptSyncOutcome(SyncAction.CREATED, CommonStatusEnum.isDisable(desired.getStatus()), recreatedId);
|
||||
}
|
||||
if (!Objects.equals(existing.getDeptSource(), DeptSourceEnum.IWORK.getSource())) {
|
||||
return new DeptSyncOutcome(SyncAction.SKIPPED, false, existing.getId());
|
||||
}
|
||||
if (!options.isAllowUpdate()) {
|
||||
return new DeptSyncOutcome(SyncAction.SKIPPED, false, existing.getId());
|
||||
}
|
||||
desired.setId(existing.getId());
|
||||
mergeDeptDefaults(desired, existing);
|
||||
boolean disabledChanged = CommonStatusEnum.isDisable(desired.getStatus()) && CommonStatusEnum.isEnable(existing.getStatus());
|
||||
deptService.updateDept(desired);
|
||||
persistDeptExternalCode(mapping.getId(), existing.getId(), systemCode, externalCode, externalName, desired.getStatus());
|
||||
return new DeptSyncOutcome(SyncAction.UPDATED, disabledChanged, existing.getId());
|
||||
}
|
||||
|
||||
private JobSyncOutcome upsertJobTitle(IWorkHrJobTitlePageRespVO.JobTitle job,
|
||||
String code,
|
||||
String name,
|
||||
Integer status,
|
||||
SyncOptions options) {
|
||||
PostDO existing = resolvePostByCode(code);
|
||||
boolean disabled = CommonStatusEnum.isDisable(status);
|
||||
if (existing == null) {
|
||||
if (!options.isCreateIfMissing()) {
|
||||
return new JobSyncOutcome(SyncAction.SKIPPED, false, null);
|
||||
}
|
||||
PostSaveReqVO createReq = new PostSaveReqVO();
|
||||
createReq.setCode(code);
|
||||
createReq.setName(name);
|
||||
createReq.setSort(defaultSort(job.getShoworder()));
|
||||
createReq.setStatus(status);
|
||||
createReq.setRemark(StrUtil.blankToDefault(job.getJobfunction(), job.getDescription()));
|
||||
Long postId = postService.createPost(createReq);
|
||||
PostDO created = new PostDO();
|
||||
created.setId(postId);
|
||||
created.setCode(code);
|
||||
created.setName(createReq.getName());
|
||||
created.setSort(createReq.getSort());
|
||||
created.setStatus(status);
|
||||
created.setRemark(createReq.getRemark());
|
||||
postCache.put(buildPostCacheKey(code), created);
|
||||
return new JobSyncOutcome(SyncAction.CREATED, disabled, postId);
|
||||
}
|
||||
if (!options.isAllowUpdate()) {
|
||||
return new JobSyncOutcome(SyncAction.SKIPPED, false, existing.getId());
|
||||
}
|
||||
PostSaveReqVO updateReq = new PostSaveReqVO();
|
||||
updateReq.setId(existing.getId());
|
||||
updateReq.setCode(code);
|
||||
updateReq.setName(name);
|
||||
updateReq.setSort(defaultSort(job.getShoworder(), existing.getSort()));
|
||||
updateReq.setStatus(status);
|
||||
updateReq.setRemark(StrUtil.blankToDefault(job.getJobfunction(), job.getDescription()));
|
||||
boolean disabledChanged = disabled && CommonStatusEnum.isEnable(existing.getStatus());
|
||||
postService.updatePost(updateReq);
|
||||
existing.setName(updateReq.getName());
|
||||
existing.setSort(updateReq.getSort());
|
||||
existing.setStatus(status);
|
||||
existing.setRemark(updateReq.getRemark());
|
||||
postCache.put(buildPostCacheKey(code), existing);
|
||||
return new JobSyncOutcome(SyncAction.UPDATED, disabledChanged, existing.getId());
|
||||
}
|
||||
|
||||
private UserSyncOutcome createUser(IWorkHrUserPageRespVO.User source,
|
||||
String username,
|
||||
Long deptId,
|
||||
Long postId,
|
||||
CommonStatusEnum status) {
|
||||
UserSaveReqVO req = buildUserSaveReq(source, username, deptId, postId, status);
|
||||
req.setPassword(DEFAULT_USER_PASSWORD);
|
||||
req.setUserSource(UserSourceEnum.IWORK.getSource());
|
||||
Long userId = adminUserService.createUser(req);
|
||||
return new UserSyncOutcome(SyncAction.CREATED, CommonStatusEnum.isDisable(req.getStatus()), userId);
|
||||
}
|
||||
|
||||
private UserSyncOutcome updateUser(AdminUserDO existing,
|
||||
IWorkHrUserPageRespVO.User source,
|
||||
String username,
|
||||
Long deptId,
|
||||
Long postId,
|
||||
CommonStatusEnum status) {
|
||||
UserSaveReqVO req = buildUserSaveReq(source, username, deptId, postId, status);
|
||||
req.setId(existing.getId());
|
||||
boolean disabledChanged = CommonStatusEnum.isDisable(status.getStatus()) && CommonStatusEnum.isEnable(existing.getStatus());
|
||||
adminUserService.updateUser(req);
|
||||
return new UserSyncOutcome(SyncAction.UPDATED, disabledChanged, existing.getId());
|
||||
}
|
||||
|
||||
private DeptSaveReqVO buildSubcompanySaveReq(IWorkHrSubcompanyPageRespVO.Subcompany data,
|
||||
Long parentId,
|
||||
boolean canceled) {
|
||||
DeptSaveReqVO req = new DeptSaveReqVO();
|
||||
req.setName(limitLength(StrUtil.blankToDefault(data.getSubcompanyname(), "未命名分部"), 30));
|
||||
req.setShortName(limitLength(data.getSubcompanyname(), 20));
|
||||
req.setCode(trimToNull(data.getSubcompanycode()));
|
||||
req.setParentId(parentId == null ? DeptDO.PARENT_ID_ROOT : parentId);
|
||||
req.setSort(defaultSort(data.getShoworder()));
|
||||
req.setStatus(toStatus(canceled));
|
||||
req.setIsCompany(Boolean.TRUE);
|
||||
req.setIsGroup(Boolean.FALSE);
|
||||
req.setDeptSource(DeptSourceEnum.IWORK.getSource());
|
||||
return req;
|
||||
}
|
||||
|
||||
private DeptSaveReqVO buildDepartmentSaveReq(IWorkHrDepartmentPageRespVO.Department data,
|
||||
Long parentId,
|
||||
boolean canceled) {
|
||||
DeptSaveReqVO req = new DeptSaveReqVO();
|
||||
req.setName(limitLength(StrUtil.blankToDefault(data.getDepartmentname(), "未命名部门"), 30));
|
||||
req.setShortName(limitLength(StrUtil.blankToDefault(data.getDepartmentmark(), data.getDepartmentname()), 20));
|
||||
req.setCode(trimToNull(data.getDepartmentcode()));
|
||||
req.setParentId(parentId == null ? DeptDO.PARENT_ID_ROOT : parentId);
|
||||
req.setSort(defaultSort(data.getShoworder()));
|
||||
req.setStatus(toStatus(canceled));
|
||||
req.setIsCompany(Boolean.FALSE);
|
||||
req.setIsGroup(Boolean.FALSE);
|
||||
req.setDeptSource(DeptSourceEnum.IWORK.getSource());
|
||||
return req;
|
||||
}
|
||||
|
||||
private UserSaveReqVO buildUserSaveReq(IWorkHrUserPageRespVO.User source,
|
||||
String username,
|
||||
Long deptId,
|
||||
Long postId,
|
||||
CommonStatusEnum status) {
|
||||
UserSaveReqVO req = new UserSaveReqVO();
|
||||
req.setUsername(username);
|
||||
req.setNickname(limitLength(StrUtil.blankToDefault(source.getLastname(), username), 30));
|
||||
req.setRemark(buildUserRemark(source));
|
||||
req.setDeptIds(singletonSet(deptId));
|
||||
if (postId != null) {
|
||||
req.setPostIds(singletonSet(postId));
|
||||
}
|
||||
req.setEmail(trimToNull(source.getEmail()));
|
||||
req.setMobile(trimToNull(source.getMobile()));
|
||||
req.setSex(resolveSex(source.getSex()));
|
||||
req.setStatus(status.getStatus());
|
||||
return req;
|
||||
}
|
||||
|
||||
private void mergeDeptDefaults(DeptSaveReqVO target, DeptDO existing) {
|
||||
target.setCode(StrUtil.blankToDefault(target.getCode(), existing.getCode()));
|
||||
target.setShortName(StrUtil.blankToDefault(target.getShortName(), existing.getShortName()));
|
||||
target.setParentId(target.getParentId() == null ? existing.getParentId() : target.getParentId());
|
||||
target.setSort(target.getSort() == null ? existing.getSort() : target.getSort());
|
||||
target.setStatus(target.getStatus() == null ? existing.getStatus() : target.getStatus());
|
||||
target.setLeaderUserId(existing.getLeaderUserId());
|
||||
target.setPhone(existing.getPhone());
|
||||
target.setEmail(existing.getEmail());
|
||||
target.setTenantId(existing.getTenantId());
|
||||
target.setIsCompany(target.getIsCompany() == null ? existing.getIsCompany() : target.getIsCompany());
|
||||
target.setIsGroup(target.getIsGroup() == null ? existing.getIsGroup() : target.getIsGroup());
|
||||
target.setDeptSource(DeptSourceEnum.IWORK.getSource());
|
||||
}
|
||||
|
||||
private ParentHolder resolveSubcompanyParent(Integer parentExternalId) {
|
||||
if (parentExternalId == null || parentExternalId <= 0) {
|
||||
return new ParentHolder(DeptDO.PARENT_ID_ROOT, false);
|
||||
}
|
||||
Long parentId = resolveDeptId(SYSTEM_CODE_SUBCOMPANY, parentExternalId.toString());
|
||||
return new ParentHolder(parentId, true);
|
||||
}
|
||||
|
||||
private ParentHolder resolveDepartmentParent(IWorkHrDepartmentPageRespVO.Department dept) {
|
||||
Integer parentDeptId = dept.getParentdeptid();
|
||||
if (parentDeptId != null && parentDeptId > 0) {
|
||||
Long parentId = resolveDeptId(SYSTEM_CODE_DEPARTMENT, parentDeptId.toString());
|
||||
return new ParentHolder(parentId, true);
|
||||
}
|
||||
Integer subcompanyId = dept.getSubcompanyid1();
|
||||
if (subcompanyId != null && subcompanyId > 0) {
|
||||
Long parentId = resolveDeptId(SYSTEM_CODE_SUBCOMPANY, subcompanyId.toString());
|
||||
return new ParentHolder(parentId, true);
|
||||
}
|
||||
return new ParentHolder(DeptDO.PARENT_ID_ROOT, false);
|
||||
}
|
||||
|
||||
private DeptExternalCodeDO getDeptMapping(String systemCode, String externalCode) {
|
||||
if (StrUtil.isBlank(externalCode)) {
|
||||
return null;
|
||||
}
|
||||
String key = buildDeptCacheKey(systemCode, externalCode);
|
||||
DeptExternalCodeDO cached = deptExternalCache.get(key);
|
||||
if (cached != null) {
|
||||
return cached;
|
||||
}
|
||||
DeptExternalCodeDO mapping = deptExternalCodeService.getBySystemCodeAndExternalCode(systemCode, externalCode);
|
||||
if (mapping != null) {
|
||||
deptExternalCache.put(key, mapping);
|
||||
}
|
||||
return mapping;
|
||||
}
|
||||
|
||||
private void persistDeptExternalCode(Long mappingId,
|
||||
Long deptId,
|
||||
String systemCode,
|
||||
String externalCode,
|
||||
String externalName,
|
||||
Integer status) {
|
||||
DeptExternalCodeSaveReqVO req = new DeptExternalCodeSaveReqVO();
|
||||
req.setDeptId(deptId);
|
||||
req.setSystemCode(systemCode);
|
||||
req.setExternalDeptCode(externalCode);
|
||||
req.setExternalDeptName(StrUtil.blankToDefault(externalName, externalCode));
|
||||
req.setStatus(status);
|
||||
req.setRemark("iWork 同步");
|
||||
DeptExternalCodeDO mapping;
|
||||
if (mappingId == null) {
|
||||
Long id = deptExternalCodeService.createDeptExternalCode(req);
|
||||
mapping = buildMapping(id, deptId, req);
|
||||
} else {
|
||||
req.setId(mappingId);
|
||||
deptExternalCodeService.updateDeptExternalCode(req);
|
||||
mapping = buildMapping(mappingId, deptId, req);
|
||||
}
|
||||
deptExternalCache.put(buildDeptCacheKey(systemCode, externalCode), mapping);
|
||||
}
|
||||
|
||||
private DeptExternalCodeDO buildMapping(Long id, Long deptId, DeptExternalCodeSaveReqVO req) {
|
||||
DeptExternalCodeDO mapping = new DeptExternalCodeDO();
|
||||
mapping.setId(id);
|
||||
mapping.setDeptId(deptId);
|
||||
mapping.setSystemCode(req.getSystemCode());
|
||||
mapping.setExternalDeptCode(req.getExternalDeptCode());
|
||||
mapping.setExternalDeptName(req.getExternalDeptName());
|
||||
mapping.setStatus(req.getStatus());
|
||||
mapping.setRemark(req.getRemark());
|
||||
return mapping;
|
||||
}
|
||||
|
||||
private Long resolveDeptId(String systemCode, String externalCode) {
|
||||
DeptExternalCodeDO mapping = getDeptMapping(systemCode, externalCode);
|
||||
return mapping == null ? null : mapping.getDeptId();
|
||||
}
|
||||
|
||||
private PostDO resolvePostByCode(String code) {
|
||||
String key = buildPostCacheKey(code);
|
||||
PostDO cached = postCache.get(key);
|
||||
if (cached != null) {
|
||||
return cached;
|
||||
}
|
||||
PostDO post = postMapper.selectByCode(code);
|
||||
if (post != null) {
|
||||
postCache.put(key, post);
|
||||
}
|
||||
return post;
|
||||
}
|
||||
|
||||
private Long resolveUserDeptId(IWorkHrUserPageRespVO.User user) {
|
||||
if (user.getDepartmentid() != null) {
|
||||
Long deptId = resolveDeptId(SYSTEM_CODE_DEPARTMENT, user.getDepartmentid().toString());
|
||||
if (deptId != null) {
|
||||
return deptId;
|
||||
}
|
||||
}
|
||||
if (user.getSubcompanyid1() != null) {
|
||||
Long deptId = resolveDeptId(SYSTEM_CODE_SUBCOMPANY, user.getSubcompanyid1().toString());
|
||||
if (deptId != null) {
|
||||
return deptId;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Long resolveUserPostId(IWorkHrUserPageRespVO.User user) {
|
||||
if (user.getJobtitleid() == null) {
|
||||
return null;
|
||||
}
|
||||
String code = buildJobCode(user.getJobtitleid());
|
||||
PostDO post = resolvePostByCode(code);
|
||||
if (post != null) {
|
||||
return post.getId();
|
||||
}
|
||||
if (StrUtil.isNotBlank(user.getJobtitlename())) {
|
||||
return postService.getOrCreatePostByName(user.getJobtitlename().trim());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void applyDeptOutcome(BatchResult result, DeptSyncOutcome outcome, String entityLabel, String name) {
|
||||
if (outcome == null) {
|
||||
return;
|
||||
}
|
||||
incrementByAction(result, outcome.action());
|
||||
if (outcome.disabled()) {
|
||||
result.increaseDisabled();
|
||||
}
|
||||
result.withMessage(StrUtil.format("{}[{}]{}", entityLabel, StrUtil.blankToDefault(name, "-"), describeAction(outcome.action())));
|
||||
}
|
||||
|
||||
private void applyJobOutcome(BatchResult result, JobSyncOutcome outcome, String name) {
|
||||
if (outcome == null) {
|
||||
return;
|
||||
}
|
||||
incrementByAction(result, outcome.action());
|
||||
if (outcome.disabled()) {
|
||||
result.increaseDisabled();
|
||||
}
|
||||
result.withMessage(StrUtil.format("岗位[{}]{}", StrUtil.blankToDefault(name, "-"), describeAction(outcome.action())));
|
||||
}
|
||||
|
||||
private void applyUserOutcome(BatchResult result, UserSyncOutcome outcome, String displayName, String username) {
|
||||
if (outcome == null) {
|
||||
return;
|
||||
}
|
||||
incrementByAction(result, outcome.action());
|
||||
if (outcome.disabled()) {
|
||||
result.increaseDisabled();
|
||||
}
|
||||
result.withMessage(StrUtil.format("人员[{}]{}",
|
||||
StrUtil.blankToDefault(displayName, username), describeAction(outcome.action())));
|
||||
}
|
||||
|
||||
private void incrementByAction(BatchResult result, SyncAction action) {
|
||||
if (action == null) {
|
||||
return;
|
||||
}
|
||||
switch (action) {
|
||||
case CREATED -> result.increaseCreated();
|
||||
case UPDATED -> result.increaseUpdated();
|
||||
case SKIPPED -> result.increaseSkipped();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldSkipByCanceled(String canceled, SyncOptions options) {
|
||||
return isCanceledFlag(canceled) && !options.isIncludeCanceled();
|
||||
}
|
||||
|
||||
private boolean isCanceledFlag(String flag) {
|
||||
if (StrUtil.isBlank(flag)) {
|
||||
return false;
|
||||
}
|
||||
String normalized = flag.trim();
|
||||
return "1".equals(normalized) || "true".equalsIgnoreCase(normalized) || "yes".equalsIgnoreCase(normalized);
|
||||
}
|
||||
|
||||
private boolean isInactiveUser(String statusFlag) {
|
||||
if (StrUtil.isBlank(statusFlag)) {
|
||||
return false;
|
||||
}
|
||||
return !"0".equals(statusFlag.trim());
|
||||
}
|
||||
|
||||
private Integer resolveSex(String sexFlag) {
|
||||
Integer external = parseInteger(sexFlag);
|
||||
Integer converted = SyncVerifyUtil.convertExternalToInternal(external);
|
||||
return converted != null ? converted : SexEnum.UNKNOWN.getSex();
|
||||
}
|
||||
|
||||
private Integer parseInteger(String raw) {
|
||||
if (StrUtil.isBlank(raw)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return Integer.parseInt(raw.trim());
|
||||
} catch (NumberFormatException ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private String resolveUsername(IWorkHrUserPageRespVO.User user) {
|
||||
String candidate = sanitizeUsername(user.getLoginid());
|
||||
if (candidate == null) {
|
||||
candidate = sanitizeUsername(user.getWorkcode());
|
||||
}
|
||||
if (candidate == null && user.getId() != null) {
|
||||
candidate = sanitizeUsername("IWORK" + user.getId());
|
||||
}
|
||||
return candidate;
|
||||
}
|
||||
|
||||
private String sanitizeUsername(String raw) {
|
||||
if (StrUtil.isBlank(raw)) {
|
||||
return null;
|
||||
}
|
||||
String normalized = raw.replaceAll("[^A-Za-z0-9]", "");
|
||||
if (StrUtil.isBlank(normalized)) {
|
||||
return null;
|
||||
}
|
||||
return normalized.length() > 30 ? normalized.substring(0, 30) : normalized;
|
||||
}
|
||||
|
||||
private Set<Long> singletonSet(Long value) {
|
||||
Set<Long> set = new HashSet<>(1);
|
||||
set.add(value);
|
||||
return set;
|
||||
}
|
||||
|
||||
private Integer defaultSort(Integer value) {
|
||||
return value == null ? DEFAULT_SORT : value;
|
||||
}
|
||||
|
||||
private Integer defaultSort(Integer value, Integer fallback) {
|
||||
return value == null ? (fallback == null ? DEFAULT_SORT : fallback) : value;
|
||||
}
|
||||
|
||||
private Integer toStatus(boolean disabled) {
|
||||
return disabled ? CommonStatusEnum.DISABLE.getStatus() : CommonStatusEnum.ENABLE.getStatus();
|
||||
}
|
||||
|
||||
private String limitLength(String value, int maxLength) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
String trimmed = value.trim();
|
||||
return trimmed.length() > maxLength ? trimmed.substring(0, maxLength) : trimmed;
|
||||
}
|
||||
|
||||
private String trimToNull(String value) {
|
||||
if (StrUtil.isBlank(value)) {
|
||||
return null;
|
||||
}
|
||||
String trimmed = value.trim();
|
||||
return trimmed.isEmpty() ? null : trimmed;
|
||||
}
|
||||
|
||||
private String buildUserRemark(IWorkHrUserPageRespVO.User user) {
|
||||
if (user.getId() == null) {
|
||||
return "iWork 同步";
|
||||
}
|
||||
return StrUtil.format("iWork 同步,源ID={}", user.getId());
|
||||
}
|
||||
|
||||
private String buildJobCode(Integer jobTitleId) {
|
||||
return JOB_CODE_PREFIX + jobTitleId;
|
||||
}
|
||||
|
||||
private String buildDeptCacheKey(String systemCode, String externalCode) {
|
||||
Long tenantId = TenantContextHolder.getTenantId();
|
||||
return (tenantId == null ? "_" : tenantId.toString()) + "::" + systemCode + "::" + externalCode;
|
||||
}
|
||||
|
||||
private String buildPostCacheKey(String code) {
|
||||
Long tenantId = TenantContextHolder.getTenantId();
|
||||
return (tenantId == null ? "_" : tenantId.toString()) + "::POST::" + code;
|
||||
}
|
||||
|
||||
private String describeAction(SyncAction action) {
|
||||
return switch (action) {
|
||||
case CREATED -> "已创建";
|
||||
case UPDATED -> "已更新";
|
||||
case SKIPPED -> "已跳过";
|
||||
};
|
||||
}
|
||||
|
||||
private record ParentHolder(Long parentId, boolean required) {
|
||||
}
|
||||
|
||||
private enum SyncAction {
|
||||
CREATED,
|
||||
UPDATED,
|
||||
SKIPPED
|
||||
}
|
||||
|
||||
private record DeptSyncOutcome(SyncAction action, boolean disabled, Long deptId) {
|
||||
}
|
||||
|
||||
private record JobSyncOutcome(SyncAction action, boolean disabled, Long postId) {
|
||||
}
|
||||
|
||||
private record UserSyncOutcome(SyncAction action, boolean disabled, Long userId) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,286 @@
|
||||
package com.zt.plat.module.system.service.integration.iwork.impl;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkDepartmentQueryReqVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkFullSyncReqVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkFullSyncRespVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkHrDepartmentPageRespVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkHrJobTitlePageRespVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkHrSubcompanyPageRespVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkHrUserPageRespVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkJobTitleQueryReqVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkSingleSyncReqVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkSingleSyncRespVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkSubcompanyQueryReqVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkSyncBatchStatVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkSyncEntityStatVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkUserQueryReqVO;
|
||||
import com.zt.plat.module.system.enums.integration.IWorkSyncEntityTypeEnum;
|
||||
import com.zt.plat.module.system.service.integration.iwork.IWorkOrgRestService;
|
||||
import com.zt.plat.module.system.service.integration.iwork.IWorkSyncProcessor;
|
||||
import com.zt.plat.module.system.service.integration.iwork.IWorkSyncService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* iWork 同步服务实现
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class IWorkSyncServiceImpl implements IWorkSyncService {
|
||||
|
||||
private final IWorkOrgRestService orgRestService;
|
||||
private final IWorkSyncProcessor syncProcessor;
|
||||
|
||||
@Override
|
||||
public IWorkFullSyncRespVO fullSync(IWorkFullSyncReqVO reqVO) {
|
||||
IWorkFullSyncRespVO respVO = new IWorkFullSyncRespVO();
|
||||
respVO.setPageSize(reqVO.getPageSize());
|
||||
List<IWorkSyncBatchStatVO> batchStats = new ArrayList<>();
|
||||
respVO.setBatches(batchStats);
|
||||
|
||||
Set<IWorkSyncEntityTypeEnum> scopes = reqVO.resolveScopes();
|
||||
int processedPages = 0;
|
||||
IWorkSyncProcessor.SyncOptions options = buildFullSyncOptions(reqVO);
|
||||
if (scopes.contains(IWorkSyncEntityTypeEnum.SUBCOMPANY)) {
|
||||
processedPages += executeSubcompanyFullSync(reqVO, options, respVO.getSubcompanyStat(), batchStats);
|
||||
}
|
||||
if (scopes.contains(IWorkSyncEntityTypeEnum.DEPARTMENT)) {
|
||||
processedPages += executeDepartmentFullSync(reqVO, options, respVO.getDepartmentStat(), batchStats);
|
||||
}
|
||||
if (scopes.contains(IWorkSyncEntityTypeEnum.JOB_TITLE)) {
|
||||
processedPages += executeJobTitleFullSync(reqVO, options, respVO.getJobTitleStat(), batchStats);
|
||||
}
|
||||
if (scopes.contains(IWorkSyncEntityTypeEnum.USER)) {
|
||||
processedPages += executeUserFullSync(reqVO, options, respVO.getUserStat(), batchStats);
|
||||
}
|
||||
respVO.setProcessedPages(processedPages);
|
||||
return respVO;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IWorkSingleSyncRespVO syncSingle(IWorkSingleSyncReqVO reqVO) {
|
||||
IWorkSingleSyncRespVO respVO = new IWorkSingleSyncRespVO();
|
||||
respVO.setEntityType(reqVO.getEntityType());
|
||||
respVO.setEntityId(reqVO.getEntityId());
|
||||
switch (reqVO.getEntityType()) {
|
||||
case SUBCOMPANY -> processSingleSubcompany(reqVO, respVO);
|
||||
case DEPARTMENT -> processSingleDepartment(reqVO, respVO);
|
||||
case JOB_TITLE -> processSingleJob(reqVO, respVO);
|
||||
case USER -> processSingleUser(reqVO, respVO);
|
||||
default -> throw new IllegalArgumentException("不支持的实体类型: " + reqVO.getEntityType());
|
||||
}
|
||||
return respVO;
|
||||
}
|
||||
|
||||
private int executeSubcompanyFullSync(IWorkFullSyncReqVO reqVO,
|
||||
IWorkSyncProcessor.SyncOptions options,
|
||||
IWorkSyncEntityStatVO stat,
|
||||
List<IWorkSyncBatchStatVO> batches) {
|
||||
return executePaged(reqVO, IWorkSyncEntityTypeEnum.SUBCOMPANY, batches, (page, pageSize) -> {
|
||||
IWorkSubcompanyQueryReqVO query = new IWorkSubcompanyQueryReqVO();
|
||||
query.setCurpage(page);
|
||||
query.setPagesize(pageSize);
|
||||
IWorkHrSubcompanyPageRespVO pageResp = orgRestService.listSubcompanies(query);
|
||||
List<IWorkHrSubcompanyPageRespVO.Subcompany> dataList = CollUtil.emptyIfNull(pageResp.getDataList());
|
||||
IWorkSyncProcessor.BatchResult result = syncProcessor.syncSubcompanies(dataList, options);
|
||||
updateStat(stat, result, dataList.size());
|
||||
return new BatchExecution(result, dataList.size());
|
||||
});
|
||||
}
|
||||
|
||||
private int executeDepartmentFullSync(IWorkFullSyncReqVO reqVO,
|
||||
IWorkSyncProcessor.SyncOptions options,
|
||||
IWorkSyncEntityStatVO stat,
|
||||
List<IWorkSyncBatchStatVO> batches) {
|
||||
return executePaged(reqVO, IWorkSyncEntityTypeEnum.DEPARTMENT, batches, (page, pageSize) -> {
|
||||
IWorkDepartmentQueryReqVO query = new IWorkDepartmentQueryReqVO();
|
||||
query.setCurpage(page);
|
||||
query.setPagesize(pageSize);
|
||||
IWorkHrDepartmentPageRespVO pageResp = orgRestService.listDepartments(query);
|
||||
List<IWorkHrDepartmentPageRespVO.Department> dataList = CollUtil.emptyIfNull(pageResp.getDataList());
|
||||
IWorkSyncProcessor.BatchResult result = syncProcessor.syncDepartments(dataList, options);
|
||||
updateStat(stat, result, dataList.size());
|
||||
return new BatchExecution(result, dataList.size());
|
||||
});
|
||||
}
|
||||
|
||||
private int executeJobTitleFullSync(IWorkFullSyncReqVO reqVO,
|
||||
IWorkSyncProcessor.SyncOptions options,
|
||||
IWorkSyncEntityStatVO stat,
|
||||
List<IWorkSyncBatchStatVO> batches) {
|
||||
return executePaged(reqVO, IWorkSyncEntityTypeEnum.JOB_TITLE, batches, (page, pageSize) -> {
|
||||
IWorkJobTitleQueryReqVO query = new IWorkJobTitleQueryReqVO();
|
||||
query.setCurpage(page);
|
||||
query.setPagesize(pageSize);
|
||||
IWorkHrJobTitlePageRespVO pageResp = orgRestService.listJobTitles(query);
|
||||
List<IWorkHrJobTitlePageRespVO.JobTitle> dataList = CollUtil.emptyIfNull(pageResp.getDataList());
|
||||
IWorkSyncProcessor.BatchResult result = syncProcessor.syncJobTitles(dataList, options);
|
||||
updateStat(stat, result, dataList.size());
|
||||
return new BatchExecution(result, dataList.size());
|
||||
});
|
||||
}
|
||||
|
||||
private int executeUserFullSync(IWorkFullSyncReqVO reqVO,
|
||||
IWorkSyncProcessor.SyncOptions options,
|
||||
IWorkSyncEntityStatVO stat,
|
||||
List<IWorkSyncBatchStatVO> batches) {
|
||||
return executePaged(reqVO, IWorkSyncEntityTypeEnum.USER, batches, (page, pageSize) -> {
|
||||
IWorkUserQueryReqVO query = new IWorkUserQueryReqVO();
|
||||
query.setCurpage(page);
|
||||
query.setPagesize(pageSize);
|
||||
IWorkHrUserPageRespVO pageResp = orgRestService.listUsers(query);
|
||||
List<IWorkHrUserPageRespVO.User> dataList = CollUtil.emptyIfNull(pageResp.getDataList());
|
||||
IWorkSyncProcessor.BatchResult result = syncProcessor.syncUsers(dataList, options);
|
||||
updateStat(stat, result, dataList.size());
|
||||
return new BatchExecution(result, dataList.size());
|
||||
});
|
||||
}
|
||||
|
||||
private void processSingleSubcompany(IWorkSingleSyncReqVO reqVO, IWorkSingleSyncRespVO respVO) {
|
||||
IWorkHrSubcompanyPageRespVO.Subcompany data = fetchSingleSubcompany(reqVO.getEntityId());
|
||||
if (data == null) {
|
||||
markNotFound(respVO, "分部");
|
||||
return;
|
||||
}
|
||||
IWorkSyncProcessor.BatchResult result = syncProcessor.syncSubcompany(data, buildSingleOptions(reqVO));
|
||||
populateSingleResult(respVO, result);
|
||||
}
|
||||
|
||||
private void processSingleDepartment(IWorkSingleSyncReqVO reqVO, IWorkSingleSyncRespVO respVO) {
|
||||
IWorkHrDepartmentPageRespVO.Department data = fetchSingleDepartment(reqVO.getEntityId());
|
||||
if (data == null) {
|
||||
markNotFound(respVO, "部门");
|
||||
return;
|
||||
}
|
||||
IWorkSyncProcessor.BatchResult result = syncProcessor.syncDepartment(data, buildSingleOptions(reqVO));
|
||||
populateSingleResult(respVO, result);
|
||||
}
|
||||
|
||||
private void processSingleJob(IWorkSingleSyncReqVO reqVO, IWorkSingleSyncRespVO respVO) {
|
||||
IWorkHrJobTitlePageRespVO.JobTitle data = fetchSingleJob(reqVO.getEntityId());
|
||||
if (data == null) {
|
||||
markNotFound(respVO, "岗位");
|
||||
return;
|
||||
}
|
||||
IWorkSyncProcessor.BatchResult result = syncProcessor.syncJobTitle(data, buildSingleOptions(reqVO));
|
||||
populateSingleResult(respVO, result);
|
||||
}
|
||||
|
||||
private void processSingleUser(IWorkSingleSyncReqVO reqVO, IWorkSingleSyncRespVO respVO) {
|
||||
IWorkHrUserPageRespVO.User data = fetchSingleUser(reqVO.getEntityId());
|
||||
if (data == null) {
|
||||
markNotFound(respVO, "人员");
|
||||
return;
|
||||
}
|
||||
IWorkSyncProcessor.BatchResult result = syncProcessor.syncUser(data, buildSingleOptions(reqVO));
|
||||
populateSingleResult(respVO, result);
|
||||
}
|
||||
|
||||
private int executePaged(IWorkFullSyncReqVO reqVO,
|
||||
IWorkSyncEntityTypeEnum type,
|
||||
List<IWorkSyncBatchStatVO> batches,
|
||||
PageExecutor executor) {
|
||||
int startPage = reqVO.getStartPage() == null ? 1 : reqVO.getStartPage();
|
||||
int pageSize = reqVO.getPageSize() == null ? 100 : reqVO.getPageSize();
|
||||
int pagesLimit = reqVO.getMaxPages() == null ? Integer.MAX_VALUE : reqVO.getMaxPages();
|
||||
int processedPages = 0;
|
||||
for (int page = startPage; processedPages < pagesLimit; page++) {
|
||||
BatchExecution execution = executor.execute(page, pageSize);
|
||||
if (execution == null || execution.totalPulled == 0) {
|
||||
break;
|
||||
}
|
||||
processedPages++;
|
||||
IWorkSyncBatchStatVO batchStat = new IWorkSyncBatchStatVO();
|
||||
batchStat.setEntityType(type);
|
||||
batchStat.setPageNumber(page);
|
||||
batchStat.setPulled(execution.totalPulled);
|
||||
batchStat.setCreated(execution.result.getCreated());
|
||||
batchStat.setSkippedExisting(execution.result.getSkipped());
|
||||
batchStat.setDisabled(execution.result.getDisabled());
|
||||
batchStat.setFailed(execution.result.getFailed());
|
||||
batches.add(batchStat);
|
||||
}
|
||||
return processedPages;
|
||||
}
|
||||
|
||||
private void updateStat(IWorkSyncEntityStatVO stat, IWorkSyncProcessor.BatchResult result, int pulled) {
|
||||
stat.incrementPulled(pulled);
|
||||
stat.incrementCreated(result.getCreated());
|
||||
stat.incrementSkipped(result.getSkipped());
|
||||
stat.incrementDisabled(result.getDisabled());
|
||||
stat.incrementFailed(result.getFailed());
|
||||
}
|
||||
|
||||
private void populateSingleResult(IWorkSingleSyncRespVO respVO, IWorkSyncProcessor.BatchResult result) {
|
||||
respVO.setCreated(result.getCreated() > 0);
|
||||
respVO.setUpdated(result.getUpdated() > 0);
|
||||
respVO.setMessage(result.getMessage());
|
||||
}
|
||||
|
||||
private void markNotFound(IWorkSingleSyncRespVO respVO, String entityName) {
|
||||
respVO.setCreated(false);
|
||||
respVO.setUpdated(false);
|
||||
respVO.setMessage(StrUtil.format("未在 iWork 中找到{}(ID={})", entityName, respVO.getEntityId()));
|
||||
}
|
||||
|
||||
private IWorkSyncProcessor.SyncOptions buildFullSyncOptions(IWorkFullSyncReqVO reqVO) {
|
||||
return IWorkSyncProcessor.SyncOptions.full(Boolean.TRUE.equals(reqVO.getIncludeCanceled()));
|
||||
}
|
||||
|
||||
private IWorkSyncProcessor.SyncOptions buildSingleOptions(IWorkSingleSyncReqVO reqVO) {
|
||||
return IWorkSyncProcessor.SyncOptions.single(Boolean.TRUE.equals(reqVO.getCreateIfMissing()));
|
||||
}
|
||||
|
||||
private IWorkHrSubcompanyPageRespVO.Subcompany fetchSingleSubcompany(Long entityId) {
|
||||
IWorkSubcompanyQueryReqVO query = new IWorkSubcompanyQueryReqVO();
|
||||
query.setCurpage(1);
|
||||
query.setPagesize(1);
|
||||
query.setParams(Collections.singletonMap("subcompanyid", entityId));
|
||||
IWorkHrSubcompanyPageRespVO pageResp = orgRestService.listSubcompanies(query);
|
||||
return CollUtil.getFirst(pageResp.getDataList());
|
||||
}
|
||||
|
||||
private IWorkHrDepartmentPageRespVO.Department fetchSingleDepartment(Long entityId) {
|
||||
IWorkDepartmentQueryReqVO query = new IWorkDepartmentQueryReqVO();
|
||||
query.setCurpage(1);
|
||||
query.setPagesize(1);
|
||||
query.setParams(Collections.singletonMap("departmentid", entityId));
|
||||
IWorkHrDepartmentPageRespVO pageResp = orgRestService.listDepartments(query);
|
||||
return CollUtil.getFirst(pageResp.getDataList());
|
||||
}
|
||||
|
||||
private IWorkHrJobTitlePageRespVO.JobTitle fetchSingleJob(Long entityId) {
|
||||
IWorkJobTitleQueryReqVO query = new IWorkJobTitleQueryReqVO();
|
||||
query.setCurpage(1);
|
||||
query.setPagesize(1);
|
||||
query.setParams(Collections.singletonMap("jobtitleid", entityId));
|
||||
IWorkHrJobTitlePageRespVO pageResp = orgRestService.listJobTitles(query);
|
||||
return CollUtil.getFirst(pageResp.getDataList());
|
||||
}
|
||||
|
||||
private IWorkHrUserPageRespVO.User fetchSingleUser(Long entityId) {
|
||||
IWorkUserQueryReqVO query = new IWorkUserQueryReqVO();
|
||||
query.setCurpage(1);
|
||||
query.setPagesize(1);
|
||||
query.setParams(Collections.singletonMap("id", entityId));
|
||||
IWorkHrUserPageRespVO pageResp = orgRestService.listUsers(query);
|
||||
return CollUtil.getFirst(pageResp.getDataList());
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
private interface PageExecutor {
|
||||
BatchExecution execute(int pageNumber, int pageSize);
|
||||
}
|
||||
|
||||
private record BatchExecution(IWorkSyncProcessor.BatchResult result, int totalPulled) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.zt.plat.module.system.service.integration.iwork.jackson;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.core.JsonToken;
|
||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* Jackson 反序列化器,允许将诸如 "0.0"、"5" 等字符串形式的数字解析为整数,
|
||||
* 用于兼容 iWork 返回的非标准整型字段。
|
||||
*/
|
||||
public class LenientIntegerDeserializer extends JsonDeserializer<Integer> {
|
||||
|
||||
@Override
|
||||
public Integer deserialize(JsonParser parser, DeserializationContext ctxt) throws IOException {
|
||||
JsonToken token = parser.currentToken();
|
||||
if (token == null) {
|
||||
token = parser.nextToken();
|
||||
}
|
||||
if (token == null) {
|
||||
return null;
|
||||
}
|
||||
if (token.isNumeric()) {
|
||||
return parser.getNumberValue().intValue();
|
||||
}
|
||||
if (token == JsonToken.VALUE_STRING) {
|
||||
String text = parser.getText();
|
||||
if (text == null) {
|
||||
return null;
|
||||
}
|
||||
String trimmed = text.trim();
|
||||
if (trimmed.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
if (trimmed.contains(".")) {
|
||||
BigDecimal decimal = new BigDecimal(trimmed);
|
||||
return decimal.intValue();
|
||||
}
|
||||
return Integer.parseInt(trimmed);
|
||||
} catch (NumberFormatException ex) {
|
||||
return (Integer) ctxt.handleWeirdStringValue(Integer.class, trimmed,
|
||||
"无法将文本转换为整数");
|
||||
}
|
||||
}
|
||||
if (token == JsonToken.VALUE_NULL) {
|
||||
return null;
|
||||
}
|
||||
return (Integer) ctxt.handleUnexpectedToken(Integer.class, parser);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
package com.zt.plat.module.system.service.integration.iwork.impl;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkOrgQueryReqVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkOrgRespVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkHrSubcompanyPageRespVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkHrSyncRespVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkOrgSyncReqVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkSubcompanyQueryReqVO;
|
||||
import com.zt.plat.module.system.framework.integration.iwork.config.IWorkProperties;
|
||||
import com.zt.plat.module.system.service.integration.iwork.IWorkOrgRestService;
|
||||
import okhttp3.mockwebserver.MockResponse;
|
||||
@@ -20,7 +22,6 @@ import java.time.Clock;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -32,6 +33,7 @@ class IWorkOrgRestServiceImplTest {
|
||||
private IWorkOrgRestService service;
|
||||
private IWorkProperties properties;
|
||||
private Clock fixedClock;
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws Exception {
|
||||
@@ -40,7 +42,8 @@ class IWorkOrgRestServiceImplTest {
|
||||
|
||||
properties = buildProperties();
|
||||
fixedClock = Clock.fixed(Instant.ofEpochMilli(1_672_531_200_000L), ZoneOffset.UTC);
|
||||
service = new IWorkOrgRestServiceImpl(properties, new ObjectMapper(), fixedClock);
|
||||
objectMapper = new ObjectMapper();
|
||||
service = new IWorkOrgRestServiceImpl(properties, objectMapper, fixedClock);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
@@ -50,41 +53,46 @@ class IWorkOrgRestServiceImplTest {
|
||||
|
||||
@Test
|
||||
void shouldListSubcompanies() throws Exception {
|
||||
mockWebServer.enqueue(jsonResponse("{\"code\":\"1\",\"data\":{\"page\":1}}"));
|
||||
mockWebServer.enqueue(jsonResponse("{\"code\":\"1\",\"data\":{\"totalSize\":1,\"totalPage\":1,\"pageSize\":10,\"pageNumber\":1,\"dataList\":[{\"subcompanyid1\":4,\"subcompanyname\":\"总部\"}]}}"));
|
||||
|
||||
IWorkOrgQueryReqVO reqVO = new IWorkOrgQueryReqVO();
|
||||
IWorkSubcompanyQueryReqVO reqVO = new IWorkSubcompanyQueryReqVO();
|
||||
reqVO.setParams(Map.of("curpage", 1));
|
||||
IWorkOrgRespVO respVO = service.listSubcompanies(reqVO);
|
||||
IWorkHrSubcompanyPageRespVO respVO = service.listSubcompanies(reqVO);
|
||||
|
||||
assertThat(respVO.isSuccess()).isTrue();
|
||||
assertThat(respVO.getPayload()).containsEntry("page", 1);
|
||||
assertThat(respVO.getTotalSize()).isEqualTo(1);
|
||||
assertThat(respVO.getDataList()).hasSize(1);
|
||||
assertThat(respVO.getDataList().get(0).getSubcompanyname()).isEqualTo("总部");
|
||||
|
||||
RecordedRequest request = mockWebServer.takeRequest();
|
||||
assertThat(request.getPath()).isEqualTo(properties.getOrg().getPaths().getSubcompanyPage());
|
||||
String decoded = URLDecoder.decode(request.getBody().readUtf8(), StandardCharsets.UTF_8);
|
||||
assertThat(decoded).contains("params={\"curpage\":1}");
|
||||
String tokenJson = extractField(decoded, "token");
|
||||
assertThat(tokenJson).isNotBlank();
|
||||
assertThat(tokenJson).contains("\"ts\":\"1672531200000\"");
|
||||
String expectedKey = DigestUtils.md5DigestAsHex("test-seed1672531200000".getBytes(StandardCharsets.UTF_8)).toUpperCase();
|
||||
assertThat(tokenJson).contains("\"key\":\"" + expectedKey + "\"");
|
||||
JsonNode bodyNode = objectMapper.readTree(decoded);
|
||||
assertThat(bodyNode.path("params").path("curpage").asInt()).isEqualTo(1);
|
||||
JsonNode tokenNode = bodyNode.path("token");
|
||||
assertThat(tokenNode.path("ts").asText()).isEqualTo("1672531200000");
|
||||
String expectedKey = DigestUtils.md5DigestAsHex("test-seed1672531200000".getBytes(StandardCharsets.UTF_8)).toUpperCase();
|
||||
assertThat(tokenNode.path("key").asText()).isEqualTo(expectedKey);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSyncDepartments() throws Exception {
|
||||
mockWebServer.enqueue(jsonResponse("{\"code\":\"1\",\"result\":{}}"));
|
||||
mockWebServer.enqueue(jsonResponse("{\"code\":\"1\",\"result\":[{\"@action\":\"add\",\"code\":\"demo\",\"result\":\"success\"}]}"));
|
||||
|
||||
IWorkOrgSyncReqVO reqVO = new IWorkOrgSyncReqVO();
|
||||
reqVO.setData(List.of(Map.of("@action", "add", "code", "demo")));
|
||||
IWorkOrgRespVO respVO = service.syncDepartments(reqVO);
|
||||
IWorkHrSyncRespVO respVO = service.syncDepartments(reqVO);
|
||||
|
||||
assertThat(respVO.isSuccess()).isTrue();
|
||||
assertThat(respVO.getPayload()).containsKey("result");
|
||||
assertThat(respVO.getResult()).hasSize(1);
|
||||
assertThat(respVO.getResult().get(0).getCode()).isEqualTo("demo");
|
||||
|
||||
RecordedRequest request = mockWebServer.takeRequest();
|
||||
assertThat(request.getPath()).isEqualTo(properties.getOrg().getPaths().getSyncDepartment());
|
||||
String decoded = URLDecoder.decode(request.getBody().readUtf8(), StandardCharsets.UTF_8);
|
||||
assertThat(decoded).contains("data=[{\"@action\":\"add\",\"code\":\"demo\"}]");
|
||||
JsonNode bodyNode = objectMapper.readTree(decoded);
|
||||
assertThat(bodyNode.path("data").isArray()).isTrue();
|
||||
assertThat(bodyNode.path("data").get(0).path("code").asText()).isEqualTo("demo");
|
||||
}
|
||||
|
||||
private MockResponse jsonResponse(String body) {
|
||||
@@ -93,14 +101,6 @@ class IWorkOrgRestServiceImplTest {
|
||||
.setBody(body);
|
||||
}
|
||||
|
||||
private String extractField(String decoded, String key) {
|
||||
return Arrays.stream(decoded.split("&"))
|
||||
.filter(part -> part.startsWith(key + "="))
|
||||
.map(part -> part.substring(key.length() + 1))
|
||||
.findFirst()
|
||||
.orElse("");
|
||||
}
|
||||
|
||||
private IWorkProperties buildProperties() {
|
||||
IWorkProperties properties = new IWorkProperties();
|
||||
properties.setBaseUrl(mockWebServer.url("/").toString());
|
||||
|
||||
Reference in New Issue
Block a user