Merge remote-tracking branch 'origin/dev' into dev

This commit is contained in:
hewencai
2025-11-25 19:09:13 +08:00
27 changed files with 2545 additions and 164 deletions

View File

@@ -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));
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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");
}
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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);
}

View File

@@ -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());
}

View File

@@ -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) {
}
}

View File

@@ -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) {
}
}

View File

@@ -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);
}
}