Compare commits
59 Commits
70868a77c0
...
2dac28d3b3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2dac28d3b3 | ||
|
|
dbb1d1905e | ||
|
|
08232eb3cb | ||
|
|
5de2801fc9 | ||
|
|
e9994a24c2 | ||
|
|
a10732119b | ||
|
|
e7efddf976 | ||
|
|
13ec805c20 | ||
|
|
61e61d08b6 | ||
|
|
5698c34185 | ||
|
|
96058e29c2 | ||
|
|
27d22de4e0 | ||
|
|
f1242e74fc | ||
|
|
0c0d82f465 | ||
|
|
12ba2cf756 | ||
|
|
b1bd193f50 | ||
|
|
0b4b87845c | ||
|
|
a2f2325119 | ||
|
|
4c79ac8a6d | ||
|
|
0c0cb27c15 | ||
|
|
a263632e49 | ||
|
|
2e2b7ac6fa | ||
|
|
9730573546 | ||
|
|
299132943c | ||
|
|
76ba994b50 | ||
|
|
dd284728b4 | ||
|
|
685ed6b504 | ||
|
|
f754b1c694 | ||
|
|
dc1db47d07 | ||
|
|
dd38e65972 | ||
|
|
02e0c81446 | ||
|
|
6c8c479984 | ||
|
|
829229a355 | ||
|
|
067f7226f4 | ||
|
|
b35df8493c | ||
|
|
518aa2a773 | ||
|
|
4003388740 | ||
|
|
f16509c107 | ||
|
|
565a625df7 | ||
|
|
adcea87bbf | ||
|
|
a79806690d | ||
|
|
8689c5e844 | ||
|
|
5be1b75be8 | ||
|
|
06d9ae2688 | ||
|
|
547b1d9afb | ||
|
|
2f9c28f166 | ||
|
|
9a0e60ad84 | ||
|
|
c24ae5bad8 | ||
|
|
64eb031486 | ||
|
|
30b22698e8 | ||
|
|
95fab27556 | ||
|
|
2efb815d59 | ||
|
|
3d5f07b7a5 | ||
|
|
77b4e62def | ||
|
|
e2dbaf12a4 | ||
|
|
f8d95218f5 | ||
|
|
e0d5c0221e | ||
|
|
59afa893b0 | ||
|
|
d4d80ce86a |
21
pom.xml
21
pom.xml
@@ -237,8 +237,8 @@
|
||||
<config.server-addr>172.16.46.63:30848</config.server-addr>
|
||||
<config.namespace>dev</config.namespace>
|
||||
<config.group>DEFAULT_GROUP</config.group>
|
||||
<config.username/>
|
||||
<config.password/>
|
||||
<config.username>nacos</config.username>
|
||||
<config.password>P@ssword25</config.password>
|
||||
<config.version>1.0.0</config.version>
|
||||
</properties>
|
||||
</profile>
|
||||
@@ -250,8 +250,8 @@
|
||||
<config.server-addr>172.16.46.63:30848</config.server-addr>
|
||||
<config.namespace>prod</config.namespace>
|
||||
<config.group>DEFAULT_GROUP</config.group>
|
||||
<config.username/>
|
||||
<config.password/>
|
||||
<config.username>nacos</config.username>
|
||||
<config.password>P@ssword25</config.password>
|
||||
<config.version>1.0.0</config.version>
|
||||
</properties>
|
||||
</profile>
|
||||
@@ -263,8 +263,8 @@
|
||||
<config.server-addr>172.16.46.63:30848</config.server-addr>
|
||||
<config.namespace>local</config.namespace>
|
||||
<config.group>DEFAULT_GROUP</config.group>
|
||||
<config.username/>
|
||||
<config.password/>
|
||||
<config.username>nacos</config.username>
|
||||
<config.password>P@ssword25</config.password>
|
||||
<config.version>1.0.0</config.version>
|
||||
</properties>
|
||||
</profile>
|
||||
@@ -277,7 +277,14 @@
|
||||
<profile>
|
||||
<id>qsj</id>
|
||||
<properties>
|
||||
<config.namespace>qsj</config.namespace>
|
||||
<env.name>dev</env.name>
|
||||
<!--Nacos 配置-->
|
||||
<config.server-addr>172.16.46.63:30848</config.server-addr>
|
||||
<config.namespace>qsj</config.namespace>
|
||||
<config.group>DEFAULT_GROUP</config.group>
|
||||
<config.username>nacos</config.username>
|
||||
<config.password>P@ssword25</config.password>
|
||||
<config.version>1.0.0</config.version>
|
||||
</properties>
|
||||
</profile>
|
||||
</profiles>
|
||||
|
||||
@@ -87,6 +87,7 @@
|
||||
<netty.version>4.1.116.Final</netty.version>
|
||||
<mqtt.version>1.2.5</mqtt.version>
|
||||
<pf4j-spring.version>0.9.0</pf4j-spring.version>
|
||||
<okhttp3.version>4.12.0</okhttp3.version>
|
||||
<!-- 规则引擎 -->
|
||||
<liteflow.version>2.15.1</liteflow.version>
|
||||
<vertx.version>4.5.13</vertx.version>
|
||||
|
||||
@@ -4,8 +4,8 @@ spring:
|
||||
cloud:
|
||||
nacos:
|
||||
server-addr: 172.16.46.63:30848 # Nacos 服务器地址
|
||||
username: # Nacos 账号
|
||||
password: # Nacos 密码
|
||||
username: ${config.username} # Nacos 账号
|
||||
password: ${config.password} # Nacos 密码
|
||||
discovery: # 【配置中心】配置项
|
||||
namespace: ${config.namespace} # 命名空间。这里使用 maven Profile 资源过滤进行动态替换
|
||||
group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
<!-- 启动服务时,是否清理历史日志,一般不建议清理 -->
|
||||
<cleanHistoryOnStart>${LOGBACK_ROLLINGPOLICY_CLEAN_HISTORY_ON_START:-false}</cleanHistoryOnStart>
|
||||
<!-- 日志文件,到达多少容量,进行滚动 -->
|
||||
<maxFileSize>${LOGBACK_ROLLINGPOLICY_MAX_FILE_SIZE:-10MB}</maxFileSize>
|
||||
<maxFileSize>${LOGBACK_ROLLINGPOLICY_MAX_FILE_SIZE:-50MB}</maxFileSize>
|
||||
<!-- 日志文件的总大小,0 表示不限制 -->
|
||||
<totalSizeCap>${LOGBACK_ROLLINGPOLICY_TOTAL_SIZE_CAP:-0}</totalSizeCap>
|
||||
<!-- 日志文件的保留天数 -->
|
||||
@@ -56,14 +56,16 @@
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!--logback的日志级别 FATAL > ERROR > WARN > INFO > DEBUG-->
|
||||
<!-- 本地环境 -->
|
||||
<springProfile name="local">
|
||||
<root level="INFO">
|
||||
<springProfile name="local,dev">
|
||||
<root level="WARN">
|
||||
<appender-ref ref="STDOUT"/>
|
||||
<appender-ref ref="GRPC"/> <!-- 本地环境下,如果不想接入 SkyWalking 日志服务,可以注释掉本行 -->
|
||||
<appender-ref ref="ASYNC"/> <!-- 本地环境下,如果不想打印日志,可以注释掉本行 -->
|
||||
</root>
|
||||
</springProfile>
|
||||
|
||||
<!-- 其它环境 -->
|
||||
<springProfile name="dev,test,stage,prod,default">
|
||||
<root level="INFO">
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
<!-- 启动服务时,是否清理历史日志,一般不建议清理 -->
|
||||
<cleanHistoryOnStart>${LOGBACK_ROLLINGPOLICY_CLEAN_HISTORY_ON_START:-false}</cleanHistoryOnStart>
|
||||
<!-- 日志文件,到达多少容量,进行滚动 -->
|
||||
<maxFileSize>${LOGBACK_ROLLINGPOLICY_MAX_FILE_SIZE:-10MB}</maxFileSize>
|
||||
<maxFileSize>${LOGBACK_ROLLINGPOLICY_MAX_FILE_SIZE:-50MB}</maxFileSize>
|
||||
<!-- 日志文件的总大小,0 表示不限制 -->
|
||||
<totalSizeCap>${LOGBACK_ROLLINGPOLICY_TOTAL_SIZE_CAP:-0}</totalSizeCap>
|
||||
<!-- 日志文件的保留天数 -->
|
||||
@@ -56,14 +56,21 @@
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!--logback的日志级别 FATAL > ERROR > WARN > INFO > DEBUG-->
|
||||
<!-- 本地环境 -->
|
||||
<springProfile name="local">
|
||||
<root level="INFO">
|
||||
<springProfile name="local,dev">
|
||||
<root level="WARN">
|
||||
<appender-ref ref="STDOUT"/>
|
||||
<appender-ref ref="GRPC"/> <!-- 本地环境下,如果不想接入 SkyWalking 日志服务,可以注释掉本行 -->
|
||||
<appender-ref ref="ASYNC"/> <!-- 本地环境下,如果不想打印日志,可以注释掉本行 -->
|
||||
</root>
|
||||
|
||||
<!--针对不同的业务路径,配置dao层的sql打印日志级别为DEBUG-->
|
||||
<logger name="com.zt.plat.module.infra.dal.mysql" level="DEBUG" additivity="false">
|
||||
<appender-ref ref="STDOUT"/>
|
||||
</logger>
|
||||
</springProfile>
|
||||
|
||||
<!-- 其它环境 -->
|
||||
<springProfile name="dev,test,stage,prod,default">
|
||||
<root level="INFO">
|
||||
|
||||
@@ -6,6 +6,7 @@ import cn.hutool.core.util.ObjUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.zt.plat.framework.common.pojo.PageResult;
|
||||
import com.zt.plat.module.infra.api.file.FileApi;
|
||||
import com.zt.plat.module.infra.api.file.dto.FileCreateReqDTO;
|
||||
import com.zt.plat.module.mp.controller.admin.material.vo.MpMaterialPageReqVO;
|
||||
import com.zt.plat.module.mp.controller.admin.material.vo.MpMaterialUploadNewsImageReqVO;
|
||||
import com.zt.plat.module.mp.controller.admin.material.vo.MpMaterialUploadPermanentReqVO;
|
||||
@@ -218,7 +219,8 @@ public class MpMaterialServiceImpl implements MpMaterialService {
|
||||
|
||||
private String uploadFile(String mediaId, File file) {
|
||||
String path = mediaId + "." + FileTypeUtil.getType(file);
|
||||
return fileApi.createFile(FileUtil.readBytes(file), path);
|
||||
FileCreateReqDTO createReqDTO = new FileCreateReqDTO().setName(file.getName()).setDirectory(path).setType(FileTypeUtil.getType(file)).setContent(FileUtil.readBytes(file));
|
||||
return fileApi.createFile(createReqDTO).getData();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
<!-- 启动服务时,是否清理历史日志,一般不建议清理 -->
|
||||
<cleanHistoryOnStart>${LOGBACK_ROLLINGPOLICY_CLEAN_HISTORY_ON_START:-false}</cleanHistoryOnStart>
|
||||
<!-- 日志文件,到达多少容量,进行滚动 -->
|
||||
<maxFileSize>${LOGBACK_ROLLINGPOLICY_MAX_FILE_SIZE:-10MB}</maxFileSize>
|
||||
<maxFileSize>${LOGBACK_ROLLINGPOLICY_MAX_FILE_SIZE:-50MB}</maxFileSize>
|
||||
<!-- 日志文件的总大小,0 表示不限制 -->
|
||||
<totalSizeCap>${LOGBACK_ROLLINGPOLICY_TOTAL_SIZE_CAP:-0}</totalSizeCap>
|
||||
<!-- 日志文件的保留天数 -->
|
||||
@@ -56,14 +56,21 @@
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!--logback的日志级别 FATAL > ERROR > WARN > INFO > DEBUG-->
|
||||
<!-- 本地环境 -->
|
||||
<springProfile name="local">
|
||||
<root level="INFO">
|
||||
<springProfile name="local,dev">
|
||||
<root level="WARN">
|
||||
<appender-ref ref="STDOUT"/>
|
||||
<appender-ref ref="GRPC"/> <!-- 本地环境下,如果不想接入 SkyWalking 日志服务,可以注释掉本行 -->
|
||||
<appender-ref ref="ASYNC"/> <!-- 本地环境下,如果不想打印日志,可以注释掉本行 -->
|
||||
</root>
|
||||
|
||||
<!--针对不同的业务路径,配置dao层的sql打印日志级别为DEBUG-->
|
||||
<logger name="com.zt.plat.module.mp.dal.mysql" level="DEBUG" additivity="false">
|
||||
<appender-ref ref="STDOUT"/>
|
||||
</logger>
|
||||
</springProfile>
|
||||
|
||||
<!-- 其它环境 -->
|
||||
<springProfile name="dev,test,stage,prod,default">
|
||||
<root level="INFO">
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
<!-- 启动服务时,是否清理历史日志,一般不建议清理 -->
|
||||
<cleanHistoryOnStart>${LOGBACK_ROLLINGPOLICY_CLEAN_HISTORY_ON_START:-false}</cleanHistoryOnStart>
|
||||
<!-- 日志文件,到达多少容量,进行滚动 -->
|
||||
<maxFileSize>${LOGBACK_ROLLINGPOLICY_MAX_FILE_SIZE:-10MB}</maxFileSize>
|
||||
<maxFileSize>${LOGBACK_ROLLINGPOLICY_MAX_FILE_SIZE:-50MB}</maxFileSize>
|
||||
<!-- 日志文件的总大小,0 表示不限制 -->
|
||||
<totalSizeCap>${LOGBACK_ROLLINGPOLICY_TOTAL_SIZE_CAP:-0}</totalSizeCap>
|
||||
<!-- 日志文件的保留天数 -->
|
||||
@@ -56,14 +56,21 @@
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!--logback的日志级别 FATAL > ERROR > WARN > INFO > DEBUG-->
|
||||
<!-- 本地环境 -->
|
||||
<springProfile name="local">
|
||||
<root level="INFO">
|
||||
<springProfile name="local,dev">
|
||||
<root level="WARN">
|
||||
<appender-ref ref="STDOUT"/>
|
||||
<appender-ref ref="GRPC"/> <!-- 本地环境下,如果不想接入 SkyWalking 日志服务,可以注释掉本行 -->
|
||||
<appender-ref ref="ASYNC"/> <!-- 本地环境下,如果不想打印日志,可以注释掉本行 -->
|
||||
</root>
|
||||
|
||||
<!--针对不同的业务路径,配置dao层的sql打印日志级别为DEBUG-->
|
||||
<logger name="com.zt.plat.module.report.dal.mysql" level="DEBUG" additivity="false">
|
||||
<appender-ref ref="STDOUT"/>
|
||||
</logger>
|
||||
</springProfile>
|
||||
|
||||
<!-- 其它环境 -->
|
||||
<springProfile name="dev,test,stage,prod,default">
|
||||
<root level="INFO">
|
||||
|
||||
@@ -14,6 +14,9 @@ public class AdminUserRespDTO implements VO {
|
||||
@Schema(description = "用户 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "用户账号", requiredMode = Schema.RequiredMode.REQUIRED, example = "zhangsan")
|
||||
private String username;
|
||||
|
||||
@Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "小王")
|
||||
private String nickname;
|
||||
|
||||
|
||||
@@ -13,7 +13,8 @@ import lombok.Getter;
|
||||
public enum DeptSourceEnum {
|
||||
|
||||
EXTERNAL(1, "外部部门"), // 系统创建的部门
|
||||
SYNC(2, "同步部门"); // 通过 OrgSyncService 同步的部门
|
||||
SYNC(2, "同步部门"), // 通过 OrgSyncService 同步的部门
|
||||
IWORK(3, "iWork 同步"); // 通过 iWork 同步的部门
|
||||
|
||||
/**
|
||||
* 类型
|
||||
|
||||
@@ -13,7 +13,8 @@ import lombok.Getter;
|
||||
public enum UserSourceEnum {
|
||||
|
||||
EXTERNAL(1, "外部用户"), // 系统创建、注册等方式产生的用户
|
||||
SYNC(2, "同步用户"); // 通过 UserSyncService 同步的用户
|
||||
SYNC(2, "同步用户"), // 通过 UserSyncService 同步的用户
|
||||
IWORK(3, "iWork 用户"); // 通过 iWork 全量/单条同步产生的用户
|
||||
|
||||
/**
|
||||
* 类型
|
||||
|
||||
@@ -1,23 +1,10 @@
|
||||
package com.zt.plat.module.system.controller.admin.integration.iwork;
|
||||
|
||||
import com.zt.plat.framework.common.pojo.CommonResult;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkAuthRegisterReqVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkAuthRegisterRespVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkAuthTokenReqVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkAuthTokenRespVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkDepartmentQueryReqVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkJobTitleQueryReqVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkOperationRespVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkOrgRespVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkOrgSyncReqVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkSubcompanyQueryReqVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkUserInfoReqVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkUserInfoRespVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkUserQueryReqVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkWorkflowCreateReqVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkWorkflowVoidReqVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.*;
|
||||
import com.zt.plat.module.system.service.integration.iwork.IWorkIntegrationService;
|
||||
import com.zt.plat.module.system.service.integration.iwork.IWorkOrgRestService;
|
||||
import com.zt.plat.module.system.service.integration.iwork.IWorkSyncService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
@@ -42,6 +29,7 @@ public class IWorkIntegrationController {
|
||||
|
||||
private final IWorkIntegrationService integrationService;
|
||||
private final IWorkOrgRestService orgRestService;
|
||||
private final IWorkSyncService syncService;
|
||||
|
||||
@PostMapping("/auth/register")
|
||||
@Operation(summary = "注册 iWork 凭证,获取服务端公钥与 secret")
|
||||
@@ -77,49 +65,87 @@ 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/departments/full-sync")
|
||||
@Operation(summary = "手动触发 iWork 部门同步")
|
||||
public CommonResult<IWorkFullSyncRespVO> fullSyncDepartments(@Valid @RequestBody IWorkFullSyncReqVO reqVO) {
|
||||
return success(syncService.fullSyncDepartments(reqVO));
|
||||
}
|
||||
|
||||
@PostMapping("/hr/job-title/sync")
|
||||
@Operation(summary = "同步岗位信息至 iWork")
|
||||
public CommonResult<IWorkOrgRespVO> syncJobTitles(@Valid @RequestBody IWorkOrgSyncReqVO reqVO) {
|
||||
return success(orgRestService.syncJobTitles(reqVO));
|
||||
@PostMapping("/hr/subcompanies/full-sync")
|
||||
@Operation(summary = "手动触发 iWork 分部同步")
|
||||
public CommonResult<IWorkFullSyncRespVO> fullSyncSubcompanies(@Valid @RequestBody IWorkFullSyncReqVO reqVO) {
|
||||
return success(syncService.fullSyncSubcompanies(reqVO));
|
||||
}
|
||||
|
||||
@PostMapping("/hr/user/sync")
|
||||
@Operation(summary = "同步人员信息至 iWork")
|
||||
public CommonResult<IWorkOrgRespVO> syncUsers(@Valid @RequestBody IWorkOrgSyncReqVO reqVO) {
|
||||
return success(orgRestService.syncUsers(reqVO));
|
||||
@PostMapping("/hr/job-titles/full-sync")
|
||||
@Operation(summary = "手动触发 iWork 岗位全量同步")
|
||||
public CommonResult<IWorkFullSyncRespVO> fullSyncJobTitles(@Valid @RequestBody IWorkFullSyncReqVO reqVO) {
|
||||
return success(syncService.fullSyncJobTitles(reqVO));
|
||||
}
|
||||
|
||||
@PostMapping("/hr/users/full-sync")
|
||||
@Operation(summary = "手动触发 iWork 人员全量同步")
|
||||
public CommonResult<IWorkFullSyncRespVO> fullSyncUsers(@Valid @RequestBody IWorkFullSyncReqVO reqVO) {
|
||||
return success(syncService.fullSyncUsers(reqVO));
|
||||
}
|
||||
|
||||
@PostMapping("/hr/single-sync")
|
||||
@Operation(summary = "按 iWork ID 同步单条组织/人员")
|
||||
public CommonResult<IWorkSingleSyncRespVO> singleSync(@Valid @RequestBody IWorkSingleSyncReqVO reqVO) {
|
||||
return success(syncService.syncSingle(reqVO));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.zt.plat.module.system.controller.admin.integration.iwork.vo;
|
||||
|
||||
import com.zt.plat.module.system.enums.integration.IWorkSyncEntityTypeEnum;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.Max;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* iWork 全量同步请求
|
||||
*/
|
||||
@Data
|
||||
public class IWorkFullSyncReqVO {
|
||||
|
||||
@Schema(description = "起始页码,从 1 开始", example = "1")
|
||||
@Min(1)
|
||||
private Integer startPage = 1;
|
||||
|
||||
@Schema(description = "最大处理页数,null 表示处理至 iWork 返回的末页", example = "10")
|
||||
@Min(1)
|
||||
private Integer maxPages;
|
||||
|
||||
@Schema(description = "每次分页从 iWork 拉取的记录数", example = "100")
|
||||
@Min(1)
|
||||
@Max(500)
|
||||
private Integer pageSize = 100;
|
||||
|
||||
@Schema(description = "同步范围列表,默认同步全部。可选:subcompany、department、jobTitle、user")
|
||||
private List<String> scopes;
|
||||
|
||||
@Schema(description = "是否包含已失效(canceled=1)的记录", example = "false")
|
||||
private Boolean includeCanceled = Boolean.FALSE;
|
||||
|
||||
public Set<IWorkSyncEntityTypeEnum> resolveScopes() {
|
||||
EnumSet<IWorkSyncEntityTypeEnum> defaults = EnumSet.allOf(IWorkSyncEntityTypeEnum.class);
|
||||
if (scopes == null || scopes.isEmpty()) {
|
||||
return defaults;
|
||||
}
|
||||
Set<IWorkSyncEntityTypeEnum> resolved = scopes.stream()
|
||||
.map(IWorkSyncEntityTypeEnum::fromCode)
|
||||
.filter(java.util.Objects::nonNull)
|
||||
.collect(Collectors.toCollection(() -> EnumSet.noneOf(IWorkSyncEntityTypeEnum.class)));
|
||||
if (resolved.isEmpty()) {
|
||||
return defaults;
|
||||
}
|
||||
return resolved;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.zt.plat.module.system.controller.admin.integration.iwork.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* iWork 全量同步响应
|
||||
*/
|
||||
@Data
|
||||
public class IWorkFullSyncRespVO {
|
||||
|
||||
@Schema(description = "本次处理的总页数")
|
||||
private Integer processedPages;
|
||||
|
||||
@Schema(description = "每次分页请求的条数")
|
||||
private Integer pageSize;
|
||||
|
||||
@Schema(description = "分部统计信息")
|
||||
private IWorkSyncEntityStatVO subcompanyStat = new IWorkSyncEntityStatVO();
|
||||
|
||||
@Schema(description = "部门统计信息")
|
||||
private IWorkSyncEntityStatVO departmentStat = new IWorkSyncEntityStatVO();
|
||||
|
||||
@Schema(description = "岗位统计信息")
|
||||
private IWorkSyncEntityStatVO jobTitleStat = new IWorkSyncEntityStatVO();
|
||||
|
||||
@Schema(description = "人员统计信息")
|
||||
private IWorkSyncEntityStatVO userStat = new IWorkSyncEntityStatVO();
|
||||
|
||||
@Schema(description = "每个批次的详细统计")
|
||||
private List<IWorkSyncBatchStatVO> batches;
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
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 com.zt.plat.module.system.service.integration.iwork.jackson.LenientIntegerDeserializer;
|
||||
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 IWorkHrDepartmentPageRespVO {
|
||||
|
||||
@Schema(description = "响应码")
|
||||
private String code;
|
||||
|
||||
@Schema(description = "提示信息")
|
||||
private String message;
|
||||
|
||||
@Schema(description = "是否成功")
|
||||
private boolean success;
|
||||
|
||||
@Schema(description = "iWork 返回的数据体")
|
||||
@JsonProperty("data")
|
||||
private PageData data;
|
||||
|
||||
@JsonIgnore
|
||||
public Integer getTotalSize() {
|
||||
return data == null ? null : data.getTotalSize();
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public void setTotalSize(Integer totalSize) {
|
||||
ensureData().setTotalSize(totalSize);
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public Integer getTotalPage() {
|
||||
return data == null ? null : data.getTotalPage();
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public void setTotalPage(Integer totalPage) {
|
||||
ensureData().setTotalPage(totalPage);
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public Integer getPageSize() {
|
||||
return data == null ? null : data.getPageSize();
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public void setPageSize(Integer pageSize) {
|
||||
ensureData().setPageSize(pageSize);
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public Integer getPageNumber() {
|
||||
return data == null ? null : data.getPageNumber();
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public void setPageNumber(Integer pageNumber) {
|
||||
ensureData().setPageNumber(pageNumber);
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public List<Department> getDataList() {
|
||||
return data == null ? null : data.getDataList();
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public void setDataList(List<Department> dataList) {
|
||||
ensureData().setDataList(dataList);
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
private PageData ensureData() {
|
||||
if (data == null) {
|
||||
data = new PageData();
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
@Data
|
||||
@Schema(description = "iWork 部门分页数据体")
|
||||
public static class PageData {
|
||||
|
||||
@Schema(description = "总条数")
|
||||
private Integer totalSize;
|
||||
|
||||
@Schema(description = "总页数")
|
||||
private Integer totalPage;
|
||||
|
||||
@Schema(description = "每页条数")
|
||||
private Integer pageSize;
|
||||
|
||||
@Schema(description = "当前页码")
|
||||
@JsonProperty("page")
|
||||
private Integer pageNumber;
|
||||
|
||||
@Schema(description = "部门数据列表")
|
||||
@JsonProperty("dataList")
|
||||
private List<Department> dataList;
|
||||
}
|
||||
|
||||
@Data
|
||||
@Schema(description = "部门信息")
|
||||
public static class Department {
|
||||
|
||||
@Schema(description = "部门 ID")
|
||||
@JsonProperty("departmentid")
|
||||
@JsonDeserialize(using = LenientIntegerDeserializer.class)
|
||||
private Integer departmentid;
|
||||
|
||||
@Schema(description = "部门 ID(iWork 主键)")
|
||||
@JsonProperty("id")
|
||||
@JsonDeserialize(using = LenientIntegerDeserializer.class)
|
||||
private Integer id;
|
||||
|
||||
@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")
|
||||
@JsonDeserialize(using = LenientIntegerDeserializer.class)
|
||||
private Integer subcompanyid1;
|
||||
|
||||
@Schema(description = "所属分部名称")
|
||||
@JsonProperty("subcompanyname")
|
||||
private String subcompanyname;
|
||||
|
||||
@Schema(description = "上级分部 ID")
|
||||
@JsonProperty("supsubcomid")
|
||||
@JsonDeserialize(using = LenientIntegerDeserializer.class)
|
||||
private Integer supsubcomid;
|
||||
|
||||
@Schema(description = "上级分部名称")
|
||||
@JsonProperty("supsubcomname")
|
||||
private String supsubcomname;
|
||||
|
||||
@Schema(description = "父部门 ID")
|
||||
@JsonProperty("supdepid")
|
||||
@JsonDeserialize(using = LenientIntegerDeserializer.class)
|
||||
private Integer supdepid;
|
||||
|
||||
@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")
|
||||
@JsonDeserialize(using = LenientIntegerDeserializer.class)
|
||||
private Integer managerid;
|
||||
|
||||
@Schema(description = "负责人名称")
|
||||
@JsonProperty("manager")
|
||||
private String manager;
|
||||
|
||||
@JsonIgnore
|
||||
private Map<String, Object> attributes;
|
||||
|
||||
@JsonAnySetter
|
||||
public void putAttribute(String key, Object value) {
|
||||
if (attributes == null) {
|
||||
attributes = new LinkedHashMap<>();
|
||||
}
|
||||
attributes.put(key, value);
|
||||
}
|
||||
|
||||
@JsonAnyGetter
|
||||
public Map<String, Object> any() {
|
||||
return attributes == null ? Collections.emptyMap() : attributes;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
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 com.zt.plat.module.system.service.integration.iwork.jackson.LenientIntegerDeserializer;
|
||||
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 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("id")
|
||||
private Integer id;
|
||||
|
||||
@Schema(description = "岗位编码")
|
||||
@JsonProperty("jobtitlecode")
|
||||
private String jobtitlecode;
|
||||
|
||||
@Schema(description = "岗位名称")
|
||||
@JsonProperty("jobtitlename")
|
||||
private String jobtitlename;
|
||||
|
||||
@Schema(description = "岗位类型")
|
||||
@JsonProperty("jobtitletype")
|
||||
private String jobtitletype;
|
||||
|
||||
@Schema(description = "所属岗位组 ID")
|
||||
@JsonProperty("jobgroupid")
|
||||
private Integer jobgroupid;
|
||||
|
||||
@Schema(description = "所属岗位组名称")
|
||||
@JsonProperty("jobgroupname")
|
||||
private String jobgroupname;
|
||||
|
||||
@Schema(description = "岗位层级")
|
||||
@JsonProperty("joblevel")
|
||||
private String joblevel;
|
||||
|
||||
@Schema(description = "岗位职责")
|
||||
@JsonProperty("jobfunction")
|
||||
private String jobfunction;
|
||||
|
||||
@Schema(description = "岗位描述")
|
||||
@JsonProperty("description")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "上级岗位 ID")
|
||||
@JsonProperty("supjobtitleid")
|
||||
private Integer supjobtitleid;
|
||||
|
||||
@Schema(description = "上级岗位名称")
|
||||
@JsonProperty("supjobtitlename")
|
||||
private String supjobtitlename;
|
||||
|
||||
@Schema(description = "显示顺序")
|
||||
@JsonProperty("showorder")
|
||||
@JsonDeserialize(using = LenientIntegerDeserializer.class)
|
||||
private Integer showorder;
|
||||
|
||||
@Schema(description = "是否已失效 (0/1)")
|
||||
@JsonProperty("canceled")
|
||||
private String canceled;
|
||||
|
||||
@JsonIgnore
|
||||
private Map<String, Object> attributes;
|
||||
|
||||
@JsonAnySetter
|
||||
public void putAttribute(String key, Object value) {
|
||||
if (attributes == null) {
|
||||
attributes = new LinkedHashMap<>();
|
||||
}
|
||||
attributes.put(key, value);
|
||||
}
|
||||
|
||||
@JsonAnyGetter
|
||||
public Map<String, Object> any() {
|
||||
return attributes == null ? Collections.emptyMap() : attributes;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
package com.zt.plat.module.system.controller.admin.integration.iwork.vo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAnyGetter;
|
||||
import com.fasterxml.jackson.annotation.JsonAnySetter;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
import com.zt.plat.module.system.service.integration.iwork.jackson.LenientIntegerDeserializer;
|
||||
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 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(iWork 主键)")
|
||||
@JsonProperty("id")
|
||||
@JsonDeserialize(using = LenientIntegerDeserializer.class)
|
||||
private Integer id;
|
||||
|
||||
@Schema(description = "分部编码")
|
||||
@JsonProperty("subcompanycode")
|
||||
private String subcompanycode;
|
||||
|
||||
@Schema(description = "分部名称")
|
||||
@JsonProperty("subcompanyname")
|
||||
private String subcompanyname;
|
||||
|
||||
@Schema(description = "所属总部 ID")
|
||||
@JsonProperty("companyid")
|
||||
private Integer companyid;
|
||||
|
||||
@Schema(description = "所属总部名称")
|
||||
@JsonProperty("companyname")
|
||||
private String companyname;
|
||||
|
||||
@Schema(description = "上级分部 ID")
|
||||
@JsonProperty("supsubcomid")
|
||||
private Integer supsubcomid;
|
||||
|
||||
@Schema(description = "上级分部名称")
|
||||
@JsonProperty("supsubcomname")
|
||||
private String supsubcomname;
|
||||
|
||||
@Schema(description = "显示顺序")
|
||||
@JsonProperty("showorder")
|
||||
@JsonDeserialize(using = LenientIntegerDeserializer.class)
|
||||
private Integer showorder;
|
||||
|
||||
@Schema(description = "分部描述")
|
||||
@JsonProperty("description")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "是否已失效(0/1)")
|
||||
@JsonProperty("canceled")
|
||||
private String canceled;
|
||||
|
||||
@Schema(description = "层级路径")
|
||||
@JsonProperty("alllevel")
|
||||
private String alllevel;
|
||||
|
||||
@JsonIgnore
|
||||
private Map<String, Object> attributes;
|
||||
|
||||
@JsonAnySetter
|
||||
public void putAttribute(String key, Object value) {
|
||||
if (attributes == null) {
|
||||
attributes = new LinkedHashMap<>();
|
||||
}
|
||||
attributes.put(key, value);
|
||||
}
|
||||
|
||||
@JsonAnyGetter
|
||||
public Map<String, Object> any() {
|
||||
return attributes == null ? Collections.emptyMap() : attributes;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package com.zt.plat.module.system.controller.admin.integration.iwork.vo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAnyGetter;
|
||||
import com.fasterxml.jackson.annotation.JsonAnySetter;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* iWork 人力同步响应。
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "iWork 人力同步响应")
|
||||
public class IWorkHrSyncRespVO {
|
||||
|
||||
@Schema(description = "响应码")
|
||||
private String code;
|
||||
|
||||
@Schema(description = "提示信息")
|
||||
private String message;
|
||||
|
||||
@Schema(description = "是否成功")
|
||||
private boolean success;
|
||||
|
||||
@Schema(description = "同步结果明细")
|
||||
private List<SyncResult> result;
|
||||
|
||||
@Data
|
||||
@Schema(description = "同步结果项")
|
||||
public static class SyncResult {
|
||||
|
||||
@Schema(description = "操作动作 add/update/delete")
|
||||
@JsonProperty("@action")
|
||||
private String action;
|
||||
|
||||
@Schema(description = "外部编码")
|
||||
@JsonProperty("code")
|
||||
private String code;
|
||||
|
||||
@Schema(description = "执行结果 success/fail")
|
||||
@JsonProperty("result")
|
||||
private String result;
|
||||
|
||||
@Schema(description = "是否成功")
|
||||
@JsonProperty("success")
|
||||
private Boolean success;
|
||||
|
||||
@Schema(description = "失败描述")
|
||||
@JsonProperty("message")
|
||||
private String message;
|
||||
|
||||
@JsonIgnore
|
||||
private Map<String, Object> attributes;
|
||||
|
||||
@JsonAnySetter
|
||||
public void putAttribute(String key, Object value) {
|
||||
if (attributes == null) {
|
||||
attributes = new LinkedHashMap<>();
|
||||
}
|
||||
attributes.put(key, value);
|
||||
}
|
||||
|
||||
@JsonAnyGetter
|
||||
public Map<String, Object> any() {
|
||||
return attributes == null ? Collections.emptyMap() : attributes;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
package com.zt.plat.module.system.controller.admin.integration.iwork.vo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAnyGetter;
|
||||
import com.fasterxml.jackson.annotation.JsonAnySetter;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import com.zt.plat.module.system.service.integration.iwork.jackson.LenientIntegerDeserializer;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* iWork 人员分页响应。
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "iWork 人员分页响应")
|
||||
public class IWorkHrUserPageRespVO {
|
||||
|
||||
@Schema(description = "响应码")
|
||||
private String code;
|
||||
|
||||
@Schema(description = "提示信息")
|
||||
private String message;
|
||||
|
||||
@Schema(description = "是否成功")
|
||||
private boolean success;
|
||||
|
||||
@Schema(description = "总条数")
|
||||
private Integer totalSize;
|
||||
|
||||
@Schema(description = "总页数")
|
||||
private Integer totalPage;
|
||||
|
||||
@Schema(description = "每页条数")
|
||||
private Integer pageSize;
|
||||
|
||||
@Schema(description = "当前页码")
|
||||
private Integer pageNumber;
|
||||
|
||||
@Schema(description = "人员数据列表")
|
||||
private List<User> dataList;
|
||||
|
||||
@Data
|
||||
@Schema(description = "人员信息")
|
||||
public static class User {
|
||||
|
||||
@Schema(description = "人员 ID")
|
||||
@JsonProperty("id")
|
||||
private Integer id;
|
||||
|
||||
@Schema(description = "人员姓名")
|
||||
@JsonProperty("lastname")
|
||||
private String lastname;
|
||||
|
||||
@Schema(description = "登录账号")
|
||||
@JsonProperty("loginid")
|
||||
private String loginid;
|
||||
|
||||
@Schema(description = "工号")
|
||||
@JsonProperty("workcode")
|
||||
private String workcode;
|
||||
|
||||
@Schema(description = "性别")
|
||||
@JsonProperty("sex")
|
||||
private String sex;
|
||||
|
||||
@Schema(description = "所属分部 ID")
|
||||
@JsonProperty("subcompanyid1")
|
||||
private Integer subcompanyid1;
|
||||
|
||||
@Schema(description = "所属分部名称")
|
||||
@JsonProperty("subcompanyname")
|
||||
private String subcompanyname;
|
||||
|
||||
@Schema(description = "所属部门 ID")
|
||||
@JsonProperty("departmentid")
|
||||
private Integer departmentid;
|
||||
|
||||
@Schema(description = "所属部门名称")
|
||||
@JsonProperty("departmentname")
|
||||
private String departmentname;
|
||||
|
||||
@Schema(description = "所属岗位 ID")
|
||||
@JsonProperty("jobtitleid")
|
||||
private Integer jobtitleid;
|
||||
|
||||
@Schema(description = "所属岗位名称")
|
||||
@JsonProperty("jobtitlename")
|
||||
private String jobtitlename;
|
||||
|
||||
@Schema(description = "手机号码")
|
||||
@JsonProperty("mobile")
|
||||
private String mobile;
|
||||
|
||||
@Schema(description = "办公电话")
|
||||
@JsonProperty("telephone")
|
||||
private String telephone;
|
||||
|
||||
@Schema(description = "邮箱")
|
||||
@JsonProperty("email")
|
||||
private String email;
|
||||
|
||||
@Schema(description = "直属上级 ID")
|
||||
@JsonProperty("managerid")
|
||||
private Integer managerid;
|
||||
|
||||
@Schema(description = "助理 ID")
|
||||
@JsonProperty("assistantid")
|
||||
private Integer assistantid;
|
||||
|
||||
@Schema(description = "安全级别")
|
||||
@JsonProperty("seclevel")
|
||||
private Integer seclevel;
|
||||
|
||||
@Schema(description = "当前状态")
|
||||
@JsonProperty("status")
|
||||
private String status;
|
||||
|
||||
@Schema(description = "入职日期")
|
||||
@JsonProperty("hiredate")
|
||||
private String hiredate;
|
||||
|
||||
@Schema(description = "离职日期")
|
||||
@JsonProperty("leavedate")
|
||||
private String leavedate;
|
||||
|
||||
@Schema(description = "出生日期")
|
||||
@JsonProperty("birthday")
|
||||
private String birthday;
|
||||
|
||||
@Schema(description = "民族")
|
||||
@JsonProperty("folk")
|
||||
private String folk;
|
||||
|
||||
@Schema(description = "婚姻状况")
|
||||
@JsonProperty("maritalstatus")
|
||||
private String maritalstatus;
|
||||
|
||||
@Schema(description = "文化程度")
|
||||
@JsonProperty("educationlevel")
|
||||
private String educationlevel;
|
||||
|
||||
@Schema(description = "籍贯")
|
||||
@JsonProperty("nativeplace")
|
||||
private String nativeplace;
|
||||
|
||||
@Schema(description = "户口所在地")
|
||||
@JsonProperty("nationality")
|
||||
private String nationality;
|
||||
|
||||
@Schema(description = "证件号码")
|
||||
@JsonProperty("certificatenum")
|
||||
private String certificatenum;
|
||||
|
||||
@Schema(description = "显示顺序")
|
||||
@JsonProperty("dsporder")
|
||||
@JsonDeserialize(using = LenientIntegerDeserializer.class)
|
||||
private Integer dsporder;
|
||||
|
||||
@Schema(description = "系统语言")
|
||||
@JsonProperty("systemlanguage")
|
||||
private String systemlanguage;
|
||||
|
||||
@Schema(description = "账号类型")
|
||||
@JsonProperty("accounttype")
|
||||
private String accounttype;
|
||||
|
||||
@JsonIgnore
|
||||
private Map<String, Object> attributes;
|
||||
|
||||
@JsonAnySetter
|
||||
public void putAttribute(String key, Object value) {
|
||||
if (attributes == null) {
|
||||
attributes = new LinkedHashMap<>();
|
||||
}
|
||||
attributes.put(key, value);
|
||||
}
|
||||
|
||||
@JsonAnyGetter
|
||||
public Map<String, Object> any() {
|
||||
return attributes == null ? Collections.emptyMap() : attributes;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,28 +1,15 @@
|
||||
package com.zt.plat.module.system.controller.admin.integration.iwork.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 对 iWork 人力组织 REST 请求的响应封装。
|
||||
* @deprecated 请改用强类型的 IWorkHr*RespVO,避免再引用该占位类。
|
||||
*/
|
||||
@Data
|
||||
public class IWorkOrgRespVO {
|
||||
@Deprecated(forRemoval = true)
|
||||
@Schema(description = "已废弃,占位用")
|
||||
public final class IWorkOrgRespVO {
|
||||
|
||||
@Schema(description = "响应中的业务数据(data 字段或整体映射)")
|
||||
private Map<String, Object> payload;
|
||||
|
||||
@Schema(description = "原始响应字符串")
|
||||
private String rawBody;
|
||||
|
||||
@Schema(description = "是否判断为成功")
|
||||
private boolean success;
|
||||
|
||||
@Schema(description = "提示信息")
|
||||
private String message;
|
||||
|
||||
@Schema(description = "响应码")
|
||||
private String code;
|
||||
private IWorkOrgRespVO() {
|
||||
throw new UnsupportedOperationException("Use IWorkHr*RespVO instead");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.zt.plat.module.system.controller.admin.integration.iwork.vo;
|
||||
|
||||
import com.zt.plat.module.system.enums.integration.IWorkSyncEntityTypeEnum;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* iWork 单条同步请求
|
||||
*/
|
||||
@Data
|
||||
public class IWorkSingleSyncReqVO {
|
||||
|
||||
@Schema(description = "同步的实体类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "user")
|
||||
@NotNull(message = "实体类型不能为空")
|
||||
private IWorkSyncEntityTypeEnum entityType;
|
||||
|
||||
@Schema(description = "iWork 提供的实体主键 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "10001")
|
||||
@NotNull(message = "实体 ID 不能为空")
|
||||
@Min(1)
|
||||
private Long entityId;
|
||||
|
||||
@Schema(description = "缺失时是否自动创建", example = "true")
|
||||
private Boolean createIfMissing = Boolean.TRUE;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.zt.plat.module.system.controller.admin.integration.iwork.vo;
|
||||
|
||||
import com.zt.plat.module.system.enums.integration.IWorkSyncEntityTypeEnum;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* iWork 单条同步响应
|
||||
*/
|
||||
@Data
|
||||
public class IWorkSingleSyncRespVO {
|
||||
|
||||
@Schema(description = "同步的实体类型")
|
||||
private IWorkSyncEntityTypeEnum entityType;
|
||||
|
||||
@Schema(description = "实体 ID")
|
||||
private Long entityId;
|
||||
|
||||
@Schema(description = "是否创建了新的记录")
|
||||
private boolean created;
|
||||
|
||||
@Schema(description = "是否对已有记录进行了更新")
|
||||
private boolean updated;
|
||||
|
||||
@Schema(description = "提示信息")
|
||||
private String message;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.zt.plat.module.system.controller.admin.integration.iwork.vo;
|
||||
|
||||
import com.zt.plat.module.system.enums.integration.IWorkSyncEntityTypeEnum;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 记录一次分页批次执行的统计信息。
|
||||
*/
|
||||
@Data
|
||||
public class IWorkSyncBatchStatVO {
|
||||
|
||||
@Schema(description = "同步的实体类型")
|
||||
private IWorkSyncEntityTypeEnum entityType;
|
||||
|
||||
@Schema(description = "当前批次处理的页码,从 1 开始")
|
||||
private Integer pageNumber;
|
||||
|
||||
@Schema(description = "本批次从 iWork 拉取的记录数量")
|
||||
private Integer pulled;
|
||||
|
||||
@Schema(description = "本批次创建的记录数量")
|
||||
private Integer created;
|
||||
|
||||
@Schema(description = "本批次因已存在而跳过的记录数量")
|
||||
private Integer skippedExisting;
|
||||
|
||||
@Schema(description = "本批次禁用的记录数量")
|
||||
private Integer disabled;
|
||||
|
||||
@Schema(description = "本批次失败的记录数量")
|
||||
private Integer failed;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.zt.plat.module.system.controller.admin.integration.iwork.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* iWork 同步实体统计信息
|
||||
*/
|
||||
@Data
|
||||
public class IWorkSyncEntityStatVO {
|
||||
|
||||
@Schema(description = "从 iWork 拉取的记录数量")
|
||||
private int pulled;
|
||||
|
||||
@Schema(description = "在本系统中新创建的记录数量")
|
||||
private int created;
|
||||
|
||||
@Schema(description = "因已存在而跳过的记录数量")
|
||||
private int skippedExisting;
|
||||
|
||||
@Schema(description = "在本系统中被禁用的记录数量")
|
||||
private int disabled;
|
||||
|
||||
@Schema(description = "同步失败的记录数量")
|
||||
private int failed;
|
||||
|
||||
public void incrementPulled(int delta) {
|
||||
this.pulled += delta;
|
||||
}
|
||||
|
||||
public void incrementCreated(int delta) {
|
||||
this.created += delta;
|
||||
}
|
||||
|
||||
public void incrementSkipped(int delta) {
|
||||
this.skippedExisting += delta;
|
||||
}
|
||||
|
||||
public void incrementDisabled(int delta) {
|
||||
this.disabled += delta;
|
||||
}
|
||||
|
||||
public void incrementFailed(int delta) {
|
||||
this.failed += delta;
|
||||
}
|
||||
}
|
||||
@@ -84,6 +84,18 @@ public class UserSaveReqVO {
|
||||
@Schema(description = "用户来源类型", example = "1")
|
||||
private Integer userSource;
|
||||
|
||||
@Schema(hidden = true)
|
||||
@JsonIgnore
|
||||
private boolean skipAssociationValidation;
|
||||
|
||||
@Schema(hidden = true)
|
||||
@JsonIgnore
|
||||
private boolean skipMobileValidation;
|
||||
|
||||
@Schema(hidden = true)
|
||||
@JsonIgnore
|
||||
private boolean skipEmailValidation;
|
||||
|
||||
// ========== 仅【创建】时,需要传递的字段 ==========
|
||||
|
||||
@Schema(description = "密码", requiredMode = Schema.RequiredMode.REQUIRED, example = "123456")
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.zt.plat.module.system.enums.integration;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* iWork 同步支持的实体类型。
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
public enum IWorkSyncEntityTypeEnum {
|
||||
|
||||
SUBCOMPANY("subcompany", "分部 / 公司"),
|
||||
DEPARTMENT("department", "部门"),
|
||||
JOB_TITLE("jobTitle", "岗位"),
|
||||
USER("user", "人员");
|
||||
|
||||
@JsonValue
|
||||
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;
|
||||
}
|
||||
|
||||
@JsonCreator(mode = JsonCreator.Mode.DELEGATING)
|
||||
public static IWorkSyncEntityTypeEnum fromJson(String code) {
|
||||
IWorkSyncEntityTypeEnum value = fromCode(code);
|
||||
if (value != null || code == null) {
|
||||
return value;
|
||||
}
|
||||
try {
|
||||
return IWorkSyncEntityTypeEnum.valueOf(code.trim().toUpperCase(Locale.ROOT));
|
||||
} catch (IllegalArgumentException ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
package com.zt.plat.module.system.framework.sms.core.client.impl;
|
||||
|
||||
import cn.hutool.core.codec.Base64;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.crypto.digest.DigestUtil;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.zt.plat.framework.common.core.KeyValue;
|
||||
import com.zt.plat.framework.common.util.http.HttpUtils;
|
||||
import com.zt.plat.module.system.framework.sms.core.client.dto.SmsReceiveRespDTO;
|
||||
import com.zt.plat.module.system.framework.sms.core.client.dto.SmsSendRespDTO;
|
||||
import com.zt.plat.module.system.framework.sms.core.client.dto.SmsTemplateRespDTO;
|
||||
import com.zt.plat.module.system.framework.sms.core.property.SmsChannelProperties;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 中国移动云MAS短信客户端实现类
|
||||
*
|
||||
* @author zt-team
|
||||
* @since 2025-01-19
|
||||
*/
|
||||
@Slf4j
|
||||
public class CmccMasSmsClient extends AbstractSmsClient {
|
||||
|
||||
private static final String URL = "https://112.35.10.201:28888/sms/submit";
|
||||
private static final String RESPONSE_SUCCESS = "success";
|
||||
|
||||
public CmccMasSmsClient(SmsChannelProperties properties) {
|
||||
super(properties);
|
||||
Assert.notEmpty(properties.getApiKey(), "apiKey 不能为空");
|
||||
Assert.notEmpty(properties.getApiSecret(), "apiSecret 不能为空");
|
||||
validateCmccMasConfig(properties);
|
||||
}
|
||||
|
||||
/**
|
||||
* 参数校验中国移动云MAS的配置
|
||||
*
|
||||
* 原因是:中国移动云MAS需要三个参数:ecName、apId、secretKey
|
||||
*
|
||||
* 解决方案:考虑到不破坏原有的 apiKey + apiSecret 的结构,所以将 ecName 和 apId 拼接到 apiKey 字段中,格式为 "ecName apId"。
|
||||
* secretKey 存储在 apiSecret 字段中。
|
||||
*
|
||||
* @param properties 配置
|
||||
*/
|
||||
private static void validateCmccMasConfig(SmsChannelProperties properties) {
|
||||
String combineKey = properties.getApiKey();
|
||||
Assert.notEmpty(combineKey, "apiKey 不能为空");
|
||||
String[] keys = combineKey.trim().split(" ");
|
||||
Assert.isTrue(keys.length == 2, "中国移动云MAS apiKey 配置格式错误,请配置为 [ecName apId]");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 ecName(企业名称)
|
||||
*/
|
||||
private String getEcName() {
|
||||
return StrUtil.subBefore(properties.getApiKey(), " ", true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 apId(应用ID)
|
||||
*/
|
||||
private String getApId() {
|
||||
return StrUtil.subAfter(properties.getApiKey(), " ", true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 secretKey(密钥)
|
||||
*/
|
||||
private String getSecretKey() {
|
||||
return properties.getApiSecret();
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送短信
|
||||
*
|
||||
* @param logId 日志ID
|
||||
* @param mobile 手机号
|
||||
* @param apiTemplateId 模板ID(本平台不使用模板,传入内容)
|
||||
* @param templateParams 模板参数
|
||||
* @return 发送结果
|
||||
*/
|
||||
@Override
|
||||
public SmsSendRespDTO sendSms(Long logId, String mobile, String apiTemplateId,
|
||||
List<KeyValue<String, Object>> templateParams) throws Throwable {
|
||||
|
||||
// 1. 构建短信内容
|
||||
String content = buildContent(apiTemplateId, templateParams);
|
||||
|
||||
// 2. 计算MAC校验值
|
||||
String mac = calculateMac(mobile, content);
|
||||
|
||||
// 3. 构建请求参数
|
||||
JSONObject requestBody = new JSONObject();
|
||||
requestBody.set("ecName", getEcName()); // 企业名称
|
||||
requestBody.set("apId", getApId()); // 应用ID
|
||||
requestBody.set("secretKey", getSecretKey()); // 密钥
|
||||
requestBody.set("sign", properties.getSignature()); // 签名编码
|
||||
requestBody.set("mobiles", mobile);
|
||||
requestBody.set("content", content);
|
||||
requestBody.set("addSerial", "");
|
||||
requestBody.set("mac", mac);
|
||||
|
||||
log.info("[sendSms][发送短信 {}]", JSONUtil.toJsonStr(requestBody));
|
||||
|
||||
// 4. Base64编码请求体
|
||||
String encodedBody = Base64.encode(requestBody.toString());
|
||||
log.info("[sendSms][Base64编码后: {}]", encodedBody);
|
||||
|
||||
// 5. 构建请求头(需要JWT Token)
|
||||
Map<String, String> headers = new HashMap<>();
|
||||
headers.put("Authorization", "Bearer " + getJwtToken());
|
||||
headers.put("Content-Type", "text/plain");
|
||||
|
||||
// 6. 发起请求
|
||||
String responseBody = HttpUtils.post(URL, headers, encodedBody);
|
||||
JSONObject response = JSONUtil.parseObj(responseBody);
|
||||
|
||||
log.info("[sendSms][收到响应 - {}]", response);
|
||||
|
||||
// 7. 解析响应
|
||||
return new SmsSendRespDTO()
|
||||
.setSuccess(response.getBool("success", false))
|
||||
.setSerialNo(response.getStr("msgGroup"))
|
||||
.setApiCode(response.getStr("rspcod"))
|
||||
.setApiMsg(response.getStr("message", "未知错误"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析短信接收状态回调
|
||||
*
|
||||
* @param text 回调文本
|
||||
* @return 接收状态列表
|
||||
*/
|
||||
@Override
|
||||
public List<SmsReceiveRespDTO> parseSmsReceiveStatus(String text) throws Throwable {
|
||||
// TODO: 根据移动云MAS回调格式实现
|
||||
log.warn("[parseSmsReceiveStatus][暂未实现短信状态回调解析]");
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询短信模板
|
||||
*
|
||||
* @param apiTemplateId 模板ID
|
||||
* @return 模板信息
|
||||
*/
|
||||
@Override
|
||||
public SmsTemplateRespDTO getSmsTemplate(String apiTemplateId) throws Throwable {
|
||||
// 移动云MAS不使用模板机制,直接发送内容
|
||||
log.debug("[getSmsTemplate][中国移动云MAS不支持模板查询]");
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算MAC校验值
|
||||
* 算法:MD5(ecName + apId + secretKey + mobiles + content + sign + addSerial)
|
||||
*
|
||||
* @param mobile 手机号
|
||||
* @param content 短信内容
|
||||
* @return MAC校验值
|
||||
*/
|
||||
private String calculateMac(String mobile, String content) {
|
||||
String rawString = getEcName() // ecName
|
||||
+ getApId() // apId
|
||||
+ getSecretKey() // secretKey
|
||||
+ mobile // mobiles
|
||||
+ content // content
|
||||
+ properties.getSignature() // sign
|
||||
+ ""; // addSerial
|
||||
|
||||
String mac = DigestUtil.md5Hex(rawString).toLowerCase();
|
||||
log.debug("[calculateMac][原始字符串长度: {}, MAC: {}]", rawString.length(), mac);
|
||||
return mac;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建短信内容
|
||||
*
|
||||
* @param apiTemplateId 模板ID
|
||||
* @param templateParams 模板参数
|
||||
* @return 短信内容
|
||||
*/
|
||||
private String buildContent(String apiTemplateId, List<KeyValue<String, Object>> templateParams) {
|
||||
// 简单实现:直接返回模板ID作为内容
|
||||
// 实际使用时需要根据业务需求构建短信内容
|
||||
if (templateParams == null || templateParams.isEmpty()) {
|
||||
return apiTemplateId;
|
||||
}
|
||||
|
||||
// 替换模板参数,支持 {{key}} 格式
|
||||
String content = apiTemplateId;
|
||||
for (KeyValue<String, Object> param : templateParams) {
|
||||
String placeholder = "{{" + param.getKey() + "}}";
|
||||
String value = String.valueOf(param.getValue());
|
||||
content = content.replace(placeholder, value);
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取JWT Token
|
||||
* TODO: 实现Token获取逻辑,可能需要:
|
||||
* 1. 调用认证接口获取Token
|
||||
* 2. 缓存Token并在过期前自动刷新
|
||||
* 3. 处理Token失效情况
|
||||
*
|
||||
* @return JWT Token
|
||||
*/
|
||||
private String getJwtToken() {
|
||||
// 临时实现:从配置中读取或调用认证接口获取
|
||||
// 实际生产环境需要实现完整的Token管理机制
|
||||
String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.example.token";
|
||||
log.warn("[getJwtToken][使用临时Token,生产环境需实现完整的Token获取机制]");
|
||||
return token;
|
||||
}
|
||||
}
|
||||
@@ -81,6 +81,7 @@ public class SmsClientFactoryImpl implements SmsClientFactory {
|
||||
case TENCENT: return new TencentSmsClient(properties);
|
||||
case HUAWEI: return new HuaweiSmsClient(properties);
|
||||
case QINIU: return new QiniuSmsClient(properties);
|
||||
case CMCC_MAS: return new CmccMasSmsClient(properties);
|
||||
}
|
||||
// 创建失败,错误日志 + 抛出异常
|
||||
log.error("[createSmsClient][配置({}) 找不到合适的客户端实现]", properties);
|
||||
|
||||
@@ -19,6 +19,7 @@ public enum SmsChannelEnum {
|
||||
TENCENT("TENCENT", "腾讯云"),
|
||||
HUAWEI("HUAWEI", "华为云"),
|
||||
QINIU("QINIU", "七牛云"),
|
||||
CMCC_MAS("CMCC_MAS", "中国移动云MAS"),
|
||||
;
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -74,16 +74,23 @@ public class DeptServiceImpl implements DeptService {
|
||||
// 校验部门名的唯一性
|
||||
validateDeptNameUnique(null, createReqVO.getParentId(), createReqVO.getName());
|
||||
// 生成并校验部门编码
|
||||
Long effectiveParentId = normalizeParentId(createReqVO.getParentId());
|
||||
boolean isTopLevel = Objects.equals(effectiveParentId, DeptDO.PARENT_ID_ROOT);
|
||||
String resolvedCode;
|
||||
if (isTopLevel) {
|
||||
resolvedCode = resolveTopLevelCode(null, createReqVO.getCode());
|
||||
boolean isIWorkSource = Objects.equals(createReqVO.getDeptSource(), DeptSourceEnum.IWORK.getSource());
|
||||
if (isIWorkSource) {
|
||||
// iWork 来源直接使用提供的编码,不再生成
|
||||
String providedCode = StrUtil.blankToDefault(createReqVO.getCode(), null);
|
||||
createReqVO.setCode(providedCode);
|
||||
} else {
|
||||
resolvedCode = generateDeptCode(effectiveParentId);
|
||||
validateDeptCodeUnique(null, resolvedCode);
|
||||
Long effectiveParentId = normalizeParentId(createReqVO.getParentId());
|
||||
boolean isTopLevel = Objects.equals(effectiveParentId, DeptDO.PARENT_ID_ROOT);
|
||||
String resolvedCode;
|
||||
if (isTopLevel) {
|
||||
resolvedCode = resolveTopLevelCode(null, createReqVO.getCode());
|
||||
} else {
|
||||
resolvedCode = generateDeptCode(effectiveParentId);
|
||||
validateDeptCodeUnique(null, resolvedCode);
|
||||
}
|
||||
createReqVO.setCode(resolvedCode);
|
||||
}
|
||||
createReqVO.setCode(resolvedCode);
|
||||
|
||||
// 插入部门
|
||||
DeptDO dept = BeanUtils.toBean(createReqVO, DeptDO.class);
|
||||
@@ -110,28 +117,35 @@ public class DeptServiceImpl implements DeptService {
|
||||
// 校验部门名的唯一性
|
||||
validateDeptNameUnique(updateReqVO.getId(), updateReqVO.getParentId(), updateReqVO.getName());
|
||||
// 如果上级发生变化,需要重新生成编码并同步子级
|
||||
boolean isIWorkSource = Objects.equals(originalDept.getDeptSource(), DeptSourceEnum.IWORK.getSource());
|
||||
Long newParentId = normalizeParentId(updateReqVO.getParentId());
|
||||
Long oldParentId = normalizeParentId(originalDept.getParentId());
|
||||
boolean parentChanged = !Objects.equals(newParentId, oldParentId);
|
||||
if (parentChanged) {
|
||||
String newCode;
|
||||
if (Objects.equals(newParentId, DeptDO.PARENT_ID_ROOT)) {
|
||||
newCode = resolveTopLevelCode(updateReqVO.getId(), updateReqVO.getCode());
|
||||
} else {
|
||||
newCode = generateDeptCode(updateReqVO.getParentId());
|
||||
validateDeptCodeUnique(updateReqVO.getId(), newCode);
|
||||
}
|
||||
updateReqVO.setCode(newCode);
|
||||
if (isIWorkSource) {
|
||||
// iWork 来源直接使用提供的编码,不再生成
|
||||
String providedCode = StrUtil.blankToDefault(updateReqVO.getCode(), null);
|
||||
updateReqVO.setCode(providedCode);
|
||||
} else {
|
||||
if (Objects.equals(newParentId, DeptDO.PARENT_ID_ROOT)) {
|
||||
String requestedCode = updateReqVO.getCode();
|
||||
if (StrUtil.isNotBlank(requestedCode) && !StrUtil.equals(requestedCode.trim(), originalDept.getCode())) {
|
||||
updateReqVO.setCode(resolveTopLevelCode(updateReqVO.getId(), requestedCode));
|
||||
if (parentChanged) {
|
||||
String newCode;
|
||||
if (Objects.equals(newParentId, DeptDO.PARENT_ID_ROOT)) {
|
||||
newCode = resolveTopLevelCode(updateReqVO.getId(), updateReqVO.getCode());
|
||||
} else {
|
||||
newCode = generateDeptCode(updateReqVO.getParentId());
|
||||
validateDeptCodeUnique(updateReqVO.getId(), newCode);
|
||||
}
|
||||
updateReqVO.setCode(newCode);
|
||||
} else {
|
||||
if (Objects.equals(newParentId, DeptDO.PARENT_ID_ROOT)) {
|
||||
String requestedCode = updateReqVO.getCode();
|
||||
if (StrUtil.isNotBlank(requestedCode) && !StrUtil.equals(requestedCode.trim(), originalDept.getCode())) {
|
||||
updateReqVO.setCode(resolveTopLevelCode(updateReqVO.getId(), requestedCode));
|
||||
} else {
|
||||
updateReqVO.setCode(originalDept.getCode());
|
||||
}
|
||||
} else {
|
||||
updateReqVO.setCode(originalDept.getCode());
|
||||
}
|
||||
} else {
|
||||
updateReqVO.setCode(originalDept.getCode());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,7 +203,7 @@ public class DeptServiceImpl implements DeptService {
|
||||
// 2. 父部门不存在
|
||||
DeptDO parentDept = deptMapper.selectById(parentId);
|
||||
if (parentDept == null) {
|
||||
throw exception(DEPT_PARENT_NOT_EXITS);
|
||||
return;
|
||||
}
|
||||
// 3. 递归校验父部门,如果父部门是自己的子部门,则报错,避免形成环路
|
||||
if (id == null) { // id 为空,说明新增,不需要考虑环路
|
||||
@@ -251,19 +265,18 @@ public class DeptServiceImpl implements DeptService {
|
||||
|
||||
private String generateDeptCode(Long parentId) {
|
||||
Long effectiveParentId = normalizeParentId(parentId);
|
||||
Long codeParentId = effectiveParentId;
|
||||
String prefix = ROOT_CODE_PREFIX;
|
||||
if (!DeptDO.PARENT_ID_ROOT.equals(effectiveParentId)) {
|
||||
DeptDO parentDept = deptMapper.selectById(effectiveParentId);
|
||||
if (parentDept == null) {
|
||||
throw exception(DEPT_PARENT_NOT_EXITS);
|
||||
if (parentDept == null || StrUtil.isBlank(parentDept.getCode())) {
|
||||
codeParentId = DeptDO.PARENT_ID_ROOT;
|
||||
} else {
|
||||
prefix = parentDept.getCode();
|
||||
}
|
||||
if (StrUtil.isBlank(parentDept.getCode())) {
|
||||
throw exception(DEPT_PARENT_CODE_NOT_INITIALIZED);
|
||||
}
|
||||
prefix = parentDept.getCode();
|
||||
}
|
||||
|
||||
int nextSequence = determineNextSequence(effectiveParentId, prefix);
|
||||
int nextSequence = determineNextSequence(codeParentId, prefix);
|
||||
assertSequenceRange(nextSequence);
|
||||
return prefix + formatSequence(nextSequence);
|
||||
}
|
||||
|
||||
@@ -17,5 +17,5 @@ public interface IWorkIntegrationErrorCodeConstants {
|
||||
ErrorCode IWORK_OPERATOR_USER_MISSING = new ErrorCode(1_010_200_007, "缺少 iWork 操作人用户编号");
|
||||
ErrorCode IWORK_WORKFLOW_ID_MISSING = new ErrorCode(1_010_200_008, "缺少 iWork 流程模板编号");
|
||||
ErrorCode IWORK_ORG_IDENTIFIER_MISSING = new ErrorCode(1_010_200_009, "iWork 人力组织接口缺少认证标识");
|
||||
ErrorCode IWORK_ORG_REMOTE_FAILED = new ErrorCode(1_010_200_010, "iWork 人力组织接口请求失败");
|
||||
ErrorCode IWORK_ORG_REMOTE_FAILED = new ErrorCode(1_010_200_010, "iWork 人力组织接口请求失败{}");
|
||||
}
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
package com.zt.plat.module.system.service.integration.iwork;
|
||||
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkDepartmentQueryReqVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkHrDepartmentPageRespVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkHrJobTitlePageRespVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkHrSubcompanyPageRespVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkHrSyncRespVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkHrUserPageRespVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkJobTitleQueryReqVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkOrgRespVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkOrgSyncReqVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkSubcompanyQueryReqVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkUserQueryReqVO;
|
||||
@@ -12,19 +16,19 @@ import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkUser
|
||||
*/
|
||||
public interface IWorkOrgRestService {
|
||||
|
||||
IWorkOrgRespVO listSubcompanies(IWorkSubcompanyQueryReqVO reqVO);
|
||||
IWorkHrSubcompanyPageRespVO listSubcompanies(IWorkSubcompanyQueryReqVO reqVO);
|
||||
|
||||
IWorkOrgRespVO listDepartments(IWorkDepartmentQueryReqVO reqVO);
|
||||
IWorkHrDepartmentPageRespVO listDepartments(IWorkDepartmentQueryReqVO reqVO);
|
||||
|
||||
IWorkOrgRespVO listJobTitles(IWorkJobTitleQueryReqVO reqVO);
|
||||
IWorkHrJobTitlePageRespVO listJobTitles(IWorkJobTitleQueryReqVO reqVO);
|
||||
|
||||
IWorkOrgRespVO listUsers(IWorkUserQueryReqVO reqVO);
|
||||
IWorkHrUserPageRespVO listUsers(IWorkUserQueryReqVO reqVO);
|
||||
|
||||
IWorkOrgRespVO syncSubcompanies(IWorkOrgSyncReqVO reqVO);
|
||||
IWorkHrSyncRespVO syncSubcompanies(IWorkOrgSyncReqVO reqVO);
|
||||
|
||||
IWorkOrgRespVO syncDepartments(IWorkOrgSyncReqVO reqVO);
|
||||
IWorkHrSyncRespVO syncDepartments(IWorkOrgSyncReqVO reqVO);
|
||||
|
||||
IWorkOrgRespVO syncJobTitles(IWorkOrgSyncReqVO reqVO);
|
||||
IWorkHrSyncRespVO syncJobTitles(IWorkOrgSyncReqVO reqVO);
|
||||
|
||||
IWorkOrgRespVO syncUsers(IWorkOrgSyncReqVO reqVO);
|
||||
IWorkHrSyncRespVO syncUsers(IWorkOrgSyncReqVO reqVO);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,195 @@
|
||||
package com.zt.plat.module.system.service.integration.iwork;
|
||||
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkHrDepartmentPageRespVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkHrJobTitlePageRespVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkHrSubcompanyPageRespVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkHrUserPageRespVO;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Abstraction for applying iWork entities into local persistence.
|
||||
*/
|
||||
public interface IWorkSyncProcessor {
|
||||
|
||||
BatchResult syncSubcompanies(List<IWorkHrSubcompanyPageRespVO.Subcompany> data, SyncOptions options);
|
||||
|
||||
BatchResult syncDepartments(List<IWorkHrDepartmentPageRespVO.Department> data, SyncOptions options);
|
||||
|
||||
BatchResult syncJobTitles(List<IWorkHrJobTitlePageRespVO.JobTitle> data, SyncOptions options);
|
||||
|
||||
BatchResult syncUsers(List<IWorkHrUserPageRespVO.User> data, SyncOptions options);
|
||||
|
||||
/**
|
||||
* Execution options shared by batch and single sync flows.
|
||||
*/
|
||||
final class SyncOptions {
|
||||
private final boolean includeCanceled;
|
||||
private final boolean allowUpdate;
|
||||
private final boolean createIfMissing;
|
||||
|
||||
private SyncOptions(boolean includeCanceled, boolean allowUpdate, boolean createIfMissing) {
|
||||
this.includeCanceled = includeCanceled;
|
||||
this.allowUpdate = allowUpdate;
|
||||
this.createIfMissing = createIfMissing;
|
||||
}
|
||||
|
||||
public static SyncOptions full(boolean includeCanceled) {
|
||||
return new SyncOptions(includeCanceled, false, true);
|
||||
}
|
||||
|
||||
public static SyncOptions single(boolean createIfMissing) {
|
||||
return new SyncOptions(true, true, Boolean.TRUE.equals(createIfMissing));
|
||||
}
|
||||
|
||||
public static SyncOptions custom(boolean includeCanceled, boolean allowUpdate, boolean createIfMissing) {
|
||||
return new SyncOptions(includeCanceled, allowUpdate, createIfMissing);
|
||||
}
|
||||
|
||||
public boolean isIncludeCanceled() {
|
||||
return includeCanceled;
|
||||
}
|
||||
|
||||
public boolean isAllowUpdate() {
|
||||
return allowUpdate;
|
||||
}
|
||||
|
||||
public boolean isCreateIfMissing() {
|
||||
return createIfMissing;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Aggregated result for a sync batch.
|
||||
*/
|
||||
final class BatchResult {
|
||||
private int pulled;
|
||||
private int created;
|
||||
private int skipped;
|
||||
private int disabled;
|
||||
private int failed;
|
||||
private int updated;
|
||||
private String message;
|
||||
|
||||
public static BatchResult empty() {
|
||||
return new BatchResult();
|
||||
}
|
||||
|
||||
public BatchResult withMessage(String message) {
|
||||
this.message = message;
|
||||
return this;
|
||||
}
|
||||
|
||||
public BatchResult merge(BatchResult other) {
|
||||
if (other == null) {
|
||||
return this;
|
||||
}
|
||||
this.pulled += other.pulled;
|
||||
this.created += other.created;
|
||||
this.skipped += other.skipped;
|
||||
this.disabled += other.disabled;
|
||||
this.failed += other.failed;
|
||||
this.updated += other.updated;
|
||||
if (Objects.nonNull(other.message)) {
|
||||
this.message = other.message;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public static BatchResult fromSingle(BatchResult single) {
|
||||
return empty().merge(single);
|
||||
}
|
||||
|
||||
public static BatchResult singleCreated(String message) {
|
||||
BatchResult result = empty();
|
||||
result.created = 1;
|
||||
result.message = message;
|
||||
return result;
|
||||
}
|
||||
|
||||
public static BatchResult singleSkipped(String message) {
|
||||
BatchResult result = empty();
|
||||
result.skipped = 1;
|
||||
result.message = message;
|
||||
return result;
|
||||
}
|
||||
|
||||
public static BatchResult singleFailed(String message) {
|
||||
BatchResult result = empty();
|
||||
result.failed = 1;
|
||||
result.message = message;
|
||||
return result;
|
||||
}
|
||||
|
||||
public BatchResult increasePulled(int delta) {
|
||||
this.pulled += delta;
|
||||
return this;
|
||||
}
|
||||
|
||||
public void increaseCreated() {
|
||||
this.created++;
|
||||
}
|
||||
|
||||
public void increaseSkipped() {
|
||||
this.skipped++;
|
||||
}
|
||||
|
||||
public void increaseDisabled() {
|
||||
this.disabled++;
|
||||
}
|
||||
|
||||
public void increaseFailed() {
|
||||
this.failed++;
|
||||
}
|
||||
|
||||
public void increaseUpdated() {
|
||||
this.updated++;
|
||||
}
|
||||
|
||||
public int getPulled() {
|
||||
return pulled;
|
||||
}
|
||||
|
||||
public int getCreated() {
|
||||
return created;
|
||||
}
|
||||
|
||||
public int getSkipped() {
|
||||
return skipped;
|
||||
}
|
||||
|
||||
public int getDisabled() {
|
||||
return disabled;
|
||||
}
|
||||
|
||||
public int getFailed() {
|
||||
return failed;
|
||||
}
|
||||
|
||||
public int getUpdated() {
|
||||
return updated;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
|
||||
default BatchResult syncSubcompany(IWorkHrSubcompanyPageRespVO.Subcompany data, SyncOptions options) {
|
||||
return syncSubcompanies(Collections.singletonList(data), options);
|
||||
}
|
||||
|
||||
default BatchResult syncDepartment(IWorkHrDepartmentPageRespVO.Department data, SyncOptions options) {
|
||||
return syncDepartments(Collections.singletonList(data), options);
|
||||
}
|
||||
|
||||
default BatchResult syncJobTitle(IWorkHrJobTitlePageRespVO.JobTitle data, SyncOptions options) {
|
||||
return syncJobTitles(Collections.singletonList(data), options);
|
||||
}
|
||||
|
||||
default BatchResult syncUser(IWorkHrUserPageRespVO.User data, SyncOptions options) {
|
||||
return syncUsers(Collections.singletonList(data), options);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
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);
|
||||
|
||||
/**
|
||||
* 仅同步部门
|
||||
*/
|
||||
IWorkFullSyncRespVO fullSyncDepartments(IWorkFullSyncReqVO reqVO);
|
||||
|
||||
/**
|
||||
* 仅同步分部
|
||||
*/
|
||||
IWorkFullSyncRespVO fullSyncSubcompanies(IWorkFullSyncReqVO reqVO);
|
||||
|
||||
/**
|
||||
* 仅同步岗位
|
||||
*/
|
||||
IWorkFullSyncRespVO fullSyncJobTitles(IWorkFullSyncReqVO reqVO);
|
||||
|
||||
/**
|
||||
* 仅同步人员(会自动包含依赖的分部、部门)
|
||||
*/
|
||||
IWorkFullSyncRespVO fullSyncUsers(IWorkFullSyncReqVO reqVO);
|
||||
|
||||
/**
|
||||
* 根据 iWork ID 进行单条同步
|
||||
*/
|
||||
IWorkSingleSyncRespVO syncSingle(IWorkSingleSyncReqVO reqVO);
|
||||
}
|
||||
@@ -6,9 +6,13 @@ import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.zt.plat.framework.common.exception.util.ServiceExceptionUtil;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkDepartmentQueryReqVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkHrDepartmentPageRespVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkHrJobTitlePageRespVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkHrSubcompanyPageRespVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkHrSyncRespVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkHrUserPageRespVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkJobTitleQueryReqVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkOrgBaseQueryReqVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkOrgRespVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkOrgSyncReqVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkSubcompanyQueryReqVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkUserQueryReqVO;
|
||||
@@ -30,8 +34,10 @@ import java.nio.charset.StandardCharsets;
|
||||
import java.time.Clock;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import static com.zt.plat.module.system.service.integration.iwork.IWorkIntegrationErrorCodeConstants.IWORK_BASE_URL_MISSING;
|
||||
import static com.zt.plat.module.system.service.integration.iwork.IWorkIntegrationErrorCodeConstants.IWORK_ORG_IDENTIFIER_MISSING;
|
||||
@@ -44,8 +50,21 @@ import static com.zt.plat.module.system.service.integration.iwork.IWorkIntegrati
|
||||
@Service
|
||||
public class IWorkOrgRestServiceImpl implements IWorkOrgRestService {
|
||||
|
||||
private static final TypeReference<Map<String, Object>> MAP_TYPE = new TypeReference<>() {
|
||||
};
|
||||
private static final TypeReference<List<IWorkHrSubcompanyPageRespVO.Subcompany>> SUBCOMPANY_LIST_TYPE =
|
||||
new TypeReference<>() {
|
||||
};
|
||||
private static final TypeReference<List<IWorkHrDepartmentPageRespVO.Department>> DEPARTMENT_LIST_TYPE =
|
||||
new TypeReference<>() {
|
||||
};
|
||||
private static final TypeReference<List<IWorkHrJobTitlePageRespVO.JobTitle>> JOB_TITLE_LIST_TYPE =
|
||||
new TypeReference<>() {
|
||||
};
|
||||
private static final TypeReference<List<IWorkHrUserPageRespVO.User>> USER_LIST_TYPE =
|
||||
new TypeReference<>() {
|
||||
};
|
||||
private static final TypeReference<List<IWorkHrSyncRespVO.SyncResult>> SYNC_RESULT_LIST_TYPE =
|
||||
new TypeReference<>() {
|
||||
};
|
||||
private static final okhttp3.MediaType JSON_MEDIA_TYPE = okhttp3.MediaType.get("application/json; charset=UTF-8");
|
||||
|
||||
private final IWorkProperties properties;
|
||||
@@ -69,7 +88,7 @@ public class IWorkOrgRestServiceImpl implements IWorkOrgRestService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public IWorkOrgRespVO listSubcompanies(IWorkSubcompanyQueryReqVO reqVO) {
|
||||
public IWorkHrSubcompanyPageRespVO listSubcompanies(IWorkSubcompanyQueryReqVO reqVO) {
|
||||
String path = orgPaths().getSubcompanyPage();
|
||||
Map<String, Object> params = buildBaseParams(reqVO);
|
||||
if (StringUtils.hasText(reqVO.getSubcompanyCode())) {
|
||||
@@ -78,11 +97,12 @@ public class IWorkOrgRestServiceImpl implements IWorkOrgRestService {
|
||||
if (StringUtils.hasText(reqVO.getSubcompanyName())) {
|
||||
params.put("subcompanyname", reqVO.getSubcompanyName());
|
||||
}
|
||||
return invokeParamsEndpoint(path, params);
|
||||
JsonNode node = invokeParamsEndpoint(path, params);
|
||||
return buildSubcompanyPageResp(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IWorkOrgRespVO listDepartments(IWorkDepartmentQueryReqVO reqVO) {
|
||||
public IWorkHrDepartmentPageRespVO listDepartments(IWorkDepartmentQueryReqVO reqVO) {
|
||||
String path = orgPaths().getDepartmentPage();
|
||||
Map<String, Object> params = buildBaseParams(reqVO);
|
||||
if (StringUtils.hasText(reqVO.getDepartmentCode())) {
|
||||
@@ -94,11 +114,12 @@ public class IWorkOrgRestServiceImpl implements IWorkOrgRestService {
|
||||
if (StringUtils.hasText(reqVO.getSubcompanyId())) {
|
||||
params.put("subcompanyid", reqVO.getSubcompanyId());
|
||||
}
|
||||
return invokeParamsEndpoint(path, params);
|
||||
JsonNode node = invokeParamsEndpoint(path, params);
|
||||
return buildDepartmentPageResp(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IWorkOrgRespVO listJobTitles(IWorkJobTitleQueryReqVO reqVO) {
|
||||
public IWorkHrJobTitlePageRespVO listJobTitles(IWorkJobTitleQueryReqVO reqVO) {
|
||||
String path = orgPaths().getJobTitlePage();
|
||||
Map<String, Object> params = buildBaseParams(reqVO);
|
||||
if (StringUtils.hasText(reqVO.getJobTitleCode())) {
|
||||
@@ -107,11 +128,12 @@ public class IWorkOrgRestServiceImpl implements IWorkOrgRestService {
|
||||
if (StringUtils.hasText(reqVO.getJobTitleName())) {
|
||||
params.put("jobtitlename", reqVO.getJobTitleName());
|
||||
}
|
||||
return invokeParamsEndpoint(path, params);
|
||||
JsonNode node = invokeParamsEndpoint(path, params);
|
||||
return buildJobTitlePageResp(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IWorkOrgRespVO listUsers(IWorkUserQueryReqVO reqVO) {
|
||||
public IWorkHrUserPageRespVO listUsers(IWorkUserQueryReqVO reqVO) {
|
||||
String path = orgPaths().getUserPage();
|
||||
Map<String, Object> params = buildBaseParams(reqVO);
|
||||
if (StringUtils.hasText(reqVO.getWorkCode())) {
|
||||
@@ -138,7 +160,8 @@ public class IWorkOrgRestServiceImpl implements IWorkOrgRestService {
|
||||
if (StringUtils.hasText(reqVO.getEmail())) {
|
||||
params.put("email", reqVO.getEmail());
|
||||
}
|
||||
return invokeParamsEndpoint(path, params);
|
||||
JsonNode node = invokeParamsEndpoint(path, params);
|
||||
return buildUserPageResp(node);
|
||||
}
|
||||
|
||||
private Map<String, Object> buildBaseParams(IWorkOrgBaseQueryReqVO reqVO) {
|
||||
@@ -156,47 +179,51 @@ public class IWorkOrgRestServiceImpl implements IWorkOrgRestService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public IWorkOrgRespVO syncSubcompanies(IWorkOrgSyncReqVO reqVO) {
|
||||
public IWorkHrSyncRespVO syncSubcompanies(IWorkOrgSyncReqVO reqVO) {
|
||||
String path = orgPaths().getSyncSubcompany();
|
||||
return invokeDataEndpoint(path, reqVO.getData());
|
||||
JsonNode node = invokeDataEndpoint(path, reqVO.getData());
|
||||
return buildSyncResp(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IWorkOrgRespVO syncDepartments(IWorkOrgSyncReqVO reqVO) {
|
||||
public IWorkHrSyncRespVO syncDepartments(IWorkOrgSyncReqVO reqVO) {
|
||||
String path = orgPaths().getSyncDepartment();
|
||||
return invokeDataEndpoint(path, reqVO.getData());
|
||||
JsonNode node = invokeDataEndpoint(path, reqVO.getData());
|
||||
return buildSyncResp(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IWorkOrgRespVO syncJobTitles(IWorkOrgSyncReqVO reqVO) {
|
||||
public IWorkHrSyncRespVO syncJobTitles(IWorkOrgSyncReqVO reqVO) {
|
||||
String path = orgPaths().getSyncJobTitle();
|
||||
return invokeDataEndpoint(path, reqVO.getData());
|
||||
JsonNode node = invokeDataEndpoint(path, reqVO.getData());
|
||||
return buildSyncResp(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IWorkOrgRespVO syncUsers(IWorkOrgSyncReqVO reqVO) {
|
||||
public IWorkHrSyncRespVO syncUsers(IWorkOrgSyncReqVO reqVO) {
|
||||
String path = orgPaths().getSyncUser();
|
||||
return invokeDataEndpoint(path, reqVO.getData());
|
||||
JsonNode node = invokeDataEndpoint(path, reqVO.getData());
|
||||
return buildSyncResp(node);
|
||||
}
|
||||
|
||||
private IWorkOrgRespVO invokeParamsEndpoint(String path, Map<String, Object> params) {
|
||||
private JsonNode invokeParamsEndpoint(String path, Map<String, Object> params) {
|
||||
Objects.requireNonNull(params, "查询参数不能为空");
|
||||
Map<String, Object> payload = new HashMap<>();
|
||||
payload.put("params", params == null ? Collections.emptyMap() : params);
|
||||
payload.put("params", params);
|
||||
return executeJson(path, payload);
|
||||
}
|
||||
|
||||
private IWorkOrgRespVO invokeDataEndpoint(String path, Object data) {
|
||||
private JsonNode invokeDataEndpoint(String path, Object data) {
|
||||
Objects.requireNonNull(data, "同步数据不能为空");
|
||||
Map<String, Object> payload = new HashMap<>();
|
||||
payload.put("data", data == null ? Collections.emptyMap() : data);
|
||||
payload.put("data", data);
|
||||
return executeJson(path, payload);
|
||||
}
|
||||
|
||||
private IWorkOrgRespVO executeJson(String path, Map<String, Object> payload) {
|
||||
private JsonNode executeJson(String path, Map<String, Object> payload) {
|
||||
// 统一封装请求体并发送 POST 调用
|
||||
assertOrgConfigured(path);
|
||||
Map<String, Object> body = new HashMap<>();
|
||||
if (payload != null && !payload.isEmpty()) {
|
||||
body.putAll(payload);
|
||||
}
|
||||
Map<String, Object> body = new HashMap<>(payload);
|
||||
body.put("token", buildTokenPayload());
|
||||
String jsonBody = toJson(body);
|
||||
|
||||
@@ -214,7 +241,10 @@ public class IWorkOrgRestServiceImpl implements IWorkOrgRestService {
|
||||
log.error("[iWork-Org] 调用 {} 失败,status={} body={}", path, response.code(), responseBody);
|
||||
throw ServiceExceptionUtil.exception(IWORK_ORG_REMOTE_FAILED, response.code(), responseBody);
|
||||
}
|
||||
return buildResponse(responseBody);
|
||||
if (!StringUtils.hasText(responseBody)) {
|
||||
throw ServiceExceptionUtil.exception(IWORK_ORG_REMOTE_FAILED, "iWork 响应为空");
|
||||
}
|
||||
return parseJson(responseBody);
|
||||
} catch (IOException ex) {
|
||||
log.error("[iWork-Org] 调用 {} 失败", path, ex);
|
||||
throw ServiceExceptionUtil.exception(IWORK_ORG_REMOTE_FAILED, ex.getMessage());
|
||||
@@ -222,6 +252,7 @@ public class IWorkOrgRestServiceImpl implements IWorkOrgRestService {
|
||||
}
|
||||
|
||||
private Map<String, String> buildTokenPayload() {
|
||||
// 按 iWork 约定生成 key + ts 结构的鉴权信息
|
||||
String tokenSeed = StringUtils.trimWhitespace(orgConfig().getTokenSeed());
|
||||
if (!StringUtils.hasText(tokenSeed)) {
|
||||
throw ServiceExceptionUtil.exception(IWORK_ORG_IDENTIFIER_MISSING);
|
||||
@@ -242,20 +273,110 @@ public class IWorkOrgRestServiceImpl implements IWorkOrgRestService {
|
||||
return hex.toUpperCase(Locale.ROOT);
|
||||
}
|
||||
|
||||
private IWorkOrgRespVO buildResponse(String responseBody) {
|
||||
// 统一解析 iWork 响应,兼容 data 节点和扁平结构
|
||||
IWorkOrgRespVO respVO = new IWorkOrgRespVO();
|
||||
respVO.setRawBody(responseBody);
|
||||
if (!StringUtils.hasText(responseBody)) {
|
||||
respVO.setPayload(Collections.emptyMap());
|
||||
// 解析并封装分部分页响应
|
||||
private IWorkHrSubcompanyPageRespVO buildSubcompanyPageResp(JsonNode node) {
|
||||
ParsedEnvelope envelope = parseEnvelope(node);
|
||||
IWorkHrSubcompanyPageRespVO respVO = new IWorkHrSubcompanyPageRespVO();
|
||||
respVO.setCode(envelope.code());
|
||||
respVO.setMessage(envelope.message());
|
||||
respVO.setSuccess(envelope.success());
|
||||
JsonNode dataNode = envelope.success() ? readNode(envelope.root(), "data") : null;
|
||||
if (dataNode == null) {
|
||||
respVO.setTotalSize(0);
|
||||
respVO.setTotalPage(0);
|
||||
respVO.setPageSize(0);
|
||||
respVO.setPageNumber(0);
|
||||
respVO.setDataList(Collections.emptyList());
|
||||
return respVO;
|
||||
}
|
||||
JsonNode node = parseJson(responseBody);
|
||||
respVO.setCode(textValue(node, "code"));
|
||||
respVO.setMessage(resolveMessage(node));
|
||||
respVO.setSuccess(isSuccess(node));
|
||||
JsonNode payloadNode = node.has("data") ? node.get("data") : node;
|
||||
respVO.setPayload(objectMapper.convertValue(payloadNode, MAP_TYPE));
|
||||
respVO.setTotalSize(readInt(dataNode, 0, "totalSize"));
|
||||
respVO.setTotalPage(readInt(dataNode, 0, "totalPage", "totalPageCount"));
|
||||
respVO.setPageSize(readInt(dataNode, 0, "pageSize", "pagesize"));
|
||||
respVO.setPageNumber(readInt(dataNode, 0, "pageNumber", "page", "curpage"));
|
||||
respVO.setDataList(readList(dataNode, "dataList", SUBCOMPANY_LIST_TYPE));
|
||||
return respVO;
|
||||
}
|
||||
|
||||
// 解析并封装部门分页响应
|
||||
private IWorkHrDepartmentPageRespVO buildDepartmentPageResp(JsonNode node) {
|
||||
ParsedEnvelope envelope = parseEnvelope(node);
|
||||
IWorkHrDepartmentPageRespVO respVO = new IWorkHrDepartmentPageRespVO();
|
||||
respVO.setCode(envelope.code());
|
||||
respVO.setMessage(envelope.message());
|
||||
respVO.setSuccess(envelope.success());
|
||||
JsonNode dataNode = envelope.success() ? readNode(envelope.root(), "data") : null;
|
||||
if (dataNode == null) {
|
||||
respVO.setTotalSize(0);
|
||||
respVO.setTotalPage(0);
|
||||
respVO.setPageSize(0);
|
||||
respVO.setPageNumber(0);
|
||||
respVO.setDataList(Collections.emptyList());
|
||||
return respVO;
|
||||
}
|
||||
respVO.setTotalSize(readInt(dataNode, 0, "totalSize"));
|
||||
respVO.setTotalPage(readInt(dataNode, 0, "totalPage", "totalPageCount"));
|
||||
respVO.setPageSize(readInt(dataNode, 0, "pageSize", "pagesize"));
|
||||
respVO.setPageNumber(readInt(dataNode, 0, "pageNumber", "page", "curpage"));
|
||||
respVO.setDataList(readList(dataNode, "dataList", DEPARTMENT_LIST_TYPE));
|
||||
return respVO;
|
||||
}
|
||||
|
||||
// 解析并封装岗位分页响应
|
||||
private IWorkHrJobTitlePageRespVO buildJobTitlePageResp(JsonNode node) {
|
||||
ParsedEnvelope envelope = parseEnvelope(node);
|
||||
IWorkHrJobTitlePageRespVO respVO = new IWorkHrJobTitlePageRespVO();
|
||||
respVO.setCode(envelope.code());
|
||||
respVO.setMessage(envelope.message());
|
||||
respVO.setSuccess(envelope.success());
|
||||
JsonNode dataNode = envelope.success() ? readNode(envelope.root(), "data") : null;
|
||||
if (dataNode == null) {
|
||||
respVO.setTotalSize(0);
|
||||
respVO.setTotalPage(0);
|
||||
respVO.setPageSize(0);
|
||||
respVO.setPageNumber(0);
|
||||
respVO.setDataList(Collections.emptyList());
|
||||
return respVO;
|
||||
}
|
||||
respVO.setTotalSize(readInt(dataNode, 0, "totalSize"));
|
||||
respVO.setTotalPage(readInt(dataNode, 0, "totalPage", "totalPageCount"));
|
||||
respVO.setPageSize(readInt(dataNode, 0, "pageSize", "pagesize"));
|
||||
respVO.setPageNumber(readInt(dataNode, 0, "pageNumber", "page", "curpage"));
|
||||
respVO.setDataList(readList(dataNode, "dataList", JOB_TITLE_LIST_TYPE));
|
||||
return respVO;
|
||||
}
|
||||
|
||||
// 解析并封装人员分页响应
|
||||
private IWorkHrUserPageRespVO buildUserPageResp(JsonNode node) {
|
||||
ParsedEnvelope envelope = parseEnvelope(node);
|
||||
IWorkHrUserPageRespVO respVO = new IWorkHrUserPageRespVO();
|
||||
respVO.setCode(envelope.code());
|
||||
respVO.setMessage(envelope.message());
|
||||
respVO.setSuccess(envelope.success());
|
||||
JsonNode dataNode = envelope.success() ? readNode(envelope.root(), "data") : null;
|
||||
if (dataNode == null) {
|
||||
respVO.setTotalSize(0);
|
||||
respVO.setTotalPage(0);
|
||||
respVO.setPageSize(0);
|
||||
respVO.setPageNumber(0);
|
||||
respVO.setDataList(Collections.emptyList());
|
||||
return respVO;
|
||||
}
|
||||
respVO.setTotalSize(readInt(dataNode, 0, "totalSize"));
|
||||
respVO.setTotalPage(readInt(dataNode, 0, "totalPage", "totalPageCount"));
|
||||
respVO.setPageSize(readInt(dataNode, 0, "pageSize", "pagesize"));
|
||||
respVO.setPageNumber(readInt(dataNode, 0, "pageNumber", "page", "curpage"));
|
||||
respVO.setDataList(readList(dataNode, "dataList", USER_LIST_TYPE));
|
||||
return respVO;
|
||||
}
|
||||
|
||||
// 解析并封装同步结果
|
||||
private IWorkHrSyncRespVO buildSyncResp(JsonNode node) {
|
||||
ParsedEnvelope envelope = parseEnvelope(node);
|
||||
IWorkHrSyncRespVO respVO = new IWorkHrSyncRespVO();
|
||||
respVO.setCode(envelope.code());
|
||||
respVO.setMessage(envelope.message());
|
||||
respVO.setSuccess(envelope.success());
|
||||
respVO.setResult(readList(envelope.root(), "result", SYNC_RESULT_LIST_TYPE));
|
||||
return respVO;
|
||||
}
|
||||
|
||||
@@ -268,34 +389,74 @@ public class IWorkOrgRestServiceImpl implements IWorkOrgRestService {
|
||||
}
|
||||
}
|
||||
|
||||
private String resolveMessage(JsonNode node) {
|
||||
private ParsedEnvelope parseEnvelope(JsonNode node) {
|
||||
// 校验 iWork 顶层响应并抽取公共字段
|
||||
if (node == null) {
|
||||
throw ServiceExceptionUtil.exception(IWORK_ORG_REMOTE_FAILED, "iWork 响应缺失");
|
||||
}
|
||||
String code = node.has("code") ? node.get("code").asText() : null;
|
||||
if (!StringUtils.hasText(code)) {
|
||||
throw ServiceExceptionUtil.exception(IWORK_ORG_REMOTE_FAILED, "iWork 响应缺少 code 字段");
|
||||
}
|
||||
String message = null;
|
||||
if (node.has("msg")) {
|
||||
message = node.get("msg").asText();
|
||||
} else if (node.has("message")) {
|
||||
message = node.get("message").asText();
|
||||
}
|
||||
boolean success = "1".equals(code);
|
||||
return new ParsedEnvelope(code, message, success, node);
|
||||
}
|
||||
|
||||
private JsonNode readNode(JsonNode parent, String field) {
|
||||
if (parent == null || !parent.has(field)) {
|
||||
return null;
|
||||
}
|
||||
if (node.has("msg")) {
|
||||
return node.get("msg").asText();
|
||||
}
|
||||
if (node.has("message")) {
|
||||
return node.get("message").asText();
|
||||
}
|
||||
return null;
|
||||
JsonNode value = parent.get(field);
|
||||
return value == null || value.isNull() ? null : value;
|
||||
}
|
||||
|
||||
private boolean isSuccess(JsonNode node) {
|
||||
if (node == null) {
|
||||
return false;
|
||||
private int readInt(JsonNode parent, int defaultValue, String... fieldNames) {
|
||||
if (parent == null || fieldNames == null) {
|
||||
return defaultValue;
|
||||
}
|
||||
if ("1".equals(textValue(node, "code"))) {
|
||||
return true;
|
||||
for (String field : fieldNames) {
|
||||
if (!StringUtils.hasText(field)) {
|
||||
continue;
|
||||
}
|
||||
JsonNode valueNode = readNode(parent, field);
|
||||
if (valueNode == null) {
|
||||
continue;
|
||||
}
|
||||
if (valueNode.isNumber()) {
|
||||
return valueNode.intValue();
|
||||
}
|
||||
if (valueNode.isTextual()) {
|
||||
try {
|
||||
return Integer.parseInt(valueNode.asText());
|
||||
} catch (NumberFormatException ex) {
|
||||
log.warn("[iWork-Org] 字段格式非数值,使用默认值: {}", field);
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
log.warn("[iWork-Org] 字段类型非整数,使用默认值: {}", field);
|
||||
return defaultValue;
|
||||
}
|
||||
if ("1".equals(textValue(node, "status"))) {
|
||||
return true;
|
||||
}
|
||||
return "1".equals(textValue(node, "success"));
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
private String textValue(JsonNode node, String field) {
|
||||
return node != null && node.has(field) ? node.get(field).asText() : null;
|
||||
private <T> List<T> readList(JsonNode parent, String field, TypeReference<List<T>> typeReference) {
|
||||
JsonNode arrayNode = readNode(parent, field);
|
||||
if (arrayNode == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
if (!arrayNode.isArray()) {
|
||||
log.warn("[iWork-Org] 字段应为数组但实际不是,尝试转换: {}", field);
|
||||
}
|
||||
return objectMapper.convertValue(arrayNode, typeReference);
|
||||
}
|
||||
|
||||
private record ParsedEnvelope(String code, String message, boolean success, JsonNode root) {
|
||||
}
|
||||
|
||||
private void assertOrgConfigured(String path) {
|
||||
@@ -318,7 +479,7 @@ public class IWorkOrgRestServiceImpl implements IWorkOrgRestService {
|
||||
|
||||
private String toJson(Object payload) {
|
||||
try {
|
||||
return objectMapper.writeValueAsString(payload == null ? Collections.emptyMap() : payload);
|
||||
return objectMapper.writeValueAsString(payload);
|
||||
} catch (JsonProcessingException ex) {
|
||||
throw ServiceExceptionUtil.exception(IWORK_ORG_REMOTE_FAILED, "序列化 JSON 失败: " + ex.getMessage());
|
||||
}
|
||||
|
||||
@@ -0,0 +1,727 @@
|
||||
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.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.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.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.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class IWorkSyncProcessorImpl implements IWorkSyncProcessor {
|
||||
|
||||
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 PostService postService;
|
||||
private final PostMapper postMapper;
|
||||
private final AdminUserService adminUserService;
|
||||
private final AdminUserMapper adminUserMapper;
|
||||
|
||||
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)) {
|
||||
logSkip("分部", sub.getId(), "iWork 标记为失效且当前不同步失效记录");
|
||||
result.increaseSkipped();
|
||||
iterator.remove();
|
||||
continue;
|
||||
}
|
||||
Integer externalId = sub.getId();
|
||||
if (externalId == null) {
|
||||
log.warn("[iWork] 分部缺少标识,跳过:{}", sub.getSubcompanyname());
|
||||
result.increaseFailed();
|
||||
iterator.remove();
|
||||
continue;
|
||||
}
|
||||
Long deptId = externalId.longValue();
|
||||
ParentHolder parentHolder = resolveSubcompanyParent(sub.getSupsubcomid());
|
||||
boolean canceled = isCanceledFlag(sub.getCanceled());
|
||||
DeptSaveReqVO saveReq = buildSubcompanySaveReq(sub, deptId, parentHolder.parentId(), canceled);
|
||||
try {
|
||||
DeptSyncOutcome outcome = upsertDept(deptId,
|
||||
saveReq,
|
||||
canceled,
|
||||
options);
|
||||
applyDeptOutcome(result, outcome, "分部", sub.getSubcompanyname());
|
||||
} catch (Exception ex) {
|
||||
log.error("[iWork] 同步分部失败: id={} name={}", sub.getId(), 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.getId(), 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)) {
|
||||
logSkip("部门", dept.getId(), "iWork 标记为失效且当前不同步失效记录");
|
||||
result.increaseSkipped();
|
||||
iterator.remove();
|
||||
continue;
|
||||
}
|
||||
Integer externalId = dept.getId();
|
||||
if (externalId == null) {
|
||||
log.warn("[iWork] 部门缺少标识,跳过:{}", dept.getDepartmentname());
|
||||
result.increaseFailed();
|
||||
iterator.remove();
|
||||
continue;
|
||||
}
|
||||
Long deptId = externalId.longValue();
|
||||
ParentHolder parentHolder = resolveDepartmentParent(dept);
|
||||
boolean canceled = isCanceledFlag(dept.getCanceled());
|
||||
DeptSaveReqVO saveReq = buildDepartmentSaveReq(dept, deptId, parentHolder.parentId(), canceled);
|
||||
try {
|
||||
DeptSyncOutcome outcome = upsertDept(deptId,
|
||||
saveReq,
|
||||
canceled,
|
||||
options);
|
||||
applyDeptOutcome(result, outcome, "部门", dept.getDepartmentname());
|
||||
} catch (Exception ex) {
|
||||
log.error("[iWork] 同步部门失败: id={} name={}", dept.getId(), 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.getId(), 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)) {
|
||||
logSkip("岗位", job.getId(), "iWork 标记为失效且当前不同步失效记录");
|
||||
result.increaseSkipped();
|
||||
continue;
|
||||
}
|
||||
if (job.getId() == null) {
|
||||
log.warn("[iWork] 岗位缺少标识,跳过:{}", job.getJobtitlename());
|
||||
result.increaseFailed();
|
||||
continue;
|
||||
}
|
||||
boolean canceled = isCanceledFlag(job.getCanceled());
|
||||
Integer status = toStatus(canceled);
|
||||
String code = buildJobCode(job.getId());
|
||||
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.getId(), 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());
|
||||
String username = resolveUsername(user);
|
||||
if (StrUtil.isBlank(username)) {
|
||||
log.warn("[iWork] 人员缺少可用账号(工号={}, 登录账号={}),跳过:id={} name={}",
|
||||
user.getWorkcode(), user.getLoginid(), 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());
|
||||
}
|
||||
Long postId = resolveUserPostId(user);
|
||||
CommonStatusEnum status = inactive ? CommonStatusEnum.DISABLE : CommonStatusEnum.ENABLE;
|
||||
AdminUserDO existing = adminUserMapper.selectByUsername(username);
|
||||
UserSyncOutcome outcome;
|
||||
if (existing == null) {
|
||||
if (!options.isCreateIfMissing()) {
|
||||
logSkip("人员", username, "系统未找到该账号且不同步缺失用户");
|
||||
result.increaseSkipped();
|
||||
continue;
|
||||
}
|
||||
outcome = createUser(user, username, deptId, postId, status);
|
||||
} else {
|
||||
if (!Objects.equals(existing.getUserSource(), UserSourceEnum.IWORK.getSource())) {
|
||||
logSkip("人员", existing.getId(), "非 iWork 来源用户,保持原状");
|
||||
result.increaseSkipped();
|
||||
continue;
|
||||
}
|
||||
if (!options.isAllowUpdate()) {
|
||||
logSkip("人员", existing.getId(), "当前策略禁止更新已存在用户");
|
||||
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(Long deptId,
|
||||
DeptSaveReqVO desired,
|
||||
boolean disabled,
|
||||
SyncOptions options) {
|
||||
if (deptId == null) {
|
||||
return new DeptSyncOutcome(SyncAction.SKIPPED, false, null);
|
||||
}
|
||||
DeptDO existing = deptService.getDept(deptId);
|
||||
if (existing == null) {
|
||||
if (!options.isCreateIfMissing()) {
|
||||
logSkip("部门", deptId, "当前策略禁止创建缺失部门");
|
||||
return new DeptSyncOutcome(SyncAction.SKIPPED, false, null);
|
||||
}
|
||||
desired.setId(deptId);
|
||||
Long createdId = deptService.createDept(desired);
|
||||
return new DeptSyncOutcome(SyncAction.CREATED, CommonStatusEnum.isDisable(desired.getStatus()), createdId);
|
||||
}
|
||||
if (!Objects.equals(existing.getDeptSource(), DeptSourceEnum.IWORK.getSource())) {
|
||||
logSkip("部门", existing.getId(), "来源非 iWork,保持原状");
|
||||
return new DeptSyncOutcome(SyncAction.SKIPPED, false, existing.getId());
|
||||
}
|
||||
if (!options.isAllowUpdate()) {
|
||||
logSkip("部门", existing.getId(), "当前策略禁止更新已存在部门");
|
||||
return new DeptSyncOutcome(SyncAction.SKIPPED, false, existing.getId());
|
||||
}
|
||||
desired.setId(existing.getId());
|
||||
mergeDeptDefaults(desired, existing);
|
||||
boolean disabledChanged = disabled && CommonStatusEnum.isEnable(existing.getStatus());
|
||||
deptService.updateDept(desired);
|
||||
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()) {
|
||||
logSkip("岗位", job.getId(), "当前策略禁止创建缺失岗位");
|
||||
return new JobSyncOutcome(SyncAction.SKIPPED, false, null);
|
||||
}
|
||||
PostSaveReqVO createReq = new PostSaveReqVO();
|
||||
Long desiredId = job.getId() == null ? null : job.getId().longValue();
|
||||
if (desiredId != null) {
|
||||
createReq.setId(desiredId);
|
||||
}
|
||||
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);
|
||||
Long effectivePostId = desiredId != null ? desiredId : postId;
|
||||
PostDO created = new PostDO();
|
||||
created.setId(effectivePostId);
|
||||
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, effectivePostId);
|
||||
}
|
||||
if (!options.isAllowUpdate()) {
|
||||
logSkip("岗位", existing.getId(), "当前策略禁止更新已存在岗位");
|
||||
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);
|
||||
Long desiredUserId = source.getId() == null ? null : source.getId().longValue();
|
||||
if (desiredUserId != null) {
|
||||
req.setId(desiredUserId);
|
||||
}
|
||||
req.setPassword(DEFAULT_USER_PASSWORD);
|
||||
req.setUserSource(UserSourceEnum.IWORK.getSource());
|
||||
Long userId = adminUserService.createUser(req);
|
||||
Long effectiveUserId = desiredUserId != null ? desiredUserId : userId;
|
||||
return new UserSyncOutcome(SyncAction.CREATED, CommonStatusEnum.isDisable(req.getStatus()), effectiveUserId);
|
||||
}
|
||||
|
||||
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 deptId,
|
||||
Long parentId,
|
||||
boolean canceled) {
|
||||
DeptSaveReqVO req = new DeptSaveReqVO();
|
||||
req.setId(deptId);
|
||||
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 deptId,
|
||||
Long parentId,
|
||||
boolean canceled) {
|
||||
DeptSaveReqVO req = new DeptSaveReqVO();
|
||||
req.setId(deptId);
|
||||
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));
|
||||
if (deptId != null) {
|
||||
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());
|
||||
req.setSkipAssociationValidation(true);
|
||||
req.setSkipMobileValidation(true);
|
||||
req.setSkipEmailValidation(true);
|
||||
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);
|
||||
}
|
||||
return new ParentHolder(parentExternalId.longValue());
|
||||
}
|
||||
|
||||
private ParentHolder resolveDepartmentParent(IWorkHrDepartmentPageRespVO.Department dept) {
|
||||
Long parentDeptId = toLong(dept.getSupdepid());
|
||||
if (parentDeptId != null && parentDeptId > 0) {
|
||||
return new ParentHolder(parentDeptId);
|
||||
}
|
||||
Long subcompanyId = toLong(dept.getSubcompanyid1());
|
||||
if (subcompanyId != null) {
|
||||
return new ParentHolder(subcompanyId);
|
||||
}
|
||||
return new ParentHolder(DeptDO.PARENT_ID_ROOT);
|
||||
}
|
||||
|
||||
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) {
|
||||
Long deptId = toLong(user.getDepartmentid());
|
||||
if (deptId != null) {
|
||||
return deptId;
|
||||
}
|
||||
Long subcompanyId = toLong(user.getSubcompanyid1());
|
||||
if (subcompanyId != null) {
|
||||
return subcompanyId;
|
||||
}
|
||||
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) {
|
||||
if (StrUtil.isBlank(sexFlag)) {
|
||||
return SexEnum.UNKNOWN.getSex();
|
||||
}
|
||||
Integer external = parseInteger(sexFlag);
|
||||
if (external != null) {
|
||||
Integer converted = SyncVerifyUtil.convertExternalToInternal(external);
|
||||
return converted != null ? converted : SexEnum.UNKNOWN.getSex();
|
||||
}
|
||||
String normalized = sexFlag.trim();
|
||||
if (isMaleFlag(normalized)) {
|
||||
return SexEnum.MALE.getSex();
|
||||
}
|
||||
if (isFemaleFlag(normalized)) {
|
||||
return SexEnum.FEMALE.getSex();
|
||||
}
|
||||
return SexEnum.UNKNOWN.getSex();
|
||||
}
|
||||
|
||||
private boolean isMaleFlag(String value) {
|
||||
return "男".equals(value) || "M".equalsIgnoreCase(value) || "MALE".equalsIgnoreCase(value);
|
||||
}
|
||||
|
||||
private boolean isFemaleFlag(String value) {
|
||||
return "女".equals(value) || "F".equalsIgnoreCase(value) || "FEMALE".equalsIgnoreCase(value);
|
||||
}
|
||||
|
||||
private Integer parseInteger(String raw) {
|
||||
if (StrUtil.isBlank(raw)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return Integer.parseInt(raw.trim());
|
||||
} catch (NumberFormatException ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private Long toLong(Integer value) {
|
||||
return value == null ? null : value.longValue();
|
||||
}
|
||||
|
||||
private String resolveUsername(IWorkHrUserPageRespVO.User user) {
|
||||
String candidate = sanitizeUsername(user.getWorkcode());
|
||||
if (candidate == null) {
|
||||
candidate = sanitizeUsername(user.getLoginid());
|
||||
}
|
||||
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 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 void logSkip(String entityLabel, Object identifier, String reason) {
|
||||
if (log.isInfoEnabled()) {
|
||||
log.info("[iWork] {}[id={}] 跳过:{}", entityLabel, identifier, reason);
|
||||
}
|
||||
}
|
||||
|
||||
private record ParentHolder(Long parentId) {
|
||||
}
|
||||
|
||||
private enum SyncAction {
|
||||
CREATED,
|
||||
UPDATED,
|
||||
SKIPPED
|
||||
}
|
||||
|
||||
private record DeptSyncOutcome(SyncAction action, boolean disabled, Long deptId) {
|
||||
}
|
||||
|
||||
private record JobSyncOutcome(SyncAction action, boolean disabled, Long postId) {
|
||||
}
|
||||
|
||||
private record UserSyncOutcome(SyncAction action, boolean disabled, Long userId) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,316 @@
|
||||
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.exception.util.ServiceExceptionUtil;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.*;
|
||||
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.*;
|
||||
|
||||
import static com.zt.plat.module.system.service.integration.iwork.IWorkIntegrationErrorCodeConstants.IWORK_ORG_REMOTE_FAILED;
|
||||
|
||||
/**
|
||||
* iWork 同步服务实现
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class IWorkSyncServiceImpl implements IWorkSyncService {
|
||||
|
||||
private final IWorkOrgRestService orgRestService;
|
||||
private final IWorkSyncProcessor syncProcessor;
|
||||
|
||||
@Override
|
||||
public IWorkFullSyncRespVO fullSync(IWorkFullSyncReqVO reqVO) {
|
||||
return runFullSync(reqVO, reqVO.resolveScopes());
|
||||
}
|
||||
|
||||
@Override
|
||||
public IWorkFullSyncRespVO fullSyncDepartments(IWorkFullSyncReqVO reqVO) {
|
||||
return runFullSync(reqVO, EnumSet.of(IWorkSyncEntityTypeEnum.DEPARTMENT));
|
||||
}
|
||||
|
||||
@Override
|
||||
public IWorkFullSyncRespVO fullSyncSubcompanies(IWorkFullSyncReqVO reqVO) {
|
||||
return runFullSync(reqVO, EnumSet.of(IWorkSyncEntityTypeEnum.SUBCOMPANY));
|
||||
}
|
||||
|
||||
@Override
|
||||
public IWorkFullSyncRespVO fullSyncJobTitles(IWorkFullSyncReqVO reqVO) {
|
||||
return runFullSync(reqVO, EnumSet.of(IWorkSyncEntityTypeEnum.JOB_TITLE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public IWorkFullSyncRespVO fullSyncUsers(IWorkFullSyncReqVO reqVO) {
|
||||
return runFullSync(reqVO, EnumSet.of(IWorkSyncEntityTypeEnum.USER));
|
||||
}
|
||||
|
||||
private IWorkFullSyncRespVO runFullSync(IWorkFullSyncReqVO reqVO, Set<IWorkSyncEntityTypeEnum> scopes) {
|
||||
IWorkFullSyncRespVO respVO = new IWorkFullSyncRespVO();
|
||||
respVO.setPageSize(reqVO.getPageSize());
|
||||
List<IWorkSyncBatchStatVO> batchStats = new ArrayList<>();
|
||||
respVO.setBatches(batchStats);
|
||||
|
||||
boolean syncUsers = scopes.contains(IWorkSyncEntityTypeEnum.USER);
|
||||
boolean syncDepartments = scopes.contains(IWorkSyncEntityTypeEnum.DEPARTMENT);
|
||||
boolean syncSubcompanies = scopes.contains(IWorkSyncEntityTypeEnum.SUBCOMPANY);
|
||||
boolean syncJobTitle = scopes.contains(IWorkSyncEntityTypeEnum.JOB_TITLE);
|
||||
int processedPages = 0;
|
||||
IWorkSyncProcessor.SyncOptions options = buildFullSyncOptions(reqVO);
|
||||
if (syncSubcompanies) {
|
||||
processedPages += executeSubcompanyFullSync(reqVO, options, respVO.getSubcompanyStat(), batchStats);
|
||||
}
|
||||
if (syncDepartments) {
|
||||
processedPages += executeDepartmentFullSync(reqVO, options, respVO.getDepartmentStat(), batchStats);
|
||||
}
|
||||
if (syncJobTitle) {
|
||||
processedPages += executeJobTitleFullSync(reqVO, options, respVO.getJobTitleStat(), batchStats);
|
||||
}
|
||||
if (syncUsers) {
|
||||
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);
|
||||
ensureIWorkSuccess("拉取分部", pageResp.isSuccess(), pageResp.getMessage());
|
||||
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);
|
||||
ensureIWorkSuccess("拉取部门", pageResp.isSuccess(), pageResp.getMessage());
|
||||
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);
|
||||
ensureIWorkSuccess("拉取岗位", pageResp.isSuccess(), pageResp.getMessage());
|
||||
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);
|
||||
ensureIWorkSuccess("拉取人员", pageResp.isSuccess(), pageResp.getMessage());
|
||||
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().toString());
|
||||
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);
|
||||
ensureIWorkSuccess("获取分部详情", pageResp.isSuccess(), pageResp.getMessage());
|
||||
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);
|
||||
ensureIWorkSuccess("获取部门详情", pageResp.isSuccess(), pageResp.getMessage());
|
||||
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);
|
||||
ensureIWorkSuccess("获取岗位详情", pageResp.isSuccess(), pageResp.getMessage());
|
||||
return CollUtil.getFirst(pageResp.getDataList());
|
||||
}
|
||||
|
||||
private IWorkHrUserPageRespVO.User fetchSingleUser(String entityId) {
|
||||
IWorkUserQueryReqVO query = new IWorkUserQueryReqVO();
|
||||
query.setCurpage(1);
|
||||
query.setPagesize(1);
|
||||
query.setParams(Collections.singletonMap("id", entityId));
|
||||
IWorkHrUserPageRespVO pageResp = orgRestService.listUsers(query);
|
||||
ensureIWorkSuccess("获取人员详情", pageResp.isSuccess(), pageResp.getMessage());
|
||||
return CollUtil.getFirst(pageResp.getDataList());
|
||||
}
|
||||
|
||||
private void ensureIWorkSuccess(String action, boolean success, String remoteMessage) {
|
||||
if (success) {
|
||||
return;
|
||||
}
|
||||
String message = StrUtil.blankToDefault(remoteMessage, StrUtil.format("{}:iWork 返回失败", action));
|
||||
throw ServiceExceptionUtil.exception(IWORK_ORG_REMOTE_FAILED, message);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
private interface PageExecutor {
|
||||
BatchExecution execute(int pageNumber, int pageSize);
|
||||
}
|
||||
|
||||
private record BatchExecution(IWorkSyncProcessor.BatchResult result, int totalPulled) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.zt.plat.module.system.service.integration.iwork.jackson;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.core.JsonToken;
|
||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* Jackson 反序列化器,允许将诸如 "0.0"、"5" 等字符串形式的数字解析为整数,
|
||||
* 用于兼容 iWork 返回的非标准整型字段。
|
||||
*/
|
||||
public class LenientIntegerDeserializer extends JsonDeserializer<Integer> {
|
||||
|
||||
@Override
|
||||
public Integer deserialize(JsonParser parser, DeserializationContext ctxt) throws IOException {
|
||||
JsonToken token = parser.currentToken();
|
||||
if (token == null) {
|
||||
token = parser.nextToken();
|
||||
}
|
||||
if (token == null) {
|
||||
return null;
|
||||
}
|
||||
if (token.isNumeric()) {
|
||||
return parser.getNumberValue().intValue();
|
||||
}
|
||||
if (token == JsonToken.VALUE_STRING) {
|
||||
String text = parser.getText();
|
||||
if (text == null) {
|
||||
return null;
|
||||
}
|
||||
String trimmed = text.trim();
|
||||
if (trimmed.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
if (trimmed.contains(".")) {
|
||||
BigDecimal decimal = new BigDecimal(trimmed);
|
||||
return decimal.intValue();
|
||||
}
|
||||
return Integer.parseInt(trimmed);
|
||||
} catch (NumberFormatException ex) {
|
||||
return (Integer) ctxt.handleWeirdStringValue(Integer.class, trimmed,
|
||||
"无法将文本转换为整数");
|
||||
}
|
||||
}
|
||||
if (token == JsonToken.VALUE_NULL) {
|
||||
return null;
|
||||
}
|
||||
return (Integer) ctxt.handleUnexpectedToken(Integer.class, parser);
|
||||
}
|
||||
}
|
||||
@@ -94,6 +94,7 @@ public class SmsSendServiceImpl implements SmsSendService {
|
||||
Boolean isSend = CommonStatusEnum.ENABLE.getStatus().equals(template.getStatus())
|
||||
&& CommonStatusEnum.ENABLE.getStatus().equals(smsChannel.getStatus());
|
||||
String content = smsTemplateService.formatSmsTemplateContent(template.getContent(), templateParams);
|
||||
|
||||
Long sendLogId = smsLogService.createSmsLog(mobile, userId, userType, isSend, template, content, templateParams);
|
||||
|
||||
// 发送 MQ 消息,异步执行发送短信
|
||||
@@ -183,7 +184,6 @@ public class SmsSendServiceImpl implements SmsSendService {
|
||||
if (CollUtil.isEmpty(receiveResults)) {
|
||||
return;
|
||||
}
|
||||
// 更新短信日志的接收结果. 因为量一般不大,所以先使用 for 循环更新
|
||||
receiveResults.forEach(result -> smsLogService.updateSmsReceiveResult(result.getLogId(),
|
||||
result.getSuccess(), result.getReceiveTime(), result.getErrorCode(), result.getErrorMsg()));
|
||||
}
|
||||
|
||||
@@ -108,7 +108,8 @@ public class AdminUserServiceImpl implements AdminUserService {
|
||||
});
|
||||
// 1.2 校验正确性
|
||||
validateUserForCreateOrUpdate(null, createReqVO.getUsername(),
|
||||
createReqVO.getMobile(), createReqVO.getEmail(), createReqVO.getDeptIds(), createReqVO.getPostIds());
|
||||
createReqVO.getMobile(), createReqVO.getEmail(), createReqVO.getDeptIds(), createReqVO.getPostIds(),
|
||||
createReqVO.isSkipAssociationValidation(), createReqVO.isSkipMobileValidation(), createReqVO.isSkipEmailValidation());
|
||||
// 2.1 插入用户
|
||||
AdminUserDO user = BeanUtils.toBean(createReqVO, AdminUserDO.class);
|
||||
user.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||
@@ -154,7 +155,7 @@ public class AdminUserServiceImpl implements AdminUserService {
|
||||
}
|
||||
});
|
||||
// 1.3 校验正确性
|
||||
validateUserForCreateOrUpdate(null, registerReqVO.getUsername(), null, null, null, null);
|
||||
validateUserForCreateOrUpdate(null, registerReqVO.getUsername(), null, null, null, null, false, false, false);
|
||||
|
||||
// 2. 插入用户
|
||||
AdminUserDO user = BeanUtils.toBean(registerReqVO, AdminUserDO.class);
|
||||
@@ -173,7 +174,8 @@ public class AdminUserServiceImpl implements AdminUserService {
|
||||
updateReqVO.setPassword(null); // 特殊:此处不更新密码
|
||||
// 1. 校验正确性
|
||||
AdminUserDO oldUser = validateUserForCreateOrUpdate(updateReqVO.getId(), updateReqVO.getUsername(),
|
||||
updateReqVO.getMobile(), updateReqVO.getEmail(), updateReqVO.getDeptIds(), updateReqVO.getPostIds());
|
||||
updateReqVO.getMobile(), updateReqVO.getEmail(), updateReqVO.getDeptIds(),
|
||||
updateReqVO.getPostIds(), updateReqVO.isSkipAssociationValidation(), updateReqVO.isSkipMobileValidation(), updateReqVO.isSkipEmailValidation());
|
||||
|
||||
// 2.1 只更新非空字段
|
||||
AdminUserDO updateObj = new AdminUserDO();
|
||||
@@ -512,7 +514,8 @@ public class AdminUserServiceImpl implements AdminUserService {
|
||||
}
|
||||
|
||||
private AdminUserDO validateUserForCreateOrUpdate(Long id, String username, String mobile, String email,
|
||||
Set<Long> deptIds, Set<Long> postIds) {
|
||||
Set<Long> deptIds, Set<Long> postIds, boolean skipAssociationValidation,
|
||||
boolean skipMobileValidation, boolean skipEmailValidation) {
|
||||
// 关闭数据权限,避免因为没有数据权限,查询不到数据,进而导致唯一校验不正确
|
||||
return DataPermissionUtils.executeIgnore(() -> {
|
||||
// 校验用户存在
|
||||
@@ -520,13 +523,17 @@ public class AdminUserServiceImpl implements AdminUserService {
|
||||
// 校验用户名唯一 - 注释掉,允许用户名重复
|
||||
// validateUsernameUnique(id, username);
|
||||
// 校验手机号唯一
|
||||
validateMobileUnique(id, mobile);
|
||||
if (!skipMobileValidation) {
|
||||
validateMobileUnique(id, mobile);
|
||||
}
|
||||
// 校验邮箱唯一
|
||||
validateEmailUnique(id, email);
|
||||
// 校验部门处于开启状态
|
||||
deptService.validateDeptList(deptIds);
|
||||
if (!skipEmailValidation) {
|
||||
validateEmailUnique(id, email);
|
||||
}
|
||||
// 校验岗位处于开启状态
|
||||
postService.validatePostList(postIds);
|
||||
if (!skipAssociationValidation) {
|
||||
postService.validatePostList(postIds);
|
||||
}
|
||||
return user;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
<!-- 启动服务时,是否清理历史日志,一般不建议清理 -->
|
||||
<cleanHistoryOnStart>${LOGBACK_ROLLINGPOLICY_CLEAN_HISTORY_ON_START:-false}</cleanHistoryOnStart>
|
||||
<!-- 日志文件,到达多少容量,进行滚动 -->
|
||||
<maxFileSize>${LOGBACK_ROLLINGPOLICY_MAX_FILE_SIZE:-10MB}</maxFileSize>
|
||||
<maxFileSize>${LOGBACK_ROLLINGPOLICY_MAX_FILE_SIZE:-50MB}</maxFileSize>
|
||||
<!-- 日志文件的总大小,0 表示不限制 -->
|
||||
<totalSizeCap>${LOGBACK_ROLLINGPOLICY_TOTAL_SIZE_CAP:-0}</totalSizeCap>
|
||||
<!-- 日志文件的保留天数 -->
|
||||
@@ -56,14 +56,21 @@
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!--logback的日志级别 FATAL > ERROR > WARN > INFO > DEBUG-->
|
||||
<!-- 本地环境 -->
|
||||
<springProfile name="local">
|
||||
<root level="INFO">
|
||||
<springProfile name="local,dev">
|
||||
<root level="WARN">
|
||||
<appender-ref ref="STDOUT"/>
|
||||
<appender-ref ref="GRPC"/> <!-- 本地环境下,如果不想接入 SkyWalking 日志服务,可以注释掉本行 -->
|
||||
<appender-ref ref="ASYNC"/> <!-- 本地环境下,如果不想打印日志,可以注释掉本行 -->
|
||||
</root>
|
||||
|
||||
<!--针对不同的业务路径,配置dao层的sql打印日志级别为DEBUG-->
|
||||
<logger name="com.zt.plat.module.system.dal.mysql" level="DEBUG" additivity="false">
|
||||
<appender-ref ref="STDOUT"/>
|
||||
</logger>
|
||||
</springProfile>
|
||||
|
||||
<!-- 其它环境 -->
|
||||
<springProfile name="dev,test,stage,prod,default">
|
||||
<root level="INFO">
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package com.zt.plat.module.system.service.integration.iwork.impl;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkOrgQueryReqVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkOrgRespVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkHrSubcompanyPageRespVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkHrSyncRespVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkOrgSyncReqVO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkSubcompanyQueryReqVO;
|
||||
import com.zt.plat.module.system.framework.integration.iwork.config.IWorkProperties;
|
||||
import com.zt.plat.module.system.service.integration.iwork.IWorkOrgRestService;
|
||||
import okhttp3.mockwebserver.MockResponse;
|
||||
@@ -20,7 +22,6 @@ import java.time.Clock;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -32,6 +33,7 @@ class IWorkOrgRestServiceImplTest {
|
||||
private IWorkOrgRestService service;
|
||||
private IWorkProperties properties;
|
||||
private Clock fixedClock;
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws Exception {
|
||||
@@ -40,7 +42,8 @@ class IWorkOrgRestServiceImplTest {
|
||||
|
||||
properties = buildProperties();
|
||||
fixedClock = Clock.fixed(Instant.ofEpochMilli(1_672_531_200_000L), ZoneOffset.UTC);
|
||||
service = new IWorkOrgRestServiceImpl(properties, new ObjectMapper(), fixedClock);
|
||||
objectMapper = new ObjectMapper();
|
||||
service = new IWorkOrgRestServiceImpl(properties, objectMapper, fixedClock);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
@@ -50,41 +53,46 @@ class IWorkOrgRestServiceImplTest {
|
||||
|
||||
@Test
|
||||
void shouldListSubcompanies() throws Exception {
|
||||
mockWebServer.enqueue(jsonResponse("{\"code\":\"1\",\"data\":{\"page\":1}}"));
|
||||
mockWebServer.enqueue(jsonResponse("{\"code\":\"1\",\"data\":{\"totalSize\":1,\"totalPage\":1,\"pageSize\":10,\"pageNumber\":1,\"dataList\":[{\"subcompanyid1\":4,\"subcompanyname\":\"总部\"}]}}"));
|
||||
|
||||
IWorkOrgQueryReqVO reqVO = new IWorkOrgQueryReqVO();
|
||||
IWorkSubcompanyQueryReqVO reqVO = new IWorkSubcompanyQueryReqVO();
|
||||
reqVO.setParams(Map.of("curpage", 1));
|
||||
IWorkOrgRespVO respVO = service.listSubcompanies(reqVO);
|
||||
IWorkHrSubcompanyPageRespVO respVO = service.listSubcompanies(reqVO);
|
||||
|
||||
assertThat(respVO.isSuccess()).isTrue();
|
||||
assertThat(respVO.getPayload()).containsEntry("page", 1);
|
||||
assertThat(respVO.getTotalSize()).isEqualTo(1);
|
||||
assertThat(respVO.getDataList()).hasSize(1);
|
||||
assertThat(respVO.getDataList().get(0).getSubcompanyname()).isEqualTo("总部");
|
||||
|
||||
RecordedRequest request = mockWebServer.takeRequest();
|
||||
assertThat(request.getPath()).isEqualTo(properties.getOrg().getPaths().getSubcompanyPage());
|
||||
String decoded = URLDecoder.decode(request.getBody().readUtf8(), StandardCharsets.UTF_8);
|
||||
assertThat(decoded).contains("params={\"curpage\":1}");
|
||||
String tokenJson = extractField(decoded, "token");
|
||||
assertThat(tokenJson).isNotBlank();
|
||||
assertThat(tokenJson).contains("\"ts\":\"1672531200000\"");
|
||||
String expectedKey = DigestUtils.md5DigestAsHex("test-seed1672531200000".getBytes(StandardCharsets.UTF_8)).toUpperCase();
|
||||
assertThat(tokenJson).contains("\"key\":\"" + expectedKey + "\"");
|
||||
JsonNode bodyNode = objectMapper.readTree(decoded);
|
||||
assertThat(bodyNode.path("params").path("curpage").asInt()).isEqualTo(1);
|
||||
JsonNode tokenNode = bodyNode.path("token");
|
||||
assertThat(tokenNode.path("ts").asText()).isEqualTo("1672531200000");
|
||||
String expectedKey = DigestUtils.md5DigestAsHex("test-seed1672531200000".getBytes(StandardCharsets.UTF_8)).toUpperCase();
|
||||
assertThat(tokenNode.path("key").asText()).isEqualTo(expectedKey);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSyncDepartments() throws Exception {
|
||||
mockWebServer.enqueue(jsonResponse("{\"code\":\"1\",\"result\":{}}"));
|
||||
mockWebServer.enqueue(jsonResponse("{\"code\":\"1\",\"result\":[{\"@action\":\"add\",\"code\":\"demo\",\"result\":\"success\"}]}"));
|
||||
|
||||
IWorkOrgSyncReqVO reqVO = new IWorkOrgSyncReqVO();
|
||||
reqVO.setData(List.of(Map.of("@action", "add", "code", "demo")));
|
||||
IWorkOrgRespVO respVO = service.syncDepartments(reqVO);
|
||||
IWorkHrSyncRespVO respVO = service.syncDepartments(reqVO);
|
||||
|
||||
assertThat(respVO.isSuccess()).isTrue();
|
||||
assertThat(respVO.getPayload()).containsKey("result");
|
||||
assertThat(respVO.getResult()).hasSize(1);
|
||||
assertThat(respVO.getResult().get(0).getCode()).isEqualTo("demo");
|
||||
|
||||
RecordedRequest request = mockWebServer.takeRequest();
|
||||
assertThat(request.getPath()).isEqualTo(properties.getOrg().getPaths().getSyncDepartment());
|
||||
String decoded = URLDecoder.decode(request.getBody().readUtf8(), StandardCharsets.UTF_8);
|
||||
assertThat(decoded).contains("data=[{\"@action\":\"add\",\"code\":\"demo\"}]");
|
||||
JsonNode bodyNode = objectMapper.readTree(decoded);
|
||||
assertThat(bodyNode.path("data").isArray()).isTrue();
|
||||
assertThat(bodyNode.path("data").get(0).path("code").asText()).isEqualTo("demo");
|
||||
}
|
||||
|
||||
private MockResponse jsonResponse(String body) {
|
||||
@@ -93,14 +101,6 @@ class IWorkOrgRestServiceImplTest {
|
||||
.setBody(body);
|
||||
}
|
||||
|
||||
private String extractField(String decoded, String key) {
|
||||
return Arrays.stream(decoded.split("&"))
|
||||
.filter(part -> part.startsWith(key + "="))
|
||||
.map(part -> part.substring(key.length() + 1))
|
||||
.findFirst()
|
||||
.orElse("");
|
||||
}
|
||||
|
||||
private IWorkProperties buildProperties() {
|
||||
IWorkProperties properties = new IWorkProperties();
|
||||
properties.setBaseUrl(mockWebServer.url("/").toString());
|
||||
|
||||
Reference in New Issue
Block a user