1. 新增 iwork 同步用户组织信息接口
2. 修复错误设置版本信息在 zt-dependencies 的 bug
This commit is contained in:
@@ -67,10 +67,10 @@
|
||||
<artifactId>zt-spring-boot-starter-redis</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Reactive HTTP client for iWork integration -->
|
||||
<!-- HTTP client for iWork integration -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-webflux</artifactId>
|
||||
<groupId>com.squareup.okhttp3</groupId>
|
||||
<artifactId>okhttp</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
|
||||
@@ -1,12 +1,19 @@
|
||||
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.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.service.integration.iwork.IWorkIntegrationService;
|
||||
import com.zt.plat.module.system.service.integration.iwork.IWorkOrgRestService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
@@ -30,6 +37,7 @@ import static com.zt.plat.framework.common.pojo.CommonResult.success;
|
||||
public class IWorkIntegrationController {
|
||||
|
||||
private final IWorkIntegrationService integrationService;
|
||||
private final IWorkOrgRestService orgRestService;
|
||||
|
||||
@PostMapping("/user/resolve")
|
||||
@Operation(summary = "根据外部标识获取 iWork 用户编号")
|
||||
@@ -48,4 +56,54 @@ public class IWorkIntegrationController {
|
||||
public CommonResult<IWorkOperationRespVO> voidWorkflow(@Valid @RequestBody IWorkWorkflowVoidReqVO reqVO) {
|
||||
return success(integrationService.voidWorkflow(reqVO));
|
||||
}
|
||||
|
||||
// ----------------- 人力组织接口 -----------------
|
||||
|
||||
@PostMapping("/hr/subcompany/page")
|
||||
@Operation(summary = "获取 iWork 分部列表")
|
||||
public CommonResult<IWorkOrgRespVO> 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) {
|
||||
return success(orgRestService.listDepartments(reqVO));
|
||||
}
|
||||
|
||||
@PostMapping("/hr/job-title/page")
|
||||
@Operation(summary = "获取 iWork 岗位列表")
|
||||
public CommonResult<IWorkOrgRespVO> 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) {
|
||||
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/department/sync")
|
||||
@Operation(summary = "同步部门信息至 iWork")
|
||||
public CommonResult<IWorkOrgRespVO> syncDepartments(@Valid @RequestBody IWorkOrgSyncReqVO reqVO) {
|
||||
return success(orgRestService.syncDepartments(reqVO));
|
||||
}
|
||||
|
||||
@PostMapping("/hr/job-title/sync")
|
||||
@Operation(summary = "同步岗位信息至 iWork")
|
||||
public CommonResult<IWorkOrgRespVO> syncJobTitles(@Valid @RequestBody IWorkOrgSyncReqVO reqVO) {
|
||||
return success(orgRestService.syncJobTitles(reqVO));
|
||||
}
|
||||
|
||||
@PostMapping("/hr/user/sync")
|
||||
@Operation(summary = "同步人员信息至 iWork")
|
||||
public CommonResult<IWorkOrgRespVO> syncUsers(@Valid @RequestBody IWorkOrgSyncReqVO reqVO) {
|
||||
return success(orgRestService.syncUsers(reqVO));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.zt.plat.module.system.controller.admin.integration.iwork.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
|
||||
/**
|
||||
* iWork 部门查询参数。
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
public class IWorkDepartmentQueryReqVO extends IWorkOrgBaseQueryReqVO {
|
||||
|
||||
@Schema(description = "部门编码")
|
||||
private String departmentCode;
|
||||
|
||||
@Schema(description = "部门名称")
|
||||
private String departmentName;
|
||||
|
||||
@Schema(description = "所属分部ID")
|
||||
private String subcompanyId;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.zt.plat.module.system.controller.admin.integration.iwork.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
|
||||
/**
|
||||
* iWork 岗位查询参数。
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
public class IWorkJobTitleQueryReqVO extends IWorkOrgBaseQueryReqVO {
|
||||
|
||||
@Schema(description = "岗位编码")
|
||||
private String jobTitleCode;
|
||||
|
||||
@Schema(description = "岗位名称")
|
||||
private String jobTitleName;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
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 组织查询基础参数。
|
||||
*/
|
||||
@Data
|
||||
public class IWorkOrgBaseQueryReqVO {
|
||||
|
||||
@Schema(description = "当前页码", example = "1")
|
||||
private Integer curpage;
|
||||
|
||||
@Schema(description = "每页条数", example = "10")
|
||||
private Integer pagesize;
|
||||
|
||||
@Schema(description = "查询参数(扩展用),将被序列化为 params 传给 iWork")
|
||||
private Map<String, Object> params;
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package com.zt.plat.module.system.controller.admin.integration.iwork.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 查询 iWork 人力组织信息所需的参数。
|
||||
*/
|
||||
@Data
|
||||
public class IWorkOrgQueryReqVO {
|
||||
|
||||
@Schema(description = "当前页码", example = "1")
|
||||
private Integer curpage;
|
||||
|
||||
@Schema(description = "每页条数", example = "10")
|
||||
private Integer pagesize;
|
||||
|
||||
// ================= 分部查询 =================
|
||||
|
||||
@Schema(description = "分部编码")
|
||||
private String subcompanyCode;
|
||||
|
||||
@Schema(description = "分部名称")
|
||||
private String subcompanyName;
|
||||
|
||||
// ================= 部门查询 =================
|
||||
|
||||
@Schema(description = "部门编码")
|
||||
private String departmentCode;
|
||||
|
||||
@Schema(description = "部门名称")
|
||||
private String departmentName;
|
||||
|
||||
@Schema(description = "所属分部ID")
|
||||
private String subcompanyId;
|
||||
|
||||
// ================= 岗位查询 =================
|
||||
|
||||
@Schema(description = "岗位编码")
|
||||
private String jobTitleCode;
|
||||
|
||||
@Schema(description = "岗位名称")
|
||||
private String jobTitleName;
|
||||
|
||||
// ================= 人员查询 =================
|
||||
|
||||
@Schema(description = "人员工号")
|
||||
private String workCode;
|
||||
|
||||
@Schema(description = "人员姓名")
|
||||
private String lastName;
|
||||
|
||||
@Schema(description = "所属部门ID")
|
||||
private String departmentId;
|
||||
|
||||
@Schema(description = "所属岗位ID")
|
||||
private String jobTitleId;
|
||||
|
||||
@Schema(description = "人员状态 (0:试用, 1:正式, 2:临时, 3:试用延期, 4:解聘, 5:离职, 6:退休, 7:无效)")
|
||||
private String status;
|
||||
|
||||
@Schema(description = "手机号")
|
||||
private String mobile;
|
||||
|
||||
@Schema(description = "邮箱")
|
||||
private String email;
|
||||
|
||||
@Schema(description = "查询参数(扩展用),将被序列化为 params 传给 iWork")
|
||||
private Map<String, Object> params;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
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 请求的响应封装。
|
||||
*/
|
||||
@Data
|
||||
public 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;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.zt.plat.module.system.controller.admin.integration.iwork.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 同步 iWork 人力组织信息的请求。
|
||||
*/
|
||||
@Data
|
||||
public class IWorkOrgSyncReqVO {
|
||||
|
||||
@Schema(description = "同步数据集合,将被序列化为 data 传给 iWork", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotEmpty(message = "同步数据不能为空")
|
||||
private List<Map<String, Object>> data;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.zt.plat.module.system.controller.admin.integration.iwork.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
|
||||
/**
|
||||
* iWork 分部查询参数。
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
public class IWorkSubcompanyQueryReqVO extends IWorkOrgBaseQueryReqVO {
|
||||
|
||||
@Schema(description = "分部编码")
|
||||
private String subcompanyCode;
|
||||
|
||||
@Schema(description = "分部名称")
|
||||
private String subcompanyName;
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.zt.plat.module.system.controller.admin.integration.iwork.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
|
||||
/**
|
||||
* iWork 人员查询参数。
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
public class IWorkUserQueryReqVO extends IWorkOrgBaseQueryReqVO {
|
||||
|
||||
@Schema(description = "人员工号")
|
||||
private String workCode;
|
||||
|
||||
@Schema(description = "人员姓名")
|
||||
private String lastName;
|
||||
|
||||
@Schema(description = "所属部门ID")
|
||||
private String departmentId;
|
||||
|
||||
@Schema(description = "所属分部ID")
|
||||
private String subcompanyId;
|
||||
|
||||
@Schema(description = "所属岗位ID")
|
||||
private String jobTitleId;
|
||||
|
||||
@Schema(description = "人员状态 (0:试用, 1:正式, 2:临时, 3:试用延期, 4:解聘, 5:离职, 6:退休, 7:无效)")
|
||||
private String status;
|
||||
|
||||
@Schema(description = "手机号")
|
||||
private String mobile;
|
||||
|
||||
@Schema(description = "邮箱")
|
||||
private String email;
|
||||
}
|
||||
@@ -19,11 +19,6 @@ import java.time.Duration;
|
||||
@ConfigurationProperties(prefix = "iwork")
|
||||
public class IWorkProperties {
|
||||
|
||||
/**
|
||||
* 是否开启 iWork 集成能力。
|
||||
*/
|
||||
private boolean enabled = false;
|
||||
|
||||
/**
|
||||
* iWork 网关的基础地址。
|
||||
*/
|
||||
@@ -56,6 +51,8 @@ public class IWorkProperties {
|
||||
private final Token token = new Token();
|
||||
@Valid
|
||||
private final Client client = new Client();
|
||||
@Valid
|
||||
private final OrgRest org = new OrgRest();
|
||||
|
||||
@Data
|
||||
public static class Paths {
|
||||
@@ -123,4 +120,26 @@ public class IWorkProperties {
|
||||
@NotNull(message = "iWork 客户端响应超时时间不能为空")
|
||||
private Duration responseTimeout;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class OrgRest {
|
||||
/**
|
||||
* 认证所需的标识(与 iWork 约定)。
|
||||
*/
|
||||
private String tokenSeed;
|
||||
@Valid
|
||||
private final OrgPaths paths = new OrgPaths();
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class OrgPaths {
|
||||
private String subcompanyPage;
|
||||
private String departmentPage;
|
||||
private String jobTitlePage;
|
||||
private String userPage;
|
||||
private String syncSubcompany;
|
||||
private String syncDepartment;
|
||||
private String syncJobTitle;
|
||||
private String syncUser;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,15 +7,15 @@ import com.zt.plat.framework.common.exception.ErrorCode;
|
||||
*/
|
||||
public interface IWorkIntegrationErrorCodeConstants {
|
||||
|
||||
ErrorCode IWORK_DISABLED = new ErrorCode(1_010_200_001, "iWork 集成未启用,请先完成配置");
|
||||
ErrorCode IWORK_BASE_URL_MISSING = new ErrorCode(1_010_200_002, "iWork 集成未配置网关地址");
|
||||
ErrorCode IWORK_CONFIGURATION_INVALID = new ErrorCode(1_010_200_003,
|
||||
ErrorCode IWORK_BASE_URL_MISSING = new ErrorCode(1_010_200_001, "iWork 集成未配置网关地址");
|
||||
ErrorCode IWORK_CONFIGURATION_INVALID = new ErrorCode(1_010_200_002,
|
||||
"iWork 集成缺少必填配置(appId/clientPublicKey/userId/workflowId)");
|
||||
ErrorCode IWORK_REGISTER_FAILED = new ErrorCode(1_010_200_004, "iWork 注册授权失败");
|
||||
ErrorCode IWORK_APPLY_TOKEN_FAILED = new ErrorCode(1_010_200_005, "iWork 令牌申请失败");
|
||||
ErrorCode IWORK_REMOTE_REQUEST_FAILED = new ErrorCode(1_010_200_006, "iWork 接口请求失败");
|
||||
ErrorCode IWORK_USER_IDENTIFIER_MISSING = new ErrorCode(1_010_200_007, "缺少用户识别信息,无法调用 iWork 接口");
|
||||
ErrorCode IWORK_OPERATOR_USER_MISSING = new ErrorCode(1_010_200_008, "缺少 iWork 操作人用户编号");
|
||||
ErrorCode IWORK_WORKFLOW_ID_MISSING = new ErrorCode(1_010_200_009, "缺少 iWork 流程模板编号");
|
||||
|
||||
ErrorCode IWORK_REGISTER_FAILED = new ErrorCode(1_010_200_003, "iWork 注册授权失败");
|
||||
ErrorCode IWORK_APPLY_TOKEN_FAILED = new ErrorCode(1_010_200_004, "iWork 令牌申请失败");
|
||||
ErrorCode IWORK_REMOTE_REQUEST_FAILED = new ErrorCode(1_010_200_005, "iWork 接口请求失败");
|
||||
ErrorCode IWORK_USER_IDENTIFIER_MISSING = new ErrorCode(1_010_200_006, "缺少用户识别信息,无法调用 iWork 接口");
|
||||
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 人力组织接口请求失败");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
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.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;
|
||||
|
||||
/**
|
||||
* iWork 人力组织 REST 接口门面。
|
||||
*/
|
||||
public interface IWorkOrgRestService {
|
||||
|
||||
IWorkOrgRespVO listSubcompanies(IWorkSubcompanyQueryReqVO reqVO);
|
||||
|
||||
IWorkOrgRespVO listDepartments(IWorkDepartmentQueryReqVO reqVO);
|
||||
|
||||
IWorkOrgRespVO listJobTitles(IWorkJobTitleQueryReqVO reqVO);
|
||||
|
||||
IWorkOrgRespVO listUsers(IWorkUserQueryReqVO reqVO);
|
||||
|
||||
IWorkOrgRespVO syncSubcompanies(IWorkOrgSyncReqVO reqVO);
|
||||
|
||||
IWorkOrgRespVO syncDepartments(IWorkOrgSyncReqVO reqVO);
|
||||
|
||||
IWorkOrgRespVO syncJobTitles(IWorkOrgSyncReqVO reqVO);
|
||||
|
||||
IWorkOrgRespVO syncUsers(IWorkOrgSyncReqVO reqVO);
|
||||
}
|
||||
@@ -23,21 +23,25 @@ import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.ToString;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import okhttp3.FormBody;
|
||||
import okhttp3.Headers;
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.Response;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.reactive.function.BodyInserters;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
import org.springframework.web.reactive.function.client.WebClientResponseException;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.PublicKey;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Base64;
|
||||
import java.util.Collections;
|
||||
@@ -58,24 +62,24 @@ public class IWorkIntegrationServiceImpl implements IWorkIntegrationService {
|
||||
|
||||
private static final TypeReference<Map<String, Object>> MAP_TYPE = new TypeReference<>() {
|
||||
};
|
||||
private static final okhttp3.MediaType JSON_MEDIA_TYPE = okhttp3.MediaType.get("application/json; charset=UTF-8");
|
||||
|
||||
private final IWorkProperties properties;
|
||||
private final ObjectMapper objectMapper;
|
||||
private final WebClient.Builder webClientBuilder;
|
||||
|
||||
private final Cache<SessionKey, IWorkSession> sessionCache = Caffeine.newBuilder()
|
||||
.maximumSize(256)
|
||||
.build();
|
||||
|
||||
private final Cache<String, PublicKey> publicKeyCache = Caffeine.newBuilder()
|
||||
private final Cache<String, PublicKey> publicKeyCache = Caffeine.newBuilder()
|
||||
.maximumSize(64)
|
||||
.build();
|
||||
|
||||
private volatile WebClient cachedWebClient;
|
||||
private volatile OkHttpClient cachedHttpClient;
|
||||
|
||||
@Override
|
||||
public IWorkUserInfoRespVO resolveUserId(IWorkUserInfoReqVO reqVO) {
|
||||
assertEnabled();
|
||||
assertConfigured();
|
||||
String appId = resolveAppId();
|
||||
String clientPublicKey = resolveClientPublicKey();
|
||||
String operatorUserId = resolveOperatorUserId(reqVO.getOperatorUserId());
|
||||
@@ -90,7 +94,7 @@ public class IWorkIntegrationServiceImpl implements IWorkIntegrationService {
|
||||
|
||||
@Override
|
||||
public IWorkOperationRespVO createWorkflow(IWorkWorkflowCreateReqVO reqVO) {
|
||||
assertEnabled();
|
||||
assertConfigured();
|
||||
String appId = resolveAppId();
|
||||
String clientPublicKey = resolveClientPublicKey();
|
||||
String operatorUserId = resolveOperatorUserId(reqVO.getOperatorUserId());
|
||||
@@ -104,7 +108,7 @@ public class IWorkIntegrationServiceImpl implements IWorkIntegrationService {
|
||||
|
||||
@Override
|
||||
public IWorkOperationRespVO voidWorkflow(IWorkWorkflowVoidReqVO reqVO) {
|
||||
assertEnabled();
|
||||
assertConfigured();
|
||||
String appId = resolveAppId();
|
||||
String clientPublicKey = resolveClientPublicKey();
|
||||
String operatorUserId = resolveOperatorUserId(reqVO.getOperatorUserId());
|
||||
@@ -119,10 +123,7 @@ public class IWorkIntegrationServiceImpl implements IWorkIntegrationService {
|
||||
return buildOperationResponse(responseBody);
|
||||
}
|
||||
|
||||
private void assertEnabled() {
|
||||
if (!properties.isEnabled()) {
|
||||
throw ServiceExceptionUtil.exception(IWORK_DISABLED);
|
||||
}
|
||||
private void assertConfigured() {
|
||||
if (!StringUtils.hasText(properties.getBaseUrl())) {
|
||||
throw ServiceExceptionUtil.exception(IWORK_BASE_URL_MISSING);
|
||||
}
|
||||
@@ -193,25 +194,13 @@ public class IWorkIntegrationServiceImpl implements IWorkIntegrationService {
|
||||
}
|
||||
|
||||
private RegistrationResult register(String appId, String clientPublicKey) {
|
||||
String responseBody;
|
||||
try {
|
||||
responseBody = webClient()
|
||||
.post()
|
||||
.uri(properties.getPaths().getRegister())
|
||||
.headers(headers -> {
|
||||
headers.set(properties.getHeaders().getAppId(), appId);
|
||||
headers.set(properties.getHeaders().getClientPublicKey(), clientPublicKey);
|
||||
})
|
||||
.retrieve()
|
||||
.bodyToMono(String.class)
|
||||
.block();
|
||||
} catch (WebClientResponseException ex) {
|
||||
log.error("[iWork] register failed. status={}, body={}", ex.getStatusCode(), ex.getResponseBodyAsString(), ex);
|
||||
throw ServiceExceptionUtil.exception(IWORK_REGISTER_FAILED, ex.getStatusCode().value(), ex.getResponseBodyAsString());
|
||||
} catch (Exception ex) {
|
||||
log.error("[iWork] register failed", ex);
|
||||
throw ServiceExceptionUtil.exception(IWORK_REGISTER_FAILED, ex.getMessage());
|
||||
}
|
||||
Request request = new Request.Builder()
|
||||
.url(resolveUrl(properties.getPaths().getRegister()))
|
||||
.header(properties.getHeaders().getAppId(), appId)
|
||||
.header(properties.getHeaders().getClientPublicKey(), clientPublicKey)
|
||||
.post(RequestBody.create(null, new byte[0]))
|
||||
.build();
|
||||
String responseBody = executeRequest(request, IWORK_REGISTER_FAILED);
|
||||
JsonNode node = parseJson(responseBody, IWORK_REGISTER_FAILED);
|
||||
String secret = textValue(node, "secret");
|
||||
String spk = textValue(node, "spk");
|
||||
@@ -222,26 +211,14 @@ public class IWorkIntegrationServiceImpl implements IWorkIntegrationService {
|
||||
}
|
||||
|
||||
private String applyToken(String appId, String encryptedSecret) {
|
||||
String responseBody;
|
||||
try {
|
||||
responseBody = webClient()
|
||||
.post()
|
||||
.uri(properties.getPaths().getApplyToken())
|
||||
.headers(headers -> {
|
||||
headers.set(properties.getHeaders().getAppId(), appId);
|
||||
headers.set(properties.getHeaders().getSecret(), encryptedSecret);
|
||||
headers.set(properties.getHeaders().getTime(), String.valueOf(properties.getToken().getTtlSeconds()));
|
||||
})
|
||||
.retrieve()
|
||||
.bodyToMono(String.class)
|
||||
.block();
|
||||
} catch (WebClientResponseException ex) {
|
||||
log.error("[iWork] apply token failed. status={}, body={}", ex.getStatusCode(), ex.getResponseBodyAsString(), ex);
|
||||
throw ServiceExceptionUtil.exception(IWORK_APPLY_TOKEN_FAILED, ex.getStatusCode().value(), ex.getResponseBodyAsString());
|
||||
} catch (Exception ex) {
|
||||
log.error("[iWork] apply token failed", ex);
|
||||
throw ServiceExceptionUtil.exception(IWORK_APPLY_TOKEN_FAILED, ex.getMessage());
|
||||
}
|
||||
Request request = new Request.Builder()
|
||||
.url(resolveUrl(properties.getPaths().getApplyToken()))
|
||||
.header(properties.getHeaders().getAppId(), appId)
|
||||
.header(properties.getHeaders().getSecret(), encryptedSecret)
|
||||
.header(properties.getHeaders().getTime(), String.valueOf(properties.getToken().getTtlSeconds()))
|
||||
.post(RequestBody.create(null, new byte[0]))
|
||||
.build();
|
||||
String responseBody = executeRequest(request, IWORK_APPLY_TOKEN_FAILED);
|
||||
JsonNode node = parseJson(responseBody, IWORK_APPLY_TOKEN_FAILED);
|
||||
String token = textValue(node, "token");
|
||||
if (!StringUtils.hasText(token)) {
|
||||
@@ -255,64 +232,54 @@ public class IWorkIntegrationServiceImpl implements IWorkIntegrationService {
|
||||
String appId,
|
||||
IWorkSession session,
|
||||
Map<String, Object> payload) {
|
||||
try {
|
||||
return webClient()
|
||||
.post()
|
||||
.uri(uriBuilder -> {
|
||||
uriBuilder.path(path);
|
||||
if (queryParams != null) {
|
||||
queryParams.forEach((key, value) -> {
|
||||
if (value != null) {
|
||||
uriBuilder.queryParam(key, value);
|
||||
}
|
||||
});
|
||||
}
|
||||
return uriBuilder.build();
|
||||
})
|
||||
.headers(headers -> setAuthHeaders(headers, appId, session))
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.bodyValue(payload == null ? Collections.emptyMap() : payload)
|
||||
.retrieve()
|
||||
.bodyToMono(String.class)
|
||||
.block();
|
||||
} catch (WebClientResponseException ex) {
|
||||
log.error("[iWork] request {} failed. status={}, body={}", path, ex.getStatusCode(), ex.getResponseBodyAsString(), ex);
|
||||
throw ServiceExceptionUtil.exception(IWORK_REMOTE_REQUEST_FAILED, ex.getStatusCode().value(), ex.getResponseBodyAsString());
|
||||
} catch (Exception ex) {
|
||||
log.error("[iWork] request {} failed", path, ex);
|
||||
throw ServiceExceptionUtil.exception(IWORK_REMOTE_REQUEST_FAILED, ex.getMessage());
|
||||
HttpUrl baseUrl = HttpUrl.parse(resolveUrl(path));
|
||||
if (baseUrl == null) {
|
||||
throw ServiceExceptionUtil.exception(IWORK_REMOTE_REQUEST_FAILED, "非法的 URL");
|
||||
}
|
||||
HttpUrl.Builder urlBuilder = baseUrl.newBuilder();
|
||||
if (queryParams != null) {
|
||||
queryParams.forEach((key, value) -> {
|
||||
if (value != null) {
|
||||
urlBuilder.addQueryParameter(key, String.valueOf(value));
|
||||
}
|
||||
});
|
||||
}
|
||||
String jsonPayload = toJsonString(payload == null ? Collections.emptyMap() : payload);
|
||||
RequestBody body = RequestBody.create(JSON_MEDIA_TYPE, jsonPayload);
|
||||
Headers headers = authHeaders(appId, session)
|
||||
.set("Content-Type", MediaType.APPLICATION_JSON_VALUE)
|
||||
.build();
|
||||
Request request = new Request.Builder()
|
||||
.url(urlBuilder.build())
|
||||
.headers(headers)
|
||||
.post(body)
|
||||
.build();
|
||||
return executeRequest(request, IWORK_REMOTE_REQUEST_FAILED);
|
||||
}
|
||||
|
||||
private String executeFormRequest(String path,
|
||||
String appId,
|
||||
IWorkSession session,
|
||||
MultiValueMap<String, String> formData) {
|
||||
try {
|
||||
return webClient()
|
||||
.post()
|
||||
.uri(path)
|
||||
.headers(headers -> setAuthHeaders(headers, appId, session))
|
||||
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
|
||||
.body(BodyInserters.fromFormData(formData))
|
||||
.retrieve()
|
||||
.bodyToMono(String.class)
|
||||
.block();
|
||||
} catch (WebClientResponseException ex) {
|
||||
log.error("[iWork] form request {} failed. status={}, body={}", path, ex.getStatusCode(), ex.getResponseBodyAsString(), ex);
|
||||
throw ServiceExceptionUtil.exception(IWORK_REMOTE_REQUEST_FAILED, ex.getStatusCode().value(), ex.getResponseBodyAsString());
|
||||
} catch (Exception ex) {
|
||||
log.error("[iWork] form request {} failed", path, ex);
|
||||
throw ServiceExceptionUtil.exception(IWORK_REMOTE_REQUEST_FAILED, ex.getMessage());
|
||||
}
|
||||
FormBody.Builder builder = new FormBody.Builder();
|
||||
formData.forEach((key, values) -> {
|
||||
if (values != null) {
|
||||
values.forEach(value -> builder.add(key, value));
|
||||
}
|
||||
});
|
||||
Request request = new Request.Builder()
|
||||
.url(resolveUrl(path))
|
||||
.headers(authHeaders(appId, session).build())
|
||||
.post(builder.build())
|
||||
.build();
|
||||
return executeRequest(request, IWORK_REMOTE_REQUEST_FAILED);
|
||||
}
|
||||
|
||||
private void setAuthHeaders(org.springframework.http.HttpHeaders headers,
|
||||
String appId,
|
||||
IWorkSession session) {
|
||||
headers.set(properties.getHeaders().getAppId(), appId);
|
||||
headers.set(properties.getHeaders().getToken(), session.getToken());
|
||||
headers.set(properties.getHeaders().getUserId(), session.getEncryptedUserId());
|
||||
private Headers.Builder authHeaders(String appId, IWorkSession session) {
|
||||
return new Headers.Builder()
|
||||
.set(properties.getHeaders().getAppId(), appId)
|
||||
.set(properties.getHeaders().getToken(), session.getToken())
|
||||
.set(properties.getHeaders().getUserId(), session.getEncryptedUserId());
|
||||
}
|
||||
|
||||
private Map<String, Object> buildUserPayload(IWorkUserInfoReqVO reqVO) {
|
||||
@@ -545,38 +512,66 @@ public class IWorkIntegrationServiceImpl implements IWorkIntegrationService {
|
||||
}
|
||||
}
|
||||
|
||||
private WebClient webClient() {
|
||||
WebClient client = cachedWebClient;
|
||||
private OkHttpClient okHttpClient() {
|
||||
OkHttpClient client = cachedHttpClient;
|
||||
if (client != null) {
|
||||
return client;
|
||||
}
|
||||
synchronized (this) {
|
||||
if (cachedWebClient == null) {
|
||||
cachedWebClient = buildWebClient();
|
||||
if (cachedHttpClient == null) {
|
||||
cachedHttpClient = buildHttpClient();
|
||||
}
|
||||
return cachedWebClient;
|
||||
return cachedHttpClient;
|
||||
}
|
||||
}
|
||||
|
||||
private WebClient buildWebClient() {
|
||||
WebClient.Builder builder = cloneBuilder();
|
||||
builder.baseUrl(properties.getBaseUrl());
|
||||
private OkHttpClient buildHttpClient() {
|
||||
OkHttpClient.Builder builder = new OkHttpClient.Builder();
|
||||
IWorkProperties.Client clientProps = properties.getClient();
|
||||
if (clientProps != null) {
|
||||
Duration responseTimeout = clientProps.getResponseTimeout();
|
||||
if (responseTimeout != null) {
|
||||
builder.filter((request, next) -> next.exchange(request).timeout(responseTimeout));
|
||||
if (clientProps.getConnectTimeout() != null) {
|
||||
builder.connectTimeout(clientProps.getConnectTimeout());
|
||||
}
|
||||
if (clientProps.getResponseTimeout() != null) {
|
||||
builder.callTimeout(clientProps.getResponseTimeout());
|
||||
builder.readTimeout(clientProps.getResponseTimeout());
|
||||
builder.writeTimeout(clientProps.getResponseTimeout());
|
||||
}
|
||||
// 连接超时时间由全局的 HttpClient 自定义器统一配置(若存在)。
|
||||
}
|
||||
builder.retryOnConnectionFailure(true);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private WebClient.Builder cloneBuilder() {
|
||||
try {
|
||||
return webClientBuilder.clone();
|
||||
} catch (UnsupportedOperationException ex) {
|
||||
return WebClient.builder();
|
||||
private String resolveUrl(String path) {
|
||||
if (StringUtils.hasText(path) && (path.startsWith("http://") || path.startsWith("https://"))) {
|
||||
return path;
|
||||
}
|
||||
String baseUrl = properties.getBaseUrl();
|
||||
if (!StringUtils.hasText(baseUrl)) {
|
||||
throw ServiceExceptionUtil.exception(IWORK_BASE_URL_MISSING);
|
||||
}
|
||||
boolean baseEndsWithSlash = baseUrl.endsWith("/");
|
||||
boolean pathStartsWithSlash = StringUtils.hasText(path) && path.startsWith("/");
|
||||
if (baseEndsWithSlash && pathStartsWithSlash) {
|
||||
return baseUrl + path.substring(1);
|
||||
}
|
||||
if (!baseEndsWithSlash && !pathStartsWithSlash) {
|
||||
return baseUrl + "/" + path;
|
||||
}
|
||||
return baseUrl + path;
|
||||
}
|
||||
|
||||
private String executeRequest(Request request, ErrorCode errorCode) {
|
||||
try (Response response = okHttpClient().newCall(request).execute()) {
|
||||
String responseBody = response.body() != null ? response.body().string() : null;
|
||||
if (!response.isSuccessful()) {
|
||||
log.error("[iWork] request {} failed. status={} body={}", request.url(), response.code(), responseBody);
|
||||
throw ServiceExceptionUtil.exception(errorCode, response.code(), responseBody);
|
||||
}
|
||||
return responseBody;
|
||||
} catch (IOException ex) {
|
||||
log.error("[iWork] request {} failed", request.url(), ex);
|
||||
throw ServiceExceptionUtil.exception(errorCode, ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,371 @@
|
||||
package com.zt.plat.module.system.service.integration.iwork.impl;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
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.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;
|
||||
import com.zt.plat.module.system.framework.integration.iwork.config.IWorkProperties;
|
||||
import com.zt.plat.module.system.service.integration.iwork.IWorkOrgRestService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import okhttp3.FormBody;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.Response;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Clock;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
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;
|
||||
import static com.zt.plat.module.system.service.integration.iwork.IWorkIntegrationErrorCodeConstants.IWORK_ORG_REMOTE_FAILED;
|
||||
|
||||
/**
|
||||
* 默认的人力组织 REST 代理实现。
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class IWorkOrgRestServiceImpl implements IWorkOrgRestService {
|
||||
|
||||
private static final TypeReference<Map<String, Object>> MAP_TYPE = new TypeReference<>() {
|
||||
};
|
||||
|
||||
private final IWorkProperties properties;
|
||||
private final ObjectMapper objectMapper;
|
||||
private final Clock clock;
|
||||
|
||||
private volatile OkHttpClient cachedHttpClient;
|
||||
|
||||
@Autowired
|
||||
public IWorkOrgRestServiceImpl(IWorkProperties properties,
|
||||
ObjectMapper objectMapper) {
|
||||
this(properties, objectMapper, Clock.systemUTC());
|
||||
}
|
||||
|
||||
IWorkOrgRestServiceImpl(IWorkProperties properties,
|
||||
ObjectMapper objectMapper,
|
||||
Clock clock) {
|
||||
this.properties = properties;
|
||||
this.objectMapper = objectMapper;
|
||||
this.clock = clock;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IWorkOrgRespVO listSubcompanies(IWorkSubcompanyQueryReqVO reqVO) {
|
||||
String path = orgPaths().getSubcompanyPage();
|
||||
Map<String, Object> params = buildBaseParams(reqVO);
|
||||
if (StringUtils.hasText(reqVO.getSubcompanyCode())) {
|
||||
params.put("subcompanycode", reqVO.getSubcompanyCode());
|
||||
}
|
||||
if (StringUtils.hasText(reqVO.getSubcompanyName())) {
|
||||
params.put("subcompanyname", reqVO.getSubcompanyName());
|
||||
}
|
||||
return invokeParamsEndpoint(path, params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IWorkOrgRespVO listDepartments(IWorkDepartmentQueryReqVO reqVO) {
|
||||
String path = orgPaths().getDepartmentPage();
|
||||
Map<String, Object> params = buildBaseParams(reqVO);
|
||||
if (StringUtils.hasText(reqVO.getDepartmentCode())) {
|
||||
params.put("departmentcode", reqVO.getDepartmentCode());
|
||||
}
|
||||
if (StringUtils.hasText(reqVO.getDepartmentName())) {
|
||||
params.put("departmentname", reqVO.getDepartmentName());
|
||||
}
|
||||
if (StringUtils.hasText(reqVO.getSubcompanyId())) {
|
||||
params.put("subcompanyid", reqVO.getSubcompanyId());
|
||||
}
|
||||
return invokeParamsEndpoint(path, params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IWorkOrgRespVO listJobTitles(IWorkJobTitleQueryReqVO reqVO) {
|
||||
String path = orgPaths().getJobTitlePage();
|
||||
Map<String, Object> params = buildBaseParams(reqVO);
|
||||
if (StringUtils.hasText(reqVO.getJobTitleCode())) {
|
||||
params.put("jobtitlecode", reqVO.getJobTitleCode());
|
||||
}
|
||||
if (StringUtils.hasText(reqVO.getJobTitleName())) {
|
||||
params.put("jobtitlename", reqVO.getJobTitleName());
|
||||
}
|
||||
return invokeParamsEndpoint(path, params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IWorkOrgRespVO listUsers(IWorkUserQueryReqVO reqVO) {
|
||||
String path = orgPaths().getUserPage();
|
||||
Map<String, Object> params = buildBaseParams(reqVO);
|
||||
if (StringUtils.hasText(reqVO.getWorkCode())) {
|
||||
params.put("workcode", reqVO.getWorkCode());
|
||||
}
|
||||
if (StringUtils.hasText(reqVO.getLastName())) {
|
||||
params.put("lastname", reqVO.getLastName());
|
||||
}
|
||||
if (StringUtils.hasText(reqVO.getSubcompanyId())) {
|
||||
params.put("subcompanyid", reqVO.getSubcompanyId());
|
||||
}
|
||||
if (StringUtils.hasText(reqVO.getDepartmentId())) {
|
||||
params.put("departmentid", reqVO.getDepartmentId());
|
||||
}
|
||||
if (StringUtils.hasText(reqVO.getJobTitleId())) {
|
||||
params.put("jobtitleid", reqVO.getJobTitleId());
|
||||
}
|
||||
if (StringUtils.hasText(reqVO.getStatus())) {
|
||||
params.put("status", reqVO.getStatus());
|
||||
}
|
||||
if (StringUtils.hasText(reqVO.getMobile())) {
|
||||
params.put("mobile", reqVO.getMobile());
|
||||
}
|
||||
if (StringUtils.hasText(reqVO.getEmail())) {
|
||||
params.put("email", reqVO.getEmail());
|
||||
}
|
||||
return invokeParamsEndpoint(path, params);
|
||||
}
|
||||
|
||||
private Map<String, Object> buildBaseParams(IWorkOrgBaseQueryReqVO reqVO) {
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
if (reqVO.getParams() != null) {
|
||||
params.putAll(reqVO.getParams());
|
||||
}
|
||||
if (reqVO.getCurpage() != null) {
|
||||
params.put("curpage", reqVO.getCurpage());
|
||||
}
|
||||
if (reqVO.getPagesize() != null) {
|
||||
params.put("pagesize", reqVO.getPagesize());
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IWorkOrgRespVO syncSubcompanies(IWorkOrgSyncReqVO reqVO) {
|
||||
String path = orgPaths().getSyncSubcompany();
|
||||
return invokeDataEndpoint(path, reqVO.getData());
|
||||
}
|
||||
|
||||
@Override
|
||||
public IWorkOrgRespVO syncDepartments(IWorkOrgSyncReqVO reqVO) {
|
||||
String path = orgPaths().getSyncDepartment();
|
||||
return invokeDataEndpoint(path, reqVO.getData());
|
||||
}
|
||||
|
||||
@Override
|
||||
public IWorkOrgRespVO syncJobTitles(IWorkOrgSyncReqVO reqVO) {
|
||||
String path = orgPaths().getSyncJobTitle();
|
||||
return invokeDataEndpoint(path, reqVO.getData());
|
||||
}
|
||||
|
||||
@Override
|
||||
public IWorkOrgRespVO syncUsers(IWorkOrgSyncReqVO reqVO) {
|
||||
String path = orgPaths().getSyncUser();
|
||||
return invokeDataEndpoint(path, reqVO.getData());
|
||||
}
|
||||
|
||||
private IWorkOrgRespVO invokeParamsEndpoint(String path, Map<String, Object> params) {
|
||||
String payload = toJson(params);
|
||||
return executeForm(path, "params", payload);
|
||||
}
|
||||
|
||||
private IWorkOrgRespVO invokeDataEndpoint(String path, Object data) {
|
||||
String payload = toJson(data);
|
||||
return executeForm(path, "data", payload);
|
||||
}
|
||||
|
||||
private IWorkOrgRespVO executeForm(String path, String fieldName, String jsonPayload) {
|
||||
assertOrgConfigured(path);
|
||||
FormBody.Builder formBuilder = new FormBody.Builder(StandardCharsets.UTF_8);
|
||||
if (StringUtils.hasText(fieldName) && StringUtils.hasText(jsonPayload)) {
|
||||
formBuilder.add(fieldName, jsonPayload);
|
||||
}
|
||||
formBuilder.add("token", buildTokenJson());
|
||||
|
||||
RequestBody requestBody = formBuilder.build();
|
||||
Request request = new Request.Builder()
|
||||
.url(resolveUrl(path))
|
||||
.header("Content-Type", MediaType.APPLICATION_FORM_URLENCODED_VALUE)
|
||||
.post(requestBody)
|
||||
.build();
|
||||
|
||||
try (Response response = okHttpClient().newCall(request).execute()) {
|
||||
String responseBody = response.body() != null ? response.body().string() : null;
|
||||
if (!response.isSuccessful()) {
|
||||
log.error("[iWork-Org] 调用 {} 失败,status={} body={}", path, response.code(), responseBody);
|
||||
throw ServiceExceptionUtil.exception(IWORK_ORG_REMOTE_FAILED, response.code(), responseBody);
|
||||
}
|
||||
return buildResponse(responseBody);
|
||||
} catch (IOException ex) {
|
||||
log.error("[iWork-Org] 调用 {} 失败", path, ex);
|
||||
throw ServiceExceptionUtil.exception(IWORK_ORG_REMOTE_FAILED, ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private String buildTokenJson() {
|
||||
String tokenSeed = StringUtils.trimWhitespace(orgConfig().getTokenSeed());
|
||||
if (!StringUtils.hasText(tokenSeed)) {
|
||||
throw ServiceExceptionUtil.exception(IWORK_ORG_IDENTIFIER_MISSING);
|
||||
}
|
||||
long ts = clock.millis();
|
||||
String raw = tokenSeed + ts;
|
||||
// 通过 MD5(tokenSeed + ts) 计算 key,并转为大写以符合 PDF 约定
|
||||
String hashed = md5Upper(raw);
|
||||
Map<String, String> token = Map.of(
|
||||
"key", hashed,
|
||||
"ts", String.valueOf(ts)
|
||||
);
|
||||
return toJson(token);
|
||||
}
|
||||
|
||||
private String md5Upper(String raw) {
|
||||
byte[] bytes = raw.getBytes(StandardCharsets.UTF_8);
|
||||
String hex = org.springframework.util.DigestUtils.md5DigestAsHex(bytes);
|
||||
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());
|
||||
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));
|
||||
return respVO;
|
||||
}
|
||||
|
||||
private JsonNode parseJson(String body) {
|
||||
try {
|
||||
return objectMapper.readTree(body);
|
||||
} catch (JsonProcessingException ex) {
|
||||
log.error("[iWork-Org] 无法解析 JSON 响应: {}", body, ex);
|
||||
throw ServiceExceptionUtil.exception(IWORK_ORG_REMOTE_FAILED, "响应不是合法 JSON");
|
||||
}
|
||||
}
|
||||
|
||||
private String resolveMessage(JsonNode node) {
|
||||
if (node == null) {
|
||||
return null;
|
||||
}
|
||||
if (node.has("msg")) {
|
||||
return node.get("msg").asText();
|
||||
}
|
||||
if (node.has("message")) {
|
||||
return node.get("message").asText();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean isSuccess(JsonNode node) {
|
||||
if (node == null) {
|
||||
return false;
|
||||
}
|
||||
if ("1".equals(textValue(node, "code"))) {
|
||||
return true;
|
||||
}
|
||||
if ("1".equals(textValue(node, "status"))) {
|
||||
return true;
|
||||
}
|
||||
return "1".equals(textValue(node, "success"));
|
||||
}
|
||||
|
||||
private String textValue(JsonNode node, String field) {
|
||||
return node != null && node.has(field) ? node.get(field).asText() : null;
|
||||
}
|
||||
|
||||
private void assertOrgConfigured(String path) {
|
||||
IWorkProperties.OrgRest org = orgConfig();
|
||||
if (!StringUtils.hasText(properties.getBaseUrl())) {
|
||||
throw ServiceExceptionUtil.exception(IWORK_BASE_URL_MISSING);
|
||||
}
|
||||
if (!StringUtils.hasText(path)) {
|
||||
throw ServiceExceptionUtil.exception(IWORK_ORG_REMOTE_FAILED, "缺少接口路径配置");
|
||||
}
|
||||
}
|
||||
|
||||
private IWorkProperties.OrgRest orgConfig() {
|
||||
return properties.getOrg();
|
||||
}
|
||||
|
||||
private IWorkProperties.OrgPaths orgPaths() {
|
||||
return orgConfig().getPaths();
|
||||
}
|
||||
|
||||
private String toJson(Object payload) {
|
||||
try {
|
||||
return objectMapper.writeValueAsString(payload == null ? Collections.emptyMap() : payload);
|
||||
} catch (JsonProcessingException ex) {
|
||||
throw ServiceExceptionUtil.exception(IWORK_ORG_REMOTE_FAILED, "序列化 JSON 失败: " + ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private OkHttpClient okHttpClient() {
|
||||
OkHttpClient client = cachedHttpClient;
|
||||
if (client != null) {
|
||||
return client;
|
||||
}
|
||||
synchronized (this) {
|
||||
if (cachedHttpClient == null) {
|
||||
cachedHttpClient = buildHttpClient();
|
||||
}
|
||||
return cachedHttpClient;
|
||||
}
|
||||
}
|
||||
|
||||
private OkHttpClient buildHttpClient() {
|
||||
OkHttpClient.Builder builder = new OkHttpClient.Builder();
|
||||
IWorkProperties.Client clientProps = properties.getClient();
|
||||
if (clientProps != null) {
|
||||
if (clientProps.getConnectTimeout() != null) {
|
||||
builder.connectTimeout(clientProps.getConnectTimeout());
|
||||
}
|
||||
if (clientProps.getResponseTimeout() != null) {
|
||||
builder.callTimeout(clientProps.getResponseTimeout());
|
||||
builder.readTimeout(clientProps.getResponseTimeout());
|
||||
builder.writeTimeout(clientProps.getResponseTimeout());
|
||||
}
|
||||
}
|
||||
builder.retryOnConnectionFailure(true);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private String resolveUrl(String path) {
|
||||
if (StringUtils.hasText(path) && (path.startsWith("http://") || path.startsWith("https://"))) {
|
||||
return path;
|
||||
}
|
||||
String baseUrl = properties.getBaseUrl();
|
||||
if (!StringUtils.hasText(baseUrl)) {
|
||||
throw ServiceExceptionUtil.exception(IWORK_BASE_URL_MISSING);
|
||||
}
|
||||
boolean baseEndsWithSlash = baseUrl.endsWith("/");
|
||||
boolean pathStartsWithSlash = StringUtils.hasText(path) && path.startsWith("/");
|
||||
if (baseEndsWithSlash && pathStartsWithSlash) {
|
||||
return baseUrl + path.substring(1);
|
||||
}
|
||||
if (!baseEndsWithSlash && !pathStartsWithSlash) {
|
||||
return baseUrl + "/" + path;
|
||||
}
|
||||
return baseUrl + path;
|
||||
}
|
||||
}
|
||||
@@ -50,8 +50,8 @@ spring:
|
||||
# Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优
|
||||
data:
|
||||
redis:
|
||||
host: nacos-redis # 地址
|
||||
port: 6379 # 端口
|
||||
host: 172.16.46.63 # 地址
|
||||
port: 30379 # 端口
|
||||
database: 1 # 数据库索引
|
||||
# password: 123456 # 密码,建议生产环境开启
|
||||
|
||||
@@ -61,6 +61,16 @@ spring:
|
||||
rocketmq:
|
||||
name-server: 172.16.46.63:30876 # RocketMQ Namesrv
|
||||
|
||||
spring:
|
||||
# RabbitMQ 配置项,对应 RabbitProperties 配置类
|
||||
rabbitmq:
|
||||
host: 127.0.0.1 # RabbitMQ 服务的地址
|
||||
port: 5672 # RabbitMQ 服务的端口
|
||||
username: guest # RabbitMQ 服务的账号
|
||||
password: guest # RabbitMQ 服务的密码
|
||||
# Kafka 配置项,对应 KafkaProperties 配置类
|
||||
kafka:
|
||||
bootstrap-servers: 127.0.0.1:9092 # 指定 Kafka Broker 地址,可以设置多个,以逗号分隔
|
||||
|
||||
--- #################### 定时任务相关配置 ####################
|
||||
xxl:
|
||||
@@ -153,3 +163,28 @@ justauth:
|
||||
type: REDIS
|
||||
prefix: 'social_auth_state:' # 缓存前缀,目前只对 Redis 缓存生效,默认 JUSTAUTH::STATE::
|
||||
timeout: 24h # 超时时长,目前只对 Redis 缓存生效,默认 3 分钟
|
||||
seata:
|
||||
enabled: true
|
||||
application-id: system-server
|
||||
tx-service-group: dev_tx_group
|
||||
enable-auto-data-source-proxy: true
|
||||
data-source-proxy-mode: AT
|
||||
registry:
|
||||
type: file
|
||||
config:
|
||||
type: file
|
||||
service:
|
||||
vgroupMapping:
|
||||
default_tx_group: default
|
||||
dev_tx_group: default
|
||||
test_tx_group: default
|
||||
prod_tx_group: default
|
||||
default:
|
||||
grouplist: 172.16.46.63:30088
|
||||
client:
|
||||
tm:
|
||||
defaultGlobalTransactionTimeout: 60000
|
||||
undo:
|
||||
logTable: undo_log
|
||||
dataValidation: true
|
||||
logSerialization: jackson
|
||||
@@ -106,7 +106,6 @@ easy-trans:
|
||||
--- #################### iWork 集成配置 ####################
|
||||
|
||||
iwork:
|
||||
enabled: true
|
||||
base-url: http://172.16.36.233:8080
|
||||
app-id:
|
||||
client-public-key:
|
||||
@@ -124,6 +123,17 @@ iwork:
|
||||
client:
|
||||
connect-timeout: 5s
|
||||
response-timeout: 30s
|
||||
org:
|
||||
token-seed: 456465
|
||||
paths:
|
||||
subcompany-page: /api/hrm/resful/getHrmsubcompanyWithPage
|
||||
department-page: /api/hrm/resful/getHrmdepartmentWithPage
|
||||
job-title-page: /api/hrm/resful/getJobtitleInfoWithPage
|
||||
user-page: /api/hrm/resful/getHrmUserInfoWithPage
|
||||
sync-subcompany: /api/hrm/resful/synSubcompany
|
||||
sync-department: /api/hrm/resful/synDepartment
|
||||
sync-job-title: /api/hrm/resful/synJobtitle
|
||||
sync-user: /api/hrm/resful/synHrmresource
|
||||
|
||||
--- #################### RPC 远程调用相关配置 ####################
|
||||
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
package com.zt.plat.module.system.service.integration.iwork.impl;
|
||||
|
||||
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.IWorkOrgSyncReqVO;
|
||||
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;
|
||||
import okhttp3.mockwebserver.MockWebServer;
|
||||
import okhttp3.mockwebserver.RecordedRequest;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.util.DigestUtils;
|
||||
|
||||
import java.net.URLDecoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
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;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class IWorkOrgRestServiceImplTest {
|
||||
|
||||
private MockWebServer mockWebServer;
|
||||
private IWorkOrgRestService service;
|
||||
private IWorkProperties properties;
|
||||
private Clock fixedClock;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws Exception {
|
||||
mockWebServer = new MockWebServer();
|
||||
mockWebServer.start();
|
||||
|
||||
properties = buildProperties();
|
||||
fixedClock = Clock.fixed(Instant.ofEpochMilli(1_672_531_200_000L), ZoneOffset.UTC);
|
||||
service = new IWorkOrgRestServiceImpl(properties, new ObjectMapper(), fixedClock);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() throws Exception {
|
||||
mockWebServer.shutdown();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldListSubcompanies() throws Exception {
|
||||
mockWebServer.enqueue(jsonResponse("{\"code\":\"1\",\"data\":{\"page\":1}}"));
|
||||
|
||||
IWorkOrgQueryReqVO reqVO = new IWorkOrgQueryReqVO();
|
||||
reqVO.setParams(Map.of("curpage", 1));
|
||||
IWorkOrgRespVO respVO = service.listSubcompanies(reqVO);
|
||||
|
||||
assertThat(respVO.isSuccess()).isTrue();
|
||||
assertThat(respVO.getPayload()).containsEntry("page", 1);
|
||||
|
||||
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 + "\"");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSyncDepartments() throws Exception {
|
||||
mockWebServer.enqueue(jsonResponse("{\"code\":\"1\",\"result\":{}}"));
|
||||
|
||||
IWorkOrgSyncReqVO reqVO = new IWorkOrgSyncReqVO();
|
||||
reqVO.setData(List.of(Map.of("@action", "add", "code", "demo")));
|
||||
IWorkOrgRespVO respVO = service.syncDepartments(reqVO);
|
||||
|
||||
assertThat(respVO.isSuccess()).isTrue();
|
||||
assertThat(respVO.getPayload()).containsKey("result");
|
||||
|
||||
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\"}]");
|
||||
}
|
||||
|
||||
private MockResponse jsonResponse(String body) {
|
||||
return new MockResponse()
|
||||
.setHeader("Content-Type", "application/json")
|
||||
.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());
|
||||
properties.getClient().setConnectTimeout(Duration.ofSeconds(5));
|
||||
properties.getClient().setResponseTimeout(Duration.ofSeconds(5));
|
||||
properties.getOrg().setTokenSeed("test-seed");
|
||||
IWorkProperties.OrgPaths paths = properties.getOrg().getPaths();
|
||||
paths.setSubcompanyPage("/api/hrm/resful/getHrmsubcompanyWithPage");
|
||||
paths.setDepartmentPage("/api/hrm/resful/getHrmdepartmentWithPage");
|
||||
paths.setJobTitlePage("/api/hrm/resful/getJobtitleInfoWithPage");
|
||||
paths.setUserPage("/api/hrm/resful/getHrmUserInfoWithPage");
|
||||
paths.setSyncSubcompany("/api/hrm/resful/synSubcompany");
|
||||
paths.setSyncDepartment("/api/hrm/resful/synDepartment");
|
||||
paths.setSyncJobTitle("/api/hrm/resful/synJobtitle");
|
||||
paths.setSyncUser("/api/hrm/resful/synHrmresource");
|
||||
return properties;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package com.zt.plat.module.system.service.sso;
|
||||
|
||||
import com.zt.plat.module.system.framework.sso.config.ExternalSsoProperties;
|
||||
import com.zt.plat.module.system.service.logger.LoginLogService;
|
||||
import com.zt.plat.module.system.service.logger.OperateLogService;
|
||||
import com.zt.plat.module.system.service.oauth2.OAuth2TokenService;
|
||||
import com.zt.plat.module.system.service.sso.strategy.ExternalSsoStrategy;
|
||||
import com.zt.plat.module.system.service.user.AdminUserService;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
class ExternalSsoServiceImplTest {
|
||||
|
||||
@Test
|
||||
void selectStrategy_returnsExactMatch_whenStrategySupportsSource() {
|
||||
ExternalSsoStrategy exactStrategy = mock(ExternalSsoStrategy.class);
|
||||
ExternalSsoStrategy fallbackStrategy = mock(ExternalSsoStrategy.class);
|
||||
when(exactStrategy.supports("BRMS")).thenReturn(true);
|
||||
|
||||
ExternalSsoServiceImpl service = newService(List.of(exactStrategy, fallbackStrategy));
|
||||
|
||||
ExternalSsoStrategy selected = invokeSelectStrategy(service, "BRMS");
|
||||
assertThat(selected).isSameAs(exactStrategy);
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectStrategy_returnsWildcardStrategy_whenNoExactMatch() {
|
||||
ExternalSsoStrategy strictStrategy = mock(ExternalSsoStrategy.class);
|
||||
ExternalSsoStrategy wildcardStrategy = mock(ExternalSsoStrategy.class);
|
||||
when(strictStrategy.supports("ERP")).thenReturn(false);
|
||||
when(strictStrategy.supports(null)).thenReturn(false);
|
||||
when(wildcardStrategy.supports("ERP")).thenReturn(false);
|
||||
when(wildcardStrategy.supports(null)).thenReturn(true);
|
||||
|
||||
ExternalSsoServiceImpl service = newService(List.of(strictStrategy, wildcardStrategy));
|
||||
|
||||
ExternalSsoStrategy selected = invokeSelectStrategy(service, "ERP");
|
||||
assertThat(selected).isSameAs(wildcardStrategy);
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectStrategy_returnsNull_whenNoStrategyAvailable() {
|
||||
ExternalSsoServiceImpl service = newService(Collections.emptyList());
|
||||
|
||||
ExternalSsoStrategy selected = invokeSelectStrategy(service, "ANY");
|
||||
assertThat(selected).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectStrategy_skipsStrategy_whenSupportsThrowsException() {
|
||||
ExternalSsoStrategy unstableStrategy = mock(ExternalSsoStrategy.class);
|
||||
ExternalSsoStrategy fallbackStrategy = mock(ExternalSsoStrategy.class);
|
||||
when(unstableStrategy.supports("CRM")).thenThrow(new IllegalStateException("boom"));
|
||||
when(unstableStrategy.supports(null)).thenReturn(false);
|
||||
when(fallbackStrategy.supports("CRM")).thenReturn(false);
|
||||
when(fallbackStrategy.supports(null)).thenReturn(true);
|
||||
|
||||
ExternalSsoServiceImpl service = newService(List.of(unstableStrategy, fallbackStrategy));
|
||||
|
||||
ExternalSsoStrategy selected = invokeSelectStrategy(service, "CRM");
|
||||
assertThat(selected).isSameAs(fallbackStrategy);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private ExternalSsoStrategy invokeSelectStrategy(ExternalSsoServiceImpl service, String sourceSystem) {
|
||||
return ReflectionTestUtils.invokeMethod(service, "selectStrategy", sourceSystem);
|
||||
}
|
||||
|
||||
private ExternalSsoServiceImpl newService(List<ExternalSsoStrategy> strategies) {
|
||||
ExternalSsoProperties properties = new ExternalSsoProperties();
|
||||
AdminUserService adminUserService = mock(AdminUserService.class);
|
||||
LoginLogService loginLogService = mock(LoginLogService.class);
|
||||
OAuth2TokenService oauth2TokenService = mock(OAuth2TokenService.class);
|
||||
OperateLogService operateLogService = mock(OperateLogService.class);
|
||||
return new ExternalSsoServiceImpl(properties, strategies, adminUserService,
|
||||
loginLogService, oauth2TokenService, operateLogService);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user