Merge remote-tracking branch 'origin/dev' into dev
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
## AdoptOpenJDK 停止发布 OpenJDK 二进制,而 Eclipse Temurin 是它的延伸,提供更好的稳定性
|
||||
|
||||
ARG BASE_IMAGE=172.16.46.66:10043/base-service/skywalking-agent-jre:9.7.0
|
||||
FROM ${BASE_IMAGE}
|
||||
FROM 172.16.46.66:10043/base-service/eclipse-temurin:21-jre
|
||||
|
||||
## 创建目录,并使用它作为工作目录
|
||||
RUN mkdir -p /zt-module-system-server
|
||||
@@ -11,15 +10,10 @@ COPY ./target/zt-module-system-server.jar app.jar
|
||||
|
||||
## 设置 TZ 时区
|
||||
## 设置 JAVA_OPTS 环境变量,可通过 docker run -e "JAVA_OPTS=" 进行覆盖
|
||||
ENV TZ=Asia/Shanghai
|
||||
ENV JAVA_OPTS="-Xms512m -Xmx512m"
|
||||
ENV SW_AGENT_HOME=/opt/skywalking/agent
|
||||
ENV SW_AGENT_NAME=zt-module-system-server
|
||||
ENV SW_AGENT_COLLECTOR_BACKEND_SERVICES=172.16.46.63:30201
|
||||
ENV AGENT_JAVA_OPTS="-javaagent:${SW_AGENT_HOME}/skywalking-agent.jar -Dskywalking.agent.service_name=${SW_AGENT_NAME} -Dskywalking.collector.backend_service=${SW_AGENT_COLLECTOR_BACKEND_SERVICES}"
|
||||
ENV TZ=Asia/Shanghai JAVA_OPTS="-Xms512m -Xmx1024m"
|
||||
|
||||
## 暴露后端项目的 48080 端口
|
||||
EXPOSE 48081
|
||||
|
||||
## 启动后端项目
|
||||
CMD java ${AGENT_JAVA_OPTS} ${JAVA_OPTS} -Djava.security.egd=file:/dev/./urandom -jar app.jar
|
||||
CMD java ${JAVA_OPTS} -Djava.security.egd=file:/dev/./urandom -jar app.jar
|
||||
|
||||
@@ -50,6 +50,9 @@ public class AuthPermissionInfoRespVO {
|
||||
@Schema(description = "用户账号", requiredMode = Schema.RequiredMode.REQUIRED, example = "zt")
|
||||
private String username;
|
||||
|
||||
@Schema(description = "工号", example = "A00123")
|
||||
private String workcode;
|
||||
|
||||
@Schema(description = "用户邮箱", example = "zt@iocoder.cn")
|
||||
private String email;
|
||||
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
package com.zt.plat.module.system.controller.admin.integration.iwork;
|
||||
|
||||
import com.zt.plat.framework.common.pojo.CommonResult;
|
||||
import com.zt.plat.framework.tenant.core.aop.TenantIgnore;
|
||||
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.annotation.security.PermitAll;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
@@ -14,7 +16,6 @@ import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import jakarta.annotation.security.PermitAll;
|
||||
|
||||
import static com.zt.plat.framework.common.pojo.CommonResult.success;
|
||||
|
||||
@@ -57,6 +58,7 @@ public class IWorkIntegrationController {
|
||||
}
|
||||
|
||||
@PermitAll
|
||||
@TenantIgnore
|
||||
@PostMapping("/callback/file")
|
||||
@Operation(summary = "iWork 文件回调:根据文件 URL 保存为附件并创建业务附件关联")
|
||||
public CommonResult<Long> callbackFile(@Valid @RequestBody IWorkFileCallbackReqVO reqVO) {
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
package com.zt.plat.module.system.controller.admin.integration.iwork.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 传递给 iWork 的单条明细记录。
|
||||
*/
|
||||
@Data
|
||||
public class IWorkDetailRecordVO {
|
||||
|
||||
@Schema(description = "记录序号,从 0 开始", example = "0")
|
||||
private Integer recordOrder;
|
||||
|
||||
@Schema(description = "明细字段列表")
|
||||
@NotEmpty(message = "明细字段不能为空")
|
||||
@Valid
|
||||
private List<IWorkFormFieldVO> fields;
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package com.zt.plat.module.system.controller.admin.integration.iwork.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* iWork 流程请求中的明细表定义。
|
||||
*/
|
||||
@Data
|
||||
public class IWorkDetailTableVO {
|
||||
|
||||
@Schema(description = "表名", example = "formtable_main_26_dt1")
|
||||
@NotBlank(message = "明细表名不能为空")
|
||||
private String tableDBName;
|
||||
|
||||
@Schema(description = "明细记录集合")
|
||||
@NotEmpty(message = "明细记录不能为空")
|
||||
@Valid
|
||||
private List<IWorkDetailRecordVO> records;
|
||||
}
|
||||
@@ -12,9 +12,9 @@ public class IWorkFileCallbackReqVO {
|
||||
@NotBlank(message = "文件 URL 不能为空")
|
||||
private String fileUrl;
|
||||
|
||||
@Schema(description = "业务 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "123456")
|
||||
@NotBlank(message = "业务 ID 不能为空")
|
||||
private String businessId;
|
||||
@Schema(description = "业务编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "DJ-2025-0001")
|
||||
@NotBlank(message = "业务编码不能为空")
|
||||
private String businessCode;
|
||||
|
||||
@Schema(description = "文件名称,可选", example = "合同附件.pdf")
|
||||
private String fileName;
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
package com.zt.plat.module.system.controller.admin.integration.iwork.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 提交给 iWork 的单个表单字段。
|
||||
*/
|
||||
@Data
|
||||
public class IWorkFormFieldVO {
|
||||
|
||||
@Schema(description = "字段名", example = "sqr")
|
||||
@NotBlank(message = "字段名不能为空")
|
||||
private String fieldName;
|
||||
|
||||
@Schema(description = "字段值", example = "张三")
|
||||
@NotBlank(message = "字段值不能为空")
|
||||
private String fieldValue;
|
||||
}
|
||||
@@ -33,6 +33,9 @@ public class IWorkFullSyncReqVO {
|
||||
@Schema(description = "同步范围列表,默认同步全部。可选:subcompany、department、jobTitle、user")
|
||||
private List<String> scopes;
|
||||
|
||||
@Schema(description = "指定同步记录的 iWork ID。传入后仅同步对应记录", example = "12345")
|
||||
private String id;
|
||||
|
||||
@Schema(description = "是否包含已失效(canceled=1)的记录", example = "false")
|
||||
private Boolean includeCanceled = Boolean.FALSE;
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.zt.plat.module.system.controller.admin.integration.iwork.vo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@@ -11,12 +12,49 @@ import java.util.Map;
|
||||
@Data
|
||||
public class IWorkOperationRespVO {
|
||||
|
||||
@Schema(description = "iWork 返回的原始数据")
|
||||
private Map<String, Object> payload;
|
||||
@Schema(description = "iWork 返回的原始数据结构")
|
||||
private Payload payload;
|
||||
|
||||
@Schema(description = "是否判断为成功")
|
||||
private boolean success;
|
||||
|
||||
@Schema(description = "返回提示信息")
|
||||
private String message;
|
||||
|
||||
@Data
|
||||
public static class Payload {
|
||||
|
||||
@Schema(description = "iWork 返回的业务状态码,例如 SUCCESS")
|
||||
private String code;
|
||||
|
||||
@Schema(description = "iWork 返回的数据体")
|
||||
private PayloadData data;
|
||||
|
||||
@Schema(description = "错误信息对象,通常为空对象")
|
||||
private Map<String, Object> errMsg;
|
||||
|
||||
@Schema(description = "返回失败时的详细信息")
|
||||
private ReqFailMsg reqFailMsg;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class PayloadData {
|
||||
|
||||
@Schema(description = "iWork 生成的请求编号 requestid")
|
||||
@JsonProperty("requestid")
|
||||
private Long requestId;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class ReqFailMsg {
|
||||
|
||||
@Schema(description = "失败时的关键参数集合")
|
||||
private Map<String, Object> keyParameters;
|
||||
|
||||
@Schema(description = "失败消息对象")
|
||||
private Map<String, Object> msgInfo;
|
||||
|
||||
@Schema(description = "其他附加参数,例如 doAutoApprove")
|
||||
private Map<String, Object> otherParams;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
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;
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
package com.zt.plat.module.system.controller.admin.integration.iwork.vo;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 获取 iWork 会话令牌的请求载荷。
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class IWorkTokenApplyReqVO extends IWorkBaseReqVO {
|
||||
}
|
||||
@@ -22,6 +22,9 @@ public class UserProfileRespVO {
|
||||
@Schema(description = "用户账号", requiredMode = Schema.RequiredMode.REQUIRED, example = "zt")
|
||||
private String username;
|
||||
|
||||
@Schema(description = "工号", example = "A00123")
|
||||
private String workcode;
|
||||
|
||||
@Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "ZT")
|
||||
private String nickname;
|
||||
|
||||
|
||||
@@ -23,6 +23,9 @@ public class UserPageReqVO extends PageParam {
|
||||
@Schema(description = "用户账号,模糊匹配", example = "zt")
|
||||
private String username;
|
||||
|
||||
@Schema(description = "工号,模糊匹配", example = "A00123")
|
||||
private String workcode;
|
||||
|
||||
@Schema(description = "手机号码,模糊匹配", example = "zt")
|
||||
private String mobile;
|
||||
|
||||
|
||||
@@ -25,6 +25,10 @@ public class UserRespVO{
|
||||
@ExcelProperty("用户名称")
|
||||
private String username;
|
||||
|
||||
@Schema(description = "工号", example = "A00123")
|
||||
@ExcelProperty("工号")
|
||||
private String workcode;
|
||||
|
||||
@Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "ZT")
|
||||
@ExcelProperty("用户昵称")
|
||||
private String nickname;
|
||||
|
||||
@@ -33,6 +33,11 @@ public class UserSaveReqVO {
|
||||
@DiffLogField(name = "用户账号")
|
||||
private String username;
|
||||
|
||||
@Schema(description = "工号", example = "A00123")
|
||||
@Length(max = 64, message = "工号长度不能超过64个字符")
|
||||
@DiffLogField(name = "工号")
|
||||
private String workcode;
|
||||
|
||||
@Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "ZT")
|
||||
@Size(max = 30, message = "用户昵称长度不能超过30个字符")
|
||||
@DiffLogField(name = "用户昵称")
|
||||
|
||||
@@ -38,6 +38,10 @@ public class AdminUserDO extends TenantBaseDO {
|
||||
*/
|
||||
@NotEmpty
|
||||
private String username;
|
||||
/**
|
||||
* 工号
|
||||
*/
|
||||
private String workcode;
|
||||
/**
|
||||
* 加密后的密码
|
||||
*
|
||||
|
||||
@@ -23,6 +23,10 @@ public interface AdminUserMapper extends BaseMapperX<AdminUserDO> {
|
||||
return selectOne(AdminUserDO::getUsername, username);
|
||||
}
|
||||
|
||||
default AdminUserDO selectByWorkcode(String workcode) {
|
||||
return selectOne(AdminUserDO::getWorkcode, workcode);
|
||||
}
|
||||
|
||||
default AdminUserDO selectByEmail(String email) {
|
||||
return selectOne(AdminUserDO::getEmail, email);
|
||||
}
|
||||
@@ -36,6 +40,7 @@ public interface AdminUserMapper extends BaseMapperX<AdminUserDO> {
|
||||
return selectJoinPage(reqVO, AdminUserDO.class, new MPJLambdaWrapperX<AdminUserDO>()
|
||||
.leftJoin(UserDeptDO.class, UserDeptDO::getUserId, AdminUserDO::getId)
|
||||
.likeIfPresent(AdminUserDO::getUsername, reqVO.getUsername())
|
||||
.likeIfPresent(AdminUserDO::getWorkcode, reqVO.getWorkcode())
|
||||
.likeIfPresent(AdminUserDO::getMobile, reqVO.getMobile())
|
||||
.eqIfPresent(AdminUserDO::getStatus, reqVO.getStatus())
|
||||
.betweenIfPresent(AdminUserDO::getCreateTime, reqVO.getCreateTime())
|
||||
|
||||
@@ -97,11 +97,6 @@ public class IWorkProperties {
|
||||
*/
|
||||
@Min(value = 1, message = "iWork Token 有效期必须大于 0")
|
||||
private long ttlSeconds;
|
||||
/**
|
||||
* Token 过期前提前刷新的秒数。
|
||||
*/
|
||||
@Min(value = 0, message = "iWork Token 提前刷新秒数不能为负数")
|
||||
private long refreshAheadSeconds;
|
||||
}
|
||||
|
||||
@Data
|
||||
|
||||
@@ -10,13 +10,13 @@ public interface IWorkIntegrationErrorCodeConstants {
|
||||
ErrorCode IWORK_BASE_URL_MISSING = new ErrorCode(1_010_200_001, "iWork 集成未配置网关地址");
|
||||
ErrorCode IWORK_CONFIGURATION_INVALID = new ErrorCode(1_010_200_002,
|
||||
"iWork 集成缺少必填配置(appId/userId/workflowId)或配置无效");
|
||||
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_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 人力组织接口请求失败{}");
|
||||
ErrorCode IWORK_ORG_REMOTE_FAILED = new ErrorCode(1_010_200_010, "iWork 人力组织接口请求失败:{}");
|
||||
ErrorCode IWORK_SEAL_REQUIRED_FIELD_MISSING = new ErrorCode(1_010_200_011, "缺少用印必填字段:{}");
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import com.zt.plat.framework.common.exception.ServiceException;
|
||||
import com.zt.plat.framework.common.exception.util.ServiceExceptionUtil;
|
||||
import com.zt.plat.framework.common.pojo.CommonResult;
|
||||
import com.zt.plat.module.infra.api.businessfile.BusinessFileApi;
|
||||
import com.zt.plat.module.infra.api.businessfile.dto.BusinessFileRespDTO;
|
||||
import com.zt.plat.module.infra.api.businessfile.dto.BusinessFileSaveReqDTO;
|
||||
import com.zt.plat.module.infra.api.file.FileApi;
|
||||
import com.zt.plat.module.infra.api.file.dto.FileCreateReqDTO;
|
||||
@@ -18,9 +19,9 @@ import com.zt.plat.module.infra.api.file.dto.FileRespDTO;
|
||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.*;
|
||||
import com.zt.plat.module.system.framework.integration.iwork.config.IWorkProperties;
|
||||
import com.zt.plat.module.system.service.integration.iwork.IWorkIntegrationService;
|
||||
import com.zt.plat.framework.tenant.core.util.TenantUtils;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.ToString;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import okhttp3.*;
|
||||
import okio.Buffer;
|
||||
@@ -37,6 +38,7 @@ import java.security.*;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
import java.time.Instant;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import static com.zt.plat.module.system.service.integration.iwork.IWorkIntegrationErrorCodeConstants.*;
|
||||
|
||||
@@ -59,10 +61,6 @@ public class IWorkIntegrationServiceImpl implements IWorkIntegrationService {
|
||||
private final FileApi fileApi;
|
||||
private final BusinessFileApi businessFileApi;
|
||||
|
||||
private final Cache<SessionKey, IWorkSession> sessionCache = Caffeine.newBuilder()
|
||||
.maximumSize(256)
|
||||
.build();
|
||||
|
||||
private final Cache<String, RegistrationState> registrationCache = Caffeine.newBuilder()
|
||||
.maximumSize(32)
|
||||
.build();
|
||||
@@ -104,7 +102,7 @@ public class IWorkIntegrationServiceImpl implements IWorkIntegrationService {
|
||||
boolean force = forceRegistration || forceToken;
|
||||
|
||||
ClientKeyPair clientKeyPair = resolveClientKeyPair(appId, forceRegistration);
|
||||
IWorkSession session = ensureSession(appId, clientKeyPair, operatorUserId, force);
|
||||
IWorkSession session = createSession(appId, clientKeyPair, operatorUserId, force);
|
||||
|
||||
IWorkAuthTokenRespVO respVO = new IWorkAuthTokenRespVO();
|
||||
respVO.setAppId(appId);
|
||||
@@ -124,7 +122,7 @@ public class IWorkIntegrationServiceImpl implements IWorkIntegrationService {
|
||||
String operatorUserId = resolveOperatorUserId(reqVO.getOperatorUserId());
|
||||
ensureIdentifier(reqVO.getIdentifierKey(), reqVO.getIdentifierValue());
|
||||
|
||||
IWorkSession session = ensureSession(appId, clientKeyPair, operatorUserId, Boolean.TRUE.equals(reqVO.getForceRefreshToken()));
|
||||
IWorkSession session = createSession(appId, clientKeyPair, operatorUserId, Boolean.TRUE.equals(reqVO.getForceRefreshToken()));
|
||||
Map<String, Object> payload = buildUserPayload(reqVO);
|
||||
String responseBody = executeJsonRequest(properties.getPaths().getUserInfo(), reqVO.getQueryParams(), appId, session, payload);
|
||||
|
||||
@@ -137,7 +135,7 @@ public class IWorkIntegrationServiceImpl implements IWorkIntegrationService {
|
||||
String appId = resolveAppId();
|
||||
ClientKeyPair clientKeyPair = resolveClientKeyPair(appId, Boolean.TRUE.equals(reqVO.getForceRefreshToken()));
|
||||
String operatorUserId = resolveOperatorUserId(reqVO.getOperatorUserId());
|
||||
IWorkSession session = ensureSession(appId, clientKeyPair, operatorUserId, Boolean.TRUE.equals(reqVO.getForceRefreshToken()));
|
||||
IWorkSession session = createSession(appId, clientKeyPair, operatorUserId, Boolean.TRUE.equals(reqVO.getForceRefreshToken()));
|
||||
|
||||
Map<String, Object> payload = buildCreatePayload(reqVO);
|
||||
String responseBody = executeFormRequest(properties.getPaths().getCreateWorkflow(), null, appId, session, payload);
|
||||
@@ -153,7 +151,7 @@ public class IWorkIntegrationServiceImpl implements IWorkIntegrationService {
|
||||
if (!StringUtils.hasText(reqVO.getRequestId())) {
|
||||
throw ServiceExceptionUtil.exception(IWORK_USER_IDENTIFIER_MISSING);
|
||||
}
|
||||
IWorkSession session = ensureSession(appId, clientKeyPair, operatorUserId, Boolean.TRUE.equals(reqVO.getForceRefreshToken()));
|
||||
IWorkSession session = createSession(appId, clientKeyPair, operatorUserId, Boolean.TRUE.equals(reqVO.getForceRefreshToken()));
|
||||
|
||||
Map<String, Object> payload = buildVoidPayload(reqVO);
|
||||
String responseBody = executeJsonRequest(properties.getPaths().getVoidWorkflow(), null, appId, session, payload);
|
||||
@@ -166,24 +164,30 @@ public class IWorkIntegrationServiceImpl implements IWorkIntegrationService {
|
||||
throw new ServiceException(IWORK_CONFIGURATION_INVALID.getCode(), "回调请求不能为空");
|
||||
}
|
||||
String fileUrl = Optional.ofNullable(reqVO.getFileUrl()).map(String::trim).orElse("");
|
||||
String businessIdStr = Optional.ofNullable(reqVO.getBusinessId()).map(String::trim).orElse("");
|
||||
String businessCode = Optional.ofNullable(reqVO.getBusinessCode()).map(String::trim).orElse("");
|
||||
if (!StringUtils.hasText(fileUrl)) {
|
||||
throw new ServiceException(IWORK_CONFIGURATION_INVALID.getCode(), "文件 URL 不能为空");
|
||||
}
|
||||
if (!StringUtils.hasText(businessIdStr)) {
|
||||
throw new ServiceException(IWORK_CONFIGURATION_INVALID.getCode(), "业务 ID 不能为空");
|
||||
if (!StringUtils.hasText(businessCode)) {
|
||||
throw new ServiceException(IWORK_CONFIGURATION_INVALID.getCode(), "业务编码不能为空");
|
||||
}
|
||||
|
||||
Long businessId;
|
||||
try {
|
||||
businessId = Long.valueOf(businessIdStr);
|
||||
} catch (NumberFormatException ex) {
|
||||
throw new ServiceException(IWORK_CONFIGURATION_INVALID.getCode(), "业务 ID 必须是数字: " + businessIdStr);
|
||||
BusinessFileRespDTO referenceBusinessFile = loadBusinessFileByBusinessCode(businessCode);
|
||||
Long tenantId = referenceBusinessFile.getTenantId();
|
||||
if (tenantId == null) {
|
||||
throw new ServiceException(IWORK_CONFIGURATION_INVALID.getCode(), "业务附件缺少租户信息,无法创建回调附件: " + businessCode);
|
||||
}
|
||||
|
||||
// 通过文件 API 创建文件
|
||||
AtomicReference<Long> attachmentIdRef = new AtomicReference<>();
|
||||
TenantUtils.execute(tenantId, () -> attachmentIdRef.set(saveCallbackAttachment(fileUrl, reqVO.getFileName(), referenceBusinessFile)));
|
||||
return attachmentIdRef.get();
|
||||
}
|
||||
|
||||
private Long saveCallbackAttachment(String fileUrl, String overrideFileName, BusinessFileRespDTO referenceBusinessFile) {
|
||||
Long businessId = referenceBusinessFile.getBusinessId();
|
||||
|
||||
FileCreateReqDTO fileCreateReqDTO = new FileCreateReqDTO();
|
||||
fileCreateReqDTO.setName(resolveFileName(reqVO.getFileName(), fileUrl));
|
||||
fileCreateReqDTO.setName(resolveFileName(overrideFileName, fileUrl));
|
||||
fileCreateReqDTO.setDirectory(null);
|
||||
fileCreateReqDTO.setType(null);
|
||||
fileCreateReqDTO.setContent(downloadFileBytes(fileUrl));
|
||||
@@ -197,6 +201,7 @@ public class IWorkIntegrationServiceImpl implements IWorkIntegrationService {
|
||||
|
||||
BusinessFileSaveReqDTO businessReq = BusinessFileSaveReqDTO.builder()
|
||||
.businessId(businessId)
|
||||
.businessCode(referenceBusinessFile.getBusinessCode())
|
||||
.fileId(fileId)
|
||||
.fileName(fileResult.getData().getName())
|
||||
.source("iwork")
|
||||
@@ -209,6 +214,21 @@ public class IWorkIntegrationServiceImpl implements IWorkIntegrationService {
|
||||
return bizResult.getData();
|
||||
}
|
||||
|
||||
private BusinessFileRespDTO loadBusinessFileByBusinessCode(String businessCode) {
|
||||
CommonResult<BusinessFileRespDTO> businessResult = businessFileApi.getBusinessFileByBusinessCode(businessCode);
|
||||
if (businessResult == null || !businessResult.isSuccess() || businessResult.getData() == null) {
|
||||
throw new ServiceException(IWORK_CONFIGURATION_INVALID.getCode(),
|
||||
"根据业务编码获取业务附件关联失败: " + Optional.ofNullable(businessResult)
|
||||
.map(CommonResult::getMsg)
|
||||
.orElse("未知错误"));
|
||||
}
|
||||
BusinessFileRespDTO businessFile = businessResult.getData();
|
||||
if (businessFile.getBusinessId() == null) {
|
||||
throw new ServiceException(IWORK_CONFIGURATION_INVALID.getCode(), "业务编码未绑定业务 ID: " + businessCode);
|
||||
}
|
||||
return businessFile;
|
||||
}
|
||||
|
||||
private byte[] downloadFileBytes(String fileUrl) {
|
||||
OkHttpClient client = okHttpClient();
|
||||
Request request = new Request.Builder().url(fileUrl).get().build();
|
||||
@@ -308,26 +328,6 @@ public class IWorkIntegrationServiceImpl implements IWorkIntegrationService {
|
||||
}
|
||||
}
|
||||
|
||||
private IWorkSession ensureSession(String appId, ClientKeyPair clientKeyPair, String operatorUserId, boolean forceRefresh) {
|
||||
SessionKey key = new SessionKey(appId, operatorUserId);
|
||||
Instant now = Instant.now();
|
||||
if (!forceRefresh) {
|
||||
IWorkSession cached = sessionCache.getIfPresent(key);
|
||||
if (cached != null && cached.isValid(now, properties.getToken().getRefreshAheadSeconds())) {
|
||||
return cached;
|
||||
}
|
||||
}
|
||||
synchronized (key.intern()) {
|
||||
IWorkSession cached = sessionCache.getIfPresent(key);
|
||||
if (!forceRefresh && cached != null && cached.isValid(now, properties.getToken().getRefreshAheadSeconds())) {
|
||||
return cached;
|
||||
}
|
||||
IWorkSession session = createSession(appId, clientKeyPair, operatorUserId, forceRefresh);
|
||||
sessionCache.put(key, session);
|
||||
return session;
|
||||
}
|
||||
}
|
||||
|
||||
private IWorkSession createSession(String appId, ClientKeyPair clientKeyPair, String operatorUserId, boolean forceRefreshRegistration) {
|
||||
RegistrationState registration = obtainRegistration(appId, clientKeyPair, forceRefreshRegistration);
|
||||
String encryptedSecret = encryptWithPublicKey(registration.secret(), registration.spk());
|
||||
@@ -651,7 +651,7 @@ public class IWorkIntegrationServiceImpl implements IWorkIntegrationService {
|
||||
return respVO;
|
||||
}
|
||||
JsonNode node = parseJson(responseBody, IWORK_REMOTE_REQUEST_FAILED);
|
||||
respVO.setPayload(objectMapper.convertValue(node, MAP_TYPE));
|
||||
respVO.setPayload(objectMapper.convertValue(node, IWorkOperationRespVO.Payload.class));
|
||||
respVO.setSuccess(isSuccess(node));
|
||||
respVO.setMessage(resolveMessage(node));
|
||||
return respVO;
|
||||
@@ -850,15 +850,26 @@ public class IWorkIntegrationServiceImpl implements IWorkIntegrationService {
|
||||
}
|
||||
|
||||
private ServiceException buildRemoteException(ErrorCode errorCode, int statusCode, String responseBody) {
|
||||
StringBuilder message = new StringBuilder(errorCode.getMsg());
|
||||
String detail = buildRemoteErrorDetail(statusCode, responseBody);
|
||||
if (!StringUtils.hasText(detail)) {
|
||||
detail = "未知错误";
|
||||
}
|
||||
return ServiceExceptionUtil.exception(errorCode, detail);
|
||||
}
|
||||
|
||||
private String buildRemoteErrorDetail(int statusCode, String responseBody) {
|
||||
StringBuilder detail = new StringBuilder();
|
||||
if (statusCode > 0) {
|
||||
message.append("(HTTP ").append(statusCode).append(")");
|
||||
detail.append("HTTP ").append(statusCode);
|
||||
}
|
||||
String reason = extractReadableReason(responseBody);
|
||||
if (StringUtils.hasText(reason)) {
|
||||
message.append(":").append(reason);
|
||||
if (detail.length() > 0) {
|
||||
detail.append(",");
|
||||
}
|
||||
detail.append(reason);
|
||||
}
|
||||
return ServiceExceptionUtil.exception0(errorCode.getCode(), message.toString());
|
||||
return detail.toString();
|
||||
}
|
||||
|
||||
private String extractReadableReason(String responseBody) {
|
||||
@@ -981,46 +992,5 @@ public class IWorkIntegrationServiceImpl implements IWorkIntegrationService {
|
||||
this.expiresAt = expiresAt;
|
||||
this.spk = spk;
|
||||
}
|
||||
|
||||
private boolean isValid(Instant now, long refreshAheadSeconds) {
|
||||
Instant refreshThreshold = expiresAt.minusSeconds(Math.max(0L, refreshAheadSeconds));
|
||||
return refreshThreshold.isAfter(now) && StringUtils.hasText(token) && StringUtils.hasText(encryptedUserId);
|
||||
}
|
||||
}
|
||||
|
||||
@ToString
|
||||
private static final class SessionKey {
|
||||
private final String appId;
|
||||
private final String operatorUserId;
|
||||
|
||||
private SessionKey(String appId, String operatorUserId) {
|
||||
this.appId = appId;
|
||||
this.operatorUserId = operatorUserId;
|
||||
}
|
||||
|
||||
private String cacheKey() {
|
||||
return appId + "::" + operatorUserId;
|
||||
}
|
||||
|
||||
private String intern() {
|
||||
return cacheKey().intern();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (!(o instanceof SessionKey that)) {
|
||||
return false;
|
||||
}
|
||||
return Objects.equals(appId, that.appId)
|
||||
&& Objects.equals(operatorUserId, that.operatorUserId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(appId, operatorUserId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -435,6 +435,7 @@ public class IWorkSyncProcessorImpl implements IWorkSyncProcessor {
|
||||
CommonStatusEnum status) {
|
||||
UserSaveReqVO req = new UserSaveReqVO();
|
||||
req.setUsername(username);
|
||||
req.setWorkcode(trimToNull(source.getWorkcode()));
|
||||
req.setNickname(limitLength(StrUtil.blankToDefault(source.getLastname(), username), 30));
|
||||
req.setRemark(buildUserRemark(source));
|
||||
if (deptId != null) {
|
||||
|
||||
@@ -14,7 +14,9 @@ import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static com.zt.plat.module.system.service.integration.iwork.IWorkIntegrationErrorCodeConstants.IWORK_ORG_REMOTE_FAILED;
|
||||
@@ -86,6 +88,7 @@ public class IWorkSyncServiceImpl implements IWorkSyncService {
|
||||
IWorkSubcompanyQueryReqVO query = new IWorkSubcompanyQueryReqVO();
|
||||
query.setCurpage(page);
|
||||
query.setPagesize(pageSize);
|
||||
applyQueryConditions(query, reqVO);
|
||||
IWorkHrSubcompanyPageRespVO pageResp = orgRestService.listSubcompanies(query);
|
||||
ensureIWorkSuccess("拉取分部", pageResp.isSuccess(), pageResp.getMessage());
|
||||
List<IWorkHrSubcompanyPageRespVO.Subcompany> dataList = CollUtil.emptyIfNull(pageResp.getDataList());
|
||||
@@ -103,6 +106,7 @@ public class IWorkSyncServiceImpl implements IWorkSyncService {
|
||||
IWorkDepartmentQueryReqVO query = new IWorkDepartmentQueryReqVO();
|
||||
query.setCurpage(page);
|
||||
query.setPagesize(pageSize);
|
||||
applyQueryConditions(query, reqVO);
|
||||
IWorkHrDepartmentPageRespVO pageResp = orgRestService.listDepartments(query);
|
||||
ensureIWorkSuccess("拉取部门", pageResp.isSuccess(), pageResp.getMessage());
|
||||
List<IWorkHrDepartmentPageRespVO.Department> dataList = CollUtil.emptyIfNull(pageResp.getDataList());
|
||||
@@ -120,6 +124,7 @@ public class IWorkSyncServiceImpl implements IWorkSyncService {
|
||||
IWorkJobTitleQueryReqVO query = new IWorkJobTitleQueryReqVO();
|
||||
query.setCurpage(page);
|
||||
query.setPagesize(pageSize);
|
||||
applyQueryConditions(query, reqVO);
|
||||
IWorkHrJobTitlePageRespVO pageResp = orgRestService.listJobTitles(query);
|
||||
ensureIWorkSuccess("拉取岗位", pageResp.isSuccess(), pageResp.getMessage());
|
||||
List<IWorkHrJobTitlePageRespVO.JobTitle> dataList = CollUtil.emptyIfNull(pageResp.getDataList());
|
||||
@@ -137,6 +142,7 @@ public class IWorkSyncServiceImpl implements IWorkSyncService {
|
||||
IWorkUserQueryReqVO query = new IWorkUserQueryReqVO();
|
||||
query.setCurpage(page);
|
||||
query.setPagesize(pageSize);
|
||||
applyQueryConditions(query, reqVO);
|
||||
IWorkHrUserPageRespVO pageResp = orgRestService.listUsers(query);
|
||||
ensureIWorkSuccess("拉取人员", pageResp.isSuccess(), pageResp.getMessage());
|
||||
List<IWorkHrUserPageRespVO.User> dataList = CollUtil.emptyIfNull(pageResp.getDataList());
|
||||
@@ -181,6 +187,21 @@ public class IWorkSyncServiceImpl implements IWorkSyncService {
|
||||
stat.incrementFailed(result.getFailed());
|
||||
}
|
||||
|
||||
private void applyQueryConditions(IWorkOrgBaseQueryReqVO query, IWorkFullSyncReqVO reqVO) {
|
||||
if (query == null || reqVO == null) {
|
||||
return;
|
||||
}
|
||||
if (StrUtil.isBlank(reqVO.getId())) {
|
||||
return;
|
||||
}
|
||||
Map<String, Object> params = query.getParams();
|
||||
if (params == null) {
|
||||
params = new HashMap<>();
|
||||
query.setParams(params);
|
||||
}
|
||||
params.put("id", reqVO.getId());
|
||||
}
|
||||
|
||||
private IWorkSyncProcessor.SyncOptions buildFullSyncOptions(IWorkFullSyncReqVO reqVO) {
|
||||
boolean includeCanceled = Boolean.TRUE.equals(reqVO.getIncludeCanceled());
|
||||
boolean allowUpdate = Boolean.TRUE.equals(reqVO.getAllowUpdate());
|
||||
|
||||
@@ -72,16 +72,16 @@ public class EbanOAuth2ServiceImpl implements EbanOAuth2Service {
|
||||
throw exception(AUTH_LOGIN_EBAN_TOKEN_INVALID);
|
||||
}
|
||||
|
||||
String username = StrUtil.trim(StrUtil.blankToDefault(userInfo.getLoginName(), userInfo.getUsername()));
|
||||
if (StrUtil.isBlank(username)) {
|
||||
log.error("E办OAuth2用户信息缺少 username 与 loginName,无法匹配账号: {}", JSONUtil.toJsonStr(userInfo));
|
||||
String workcode = StrUtil.trim(StrUtil.blankToDefault(userInfo.getLoginName(), userInfo.getUsername()));
|
||||
if (StrUtil.isBlank(workcode)) {
|
||||
log.error("E办OAuth2用户信息缺少工号(loginName),无法匹配账号: {}", JSONUtil.toJsonStr(userInfo));
|
||||
throw exception(AUTH_LOGIN_EBAN_TOKEN_INVALID);
|
||||
}
|
||||
|
||||
AdminUserDO user = userService.getUserByUsername(username);
|
||||
AdminUserDO user = userService.getUserByWorkcode(workcode);
|
||||
if (user == null) {
|
||||
createLoginLog(null, username, LoginLogTypeEnum.LOGIN_SOCIAL, LoginResultEnum.BAD_CREDENTIALS);
|
||||
log.warn("E办OAuth2用户displayName未在系统中找到对应账号: {}", username);
|
||||
createLoginLog(null, workcode, LoginLogTypeEnum.LOGIN_SOCIAL, LoginResultEnum.BAD_CREDENTIALS);
|
||||
log.warn("E办OAuth2用户工号未在系统中找到对应账号: {}", workcode);
|
||||
throw exception(AUTH_LOGIN_EBAN_USER_NOT_SYNC);
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ public class EbanOAuth2ServiceImpl implements EbanOAuth2Service {
|
||||
|
||||
EbanTokenInfo tokenInfo = userInfo.getTokenInfo();
|
||||
if (tokenInfo == null || StrUtil.isBlank(tokenInfo.getAccessToken())) {
|
||||
log.error("E办OAuth2回调缺少有效的token信息,uid={}, username={}", userInfo.getUid(), username);
|
||||
log.error("E办OAuth2回调缺少有效的token信息,uid={}, username={}", userInfo.getUid(), userInfo.getUsername());
|
||||
throw exception(AUTH_LOGIN_EBAN_TOKEN_INVALID);
|
||||
}
|
||||
|
||||
@@ -105,7 +105,7 @@ public class EbanOAuth2ServiceImpl implements EbanOAuth2Service {
|
||||
tokenInfo.getExpiresIn(),
|
||||
userInfo
|
||||
);
|
||||
log.info("成功保存E办token,userId={}, uid={}, username={}", user.getId(), userInfo.getUid(), username);
|
||||
log.info("成功保存E办token,userId={}, uid={}, workcode={}", user.getId(), userInfo.getUid(), workcode);
|
||||
|
||||
return createTokenAfterLoginSuccess(user.getId(), user.getUsername(), LoginLogTypeEnum.LOGIN_SOCIAL, ebanAccessTokenDO);
|
||||
|
||||
|
||||
@@ -101,6 +101,14 @@ public interface AdminUserService {
|
||||
*/
|
||||
AdminUserDO getUserByUsername(String username);
|
||||
|
||||
/**
|
||||
* 通过工号查询用户
|
||||
*
|
||||
* @param workcode 工号
|
||||
* @return 用户对象信息
|
||||
*/
|
||||
AdminUserDO getUserByWorkcode(String workcode);
|
||||
|
||||
/**
|
||||
* 通过手机号获取用户
|
||||
*
|
||||
|
||||
@@ -128,6 +128,7 @@ public class AdminUserServiceImpl implements AdminUserService {
|
||||
if (user.getUserSource() == null) {
|
||||
user.setUserSource(UserSourceEnum.EXTERNAL.getSource());
|
||||
}
|
||||
user.setWorkcode(normalizeWorkcode(createReqVO.getWorkcode()));
|
||||
PasswordStrategyEnum passwordStrategy = determinePasswordStrategy(user.getUserSource());
|
||||
user.setAvatar(normalizeAvatarValue(createReqVO.getAvatar()));
|
||||
user.setPassword(encodePassword(createReqVO.getPassword(), passwordStrategy));
|
||||
@@ -196,6 +197,9 @@ public class AdminUserServiceImpl implements AdminUserService {
|
||||
if (StrUtil.isNotBlank(updateReqVO.getNickname())) {
|
||||
updateObj.setNickname(updateReqVO.getNickname());
|
||||
}
|
||||
if (updateReqVO.getWorkcode() != null) {
|
||||
updateObj.setWorkcode(normalizeWorkcode(updateReqVO.getWorkcode()));
|
||||
}
|
||||
if (StrUtil.isNotBlank(updateReqVO.getMobile())) {
|
||||
updateObj.setMobile(updateReqVO.getMobile());
|
||||
}
|
||||
@@ -345,6 +349,19 @@ public class AdminUserServiceImpl implements AdminUserService {
|
||||
return user;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AdminUserDO getUserByWorkcode(String workcode) {
|
||||
String normalized = normalizeWorkcode(workcode);
|
||||
if (StrUtil.isBlank(normalized)) {
|
||||
return null;
|
||||
}
|
||||
AdminUserDO user = userMapper.selectByWorkcode(normalized);
|
||||
if (user != null) {
|
||||
fillUserDeptInfo(Collections.singletonList(user));
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AdminUserDO getUserByMobile(String mobile) {
|
||||
AdminUserDO user = userMapper.selectByMobile(mobile);
|
||||
@@ -525,6 +542,13 @@ public class AdminUserServiceImpl implements AdminUserService {
|
||||
return StrUtil.isBlank(avatarValue) ? null : avatarValue.trim();
|
||||
}
|
||||
|
||||
private String normalizeWorkcode(String workcode) {
|
||||
if (StrUtil.isBlank(workcode)) {
|
||||
return null;
|
||||
}
|
||||
return workcode.trim();
|
||||
}
|
||||
|
||||
private AdminUserDO validateUserForCreateOrUpdate(Long id, String username, String mobile, String email,
|
||||
Set<Long> deptIds, Set<Long> postIds, boolean skipAssociationValidation,
|
||||
boolean skipMobileValidation, boolean skipEmailValidation) {
|
||||
|
||||
@@ -118,10 +118,9 @@ iwork:
|
||||
void-workflow: /api/workflow/paService/doCancelRequest
|
||||
token:
|
||||
ttl-seconds: 3600
|
||||
refresh-ahead-seconds: 60
|
||||
client:
|
||||
connect-timeout: 5s
|
||||
response-timeout: 30s
|
||||
connect-timeout: 60s
|
||||
response-timeout: 60s
|
||||
org:
|
||||
token-seed: 456465
|
||||
paths:
|
||||
|
||||
Reference in New Issue
Block a user