Merge remote-tracking branch 'base-version/main' into dev
This commit is contained in:
@@ -1,12 +1,12 @@
|
|||||||
package com.zt.plat.framework.tenant.core.db;
|
package com.zt.plat.framework.tenant.core.db;
|
||||||
|
|
||||||
import com.zt.plat.framework.tenant.config.TenantProperties;
|
|
||||||
import com.zt.plat.framework.tenant.core.aop.TenantIgnore;
|
|
||||||
import com.zt.plat.framework.tenant.core.context.TenantContextHolder;
|
|
||||||
import com.baomidou.mybatisplus.core.metadata.TableInfo;
|
import com.baomidou.mybatisplus.core.metadata.TableInfo;
|
||||||
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
|
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
|
||||||
import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
|
import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
|
||||||
import com.baomidou.mybatisplus.extension.toolkit.SqlParserUtils;
|
import com.baomidou.mybatisplus.extension.toolkit.SqlParserUtils;
|
||||||
|
import com.zt.plat.framework.tenant.config.TenantProperties;
|
||||||
|
import com.zt.plat.framework.tenant.core.aop.TenantIgnore;
|
||||||
|
import com.zt.plat.framework.tenant.core.context.TenantContextHolder;
|
||||||
import net.sf.jsqlparser.expression.Expression;
|
import net.sf.jsqlparser.expression.Expression;
|
||||||
import net.sf.jsqlparser.expression.LongValue;
|
import net.sf.jsqlparser.expression.LongValue;
|
||||||
|
|
||||||
@@ -69,7 +69,12 @@ public class TenantDatabaseInterceptor implements TenantLineHandler {
|
|||||||
// 找不到的表,说明不是 zt 项目里的,不进行拦截(忽略租户)
|
// 找不到的表,说明不是 zt 项目里的,不进行拦截(忽略租户)
|
||||||
TableInfo tableInfo = TableInfoHelper.getTableInfo(tableName);
|
TableInfo tableInfo = TableInfoHelper.getTableInfo(tableName);
|
||||||
if (tableInfo == null) {
|
if (tableInfo == null) {
|
||||||
return true;
|
tableName = tableName.toLowerCase();
|
||||||
|
tableInfo = TableInfoHelper.getTableInfo(tableName);
|
||||||
|
}
|
||||||
|
if (tableInfo == null) {
|
||||||
|
tableName = tableName.toLowerCase();
|
||||||
|
tableInfo = TableInfoHelper.getTableInfo(tableName);
|
||||||
}
|
}
|
||||||
// 如果继承了 TenantBaseDO 基类,显然不忽略租户
|
// 如果继承了 TenantBaseDO 基类,显然不忽略租户
|
||||||
if (TenantBaseDO.class.isAssignableFrom(tableInfo.getEntityType())) {
|
if (TenantBaseDO.class.isAssignableFrom(tableInfo.getEntityType())) {
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
package com.zt.plat.module.system.api.iwork;
|
||||||
|
|
||||||
|
import com.zt.plat.framework.common.pojo.CommonResult;
|
||||||
|
import com.zt.plat.module.system.api.iwork.dto.*;
|
||||||
|
import com.zt.plat.module.system.enums.ApiConstants;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import org.springframework.cloud.openfeign.FeignClient;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RPC 服务 - iWork 集成
|
||||||
|
*/
|
||||||
|
@FeignClient(name = ApiConstants.NAME, contextId = "iWorkIntegrationApi")
|
||||||
|
@Tag(name = "RPC 服务 - iWork 集成")
|
||||||
|
public interface IWorkIntegrationApi {
|
||||||
|
|
||||||
|
String PREFIX = ApiConstants.PREFIX + "/integration/iwork";
|
||||||
|
|
||||||
|
// ----------------- 认证 / 会话 -----------------
|
||||||
|
|
||||||
|
@PostMapping(PREFIX + "/auth/register")
|
||||||
|
@Operation(summary = "注册 iWork 凭证,获取服务端公钥与 secret")
|
||||||
|
CommonResult<IWorkAuthRegisterRespDTO> register(@RequestBody IWorkAuthRegisterReqDTO reqDTO);
|
||||||
|
|
||||||
|
@PostMapping(PREFIX + "/auth/token")
|
||||||
|
@Operation(summary = "申请 iWork Token(独立接口)")
|
||||||
|
CommonResult<IWorkAuthTokenRespDTO> acquireToken(@RequestBody IWorkAuthTokenReqDTO reqDTO);
|
||||||
|
|
||||||
|
// ----------------- 流程类能力 -----------------
|
||||||
|
|
||||||
|
@PostMapping(PREFIX + "/user/resolve")
|
||||||
|
@Operation(summary = "根据外部标识获取 iWork 用户编号")
|
||||||
|
CommonResult<IWorkUserInfoRespDTO> resolveUser(@RequestBody IWorkUserInfoReqDTO reqDTO);
|
||||||
|
|
||||||
|
@PostMapping(PREFIX + "/workflow/create")
|
||||||
|
@Operation(summary = "发起 iWork 流程")
|
||||||
|
CommonResult<IWorkOperationRespDTO> createWorkflow(@RequestBody IWorkWorkflowCreateReqDTO reqDTO);
|
||||||
|
|
||||||
|
@PostMapping(PREFIX + "/workflow/void")
|
||||||
|
@Operation(summary = "作废 / 干预 iWork 流程")
|
||||||
|
CommonResult<IWorkOperationRespDTO> voidWorkflow(@RequestBody IWorkWorkflowVoidReqDTO reqDTO);
|
||||||
|
|
||||||
|
// ----------------- 人力组织分页接口 -----------------
|
||||||
|
|
||||||
|
@PostMapping(PREFIX + "/hr/subcompany/page")
|
||||||
|
@Operation(summary = "获取 iWork 分部列表")
|
||||||
|
CommonResult<IWorkHrSubcompanyPageRespDTO> listSubcompanies(@RequestBody IWorkOrgPageReqDTO reqDTO);
|
||||||
|
|
||||||
|
@PostMapping(PREFIX + "/hr/department/page")
|
||||||
|
@Operation(summary = "获取 iWork 部门列表")
|
||||||
|
CommonResult<IWorkHrDepartmentPageRespDTO> listDepartments(@RequestBody IWorkOrgPageReqDTO reqDTO);
|
||||||
|
|
||||||
|
@PostMapping(PREFIX + "/hr/job-title/page")
|
||||||
|
@Operation(summary = "获取 iWork 岗位列表")
|
||||||
|
CommonResult<IWorkHrJobTitlePageRespDTO> listJobTitles(@RequestBody IWorkOrgPageReqDTO reqDTO);
|
||||||
|
|
||||||
|
@PostMapping(PREFIX + "/hr/user/page")
|
||||||
|
@Operation(summary = "获取 iWork 人员列表")
|
||||||
|
CommonResult<IWorkHrUserPageRespDTO> listUsers(@RequestBody IWorkOrgPageReqDTO reqDTO);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package com.zt.plat.module.system.api.iwork.dto;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* iWork 注册授权请求 DTO(供其他模块通过 Feign 调用 system-server 时使用)
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class IWorkAuthRegisterReqDTO {
|
||||||
|
|
||||||
|
@Schema(description = "iWork 应用编码", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
private String appCode;
|
||||||
|
|
||||||
|
@Schema(description = "iWork 网关地址", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
|
||||||
|
private String baseUrl;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package com.zt.plat.module.system.api.iwork.dto;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* iWork 注册授权响应 DTO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class IWorkAuthRegisterRespDTO {
|
||||||
|
|
||||||
|
@Schema(description = "服务端公钥(Base64)")
|
||||||
|
private String publicKey;
|
||||||
|
|
||||||
|
@Schema(description = "服务端下发的 secret")
|
||||||
|
private String secret;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.zt.plat.module.system.api.iwork.dto;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* iWork Token 申请请求 DTO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class IWorkAuthTokenReqDTO {
|
||||||
|
|
||||||
|
@Schema(description = "应用编码", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
private String appCode;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package com.zt.plat.module.system.api.iwork.dto;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* iWork Token 响应 DTO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class IWorkAuthTokenRespDTO {
|
||||||
|
|
||||||
|
@Schema(description = "访问令牌")
|
||||||
|
private String accessToken;
|
||||||
|
|
||||||
|
@Schema(description = "过期时间(秒)")
|
||||||
|
private Long expiresIn;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package com.zt.plat.module.system.api.iwork.dto;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* iWork 部门分页响应 DTO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class IWorkHrDepartmentPageRespDTO {
|
||||||
|
|
||||||
|
@Schema(description = "总条数")
|
||||||
|
private Long total;
|
||||||
|
|
||||||
|
@Schema(description = "当前页数据")
|
||||||
|
private List<Item> list;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class Item {
|
||||||
|
|
||||||
|
@Schema(description = "部门编号")
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
@Schema(description = "部门名称")
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package com.zt.plat.module.system.api.iwork.dto;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* iWork 岗位分页响应 DTO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class IWorkHrJobTitlePageRespDTO {
|
||||||
|
|
||||||
|
@Schema(description = "总条数")
|
||||||
|
private Long total;
|
||||||
|
|
||||||
|
@Schema(description = "当前页数据")
|
||||||
|
private List<Item> list;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class Item {
|
||||||
|
|
||||||
|
@Schema(description = "岗位编号")
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
@Schema(description = "岗位名称")
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package com.zt.plat.module.system.api.iwork.dto;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* iWork 分部分页响应 DTO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class IWorkHrSubcompanyPageRespDTO {
|
||||||
|
|
||||||
|
@Schema(description = "总条数")
|
||||||
|
private Long total;
|
||||||
|
|
||||||
|
@Schema(description = "当前页数据")
|
||||||
|
private List<Item> list;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class Item {
|
||||||
|
|
||||||
|
@Schema(description = "分部编号")
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
@Schema(description = "分部名称")
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package com.zt.plat.module.system.api.iwork.dto;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* iWork 人员分页响应 DTO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class IWorkHrUserPageRespDTO {
|
||||||
|
|
||||||
|
@Schema(description = "总条数")
|
||||||
|
private Long total;
|
||||||
|
|
||||||
|
@Schema(description = "当前页数据")
|
||||||
|
private List<Item> list;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class Item {
|
||||||
|
|
||||||
|
@Schema(description = "人员编号")
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
@Schema(description = "人员名称")
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package com.zt.plat.module.system.api.iwork.dto;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* iWork 操作结果响应 DTO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class IWorkOperationRespDTO {
|
||||||
|
|
||||||
|
@Schema(description = "是否成功")
|
||||||
|
private Boolean success;
|
||||||
|
|
||||||
|
@Schema(description = "iWork 返回的操作编号或实例编号")
|
||||||
|
private String operationId;
|
||||||
|
|
||||||
|
@Schema(description = "提示信息")
|
||||||
|
private String message;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package com.zt.plat.module.system.api.iwork.dto;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* iWork 人力组织分页查询通用请求 DTO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class IWorkOrgPageReqDTO {
|
||||||
|
|
||||||
|
@Schema(description = "页码", example = "1", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
private Integer pageNo;
|
||||||
|
|
||||||
|
@Schema(description = "每页大小", example = "20", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
private Integer pageSize;
|
||||||
|
|
||||||
|
@Schema(description = "关键字过滤")
|
||||||
|
private String keyword;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.zt.plat.module.system.api.iwork.dto;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据外部标识解析 iWork 用户请求 DTO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class IWorkUserInfoReqDTO {
|
||||||
|
|
||||||
|
@Schema(description = "外部系统中的用户唯一标识", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
private String externalUserCode;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package com.zt.plat.module.system.api.iwork.dto;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* iWork 用户解析响应 DTO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class IWorkUserInfoRespDTO {
|
||||||
|
|
||||||
|
@Schema(description = "iWork 用户编号")
|
||||||
|
private String userId;
|
||||||
|
|
||||||
|
@Schema(description = "iWork 用户名称")
|
||||||
|
private String userName;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package com.zt.plat.module.system.api.iwork.dto;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发起 iWork 流程请求 DTO
|
||||||
|
*
|
||||||
|
* 与 IWorkWorkflowCreateReqVO 字段一一对应,便于 Feign 调用。
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class IWorkWorkflowCreateReqDTO {
|
||||||
|
|
||||||
|
@Schema(description = "用印申请人(iWork 人员 ID)", example = "1001")
|
||||||
|
private String jbr;
|
||||||
|
|
||||||
|
@Schema(description = "用印部门 ID", example = "2001")
|
||||||
|
private String yybm;
|
||||||
|
|
||||||
|
@Schema(description = "用印单位(分部 ID)", example = "3001")
|
||||||
|
private String fb;
|
||||||
|
|
||||||
|
@Schema(description = "申请时间,格式 yyyy-MM-dd", example = "2025-01-01")
|
||||||
|
private String sqsj;
|
||||||
|
|
||||||
|
@Schema(description = "用印去向")
|
||||||
|
private String yyqx;
|
||||||
|
|
||||||
|
@Schema(description = "用印依据附件 URL")
|
||||||
|
private String yyfkUrl;
|
||||||
|
|
||||||
|
@Schema(description = "用印事由或内容摘要")
|
||||||
|
private String yysy;
|
||||||
|
|
||||||
|
@Schema(description = "用印材料附件 URL(必填)")
|
||||||
|
private String xyywjUrl;
|
||||||
|
|
||||||
|
@Schema(description = "用印材料附件文件名(必填)")
|
||||||
|
private String xyywjFileName;
|
||||||
|
|
||||||
|
@Schema(description = "用印事项")
|
||||||
|
private String yysx;
|
||||||
|
|
||||||
|
@Schema(description = "业务系统单据编号(用于派生流程标题)", example = "DJ-2025-0001")
|
||||||
|
private String ywxtdjbh;
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package com.zt.plat.module.system.api.iwork.dto;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 作废 / 干预 iWork 流程请求 DTO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class IWorkWorkflowVoidReqDTO {
|
||||||
|
|
||||||
|
@Schema(description = "iWork 实例编号", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
private String instanceId;
|
||||||
|
|
||||||
|
@Schema(description = "操作人 iWork 用户编号", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
private String operatorUserId;
|
||||||
|
|
||||||
|
@Schema(description = "作废原因")
|
||||||
|
private String reason;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -61,6 +61,7 @@ public interface ErrorCodeConstants {
|
|||||||
ErrorCode USER_IMPORT_INIT_PASSWORD = new ErrorCode(1_002_003_009, "初始密码不能为空");
|
ErrorCode USER_IMPORT_INIT_PASSWORD = new ErrorCode(1_002_003_009, "初始密码不能为空");
|
||||||
ErrorCode USER_MOBILE_NOT_EXISTS = new ErrorCode(1_002_003_010, "该手机号尚未注册");
|
ErrorCode USER_MOBILE_NOT_EXISTS = new ErrorCode(1_002_003_010, "该手机号尚未注册");
|
||||||
ErrorCode USER_REGISTER_DISABLED = new ErrorCode(1_002_003_011, "注册功能已关闭");
|
ErrorCode USER_REGISTER_DISABLED = new ErrorCode(1_002_003_011, "注册功能已关闭");
|
||||||
|
ErrorCode USER_PASSWORD_MODIFY_FORBIDDEN = new ErrorCode(1_002_003_012, "该用户来源不支持修改密码");
|
||||||
|
|
||||||
// ========== 部门模块 1-002-004-000 ==========
|
// ========== 部门模块 1-002-004-000 ==========
|
||||||
ErrorCode DEPT_NAME_DUPLICATE = new ErrorCode(1_002_004_000, "当前上级部门已存在同名子部门");
|
ErrorCode DEPT_NAME_DUPLICATE = new ErrorCode(1_002_004_000, "当前上级部门已存在同名子部门");
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package com.zt.plat.module.system.enums.user;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 密码处理策略,用于区分本地账户与外部同步账户的密码存储/校验方式。
|
||||||
|
*/
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Getter
|
||||||
|
public enum PasswordStrategyEnum {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 本地创建或注册用户,使用 Spring Security {@code PasswordEncoder}(BCrypt)。
|
||||||
|
*/
|
||||||
|
LOCAL_BCRYPT("LOCAL_BCRYPT"),
|
||||||
|
/**
|
||||||
|
* iWork 同步的 MD5 密文,直接按大写 MD5 存储及校验。
|
||||||
|
*/
|
||||||
|
IWORK_MD5("IWORK_MD5");
|
||||||
|
|
||||||
|
private final String label;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -3,6 +3,9 @@ package com.zt.plat.module.system.enums.user;
|
|||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户来源枚举
|
* 用户来源枚举
|
||||||
*
|
*
|
||||||
@@ -12,9 +15,9 @@ import lombok.Getter;
|
|||||||
@Getter
|
@Getter
|
||||||
public enum UserSourceEnum {
|
public enum UserSourceEnum {
|
||||||
|
|
||||||
EXTERNAL(1, "外部用户"), // 系统创建、注册等方式产生的用户
|
EXTERNAL(1, "外部用户", PasswordStrategyEnum.LOCAL_BCRYPT), // 系统创建、注册等方式产生的用户
|
||||||
SYNC(2, "同步用户"), // 通过 UserSyncService 同步的用户
|
SYNC(2, "同步用户", PasswordStrategyEnum.LOCAL_BCRYPT), // 通过 UserSyncService 同步的用户
|
||||||
IWORK(3, "iWork 用户"); // 通过 iWork 全量/单条同步产生的用户
|
IWORK(3, "iWork 用户", PasswordStrategyEnum.IWORK_MD5); // 通过 iWork 全量/单条同步产生的用户
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 类型
|
* 类型
|
||||||
@@ -24,5 +27,28 @@ public enum UserSourceEnum {
|
|||||||
* 名字
|
* 名字
|
||||||
*/
|
*/
|
||||||
private final String name;
|
private final String name;
|
||||||
|
/**
|
||||||
|
* 默认密码策略
|
||||||
|
*/
|
||||||
|
private final PasswordStrategyEnum passwordStrategy;
|
||||||
|
|
||||||
|
public static UserSourceEnum of(Integer source) {
|
||||||
|
if (source == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return Arrays.stream(values())
|
||||||
|
.filter(item -> Objects.equals(item.source, source))
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PasswordStrategyEnum resolvePasswordStrategy(Integer source) {
|
||||||
|
UserSourceEnum matched = of(source);
|
||||||
|
return matched == null ? PasswordStrategyEnum.LOCAL_BCRYPT : matched.getPasswordStrategy();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isExternal() {
|
||||||
|
return this == EXTERNAL;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
package com.zt.plat.module.system.api.iwork;
|
||||||
|
|
||||||
|
import com.zt.plat.framework.common.pojo.CommonResult;
|
||||||
|
import com.zt.plat.framework.common.util.object.BeanUtils;
|
||||||
|
import com.zt.plat.module.system.api.iwork.dto.*;
|
||||||
|
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 jakarta.annotation.Resource;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import static com.zt.plat.framework.common.pojo.CommonResult.success;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* iWork 集成 Feign API 实现类。
|
||||||
|
* <p>
|
||||||
|
* 将 system-api 模块中的 DTO 与现有 Controller VO 进行简单转换,
|
||||||
|
* 再委托给 Service 层完成实际业务逻辑,供其他模块通过 Feign 统一调用。
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@Validated
|
||||||
|
public class IWorkIntegrationApiImpl implements IWorkIntegrationApi {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private IWorkIntegrationService integrationService;
|
||||||
|
@Resource
|
||||||
|
private IWorkOrgRestService orgRestService;
|
||||||
|
@Resource
|
||||||
|
private IWorkSyncService syncService;
|
||||||
|
|
||||||
|
// ----------------- 认证 / 会话 -----------------
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CommonResult<IWorkAuthRegisterRespDTO> register(IWorkAuthRegisterReqDTO reqDTO) {
|
||||||
|
IWorkAuthRegisterReqVO reqVO = BeanUtils.toBean(reqDTO, IWorkAuthRegisterReqVO.class);
|
||||||
|
IWorkAuthRegisterRespVO respVO = integrationService.registerSession(reqVO);
|
||||||
|
IWorkAuthRegisterRespDTO respDTO = BeanUtils.toBean(respVO, IWorkAuthRegisterRespDTO.class);
|
||||||
|
return success(respDTO);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CommonResult<IWorkAuthTokenRespDTO> acquireToken(IWorkAuthTokenReqDTO reqDTO) {
|
||||||
|
IWorkAuthTokenReqVO reqVO = BeanUtils.toBean(reqDTO, IWorkAuthTokenReqVO.class);
|
||||||
|
IWorkAuthTokenRespVO respVO = integrationService.acquireToken(reqVO);
|
||||||
|
IWorkAuthTokenRespDTO respDTO = BeanUtils.toBean(respVO, IWorkAuthTokenRespDTO.class);
|
||||||
|
return success(respDTO);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------- 流程类能力 -----------------
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CommonResult<IWorkUserInfoRespDTO> resolveUser(IWorkUserInfoReqDTO reqDTO) {
|
||||||
|
IWorkUserInfoReqVO reqVO = BeanUtils.toBean(reqDTO, IWorkUserInfoReqVO.class);
|
||||||
|
IWorkUserInfoRespVO respVO = integrationService.resolveUserId(reqVO);
|
||||||
|
IWorkUserInfoRespDTO respDTO = BeanUtils.toBean(respVO, IWorkUserInfoRespDTO.class);
|
||||||
|
return success(respDTO);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CommonResult<IWorkOperationRespDTO> createWorkflow(IWorkWorkflowCreateReqDTO reqDTO) {
|
||||||
|
IWorkWorkflowCreateReqVO reqVO = BeanUtils.toBean(reqDTO, IWorkWorkflowCreateReqVO.class);
|
||||||
|
IWorkOperationRespVO respVO = integrationService.createWorkflow(reqVO);
|
||||||
|
IWorkOperationRespDTO respDTO = BeanUtils.toBean(respVO, IWorkOperationRespDTO.class);
|
||||||
|
return success(respDTO);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CommonResult<IWorkOperationRespDTO> voidWorkflow(IWorkWorkflowVoidReqDTO reqDTO) {
|
||||||
|
IWorkWorkflowVoidReqVO reqVO = BeanUtils.toBean(reqDTO, IWorkWorkflowVoidReqVO.class);
|
||||||
|
IWorkOperationRespVO respVO = integrationService.voidWorkflow(reqVO);
|
||||||
|
IWorkOperationRespDTO respDTO = BeanUtils.toBean(respVO, IWorkOperationRespDTO.class);
|
||||||
|
return success(respDTO);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------- 人力组织分页接口 -----------------
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CommonResult<IWorkHrSubcompanyPageRespDTO> listSubcompanies(IWorkOrgPageReqDTO reqDTO) {
|
||||||
|
IWorkSubcompanyQueryReqVO reqVO = BeanUtils.toBean(reqDTO, IWorkSubcompanyQueryReqVO.class);
|
||||||
|
IWorkHrSubcompanyPageRespVO respVO = orgRestService.listSubcompanies(reqVO);
|
||||||
|
IWorkHrSubcompanyPageRespDTO respDTO = BeanUtils.toBean(respVO, IWorkHrSubcompanyPageRespDTO.class);
|
||||||
|
return success(respDTO);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CommonResult<IWorkHrDepartmentPageRespDTO> listDepartments(IWorkOrgPageReqDTO reqDTO) {
|
||||||
|
IWorkDepartmentQueryReqVO reqVO = BeanUtils.toBean(reqDTO, IWorkDepartmentQueryReqVO.class);
|
||||||
|
IWorkHrDepartmentPageRespVO respVO = orgRestService.listDepartments(reqVO);
|
||||||
|
IWorkHrDepartmentPageRespDTO respDTO = BeanUtils.toBean(respVO, IWorkHrDepartmentPageRespDTO.class);
|
||||||
|
return success(respDTO);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CommonResult<IWorkHrJobTitlePageRespDTO> listJobTitles(IWorkOrgPageReqDTO reqDTO) {
|
||||||
|
IWorkJobTitleQueryReqVO reqVO = BeanUtils.toBean(reqDTO, IWorkJobTitleQueryReqVO.class);
|
||||||
|
IWorkHrJobTitlePageRespVO respVO = orgRestService.listJobTitles(reqVO);
|
||||||
|
IWorkHrJobTitlePageRespDTO respDTO = BeanUtils.toBean(respVO, IWorkHrJobTitlePageRespDTO.class);
|
||||||
|
return success(respDTO);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CommonResult<IWorkHrUserPageRespDTO> listUsers(IWorkOrgPageReqDTO reqDTO) {
|
||||||
|
IWorkUserQueryReqVO reqVO = BeanUtils.toBean(reqDTO, IWorkUserQueryReqVO.class);
|
||||||
|
IWorkHrUserPageRespVO respVO = orgRestService.listUsers(reqVO);
|
||||||
|
IWorkHrUserPageRespDTO respDTO = BeanUtils.toBean(respVO, IWorkHrUserPageRespDTO.class);
|
||||||
|
return success(respDTO);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -113,12 +113,6 @@ public class IWorkIntegrationController {
|
|||||||
|
|
||||||
// ----------------- 同步到本地 -----------------
|
// ----------------- 同步到本地 -----------------
|
||||||
|
|
||||||
@PostMapping("/hr/full-sync")
|
|
||||||
@Operation(summary = "手动触发 iWork 组织/人员同步")
|
|
||||||
public CommonResult<IWorkFullSyncRespVO> fullSync(@Valid @RequestBody IWorkFullSyncReqVO reqVO) {
|
|
||||||
return success(syncService.fullSync(reqVO));
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping("/hr/departments/full-sync")
|
@PostMapping("/hr/departments/full-sync")
|
||||||
@Operation(summary = "手动触发 iWork 部门同步")
|
@Operation(summary = "手动触发 iWork 部门同步")
|
||||||
public CommonResult<IWorkFullSyncRespVO> fullSyncDepartments(@Valid @RequestBody IWorkFullSyncReqVO reqVO) {
|
public CommonResult<IWorkFullSyncRespVO> fullSyncDepartments(@Valid @RequestBody IWorkFullSyncReqVO reqVO) {
|
||||||
@@ -142,10 +136,4 @@ public class IWorkIntegrationController {
|
|||||||
public CommonResult<IWorkFullSyncRespVO> fullSyncUsers(@Valid @RequestBody IWorkFullSyncReqVO reqVO) {
|
public CommonResult<IWorkFullSyncRespVO> fullSyncUsers(@Valid @RequestBody IWorkFullSyncReqVO reqVO) {
|
||||||
return success(syncService.fullSyncUsers(reqVO));
|
return success(syncService.fullSyncUsers(reqVO));
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/hr/single-sync")
|
|
||||||
@Operation(summary = "按 iWork ID 同步单条组织/人员")
|
|
||||||
public CommonResult<IWorkSingleSyncRespVO> singleSync(@Valid @RequestBody IWorkSingleSyncReqVO reqVO) {
|
|
||||||
return success(syncService.syncSingle(reqVO));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -171,6 +171,10 @@ public class IWorkHrUserPageRespVO {
|
|||||||
@JsonProperty("accounttype")
|
@JsonProperty("accounttype")
|
||||||
private String accounttype;
|
private String accounttype;
|
||||||
|
|
||||||
|
@Schema(description = "用户密码(MD5 密文)")
|
||||||
|
@JsonProperty("password")
|
||||||
|
private String password;
|
||||||
|
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
private Map<String, Object> attributes;
|
private Map<String, Object> attributes;
|
||||||
|
|
||||||
|
|||||||
@@ -14,9 +14,6 @@ public class IWorkOperationRespVO {
|
|||||||
@Schema(description = "iWork 返回的原始数据")
|
@Schema(description = "iWork 返回的原始数据")
|
||||||
private Map<String, Object> payload;
|
private Map<String, Object> payload;
|
||||||
|
|
||||||
@Schema(description = "iWork 返回的原始字符串")
|
|
||||||
private String rawBody;
|
|
||||||
|
|
||||||
@Schema(description = "是否判断为成功")
|
@Schema(description = "是否判断为成功")
|
||||||
private boolean success;
|
private boolean success;
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
package com.zt.plat.module.system.controller.admin.integration.iwork.vo;
|
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated 请改用强类型的 IWorkHr*RespVO,避免再引用该占位类。
|
|
||||||
*/
|
|
||||||
@Deprecated(forRemoval = true)
|
|
||||||
@Schema(description = "已废弃,占位用")
|
|
||||||
public final class IWorkOrgRespVO {
|
|
||||||
|
|
||||||
private IWorkOrgRespVO() {
|
|
||||||
throw new UnsupportedOperationException("Use IWorkHr*RespVO instead");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
package com.zt.plat.module.system.controller.admin.integration.iwork.vo;
|
|
||||||
|
|
||||||
import com.zt.plat.module.system.enums.integration.IWorkSyncEntityTypeEnum;
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
|
||||||
import jakarta.validation.constraints.Min;
|
|
||||||
import jakarta.validation.constraints.NotNull;
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* iWork 单条同步请求
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
public class IWorkSingleSyncReqVO {
|
|
||||||
|
|
||||||
@Schema(description = "同步的实体类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "user")
|
|
||||||
@NotNull(message = "实体类型不能为空")
|
|
||||||
private IWorkSyncEntityTypeEnum entityType;
|
|
||||||
|
|
||||||
@Schema(description = "iWork 提供的实体主键 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "10001")
|
|
||||||
@NotNull(message = "实体 ID 不能为空")
|
|
||||||
@Min(1)
|
|
||||||
private Long entityId;
|
|
||||||
|
|
||||||
@Schema(description = "缺失时是否自动创建", example = "true")
|
|
||||||
private Boolean createIfMissing = Boolean.TRUE;
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
package com.zt.plat.module.system.controller.admin.integration.iwork.vo;
|
|
||||||
|
|
||||||
import com.zt.plat.module.system.enums.integration.IWorkSyncEntityTypeEnum;
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* iWork 单条同步响应
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
public class IWorkSingleSyncRespVO {
|
|
||||||
|
|
||||||
@Schema(description = "同步的实体类型")
|
|
||||||
private IWorkSyncEntityTypeEnum entityType;
|
|
||||||
|
|
||||||
@Schema(description = "实体 ID")
|
|
||||||
private Long entityId;
|
|
||||||
|
|
||||||
@Schema(description = "是否创建了新的记录")
|
|
||||||
private boolean created;
|
|
||||||
|
|
||||||
@Schema(description = "是否对已有记录进行了更新")
|
|
||||||
private boolean updated;
|
|
||||||
|
|
||||||
@Schema(description = "提示信息")
|
|
||||||
private String message;
|
|
||||||
}
|
|
||||||
@@ -14,9 +14,6 @@ public class IWorkUserInfoRespVO {
|
|||||||
@Schema(description = "iWork 返回的原始数据")
|
@Schema(description = "iWork 返回的原始数据")
|
||||||
private Map<String, Object> payload;
|
private Map<String, Object> payload;
|
||||||
|
|
||||||
@Schema(description = "iWork 返回的原始字符串")
|
|
||||||
private String rawBody;
|
|
||||||
|
|
||||||
@Schema(description = "是否判断为成功")
|
@Schema(description = "是否判断为成功")
|
||||||
private boolean success;
|
private boolean success;
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,9 @@
|
|||||||
package com.zt.plat.module.system.controller.admin.integration.iwork.vo;
|
package com.zt.plat.module.system.controller.admin.integration.iwork.vo;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
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 lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 发起 iWork 流程的请求体。
|
* 发起 iWork 流程的请求体。
|
||||||
*/
|
*/
|
||||||
@@ -17,25 +11,36 @@ import java.util.Map;
|
|||||||
@EqualsAndHashCode(callSuper = true)
|
@EqualsAndHashCode(callSuper = true)
|
||||||
public class IWorkWorkflowCreateReqVO extends IWorkBaseReqVO {
|
public class IWorkWorkflowCreateReqVO extends IWorkBaseReqVO {
|
||||||
|
|
||||||
@Schema(description = "流程标题", example = "测试流程")
|
@Schema(description = "用印申请人(iWork 人员 ID)", example = "1001")
|
||||||
@NotBlank(message = "流程标题不能为空")
|
private String jbr;
|
||||||
private String requestName;
|
|
||||||
|
|
||||||
@Schema(description = "流程模板编号,可为空使用默认配置", example = "54")
|
@Schema(description = "用印部门 ID", example = "2001")
|
||||||
private Long workflowId;
|
private String yybm;
|
||||||
|
|
||||||
@Schema(description = "主表字段")
|
@Schema(description = "用印单位(分部 ID)", example = "3001")
|
||||||
@NotEmpty(message = "主表字段不能为空")
|
private String fb;
|
||||||
@Valid
|
|
||||||
private List<IWorkFormFieldVO> mainFields;
|
|
||||||
|
|
||||||
@Schema(description = "明细表数据")
|
@Schema(description = "申请时间,格式 yyyy-MM-dd", example = "2025-01-01")
|
||||||
@Valid
|
private String sqsj;
|
||||||
private List<IWorkDetailTableVO> detailTables;
|
|
||||||
|
|
||||||
@Schema(description = "额外参数")
|
@Schema(description = "用印去向")
|
||||||
private Map<String, Object> otherParams;
|
private String yyqx;
|
||||||
|
|
||||||
@Schema(description = "额外 Form 数据")
|
@Schema(description = "用印依据附件 URL")
|
||||||
private Map<String, String> formExtras;
|
private String yyfkUrl;
|
||||||
|
|
||||||
|
@Schema(description = "用印事由或内容摘要")
|
||||||
|
private String yysy;
|
||||||
|
|
||||||
|
@Schema(description = "用印材料附件 URL(必填)")
|
||||||
|
private String xyywjUrl;
|
||||||
|
|
||||||
|
@Schema(description = "用印材料附件文件名(必填)")
|
||||||
|
private String xyywjFileName;
|
||||||
|
|
||||||
|
@Schema(description = "用印事项")
|
||||||
|
private String yysx;
|
||||||
|
|
||||||
|
@Schema(description = "业务系统单据编号(用于派生流程标题)", example = "DJ-2025-0001")
|
||||||
|
private String ywxtdjbh;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,11 +34,6 @@ public class IWorkProperties {
|
|||||||
*/
|
*/
|
||||||
private String clientPublicKey;
|
private String clientPublicKey;
|
||||||
|
|
||||||
/**
|
|
||||||
* 当调用方未指定流程编号时使用的默认流程模板编号。
|
|
||||||
*/
|
|
||||||
private Long workflowId;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 当请求未指定操作人时使用的默认用户编号。
|
* 当请求未指定操作人时使用的默认用户编号。
|
||||||
*/
|
*/
|
||||||
@@ -53,6 +48,8 @@ public class IWorkProperties {
|
|||||||
private final Client client = new Client();
|
private final Client client = new Client();
|
||||||
@Valid
|
@Valid
|
||||||
private final OrgRest org = new OrgRest();
|
private final OrgRest org = new OrgRest();
|
||||||
|
@Valid
|
||||||
|
private final Workflow workflow = new Workflow();
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
public static class Paths {
|
public static class Paths {
|
||||||
@@ -142,4 +139,13 @@ public class IWorkProperties {
|
|||||||
private String syncJobTitle;
|
private String syncJobTitle;
|
||||||
private String syncUser;
|
private String syncUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class Workflow {
|
||||||
|
/**
|
||||||
|
* 用印流程对应的 iWork 模板编号。
|
||||||
|
*/
|
||||||
|
@NotBlank(message = "iWork 用印流程模板编号不能为空")
|
||||||
|
private String sealWorkflowId;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ public class AdminAuthServiceImpl implements AdminAuthService {
|
|||||||
createLoginLog(null, username, logTypeEnum, LoginResultEnum.BAD_CREDENTIALS);
|
createLoginLog(null, username, logTypeEnum, LoginResultEnum.BAD_CREDENTIALS);
|
||||||
throw exception(AUTH_LOGIN_BAD_CREDENTIALS);
|
throw exception(AUTH_LOGIN_BAD_CREDENTIALS);
|
||||||
}
|
}
|
||||||
if (!userService.isPasswordMatch(password, user.getPassword())) {
|
if (!userService.isPasswordMatch(user, password)) {
|
||||||
createLoginLog(user.getId(), username, logTypeEnum, LoginResultEnum.BAD_CREDENTIALS);
|
createLoginLog(user.getId(), username, logTypeEnum, LoginResultEnum.BAD_CREDENTIALS);
|
||||||
throw exception(AUTH_LOGIN_BAD_CREDENTIALS);
|
throw exception(AUTH_LOGIN_BAD_CREDENTIALS);
|
||||||
}
|
}
|
||||||
@@ -299,7 +299,7 @@ public class AdminAuthServiceImpl implements AdminAuthService {
|
|||||||
if (length < 4 || length > 16) {
|
if (length < 4 || length > 16) {
|
||||||
throw exception(AUTH_LOGIN_BAD_CREDENTIALS);
|
throw exception(AUTH_LOGIN_BAD_CREDENTIALS);
|
||||||
}
|
}
|
||||||
if (!userService.isPasswordMatch(password, user.getPassword())) {
|
if (!userService.isPasswordMatch(user, password)) {
|
||||||
throw exception(AUTH_LOGIN_BAD_CREDENTIALS);
|
throw exception(AUTH_LOGIN_BAD_CREDENTIALS);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -436,22 +436,11 @@ public class AdminAuthServiceImpl implements AdminAuthService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 判断是否为内部用户
|
* 判断是否为内部用户,仅通过 E 办同步(SYNC)来源的账号才视为内部用户
|
||||||
* 根据UserSourceEnum判断:同步用户为内部用户,外部用户为外部用户
|
|
||||||
*/
|
*/
|
||||||
private boolean isInternalUser(AdminUserDO user) {
|
private boolean isInternalUser(AdminUserDO user) {
|
||||||
// 根据userSource字段判断用户类型
|
|
||||||
Integer userSource = user.getUserSource();
|
Integer userSource = user.getUserSource();
|
||||||
|
return Objects.equals(userSource, UserSourceEnum.SYNC.getSource());
|
||||||
// 同步用户(SYNC = 2)为内部用户,需要使用E办登录
|
|
||||||
if (userSource != null &&
|
|
||||||
(userSource.equals(UserSourceEnum.SYNC.getSource()) ||
|
|
||||||
userSource.equals(UserSourceEnum.IWORK.getSource()))) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 外部用户(EXTERNAL = 1)或其他情况为外部用户,使用账号密码登录
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -18,4 +18,5 @@ public interface IWorkIntegrationErrorCodeConstants {
|
|||||||
ErrorCode IWORK_WORKFLOW_ID_MISSING = new ErrorCode(1_010_200_008, "缺少 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_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, "缺少用印必填字段:{}");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,19 +2,12 @@ package com.zt.plat.module.system.service.integration.iwork;
|
|||||||
|
|
||||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkFullSyncReqVO;
|
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkFullSyncReqVO;
|
||||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkFullSyncRespVO;
|
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkFullSyncRespVO;
|
||||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkSingleSyncReqVO;
|
|
||||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkSingleSyncRespVO;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* iWork 组织/人员同步服务
|
* iWork 组织/人员同步服务
|
||||||
*/
|
*/
|
||||||
public interface IWorkSyncService {
|
public interface IWorkSyncService {
|
||||||
|
|
||||||
/**
|
|
||||||
* 发起全量分批同步
|
|
||||||
*/
|
|
||||||
IWorkFullSyncRespVO fullSync(IWorkFullSyncReqVO reqVO);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 仅同步部门
|
* 仅同步部门
|
||||||
*/
|
*/
|
||||||
@@ -35,8 +28,4 @@ public interface IWorkSyncService {
|
|||||||
*/
|
*/
|
||||||
IWorkFullSyncRespVO fullSyncUsers(IWorkFullSyncReqVO reqVO);
|
IWorkFullSyncRespVO fullSyncUsers(IWorkFullSyncReqVO reqVO);
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据 iWork ID 进行单条同步
|
|
||||||
*/
|
|
||||||
IWorkSingleSyncRespVO syncSingle(IWorkSingleSyncReqVO reqVO);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ public class IWorkIntegrationServiceImpl implements IWorkIntegrationService {
|
|||||||
IWorkSession session = ensureSession(appId, clientKeyPair, operatorUserId, Boolean.TRUE.equals(reqVO.getForceRefreshToken()));
|
IWorkSession session = ensureSession(appId, clientKeyPair, operatorUserId, Boolean.TRUE.equals(reqVO.getForceRefreshToken()));
|
||||||
|
|
||||||
Map<String, Object> payload = buildCreatePayload(reqVO);
|
Map<String, Object> payload = buildCreatePayload(reqVO);
|
||||||
String responseBody = executeJsonRequest(properties.getPaths().getCreateWorkflow(), null, appId, session, payload);
|
String responseBody = executeFormRequest(properties.getPaths().getCreateWorkflow(), null, appId, session, payload);
|
||||||
return buildOperationResponse(responseBody);
|
return buildOperationResponse(responseBody);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -324,6 +324,39 @@ public class IWorkIntegrationServiceImpl implements IWorkIntegrationService {
|
|||||||
return executeRequest(request, IWORK_REMOTE_REQUEST_FAILED);
|
return executeRequest(request, IWORK_REMOTE_REQUEST_FAILED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String executeFormRequest(String path,
|
||||||
|
Map<String, Object> queryParams,
|
||||||
|
String appId,
|
||||||
|
IWorkSession session,
|
||||||
|
Map<String, Object> formFields) {
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
FormBody.Builder bodyBuilder = new FormBody.Builder();
|
||||||
|
if (formFields != null) {
|
||||||
|
formFields.forEach((key, value) -> {
|
||||||
|
if (StringUtils.hasText(key) && value != null) {
|
||||||
|
bodyBuilder.add(key, toFormValue(value));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Request request = new Request.Builder()
|
||||||
|
.url(urlBuilder.build())
|
||||||
|
.headers(authHeaders(appId, session).build())
|
||||||
|
.post(bodyBuilder.build())
|
||||||
|
.build();
|
||||||
|
return executeRequest(request, IWORK_REMOTE_REQUEST_FAILED);
|
||||||
|
}
|
||||||
|
|
||||||
private Headers.Builder authHeaders(String appId, IWorkSession session) {
|
private Headers.Builder authHeaders(String appId, IWorkSession session) {
|
||||||
return new Headers.Builder()
|
return new Headers.Builder()
|
||||||
.set(properties.getHeaders().getAppId(), appId)
|
.set(properties.getHeaders().getAppId(), appId)
|
||||||
@@ -331,6 +364,19 @@ public class IWorkIntegrationServiceImpl implements IWorkIntegrationService {
|
|||||||
.set(properties.getHeaders().getUserId(), session.getEncryptedUserId());
|
.set(properties.getHeaders().getUserId(), session.getEncryptedUserId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String toFormValue(Object value) {
|
||||||
|
if (value == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
if (value instanceof CharSequence || value instanceof Number || value instanceof Boolean) {
|
||||||
|
return String.valueOf(value);
|
||||||
|
}
|
||||||
|
if (value.getClass().isArray() || value instanceof Collection<?> || value instanceof Map<?, ?>) {
|
||||||
|
return toJsonString(value);
|
||||||
|
}
|
||||||
|
return value.toString();
|
||||||
|
}
|
||||||
|
|
||||||
private Map<String, Object> buildUserPayload(IWorkUserInfoReqVO reqVO) {
|
private Map<String, Object> buildUserPayload(IWorkUserInfoReqVO reqVO) {
|
||||||
Map<String, Object> payload = new HashMap<>();
|
Map<String, Object> payload = new HashMap<>();
|
||||||
if (reqVO.getPayload() != null) {
|
if (reqVO.getPayload() != null) {
|
||||||
@@ -342,27 +388,125 @@ public class IWorkIntegrationServiceImpl implements IWorkIntegrationService {
|
|||||||
|
|
||||||
private Map<String, Object> buildCreatePayload(IWorkWorkflowCreateReqVO reqVO) {
|
private Map<String, Object> buildCreatePayload(IWorkWorkflowCreateReqVO reqVO) {
|
||||||
Map<String, Object> payload = new LinkedHashMap<>();
|
Map<String, Object> payload = new LinkedHashMap<>();
|
||||||
payload.put("requestName", reqVO.getRequestName());
|
SealRequestFields fields = resolveSealFields(reqVO);
|
||||||
payload.put("workflowId", resolveWorkflowId(reqVO.getWorkflowId()));
|
payload.put("requestName", buildRequestName(fields.ywxtdjbh()));
|
||||||
payload.put("mainData", convertFormFields(reqVO.getMainFields()));
|
payload.put("workflowId", parseWorkflowId(fields.workflowId()));
|
||||||
if (reqVO.getDetailTables() != null && !reqVO.getDetailTables().isEmpty()) {
|
payload.put("mainData", buildSealMainData(fields));
|
||||||
payload.put("detailData", convertDetailTables(reqVO.getDetailTables()));
|
|
||||||
}
|
|
||||||
if (reqVO.getOtherParams() != null && !reqVO.getOtherParams().isEmpty()) {
|
|
||||||
payload.put("otherParams", reqVO.getOtherParams());
|
|
||||||
}
|
|
||||||
appendPayloadExtras(payload, reqVO.getFormExtras());
|
|
||||||
return payload;
|
return payload;
|
||||||
}
|
}
|
||||||
|
|
||||||
private long resolveWorkflowId(Long requestWorkflowId) {
|
private String buildRequestName(String billNo) {
|
||||||
if (requestWorkflowId != null) {
|
return "用印-" + billNo;
|
||||||
return requestWorkflowId;
|
}
|
||||||
|
|
||||||
|
private long parseWorkflowId(String workflowId) {
|
||||||
|
try {
|
||||||
|
return Long.parseLong(workflowId);
|
||||||
|
} catch (NumberFormatException ex) {
|
||||||
|
throw ServiceExceptionUtil.exception(IWORK_SEAL_REQUIRED_FIELD_MISSING, "workflowId");
|
||||||
}
|
}
|
||||||
if (properties.getWorkflowId() != null) {
|
}
|
||||||
return properties.getWorkflowId();
|
|
||||||
|
private List<Map<String, Object>> buildSealMainData(SealRequestFields fields) {
|
||||||
|
List<Map<String, Object>> main = new ArrayList<>();
|
||||||
|
addField(main, "jbr", fields.jbr());
|
||||||
|
addField(main, "yybm", fields.yybm());
|
||||||
|
addField(main, "fb", fields.fb());
|
||||||
|
addField(main, "sqsj", fields.sqsj());
|
||||||
|
addField(main, "yyqx", fields.yyqx());
|
||||||
|
addField(main, "yyfk", fields.yyfkUrl());
|
||||||
|
addField(main, "yysy", fields.yysy());
|
||||||
|
// xyywj 需要是一个数组结构 [{fileName,filePath}, ...]
|
||||||
|
addJsonField(main, "xyywj", buildSealAttachmentValue(fields.xyywjUrl()));
|
||||||
|
addField(main, "yysx", fields.yysx());
|
||||||
|
addField(main, "lclx", SealRequestFields.DEFAULT_FLOW_TYPE);
|
||||||
|
addField(main, "qsdz", SealRequestFields.DEFAULT_SIGN_ACTION);
|
||||||
|
addField(main, "ywxtdjbh", fields.ywxtdjbh());
|
||||||
|
return main;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addField(List<Map<String, Object>> target, String name, String value) {
|
||||||
|
if (!StringUtils.hasText(value)) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
throw ServiceExceptionUtil.exception(IWORK_WORKFLOW_ID_MISSING);
|
Map<String, Object> map = new HashMap<>(2);
|
||||||
|
map.put("fieldName", name);
|
||||||
|
map.put("fieldValue", value);
|
||||||
|
target.add(map);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addJsonField(List<Map<String, Object>> target, String name, Object value) {
|
||||||
|
if (value == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Map<String, Object> map = new HashMap<>(2);
|
||||||
|
map.put("fieldName", name);
|
||||||
|
map.put("fieldValue", value);
|
||||||
|
target.add(map);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将单个附件 URL 封装成 iWork 需要的数组结构:
|
||||||
|
* [ {"fileName": "xxx", "filePath": "url"} ]
|
||||||
|
*/
|
||||||
|
private List<Map<String, Object>> buildSealAttachmentValue(String xyywjUrl) {
|
||||||
|
String url = trimToNull(xyywjUrl);
|
||||||
|
if (url == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Map<String, Object> file = new LinkedHashMap<>(2);
|
||||||
|
// 这里简单从 URL 截取文件名,调用方也可以直接传入已经带文件名的 URL
|
||||||
|
String fileName = extractFileNameFromUrl(url);
|
||||||
|
file.put("fileName", fileName);
|
||||||
|
file.put("filePath", url);
|
||||||
|
List<Map<String, Object>> list = new ArrayList<>(1);
|
||||||
|
list.add(file);
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String extractFileNameFromUrl(String url) {
|
||||||
|
String trimmed = trimToNull(url);
|
||||||
|
if (trimmed == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
int queryIndex = trimmed.indexOf('?');
|
||||||
|
String pathPart = queryIndex >= 0 ? trimmed.substring(0, queryIndex) : trimmed;
|
||||||
|
int slashIndex = pathPart.lastIndexOf('/');
|
||||||
|
if (slashIndex >= 0 && slashIndex < pathPart.length() - 1) {
|
||||||
|
return pathPart.substring(slashIndex + 1);
|
||||||
|
}
|
||||||
|
return pathPart;
|
||||||
|
}
|
||||||
|
|
||||||
|
private SealRequestFields resolveSealFields(IWorkWorkflowCreateReqVO reqVO) {
|
||||||
|
String jbr = requireSealField(reqVO.getJbr(), "jbr");
|
||||||
|
String yybm = requireSealField(reqVO.getYybm(), "yybm");
|
||||||
|
String fb = requireSealField(reqVO.getFb(), "fb");
|
||||||
|
String sqsj = requireSealField(reqVO.getSqsj(), "sqsj");
|
||||||
|
String yyqx = requireSealField(reqVO.getYyqx(), "yyqx");
|
||||||
|
String xyywjUrl = requireSealField(reqVO.getXyywjUrl(), "xyywjUrl");
|
||||||
|
String yysx = requireSealField(reqVO.getYysx(), "yysx");
|
||||||
|
String billNo = requireSealField(reqVO.getYwxtdjbh(), "ywxtdjbh");
|
||||||
|
String yyfkUrl = trimToNull(reqVO.getYyfkUrl());
|
||||||
|
String yysy = trimToNull(reqVO.getYysy());
|
||||||
|
String workflowId = requireSealField(properties.getWorkflow().getSealWorkflowId(), "workflowId");
|
||||||
|
return new SealRequestFields(jbr, yybm, fb, sqsj, yyqx, yyfkUrl, yysy, xyywjUrl, yysx, billNo, workflowId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String requireSealField(String value, String fieldName) {
|
||||||
|
String trimmed = trimToNull(value);
|
||||||
|
if (trimmed == null) {
|
||||||
|
throw ServiceExceptionUtil.exception(IWORK_SEAL_REQUIRED_FIELD_MISSING, fieldName);
|
||||||
|
}
|
||||||
|
return trimmed;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String trimToNull(String value) {
|
||||||
|
if (!StringUtils.hasText(value)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String trimmed = value.trim();
|
||||||
|
return trimmed.isEmpty() ? null : trimmed;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<String, Object> buildVoidPayload(IWorkWorkflowVoidReqVO reqVO) {
|
private Map<String, Object> buildVoidPayload(IWorkWorkflowVoidReqVO reqVO) {
|
||||||
@@ -389,35 +533,8 @@ public class IWorkIntegrationServiceImpl implements IWorkIntegrationService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Map<String, Object>> convertFormFields(List<IWorkFormFieldVO> fields) {
|
|
||||||
return fields.stream().map(field -> {
|
|
||||||
Map<String, Object> map = new HashMap<>(2);
|
|
||||||
map.put("fieldName", field.getFieldName());
|
|
||||||
map.put("fieldValue", field.getFieldValue());
|
|
||||||
return map;
|
|
||||||
}).toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<Map<String, Object>> convertDetailTables(List<IWorkDetailTableVO> tables) {
|
|
||||||
return tables.stream().map(table -> {
|
|
||||||
Map<String, Object> tableMap = new HashMap<>(2);
|
|
||||||
tableMap.put("tableDBName", table.getTableDBName());
|
|
||||||
List<Map<String, Object>> records = table.getRecords().stream().map(record -> {
|
|
||||||
Map<String, Object> recordMap = new HashMap<>(2);
|
|
||||||
if (record.getRecordOrder() != null) {
|
|
||||||
recordMap.put("recordOrder", record.getRecordOrder());
|
|
||||||
}
|
|
||||||
recordMap.put("workflowRequestTableFields", convertFormFields(record.getFields()));
|
|
||||||
return recordMap;
|
|
||||||
}).toList();
|
|
||||||
tableMap.put("workflowRequestTableRecords", records);
|
|
||||||
return tableMap;
|
|
||||||
}).toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
private IWorkUserInfoRespVO buildUserInfoResponse(String responseBody) {
|
private IWorkUserInfoRespVO buildUserInfoResponse(String responseBody) {
|
||||||
IWorkUserInfoRespVO respVO = new IWorkUserInfoRespVO();
|
IWorkUserInfoRespVO respVO = new IWorkUserInfoRespVO();
|
||||||
respVO.setRawBody(responseBody);
|
|
||||||
if (!StringUtils.hasText(responseBody)) {
|
if (!StringUtils.hasText(responseBody)) {
|
||||||
return respVO;
|
return respVO;
|
||||||
}
|
}
|
||||||
@@ -432,7 +549,6 @@ public class IWorkIntegrationServiceImpl implements IWorkIntegrationService {
|
|||||||
|
|
||||||
private IWorkOperationRespVO buildOperationResponse(String responseBody) {
|
private IWorkOperationRespVO buildOperationResponse(String responseBody) {
|
||||||
IWorkOperationRespVO respVO = new IWorkOperationRespVO();
|
IWorkOperationRespVO respVO = new IWorkOperationRespVO();
|
||||||
respVO.setRawBody(responseBody);
|
|
||||||
if (!StringUtils.hasText(responseBody)) {
|
if (!StringUtils.hasText(responseBody)) {
|
||||||
return respVO;
|
return respVO;
|
||||||
}
|
}
|
||||||
@@ -666,6 +782,21 @@ public class IWorkIntegrationServiceImpl implements IWorkIntegrationService {
|
|||||||
return value.replace("'", "'\"'\"'");
|
return value.replace("'", "'\"'\"'");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private record SealRequestFields(String jbr,
|
||||||
|
String yybm,
|
||||||
|
String fb,
|
||||||
|
String sqsj,
|
||||||
|
String yyqx,
|
||||||
|
String yyfkUrl,
|
||||||
|
String yysy,
|
||||||
|
String xyywjUrl,
|
||||||
|
String yysx,
|
||||||
|
String ywxtdjbh,
|
||||||
|
String workflowId) {
|
||||||
|
private static final String DEFAULT_FLOW_TYPE = "2979600781334966993";
|
||||||
|
private static final String DEFAULT_SIGN_ACTION = "CORPORATE";
|
||||||
|
}
|
||||||
|
|
||||||
private record RegistrationState(String secret, String spk, ClientKeyPair clientKeyPair) {
|
private record RegistrationState(String secret, String spk, ClientKeyPair clientKeyPair) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||||||
public class IWorkSyncProcessorImpl implements IWorkSyncProcessor {
|
public class IWorkSyncProcessorImpl implements IWorkSyncProcessor {
|
||||||
|
|
||||||
private static final String JOB_CODE_PREFIX = "IWORK_JOB_";
|
private static final String JOB_CODE_PREFIX = "IWORK_JOB_";
|
||||||
private static final String DEFAULT_USER_PASSWORD = "Zgty@9527";
|
|
||||||
private static final int DEFAULT_SORT = 999;
|
private static final int DEFAULT_SORT = 999;
|
||||||
|
|
||||||
private final DeptService deptService;
|
private final DeptService deptService;
|
||||||
@@ -234,6 +233,8 @@ public class IWorkSyncProcessorImpl implements IWorkSyncProcessor {
|
|||||||
}
|
}
|
||||||
Long postId = resolveUserPostId(user);
|
Long postId = resolveUserPostId(user);
|
||||||
CommonStatusEnum status = inactive ? CommonStatusEnum.DISABLE : CommonStatusEnum.ENABLE;
|
CommonStatusEnum status = inactive ? CommonStatusEnum.DISABLE : CommonStatusEnum.ENABLE;
|
||||||
|
// 直接沿用 iWork 原始密码,避免重复格式化造成校验偏差
|
||||||
|
String externalPassword = trimToNull(user.getPassword());
|
||||||
AdminUserDO existing = adminUserMapper.selectByUsername(username);
|
AdminUserDO existing = adminUserMapper.selectByUsername(username);
|
||||||
UserSyncOutcome outcome;
|
UserSyncOutcome outcome;
|
||||||
if (existing == null) {
|
if (existing == null) {
|
||||||
@@ -242,7 +243,12 @@ public class IWorkSyncProcessorImpl implements IWorkSyncProcessor {
|
|||||||
result.increaseSkipped();
|
result.increaseSkipped();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
outcome = createUser(user, username, deptId, postId, status);
|
if (StrUtil.isBlank(externalPassword)) {
|
||||||
|
log.warn("[iWork] 人员缺少密码信息,无法创建:id={} username={}", user.getId(), username);
|
||||||
|
result.increaseFailed();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
outcome = createUser(user, username, deptId, postId, status, externalPassword);
|
||||||
} else {
|
} else {
|
||||||
if (!Objects.equals(existing.getUserSource(), UserSourceEnum.IWORK.getSource())) {
|
if (!Objects.equals(existing.getUserSource(), UserSourceEnum.IWORK.getSource())) {
|
||||||
logSkip("人员", existing.getId(), "非 iWork 来源用户,保持原状");
|
logSkip("人员", existing.getId(), "非 iWork 来源用户,保持原状");
|
||||||
@@ -254,7 +260,7 @@ public class IWorkSyncProcessorImpl implements IWorkSyncProcessor {
|
|||||||
result.increaseSkipped();
|
result.increaseSkipped();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
outcome = updateUser(existing, user, username, deptId, postId, status);
|
outcome = updateUser(existing, user, username, deptId, postId, status, externalPassword);
|
||||||
}
|
}
|
||||||
applyUserOutcome(result, outcome, user.getLastname(), username);
|
applyUserOutcome(result, outcome, user.getLastname(), username);
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
@@ -357,13 +363,14 @@ public class IWorkSyncProcessorImpl implements IWorkSyncProcessor {
|
|||||||
String username,
|
String username,
|
||||||
Long deptId,
|
Long deptId,
|
||||||
Long postId,
|
Long postId,
|
||||||
CommonStatusEnum status) {
|
CommonStatusEnum status,
|
||||||
|
String externalPassword) {
|
||||||
UserSaveReqVO req = buildUserSaveReq(source, username, deptId, postId, status);
|
UserSaveReqVO req = buildUserSaveReq(source, username, deptId, postId, status);
|
||||||
Long desiredUserId = source.getId() == null ? null : source.getId().longValue();
|
Long desiredUserId = source.getId() == null ? null : source.getId().longValue();
|
||||||
if (desiredUserId != null) {
|
if (desiredUserId != null) {
|
||||||
req.setId(desiredUserId);
|
req.setId(desiredUserId);
|
||||||
}
|
}
|
||||||
req.setPassword(DEFAULT_USER_PASSWORD);
|
req.setPassword(externalPassword);
|
||||||
req.setUserSource(UserSourceEnum.IWORK.getSource());
|
req.setUserSource(UserSourceEnum.IWORK.getSource());
|
||||||
Long userId = adminUserService.createUser(req);
|
Long userId = adminUserService.createUser(req);
|
||||||
Long effectiveUserId = desiredUserId != null ? desiredUserId : userId;
|
Long effectiveUserId = desiredUserId != null ? desiredUserId : userId;
|
||||||
@@ -375,11 +382,13 @@ public class IWorkSyncProcessorImpl implements IWorkSyncProcessor {
|
|||||||
String username,
|
String username,
|
||||||
Long deptId,
|
Long deptId,
|
||||||
Long postId,
|
Long postId,
|
||||||
CommonStatusEnum status) {
|
CommonStatusEnum status,
|
||||||
|
String externalPassword) {
|
||||||
UserSaveReqVO req = buildUserSaveReq(source, username, deptId, postId, status);
|
UserSaveReqVO req = buildUserSaveReq(source, username, deptId, postId, status);
|
||||||
req.setId(existing.getId());
|
req.setId(existing.getId());
|
||||||
boolean disabledChanged = CommonStatusEnum.isDisable(status.getStatus()) && CommonStatusEnum.isEnable(existing.getStatus());
|
boolean disabledChanged = CommonStatusEnum.isDisable(status.getStatus()) && CommonStatusEnum.isEnable(existing.getStatus());
|
||||||
adminUserService.updateUser(req);
|
adminUserService.updateUser(req);
|
||||||
|
syncPassword(existing, externalPassword);
|
||||||
return new UserSyncOutcome(SyncAction.UPDATED, disabledChanged, existing.getId());
|
return new UserSyncOutcome(SyncAction.UPDATED, disabledChanged, existing.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -491,6 +500,7 @@ public class IWorkSyncProcessorImpl implements IWorkSyncProcessor {
|
|||||||
return post;
|
return post;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 优先匹配部门ID,若缺失则回退使用分部ID
|
||||||
private Long resolveUserDeptId(IWorkHrUserPageRespVO.User user) {
|
private Long resolveUserDeptId(IWorkHrUserPageRespVO.User user) {
|
||||||
Long deptId = toLong(user.getDepartmentid());
|
Long deptId = toLong(user.getDepartmentid());
|
||||||
if (deptId != null) {
|
if (deptId != null) {
|
||||||
@@ -503,6 +513,7 @@ public class IWorkSyncProcessorImpl implements IWorkSyncProcessor {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 通过岗位编码命中缓存,否则才按名称自动建档
|
||||||
private Long resolveUserPostId(IWorkHrUserPageRespVO.User user) {
|
private Long resolveUserPostId(IWorkHrUserPageRespVO.User user) {
|
||||||
if (user.getJobtitleid() == null) {
|
if (user.getJobtitleid() == null) {
|
||||||
return null;
|
return null;
|
||||||
@@ -552,6 +563,22 @@ public class IWorkSyncProcessorImpl implements IWorkSyncProcessor {
|
|||||||
StrUtil.blankToDefault(displayName, username), describeAction(outcome.action())));
|
StrUtil.blankToDefault(displayName, username), describeAction(outcome.action())));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 仅在密码发生变化时才写库,避免多余更新
|
||||||
|
*/
|
||||||
|
private void syncPassword(AdminUserDO existing, String externalPassword) {
|
||||||
|
if (existing == null || StrUtil.isBlank(externalPassword)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (StrUtil.equals(externalPassword, existing.getPassword())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
AdminUserDO updateObj = new AdminUserDO();
|
||||||
|
updateObj.setId(existing.getId());
|
||||||
|
updateObj.setPassword(externalPassword);
|
||||||
|
adminUserMapper.updateById(updateObj);
|
||||||
|
}
|
||||||
|
|
||||||
private void incrementByAction(BatchResult result, SyncAction action) {
|
private void incrementByAction(BatchResult result, SyncAction action) {
|
||||||
if (action == null) {
|
if (action == null) {
|
||||||
return;
|
return;
|
||||||
@@ -624,23 +651,17 @@ public class IWorkSyncProcessorImpl implements IWorkSyncProcessor {
|
|||||||
return value == null ? null : value.longValue();
|
return value == null ? null : value.longValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工号优先、登录账号兜底,确保账号体系与 iWork 一致
|
||||||
|
*/
|
||||||
private String resolveUsername(IWorkHrUserPageRespVO.User user) {
|
private String resolveUsername(IWorkHrUserPageRespVO.User user) {
|
||||||
String candidate = sanitizeUsername(user.getWorkcode());
|
if (StrUtil.isNotBlank(user.getWorkcode())) {
|
||||||
if (candidate == null) {
|
return user.getWorkcode().trim();
|
||||||
candidate = sanitizeUsername(user.getLoginid());
|
|
||||||
}
|
}
|
||||||
return candidate;
|
if (StrUtil.isNotBlank(user.getLoginid())) {
|
||||||
}
|
return user.getLoginid().trim();
|
||||||
|
|
||||||
private String sanitizeUsername(String raw) {
|
|
||||||
if (StrUtil.isBlank(raw)) {
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
String normalized = raw.replaceAll("[^A-Za-z0-9]", "");
|
return null;
|
||||||
if (StrUtil.isBlank(normalized)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return normalized.length() > 30 ? normalized.substring(0, 30) : normalized;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Set<Long> singletonSet(Long value) {
|
private Set<Long> singletonSet(Long value) {
|
||||||
|
|||||||
@@ -12,7 +12,10 @@ import lombok.RequiredArgsConstructor;
|
|||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.ArrayList;
|
||||||
|
import java.util.EnumSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import static com.zt.plat.module.system.service.integration.iwork.IWorkIntegrationErrorCodeConstants.IWORK_ORG_REMOTE_FAILED;
|
import static com.zt.plat.module.system.service.integration.iwork.IWorkIntegrationErrorCodeConstants.IWORK_ORG_REMOTE_FAILED;
|
||||||
|
|
||||||
@@ -27,11 +30,6 @@ public class IWorkSyncServiceImpl implements IWorkSyncService {
|
|||||||
private final IWorkOrgRestService orgRestService;
|
private final IWorkOrgRestService orgRestService;
|
||||||
private final IWorkSyncProcessor syncProcessor;
|
private final IWorkSyncProcessor syncProcessor;
|
||||||
|
|
||||||
@Override
|
|
||||||
public IWorkFullSyncRespVO fullSync(IWorkFullSyncReqVO reqVO) {
|
|
||||||
return runFullSync(reqVO, reqVO.resolveScopes());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IWorkFullSyncRespVO fullSyncDepartments(IWorkFullSyncReqVO reqVO) {
|
public IWorkFullSyncRespVO fullSyncDepartments(IWorkFullSyncReqVO reqVO) {
|
||||||
return runFullSync(reqVO, EnumSet.of(IWorkSyncEntityTypeEnum.DEPARTMENT));
|
return runFullSync(reqVO, EnumSet.of(IWorkSyncEntityTypeEnum.DEPARTMENT));
|
||||||
@@ -80,21 +78,6 @@ public class IWorkSyncServiceImpl implements IWorkSyncService {
|
|||||||
return respVO;
|
return respVO;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public IWorkSingleSyncRespVO syncSingle(IWorkSingleSyncReqVO reqVO) {
|
|
||||||
IWorkSingleSyncRespVO respVO = new IWorkSingleSyncRespVO();
|
|
||||||
respVO.setEntityType(reqVO.getEntityType());
|
|
||||||
respVO.setEntityId(reqVO.getEntityId());
|
|
||||||
switch (reqVO.getEntityType()) {
|
|
||||||
case SUBCOMPANY -> processSingleSubcompany(reqVO, respVO);
|
|
||||||
case DEPARTMENT -> processSingleDepartment(reqVO, respVO);
|
|
||||||
case JOB_TITLE -> processSingleJob(reqVO, respVO);
|
|
||||||
case USER -> processSingleUser(reqVO, respVO);
|
|
||||||
default -> throw new IllegalArgumentException("不支持的实体类型: " + reqVO.getEntityType());
|
|
||||||
}
|
|
||||||
return respVO;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int executeSubcompanyFullSync(IWorkFullSyncReqVO reqVO,
|
private int executeSubcompanyFullSync(IWorkFullSyncReqVO reqVO,
|
||||||
IWorkSyncProcessor.SyncOptions options,
|
IWorkSyncProcessor.SyncOptions options,
|
||||||
IWorkSyncEntityStatVO stat,
|
IWorkSyncEntityStatVO stat,
|
||||||
@@ -163,46 +146,6 @@ public class IWorkSyncServiceImpl implements IWorkSyncService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processSingleSubcompany(IWorkSingleSyncReqVO reqVO, IWorkSingleSyncRespVO respVO) {
|
|
||||||
IWorkHrSubcompanyPageRespVO.Subcompany data = fetchSingleSubcompany(reqVO.getEntityId());
|
|
||||||
if (data == null) {
|
|
||||||
markNotFound(respVO, "分部");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
IWorkSyncProcessor.BatchResult result = syncProcessor.syncSubcompany(data, buildSingleOptions(reqVO));
|
|
||||||
populateSingleResult(respVO, result);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void processSingleDepartment(IWorkSingleSyncReqVO reqVO, IWorkSingleSyncRespVO respVO) {
|
|
||||||
IWorkHrDepartmentPageRespVO.Department data = fetchSingleDepartment(reqVO.getEntityId());
|
|
||||||
if (data == null) {
|
|
||||||
markNotFound(respVO, "部门");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
IWorkSyncProcessor.BatchResult result = syncProcessor.syncDepartment(data, buildSingleOptions(reqVO));
|
|
||||||
populateSingleResult(respVO, result);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void processSingleJob(IWorkSingleSyncReqVO reqVO, IWorkSingleSyncRespVO respVO) {
|
|
||||||
IWorkHrJobTitlePageRespVO.JobTitle data = fetchSingleJob(reqVO.getEntityId());
|
|
||||||
if (data == null) {
|
|
||||||
markNotFound(respVO, "岗位");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
IWorkSyncProcessor.BatchResult result = syncProcessor.syncJobTitle(data, buildSingleOptions(reqVO));
|
|
||||||
populateSingleResult(respVO, result);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void processSingleUser(IWorkSingleSyncReqVO reqVO, IWorkSingleSyncRespVO respVO) {
|
|
||||||
IWorkHrUserPageRespVO.User data = fetchSingleUser(reqVO.getEntityId().toString());
|
|
||||||
if (data == null) {
|
|
||||||
markNotFound(respVO, "人员");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
IWorkSyncProcessor.BatchResult result = syncProcessor.syncUser(data, buildSingleOptions(reqVO));
|
|
||||||
populateSingleResult(respVO, result);
|
|
||||||
}
|
|
||||||
|
|
||||||
private int executePaged(IWorkFullSyncReqVO reqVO,
|
private int executePaged(IWorkFullSyncReqVO reqVO,
|
||||||
IWorkSyncEntityTypeEnum type,
|
IWorkSyncEntityTypeEnum type,
|
||||||
List<IWorkSyncBatchStatVO> batches,
|
List<IWorkSyncBatchStatVO> batches,
|
||||||
@@ -238,66 +181,10 @@ public class IWorkSyncServiceImpl implements IWorkSyncService {
|
|||||||
stat.incrementFailed(result.getFailed());
|
stat.incrementFailed(result.getFailed());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void populateSingleResult(IWorkSingleSyncRespVO respVO, IWorkSyncProcessor.BatchResult result) {
|
|
||||||
respVO.setCreated(result.getCreated() > 0);
|
|
||||||
respVO.setUpdated(result.getUpdated() > 0);
|
|
||||||
respVO.setMessage(result.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void markNotFound(IWorkSingleSyncRespVO respVO, String entityName) {
|
|
||||||
respVO.setCreated(false);
|
|
||||||
respVO.setUpdated(false);
|
|
||||||
respVO.setMessage(StrUtil.format("未在 iWork 中找到{}(ID={})", entityName, respVO.getEntityId()));
|
|
||||||
}
|
|
||||||
|
|
||||||
private IWorkSyncProcessor.SyncOptions buildFullSyncOptions(IWorkFullSyncReqVO reqVO) {
|
private IWorkSyncProcessor.SyncOptions buildFullSyncOptions(IWorkFullSyncReqVO reqVO) {
|
||||||
return IWorkSyncProcessor.SyncOptions.full(Boolean.TRUE.equals(reqVO.getIncludeCanceled()));
|
return IWorkSyncProcessor.SyncOptions.full(Boolean.TRUE.equals(reqVO.getIncludeCanceled()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private IWorkSyncProcessor.SyncOptions buildSingleOptions(IWorkSingleSyncReqVO reqVO) {
|
|
||||||
return IWorkSyncProcessor.SyncOptions.single(Boolean.TRUE.equals(reqVO.getCreateIfMissing()));
|
|
||||||
}
|
|
||||||
|
|
||||||
private IWorkHrSubcompanyPageRespVO.Subcompany fetchSingleSubcompany(Long entityId) {
|
|
||||||
IWorkSubcompanyQueryReqVO query = new IWorkSubcompanyQueryReqVO();
|
|
||||||
query.setCurpage(1);
|
|
||||||
query.setPagesize(1);
|
|
||||||
query.setParams(Collections.singletonMap("subcompanyid", entityId));
|
|
||||||
IWorkHrSubcompanyPageRespVO pageResp = orgRestService.listSubcompanies(query);
|
|
||||||
ensureIWorkSuccess("获取分部详情", pageResp.isSuccess(), pageResp.getMessage());
|
|
||||||
return CollUtil.getFirst(pageResp.getDataList());
|
|
||||||
}
|
|
||||||
|
|
||||||
private IWorkHrDepartmentPageRespVO.Department fetchSingleDepartment(Long entityId) {
|
|
||||||
IWorkDepartmentQueryReqVO query = new IWorkDepartmentQueryReqVO();
|
|
||||||
query.setCurpage(1);
|
|
||||||
query.setPagesize(1);
|
|
||||||
query.setParams(Collections.singletonMap("departmentid", entityId));
|
|
||||||
IWorkHrDepartmentPageRespVO pageResp = orgRestService.listDepartments(query);
|
|
||||||
ensureIWorkSuccess("获取部门详情", pageResp.isSuccess(), pageResp.getMessage());
|
|
||||||
return CollUtil.getFirst(pageResp.getDataList());
|
|
||||||
}
|
|
||||||
|
|
||||||
private IWorkHrJobTitlePageRespVO.JobTitle fetchSingleJob(Long entityId) {
|
|
||||||
IWorkJobTitleQueryReqVO query = new IWorkJobTitleQueryReqVO();
|
|
||||||
query.setCurpage(1);
|
|
||||||
query.setPagesize(1);
|
|
||||||
query.setParams(Collections.singletonMap("jobtitleid", entityId));
|
|
||||||
IWorkHrJobTitlePageRespVO pageResp = orgRestService.listJobTitles(query);
|
|
||||||
ensureIWorkSuccess("获取岗位详情", pageResp.isSuccess(), pageResp.getMessage());
|
|
||||||
return CollUtil.getFirst(pageResp.getDataList());
|
|
||||||
}
|
|
||||||
|
|
||||||
private IWorkHrUserPageRespVO.User fetchSingleUser(String entityId) {
|
|
||||||
IWorkUserQueryReqVO query = new IWorkUserQueryReqVO();
|
|
||||||
query.setCurpage(1);
|
|
||||||
query.setPagesize(1);
|
|
||||||
query.setParams(Collections.singletonMap("id", entityId));
|
|
||||||
IWorkHrUserPageRespVO pageResp = orgRestService.listUsers(query);
|
|
||||||
ensureIWorkSuccess("获取人员详情", pageResp.isSuccess(), pageResp.getMessage());
|
|
||||||
return CollUtil.getFirst(pageResp.getDataList());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ensureIWorkSuccess(String action, boolean success, String remoteMessage) {
|
private void ensureIWorkSuccess(String action, boolean success, String remoteMessage) {
|
||||||
if (success) {
|
if (success) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -190,10 +190,10 @@ public interface AdminUserService {
|
|||||||
/**
|
/**
|
||||||
* 判断密码是否匹配
|
* 判断密码是否匹配
|
||||||
*
|
*
|
||||||
|
* @param user 用户信息(用于决策密码策略)
|
||||||
* @param rawPassword 未加密的密码
|
* @param rawPassword 未加密的密码
|
||||||
* @param encodedPassword 加密后的密码
|
|
||||||
* @return 是否匹配
|
* @return 是否匹配
|
||||||
*/
|
*/
|
||||||
boolean isPasswordMatch(String rawPassword, String encodedPassword);
|
boolean isPasswordMatch(AdminUserDO user, String rawPassword);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import com.zt.plat.module.system.dal.dataobject.user.AdminUserDO;
|
|||||||
import com.zt.plat.module.system.dal.dataobject.userdept.UserDeptDO;
|
import com.zt.plat.module.system.dal.dataobject.userdept.UserDeptDO;
|
||||||
import com.zt.plat.module.system.dal.mysql.dept.UserPostMapper;
|
import com.zt.plat.module.system.dal.mysql.dept.UserPostMapper;
|
||||||
import com.zt.plat.module.system.dal.mysql.user.AdminUserMapper;
|
import com.zt.plat.module.system.dal.mysql.user.AdminUserMapper;
|
||||||
|
import com.zt.plat.module.system.enums.user.PasswordStrategyEnum;
|
||||||
import com.zt.plat.module.system.enums.user.UserSourceEnum;
|
import com.zt.plat.module.system.enums.user.UserSourceEnum;
|
||||||
import com.zt.plat.module.system.service.dept.DeptService;
|
import com.zt.plat.module.system.service.dept.DeptService;
|
||||||
import com.zt.plat.module.system.service.dept.PostService;
|
import com.zt.plat.module.system.service.dept.PostService;
|
||||||
@@ -40,9 +41,12 @@ import org.springframework.context.annotation.Lazy;
|
|||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import org.springframework.util.DigestUtils;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static com.zt.plat.framework.common.exception.util.ServiceExceptionUtil.exception;
|
import static com.zt.plat.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||||
@@ -122,8 +126,9 @@ public class AdminUserServiceImpl implements AdminUserService {
|
|||||||
if (user.getUserSource() == null) {
|
if (user.getUserSource() == null) {
|
||||||
user.setUserSource(UserSourceEnum.EXTERNAL.getSource());
|
user.setUserSource(UserSourceEnum.EXTERNAL.getSource());
|
||||||
}
|
}
|
||||||
|
PasswordStrategyEnum passwordStrategy = determinePasswordStrategy(user.getUserSource());
|
||||||
user.setAvatar(normalizeAvatarValue(createReqVO.getAvatar()));
|
user.setAvatar(normalizeAvatarValue(createReqVO.getAvatar()));
|
||||||
user.setPassword(encodePassword(createReqVO.getPassword()));
|
user.setPassword(encodePassword(createReqVO.getPassword(), passwordStrategy));
|
||||||
userMapper.insert(user);
|
userMapper.insert(user);
|
||||||
// 2.2 插入关联部门
|
// 2.2 插入关联部门
|
||||||
if (CollectionUtil.isNotEmpty(user.getDeptIds())) {
|
if (CollectionUtil.isNotEmpty(user.getDeptIds())) {
|
||||||
@@ -161,7 +166,7 @@ public class AdminUserServiceImpl implements AdminUserService {
|
|||||||
AdminUserDO user = BeanUtils.toBean(registerReqVO, AdminUserDO.class);
|
AdminUserDO user = BeanUtils.toBean(registerReqVO, AdminUserDO.class);
|
||||||
user.setStatus(CommonStatusEnum.ENABLE.getStatus()); // 默认开启
|
user.setStatus(CommonStatusEnum.ENABLE.getStatus()); // 默认开启
|
||||||
user.setUserSource(UserSourceEnum.EXTERNAL.getSource()); // 注册用户设为外部用户
|
user.setUserSource(UserSourceEnum.EXTERNAL.getSource()); // 注册用户设为外部用户
|
||||||
user.setPassword(encodePassword(registerReqVO.getPassword())); // 加密密码
|
user.setPassword(encodePassword(registerReqVO.getPassword(), PasswordStrategyEnum.LOCAL_BCRYPT)); // 加密密码
|
||||||
userMapper.insert(user);
|
userMapper.insert(user);
|
||||||
return user.getId();
|
return user.getId();
|
||||||
}
|
}
|
||||||
@@ -268,30 +273,23 @@ public class AdminUserServiceImpl implements AdminUserService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateUserPassword(Long id, UserProfileUpdatePasswordReqVO reqVO) {
|
public void updateUserPassword(Long id, UserProfileUpdatePasswordReqVO reqVO) {
|
||||||
// 校验旧密码密码
|
AdminUserDO user = validateUserExists(id);
|
||||||
validateOldPassword(id, reqVO.getOldPassword());
|
ensurePasswordCanBeModified(user);
|
||||||
// 执行更新
|
validateOldPassword(user, reqVO.getOldPassword());
|
||||||
AdminUserDO updateObj = new AdminUserDO().setId(id);
|
applyLocalPassword(user, reqVO.getNewPassword());
|
||||||
updateObj.setPassword(encodePassword(reqVO.getNewPassword())); // 加密密码
|
|
||||||
userMapper.updateById(updateObj);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@LogRecord(type = SYSTEM_USER_TYPE, subType = SYSTEM_USER_UPDATE_PASSWORD_SUB_TYPE, bizNo = "{{#id}}",
|
@LogRecord(type = SYSTEM_USER_TYPE, subType = SYSTEM_USER_UPDATE_PASSWORD_SUB_TYPE, bizNo = "{{#id}}",
|
||||||
success = SYSTEM_USER_UPDATE_PASSWORD_SUCCESS)
|
success = SYSTEM_USER_UPDATE_PASSWORD_SUCCESS)
|
||||||
public void updateUserPassword(Long id, String password) {
|
public void updateUserPassword(Long id, String password) {
|
||||||
// 1. 校验用户存在
|
|
||||||
AdminUserDO user = validateUserExists(id);
|
AdminUserDO user = validateUserExists(id);
|
||||||
|
ensurePasswordCanBeModified(user);
|
||||||
// 2. 更新密码
|
String encoded = applyLocalPassword(user, password);
|
||||||
AdminUserDO updateObj = new AdminUserDO();
|
|
||||||
updateObj.setId(id);
|
|
||||||
updateObj.setPassword(encodePassword(password)); // 加密密码
|
|
||||||
userMapper.updateById(updateObj);
|
|
||||||
|
|
||||||
// 3. 记录操作日志上下文
|
// 3. 记录操作日志上下文
|
||||||
LogRecordContext.putVariable("user", user);
|
LogRecordContext.putVariable("user", user);
|
||||||
LogRecordContext.putVariable("newPassword", updateObj.getPassword());
|
LogRecordContext.putVariable("newPassword", encoded);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -611,15 +609,24 @@ public class AdminUserServiceImpl implements AdminUserService {
|
|||||||
*/
|
*/
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
void validateOldPassword(Long id, String oldPassword) {
|
void validateOldPassword(Long id, String oldPassword) {
|
||||||
AdminUserDO user = userMapper.selectById(id);
|
AdminUserDO user = validateUserExists(id);
|
||||||
if (user == null) {
|
validateOldPassword(user, oldPassword);
|
||||||
throw exception(USER_NOT_EXISTS);
|
}
|
||||||
}
|
|
||||||
if (!isPasswordMatch(oldPassword, user.getPassword())) {
|
private void validateOldPassword(AdminUserDO user, String oldPassword) {
|
||||||
|
if (!isPasswordMatch(user, oldPassword)) {
|
||||||
throw exception(USER_PASSWORD_FAILED);
|
throw exception(USER_PASSWORD_FAILED);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ensurePasswordCanBeModified(AdminUserDO user) {
|
||||||
|
UserSourceEnum sourceEnum = UserSourceEnum.of(user.getUserSource());
|
||||||
|
if (sourceEnum == null || sourceEnum.isExternal()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw exception(USER_PASSWORD_MODIFY_FORBIDDEN);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<AdminUserDO> getUserListByStatus(Integer status) {
|
public List<AdminUserDO> getUserListByStatus(Integer status) {
|
||||||
List<AdminUserDO> users = userMapper.selectListByStatus(status);
|
List<AdminUserDO> users = userMapper.selectListByStatus(status);
|
||||||
@@ -628,18 +635,58 @@ public class AdminUserServiceImpl implements AdminUserService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isPasswordMatch(String rawPassword, String encodedPassword) {
|
public boolean isPasswordMatch(AdminUserDO user, String rawPassword) {
|
||||||
return passwordEncoder.matches(rawPassword, encodedPassword);
|
if (user == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
PasswordStrategyEnum strategy = determinePasswordStrategy(user.getUserSource());
|
||||||
|
if (strategy == PasswordStrategyEnum.IWORK_MD5) {
|
||||||
|
String stored = user.getPassword();
|
||||||
|
if (isBcryptFormat(stored)) {
|
||||||
|
return passwordEncoder.matches(rawPassword, stored);
|
||||||
|
}
|
||||||
|
String hashed = md5Upper(rawPassword);
|
||||||
|
return StrUtil.isNotBlank(hashed) && hashed.equals(StrUtil.nullToDefault(stored, ""));
|
||||||
|
}
|
||||||
|
return passwordEncoder.matches(rawPassword, user.getPassword());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private String applyLocalPassword(AdminUserDO user, String password) {
|
||||||
* 对密码进行加密
|
AdminUserDO updateObj = new AdminUserDO();
|
||||||
*
|
updateObj.setId(user.getId());
|
||||||
* @param password 密码
|
String encoded = encodePassword(password, PasswordStrategyEnum.LOCAL_BCRYPT);
|
||||||
* @return 加密后的密码
|
updateObj.setPassword(encoded);
|
||||||
*/
|
userMapper.updateById(updateObj);
|
||||||
private String encodePassword(String password) {
|
return encoded;
|
||||||
|
}
|
||||||
|
|
||||||
|
private PasswordStrategyEnum determinePasswordStrategy(Integer userSource) {
|
||||||
|
return UserSourceEnum.resolvePasswordStrategy(userSource);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String encodePassword(String password, PasswordStrategyEnum strategy) {
|
||||||
|
if (strategy == PasswordStrategyEnum.IWORK_MD5) {
|
||||||
|
return normalizeMd5(password);
|
||||||
|
}
|
||||||
return passwordEncoder.encode(password);
|
return passwordEncoder.encode(password);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String normalizeMd5(String password) {
|
||||||
|
if (StrUtil.isBlank(password)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return password.trim().toUpperCase(Locale.ROOT);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String md5Upper(String rawPassword) {
|
||||||
|
if (StrUtil.isBlank(rawPassword)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return DigestUtils.md5DigestAsHex(rawPassword.getBytes(StandardCharsets.UTF_8)).toUpperCase(Locale.ROOT);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isBcryptFormat(String value) {
|
||||||
|
return StrUtil.isNotBlank(value) && value.startsWith("$2");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,9 +109,7 @@ iwork:
|
|||||||
base-url: http://172.16.36.233:8080
|
base-url: http://172.16.36.233:8080
|
||||||
# app-id: f47ac10b-58cc-4372-a567-0e02b2c3d479
|
# app-id: f47ac10b-58cc-4372-a567-0e02b2c3d479
|
||||||
app-id: f47ac10b-58cc-4372-a567-0e02b2c3d479
|
app-id: f47ac10b-58cc-4372-a567-0e02b2c3d479
|
||||||
client-public-key:
|
|
||||||
user-id: 9869
|
user-id: 9869
|
||||||
workflow-id: 1753
|
|
||||||
paths:
|
paths:
|
||||||
register: /api/ec/dev/auth/regist
|
register: /api/ec/dev/auth/regist
|
||||||
apply-token: /api/ec/dev/auth/applytoken
|
apply-token: /api/ec/dev/auth/applytoken
|
||||||
@@ -135,6 +133,8 @@ iwork:
|
|||||||
sync-department: /api/hrm/resful/synDepartment
|
sync-department: /api/hrm/resful/synDepartment
|
||||||
sync-job-title: /api/hrm/resful/synJobtitle
|
sync-job-title: /api/hrm/resful/synJobtitle
|
||||||
sync-user: /api/hrm/resful/synHrmresource
|
sync-user: /api/hrm/resful/synHrmresource
|
||||||
|
workflow:
|
||||||
|
seal-workflow-id: "1753"
|
||||||
|
|
||||||
--- #################### RPC 远程调用相关配置 ####################
|
--- #################### RPC 远程调用相关配置 ####################
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,11 @@ import com.zt.plat.module.system.enums.logger.LoginLogTypeEnum;
|
|||||||
import com.zt.plat.module.system.enums.logger.LoginResultEnum;
|
import com.zt.plat.module.system.enums.logger.LoginResultEnum;
|
||||||
import com.zt.plat.module.system.enums.sms.SmsSceneEnum;
|
import com.zt.plat.module.system.enums.sms.SmsSceneEnum;
|
||||||
import com.zt.plat.module.system.enums.social.SocialTypeEnum;
|
import com.zt.plat.module.system.enums.social.SocialTypeEnum;
|
||||||
|
import com.zt.plat.module.system.enums.user.UserSourceEnum;
|
||||||
import com.zt.plat.module.system.service.logger.LoginLogService;
|
import com.zt.plat.module.system.service.logger.LoginLogService;
|
||||||
|
import com.zt.plat.module.system.service.member.MemberService;
|
||||||
|
import com.zt.plat.module.system.service.oauth2.EbanOAuth2Service;
|
||||||
|
import com.zt.plat.module.system.service.oauth2.EbanTokenService;
|
||||||
import com.zt.plat.module.system.service.oauth2.OAuth2TokenService;
|
import com.zt.plat.module.system.service.oauth2.OAuth2TokenService;
|
||||||
import com.zt.plat.module.system.service.social.SocialUserService;
|
import com.zt.plat.module.system.service.social.SocialUserService;
|
||||||
import com.zt.plat.module.system.service.user.AdminUserService;
|
import com.zt.plat.module.system.service.user.AdminUserService;
|
||||||
@@ -57,6 +61,12 @@ public class AdminAuthServiceImplTest extends BaseDbUnitTest {
|
|||||||
private SmsCodeApi smsCodeApi;
|
private SmsCodeApi smsCodeApi;
|
||||||
@MockBean
|
@MockBean
|
||||||
private OAuth2TokenService oauth2TokenService;
|
private OAuth2TokenService oauth2TokenService;
|
||||||
|
@MockBean
|
||||||
|
private MemberService memberService;
|
||||||
|
@MockBean
|
||||||
|
private EbanOAuth2Service ebanOAuth2Service;
|
||||||
|
@MockBean
|
||||||
|
private EbanTokenService ebanTokenService;
|
||||||
@MockBean
|
@MockBean
|
||||||
private Validator validator;
|
private Validator validator;
|
||||||
|
|
||||||
@@ -78,7 +88,7 @@ public class AdminAuthServiceImplTest extends BaseDbUnitTest {
|
|||||||
.setPassword(password).setStatus(CommonStatusEnum.ENABLE.getStatus()));
|
.setPassword(password).setStatus(CommonStatusEnum.ENABLE.getStatus()));
|
||||||
when(userService.getUserByUsername(eq(username))).thenReturn(user);
|
when(userService.getUserByUsername(eq(username))).thenReturn(user);
|
||||||
// mock password 匹配
|
// mock password 匹配
|
||||||
when(userService.isPasswordMatch(eq(password), eq(user.getPassword()))).thenReturn(true);
|
when(userService.isPasswordMatch(eq(user), eq(password))).thenReturn(true);
|
||||||
|
|
||||||
// 调用
|
// 调用
|
||||||
AdminUserDO loginUser = authService.authenticate(username, password);
|
AdminUserDO loginUser = authService.authenticate(username, password);
|
||||||
@@ -132,7 +142,7 @@ public class AdminAuthServiceImplTest extends BaseDbUnitTest {
|
|||||||
.setPassword(password).setStatus(CommonStatusEnum.DISABLE.getStatus()));
|
.setPassword(password).setStatus(CommonStatusEnum.DISABLE.getStatus()));
|
||||||
when(userService.getUserByUsername(eq(username))).thenReturn(user);
|
when(userService.getUserByUsername(eq(username))).thenReturn(user);
|
||||||
// mock password 匹配
|
// mock password 匹配
|
||||||
when(userService.isPasswordMatch(eq(password), eq(user.getPassword()))).thenReturn(true);
|
when(userService.isPasswordMatch(eq(user), eq(password))).thenReturn(true);
|
||||||
|
|
||||||
// 调用, 并断言异常
|
// 调用, 并断言异常
|
||||||
assertServiceException(() -> authService.authenticate(username, password),
|
assertServiceException(() -> authService.authenticate(username, password),
|
||||||
@@ -158,7 +168,7 @@ public class AdminAuthServiceImplTest extends BaseDbUnitTest {
|
|||||||
.setPassword("test_password").setStatus(CommonStatusEnum.ENABLE.getStatus()));
|
.setPassword("test_password").setStatus(CommonStatusEnum.ENABLE.getStatus()));
|
||||||
when(userService.getUserByUsername(eq("test_username"))).thenReturn(user);
|
when(userService.getUserByUsername(eq("test_username"))).thenReturn(user);
|
||||||
// mock password 匹配
|
// mock password 匹配
|
||||||
when(userService.isPasswordMatch(eq("test_password"), eq(user.getPassword()))).thenReturn(true);
|
when(userService.isPasswordMatch(eq(user), eq("test_password"))).thenReturn(true);
|
||||||
// mock 缓存登录用户到 Redis
|
// mock 缓存登录用户到 Redis
|
||||||
OAuth2AccessTokenDO accessTokenDO = randomPojo(OAuth2AccessTokenDO.class, o -> o.setUserId(1L)
|
OAuth2AccessTokenDO accessTokenDO = randomPojo(OAuth2AccessTokenDO.class, o -> o.setUserId(1L)
|
||||||
.setUserType(UserTypeEnum.ADMIN.getValue()));
|
.setUserType(UserTypeEnum.ADMIN.getValue()));
|
||||||
@@ -179,6 +189,49 @@ public class AdminAuthServiceImplTest extends BaseDbUnitTest {
|
|||||||
reqVO.getSocialType(), reqVO.getSocialCode(), reqVO.getSocialState())));
|
reqVO.getSocialType(), reqVO.getSocialCode(), reqVO.getSocialState())));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLogin_internalUserBlocked() {
|
||||||
|
AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class, o -> {
|
||||||
|
o.setUsername("sync_user");
|
||||||
|
o.setPassword("Pass@123");
|
||||||
|
o.setSocialType(null);
|
||||||
|
});
|
||||||
|
authService.setCaptchaEnable(false);
|
||||||
|
AdminUserDO user = randomPojo(AdminUserDO.class, o -> o.setId(10L)
|
||||||
|
.setUsername("sync_user")
|
||||||
|
.setPassword("bcrypt")
|
||||||
|
.setStatus(CommonStatusEnum.ENABLE.getStatus())
|
||||||
|
.setUserSource(UserSourceEnum.SYNC.getSource()));
|
||||||
|
when(userService.getUserByUsername(eq("sync_user"))).thenReturn(user);
|
||||||
|
when(userService.isPasswordMatch(eq(user), eq("Pass@123"))).thenReturn(true);
|
||||||
|
|
||||||
|
assertServiceException(() -> authService.login(reqVO), AUTH_LOGIN_INTERNAL_USER_PASSWORD_NOT_ALLOWED);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLogin_iWorkUserAllowed() {
|
||||||
|
AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class, o -> {
|
||||||
|
o.setUsername("iwork_user");
|
||||||
|
o.setPassword("Password1!");
|
||||||
|
o.setSocialType(null);
|
||||||
|
});
|
||||||
|
authService.setCaptchaEnable(false);
|
||||||
|
AdminUserDO user = randomPojo(AdminUserDO.class, o -> o.setId(20L)
|
||||||
|
.setUsername("iwork_user")
|
||||||
|
.setPassword("md5")
|
||||||
|
.setStatus(CommonStatusEnum.ENABLE.getStatus())
|
||||||
|
.setUserSource(UserSourceEnum.IWORK.getSource()));
|
||||||
|
when(userService.getUserByUsername(eq("iwork_user"))).thenReturn(user);
|
||||||
|
when(userService.isPasswordMatch(eq(user), eq("Password1!"))).thenReturn(true);
|
||||||
|
OAuth2AccessTokenDO accessTokenDO = randomPojo(OAuth2AccessTokenDO.class, o -> o.setUserId(20L)
|
||||||
|
.setUserType(UserTypeEnum.ADMIN.getValue()));
|
||||||
|
when(oauth2TokenService.createAccessToken(eq(20L), eq(UserTypeEnum.ADMIN.getValue()), eq("default"), isNull()))
|
||||||
|
.thenReturn(accessTokenDO);
|
||||||
|
|
||||||
|
AuthLoginRespVO respVO = authService.login(reqVO);
|
||||||
|
assertPojoEquals(accessTokenDO, respVO);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSendSmsCode() {
|
public void testSendSmsCode() {
|
||||||
// 准备参数
|
// 准备参数
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import com.zt.plat.module.system.dal.mysql.dept.UserPostMapper;
|
|||||||
import com.zt.plat.module.system.dal.mysql.user.AdminUserMapper;
|
import com.zt.plat.module.system.dal.mysql.user.AdminUserMapper;
|
||||||
import com.zt.plat.module.system.dal.mysql.userdept.UserDeptMapper;
|
import com.zt.plat.module.system.dal.mysql.userdept.UserDeptMapper;
|
||||||
import com.zt.plat.module.system.enums.common.SexEnum;
|
import com.zt.plat.module.system.enums.common.SexEnum;
|
||||||
|
import com.zt.plat.module.system.enums.user.UserSourceEnum;
|
||||||
import com.zt.plat.module.system.service.dept.DeptServiceImpl;
|
import com.zt.plat.module.system.service.dept.DeptServiceImpl;
|
||||||
import com.zt.plat.module.system.service.dept.PostService;
|
import com.zt.plat.module.system.service.dept.PostService;
|
||||||
import com.zt.plat.module.system.service.permission.PermissionService;
|
import com.zt.plat.module.system.service.permission.PermissionService;
|
||||||
@@ -35,7 +36,9 @@ import org.mockito.stubbing.Answer;
|
|||||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||||
import org.springframework.context.annotation.Import;
|
import org.springframework.context.annotation.Import;
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.util.DigestUtils;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
@@ -267,6 +270,32 @@ public class AdminUserServiceImplTest extends BaseDbUnitTest {
|
|||||||
assertEquals("encode:" + password, user.getPassword());
|
assertEquals("encode:" + password, user.getPassword());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUpdateUserPassword_forbiddenForIWork() {
|
||||||
|
AdminUserDO dbUser = randomAdminUserDO(o -> o.setUserSource(UserSourceEnum.IWORK.getSource()));
|
||||||
|
userMapper.insert(dbUser);
|
||||||
|
Long userId = dbUser.getId();
|
||||||
|
UserProfileUpdatePasswordReqVO reqVO = randomPojo(UserProfileUpdatePasswordReqVO.class, o -> {
|
||||||
|
o.setOldPassword("oldPwd");
|
||||||
|
o.setNewPassword("newPwd");
|
||||||
|
});
|
||||||
|
|
||||||
|
assertServiceException(() -> userService.updateUserPassword(userId, reqVO), USER_PASSWORD_MODIFY_FORBIDDEN);
|
||||||
|
assertServiceException(() -> userService.updateUserPassword(userId, "anotherPwd"), USER_PASSWORD_MODIFY_FORBIDDEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIsPasswordMatch_iWorkMd5() {
|
||||||
|
String rawPassword = "Abc12345";
|
||||||
|
String md5 = DigestUtils.md5DigestAsHex(rawPassword.getBytes(StandardCharsets.UTF_8)).toUpperCase(Locale.ROOT);
|
||||||
|
AdminUserDO user = randomAdminUserDO(o -> {
|
||||||
|
o.setUserSource(UserSourceEnum.IWORK.getSource());
|
||||||
|
o.setPassword(md5);
|
||||||
|
});
|
||||||
|
|
||||||
|
assertTrue(userService.isPasswordMatch(user, rawPassword));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testUpdateUserStatus() {
|
public void testUpdateUserStatus() {
|
||||||
// mock 数据
|
// mock 数据
|
||||||
|
|||||||
Reference in New Issue
Block a user