diff --git a/blade-common/src/main/java/org/springblade/common/constant/CommonConstant.java b/blade-common/src/main/java/org/springblade/common/constant/CommonConstant.java
index a3e1cfa..e6cda7e 100644
--- a/blade-common/src/main/java/org/springblade/common/constant/CommonConstant.java
+++ b/blade-common/src/main/java/org/springblade/common/constant/CommonConstant.java
@@ -72,4 +72,35 @@ public interface CommonConstant {
*/
Integer API_SCOPE_CATEGORY = 2;
+
+ /**
+ * 是 2
+ */
+ Integer YES = 2;
+ /**
+ * 不是 1
+ */
+ Integer NO = 1;
+ /**
+ * 系统名称
+ */
+ String SYSTEM_CODE = "CK-ERP";
+ /**
+ * 默认密码参数值
+ */
+ String DEFAULT_PARAM_PASSWORD = "account.initPassword";
+ /**
+ * 默认角色参数值
+ */
+ String DEFAULT_PARAM_ROLE = "account.initRole";
+ /**
+ * 默认角色
+ */
+ String DEFAULT_ROLE = "default";
+ /**
+ * 同步的子公司名称根公司的值
+ */
+ String DICT_KEY_SYNC_ROOT_COMPANY_NAME = "root_company";
+
+
}
diff --git a/blade-common/src/main/java/org/springblade/common/constant/DataStatusEnum.java b/blade-common/src/main/java/org/springblade/common/constant/DataStatusEnum.java
new file mode 100644
index 0000000..f7c8adb
--- /dev/null
+++ b/blade-common/src/main/java/org/springblade/common/constant/DataStatusEnum.java
@@ -0,0 +1,69 @@
+package org.springblade.common.constant;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 数据状态枚举
+ *
+ * @author vic
+ * @date 2024/8/26 6:45 PM
+ * @description DataStatusEnum
+ */
+@Getter
+@AllArgsConstructor
+public enum DataStatusEnum {
+
+ /**
+ *
+ */
+ ENABLE(1, "启用"),
+
+ DISABLE(2, "禁用"),
+
+
+ ;
+
+
+ private final Integer code;
+
+ private final String name;
+
+
+ /**
+ * 匹配枚举值
+ *
+ * @param code
+ * @return
+ */
+ public static boolean match(Integer code) {
+ for (DataStatusEnum state : values()) {
+ if (state.code.equals(code)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static String getNameStr(Integer code) {
+ for (DataStatusEnum state : values()) {
+ if (state.code.equals(code)) {
+ return state.name;
+ }
+ }
+ return null;
+
+ }
+
+
+ /**
+ * 是否可以编辑修改表单 .没有审批业务的使用此方法, 系统设计的是反的
+ * true:可编辑 false:不可编辑
+ * @param value
+ * @return
+ */
+ public static boolean canEdit(Integer value) {
+ return DISABLE.getCode().equals(value);
+ }
+
+}
diff --git a/blade-common/src/main/java/org/springblade/common/constant/DictTypeEnum.java b/blade-common/src/main/java/org/springblade/common/constant/DictTypeEnum.java
new file mode 100644
index 0000000..78dd418
--- /dev/null
+++ b/blade-common/src/main/java/org/springblade/common/constant/DictTypeEnum.java
@@ -0,0 +1,171 @@
+package org.springblade.common.constant;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 系统字典类型枚举
+ * 响功能和流程的字典
+ * @author vic
+ * @date 2024/9/9 8:56 PM
+ * @description DictTypeEnum
+ */
+@Getter
+@AllArgsConstructor
+public enum DictTypeEnum {
+ /**
+ *
+ */
+ DATA_STATUS("data_status", "数据状态"),
+ APPROVE_STATUS("approve_state", "通用审批状态"),
+ COOPERATE_STATUS("cooperate_status", "合作状态"),
+ CUSTOMER_TYPE("customer_type", "客户类型"),
+ DELIVERER_TYPE("deliverer_type", "投送人类型"),
+ ENTERPRISE_TYPE("enterprise_type", "企业类型"),
+ SETTLEMENT_TYPE("settlement_type", "结算中心类型"),
+ CUSTOMER_COMPANY_TYPE("company_type","客户管理的企业类型"),
+ PARTNER_TYPE("partner_type","合作伙伴类型"),
+ SYSTEM_YES_NO("yes_no","系统是否"),
+ PLATFORM_TYPE("settlement_type","平台类型/结算中心类型"),
+ TAXPAYER_TYPE("taxpayer_type","纳税人分类"),
+ //部门简称
+ DEPARTMENT_SHORT_NAME("department_short_name","部门简称"),
+ PROCESS_TYPE("process_type","流程类型"),
+
+ //评级维度
+ RATING_DIMENSION("rating_dimension","评级维度"),
+ // oa公司部门同步
+ SYNC_COMPANY_NAME("sync_company_name","同步公司名称"),
+ SYNC_ROOT_COMPANY_DEPT_NAME("sync_root_company_dept_name","同步根公司下部门名称"),
+ DEPARTMENT_CODE("department_code","部门编号"),
+
+ //公式管理
+ FORMULA_TYPE("formula_type","公式类型"),
+
+ //财务管理
+ FINANCIAL_TYPE("financial_type","财务类型"),
+ SAP_TYPE("sap_type","财务结算类型"),
+ SETTLEMENT_CLASSIFICATION("settlement_classification","财务结算分类"),//内部结算、外部结算
+ SAP_DATA_STATUS("sap_data_status","财务结算单状态"),
+ SETTLEMENT_VOUCHER_TYPE("settlement_voucher_type","结算凭证类型"),
+ SETTLEMENT_NEXT_STATUS("settlement_next_status","下一步结算状态"),
+
+ PAYMENT_TYPE("payment_type","财务付款分类"),
+ PAYMENT_DATA_STATUS("payment_data_status","财务付款单状态"),
+ PAYEMENT_CLASSIFICATION("payment_classification","财务付款分类"), //内部付款、外部付款
+ PAYMENT_METHOD_TYPE("payment_method_type","财务付款支付方式"),
+
+ SAP_VERIFICATION_METHOD("verification_method","验证方式"),
+
+ /** 结算管理列表页的开票状态、付款状态 **/
+ SETT_PAYMENT_STATUS("payment_status","付款状态"),
+ SETT_INVOICE_STATUS("invoice_status","开票状态"),
+
+
+ // 车辆运输凭证
+ CERTIFICATE_BATCH_STATUS("certificate_batch_status","车辆运输凭证批次状态"),
+ CERTIFICATE_IDENTIFICATION_STATUS("certificate_identification_status","车辆运输凭证识别状态"),
+ CERTIFICATE_IDENTIFICATION_RESULT("certificate_identification_result","车辆运输凭证识别结果"),
+
+ // 文件任务状态
+ FILE_TASK_STATUS("file_task_status", "文件任务状态"),
+ // 运单管理-差异项 总净重
+ WEIGHT_STATUS("weight_status", "总净重"),
+ // 运单管理-比对结果 差异类型
+ DIFFERENCE_TYPE("difference_type", "差异类型"),
+ // 收款
+ CLAIM_STATUS("claim_status", "认领状态"),
+ RECEIPT_SOURCE("receipt_source", "收款来源"),
+ CURRENCY("currency", "币种"),
+
+ TRAJECTORY_STATUS("trajectory_status", "轨迹状态"),
+
+ HOME_BASE_TYPE("base_type","基地类型"),
+
+ FEE_NAME_TYPE("fee_name_type","费用类型"),
+
+ TAX_CODE("taxCode","税收编码"),
+ INPUT_INVOICE_STATUS("input_invoice_status","录入开票状态"),
+
+ /**
+ * 城矿结算公司财务负责人
+ */
+ SETTLEMENT_FINANCE_CONTROLLER("settlement_finance_controller", "城矿结算公司财务负责人"),
+
+ /**
+ * 付款单据类型
+ */
+ PAYMENT_DOCUMENT_TYPE("payment_document_type", "付款单据类型"),
+
+ /**
+ * 归档状态
+ */
+ ARCHIVE_STATUS("archive_status", "归档状态"),
+
+ /**
+ * 预警类型
+ */
+ WARNING_TYPE("warning_type", "预警类型"),
+
+ /**
+ * 消息状态
+ */
+ MESSAGE_STATUS("message_status", "消息状态"),
+
+ /**
+ * 通知类型
+ */
+ NOTICE_TYPE("notice_type", "通知类型"),
+
+ /**
+ * 通知对象类型
+ */
+ NOTICE_OBJECT_TYPE("notice_object_type", "通知对象类型"),
+
+ /**
+ * 项目磅单校验类型
+ */
+ PROJECT_POUND_VALID_TYPE("project_pound_valid_type", "项目磅单校验类型"),
+
+ /**
+ * 推送单据类型
+ */
+ PUSH_DOC_TYPE("push_doc_type", "推送单据类型"),
+
+ /**
+ * mk流程状态
+ */
+ MK_STATUS("mk_status", "mk流程状态"),
+
+ ;
+
+ final String type;
+
+ final String text;
+
+
+ /**
+ * 匹配枚举值
+ *
+ * @param type
+ * @return
+ */
+ public static boolean match(String type) {
+ for (DictTypeEnum state : values()) {
+ if (state.type.equals(type)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static String getNameStr(String type) {
+ for (DictTypeEnum state : values()) {
+ if (state.type.equals(type)) {
+ return state.text;
+ }
+ }
+ return null;
+ }
+
+}
diff --git a/blade-gateway/src/main/resources/application.yml b/blade-gateway/src/main/resources/application.yml
index d0c3371..bbcb4bd 100644
--- a/blade-gateway/src/main/resources/application.yml
+++ b/blade-gateway/src/main/resources/application.yml
@@ -14,3 +14,6 @@ knife4j:
- blade-admin
- blade-gateway
- blade-log
+
+server:
+ port: 28080
diff --git a/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/entity/DataSyncRecord.java b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/entity/DataSyncRecord.java
new file mode 100644
index 0000000..6c8de03
--- /dev/null
+++ b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/entity/DataSyncRecord.java
@@ -0,0 +1,88 @@
+package org.springblade.system.pojo.entity;
+
+import com.baomidou.mybatisplus.annotation.*;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+import io.swagger.v3.oas.annotations.media.Schema;
+
+import java.util.Date;
+
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
+import lombok.experimental.Accessors;
+import org.springblade.core.tool.utils.DateUtil;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.io.Serializable;
+import java.io.Serial;
+
+/**
+ * 数据同步记录表 实体类
+ *
+ * @author BladeX
+ * @since 2024-09-27
+ */
+@Accessors(chain = true)
+@Data
+@TableName("blade_data_sync_record")
+@Schema(description = "DataSyncRecord对象")
+public class DataSyncRecord implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 主键
+ */
+ @JsonSerialize(using = ToStringSerializer.class)
+ @Schema(description = "主键")
+ @TableId(value = "id", type = IdType.ASSIGN_ID)
+ private Long id;
+
+ /**
+ * 系统编码
+ */
+ @Schema(description = "系统编码")
+ private String systemCode;
+ /**
+ * 数据类型
+ */
+ @Schema(description = "数据类型")
+ private String dataType;
+ /**
+ * 模式
+ */
+ @Schema(description = "模式")
+ private String mode;
+ /**
+ * 结果
+ */
+ @Schema(description = "结果")
+ private String result;
+ /**
+ * 同步时间
+ */
+ @Schema(description = "同步时间")
+ private Date syncTime;
+ /**
+ * 租户ID
+ */
+ @Schema(description = "租户ID")
+ @TableField(fill = FieldFill.INSERT)
+ private String tenantId;
+ /**
+ * 创建时间
+ */
+ @DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME)
+ @JsonFormat(pattern = DateUtil.PATTERN_TIME_MINI)
+ @Schema(description = "创建时间", hidden = true)
+ @TableField(fill = FieldFill.INSERT)
+ private Date createTime;
+ /**
+ * 状态[0:未删除,1:删除]
+ */
+ @TableLogic
+ @Schema(description = "是否已删除", hidden = true)
+ @TableField(fill = FieldFill.INSERT)
+ private Integer isDeleted;
+}
diff --git a/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/entity/Dept.java b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/entity/Dept.java
index 32da844..53002eb 100644
--- a/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/entity/Dept.java
+++ b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/entity/Dept.java
@@ -33,9 +33,11 @@ import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
+import org.springblade.core.tenant.mp.TenantEntity;
import java.io.Serial;
import java.io.Serializable;
+import java.util.Date;
/**
* 实体类
@@ -45,7 +47,7 @@ import java.io.Serializable;
@Data
@TableName("blade_dept")
@Schema(description = "Dept对象")
-public class Dept implements Serializable {
+public class Dept extends TenantEntity {
@Serial
private static final long serialVersionUID = 1L;
@@ -127,4 +129,30 @@ public class Dept implements Serializable {
private Integer isDeleted;
+ /**
+ * 部门编码
+ */
+ @Schema(description = "部门编码")
+ private String deptCode;
+ /**
+ * 父部门编码
+ */
+ @Schema(description = "父部门编码")
+ private String parentCode;
+ /**
+ * 同步时间
+ */
+ @Schema(description = "同步时间")
+ private Date syncTime;
+ /**
+ * 所属公司编码
+ */
+ @Schema(description = "所属公司编码")
+ private String belongCompanyCode;
+ /**
+ * 是否是oa的部门
+ */
+ @Schema(description = "是否是oa的部门")
+ private Integer isOa;
+
}
diff --git a/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/enums/DataSync.java b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/enums/DataSync.java
new file mode 100644
index 0000000..2ea2bfa
--- /dev/null
+++ b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/enums/DataSync.java
@@ -0,0 +1,69 @@
+package org.springblade.system.pojo.enums;
+
+/**
+ * 数据同步枚举类
+ * @author bfhuange
+ * @date 2024/9/27
+ */
+public class DataSync {
+
+ /**
+ * 系统编码
+ */
+ public enum SystemCode {
+ /**
+ * oa 系统
+ */
+ OA,
+ /**
+ * mk 系统
+ */
+ MK,
+ ;
+ }
+
+ /**
+ * 数据类型
+ */
+ public enum DataType {
+ /**
+ * 组织,包含公司和部门
+ */
+ ORG,
+ /**
+ * 人员
+ */
+ PERSON,
+ ;
+ }
+
+ /**
+ * 模式
+ */
+ public enum Mode {
+ /**
+ * 获取
+ */
+ FETCH,
+ /**
+ * 推送
+ */
+ PUSH,
+ ;
+ }
+
+ /**
+ * 结果
+ */
+ public enum Result {
+ /**
+ * 成功
+ */
+ SUCCESS,
+ /**
+ * 失败
+ */
+ FAIL,
+ ;
+ }
+}
diff --git a/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/enums/DeptCategory.java b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/enums/DeptCategory.java
new file mode 100644
index 0000000..cf21c31
--- /dev/null
+++ b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/enums/DeptCategory.java
@@ -0,0 +1,32 @@
+package org.springblade.system.pojo.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 部门类型枚举
+ * @author bfhuange
+ * @date 2024/8/26
+ */
+@AllArgsConstructor
+@Getter
+public enum DeptCategory {
+ /**
+ * 公司
+ */
+ COMPANY(1, "公司"),
+ /**
+ * 部门
+ */
+ DEPT(2, "部门"),
+ ;
+
+ /**
+ * 部门类型编码
+ */
+ private final Integer code;
+ /**
+ * 部门类型名称
+ */
+ private final String name;
+}
diff --git a/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/enums/UserSex.java b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/enums/UserSex.java
new file mode 100644
index 0000000..3b37571
--- /dev/null
+++ b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/enums/UserSex.java
@@ -0,0 +1,43 @@
+package org.springblade.system.pojo.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.stream.Stream;
+
+/**
+ * 用户性别枚举
+ * @author bfhuange
+ * @date 2024/8/27
+ */
+@Getter
+@AllArgsConstructor
+public enum UserSex {
+ /**
+ * 男
+ */
+ MAN(1, "男"),
+ /**
+ * 女
+ */
+ WOMAN(2, "女"),
+ ;
+ /**
+ * 编码
+ */
+ private final Integer code;
+ /**
+ * 名称
+ */
+ private final String name;
+
+ public static Integer getCodeByName(String name) {
+ if (name == null || name.isEmpty()) {
+ return null;
+ }
+ return Stream.of(values())
+ .filter(e -> e.getName().equals(name))
+ .map(UserSex::getCode)
+ .findFirst().orElse(null);
+ }
+}
diff --git a/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/vo/DeptListVO.java b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/vo/DeptListVO.java
new file mode 100644
index 0000000..9346dd4
--- /dev/null
+++ b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/vo/DeptListVO.java
@@ -0,0 +1,52 @@
+package org.springblade.system.pojo.vo;
+
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * @author bfhuange
+ * @date 2024/8/27
+ */
+@Data
+@Schema(description = "DeptListVO")
+public class DeptListVO implements Serializable {
+ @Serial
+ private static final long serialVersionUID = 1L;
+ /**
+ * 主键id
+ */
+ @JsonSerialize(using = ToStringSerializer.class)
+ @Schema(description = "主键id")
+ private Long id;
+ /**
+ * 部门编码
+ */
+ @Schema(description = "部门编码")
+ private String deptCode;
+ /**
+ * 机构名
+ */
+ @Schema(description = "机构名")
+ private String deptName;
+ /**
+ * 父主键
+ */
+ @JsonSerialize(using = ToStringSerializer.class)
+ @Schema(description = "父主键")
+ private Long parentId;
+ /**
+ * 父部门编码
+ */
+ @Schema(description = "父部门编码")
+ private String parentCode;
+ /**
+ * 部门类型
+ */
+ @Schema(description = "部门类型")
+ private Integer deptCategory;
+}
diff --git a/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/vo/UserDeptIdsVO.java b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/vo/UserDeptIdsVO.java
new file mode 100644
index 0000000..9486204
--- /dev/null
+++ b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/vo/UserDeptIdsVO.java
@@ -0,0 +1,37 @@
+package org.springblade.system.pojo.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 用户部门id
+ * @author bfhuange
+ * @date 2024/9/12
+ */
+@Data
+@Schema(description = "UserDeptIdsVO对象")
+public class UserDeptIdsVO implements Serializable {
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 用户id
+ */
+ @Schema(description = "用户id")
+ private Long userId;
+
+ /**
+ * 部门id,多个用逗号拼接
+ */
+ @Schema(description = "部门id,多个用逗号拼接")
+ private String deptIds;
+
+ /**
+ * 部门编码,多个用逗号拼接
+ */
+ @Schema(description = "部门编码,多个用逗号拼接")
+ private String deptCodes;
+}
diff --git a/blade-service-api/blade-system-api/src/main/java/org/springblade/system/util/DataSyncRecordUtils.java b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/util/DataSyncRecordUtils.java
new file mode 100644
index 0000000..aca6cea
--- /dev/null
+++ b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/util/DataSyncRecordUtils.java
@@ -0,0 +1,83 @@
+package org.springblade.system.util;
+
+import org.springblade.system.pojo.entity.DataSyncRecord;
+import org.springblade.system.pojo.enums.DataSync;
+
+/**
+ * 数据同步记录工具类
+ * @author bfhuange
+ * @date 2024/9/27
+ */
+public class DataSyncRecordUtils {
+ private DataSyncRecordUtils() {}
+
+ /**
+ * 创建获取oa组织的数据同步记录
+ * @return
+ */
+ public static DataSyncRecord createOAOrgFetch() {
+ return DataSyncRecordUtils.createOAFetch()
+ // 数据类型是组织
+ .setDataType(DataSync.DataType.ORG.name());
+ }
+
+ /**
+ * 创建获取oa人员的数据同步记录
+ * @return
+ */
+ public static DataSyncRecord createOAPersonFetch() {
+ return DataSyncRecordUtils.createOAFetch()
+ // 数据类型是人员
+ .setDataType(DataSync.DataType.PERSON.name());
+ }
+
+ /**
+ * 创建推送mk组织的数据同步记录
+ * @return
+ */
+ public static DataSyncRecord createMKOrgPush() {
+ return DataSyncRecordUtils.createMKPush()
+ // 数据类型是组织
+ .setDataType(DataSync.DataType.ORG.name());
+ }
+
+ /**
+ * 创建推送mk人员的数据同步记录
+ * @return
+ */
+ public static DataSyncRecord createMKPersonPush() {
+ return DataSyncRecordUtils.createMKPush()
+ // 数据类型是人员
+ .setDataType(DataSync.DataType.PERSON.name());
+ }
+
+ /**
+ * 创建oa 获取数据记录
+ *
+ * @return
+ */
+ private static DataSyncRecord createOAFetch() {
+ return new DataSyncRecord()
+ // oa 系统
+ .setSystemCode(DataSync.SystemCode.OA.name())
+ // 获取数据
+ .setMode(DataSync.Mode.FETCH.name())
+ // 默认结果是成功
+ .setResult(DataSync.Result.SUCCESS.name());
+ }
+
+ /**
+ * 创建mk 推送数据记录
+ *
+ * @return
+ */
+ private static DataSyncRecord createMKPush() {
+ return new DataSyncRecord()
+ // mk 系统
+ .setSystemCode(DataSync.SystemCode.MK.name())
+ // 推送数据
+ .setMode(DataSync.Mode.PUSH.name())
+ // 默认结果是成功
+ .setResult(DataSync.Result.SUCCESS.name());
+ }
+}
diff --git a/blade-service-api/blade-user-api/src/main/java/org/springblade/system/pojo/context/IPhone.java b/blade-service-api/blade-user-api/src/main/java/org/springblade/system/pojo/context/IPhone.java
new file mode 100644
index 0000000..7e3f6bd
--- /dev/null
+++ b/blade-service-api/blade-user-api/src/main/java/org/springblade/system/pojo/context/IPhone.java
@@ -0,0 +1,19 @@
+package org.springblade.system.pojo.context;
+
+/**
+ * 获取设置手机号接口
+ * @author bfhuange
+ * @since 2026/4/1
+ */
+public interface IPhone {
+ /**
+ * 设置手机号
+ * @return
+ */
+ void setPhone(String phone);
+ /**
+ * 获取手机号
+ * @return
+ */
+ String getPhone();
+}
diff --git a/blade-service-api/blade-user-api/src/main/java/org/springblade/system/pojo/entity/User.java b/blade-service-api/blade-user-api/src/main/java/org/springblade/system/pojo/entity/User.java
index 9da8101..63ee3cc 100644
--- a/blade-service-api/blade-user-api/src/main/java/org/springblade/system/pojo/entity/User.java
+++ b/blade-service-api/blade-user-api/src/main/java/org/springblade/system/pojo/entity/User.java
@@ -31,6 +31,7 @@ import lombok.EqualsAndHashCode;
import org.springblade.core.tenant.mp.TenantEntity;
import org.springblade.core.tool.jackson.Sensitive;
import org.springblade.core.tool.sensitive.SensitiveType;
+import org.springblade.system.pojo.context.IPhone;
import java.io.Serial;
import java.util.Date;
@@ -43,7 +44,7 @@ import java.util.Date;
@Data
@TableName("blade_user")
@EqualsAndHashCode(callSuper = true)
-public class User extends TenantEntity {
+public class User extends TenantEntity implements IPhone {
@Serial
private static final long serialVersionUID = 1L;
@@ -116,4 +117,22 @@ public class User extends TenantEntity {
private Integer isLeader;
+ /**
+ * 部门编码
+ */
+ private String deptCodes;
+ /**
+ * 同步时间
+ */
+ private Date syncTime;
+ /**
+ * 是否是oa的账号
+ */
+ private Integer isOa;
+ /**
+ * 是否推送mk
+ */
+ private Integer isPushMk;
+
+
}
diff --git a/blade-service-api/blade-user-api/src/main/java/org/springblade/system/pojo/entity/UserDept.java b/blade-service-api/blade-user-api/src/main/java/org/springblade/system/pojo/entity/UserDept.java
index 3ccc986..ff76ad7 100644
--- a/blade-service-api/blade-user-api/src/main/java/org/springblade/system/pojo/entity/UserDept.java
+++ b/blade-service-api/blade-user-api/src/main/java/org/springblade/system/pojo/entity/UserDept.java
@@ -36,6 +36,7 @@ import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
+import java.util.Date;
/**
* 实体类
@@ -85,4 +86,45 @@ public class UserDept implements Serializable {
@Schema(description = "是否已删除")
private Integer isDeleted;
+
+ /**
+ * 公司编码
+ */
+ @Schema(description = "公司编码")
+ private String companyCode;
+ /**
+ * 公司名
+ */
+ @Schema(description = "公司名")
+ private String companyName;
+ /**
+ * 部门编码
+ */
+ @Schema(description = "部门编码")
+ private String deptCode;
+ /**
+ * 部门名
+ */
+ @Schema(description = "部门名")
+ private String deptName;
+ /**
+ * 排序
+ */
+ @Schema(description = "排序")
+ private Integer sort;
+ /**
+ * oaid
+ */
+ @Schema(description = "oaid")
+ private String oaId;
+ /**
+ * oa的loginId
+ */
+ @Schema(description = "oa的loginId")
+ private String oaLoginId;
+ /**
+ * 同步时间
+ */
+ @Schema(description = "同步时间")
+ private Date syncTime;
}
diff --git a/blade-service-api/blade-user-api/src/main/java/org/springblade/system/pojo/vo/UserListVO.java b/blade-service-api/blade-user-api/src/main/java/org/springblade/system/pojo/vo/UserListVO.java
new file mode 100644
index 0000000..680eeda
--- /dev/null
+++ b/blade-service-api/blade-user-api/src/main/java/org/springblade/system/pojo/vo/UserListVO.java
@@ -0,0 +1,38 @@
+package org.springblade.system.pojo.vo;
+
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * @author bfhuange
+ * @date 2024/8/28
+ */
+@Data
+@Schema(description = "UserListVO对象")
+public class UserListVO implements Serializable {
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 主键ID
+ */
+ @JsonSerialize(using = ToStringSerializer.class)
+ private Long id;
+ /**
+ * 用户编号
+ */
+ private String code;
+ /**
+ * 昵称
+ */
+ private String name;
+ /**
+ * 手机
+ */
+ private String phone;
+}
diff --git a/blade-service/blade-system/pom.xml b/blade-service/blade-system/pom.xml
index 12b0c16..87f4b33 100644
--- a/blade-service/blade-system/pom.xml
+++ b/blade-service/blade-system/pom.xml
@@ -38,6 +38,33 @@
org.springblade
blade-user-api
+
+
+ org.springblade
+ blade-oa-api
+
+
+ org.springblade
+ blade-mk-api
+
+
+
+ org.mapstruct
+ mapstruct
+
+
+
+ org.springblade
+ blade-starter-powerjob
+
+
+
+
+ org.springblade
+ blade-core-oauth2
+
+
+
diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/controller/MKPushController.java b/blade-service/blade-system/src/main/java/org/springblade/system/controller/MKPushController.java
new file mode 100644
index 0000000..42fe7ae
--- /dev/null
+++ b/blade-service/blade-system/src/main/java/org/springblade/system/controller/MKPushController.java
@@ -0,0 +1,50 @@
+package org.springblade.system.controller;
+
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.RequiredArgsConstructor;
+import org.springblade.core.secure.annotation.PreAuth;
+import org.springblade.core.tool.api.R;
+import org.springblade.core.tool.constant.RoleConstant;
+import org.springblade.system.service.IMKPushService;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * mk数据推送
+ * @author bfhuange
+ * @date 2024/9/24
+ */
+@Tag(name = "mk数据推送", description = "mk数据推送")
+@RequestMapping("/mk")
+@RestController
+@RequiredArgsConstructor
+public class MKPushController {
+
+ private final IMKPushService mkPushService;
+
+ /**
+ * 推送公司和部门
+ * @return
+ */
+ @Operation(summary = "推送公司和部门")
+ @PostMapping("/pushCompanyAndDept")
+ @PreAuth(RoleConstant.HAS_ROLE_ADMIN)
+ public R pushCompanyAndDept() {
+ mkPushService.pushOrgAndRecord(null);
+ return R.data(true);
+ }
+
+ /**
+ * 推送人员
+ * @return
+ */
+ @Operation(summary = "推送人员")
+ @PostMapping("/pushPerson")
+ @PreAuth(RoleConstant.HAS_ROLE_ADMIN)
+ public R pushPerson() {
+ mkPushService.pushPersonAndRecord(null);
+ return R.data(true);
+ }
+}
diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/controller/OAController.java b/blade-service/blade-system/src/main/java/org/springblade/system/controller/OAController.java
new file mode 100644
index 0000000..c862504
--- /dev/null
+++ b/blade-service/blade-system/src/main/java/org/springblade/system/controller/OAController.java
@@ -0,0 +1,78 @@
+package org.springblade.system.controller;
+
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.RequiredArgsConstructor;
+import org.springblade.core.log.annotation.ApiLog;
+import org.springblade.core.secure.annotation.PreAuth;
+import org.springblade.core.tool.api.R;
+import org.springblade.system.service.IOASyncService;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * oa数据同步
+ * @author bfhuange
+ * @date 2024/8/23
+ */
+@Tag(name = "oa数据同步", description = "oa数据同步")
+@RequestMapping("/oa")
+@RestController
+@RequiredArgsConstructor
+public class OAController {
+
+ private final IOASyncService oaService;
+
+ /**
+ * 增量同步公司和部门并推送MK
+ * @return
+ */
+ @ApiLog("系统管理-机构管理-增量同步")
+ @Operation(summary = "增量同步公司和部门并推送MK")
+ @PostMapping("/syncCompanyAndDeptAndPushMK")
+ @PreAuth("hasPermission('oasync:org:inc')")
+ public R syncCompanyAndDeptAndPushMK() {
+ oaService.syncCompanyAndDeptAndPushMK(false);
+ return R.data(true);
+ }
+
+ /**
+ * 全量同步公司和部门并推送MK
+ * @return
+ */
+ @ApiLog("系统管理-机构管理-全量同步")
+ @Operation(summary = "全量同步公司和部门并推送MK")
+ @PostMapping("/syncAllCompanyAndDeptAndPushMK")
+ @PreAuth("hasPermission('oasync:org:all')")
+ public R syncAllCompanyAndDeptAndPushMK() {
+ oaService.syncCompanyAndDeptAndPushMK(true);
+ return R.data(true);
+ }
+
+ /**
+ * 增量同步人员并推送MK
+ * @return
+ */
+ @ApiLog("系统管理-用户管理-增量同步")
+ @Operation(summary = "增量同步人员并推送MK")
+ @PostMapping("/syncPersonAndPushMK")
+ @PreAuth("hasPermission('oasync:person:inc')")
+ public R syncPersonAndPushMK() {
+ oaService.syncPersonAndPushMK(false);
+ return R.data(true);
+ }
+
+ /**
+ * 全量同步人员并推送MK
+ * @return
+ */
+ @ApiLog("系统管理-用户管理-全量同步")
+ @Operation(summary = "全量同步人员并推送MK")
+ @PostMapping("/syncAllPersonAndPushMK")
+ @PreAuth("hasPermission('oasync:person:all')")
+ public R syncAllPersonAndPushMK() {
+ oaService.syncPersonAndPushMK(true);
+ return R.data(true);
+ }
+}
diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/convert/DeptConvert.java b/blade-service/blade-system/src/main/java/org/springblade/system/convert/DeptConvert.java
new file mode 100644
index 0000000..3199b7e
--- /dev/null
+++ b/blade-service/blade-system/src/main/java/org/springblade/system/convert/DeptConvert.java
@@ -0,0 +1,142 @@
+package org.springblade.system.convert;
+
+import org.mapstruct.*;
+import org.springblade.common.constant.CommonConstant;
+import org.springblade.common.constant.DataStatusEnum;
+import org.springblade.system.pojo.entity.Dept;
+import org.springblade.system.pojo.enums.DeptCategory;
+import org.springblade.system.pojo.vo.DeptListVO;
+import org.springblade.thirdparty.mk.constant.MKConstant;
+import org.springblade.thirdparty.mk.pojo.dto.MKPushOrgDeptDTO;
+import org.springblade.thirdparty.oa.constant.OAConstant;
+import org.springblade.thirdparty.oa.constant.OAConvertConstant;
+import org.springblade.thirdparty.oa.pojo.response.OACompanyResponse;
+import org.springblade.thirdparty.oa.pojo.response.OADepartmentResponse;
+import org.springblade.thirdparty.oa.util.OAUtils;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * oa公司、部门到本系统部门转换工具
+ * @author bfhuange
+ * @date 2024/8/26
+ */
+@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, unmappedTargetPolicy = ReportingPolicy.IGNORE)
+public interface DeptConvert {
+
+ DeptListVO entity2vo(Dept entity);
+
+ List entities2vos(List entities);
+
+ @Mapping(source = "deptCode", target = "fdId")
+ @Mapping(source = "deptName", target = "fdName")
+ @Mapping(source = "deptCode", target = "fdNo")
+ @Mapping(source = "sort", target = "fdOrder")
+ @Mapping(source = "status", target = "fdIsAvailable", qualifiedByName = "fdIsAvailable")
+ @Mapping(source = "deptCategory", target = "fdOrgType", qualifiedByName = "fdOrgType")
+ @Mapping(source = "deptCategory", target = "fdSourceSystem", qualifiedByName = "fdSourceSystem")
+ @Mapping(source = "parentCode", target = "fdParent")
+ MKPushOrgDeptDTO entity2dto(Dept entity);
+
+ List entities2dtos(List entities);
+
+ @Mapping(source = "subcompanyname", target = "deptName")
+ @Mapping(source = "subcompanydesc", target = "fullName")
+ // 父公司id转为父部门编码,要拼接前缀
+ @Mapping(source = "supsubcomid", target = "parentCode")
+ @Mapping(source = "created", target = "createTime", dateFormat = "yyyy-MM-dd HH:mm:ss")
+ @Mapping(source = "modified", target = "updateTime", dateFormat = "yyyy-MM-dd HH:mm:ss")
+ Dept baseConvert(OACompanyResponse company);
+
+ @Mapping(source = "departmentname", target = "deptName")
+ @Mapping(source = "departmentmark", target = "fullName")
+ // 父部门id转为父部门编码,要拼接前缀
+ @Mapping(source = "supdepid", target = "parentCode")
+ // 所属子公司id转为所属公司编码,要拼接前缀
+ @Mapping(source = "subcompanyid1", target = "belongCompanyCode")
+ @Mapping(source = "created", target = "createTime", dateFormat = "yyyy-MM-dd HH:mm:ss")
+ @Mapping(source = "modified", target = "updateTime", dateFormat = "yyyy-MM-dd HH:mm:ss")
+ Dept baseConvert(OADepartmentResponse department);
+
+ /**
+ * oa公司转本系统部门
+ * @param company
+ * @return
+ */
+ default Dept company2dept(OACompanyResponse company) {
+ Dept dept = this.baseConvert(company);
+ // 部门编码添加前缀
+ dept.setDeptCode(OAConvertConstant.COMPANY_OA_PREFIX + company.getId());
+ String parentCode = dept.getParentCode();
+ if (!OAConstant.ROOT_COMPANY_ID.equals(parentCode)) {
+ // 父部门编码不是0,拼接前缀
+ dept.setParentCode(OAConvertConstant.COMPANY_OA_PREFIX + parentCode);
+ // 父部门id后续处理
+ } else {
+ // 父部门编码是0,父id也设置为0
+ dept.setParentId(OAConvertConstant.ROOT_PARENT_ID);
+ }
+ // 公司所属公司编码就是他自己的部门编码
+ dept.setBelongCompanyCode(dept.getDeptCode());
+ // 显示顺序处理
+ dept.setSort(OAUtils.parseInt(company.getShoworder()));
+ // 部门类型,公司
+ dept.setDeptCategory(DeptCategory.COMPANY.getCode());
+ dept.setSyncTime(new Date());
+ // 是oa的
+ dept.setIsOa(CommonConstant.YES);
+ return dept;
+ }
+
+ /**
+ * oa部门转本系统部门
+ * @param oaDept
+ * @param companyDeptMap 所有公司的部门编码与id map
+ * @return
+ */
+ default Dept dept2dept(OADepartmentResponse oaDept, Map companyDeptMap) {
+ Dept dept = this.baseConvert(oaDept);
+ // 部门编码添加前缀
+ dept.setDeptCode(OAConvertConstant.DEPARTMENT_OA_PREFIX + oaDept.getId());
+ // 部门所属公司编码就是他所属公司编码加前缀
+ dept.setBelongCompanyCode(OAConvertConstant.COMPANY_OA_PREFIX + dept.getBelongCompanyCode());
+ String parentCode = dept.getParentCode();
+ if (!OAConstant.ROOT_COMPANY_ID.equals(parentCode)) {
+ // 父部门编码不是0,拼接前缀
+ dept.setParentCode(OAConvertConstant.DEPARTMENT_OA_PREFIX + parentCode);
+ // 父部门id后续处理
+ } else {
+ // 父部门编码是0,设置为公司编码
+ dept.setParentCode(dept.getBelongCompanyCode());
+ // 根据父部门编码获取对应的id
+ dept.setParentId(companyDeptMap.get(dept.getParentCode()));
+ }
+
+ // 显示顺序处理
+ dept.setSort(OAUtils.parseInt(oaDept.getShoworder()));
+ // 部门类型,部门
+ dept.setDeptCategory(DeptCategory.DEPT.getCode());
+ dept.setSyncTime(new Date());
+ // 是oa的
+ dept.setIsOa(CommonConstant.YES);
+ return dept;
+ }
+
+ @Named("fdIsAvailable")
+ default Boolean fdIsAvailable(Integer status) {
+ return DataStatusEnum.ENABLE.getCode().equals(status);
+ }
+
+ @Named("fdOrgType")
+ default String fdOrgType(Integer deptCategory) {
+ return DeptCategory.COMPANY.getCode().equals(deptCategory) ? MKConstant.ORG_TYPE_ORG : MKConstant.ORG_TYPE_DEPT;
+ }
+
+ @Named("fdSourceSystem")
+ default String fdSourceSystem(Integer deptCategory) {
+ return CommonConstant.SYSTEM_CODE;
+ }
+
+}
diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/convert/UserConvert.java b/blade-service/blade-system/src/main/java/org/springblade/system/convert/UserConvert.java
new file mode 100644
index 0000000..924daee
--- /dev/null
+++ b/blade-service/blade-system/src/main/java/org/springblade/system/convert/UserConvert.java
@@ -0,0 +1,200 @@
+package org.springblade.system.convert;
+
+import org.apache.commons.lang3.StringUtils;
+import org.mapstruct.*;
+import org.springblade.common.constant.CommonConstant;
+import org.springblade.common.constant.DataStatusEnum;
+import org.springblade.core.tool.utils.CollectionUtil;
+import org.springblade.core.tool.utils.Func;
+import org.springblade.system.pojo.entity.User;
+import org.springblade.system.pojo.entity.UserDept;
+import org.springblade.system.pojo.enums.UserSex;
+import org.springblade.system.pojo.enums.UserType;
+import org.springblade.system.pojo.vo.UserListVO;
+import org.springblade.thirdparty.mk.constant.MKConstant;
+import org.springblade.thirdparty.mk.pojo.dto.MKAuxiliaryDeptDTO;
+import org.springblade.thirdparty.mk.pojo.dto.MKPushPersonDTO;
+import org.springblade.thirdparty.oa.constant.OAConvertConstant;
+import org.springblade.thirdparty.oa.pojo.response.OAPersonResponse;
+import org.springblade.thirdparty.oa.util.OAUtils;
+
+import java.util.*;
+
+/**
+ * @author bfhuange
+ * @date 2024/8/26
+ */
+@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, unmappedTargetPolicy = ReportingPolicy.IGNORE)
+public interface UserConvert {
+
+ List entities2vos(List list);
+
+ default List entities2dtos(List records, String defaultPassword) {
+ if (CollectionUtil.isEmpty(records)) {
+ return Collections.emptyList();
+ }
+ return records.stream()
+ .map(user -> {
+ MKPushPersonDTO personDTO = entity2dto(user);
+ if (personDTO == null) {
+ return null;
+ }
+ if (CommonConstant.NO.equals(user.getIsPushMk())) {
+ // 未推送mk的才设置默认密码
+ personDTO.setFdPassword(defaultPassword);
+ }
+ return personDTO;
+ }).toList();
+ }
+
+ @Mapping(source = "account", target = "fdId")
+ @Mapping(source = "account", target = "fdLoginName")
+ @Mapping(source = "name", target = "fdName")
+ @Mapping(source = "phone", target = "fdMobileNo")
+ @Mapping(source = "code", target = "fdNo")
+ @Mapping(source = "sex", target = "fdSex", qualifiedByName = "fdSex")
+ @Mapping(source = "status", target = "fdIsAvailable", qualifiedByName = "fdIsAvailable")
+ @Mapping(source = "account", target = "fdSourceSystem", qualifiedByName = "fdSourceSystem")
+ @Mapping(source = "deptCodes", target = "fdParent", qualifiedByName = "fdParent")
+ @Mapping(source = "deptCodes", target = "fdAuxiliaryDept", qualifiedByName = "fdAuxiliaryDept")
+ MKPushPersonDTO entity2dto(User entity);
+
+ @Mapping(target = "id", ignore = true)
+ @Mapping(target = "status", ignore = true)
+ @Mapping(target = "password", ignore = true)
+ @Mapping(target = "birthday", ignore = true)
+ @Mapping(target = "sex", ignore = true)
+ @Mapping(source = "workcode", target = "code")
+ @Mapping(source = "lastname", target = "name")
+ @Mapping(source = "lastname", target = "realName")
+ @Mapping(source = "mobile", target = "phone")
+ @Mapping(source = "mobile", target = "account")
+ @Mapping(source = "email", target = "email")
+ User baseConvert(OAPersonResponse person);
+
+ @Mapping(source = "subcompanyname", target = "companyName")
+ @Mapping(source = "departmentname", target = "deptName")
+ @Mapping(source = "id", target = "oaId")
+ @Mapping(source = "loginid", target = "oaLoginId")
+ UserDept userDeptConvert(OAPersonResponse person);
+
+ /**
+ * oa人员转本系统用户
+ * @param person
+ * @param defaultPassword 默认密码
+ * @return
+ */
+ default User person2user(OAPersonResponse person, String defaultPassword) {
+ User user = baseConvert(person);
+
+ // 密码
+ user.setPassword(defaultPassword);
+ // 性别
+ user.setSex(UserSex.getCodeByName(person.getSex()));
+ // 状态转换
+ Integer status = getStatus(person);
+ user.setStatus(status);
+ // web平台
+ user.setUserType(UserType.WEB.getCategory());
+ user.setSyncTime(new Date());
+ // 从oa同步的
+ user.setIsOa(CommonConstant.YES);
+ return user;
+ }
+
+ /**
+ * oa人员转本系统用户部门
+ * @param person
+ * @param userMap 所有用户手机号与id map
+ * @return
+ */
+ default UserDept person2userDept(OAPersonResponse person, Map userMap) {
+ UserDept userDept = userDeptConvert(person);
+ // 公司编码添加前缀
+ userDept.setCompanyCode(OAConvertConstant.COMPANY_OA_PREFIX + person.getSubcompanyid1());
+ // 部门编码添加前缀
+ userDept.setDeptCode(OAConvertConstant.DEPARTMENT_OA_PREFIX + person.getDepartmentid());
+ // 状态转换
+ Integer status = getStatus(person);
+ userDept.setStatus(status);
+ // 排序
+ userDept.setSort(OAUtils.parseInt(person.getDsporder()));
+ // 用户id
+ userDept.setUserId(userMap.get(person.getMobile()));
+ userDept.setSyncTime(new Date());
+ return userDept;
+ }
+
+ /**
+ * 转换状态
+ * @param person
+ * @return
+ */
+ private Integer getStatus(OAPersonResponse person) {
+ // 过滤状态 0-试用 1-正式 2-临时 3-试用延期 4-解聘 5-离职 6-退休 7-无效
+ return switch (person.getStatus()) {
+ case "0", "1", "2", "3" -> DataStatusEnum.ENABLE.getCode();
+ default -> DataStatusEnum.DISABLE.getCode();
+ };
+ }
+
+ @Named("fdIsAvailable")
+ default Boolean fdIsAvailable(Integer status) {
+ return DataStatusEnum.ENABLE.getCode().equals(status);
+ }
+
+ @Named("fdSourceSystem")
+ default String fdSourceSystem(String account) {
+ return CommonConstant.SYSTEM_CODE;
+ }
+
+ @Named("fdSex")
+ default String fdSex(Integer sex) {
+ if (sex == null) {
+ return null;
+ }
+ if (UserSex.MAN.getCode().equals(sex)) {
+ return MKConstant.SEX_MAN;
+ }
+ if (UserSex.WOMAN.getCode().equals(sex)) {
+ return MKConstant.SEX_WOMAN;
+ }
+ return MKConstant.SEX_UNKONW;
+ }
+
+ @Named("fdParent")
+ default String fdParent(String deptCodes) {
+ if (StringUtils.isBlank(deptCodes)) {
+ return null;
+ }
+ // 第一个部门编码作为主部门id
+ return Func.toStrList(deptCodes).stream()
+ // 非外部组织编码
+ .filter(deptCode -> !deptCode.startsWith(OAConvertConstant.COMPANY_EX_PREFIX) && !deptCode.startsWith(OAConvertConstant.DEPARTMENT_EX_PREFIX))
+ .findFirst()
+ .orElse(null);
+ }
+
+ @Named("fdAuxiliaryDept")
+ default List fdAuxiliaryDept(String deptCodes) {
+ if (StringUtils.isBlank(deptCodes)) {
+ return null;
+ }
+ List deptCodeList = Func.toStrList(deptCodes).stream()
+ // 非外部组织编码
+ .filter(deptCode -> !deptCode.startsWith(OAConvertConstant.COMPANY_EX_PREFIX) && !deptCode.startsWith(OAConvertConstant.DEPARTMENT_EX_PREFIX))
+ .toList();
+ if (deptCodeList.size() <= 1) {
+ return null;
+ }
+ deptCodeList = new ArrayList<>(deptCodeList);
+ // 移除第一个部门编码,其他的作为辅部门
+ deptCodeList.remove(0);
+ return deptCodeList.stream()
+ .map(deptCode -> {
+ MKAuxiliaryDeptDTO deptDTO = new MKAuxiliaryDeptDTO();
+ deptDTO.setFdId(deptCode);
+ return deptDTO;
+ }).toList();
+ }
+}
diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/log/ComposeLogUtil.java b/blade-service/blade-system/src/main/java/org/springblade/system/log/ComposeLogUtil.java
new file mode 100644
index 0000000..92f9eb2
--- /dev/null
+++ b/blade-service/blade-system/src/main/java/org/springblade/system/log/ComposeLogUtil.java
@@ -0,0 +1,89 @@
+package org.springblade.system.log;
+
+import lombok.extern.slf4j.Slf4j;
+import org.slf4j.Logger;
+import tech.powerjob.worker.log.OmsLogger;
+
+import java.util.LinkedList;
+
+/**
+ * 日志工具类
+ * @author bfhuange
+ * @date 2024/9/25
+ */
+@Slf4j
+public class ComposeLogUtil {
+ /**
+ * oms logger
+ */
+ private static final ThreadLocal OMS_LOGGER_CONTEXT = new ThreadLocal<>();
+ /**
+ * 组合 logger
+ */
+ private static final ThreadLocal> CONTEXT = new ThreadLocal<>();
+ /**
+ * 默认日志记录器
+ */
+ private static final ComposeLogger DEFAULT = new ComposeLogger(log, null);
+ private ComposeLogUtil() {}
+
+ /**
+ * 设置oms logger
+ * @param omsLogger
+ */
+ public static void setOmsLogger(OmsLogger omsLogger) {
+ OMS_LOGGER_CONTEXT.set(omsLogger);
+ }
+
+ /**
+ * 获取oms logger
+ * @return
+ */
+ public static OmsLogger getOmsLogger() {
+ return OMS_LOGGER_CONTEXT.get();
+ }
+
+ /**
+ * 移除oms logger
+ */
+ public static void removeOmsLogger() {
+ OMS_LOGGER_CONTEXT.remove();
+ }
+
+ /**
+ * 添加日志记录器
+ * @param log
+ */
+ public static void addLog(Logger log) {
+ LinkedList list = CONTEXT.get();
+ if (list == null) {
+ list = new LinkedList<>();
+ CONTEXT.set(list);
+ }
+ ComposeLogger omsLog = new ComposeLogger(log, ComposeLogUtil.getOmsLogger());
+ list.addLast(omsLog);
+ }
+
+ /**
+ * 获取最后一个日志记录器
+ * @return
+ */
+ public static ComposeLogger getLastLog() {
+ LinkedList list = CONTEXT.get();
+ if (list == null || list.isEmpty()) {
+ return DEFAULT;
+ }
+ return list.getLast();
+ }
+
+ /**
+ * 清除最后一个日志记录器
+ */
+ public static void removeLastLog() {
+ LinkedList list = CONTEXT.get();
+ if (list == null || list.isEmpty()) {
+ return;
+ }
+ list.removeLast();
+ }
+}
diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/log/ComposeLogger.java b/blade-service/blade-system/src/main/java/org/springblade/system/log/ComposeLogger.java
new file mode 100644
index 0000000..b822e81
--- /dev/null
+++ b/blade-service/blade-system/src/main/java/org/springblade/system/log/ComposeLogger.java
@@ -0,0 +1,48 @@
+package org.springblade.system.log;
+
+import lombok.RequiredArgsConstructor;
+import org.slf4j.Logger;
+import tech.powerjob.worker.log.OmsLogger;
+
+/**
+ * 组合日志,兼容 powerjob 日志
+ * @author bfhuange
+ * @date 2024/8/30
+ */
+@RequiredArgsConstructor
+public class ComposeLogger implements OmsLogger {
+ private final Logger log;
+ private final OmsLogger omsLog;
+
+ @Override
+ public void debug(String s, Object... objects) {
+ log.debug(s, objects);
+ if (omsLog != null) {
+ omsLog.debug(s, objects);
+ }
+ }
+
+ @Override
+ public void info(String s, Object... objects) {
+ log.info(s, objects);
+ if (omsLog != null) {
+ omsLog.info(s, objects);
+ }
+ }
+
+ @Override
+ public void warn(String s, Object... objects) {
+ log.warn(s, objects);
+ if (omsLog != null) {
+ omsLog.warn(s, objects);
+ }
+ }
+
+ @Override
+ public void error(String s, Object... objects) {
+ log.error(s, objects);
+ if (omsLog != null) {
+ omsLog.error(s, objects);
+ }
+ }
+}
diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/mapper/DataSyncRecordMapper.java b/blade-service/blade-system/src/main/java/org/springblade/system/mapper/DataSyncRecordMapper.java
new file mode 100644
index 0000000..ba8e43e
--- /dev/null
+++ b/blade-service/blade-system/src/main/java/org/springblade/system/mapper/DataSyncRecordMapper.java
@@ -0,0 +1,13 @@
+package org.springblade.system.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.springblade.system.pojo.entity.DataSyncRecord;
+
+/**
+ * 数据同步记录表 Mapper 接口
+ *
+ * @author BladeX
+ * @since 2024-09-27
+ */
+public interface DataSyncRecordMapper extends BaseMapper {
+}
diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/mapper/DataSyncRecordMapper.xml b/blade-service/blade-system/src/main/java/org/springblade/system/mapper/DataSyncRecordMapper.xml
new file mode 100644
index 0000000..1293836
--- /dev/null
+++ b/blade-service/blade-system/src/main/java/org/springblade/system/mapper/DataSyncRecordMapper.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/mapper/UserDeptMapper.java b/blade-service/blade-system/src/main/java/org/springblade/system/mapper/UserDeptMapper.java
index 14e6eec..287c9e2 100644
--- a/blade-service/blade-system/src/main/java/org/springblade/system/mapper/UserDeptMapper.java
+++ b/blade-service/blade-system/src/main/java/org/springblade/system/mapper/UserDeptMapper.java
@@ -26,7 +26,12 @@
package org.springblade.system.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Param;
import org.springblade.system.pojo.entity.UserDept;
+import org.springblade.system.pojo.vo.UserDeptIdsVO;
+
+import java.util.Collection;
+import java.util.List;
/**
* Mapper 接口
@@ -35,5 +40,11 @@ import org.springblade.system.pojo.entity.UserDept;
*/
public interface UserDeptMapper extends BaseMapper {
+ /**
+ * 根据用户id列表查询对应的部门id列表拼接的字符串
+ * @param userIds
+ * @return
+ */
+ List queryUserDeptIds(@Param("userIds") Collection userIds);
}
diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/mapper/UserDeptMapper.xml b/blade-service/blade-system/src/main/java/org/springblade/system/mapper/UserDeptMapper.xml
index 843e083..03182d8 100644
--- a/blade-service/blade-system/src/main/java/org/springblade/system/mapper/UserDeptMapper.xml
+++ b/blade-service/blade-system/src/main/java/org/springblade/system/mapper/UserDeptMapper.xml
@@ -2,4 +2,19 @@
+
+
diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/IDataSyncRecordService.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/IDataSyncRecordService.java
new file mode 100644
index 0000000..92b5f4c
--- /dev/null
+++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/IDataSyncRecordService.java
@@ -0,0 +1,26 @@
+package org.springblade.system.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import org.springblade.system.pojo.entity.DataSyncRecord;
+
+/**
+ * 数据同步记录表 服务类
+ *
+ * @author BladeX
+ * @since 2024-09-27
+ */
+public interface IDataSyncRecordService extends IService {
+
+ /**
+ * 使用新事物提交
+ * @param entity
+ */
+ void saveWithNewTransaction(DataSyncRecord entity);
+
+ /**
+ * 查询最后一次同步成功的记录
+ * @param param
+ * @return
+ */
+ DataSyncRecord getLastSuccessRecord(DataSyncRecord param);
+}
diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/IDeptService.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/IDeptService.java
index 022c6dc..84138bb 100644
--- a/blade-service/blade-system/src/main/java/org/springblade/system/service/IDeptService.java
+++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/IDeptService.java
@@ -30,6 +30,7 @@ import org.springblade.system.pojo.entity.Dept;
import org.springblade.system.pojo.vo.DeptVO;
import org.springblade.system.pojo.vo.UserVO;
+import java.util.Date;
import java.util.List;
import java.util.Map;
@@ -157,4 +158,11 @@ public interface IDeptService extends IService {
*/
List deptLeaderInfo(Long deptId);
+
+ /**
+ * 更新部门祖级列表
+ * @param startTime 要更新的部门的同步时间或更新时间起始
+ */
+ void updateAncestors(Date startTime);
+
}
diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/IMKPushService.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/IMKPushService.java
new file mode 100644
index 0000000..cafb90a
--- /dev/null
+++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/IMKPushService.java
@@ -0,0 +1,24 @@
+package org.springblade.system.service;
+
+import java.util.Date;
+
+/**
+ * mk 推送接口
+ * @author bfhuange
+ * @date 2024/9/24
+ */
+public interface IMKPushService {
+ /**
+ * 推送所有公司和部门并记录
+ *
+ * @param oaStartTime oa同步查询开始时间
+ */
+ void pushOrgAndRecord(Date oaStartTime);
+
+ /**
+ * 推送人员并记录
+ *
+ * @param oaStartTime oa同步查询开始时间
+ */
+ void pushPersonAndRecord(Date oaStartTime);
+}
diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/IOASyncService.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/IOASyncService.java
new file mode 100644
index 0000000..1c7c1d9
--- /dev/null
+++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/IOASyncService.java
@@ -0,0 +1,21 @@
+package org.springblade.system.service;
+
+/**
+ * oa同步接口
+ * @author bfhuange
+ * @date 2024/8/23
+ */
+public interface IOASyncService {
+
+ /**
+ * 从oa同步公司和部门并且推送到mk
+ * @param syncAll 是否同步所有
+ */
+ void syncCompanyAndDeptAndPushMK(boolean syncAll);
+
+ /**
+ * 从oa同步人员并且推送到mk
+ * @param syncAll 是否同步所有
+ */
+ void syncPersonAndPushMK(boolean syncAll);
+}
diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/IUserDeptService.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/IUserDeptService.java
index 87593ce..e3628cf 100644
--- a/blade-service/blade-system/src/main/java/org/springblade/system/service/IUserDeptService.java
+++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/IUserDeptService.java
@@ -28,6 +28,10 @@ package org.springblade.system.service;
import com.baomidou.mybatisplus.extension.service.IService;
import org.springblade.system.pojo.entity.UserDept;
+import org.springblade.system.pojo.vo.UserDeptIdsVO;
+
+import java.util.Collection;
+import java.util.List;
/**
* 服务类
@@ -36,4 +40,11 @@ import org.springblade.system.pojo.entity.UserDept;
*/
public interface IUserDeptService extends IService {
+ /**
+ * 根据用户id列表查询对应的部门id列表拼接的字符串
+ * @param userIds
+ * @return
+ */
+ List queryUserDeptIds(Collection userIds);
+
}
diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/IUserService.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/IUserService.java
index d1db254..b749959 100644
--- a/blade-service/blade-system/src/main/java/org/springblade/system/service/IUserService.java
+++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/IUserService.java
@@ -30,6 +30,7 @@ import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.springblade.core.mp.base.BaseService;
import org.springblade.core.mp.support.Query;
+import org.springblade.system.pojo.context.IPhone;
import org.springblade.system.pojo.entity.User;
import org.springblade.system.pojo.entity.UserInfo;
import org.springblade.system.pojo.entity.UserOauth;
@@ -336,4 +337,18 @@ public interface IUserService extends BaseService {
* @return 用户分页(UserVO 包含角色、部门、岗位名称)
*/
IPage userPage(IPage page, String account, String name);
+
+
+ /**
+ * 解密手机号
+ * @param user
+ */
+ void decryptPhone(IPhone user);
+
+ /**
+ * 加密手机号
+ * @param user
+ */
+ void encryptPhone(IPhone user);
+
}
diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/DataSyncRecordServiceImpl.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/DataSyncRecordServiceImpl.java
new file mode 100644
index 0000000..48ae208
--- /dev/null
+++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/DataSyncRecordServiceImpl.java
@@ -0,0 +1,42 @@
+package org.springblade.system.service.impl;
+
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springblade.system.mapper.DataSyncRecordMapper;
+import org.springblade.system.pojo.entity.DataSyncRecord;
+import org.springblade.system.pojo.enums.DataSync;
+import org.springblade.system.service.IDataSyncRecordService;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Propagation;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * 数据同步记录表 服务实现类
+ *
+ * @author BladeX
+ * @since 2024-09-27
+ */
+@Service
+public class DataSyncRecordServiceImpl extends ServiceImpl implements IDataSyncRecordService {
+
+ @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
+ @Override
+ public void saveWithNewTransaction(DataSyncRecord entity) {
+ this.save(entity);
+ }
+
+ @Override
+ public DataSyncRecord getLastSuccessRecord(DataSyncRecord param) {
+ // 根据系统编码,数据类型,模式 查询最新一条成功的记录
+ return this.getOne(Wrappers.lambdaQuery()
+ .eq(DataSyncRecord::getSystemCode, param.getSystemCode())
+ .eq(DataSyncRecord::getDataType, param.getDataType())
+ .eq(DataSyncRecord::getMode, param.getMode())
+ // 只查成功的
+ .eq(DataSyncRecord::getResult, DataSync.Result.SUCCESS.name())
+ // 同步时间倒序排列
+ .orderByDesc(DataSyncRecord::getSyncTime)
+ .last("limit 1")
+ );
+ }
+}
diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/DeptServiceImpl.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/DeptServiceImpl.java
index 39a6f0b..3fba915 100644
--- a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/DeptServiceImpl.java
+++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/DeptServiceImpl.java
@@ -30,15 +30,14 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
import org.springblade.core.log.exception.ServiceException;
import org.springblade.core.mp.support.Condition;
import org.springblade.core.secure.utils.AuthUtil;
import org.springblade.core.tenant.TenantGuard;
import org.springblade.core.tool.constant.BladeConstant;
import org.springblade.core.tool.node.ForestNodeMerger;
-import org.springblade.core.tool.utils.Func;
-import org.springblade.core.tool.utils.StringPool;
-import org.springblade.core.tool.utils.StringUtil;
+import org.springblade.core.tool.utils.*;
import org.springblade.system.cache.SysCache;
import org.springblade.system.mapper.DeptMapper;
import org.springblade.system.pojo.entity.Dept;
@@ -49,12 +48,12 @@ import org.springblade.system.service.IDeptService;
import org.springblade.system.service.IUserService;
import org.springblade.system.wrapper.DeptWrapper;
import org.springblade.system.wrapper.UserWrapper;
+import org.springblade.thirdparty.oa.constant.OAConvertConstant;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
+import java.util.function.Function;
import java.util.stream.Collectors;
import static org.springblade.core.tenant.TenantGuard.EntityType.DEPT;
@@ -67,6 +66,7 @@ import static org.springblade.core.tenant.TenantGuard.EntityType.DEPT_PARENT;
*/
@Service
@RequiredArgsConstructor
+@Slf4j
public class DeptServiceImpl extends ServiceImpl implements IDeptService {
private static final String TENANT_ID = "tenantId";
@@ -265,4 +265,121 @@ public class DeptServiceImpl extends ServiceImpl implements ID
}
}
+
+ @Transactional(rollbackFor = Exception.class)
+ @Override
+ public void updateAncestors(Date startTime) {
+ // 1. 查询需要更新祖级列表的部门
+ List list = this.list(Wrappers.lambdaQuery()
+ // 祖级列表为空
+ .isNull(Dept::getAncestors)
+ // 开始时间不为空,查询同步时间大于开始时间或修改时间大于开始时间的数据
+ .or(startTime != null, wrapper -> wrapper.ge(Dept::getSyncTime, startTime))
+ .or(startTime != null, wrapper -> wrapper.ge(Dept::getUpdateTime, startTime))
+ );
+ if (CollectionUtil.isEmpty(list)) {
+ log.info("需要更新祖级列表的部门为空");
+ return;
+ }
+ // 所有公司部门列表
+ List allList = this.list().stream()
+ .map(dept -> BeanUtil.copyProperties(dept, DeptVO.class))
+ .toList();
+ // id对应的部门map
+ Map idDeptMap = allList.stream()
+ .collect(Collectors.toMap(DeptVO::getId, Function.identity(), (a, b) -> b));
+ // 获取根节点列表
+ List rootList = getRootList(allList, idDeptMap);
+ // 计算祖级列表
+ setAncestors(rootList);
+
+ // 3. 更新祖级列表
+ List updateParams = list.stream()
+ .filter(dept -> {
+ DeptVO vo = idDeptMap.get(dept.getId());
+ if (vo == null) {
+ return false;
+ }
+ if (vo.getAncestors() == null) {
+ return false;
+ }
+ // 祖级列表和数据库的不一样的才需要更新
+ return !StringUtil.equals(vo.getAncestors(), dept.getAncestors());
+ })
+ .map(dept -> {
+ Dept updateParam = new Dept();
+ updateParam.setId(dept.getId());
+ DeptVO vo = idDeptMap.get(dept.getId());
+ updateParam.setAncestors(vo.getAncestors());
+ return updateParam;
+ }).toList();
+ if (CollectionUtil.isNotEmpty(updateParams)) {
+ this.updateBatchById(updateParams);
+ }
+ }
+
+ /**
+ * 获取根节点列表
+ * @param allList
+ * @param idDeptMap
+ * @return
+ */
+ private List getRootList(List allList, Map idDeptMap) {
+ if (CollectionUtil.isEmpty(allList)) {
+ return Collections.emptyList();
+ }
+ // 根节点列表
+ List rootList = new ArrayList<>();
+ for (DeptVO vo : allList) {
+ Long parentId = vo.getParentId();
+ if (OAConvertConstant.ROOT_PARENT_ID.equals(parentId)) {
+ // 父节点是根节点,添加到根节点列表
+ rootList.add(vo);
+ continue;
+ }
+ DeptVO parent = idDeptMap.get(parentId);
+ if (parent != null) {
+ // 父节点存在,放入父节点的子节点列表中
+ if (parent.getChildren() == null) {
+ parent.setChildren(new ArrayList<>());
+ }
+ parent.getChildren().add(vo);
+ }
+ }
+ return rootList;
+ }
+
+ /**
+ * 计算祖级列表
+ * @param rootList 根节点列表
+ */
+ private void setAncestors(List rootList) {
+ if (CollectionUtil.isEmpty(rootList)) {
+ return;
+ }
+ // 根节点的祖级列表是 0, 最后拼接一个逗号,防止查询的时候有包含的
+ String ancestors = OAConvertConstant.ROOT_PARENT_ID + ",";
+ setAncestors(rootList, ancestors);
+ }
+
+ /**
+ * 设置祖级列表
+ * @param list 节点列表
+ * @param ancestors 祖级列表
+ */
+ private void setAncestors(List list, String ancestors) {
+ if (CollectionUtil.isEmpty(list)) {
+ return;
+ }
+ for (DeptVO vo : list) {
+ vo.setAncestors(ancestors);
+ if (CollectionUtil.isNotEmpty(vo.getChildren())) {
+ // 子节点列表不为空,设置子节点的祖级列表
+ String newAncestors = ancestors + vo.getId() + ",";
+ setAncestors(vo.getChildren(), newAncestors);
+ }
+ }
+ }
+
+
}
diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/MKPushServiceImpl.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/MKPushServiceImpl.java
new file mode 100644
index 0000000..f04b413
--- /dev/null
+++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/MKPushServiceImpl.java
@@ -0,0 +1,280 @@
+package org.springblade.system.service.impl;
+
+import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.core.lang.UUID;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springblade.common.constant.CommonConstant;
+import org.springblade.core.tool.utils.DigestUtil;
+import org.springblade.system.cache.ParamCache;
+import org.springblade.system.convert.DeptConvert;
+import org.springblade.system.convert.UserConvert;
+import org.springblade.system.log.ComposeLogUtil;
+import org.springblade.system.pojo.entity.DataSyncRecord;
+import org.springblade.system.pojo.entity.Dept;
+import org.springblade.system.pojo.entity.User;
+import org.springblade.system.pojo.enums.DataSync;
+import org.springblade.system.pojo.enums.DeptCategory;
+import org.springblade.system.service.IDataSyncRecordService;
+import org.springblade.system.service.IDeptService;
+import org.springblade.system.service.IMKPushService;
+import org.springblade.system.service.IUserService;
+import org.springblade.system.util.DataSyncRecordUtils;
+import org.springblade.thirdparty.mk.pojo.dto.MKPushDTO;
+import org.springblade.thirdparty.mk.pojo.dto.MKPushOrgDeptDTO;
+import org.springblade.thirdparty.mk.pojo.dto.MKPushPersonDTO;
+import org.springblade.thirdparty.mk.service.IMKService;
+import org.springframework.stereotype.Service;
+
+import java.util.Date;
+import java.util.List;
+import java.util.function.BiConsumer;
+import java.util.function.Supplier;
+import java.util.function.UnaryOperator;
+
+import static org.springblade.common.constant.CommonConstant.DEFAULT_PARAM_PASSWORD;
+
+/**
+ * mk 推送实现
+ * @author bfhuange
+ * @date 2024/9/24
+ */
+@Slf4j
+@RequiredArgsConstructor
+@Service
+public class MKPushServiceImpl implements IMKPushService {
+ /**
+ * 分页查询每页最大大小
+ */
+ private static final long MAX_SIZE_PER_PAGE = 1000;
+
+ private final IDeptService deptService;
+ private final IUserService userService;
+ private final IMKService mkService;
+ private final DeptConvert deptConvert;
+ private final UserConvert userConvert;
+ private final IDataSyncRecordService dataSyncRecordService;
+
+ @Override
+ public void pushOrgAndRecord(Date oaStartTime) {
+ try {
+ ComposeLogUtil.addLog(log);
+ // 推送所有公司和部门并记录
+ this.pushAndRecord(oaStartTime, DataSyncRecordUtils::createMKOrgPush, (startTime, endTime) -> {
+ // 1. 推送公司
+ pushDept(DeptCategory.COMPANY, startTime, endTime);
+ // 2. 推送部门
+ pushDept(DeptCategory.DEPT, startTime, endTime);
+ });
+ } finally {
+ ComposeLogUtil.removeLastLog();
+ }
+ }
+
+ @Override
+ public void pushPersonAndRecord(Date oaStartTime) {
+ try {
+ ComposeLogUtil.addLog(log);
+ // 推送人员并记录
+ this.pushAndRecord(oaStartTime, DataSyncRecordUtils::createMKPersonPush, (startTime, endTime) -> {
+ // 分页查询函数
+ UnaryOperator> queryUser = this.getQueryUser(startTime, endTime);
+ // 分页推送函数
+ BiConsumer, String> pushConsumer = this::pushUser;
+ // 公共推送
+ pushCommon(queryUser, pushConsumer);
+ });
+ } finally {
+ ComposeLogUtil.removeLastLog();
+ }
+ }
+
+ /**
+ * 推送并记录
+ * @param oaStartTime
+ * @param recordSupplier
+ * @param pushHandler
+ */
+ private void pushAndRecord(Date oaStartTime, Supplier recordSupplier, BiConsumer pushHandler) {
+ // 创建记录
+ DataSyncRecord record = recordSupplier.get();
+ // 查询最新一条成功记录
+ DataSyncRecord lastSuccessRecord = dataSyncRecordService.getLastSuccessRecord(record);
+ // 获取推送数据的开始时间
+ Date startTime = getStartTime(lastSuccessRecord, oaStartTime);
+ // 推送开始时间作为截止时间,只同步这个时间段的更新的数据
+ Date endTime = new Date();
+ record.setSyncTime(endTime);
+ try {
+ pushHandler.accept(startTime, endTime);
+ } catch (Exception e) {
+ // 有异常,结果改成失败
+ record.setResult(DataSync.Result.FAIL.name());
+ throw e;
+ } finally {
+ // 新事务保存同步记录
+ dataSyncRecordService.saveWithNewTransaction(record);
+ }
+ }
+
+ /**
+ * 获取推送开始时间
+ * @param lastSuccessRecord 最后一条同步成功的记录
+ * @param oaStartTime oa同步查询开始时间
+ * @return
+ */
+ private Date getStartTime(DataSyncRecord lastSuccessRecord, Date oaStartTime) {
+ if (lastSuccessRecord == null || lastSuccessRecord.getSyncTime() == null) {
+ // 最后一条推送成功记录为空,返回空推送全部
+ return null;
+ }
+ if (oaStartTime == null) {
+ // oa 同步查询开始时间为空,且最后一条推送成功记录不为空
+ return lastSuccessRecord.getSyncTime();
+ }
+ // 否则取2个时间较小的那个
+ Date lastSyncTime = lastSuccessRecord.getSyncTime();
+ return oaStartTime.before(lastSyncTime) ? oaStartTime : lastSyncTime;
+ }
+
+ /**
+ * 同步公司/部门
+ *
+ * @param deptCategory 类型
+ * @param startTime 数据更新开始时间
+ * @param endTime 数据更新截止时间
+ */
+ private void pushDept(DeptCategory deptCategory, Date startTime, Date endTime) {
+ // 分页查询函数
+ UnaryOperator> queryDept = getQueryDept(deptCategory, startTime, endTime);
+ // 推送函数
+ BiConsumer, String> pushDept = getPushDept(deptCategory);
+ // 公共推送
+ pushCommon(queryDept, pushDept);
+ }
+
+ /**
+ * 获取查询部门的方法
+ *
+ * @param deptCategory
+ * @param startTime
+ * @param endTime
+ * @return
+ */
+ private UnaryOperator> getQueryDept(DeptCategory deptCategory, Date startTime, Date endTime) {
+ return page -> deptService.page(page, Wrappers.lambdaQuery()
+ .eq(Dept::getDeptCategory, deptCategory.getCode())
+ .eq(Dept::getIsOa, CommonConstant.YES)
+ // 查询同步时间小于结束时间且修改时间小于结束时间的部门
+ .lt(Dept::getSyncTime, endTime)
+ .lt(Dept::getUpdateTime, endTime)
+ // 开始时间不为空,查询同步时间大于开始时间或修改时间大于开始时间的数据
+ .and(startTime != null, wrapper -> wrapper.ge(Dept::getSyncTime, startTime)
+ .or(w -> w.ge(Dept::getUpdateTime, startTime)))
+ );
+ }
+
+ /**
+ * 获取推送部门的方法
+ * @param deptCategory
+ * @return
+ */
+ private BiConsumer, String> getPushDept(DeptCategory deptCategory) {
+ return (page, batchId) -> {
+ List records = page.getRecords();
+ if (CollectionUtil.isEmpty(records)) {
+ return;
+ }
+ // 当前页是否最后一页
+ boolean lastPage = page.getCurrent() == page.getPages();
+ MKPushDTO param = new MKPushDTO<>();
+ param.setBatchId(batchId);
+ param.setFinish(lastPage);
+ List list = deptConvert.entities2dtos(records);
+ param.setMessage(list);
+ // 推送公司
+ boolean result = mkService.pushOrgDept(param);
+ ComposeLogUtil.getLastLog().info("mk推送{} 批次号:{} 当前页:{} 当前页数量:{} 结果:{}", deptCategory.getName(), batchId, page.getCurrent(), page.getSize(), result);
+ };
+ }
+
+
+ /**
+ * 获取查询人员的方法
+ *
+ * @param startTime 开始时间
+ * @param endTime 结束时间
+ * @return
+ */
+ private UnaryOperator> getQueryUser(Date startTime, Date endTime) {
+ return page -> userService.page(page, Wrappers.lambdaQuery()
+ .eq(User::getIsOa, CommonConstant.YES)
+ // 查询同步时间小于结束时间且修改时间小于结束时间的用户
+ .lt(User::getSyncTime, endTime)
+ .lt(User::getUpdateTime, endTime)
+ // 开始时间不为空,查询同步时间大于更新时间或修改时间大于更新时间的数据
+ .and(startTime != null, wrapper -> wrapper.ge(User::getSyncTime, startTime)
+ .or(w -> w.ge(User::getUpdateTime, startTime)))
+ );
+ }
+
+ /**
+ * 推送人员
+ * @param page
+ * @param batchId
+ */
+ private void pushUser(IPage page, String batchId) {
+ List records = page.getRecords();
+ if (CollectionUtil.isEmpty(records)) {
+ return;
+ }
+ // 当前页是否最后一页
+ boolean lastPage = page.getCurrent() == page.getPages();
+ MKPushDTO param = new MKPushDTO<>();
+ param.setBatchId(batchId);
+ param.setFinish(lastPage);
+ // 默认密码及参数转换
+ String defaultPassword = ParamCache.getValue(DEFAULT_PARAM_PASSWORD);
+ // 解密手机号
+ records.forEach(userService::decryptPhone);
+ List list = userConvert.entities2dtos(records, DigestUtil.md5Hex(defaultPassword));
+ param.setMessage(list);
+ // 推送人员
+ boolean result = mkService.pushPerson(param);
+ ComposeLogUtil.getLastLog().info("mk推送人员 批次号:{} 当前页:{} 当前页数量:{} 结果:{}", batchId, page.getCurrent(), page.getSize(), result);
+ if (result) {
+ // 推送成功,修改用户推送状态
+ List updateIds = records.stream()
+ .map(User::getId)
+ .toList();
+ userService.lambdaUpdate()
+ // 设置是否推送mk为是
+ .set(User::getIsPushMk, CommonConstant.YES)
+ .in(User::getId, updateIds)
+ // 筛选未推送mk的
+ .eq(User::getIsPushMk, CommonConstant.NO)
+ .update();
+ }
+ }
+
+ /**
+ * 公共推送
+ * @param queryOperator 分页查询
+ * @param pushConsumer 数据推送
+ * @param
+ */
+ private void pushCommon(UnaryOperator> queryOperator, BiConsumer, String> pushConsumer) {
+ IPage page = new Page<>(1, MAX_SIZE_PER_PAGE);
+ String batchId = UUID.fastUUID().toString(true);
+ do {
+ // 分页查询数据
+ page = queryOperator.apply(page);
+ // 推送数据
+ pushConsumer.accept(page, batchId);
+ page.setCurrent(page.getCurrent() + 1);
+ } while (page.getCurrent() <= page.getPages());
+ }
+}
diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/OASyncServiceImpl.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/OASyncServiceImpl.java
new file mode 100644
index 0000000..38e7eb9
--- /dev/null
+++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/OASyncServiceImpl.java
@@ -0,0 +1,649 @@
+package org.springblade.system.service.impl;
+
+import cn.hutool.core.collection.CollectionUtil;
+import com.alibaba.fastjson2.JSON;
+import com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springblade.common.constant.DictTypeEnum;
+import org.springblade.core.cache.utils.CacheUtil;
+import org.springblade.core.log.exception.ServiceException;
+import org.springblade.core.tool.utils.DateUtil;
+import org.springblade.core.tool.utils.DigestUtil;
+import org.springblade.system.cache.DictCache;
+import org.springblade.system.cache.ParamCache;
+import org.springblade.system.convert.DeptConvert;
+import org.springblade.system.convert.UserConvert;
+import org.springblade.system.log.ComposeLogUtil;
+import org.springblade.system.pojo.entity.*;
+import org.springblade.system.pojo.enums.DataSync;
+import org.springblade.system.pojo.enums.DeptCategory;
+import org.springblade.system.pojo.vo.UserDeptIdsVO;
+import org.springblade.system.service.*;
+import org.springblade.system.util.DataSyncRecordUtils;
+import org.springblade.thirdparty.oa.constant.OAConstant;
+import org.springblade.thirdparty.oa.constant.OAConvertConstant;
+import org.springblade.thirdparty.oa.feign.IOAClient;
+import org.springblade.thirdparty.oa.pojo.response.OACompanyResponse;
+import org.springblade.thirdparty.oa.pojo.response.OADepartmentResponse;
+import org.springblade.thirdparty.oa.pojo.response.OAPersonResponse;
+import org.springblade.thirdparty.oa.pojo.search.OACompanySearch;
+import org.springblade.thirdparty.oa.pojo.search.OADepartmentSearch;
+import org.springblade.thirdparty.oa.pojo.search.OAPersonSearch;
+import org.springblade.thirdparty.oa.pojo.search.OASearch;
+import org.springblade.thirdparty.oa.util.OAUtils;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.*;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+
+import static org.springblade.common.constant.CommonConstant.*;
+import static org.springblade.core.cache.constant.CacheConstant.SYS_CACHE;
+import static org.springblade.core.cache.constant.CacheConstant.USER_CACHE;
+
+/**
+ * @author bfhuange
+ * @date 2024/8/23
+ */
+@Slf4j
+@RequiredArgsConstructor
+@Service
+public class OASyncServiceImpl implements IOASyncService {
+ /**
+ * 用户表部门id最大长度
+ */
+ private static final int MAX_DEPT_ID_LENGTH = 2000;
+
+ private final IOAClient oaClient;
+ private final DeptConvert deptConvert;
+ private final IDeptService deptService;
+ private final IUserService userService;
+ private final UserConvert userConvert;
+ private final IUserDeptService userDeptService;
+ private final IRoleService roleService;
+ private final IMKPushService mkPushService;
+ private final IDataSyncRecordService dataSyncRecordService;
+
+ @Transactional(rollbackFor = Exception.class)
+ @Override
+ public void syncCompanyAndDeptAndPushMK(boolean syncAll) {
+ try {
+ ComposeLogUtil.addLog(log);
+ // 1. 同步公司和部门并记录
+ Date startTime = this.syncAndRecord(DataSyncRecordUtils::createOAOrgFetch, queryStartTime -> {
+ // 1. 同步公司
+ this.syncCompany(queryStartTime);
+ // 2. 同步部门
+ this.syncDept(queryStartTime);
+ // 2. 更新部门祖级列表
+ deptService.updateAncestors(queryStartTime);
+ }, syncAll);
+ // 2. 推送mk
+ mkPushService.pushOrgAndRecord(startTime);
+ } finally {
+ ComposeLogUtil.removeLastLog();
+ }
+ }
+
+ @Transactional(rollbackFor = Exception.class)
+ @Override
+ public void syncPersonAndPushMK(boolean syncAll) {
+ try {
+ ComposeLogUtil.addLog(log);
+ // 1. 同步人员并记录
+ Date startTime = this.syncAndRecord(DataSyncRecordUtils::createOAPersonFetch, this::syncPerson, syncAll);
+ // 2. 推送mk
+ mkPushService.pushPersonAndRecord(startTime);
+ } finally {
+ ComposeLogUtil.removeLastLog();
+ }
+ }
+
+ /**
+ * 同步并记录
+ *
+ * @param recordSupplier 同步记录提供
+ * @param syncHandler 同步处理器
+ * @param syncAll 是否同步所有
+ * @return
+ */
+ private Date syncAndRecord(Supplier recordSupplier, Consumer syncHandler, boolean syncAll) {
+ // 创建记录
+ DataSyncRecord record = recordSupplier.get();
+ // 查询最新一条成功记录
+ DataSyncRecord lastSuccessRecord = syncAll ? null : dataSyncRecordService.getLastSuccessRecord(record);
+ // 同步所有,开始时间设置为null,否则最新成功记录的同步时间作为查询开始时间,第一次为null同步所有
+ Date startTime = syncAll ? null : Optional.ofNullable(lastSuccessRecord)
+ .map(DataSyncRecord::getSyncTime)
+ .orElse(null);
+ // 同步时间设置早一些,可能下次增量同步会多同步一些数据
+ // 设置晚了,增量同步可能会漏同步数据
+ record.setSyncTime(new Date());
+ try {
+ // 1. 同步处理
+ syncHandler.accept(startTime);
+ } catch (Exception e) {
+ // 有异常,结果改成失败
+ record.setResult(DataSync.Result.FAIL.name());
+ throw e;
+ } finally {
+ // 新事务保存同步记录
+ dataSyncRecordService.saveWithNewTransaction(record);
+ }
+ return startTime;
+ }
+
+ /**
+ * 同步公司
+ * @param startTime 查询开始时间
+ */
+ private void syncCompany(Date startTime) {
+ // 未处理的数据
+ List notHandleList = new ArrayList<>();
+ // 1. 设置查询参数
+ OACompanySearch companySearch = new OACompanySearch();
+ companySearch.setCurPage(1);
+ if (startTime != null) {
+ // 开始时间不为空,设置修改时间参数
+ companySearch.setModified(DateUtil.format(startTime, DateUtil.PATTERN_DATETIME));
+ }
+ // 2. 分页查询并处理数据
+ OAUtils.pageSyncHandler(companySearch, param -> oaClient.queryCompanyPage(new OASearch<>(param)), response -> {
+ ComposeLogUtil.getLastLog().error("调用OA接口查询公司信息失败 {}", JSON.toJSONString(response));
+ return new ServiceException("调用OA接口查询公司信息失败");
+ }, 10000, ComposeLogUtil.getLastLog()::info).accept(list -> {
+ // 处理数据
+ List deptList = handleCompany(list);
+ notHandleList.addAll(deptList);
+ });
+ // 3. 未处理的数据
+ if (CollectionUtil.isNotEmpty(notHandleList)) {
+ ComposeLogUtil.getLastLog().warn("同步公司,还有未处理的数据:{}", JSON.toJSONString(notHandleList));
+ }
+ // 清除部门缓存
+ CacheUtil.clear(SYS_CACHE);
+ CacheUtil.clear(SYS_CACHE, Boolean.FALSE);
+ }
+
+ /**
+ * 同步部门
+ * @param startTime 查询开始时间
+ */
+ private void syncDept(Date startTime) {
+ String subCompanyIds = getSubCompanyIds();
+ if (StringUtils.isEmpty(subCompanyIds)) {
+ ComposeLogUtil.getLastLog().warn("未查询到子公司查询参数");
+ return;
+ }
+ // 未处理的数据
+ List notHandleList = new ArrayList<>();
+ // 1. 设置查询参数
+ OADepartmentSearch departmentSearch = new OADepartmentSearch();
+ departmentSearch.setCurPage(1);
+ departmentSearch.setSubcompanyid1(subCompanyIds);
+ if (startTime != null) {
+ // 开始时间不为空,设置修改时间参数
+ departmentSearch.setModified(DateUtil.format(startTime, DateUtil.PATTERN_DATETIME));
+ }
+ // 2. 分页查询并处理数据
+ OAUtils.pageSyncHandler(departmentSearch, param -> oaClient.queryDepartmentPage(new OASearch<>(param)), response -> {
+ ComposeLogUtil.getLastLog().error("调用OA接口查询部门信息失败 {}", JSON.toJSONString(response));
+ return new ServiceException("调用OA接口查询部门信息失败");
+ }, 10000, ComposeLogUtil.getLastLog()::info).accept(list -> {
+ // 处理数据
+ List deptList = handleDept(list);
+ notHandleList.addAll(deptList);
+ });
+ // 3. 未处理的数据
+ if (CollectionUtil.isNotEmpty(notHandleList)) {
+ ComposeLogUtil.getLastLog().warn("同步部门,还有未处理的数据:{}", JSON.toJSONString(notHandleList));
+ }
+ // 清除部门缓存
+ CacheUtil.clear(SYS_CACHE);
+ CacheUtil.clear(SYS_CACHE, Boolean.FALSE);
+ }
+
+ /**
+ * 同步人员
+ * @param startTime 查询开始时间
+ */
+ private void syncPerson(Date startTime) {
+ String subCompanyIds = getSubCompanyIds();
+ if (StringUtils.isEmpty(subCompanyIds)) {
+ ComposeLogUtil.getLastLog().warn("未查询到子公司查询参数");
+ return;
+ }
+ // 1. 设置查询参数
+ OAPersonSearch personSearch = new OAPersonSearch();
+ personSearch.setCurPage(1);
+ personSearch.setSubcompanyid1(subCompanyIds);
+ if (startTime != null) {
+ // 开始时间不为空,设置修改时间参数
+ personSearch.setModified(DateUtil.format(startTime, DateUtil.PATTERN_DATETIME));
+ }
+ // 2. 分页查询并处理数据
+ OAUtils.pageSyncHandler(personSearch, param -> oaClient.queryPersonPage(new OASearch<>(param)), response -> {
+ ComposeLogUtil.getLastLog().error("调用OA接口查询人员信息失败 {}", JSON.toJSONString(response));
+ return new ServiceException("调用OA接口查询人员信息失败");
+ }, 10000, ComposeLogUtil.getLastLog()::info).accept(this::handlePerson);
+ // 清除用户缓存
+ CacheUtil.clear(USER_CACHE);
+ }
+
+ /**
+ * 处理oa人员
+ * @param oaPersons
+ */
+ private void handlePerson(List oaPersons) {
+ if (CollectionUtil.isEmpty(oaPersons)) {
+ // 数据为空,直接返回
+ return;
+ }
+ // 根公司id
+ String rootCompanyId = getRootCompanyId();
+ if (rootCompanyId == null) {
+ return;
+ }
+ // 根公司下要同步的部门id
+ Set rootCompanyDeptIds = getRootCompanyDeptIds(rootCompanyId);
+ oaPersons = oaPersons.stream()
+ // 公司不是根公司,或者部门在根公司下要同步的部门列表中
+ .filter(oaPerson -> !rootCompanyId.equals(oaPerson.getSubcompanyid1()) || rootCompanyDeptIds.contains(oaPerson.getDepartmentid()))
+ .toList();
+ // 根据手机号转成set
+ TreeSet oaPersonSet = CollectionUtil.toTreeSet(oaPersons, Comparator.comparing(OAPersonResponse::getMobile));
+ // 默认密码
+ String defaultPassword = DigestUtil.encrypt(ParamCache.getValue(DEFAULT_PARAM_PASSWORD));
+ // 转换数据
+ List users = oaPersonSet.stream()
+ // 只需要手机不为空的
+ .filter(person -> StringUtils.isNotBlank(person.getMobile()))
+ .map(person -> userConvert.person2user(person, defaultPassword))
+ .toList();
+ List phones = users.stream()
+ .map(User::getPhone)
+ .filter(StringUtils::isNotBlank)
+ .distinct()
+ .toList();
+ // 查询所有用户
+ Map userMap = userService.list(Wrappers.lambdaQuery()
+ //.eq(User::getIsDeleted, BladeConstant.DB_NOT_DELETED)
+ .in(User::getPhone, phones)
+ ).stream()
+ // 解密手机号
+ .peek(userService::decryptPhone)
+ .collect(Collectors.toMap(User::getPhone, User::getId, (a, b) -> b));
+ // 数据库存在的所有用户id
+ Set existsUserIds = new HashSet<>(userMap.values());
+ users.forEach(user -> {
+ if (userMap.containsKey(user.getPhone())) {
+ // 根据手机号获取对应的用户id
+ user.setId(userMap.get(user.getPhone()));
+ // 清空密码,不修改密码
+ user.setPassword(null);
+ } else {
+ // 没有就生成一个id
+ user.setId(DefaultIdentifierGenerator.getInstance().nextId(null));
+ userService.encryptPhone(user);
+ userMap.put(user.getPhone(), user.getId());
+ }
+ });
+
+ // 不存在的新增
+ List addUsers = users.stream()
+ .filter(user -> !existsUserIds.contains(user.getId()))
+ .toList();
+ if (CollectionUtil.isNotEmpty(addUsers)) {
+ ComposeLogUtil.getLastLog().info("批量新增用户:{}", addUsers.size());
+ userService.saveBatch(addUsers);
+ }
+ // 存在的修改
+ List updateUsers = users.stream()
+ .filter(user -> existsUserIds.contains(user.getId()))
+ .toList();
+ if (CollectionUtil.isNotEmpty(updateUsers)) {
+ ComposeLogUtil.getLastLog().info("批量修改用户:{}", updateUsers.size());
+ userService.updateBatchById(updateUsers);
+ }
+ // 没有手机号的数据 = 手机号为空的数量
+ long noPhoneNum = oaPersons.stream()
+ .map(OAPersonResponse::getMobile)
+ .filter(StringUtils::isBlank)
+ .count();
+ ComposeLogUtil.getLastLog().info("没有手机号的数据:{}", noPhoneNum);
+
+ Map userDeptMap = userDeptService.list(Wrappers.lambdaQuery()
+ .in(UserDept::getUserId, userMap.values())
+ ).stream()
+ .collect(Collectors.toMap(this::getUserDeptKey, UserDept::getId, (a, b) -> b));
+ // 数据库存在的所有用户部门id
+ Set existsUserDeptIds = new HashSet<>(userDeptMap.values());
+
+ List userDeptList = oaPersons.stream()
+ // 只要包含用户手机号的
+ .filter(oaPerson -> userMap.containsKey(oaPerson.getMobile()))
+ .map(oaPerson -> userConvert.person2userDept(oaPerson, userMap))
+ .toList();
+ userDeptList.forEach(userDept -> {
+ String userDeptKey = getUserDeptKey(userDept);
+ if (userDeptMap.containsKey(userDeptKey)) {
+ // 根据key获取用户部门id
+ userDept.setId(userDeptMap.get(userDeptKey));
+ } else {
+ // 没有就生成一个id
+ userDept.setId(DefaultIdentifierGenerator.getInstance().nextId(null));
+ }
+ });
+ // 不存在的新增
+ List addList = userDeptList.stream()
+ .filter(userDept -> !existsUserDeptIds.contains(userDept.getId()))
+ .toList();
+ if (CollectionUtil.isNotEmpty(addList)) {
+ ComposeLogUtil.getLastLog().info("批量新增用户部门:{}", addList.size());
+ userDeptService.saveBatch(addList);
+ }
+ // 存在的修改
+ List updateList = userDeptList.stream()
+ .filter(userDept -> existsUserDeptIds.contains(userDept.getId()))
+ .toList();
+ if (CollectionUtil.isNotEmpty(updateList)) {
+ ComposeLogUtil.getLastLog().info("批量修改用户部门:{}", updateList.size());
+ userDeptService.updateBatchById(updateList);
+ }
+ // 回写部门id到用户表
+ Collection userIds = userMap.values();
+ List list = userDeptService.queryUserDeptIds(userIds);
+ // 查询没有角色的用户id
+ Set noRoleUserIds = userService.list(Wrappers.lambdaQuery()
+ .in(User::getId, userIds)
+ .isNull(User::getRoleId)
+ ).stream()
+ .map(User::getId)
+ .collect(Collectors.toSet());
+ // 获取默认角色id
+ String defaultRoleId = getDefaultRoleId();
+ List updateUserParams = list.stream()
+ // 过滤掉空部门id及长度超长的
+ .filter(userDeptIds -> StringUtils.isNotBlank(userDeptIds.getDeptIds()) && userDeptIds.getDeptIds().length() <= MAX_DEPT_ID_LENGTH)
+ .map(userDeptIds -> {
+ User user = new User();
+ user.setId(userDeptIds.getUserId());
+ user.setDeptId(userDeptIds.getDeptIds());
+ user.setDeptCodes(userDeptIds.getDeptCodes());
+ if (noRoleUserIds.contains(userDeptIds.getUserId())) {
+ user.setRoleId(defaultRoleId);
+ }
+ return user;
+ }).toList();
+ userService.updateBatchById(updateUserParams);
+ }
+
+ /**
+ * 处理oa公司
+ * @param oaCompanies
+ * @return 未处理的数据
+ */
+ private List handleCompany(List oaCompanies) {
+ if (CollectionUtil.isEmpty(oaCompanies)) {
+ // 数据为空,直接返回
+ return Collections.emptyList();
+ }
+ // 获取需要的公司名称
+ Set companyNames = getCompanyNames();
+ // 转换数据
+ List allParam = oaCompanies.stream()
+ // 筛选只要需要的公司
+ .filter(company -> companyNames.contains(company.getSubcompanyname()))
+ .map(deptConvert::company2dept)
+ .toList();
+
+ // 查询数据库的部门,转换成map
+ Map deptMap = getAllCompanyDeptMap();
+
+ // 处理部门
+ return handleDept(allParam, deptMap, DeptCategory.COMPANY);
+ }
+
+ /**
+ * 处理oa部门
+ * @param oaDepts
+ * @return 未处理的数据
+ */
+ private List handleDept(List oaDepts) {
+ if (CollectionUtil.isEmpty(oaDepts)) {
+ // 数据为空,直接返回
+ return Collections.emptyList();
+ }
+ // 查询所有公司的编码和id的map
+ Map companyDeptMap = getAllCompanyDeptMap();
+ // 根公司id
+ String rootCompanyId = getRootCompanyId();
+ if (rootCompanyId == null) {
+ return Collections.emptyList();
+ }
+ // 根公司下要同步的部门名称
+ Set rootCompanyDeptNames = getRootCompanyDeptNames();
+ // 转换数据
+ List allParam = oaDepts.stream()
+ // 部门的公司不是根公司,或者部门名称在根公司下需要同步的部门列表中,且是根部门
+ .filter(oaDept -> !rootCompanyId.equals(oaDept.getSubcompanyid1()) || (OAConstant.ROOT_COMPANY_ID.equals(oaDept.getSupdepid()) && rootCompanyDeptNames.contains(oaDept.getDepartmentname())))
+ .map(dept -> deptConvert.dept2dept(dept, companyDeptMap))
+ .toList();
+ if (CollectionUtil.isEmpty(allParam)) {
+ return Collections.emptyList();
+ }
+ Set deptCodes = allParam.stream()
+ .map(Dept::getDeptCode)
+ .collect(Collectors.toSet());
+ // 查询数据库的部门,转换成map
+ Map deptMap = deptService.list(Wrappers.lambdaQuery()
+ .eq(Dept::getDeptCategory, DeptCategory.DEPT.getCode())
+ .in(Dept::getDeptCode, deptCodes)
+ ).stream()
+ .filter(dept -> StringUtils.isNotEmpty(dept.getDeptCode()))
+ .collect(Collectors.toMap(Dept::getDeptCode, Dept::getId, (a, b) -> b));
+ // 处理部门
+ return this.handleDept(allParam, deptMap, DeptCategory.DEPT);
+ }
+
+ /**
+ * 处理部门
+ * @param allParam 待处理的部门
+ * @param deptMap 数据库所有部门编码和id map
+ * @return 未处理的数据
+ */
+ private List handleDept(List allParam, Map deptMap, DeptCategory deptCategory) {
+ // 数据库存在的所有部门id
+ Set existsDeptIds = new HashSet<>(deptMap.values());
+ allParam.forEach(dept -> {
+ if (deptMap.containsKey(dept.getDeptCode())) {
+ // 根据部门编码获取对应的部门id
+ dept.setId(deptMap.get(dept.getDeptCode()));
+ } else {
+ // 没有就生成一个id
+ dept.setId(DefaultIdentifierGenerator.getInstance().nextId(null));
+ deptMap.put(dept.getDeptCode(), dept.getId());
+ }
+ });
+
+ // 重新把没有父id的数据设置一下父id
+ allParam.stream()
+ .filter(dept -> dept.getParentId() == null && deptMap.containsKey(dept.getDeptCode()))
+ .forEach(dept -> dept.setParentId(deptMap.get(dept.getParentCode())));
+
+ // 父id不为空,保存数据
+ List list = allParam.stream()
+ .filter(dept -> dept.getParentId() != null)
+ .toList();
+ // 不存在的新增
+ List addList = list.stream()
+ .filter(dept -> !existsDeptIds.contains(dept.getId()))
+ .toList();
+ if (CollectionUtil.isNotEmpty(addList)) {
+ ComposeLogUtil.getLastLog().info("批量新增{}:{}", deptCategory.getName(), addList.size());
+ deptService.saveBatch(addList);
+ }
+ // 存在的修改
+ List updateList = list.stream()
+ .filter(dept -> existsDeptIds.contains(dept.getId()))
+ .toList();
+ if (CollectionUtil.isNotEmpty(updateList)) {
+ ComposeLogUtil.getLastLog().info("批量修改{}:{}", deptCategory.getName(), updateList.size());
+ deptService.updateBatchById(updateList);
+ }
+ // 返回未处理的
+ return allParam.stream()
+ .filter(dept -> dept.getParentId() == null)
+ .toList();
+ }
+
+ /**
+ * 获取oa查询参数,子公司id参数
+ * @return
+ */
+ private String getSubCompanyIds() {
+ Set companyNames = this.getCompanyNames();
+ if (companyNames.isEmpty()) {
+ return null;
+ }
+ return deptService.list(Wrappers.lambdaQuery()
+ // 根据名称查询公司
+ .in(Dept::getDeptName, companyNames)
+ .eq(Dept::getDeptCategory, DeptCategory.COMPANY.getCode())
+ ).stream()
+ // 只要部门编码不为空,且包含了oa公司前缀的
+ .filter(dept -> StringUtils.isNotBlank(dept.getDeptCode()) && dept.getDeptCode().contains(OAConvertConstant.COMPANY_OA_PREFIX))
+ // 去掉oa公司前缀
+ .map(dept -> dept.getDeptCode().replace(OAConvertConstant.COMPANY_OA_PREFIX, ""))
+ .collect(Collectors.joining(","));
+ }
+
+ /**
+ * 获取默认角色id
+ */
+ private String getDefaultRoleId() {
+ // 从参数管理查询,没有取默认值,作为角色别名,查询角色
+ String defaultRole = ParamCache.getValue(DEFAULT_PARAM_ROLE);
+ if (defaultRole == null) {
+ defaultRole = DEFAULT_ROLE;
+ }
+ List roleList = roleService.list(Wrappers.lambdaQuery()
+ .eq(Role::getRoleAlias, defaultRole)
+ );
+ if (CollectionUtil.isEmpty(roleList)) {
+ return null;
+ }
+ return roleList.get(0).getId().toString();
+ }
+
+ /**
+ * 获取所有公司的编码和id的map
+ * @return
+ */
+ private Map getAllCompanyDeptMap() {
+ return deptService.list(Wrappers.lambdaQuery()
+ .eq(Dept::getDeptCategory, DeptCategory.COMPANY.getCode())
+ ).stream()
+ .filter(dept -> StringUtils.isNotEmpty(dept.getDeptCode()))
+ .collect(Collectors.toMap(Dept::getDeptCode, Dept::getId, (a, b) -> b));
+ }
+
+ /**
+ * 获取需要的公司id
+ * @return
+ */
+ private Set getCompanyNames() {
+ List list = DictCache.getList(DictTypeEnum.SYNC_COMPANY_NAME.getType());
+ if (CollectionUtil.isEmpty(list)) {
+ ComposeLogUtil.getLastLog().warn("未查询到要同步的公司名称");
+ return Collections.emptySet();
+ }
+ return list.stream()
+ .filter(Objects::nonNull)
+ .map(Dict::getDictValue)
+ .filter(StringUtils::isNotBlank)
+ .collect(Collectors.toSet());
+ }
+
+ /**
+ * 获取根公司下需要同步的部门名称
+ * @return
+ */
+ private Set getRootCompanyDeptNames() {
+ List list = DictCache.getList(DictTypeEnum.SYNC_ROOT_COMPANY_DEPT_NAME.getType());
+ if (CollectionUtil.isEmpty(list)) {
+ ComposeLogUtil.getLastLog().warn("未查询到要同步的根公司下的部门名称");
+ return Collections.emptySet();
+ }
+ return list.stream()
+ .filter(Objects::nonNull)
+ .map(Dict::getDictValue)
+ .filter(StringUtils::isNotBlank)
+ .collect(Collectors.toSet());
+ }
+
+ /**
+ * 获取根公司下需要同步的部门id
+ * @param rootCompanyId
+ * @return
+ */
+ private Set getRootCompanyDeptIds(String rootCompanyId) {
+ Set rootCompanyDeptNames = this.getRootCompanyDeptNames();
+ if (CollectionUtil.isEmpty(rootCompanyDeptNames)) {
+ return Collections.emptySet();
+ }
+ // 查询父部门编码是根公司的,部门类型的,根公司下部门名称的部门
+ return deptService.list(Wrappers.lambdaQuery()
+ .in(Dept::getDeptName, rootCompanyDeptNames)
+ .eq(Dept::getDeptCategory, DeptCategory.DEPT.getCode())
+ .eq(Dept::getParentCode, OAConvertConstant.COMPANY_OA_PREFIX + rootCompanyId)
+ ).stream()
+ .filter(dept -> StringUtils.isNotBlank(dept.getDeptCode()))
+ .map(dept -> dept.getDeptCode().replace(OAConvertConstant.DEPARTMENT_OA_PREFIX, ""))
+ .collect(Collectors.toSet());
+ }
+
+ /**
+ * 根公司id
+ * @return
+ */
+ private String getRootCompanyId() {
+ String companyName = DictCache.getValue(DictTypeEnum.SYNC_COMPANY_NAME.getType(), DICT_KEY_SYNC_ROOT_COMPANY_NAME);
+ if (StringUtils.isBlank(companyName)) {
+ ComposeLogUtil.getLastLog().warn("未查询到配置的根公司名称");
+ return null;
+ }
+ Dept dept = deptService.getOne(Wrappers.lambdaQuery()
+ .eq(Dept::getDeptName, companyName)
+ .eq(Dept::getDeptCategory, DeptCategory.COMPANY.getCode())
+ .last("limit 1")
+ );
+ if (dept == null) {
+ ComposeLogUtil.getLastLog().warn("未查询到根公司 名称:{}", companyName);
+ return null;
+ }
+ if (StringUtils.isBlank(dept.getDeptCode())) {
+ ComposeLogUtil.getLastLog().warn("根公司编码为空:{}", companyName);
+ return null;
+ }
+ // 部门编码去掉前缀,就是oa的id
+ return dept.getDeptCode().replace(OAConvertConstant.COMPANY_OA_PREFIX, "");
+ }
+
+ /**
+ * 获取用户部门唯一标识,用户id+公司编码+部门编码
+ * @param userDept
+ * @return
+ */
+ private String getUserDeptKey(UserDept userDept) {
+ if (userDept == null) {
+ return null;
+ }
+ return userDept.getUserId() + userDept.getCompanyCode() + userDept.getDeptCode();
+ }
+}
diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/UserDeptServiceImpl.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/UserDeptServiceImpl.java
index b882a88..88b1773 100644
--- a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/UserDeptServiceImpl.java
+++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/UserDeptServiceImpl.java
@@ -26,12 +26,18 @@
package org.springblade.system.service.impl;
+import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springblade.system.pojo.entity.UserDept;
import org.springblade.system.mapper.UserDeptMapper;
+import org.springblade.system.pojo.vo.UserDeptIdsVO;
import org.springblade.system.service.IUserDeptService;
import org.springframework.stereotype.Service;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
/**
* 服务实现类
*
@@ -40,4 +46,12 @@ import org.springframework.stereotype.Service;
@Service
public class UserDeptServiceImpl extends ServiceImpl implements IUserDeptService {
+ @Override
+ public List queryUserDeptIds(Collection userIds) {
+ if (CollectionUtil.isEmpty(userIds)) {
+ return Collections.emptyList();
+ }
+ return baseMapper.queryUserDeptIds(userIds);
+ }
+
}
diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/UserServiceImpl.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/UserServiceImpl.java
index f74abc6..9c10c35 100644
--- a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/UserServiceImpl.java
+++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/UserServiceImpl.java
@@ -32,6 +32,7 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.AllArgsConstructor;
+import org.bouncycastle.util.encoders.Hex;
import org.springblade.common.constant.ParamConstant;
import org.springblade.common.constant.TenantConstant;
import org.springblade.core.cache.utils.CacheUtil;
@@ -40,6 +41,7 @@ import org.springblade.core.mp.base.BaseServiceImpl;
import org.springblade.core.mp.enums.StatusType;
import org.springblade.core.mp.support.Condition;
import org.springblade.core.mp.support.Query;
+import org.springblade.core.oauth2.props.OAuth2Properties;
import org.springblade.core.secure.utils.AuthUtil;
import org.springblade.core.tenant.BladeTenantProperties;
import org.springblade.core.tenant.TenantGuard;
@@ -54,6 +56,7 @@ import org.springblade.system.cache.SysCache;
import org.springblade.system.cache.UserCache;
import org.springblade.system.excel.UserExcel;
import org.springblade.system.mapper.UserMapper;
+import org.springblade.system.pojo.context.IPhone;
import org.springblade.system.pojo.entity.*;
import org.springblade.system.pojo.enums.DictEnum;
import org.springblade.system.pojo.enums.UserType;
@@ -91,6 +94,9 @@ public class UserServiceImpl extends BaseServiceImpl implement
private final IRoleService roleService;
private final BladeTenantProperties tenantProperties;
+ private final OAuth2Properties properties;
+
+
@Override
public User getDetail(User user) {
QueryWrapper queryWrapper = Condition.getQueryWrapper(user);
@@ -751,4 +757,25 @@ public class UserServiceImpl extends BaseServiceImpl implement
}
}
+
+ @Override
+ public void decryptPhone(IPhone user) {
+// if (user == null) {
+// return;
+// }
+// if (user.getPhone() != null && user.getPhone().length() > 20) {
+// user.setPhone(SM2Util.decrypt(user.getPhone(), properties.getPublicKey(), properties.getPrivateKey()));
+// }
+ }
+
+ @Override
+ public void encryptPhone(IPhone user) {
+// if (user == null) {
+// return;
+// }
+// if(StringUtil.isNotBlank(user.getPhone())) {
+// user.setPhone(Hex.toHexString(SM2Util.encrypt(user.getPhone(),properties.getPublicKey())));
+// }
+ }
+
}
diff --git a/blade-third-party-api/blade-mk-api/pom.xml b/blade-third-party-api/blade-mk-api/pom.xml
new file mode 100644
index 0000000..de6fe1d
--- /dev/null
+++ b/blade-third-party-api/blade-mk-api/pom.xml
@@ -0,0 +1,29 @@
+
+
+ 4.0.0
+
+ org.springblade
+ blade-third-party-api
+ ${revision}
+
+
+ blade-mk-api
+ ${project.artifactId}
+ jar
+
+
+
+ org.springblade
+ blade-starter-redis
+
+
+
+ commons-io
+ commons-io
+ 2.11.0
+
+
+
+
diff --git a/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/config/MKFeignClientConfig.java b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/config/MKFeignClientConfig.java
new file mode 100644
index 0000000..309cc4f
--- /dev/null
+++ b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/config/MKFeignClientConfig.java
@@ -0,0 +1,17 @@
+package org.springblade.thirdparty.mk.config;
+
+import feign.RequestInterceptor;
+import org.springframework.context.annotation.Bean;
+
+/**
+ * @author bfhuange
+ * @since 2024/12/18
+ */
+public class MKFeignClientConfig {
+ @Bean
+ public RequestInterceptor requestInterceptor() {
+ return template -> {
+ // 空实现屏蔽 全局拦截器 BladeFeignRequestInterceptor
+ };
+ }
+}
diff --git a/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/config/MKProperties.java b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/config/MKProperties.java
new file mode 100644
index 0000000..7b884c7
--- /dev/null
+++ b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/config/MKProperties.java
@@ -0,0 +1,61 @@
+package org.springblade.thirdparty.mk.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * mk配置类
+ * @author bfhuange
+ * @date 2024/9/11
+ */
+@Data
+@ConfigurationProperties(prefix = "third-party.mk")
+public class MKProperties {
+ /**
+ * 应用key mk-集成平台-本系统API网关-账号管理
+ */
+ private String appKey;
+ /**
+ * 应用secret mk-集成平台-本系统API网关-账号管理
+ */
+ private String appSecret;
+ /**
+ * mk基础url,接口调用地址,内网地址
+ */
+ private String baseUrl;
+ /**
+ * mk 登录地址,外网地址
+ */
+ private String loginUrl;
+
+ //======================== mk 认证相关配置================================
+
+ /**
+ * oauth 应用id mk-系统管理-安全管理-认证服务
+ */
+ private String oauthAppId;
+ /**
+ * oauth 应用秘钥 mk-系统管理-安全管理-认证服务
+ */
+ private String oauthAppSecret;
+ /**
+ * mk 流程提交标题前缀
+ */
+ private String subjectPrefix;
+ /**
+ * mk 流程模板编码前缀
+ */
+ private String templateCodePrefix;
+ /**
+ * sso登录地址
+ */
+ private String mkSsoLoginUrl = "/data/sys-oauth/ssoLogin";
+ /**
+ * 校验请求登录页面的来源地址,
+ */
+ private boolean checkReferer = true;
+ /**
+ * erp系统基础链接,用于校验referer是否是erp的,仅在 checkReferer 为true生效
+ */
+ private String erpBaseUrls;
+}
diff --git a/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/config/ThirdPartyMKAutoConfiguration.java b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/config/ThirdPartyMKAutoConfiguration.java
new file mode 100644
index 0000000..6cc5a72
--- /dev/null
+++ b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/config/ThirdPartyMKAutoConfiguration.java
@@ -0,0 +1,16 @@
+package org.springblade.thirdparty.mk.config;
+
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * 第三方api mk配置类
+ * @author bfhuange
+ * @date 2024/9/11
+ */
+@ComponentScan("org.springblade.thirdparty.mk")
+@EnableConfigurationProperties(MKProperties.class)
+@Configuration
+public class ThirdPartyMKAutoConfiguration {
+}
diff --git a/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/constant/MKConstant.java b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/constant/MKConstant.java
new file mode 100644
index 0000000..7baee0d
--- /dev/null
+++ b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/constant/MKConstant.java
@@ -0,0 +1,73 @@
+package org.springblade.thirdparty.mk.constant;
+
+/**
+ * mk常量类
+ * @author bfhuange
+ * @date 2024/9/11
+ */
+public class MKConstant {
+ /**
+ * 获取token 成功状态码
+ */
+ public static final int TOKEN_SUCCESS_CODE = 0;
+
+ /**
+ * mk token缓存key
+ */
+ public static final String MK_TOKEN_KEY = "blade:third:mk:token";
+
+ /**
+ * 组织架构类型: org-机构
+ */
+ public static final String ORG_TYPE_ORG = "org";
+ /**
+ * 组织架构类型: dept-部门
+ */
+ public static final String ORG_TYPE_DEPT = "dept";
+ /**
+ * 性别 男性 M表示男性,F表示女性,UN-未知
+ */
+ public static final String SEX_MAN = "M";
+ /**
+ * 性别 女性 M表示男性,F表示女性,UN-未知
+ */
+ public static final String SEX_WOMAN = "F";
+ /**
+ * 性别 未知 M表示男性,F表示女性,UN-未知
+ */
+ public static final String SEX_UNKONW = "UN";
+ /**
+ * 操作类型 起草人撤销
+ */
+ public static final String OPERATE_TYPE_REVOKE = "drafter_retract";
+ /**
+ * 操作类型 起草人废弃
+ */
+ public static final String OPERATE_TYPE_ABANDON = "drafter_abandon";
+ /**
+ * 起草节点id
+ */
+ public static final String DAFTER_NODE_ID = "N2";
+ /**
+ * 抄送节点
+ */
+ public static final String NODE_TYPE_SEND = "send";
+ /**
+ * 电子签名类型
+ */
+ public static final String FILE_TYPE_SIGN = "electronicSign";
+
+ /**
+ * 排序
+ */
+ public static class Sort {
+ /**
+ * 升序
+ */
+ public static final String ASC = "asc";
+ /**
+ * 降序
+ */
+ public static final String DESC = "desc";
+ }
+}
diff --git a/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/constant/MKDoc.java b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/constant/MKDoc.java
new file mode 100644
index 0000000..5ca4d16
--- /dev/null
+++ b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/constant/MKDoc.java
@@ -0,0 +1,56 @@
+package org.springblade.thirdparty.mk.constant;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 文档类型
+ * myDraft 我的起草
+ * myCreated 我发起的
+ * myCreatedAndDraft 我发起的和我起草的
+ * myApproving 我的待审
+ * myApproved 我的已审
+ * myReading 我的待阅
+ * myReaded 我的已阅
+ * myFocus 我关注的
+ * myAll 我所有的( 含我发起的,我审批的,待我审的)
+ * @author bfhuange
+ * @since 2025/4/2
+ */
+@AllArgsConstructor
+@Getter
+public enum MKDoc {
+ /**
+ * 我的待审
+ */
+ APPROVING("myApproving", "我的待审"),
+ /**
+ * 我的已审
+ */
+ APPROVED("myApproved", "我的已审"),
+ /**
+ * 我的待阅
+ */
+ READING("myReading", "我的待阅"),
+ /**
+ * 我的已阅
+ */
+ READ("myReaded", "我的已阅"),
+ /**
+ * 我发起的
+ */
+ CREATE("myCreated", "我发起的"),
+ /**
+ * 我参与的
+ */
+ RELATED("myRelated", "我参与的"),
+ ;
+ /**
+ * 编码
+ */
+ private final String code;
+ /**
+ * 名称
+ */
+ private final String name;
+}
diff --git a/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/exception/MKException.java b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/exception/MKException.java
new file mode 100644
index 0000000..b693ea2
--- /dev/null
+++ b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/exception/MKException.java
@@ -0,0 +1,17 @@
+package org.springblade.thirdparty.mk.exception;
+
+/**
+ * mk 接口异常
+ * @author bfhuange
+ * @date 2024/9/13
+ */
+public class MKException extends RuntimeException {
+
+ public MKException(String message) {
+ super(message);
+ }
+
+ public MKException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/exception/MKExceptionTranslator.java b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/exception/MKExceptionTranslator.java
new file mode 100644
index 0000000..6f4280f
--- /dev/null
+++ b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/exception/MKExceptionTranslator.java
@@ -0,0 +1,28 @@
+package org.springblade.thirdparty.mk.exception;
+
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springblade.core.tool.api.R;
+import org.springblade.core.tool.api.ResultCode;
+import org.springblade.core.tool.utils.Func;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+
+/**
+ * @author bfhuange
+ * @since 2025/3/29
+ */
+@AllArgsConstructor
+@Slf4j
+@RestControllerAdvice
+public class MKExceptionTranslator {
+
+ @ExceptionHandler(MKException.class)
+ @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
+ public R handleError(MKException e) {
+ log.error("mk异常", e);
+ return R.fail(ResultCode.INTERNAL_SERVER_ERROR, (Func.isEmpty(e.getMessage()) ? ResultCode.INTERNAL_SERVER_ERROR.getMessage() : e.getMessage()));
+ }
+}
diff --git a/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/feign/IMKClient.java b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/feign/IMKClient.java
new file mode 100644
index 0000000..75dbed4
--- /dev/null
+++ b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/feign/IMKClient.java
@@ -0,0 +1,170 @@
+package org.springblade.thirdparty.mk.feign;
+
+import feign.Response;
+import org.springblade.thirdparty.mk.config.MKFeignClientConfig;
+import org.springblade.thirdparty.mk.pojo.dto.*;
+import org.springblade.thirdparty.mk.pojo.dto.approval.MKApprovalDTO;
+import org.springblade.thirdparty.mk.pojo.dto.approval.MKProcessDTO;
+import org.springblade.thirdparty.mk.pojo.vo.*;
+import org.springframework.cloud.openfeign.FeignClient;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestParam;
+
+import java.util.List;
+
+/**
+ * MK接口
+ * @author bfhuange
+ * @date 2024/9/11
+ */
+@FeignClient(name = "MK", url = "${thirdParty.mk.baseUrl}", configuration = MKFeignClientConfig.class)
+public interface IMKClient {
+
+ /**
+ * 获取mk token
+ * @param param
+ * @return
+ */
+ @PostMapping("${thirdParty.mk.getTokenUrl:/authapi/getToken}")
+ MKTokenVO getToken(@RequestBody MKTokenDTO param);
+
+ /**
+ * 流程提交接口
+ * @param param
+ * @param accessToken
+ * @return
+ */
+ @PostMapping("${thirdParty.mk.processSubmitUrl:/openapi/sys-lbpm/sysLbpmProcess/openSupport/submit}")
+ MKResultVO processSubmit(@RequestBody MKProcessCreateDTO param, @RequestParam("access_token") String accessToken);
+
+ /**
+ * 流程执行接口
+ * @param param
+ * @param accessToken
+ * @return
+ */
+ @PostMapping("${thirdParty.mk.processExecuteUrl:/openapi/sys-lbpm/sysLbpmProcess/openSupport/execute}")
+ MKResultVO processExecute(@RequestBody MKProcessExecuteDTO param, @RequestParam("access_token") String accessToken);
+
+ /**
+ * 流程删除接口
+ * @param param
+ * @param accessToken
+ * @return
+ */
+ @PostMapping("${thirdParty.mk.processDeleteUrl:/openapi/sys-lbpm/sysLbpmProcess/openSupport/delete}")
+ MKResultVO processDelete(@RequestBody MKProcessDeleteDTO param, @RequestParam("access_token") String accessToken);
+
+ /**
+ * 获取流程当前节点详情
+ * @param param
+ * @param accessToken
+ * @return
+ */
+ @PostMapping("${thirdParty.mk.getCurrentNodesUrl:/openapi/sys-lbpm/sysLbpmProcess/openSupport/getCurNodesInfo}")
+ MKResultVO> getCurrentNodes(@RequestBody MKCurrentNodesDTO param, @RequestParam("access_token") String accessToken);
+
+ /**
+ * 获取节点处理人信息
+ * @param param
+ * @param accessToken
+ * @return
+ */
+ @PostMapping("${thirdParty.mk.getNodeHandlersUrl:/openapi/sys-lbpm/sysLbpmProcess/openSupport/getNodeHandlerInfos}")
+ MKResultVO getNodeHandlers(@RequestBody MKNodeHandlersDTO param, @RequestParam("access_token") String accessToken);
+
+ /**
+ * 根据流程模板获取该模板下所有的人工节点信息
+ * @param param
+ * @param accessToken
+ * @return
+ */
+ @PostMapping("${thirdParty.mk.getManualNodeUrl:/openapi/sys-lbpm/sysLbpmTemplate/openSupport/getManualNode}")
+ MKResultVO> getManualNode(@RequestBody MKManualNodeDTO param, @RequestParam("access_token") String accessToken);
+
+ //===========================人员组织推送接口============================================
+
+ /**
+ * 推送公司部门
+ * @param param
+ * @param accessToken
+ * @return
+ */
+ @PostMapping("${thirdParty.mk.pushOrgDeptUrl:/openapi/sys-org/v2/push/orgDept}")
+ MKResultVO