Merge branch 'refs/heads/zt-test' into test

This commit is contained in:
FCL
2026-01-29 10:21:46 +08:00
177 changed files with 7175 additions and 397 deletions

View File

@@ -2,6 +2,7 @@ package com.zt.plat.module.system.api.dept;
import com.zt.plat.framework.common.pojo.CommonResult;
import com.zt.plat.framework.common.util.collection.CollectionUtils;
import com.zt.plat.framework.common.util.object.BeanUtils;
import com.zt.plat.module.system.api.dept.dto.*;
import com.zt.plat.module.system.enums.ApiConstants;
import io.swagger.v3.oas.annotations.Operation;
@@ -15,6 +16,8 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import static com.zt.plat.framework.common.pojo.CommonResult.success;
@FeignClient(name = ApiConstants.NAME) // TODO ZTfallbackFactory =
@Tag(name = "RPC 服务 - 部门")
public interface DeptApi {
@@ -86,6 +89,11 @@ public interface DeptApi {
@Parameter(name = "userId", description = "用户编号", example = "1", required = true)
CommonResult<Set<CompanyDeptInfoRespDTO>> getCompanyDeptInfoListByUserId(@RequestParam("userId") Long userId);
@GetMapping(PREFIX+"/up-find-company-node")
@Operation(summary = "获取公司节点信息", description = "通过部门编号,向上追溯部门信息,直到上级部门是公司,返回追溯到的部门信息列表")
@Parameter(name = "deptId", description = "部门编号", required = true, example = "1024")
CommonResult<List<DeptRespDTO>> upFindCompanyNode(@RequestParam("deptId") Long deptId);
// ========== 数据同步专用接口 ==========
@PostMapping(PREFIX + "/sync")

View File

@@ -46,18 +46,18 @@ public interface IWorkIntegrationApi {
@PostMapping(PREFIX + "/hr/subcompany/page")
@Operation(summary = "获取 iWork 分部列表")
CommonResult<IWorkHrSubcompanyPageRespDTO> listSubcompanies(@RequestBody IWorkOrgPageReqDTO reqDTO);
CommonResult<IWorkHrSubcompanyPageRespDTO> listSubcompanies(@RequestBody IWorkSubcompanyQueryReqDTO reqDTO);
@PostMapping(PREFIX + "/hr/department/page")
@Operation(summary = "获取 iWork 部门列表")
CommonResult<IWorkHrDepartmentPageRespDTO> listDepartments(@RequestBody IWorkOrgPageReqDTO reqDTO);
CommonResult<IWorkHrDepartmentPageRespDTO> listDepartments(@RequestBody IWorkDepartmentQueryReqDTO reqDTO);
@PostMapping(PREFIX + "/hr/job-title/page")
@Operation(summary = "获取 iWork 岗位列表")
CommonResult<IWorkHrJobTitlePageRespDTO> listJobTitles(@RequestBody IWorkOrgPageReqDTO reqDTO);
CommonResult<IWorkHrJobTitlePageRespDTO> listJobTitles(@RequestBody IWorkJobTitleQueryReqDTO reqDTO);
@PostMapping(PREFIX + "/hr/user/page")
@Operation(summary = "获取 iWork 人员列表")
CommonResult<IWorkHrUserPageRespDTO> listUsers(@RequestBody IWorkOrgPageReqDTO reqDTO);
CommonResult<IWorkHrUserPageRespDTO> listUsers(@RequestBody IWorkUserQueryReqDTO reqDTO);
}

View File

@@ -0,0 +1,48 @@
package com.zt.plat.module.system.api.iwork.dto;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
/**
* iWork 部门查询参数。
*/
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class IWorkDepartmentQueryReqDTO extends IWorkOrgBaseQueryReqDTO {
@JsonProperty("departmentcode")
@Schema(description = "部门编号")
private String departmentCode;
@JsonProperty("departmentname")
@Schema(description = "部门名称")
private String departmentName;
@JsonProperty("subcompanyid1")
@Schema(description = "分部 ID")
private String subcompanyId1;
@JsonProperty("created")
@Schema(description = "创建时间戳(>=")
private String created;
@JsonProperty("modified")
@Schema(description = "修改时间戳(>=")
private String modified;
@JsonProperty("canceled")
@Schema(description = "封存标志默认查询非封存数据。1:封存")
private String canceled;
@JsonProperty("custom_data")
@Schema(description = "自定义字段列表(逗号分隔)")
private String customData;
@JsonProperty("id")
@Schema(description = "OA 部门 ID")
private String id;
}

View File

@@ -0,0 +1,32 @@
package com.zt.plat.module.system.api.iwork.dto;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
/**
* iWork 岗位查询参数。
*/
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class IWorkJobTitleQueryReqDTO extends IWorkOrgBaseQueryReqDTO {
@JsonProperty("jobtitlename")
@Schema(description = "岗位名称")
private String jobTitleName;
@JsonProperty("created")
@Schema(description = "创建时间戳(>=")
private String created;
@JsonProperty("modified")
@Schema(description = "修改时间戳(>=")
private String modified;
@JsonProperty("id")
@Schema(description = "岗位 ID")
private String id;
}

View File

@@ -0,0 +1,17 @@
package com.zt.plat.module.system.api.iwork.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* iWork 人力组织查询基础参数。
*/
@Data
public class IWorkOrgBaseQueryReqDTO {
@Schema(description = "当前页码", example = "1")
private Integer curpage;
@Schema(description = "每页条数", example = "20")
private Integer pagesize;
}

View File

@@ -1,21 +0,0 @@
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;
}

View File

@@ -0,0 +1,40 @@
package com.zt.plat.module.system.api.iwork.dto;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
/**
* iWork 分部查询参数。
*/
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class IWorkSubcompanyQueryReqDTO extends IWorkOrgBaseQueryReqDTO {
@JsonProperty("subcompanycode")
@Schema(description = "分部编号")
private String subcompanyCode;
@JsonProperty("subcompanyname")
@Schema(description = "分部名称")
private String subcompanyName;
@JsonProperty("modified")
@Schema(description = "修改时间戳(>=")
private String modified;
@JsonProperty("canceled")
@Schema(description = "封存标志默认查询非封存数据。1:封存")
private String canceled;
@JsonProperty("custom_data")
@Schema(description = "自定义字段列表(逗号分隔)")
private String customData;
@JsonProperty("id")
@Schema(description = "OA 分部 ID")
private String id;
}

View File

@@ -0,0 +1,60 @@
package com.zt.plat.module.system.api.iwork.dto;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
/**
* iWork 人员查询参数。
*/
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class IWorkUserQueryReqDTO extends IWorkOrgBaseQueryReqDTO {
@JsonProperty("workcode")
@Schema(description = "人员编号")
private String workCode;
@JsonProperty("subcompanyid1")
@Schema(description = "分部 ID")
private String subcompanyId1;
@JsonProperty("departmentid")
@Schema(description = "部门 ID")
private String departmentId;
@JsonProperty("jobtitleid")
@Schema(description = "岗位 ID")
private String jobTitleId;
@JsonProperty("id")
@Schema(description = "人员 ID")
private String id;
@JsonProperty("loginid")
@Schema(description = "登录名")
private String loginId;
@JsonProperty("created")
@Schema(description = "创建时间戳(>=")
private String created;
@JsonProperty("modified")
@Schema(description = "修改时间戳(>=")
private String modified;
@JsonProperty("base_custom_data")
@Schema(description = "基本信息自定义字段列表(逗号分隔)")
private String baseCustomData;
@JsonProperty("person_custom_data")
@Schema(description = "个人信息自定义字段列表(逗号分隔)")
private String personCustomData;
@JsonProperty("work_custom_data")
@Schema(description = "工作信息自定义字段列表(逗号分隔)")
private String workCustomData;
}

View File

@@ -36,9 +36,9 @@ public class IWorkWorkflowCreateReqDTO extends IWorkBaseReqDTO {
@Schema(description = "用印材料附件 URL必填")
private String xyywjUrl;
@Schema(description = "用印材料附件文件名(必填")
private String xyywjFileName;
@Schema(description = "业务回调标识回调分发使用≤255 字符")
private String bizCallbackKey;
@Schema(description = "用印事项")
private String yysx;

View File

@@ -0,0 +1,39 @@
package com.zt.plat.module.system.api.push;
import com.zt.plat.framework.common.pojo.CommonResult;
import com.zt.plat.module.system.enums.ApiConstants;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
/**
* 外部系统推送配置 Feign API
*
* @author ZT Cloud
*/
@FeignClient(name = ApiConstants.NAME)
@Tag(name = "RPC 服务 - 外部系统推送配置")
public interface ExternalPushConfigApi {
String PREFIX = ApiConstants.PREFIX + "/external-push-config";
/**
* 判断是否允许推送到外部系统
*
* @param companyId 公司编号(可选,为 null 时表示不限制公司)
* @param deptId 部门编号(可选,为 null 时只按公司配置判断)
* @param businessType 业务类型可选PURCHASE/SALE/PRODUCTION为 null 时表示所有业务类型)
* @param externalSystem 外部系统标识可选ERP/IWORK/等,为 null 时表示所有外部系统)
* @return 是否允许推送true=允许false=禁止,默认 true
*/
@GetMapping(PREFIX + "/is-push-enabled")
@Operation(summary = "判断是否允许推送到外部系统")
CommonResult<Boolean> isPushEnabled(
@RequestParam(value = "companyId", required = false) @Parameter(description = "公司编号") Long companyId,
@RequestParam(value = "deptId", required = false) @Parameter(description = "部门编号") Long deptId,
@RequestParam(value = "businessType", required = false) @Parameter(description = "业务类型") String businessType,
@RequestParam(value = "externalSystem", required = false) @Parameter(description = "外部系统标识") String externalSystem);
}

View File

@@ -230,4 +230,11 @@ public interface ErrorCodeConstants {
// ========== 门户网站 1-002-033-000 ==========
ErrorCode PORTAL_NOT_EXISTS = new ErrorCode(1_002_033_000, "门户不存在");
// ========== 外部系统推送配置 1_002_034_000 ==========
ErrorCode EXTERNAL_PUSH_CONFIG_NOT_EXISTS = new ErrorCode(1_002_034_001, "外部系统推送配置不存在");
ErrorCode EXTERNAL_PUSH_CONFIG_EXISTS = new ErrorCode(1_002_034_002, "该配置已存在");
ErrorCode EXTERNAL_PUSH_CONFIG_COMPANY_INVALID = new ErrorCode(1_002_034_003, "公司编号必须是公司节点is_company=1");
ErrorCode EXTERNAL_PUSH_CONFIG_DEPT_INVALID = new ErrorCode(1_002_034_004, "部门编号必须是部门节点is_company=0");
ErrorCode EXTERNAL_PUSH_CONFIG_BUSINESS_TYPE_INVALID = new ErrorCode(1_002_034_005, "业务类型无效,仅支持 PURCHASE/SALE/PRODUCTION");
}

View File

@@ -0,0 +1,60 @@
package com.zt.plat.module.system.enums.permission;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 数据规则条件枚举
*
* 用于菜单数据规则的条件类型
*
* @author ZT
*/
@Getter
@AllArgsConstructor
public enum DataRuleConditionEnum {
EQ("=", "等于"),
NE("!=", "不等于"),
GT(">", "大于"),
GE(">=", "大于等于"),
LT("<", "小于"),
LE("<=", "小于等于"),
IN("IN", "包含"),
NOT_IN("NOT_IN", "不包含"),
LIKE("LIKE", "模糊匹配"),
LEFT_LIKE("LEFT_LIKE", "左模糊"),
RIGHT_LIKE("RIGHT_LIKE", "右模糊"),
NOT_LIKE("NOT_LIKE", "不匹配"),
IS_NULL("IS_NULL", "为空"),
IS_NOT_NULL("IS_NOT_NULL", "不为空"),
SQL_RULE("SQL_RULE", "自定义SQL");
/**
* 条件符号
*/
private final String condition;
/**
* 条件描述
*/
private final String description;
/**
* 根据条件符号查找枚举
*
* @param condition 条件符号
* @return 枚举值
*/
public static DataRuleConditionEnum findByCondition(String condition) {
if (condition == null) {
return null;
}
for (DataRuleConditionEnum value : values()) {
if (value.condition.equals(condition)) {
return value;
}
}
return null;
}
}

View File

@@ -0,0 +1,53 @@
package com.zt.plat.module.system.enums.permission;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 数据规则变量枚举
*
* 用于菜单数据规则的变量替换
*
* @author ZT
*/
@Getter
@AllArgsConstructor
public enum DataRuleVariableEnum {
USER_ID("#{userId}", "当前用户ID"),
USERNAME("#{username}", "当前用户名"),
DEPT_ID("#{deptId}", "当前用户部门ID"),
DEPT_IDS("#{deptIds}", "当前用户所有部门ID"),
ORG_CODE("#{orgCode}", "当前用户组织编码"),
TENANT_ID("#{tenantId}", "当前租户ID"),
CURRENT_DATE("#{currentDate}", "当前日期"),
CURRENT_TIME("#{currentTime}", "当前时间");
/**
* 变量名
*/
private final String variable;
/**
* 变量描述
*/
private final String description;
/**
* 根据变量名查找枚举
*
* @param variable 变量名
* @return 枚举值
*/
public static DataRuleVariableEnum findByVariable(String variable) {
if (variable == null) {
return null;
}
for (DataRuleVariableEnum value : values()) {
if (value.variable.equals(variable)) {
return value;
}
}
return null;
}
}

View File

@@ -0,0 +1,70 @@
package com.zt.plat.module.system.enums.push;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 业务类型枚举
*
* @author ZT Cloud
*/
@AllArgsConstructor
@Getter
public enum BusinessTypeEnum {
PURCHASE(1, "PURCHASE", "采购"),
SALE(2, "SALE", "销售"),
PRODUCTION(3, "PRODUCTION", "生产");
/**
* 类型
*/
private final Integer type;
/**
* 编码
*/
private final String code;
/**
* 名称
*/
private final String name;
/**
* 根据编码获取枚举
*/
public static BusinessTypeEnum valueOfCode(String code) {
if (code == null) {
return null;
}
for (BusinessTypeEnum value : BusinessTypeEnum.values()) {
if (value.getCode().equals(code)) {
return value;
}
}
return null;
}
/**
* 根据类型获取枚举
*/
public static BusinessTypeEnum valueOfType(Integer type) {
if (type == null) {
return null;
}
for (BusinessTypeEnum value : BusinessTypeEnum.values()) {
if (value.getType().equals(type)) {
return value;
}
}
return null;
}
/**
* 验证编码是否有效
*/
public static boolean isValidCode(String code) {
return valueOfCode(code) != null;
}
}

View File

@@ -0,0 +1,23 @@
package com.zt.plat.module.system.mq.iwork;
import lombok.Builder;
import lombok.Data;
@Data
@Builder
public class IWorkBizCallbackMessage {
/** 统一回调主题 */
public static final String TOPIC = "SYSTEM_IWORK_BIZ_CALLBACK";
/** requestId 唯一标识 */
private String requestId;
/** 业务回调标识 */
private String bizCallbackKey;
/** 回调负载对象(可为 Map */
private Object payload;
/** 当前尝试次数,从 0 开始 */
private int attempt;
/** 最大尝试次数 */
private int maxAttempts;
}

View File

@@ -0,0 +1,31 @@
package com.zt.plat.module.system.mq.iwork;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class IWorkBizCallbackResultMessage {
/** 统一回调结果主题 */
public static final String TOPIC = "SYSTEM_IWORK_BIZ_CALLBACK_RESULT";
/** requestId 唯一标识 */
private String requestId;
/** 业务回调标识,对应发送方设置的 tag */
private String bizCallbackKey;
/** 是否成功 */
private boolean success;
/** 错误消息 */
private String errorMessage;
/** 当前尝试次数(业务侧可回传) */
private int attempt;
/** 最大尝试次数 */
private int maxAttempts;
/** 回调负载(用于 system 端重试再投递) */
private Object payload;
}

View File

@@ -91,6 +91,10 @@
<logger name="com.zt.plat.module.infra.dal.mysql" level="DEBUG" additivity="false">
<appender-ref ref="STDOUT"/>
</logger>
<logger name="com.zt.plat.module.system.dal.mysql" level="DEBUG" additivity="false">
<appender-ref ref="STDOUT"/>
</logger>
</springProfile>
<!-- 其它环境 -->
@@ -103,8 +107,4 @@
</root>
</springProfile>
<!-- <logger name="com.zt.plat.module.system.dal" level="DEBUG" additivity="false">-->
<!-- <appender-ref ref="STDOUT"/>-->
<!-- </logger>-->
</configuration>

View File

@@ -4,6 +4,8 @@ import com.zt.plat.framework.common.enums.CommonStatusEnum;
import com.zt.plat.framework.common.pojo.CommonResult;
import com.zt.plat.framework.common.pojo.CompanyDeptInfo;
import com.zt.plat.framework.common.util.object.BeanUtils;
import com.zt.plat.framework.datapermission.core.annotation.CompanyDataPermissionIgnore;
import com.zt.plat.framework.datapermission.core.annotation.DeptDataPermissionIgnore;
import com.zt.plat.module.system.api.dept.dto.*;
import com.zt.plat.module.system.controller.admin.dept.vo.dept.DeptListReqVO;
import com.zt.plat.module.system.controller.admin.dept.vo.dept.DeptSaveReqVO;
@@ -78,6 +80,8 @@ public class DeptApiImpl implements DeptApi {
}
@Override
@CompanyDataPermissionIgnore
@DeptDataPermissionIgnore
public CommonResult<DeptRespDTO> getDept(Long id) {
DeptDO dept = deptService.getDept(id);
return success(BeanUtils.toBean(dept, DeptRespDTO.class));
@@ -107,6 +111,12 @@ public class DeptApiImpl implements DeptApi {
return success(BeanUtils.toBean(companyDeptInfos, CompanyDeptInfoRespDTO.class));
}
@Override
public CommonResult<List<DeptRespDTO>> upFindCompanyNode(Long deptId) {
List<DeptDO> depts = deptService.upFindCompanyNode(deptId);
return success(BeanUtils.toBean(depts, DeptRespDTO.class));
}
// ========== 数据同步专用接口 ==========
@Override

View File

@@ -77,7 +77,7 @@ public class IWorkIntegrationApiImpl implements IWorkIntegrationApi {
// ----------------- 人力组织分页接口 -----------------
@Override
public CommonResult<IWorkHrSubcompanyPageRespDTO> listSubcompanies(IWorkOrgPageReqDTO reqDTO) {
public CommonResult<IWorkHrSubcompanyPageRespDTO> listSubcompanies(IWorkSubcompanyQueryReqDTO reqDTO) {
IWorkSubcompanyQueryReqVO reqVO = BeanUtils.toBean(reqDTO, IWorkSubcompanyQueryReqVO.class);
IWorkHrSubcompanyPageRespVO respVO = orgRestService.listSubcompanies(reqVO);
IWorkHrSubcompanyPageRespDTO respDTO = BeanUtils.toBean(respVO, IWorkHrSubcompanyPageRespDTO.class);
@@ -85,7 +85,7 @@ public class IWorkIntegrationApiImpl implements IWorkIntegrationApi {
}
@Override
public CommonResult<IWorkHrDepartmentPageRespDTO> listDepartments(IWorkOrgPageReqDTO reqDTO) {
public CommonResult<IWorkHrDepartmentPageRespDTO> listDepartments(IWorkDepartmentQueryReqDTO reqDTO) {
IWorkDepartmentQueryReqVO reqVO = BeanUtils.toBean(reqDTO, IWorkDepartmentQueryReqVO.class);
IWorkHrDepartmentPageRespVO respVO = orgRestService.listDepartments(reqVO);
IWorkHrDepartmentPageRespDTO respDTO = BeanUtils.toBean(respVO, IWorkHrDepartmentPageRespDTO.class);
@@ -93,7 +93,7 @@ public class IWorkIntegrationApiImpl implements IWorkIntegrationApi {
}
@Override
public CommonResult<IWorkHrJobTitlePageRespDTO> listJobTitles(IWorkOrgPageReqDTO reqDTO) {
public CommonResult<IWorkHrJobTitlePageRespDTO> listJobTitles(IWorkJobTitleQueryReqDTO reqDTO) {
IWorkJobTitleQueryReqVO reqVO = BeanUtils.toBean(reqDTO, IWorkJobTitleQueryReqVO.class);
IWorkHrJobTitlePageRespVO respVO = orgRestService.listJobTitles(reqVO);
IWorkHrJobTitlePageRespDTO respDTO = BeanUtils.toBean(respVO, IWorkHrJobTitlePageRespDTO.class);
@@ -101,7 +101,7 @@ public class IWorkIntegrationApiImpl implements IWorkIntegrationApi {
}
@Override
public CommonResult<IWorkHrUserPageRespDTO> listUsers(IWorkOrgPageReqDTO reqDTO) {
public CommonResult<IWorkHrUserPageRespDTO> listUsers(IWorkUserQueryReqDTO reqDTO) {
IWorkUserQueryReqVO reqVO = BeanUtils.toBean(reqDTO, IWorkUserQueryReqVO.class);
IWorkHrUserPageRespVO respVO = orgRestService.listUsers(reqVO);
IWorkHrUserPageRespDTO respDTO = BeanUtils.toBean(respVO, IWorkHrUserPageRespDTO.class);

View File

@@ -0,0 +1,28 @@
package com.zt.plat.module.system.api.push;
import com.zt.plat.framework.common.pojo.CommonResult;
import com.zt.plat.module.system.service.push.ExternalPushConfigService;
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;
/**
* 外部系统推送配置 Feign API 实现类
*
* @author ZT Cloud
*/
@RestController
@Validated
public class ExternalPushConfigApiImpl implements ExternalPushConfigApi {
@Resource
private ExternalPushConfigService externalPushConfigService;
@Override
public CommonResult<Boolean> isPushEnabled(Long companyId, Long deptId, String businessType, String externalSystem) {
Boolean result = externalPushConfigService.isPushEnabled(companyId, deptId, businessType, externalSystem);
return success(result);
}
}

View File

@@ -6,7 +6,6 @@ import com.zt.plat.module.system.enums.social.SocialTypeEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.AssertTrue;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.Pattern;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
@@ -22,8 +21,8 @@ public class AuthLoginReqVO extends CaptchaVerificationReqVO {
@Schema(description = "账号", requiredMode = Schema.RequiredMode.REQUIRED, example = "ztyuanma")
@NotEmpty(message = "登录账号不能为空")
@Length(min = 4, max = 16, message = "账号长度为 4-16 位")
@Pattern(regexp = "^[A-Za-z0-9]+$", message = "账号格式为数字以及字母")
@Length(min = 1, max = 16, message = "账号长度为 1-16 位")
// @Pattern(regexp = "^[A-Za-z0-9]+$", message = "账号格式为数字以及字母")
private String username;
@Schema(description = "密码", requiredMode = Schema.RequiredMode.REQUIRED, example = "buzhidao")

View File

@@ -2,7 +2,6 @@ package com.zt.plat.module.system.controller.admin.auth.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.Pattern;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
@@ -19,7 +18,7 @@ public class AuthTestLoginReqVO {
@Schema(description = "账号", requiredMode = Schema.RequiredMode.REQUIRED, example = "ztyuanma")
@NotEmpty(message = "登录账号不能为空")
@Length(min = 4, max = 16, message = "账号长度为 4-16 位")
@Pattern(regexp = "^[A-Za-z0-9]+$", message = "账号格式为数字以及字母")
// @Pattern(regexp = "^[A-Za-z0-9]+$", message = "账号格式为数字以及字母")
private String username;
@Schema(description = "密码", requiredMode = Schema.RequiredMode.REQUIRED, example = "buzhidao")

View File

@@ -165,4 +165,11 @@ public class DeptController {
return success(BeanUtils.toBean(companyDeptInfos, CompanyDeptInfoRespDTO.class));
}
@GetMapping("/up-find-company-node")
@Operation(summary = "获取公司节点信息", description = "通过部门编号,向上追溯部门信息,直到上级部门是公司,返回追溯到的部门信息列表")
@Parameter(name = "deptId", description = "部门编号", required = true, example = "1024")
public CommonResult<List<DeptRespVO>> upFindCompanyNode(@RequestParam("deptId") Long deptId) {
List<DeptDO> list = deptService.upFindCompanyNode(deptId);
return success(BeanUtils.toBean(list, DeptRespVO.class));
}
}

View File

@@ -3,6 +3,7 @@ package com.zt.plat.module.system.controller.admin.integration.iwork;
import com.zt.plat.framework.common.pojo.CommonResult;
import com.zt.plat.framework.tenant.core.aop.TenantIgnore;
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.*;
import com.zt.plat.module.system.service.integration.iwork.IWorkCallbackLogService;
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;
@@ -19,6 +20,8 @@ import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.security.access.prepost.PreAuthorize;
import static com.zt.plat.framework.common.pojo.CommonResult.success;
/**
* 提供统一 iWork 流程能力的管理端接口。
@@ -34,6 +37,7 @@ public class IWorkIntegrationController {
private final IWorkIntegrationService integrationService;
private final IWorkOrgRestService orgRestService;
private final IWorkSyncService syncService;
private final IWorkCallbackLogService callbackLogService;
@PostMapping("/auth/register")
@Operation(summary = "注册 iWork 凭证,获取服务端公钥与 secret")
@@ -87,6 +91,39 @@ public class IWorkIntegrationController {
return success(integrationService.voidWorkflow(reqVO));
}
@PreAuthorize("@ss.hasPermission('system:iwork:log:query')")
@PostMapping("/log/page")
@Operation(summary = "iWork 回调日志分页查询")
public CommonResult<com.zt.plat.framework.common.pojo.PageResult<IWorkCallbackLogRespVO>> pageLogs(@Valid @RequestBody IWorkCallbackLogPageReqVO reqVO) {
com.zt.plat.framework.common.pojo.PageResult<com.zt.plat.module.system.dal.dataobject.iwork.IWorkSealLogDO> page = callbackLogService.page(reqVO);
java.util.List<IWorkCallbackLogRespVO> mapped = new java.util.ArrayList<>();
for (com.zt.plat.module.system.dal.dataobject.iwork.IWorkSealLogDO log : page.getList()) {
IWorkCallbackLogRespVO vo = new IWorkCallbackLogRespVO();
vo.setId(log.getId());
vo.setRequestId(log.getRequestId());
vo.setBusinessCode(log.getBusinessCode());
vo.setBizCallbackKey(log.getBizCallbackKey());
vo.setStatus(log.getStatus());
vo.setRetryCount(log.getRetryCount());
vo.setMaxRetry(log.getMaxRetry());
vo.setLastErrorMessage(log.getLastErrorMessage());
vo.setRawCallback(log.getRawCallback());
vo.setLastCallbackTime(log.getLastCallbackTime());
vo.setCreateTime(log.getCreateTime());
vo.setUpdateTime(log.getUpdateTime());
mapped.add(vo);
}
return success(new com.zt.plat.framework.common.pojo.PageResult<>(mapped, page.getTotal(), page.getSummary()));
}
@PreAuthorize("@ss.hasPermission('system:iwork:log:retry')")
@PostMapping("/log/retry")
@Operation(summary = "iWork 回调手工重试")
public CommonResult<Boolean> retry(@Valid @RequestBody IWorkWorkflowVoidReqVO reqVO) {
callbackLogService.resetAndDispatch(reqVO.getRequestId());
return success(true);
}
// ----------------- 人力组织接口 -----------------
@PostMapping("/hr/subcompany/page")

View File

@@ -0,0 +1,39 @@
package com.zt.plat.module.system.controller.admin.integration.iwork.vo;
import com.zt.plat.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static com.zt.plat.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - iWork 用印回调日志分页查询")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class IWorkCallbackLogPageReqVO extends PageParam {
@Schema(description = "requestId")
private String requestId;
@Schema(description = "业务单号")
private String businessCode;
@Schema(description = "业务回调标识")
private String bizCallbackKey;
@Schema(description = "状态")
private Integer status;
@Schema(description = "创建时间范围")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] createTime;
@Schema(description = "最后回调时间范围")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] lastCallbackTime;
}

View File

@@ -0,0 +1,24 @@
package com.zt.plat.module.system.controller.admin.integration.iwork.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "iWork 用印回调日志响应 VO")
@Data
public class IWorkCallbackLogRespVO {
private Long id;
private String requestId;
private String businessCode;
private String bizCallbackKey;
private Integer status;
private Integer retryCount;
private Integer maxRetry;
private String lastErrorMessage;
private String rawCallback;
private LocalDateTime lastCallbackTime;
private LocalDateTime createTime;
private LocalDateTime updateTime;
}

View File

@@ -1,5 +1,6 @@
package com.zt.plat.module.system.controller.admin.integration.iwork.vo;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
@@ -13,12 +14,35 @@ import lombok.ToString;
@ToString(callSuper = true)
public class IWorkDepartmentQueryReqVO extends IWorkOrgBaseQueryReqVO {
@Schema(description = "部门编码")
@JsonProperty("departmentcode")
@Schema(description = "部门编号")
private String departmentCode;
@JsonProperty("departmentname")
@Schema(description = "部门名称")
private String departmentName;
@Schema(description = "所属分部ID")
private String subcompanyId;
@JsonProperty("subcompanyid1")
@Schema(description = "分部 ID")
private String subcompanyId1;
@JsonProperty("created")
@Schema(description = "创建时间戳(>=")
private String created;
@JsonProperty("modified")
@Schema(description = "修改时间戳(>=")
private String modified;
@JsonProperty("canceled")
@Schema(description = "封存标志默认查询非封存数据。1:封存")
private String canceled;
@JsonProperty("custom_data")
@Schema(description = "自定义字段列表(逗号分隔)")
private String customData;
@JsonProperty("id")
@Schema(description = "OA 部门 ID")
private String id;
}

View File

@@ -2,12 +2,21 @@ package com.zt.plat.module.system.controller.admin.integration.iwork.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Data;
@Schema(name = "IWorkFileCallbackReqVO", description = "iWork 文件回调请求 VO")
@Data
public class IWorkFileCallbackReqVO {
@Schema(description = "iWork requestId唯一标识", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "requestId 不能为空")
private String requestId;
@Schema(description = "业务回调标识 bizCallbackKey≤255 字符", example = "seal-flow-callback")
@Size(max = 255, message = "bizCallbackKey 长度不能超过 255 字符")
private String bizCallbackKey;
@Schema(description = "文件下载 URL", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://example.com/file.pdf")
@NotBlank(message = "文件 URL 不能为空")
private String fileUrl;
@@ -19,6 +28,9 @@ public class IWorkFileCallbackReqVO {
@Schema(description = "文件名称,可选", example = "合同附件.pdf")
private String fileName;
@Schema(description = "OA 单点下载使用的 ssoToken可选", example = "6102A7C13F09DD6B1AF06CDA0E479AC8...")
private String ssoToken;
@Schema(description = "业务回调状态/结果码,可选")
private String status;
@Schema(description = "原始回调文本(可选,若不传则使用 payload 或请求体序列化)")
private String rawBody;
}

View File

@@ -1,5 +1,6 @@
package com.zt.plat.module.system.controller.admin.integration.iwork.vo;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.zt.plat.module.system.enums.integration.IWorkSyncEntityTypeEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Max;
@@ -42,6 +43,70 @@ public class IWorkFullSyncReqVO {
@Schema(description = "是否允许更新已存在的本地实体", example = "false")
private Boolean allowUpdate = Boolean.FALSE;
@JsonProperty("departmentcode")
@Schema(description = "部门编号")
private String departmentCode;
@JsonProperty("departmentname")
@Schema(description = "部门名称")
private String departmentName;
@JsonProperty("subcompanycode")
@Schema(description = "分部编号")
private String subcompanyCode;
@JsonProperty("subcompanyname")
@Schema(description = "分部名称")
private String subcompanyName;
@JsonProperty("subcompanyid1")
@Schema(description = "分部 ID")
private String subcompanyId1;
@JsonProperty("jobtitleid")
@Schema(description = "岗位 ID")
private String jobTitleId;
@JsonProperty("jobtitlename")
@Schema(description = "岗位名称")
private String jobTitleName;
@JsonProperty("workcode")
@Schema(description = "人员编号")
private String workCode;
@JsonProperty("loginid")
@Schema(description = "登录名")
private String loginId;
@JsonProperty("created")
@Schema(description = "创建时间戳(>=")
private String created;
@JsonProperty("modified")
@Schema(description = "修改时间戳(>=")
private String modified;
@JsonProperty("canceled")
@Schema(description = "封存标志默认查询非封存数据。1:封存")
private String canceled;
@JsonProperty("custom_data")
@Schema(description = "自定义字段列表(逗号分隔)")
private String customData;
@JsonProperty("base_custom_data")
@Schema(description = "基本信息自定义字段列表(逗号分隔)")
private String baseCustomData;
@JsonProperty("person_custom_data")
@Schema(description = "个人信息自定义字段列表(逗号分隔)")
private String personCustomData;
@JsonProperty("work_custom_data")
@Schema(description = "工作信息自定义字段列表(逗号分隔)")
private String workCustomData;
public Set<IWorkSyncEntityTypeEnum> resolveScopes() {
EnumSet<IWorkSyncEntityTypeEnum> defaults = EnumSet.allOf(IWorkSyncEntityTypeEnum.class);
if (scopes == null || scopes.isEmpty()) {

View File

@@ -1,5 +1,6 @@
package com.zt.plat.module.system.controller.admin.integration.iwork.vo;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
@@ -13,9 +14,19 @@ import lombok.ToString;
@ToString(callSuper = true)
public class IWorkJobTitleQueryReqVO extends IWorkOrgBaseQueryReqVO {
@Schema(description = "岗位编码")
private String jobTitleCode;
@JsonProperty("jobtitlename")
@Schema(description = "岗位名称")
private String jobTitleName;
@JsonProperty("created")
@Schema(description = "创建时间戳(>=")
private String created;
@JsonProperty("modified")
@Schema(description = "修改时间戳(>=")
private String modified;
@JsonProperty("id")
@Schema(description = "岗位 ID")
private String id;
}

View File

@@ -14,6 +14,6 @@ public class IWorkOaTokenReqVO {
@NotBlank(message = "loginId 不能为空")
private String loginId;
@Schema(description = "应用 appid未填则使用配置默认", example = "a17ca6ca-88b0-463e-bffa-7995086bf225")
@Schema(description = "应用 appid已固定使用配置值,无需传递", example = "")
private String appId;
}

View File

@@ -3,7 +3,6 @@ package com.zt.plat.module.system.controller.admin.integration.iwork.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.Map;
/**
* iWork 组织查询基础参数。
@@ -16,7 +15,4 @@ public class IWorkOrgBaseQueryReqVO {
@Schema(description = "每页条数", example = "10")
private Integer pagesize;
@Schema(description = "查询参数(扩展用),将被序列化为 params 传给 iWork")
private Map<String, Object> params;
}

View File

@@ -1,5 +1,6 @@
package com.zt.plat.module.system.controller.admin.integration.iwork.vo;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
@@ -13,9 +14,27 @@ import lombok.ToString;
@ToString(callSuper = true)
public class IWorkSubcompanyQueryReqVO extends IWorkOrgBaseQueryReqVO {
@Schema(description = "分部编码")
@JsonProperty("subcompanycode")
@Schema(description = "分部编号")
private String subcompanyCode;
@JsonProperty("subcompanyname")
@Schema(description = "分部名称")
private String subcompanyName;
@JsonProperty("modified")
@Schema(description = "修改时间戳(>=")
private String modified;
@JsonProperty("canceled")
@Schema(description = "封存标志默认查询非封存数据。1:封存")
private String canceled;
@JsonProperty("custom_data")
@Schema(description = "自定义字段列表(逗号分隔)")
private String customData;
@JsonProperty("id")
@Schema(description = "OA 分部 ID")
private String id;
}

View File

@@ -1,5 +1,6 @@
package com.zt.plat.module.system.controller.admin.integration.iwork.vo;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
@@ -13,27 +14,47 @@ import lombok.ToString;
@ToString(callSuper = true)
public class IWorkUserQueryReqVO extends IWorkOrgBaseQueryReqVO {
@Schema(description = "人员工号")
@JsonProperty("workcode")
@Schema(description = "人员编号")
private String workCode;
@Schema(description = "人员姓名")
private String lastName;
@JsonProperty("subcompanyid1")
@Schema(description = "分部 ID")
private String subcompanyId1;
@Schema(description = "所属部门ID")
@JsonProperty("departmentid")
@Schema(description = "部门 ID")
private String departmentId;
@Schema(description = "所属分部ID")
private String subcompanyId;
@Schema(description = "所属岗位ID")
@JsonProperty("jobtitleid")
@Schema(description = "岗位 ID")
private String jobTitleId;
@Schema(description = "人员状态 (0:试用, 1:正式, 2:临时, 3:试用延期, 4:解聘, 5:离职, 6:退休, 7:无效)")
private String status;
@JsonProperty("id")
@Schema(description = "人员 ID")
private String id;
@Schema(description = "手机号")
private String mobile;
@JsonProperty("loginid")
@Schema(description = "登录名")
private String loginId;
@Schema(description = "邮箱")
private String email;
@JsonProperty("created")
@Schema(description = "创建时间戳(>=")
private String created;
@JsonProperty("modified")
@Schema(description = "修改时间戳(>=")
private String modified;
@JsonProperty("base_custom_data")
@Schema(description = "基本信息自定义字段列表(逗号分隔)")
private String baseCustomData;
@JsonProperty("person_custom_data")
@Schema(description = "个人信息自定义字段列表(逗号分隔)")
private String personCustomData;
@JsonProperty("work_custom_data")
@Schema(description = "工作信息自定义字段列表(逗号分隔)")
private String workCustomData;
}

View File

@@ -0,0 +1,26 @@
package com.zt.plat.module.system.controller.admin.integration.iwork.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
@Schema(description = "iWork 流程回调请求")
@Data
public class IWorkWorkflowCallbackReqVO {
@Schema(description = "iWork requestId唯一标识", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "requestId 不能为空")
private String requestId;
@Schema(description = "业务单号 (ywxtdjbh)")
private String businessCode;
@Schema(description = "业务回调标识 bizCallbackKey")
private String bizCallbackKey;
@Schema(description = "回调状态/结果码")
private String status;
@Schema(description = "原始回调文本(可截断存储)")
private String rawBody;
}

View File

@@ -34,9 +34,9 @@ public class IWorkWorkflowCreateReqVO extends IWorkBaseReqVO {
@Schema(description = "用印材料附件 URL必填")
private String xyywjUrl;
@Schema(description = "用印材料附件文件名(必填")
private String xyywjFileName;
@Schema(description = "业务回调标识回调分发使用≤255 字符")
private String bizCallbackKey;
@Schema(description = "用印事项")
private String yysx;

View File

@@ -0,0 +1,77 @@
package com.zt.plat.module.system.controller.admin.permission;
import com.zt.plat.framework.common.pojo.CommonResult;
import com.zt.plat.module.system.controller.admin.permission.vo.menudatarule.MenuDataRuleRespVO;
import com.zt.plat.module.system.controller.admin.permission.vo.menudatarule.MenuDataRuleSaveReqVO;
import com.zt.plat.module.system.convert.permission.MenuDataRuleConvert;
import com.zt.plat.module.system.dal.dataobject.permission.MenuDataRuleDO;
import com.zt.plat.module.system.service.permission.MenuDataRuleService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import static com.zt.plat.framework.common.pojo.CommonResult.success;
/**
* 菜单数据规则 Controller
*
* @author ZT
*/
@Tag(name = "管理后台 - 菜单数据规则")
@RestController
@RequestMapping("/system/menu-data-rule")
@Validated
public class MenuDataRuleController {
@Resource
private MenuDataRuleService menuDataRuleService;
@PostMapping("/create")
@Operation(summary = "创建菜单数据规则")
@PreAuthorize("@ss.hasPermission('system:menu:update')")
public CommonResult<Long> createMenuDataRule(@Valid @RequestBody MenuDataRuleSaveReqVO createReqVO) {
return success(menuDataRuleService.createMenuDataRule(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新菜单数据规则")
@PreAuthorize("@ss.hasPermission('system:menu:update')")
public CommonResult<Boolean> updateMenuDataRule(@Valid @RequestBody MenuDataRuleSaveReqVO updateReqVO) {
menuDataRuleService.updateMenuDataRule(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除菜单数据规则")
@Parameter(name = "id", description = "规则ID", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('system:menu:update')")
public CommonResult<Boolean> deleteMenuDataRule(@RequestParam("id") Long id) {
menuDataRuleService.deleteMenuDataRule(id);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得菜单数据规则")
@Parameter(name = "id", description = "规则ID", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('system:menu:query')")
public CommonResult<MenuDataRuleRespVO> getMenuDataRule(@RequestParam("id") Long id) {
MenuDataRuleDO rule = menuDataRuleService.getMenuDataRule(id);
return success(MenuDataRuleConvert.INSTANCE.convert(rule));
}
@GetMapping("/list")
@Operation(summary = "获得菜单的所有数据规则")
@Parameter(name = "menuId", description = "菜单ID", required = true, example = "1")
@PreAuthorize("@ss.hasPermission('system:menu:query')")
public CommonResult<List<MenuDataRuleRespVO>> getMenuDataRuleList(@RequestParam("menuId") Long menuId) {
List<MenuDataRuleDO> list = menuDataRuleService.getMenuDataRuleListByMenuId(menuId);
return success(MenuDataRuleConvert.INSTANCE.convertList(list));
}
}

View File

@@ -66,6 +66,9 @@ public class PermissionController {
PermissionAssignRoleMenuItemReqVO reqVO = new PermissionAssignRoleMenuItemReqVO();
reqVO.setId(menu.getMenuId());
reqVO.setShowMenu(menu.getShowMenu());
// 获取该角色在该菜单下的数据规则ID列表
Set<Long> dataRuleIds = permissionService.getRoleMenuDataRules(roleId, menu.getMenuId());
reqVO.setDataRuleIds(dataRuleIds != null ? new ArrayList<>(dataRuleIds) : null);
return reqVO;
}).collect(Collectors.toSet());
return success(result);
@@ -83,6 +86,10 @@ public class PermissionController {
// 更新菜单的显示状态
permissionService.updateMenuDisplay(reqVO.getRoleId(), reqVO.getMenus());
// 保存菜单数据规则关联
permissionService.assignRoleMenuDataRules(reqVO.getRoleId(), reqVO.getMenus());
return success(true);
}

View File

@@ -11,6 +11,7 @@ import com.zt.plat.module.system.controller.admin.permission.vo.role.RolePageReq
import com.zt.plat.module.system.controller.admin.permission.vo.role.RoleRespVO;
import com.zt.plat.module.system.controller.admin.permission.vo.role.RoleSaveReqVO;
import com.zt.plat.module.system.dal.dataobject.permission.RoleDO;
import com.zt.plat.framework.datapermission.core.menudatapermission.annotation.PermissionData;
import com.zt.plat.module.system.service.permission.RoleService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
@@ -78,6 +79,7 @@ public class RoleController {
@GetMapping("/page")
@Operation(summary = "获得角色分页")
@PreAuthorize("@ss.hasPermission('system:role:query')")
@PermissionData(pageComponent = "system/role/index")
public CommonResult<PageResult<RoleRespVO>> getRolePage(RolePageReqVO pageReqVO) {
PageResult<RoleDO> pageResult = roleService.getRolePage(pageReqVO);
// 获取所有父级角色信息

View File

@@ -0,0 +1,46 @@
package com.zt.plat.module.system.controller.admin.permission.vo.menudatarule;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 菜单数据规则 Response VO
*
* @author ZT
*/
@Schema(description = "管理后台 - 菜单数据规则 Response VO")
@Data
public class MenuDataRuleRespVO {
@Schema(description = "规则ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long id;
@Schema(description = "菜单ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Long menuId;
@Schema(description = "规则名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "仅看本部门数据")
private String ruleName;
@Schema(description = "规则字段", example = "dept_id")
private String ruleColumn;
@Schema(description = "规则条件", requiredMode = Schema.RequiredMode.REQUIRED, example = "=")
private String ruleConditions;
@Schema(description = "规则值", requiredMode = Schema.RequiredMode.REQUIRED, example = "#{deptId}")
private String ruleValue;
@Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer status;
@Schema(description = "排序", example = "1")
private Integer sort;
@Schema(description = "备注", example = "限制只能查看本部门数据")
private String remark;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
}

View File

@@ -0,0 +1,53 @@
package com.zt.plat.module.system.controller.admin.permission.vo.menudatarule;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
/**
* 菜单数据规则创建/修改 Request VO
*
* @author ZT
*/
@Schema(description = "管理后台 - 菜单数据规则创建/修改 Request VO")
@Data
public class MenuDataRuleSaveReqVO {
@Schema(description = "规则ID", example = "1024")
private Long id;
@Schema(description = "菜单ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "菜单ID不能为空")
private Long menuId;
@Schema(description = "规则名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "仅看本部门数据")
@NotBlank(message = "规则名称不能为空")
@Size(max = 100, message = "规则名称长度不能超过 100 个字符")
private String ruleName;
@Schema(description = "规则字段", example = "dept_id")
@Size(max = 100, message = "规则字段长度不能超过 100 个字符")
private String ruleColumn;
@Schema(description = "规则条件", requiredMode = Schema.RequiredMode.REQUIRED, example = "=")
@NotBlank(message = "规则条件不能为空")
@Size(max = 20, message = "规则条件长度不能超过 20 个字符")
private String ruleConditions;
@Schema(description = "规则值", requiredMode = Schema.RequiredMode.REQUIRED, example = "#{deptId}")
@NotBlank(message = "规则值不能为空")
@Size(max = 500, message = "规则值长度不能超过 500 个字符")
private String ruleValue;
@Schema(description = "状态", example = "1")
private Integer status;
@Schema(description = "排序", example = "1")
private Integer sort;
@Schema(description = "备注", example = "限制只能查看本部门数据")
@Size(max = 500, message = "备注长度不能超过 500 个字符")
private String remark;
}

View File

@@ -4,6 +4,8 @@ import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.util.List;
@Schema(description = "管理后台 - 赋予角色菜单--菜单列表 Request VO")
@Data
@@ -19,4 +21,7 @@ public class PermissionAssignRoleMenuItemReqVO {
@Schema(description = "是否显示菜单按钮是否点击过(避免大量更新数据,只更新点击过的)")
private Boolean showMenuChanged = false;
@Schema(description = "菜单数据规则ID列表", example = "[1, 2, 3]")
private List<Long> dataRuleIds;
}

View File

@@ -4,6 +4,9 @@ import com.zt.plat.framework.common.pojo.CommonResult;
import com.zt.plat.framework.common.pojo.PageResult;
import com.zt.plat.framework.common.util.object.BeanUtils;
import com.zt.plat.framework.excel.core.util.ExcelUtils;
import com.zt.plat.framework.tenant.core.aop.TenantIgnore;
import com.zt.plat.module.infra.api.file.FileApi;
import com.zt.plat.module.infra.api.file.dto.FileRespDTO;
import com.zt.plat.module.system.controller.admin.portal.vo.PortalPageReqVO;
import com.zt.plat.module.system.controller.admin.portal.vo.PortalRespVO;
import com.zt.plat.module.system.controller.admin.portal.vo.PortalSaveReqVO;
@@ -13,6 +16,7 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.annotation.security.PermitAll;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
@@ -20,7 +24,12 @@ import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.springframework.util.StringUtils;
import static com.zt.plat.framework.common.pojo.CommonResult.success;
import static com.zt.plat.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@@ -39,6 +48,9 @@ public class PortalController {
@Resource
private PortalService portalService;
@Resource
private FileApi fileApi;
@PostMapping("/create")
@Operation(summary = "创建门户网站")
@PreAuthorize("@ss.hasPermission('system:portal:create')")
@@ -97,9 +109,60 @@ public class PortalController {
*/
@GetMapping("/list")
@Operation(summary = "获取我的门户列表")
@PermitAll
@TenantIgnore
public CommonResult<List<PortalRespVO>> getMyPortalList() {
Long userId = getLoginUserId();
List<PortalDO> portals = portalService.getPortalListByUserId(userId);
Long userId = null;
try {
userId = getLoginUserId();
} catch (Exception ignored) {
// 未登录时获取公开门户
}
List<PortalDO> portals = (userId == null)
? portalService.getPublicPortalList()
: portalService.getPortalListByUserId(userId);
return success(BeanUtils.toBean(portals, PortalRespVO.class));
}
/**
* 匿名获取公开门户的图标文件信息
* 仅允许访问门户中已配置的图标文件
*/
@GetMapping("/public-icon-files")
@Operation(summary = "获取公开门户图标文件信息")
@PermitAll
@TenantIgnore
public CommonResult<List<FileRespDTO>> getPublicPortalIconFiles(@RequestParam("fileIds") List<Long> fileIds) {
if (fileIds == null || fileIds.isEmpty()) {
return success(java.util.Collections.emptyList());
}
List<PortalDO> portals = portalService.getPublicPortalList();
Set<Long> allowedFileIds = new HashSet<>();
for (PortalDO portal : portals) {
if (portal.getIconType() == null || portal.getIconType() != 2) {
continue;
}
if (!StringUtils.hasText(portal.getIconFileId())) {
continue;
}
try {
allowedFileIds.add(Long.parseLong(portal.getIconFileId()));
} catch (NumberFormatException ignored) {
// ignore invalid fileId
}
}
List<FileRespDTO> result = new ArrayList<>();
for (Long fileId : fileIds) {
if (!allowedFileIds.contains(fileId)) {
continue;
}
CommonResult<FileRespDTO> fileResult = fileApi.getFileInfo(fileId);
if (fileResult != null && fileResult.isSuccess() && fileResult.getData() != null) {
result.add(fileResult.getData());
}
}
return success(result);
}
}

View File

@@ -0,0 +1,160 @@
package com.zt.plat.module.system.controller.admin.push;
import com.zt.plat.framework.common.pojo.CommonResult;
import com.zt.plat.framework.common.pojo.PageResult;
import com.zt.plat.framework.common.util.collection.CollectionUtils;
import com.zt.plat.framework.common.util.object.BeanUtils;
import com.zt.plat.module.system.controller.admin.push.vo.BusinessTypeRespVO;
import com.zt.plat.module.system.controller.admin.push.vo.ExternalPushConfigPageReqVO;
import com.zt.plat.module.system.controller.admin.push.vo.ExternalPushConfigRespVO;
import com.zt.plat.module.system.controller.admin.push.vo.ExternalPushConfigSaveReqVO;
import com.zt.plat.module.system.dal.dataobject.dept.DeptDO;
import com.zt.plat.module.system.dal.dataobject.push.ExternalPushConfigDO;
import com.zt.plat.module.system.enums.push.BusinessTypeEnum;
import com.zt.plat.module.system.service.dept.DeptService;
import com.zt.plat.module.system.service.push.ExternalPushConfigService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import static com.zt.plat.framework.common.pojo.CommonResult.success;
/**
* 外部系统推送配置 Controller
*
* @author ZT Cloud
*/
@Tag(name = "管理后台 - 外部系统推送配置")
@RestController
@RequestMapping("/system/external-push-config")
@Validated
public class ExternalPushConfigController {
@Resource
private ExternalPushConfigService externalPushConfigService;
@Resource
private DeptService deptService;
@PostMapping("/create")
@Operation(summary = "创建推送配置")
@PreAuthorize("@ss.hasPermission('system:external-push-config:create')")
public CommonResult<Long> create(@Valid @RequestBody ExternalPushConfigSaveReqVO createReqVO) {
Long id = externalPushConfigService.createExternalPushConfig(createReqVO);
return success(id);
}
@PutMapping("/update")
@Operation(summary = "修改推送配置")
@PreAuthorize("@ss.hasPermission('system:external-push-config:update')")
public CommonResult<Boolean> update(@Valid @RequestBody ExternalPushConfigSaveReqVO updateReqVO) {
externalPushConfigService.updateExternalPushConfig(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除推送配置")
@PreAuthorize("@ss.hasPermission('system:external-push-config:delete')")
public CommonResult<Boolean> delete(@RequestParam("id") Long id) {
externalPushConfigService.deleteExternalPushConfig(id);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获取推送配置详情")
@PreAuthorize("@ss.hasPermission('system:external-push-config:query')")
public CommonResult<ExternalPushConfigRespVO> get(@RequestParam("id") Long id) {
ExternalPushConfigDO entity = externalPushConfigService.getExternalPushConfig(id);
ExternalPushConfigRespVO respVO = BeanUtils.toBean(entity, ExternalPushConfigRespVO.class);
fillDeptInfo(List.of(respVO));
return success(respVO);
}
@GetMapping("/page")
@Operation(summary = "分页查询推送配置")
@PreAuthorize("@ss.hasPermission('system:external-push-config:query')")
public CommonResult<PageResult<ExternalPushConfigRespVO>> page(@Valid ExternalPushConfigPageReqVO reqVO) {
PageResult<ExternalPushConfigDO> pageResult = externalPushConfigService.getExternalPushConfigPage(reqVO);
PageResult<ExternalPushConfigRespVO> result = BeanUtils.toBean(pageResult, ExternalPushConfigRespVO.class);
fillDeptInfo(result.getList());
return success(result);
}
@GetMapping("/is-push-enabled")
@Operation(summary = "判断是否允许推送")
@Parameter(name = "companyId", description = "公司编号(为空表示不限制公司)")
@Parameter(name = "deptId", description = "部门编号")
@Parameter(name = "businessType", description = "业务类型(为空表示所有业务类型)")
@Parameter(name = "externalSystem", description = "外部系统标识(为空表示所有外部系统)")
@PreAuthorize("@ss.hasPermission('system:external-push-config:query')")
public CommonResult<Boolean> isPushEnabled(
@RequestParam(value = "companyId", required = false) Long companyId,
@RequestParam(value = "deptId", required = false) Long deptId,
@RequestParam(value = "businessType", required = false) String businessType,
@RequestParam(value = "externalSystem", required = false) String externalSystem) {
Boolean result = externalPushConfigService.isPushEnabled(companyId, deptId, businessType, externalSystem);
return success(result);
}
@GetMapping("/business-types")
@Operation(summary = "获取业务类型列表")
public CommonResult<List<BusinessTypeRespVO>> getBusinessTypes() {
List<BusinessTypeRespVO> result = Arrays.stream(BusinessTypeEnum.values())
.map(e -> new BusinessTypeRespVO(e.getType(), e.getCode(), e.getName()))
.collect(Collectors.toList());
return success(result);
}
/**
* 填充公司和部门名称
*/
private void fillDeptInfo(List<ExternalPushConfigRespVO> list) {
if (list == null || list.isEmpty()) {
return;
}
// 收集所有公司ID和部门ID
Set<Long> deptIds = list.stream()
.flatMap(item -> {
Set<Long> ids = new java.util.HashSet<>();
ids.add(item.getCompanyId());
if (item.getDeptId() != null) {
ids.add(item.getDeptId());
}
return ids.stream();
})
.collect(Collectors.toSet());
if (deptIds.isEmpty()) {
return;
}
// 批量查询部门信息
Map<Long, DeptDO> deptMap = CollectionUtils.convertMap(
deptService.getDeptList(deptIds), DeptDO::getId);
// 填充名称
list.forEach(item -> {
DeptDO company = deptMap.get(item.getCompanyId());
if (company != null) {
item.setCompanyName(company.getName());
}
if (item.getDeptId() != null) {
DeptDO dept = deptMap.get(item.getDeptId());
if (dept != null) {
item.setDeptName(dept.getName());
}
}
});
}
}

View File

@@ -0,0 +1,22 @@
package com.zt.plat.module.system.controller.admin.push.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Schema(description = "管理后台 - 业务类型 Response VO")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class BusinessTypeRespVO {
@Schema(description = "类型", example = "1")
private Integer type;
@Schema(description = "编码", example = "PURCHASE")
private String code;
@Schema(description = "名称", example = "采购")
private String name;
}

View File

@@ -0,0 +1,33 @@
package com.zt.plat.module.system.controller.admin.push.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
@Schema(description = "管理后台 - 外部系统推送配置基础信息")
@Data
public class ExternalPushConfigBaseVO {
@Schema(description = "公司编号(为空表示不限制公司)", example = "1024")
private Long companyId;
@Schema(description = "部门编号(为空表示公司级配置)", example = "2048")
private Long deptId;
@Schema(description = "业务类型(为空表示所有业务类型)", example = "PURCHASE")
@Size(max = 32, message = "业务类型长度不能超过 32 个字符")
private String businessType;
@Schema(description = "外部系统标识(为空表示所有外部系统)", example = "ERP")
@Size(max = 64, message = "外部系统标识长度不能超过 64 个字符")
private String externalSystem;
@Schema(description = "是否启用推送", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
@NotNull(message = "推送开关不能为空")
private Boolean enablePush;
@Schema(description = "备注", example = "ERP 采购单推送配置")
@Size(max = 512, message = "备注长度不能超过 512 个字符")
private String remark;
}

View File

@@ -0,0 +1,27 @@
package com.zt.plat.module.system.controller.admin.push.vo;
import com.zt.plat.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Schema(description = "管理后台 - 外部系统推送配置分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
public class ExternalPushConfigPageReqVO extends PageParam {
@Schema(description = "公司编号", example = "1024")
private Long companyId;
@Schema(description = "部门编号", example = "2048")
private Long deptId;
@Schema(description = "业务类型", example = "PURCHASE")
private String businessType;
@Schema(description = "外部系统标识", example = "ERP")
private String externalSystem;
@Schema(description = "是否启用推送", example = "true")
private Boolean enablePush;
}

View File

@@ -0,0 +1,28 @@
package com.zt.plat.module.system.controller.admin.push.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - 外部系统推送配置 Response VO")
@Data
@EqualsAndHashCode(callSuper = true)
public class ExternalPushConfigRespVO extends ExternalPushConfigBaseVO {
@Schema(description = "配置编号", example = "1024")
private Long id;
@Schema(description = "公司名称", example = "浙江中天建设集团")
private String companyName;
@Schema(description = "部门名称", example = "采购部")
private String deptName;
@Schema(description = "创建时间")
private LocalDateTime createTime;
@Schema(description = "最后更新时间")
private LocalDateTime updateTime;
}

View File

@@ -0,0 +1,14 @@
package com.zt.plat.module.system.controller.admin.push.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Schema(description = "管理后台 - 外部系统推送配置创建/修改 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
public class ExternalPushConfigSaveReqVO extends ExternalPushConfigBaseVO {
@Schema(description = "配置编号", example = "1024")
private Long id;
}

View File

@@ -10,7 +10,10 @@ import com.zt.plat.module.system.framework.operatelog.core.DeptParseFunction;
import com.zt.plat.module.system.framework.operatelog.core.PostParseFunction;
import com.zt.plat.module.system.framework.operatelog.core.SexParseFunction;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.*;
import jakarta.validation.constraints.AssertTrue;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
@@ -28,7 +31,7 @@ public class UserSaveReqVO {
@Schema(description = "用户账号", requiredMode = Schema.RequiredMode.REQUIRED, example = "zt")
@NotBlank(message = "用户账号不能为空")
@Pattern(regexp = "^[a-zA-Z0-9]+$", message = "用户账号由 数字、字母 组成")
// @Pattern(regexp = "^[a-zA-Z0-9]+$", message = "用户账号由 数字、字母 组成")
@Size(min = 1, max = 30, message = "用户账号长度为 1-30 个字符")
@DiffLogField(name = "用户账号")
private String username;

View File

@@ -1,48 +0,0 @@
package com.zt.plat.module.system.controller.app.portal;
import com.zt.plat.framework.common.pojo.CommonResult;
import com.zt.plat.framework.common.util.object.BeanUtils;
import com.zt.plat.module.system.controller.admin.portal.vo.PortalRespVO;
import com.zt.plat.module.system.dal.dataobject.portal.PortalDO;
import com.zt.plat.module.system.service.portal.PortalService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import static com.zt.plat.framework.common.pojo.CommonResult.success;
import static com.zt.plat.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
/**
* 用户端 - 门户网站 Controller
*
* @author 中铜数字供应链平台
*/
@Tag(name = "用户端 - 门户网站")
@RestController
@RequestMapping("/system/portal")
@Validated
public class AppPortalController {
@Resource
private PortalService portalService;
/**
* 获取当前用户可访问的门户列表
* 此接口无需权限验证,因为已经通过登录验证,
* 返回的门户列表已经根据用户权限进行了过滤
*/
@GetMapping("/list")
@Operation(summary = "获取我的门户列表")
public CommonResult<List<PortalRespVO>> getMyPortalList() {
Long userId = getLoginUserId();
List<PortalDO> portals = portalService.getPortalListByUserId(userId);
return success(BeanUtils.toBean(portals, PortalRespVO.class));
}
}

View File

@@ -0,0 +1,26 @@
package com.zt.plat.module.system.convert.permission;
import com.zt.plat.module.system.controller.admin.permission.vo.menudatarule.MenuDataRuleRespVO;
import com.zt.plat.module.system.controller.admin.permission.vo.menudatarule.MenuDataRuleSaveReqVO;
import com.zt.plat.module.system.dal.dataobject.permission.MenuDataRuleDO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import java.util.List;
/**
* 菜单数据规则 Convert
*
* @author ZT
*/
@Mapper
public interface MenuDataRuleConvert {
MenuDataRuleConvert INSTANCE = Mappers.getMapper(MenuDataRuleConvert.class);
MenuDataRuleDO convert(MenuDataRuleSaveReqVO bean);
MenuDataRuleRespVO convert(MenuDataRuleDO bean);
List<MenuDataRuleRespVO> convertList(List<MenuDataRuleDO> list);
}

View File

@@ -0,0 +1,72 @@
package com.zt.plat.module.system.dal.dataobject.iwork;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.zt.plat.framework.mybatis.core.dataobject.BaseDO;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.time.LocalDateTime;
/**
* iWork 用印流程回调日志。
*/
@TableName("system_iwork_seal_log")
@KeySequence("system_iwork_seal_log_seq")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class IWorkSealLogDO extends BaseDO {
@TableId(type = IdType.ASSIGN_ID)
private Long id;
/**
* iWork 返回的请求编号,唯一业务标识。
*/
private String requestId;
/**
* 业务单号ywxtdjbh
*/
private String businessCode;
/**
* 业务回调标识。
*/
private String bizCallbackKey;
/**
* 状态枚举,参考 IWorkCallbackStatusEnum。
*/
private Integer status;
/**
* 已执行的自动/手工重试次数。
*/
private Integer retryCount;
/**
* 最大重试次数(快照)。
*/
private Integer maxRetry;
/**
* 最后一次错误信息。
*/
private String lastErrorMessage;
/**
* 回调原始负载(截断)。
*/
private String rawCallback;
/**
* 最近一次回调时间。
*/
private LocalDateTime lastCallbackTime;
}

View File

@@ -0,0 +1,67 @@
package com.zt.plat.module.system.dal.dataobject.permission;
import com.zt.plat.framework.tenant.core.db.TenantBaseDO;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 菜单数据规则 DO
*
* @author ZT
*/
@TableName("system_menu_data_rule")
@KeySequence("system_menu_data_rule_seq")
@Data
@EqualsAndHashCode(callSuper = true)
public class MenuDataRuleDO extends TenantBaseDO {
/**
* 规则ID
*/
@TableId(type = IdType.ASSIGN_ID)
private Long id;
/**
* 菜单ID
*/
private Long menuId;
/**
* 规则名称
*/
private String ruleName;
/**
* 规则字段(数据库列名)
*/
private String ruleColumn;
/**
* 规则条件(=、>、<、IN、LIKE等
*/
private String ruleConditions;
/**
* 规则值(支持变量如#{userId}、#{deptId}
*/
private String ruleValue;
/**
* 状态0=禁用 1=启用)
*/
private Integer status;
/**
* 排序
*/
private Integer sort;
/**
* 备注
*/
private String remark;
}

View File

@@ -0,0 +1,42 @@
package com.zt.plat.module.system.dal.dataobject.permission;
import com.zt.plat.framework.tenant.core.db.TenantBaseDO;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 角色菜单数据规则关联 DO
*
* @author ZT
*/
@TableName("system_role_menu_data_rule")
@KeySequence("system_role_menu_data_rule_seq")
@Data
@EqualsAndHashCode(callSuper = true)
public class RoleMenuDataRuleDO extends TenantBaseDO {
/**
* 自增主键
*/
@TableId(type = IdType.ASSIGN_ID)
private Long id;
/**
* 角色ID
*/
private Long roleId;
/**
* 菜单ID
*/
private Long menuId;
/**
* 数据规则ID
*/
private Long dataRuleId;
}

View File

@@ -0,0 +1,76 @@
package com.zt.plat.module.system.dal.dataobject.push;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.zt.plat.framework.tenant.core.db.TenantBaseDO;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 外部系统推送配置 DO
*
* 用于配置不同公司/部门/业务类型下的外部系统推送开关
*
* @author ZT Cloud
*/
@TableName("system_external_push_config")
@KeySequence("system_external_push_config_seq")
@Data
@EqualsAndHashCode(callSuper = true)
public class ExternalPushConfigDO extends TenantBaseDO {
/**
* 主键编号
*/
@TableId(type = IdType.ASSIGN_ID)
private Long id;
/**
* 公司编号(可为空)
*
* 关联 system_dept 表is_company = 1
* 为空表示不限制公司
*/
private Long companyId;
/**
* 部门编号(可为空)
*
* 关联 system_dept 表is_company = 0
* 为空表示公司级配置
*/
private Long deptId;
/**
* 业务类型(可为空)
*
* 枚举值PURCHASE, SALE, PRODUCTION
* 为空表示所有业务类型
* 枚举 {@link com.zt.plat.module.system.enums.push.BusinessTypeEnum}
*/
private String businessType;
/**
* 外部系统标识(可为空)
*
* 如ERP, IWORK
* 为空表示所有外部系统
* 枚举 {@link com.zt.plat.module.system.enums.dept.ExternalPlatformEnum}
*/
private String externalSystem;
/**
* 是否启用推送
*
* true启用推送
* false停用推送
*/
private Boolean enablePush;
/**
* 备注
*/
private String remark;
}

View File

@@ -10,6 +10,8 @@ import com.zt.plat.framework.mybatis.core.query.LambdaQueryWrapperX;
import com.zt.plat.module.system.controller.admin.dept.vo.dept.DeptListReqVO;
import com.zt.plat.module.system.dal.dataobject.dept.DeptDO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.Collection;
import java.util.List;
@@ -167,4 +169,9 @@ public interface DeptMapper extends BaseMapperX<DeptDO> {
);
}
@Select("""
SELECT sd.* FROM SYSTEM_DEPT sd START WITH sd.id = #{deptId}
CONNECT BY PRIOR sd.parent_id = sd.id AND PRIOR sd.is_company <> 1 ;
""")
List<DeptDO> upFindCompanyNode(@Param("deptId") Long deptId);
}

View File

@@ -9,6 +9,7 @@ import org.apache.ibatis.annotations.Mapper;
import java.util.Collection;
import java.util.List;
import java.util.Collections;
@Mapper
public interface PostMapper extends BaseMapperX<PostDO> {
@@ -35,4 +36,12 @@ public interface PostMapper extends BaseMapperX<PostDO> {
return selectOne(PostDO::getCode, code);
}
default List<PostDO> selectByCodes(Collection<String> codes) {
if (codes == null || codes.isEmpty()) {
return Collections.emptyList();
}
return selectList(new LambdaQueryWrapperX<PostDO>()
.in(PostDO::getCode, codes));
}
}

View File

@@ -0,0 +1,27 @@
package com.zt.plat.module.system.dal.mysql.iwork;
import com.zt.plat.framework.common.pojo.PageResult;
import com.zt.plat.framework.mybatis.core.mapper.BaseMapperX;
import com.zt.plat.framework.mybatis.core.query.LambdaQueryWrapperX;
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkCallbackLogPageReqVO;
import com.zt.plat.module.system.dal.dataobject.iwork.IWorkSealLogDO;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface IWorkSealLogMapper extends BaseMapperX<IWorkSealLogDO> {
default IWorkSealLogDO selectByRequestId(String requestId) {
return selectOne(IWorkSealLogDO::getRequestId, requestId);
}
default PageResult<IWorkSealLogDO> selectPage(IWorkCallbackLogPageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<IWorkSealLogDO>()
.eqIfPresent(IWorkSealLogDO::getRequestId, reqVO.getRequestId())
.eqIfPresent(IWorkSealLogDO::getBusinessCode, reqVO.getBusinessCode())
.eqIfPresent(IWorkSealLogDO::getBizCallbackKey, reqVO.getBizCallbackKey())
.eqIfPresent(IWorkSealLogDO::getStatus, reqVO.getStatus())
.betweenIfPresent(IWorkSealLogDO::getCreateTime, reqVO.getCreateTime())
.betweenIfPresent(IWorkSealLogDO::getLastCallbackTime, reqVO.getLastCallbackTime())
.orderByDesc(IWorkSealLogDO::getId));
}
}

View File

@@ -0,0 +1,59 @@
package com.zt.plat.module.system.dal.mysql.permission;
import com.zt.plat.framework.mybatis.core.mapper.BaseMapperX;
import com.zt.plat.module.system.dal.dataobject.permission.MenuDataRuleDO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.Collection;
import java.util.List;
/**
* 菜单数据规则 Mapper
*
* @author ZT
*/
@Mapper
public interface MenuDataRuleMapper extends BaseMapperX<MenuDataRuleDO> {
/**
* 根据菜单ID查询规则列表
*
* @param menuId 菜单ID
* @return 规则列表
*/
default List<MenuDataRuleDO> selectListByMenuId(Long menuId) {
return selectList(MenuDataRuleDO::getMenuId, menuId);
}
/**
* 根据角色和菜单查询规则ID列表
*
* @param roleIds 角色ID集合
* @param menuId 菜单ID
* @return 规则ID列表
*/
@Select("<script>" +
"SELECT DISTINCT rmdr.data_rule_id " +
"FROM system_role_menu_data_rule rmdr " +
"WHERE rmdr.role_id IN " +
"<foreach collection='roleIds' item='roleId' open='(' separator=',' close=')'>" +
"#{roleId}" +
"</foreach>" +
"AND rmdr.menu_id = #{menuId} " +
"AND rmdr.deleted = 0" +
"</script>")
List<Long> selectRuleIdsByRoleAndMenu(@Param("roleIds") Collection<Long> roleIds,
@Param("menuId") Long menuId);
/**
* 批量查询菜单的规则
*
* @param menuIds 菜单ID集合
* @return 规则列表
*/
default List<MenuDataRuleDO> selectListByMenuIds(Collection<Long> menuIds) {
return selectList("menu_id", menuIds);
}
}

View File

@@ -33,4 +33,8 @@ public interface MenuMapper extends BaseMapperX<MenuDO> {
return selectOne(MenuDO::getComponentName, componentName);
}
default MenuDO selectByComponent(String component) {
return selectOne(MenuDO::getComponent, component);
}
}

View File

@@ -0,0 +1,43 @@
package com.zt.plat.module.system.dal.mysql.permission;
import com.zt.plat.framework.mybatis.core.mapper.BaseMapperX;
import com.zt.plat.module.system.dal.dataobject.permission.RoleMenuDataRuleDO;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.apache.ibatis.annotations.Mapper;
import java.util.Collection;
import java.util.List;
/**
* 角色菜单数据规则关联 Mapper
*
* @author ZT
*/
@Mapper
public interface RoleMenuDataRuleMapper extends BaseMapperX<RoleMenuDataRuleDO> {
/**
* 根据角色ID和菜单ID查询规则关联
*
* @param roleId 角色ID
* @param menuId 菜单ID
* @return 规则关联列表
*/
default List<RoleMenuDataRuleDO> selectListByRoleAndMenu(Long roleId, Long menuId) {
return selectList(new LambdaQueryWrapper<RoleMenuDataRuleDO>()
.eq(RoleMenuDataRuleDO::getRoleId, roleId)
.eq(RoleMenuDataRuleDO::getMenuId, menuId));
}
/**
* 根据角色ID和菜单ID删除规则关联
*
* @param roleId 角色ID
* @param menuId 菜单ID
*/
default void deleteByRoleAndMenu(Long roleId, Long menuId) {
delete(new LambdaQueryWrapper<RoleMenuDataRuleDO>()
.eq(RoleMenuDataRuleDO::getRoleId, roleId)
.eq(RoleMenuDataRuleDO::getMenuId, menuId));
}
}

View File

@@ -0,0 +1,92 @@
package com.zt.plat.module.system.dal.mysql.push;
import com.zt.plat.framework.common.pojo.PageResult;
import com.zt.plat.framework.mybatis.core.mapper.BaseMapperX;
import com.zt.plat.framework.mybatis.core.query.LambdaQueryWrapperX;
import com.zt.plat.module.system.controller.admin.push.vo.ExternalPushConfigPageReqVO;
import com.zt.plat.module.system.dal.dataobject.push.ExternalPushConfigDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
* 外部系统推送配置 Mapper
*
* @author ZT Cloud
*/
@Mapper
public interface ExternalPushConfigMapper extends BaseMapperX<ExternalPushConfigDO> {
default PageResult<ExternalPushConfigDO> selectPage(ExternalPushConfigPageReqVO reqVO) {
LambdaQueryWrapperX<ExternalPushConfigDO> wrapper = new LambdaQueryWrapperX<ExternalPushConfigDO>()
.eqIfPresent(ExternalPushConfigDO::getCompanyId, reqVO.getCompanyId())
.eqIfPresent(ExternalPushConfigDO::getBusinessType, reqVO.getBusinessType())
.eqIfPresent(ExternalPushConfigDO::getExternalSystem, reqVO.getExternalSystem())
.eqIfPresent(ExternalPushConfigDO::getEnablePush, reqVO.getEnablePush());
// 如果传了 companyId 但没传 deptId则只查公司级配置dept_id IS NULL
if (reqVO.getCompanyId() != null && reqVO.getDeptId() == null) {
wrapper.isNull(ExternalPushConfigDO::getDeptId);
} else if (reqVO.getDeptId() != null) {
// 如果传了 deptId则查指定部门的配置
wrapper.eq(ExternalPushConfigDO::getDeptId, reqVO.getDeptId());
}
// 如果都没传,则查所有配置
wrapper.orderByDesc(ExternalPushConfigDO::getId);
return selectPage(reqVO, wrapper);
}
/**
* 通用查询配置方法
*
* @param companyId 公司IDnull 表示查询 company_id IS NULL 的记录)
* @param deptId 部门IDnull 表示查询 dept_id IS NULL 的记录)
* @param businessType 业务类型null 表示查询 business_type IS NULL 的记录)
* @param externalSystem 外部系统null 表示查询 external_system IS NULL 的记录)
* @return 配置对象
*/
default ExternalPushConfigDO selectByConfig(Long companyId, Long deptId, String businessType, String externalSystem) {
LambdaQueryWrapperX<ExternalPushConfigDO> wrapper = new LambdaQueryWrapperX<>();
if (companyId == null) {
wrapper.isNull(ExternalPushConfigDO::getCompanyId);
} else {
wrapper.eq(ExternalPushConfigDO::getCompanyId, companyId);
}
if (deptId == null) {
wrapper.isNull(ExternalPushConfigDO::getDeptId);
} else {
wrapper.eq(ExternalPushConfigDO::getDeptId, deptId);
}
if (businessType == null) {
wrapper.isNull(ExternalPushConfigDO::getBusinessType);
} else {
wrapper.eq(ExternalPushConfigDO::getBusinessType, businessType);
}
if (externalSystem == null) {
wrapper.isNull(ExternalPushConfigDO::getExternalSystem);
} else {
wrapper.eq(ExternalPushConfigDO::getExternalSystem, externalSystem);
}
return selectOne(wrapper);
}
/**
* 查询公司下所有配置
*/
default List<ExternalPushConfigDO> selectListByCompanyId(Long companyId) {
return selectList(ExternalPushConfigDO::getCompanyId, companyId);
}
/**
* 查询部门下所有配置
*/
default List<ExternalPushConfigDO> selectListByDeptId(Long deptId) {
return selectList(ExternalPushConfigDO::getDeptId, deptId);
}
}

View File

@@ -42,6 +42,7 @@ public class IWorkProperties {
private final Client client = new Client();
private final OrgRest org = new OrgRest();
private final Workflow workflow = new Workflow();
private final Callback callback = new Callback();
private final Oa oa = new Oa();
@Data
@@ -127,6 +128,26 @@ public class IWorkProperties {
private String sealWorkflowId;
}
@Data
public static class Callback {
/**
* 业务回调重试配置。
*/
private final Retry retry = new Retry();
}
@Data
public static class Retry {
/**
* 最大重试次数,默认 3。
*/
private int maxAttempts = 3;
/**
* 重试延迟(秒),默认 5。
*/
private int delaySeconds = 5;
}
@Data
public static class Oa {
/**

View File

@@ -0,0 +1,40 @@
package com.zt.plat.module.system.job.sync;
import com.xxl.job.core.handler.annotation.XxlJob;
import com.zt.plat.framework.tenant.core.job.TenantJob;
import com.zt.plat.module.system.service.sync.SyncIWorkOrgChangeService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* 用于定时同步 iWork 当日变更的组织数据
* 同步时间每日23:00
*/
@Component
@Slf4j
public class SyncIWorkOrgChangeJob {
@Resource
private SyncIWorkOrgChangeService syncIWorkOrgChangeService;
/**
* 执行定时任务
* 配置执行频率每日23:00时执行一次
* cron表达式0 0 23 * * ?
*/
@XxlJob("syncIWorkOrgChangeJob")
@TenantJob
public void execute() {
log.info("[syncIWorkOrgChangeJob][开始执行同步 iWork 当日变更组织任务]");
try {
int processedCount = syncIWorkOrgChangeService.process();
if (processedCount > 0) {
log.info("[syncIWorkOrgChangeJob][同步任务执行完成,处理了 {} 条记录]", processedCount);
}
} catch (Exception e) {
log.error("[syncIWorkOrgChangeJob][同步任务执行失败]", e);
throw e;
}
}
}

View File

@@ -0,0 +1,39 @@
package com.zt.plat.module.system.job.sync;
import com.xxl.job.core.handler.annotation.XxlJob;
import com.zt.plat.framework.tenant.core.job.TenantJob;
import com.zt.plat.module.system.service.sync.SyncIWorkUserChangeService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* 用于定时同步iWork当日修改的用户数据
* 同步时间每日23:00
*/
@Component
@Slf4j
public class SyncIWorkUserChangeJob {
@Resource
private SyncIWorkUserChangeService syncIWorkUserChangeService;
/**
* 执行批量重跑任务
* 配置执行频率每日23:00时执行一次
* cron表达式0 0 23 * * ?
*/
@XxlJob("syncIWorkUserChangeJob")
@TenantJob
public void execute() {
log.info("[syncLogBatchRerunJob][开始执行同步iWork当日变更用户任务]");
try{
int processedCount = syncIWorkUserChangeService.process();
if (processedCount > 0) {
log.info("[syncLogBatchRerunJob][同步任务执行完成,处理了 {} 条记录]", processedCount);
}
} catch (Exception e) {
log.error("[syncLogBatchRerunJob][同步iWork当日变更用户任务执行异常]", e);
}
}
}

View File

@@ -0,0 +1,66 @@
package com.zt.plat.module.system.mq.iwork;
import com.zt.plat.module.system.framework.integration.iwork.config.IWorkProperties;
import com.zt.plat.module.system.service.integration.iwork.IWorkCallbackLogService;
import com.zt.plat.module.system.mq.iwork.IWorkBizCallbackMessage;
import com.zt.plat.module.system.mq.iwork.IWorkBizCallbackResultMessage;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* 监听业务模块回传的处理结果,并在失败时由 system 模块负责重试投递原始回调消息。
*/
@Slf4j
@Component
@RequiredArgsConstructor
@RocketMQMessageListener(topic = IWorkBizCallbackResultMessage.TOPIC, consumerGroup = IWorkBizCallbackResultMessage.TOPIC + "_CONSUMER")
public class IWorkBizCallbackListener implements RocketMQListener<IWorkBizCallbackResultMessage>, InitializingBean {
private final IWorkCallbackLogService logService;
private final IWorkProperties properties;
private final IWorkBizCallbackProducer producer;
private ScheduledExecutorService scheduler;
@Override
public void afterPropertiesSet() {
scheduler = Executors.newScheduledThreadPool(1, r -> new Thread(r, "iwork-callback-retry"));
}
@Override
public void onMessage(IWorkBizCallbackResultMessage message) {
String key = message.getBizCallbackKey();
if (message.isSuccess()) {
logService.markSuccess(message.getRequestId());
return;
}
int attempt = message.getAttempt() + 1;
logService.incrementRetry(message.getRequestId());
int maxAttempts = message.getMaxAttempts() > 0 ? message.getMaxAttempts() : properties.getCallback().getRetry().getMaxAttempts();
if (attempt < maxAttempts) {
logService.markFailure(message.getRequestId(), message.getErrorMessage(), true, maxAttempts);
IWorkBizCallbackMessage next = IWorkBizCallbackMessage.builder()
.requestId(message.getRequestId())
.bizCallbackKey(key)
.payload(message.getPayload())
.attempt(attempt)
.maxAttempts(maxAttempts)
.build();
int delay = properties.getCallback().getRetry().getDelaySeconds();
scheduler.schedule(() -> producer.send(next), delay, TimeUnit.SECONDS);
} else {
logService.markFailure(message.getRequestId(), message.getErrorMessage(), false, maxAttempts);
}
}
}

View File

@@ -0,0 +1,18 @@
package com.zt.plat.module.system.mq.iwork;
import lombok.RequiredArgsConstructor;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.stereotype.Component;
@Component
@RequiredArgsConstructor
public class IWorkBizCallbackProducer {
private final RocketMQTemplate rocketMQTemplate;
public void send(IWorkBizCallbackMessage message) {
// 使用 tag=bizCallbackKey方便业务侧按 key 订阅
String destination = IWorkBizCallbackMessage.TOPIC + ":" + message.getBizCallbackKey();
rocketMQTemplate.syncSend(destination, message);
}
}

View File

@@ -110,6 +110,9 @@ public class AdminAuthServiceImpl implements AdminAuthService {
final LoginLogTypeEnum logTypeEnum = LoginLogTypeEnum.LOGIN_USERNAME;
// 校验账号是否存在
AdminUserDO user = userService.getUserByUsername(username);
if (user == null) {
user = userService.getUserByWorkcode(username);
}
if (user == null) {
createLoginLog(null, username, logTypeEnum, LoginResultEnum.BAD_CREDENTIALS);
throw exception(AUTH_LOGIN_BAD_CREDENTIALS);

View File

@@ -185,4 +185,11 @@ public interface DeptService {
// ========== 数据同步专用接口 ==========
void syncDept(DeptSaveReqVO syncReqVO);
/**
* 向上查找公司节点信息
* @param deptId
* @return
*/
List<DeptDO> upFindCompanyNode(Long deptId);
}

View File

@@ -16,13 +16,12 @@ import com.zt.plat.module.system.dal.dataobject.dept.DeptDO;
import com.zt.plat.module.system.dal.dataobject.userdept.UserDeptDO;
import com.zt.plat.module.system.dal.mysql.dept.DeptMapper;
import com.zt.plat.module.system.dal.mysql.userdept.UserDeptMapper;
import com.zt.plat.module.system.service.dept.DeptExternalCodeService;
import com.zt.plat.module.system.dal.redis.RedisKeyConstants;
import com.zt.plat.module.system.enums.dept.DeptSourceEnum;
import com.zt.plat.module.system.service.permission.PermissionService;
import org.apache.seata.spring.annotation.GlobalTransactional;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.seata.spring.annotation.GlobalTransactional;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
@@ -762,6 +761,7 @@ public class DeptServiceImpl implements DeptService {
return deptIds;
}
@DataPermission(enable = false)
private Long resolveNearestCompanyId(Long deptId, Map<Long, DeptDO> deptCache) {
DeptDO current = loadDept(deptId, deptCache);
while (current != null) {
@@ -960,4 +960,9 @@ public class DeptServiceImpl implements DeptService {
// 注意:不发布变更事件,避免循环同步
}
@Override
public List<DeptDO> upFindCompanyNode(Long deptId) {
return deptMapper.upFindCompanyNode(deptId);
}
}

View File

@@ -0,0 +1,21 @@
package com.zt.plat.module.system.service.integration.iwork;
import com.zt.plat.framework.common.pojo.PageResult;
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkCallbackLogPageReqVO;
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkWorkflowCallbackReqVO;
import com.zt.plat.module.system.dal.dataobject.iwork.IWorkSealLogDO;
public interface IWorkCallbackLogService {
IWorkSealLogDO upsertOnCallback(IWorkWorkflowCallbackReqVO reqVO, int maxRetry, String rawBody);
void markSuccess(String requestId);
void markFailure(String requestId, String error, boolean retrying, int maxRetry);
void incrementRetry(String requestId);
PageResult<IWorkSealLogDO> page(IWorkCallbackLogPageReqVO reqVO);
void resetAndDispatch(String requestId);
}

View File

@@ -0,0 +1,16 @@
package com.zt.plat.module.system.service.integration.iwork.callback;
public interface IWorkBizCallbackHandler {
/**
* 返回 handler 能处理的 bizCallbackKey。
*/
String getBizCallbackKey();
/**
* 处理业务回调。
* @param requestId iWork requestId
* @param payload 回调负载
*/
void handle(String requestId, Object payload) throws Exception;
}

View File

@@ -0,0 +1,20 @@
package com.zt.plat.module.system.service.integration.iwork.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public enum IWorkCallbackStatusEnum {
CREATE_PENDING(0),
CREATE_SUCCESS(1),
CREATE_FAILED(2),
CALLBACK_PENDING(3),
CALLBACK_SUCCESS(4),
CALLBACK_FAILED(5),
CALLBACK_RETRYING(6),
CALLBACK_RETRY_FAILED(7);
private final int status;
}

View File

@@ -0,0 +1,130 @@
package com.zt.plat.module.system.service.integration.iwork.impl;
import com.zt.plat.framework.common.pojo.PageResult;
import com.zt.plat.framework.common.util.json.JsonUtils;
import com.zt.plat.framework.common.util.object.ObjectUtils;
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkCallbackLogPageReqVO;
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkWorkflowCallbackReqVO;
import com.zt.plat.module.system.dal.dataobject.iwork.IWorkSealLogDO;
import com.zt.plat.module.system.dal.mysql.iwork.IWorkSealLogMapper;
import com.zt.plat.module.system.framework.integration.iwork.config.IWorkProperties;
import com.zt.plat.module.system.mq.iwork.IWorkBizCallbackMessage;
import com.zt.plat.module.system.mq.iwork.IWorkBizCallbackProducer;
import com.zt.plat.module.system.service.integration.iwork.IWorkCallbackLogService;
import com.zt.plat.module.system.service.integration.iwork.enums.IWorkCallbackStatusEnum;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import java.time.LocalDateTime;
import java.util.Optional;
@Service
@RequiredArgsConstructor
public class IWorkCallbackLogServiceImpl implements IWorkCallbackLogService {
private static final int RAW_MAX = 2000;
private final IWorkSealLogMapper logMapper;
private final IWorkBizCallbackProducer producer;
private final IWorkProperties properties;
@Override
@Transactional(rollbackFor = Exception.class)
public IWorkSealLogDO upsertOnCallback(IWorkWorkflowCallbackReqVO reqVO, int maxRetry, String rawBody) {
IWorkSealLogDO existing = logMapper.selectByRequestId(reqVO.getRequestId());
IWorkSealLogDO log = Optional.ofNullable(existing).orElseGet(IWorkSealLogDO::new);
log.setRequestId(reqVO.getRequestId());
log.setBusinessCode(reqVO.getBusinessCode());
log.setBizCallbackKey(reqVO.getBizCallbackKey());
log.setStatus(IWorkCallbackStatusEnum.CALLBACK_PENDING.getStatus());
log.setRetryCount(ObjectUtils.defaultIfNull(log.getRetryCount(), 0));
log.setMaxRetry(maxRetry);
log.setRawCallback(truncate(rawBody));
log.setLastCallbackTime(LocalDateTime.now());
if (log.getId() == null) {
logMapper.insert(log);
} else {
logMapper.updateById(log);
}
return log;
}
@Override
public void markSuccess(String requestId) {
IWorkSealLogDO log = new IWorkSealLogDO();
log.setRequestId(requestId);
log.setStatus(IWorkCallbackStatusEnum.CALLBACK_SUCCESS.getStatus());
log.setLastErrorMessage(null);
log.setLastCallbackTime(LocalDateTime.now());
logMapper.update(log, new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<IWorkSealLogDO>()
.eq(IWorkSealLogDO::getRequestId, requestId));
}
@Override
public void markFailure(String requestId, String error, boolean retrying, int maxRetry) {
IWorkSealLogDO log = new IWorkSealLogDO();
log.setRequestId(requestId);
log.setStatus(retrying ? IWorkCallbackStatusEnum.CALLBACK_RETRYING.getStatus() : IWorkCallbackStatusEnum.CALLBACK_FAILED.getStatus());
log.setLastErrorMessage(error);
log.setLastCallbackTime(LocalDateTime.now());
log.setMaxRetry(maxRetry);
logMapper.update(log, new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<IWorkSealLogDO>()
.eq(IWorkSealLogDO::getRequestId, requestId));
}
@Override
public void incrementRetry(String requestId) {
IWorkSealLogDO db = logMapper.selectByRequestId(requestId);
if (db == null) {
return;
}
IWorkSealLogDO log = new IWorkSealLogDO();
log.setId(db.getId());
log.setRetryCount(ObjectUtils.defaultIfNull(db.getRetryCount(), 0) + 1);
log.setLastCallbackTime(LocalDateTime.now());
logMapper.updateById(log);
}
@Override
public PageResult<IWorkSealLogDO> page(IWorkCallbackLogPageReqVO reqVO) {
return logMapper.selectPage(reqVO);
}
@Override
public void resetAndDispatch(String requestId) {
IWorkSealLogDO db = logMapper.selectByRequestId(requestId);
if (db == null) {
return;
}
IWorkSealLogDO log = new IWorkSealLogDO();
log.setId(db.getId());
log.setRetryCount(0);
log.setStatus(IWorkCallbackStatusEnum.CALLBACK_RETRYING.getStatus());
log.setLastCallbackTime(LocalDateTime.now());
logMapper.updateById(log);
int maxAttempts = properties.getCallback().getRetry().getMaxAttempts();
Object payload;
try {
payload = JsonUtils.parseObject(db.getRawCallback(), Object.class);
} catch (Exception ex) {
payload = db.getRawCallback();
}
producer.send(IWorkBizCallbackMessage.builder()
.requestId(db.getRequestId())
.bizCallbackKey(db.getBizCallbackKey())
.payload(payload)
.attempt(0)
.maxAttempts(maxAttempts)
.build());
}
private String truncate(String raw) {
if (!StringUtils.hasText(raw)) {
return raw;
}
return raw.length() > RAW_MAX ? raw.substring(0, RAW_MAX) : raw;
}
}

View File

@@ -182,7 +182,7 @@ public class IWorkIntegrationServiceImpl implements IWorkIntegrationService {
}
AtomicReference<Long> attachmentIdRef = new AtomicReference<>();
TenantUtils.execute(tenantId, () -> attachmentIdRef.set(saveCallbackAttachment(fileUrl, reqVO.getFileName(), referenceBusinessFile, reqVO.getSsoToken())));
TenantUtils.execute(tenantId, () -> attachmentIdRef.set(saveCallbackAttachment(fileUrl, reqVO.getFileName(), referenceBusinessFile)));
return attachmentIdRef.get();
}
@@ -251,14 +251,14 @@ public class IWorkIntegrationServiceImpl implements IWorkIntegrationService {
return executeOaRequest(request);
}
private Long saveCallbackAttachment(String fileUrl, String overrideFileName, BusinessFileRespDTO referenceBusinessFile, String ssoToken) {
private Long saveCallbackAttachment(String fileUrl, String overrideFileName, BusinessFileRespDTO referenceBusinessFile) {
Long businessId = referenceBusinessFile.getBusinessId();
FileCreateReqDTO fileCreateReqDTO = new FileCreateReqDTO();
fileCreateReqDTO.setName(resolveFileName(overrideFileName, fileUrl));
fileCreateReqDTO.setDirectory(null);
fileCreateReqDTO.setType(null);
fileCreateReqDTO.setContent(downloadFileBytes(fileUrl, ssoToken));
fileCreateReqDTO.setContent(downloadFileBytes(fileUrl));
CommonResult<FileRespDTO> fileResult = fileApi.createFileWithReturn(fileCreateReqDTO);
if (fileResult == null || !fileResult.isSuccess() || fileResult.getData() == null) {
@@ -297,10 +297,8 @@ public class IWorkIntegrationServiceImpl implements IWorkIntegrationService {
return businessFile;
}
private byte[] downloadFileBytes(String fileUrl, String ssoToken) {
// 如果回调已提供 ssoToken按需拼接后下载 OA 附件
String finalUrl = appendSsoTokenIfNeeded(fileUrl, ssoToken);
private byte[] downloadFileBytes(String fileUrl) {
String finalUrl = fileUrl;
OkHttpClient client = okHttpClient();
Request request = new Request.Builder().url(finalUrl).get().build();
try (Response response = client.newCall(request).execute()) {
@@ -317,22 +315,6 @@ public class IWorkIntegrationServiceImpl implements IWorkIntegrationService {
}
}
private String appendSsoTokenIfNeeded(String fileUrl, String ssoToken) {
// 未提供 token 或 URL 为空,直接返回原链接
if (!StringUtils.hasText(ssoToken) || !StringUtils.hasText(fileUrl)) {
return fileUrl;
}
// 已包含 ssoToken不区分大小写则不重复添加
String lower = fileUrl.toLowerCase();
if (lower.contains("ssotoken=")) {
return fileUrl;
}
// 简单拼接查询参数
return fileUrl.contains("?")
? fileUrl + "&ssoToken=" + ssoToken
: fileUrl + "?ssoToken=" + ssoToken;
}
private String resolveFileName(String overrideName, String fileUrl) {
if (StringUtils.hasText(overrideName)) {
return overrideName;

View File

@@ -92,6 +92,18 @@ public class IWorkOrgRestServiceImpl implements IWorkOrgRestService {
if (StringUtils.hasText(reqVO.getSubcompanyName())) {
params.put("subcompanyname", reqVO.getSubcompanyName());
}
if (StringUtils.hasText(reqVO.getModified())) {
params.put("modified", reqVO.getModified());
}
if (StringUtils.hasText(reqVO.getCanceled())) {
params.put("canceled", reqVO.getCanceled());
}
if (StringUtils.hasText(reqVO.getCustomData())) {
params.put("custom_data", reqVO.getCustomData());
}
if (StringUtils.hasText(reqVO.getId())) {
params.put("id", reqVO.getId());
}
JsonNode node = invokeParamsEndpoint(path, params);
return buildSubcompanyPageResp(node);
}
@@ -106,8 +118,23 @@ public class IWorkOrgRestServiceImpl implements IWorkOrgRestService {
if (StringUtils.hasText(reqVO.getDepartmentName())) {
params.put("departmentname", reqVO.getDepartmentName());
}
if (StringUtils.hasText(reqVO.getSubcompanyId())) {
params.put("subcompanyid", reqVO.getSubcompanyId());
if (StringUtils.hasText(reqVO.getSubcompanyId1())) {
params.put("subcompanyid1", reqVO.getSubcompanyId1());
}
if (StringUtils.hasText(reqVO.getCreated())) {
params.put("created", reqVO.getCreated());
}
if (StringUtils.hasText(reqVO.getModified())) {
params.put("modified", reqVO.getModified());
}
if (StringUtils.hasText(reqVO.getCanceled())) {
params.put("canceled", reqVO.getCanceled());
}
if (StringUtils.hasText(reqVO.getCustomData())) {
params.put("custom_data", reqVO.getCustomData());
}
if (StringUtils.hasText(reqVO.getId())) {
params.put("id", reqVO.getId());
}
JsonNode node = invokeParamsEndpoint(path, params);
return buildDepartmentPageResp(node);
@@ -117,12 +144,18 @@ public class IWorkOrgRestServiceImpl implements IWorkOrgRestService {
public IWorkHrJobTitlePageRespVO listJobTitles(IWorkJobTitleQueryReqVO reqVO) {
String path = orgPaths().getJobTitlePage();
Map<String, Object> params = buildBaseParams(reqVO);
if (StringUtils.hasText(reqVO.getJobTitleCode())) {
params.put("jobtitlecode", reqVO.getJobTitleCode());
}
if (StringUtils.hasText(reqVO.getJobTitleName())) {
params.put("jobtitlename", reqVO.getJobTitleName());
}
if (StringUtils.hasText(reqVO.getCreated())) {
params.put("created", reqVO.getCreated());
}
if (StringUtils.hasText(reqVO.getModified())) {
params.put("modified", reqVO.getModified());
}
if (StringUtils.hasText(reqVO.getId())) {
params.put("id", reqVO.getId());
}
JsonNode node = invokeParamsEndpoint(path, params);
return buildJobTitlePageResp(node);
}
@@ -134,11 +167,8 @@ public class IWorkOrgRestServiceImpl implements IWorkOrgRestService {
if (StringUtils.hasText(reqVO.getWorkCode())) {
params.put("workcode", reqVO.getWorkCode());
}
if (StringUtils.hasText(reqVO.getLastName())) {
params.put("lastname", reqVO.getLastName());
}
if (StringUtils.hasText(reqVO.getSubcompanyId())) {
params.put("subcompanyid", reqVO.getSubcompanyId());
if (StringUtils.hasText(reqVO.getSubcompanyId1())) {
params.put("subcompanyid1", reqVO.getSubcompanyId1());
}
if (StringUtils.hasText(reqVO.getDepartmentId())) {
params.put("departmentid", reqVO.getDepartmentId());
@@ -146,14 +176,26 @@ public class IWorkOrgRestServiceImpl implements IWorkOrgRestService {
if (StringUtils.hasText(reqVO.getJobTitleId())) {
params.put("jobtitleid", reqVO.getJobTitleId());
}
if (StringUtils.hasText(reqVO.getStatus())) {
params.put("status", reqVO.getStatus());
if (StringUtils.hasText(reqVO.getId())) {
params.put("id", reqVO.getId());
}
if (StringUtils.hasText(reqVO.getMobile())) {
params.put("mobile", reqVO.getMobile());
if (StringUtils.hasText(reqVO.getLoginId())) {
params.put("loginid", reqVO.getLoginId());
}
if (StringUtils.hasText(reqVO.getEmail())) {
params.put("email", reqVO.getEmail());
if (StringUtils.hasText(reqVO.getCreated())) {
params.put("created", reqVO.getCreated());
}
if (StringUtils.hasText(reqVO.getModified())) {
params.put("modified", reqVO.getModified());
}
if (StringUtils.hasText(reqVO.getBaseCustomData())) {
params.put("base_custom_data", reqVO.getBaseCustomData());
}
if (StringUtils.hasText(reqVO.getPersonCustomData())) {
params.put("person_custom_data", reqVO.getPersonCustomData());
}
if (StringUtils.hasText(reqVO.getWorkCustomData())) {
params.put("work_custom_data", reqVO.getWorkCustomData());
}
JsonNode node = invokeParamsEndpoint(path, params);
return buildUserPageResp(node);
@@ -161,9 +203,6 @@ public class IWorkOrgRestServiceImpl implements IWorkOrgRestService {
private Map<String, Object> buildBaseParams(IWorkOrgBaseQueryReqVO reqVO) {
Map<String, Object> params = new HashMap<>();
if (reqVO.getParams() != null) {
params.putAll(reqVO.getParams());
}
if (reqVO.getCurpage() != null) {
params.put("curpage", reqVO.getCurpage());
}

View File

@@ -13,8 +13,11 @@ import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkHrUs
import com.zt.plat.module.system.controller.admin.user.vo.user.UserSaveReqVO;
import com.zt.plat.module.system.dal.dataobject.dept.DeptDO;
import com.zt.plat.module.system.dal.dataobject.dept.PostDO;
import com.zt.plat.module.system.dal.dataobject.dept.UserPostDO;
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.mysql.dept.PostMapper;
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.enums.common.SexEnum;
import com.zt.plat.module.system.enums.dept.DeptSourceEnum;
@@ -24,6 +27,7 @@ import com.zt.plat.module.system.service.dept.DeptService;
import com.zt.plat.module.system.service.dept.PostService;
import com.zt.plat.module.system.service.integration.iwork.IWorkSyncProcessor;
import com.zt.plat.module.system.service.user.AdminUserService;
import com.zt.plat.module.system.service.userdept.UserDeptService;
import com.zt.plat.module.system.util.sync.SyncVerifyUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -33,6 +37,7 @@ import org.springframework.util.DigestUtils;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
@Slf4j
@Service
@@ -47,8 +52,10 @@ public class IWorkSyncProcessorImpl implements IWorkSyncProcessor {
private final DeptService deptService;
private final PostService postService;
private final PostMapper postMapper;
private final UserPostMapper userPostMapper;
private final AdminUserService adminUserService;
private final AdminUserMapper adminUserMapper;
private final UserDeptService userDeptService;
private final Map<String, PostDO> postCache = new ConcurrentHashMap<>();
@@ -322,7 +329,41 @@ public class IWorkSyncProcessorImpl implements IWorkSyncProcessor {
if (records.isEmpty()) {
return result;
}
long batchStart = System.currentTimeMillis();
result.increasePulled(records.size());
// 预取已有用户,避免逐条查询
long preloadUsersStart = System.currentTimeMillis();
Map<Long, AdminUserDO> existingUsers = new HashMap<>();
List<Long> userIds = records.stream()
.map(IWorkHrUserPageRespVO.User::getId)
.filter(Objects::nonNull)
.map(Integer::longValue)
.distinct()
.toList();
if (!userIds.isEmpty()) {
List<AdminUserDO> users = adminUserMapper.selectBatchIds(userIds);
if (CollUtil.isNotEmpty(users)) {
users.forEach(user -> existingUsers.put(user.getId(), user));
}
}
long preloadUsersMs = System.currentTimeMillis() - preloadUsersStart;
// 预取岗位,避免逐条按编码查询
long preloadPostsStart = System.currentTimeMillis();
List<String> postCodes = records.stream()
.map(IWorkHrUserPageRespVO.User::getJobtitleid)
.filter(Objects::nonNull)
.map(this::buildJobCode)
.distinct()
.toList();
if (!postCodes.isEmpty()) {
List<PostDO> posts = postMapper.selectByCodes(postCodes);
if (CollUtil.isNotEmpty(posts)) {
posts.forEach(post -> postCache.put(buildPostCacheKey(post.getCode()), post));
}
}
long preloadPostsMs = System.currentTimeMillis() - preloadPostsStart;
for (IWorkHrUserPageRespVO.User user : records) {
if (user == null) {
continue;
@@ -344,7 +385,7 @@ public class IWorkSyncProcessorImpl implements IWorkSyncProcessor {
CommonStatusEnum status = inactive ? CommonStatusEnum.DISABLE : CommonStatusEnum.ENABLE;
// 直接沿用 iWork 原始密码,避免重复格式化造成校验偏差
String externalPassword = trimToNull(user.getPassword());
AdminUserDO existing = adminUserMapper.selectById(user.getId());
AdminUserDO existing = user.getId() == null ? null : existingUsers.get(user.getId().longValue());
UserSyncOutcome outcome;
if (existing == null) {
if (!options.isCreateIfMissing()) {
@@ -377,6 +418,9 @@ public class IWorkSyncProcessorImpl implements IWorkSyncProcessor {
result.withMessage("同步人员失败: " + ex.getMessage());
}
}
long totalMs = System.currentTimeMillis() - batchStart;
log.info("[iWork] 人员批次同步完成 size={} preloadUsersMs={} preloadPostsMs={} totalMs={}",
records.size(), preloadUsersMs, preloadPostsMs, totalMs);
return result;
}
//TODO
@@ -494,12 +538,49 @@ public class IWorkSyncProcessorImpl implements IWorkSyncProcessor {
String externalPassword) {
UserSaveReqVO req = buildUserSaveReq(source, username, deptId, postId, status);
req.setId(existing.getId());
Long iworkDeptId = resolveIWorkDeptId(deptId);
req.setDeptIds(null);
req.setPostIds(null);
boolean disabledChanged = CommonStatusEnum.isDisable(status.getStatus()) && CommonStatusEnum.isEnable(existing.getStatus());
adminUserService.updateUser(req);
syncPassword(existing, externalPassword);
boolean infoChanged = isUserInfoChanged(existing, req);
boolean passwordChanged = isPasswordChanged(existing, externalPassword);
boolean deptChanged = syncIWorkUserDeptRelations(existing.getId(), iworkDeptId);
boolean postChanged = syncIWorkUserPostRelations(existing.getId(), postId);
if (!infoChanged && !passwordChanged && !deptChanged && !postChanged) {
return new UserSyncOutcome(SyncAction.SKIPPED, false, existing.getId());
}
if (infoChanged) {
adminUserService.updateUser(req);
}
if (passwordChanged) {
syncPassword(existing, externalPassword);
}
return new UserSyncOutcome(SyncAction.UPDATED, disabledChanged, existing.getId());
}
private boolean isUserInfoChanged(AdminUserDO existing, UserSaveReqVO req) {
if (existing == null || req == null) {
return false;
}
return !Objects.equals(existing.getUsername(), req.getUsername())
|| !Objects.equals(existing.getWorkcode(), req.getWorkcode())
|| !Objects.equals(existing.getNickname(), req.getNickname())
|| !Objects.equals(existing.getRemark(), req.getRemark())
|| !Objects.equals(existing.getEmail(), req.getEmail())
|| !Objects.equals(existing.getMobile(), req.getMobile())
|| !Objects.equals(existing.getSex(), req.getSex())
|| !Objects.equals(existing.getStatus(), req.getStatus());
}
private boolean isPasswordChanged(AdminUserDO existing, String externalPassword) {
if (existing == null || StrUtil.isBlank(externalPassword)) {
return false;
}
return !StrUtil.equals(externalPassword, existing.getPassword());
}
private DeptSaveReqVO buildSubcompanySaveReq(IWorkHrSubcompanyPageRespVO.Subcompany data,
Long deptId,
Long parentId,
@@ -550,8 +631,9 @@ public class IWorkSyncProcessorImpl implements IWorkSyncProcessor {
req.setWorkcode(resolveWorkcode(source));
req.setNickname(limitLength(StrUtil.blankToDefault(source.getLastname(), username), 30));
req.setRemark(buildUserRemark(source));
if (deptId != null) {
req.setDeptIds(singletonSet(deptId));
Long iworkDeptId = resolveIWorkDeptId(deptId);
if (iworkDeptId != null) {
req.setDeptIds(singletonSet(iworkDeptId));
}
if (postId != null) {
req.setPostIds(singletonSet(postId));
@@ -566,6 +648,74 @@ public class IWorkSyncProcessorImpl implements IWorkSyncProcessor {
return req;
}
private boolean syncIWorkUserDeptRelations(Long userId, Long iworkDeptId) {
if (userId == null) {
return false;
}
List<UserDeptDO> relations = userDeptService.getValidUserDeptListByUserIds(Collections.singleton(userId));
Set<Long> existingDeptIds = relations.stream()
.map(UserDeptDO::getDeptId)
.filter(Objects::nonNull)
.collect(Collectors.toCollection(LinkedHashSet::new));
Set<Long> existingIworkDeptIds = new LinkedHashSet<>();
if (CollUtil.isNotEmpty(existingDeptIds)) {
Map<Long, DeptDO> deptMap = deptService.getDeptMap(existingDeptIds);
existingIworkDeptIds = deptMap.values().stream()
.filter(this::isIWorkDept)
.map(DeptDO::getId)
.filter(Objects::nonNull)
.collect(Collectors.toCollection(LinkedHashSet::new));
}
Set<Long> desiredIworkDeptIds = iworkDeptId == null
? Collections.emptySet()
: Collections.singleton(iworkDeptId);
if (existingIworkDeptIds.equals(desiredIworkDeptIds)) {
return false;
}
Collection<Long> toDelete = CollUtil.subtract(existingIworkDeptIds, desiredIworkDeptIds);
Collection<Long> toAdd = CollUtil.subtract(desiredIworkDeptIds, existingIworkDeptIds);
if (CollUtil.isNotEmpty(toDelete)) {
userDeptService.deleteUserDeptByUserIdAndDeptIds(userId, toDelete);
}
if (CollUtil.isNotEmpty(toAdd)) {
userDeptService.batchCreateUserDept(Collections.singletonList(
new UserDeptDO().setUserId(userId).setDeptId(iworkDeptId)));
}
return true;
}
private boolean syncIWorkUserPostRelations(Long userId, Long postId) {
if (userId == null || postId == null) {
return false;
}
List<UserPostDO> relations = userPostMapper.selectListByUserId(userId);
Set<Long> existingPostIds = relations.stream()
.map(UserPostDO::getPostId)
.filter(Objects::nonNull)
.collect(Collectors.toCollection(LinkedHashSet::new));
if (existingPostIds.contains(postId)) {
return false;
}
userPostMapper.insertBatch(Collections.singletonList(
new UserPostDO().setUserId(userId).setPostId(postId)));
return true;
}
private Long resolveIWorkDeptId(Long deptId) {
if (deptId == null) {
return null;
}
DeptDO dept = deptService.getDept(deptId);
return isIWorkDept(dept) ? deptId : null;
}
private boolean isIWorkDept(DeptDO dept) {
if (dept == null) {
return false;
}
return Objects.equals(dept.getDeptSource(), DeptSourceEnum.IWORK.getSource());
}
private void mergeDeptDefaults(DeptSaveReqVO target, DeptDO existing) {
target.setCode(StrUtil.blankToDefault(target.getCode(), existing.getCode()));
target.setShortName(StrUtil.blankToDefault(target.getShortName(), existing.getShortName()));
@@ -729,7 +879,12 @@ public class IWorkSyncProcessorImpl implements IWorkSyncProcessor {
if (StrUtil.isBlank(statusFlag)) {
return false;
}
return !"0".equals(statusFlag.trim());
Integer status = parseInteger(statusFlag);
if (status == null) {
return false;
}
// iWork 状态0试用、1正式、2临时、3试用延期、4解聘、5离职、6退休、7无效
return status >= 4;
}
private Integer resolveSex(String sexFlag) {

View File

@@ -11,14 +11,13 @@ import com.zt.plat.module.system.service.integration.iwork.IWorkSyncProcessor;
import com.zt.plat.module.system.service.integration.iwork.IWorkSyncService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static com.zt.plat.module.system.service.integration.iwork.IWorkIntegrationErrorCodeConstants.IWORK_ORG_REMOTE_FAILED;
@@ -217,11 +216,14 @@ public class IWorkSyncServiceImpl implements IWorkSyncService {
int pagesLimit = reqVO.getMaxPages() == null ? Integer.MAX_VALUE : reqVO.getMaxPages();
int processedPages = 0;
for (int page = startPage; processedPages < pagesLimit; page++) {
long pageStart = System.currentTimeMillis();
BatchExecution execution = executor.execute(page, pageSize);
long pageMs = System.currentTimeMillis() - pageStart;
if (execution == null || execution.totalPulled == 0) {
break;
}
processedPages++;
log.info("[iWork] 全量同步 {} 页={} pulled={} costMs={}", type, page, execution.totalPulled, pageMs);
IWorkSyncBatchStatVO batchStat = new IWorkSyncBatchStatVO();
batchStat.setEntityType(type);
batchStat.setPageNumber(page);
@@ -258,15 +260,18 @@ public class IWorkSyncServiceImpl implements IWorkSyncService {
if (query == null || reqVO == null) {
return;
}
copyQueryParameters(reqVO, query); // 设置查询条件
if (StrUtil.isBlank(reqVO.getId())) {
return;
}
Map<String, Object> params = query.getParams();
if (params == null) {
params = new HashMap<>();
query.setParams(params);
applyQueryId(query, reqVO.getId());
}
private void copyQueryParameters(IWorkFullSyncReqVO reqVO, IWorkOrgBaseQueryReqVO query) {
BeanUtils.copyProperties(reqVO, query);
if (query instanceof IWorkUserQueryReqVO userQuery) {
userQuery.setDepartmentId(reqVO.getDepartmentCode()); // 设置部门编号
}
params.put("id", reqVO.getId());
}
/**
@@ -385,12 +390,28 @@ public class IWorkSyncServiceImpl implements IWorkSyncService {
if (StrUtil.isBlank(reqVO.getId())) {
return;
}
Map<String, Object> params = query.getParams();
if (params == null) {
params = new HashMap<>();
query.setParams(params);
applyQueryId(query, reqVO.getId());
}
private void applyQueryId(IWorkOrgBaseQueryReqVO query, String id) {
if (query == null || StrUtil.isBlank(id)) {
return;
}
if (query instanceof IWorkSubcompanyQueryReqVO subcompanyQuery) {
subcompanyQuery.setId(id);
return;
}
if (query instanceof IWorkDepartmentQueryReqVO departmentQuery) {
departmentQuery.setId(id);
return;
}
if (query instanceof IWorkJobTitleQueryReqVO jobTitleQuery) {
jobTitleQuery.setId(id);
return;
}
if (query instanceof IWorkUserQueryReqVO userQuery) {
userQuery.setId(id);
}
params.put("id", reqVO.getId());
}
@@ -403,11 +424,14 @@ public class IWorkSyncServiceImpl implements IWorkSyncService {
int pagesLimit = reqVO.getMaxPages() == null ? Integer.MAX_VALUE : reqVO.getMaxPages();
int processedPages = 0;
for (int page = startPage; processedPages < pagesLimit; page++) {
long pageStart = System.currentTimeMillis();
BatchExecution execution = executor.execute(page, pageSize);
long pageMs = System.currentTimeMillis() - pageStart;
if (execution == null || execution.totalPulled == 0) {
break;
}
processedPages++;
log.info("[iWork] 手动同步 {} 页={} pulled={} costMs={}", type, page, execution.totalPulled, pageMs);
IWorkSyncBatchStatVO batchStat = new IWorkSyncBatchStatVO();
batchStat.setEntityType(type);
batchStat.setPageNumber(page);

View File

@@ -203,6 +203,7 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService {
.put(LoginUser.INFO_KEY_TENANT_ID, user.getTenantId().toString())
.put(LoginUser.INFO_KEY_USERNAME, user.getUsername())
.put(LoginUser.INFO_KEY_PHONE, user.getMobile())
.put(LoginUser.INFO_KEY_WORK_CODE, user.getWorkcode())
.put(LoginUser.INFO_KEY_POST_IDS, CollUtil.isEmpty(user.getPostIds()) ? "[]" : JsonUtils.toJsonString(user.getPostIds()))
.build();
} else if (userType.equals(UserTypeEnum.MEMBER.getValue())) {

View File

@@ -0,0 +1,72 @@
package com.zt.plat.module.system.service.permission;
import com.zt.plat.module.system.controller.admin.permission.vo.menudatarule.MenuDataRuleSaveReqVO;
import com.zt.plat.module.system.dal.dataobject.permission.MenuDataRuleDO;
import jakarta.validation.Valid;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
* 菜单数据规则 Service 接口
*
* @author ZT
*/
public interface MenuDataRuleService {
/**
* 创建菜单数据规则
*
* @param createReqVO 创建信息
* @return 规则ID
*/
Long createMenuDataRule(@Valid MenuDataRuleSaveReqVO createReqVO);
/**
* 更新菜单数据规则
*
* @param updateReqVO 更新信息
*/
void updateMenuDataRule(@Valid MenuDataRuleSaveReqVO updateReqVO);
/**
* 删除菜单数据规则
*
* @param id 规则ID
*/
void deleteMenuDataRule(Long id);
/**
* 获取菜单数据规则
*
* @param id 规则ID
* @return 规则信息
*/
MenuDataRuleDO getMenuDataRule(Long id);
/**
* 获取菜单的所有数据规则
*
* @param menuId 菜单ID
* @return 规则列表
*/
List<MenuDataRuleDO> getMenuDataRuleListByMenuId(Long menuId);
/**
* 获取用户在指定菜单下的有效数据规则
*
* @param userId 用户ID
* @param menuId 菜单ID
* @return 规则列表
*/
List<MenuDataRuleDO> getUserMenuDataRules(Long userId, Long menuId);
/**
* 批量获取菜单的数据规则(带缓存)
*
* @param menuIds 菜单ID列表
* @return 菜单ID -> 规则列表的映射
*/
Map<Long, List<MenuDataRuleDO>> getMenuDataRuleMap(Collection<Long> menuIds);
}

View File

@@ -0,0 +1,56 @@
package com.zt.plat.module.system.service.permission;
import cn.hutool.core.util.StrUtil;
import com.zt.plat.module.system.dal.dataobject.permission.MenuDO;
import com.zt.plat.module.system.dal.mysql.permission.MenuMapper;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* 页面组件映射服务
* 根据pageComponent查询对应的菜单ID
*
* @author ZT
*/
@Service
@Slf4j
public class PageComponentMappingService {
@Resource
private MenuMapper menuMapper;
/**
* 根据页面组件路径获取菜单ID
*
* @param pageComponent 页面组件路径system/role/index
* @return 菜单ID如果未找到返回null
*/
public Long getMenuIdByPageComponent(String pageComponent) {
if (StrUtil.isBlank(pageComponent)) {
return null;
}
log.debug("[getMenuIdByPageComponent][查询pageComponent: {}]", pageComponent);
// 使用精确匹配查询菜单
MenuDO menu = menuMapper.selectByComponent(pageComponent);
if (menu != null) {
log.debug("[getMenuIdByPageComponent][找到匹配菜单: ID={}, Name={}, Component={}]",
menu.getId(), menu.getName(), menu.getComponent());
// 兼容达梦数据库ID可能是Integer类型需要转换为Long
Object id = menu.getId();
if (id instanceof Number) {
return ((Number) id).longValue();
}
return (Long) id;
}
log.warn("[getMenuIdByPageComponent][未找到匹配的菜单: {}]", pageComponent);
return null;
}
}

View File

@@ -86,6 +86,23 @@ public interface PermissionService {
*/
Set<Long> getMenuRoleIdListByMenuIdFromCache(Long menuId);
/**
* 批量设置角色-菜单-规则关联
*
* @param roleId 角色编号
* @param menuDataRules 菜单和规则的映射关系
*/
void assignRoleMenuDataRules(Long roleId, Collection<PermissionAssignRoleMenuItemReqVO> menuDataRules);
/**
* 获取角色在指定菜单下已选择的数据规则ID列表
*
* @param roleId 角色编号
* @param menuId 菜单编号
* @return 数据规则ID列表
*/
Set<Long> getRoleMenuDataRules(Long roleId, Long menuId);
// ========== 用户-角色的相关方法 ==========
/**

View File

@@ -76,6 +76,8 @@ public class PermissionServiceImpl implements PermissionService {
private RoleMenuMapper roleMenuMapper;
@Resource
private UserRoleMapper userRoleMapper;
@Resource
private com.zt.plat.module.system.dal.mysql.permission.RoleMenuDataRuleMapper roleMenuDataRuleMapper;
private RoleService roleService;
@Resource
@@ -221,6 +223,45 @@ public class PermissionServiceImpl implements PermissionService {
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public void assignRoleMenuDataRules(Long roleId, Collection<PermissionAssignRoleMenuItemReqVO> menuDataRules) {
if (CollUtil.isEmpty(menuDataRules)) {
return;
}
// 遍历每个菜单,更新其数据规则关联
for (PermissionAssignRoleMenuItemReqVO menuDataRule : menuDataRules) {
Long menuId = menuDataRule.getId();
List<Long> dataRuleIds = menuDataRule.getDataRuleIds();
// 删除该角色在该菜单下的旧规则关联
roleMenuDataRuleMapper.deleteByRoleAndMenu(roleId, menuId);
// 如果有新规则,则插入
if (CollUtil.isNotEmpty(dataRuleIds)) {
List<com.zt.plat.module.system.dal.dataobject.permission.RoleMenuDataRuleDO> entities =
dataRuleIds.stream().map(ruleId -> {
com.zt.plat.module.system.dal.dataobject.permission.RoleMenuDataRuleDO entity =
new com.zt.plat.module.system.dal.dataobject.permission.RoleMenuDataRuleDO();
entity.setRoleId(roleId);
entity.setMenuId(menuId);
entity.setDataRuleId(ruleId);
return entity;
}).collect(Collectors.toList());
roleMenuDataRuleMapper.insertBatch(entities);
}
}
}
@Override
public Set<Long> getRoleMenuDataRules(Long roleId, Long menuId) {
List<com.zt.plat.module.system.dal.dataobject.permission.RoleMenuDataRuleDO> list =
roleMenuDataRuleMapper.selectListByRoleAndMenu(roleId, menuId);
return CollectionUtils.convertSet(list,
com.zt.plat.module.system.dal.dataobject.permission.RoleMenuDataRuleDO::getDataRuleId);
}
@Override
@Transactional(rollbackFor = Exception.class)
@Caching(evict = {

View File

@@ -0,0 +1,109 @@
package com.zt.plat.module.system.service.permission.impl;
import cn.hutool.core.collection.CollUtil;
import com.zt.plat.module.system.controller.admin.permission.vo.menudatarule.MenuDataRuleSaveReqVO;
import com.zt.plat.module.system.convert.permission.MenuDataRuleConvert;
import com.zt.plat.module.system.dal.dataobject.permission.MenuDataRuleDO;
import com.zt.plat.module.system.dal.mysql.permission.MenuDataRuleMapper;
import com.zt.plat.module.system.service.permission.MenuDataRuleService;
import com.zt.plat.module.system.service.permission.PermissionService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import jakarta.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;
import static com.zt.plat.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.zt.plat.module.system.enums.ErrorCodeConstants.*;
/**
* 菜单数据规则 Service 实现类
*
* @author ZT
*/
@Service
@Validated
@Slf4j
public class MenuDataRuleServiceImpl implements MenuDataRuleService {
@Resource
private MenuDataRuleMapper menuDataRuleMapper;
@Resource
private PermissionService permissionService;
@Override
@CacheEvict(value = "menuDataRule", key = "#createReqVO.menuId")
public Long createMenuDataRule(MenuDataRuleSaveReqVO createReqVO) {
MenuDataRuleDO rule = MenuDataRuleConvert.INSTANCE.convert(createReqVO);
menuDataRuleMapper.insert(rule);
return rule.getId();
}
@Override
@CacheEvict(value = "menuDataRule", key = "#updateReqVO.menuId")
public void updateMenuDataRule(MenuDataRuleSaveReqVO updateReqVO) {
validateMenuDataRuleExists(updateReqVO.getId());
MenuDataRuleDO updateObj = MenuDataRuleConvert.INSTANCE.convert(updateReqVO);
menuDataRuleMapper.updateById(updateObj);
}
@Override
public void deleteMenuDataRule(Long id) {
MenuDataRuleDO rule = validateMenuDataRuleExists(id);
menuDataRuleMapper.deleteById(id);
}
@Override
public MenuDataRuleDO getMenuDataRule(Long id) {
return menuDataRuleMapper.selectById(id);
}
@Override
@Cacheable(value = "menuDataRule", key = "#menuId")
public List<MenuDataRuleDO> getMenuDataRuleListByMenuId(Long menuId) {
return menuDataRuleMapper.selectListByMenuId(menuId);
}
@Override
public List<MenuDataRuleDO> getUserMenuDataRules(Long userId, Long menuId) {
Set<Long> roleIds = permissionService.getUserRoleIdListByUserId(userId);
if (CollUtil.isEmpty(roleIds)) {
return Collections.emptyList();
}
List<MenuDataRuleDO> allRules = getMenuDataRuleListByMenuId(menuId);
if (CollUtil.isEmpty(allRules)) {
return Collections.emptyList();
}
List<Long> ruleIds = menuDataRuleMapper.selectRuleIdsByRoleAndMenu(roleIds, menuId);
// 如果角色没有关联任何规则,返回空列表(不应用任何过滤)
if (CollUtil.isEmpty(ruleIds)) {
return Collections.emptyList();
}
return allRules.stream()
.filter(rule -> ruleIds.contains(rule.getId()) && rule.getStatus() == 1)
.collect(Collectors.toList());
}
@Override
public Map<Long, List<MenuDataRuleDO>> getMenuDataRuleMap(Collection<Long> menuIds) {
List<MenuDataRuleDO> rules = menuDataRuleMapper.selectListByMenuIds(menuIds);
return rules.stream().collect(Collectors.groupingBy(MenuDataRuleDO::getMenuId));
}
private MenuDataRuleDO validateMenuDataRuleExists(Long id) {
MenuDataRuleDO rule = menuDataRuleMapper.selectById(id);
if (rule == null) {
throw exception(MENU_NOT_EXISTS);
}
return rule;
}
}

View File

@@ -60,4 +60,11 @@ public interface PortalService {
*/
List<PortalDO> getPortalListByUserId(Long userId);
/**
* 获得公开门户列表(无需登录)
*
* @return 门户列表
*/
List<PortalDO> getPublicPortalList();
}

View File

@@ -126,6 +126,11 @@ public class PortalServiceImpl implements PortalService {
return portalMapper.selectListByPermissions(permissions);
}
@Override
public List<PortalDO> getPublicPortalList() {
return portalMapper.selectListByPermissions(Collections.emptyList());
}
@VisibleForTesting
public PortalDO validatePortalExists(Long id) {
if (id == null) {

View File

@@ -0,0 +1,53 @@
package com.zt.plat.module.system.service.push;
import com.zt.plat.framework.common.pojo.PageResult;
import com.zt.plat.module.system.controller.admin.push.vo.ExternalPushConfigPageReqVO;
import com.zt.plat.module.system.controller.admin.push.vo.ExternalPushConfigSaveReqVO;
import com.zt.plat.module.system.dal.dataobject.push.ExternalPushConfigDO;
import jakarta.validation.Valid;
/**
* 外部系统推送配置 Service 接口
*
* @author ZT Cloud
*/
public interface ExternalPushConfigService {
/**
* 创建推送配置
*/
Long createExternalPushConfig(@Valid ExternalPushConfigSaveReqVO createReqVO);
/**
* 修改推送配置
*/
void updateExternalPushConfig(@Valid ExternalPushConfigSaveReqVO updateReqVO);
/**
* 删除推送配置
*/
void deleteExternalPushConfig(Long id);
/**
* 获取推送配置详情
*/
ExternalPushConfigDO getExternalPushConfig(Long id);
/**
* 分页查询推送配置
*/
PageResult<ExternalPushConfigDO> getExternalPushConfigPage(ExternalPushConfigPageReqVO reqVO);
/**
* 判断是否允许推送(核心业务逻辑)
*
* 优先级:部门配置 > 公司配置 > 默认允许
*
* @param companyId 公司编号(必填)
* @param deptId 部门编号(可选)
* @param businessType 业务类型(必填)
* @param externalSystem 外部系统标识(必填)
* @return 是否允许推送true=允许false=禁止,默认 true
*/
Boolean isPushEnabled(Long companyId, Long deptId, String businessType, String externalSystem);
}

View File

@@ -0,0 +1,280 @@
package com.zt.plat.module.system.service.push;
import cn.hutool.core.util.StrUtil;
import com.zt.plat.framework.common.pojo.PageResult;
import com.zt.plat.framework.common.util.object.BeanUtils;
import com.zt.plat.module.system.controller.admin.push.vo.ExternalPushConfigPageReqVO;
import com.zt.plat.module.system.controller.admin.push.vo.ExternalPushConfigSaveReqVO;
import com.zt.plat.module.system.dal.dataobject.dept.DeptDO;
import com.zt.plat.module.system.dal.dataobject.push.ExternalPushConfigDO;
import com.zt.plat.module.system.dal.mysql.dept.DeptMapper;
import com.zt.plat.module.system.dal.mysql.push.ExternalPushConfigMapper;
import com.zt.plat.module.system.enums.push.BusinessTypeEnum;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import static com.zt.plat.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.zt.plat.module.system.enums.ErrorCodeConstants.*;
/**
* 外部系统推送配置 Service 实现类
*
* @author ZT Cloud
*/
@Service
@Validated
public class ExternalPushConfigServiceImpl implements ExternalPushConfigService {
@Resource
private ExternalPushConfigMapper externalPushConfigMapper;
@Resource
private DeptMapper deptMapper;
@Override
public Long createExternalPushConfig(ExternalPushConfigSaveReqVO createReqVO) {
// 参数规范化
normalizeRequest(createReqVO);
// 业务校验
validateForCreateOrUpdate(null, createReqVO);
// 创建配置
ExternalPushConfigDO entity = BeanUtils.toBean(createReqVO, ExternalPushConfigDO.class);
if (entity.getEnablePush() == null) {
entity.setEnablePush(true);
}
externalPushConfigMapper.insert(entity);
return entity.getId();
}
@Override
public void updateExternalPushConfig(ExternalPushConfigSaveReqVO updateReqVO) {
// 参数规范化
normalizeRequest(updateReqVO);
// 校验存在
validateExists(updateReqVO.getId());
// 业务校验
validateForCreateOrUpdate(updateReqVO.getId(), updateReqVO);
// 更新配置
ExternalPushConfigDO updateObj = BeanUtils.toBean(updateReqVO, ExternalPushConfigDO.class);
externalPushConfigMapper.updateById(updateObj);
}
@Override
public void deleteExternalPushConfig(Long id) {
validateExists(id);
externalPushConfigMapper.deleteById(id);
}
@Override
public ExternalPushConfigDO getExternalPushConfig(Long id) {
return externalPushConfigMapper.selectById(id);
}
@Override
public PageResult<ExternalPushConfigDO> getExternalPushConfigPage(ExternalPushConfigPageReqVO reqVO) {
return externalPushConfigMapper.selectPage(reqVO);
}
@Override
public Boolean isPushEnabled(Long companyId, Long deptId, String businessType, String externalSystem) {
// 规范化参数
String normalizedBusinessType = StrUtil.isNotBlank(businessType) ? businessType.trim().toUpperCase() : null;
String normalizedExternalSystem = StrUtil.isNotBlank(externalSystem) ? externalSystem.trim().toUpperCase() : null;
// 优先级 1部门级配置如果 deptId 不为空),递归查找父部门
if (deptId != null) {
ExternalPushConfigDO deptConfig = findDeptConfigRecursive(
companyId, deptId, normalizedBusinessType, normalizedExternalSystem);
if (deptConfig != null) {
return deptConfig.getEnablePush() != null ? deptConfig.getEnablePush() : true;
}
}
// 优先级 2公司级配置dept_id 为 null
if (companyId != null) {
ExternalPushConfigDO companyConfig = externalPushConfigMapper.selectByConfig(
companyId, null, normalizedBusinessType, normalizedExternalSystem);
if (companyConfig != null) {
return companyConfig.getEnablePush() != null ? companyConfig.getEnablePush() : true;
}
}
// 优先级 3全局配置company_id 和 dept_id 都为空)
ExternalPushConfigDO globalConfig = externalPushConfigMapper.selectByConfig(
null, null, normalizedBusinessType, normalizedExternalSystem);
if (globalConfig != null) {
return globalConfig.getEnablePush() != null ? globalConfig.getEnablePush() : true;
}
// 优先级 4没有配置默认允许推送
return true;
}
/**
* 递归查找部门配置(包括父部门)
*
* @param companyId 公司ID
* @param deptId 部门ID
* @param businessType 业务类型
* @param externalSystem 外部系统
* @return 配置对象,如果找不到返回 null
*/
private ExternalPushConfigDO findDeptConfigRecursive(Long companyId, Long deptId,
String businessType, String externalSystem) {
if (deptId == null) {
return null;
}
// 查询当前部门的配置
ExternalPushConfigDO config = externalPushConfigMapper.selectByConfig(
companyId, deptId, businessType, externalSystem);
if (config != null) {
return config;
}
// 查询当前部门信息获取父部门ID
DeptDO dept = deptMapper.selectById(deptId);
if (dept == null || dept.getParentId() == null || dept.getParentId() == 0L) {
// 没有父部门了,返回 null
return null;
}
// 检查父部门是否是公司节点
DeptDO parentDept = deptMapper.selectById(dept.getParentId());
if (parentDept != null && Boolean.TRUE.equals(parentDept.getIsCompany())) {
// 父部门是公司,不再向上查找(公司级配置在外层处理)
return null;
}
// 递归查找父部门的配置
return findDeptConfigRecursive(companyId, dept.getParentId(), businessType, externalSystem);
}
//==================== 私有方法 ====================
/**
* 校验配置是否存在
*/
private ExternalPushConfigDO validateExists(Long id) {
if (id == null) {
throw exception(EXTERNAL_PUSH_CONFIG_NOT_EXISTS);
}
ExternalPushConfigDO entity = externalPushConfigMapper.selectById(id);
if (entity == null) {
throw exception(EXTERNAL_PUSH_CONFIG_NOT_EXISTS);
}
return entity;
}
/**
* 业务校验
*/
private void validateForCreateOrUpdate(Long id, ExternalPushConfigSaveReqVO reqVO) {
// 1. 如果指定公司,校验公司存在且是公司节点
if (reqVO.getCompanyId() != null) {
DeptDO company = deptMapper.selectById(reqVO.getCompanyId());
if (company == null) {
throw exception(DEPT_NOT_FOUND);
}
if (!Boolean.TRUE.equals(company.getIsCompany())) {
throw exception(EXTERNAL_PUSH_CONFIG_COMPANY_INVALID);
}
}
// 2. 如果指定部门,校验部门存在且不是公司节点
if (reqVO.getDeptId() != null) {
DeptDO dept = deptMapper.selectById(reqVO.getDeptId());
if (dept == null) {
throw exception(DEPT_NOT_FOUND);
}
if (Boolean.TRUE.equals(dept.getIsCompany())) {
throw exception(EXTERNAL_PUSH_CONFIG_DEPT_INVALID);
}
}
// 3. 如果指定业务类型,校验业务类型有效性
if (StrUtil.isNotBlank(reqVO.getBusinessType())) {
if (!BusinessTypeEnum.isValidCode(reqVO.getBusinessType())) {
throw exception(EXTERNAL_PUSH_CONFIG_BUSINESS_TYPE_INVALID);
}
}
// 4. 校验唯一性:同一租户+公司+部门+业务类型+外部系统的配置唯一
ExternalPushConfigDO existing = externalPushConfigMapper.selectByConfig(
reqVO.getCompanyId(), reqVO.getDeptId(),
reqVO.getBusinessType(), reqVO.getExternalSystem());
if (existing != null && (id == null || !existing.getId().equals(id))) {
throw exception(EXTERNAL_PUSH_CONFIG_EXISTS);
}
}
/**
* 参数规范化
*/
private void normalizeRequest(ExternalPushConfigSaveReqVO reqVO) {
if (reqVO == null) {
return;
}
// 如果 companyId 为空但 deptId 不为空自动查找并填充顶级公司ID
if (reqVO.getCompanyId() == null && reqVO.getDeptId() != null) {
Long topCompanyId = findTopCompanyId(reqVO.getDeptId());
if (topCompanyId != null) {
reqVO.setCompanyId(topCompanyId);
}
}
if (StrUtil.isNotBlank(reqVO.getBusinessType())) {
reqVO.setBusinessType(reqVO.getBusinessType().trim().toUpperCase());
}
if (StrUtil.isNotBlank(reqVO.getExternalSystem())) {
reqVO.setExternalSystem(reqVO.getExternalSystem().trim().toUpperCase());
}
}
/**
* 查找部门的顶级公司ID
*
* @param deptId 部门ID
* @return 顶级公司ID如果不存在返回 null
*/
private Long findTopCompanyId(Long deptId) {
if (deptId == null) {
return null;
}
DeptDO dept = deptMapper.selectById(deptId);
if (dept == null) {
return null;
}
// 递归向上查找直到找到公司节点或者parentId为空/0
Long currentDeptId = deptId;
while (currentDeptId != null) {
DeptDO currentDept = deptMapper.selectById(currentDeptId);
if (currentDept == null) {
break;
}
// 如果当前节点是公司返回其ID
if (Boolean.TRUE.equals(currentDept.getIsCompany())) {
return currentDept.getId();
}
// 如果没有父节点或父节点为0结束查找
if (currentDept.getParentId() == null || currentDept.getParentId() == 0L) {
break;
}
// 继续向上查找
currentDeptId = currentDept.getParentId();
}
return null;
}
}

View File

@@ -0,0 +1,13 @@
package com.zt.plat.module.system.service.sync;
/**
* 定时同步 iWork 组织变更服务
*/
public interface SyncIWorkOrgChangeService {
/**
* 执行同步
* @return 拉取记录数量
*/
int process();
}

View File

@@ -0,0 +1,41 @@
package com.zt.plat.module.system.service.sync;
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.service.integration.iwork.IWorkSyncService;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
@Service
public class SyncIWorkOrgChangeServiceImpl implements SyncIWorkOrgChangeService {
@Resource
private IWorkSyncService iWorkSyncService;
@Override
public int process() {
IWorkFullSyncReqVO reqVO = new IWorkFullSyncReqVO();
reqVO.setPageSize(10);
ZoneId zone = ZoneId.of("Asia/Shanghai");
String startOfToday = LocalDate.now(zone)
.atStartOfDay(zone)
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
reqVO.setModified(startOfToday);
IWorkFullSyncRespVO subcompanyResp = iWorkSyncService.fullSyncSubcompanies(reqVO);
IWorkFullSyncRespVO departmentResp = iWorkSyncService.fullSyncDepartments(reqVO);
return countPulled(subcompanyResp) + countPulled(departmentResp);
}
private int countPulled(IWorkFullSyncRespVO respVO) {
if (respVO == null || respVO.getBatches() == null) {
return 0;
}
return respVO.getBatches().stream()
.mapToInt(batch -> batch.getPulled() == null ? 0 : batch.getPulled())
.sum();
}
}

View File

@@ -0,0 +1,14 @@
package com.zt.plat.module.system.service.sync;
/**
* 同步iWork当日修改的用户数据
*/
public interface SyncIWorkUserChangeService {
/**
* 同步入口
* @return
*/
int process();
}

View File

@@ -0,0 +1,37 @@
package com.zt.plat.module.system.service.sync;
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.service.integration.iwork.IWorkSyncService;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
@Service
public class SyncIWorkUserChangeServiceImpl implements SyncIWorkUserChangeService {
@Resource
private IWorkSyncService iWorkSyncService;
@Override
public int process() {
IWorkFullSyncReqVO reqVO = new IWorkFullSyncReqVO();
reqVO.setPageSize(10);
// 设置修改日期的查询条件为当日0时起
ZoneId zone = ZoneId.of("Asia/Shanghai");
String startOfToday = LocalDate.now(zone)
.atStartOfDay(zone)
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
reqVO.setModified(startOfToday);
IWorkFullSyncRespVO respVO = iWorkSyncService.fullSyncUsers(reqVO);
if(respVO!=null && respVO.getBatches()!=null) {
return respVO.getBatches().stream()
.mapToInt(b -> b.getPulled() == null ? 0 : b.getPulled())
.sum();
}
return 0;
}
}

View File

@@ -70,6 +70,13 @@ public interface UserDeptService {
*/
void deleteUserDeptByUserId(Long userId);
/**
* 根据用户ID与部门ID集合删除用户与部门关系
* @param userId 用户ID
* @param deptIds 部门ID集合
*/
void deleteUserDeptByUserIdAndDeptIds(Long userId, Collection<Long> deptIds);
/**
* 批量创建用户与部门关系
* @param createReqVOList 创建信息列表

View File

@@ -3,6 +3,7 @@ package com.zt.plat.module.system.service.userdept;
import cn.hutool.core.collection.CollUtil;
import com.zt.plat.framework.common.util.object.BeanUtils;
import com.zt.plat.framework.security.core.LoginUser;
import com.zt.plat.framework.mybatis.core.query.LambdaQueryWrapperX;
import com.zt.plat.module.system.dal.dataobject.userdept.UserDeptDO;
import com.zt.plat.module.system.dal.mysql.userdept.UserDeptMapper;
import jakarta.annotation.Resource;
@@ -128,10 +129,20 @@ public class UserDeptServiceImpl implements UserDeptService {
@Override
public void deleteUserDeptByUserId(Long userId) {
if (userId == null) return;
userDeptMapper.delete(new com.zt.plat.framework.mybatis.core.query.LambdaQueryWrapperX<UserDeptDO>()
userDeptMapper.delete(new LambdaQueryWrapperX<UserDeptDO>()
.eq(UserDeptDO::getUserId, userId));
}
@Override
public void deleteUserDeptByUserIdAndDeptIds(Long userId, Collection<Long> deptIds) {
if (userId == null || CollUtil.isEmpty(deptIds)) {
return;
}
userDeptMapper.delete(new LambdaQueryWrapperX<UserDeptDO>()
.eq(UserDeptDO::getUserId, userId)
.in(UserDeptDO::getDeptId, deptIds));
}
@Override
@Transactional(rollbackFor = Exception.class)
public void batchCreateUserDept(List<UserDeptDO> createReqVOList) {

View File

@@ -0,0 +1,63 @@
package com.zt.plat.module.system.service.integration.iwork.impl;
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkWorkflowCallbackReqVO;
import com.zt.plat.module.system.dal.dataobject.iwork.IWorkSealLogDO;
import com.zt.plat.module.system.dal.mysql.iwork.IWorkSealLogMapper;
import com.zt.plat.module.system.framework.integration.iwork.config.IWorkProperties;
import com.zt.plat.module.system.mq.iwork.IWorkBizCallbackProducer;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
class IWorkCallbackLogServiceImplTest {
private IWorkSealLogMapper mapper;
private IWorkBizCallbackProducer producer;
private IWorkProperties properties;
private IWorkCallbackLogServiceImpl service;
@BeforeEach
void setup() {
mapper = mock(IWorkSealLogMapper.class);
producer = mock(IWorkBizCallbackProducer.class);
properties = new IWorkProperties();
service = new IWorkCallbackLogServiceImpl(mapper, producer, properties);
}
@Test
void upsertOnCallback_shouldTruncateRaw() {
String longRaw = "x".repeat(2100);
IWorkWorkflowCallbackReqVO req = new IWorkWorkflowCallbackReqVO();
req.setRequestId("REQ-1");
req.setBizCallbackKey("key");
ArgumentCaptor<IWorkSealLogDO> captor = ArgumentCaptor.forClass(IWorkSealLogDO.class);
when(mapper.selectByRequestId("REQ-1")).thenReturn(null);
service.upsertOnCallback(req, 3, longRaw);
verify(mapper).insert(captor.capture());
IWorkSealLogDO saved = captor.getValue();
assertThat(saved.getRawCallback()).hasSize(2000);
assertThat(saved.getMaxRetry()).isEqualTo(3);
}
@Test
void incrementRetry_shouldIncreaseCount() {
IWorkSealLogDO existing = new IWorkSealLogDO();
existing.setId(1L);
existing.setRequestId("REQ-2");
existing.setRetryCount(1);
when(mapper.selectByRequestId("REQ-2")).thenReturn(existing);
service.incrementRetry("REQ-2");
ArgumentCaptor<IWorkSealLogDO> captor = ArgumentCaptor.forClass(IWorkSealLogDO.class);
verify(mapper).updateById(captor.capture());
assertThat(captor.getValue().getRetryCount()).isEqualTo(2);
}
}