diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..c9bd08f
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,527 @@
+
+
+ 4.0.0
+
+ org.dromara
+ flexible-employment
+ ${revision}
+
+ flexible-employment
+ flexible-employment微服务系统
+
+
+ 2.1.2
+ UTF-8
+ UTF-8
+ 17
+ 3.1.7
+ 2022.0.4
+ 3.1.8
+ 3.0.3
+ 3.5.13
+ 3.5.4
+ 3.9.1
+ 4.2.0
+ 2.3
+ 2.2.15
+ 2.2.0
+ 0.15.0
+ 5.2.3
+ 3.3.3
+ 5.8.22
+ 3.24.3
+ 2.2.5
+ 4.3.6
+ 2.4.0
+ 1.37.0
+ 1.18.30
+ 7.4
+ 2.0.0-beta4
+ 7.14.0
+ 8.16.0
+ 1.76
+ 2.14.4
+ 1.3.5
+ 0.2.0
+ 1.16.6
+
+ 2.7.0
+
+ 1.2.83
+
+
+ 1.12.600
+ 4.10.0
+
+
+ 2.2.0
+
+
+ 3.11.0
+ 3.1.2
+ 1.3.0
+
+
+
+
+ dev
+
+
+ pro
+ 116.255.147.55:8848
+ DEFAULT_GROUP
+ DEFAULT_GROUP
+ 127.0.0.1:4560
+
+
+
+ true
+
+
+
+ prod
+
+ prod
+ 127.0.0.1:8848
+ DEFAULT_GROUP
+ DEFAULT_GROUP
+ 127.0.0.1:4560
+
+
+
+ false
+
+
+
+
+
+
+
+
+
+
+ org.springframework.cloud
+ spring-cloud-dependencies
+ ${spring-cloud.version}
+ pom
+ import
+
+
+
+
+ org.dromara
+ ruoyi-common-alibaba-bom
+ ${revision}
+ pom
+ import
+
+
+
+
+ org.springframework.boot
+ spring-boot-dependencies
+ ${spring-boot.version}
+ pom
+ import
+
+
+
+
+ cn.hutool
+ hutool-bom
+ ${hutool.version}
+ pom
+ import
+
+
+
+
+ me.zhyd.oauth
+ JustAuth
+ ${justauth.version}
+
+
+
+
+ org.dromara
+ ruoyi-common-bom
+ ${revision}
+ pom
+ import
+
+
+
+
+ org.dromara
+ ruoyi-api-bom
+ ${revision}
+ pom
+ import
+
+
+
+ cn.dev33
+ sa-token-core
+ ${satoken.version}
+
+
+
+ cn.dev33
+ sa-token-spring-boot3-starter
+ ${satoken.version}
+
+
+
+
+ org.mybatis.spring.boot
+ mybatis-spring-boot-starter
+ ${spring-boot.mybatis}
+
+
+
+ org.mybatis
+ mybatis
+ ${mybatis.version}
+
+
+
+ com.baomidou
+ mybatis-plus-boot-starter
+ ${mybatis-plus.version}
+
+
+
+ com.baomidou
+ mybatis-plus-annotation
+ ${mybatis-plus.version}
+
+
+
+ p6spy
+ p6spy
+ ${p6spy.version}
+
+
+
+ io.swagger.core.v3
+ swagger-annotations
+ ${swagger.core.version}
+
+
+
+ org.springdoc
+ springdoc-openapi-starter-webmvc-api
+ ${springdoc.version}
+
+
+
+ com.github.therapi
+ therapi-runtime-javadoc
+ ${therapi-javadoc.version}
+
+
+
+ org.projectlombok
+ lombok
+ ${lombok.version}
+
+
+
+ org.apache.poi
+ poi
+ ${poi.version}
+
+
+ org.apache.poi
+ poi-ooxml
+ ${poi.version}
+
+
+ com.alibaba
+ easyexcel
+ ${easyexcel.version}
+
+
+ org.apache.poi
+ poi-ooxml-schemas
+
+
+
+
+
+
+ org.apache.velocity
+ velocity-engine-core
+ ${velocity.version}
+
+
+
+
+ org.redisson
+ redisson-spring-boot-starter
+ ${redisson.version}
+
+
+
+ com.baomidou
+ lock4j-redisson-spring-boot-starter
+ ${lock4j.version}
+
+
+
+
+ tech.powerjob
+ powerjob-worker
+ ${powerjob.version}
+
+
+ tech.powerjob
+ powerjob-official-processors
+ ${powerjob.version}
+
+
+
+
+ com.xuxueli
+ xxl-job-core
+ ${xxl-job.version}
+
+
+
+
+ org.bouncycastle
+ bcprov-jdk15to18
+ ${bouncycastle.version}
+
+
+
+
+ net.logstash.logback
+ logstash-logback-encoder
+ ${logstash.version}
+
+
+
+ org.dromara.easy-es
+ easy-es-boot-starter
+ ${easy-es.version}
+
+
+ org.elasticsearch
+ elasticsearch
+ ${elasticsearch.version}
+
+
+ org.elasticsearch.client
+ elasticsearch-rest-high-level-client
+ ${elasticsearch.version}
+
+
+
+
+ org.apache.skywalking
+ apm-toolkit-logback-1.x
+ ${skywalking-toolkit.version}
+
+
+ org.apache.skywalking
+ apm-toolkit-trace
+ ${skywalking-toolkit.version}
+
+
+
+ com.squareup.okhttp3
+ okhttp
+ ${okhttp.version}
+
+
+
+ com.amazonaws
+ aws-java-sdk-s3
+ ${aws-java-sdk-s3.version}
+
+
+
+
+ org.dromara.sms4j
+ sms4j-spring-boot-starter
+ ${sms4j.version}
+
+
+
+
+ org.lionsoul
+ ip2region
+ ${ip2region.version}
+
+
+
+ com.alibaba
+ fastjson
+ ${fastjson.version}
+
+
+
+ com.alibaba
+ transmittable-thread-local
+ ${alibaba-ttl.version}
+
+
+
+ io.github.linpeilie
+ mapstruct-plus-spring-boot-starter
+ ${mapstruct-plus.version}
+
+
+
+
+
+
+ ruoyi-auth
+ ruoyi-gateway
+ ruoyi-visual
+ ruoyi-modules
+ ruoyi-api
+ ruoyi-common
+ ruoyi-example
+
+ pom
+
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ ${maven-compiler-plugin.verison}
+
+ ${java.version}
+ ${java.version}
+ ${project.build.sourceEncoding}
+
+
+ com.github.therapi
+ therapi-runtime-javadoc-scribe
+ 0.15.0
+
+
+ org.projectlombok
+ lombok
+ ${lombok.version}
+
+
+ org.springframework.boot
+ spring-boot-configuration-processor
+ ${spring-boot.version}
+
+
+ io.github.linpeilie
+ mapstruct-plus-processor
+ ${mapstruct-plus.version}
+
+
+ org.projectlombok
+ lombok-mapstruct-binding
+ ${mapstruct-plus.lombok.version}
+
+
+
+ -parameters
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ ${maven-surefire-plugin.version}
+
+ -Dfile.encoding=UTF-8
+
+ ${profiles.active}
+
+ exclude
+
+
+
+
+ org.codehaus.mojo
+ flatten-maven-plugin
+ ${flatten-maven-plugin.version}
+
+ true
+ resolveCiFriendliesOnly
+
+
+
+ flatten
+ process-resources
+
+ flatten
+
+
+
+ flatten.clean
+ clean
+
+ clean
+
+
+
+
+
+
+
+ src/main/resources
+
+ false
+
+
+ src/main/webapp/
+
+
+ src/main/resources
+
+
+ application*
+ bootstrap*
+ logback*
+
+
+ true
+
+
+
+
+
+
+ public
+ huawei nexus
+ https://mirrors.huaweicloud.com/repository/maven/
+
+ true
+
+
+
+
+
+
+ public
+ huawei nexus
+ https://mirrors.huaweicloud.com/repository/maven/
+
+ true
+
+
+ false
+
+
+
+
+
+
diff --git a/ruoyi-api/ruoyi-api-payment/src/main/java/org/dromara/payment/api/common/domain/RemoteBusOperLogVo.java b/ruoyi-api/ruoyi-api-payment/src/main/java/org/dromara/payment/api/common/domain/RemoteBusOperLogVo.java
index 2472f78..fdf47be 100644
--- a/ruoyi-api/ruoyi-api-payment/src/main/java/org/dromara/payment/api/common/domain/RemoteBusOperLogVo.java
+++ b/ruoyi-api/ruoyi-api-payment/src/main/java/org/dromara/payment/api/common/domain/RemoteBusOperLogVo.java
@@ -1,4 +1,4 @@
-package org.dromara.payment.common.api.domain;
+package org.dromara.payment.api.common.domain;
import lombok.Data;
diff --git a/ruoyi-api/ruoyi-api-payment/src/main/java/org/dromara/payment/api/common/domain/RemoteTranLogVo.java b/ruoyi-api/ruoyi-api-payment/src/main/java/org/dromara/payment/api/common/domain/RemoteTranLogVo.java
index eef62fd..96230ad 100644
--- a/ruoyi-api/ruoyi-api-payment/src/main/java/org/dromara/payment/api/common/domain/RemoteTranLogVo.java
+++ b/ruoyi-api/ruoyi-api-payment/src/main/java/org/dromara/payment/api/common/domain/RemoteTranLogVo.java
@@ -1,4 +1,4 @@
-package org.dromara.payment.common.api.domain;
+package org.dromara.payment.api.common.domain;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
@@ -39,7 +39,7 @@ public class RemoteTranLogVo implements Serializable {
/**
* 交易场景id
-扣除服务费:任务id
+扣除服务费:任务id
账单付款:账单明细id
*/
private Long buisId;
diff --git a/ruoyi-api/ruoyi-api-payment/src/main/java/org/dromara/payment/api/earlyWarn/domain/RemoteLgEarlyWarnDtlVo.java b/ruoyi-api/ruoyi-api-payment/src/main/java/org/dromara/payment/api/earlyWarn/domain/RemoteLgEarlyWarnDtlVo.java
new file mode 100644
index 0000000..3417004
--- /dev/null
+++ b/ruoyi-api/ruoyi-api-payment/src/main/java/org/dromara/payment/api/earlyWarn/domain/RemoteLgEarlyWarnDtlVo.java
@@ -0,0 +1,150 @@
+package org.dromara.payment.api.earlyWarn.domain;
+
+import java.math.BigDecimal;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+
+
+/**
+ * 风控预警设置明细视图对象 lg_early_warn_dtl
+ *
+ * @author LionLi
+ * @date 2025-06-10
+ */
+@Data
+public class RemoteLgEarlyWarnDtlVo implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 自增主键
+ */
+ private Long id;
+
+ /**
+ * 风控预警ID
+ */
+ private Long earlyWarnId;
+
+ /**
+ * 所属税源地
+ */
+ private Long taxSourcesId;
+
+ /**
+ * 风险类型[1:单人单笔发放金额预警 2:批次内单笔金额占比预警 3:批次内发放金额相似预警 4:单人每年发放金额预警 5:单人连续月份发放金额相似预警 6:企业风险预警 7:发放年龄预警 8:企业高管身份预警]
+ */
+ private Integer riskType;
+
+ /**
+ * 触发规则[1 单人单笔金额 2 批次占比 3 离散度 4 单人每年金额 5 单人连续月份 6 企业信息异常(经营异常、行政处罚、违法欠税等)7 发放人员年龄 8 发放人员姓名命中企业高管名单]
+ */
+ private Integer triggerRule;
+
+ /**
+ * 单人单笔金额
+ */
+ private BigDecimal singleAmount;
+
+ /**
+ * 批次总笔数
+ */
+ private BigDecimal totalAmountBatch;
+
+ /**
+ * 单笔发放金额起始值
+ */
+ private BigDecimal singlePaymentAmountStart;
+
+ /**
+ * 单笔发放金额结束值
+ */
+ private BigDecimal singlePaymentAmountEnd;
+
+ /**
+ * 占比总数
+ */
+ private BigDecimal proportionTotal;
+
+ /**
+ * 离散度
+ */
+ private BigDecimal dispersion;
+
+ /**
+ * 单人每年金额
+ */
+ private BigDecimal singleYearAmount;
+
+ /**
+ * 累计发放金额波动起始值
+ */
+ private BigDecimal waveAmountStart;
+
+ /**
+ * 累计发放金额波动结束值
+ */
+ private BigDecimal waveAmountEnd;
+
+ /**
+ * 累计发放金额
+ */
+ private BigDecimal waveAmount;
+
+ /**
+ * 单人连续月份
+ */
+ private Long singleContinuousMonth;
+
+ /**
+ * 发放年龄
+ */
+ private Long issuingAge;
+
+ /**
+ * 处理频率[1 每次提交发放 2 每月最后一天]
+ */
+ private Integer handleFrequency;
+
+ /**
+ * 处理方式[1 发放需审核 2 仅风险预警]
+ */
+ private Integer handleWay;
+
+ /**
+ * 风险原因
+ */
+ private String riskReasons;
+
+ /**
+ * 是否已删除[0:否,1:是]
+ */
+ private Integer isDelete;
+
+ /**
+ * 创建者user_id
+ */
+ private Long createUserId;
+
+ /**
+ * 更新者user_id
+ */
+ private Long updateUserId;
+
+ /**
+ * 备注
+ */
+ private String remark;
+
+ /**
+ *
+ */
+ private Long singleMonthLimitAmount;
+
+
+}
diff --git a/ruoyi-api/ruoyi-api-payment/src/main/java/org/dromara/payment/api/earlyWarn/domain/RemoteLgEarlyWarnVo.java b/ruoyi-api/ruoyi-api-payment/src/main/java/org/dromara/payment/api/earlyWarn/domain/RemoteLgEarlyWarnVo.java
new file mode 100644
index 0000000..f0cbff1
--- /dev/null
+++ b/ruoyi-api/ruoyi-api-payment/src/main/java/org/dromara/payment/api/earlyWarn/domain/RemoteLgEarlyWarnVo.java
@@ -0,0 +1,143 @@
+package org.dromara.payment.api.earlyWarn.domain;
+
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+
+/**
+ * 风控预警设置视图对象 lg_early_warn
+ *
+ * @author LionLi
+ * @date 2025-06-10
+ */
+@Data
+public class RemoteLgEarlyWarnVo implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 自增主键
+ */
+ private Long id;
+
+ /**
+ * 风险类型[1:单人单笔发放金额预警 2:批次内单笔金额占比预警 3:批次内发放金额相似预警 4:单人每年发放金额预警 5:单人连续月份发放金额相似预警 6:企业风险预警 7:发放年龄预警 8:企业高管身份预警]
+ */
+ private Integer riskType;
+
+ /**
+ * 触发规则[1 单人单笔金额 2 批次占比 3 离散度 4 单人每年金额 5 单人连续月份 6 企业信息异常(经营异常、行政处罚、违法欠税等)7 发放人员年龄 8 发放人员姓名命中企业高管名单]
+ */
+ private Integer triggerRule;
+
+ /**
+ * 单人单笔金额
+ */
+ private BigDecimal singleAmount;
+
+ /**
+ * 批次总笔数
+ */
+ private BigDecimal totalAmountBatch;
+
+ /**
+ * 单笔发放金额起始值
+ */
+ private BigDecimal singlePaymentAmountStart;
+
+ /**
+ * 单笔发放金额结束值
+ */
+ private BigDecimal singlePaymentAmountEnd;
+
+ /**
+ * 占比总数
+ */
+ private BigDecimal proportionTotal;
+
+ /**
+ * 离散度
+ */
+ private BigDecimal dispersion;
+
+ /**
+ * 单人每年金额
+ */
+ private BigDecimal singleYearAmount;
+
+ /**
+ * 累计发放金额波动起始值
+ */
+ private BigDecimal waveAmountStart;
+
+ /**
+ * 累计发放金额波动结束值
+ */
+ private BigDecimal waveAmountEnd;
+
+ /**
+ * 累计发放金额
+ */
+ private BigDecimal waveAmount;
+
+ /**
+ * 单人连续月份
+ */
+ private Long singleContinuousMonth;
+
+ /**
+ * 发放年龄
+ */
+ private Long issuingAge;
+
+ /**
+ * 处理频率[1 每次提交发放 2 每月最后一天]
+ */
+ private Integer handleFrequency;
+
+ /**
+ * 处理方式[1 发放需审核 2 仅风险预警]
+ */
+ private Integer handleWay;
+
+ /**
+ * 风险原因
+ */
+ private String riskReasons;
+
+ /**
+ * 是否已删除[0:否,1:是]
+ */
+ private Integer isDelete;
+
+ /**
+ * 创建者user_id
+ */
+ private Long createUserId;
+
+ /**
+ * 更新者user_id
+ */
+ private Long updateUserId;
+
+ /**
+ * 备注
+ */
+ private String remark;
+
+ /**
+ * 单人单月限额
+ */
+ private Long singleMonthLimit;
+
+ /**
+ * 单人月限额
+ */
+ private Long singleMonthLimitAmount;
+
+
+}
diff --git a/ruoyi-api/ruoyi-api-payment/src/main/java/org/dromara/payment/api/invoice/domain/RemoteMerInvoiceAddressVo.java b/ruoyi-api/ruoyi-api-payment/src/main/java/org/dromara/payment/api/invoice/domain/RemoteMerInvoiceAddressVo.java
index 468a5d4..0149d3f 100644
--- a/ruoyi-api/ruoyi-api-payment/src/main/java/org/dromara/payment/api/invoice/domain/RemoteMerInvoiceAddressVo.java
+++ b/ruoyi-api/ruoyi-api-payment/src/main/java/org/dromara/payment/api/invoice/domain/RemoteMerInvoiceAddressVo.java
@@ -1,4 +1,4 @@
-package org.dromara.payment.invoice.api.domain;
+package org.dromara.payment.api.invoice.domain;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
diff --git a/ruoyi-api/ruoyi-api-payment/src/main/java/org/dromara/payment/api/invoice/domain/RemoteMerInvoiceBillVo.java b/ruoyi-api/ruoyi-api-payment/src/main/java/org/dromara/payment/api/invoice/domain/RemoteMerInvoiceBillVo.java
index 592f39a..dbc5c06 100644
--- a/ruoyi-api/ruoyi-api-payment/src/main/java/org/dromara/payment/api/invoice/domain/RemoteMerInvoiceBillVo.java
+++ b/ruoyi-api/ruoyi-api-payment/src/main/java/org/dromara/payment/api/invoice/domain/RemoteMerInvoiceBillVo.java
@@ -1,4 +1,4 @@
-package org.dromara.payment.invoice.api.domain;
+package org.dromara.payment.api.invoice.domain;
import lombok.Data;
diff --git a/ruoyi-api/ruoyi-api-payment/src/main/java/org/dromara/payment/api/invoice/domain/RemoteMerInvoiceDetailVo.java b/ruoyi-api/ruoyi-api-payment/src/main/java/org/dromara/payment/api/invoice/domain/RemoteMerInvoiceDetailVo.java
index 5faa513..941456e 100644
--- a/ruoyi-api/ruoyi-api-payment/src/main/java/org/dromara/payment/api/invoice/domain/RemoteMerInvoiceDetailVo.java
+++ b/ruoyi-api/ruoyi-api-payment/src/main/java/org/dromara/payment/api/invoice/domain/RemoteMerInvoiceDetailVo.java
@@ -1,4 +1,4 @@
-package org.dromara.payment.invoice.api.domain;
+package org.dromara.payment.api.invoice.domain;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
diff --git a/ruoyi-api/ruoyi-api-payment/src/main/java/org/dromara/payment/api/invoice/domain/RemoteMerInvoiceTypeVo.java b/ruoyi-api/ruoyi-api-payment/src/main/java/org/dromara/payment/api/invoice/domain/RemoteMerInvoiceTypeVo.java
index 9737ae9..eddfc6f 100644
--- a/ruoyi-api/ruoyi-api-payment/src/main/java/org/dromara/payment/api/invoice/domain/RemoteMerInvoiceTypeVo.java
+++ b/ruoyi-api/ruoyi-api-payment/src/main/java/org/dromara/payment/api/invoice/domain/RemoteMerInvoiceTypeVo.java
@@ -1,4 +1,4 @@
-package org.dromara.payment.invoice.api.domain;
+package org.dromara.payment.api.invoice.domain;
import lombok.Data;
diff --git a/ruoyi-api/ruoyi-api-payment/src/main/java/org/dromara/payment/api/invoice/domain/RemoteMerInvoiceVo.java b/ruoyi-api/ruoyi-api-payment/src/main/java/org/dromara/payment/api/invoice/domain/RemoteMerInvoiceVo.java
index 5c8b55f..58ad136 100644
--- a/ruoyi-api/ruoyi-api-payment/src/main/java/org/dromara/payment/api/invoice/domain/RemoteMerInvoiceVo.java
+++ b/ruoyi-api/ruoyi-api-payment/src/main/java/org/dromara/payment/api/invoice/domain/RemoteMerInvoiceVo.java
@@ -1,4 +1,4 @@
-package org.dromara.payment.invoice.api.domain;
+package org.dromara.payment.api.invoice.domain;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
@@ -233,4 +233,8 @@ public class RemoteMerInvoiceVo implements Serializable {
private String iogisticsReceivingBy;
+ private String categoryOne;
+
+ private String categoryTwo;
+
}
diff --git a/ruoyi-api/ruoyi-api-payment/src/main/java/org/dromara/payment/api/task/domain/RemoteMerTaskVo.java b/ruoyi-api/ruoyi-api-payment/src/main/java/org/dromara/payment/api/task/domain/RemoteMerTaskVo.java
index 78f2890..0fb42ca 100644
--- a/ruoyi-api/ruoyi-api-payment/src/main/java/org/dromara/payment/api/task/domain/RemoteMerTaskVo.java
+++ b/ruoyi-api/ruoyi-api-payment/src/main/java/org/dromara/payment/api/task/domain/RemoteMerTaskVo.java
@@ -237,5 +237,12 @@ public class RemoteMerTaskVo implements Serializable {
*/
private String sydVerifyReason;
+ private String itemTypeId;
+ private String itemTypeName;
+ private String taskTypeId;
+ private String taskTypeName;
+ private String itemId;
+ private String taskId;
+
}
diff --git a/ruoyi-api/ruoyi-api-payment/src/main/java/org/dromara/payment/api/channel/domain/RemoteBusChannelAccountSynVo.java b/ruoyi-api/ruoyi-api-payment/src/main/java/org/dromara/payment/channel/domain/RemoteBusChannelAccountSynVo.java
similarity index 93%
rename from ruoyi-api/ruoyi-api-payment/src/main/java/org/dromara/payment/api/channel/domain/RemoteBusChannelAccountSynVo.java
rename to ruoyi-api/ruoyi-api-payment/src/main/java/org/dromara/payment/channel/domain/RemoteBusChannelAccountSynVo.java
index 6035830..39ae113 100644
--- a/ruoyi-api/ruoyi-api-payment/src/main/java/org/dromara/payment/api/channel/domain/RemoteBusChannelAccountSynVo.java
+++ b/ruoyi-api/ruoyi-api-payment/src/main/java/org/dromara/payment/channel/domain/RemoteBusChannelAccountSynVo.java
@@ -1,4 +1,4 @@
-package org.dromara.payment.channel.api.domain;
+package org.dromara.payment.channel.domain;
import lombok.Data;
diff --git a/ruoyi-api/ruoyi-api-payment/src/main/java/org/dromara/payment/api/channel/domain/RemoteBusChannelAccountVo.java b/ruoyi-api/ruoyi-api-payment/src/main/java/org/dromara/payment/channel/domain/RemoteBusChannelAccountVo.java
similarity index 96%
rename from ruoyi-api/ruoyi-api-payment/src/main/java/org/dromara/payment/api/channel/domain/RemoteBusChannelAccountVo.java
rename to ruoyi-api/ruoyi-api-payment/src/main/java/org/dromara/payment/channel/domain/RemoteBusChannelAccountVo.java
index 2484178..0292d7f 100644
--- a/ruoyi-api/ruoyi-api-payment/src/main/java/org/dromara/payment/api/channel/domain/RemoteBusChannelAccountVo.java
+++ b/ruoyi-api/ruoyi-api-payment/src/main/java/org/dromara/payment/channel/domain/RemoteBusChannelAccountVo.java
@@ -1,4 +1,4 @@
-package org.dromara.payment.channel.api.domain;
+package org.dromara.payment.channel.domain;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
diff --git a/ruoyi-api/ruoyi-api-payment/src/main/java/org/dromara/payment/api/channel/domain/RemoteBusChannelCashoutVo.java b/ruoyi-api/ruoyi-api-payment/src/main/java/org/dromara/payment/channel/domain/RemoteBusChannelCashoutVo.java
similarity index 97%
rename from ruoyi-api/ruoyi-api-payment/src/main/java/org/dromara/payment/api/channel/domain/RemoteBusChannelCashoutVo.java
rename to ruoyi-api/ruoyi-api-payment/src/main/java/org/dromara/payment/channel/domain/RemoteBusChannelCashoutVo.java
index 4888ab2..5154a8c 100644
--- a/ruoyi-api/ruoyi-api-payment/src/main/java/org/dromara/payment/api/channel/domain/RemoteBusChannelCashoutVo.java
+++ b/ruoyi-api/ruoyi-api-payment/src/main/java/org/dromara/payment/channel/domain/RemoteBusChannelCashoutVo.java
@@ -1,4 +1,4 @@
-package org.dromara.payment.channel.api.domain;
+package org.dromara.payment.channel.domain;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
diff --git a/ruoyi-api/ruoyi-api-payment/src/main/java/org/dromara/payment/api/channel/domain/RemoteBusChannelFlowsVo.java b/ruoyi-api/ruoyi-api-payment/src/main/java/org/dromara/payment/channel/domain/RemoteBusChannelFlowsVo.java
similarity index 98%
rename from ruoyi-api/ruoyi-api-payment/src/main/java/org/dromara/payment/api/channel/domain/RemoteBusChannelFlowsVo.java
rename to ruoyi-api/ruoyi-api-payment/src/main/java/org/dromara/payment/channel/domain/RemoteBusChannelFlowsVo.java
index 604ae50..98e8d8e 100644
--- a/ruoyi-api/ruoyi-api-payment/src/main/java/org/dromara/payment/api/channel/domain/RemoteBusChannelFlowsVo.java
+++ b/ruoyi-api/ruoyi-api-payment/src/main/java/org/dromara/payment/channel/domain/RemoteBusChannelFlowsVo.java
@@ -1,4 +1,4 @@
-package org.dromara.payment.channel.api.domain;
+package org.dromara.payment.channel.domain;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
diff --git a/ruoyi-api/ruoyi-api-payment/src/main/java/org/dromara/payment/api/channel/domain/RemoteBusChannelProductVo.java b/ruoyi-api/ruoyi-api-payment/src/main/java/org/dromara/payment/channel/domain/RemoteBusChannelProductVo.java
similarity index 97%
rename from ruoyi-api/ruoyi-api-payment/src/main/java/org/dromara/payment/api/channel/domain/RemoteBusChannelProductVo.java
rename to ruoyi-api/ruoyi-api-payment/src/main/java/org/dromara/payment/channel/domain/RemoteBusChannelProductVo.java
index 2201af2..0e42774 100644
--- a/ruoyi-api/ruoyi-api-payment/src/main/java/org/dromara/payment/api/channel/domain/RemoteBusChannelProductVo.java
+++ b/ruoyi-api/ruoyi-api-payment/src/main/java/org/dromara/payment/channel/domain/RemoteBusChannelProductVo.java
@@ -1,4 +1,4 @@
-package org.dromara.payment.channel.api.domain;
+package org.dromara.payment.channel.domain;
import java.math.BigDecimal;
import lombok.Data;
diff --git a/ruoyi-api/ruoyi-api-payment/src/main/java/org/dromara/payment/api/channel/domain/RemoteBusChannelSalesmanVo.java b/ruoyi-api/ruoyi-api-payment/src/main/java/org/dromara/payment/channel/domain/RemoteBusChannelSalesmanVo.java
similarity index 94%
rename from ruoyi-api/ruoyi-api-payment/src/main/java/org/dromara/payment/api/channel/domain/RemoteBusChannelSalesmanVo.java
rename to ruoyi-api/ruoyi-api-payment/src/main/java/org/dromara/payment/channel/domain/RemoteBusChannelSalesmanVo.java
index 7daae45..85da3e3 100644
--- a/ruoyi-api/ruoyi-api-payment/src/main/java/org/dromara/payment/api/channel/domain/RemoteBusChannelSalesmanVo.java
+++ b/ruoyi-api/ruoyi-api-payment/src/main/java/org/dromara/payment/channel/domain/RemoteBusChannelSalesmanVo.java
@@ -1,4 +1,4 @@
-package org.dromara.payment.channel.api.domain;
+package org.dromara.payment.channel.domain;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
diff --git a/ruoyi-api/ruoyi-api-payment/src/main/java/org/dromara/payment/api/channel/domain/RemoteBusChannelVo.java b/ruoyi-api/ruoyi-api-payment/src/main/java/org/dromara/payment/channel/domain/RemoteBusChannelVo.java
similarity index 98%
rename from ruoyi-api/ruoyi-api-payment/src/main/java/org/dromara/payment/api/channel/domain/RemoteBusChannelVo.java
rename to ruoyi-api/ruoyi-api-payment/src/main/java/org/dromara/payment/channel/domain/RemoteBusChannelVo.java
index 0695320..d113034 100644
--- a/ruoyi-api/ruoyi-api-payment/src/main/java/org/dromara/payment/api/channel/domain/RemoteBusChannelVo.java
+++ b/ruoyi-api/ruoyi-api-payment/src/main/java/org/dromara/payment/channel/domain/RemoteBusChannelVo.java
@@ -1,4 +1,4 @@
-package org.dromara.payment.channel.api.domain;
+package org.dromara.payment.channel.domain;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
diff --git a/ruoyi-auth/src/main/resources/application.yml b/ruoyi-auth/src/main/resources/application.yml
index b00d227..0878430 100644
--- a/ruoyi-auth/src/main/resources/application.yml
+++ b/ruoyi-auth/src/main/resources/application.yml
@@ -20,13 +20,13 @@ spring:
discovery:
# 注册组
username: nacos
- password: baidu123
+ password: xzs112233@
group: @nacos.discovery.group@
namespace: ${spring.profiles.active}
config:
# 配置组
username: nacos
- password: baidu123
+ password: xzs112233@
group: @nacos.config.group@
namespace: ${spring.profiles.active}
config:
diff --git a/ruoyi-common/pom.xml b/ruoyi-common/pom.xml
new file mode 100644
index 0000000..95bd88e
--- /dev/null
+++ b/ruoyi-common/pom.xml
@@ -0,0 +1,54 @@
+
+
+
+ org.dromara
+ flexible-employment
+ ${revision}
+
+ 4.0.0
+
+
+ ruoyi-common-bom
+ ruoyi-common-alibaba-bom
+ ruoyi-common-log
+ ruoyi-common-dict
+ ruoyi-common-excel
+ ruoyi-common-core
+ ruoyi-common-redis
+ ruoyi-common-doc
+ ruoyi-common-security
+ ruoyi-common-satoken
+ ruoyi-common-web
+ ruoyi-common-mybatis
+ ruoyi-common-job
+ ruoyi-common-dubbo
+ ruoyi-common-seata
+ ruoyi-common-loadbalancer
+ ruoyi-common-oss
+ ruoyi-common-ratelimiter
+ ruoyi-common-idempotent
+ ruoyi-common-mail
+ ruoyi-common-sms
+ ruoyi-common-logstash
+ ruoyi-common-elasticsearch
+ ruoyi-common-sentinel
+ ruoyi-common-skylog
+ ruoyi-common-prometheus
+ ruoyi-common-translation
+ ruoyi-common-sensitive
+ ruoyi-common-json
+ ruoyi-common-encrypt
+ ruoyi-common-tenant
+ ruoyi-common-websocket
+ ruoyi-common-social
+
+
+ ruoyi-common
+ pom
+
+
+ ruoyi-common通用模块
+
+
+
diff --git a/ruoyi-common/ruoyi-common-alibaba-bom/pom.xml b/ruoyi-common/ruoyi-common-alibaba-bom/pom.xml
new file mode 100644
index 0000000..4a7cb38
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-alibaba-bom/pom.xml
@@ -0,0 +1,176 @@
+
+
+ 4.0.0
+
+ org.dromara
+ ruoyi-common-alibaba-bom
+ ${revision}
+ pom
+
+
+ ruoyi-common-alibaba-bom alibaba依赖项
+
+
+
+ 2.1.2
+ 2022.0.0.0
+ 1.8.6
+ 1.7.1
+ 2.2.1
+ 3.2.7
+ 1.0.11
+
+
+
+
+ com.alibaba.cloud
+ spring-cloud-alibaba-dependencies
+ ${spring-cloud-alibaba.version}
+ pom
+ import
+
+
+ com.alibaba.nacos
+ nacos-client
+ ${nacos.client.version}
+
+
+ com.alibaba.csp
+ sentinel-core
+ ${sentinel.version}
+
+
+ com.alibaba.csp
+ sentinel-parameter-flow-control
+ ${sentinel.version}
+
+
+ com.alibaba.csp
+ sentinel-datasource-extension
+ ${sentinel.version}
+
+
+ com.alibaba.csp
+ sentinel-datasource-apollo
+ ${sentinel.version}
+
+
+ com.alibaba.csp
+ sentinel-datasource-zookeeper
+ ${sentinel.version}
+
+
+ com.alibaba.csp
+ sentinel-datasource-nacos
+ ${sentinel.version}
+
+
+ com.alibaba.csp
+ sentinel-datasource-redis
+ ${sentinel.version}
+
+
+ com.alibaba.csp
+ sentinel-datasource-consul
+ ${sentinel.version}
+
+
+ com.alibaba.csp
+ sentinel-web-servlet
+ ${sentinel.version}
+
+
+ com.alibaba.csp
+ sentinel-spring-cloud-gateway-adapter
+ ${sentinel.version}
+
+
+ com.alibaba.csp
+ sentinel-transport-simple-http
+ ${sentinel.version}
+
+
+ com.alibaba.csp
+ sentinel-annotation-aspectj
+ ${sentinel.version}
+
+
+ com.alibaba.csp
+ sentinel-reactor-adapter
+ ${sentinel.version}
+
+
+ com.alibaba.csp
+ sentinel-cluster-server-default
+ ${sentinel.version}
+
+
+ com.alibaba.csp
+ sentinel-cluster-client-default
+ ${sentinel.version}
+
+
+ com.alibaba.csp
+ sentinel-spring-webflux-adapter
+ ${sentinel.version}
+
+
+ com.alibaba.csp
+ sentinel-api-gateway-adapter-common
+ ${sentinel.version}
+
+
+ com.alibaba.csp
+ sentinel-spring-webmvc-adapter
+ ${sentinel.version}
+
+
+ com.alibaba.csp
+ sentinel-dubbo-adapter
+ ${sentinel.version}
+
+
+ com.alibaba.csp
+ sentinel-apache-dubbo-adapter
+ ${sentinel.version}
+
+
+ com.alibaba.csp
+ sentinel-apache-dubbo3-adapter
+ ${sentinel.version}
+
+
+ io.seata
+ seata-spring-boot-starter
+ ${seata.version}
+
+
+
+
+ org.apache.dubbo
+ dubbo-spring-boot-starter
+ ${dubbo.version}
+
+
+
+ org.apache.dubbo
+ dubbo-spring-boot-actuator
+ ${dubbo.version}
+
+
+
+ org.apache.dubbo
+ dubbo
+ ${dubbo.version}
+
+
+
+ com.alibaba.spring
+ spring-context-support
+ ${spring.context.support.version}
+
+
+
+
diff --git a/ruoyi-common/ruoyi-common-bom/pom.xml b/ruoyi-common/ruoyi-common-bom/pom.xml
new file mode 100644
index 0000000..043824e
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-bom/pom.xml
@@ -0,0 +1,223 @@
+
+
+ 4.0.0
+
+ org.dromara
+ ruoyi-common-bom
+ ${revision}
+ pom
+
+
+ ruoyi-common-bom common依赖项
+
+
+
+ 2.1.2
+
+
+
+
+
+
+ org.dromara
+ ruoyi-common-core
+ ${revision}
+
+
+
+
+ org.dromara
+ ruoyi-common-doc
+ ${revision}
+
+
+
+
+ org.dromara
+ ruoyi-common-security
+ ${revision}
+
+
+
+ org.dromara
+ ruoyi-common-satoken
+ ${revision}
+
+
+
+
+ org.dromara
+ ruoyi-common-log
+ ${revision}
+
+
+
+
+ org.dromara
+ ruoyi-common-dict
+ ${revision}
+
+
+
+
+ org.dromara
+ ruoyi-common-excel
+ ${revision}
+
+
+
+
+ org.dromara
+ ruoyi-common-redis
+ ${revision}
+
+
+
+
+ org.dromara
+ ruoyi-common-web
+ ${revision}
+
+
+
+
+ org.dromara
+ ruoyi-common-mybatis
+ ${revision}
+
+
+
+ org.dromara
+ ruoyi-common-job
+ ${revision}
+
+
+
+ org.dromara
+ ruoyi-common-dubbo
+ ${revision}
+
+
+
+ org.dromara
+ ruoyi-common-seata
+ ${revision}
+
+
+
+ org.dromara
+ ruoyi-common-loadbalancer
+ ${revision}
+
+
+
+ org.dromara
+ ruoyi-common-oss
+ ${revision}
+
+
+
+
+ org.dromara
+ ruoyi-common-ratelimiter
+ ${revision}
+
+
+
+ org.dromara
+ ruoyi-common-idempotent
+ ${revision}
+
+
+
+ org.dromara
+ ruoyi-common-mail
+ ${revision}
+
+
+
+ org.dromara
+ ruoyi-common-sms
+ ${revision}
+
+
+
+ org.dromara
+ ruoyi-common-logstash
+ ${revision}
+
+
+
+ org.dromara
+ ruoyi-common-elasticsearch
+ ${revision}
+
+
+
+ org.dromara
+ ruoyi-common-sentinel
+ ${revision}
+
+
+
+ org.dromara
+ ruoyi-common-skylog
+ ${revision}
+
+
+
+ org.dromara
+ ruoyi-common-prometheus
+ ${revision}
+
+
+
+ org.dromara
+ ruoyi-common-translation
+ ${revision}
+
+
+
+
+ org.dromara
+ ruoyi-common-sensitive
+ ${revision}
+
+
+
+
+ org.dromara
+ ruoyi-common-json
+ ${revision}
+
+
+
+ org.dromara
+ ruoyi-common-encrypt
+ ${revision}
+
+
+
+
+ org.dromara
+ ruoyi-common-tenant
+ ${revision}
+
+
+
+ org.dromara
+ ruoyi-common-websocket
+ ${revision}
+
+
+
+ org.dromara
+ ruoyi-common-social
+ ${revision}
+
+
+
+
+
diff --git a/ruoyi-common/ruoyi-common-core/pom.xml b/ruoyi-common/ruoyi-common-core/pom.xml
new file mode 100644
index 0000000..01d876e
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/pom.xml
@@ -0,0 +1,104 @@
+
+
+
+ org.dromara
+ ruoyi-common
+ ${revision}
+
+ 4.0.0
+
+ ruoyi-common-core
+
+
+ ruoyi-common-core 核心模块
+
+
+
+
+
+ org.springframework
+ spring-context-support
+
+
+
+
+ org.springframework
+ spring-web
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-validation
+
+
+
+ org.springframework.boot
+ spring-boot-starter-aop
+
+
+
+
+ org.apache.commons
+ commons-lang3
+
+
+
+
+ jakarta.servlet
+ jakarta.servlet-api
+
+
+
+ io.swagger.core.v3
+ swagger-annotations
+
+
+
+ cn.hutool
+ hutool-core
+
+
+
+ cn.hutool
+ hutool-http
+
+
+
+ cn.hutool
+ hutool-extra
+
+
+
+ org.projectlombok
+ lombok
+
+
+
+
+ org.springframework.boot
+ spring-boot-configuration-processor
+
+
+
+ org.springframework.boot
+ spring-boot-properties-migrator
+ runtime
+
+
+
+ io.github.linpeilie
+ mapstruct-plus-spring-boot-starter
+
+
+
+
+ org.lionsoul
+ ip2region
+
+
+
+
+
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/config/ApplicationConfig.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/config/ApplicationConfig.java
new file mode 100644
index 0000000..07500ba
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/config/ApplicationConfig.java
@@ -0,0 +1,16 @@
+package org.dromara.common.core.config;
+
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.context.annotation.EnableAspectJAutoProxy;
+
+/**
+ * 程序注解配置
+ *
+ * @author Lion Li
+ */
+@AutoConfiguration
+// 表示通过aop框架暴露该代理对象,AopContext能够访问
+@EnableAspectJAutoProxy(exposeProxy = true)
+public class ApplicationConfig {
+
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/config/AsyncConfig.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/config/AsyncConfig.java
new file mode 100644
index 0000000..5a8714e
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/config/AsyncConfig.java
@@ -0,0 +1,111 @@
+package org.dromara.common.core.config;
+
+import cn.hutool.core.util.ArrayUtil;
+import jakarta.annotation.PreDestroy;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.concurrent.BasicThreadFactory;
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.core.utils.SpringUtils;
+import org.dromara.common.core.utils.Threads;
+import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.context.annotation.Bean;
+import org.springframework.scheduling.annotation.AsyncConfigurer;
+import org.springframework.scheduling.annotation.EnableAsync;
+
+import java.util.Arrays;
+import java.util.concurrent.*;
+
+/**
+ * 异步配置
+ *
+ * @author Lion Li
+ */
+@Slf4j
+@EnableAsync(proxyTargetClass = true)
+@AutoConfiguration
+public class AsyncConfig implements AsyncConfigurer {
+
+ private final int corePoolSize = Runtime.getRuntime().availableProcessors() + 1;
+ private ScheduledExecutorService scheduledExecutorService;
+
+ /**
+ * 执行周期性或定时任务
+ */
+ @Bean(name = "scheduledExecutorService")
+ public ScheduledExecutorService scheduledExecutorService() {
+ ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(corePoolSize,
+ new BasicThreadFactory.Builder().namingPattern("schedule-pool-%d").daemon(true).build(),
+ new ThreadPoolExecutor.CallerRunsPolicy()) {
+ @Override
+ protected void afterExecute(Runnable r, Throwable t) {
+ super.afterExecute(r, t);
+ printException(r, t);
+ }
+ };
+ this.scheduledExecutorService = scheduledThreadPoolExecutor;
+ return scheduledThreadPoolExecutor;
+ }
+
+ /**
+ * 销毁事件
+ */
+ @PreDestroy
+ public void destroy() {
+ try {
+ log.info("====关闭后台任务任务线程池====");
+ Threads.shutdownAndAwaitTermination(scheduledExecutorService);
+ } catch (Exception e) {
+ log.error(e.getMessage(), e);
+ }
+ }
+
+ /**
+ * 自定义 @Async 注解使用系统线程池
+ */
+ @Override
+ public Executor getAsyncExecutor() {
+ return SpringUtils.getBean("scheduledExecutorService");
+ }
+
+ /**
+ * 异步执行异常处理
+ */
+ @Override
+ public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
+ return (throwable, method, objects) -> {
+ throwable.printStackTrace();
+ StringBuilder sb = new StringBuilder();
+ sb.append("Exception message - ").append(throwable.getMessage())
+ .append(", Method name - ").append(method.getName());
+ if (ArrayUtil.isNotEmpty(objects)) {
+ sb.append(", Parameter value - ").append(Arrays.toString(objects));
+ }
+ throw new ServiceException(sb.toString());
+ };
+ }
+
+ /**
+ * 打印线程异常信息
+ */
+ public void printException(Runnable r, Throwable t) {
+ if (t == null && r instanceof Future>) {
+ try {
+ Future> future = (Future>) r;
+ if (future.isDone()) {
+ future.get();
+ }
+ } catch (CancellationException ce) {
+ t = ce;
+ } catch (ExecutionException ee) {
+ t = ee.getCause();
+ } catch (InterruptedException ie) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ if (t != null) {
+ log.error(t.getMessage(), t);
+ }
+ }
+
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/config/ValidatorConfig.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/config/ValidatorConfig.java
new file mode 100644
index 0000000..45c5bd1
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/config/ValidatorConfig.java
@@ -0,0 +1,40 @@
+package org.dromara.common.core.config;
+
+import jakarta.validation.Validator;
+import org.hibernate.validator.HibernateValidator;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.context.MessageSource;
+import org.springframework.context.annotation.Bean;
+import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
+
+import java.util.Properties;
+
+/**
+ * 校验框架配置类
+ *
+ * @author Lion Li
+ */
+@AutoConfiguration
+public class ValidatorConfig {
+
+ /**
+ * 配置校验框架 快速返回模式
+ */
+ @Bean
+ public Validator validator(MessageSource messageSource) {
+ try (LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean()) {
+ // 国际化
+ factoryBean.setValidationMessageSource(messageSource);
+ // 设置使用 HibernateValidator 校验器
+ factoryBean.setProviderClass(HibernateValidator.class);
+ Properties properties = new Properties();
+ // 设置 快速异常返回
+ properties.setProperty("hibernate.validator.fail_fast", "true");
+ factoryBean.setValidationProperties(properties);
+ // 加载配置
+ factoryBean.afterPropertiesSet();
+ return factoryBean.getValidator();
+ }
+ }
+
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/CacheConstants.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/CacheConstants.java
new file mode 100644
index 0000000..67bc8e4
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/CacheConstants.java
@@ -0,0 +1,25 @@
+package org.dromara.common.core.constant;
+
+/**
+ * 缓存的key 常量
+ *
+ * @author Lion Li
+ */
+public interface CacheConstants {
+
+ /**
+ * 在线用户 redis key
+ */
+ String ONLINE_TOKEN_KEY = "online_tokens:";
+
+ /**
+ * 参数管理 cache key
+ */
+ String SYS_CONFIG_KEY = "sys_config:";
+
+ /**
+ * 字典管理 cache key
+ */
+ String SYS_DICT_KEY = "sys_dict:";
+
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/CacheNames.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/CacheNames.java
new file mode 100644
index 0000000..e59277a
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/CacheNames.java
@@ -0,0 +1,68 @@
+package org.dromara.common.core.constant;
+
+/**
+ * 缓存组名称常量
+ *
+ * key 格式为 cacheNames#ttl#maxIdleTime#maxSize
+ *
+ * ttl 过期时间 如果设置为0则不过期 默认为0
+ * maxIdleTime 最大空闲时间 根据LRU算法清理空闲数据 如果设置为0则不检测 默认为0
+ * maxSize 组最大长度 根据LRU算法清理溢出数据 如果设置为0则无限长 默认为0
+ *
+ * 例子: test#60s、test#0#60s、test#0#1m#1000、test#1h#0#500
+ *
+ * @author Lion Li
+ */
+public interface CacheNames {
+
+ /**
+ * 演示案例
+ */
+ String DEMO_CACHE = "demo:cache#60s#10m#20";
+
+ /**
+ * 系统配置
+ */
+ String SYS_CONFIG = "sys_config";
+
+ /**
+ * 数据字典
+ */
+ String SYS_DICT = "sys_dict";
+
+ /**
+ * 租户
+ */
+ String SYS_TENANT = GlobalConstants.GLOBAL_REDIS_KEY + "sys_tenant#30d";
+
+ /**
+ * 用户账户
+ */
+ String SYS_USER_NAME = "sys_user_name#30d";
+
+ /**
+ * 用户名称
+ */
+ String SYS_NICKNAME = "sys_nickname#30d";
+
+ /**
+ * 部门
+ */
+ String SYS_DEPT = "sys_dept#30d";
+
+ /**
+ * OSS内容
+ */
+ String SYS_OSS = "sys_oss#30d";
+
+ /**
+ * OSS配置
+ */
+ String SYS_OSS_CONFIG = GlobalConstants.GLOBAL_REDIS_KEY + "sys_oss_config";
+
+ /**
+ * 在线用户
+ */
+ String ONLINE_TOKEN = "online_tokens";
+
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/Constants.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/Constants.java
new file mode 100644
index 0000000..cdbda89
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/Constants.java
@@ -0,0 +1,81 @@
+package org.dromara.common.core.constant;
+
+/**
+ * 通用常量信息
+ *
+ * @author ruoyi
+ */
+public interface Constants {
+
+ /**
+ * UTF-8 字符集
+ */
+ String UTF8 = "UTF-8";
+
+ /**
+ * GBK 字符集
+ */
+ String GBK = "GBK";
+
+ /**
+ * www主域
+ */
+ String WWW = "www.";
+
+ /**
+ * http请求
+ */
+ String HTTP = "http://";
+
+ /**
+ * https请求
+ */
+ String HTTPS = "https://";
+
+ /**
+ * 通用成功标识
+ */
+ String SUCCESS = "0";
+
+ /**
+ * 通用失败标识
+ */
+ String FAIL = "1";
+
+ /**
+ * 登录成功
+ */
+ String LOGIN_SUCCESS = "Success";
+
+ /**
+ * 注销
+ */
+ String LOGOUT = "Logout";
+
+ /**
+ * 注册
+ */
+ String REGISTER = "Register";
+
+ /**
+ * 登录失败
+ */
+ String LOGIN_FAIL = "Error";
+
+ /**
+ * 验证码有效期(分钟)
+ */
+ Integer CAPTCHA_EXPIRATION = 2;
+
+ /**
+ * 令牌
+ */
+ String TOKEN = "token";
+
+ /**
+ * 顶级部门id
+ */
+ Long TOP_PARENT_ID = 0L;
+
+}
+
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/GlobalConstants.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/GlobalConstants.java
new file mode 100644
index 0000000..772da3d
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/GlobalConstants.java
@@ -0,0 +1,61 @@
+package org.dromara.common.core.constant;
+
+/**
+ * 全局的key常量 (业务无关的key)
+ *
+ * @author Lion Li
+ */
+public interface GlobalConstants {
+
+ /**
+ * 全局 redis key (业务无关的key)
+ */
+ String GLOBAL_REDIS_KEY = "global:";
+
+ /**
+ * 验证码 redis key
+ */
+ String CAPTCHA_CODE_KEY = GLOBAL_REDIS_KEY + "captcha_codes:";
+
+
+ /**
+ * 验证码 redis key
+ */
+ String CAPTCHA_MP_PHONE_CODE_KEY = GLOBAL_REDIS_KEY + "mphone_codes:";
+
+
+ /**
+ * 验证码 redis key
+ */
+ String CAPTCHA_PAY_PWD_PHONE_CODE_KEY = GLOBAL_REDIS_KEY + "paypwd_codes:";
+
+
+
+ /**
+ * 防重提交 redis key
+ */
+ String REPEAT_SUBMIT_KEY = GLOBAL_REDIS_KEY + "repeat_submit:";
+
+ /**
+ * 限流 redis key
+ */
+ String RATE_LIMIT_KEY = GLOBAL_REDIS_KEY + "rate_limit:";
+
+ /**
+ * 登录账户密码错误次数 redis key
+ */
+ String PWD_ERR_CNT_KEY = GLOBAL_REDIS_KEY + "pwd_err_cnt:";
+
+ /**
+ * 三方认证 redis key
+ */
+ String SOCIAL_AUTH_CODE_KEY = GLOBAL_REDIS_KEY + "social_auth_codes:";
+
+
+
+ /**
+ * 用工簽約錯誤信息
+ */
+ String USERWORK_SIGN_ERROR_KEY = "userWork_sign:";
+
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/HttpStatus.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/HttpStatus.java
new file mode 100644
index 0000000..85566e8
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/HttpStatus.java
@@ -0,0 +1,93 @@
+package org.dromara.common.core.constant;
+
+/**
+ * 返回状态码
+ *
+ * @author Lion Li
+ */
+public interface HttpStatus {
+ /**
+ * 操作成功
+ */
+ int SUCCESS = 200;
+
+ /**
+ * 对象创建成功
+ */
+ int CREATED = 201;
+
+ /**
+ * 请求已经被接受
+ */
+ int ACCEPTED = 202;
+
+ /**
+ * 操作已经执行成功,但是没有返回数据
+ */
+ int NO_CONTENT = 204;
+
+ /**
+ * 资源已被移除
+ */
+ int MOVED_PERM = 301;
+
+ /**
+ * 重定向
+ */
+ int SEE_OTHER = 303;
+
+ /**
+ * 资源没有被修改
+ */
+ int NOT_MODIFIED = 304;
+
+ /**
+ * 参数列表错误(缺少,格式不匹配)
+ */
+ int BAD_REQUEST = 400;
+
+ /**
+ * 未授权
+ */
+ int UNAUTHORIZED = 401;
+
+ /**
+ * 访问受限,授权过期
+ */
+ int FORBIDDEN = 403;
+
+ /**
+ * 资源,服务未找到
+ */
+ int NOT_FOUND = 404;
+
+ /**
+ * 不允许的http方法
+ */
+ int BAD_METHOD = 405;
+
+ /**
+ * 资源冲突,或者资源被锁
+ */
+ int CONFLICT = 409;
+
+ /**
+ * 不支持的数据,媒体类型
+ */
+ int UNSUPPORTED_TYPE = 415;
+
+ /**
+ * 系统内部错误
+ */
+ int ERROR = 500;
+
+ /**
+ * 接口未实现
+ */
+ int NOT_IMPLEMENTED = 501;
+
+ /**
+ * 系统警告消息
+ */
+ int WARN = 601;
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/MpContants.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/MpContants.java
new file mode 100644
index 0000000..dd413b8
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/MpContants.java
@@ -0,0 +1,14 @@
+package org.dromara.common.core.constant;
+
+/**
+ * @author sunzexing
+ * @version 1.0
+ * @title MpContants
+ * @description 公众号常量
+ * @create 2024-04-07 15:21
+ */
+public interface MpContants {
+
+ String MP_DOMAIN_KEY = "mp_domain_config:";
+
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/TenantConstants.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/TenantConstants.java
new file mode 100644
index 0000000..86b63c9
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/TenantConstants.java
@@ -0,0 +1,45 @@
+package org.dromara.common.core.constant;
+
+/**
+ * 租户常量信息
+ *
+ * @author Lion Li
+ */
+public interface TenantConstants {
+
+ /**
+ * 租户正常状态
+ */
+ String NORMAL = "0";
+
+ /**
+ * 租户封禁状态
+ */
+ String DISABLE = "1";
+
+ /**
+ * 超级管理员ID
+ */
+ Long SUPER_ADMIN_ID = 1L;
+
+ /**
+ * 超级管理员角色 roleKey
+ */
+ String SUPER_ADMIN_ROLE_KEY = "superadmin";
+
+ /**
+ * 租户管理员角色 roleKey
+ */
+ String TENANT_ADMIN_ROLE_KEY = "admin";
+
+ /**
+ * 租户管理员角色名称
+ */
+ String TENANT_ADMIN_ROLE_NAME = "管理员";
+
+ /**
+ * 默认租户ID
+ */
+ String DEFAULT_TENANT_ID = "000000";
+
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/UserConstants.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/UserConstants.java
new file mode 100644
index 0000000..123b493
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/UserConstants.java
@@ -0,0 +1,141 @@
+package org.dromara.common.core.constant;
+
+/**
+ * 用户常量信息
+ *
+ * @author ruoyi
+ */
+public interface UserConstants {
+
+ /**
+ * 平台内系统用户的唯一标志
+ */
+ String SYS_USER = "SYS_USER";
+
+ /**
+ * 正常状态
+ */
+ String NORMAL = "0";
+
+ /**
+ * 异常状态
+ */
+ String EXCEPTION = "1";
+
+ /**
+ * 用户正常状态
+ */
+ String USER_NORMAL = "0";
+
+ /**
+ * 用户封禁状态
+ */
+ String USER_DISABLE = "1";
+
+ /**
+ * 角色正常状态
+ */
+ String ROLE_NORMAL = "0";
+
+ /**
+ * 角色封禁状态
+ */
+ String ROLE_DISABLE = "1";
+
+ /**
+ * 部门正常状态
+ */
+ String DEPT_NORMAL = "0";
+
+ /**
+ * 部门停用状态
+ */
+ String DEPT_DISABLE = "1";
+
+ /**
+ * 岗位正常状态
+ */
+ String POST_NORMAL = "0";
+
+ /**
+ * 岗位停用状态
+ */
+ String POST_DISABLE = "1";
+
+ /**
+ * 字典正常状态
+ */
+ String DICT_NORMAL = "0";
+
+ /**
+ * 是否为系统默认(是)
+ */
+ String YES = "Y";
+
+ /**
+ * 是否菜单外链(是)
+ */
+ String YES_FRAME = "0";
+
+ /**
+ * 是否菜单外链(否)
+ */
+ String NO_FRAME = "1";
+
+ /**
+ * 菜单正常状态
+ */
+ String MENU_NORMAL = "0";
+
+ /**
+ * 菜单停用状态
+ */
+ String MENU_DISABLE = "1";
+
+ /**
+ * 菜单类型(目录)
+ */
+ String TYPE_DIR = "M";
+
+ /**
+ * 菜单类型(菜单)
+ */
+ String TYPE_MENU = "C";
+
+ /**
+ * 菜单类型(按钮)
+ */
+ String TYPE_BUTTON = "F";
+
+ /**
+ * Layout组件标识
+ */
+ String LAYOUT = "Layout";
+
+ /**
+ * ParentView组件标识
+ */
+ String PARENT_VIEW = "ParentView";
+
+ /**
+ * InnerLink组件标识
+ */
+ String INNER_LINK = "InnerLink";
+
+ /**
+ * 用户名长度限制
+ */
+ int USERNAME_MIN_LENGTH = 2;
+ int USERNAME_MAX_LENGTH = 20;
+
+ /**
+ * 密码长度限制
+ */
+ int PASSWORD_MIN_LENGTH = 5;
+ int PASSWORD_MAX_LENGTH = 20;
+
+ /**
+ * 超级管理员ID
+ */
+ Long SUPER_ADMIN_ID = 1L;
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/CascaderVo.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/CascaderVo.java
new file mode 100644
index 0000000..07bf2f0
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/CascaderVo.java
@@ -0,0 +1,25 @@
+package org.dromara.common.core.domain;
+
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * @author sunzexing
+ * @version 1.0
+ * @title CascaderVo
+ * @description
+ * @create 2024-04-18 15:39
+ */
+
+@Data
+public class CascaderVo implements Serializable {
+
+ private String label;
+
+ private String value;
+
+
+
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/R.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/R.java
new file mode 100644
index 0000000..625f58b
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/R.java
@@ -0,0 +1,120 @@
+package org.dromara.common.core.domain;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.dromara.common.core.constant.HttpStatus;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 响应信息主体
+ *
+ * @author Lion Li
+ */
+@Data
+@NoArgsConstructor
+public class R implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 成功
+ */
+ public static final int SUCCESS = 200;
+
+ /**
+ * 失败
+ */
+ public static final int FAIL = 500;
+
+ /**
+ * 消息状态码
+ */
+ private int code;
+
+ /**
+ * 消息内容
+ */
+ private String msg;
+
+ /**
+ * 数据对象
+ */
+ private T data;
+
+ public static R ok() {
+ return restResult(null, SUCCESS, "操作成功");
+ }
+
+ public static R ok(T data) {
+ return restResult(data, SUCCESS, "操作成功");
+ }
+
+ public static R ok(String msg) {
+ return restResult(null, SUCCESS, msg);
+ }
+
+ public static R ok(String msg, T data) {
+ return restResult(data, SUCCESS, msg);
+ }
+
+ public static R fail() {
+ return restResult(null, FAIL, "操作失败");
+ }
+
+ public static R fail(String msg) {
+ return restResult(null, FAIL, msg);
+ }
+
+ public static R fail(T data) {
+ return restResult(data, FAIL, "操作失败");
+ }
+
+ public static R fail(String msg, T data) {
+ return restResult(data, FAIL, msg);
+ }
+
+ public static R fail(int code, String msg) {
+ return restResult(null, code, msg);
+ }
+
+ /**
+ * 返回警告消息
+ *
+ * @param msg 返回内容
+ * @return 警告消息
+ */
+ public static R warn(String msg) {
+ return restResult(null, HttpStatus.WARN, msg);
+ }
+
+ /**
+ * 返回警告消息
+ *
+ * @param msg 返回内容
+ * @param data 数据对象
+ * @return 警告消息
+ */
+ public static R warn(String msg, T data) {
+ return restResult(data, HttpStatus.WARN, msg);
+ }
+
+ private static R restResult(T data, int code, String msg) {
+ R r = new R<>();
+ r.setCode(code);
+ r.setData(data);
+ r.setMsg(msg);
+ return r;
+ }
+
+ public static Boolean isError(R ret) {
+ return !isSuccess(ret);
+ }
+
+ public static Boolean isSuccess(R ret) {
+ return R.SUCCESS == ret.getCode();
+ }
+
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/model/LoginBody.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/model/LoginBody.java
new file mode 100644
index 0000000..1104c6a
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/model/LoginBody.java
@@ -0,0 +1,52 @@
+package org.dromara.common.core.domain.model;
+
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 用户登录对象
+ *
+ * @author Lion Li
+ */
+@Data
+@NoArgsConstructor
+public class LoginBody {
+
+ /**
+ * 客户端id
+ */
+ @NotBlank(message = "{auth.clientid.not.blank}")
+ private String clientId;
+
+ /**
+ * 授权类型
+ */
+ @NotBlank(message = "{auth.grant.type.not.blank}")
+ private String grantType;
+
+ /**
+ * 租户ID
+ */
+ private String tenantId;
+
+ /**
+ * 验证码
+ */
+ private String code;
+
+ /**
+ * 唯一标识
+ */
+ private String uuid;
+
+ /**
+ * 用户id
+ */
+ private Long id;
+
+ /**
+ * 服务商id
+ */
+ private Long sydId;
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/BusRole.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/BusRole.java
new file mode 100644
index 0000000..1cde7e4
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/BusRole.java
@@ -0,0 +1,67 @@
+package org.dromara.common.core.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 用户状态
+ *
+ * @author ruoyi
+ */
+@Getter
+@AllArgsConstructor
+public enum BusRole {
+ /**
+ * 平台
+ */
+ PLATFORM(5, "平台"),
+ /**
+ * 服务商
+ */
+ SERVICE(6, "服务商"),
+ /**
+ * 运营商
+ */
+ OPERATOR(7, "运营商"),
+ /**
+ * 代理商
+ */
+ AGENT(8, "代理商"),
+ /**
+ * 商户
+ */
+ MERCHANT(9,"商户"),
+ /**
+ * 自雇者
+ */
+ WORKER(10, "自雇者");
+
+ private final int id;
+ private final String name;
+
+
+
+ public static String getName(Integer id){
+ BusRole[] brs = BusRole.values();
+ for(BusRole br: brs){
+ if(br.getId() == id){
+ return br.getName();
+ }
+ }
+
+ return null;
+ }
+
+ public static BusRole getRole(Integer id){
+ BusRole[] brs = BusRole.values();
+ for(BusRole br: brs){
+ if(br.getId() == id){
+ return br;
+ }
+ }
+
+ return null;
+ }
+
+
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/DeviceType.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/DeviceType.java
new file mode 100644
index 0000000..09bf44b
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/DeviceType.java
@@ -0,0 +1,32 @@
+package org.dromara.common.core.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 设备类型
+ * 针对一套 用户体系
+ *
+ * @author Lion Li
+ */
+@Getter
+@AllArgsConstructor
+public enum DeviceType {
+
+ /**
+ * pc端
+ */
+ PC("pc"),
+
+ /**
+ * app端
+ */
+ APP("app"),
+
+ /**
+ * 小程序端
+ */
+ XCX("xcx");
+
+ private final String device;
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/LoginType.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/LoginType.java
new file mode 100644
index 0000000..0fa1245
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/LoginType.java
@@ -0,0 +1,49 @@
+package org.dromara.common.core.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 登录类型
+ *
+ * @author Lion Li
+ */
+@Getter
+@AllArgsConstructor
+public enum LoginType {
+
+ /**
+ * 密码登录
+ */
+ PASSWORD("user.password.retry.limit.exceed", "user.password.retry.limit.count"),
+
+ /**
+ * 短信登录
+ */
+ SMS("sms.code.retry.limit.exceed", "sms.code.retry.limit.count"),
+
+ /**
+ * 邮箱登录
+ */
+ EMAIL("email.code.retry.limit.exceed", "email.code.retry.limit.count"),
+
+ /**
+ * 小程序登录
+ */
+ XCX("", ""),
+
+ /**
+ * 微信公众号授权登录
+ */
+ WeiXinPub("", "");
+
+ /**
+ * 登录重试超出限制提示
+ */
+ final String retryLimitExceed;
+
+ /**
+ * 登录重试限制计数提示
+ */
+ final String retryLimitCount;
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/SignType.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/SignType.java
new file mode 100644
index 0000000..98603dc
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/SignType.java
@@ -0,0 +1,23 @@
+package org.dromara.common.core.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * @author sunzexing
+ * @version 1.0
+ * @title SignType
+ * @description
+ * @create 2024-05-20 10:04
+ */
+@Getter
+@AllArgsConstructor
+public enum SignType {
+
+ USER_WORKER_PROTOCOL(1, "用工签约协议"),
+ USER_WORKER_CONFIRMATION(2, "用工确认单");
+
+ private final int id;
+ private final String name;
+
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/SmsType.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/SmsType.java
new file mode 100644
index 0000000..f740143
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/SmsType.java
@@ -0,0 +1,76 @@
+package org.dromara.common.core.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.dromara.common.core.constant.GlobalConstants;
+import org.dromara.common.core.constant.UserConstants;
+
+/**
+ * @author sunzexing
+ * @version 1.0
+ * @title SmsType
+ * @description 短信发送类型
+ * @create 2024-03-27 10:47
+ */
+@Getter
+@AllArgsConstructor
+public enum SmsType {
+
+ MP(1,"公众号登录验证码",2,"temp_dlyzm"),
+
+ SERVICE(2,"服务商登录验证码",2,"temp_dlyzm"),
+
+ AGENT(3,"代理商登录验证码",2,"temp_dlyzm"),
+
+ MERCHANT(4,"商户登录验证码",2,"temp_dlyzm"),
+
+ OPERATORS(5,"运营商登录验证码",2,"temp_dlyzm"),
+
+ PLATFORM(6,"平台登录验证码",2,"temp_dlyzm"),
+
+ RESET_PWD(7,"重置登录密码",2,"temp_czdlmm"),
+
+ RESET_PAY_PWD(8,"重置支付密码",2,"temp_czzfmm"),
+
+ UNIT_OPEN(9,"账号开通",2,"temp_zhkt"),
+
+ PAY(10,"结算支付",2,"temp_zfjs");
+
+ private int iType;
+
+ private String desc;
+
+ private int expireTime;
+
+ private String tempId;
+
+ /**
+ * 获取相应角色的登录短信类型
+ * @param roleId 角色id
+ * @return
+ */
+ public static SmsType getSmsTypeByRole(long roleId){
+ SmsType smsType = null;
+ if(roleId == BusRole.MERCHANT.getId()){
+ smsType = SmsType.MERCHANT;
+ }else if(roleId == BusRole.AGENT.getId()){
+ smsType = SmsType.AGENT;
+ }else if(roleId== BusRole.PLATFORM.getId()){
+ smsType = SmsType.PLATFORM;
+ }else if(roleId == BusRole.SERVICE.getId()){
+ smsType = SmsType.SERVICE;
+ }else if(roleId == BusRole.OPERATOR.getId()){
+ smsType = SmsType.OPERATORS;
+ }
+ return smsType;
+ }
+
+ /**
+ * 获取缓存key
+ * @param phone 手机号
+ * @return
+ */
+ public String getCacheKey(String phone){
+ return GlobalConstants.CAPTCHA_CODE_KEY + ":" + getIType() + ":" + phone;
+ }
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/TenantStatus.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/TenantStatus.java
new file mode 100644
index 0000000..400a399
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/TenantStatus.java
@@ -0,0 +1,30 @@
+package org.dromara.common.core.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 用户状态
+ *
+ * @author LionLi
+ */
+@Getter
+@AllArgsConstructor
+public enum TenantStatus {
+ /**
+ * 正常
+ */
+ OK("0", "正常"),
+ /**
+ * 停用
+ */
+ DISABLE("1", "停用"),
+ /**
+ * 删除
+ */
+ DELETED("2", "删除");
+
+ private final String code;
+ private final String info;
+
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/UserStatus.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/UserStatus.java
new file mode 100644
index 0000000..be7e44d
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/UserStatus.java
@@ -0,0 +1,30 @@
+package org.dromara.common.core.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 用户状态
+ *
+ * @author ruoyi
+ */
+@Getter
+@AllArgsConstructor
+public enum UserStatus {
+ /**
+ * 正常
+ */
+ OK("0", "正常"),
+ /**
+ * 停用
+ */
+ DISABLE("1", "停用"),
+ /**
+ * 删除
+ */
+ DELETED("2", "删除");
+
+ private final String code;
+ private final String info;
+
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/UserType.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/UserType.java
new file mode 100644
index 0000000..69e4753
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/UserType.java
@@ -0,0 +1,37 @@
+package org.dromara.common.core.enums;
+
+import org.dromara.common.core.utils.StringUtils;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 设备类型
+ * 针对多套 用户体系
+ *
+ * @author Lion Li
+ */
+@Getter
+@AllArgsConstructor
+public enum UserType {
+
+ /**
+ * pc端
+ */
+ SYS_USER("sys_user"),
+
+ /**
+ * app端
+ */
+ APP_USER("app_user");
+
+ private final String userType;
+
+ public static UserType getUserType(String str) {
+ for (UserType value : values()) {
+ if (StringUtils.contains(str, value.getUserType())) {
+ return value;
+ }
+ }
+ throw new RuntimeException("'UserType' not found By " + str);
+ }
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/ServiceException.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/ServiceException.java
new file mode 100644
index 0000000..4fb097a
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/ServiceException.java
@@ -0,0 +1,70 @@
+package org.dromara.common.core.exception;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+
+/**
+ * 业务异常
+ *
+ * @author ruoyi
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@NoArgsConstructor
+@AllArgsConstructor
+public final class ServiceException extends RuntimeException {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 错误码
+ */
+ private Integer code;
+
+ /**
+ * 错误提示
+ */
+ private String message;
+
+ /**
+ * 错误明细,内部调试错误
+ */
+ private String detailMessage;
+
+ public ServiceException(String message) {
+ this.message = message;
+ }
+
+ public ServiceException(String message, Integer code) {
+ this.message = message;
+ this.code = code;
+ }
+
+ public String getDetailMessage() {
+ return detailMessage;
+ }
+
+ @Override
+ public String getMessage() {
+ return message;
+ }
+
+ public Integer getCode() {
+ return code;
+ }
+
+ public ServiceException setMessage(String message) {
+ this.message = message;
+ return this;
+ }
+
+ public ServiceException setDetailMessage(String detailMessage) {
+ this.detailMessage = detailMessage;
+ return this;
+ }
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/base/BaseException.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/base/BaseException.java
new file mode 100644
index 0000000..8df8acf
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/base/BaseException.java
@@ -0,0 +1,73 @@
+package org.dromara.common.core.exception.base;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+import org.dromara.common.core.utils.MessageUtils;
+import org.dromara.common.core.utils.StringUtils;
+
+import java.io.Serial;
+
+/**
+ * 基础异常
+ *
+ * @author ruoyi
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@NoArgsConstructor
+@AllArgsConstructor
+public class BaseException extends RuntimeException {
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 所属模块
+ */
+ private String module;
+
+ /**
+ * 错误码
+ */
+ private String code;
+
+ /**
+ * 错误码对应的参数
+ */
+ private Object[] args;
+
+ /**
+ * 错误消息
+ */
+ private String defaultMessage;
+
+ public BaseException(String module, String code, Object[] args) {
+ this(module, code, args, null);
+ }
+
+ public BaseException(String module, String defaultMessage) {
+ this(module, null, null, defaultMessage);
+ }
+
+ public BaseException(String code, Object[] args) {
+ this(null, code, args, null);
+ }
+
+ public BaseException(String defaultMessage) {
+ this(null, null, null, defaultMessage);
+ }
+
+ @Override
+ public String getMessage() {
+ String message = null;
+ if (!StringUtils.isEmpty(code)) {
+ message = MessageUtils.message(code, args);
+ }
+ if (message == null) {
+ message = defaultMessage;
+ }
+ return message;
+ }
+
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/file/FileException.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/file/FileException.java
new file mode 100644
index 0000000..d374fc0
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/file/FileException.java
@@ -0,0 +1,21 @@
+package org.dromara.common.core.exception.file;
+
+import org.dromara.common.core.exception.base.BaseException;
+
+import java.io.Serial;
+
+/**
+ * 文件信息异常类
+ *
+ * @author ruoyi
+ */
+public class FileException extends BaseException {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ public FileException(String code, Object[] args) {
+ super("file", code, args, null);
+ }
+
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/file/FileNameLengthLimitExceededException.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/file/FileNameLengthLimitExceededException.java
new file mode 100644
index 0000000..af98124
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/file/FileNameLengthLimitExceededException.java
@@ -0,0 +1,18 @@
+package org.dromara.common.core.exception.file;
+
+import java.io.Serial;
+
+/**
+ * 文件名称超长限制异常类
+ *
+ * @author ruoyi
+ */
+public class FileNameLengthLimitExceededException extends FileException {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ public FileNameLengthLimitExceededException(int defaultFileNameLength) {
+ super("upload.filename.exceed.length", new Object[]{defaultFileNameLength});
+ }
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/file/FileSizeLimitExceededException.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/file/FileSizeLimitExceededException.java
new file mode 100644
index 0000000..1eb8d40
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/file/FileSizeLimitExceededException.java
@@ -0,0 +1,18 @@
+package org.dromara.common.core.exception.file;
+
+import java.io.Serial;
+
+/**
+ * 文件名大小限制异常类
+ *
+ * @author ruoyi
+ */
+public class FileSizeLimitExceededException extends FileException {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ public FileSizeLimitExceededException(long defaultMaxSize) {
+ super("upload.exceed.maxSize", new Object[]{defaultMaxSize});
+ }
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/user/CaptchaException.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/user/CaptchaException.java
new file mode 100644
index 0000000..a18e581
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/user/CaptchaException.java
@@ -0,0 +1,21 @@
+package org.dromara.common.core.exception.user;
+
+import java.io.Serial;
+
+/**
+ * 验证码错误异常类
+ *
+ * @author Lion Li
+ */
+public class CaptchaException extends UserException {
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ public CaptchaException() {
+ super("user.jcaptcha.error");
+ }
+
+ public CaptchaException(String msg) {
+ super(msg);
+ }
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/user/CaptchaExpireException.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/user/CaptchaExpireException.java
new file mode 100644
index 0000000..f4b8cac
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/user/CaptchaExpireException.java
@@ -0,0 +1,18 @@
+package org.dromara.common.core.exception.user;
+
+import java.io.Serial;
+
+/**
+ * 验证码失效异常类
+ *
+ * @author ruoyi
+ */
+public class CaptchaExpireException extends UserException {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ public CaptchaExpireException() {
+ super("user.jcaptcha.expire");
+ }
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/user/UserException.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/user/UserException.java
new file mode 100644
index 0000000..024fed6
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/user/UserException.java
@@ -0,0 +1,20 @@
+package org.dromara.common.core.exception.user;
+
+import org.dromara.common.core.exception.base.BaseException;
+
+import java.io.Serial;
+
+/**
+ * 用户信息异常类
+ *
+ * @author ruoyi
+ */
+public class UserException extends BaseException {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ public UserException(String code, Object... args) {
+ super("user", code, args, null);
+ }
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/factory/YmlPropertySourceFactory.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/factory/YmlPropertySourceFactory.java
new file mode 100644
index 0000000..af61b90
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/factory/YmlPropertySourceFactory.java
@@ -0,0 +1,31 @@
+package org.dromara.common.core.factory;
+
+import org.dromara.common.core.utils.StringUtils;
+import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
+import org.springframework.core.env.PropertiesPropertySource;
+import org.springframework.core.env.PropertySource;
+import org.springframework.core.io.support.DefaultPropertySourceFactory;
+import org.springframework.core.io.support.EncodedResource;
+
+import java.io.IOException;
+
+/**
+ * yml 配置源工厂
+ *
+ * @author Lion Li
+ */
+public class YmlPropertySourceFactory extends DefaultPropertySourceFactory {
+
+ @Override
+ public PropertySource> createPropertySource(String name, EncodedResource resource) throws IOException {
+ String sourceName = resource.getResource().getFilename();
+ if (StringUtils.isNotBlank(sourceName) && StringUtils.endsWithAny(sourceName, ".yml", ".yaml")) {
+ YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
+ factory.setResources(resource.getResource());
+ factory.afterPropertiesSet();
+ return new PropertiesPropertySource(sourceName, factory.getObject());
+ }
+ return super.createPropertySource(name, resource);
+ }
+
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/BaseService.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/BaseService.java
new file mode 100644
index 0000000..4827624
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/BaseService.java
@@ -0,0 +1,21 @@
+package org.dromara.common.core.service;
+
+import org.dromara.common.core.exception.base.BaseException;
+
+/**
+ * @author sunzexing
+ * @version 1.0
+ * @title BaseService
+ * @description
+ * @create 2024-04-20 16:05
+ */
+public class BaseService {
+
+
+
+ public void invalidationParamsException(String msg){
+ throw new BaseException(msg);
+ }
+
+
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/DictService.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/DictService.java
new file mode 100644
index 0000000..9f2632f
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/DictService.java
@@ -0,0 +1,63 @@
+package org.dromara.common.core.service;
+
+import org.dromara.common.core.utils.StringUtils;
+
+import java.util.Map;
+
+/**
+ * 字典服务服务
+ *
+ * @author Lion Li
+ */
+public interface DictService {
+
+ /**
+ * 根据字典类型和字典值获取字典标签
+ *
+ * @param dictType 字典类型
+ * @param dictValue 字典值
+ * @return 字典标签
+ */
+ default String getDictLabel(String dictType, String dictValue) {
+ return getDictLabel(dictType, dictValue, StringUtils.SEPARATOR);
+ }
+
+ /**
+ * 根据字典类型和字典标签获取字典值
+ *
+ * @param dictType 字典类型
+ * @param dictLabel 字典标签
+ * @return 字典值
+ */
+ default String getDictValue(String dictType, String dictLabel) {
+ return getDictValue(dictType, dictLabel, StringUtils.SEPARATOR);
+ }
+
+ /**
+ * 根据字典类型和字典值获取字典标签
+ *
+ * @param dictType 字典类型
+ * @param dictValue 字典值
+ * @param separator 分隔符
+ * @return 字典标签
+ */
+ String getDictLabel(String dictType, String dictValue, String separator);
+
+ /**
+ * 根据字典类型和字典标签获取字典值
+ *
+ * @param dictType 字典类型
+ * @param dictLabel 字典标签
+ * @param separator 分隔符
+ * @return 字典值
+ */
+ String getDictValue(String dictType, String dictLabel, String separator);
+
+ /**
+ * 获取字典下所有的字典值与标签
+ *
+ * @param dictType 字典类型
+ * @return dictValue为key,dictLabel为值组成的Map
+ */
+ Map getAllDictByDictType(String dictType);
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/DateUtils.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/DateUtils.java
new file mode 100644
index 0000000..89da744
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/DateUtils.java
@@ -0,0 +1,254 @@
+package org.dromara.common.core.utils;
+
+import cn.hutool.core.date.DateUtil;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import org.apache.commons.lang3.time.DateFormatUtils;
+
+import java.lang.management.ManagementFactory;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.util.Calendar;
+import java.util.Date;
+
+/**
+ * 时间工具类
+ *
+ * @author ruoyi
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class DateUtils extends org.apache.commons.lang3.time.DateUtils {
+
+ public static final String YYYY = "yyyy";
+
+ public static final String YYYY_MM = "yyyy-MM";
+
+ public static final String YYYYMMDD = "yyyyMMdd";
+
+ public static final String YYYY_MM_DD = "yyyy-MM-dd";
+
+ public static final String YYYYMMDDHHMMSS = "yyyyMMddHHmmss";
+
+ public static final String YYYYMMDDHHMMSSSSS = "yyyyMMddHHmmssSSS";
+
+ public static final String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss";
+
+ public static final String HH_mm_ss = "HH:mm:ss";
+
+ private static final String[] PARSE_PATTERNS = {
+ "yyyy-MM-dd", "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", "yyyy-MM",
+ "yyyy/MM/dd", "yyyy/MM/dd HH:mm:ss", "yyyy/MM/dd HH:mm", "yyyy/MM",
+ "yyyy.MM.dd", "yyyy.MM.dd HH:mm:ss", "yyyy.MM.dd HH:mm", "yyyy.MM"};
+
+ /**
+ * 获取当前Date型日期
+ *
+ * @return Date() 当前日期
+ */
+ public static Date getNowDate() {
+ return new Date();
+ }
+
+ /**
+ * 获取当前日期, 默认格式为yyyy-MM-dd
+ *
+ * @return String
+ */
+ public static String getDate() {
+ return dateTimeNow(YYYY_MM_DD);
+ }
+
+ public static String getTime() {
+ return dateTimeNow(YYYY_MM_DD_HH_MM_SS);
+ }
+
+ public static String dateTimeNow() {
+ return dateTimeNow(YYYYMMDDHHMMSS);
+ }
+
+ public static String dateTimeNow(final String format) {
+ return parseDateToStr(format, new Date());
+ }
+
+ public static String dateTime(final Date date) {
+ return parseDateToStr(YYYY_MM_DD, date);
+ }
+
+ public static String parseDateToStr(final String format, final Date date) {
+ return new SimpleDateFormat(format).format(date);
+ }
+
+ public static Date dateTime(final String format, final String ts) {
+ try {
+ return new SimpleDateFormat(format).parse(ts);
+ } catch (ParseException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * 日期路径 即年/月/日 如2018/08/08
+ */
+ public static String datePath() {
+ Date now = new Date();
+ return DateFormatUtils.format(now, "yyyy/MM/dd");
+ }
+
+ /**
+ * 日期路径 即年/月/日 如20180808
+ */
+ public static String dateTime() {
+ Date now = new Date();
+ return DateFormatUtils.format(now, "yyyyMMdd");
+ }
+
+ /**
+ * 日期型字符串转化为日期 格式
+ */
+ public static Date parseDate(Object str) {
+ if (str == null) {
+ return null;
+ }
+ try {
+ return parseDate(str.toString(), PARSE_PATTERNS);
+ } catch (ParseException e) {
+ return null;
+ }
+ }
+
+ /**
+ * 获取服务器启动时间
+ */
+ public static Date getServerStartDate() {
+ long time = ManagementFactory.getRuntimeMXBean().getStartTime();
+ return new Date(time);
+ }
+
+ /**
+ * 计算相差天数
+ */
+ public static int differentDaysByMillisecond(Date date1, Date date2) {
+ return Math.abs((int) ((date2.getTime() - date1.getTime()) / (1000 * 3600 * 24)));
+ }
+
+ /**
+ * 计算两个时间差
+ */
+ public static String getDatePoor(Date endDate, Date nowDate) {
+ long nd = 1000 * 24 * 60 * 60;
+ long nh = 1000 * 60 * 60;
+ long nm = 1000 * 60;
+ // long ns = 1000;
+ // 获得两个时间的毫秒时间差异
+ long diff = endDate.getTime() - nowDate.getTime();
+ // 计算差多少天
+ long day = diff / nd;
+ // 计算差多少小时
+ long hour = diff % nd / nh;
+ // 计算差多少分钟
+ long min = diff % nd % nh / nm;
+ // 计算差多少秒//输出结果
+ // long sec = diff % nd % nh % nm / ns;
+ return day + "天" + hour + "小时" + min + "分钟";
+ }
+
+ /**
+ * 增加 LocalDateTime ==> Date
+ */
+ public static Date toDate(LocalDateTime temporalAccessor) {
+ ZonedDateTime zdt = temporalAccessor.atZone(ZoneId.systemDefault());
+ return Date.from(zdt.toInstant());
+ }
+
+ /**
+ * 增加 LocalDate ==> Date
+ */
+ public static Date toDate(LocalDate temporalAccessor) {
+ LocalDateTime localDateTime = LocalDateTime.of(temporalAccessor, LocalTime.of(0, 0, 0));
+ ZonedDateTime zdt = localDateTime.atZone(ZoneId.systemDefault());
+ return Date.from(zdt.toInstant());
+ }
+
+ /**
+ * 根据开始日期获取days天后的日期
+ *
+ * @author 张涛
+ * @date 2015年11月27日
+ * @param beginDate
+ * 开始日期
+ * @param days
+ * 天数
+ * @return
+ */
+ public static Date getEndDateByDays(Date beginDate, int days) {
+ Calendar calendar = Calendar.getInstance();
+ calendar.setTime(beginDate);
+ calendar.add(Calendar.DATE, days);
+ return calendar.getTime();
+ }
+
+ /**
+ * 根据规则转换成指定的时间字符串
+ *
+ * @param pattern
+ * @param date
+ * @return
+ */
+ public static String parseDateToString(String pattern, Date date) {
+ DateFormat df1 = new SimpleDateFormat(pattern);
+ return df1.format(date);
+ }
+
+ public static Date parseStringToDate(String pattern, String dateString) {
+ DateFormat df1 = new SimpleDateFormat(pattern);
+ Date date = null;
+ try {
+ if (dateString != null && !dateString.equals("")) {
+ date = df1.parse(dateString);
+ }
+ } catch (ParseException e) {
+ e.printStackTrace();
+ }
+
+ return date;
+ }
+
+ /**
+ * 获取days天后的日期
+ *
+ * @param days
+ * @return
+ */
+ public static String getEndDateByDays(String beginTime, int days) {
+ Calendar calendar = Calendar.getInstance();
+ calendar.setTime(DateUtils.parseStringToDate(YYYY_MM_DD,
+ beginTime));
+ calendar.add(Calendar.DATE, days);
+ return DateUtils.parseDateToString(YYYY_MM_DD,
+ calendar.getTime());
+ }
+
+ /**
+ * 小时的加减
+ *
+ * @param date
+ * @param i
+ * @return
+ */
+ public static Date addOneHour(Date date, int i) {
+ Calendar calendar = Calendar.getInstance();
+ calendar.setTime(date);
+ calendar.add(Calendar.HOUR, i);
+ return calendar.getTime();
+ }
+
+ public static Date addOneDay(Date date){
+ return DateUtil.offsetDay(date,1);
+ }
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/MapstructUtils.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/MapstructUtils.java
new file mode 100644
index 0000000..a3fcb8e
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/MapstructUtils.java
@@ -0,0 +1,92 @@
+package org.dromara.common.core.utils;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.map.MapUtil;
+import cn.hutool.core.util.ObjectUtil;
+import io.github.linpeilie.Converter;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Mapstruct 工具类
+ * 参考文档:mapstruct-plus
+ *
+ * @author Michelle.Chung
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class MapstructUtils {
+
+ private final static Converter CONVERTER = SpringUtils.getBean(Converter.class);
+
+ /**
+ * 将 T 类型对象,转换为 desc 类型的对象并返回
+ *
+ * @param source 数据来源实体
+ * @param desc 描述对象 转换后的对象
+ * @return desc
+ */
+ public static V convert(T source, Class desc) {
+ if (ObjectUtil.isNull(source)) {
+ return null;
+ }
+ if (ObjectUtil.isNull(desc)) {
+ return null;
+ }
+ return CONVERTER.convert(source, desc);
+ }
+
+ /**
+ * 将 T 类型对象,按照配置的映射字段规则,给 desc 类型的对象赋值并返回 desc 对象
+ *
+ * @param source 数据来源实体
+ * @param desc 转换后的对象
+ * @return desc
+ */
+ public static V convert(T source, V desc) {
+ if (ObjectUtil.isNull(source)) {
+ return null;
+ }
+ if (ObjectUtil.isNull(desc)) {
+ return null;
+ }
+ return CONVERTER.convert(source, desc);
+ }
+
+ /**
+ * 将 T 类型的集合,转换为 desc 类型的集合并返回
+ *
+ * @param sourceList 数据来源实体列表
+ * @param desc 描述对象 转换后的对象
+ * @return desc
+ */
+ public static List convert(List sourceList, Class desc) {
+ if (ObjectUtil.isNull(sourceList)) {
+ return null;
+ }
+ if (CollUtil.isEmpty(sourceList)) {
+ return CollUtil.newArrayList();
+ }
+ return CONVERTER.convert(sourceList, desc);
+ }
+
+ /**
+ * 将 Map 转换为 beanClass 类型的集合并返回
+ *
+ * @param map 数据来源
+ * @param beanClass bean类
+ * @return bean对象
+ */
+ public static T convert(Map map, Class beanClass) {
+ if (MapUtil.isEmpty(map)) {
+ return null;
+ }
+ if (ObjectUtil.isNull(beanClass)) {
+ return null;
+ }
+ return CONVERTER.convert(map, beanClass);
+ }
+
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/MessageUtils.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/MessageUtils.java
new file mode 100644
index 0000000..48dfc08
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/MessageUtils.java
@@ -0,0 +1,33 @@
+package org.dromara.common.core.utils;
+
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import org.springframework.context.MessageSource;
+import org.springframework.context.NoSuchMessageException;
+import org.springframework.context.i18n.LocaleContextHolder;
+
+/**
+ * 获取i18n资源文件
+ *
+ * @author Lion Li
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class MessageUtils {
+
+ private static final MessageSource MESSAGE_SOURCE = SpringUtils.getBean(MessageSource.class);
+
+ /**
+ * 根据消息键和参数 获取消息 委托给spring messageSource
+ *
+ * @param code 消息键
+ * @param args 参数
+ * @return 获取国际化翻译值
+ */
+ public static String message(String code, Object... args) {
+ try {
+ return MESSAGE_SOURCE.getMessage(code, args, LocaleContextHolder.getLocale());
+ } catch (NoSuchMessageException e) {
+ return code;
+ }
+ }
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ReUtil.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ReUtil.java
new file mode 100644
index 0000000..2de7f4f
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ReUtil.java
@@ -0,0 +1,148 @@
+package org.dromara.common.core.utils;
+
+
+import cn.hutool.core.convert.Convert;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class ReUtil {
+ public final static Pattern GROUP_VAR = Pattern.compile("\\$(\\d+)");
+
+ /**
+ * 正则中需要被转义的关键字
+ */
+ public final static Set RE_KEYS = new HashSet<>(
+ Arrays.asList('$', '(', ')', '*', '+', '.', '[', ']', '?', '\\', '^', '{', '}', '|'));
+ ;
+
+ /**
+ * 正则替换指定值
+ * 通过正则查找到字符串,然后把匹配到的字符串加入到replacementTemplate中,$1表示分组1的字符串
+ *
+ *
+ * 例如:原字符串是:中文1234,我想把1234换成(1234),则可以:
+ *
+ *
+ * ReUtil.replaceAll("中文1234", "(\\d+)", "($1)"))
+ *
+ * 结果:中文(1234)
+ *
+ *
+ * @param content 文本
+ * @param regex 正则
+ * @param replacementTemplate 替换的文本模板,可以使用$1类似的变量提取正则匹配出的内容
+ * @return 处理后的文本
+ */
+ public static String replaceAll(CharSequence content, String regex, String replacementTemplate) {
+ final Pattern pattern = Pattern.compile(regex, Pattern.DOTALL);
+ return replaceAll(content, pattern, replacementTemplate);
+ }
+
+ /**
+ * 正则替换指定值
+ * 通过正则查找到字符串,然后把匹配到的字符串加入到replacementTemplate中,$1表示分组1的字符串
+ *
+ * @param content 文本
+ * @param pattern {@link Pattern}
+ * @param replacementTemplate 替换的文本模板,可以使用$1类似的变量提取正则匹配出的内容
+ * @return 处理后的文本
+ * @since 3.0.4
+ */
+ public static String replaceAll(CharSequence content, Pattern pattern, String replacementTemplate) {
+ if (StringUtils.isEmpty(content)) {
+ return StringUtils.EMPTY;
+ }
+
+ final Matcher matcher = pattern.matcher(content);
+ boolean result = matcher.find();
+ if (result) {
+ final Set varNums = findAll(GROUP_VAR, replacementTemplate, 1, new HashSet<>());
+ final StringBuffer sb = new StringBuffer();
+ do {
+ String replacement = replacementTemplate;
+ for (String var : varNums) {
+ int group = Integer.parseInt(var);
+ replacement = replacement.replace("$" + var, matcher.group(group));
+ }
+ matcher.appendReplacement(sb, escape(replacement));
+ result = matcher.find();
+ }
+ while (result);
+ matcher.appendTail(sb);
+ return sb.toString();
+ }
+ return Convert.toStr(content);
+ }
+
+ /**
+ * 取得内容中匹配的所有结果
+ *
+ * @param 集合类型
+ * @param pattern 编译后的正则模式
+ * @param content 被查找的内容
+ * @param group 正则的分组
+ * @param collection 返回的集合类型
+ * @return 结果集
+ */
+ public static > T findAll(Pattern pattern, CharSequence content, int group,
+ T collection) {
+ if (null == pattern || null == content) {
+ return null;
+ }
+
+ if (null == collection) {
+ throw new NullPointerException("Null collection param provided!");
+ }
+
+ final Matcher matcher = pattern.matcher(content);
+ while (matcher.find()) {
+ collection.add(matcher.group(group));
+ }
+ return collection;
+ }
+
+ /**
+ * 转义字符,将正则的关键字转义
+ *
+ * @param c 字符
+ * @return 转义后的文本
+ */
+ public static String escape(char c) {
+ final StringBuilder builder = new StringBuilder();
+ if (RE_KEYS.contains(c)) {
+ builder.append('\\');
+ }
+ builder.append(c);
+ return builder.toString();
+ }
+
+ /**
+ * 转义字符串,将正则的关键字转义
+ *
+ * @param content 文本
+ * @return 转义后的文本
+ */
+ public static String escape(CharSequence content) {
+ if (StringUtils.isBlank(content)) {
+ return StringUtils.EMPTY;
+ }
+
+ final StringBuilder builder = new StringBuilder();
+ int len = content.length();
+ char current;
+ for (int i = 0; i < len; i++) {
+ current = content.charAt(i);
+ if (RE_KEYS.contains(current)) {
+ builder.append('\\');
+ }
+ builder.append(current);
+ }
+ return builder.toString();
+ }
+
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ServletUtils.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ServletUtils.java
new file mode 100644
index 0000000..a1316eb
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ServletUtils.java
@@ -0,0 +1,228 @@
+package org.dromara.common.core.utils;
+
+import cn.hutool.core.convert.Convert;
+import cn.hutool.extra.servlet.JakartaServletUtil;
+import cn.hutool.http.HttpStatus;
+import jakarta.servlet.ServletRequest;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.servlet.http.HttpSession;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import org.springframework.http.MediaType;
+import org.springframework.util.LinkedCaseInsensitiveMap;
+import org.springframework.web.context.request.RequestAttributes;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+
+import java.io.IOException;
+import java.net.URLDecoder;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 客户端工具类
+ *
+ * @author ruoyi
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class ServletUtils extends JakartaServletUtil {
+
+ /**
+ * 获取String参数
+ */
+ public static String getParameter(String name) {
+ return getRequest().getParameter(name);
+ }
+
+ /**
+ * 获取String参数
+ */
+ public static String getParameter(String name, String defaultValue) {
+ return Convert.toStr(getRequest().getParameter(name), defaultValue);
+ }
+
+ /**
+ * 获取Integer参数
+ */
+ public static Integer getParameterToInt(String name) {
+ return Convert.toInt(getRequest().getParameter(name));
+ }
+
+ /**
+ * 获取Integer参数
+ */
+ public static Integer getParameterToInt(String name, Integer defaultValue) {
+ return Convert.toInt(getRequest().getParameter(name), defaultValue);
+ }
+
+ /**
+ * 获取Boolean参数
+ */
+ public static Boolean getParameterToBool(String name) {
+ return Convert.toBool(getRequest().getParameter(name));
+ }
+
+ /**
+ * 获取Boolean参数
+ */
+ public static Boolean getParameterToBool(String name, Boolean defaultValue) {
+ return Convert.toBool(getRequest().getParameter(name), defaultValue);
+ }
+
+ /**
+ * 获得所有请求参数
+ *
+ * @param request 请求对象{@link ServletRequest}
+ * @return Map
+ */
+ public static Map getParams(ServletRequest request) {
+ final Map map = request.getParameterMap();
+ return Collections.unmodifiableMap(map);
+ }
+
+ /**
+ * 获得所有请求参数
+ *
+ * @param request 请求对象{@link ServletRequest}
+ * @return Map
+ */
+ public static Map getParamMap(ServletRequest request) {
+ Map params = new HashMap<>();
+ for (Map.Entry entry : getParams(request).entrySet()) {
+ params.put(entry.getKey(), StringUtils.join(entry.getValue(), StringUtils.SEPARATOR));
+ }
+ return params;
+ }
+
+ /**
+ * 获取request
+ */
+ public static HttpServletRequest getRequest() {
+ try {
+ return getRequestAttributes().getRequest();
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ /**
+ * 获取response
+ */
+ public static HttpServletResponse getResponse() {
+ try {
+ return getRequestAttributes().getResponse();
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ /**
+ * 获取session
+ */
+ public static HttpSession getSession() {
+ return getRequest().getSession();
+ }
+
+ public static ServletRequestAttributes getRequestAttributes() {
+ try {
+ RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
+ return (ServletRequestAttributes) attributes;
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ public static String getHeader(HttpServletRequest request, String name) {
+ String value = request.getHeader(name);
+ if (StringUtils.isEmpty(value)) {
+ return StringUtils.EMPTY;
+ }
+ return urlDecode(value);
+ }
+
+ public static Map getHeaders(HttpServletRequest request) {
+ Map map = new LinkedCaseInsensitiveMap<>();
+ Enumeration enumeration = request.getHeaderNames();
+ if (enumeration != null) {
+ while (enumeration.hasMoreElements()) {
+ String key = enumeration.nextElement();
+ String value = request.getHeader(key);
+ map.put(key, value);
+ }
+ }
+ return map;
+ }
+
+ /**
+ * 将字符串渲染到客户端
+ *
+ * @param response 渲染对象
+ * @param string 待渲染的字符串
+ */
+ public static void renderString(HttpServletResponse response, String string) {
+ try {
+ response.setStatus(HttpStatus.HTTP_OK);
+ response.setContentType(MediaType.APPLICATION_JSON_VALUE);
+ response.setCharacterEncoding(StandardCharsets.UTF_8.toString());
+ response.getWriter().print(string);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * 是否是Ajax异步请求
+ *
+ * @param request
+ */
+ public static boolean isAjaxRequest(HttpServletRequest request) {
+
+ String accept = request.getHeader("accept");
+ if (accept != null && accept.contains(MediaType.APPLICATION_JSON_VALUE)) {
+ return true;
+ }
+
+ String xRequestedWith = request.getHeader("X-Requested-With");
+ if (xRequestedWith != null && xRequestedWith.contains("XMLHttpRequest")) {
+ return true;
+ }
+
+ String uri = request.getRequestURI();
+ if (StringUtils.equalsAnyIgnoreCase(uri, ".json", ".xml")) {
+ return true;
+ }
+
+ String ajax = request.getParameter("__ajax");
+ return StringUtils.equalsAnyIgnoreCase(ajax, "json", "xml");
+ }
+
+ public static String getClientIP() {
+ return getClientIP(getRequest());
+ }
+
+ /**
+ * 内容编码
+ *
+ * @param str 内容
+ * @return 编码后的内容
+ */
+ public static String urlEncode(String str) {
+ return URLEncoder.encode(str, StandardCharsets.UTF_8);
+ }
+
+ /**
+ * 内容解码
+ *
+ * @param str 内容
+ * @return 解码后的内容
+ */
+ public static String urlDecode(String str) {
+ return URLDecoder.decode(str, StandardCharsets.UTF_8);
+ }
+
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/SpringUtils.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/SpringUtils.java
new file mode 100644
index 0000000..0af4d51
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/SpringUtils.java
@@ -0,0 +1,60 @@
+package org.dromara.common.core.utils;
+
+import cn.hutool.extra.spring.SpringUtil;
+import org.springframework.aop.framework.AopContext;
+import org.springframework.beans.factory.NoSuchBeanDefinitionException;
+import org.springframework.context.ApplicationContext;
+
+/**
+ * spring工具类
+ *
+ * @author Lion Li
+ */
+public final class SpringUtils extends SpringUtil {
+
+ /**
+ * 如果BeanFactory包含一个与所给名称匹配的bean定义,则返回true
+ */
+ public static boolean containsBean(String name) {
+ return getBeanFactory().containsBean(name);
+ }
+
+ /**
+ * 判断以给定名字注册的bean定义是一个singleton还是一个prototype。
+ * 如果与给定名字相应的bean定义没有被找到,将会抛出一个异常(NoSuchBeanDefinitionException)
+ */
+ public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException {
+ return getBeanFactory().isSingleton(name);
+ }
+
+ /**
+ * @return Class 注册对象的类型
+ */
+ public static Class> getType(String name) throws NoSuchBeanDefinitionException {
+ return getBeanFactory().getType(name);
+ }
+
+ /**
+ * 如果给定的bean名字在bean定义中有别名,则返回这些别名
+ */
+ public static String[] getAliases(String name) throws NoSuchBeanDefinitionException {
+ return getBeanFactory().getAliases(name);
+ }
+
+ /**
+ * 获取aop代理对象
+ */
+ @SuppressWarnings("unchecked")
+ public static T getAopProxy(T invoker) {
+ return (T) AopContext.currentProxy();
+ }
+
+
+ /**
+ * 获取spring上下文
+ */
+ public static ApplicationContext context() {
+ return getApplicationContext();
+ }
+
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/StreamUtils.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/StreamUtils.java
new file mode 100644
index 0000000..ecdc296
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/StreamUtils.java
@@ -0,0 +1,269 @@
+package org.dromara.common.core.utils;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.map.MapUtil;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+import java.util.*;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+/**
+ * stream 流工具类
+ *
+ * @author Lion Li
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class StreamUtils {
+
+ /**
+ * 将collection过滤
+ *
+ * @param collection 需要转化的集合
+ * @param function 过滤方法
+ * @return 过滤后的list
+ */
+ public static List filter(Collection collection, Predicate function) {
+ if (CollUtil.isEmpty(collection)) {
+ return CollUtil.newArrayList();
+ }
+ // 注意此处不要使用 .toList() 新语法 因为返回的是不可变List 会导致序列化问题
+ return collection.stream().filter(function).collect(Collectors.toList());
+ }
+
+ /**
+ * 将collection拼接
+ *
+ * @param collection 需要转化的集合
+ * @param function 拼接方法
+ * @return 拼接后的list
+ */
+ public static String join(Collection collection, Function function) {
+ return join(collection, function, StringUtils.SEPARATOR);
+ }
+
+ /**
+ * 将collection拼接
+ *
+ * @param collection 需要转化的集合
+ * @param function 拼接方法
+ * @param delimiter 拼接符
+ * @return 拼接后的list
+ */
+ public static String join(Collection collection, Function function, CharSequence delimiter) {
+ if (CollUtil.isEmpty(collection)) {
+ return StringUtils.EMPTY;
+ }
+ return collection.stream().map(function).filter(Objects::nonNull).collect(Collectors.joining(delimiter));
+ }
+
+ /**
+ * 将collection排序
+ *
+ * @param collection 需要转化的集合
+ * @param comparing 排序方法
+ * @return 排序后的list
+ */
+ public static List sorted(Collection collection, Comparator comparing) {
+ if (CollUtil.isEmpty(collection)) {
+ return CollUtil.newArrayList();
+ }
+ // 注意此处不要使用 .toList() 新语法 因为返回的是不可变List 会导致序列化问题
+ return collection.stream().filter(Objects::nonNull).sorted(comparing).collect(Collectors.toList());
+ }
+
+ /**
+ * 将collection转化为类型不变的map
+ * {@code Collection ----> Map}
+ *
+ * @param collection 需要转化的集合
+ * @param key V类型转化为K类型的lambda方法
+ * @param collection中的泛型
+ * @param map中的key类型
+ * @return 转化后的map
+ */
+ public static Map toIdentityMap(Collection collection, Function key) {
+ if (CollUtil.isEmpty(collection)) {
+ return MapUtil.newHashMap();
+ }
+ return collection.stream().filter(Objects::nonNull).collect(Collectors.toMap(key, Function.identity(), (l, r) -> l));
+ }
+
+ /**
+ * 将Collection转化为map(value类型与collection的泛型不同)
+ * {@code Collection -----> Map }
+ *
+ * @param collection 需要转化的集合
+ * @param key E类型转化为K类型的lambda方法
+ * @param value E类型转化为V类型的lambda方法
+ * @param collection中的泛型
+ * @param map中的key类型
+ * @param map中的value类型
+ * @return 转化后的map
+ */
+ public static Map toMap(Collection collection, Function key, Function value) {
+ if (CollUtil.isEmpty(collection)) {
+ return MapUtil.newHashMap();
+ }
+ return collection.stream().filter(Objects::nonNull).collect(Collectors.toMap(key, value, (l, r) -> l));
+ }
+
+ /**
+ * 将collection按照规则(比如有相同的班级id)分类成map
+ * {@code Collection -------> Map> }
+ *
+ * @param collection 需要分类的集合
+ * @param key 分类的规则
+ * @param collection中的泛型
+ * @param map中的key类型
+ * @return 分类后的map
+ */
+ public static Map> groupByKey(Collection collection, Function key) {
+ if (CollUtil.isEmpty(collection)) {
+ return MapUtil.newHashMap();
+ }
+ return collection
+ .stream().filter(Objects::nonNull)
+ .collect(Collectors.groupingBy(key, LinkedHashMap::new, Collectors.toList()));
+ }
+
+ /**
+ * 将collection按照两个规则(比如有相同的年级id,班级id)分类成双层map
+ * {@code Collection ---> Map>> }
+ *
+ * @param collection 需要分类的集合
+ * @param key1 第一个分类的规则
+ * @param key2 第二个分类的规则
+ * @param 集合元素类型
+ * @param 第一个map中的key类型
+ * @param 第二个map中的key类型
+ * @return 分类后的map
+ */
+ public static Map>> groupBy2Key(Collection collection, Function key1, Function key2) {
+ if (CollUtil.isEmpty(collection)) {
+ return MapUtil.newHashMap();
+ }
+ return collection
+ .stream().filter(Objects::nonNull)
+ .collect(Collectors.groupingBy(key1, LinkedHashMap::new, Collectors.groupingBy(key2, LinkedHashMap::new, Collectors.toList())));
+ }
+
+ /**
+ * 将collection按照两个规则(比如有相同的年级id,班级id)分类成双层map
+ * {@code Collection ---> Map> }
+ *
+ * @param collection 需要分类的集合
+ * @param key1 第一个分类的规则
+ * @param key2 第二个分类的规则
+ * @param 第一个map中的key类型
+ * @param 第二个map中的key类型
+ * @param collection中的泛型
+ * @return 分类后的map
+ */
+ public static Map> group2Map(Collection collection, Function key1, Function key2) {
+ if (CollUtil.isEmpty(collection) || key1 == null || key2 == null) {
+ return MapUtil.newHashMap();
+ }
+ return collection
+ .stream().filter(Objects::nonNull)
+ .collect(Collectors.groupingBy(key1, LinkedHashMap::new, Collectors.toMap(key2, Function.identity(), (l, r) -> l)));
+ }
+
+ /**
+ * 将collection转化为List集合,但是两者的泛型不同
+ * {@code Collection ------> List }
+ *
+ * @param collection 需要转化的集合
+ * @param function collection中的泛型转化为list泛型的lambda表达式
+ * @param collection中的泛型
+ * @param List中的泛型
+ * @return 转化后的list
+ */
+ public static List toList(Collection collection, Function function) {
+ return toList(collection,function,Objects::nonNull);
+ }
+
+ /**
+ * 将collection转化为List集合,但是两者的泛型不同
+ * {@code Collection ------> List }
+ *
+ * @param collection 需要转化的集合
+ * @param function collection中的泛型转化为list泛型的lambda表达式
+ * @param collection中的泛型
+ * @param List中的泛型
+ * @param filter 过滤方法
+ * @return 转化后的list
+ */
+ public static List toList(Collection collection, Function function, Predicate filter) {
+ if (CollUtil.isEmpty(collection)) {
+ return CollUtil.newArrayList();
+ }
+ return collection
+ .stream()
+ .map(function)
+ .filter(filter)
+ // 注意此处不要使用 .toList() 新语法 因为返回的是不可变List 会导致序列化问题
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * 将collection转化为Set集合,但是两者的泛型不同
+ * {@code Collection ------> Set }
+ *
+ * @param collection 需要转化的集合
+ * @param function collection中的泛型转化为set泛型的lambda表达式
+ * @param collection中的泛型
+ * @param Set中的泛型
+ * @return 转化后的Set
+ */
+ public static Set toSet(Collection collection, Function function) {
+ if (CollUtil.isEmpty(collection) || function == null) {
+ return CollUtil.newHashSet();
+ }
+ return collection
+ .stream()
+ .map(function)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toSet());
+ }
+
+
+ /**
+ * 合并两个相同key类型的map
+ *
+ * @param map1 第一个需要合并的 map
+ * @param map2 第二个需要合并的 map
+ * @param merge 合并的lambda,将key value1 value2合并成最终的类型,注意value可能为空的情况
+ * @param map中的key类型
+ * @param 第一个 map的value类型
+ * @param 第二个 map的value类型
+ * @param 最终map的value类型
+ * @return 合并后的map
+ */
+ public static Map merge(Map map1, Map map2, BiFunction merge) {
+ if (MapUtil.isEmpty(map1) && MapUtil.isEmpty(map2)) {
+ return MapUtil.newHashMap();
+ } else if (MapUtil.isEmpty(map1)) {
+ map1 = MapUtil.newHashMap();
+ } else if (MapUtil.isEmpty(map2)) {
+ map2 = MapUtil.newHashMap();
+ }
+ Set key = new HashSet<>();
+ key.addAll(map1.keySet());
+ key.addAll(map2.keySet());
+ Map map = new HashMap<>();
+ for (K t : key) {
+ X x = map1.get(t);
+ Y y = map2.get(t);
+ V z = merge.apply(x, y);
+ if (z != null) {
+ map.put(t, z);
+ }
+ }
+ return map;
+ }
+
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/StringUtils.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/StringUtils.java
new file mode 100644
index 0000000..b576ae4
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/StringUtils.java
@@ -0,0 +1,441 @@
+package org.dromara.common.core.utils;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.lang.Validator;
+import cn.hutool.core.util.IdcardUtil;
+import cn.hutool.core.util.RandomUtil;
+import cn.hutool.core.util.StrUtil;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import org.springframework.util.AntPathMatcher;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.text.DecimalFormat;
+import java.util.*;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+/**
+ * 字符串工具类
+ *
+ * @author Lion Li
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class StringUtils extends org.apache.commons.lang3.StringUtils {
+
+ public static final String SEPARATOR = ",";
+
+ /**
+ * 获取参数不为空值
+ *
+ * @param str defaultValue 要判断的value
+ * @return value 返回值
+ */
+ public static String blankToDefault(String str, String defaultValue) {
+ return StrUtil.blankToDefault(str, defaultValue);
+ }
+
+ /**
+ * * 判断一个字符串是否为空串
+ *
+ * @param str String
+ * @return true:为空 false:非空
+ */
+ public static boolean isEmpty(String str) {
+ return StrUtil.isEmpty(str);
+ }
+
+ /**
+ * * 判断一个字符串是否为非空串
+ *
+ * @param str String
+ * @return true:非空串 false:空串
+ */
+ public static boolean isNotEmpty(String str) {
+ return !isEmpty(str);
+ }
+
+ /**
+ * 去空格
+ */
+ public static String trim(String str) {
+ return StrUtil.trim(str);
+ }
+
+ /**
+ * 截取字符串
+ *
+ * @param str 字符串
+ * @param start 开始
+ * @return 结果
+ */
+ public static String substring(final String str, int start) {
+ return substring(str, start, str.length());
+ }
+
+ /**
+ * 截取字符串
+ *
+ * @param str 字符串
+ * @param start 开始
+ * @param end 结束
+ * @return 结果
+ */
+ public static String substring(final String str, int start, int end) {
+ return StrUtil.sub(str, start, end);
+ }
+
+ /**
+ * 格式化文本, {} 表示占位符
+ * 此方法只是简单将占位符 {} 按照顺序替换为参数
+ * 如果想输出 {} 使用 \\转义 { 即可,如果想输出 {} 之前的 \ 使用双转义符 \\\\ 即可
+ * 例:
+ * 通常使用:format("this is {} for {}", "a", "b") -> this is a for b
+ * 转义{}: format("this is \\{} for {}", "a", "b") -> this is {} for a
+ * 转义\: format("this is \\\\{} for {}", "a", "b") -> this is \a for b
+ *
+ * @param template 文本模板,被替换的部分用 {} 表示
+ * @param params 参数值
+ * @return 格式化后的文本
+ */
+ public static String format(String template, Object... params) {
+ return StrUtil.format(template, params);
+ }
+
+ /**
+ * 是否为http(s)://开头
+ *
+ * @param link 链接
+ * @return 结果
+ */
+ public static boolean ishttp(String link) {
+ return Validator.isUrl(link);
+ }
+
+ /**
+ * 字符串转set
+ *
+ * @param str 字符串
+ * @param sep 分隔符
+ * @return set集合
+ */
+ public static Set str2Set(String str, String sep) {
+ return new HashSet<>(str2List(str, sep, true, false));
+ }
+
+ /**
+ * 字符串转list
+ *
+ * @param str 字符串
+ * @param sep 分隔符
+ * @param filterBlank 过滤纯空白
+ * @param trim 去掉首尾空白
+ * @return list集合
+ */
+ public static List str2List(String str, String sep, boolean filterBlank, boolean trim) {
+ List list = new ArrayList<>();
+ if (isEmpty(str)) {
+ return list;
+ }
+
+ // 过滤空白字符串
+ if (filterBlank && isBlank(str)) {
+ return list;
+ }
+ String[] split = str.split(sep);
+ for (String string : split) {
+ if (filterBlank && isBlank(string)) {
+ continue;
+ }
+ if (trim) {
+ string = trim(string);
+ }
+ list.add(string);
+ }
+
+ return list;
+ }
+
+ /**
+ * 查找指定字符串是否包含指定字符串列表中的任意一个字符串同时串忽略大小写
+ *
+ * @param cs 指定字符串
+ * @param searchCharSequences 需要检查的字符串数组
+ * @return 是否包含任意一个字符串
+ */
+ public static boolean containsAnyIgnoreCase(CharSequence cs, CharSequence... searchCharSequences) {
+ return StrUtil.containsAnyIgnoreCase(cs, searchCharSequences);
+ }
+
+ /**
+ * 驼峰转下划线命名
+ */
+ public static String toUnderScoreCase(String str) {
+ return StrUtil.toUnderlineCase(str);
+ }
+
+ /**
+ * 是否包含字符串
+ *
+ * @param str 验证字符串
+ * @param strs 字符串组
+ * @return 包含返回true
+ */
+ public static boolean inStringIgnoreCase(String str, String... strs) {
+ return StrUtil.equalsAnyIgnoreCase(str, strs);
+ }
+
+ /**
+ * 将下划线大写方式命名的字符串转换为驼峰式。如果转换前的下划线大写方式命名的字符串为空,则返回空字符串。 例如:HELLO_WORLD->HelloWorld
+ *
+ * @param name 转换前的下划线大写方式命名的字符串
+ * @return 转换后的驼峰式命名的字符串
+ */
+ public static String convertToCamelCase(String name) {
+ return StrUtil.upperFirst(StrUtil.toCamelCase(name));
+ }
+
+ /**
+ * 驼峰式命名法 例如:user_name->userName
+ */
+ public static String toCamelCase(String s) {
+ return StrUtil.toCamelCase(s);
+ }
+
+ /**
+ * 查找指定字符串是否匹配指定字符串列表中的任意一个字符串
+ *
+ * @param str 指定字符串
+ * @param strs 需要检查的字符串数组
+ * @return 是否匹配
+ */
+ public static boolean matches(String str, List strs) {
+ if (isEmpty(str) || CollUtil.isEmpty(strs)) {
+ return false;
+ }
+ for (String pattern : strs) {
+ if (isMatch(pattern, str)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * 判断url是否与规则配置:
+ * ? 表示单个字符;
+ * * 表示一层路径内的任意字符串,不可跨层级;
+ * ** 表示任意层路径;
+ *
+ * @param pattern 匹配规则
+ * @param url 需要匹配的url
+ */
+ public static boolean isMatch(String pattern, String url) {
+ AntPathMatcher matcher = new AntPathMatcher();
+ return matcher.match(pattern, url);
+ }
+
+ /**
+ * 数字左边补齐0,使之达到指定长度。注意,如果数字转换为字符串后,长度大于size,则只保留 最后size个字符。
+ *
+ * @param num 数字对象
+ * @param size 字符串指定长度
+ * @return 返回数字的字符串格式,该字符串为指定长度。
+ */
+ public static String padl(final Number num, final int size) {
+ return padl(num.toString(), size, '0');
+ }
+
+ /**
+ * 字符串左补齐。如果原始字符串s长度大于size,则只保留最后size个字符。
+ *
+ * @param s 原始字符串
+ * @param size 字符串指定长度
+ * @param c 用于补齐的字符
+ * @return 返回指定长度的字符串,由原字符串左补齐或截取得到。
+ */
+ public static String padl(final String s, final int size, final char c) {
+ final StringBuilder sb = new StringBuilder(size);
+ if (s != null) {
+ final int len = s.length();
+ if (s.length() <= size) {
+ sb.append(String.valueOf(c).repeat(size - len));
+ sb.append(s);
+ } else {
+ return s.substring(len - size, len);
+ }
+ } else {
+ sb.append(String.valueOf(c).repeat(Math.max(0, size)));
+ }
+ return sb.toString();
+ }
+
+ /**
+ * 切分字符串(分隔符默认逗号)
+ *
+ * @param str 被切分的字符串
+ * @return 分割后的数据列表
+ */
+ public static List splitList(String str) {
+ return splitTo(str, Convert::toStr);
+ }
+
+ /**
+ * 切分字符串
+ *
+ * @param str 被切分的字符串
+ * @param separator 分隔符
+ * @return 分割后的数据列表
+ */
+ public static List splitList(String str, String separator) {
+ return splitTo(str, separator, Convert::toStr);
+ }
+
+ /**
+ * 切分字符串自定义转换(分隔符默认逗号)
+ *
+ * @param str 被切分的字符串
+ * @param mapper 自定义转换
+ * @return 分割后的数据列表
+ */
+ public static List splitTo(String str, Function super Object, T> mapper) {
+ return splitTo(str, SEPARATOR, mapper);
+ }
+
+ /**
+ * 切分字符串自定义转换
+ *
+ * @param str 被切分的字符串
+ * @param separator 分隔符
+ * @param mapper 自定义转换
+ * @return 分割后的数据列表
+ */
+ public static List splitTo(String str, String separator, Function super Object, T> mapper) {
+ if (isBlank(str)) {
+ return new ArrayList<>(0);
+ }
+ return StrUtil.split(str, separator)
+ .stream()
+ .filter(Objects::nonNull)
+ .map(mapper)
+ .collect(Collectors.toList());
+ }
+
+ public static boolean isNotBlank(Object obj){
+ return obj != null && isNotBlank(obj+"");
+ }
+
+
+ /*
+ * @Param
+ * @Return
+ * @Author sunzexing
+ * @Date 2024-04-20 17:20
+ * 说明:隱藏身份证
+ **/
+
+ public static String idcardHide(String idcard){
+
+ if(StringUtils.isEmpty(idcard) || idcard.length() < 8){
+ return idcard;
+ }
+ return IdcardUtil.hide(idcard,4,idcard.length()-3);
+ }
+
+
+ /*
+ * @Param
+ * @Return
+ * @Author sunzexing
+ * @Date 2024-04-20 17:20
+ * 说明:隱藏手机号
+ **/
+ public static String phoneHide(String phone){
+
+ if(StringUtils.isEmpty(phone) || phone.length() < 8){
+ return phone;
+ }
+ return IdcardUtil.hide(phone,3,phone.length()-4);
+ }
+
+
+ /****
+ * 公司名字隐藏
+ * @param merName
+ * @return
+ */
+ public static String merNameHide(String merName){
+
+ if(merName.length() <=4 ){
+ return merName.substring(0,1)+"****";
+ }else{
+ return IdcardUtil.hide(merName,2,merName.length()-2);
+ }
+
+ }
+
+
+ /***
+ * 银行卡
+ * @param bankCard
+ * @return
+ */
+ public static String bankHide(String bankCard){
+ return IdcardUtil.hide(bankCard,4,bankCard.length()-4);
+ }
+
+
+
+ public static String currencyFormatter(Object ipt){
+ DecimalFormat df = new DecimalFormat("###.00"); // 格式化为元,保留两位小数
+ String formattedAmount = df.format(ipt);
+ return formattedAmount;
+ }
+
+
+
+
+ public static String perStr(BigDecimal bd){
+ if(bd == null){
+ return "--";
+ }else{
+ return bd.toString()+"%";
+ }
+ }
+
+
+ public static String fenToYuanFormatter(Long fen){
+ if(fen == null){
+ return "--";
+ }
+ return new BigDecimal(String.valueOf(fen)).divide(new BigDecimal("100")).setScale(2).toString();
+ }
+
+
+ /***
+ * 获取域名附件的ossID
+ * @param url
+ * @return
+ */
+ public static String getOssIdDomainFileName(String url){
+ return StrUtil.subBefore(StrUtil.subAfter(url,"/",true),".",true);
+ }
+
+
+ /***
+ * 获取域名附件的后缀
+ * @param url
+ * @return
+ */
+ public static String getDomainSuffix(String url){
+ return StrUtil.subAfter(url,".",true);
+ }
+
+
+ public static String randmNumber(int size){
+ return RandomUtil.randomNumbers(size);
+ }
+
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/Threads.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/Threads.java
new file mode 100644
index 0000000..ae6cfa3
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/Threads.java
@@ -0,0 +1,75 @@
+package org.dromara.common.core.utils;
+
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.concurrent.*;
+
+/**
+ * 线程相关工具类.
+ *
+ * @author ruoyi
+ */
+@Slf4j
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class Threads {
+
+ /**
+ * sleep等待,单位为毫秒
+ */
+ public static void sleep(long milliseconds) {
+ try {
+ Thread.sleep(milliseconds);
+ } catch (InterruptedException e) {
+ return;
+ }
+ }
+
+ /**
+ * 停止线程池
+ * 先使用shutdown, 停止接收新任务并尝试完成所有已存在任务.
+ * 如果超时, 则调用shutdownNow, 取消在workQueue中Pending的任务,并中断所有阻塞函数.
+ * 如果仍然超時,則強制退出.
+ * 另对在shutdown时线程本身被调用中断做了处理.
+ */
+ public static void shutdownAndAwaitTermination(ExecutorService pool) {
+ if (pool != null && !pool.isShutdown()) {
+ pool.shutdown();
+ try {
+ if (!pool.awaitTermination(120, TimeUnit.SECONDS)) {
+ pool.shutdownNow();
+ if (!pool.awaitTermination(120, TimeUnit.SECONDS)) {
+ log.info("Pool did not terminate");
+ }
+ }
+ } catch (InterruptedException ie) {
+ pool.shutdownNow();
+ Thread.currentThread().interrupt();
+ }
+ }
+ }
+
+ /**
+ * 打印线程异常信息
+ */
+ public static void printException(Runnable r, Throwable t) {
+ if (t == null && r instanceof Future>) {
+ try {
+ Future> future = (Future>) r;
+ if (future.isDone()) {
+ future.get();
+ }
+ } catch (CancellationException ce) {
+ t = ce;
+ } catch (ExecutionException ee) {
+ t = ee.getCause();
+ } catch (InterruptedException ie) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ if (t != null) {
+ log.error(t.getMessage(), t);
+ }
+ }
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/TreeBuildUtils.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/TreeBuildUtils.java
new file mode 100644
index 0000000..d0163e6
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/TreeBuildUtils.java
@@ -0,0 +1,35 @@
+package org.dromara.common.core.utils;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.lang.tree.Tree;
+import cn.hutool.core.lang.tree.TreeNodeConfig;
+import cn.hutool.core.lang.tree.TreeUtil;
+import cn.hutool.core.lang.tree.parser.NodeParser;
+import org.dromara.common.core.utils.reflect.ReflectUtils;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+/**
+ * 扩展 hutool TreeUtil 封装系统树构建
+ *
+ * @author Lion Li
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class TreeBuildUtils extends TreeUtil {
+
+ /**
+ * 根据前端定制差异化字段
+ */
+ public static final TreeNodeConfig DEFAULT_CONFIG = TreeNodeConfig.DEFAULT_CONFIG.setNameKey("label");
+
+ public static List> build(List list, NodeParser nodeParser) {
+ if (CollUtil.isEmpty(list)) {
+ return null;
+ }
+ K k = ReflectUtils.invokeGetter(list.get(0), "parentId");
+ return TreeUtil.build(list, k, DEFAULT_CONFIG, nodeParser);
+ }
+
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ValidatorUtils.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ValidatorUtils.java
new file mode 100644
index 0000000..f94f916
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ValidatorUtils.java
@@ -0,0 +1,28 @@
+package org.dromara.common.core.utils;
+
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+import jakarta.validation.ConstraintViolation;
+import jakarta.validation.ConstraintViolationException;
+import jakarta.validation.Validator;
+import java.util.Set;
+
+/**
+ * Validator 校验框架工具
+ *
+ * @author Lion Li
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class ValidatorUtils {
+
+ private static final Validator VALID = SpringUtils.getBean(Validator.class);
+
+ public static void validate(T object, Class>... groups) {
+ Set> validate = VALID.validate(object, groups);
+ if (!validate.isEmpty()) {
+ throw new ConstraintViolationException("参数校验异常", validate);
+ }
+ }
+
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/file/FileUtils.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/file/FileUtils.java
new file mode 100644
index 0000000..3fcc804
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/file/FileUtils.java
@@ -0,0 +1,88 @@
+package org.dromara.common.core.utils.file;
+
+import cn.hutool.core.io.FileUtil;
+import cn.hutool.core.io.IoUtil;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.core.utils.StringUtils;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+/**
+ * 文件处理工具类
+ *
+ * @author Lion Li
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class FileUtils extends FileUtil {
+
+ /**
+ * 下载文件名重新编码
+ *
+ * @param response 响应对象
+ * @param realFileName 真实文件名
+ */
+ public static void setAttachmentResponseHeader(HttpServletResponse response, String realFileName) {
+ String percentEncodedFileName = percentEncode(realFileName);
+ String contentDispositionValue = "attachment; filename=%s;filename*=utf-8''%s".formatted(percentEncodedFileName, percentEncodedFileName);
+ response.addHeader("Access-Control-Expose-Headers", "Content-Disposition,download-filename");
+ response.setHeader("Content-disposition", contentDispositionValue);
+ response.setHeader("download-filename", percentEncodedFileName);
+ }
+
+ /**
+ * 将文件打成压缩包并下载
+ * @param zipName 压缩包文件名
+ * @param response 响应对象
+ * @param files 压缩包内文件列表(物理路径)
+ */
+ public static void downloadZip(String zipName,HttpServletResponse response, List files) throws IOException {
+ if(files == null || files.size() == 0){
+ throw new ServiceException("压缩包内容不能为空");
+ }
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ ZipOutputStream zip = new ZipOutputStream(outputStream);
+ boolean haveFile = false;
+ for (String file:files
+ ) {
+ if(StringUtils.isNotBlank(file) && exist(file)){
+ haveFile = true;
+ // 添加到zip
+ zip.putNextEntry(new ZipEntry(file));
+ zip.flush();
+ zip.closeEntry();
+ }
+ }
+ IoUtil.close(zip);
+ if(!haveFile){
+ throw new ServiceException("压缩包内容不能为空");
+ }
+ byte[] data = outputStream.toByteArray();
+ response.reset();
+ //response.addHeader("Access-Control-Allow-Origin", "*");
+ response.addHeader("Access-Control-Expose-Headers", "Content-Disposition");
+ response.setHeader("Content-Disposition", "attachment; filename=\""+zipName+"\"");
+ response.addHeader("Content-Length", "" + data.length);
+ response.setContentType("application/octet-stream; charset=UTF-8");
+ IoUtil.write(response.getOutputStream(), false, data);
+ }
+
+ /**
+ * 百分号编码工具方法
+ *
+ * @param s 需要百分号编码的字符串
+ * @return 百分号编码后的字符串
+ */
+ public static String percentEncode(String s) {
+ String encode = URLEncoder.encode(s, StandardCharsets.UTF_8);
+ return encode.replaceAll("\\+", "%20");
+ }
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/file/MimeTypeUtils.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/file/MimeTypeUtils.java
new file mode 100644
index 0000000..23fa2cf
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/file/MimeTypeUtils.java
@@ -0,0 +1,40 @@
+package org.dromara.common.core.utils.file;
+
+/**
+ * 媒体类型工具类
+ *
+ * @author ruoyi
+ */
+public class MimeTypeUtils {
+ public static final String IMAGE_PNG = "image/png";
+
+ public static final String IMAGE_JPG = "image/jpg";
+
+ public static final String IMAGE_JPEG = "image/jpeg";
+
+ public static final String IMAGE_BMP = "image/bmp";
+
+ public static final String IMAGE_GIF = "image/gif";
+
+ public static final String[] IMAGE_EXTENSION = {"bmp", "gif", "jpg", "jpeg", "png"};
+
+ public static final String[] FLASH_EXTENSION = {"swf", "flv"};
+
+ public static final String[] MEDIA_EXTENSION = {"swf", "flv", "mp3", "wav", "wma", "wmv", "mid", "avi", "mpg",
+ "asf", "rm", "rmvb"};
+
+ public static final String[] VIDEO_EXTENSION = {"mp4", "avi", "rmvb"};
+
+ public static final String[] DEFAULT_ALLOWED_EXTENSION = {
+ // 图片
+ "bmp", "gif", "jpg", "jpeg", "png",
+ // word excel powerpoint
+ "doc", "docx", "xls", "xlsx", "ppt", "pptx", "html", "htm", "txt",
+ // 压缩文件
+ "rar", "zip", "gz", "bz2",
+ // 视频格式
+ "mp4", "avi", "rmvb",
+ // pdf
+ "pdf"};
+
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ip/AddressUtils.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ip/AddressUtils.java
new file mode 100644
index 0000000..65f9fb7
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ip/AddressUtils.java
@@ -0,0 +1,39 @@
+package org.dromara.common.core.utils.ip;
+
+import cn.hutool.core.net.NetUtil;
+import cn.hutool.http.HtmlUtil;
+import org.dromara.common.core.utils.StringUtils;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * 获取地址类
+ *
+ * @author Lion Li
+ */
+@Slf4j
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class AddressUtils {
+
+ // 未知地址
+ public static final String UNKNOWN = "XX XX";
+
+ public static String getRealAddressByIP(String ip) {
+ if (StringUtils.isBlank(ip)) {
+ return UNKNOWN;
+ }
+ // 内网不查询
+ ip = StringUtils.contains(ip, "0:0:0:0:0:0:0:1") ? "127.0.0.1" : HtmlUtil.cleanHtmlTag(ip);
+ try{
+ if (NetUtil.isInnerIP(ip)) {
+ return "内网IP";
+ }
+ }catch (Exception e){
+ e.printStackTrace();
+ return UNKNOWN;
+ }
+
+ return RegionUtils.getCityInfo(ip);
+ }
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ip/RegionUtils.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ip/RegionUtils.java
new file mode 100644
index 0000000..6e2a44e
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ip/RegionUtils.java
@@ -0,0 +1,67 @@
+package org.dromara.common.core.utils.ip;
+
+import cn.hutool.core.io.FileUtil;
+import cn.hutool.core.io.resource.ClassPathResource;
+import cn.hutool.core.util.ObjectUtil;
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.core.utils.file.FileUtils;
+import lombok.extern.slf4j.Slf4j;
+import org.lionsoul.ip2region.xdb.Searcher;
+
+import java.io.File;
+
+/**
+ * 根据ip地址定位工具类,离线方式
+ * 参考地址:集成 ip2region 实现离线IP地址定位库
+ *
+ * @author lishuyan
+ */
+@Slf4j
+public class RegionUtils {
+
+ private static final Searcher SEARCHER;
+
+ static {
+ String fileName = "/ip2region.xdb";
+ File existFile = FileUtils.file(FileUtil.getTmpDir() + FileUtil.FILE_SEPARATOR + fileName);
+ if (!FileUtils.exist(existFile)) {
+ ClassPathResource fileStream = new ClassPathResource(fileName);
+ if (ObjectUtil.isEmpty(fileStream.getStream())) {
+ throw new ServiceException("RegionUtils初始化失败,原因:IP地址库数据不存在!");
+ }
+ FileUtils.writeFromStream(fileStream.getStream(), existFile);
+ }
+
+ String dbPath = existFile.getPath();
+
+ // 1、从 dbPath 加载整个 xdb 到内存。
+ byte[] cBuff;
+ try {
+ cBuff = Searcher.loadContentFromFile(dbPath);
+ } catch (Exception e) {
+ throw new ServiceException("RegionUtils初始化失败,原因:从ip2region.xdb文件加载内容失败!" + e.getMessage());
+ }
+ // 2、使用上述的 cBuff 创建一个完全基于内存的查询对象。
+ try {
+ SEARCHER = Searcher.newWithBuffer(cBuff);
+ } catch (Exception e) {
+ throw new ServiceException("RegionUtils初始化失败,原因:" + e.getMessage());
+ }
+ }
+
+ /**
+ * 根据IP地址离线获取城市
+ */
+ public static String getCityInfo(String ip) {
+ try {
+ ip = ip.trim();
+ // 3、执行查询
+ String region = SEARCHER.search(ip);
+ return region.replace("0|", "").replace("|0", "");
+ } catch (Exception e) {
+ log.error("IP地址离线获取城市异常 {}", ip);
+ return "未知";
+ }
+ }
+
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/reflect/ReflectUtils.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/reflect/ReflectUtils.java
new file mode 100644
index 0000000..367e8c9
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/reflect/ReflectUtils.java
@@ -0,0 +1,56 @@
+package org.dromara.common.core.utils.reflect;
+
+import cn.hutool.core.util.ReflectUtil;
+import org.dromara.common.core.utils.StringUtils;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+import java.lang.reflect.Method;
+
+/**
+ * 反射工具类. 提供调用getter/setter方法, 访问私有变量, 调用私有方法, 获取泛型类型Class, 被AOP过的真实类等工具函数.
+ *
+ * @author Lion Li
+ */
+@SuppressWarnings("rawtypes")
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class ReflectUtils extends ReflectUtil {
+
+ private static final String SETTER_PREFIX = "set";
+
+ private static final String GETTER_PREFIX = "get";
+
+ /**
+ * 调用Getter方法.
+ * 支持多级,如:对象名.对象名.方法
+ */
+ @SuppressWarnings("unchecked")
+ public static E invokeGetter(Object obj, String propertyName) {
+ Object object = obj;
+ for (String name : StringUtils.split(propertyName, ".")) {
+ String getterMethodName = GETTER_PREFIX + StringUtils.capitalize(name);
+ object = invoke(object, getterMethodName);
+ }
+ return (E) object;
+ }
+
+ /**
+ * 调用Setter方法, 仅匹配方法名。
+ * 支持多级,如:对象名.对象名.方法
+ */
+ public static void invokeSetter(Object obj, String propertyName, E value) {
+ Object object = obj;
+ String[] names = StringUtils.split(propertyName, ".");
+ for (int i = 0; i < names.length; i++) {
+ if (i < names.length - 1) {
+ String getterMethodName = GETTER_PREFIX + StringUtils.capitalize(names[i]);
+ object = invoke(object, getterMethodName);
+ } else {
+ String setterMethodName = SETTER_PREFIX + StringUtils.capitalize(names[i]);
+ Method method = getMethodByName(object.getClass(), setterMethodName);
+ invoke(object, method, value);
+ }
+ }
+ }
+
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/sql/SqlUtil.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/sql/SqlUtil.java
new file mode 100644
index 0000000..3e109b2
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/sql/SqlUtil.java
@@ -0,0 +1,56 @@
+package org.dromara.common.core.utils.sql;
+
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import org.dromara.common.core.utils.StringUtils;
+
+/**
+ * sql操作工具类
+ *
+ * @author ruoyi
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class SqlUtil {
+
+ /**
+ * 定义常用的 sql关键字
+ */
+ public static final String SQL_REGEX = "select |insert |delete |update |drop |count |exec |chr |mid |master |truncate |char |and |declare ";
+
+ /**
+ * 仅支持字母、数字、下划线、空格、逗号、小数点(支持多个字段排序)
+ */
+ public static final String SQL_PATTERN = "[a-zA-Z0-9_\\ \\,\\.]+";
+
+ /**
+ * 检查字符,防止注入绕过
+ */
+ public static String escapeOrderBySql(String value) {
+ if (StringUtils.isNotEmpty(value) && !isValidOrderBySql(value)) {
+ throw new IllegalArgumentException("参数不符合规范,不能进行查询");
+ }
+ return value;
+ }
+
+ /**
+ * 验证 order by 语法是否符合规范
+ */
+ public static boolean isValidOrderBySql(String value) {
+ return value.matches(SQL_PATTERN);
+ }
+
+ /**
+ * SQL关键字检查
+ */
+ public static void filterKeyword(String value) {
+ if (StringUtils.isEmpty(value)) {
+ return;
+ }
+ String[] sqlKeywords = StringUtils.split(SQL_REGEX, "\\|");
+ for (String sqlKeyword : sqlKeywords) {
+ if (StringUtils.indexOfIgnoreCase(value, sqlKeyword) > -1) {
+ throw new IllegalArgumentException("参数存在SQL注入风险");
+ }
+ }
+ }
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/AddGroup.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/AddGroup.java
new file mode 100644
index 0000000..0275899
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/AddGroup.java
@@ -0,0 +1,9 @@
+package org.dromara.common.core.validate;
+
+/**
+ * 校验分组 add
+ *
+ * @author Lion Li
+ */
+public interface AddGroup {
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/EditGroup.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/EditGroup.java
new file mode 100644
index 0000000..77c5040
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/EditGroup.java
@@ -0,0 +1,9 @@
+package org.dromara.common.core.validate;
+
+/**
+ * 校验分组 edit
+ *
+ * @author Lion Li
+ */
+public interface EditGroup {
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/QueryGroup.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/QueryGroup.java
new file mode 100644
index 0000000..02a0ac2
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/QueryGroup.java
@@ -0,0 +1,9 @@
+package org.dromara.common.core.validate;
+
+/**
+ * 校验分组 query
+ *
+ * @author Lion Li
+ */
+public interface QueryGroup {
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/xss/Xss.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/xss/Xss.java
new file mode 100644
index 0000000..eed495f
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/xss/Xss.java
@@ -0,0 +1,26 @@
+package org.dromara.common.core.xss;
+
+import jakarta.validation.Constraint;
+import jakarta.validation.Payload;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 自定义xss校验注解
+ *
+ * @author Lion Li
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(value = {ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.PARAMETER})
+@Constraint(validatedBy = {XssValidator.class})
+public @interface Xss {
+
+ String message() default "不允许任何脚本运行";
+
+ Class>[] groups() default {};
+
+ Class extends Payload>[] payload() default {};
+
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/xss/XssValidator.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/xss/XssValidator.java
new file mode 100644
index 0000000..9c32563
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/xss/XssValidator.java
@@ -0,0 +1,21 @@
+package org.dromara.common.core.xss;
+
+import cn.hutool.core.util.ReUtil;
+import cn.hutool.http.HtmlUtil;
+
+import jakarta.validation.ConstraintValidator;
+import jakarta.validation.ConstraintValidatorContext;
+
+/**
+ * 自定义xss校验注解实现
+ *
+ * @author Lion Li
+ */
+public class XssValidator implements ConstraintValidator {
+
+ @Override
+ public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {
+ return !ReUtil.contains(HtmlUtil.RE_HTML_MARK, value);
+ }
+
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/resources/i18n/messages.properties b/ruoyi-common/ruoyi-common-core/src/main/resources/i18n/messages.properties
new file mode 100644
index 0000000..cce11c8
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/resources/i18n/messages.properties
@@ -0,0 +1,61 @@
+#错误消息
+not.null=* 必须填写
+user.jcaptcha.error=验证码错误
+user.jcaptcha.expire=验证码已失效
+user.not.exists=对不起, 您的账号:{0} 不存在.
+user.password.not.match=用户不存在/密码错误
+user.password.retry.limit.count=密码输入错误{0}次
+user.password.retry.limit.exceed=密码输入错误{0}次,帐户锁定{1}分钟
+user.password.delete=对不起,您的账号:{0} 已被删除
+user.blocked=对不起,您的账号:{0} 已禁用,请联系管理员
+role.blocked=角色已封禁,请联系管理员
+user.logout.success=退出成功
+length.not.valid=长度必须在{min}到{max}个字符之间
+user.username.not.blank=用户名不能为空
+user.username.not.valid=* 2到20个汉字、字母、数字或下划线组成,且必须以非数字开头
+user.username.length.valid=账户长度必须在{min}到{max}个字符之间
+user.password.not.blank=用户密码不能为空
+user.password.length.valid=用户密码长度必须在{min}到{max}个字符之间
+user.password.not.valid=* 5-50个字符
+user.email.not.valid=邮箱格式错误
+user.email.not.blank=邮箱不能为空
+user.phonenumber.not.blank=用户手机号不能为空
+user.mobile.phone.number.not.valid=手机号格式错误
+user.login.success=登录成功
+user.register.success=注册成功
+user.register.save.error=保存用户 {0} 失败,注册账号已存在
+user.register.error=注册失败,请联系系统管理人员
+user.notfound=请重新登录
+user.forcelogout=管理员强制退出,请重新登录
+user.unknown.error=未知错误,请重新登录
+auth.grant.type.error=认证权限类型错误
+auth.grant.type.blocked=认证权限类型已禁用
+auth.grant.type.not.blank=认证权限类型不能为空
+auth.clientid.not.blank=认证客户端id不能为空
+##文件上传消息
+upload.exceed.maxSize=上传的文件大小超出限制的文件大小!
允许的文件最大大小是:{0}MB!
+upload.filename.exceed.length=上传的文件名最长{0}个字符
+##权限
+no.permission=您没有数据的权限,请联系管理员添加权限 [{0}]
+no.create.permission=您没有创建数据的权限,请联系管理员添加权限 [{0}]
+no.update.permission=您没有修改数据的权限,请联系管理员添加权限 [{0}]
+no.delete.permission=您没有删除数据的权限,请联系管理员添加权限 [{0}]
+no.export.permission=您没有导出数据的权限,请联系管理员添加权限 [{0}]
+no.view.permission=您没有查看数据的权限,请联系管理员添加权限 [{0}]
+repeat.submit.message=不允许重复提交,请稍候再试
+rate.limiter.message=访问过于频繁,请稍候再试
+sms.code.not.blank=短信验证码不能为空
+sms.code.retry.limit.count=短信验证码输入错误{0}次
+sms.code.retry.limit.exceed=短信验证码输入错误{0}次,帐户锁定{1}分钟
+email.code.not.blank=邮箱验证码不能为空
+email.code.retry.limit.count=邮箱验证码输入错误{0}次
+email.code.retry.limit.exceed=邮箱验证码输入错误{0}次,帐户锁定{1}分钟
+xcx.code.not.blank=小程序[code]不能为空
+social.source.not.blank=第三方登录平台[source]不能为空
+social.code.not.blank=第三方登录平台[code]不能为空
+social.state.not.blank=第三方登录平台[state]不能为空
+##租户
+tenant.number.not.blank=租户编号不能为空
+tenant.not.exists=对不起, 您的租户不存在,请联系管理员
+tenant.blocked=对不起,您的租户已禁用,请联系管理员
+tenant.expired=对不起,您的租户已过期,请联系管理员
diff --git a/ruoyi-common/ruoyi-common-core/src/main/resources/i18n/messages_en_US.properties b/ruoyi-common/ruoyi-common-core/src/main/resources/i18n/messages_en_US.properties
new file mode 100644
index 0000000..f948c4a
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/resources/i18n/messages_en_US.properties
@@ -0,0 +1,61 @@
+#错误消息
+not.null=* Required fill in
+user.jcaptcha.error=Captcha error
+user.jcaptcha.expire=Captcha invalid
+user.not.exists=Sorry, your account: {0} does not exist
+user.password.not.match=User does not exist/Password error
+user.password.retry.limit.count=Password input error {0} times
+user.password.retry.limit.exceed=Password input error {0} times, account locked for {1} minutes
+user.password.delete=Sorry, your account:{0} has been deleted
+user.blocked=Sorry, your account: {0} has been disabled. Please contact the administrator
+role.blocked=Role disabled,please contact administrators
+user.logout.success=Exit successful
+length.not.valid=The length must be between {min} and {max} characters
+user.username.not.blank=Username cannot be blank
+user.username.not.valid=* 2 to 20 chinese characters, letters, numbers or underscores, and must start with a non number
+user.username.length.valid=Account length must be between {min} and {max} characters
+user.password.not.blank=Password cannot be empty
+user.password.length.valid=Password length must be between {min} and {max} characters
+user.password.not.valid=* 5-50 characters
+user.email.not.valid=Mailbox format error
+user.email.not.blank=Mailbox cannot be blank
+user.phonenumber.not.blank=Phone number cannot be blank
+user.mobile.phone.number.not.valid=Phone number format error
+user.login.success=Login successful
+user.register.success=Register successful
+user.register.save.error=Failed to save user {0}, The registered account already exists
+user.register.error=Register failed, please contact system administrator
+user.notfound=Please login again
+user.forcelogout=The administrator is forced to exit,please login again
+user.unknown.error=Unknown error, please login again
+auth.grant.type.error=Auth grant type error
+auth.grant.type.blocked=Auth grant type disabled
+auth.grant.type.not.blank=Auth grant type cannot be blank
+auth.clientid.not.blank=Auth clientid cannot be blank
+##文件上传消息
+upload.exceed.maxSize=The uploaded file size exceeds the limit file size!
the maximum allowed file size is:{0}MB!
+upload.filename.exceed.length=The maximum length of uploaded file name is {0} characters
+##权限
+no.permission=You do not have permission to the data,please contact your administrator to add permissions [{0}]
+no.create.permission=You do not have permission to create data,please contact your administrator to add permissions [{0}]
+no.update.permission=You do not have permission to modify data,please contact your administrator to add permissions [{0}]
+no.delete.permission=You do not have permission to delete data,please contact your administrator to add permissions [{0}]
+no.export.permission=You do not have permission to export data,please contact your administrator to add permissions [{0}]
+no.view.permission=You do not have permission to view data,please contact your administrator to add permissions [{0}]
+repeat.submit.message=Repeat submit is not allowed, please try again later
+rate.limiter.message=Visit too frequently, please try again later
+sms.code.not.blank=Sms code cannot be blank
+sms.code.retry.limit.count=Sms code input error {0} times
+sms.code.retry.limit.exceed=Sms code input error {0} times, account locked for {1} minutes
+email.code.not.blank=Email code cannot be blank
+email.code.retry.limit.count=Email code input error {0} times
+email.code.retry.limit.exceed=Email code input error {0} times, account locked for {1} minutes
+xcx.code.not.blank=Mini program [code] cannot be blank
+social.source.not.blank=Social login platform [source] cannot be blank
+social.code.not.blank=Social login platform [code] cannot be blank
+social.state.not.blank=Social login platform [state] cannot be blank
+##租户
+tenant.number.not.blank=Tenant number cannot be blank
+tenant.not.exists=Sorry, your tenant does not exist. Please contact the administrator
+tenant.blocked=Sorry, your tenant is disabled. Please contact the administrator
+tenant.expired=Sorry, your tenant has expired. Please contact the administrator.
diff --git a/ruoyi-common/ruoyi-common-core/src/main/resources/i18n/messages_zh_CN.properties b/ruoyi-common/ruoyi-common-core/src/main/resources/i18n/messages_zh_CN.properties
new file mode 100644
index 0000000..cce11c8
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/resources/i18n/messages_zh_CN.properties
@@ -0,0 +1,61 @@
+#错误消息
+not.null=* 必须填写
+user.jcaptcha.error=验证码错误
+user.jcaptcha.expire=验证码已失效
+user.not.exists=对不起, 您的账号:{0} 不存在.
+user.password.not.match=用户不存在/密码错误
+user.password.retry.limit.count=密码输入错误{0}次
+user.password.retry.limit.exceed=密码输入错误{0}次,帐户锁定{1}分钟
+user.password.delete=对不起,您的账号:{0} 已被删除
+user.blocked=对不起,您的账号:{0} 已禁用,请联系管理员
+role.blocked=角色已封禁,请联系管理员
+user.logout.success=退出成功
+length.not.valid=长度必须在{min}到{max}个字符之间
+user.username.not.blank=用户名不能为空
+user.username.not.valid=* 2到20个汉字、字母、数字或下划线组成,且必须以非数字开头
+user.username.length.valid=账户长度必须在{min}到{max}个字符之间
+user.password.not.blank=用户密码不能为空
+user.password.length.valid=用户密码长度必须在{min}到{max}个字符之间
+user.password.not.valid=* 5-50个字符
+user.email.not.valid=邮箱格式错误
+user.email.not.blank=邮箱不能为空
+user.phonenumber.not.blank=用户手机号不能为空
+user.mobile.phone.number.not.valid=手机号格式错误
+user.login.success=登录成功
+user.register.success=注册成功
+user.register.save.error=保存用户 {0} 失败,注册账号已存在
+user.register.error=注册失败,请联系系统管理人员
+user.notfound=请重新登录
+user.forcelogout=管理员强制退出,请重新登录
+user.unknown.error=未知错误,请重新登录
+auth.grant.type.error=认证权限类型错误
+auth.grant.type.blocked=认证权限类型已禁用
+auth.grant.type.not.blank=认证权限类型不能为空
+auth.clientid.not.blank=认证客户端id不能为空
+##文件上传消息
+upload.exceed.maxSize=上传的文件大小超出限制的文件大小!
允许的文件最大大小是:{0}MB!
+upload.filename.exceed.length=上传的文件名最长{0}个字符
+##权限
+no.permission=您没有数据的权限,请联系管理员添加权限 [{0}]
+no.create.permission=您没有创建数据的权限,请联系管理员添加权限 [{0}]
+no.update.permission=您没有修改数据的权限,请联系管理员添加权限 [{0}]
+no.delete.permission=您没有删除数据的权限,请联系管理员添加权限 [{0}]
+no.export.permission=您没有导出数据的权限,请联系管理员添加权限 [{0}]
+no.view.permission=您没有查看数据的权限,请联系管理员添加权限 [{0}]
+repeat.submit.message=不允许重复提交,请稍候再试
+rate.limiter.message=访问过于频繁,请稍候再试
+sms.code.not.blank=短信验证码不能为空
+sms.code.retry.limit.count=短信验证码输入错误{0}次
+sms.code.retry.limit.exceed=短信验证码输入错误{0}次,帐户锁定{1}分钟
+email.code.not.blank=邮箱验证码不能为空
+email.code.retry.limit.count=邮箱验证码输入错误{0}次
+email.code.retry.limit.exceed=邮箱验证码输入错误{0}次,帐户锁定{1}分钟
+xcx.code.not.blank=小程序[code]不能为空
+social.source.not.blank=第三方登录平台[source]不能为空
+social.code.not.blank=第三方登录平台[code]不能为空
+social.state.not.blank=第三方登录平台[state]不能为空
+##租户
+tenant.number.not.blank=租户编号不能为空
+tenant.not.exists=对不起, 您的租户不存在,请联系管理员
+tenant.blocked=对不起,您的租户已禁用,请联系管理员
+tenant.expired=对不起,您的租户已过期,请联系管理员
diff --git a/ruoyi-common/ruoyi-common-core/src/main/resources/ip2region.xdb b/ruoyi-common/ruoyi-common-core/src/main/resources/ip2region.xdb
new file mode 100644
index 0000000..31f96a1
Binary files /dev/null and b/ruoyi-common/ruoyi-common-core/src/main/resources/ip2region.xdb differ
diff --git a/ruoyi-common/ruoyi-common-dict/pom.xml b/ruoyi-common/ruoyi-common-dict/pom.xml
new file mode 100644
index 0000000..6cf4efc
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-dict/pom.xml
@@ -0,0 +1,42 @@
+
+
+
+ org.dromara
+ ruoyi-common
+ ${revision}
+
+ 4.0.0
+
+ ruoyi-common-dict
+
+
+ ruoyi-common-dict 字典
+
+
+
+
+
+
+ org.dromara
+ ruoyi-common-redis
+
+
+
+ org.dromara
+ ruoyi-api-system
+
+
+
+ org.apache.dubbo
+ dubbo-spring-boot-starter
+ provided
+
+
+ cn.dev33
+ sa-token-core
+
+
+
+
diff --git a/ruoyi-common/ruoyi-common-dict/src/main/java/org/dromara/common/dict/service/impl/DictServiceImpl.java b/ruoyi-common/ruoyi-common-dict/src/main/java/org/dromara/common/dict/service/impl/DictServiceImpl.java
new file mode 100644
index 0000000..2de6c55
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-dict/src/main/java/org/dromara/common/dict/service/impl/DictServiceImpl.java
@@ -0,0 +1,92 @@
+package org.dromara.common.dict.service.impl;
+
+import cn.dev33.satoken.context.SaHolder;
+import cn.hutool.core.util.ObjectUtil;
+import org.dromara.common.core.constant.CacheConstants;
+import org.dromara.common.core.service.DictService;
+import org.dromara.common.core.utils.StreamUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.system.api.RemoteDictService;
+import org.apache.dubbo.config.annotation.DubboReference;
+import org.dromara.system.api.domain.vo.RemoteDictDataVo;
+import org.springframework.stereotype.Service;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * 字典服务服务
+ *
+ * @author Lion Li
+ */
+@Service
+public class DictServiceImpl implements DictService {
+
+ @DubboReference
+ private RemoteDictService remoteDictService;
+
+ /**
+ * 根据字典类型和字典值获取字典标签
+ *
+ * @param dictType 字典类型
+ * @param dictValue 字典值
+ * @param separator 分隔符
+ * @return 字典标签
+ */
+ @SuppressWarnings("unchecked cast")
+ @Override
+ public String getDictLabel(String dictType, String dictValue, String separator) {
+ // 优先从本地缓存获取
+ List datas = (List) SaHolder.getStorage().get(CacheConstants.SYS_DICT_KEY + dictType);
+ if (ObjectUtil.isNull(datas)) {
+ datas = remoteDictService.selectDictDataByType(dictType);
+ SaHolder.getStorage().set(CacheConstants.SYS_DICT_KEY + dictType, datas);
+ }
+
+ Map map = StreamUtils.toMap(datas, RemoteDictDataVo::getDictValue, RemoteDictDataVo::getDictLabel);
+ if (StringUtils.containsAny(dictValue, separator)) {
+ return Arrays.stream(dictValue.split(separator))
+ .map(v -> map.getOrDefault(v, StringUtils.EMPTY))
+ .collect(Collectors.joining(separator));
+ } else {
+ return map.getOrDefault(dictValue, StringUtils.EMPTY);
+ }
+ }
+
+ /**
+ * 根据字典类型和字典标签获取字典值
+ *
+ * @param dictType 字典类型
+ * @param dictLabel 字典标签
+ * @param separator 分隔符
+ * @return 字典值
+ */
+ @SuppressWarnings("unchecked cast")
+ @Override
+ public String getDictValue(String dictType, String dictLabel, String separator) {
+ // 优先从本地缓存获取
+ List datas = (List) SaHolder.getStorage().get(CacheConstants.SYS_DICT_KEY + dictType);
+ if (ObjectUtil.isNull(datas)) {
+ datas = remoteDictService.selectDictDataByType(dictType);
+ SaHolder.getStorage().set(CacheConstants.SYS_DICT_KEY + dictType, datas);
+ }
+
+ Map map = StreamUtils.toMap(datas, RemoteDictDataVo::getDictLabel, RemoteDictDataVo::getDictValue);
+ if (StringUtils.containsAny(dictLabel, separator)) {
+ return Arrays.stream(dictLabel.split(separator))
+ .map(l -> map.getOrDefault(l, StringUtils.EMPTY))
+ .collect(Collectors.joining(separator));
+ } else {
+ return map.getOrDefault(dictLabel, StringUtils.EMPTY);
+ }
+ }
+
+ @Override
+ public Map getAllDictByDictType(String dictType) {
+ List list = remoteDictService.selectDictDataByType(dictType);
+ return StreamUtils.toMap(list, RemoteDictDataVo::getDictValue, RemoteDictDataVo::getDictLabel);
+ }
+
+}
diff --git a/ruoyi-common/ruoyi-common-dict/src/main/java/org/dromara/common/dict/utils/DictUtils.java b/ruoyi-common/ruoyi-common-dict/src/main/java/org/dromara/common/dict/utils/DictUtils.java
new file mode 100644
index 0000000..93b2403
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-dict/src/main/java/org/dromara/common/dict/utils/DictUtils.java
@@ -0,0 +1,51 @@
+package org.dromara.common.dict.utils;
+
+import org.dromara.common.core.constant.CacheNames;
+import org.dromara.common.redis.utils.CacheUtils;
+import org.dromara.system.api.domain.vo.RemoteDictDataVo;
+
+import java.util.List;
+
+/**
+ * 字典工具类
+ *
+ * @author ruoyi
+ */
+public class DictUtils {
+ /**
+ * 设置字典缓存
+ *
+ * @param key 参数键
+ * @param dictDatas 字典数据列表
+ */
+ public static void setDictCache(String key, List dictDatas) {
+ CacheUtils.put(CacheNames.SYS_DICT, key, dictDatas);
+ }
+
+ /**
+ * 获取字典缓存
+ *
+ * @param key 参数键
+ * @return dictDatas 字典数据列表
+ */
+ public static List getDictCache(String key) {
+ return CacheUtils.get(CacheNames.SYS_DICT, key);
+ }
+
+ /**
+ * 删除指定字典缓存
+ *
+ * @param key 字典键
+ */
+ public static void removeDictCache(String key) {
+ CacheUtils.evict(CacheNames.SYS_DICT, key);
+ }
+
+ /**
+ * 清空字典缓存
+ */
+ public static void clearDictCache() {
+ CacheUtils.clear(CacheNames.SYS_DICT);
+ }
+
+}
diff --git a/ruoyi-common/ruoyi-common-doc/pom.xml b/ruoyi-common/ruoyi-common-doc/pom.xml
new file mode 100644
index 0000000..26a35fc
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-doc/pom.xml
@@ -0,0 +1,42 @@
+
+
+
+ org.dromara
+ ruoyi-common
+ ${revision}
+
+ 4.0.0
+
+ ruoyi-common-doc
+
+
+ ruoyi-common-doc 系统接口
+
+
+
+
+
+ org.dromara
+ ruoyi-common-core
+
+
+
+ org.springdoc
+ springdoc-openapi-starter-webmvc-api
+
+
+
+ com.github.therapi
+ therapi-runtime-javadoc
+
+
+
+ com.fasterxml.jackson.module
+ jackson-module-kotlin
+
+
+
+
+
diff --git a/ruoyi-common/ruoyi-common-doc/src/main/java/org/dromara/common/doc/config/PlusPaths.java b/ruoyi-common/ruoyi-common-doc/src/main/java/org/dromara/common/doc/config/PlusPaths.java
new file mode 100644
index 0000000..23075d2
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-doc/src/main/java/org/dromara/common/doc/config/PlusPaths.java
@@ -0,0 +1,15 @@
+package org.dromara.common.doc.config;
+
+import io.swagger.v3.oas.models.Paths;
+
+/**
+ * 单独使用一个类便于判断 解决springdoc路径拼接重复问题
+ *
+ * @author Lion Li
+ */
+public class PlusPaths extends Paths {
+
+ public PlusPaths() {
+ super();
+ }
+}
diff --git a/ruoyi-common/ruoyi-common-doc/src/main/java/org/dromara/common/doc/config/SpringDocAutoConfiguration.java b/ruoyi-common/ruoyi-common-doc/src/main/java/org/dromara/common/doc/config/SpringDocAutoConfiguration.java
new file mode 100644
index 0000000..a6adaec
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-doc/src/main/java/org/dromara/common/doc/config/SpringDocAutoConfiguration.java
@@ -0,0 +1,117 @@
+package org.dromara.common.doc.config;
+
+import io.swagger.v3.oas.models.OpenAPI;
+import io.swagger.v3.oas.models.Paths;
+import io.swagger.v3.oas.models.info.Info;
+import io.swagger.v3.oas.models.security.SecurityRequirement;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.doc.config.properties.SpringDocProperties;
+import org.dromara.common.doc.handler.OpenApiHandler;
+import org.springdoc.core.configuration.SpringDocConfiguration;
+import org.springdoc.core.customizers.OpenApiBuilderCustomizer;
+import org.springdoc.core.customizers.OpenApiCustomizer;
+import org.springdoc.core.customizers.ServerBaseUrlCustomizer;
+import org.springdoc.core.properties.SpringDocConfigProperties;
+import org.springdoc.core.providers.JavadocProvider;
+import org.springdoc.core.service.OpenAPIService;
+import org.springdoc.core.service.SecurityService;
+import org.springdoc.core.utils.PropertyResolverUtils;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.autoconfigure.web.ServerProperties;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+
+import java.util.*;
+
+/**
+ * Swagger 文档配置
+ *
+ * @author Lion Li
+ */
+@RequiredArgsConstructor
+@AutoConfiguration(before = SpringDocConfiguration.class)
+@EnableConfigurationProperties(SpringDocProperties.class)
+@ConditionalOnProperty(name = "springdoc.api-docs.enabled", havingValue = "true", matchIfMissing = true)
+public class SpringDocAutoConfiguration {
+
+ private final ServerProperties serverProperties;
+
+ @Value("${spring.application.name}")
+ private String appName;
+
+ @Bean
+ @ConditionalOnMissingBean(OpenAPI.class)
+ public OpenAPI openApi(SpringDocProperties properties) {
+ OpenAPI openApi = new OpenAPI();
+ // 文档基本信息
+ SpringDocProperties.InfoProperties infoProperties = properties.getInfo();
+ Info info = convertInfo(infoProperties);
+ openApi.info(info);
+ // 扩展文档信息
+ openApi.externalDocs(properties.getExternalDocs());
+ openApi.tags(properties.getTags());
+ openApi.paths(properties.getPaths());
+ openApi.components(properties.getComponents());
+ Set keySet = properties.getComponents().getSecuritySchemes().keySet();
+ List list = new ArrayList<>();
+ SecurityRequirement securityRequirement = new SecurityRequirement();
+ keySet.forEach(securityRequirement::addList);
+ list.add(securityRequirement);
+ openApi.security(list);
+
+ return openApi;
+ }
+
+ private Info convertInfo(SpringDocProperties.InfoProperties infoProperties) {
+ Info info = new Info();
+ info.setTitle(infoProperties.getTitle());
+ info.setDescription(infoProperties.getDescription());
+ info.setContact(infoProperties.getContact());
+ info.setLicense(infoProperties.getLicense());
+ info.setVersion(infoProperties.getVersion());
+ return info;
+ }
+
+ /**
+ * 自定义 openapi 处理器
+ */
+ @Bean
+ public OpenAPIService openApiBuilder(Optional openAPI,
+ SecurityService securityParser,
+ SpringDocConfigProperties springDocConfigProperties, PropertyResolverUtils propertyResolverUtils,
+ Optional> openApiBuilderCustomisers,
+ Optional> serverBaseUrlCustomisers, Optional javadocProvider) {
+ return new OpenApiHandler(openAPI, securityParser, springDocConfigProperties, propertyResolverUtils, openApiBuilderCustomisers, serverBaseUrlCustomisers, javadocProvider);
+ }
+
+ /**
+ * 对已经生成好的 OpenApi 进行自定义操作
+ */
+ @Bean
+ public OpenApiCustomizer openApiCustomizer() {
+ // 拼接服务路径
+ String appPath = "/" + StringUtils.substring(appName, appName.indexOf("-") + 1);
+ String contextPath = serverProperties.getServlet().getContextPath();
+ String finalContextPath;
+ if (StringUtils.isBlank(contextPath) || "/".equals(contextPath)) {
+ finalContextPath = appPath;
+ } else {
+ finalContextPath = appPath + contextPath;
+ }
+ // 对所有路径增加前置上下文路径
+ return openApi -> {
+ Paths oldPaths = openApi.getPaths();
+ if (oldPaths instanceof PlusPaths) {
+ return;
+ }
+ PlusPaths newPaths = new PlusPaths();
+ oldPaths.forEach((k, v) -> newPaths.addPathItem(finalContextPath + k, v));
+ openApi.setPaths(newPaths);
+ };
+ }
+
+}
diff --git a/ruoyi-common/ruoyi-common-doc/src/main/java/org/dromara/common/doc/config/properties/SpringDocProperties.java b/ruoyi-common/ruoyi-common-doc/src/main/java/org/dromara/common/doc/config/properties/SpringDocProperties.java
new file mode 100644
index 0000000..eae3b4c
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-doc/src/main/java/org/dromara/common/doc/config/properties/SpringDocProperties.java
@@ -0,0 +1,94 @@
+package org.dromara.common.doc.config.properties;
+
+import io.swagger.v3.oas.models.Components;
+import io.swagger.v3.oas.models.ExternalDocumentation;
+import io.swagger.v3.oas.models.Paths;
+import io.swagger.v3.oas.models.info.Contact;
+import io.swagger.v3.oas.models.info.License;
+import io.swagger.v3.oas.models.tags.Tag;
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.boot.context.properties.NestedConfigurationProperty;
+
+import java.util.List;
+
+/**
+ * swagger 配置属性
+ *
+ * @author Lion Li
+ */
+@Data
+@ConfigurationProperties(prefix = "springdoc")
+public class SpringDocProperties {
+
+ /**
+ * 文档基本信息
+ */
+ @NestedConfigurationProperty
+ private InfoProperties info = new InfoProperties();
+
+ /**
+ * 扩展文档地址
+ */
+ @NestedConfigurationProperty
+ private ExternalDocumentation externalDocs;
+
+ /**
+ * 标签
+ */
+ private List tags = null;
+
+ /**
+ * 路径
+ */
+ @NestedConfigurationProperty
+ private Paths paths = null;
+
+ /**
+ * 组件
+ */
+ @NestedConfigurationProperty
+ private Components components = null;
+
+ /**
+ *
+ * 文档的基础属性信息
+ *
+ *
+ * @see io.swagger.v3.oas.models.info.Info
+ *
+ * 为了 springboot 自动生产配置提示信息,所以这里复制一个类出来
+ */
+ @Data
+ public static class InfoProperties {
+
+ /**
+ * 标题
+ */
+ private String title = null;
+
+ /**
+ * 描述
+ */
+ private String description = null;
+
+ /**
+ * 联系人信息
+ */
+ @NestedConfigurationProperty
+ private Contact contact = null;
+
+ /**
+ * 许可证
+ */
+ @NestedConfigurationProperty
+ private License license = null;
+
+ /**
+ * 版本
+ */
+ private String version = null;
+
+ }
+
+}
diff --git a/ruoyi-common/ruoyi-common-doc/src/main/java/org/dromara/common/doc/handler/OpenApiHandler.java b/ruoyi-common/ruoyi-common-doc/src/main/java/org/dromara/common/doc/handler/OpenApiHandler.java
new file mode 100644
index 0000000..1ee9e73
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-doc/src/main/java/org/dromara/common/doc/handler/OpenApiHandler.java
@@ -0,0 +1,252 @@
+package org.dromara.common.doc.handler;
+
+import cn.hutool.core.io.IoUtil;
+import io.swagger.v3.core.jackson.TypeNameResolver;
+import io.swagger.v3.core.util.AnnotationsUtils;
+import io.swagger.v3.oas.annotations.tags.Tags;
+import io.swagger.v3.oas.models.Components;
+import io.swagger.v3.oas.models.OpenAPI;
+import io.swagger.v3.oas.models.Operation;
+import io.swagger.v3.oas.models.Paths;
+import io.swagger.v3.oas.models.tags.Tag;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springdoc.core.customizers.OpenApiBuilderCustomizer;
+import org.springdoc.core.customizers.ServerBaseUrlCustomizer;
+import org.springdoc.core.properties.SpringDocConfigProperties;
+import org.springdoc.core.providers.JavadocProvider;
+import org.springdoc.core.service.OpenAPIService;
+import org.springdoc.core.service.SecurityService;
+import org.springdoc.core.utils.PropertyResolverUtils;
+import org.springframework.context.ApplicationContext;
+import org.springframework.core.annotation.AnnotatedElementUtils;
+import org.springframework.util.CollectionUtils;
+import org.springframework.web.method.HandlerMethod;
+
+import java.io.StringReader;
+import java.lang.reflect.Method;
+import java.util.*;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * 自定义 openapi 处理器
+ * 对源码功能进行修改 增强使用
+ */
+@Slf4j
+@SuppressWarnings("all")
+public class OpenApiHandler extends OpenAPIService {
+
+ /**
+ * The Basic error controller.
+ */
+ private static Class> basicErrorController;
+
+ /**
+ * The Security parser.
+ */
+ private final SecurityService securityParser;
+
+ /**
+ * The Mappings map.
+ */
+ private final Map mappingsMap = new HashMap<>();
+
+ /**
+ * The Springdoc tags.
+ */
+ private final Map springdocTags = new HashMap<>();
+
+ /**
+ * The Open api builder customisers.
+ */
+ private final Optional> openApiBuilderCustomisers;
+
+ /**
+ * The server base URL customisers.
+ */
+ private final Optional> serverBaseUrlCustomizers;
+
+ /**
+ * The Spring doc config properties.
+ */
+ private final SpringDocConfigProperties springDocConfigProperties;
+
+ /**
+ * The Cached open api map.
+ */
+ private final Map cachedOpenAPI = new HashMap<>();
+
+ /**
+ * The Property resolver utils.
+ */
+ private final PropertyResolverUtils propertyResolverUtils;
+
+ /**
+ * The javadoc provider.
+ */
+ private final Optional javadocProvider;
+
+ /**
+ * The Context.
+ */
+ private ApplicationContext context;
+
+ /**
+ * The Open api.
+ */
+ private OpenAPI openAPI;
+
+ /**
+ * The Is servers present.
+ */
+ private boolean isServersPresent;
+
+ /**
+ * The Server base url.
+ */
+ private String serverBaseUrl;
+
+ /**
+ * Instantiates a new Open api builder.
+ *
+ * @param openAPI the open api
+ * @param securityParser the security parser
+ * @param springDocConfigProperties the spring doc config properties
+ * @param propertyResolverUtils the property resolver utils
+ * @param openApiBuilderCustomizers the open api builder customisers
+ * @param serverBaseUrlCustomizers the server base url customizers
+ * @param javadocProvider the javadoc provider
+ */
+ public OpenApiHandler(Optional openAPI, SecurityService securityParser,
+ SpringDocConfigProperties springDocConfigProperties, PropertyResolverUtils propertyResolverUtils,
+ Optional> openApiBuilderCustomizers,
+ Optional> serverBaseUrlCustomizers,
+ Optional javadocProvider) {
+ super(openAPI, securityParser, springDocConfigProperties, propertyResolverUtils, openApiBuilderCustomizers, serverBaseUrlCustomizers, javadocProvider);
+ if (openAPI.isPresent()) {
+ this.openAPI = openAPI.get();
+ if (this.openAPI.getComponents() == null)
+ this.openAPI.setComponents(new Components());
+ if (this.openAPI.getPaths() == null)
+ this.openAPI.setPaths(new Paths());
+ if (!CollectionUtils.isEmpty(this.openAPI.getServers()))
+ this.isServersPresent = true;
+ }
+ this.propertyResolverUtils = propertyResolverUtils;
+ this.securityParser = securityParser;
+ this.springDocConfigProperties = springDocConfigProperties;
+ this.openApiBuilderCustomisers = openApiBuilderCustomizers;
+ this.serverBaseUrlCustomizers = serverBaseUrlCustomizers;
+ this.javadocProvider = javadocProvider;
+ if (springDocConfigProperties.isUseFqn())
+ TypeNameResolver.std.setUseFqn(true);
+ }
+
+ @Override
+ public Operation buildTags(HandlerMethod handlerMethod, Operation operation, OpenAPI openAPI, Locale locale) {
+
+ Set tags = new HashSet<>();
+ Set tagsStr = new HashSet<>();
+
+ buildTagsFromMethod(handlerMethod.getMethod(), tags, tagsStr, locale);
+ buildTagsFromClass(handlerMethod.getBeanType(), tags, tagsStr, locale);
+
+ if (!CollectionUtils.isEmpty(tagsStr))
+ tagsStr = tagsStr.stream()
+ .map(str -> propertyResolverUtils.resolve(str, locale))
+ .collect(Collectors.toSet());
+
+ if (springdocTags.containsKey(handlerMethod)) {
+ Tag tag = springdocTags.get(handlerMethod);
+ tagsStr.add(tag.getName());
+ if (openAPI.getTags() == null || !openAPI.getTags().contains(tag)) {
+ openAPI.addTagsItem(tag);
+ }
+ }
+
+ if (!CollectionUtils.isEmpty(tagsStr)) {
+ if (CollectionUtils.isEmpty(operation.getTags()))
+ operation.setTags(new ArrayList<>(tagsStr));
+ else {
+ Set operationTagsSet = new HashSet<>(operation.getTags());
+ operationTagsSet.addAll(tagsStr);
+ operation.getTags().clear();
+ operation.getTags().addAll(operationTagsSet);
+ }
+ }
+
+ if (isAutoTagClasses(operation)) {
+
+
+ if (javadocProvider.isPresent()) {
+ String description = javadocProvider.get().getClassJavadoc(handlerMethod.getBeanType());
+ if (StringUtils.isNotBlank(description)) {
+ Tag tag = new Tag();
+
+ // 自定义部分 修改使用java注释当tag名
+ List list = IoUtil.readLines(new StringReader(description), new ArrayList<>());
+ // tag.setName(tagAutoName);
+ tag.setName(list.get(0));
+ operation.addTagsItem(list.get(0));
+
+ tag.setDescription(description);
+ if (openAPI.getTags() == null || !openAPI.getTags().contains(tag)) {
+ openAPI.addTagsItem(tag);
+ }
+ }
+ } else {
+ String tagAutoName = splitCamelCase(handlerMethod.getBeanType().getSimpleName());
+ operation.addTagsItem(tagAutoName);
+ }
+ }
+
+ if (!CollectionUtils.isEmpty(tags)) {
+ // Existing tags
+ List openApiTags = openAPI.getTags();
+ if (!CollectionUtils.isEmpty(openApiTags))
+ tags.addAll(openApiTags);
+ openAPI.setTags(new ArrayList<>(tags));
+ }
+
+ // Handle SecurityRequirement at operation level
+ io.swagger.v3.oas.annotations.security.SecurityRequirement[] securityRequirements = securityParser
+ .getSecurityRequirements(handlerMethod);
+ if (securityRequirements != null) {
+ if (securityRequirements.length == 0)
+ operation.setSecurity(Collections.emptyList());
+ else
+ securityParser.buildSecurityRequirement(securityRequirements, operation);
+ }
+
+ return operation;
+ }
+
+ private void buildTagsFromMethod(Method method, Set tags, Set tagsStr, Locale locale) {
+ // method tags
+ Set tagsSet = AnnotatedElementUtils
+ .findAllMergedAnnotations(method, Tags.class);
+ Set methodTags = tagsSet.stream()
+ .flatMap(x -> Stream.of(x.value())).collect(Collectors.toSet());
+ methodTags.addAll(AnnotatedElementUtils.findAllMergedAnnotations(method, io.swagger.v3.oas.annotations.tags.Tag.class));
+ if (!CollectionUtils.isEmpty(methodTags)) {
+ tagsStr.addAll(methodTags.stream().map(tag -> propertyResolverUtils.resolve(tag.name(), locale)).collect(Collectors.toSet()));
+ List allTags = new ArrayList<>(methodTags);
+ addTags(allTags, tags, locale);
+ }
+ }
+
+ private void addTags(List sourceTags, Set tags, Locale locale) {
+ Optional> optionalTagSet = AnnotationsUtils
+ .getTags(sourceTags.toArray(new io.swagger.v3.oas.annotations.tags.Tag[0]), true);
+ optionalTagSet.ifPresent(tagsSet -> {
+ tagsSet.forEach(tag -> {
+ tag.name(propertyResolverUtils.resolve(tag.getName(), locale));
+ tag.description(propertyResolverUtils.resolve(tag.getDescription(), locale));
+ if (tags.stream().noneMatch(t -> t.getName().equals(tag.getName())))
+ tags.add(tag);
+ });
+ });
+ }
+
+}
diff --git a/ruoyi-common/ruoyi-common-doc/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/ruoyi-common/ruoyi-common-doc/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100644
index 0000000..f039b2b
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-doc/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1 @@
+org.dromara.common.doc.config.SpringDocAutoConfiguration
diff --git a/ruoyi-common/ruoyi-common-dubbo/pom.xml b/ruoyi-common/ruoyi-common-dubbo/pom.xml
new file mode 100644
index 0000000..ce5bb3e
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-dubbo/pom.xml
@@ -0,0 +1,63 @@
+
+
+
+ org.dromara
+ ruoyi-common
+ ${revision}
+
+ 4.0.0
+
+ ruoyi-common-dubbo
+
+
+ ruoyi-common-dubbo
+
+
+
+
+ org.dromara
+ ruoyi-common-json
+
+
+
+ org.springframework.cloud
+ spring-cloud-context
+
+
+
+ org.apache.dubbo
+ dubbo-spring-boot-starter
+
+
+
+ org.apache.dubbo
+ dubbo-spring-boot-actuator
+
+
+
+ org.projectlombok
+ lombok
+
+
+
+ cn.dev33
+ sa-token-spring-boot3-starter
+
+
+
+
+ cn.dev33
+ sa-token-dubbo3
+ ${satoken.version}
+
+
+ org.apache.dubbo
+ dubbo
+
+
+
+
+
+
diff --git a/ruoyi-common/ruoyi-common-dubbo/src/main/java/org/dromara/common/dubbo/config/DubboConfiguration.java b/ruoyi-common/ruoyi-common-dubbo/src/main/java/org/dromara/common/dubbo/config/DubboConfiguration.java
new file mode 100644
index 0000000..6fc622e
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-dubbo/src/main/java/org/dromara/common/dubbo/config/DubboConfiguration.java
@@ -0,0 +1,17 @@
+package org.dromara.common.dubbo.config;
+
+import org.dromara.common.core.factory.YmlPropertySourceFactory;
+import org.dromara.common.dubbo.properties.DubboCustomProperties;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.PropertySource;
+
+/**
+ * dubbo 配置类
+ */
+@AutoConfiguration
+@EnableConfigurationProperties(DubboCustomProperties.class)
+@PropertySource(value = "classpath:common-dubbo.yml", factory = YmlPropertySourceFactory.class)
+public class DubboConfiguration {
+
+}
diff --git a/ruoyi-common/ruoyi-common-dubbo/src/main/java/org/dromara/common/dubbo/enumd/RequestLogEnum.java b/ruoyi-common/ruoyi-common-dubbo/src/main/java/org/dromara/common/dubbo/enumd/RequestLogEnum.java
new file mode 100644
index 0000000..f238eeb
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-dubbo/src/main/java/org/dromara/common/dubbo/enumd/RequestLogEnum.java
@@ -0,0 +1,18 @@
+package org.dromara.common.dubbo.enumd;
+
+import lombok.AllArgsConstructor;
+
+/**
+ * 请求日志泛型
+ *
+ * @author Lion Li
+ */
+@AllArgsConstructor
+public enum RequestLogEnum {
+
+ /**
+ * info 基础信息 param 参数信息 full 全部
+ */
+ INFO, PARAM, FULL;
+
+}
diff --git a/ruoyi-common/ruoyi-common-dubbo/src/main/java/org/dromara/common/dubbo/filter/DubboRequestFilter.java b/ruoyi-common/ruoyi-common-dubbo/src/main/java/org/dromara/common/dubbo/filter/DubboRequestFilter.java
new file mode 100644
index 0000000..382dc94
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-dubbo/src/main/java/org/dromara/common/dubbo/filter/DubboRequestFilter.java
@@ -0,0 +1,58 @@
+package org.dromara.common.dubbo.filter;
+
+import org.dromara.common.core.utils.SpringUtils;
+import org.dromara.common.dubbo.enumd.RequestLogEnum;
+import org.dromara.common.dubbo.properties.DubboCustomProperties;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.dubbo.common.constants.CommonConstants;
+import org.apache.dubbo.common.extension.Activate;
+import org.apache.dubbo.rpc.*;
+import org.apache.dubbo.rpc.service.GenericService;
+import org.dromara.common.json.utils.JsonUtils;
+
+/**
+ * dubbo日志过滤器
+ *
+ * @author Lion Li
+ */
+@Slf4j
+@Activate(group = {CommonConstants.PROVIDER, CommonConstants.CONSUMER}, order = Integer.MAX_VALUE)
+public class DubboRequestFilter implements Filter {
+
+ @Override
+ public Result invoke(Invoker> invoker, Invocation invocation) throws RpcException {
+ DubboCustomProperties properties = SpringUtils.getBean(DubboCustomProperties.class);
+ if (!properties.getRequestLog()) {
+ // 未开启则跳过日志逻辑
+ return invoker.invoke(invocation);
+ }
+ String client = CommonConstants.PROVIDER;
+ if (RpcContext.getServiceContext().isConsumerSide()) {
+ client = CommonConstants.CONSUMER;
+ }
+ String baselog = "Client[" + client + "],InterfaceName=[" + invocation.getInvoker().getInterface().getSimpleName() + "],MethodName=[" + invocation.getMethodName() + "]";
+ if (properties.getLogLevel() == RequestLogEnum.INFO) {
+ log.info("DUBBO - 服务调用: {}", baselog);
+ } else {
+ log.info("DUBBO - 服务调用: {},Parameter={}", baselog, invocation.getArguments());
+ }
+
+ long startTime = System.currentTimeMillis();
+ // 执行接口调用逻辑
+ Result result = invoker.invoke(invocation);
+ // 调用耗时
+ long elapsed = System.currentTimeMillis() - startTime;
+ // 如果发生异常 则打印异常日志
+ if (result.hasException() && invoker.getInterface().equals(GenericService.class)) {
+ log.error("DUBBO - 服务异常: {},Exception={}", baselog, result.getException());
+ } else {
+ if (properties.getLogLevel() == RequestLogEnum.INFO) {
+ log.info("DUBBO - 服务响应: {},SpendTime=[{}ms]", baselog, elapsed);
+ } else if (properties.getLogLevel() == RequestLogEnum.FULL) {
+ log.info("DUBBO - 服务响应: {},SpendTime=[{}ms],Response={}", baselog, elapsed, JsonUtils.toJsonString(new Object[]{result.getValue()}));
+ }
+ }
+ return result;
+ }
+
+}
diff --git a/ruoyi-common/ruoyi-common-dubbo/src/main/java/org/dromara/common/dubbo/properties/DubboCustomProperties.java b/ruoyi-common/ruoyi-common-dubbo/src/main/java/org/dromara/common/dubbo/properties/DubboCustomProperties.java
new file mode 100644
index 0000000..8918088
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-dubbo/src/main/java/org/dromara/common/dubbo/properties/DubboCustomProperties.java
@@ -0,0 +1,22 @@
+package org.dromara.common.dubbo.properties;
+
+import lombok.Data;
+import org.dromara.common.dubbo.enumd.RequestLogEnum;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.cloud.context.config.annotation.RefreshScope;
+
+/**
+ * 自定义配置
+ *
+ * @author Lion Li
+ */
+@Data
+@RefreshScope
+@ConfigurationProperties(prefix = "dubbo.custom")
+public class DubboCustomProperties {
+
+ private Boolean requestLog;
+
+ private RequestLogEnum logLevel;
+
+}
diff --git a/ruoyi-common/ruoyi-common-dubbo/src/main/resources/META-INF/dubbo/org.apache.dubbo.rpc.Filter b/ruoyi-common/ruoyi-common-dubbo/src/main/resources/META-INF/dubbo/org.apache.dubbo.rpc.Filter
new file mode 100644
index 0000000..6f766ab
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-dubbo/src/main/resources/META-INF/dubbo/org.apache.dubbo.rpc.Filter
@@ -0,0 +1 @@
+dubboRequestFilter=org.dromara.common.dubbo.filter.DubboRequestFilter
diff --git a/ruoyi-common/ruoyi-common-dubbo/src/main/resources/common-dubbo.yml b/ruoyi-common/ruoyi-common-dubbo/src/main/resources/common-dubbo.yml
new file mode 100644
index 0000000..fdc21f6
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-dubbo/src/main/resources/common-dubbo.yml
@@ -0,0 +1,30 @@
+# 内置配置 不允许修改 如需修改请在 nacos 上写相同配置覆盖
+dubbo:
+ application:
+ logger: slf4j
+ # 元数据中心 local 本地 remote 远程 这里使用远程便于其他服务获取
+ metadataType: remote
+ # 可选值 interface、instance、all,默认是 all,即接口级地址、应用级地址都注册
+ register-mode: instance
+ service-discovery:
+ # FORCE_INTERFACE,只消费接口级地址,如无地址则报错,单订阅 2.x 地址
+ # APPLICATION_FIRST,智能决策接口级/应用级地址,双订阅
+ # FORCE_APPLICATION,只消费应用级地址,如无地址则报错,单订阅 3.x 地址
+ migration: FORCE_APPLICATION
+ # 注册中心配置
+ registry:
+ address: nacos://${spring.cloud.nacos.server-addr}
+ group: DUBBO_GROUP
+ parameters:
+ namespace: ${spring.profiles.active}
+ # 消费者相关配置
+ consumer:
+ # 结果缓存(LRU算法)
+ # 会有数据不一致问题 建议在注解局部开启
+ cache: false
+ # 支持校验注解
+ validation: jvalidationNew
+ # 调用重试 不包括第一次 0为不需要重试
+ retries: 0
+ # 初始化检查
+ check: false
diff --git a/ruoyi-common/ruoyi-common-elasticsearch/pom.xml b/ruoyi-common/ruoyi-common-elasticsearch/pom.xml
new file mode 100644
index 0000000..401320d
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-elasticsearch/pom.xml
@@ -0,0 +1,24 @@
+
+
+
+ org.dromara
+ ruoyi-common
+ ${revision}
+
+ 4.0.0
+
+ ruoyi-common-elasticsearch
+
+
+ ruoyi-common-elasticsearch ES搜索引擎服务
+
+
+
+
+ org.dromara.easy-es
+ easy-es-boot-starter
+
+
+
diff --git a/ruoyi-common/ruoyi-common-elasticsearch/src/main/java/org/dromara/common/elasticsearch/config/EasyEsConfiguration.java b/ruoyi-common/ruoyi-common-elasticsearch/src/main/java/org/dromara/common/elasticsearch/config/EasyEsConfiguration.java
new file mode 100644
index 0000000..3d6354a
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-elasticsearch/src/main/java/org/dromara/common/elasticsearch/config/EasyEsConfiguration.java
@@ -0,0 +1,17 @@
+package org.dromara.common.elasticsearch.config;
+
+import org.dromara.easyes.starter.register.EsMapperScan;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+
+/**
+ * easy-es 配置
+ *
+ * @author Lion Li
+ */
+@AutoConfiguration
+@ConditionalOnProperty(value = "easy-es.enable", havingValue = "true")
+@EsMapperScan("org.dromara.**.esmapper")
+public class EasyEsConfiguration {
+
+}
diff --git a/ruoyi-common/ruoyi-common-elasticsearch/src/main/resources/META-INF/spring.factories b/ruoyi-common/ruoyi-common-elasticsearch/src/main/resources/META-INF/spring.factories
new file mode 100644
index 0000000..1a18290
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-elasticsearch/src/main/resources/META-INF/spring.factories
@@ -0,0 +1,2 @@
+org.springframework.boot.env.EnvironmentPostProcessor=\
+ org.dromara.common.elasticsearch.config.ActuatorEnvironmentPostProcessor
diff --git a/ruoyi-common/ruoyi-common-encrypt/pom.xml b/ruoyi-common/ruoyi-common-encrypt/pom.xml
new file mode 100644
index 0000000..64d189b
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-encrypt/pom.xml
@@ -0,0 +1,55 @@
+
+
+
+ org.dromara
+ ruoyi-common
+ ${revision}
+
+ 4.0.0
+
+ ruoyi-common-encrypt
+
+
+ ruoyi-common-encrypt 数据加解密模块
+
+
+
+
+
+ org.dromara
+ ruoyi-common-core
+
+
+
+ org.mybatis.spring.boot
+ mybatis-spring-boot-starter
+
+
+
+
+ org.bouncycastle
+ bcprov-jdk15to18
+
+
+
+
+ cn.hutool
+ hutool-crypto
+
+
+
+ org.springframework
+ spring-webmvc
+
+
+
+
+
diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/annotation/ApiEncrypt.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/annotation/ApiEncrypt.java
new file mode 100644
index 0000000..7f52de8
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/annotation/ApiEncrypt.java
@@ -0,0 +1,20 @@
+package org.dromara.common.encrypt.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * 强制加密注解
+ *
+ * @author Michelle.Chung
+ */
+@Documented
+@Target({ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface ApiEncrypt {
+
+ /**
+ * 响应加密忽略,默认不加密,为 true 时加密
+ */
+ boolean response() default false;
+
+}
diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/annotation/EncryptField.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/annotation/EncryptField.java
new file mode 100644
index 0000000..d357d72
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/annotation/EncryptField.java
@@ -0,0 +1,44 @@
+package org.dromara.common.encrypt.annotation;
+
+import org.dromara.common.encrypt.enumd.AlgorithmType;
+import org.dromara.common.encrypt.enumd.EncodeType;
+
+import java.lang.annotation.*;
+
+/**
+ * 字段加密注解
+ *
+ * @author 老马
+ */
+@Documented
+@Inherited
+@Target({ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface EncryptField {
+
+ /**
+ * 加密算法
+ */
+ AlgorithmType algorithm() default AlgorithmType.DEFAULT;
+
+ /**
+ * 秘钥。AES、SM4需要
+ */
+ String password() default "";
+
+ /**
+ * 公钥。RSA、SM2需要
+ */
+ String publicKey() default "";
+
+ /**
+ * 私钥。RSA、SM2需要
+ */
+ String privateKey() default "";
+
+ /**
+ * 编码方式。对加密算法为BASE64的不起作用
+ */
+ EncodeType encode() default EncodeType.DEFAULT;
+
+}
diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/config/ApiDecryptAutoConfiguration.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/config/ApiDecryptAutoConfiguration.java
new file mode 100644
index 0000000..098f6bc
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/config/ApiDecryptAutoConfiguration.java
@@ -0,0 +1,32 @@
+package org.dromara.common.encrypt.config;
+
+import jakarta.servlet.DispatcherType;
+import org.dromara.common.encrypt.filter.CryptoFilter;
+import org.dromara.common.encrypt.properties.ApiDecryptProperties;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.boot.web.servlet.FilterRegistrationBean;
+import org.springframework.context.annotation.Bean;
+
+/**
+ * api 解密自动配置
+ *
+ * @author wdhcr
+ */
+@AutoConfiguration
+@EnableConfigurationProperties(ApiDecryptProperties.class)
+@ConditionalOnProperty(value = "api-decrypt.enabled", havingValue = "true")
+public class ApiDecryptAutoConfiguration {
+
+ @Bean
+ public FilterRegistrationBean cryptoFilterRegistration(ApiDecryptProperties properties) {
+ FilterRegistrationBean registration = new FilterRegistrationBean<>();
+ registration.setDispatcherTypes(DispatcherType.REQUEST);
+ registration.setFilter(new CryptoFilter(properties));
+ registration.addUrlPatterns("/*");
+ registration.setName("cryptoFilter");
+ registration.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE);
+ return registration;
+ }
+}
diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/config/EncryptorAutoConfiguration.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/config/EncryptorAutoConfiguration.java
new file mode 100644
index 0000000..e988a3a
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/config/EncryptorAutoConfiguration.java
@@ -0,0 +1,41 @@
+package org.dromara.common.encrypt.config;
+
+import org.dromara.common.encrypt.core.EncryptorManager;
+import org.dromara.common.encrypt.interceptor.MybatisDecryptInterceptor;
+import org.dromara.common.encrypt.interceptor.MybatisEncryptInterceptor;
+import org.dromara.common.encrypt.properties.EncryptorProperties;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+
+/**
+ * 加解密配置
+ *
+ * @author 老马
+ * @version 4.6.0
+ */
+@AutoConfiguration
+@EnableConfigurationProperties(EncryptorProperties.class)
+@ConditionalOnProperty(value = "mybatis-encryptor.enable", havingValue = "true")
+public class EncryptorAutoConfiguration {
+
+ @Autowired
+ private EncryptorProperties properties;
+
+ @Bean
+ public EncryptorManager encryptorManager() {
+ return new EncryptorManager();
+ }
+
+ @Bean
+ public MybatisEncryptInterceptor mybatisEncryptInterceptor(EncryptorManager encryptorManager) {
+ return new MybatisEncryptInterceptor(encryptorManager, properties);
+ }
+
+ @Bean
+ public MybatisDecryptInterceptor mybatisDecryptInterceptor(EncryptorManager encryptorManager) {
+ return new MybatisDecryptInterceptor(encryptorManager, properties);
+ }
+}
diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/EncryptContext.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/EncryptContext.java
new file mode 100644
index 0000000..2f02eaf
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/EncryptContext.java
@@ -0,0 +1,41 @@
+package org.dromara.common.encrypt.core;
+
+import org.dromara.common.encrypt.enumd.AlgorithmType;
+import org.dromara.common.encrypt.enumd.EncodeType;
+import lombok.Data;
+
+/**
+ * 加密上下文 用于encryptor传递必要的参数。
+ *
+ * @author 老马
+ * @version 4.6.0
+ */
+@Data
+public class EncryptContext {
+
+ /**
+ * 默认算法
+ */
+ private AlgorithmType algorithm;
+
+ /**
+ * 安全秘钥
+ */
+ private String password;
+
+ /**
+ * 公钥
+ */
+ private String publicKey;
+
+ /**
+ * 私钥
+ */
+ private String privateKey;
+
+ /**
+ * 编码方式,base64/hex
+ */
+ private EncodeType encode;
+
+}
diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/EncryptorManager.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/EncryptorManager.java
new file mode 100644
index 0000000..782995d
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/EncryptorManager.java
@@ -0,0 +1,100 @@
+package org.dromara.common.encrypt.core;
+
+import cn.hutool.core.util.ReflectUtil;
+import org.dromara.common.encrypt.annotation.EncryptField;
+import lombok.extern.slf4j.Slf4j;
+
+import java.lang.reflect.Field;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
+
+/**
+ * 加密管理类
+ *
+ * @author 老马
+ * @version 4.6.0
+ */
+@Slf4j
+public class EncryptorManager {
+
+ /**
+ * 缓存加密器
+ */
+ Map encryptorMap = new ConcurrentHashMap<>();
+
+ /**
+ * 类加密字段缓存
+ */
+ Map, Set> fieldCache = new ConcurrentHashMap<>();
+
+ /**
+ * 获取类加密字段缓存
+ */
+ public Set getFieldCache(Class> sourceClazz) {
+ return fieldCache.computeIfAbsent(sourceClazz, clazz -> {
+ Set fieldSet = new HashSet<>();
+ while (clazz != null) {
+ Field[] fields = clazz.getDeclaredFields();
+ fieldSet.addAll(Arrays.asList(fields));
+ clazz = clazz.getSuperclass();
+ }
+ fieldSet = fieldSet.stream().filter(field ->
+ field.isAnnotationPresent(EncryptField.class) && field.getType() == String.class)
+ .collect(Collectors.toSet());
+ for (Field field : fieldSet) {
+ field.setAccessible(true);
+ }
+ return fieldSet;
+ });
+ }
+
+ /**
+ * 注册加密执行者到缓存
+ *
+ * @param encryptContext 加密执行者需要的相关配置参数
+ */
+ public IEncryptor registAndGetEncryptor(EncryptContext encryptContext) {
+ if (encryptorMap.containsKey(encryptContext)) {
+ return encryptorMap.get(encryptContext);
+ }
+ IEncryptor encryptor = ReflectUtil.newInstance(encryptContext.getAlgorithm().getClazz(), encryptContext);
+ encryptorMap.put(encryptContext, encryptor);
+ return encryptor;
+ }
+
+ /**
+ * 移除缓存中的加密执行者
+ *
+ * @param encryptContext 加密执行者需要的相关配置参数
+ */
+ public void removeEncryptor(EncryptContext encryptContext) {
+ this.encryptorMap.remove(encryptContext);
+ }
+
+ /**
+ * 根据配置进行加密。会进行本地缓存对应的算法和对应的秘钥信息。
+ *
+ * @param value 待加密的值
+ * @param encryptContext 加密相关的配置信息
+ */
+ public String encrypt(String value, EncryptContext encryptContext) {
+ IEncryptor encryptor = this.registAndGetEncryptor(encryptContext);
+ return encryptor.encrypt(value, encryptContext.getEncode());
+ }
+
+ /**
+ * 根据配置进行解密
+ *
+ * @param value 待解密的值
+ * @param encryptContext 加密相关的配置信息
+ */
+ public String decrypt(String value, EncryptContext encryptContext) {
+ IEncryptor encryptor = this.registAndGetEncryptor(encryptContext);
+ return encryptor.decrypt(value);
+ }
+
+}
diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/IEncryptor.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/IEncryptor.java
new file mode 100644
index 0000000..dbc4420
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/IEncryptor.java
@@ -0,0 +1,35 @@
+package org.dromara.common.encrypt.core;
+
+import org.dromara.common.encrypt.enumd.AlgorithmType;
+import org.dromara.common.encrypt.enumd.EncodeType;
+
+/**
+ * 加解者
+ *
+ * @author 老马
+ * @version 4.6.0
+ */
+public interface IEncryptor {
+
+ /**
+ * 获得当前算法
+ */
+ AlgorithmType algorithm();
+
+ /**
+ * 加密
+ *
+ * @param value 待加密字符串
+ * @param encodeType 加密后的编码格式
+ * @return 加密后的字符串
+ */
+ String encrypt(String value, EncodeType encodeType);
+
+ /**
+ * 解密
+ *
+ * @param value 待加密字符串
+ * @return 解密后的字符串
+ */
+ String decrypt(String value);
+}
diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/encryptor/AbstractEncryptor.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/encryptor/AbstractEncryptor.java
new file mode 100644
index 0000000..858d229
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/encryptor/AbstractEncryptor.java
@@ -0,0 +1,18 @@
+package org.dromara.common.encrypt.core.encryptor;
+
+import org.dromara.common.encrypt.core.EncryptContext;
+import org.dromara.common.encrypt.core.IEncryptor;
+
+/**
+ * 所有加密执行者的基类
+ *
+ * @author 老马
+ * @version 4.6.0
+ */
+public abstract class AbstractEncryptor implements IEncryptor {
+
+ public AbstractEncryptor(EncryptContext context) {
+ // 用户配置校验与配置注入
+ }
+
+}
diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/encryptor/AesEncryptor.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/encryptor/AesEncryptor.java
new file mode 100644
index 0000000..e4dc597
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/encryptor/AesEncryptor.java
@@ -0,0 +1,55 @@
+package org.dromara.common.encrypt.core.encryptor;
+
+import org.dromara.common.encrypt.core.EncryptContext;
+import org.dromara.common.encrypt.enumd.AlgorithmType;
+import org.dromara.common.encrypt.enumd.EncodeType;
+import org.dromara.common.encrypt.utils.EncryptUtils;
+
+/**
+ * AES算法实现
+ *
+ * @author 老马
+ * @version 4.6.0
+ */
+public class AesEncryptor extends AbstractEncryptor {
+
+ private final EncryptContext context;
+
+ public AesEncryptor(EncryptContext context) {
+ super(context);
+ this.context = context;
+ }
+
+ /**
+ * 获得当前算法
+ */
+ @Override
+ public AlgorithmType algorithm() {
+ return AlgorithmType.AES;
+ }
+
+ /**
+ * 加密
+ *
+ * @param value 待加密字符串
+ * @param encodeType 加密后的编码格式
+ */
+ @Override
+ public String encrypt(String value, EncodeType encodeType) {
+ if (encodeType == EncodeType.HEX) {
+ return EncryptUtils.encryptByAesHex(value, context.getPassword());
+ } else {
+ return EncryptUtils.encryptByAes(value, context.getPassword());
+ }
+ }
+
+ /**
+ * 解密
+ *
+ * @param value 待加密字符串
+ */
+ @Override
+ public String decrypt(String value) {
+ return EncryptUtils.decryptByAes(value, context.getPassword());
+ }
+}
diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/encryptor/Base64Encryptor.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/encryptor/Base64Encryptor.java
new file mode 100644
index 0000000..0028548
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/encryptor/Base64Encryptor.java
@@ -0,0 +1,48 @@
+package org.dromara.common.encrypt.core.encryptor;
+
+import org.dromara.common.encrypt.core.EncryptContext;
+import org.dromara.common.encrypt.enumd.AlgorithmType;
+import org.dromara.common.encrypt.enumd.EncodeType;
+import org.dromara.common.encrypt.utils.EncryptUtils;
+
+/**
+ * Base64算法实现
+ *
+ * @author 老马
+ * @version 4.6.0
+ */
+public class Base64Encryptor extends AbstractEncryptor {
+
+ public Base64Encryptor(EncryptContext context) {
+ super(context);
+ }
+
+ /**
+ * 获得当前算法
+ */
+ @Override
+ public AlgorithmType algorithm() {
+ return AlgorithmType.BASE64;
+ }
+
+ /**
+ * 加密
+ *
+ * @param value 待加密字符串
+ * @param encodeType 加密后的编码格式
+ */
+ @Override
+ public String encrypt(String value, EncodeType encodeType) {
+ return EncryptUtils.encryptByBase64(value);
+ }
+
+ /**
+ * 解密
+ *
+ * @param value 待加密字符串
+ */
+ @Override
+ public String decrypt(String value) {
+ return EncryptUtils.decryptByBase64(value);
+ }
+}
diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/encryptor/RsaEncryptor.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/encryptor/RsaEncryptor.java
new file mode 100644
index 0000000..5f03a4b
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/encryptor/RsaEncryptor.java
@@ -0,0 +1,62 @@
+package org.dromara.common.encrypt.core.encryptor;
+
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.encrypt.core.EncryptContext;
+import org.dromara.common.encrypt.enumd.AlgorithmType;
+import org.dromara.common.encrypt.enumd.EncodeType;
+import org.dromara.common.encrypt.utils.EncryptUtils;
+
+
+/**
+ * RSA算法实现
+ *
+ * @author 老马
+ * @version 4.6.0
+ */
+public class RsaEncryptor extends AbstractEncryptor {
+
+ private final EncryptContext context;
+
+ public RsaEncryptor(EncryptContext context) {
+ super(context);
+ String privateKey = context.getPrivateKey();
+ String publicKey = context.getPublicKey();
+ if (StringUtils.isAnyEmpty(privateKey, publicKey)) {
+ throw new IllegalArgumentException("RSA公私钥均需要提供,公钥加密,私钥解密。");
+ }
+ this.context = context;
+ }
+
+ /**
+ * 获得当前算法
+ */
+ @Override
+ public AlgorithmType algorithm() {
+ return AlgorithmType.RSA;
+ }
+
+ /**
+ * 加密
+ *
+ * @param value 待加密字符串
+ * @param encodeType 加密后的编码格式
+ */
+ @Override
+ public String encrypt(String value, EncodeType encodeType) {
+ if (encodeType == EncodeType.HEX) {
+ return EncryptUtils.encryptByRsaHex(value, context.getPublicKey());
+ } else {
+ return EncryptUtils.encryptByRsa(value, context.getPublicKey());
+ }
+ }
+
+ /**
+ * 解密
+ *
+ * @param value 待加密字符串
+ */
+ @Override
+ public String decrypt(String value) {
+ return EncryptUtils.decryptByRsa(value, context.getPrivateKey());
+ }
+}
diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/encryptor/Sm2Encryptor.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/encryptor/Sm2Encryptor.java
new file mode 100644
index 0000000..aec5d82
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/encryptor/Sm2Encryptor.java
@@ -0,0 +1,61 @@
+package org.dromara.common.encrypt.core.encryptor;
+
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.encrypt.core.EncryptContext;
+import org.dromara.common.encrypt.enumd.AlgorithmType;
+import org.dromara.common.encrypt.enumd.EncodeType;
+import org.dromara.common.encrypt.utils.EncryptUtils;
+
+/**
+ * sm2算法实现
+ *
+ * @author 老马
+ * @version 4.6.0
+ */
+public class Sm2Encryptor extends AbstractEncryptor {
+
+ private final EncryptContext context;
+
+ public Sm2Encryptor(EncryptContext context) {
+ super(context);
+ String privateKey = context.getPrivateKey();
+ String publicKey = context.getPublicKey();
+ if (StringUtils.isAnyEmpty(privateKey, publicKey)) {
+ throw new IllegalArgumentException("SM2公私钥均需要提供,公钥加密,私钥解密。");
+ }
+ this.context = context;
+ }
+
+ /**
+ * 获得当前算法
+ */
+ @Override
+ public AlgorithmType algorithm() {
+ return AlgorithmType.SM2;
+ }
+
+ /**
+ * 加密
+ *
+ * @param value 待加密字符串
+ * @param encodeType 加密后的编码格式
+ */
+ @Override
+ public String encrypt(String value, EncodeType encodeType) {
+ if (encodeType == EncodeType.HEX) {
+ return EncryptUtils.encryptBySm2Hex(value, context.getPublicKey());
+ } else {
+ return EncryptUtils.encryptBySm2(value, context.getPublicKey());
+ }
+ }
+
+ /**
+ * 解密
+ *
+ * @param value 待加密字符串
+ */
+ @Override
+ public String decrypt(String value) {
+ return EncryptUtils.decryptBySm2(value, context.getPrivateKey());
+ }
+}
diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/encryptor/Sm4Encryptor.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/encryptor/Sm4Encryptor.java
new file mode 100644
index 0000000..adaf674
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/encryptor/Sm4Encryptor.java
@@ -0,0 +1,55 @@
+package org.dromara.common.encrypt.core.encryptor;
+
+import org.dromara.common.encrypt.core.EncryptContext;
+import org.dromara.common.encrypt.enumd.AlgorithmType;
+import org.dromara.common.encrypt.enumd.EncodeType;
+import org.dromara.common.encrypt.utils.EncryptUtils;
+
+/**
+ * sm4算法实现
+ *
+ * @author 老马
+ * @version 4.6.0
+ */
+public class Sm4Encryptor extends AbstractEncryptor {
+
+ private final EncryptContext context;
+
+ public Sm4Encryptor(EncryptContext context) {
+ super(context);
+ this.context = context;
+ }
+
+ /**
+ * 获得当前算法
+ */
+ @Override
+ public AlgorithmType algorithm() {
+ return AlgorithmType.SM4;
+ }
+
+ /**
+ * 加密
+ *
+ * @param value 待加密字符串
+ * @param encodeType 加密后的编码格式
+ */
+ @Override
+ public String encrypt(String value, EncodeType encodeType) {
+ if (encodeType == EncodeType.HEX) {
+ return EncryptUtils.encryptBySm4Hex(value, context.getPassword());
+ } else {
+ return EncryptUtils.encryptBySm4(value, context.getPassword());
+ }
+ }
+
+ /**
+ * 解密
+ *
+ * @param value 待加密字符串
+ */
+ @Override
+ public String decrypt(String value) {
+ return EncryptUtils.decryptBySm4(value, context.getPassword());
+ }
+}
diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/enumd/AlgorithmType.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/enumd/AlgorithmType.java
new file mode 100644
index 0000000..7f54939
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/enumd/AlgorithmType.java
@@ -0,0 +1,48 @@
+package org.dromara.common.encrypt.enumd;
+
+import org.dromara.common.encrypt.core.encryptor.*;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 算法名称
+ *
+ * @author 老马
+ * @version 4.6.0
+ */
+@Getter
+@AllArgsConstructor
+public enum AlgorithmType {
+
+ /**
+ * 默认走yml配置
+ */
+ DEFAULT(null),
+
+ /**
+ * base64
+ */
+ BASE64(Base64Encryptor.class),
+
+ /**
+ * aes
+ */
+ AES(AesEncryptor.class),
+
+ /**
+ * rsa
+ */
+ RSA(RsaEncryptor.class),
+
+ /**
+ * sm2
+ */
+ SM2(Sm2Encryptor.class),
+
+ /**
+ * sm4
+ */
+ SM4(Sm4Encryptor.class);
+
+ private final Class extends AbstractEncryptor> clazz;
+}
diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/enumd/EncodeType.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/enumd/EncodeType.java
new file mode 100644
index 0000000..f471221
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/enumd/EncodeType.java
@@ -0,0 +1,26 @@
+package org.dromara.common.encrypt.enumd;
+
+/**
+ * 编码类型
+ *
+ * @author 老马
+ * @version 4.6.0
+ */
+public enum EncodeType {
+
+ /**
+ * 默认使用yml配置
+ */
+ DEFAULT,
+
+ /**
+ * base64编码
+ */
+ BASE64,
+
+ /**
+ * 16进制编码
+ */
+ HEX;
+
+}
diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/filter/CryptoFilter.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/filter/CryptoFilter.java
new file mode 100644
index 0000000..8d898c0
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/filter/CryptoFilter.java
@@ -0,0 +1,115 @@
+package org.dromara.common.encrypt.filter;
+
+import cn.hutool.core.util.ObjectUtil;
+import jakarta.servlet.*;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.dromara.common.core.constant.HttpStatus;
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.core.utils.SpringUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.encrypt.annotation.ApiEncrypt;
+import org.dromara.common.encrypt.properties.ApiDecryptProperties;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.MediaType;
+import org.springframework.web.method.HandlerMethod;
+import org.springframework.web.servlet.HandlerExceptionResolver;
+import org.springframework.web.servlet.HandlerExecutionChain;
+import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
+
+import java.io.IOException;
+
+
+/**
+ * Crypto 过滤器
+ *
+ * @author wdhcr
+ */
+public class CryptoFilter implements Filter {
+ private final ApiDecryptProperties properties;
+
+ public CryptoFilter(ApiDecryptProperties properties) {
+ this.properties = properties;
+ }
+
+ @Override
+ public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
+ HttpServletRequest servletRequest = (HttpServletRequest) request;
+ HttpServletResponse servletResponse = (HttpServletResponse) response;
+
+ boolean responseFlag = false;
+ ServletRequest requestWrapper = null;
+ ServletResponse responseWrapper = null;
+ EncryptResponseBodyWrapper responseBodyWrapper = null;
+
+ // 是否为 json 请求
+ if (StringUtils.startsWithIgnoreCase(request.getContentType(), MediaType.APPLICATION_JSON_VALUE)) {
+ // 是否为 put 或者 post 请求
+ if (HttpMethod.PUT.matches(servletRequest.getMethod()) || HttpMethod.POST.matches(servletRequest.getMethod())) {
+ // 是否存在加密标头
+ String headerValue = servletRequest.getHeader(properties.getHeaderFlag());
+ // 获取加密注解
+ ApiEncrypt apiEncrypt = this.getApiEncryptAnnotation(servletRequest);
+ responseFlag = apiEncrypt != null && apiEncrypt.response();
+ if (StringUtils.isNotBlank(headerValue)) {
+ // 请求解密
+ requestWrapper = new DecryptRequestBodyWrapper(servletRequest, properties.getPrivateKey(), properties.getHeaderFlag());
+ } else {
+ // 是否有注解,有就报错,没有放行
+ if (ObjectUtil.isNotNull(apiEncrypt)) {
+ HandlerExceptionResolver exceptionResolver = SpringUtils.getBean("handlerExceptionResolver", HandlerExceptionResolver.class);
+ exceptionResolver.resolveException(
+ servletRequest, servletResponse, null,
+ new ServiceException("没有访问权限,请联系管理员授权", HttpStatus.FORBIDDEN));
+ return;
+ }
+ }
+ // 判断是否响应加密
+ if (responseFlag) {
+ responseBodyWrapper = new EncryptResponseBodyWrapper(servletResponse);
+ responseWrapper = responseBodyWrapper;
+ }
+ }
+ }
+
+ chain.doFilter(
+ ObjectUtil.defaultIfNull(requestWrapper, request),
+ ObjectUtil.defaultIfNull(responseWrapper, response));
+
+ if (responseFlag) {
+ servletResponse.reset();
+ // 对原始内容加密
+ String encryptContent = responseBodyWrapper.getEncryptContent(
+ servletResponse, properties.getPublicKey(), properties.getHeaderFlag());
+ // 对加密后的内容写出
+ servletResponse.getWriter().write(encryptContent);
+ }
+ }
+
+ /**
+ * 获取 ApiEncrypt 注解
+ */
+ private ApiEncrypt getApiEncryptAnnotation(HttpServletRequest servletRequest) {
+ RequestMappingHandlerMapping handlerMapping = SpringUtils.getBean("requestMappingHandlerMapping", RequestMappingHandlerMapping.class);
+ // 获取注解
+ try {
+ HandlerExecutionChain mappingHandler = handlerMapping.getHandler(servletRequest);
+ if (ObjectUtil.isNotNull(mappingHandler)) {
+ Object handler = mappingHandler.getHandler();
+ if (ObjectUtil.isNotNull(handler)) {
+ // 从handler获取注解
+ if (handler instanceof HandlerMethod handlerMethod) {
+ return handlerMethod.getMethodAnnotation(ApiEncrypt.class);
+ }
+ }
+ }
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ return null;
+ }
+
+ @Override
+ public void destroy() {
+ }
+}
diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/filter/DecryptRequestBodyWrapper.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/filter/DecryptRequestBodyWrapper.java
new file mode 100644
index 0000000..98f4bc7
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/filter/DecryptRequestBodyWrapper.java
@@ -0,0 +1,94 @@
+package org.dromara.common.encrypt.filter;
+
+import cn.hutool.core.io.IoUtil;
+import jakarta.servlet.ReadListener;
+import jakarta.servlet.ServletInputStream;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletRequestWrapper;
+import org.dromara.common.core.constant.Constants;
+import org.dromara.common.encrypt.utils.EncryptUtils;
+import org.springframework.http.MediaType;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * 解密请求参数工具类
+ *
+ * @author wdhcr
+ */
+public class DecryptRequestBodyWrapper extends HttpServletRequestWrapper {
+
+ private final byte[] body;
+
+ public DecryptRequestBodyWrapper(HttpServletRequest request, String privateKey, String headerFlag) throws IOException {
+ super(request);
+ // 获取 AES 密码 采用 RSA 加密
+ String headerRsa = request.getHeader(headerFlag);
+ String decryptAes = EncryptUtils.decryptByRsa(headerRsa, privateKey);
+ // 解密 AES 密码
+ String aesPassword = EncryptUtils.decryptByBase64(decryptAes);
+ request.setCharacterEncoding(Constants.UTF8);
+ byte[] readBytes = IoUtil.readBytes(request.getInputStream(), false);
+ String requestBody = new String(readBytes, StandardCharsets.UTF_8);
+ // 解密 body 采用 AES 加密
+ String decryptBody = EncryptUtils.decryptByAes(requestBody, aesPassword);
+ body = decryptBody.getBytes(StandardCharsets.UTF_8);
+ }
+
+ @Override
+ public BufferedReader getReader() {
+ return new BufferedReader(new InputStreamReader(getInputStream()));
+ }
+
+
+ @Override
+ public int getContentLength() {
+ return body.length;
+ }
+
+ @Override
+ public long getContentLengthLong() {
+ return body.length;
+ }
+
+ @Override
+ public String getContentType() {
+ return MediaType.APPLICATION_JSON_VALUE;
+ }
+
+
+ @Override
+ public ServletInputStream getInputStream() {
+ final ByteArrayInputStream bais = new ByteArrayInputStream(body);
+ return new ServletInputStream() {
+ @Override
+ public int read() {
+ return bais.read();
+ }
+
+ @Override
+ public int available() {
+ return body.length;
+ }
+
+ @Override
+ public boolean isFinished() {
+ return false;
+ }
+
+ @Override
+ public boolean isReady() {
+ return false;
+ }
+
+ @Override
+ public void setReadListener(ReadListener readListener) {
+
+ }
+ };
+ }
+}
diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/filter/EncryptResponseBodyWrapper.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/filter/EncryptResponseBodyWrapper.java
new file mode 100644
index 0000000..5eb34a7
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/filter/EncryptResponseBodyWrapper.java
@@ -0,0 +1,123 @@
+package org.dromara.common.encrypt.filter;
+
+import cn.hutool.core.util.RandomUtil;
+import jakarta.servlet.ServletOutputStream;
+import jakarta.servlet.WriteListener;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.servlet.http.HttpServletResponseWrapper;
+import org.dromara.common.encrypt.utils.EncryptUtils;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * 加密响应参数包装类
+ *
+ * @author Michelle.Chung
+ */
+public class EncryptResponseBodyWrapper extends HttpServletResponseWrapper {
+
+ private final ByteArrayOutputStream byteArrayOutputStream;
+ private final ServletOutputStream servletOutputStream;
+ private final PrintWriter printWriter;
+
+ public EncryptResponseBodyWrapper(HttpServletResponse response) throws IOException {
+ super(response);
+ this.byteArrayOutputStream = new ByteArrayOutputStream();
+ this.servletOutputStream = this.getOutputStream();
+ this.printWriter = new PrintWriter(new OutputStreamWriter(byteArrayOutputStream));
+ }
+
+ @Override
+ public PrintWriter getWriter() {
+ return printWriter;
+ }
+
+ @Override
+ public void flushBuffer() throws IOException {
+ if (servletOutputStream != null) {
+ servletOutputStream.flush();
+ }
+ if (printWriter != null) {
+ printWriter.flush();
+ }
+ }
+
+ @Override
+ public void reset() {
+ byteArrayOutputStream.reset();
+ }
+
+ public byte[] getResponseData() throws IOException {
+ flushBuffer();
+ return byteArrayOutputStream.toByteArray();
+ }
+
+ public String getContent() throws IOException {
+ flushBuffer();
+ return byteArrayOutputStream.toString();
+ }
+
+ /**
+ * 获取加密内容
+ *
+ * @param servletResponse response
+ * @param publicKey RSA公钥 (用于加密 AES 秘钥)
+ * @param headerFlag 请求头标志
+ * @return 加密内容
+ * @throws IOException
+ */
+ public String getEncryptContent(HttpServletResponse servletResponse, String publicKey, String headerFlag) throws IOException {
+ // 生成秘钥
+ String aesPassword = RandomUtil.randomString(32);
+ // 秘钥使用 Base64 编码
+ String encryptAes = EncryptUtils.encryptByBase64(aesPassword);
+ // Rsa 公钥加密 Base64 编码
+ String encryptPassword = EncryptUtils.encryptByRsa(encryptAes, publicKey);
+
+ // 设置响应头
+ servletResponse.setHeader(headerFlag, encryptPassword);
+ servletResponse.setHeader("Access-Control-Allow-Origin", "*");
+ servletResponse.setHeader("Access-Control-Allow-Methods", "*");
+ servletResponse.setCharacterEncoding(StandardCharsets.UTF_8.toString());
+
+ // 获取原始内容
+ String originalBody = this.getContent();
+ // 对内容进行加密
+ return EncryptUtils.encryptByAes(originalBody, aesPassword);
+ }
+
+ @Override
+ public ServletOutputStream getOutputStream() throws IOException {
+ return new ServletOutputStream() {
+ @Override
+ public boolean isReady() {
+ return false;
+ }
+
+ @Override
+ public void setWriteListener(WriteListener writeListener) {
+
+ }
+
+ @Override
+ public void write(int b) throws IOException {
+ byteArrayOutputStream.write(b);
+ }
+
+ @Override
+ public void write(byte[] b) throws IOException {
+ byteArrayOutputStream.write(b);
+ }
+
+ @Override
+ public void write(byte[] b, int off, int len) throws IOException {
+ byteArrayOutputStream.write(b, off, len);
+ }
+ };
+ }
+
+}
diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/interceptor/MybatisDecryptInterceptor.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/interceptor/MybatisDecryptInterceptor.java
new file mode 100644
index 0000000..7c2508f
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/interceptor/MybatisDecryptInterceptor.java
@@ -0,0 +1,116 @@
+package org.dromara.common.encrypt.interceptor;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.util.ObjectUtil;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.ibatis.executor.resultset.ResultSetHandler;
+import org.apache.ibatis.plugin.*;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.encrypt.annotation.EncryptField;
+import org.dromara.common.encrypt.core.EncryptContext;
+import org.dromara.common.encrypt.core.EncryptorManager;
+import org.dromara.common.encrypt.enumd.AlgorithmType;
+import org.dromara.common.encrypt.enumd.EncodeType;
+import org.dromara.common.encrypt.properties.EncryptorProperties;
+
+import java.lang.reflect.Field;
+import java.sql.Statement;
+import java.util.*;
+
+/**
+ * 出参解密拦截器
+ *
+ * @author 老马
+ * @version 4.6.0
+ */
+@Slf4j
+@Intercepts({@Signature(
+ type = ResultSetHandler.class,
+ method = "handleResultSets",
+ args = {Statement.class})
+})
+@AllArgsConstructor
+public class MybatisDecryptInterceptor implements Interceptor {
+
+ private final EncryptorManager encryptorManager;
+ private final EncryptorProperties defaultProperties;
+
+ @Override
+ public Object intercept(Invocation invocation) throws Throwable {
+ // 获取执行mysql执行结果
+ Object result = invocation.proceed();
+ if (result == null) {
+ return null;
+ }
+ decryptHandler(result);
+ return result;
+ }
+
+ /**
+ * 解密对象
+ *
+ * @param sourceObject 待加密对象
+ */
+ private void decryptHandler(Object sourceObject) {
+ if (ObjectUtil.isNull(sourceObject)) {
+ return;
+ }
+ if (sourceObject instanceof Map, ?> map) {
+ new HashSet<>(map.values()).forEach(this::decryptHandler);
+ return;
+ }
+ if (sourceObject instanceof List> list) {
+ if(CollUtil.isEmpty(list)) {
+ return;
+ }
+ // 判断第一个元素是否含有注解。如果没有直接返回,提高效率
+ Object firstItem = list.get(0);
+ if (ObjectUtil.isNull(firstItem) || CollUtil.isEmpty(encryptorManager.getFieldCache(firstItem.getClass()))) {
+ return;
+ }
+ list.forEach(this::decryptHandler);
+ return;
+ }
+ Set fields = encryptorManager.getFieldCache(sourceObject.getClass());
+ try {
+ for (Field field : fields) {
+ field.set(sourceObject, this.decryptField(Convert.toStr(field.get(sourceObject)), field));
+ }
+ } catch (Exception e) {
+ log.error("处理解密字段时出错", e);
+ }
+ }
+
+ /**
+ * 字段值进行加密。通过字段的批注注册新的加密算法
+ *
+ * @param value 待加密的值
+ * @param field 待加密字段
+ * @return 加密后结果
+ */
+ private String decryptField(String value, Field field) {
+ if (ObjectUtil.isNull(value)) {
+ return null;
+ }
+ EncryptField encryptField = field.getAnnotation(EncryptField.class);
+ EncryptContext encryptContext = new EncryptContext();
+ encryptContext.setAlgorithm(encryptField.algorithm() == AlgorithmType.DEFAULT ? defaultProperties.getAlgorithm() : encryptField.algorithm());
+ encryptContext.setEncode(encryptField.encode() == EncodeType.DEFAULT ? defaultProperties.getEncode() : encryptField.encode());
+ encryptContext.setPassword(StringUtils.isBlank(encryptField.password()) ? defaultProperties.getPassword() : encryptField.password());
+ encryptContext.setPrivateKey(StringUtils.isBlank(encryptField.privateKey()) ? defaultProperties.getPrivateKey() : encryptField.privateKey());
+ encryptContext.setPublicKey(StringUtils.isBlank(encryptField.publicKey()) ? defaultProperties.getPublicKey() : encryptField.publicKey());
+ return this.encryptorManager.decrypt(value, encryptContext);
+ }
+
+ @Override
+ public Object plugin(Object target) {
+ return Plugin.wrap(target, this);
+ }
+
+ @Override
+ public void setProperties(Properties properties) {
+
+ }
+}
diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/interceptor/MybatisEncryptInterceptor.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/interceptor/MybatisEncryptInterceptor.java
new file mode 100644
index 0000000..152f7db
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/interceptor/MybatisEncryptInterceptor.java
@@ -0,0 +1,120 @@
+package org.dromara.common.encrypt.interceptor;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.util.ObjectUtil;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.ibatis.executor.parameter.ParameterHandler;
+import org.apache.ibatis.plugin.Interceptor;
+import org.apache.ibatis.plugin.Intercepts;
+import org.apache.ibatis.plugin.Invocation;
+import org.apache.ibatis.plugin.Signature;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.encrypt.annotation.EncryptField;
+import org.dromara.common.encrypt.core.EncryptContext;
+import org.dromara.common.encrypt.core.EncryptorManager;
+import org.dromara.common.encrypt.enumd.AlgorithmType;
+import org.dromara.common.encrypt.enumd.EncodeType;
+import org.dromara.common.encrypt.properties.EncryptorProperties;
+
+import java.lang.reflect.Field;
+import java.sql.PreparedStatement;
+import java.util.*;
+
+/**
+ * 入参加密拦截器
+ *
+ * @author 老马
+ * @version 4.6.0
+ */
+@Slf4j
+@Intercepts({@Signature(
+ type = ParameterHandler.class,
+ method = "setParameters",
+ args = {PreparedStatement.class})
+})
+@AllArgsConstructor
+public class MybatisEncryptInterceptor implements Interceptor {
+
+ private final EncryptorManager encryptorManager;
+ private final EncryptorProperties defaultProperties;
+
+ @Override
+ public Object intercept(Invocation invocation) throws Throwable {
+ return invocation;
+ }
+
+ @Override
+ public Object plugin(Object target) {
+ if (target instanceof ParameterHandler parameterHandler) {
+ // 进行加密操作
+ Object parameterObject = parameterHandler.getParameterObject();
+ if (ObjectUtil.isNotNull(parameterObject) && !(parameterObject instanceof String)) {
+ this.encryptHandler(parameterObject);
+ }
+ }
+ return target;
+ }
+
+ /**
+ * 加密对象
+ *
+ * @param sourceObject 待加密对象
+ */
+ private void encryptHandler(Object sourceObject) {
+ if (ObjectUtil.isNull(sourceObject)) {
+ return;
+ }
+ if (sourceObject instanceof Map, ?> map) {
+ new HashSet<>(map.values()).forEach(this::encryptHandler);
+ return;
+ }
+ if (sourceObject instanceof List> list) {
+ if(CollUtil.isEmpty(list)) {
+ return;
+ }
+ // 判断第一个元素是否含有注解。如果没有直接返回,提高效率
+ Object firstItem = list.get(0);
+ if (ObjectUtil.isNull(firstItem) || CollUtil.isEmpty(encryptorManager.getFieldCache(firstItem.getClass()))) {
+ return;
+ }
+ list.forEach(this::encryptHandler);
+ return;
+ }
+ Set fields = encryptorManager.getFieldCache(sourceObject.getClass());
+ try {
+ for (Field field : fields) {
+ field.set(sourceObject, this.encryptField(Convert.toStr(field.get(sourceObject)), field));
+ }
+ } catch (Exception e) {
+ log.error("处理加密字段时出错", e);
+ }
+ }
+
+ /**
+ * 字段值进行加密。通过字段的批注注册新的加密算法
+ *
+ * @param value 待加密的值
+ * @param field 待加密字段
+ * @return 加密后结果
+ */
+ private String encryptField(String value, Field field) {
+ if (ObjectUtil.isNull(value)) {
+ return null;
+ }
+ EncryptField encryptField = field.getAnnotation(EncryptField.class);
+ EncryptContext encryptContext = new EncryptContext();
+ encryptContext.setAlgorithm(encryptField.algorithm() == AlgorithmType.DEFAULT ? defaultProperties.getAlgorithm() : encryptField.algorithm());
+ encryptContext.setEncode(encryptField.encode() == EncodeType.DEFAULT ? defaultProperties.getEncode() : encryptField.encode());
+ encryptContext.setPassword(StringUtils.isBlank(encryptField.password()) ? defaultProperties.getPassword() : encryptField.password());
+ encryptContext.setPrivateKey(StringUtils.isBlank(encryptField.privateKey()) ? defaultProperties.getPrivateKey() : encryptField.privateKey());
+ encryptContext.setPublicKey(StringUtils.isBlank(encryptField.publicKey()) ? defaultProperties.getPublicKey() : encryptField.publicKey());
+ return this.encryptorManager.encrypt(value, encryptContext);
+ }
+
+
+ @Override
+ public void setProperties(Properties properties) {
+ }
+}
diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/properties/ApiDecryptProperties.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/properties/ApiDecryptProperties.java
new file mode 100644
index 0000000..6aadb3e
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/properties/ApiDecryptProperties.java
@@ -0,0 +1,34 @@
+package org.dromara.common.encrypt.properties;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * api解密属性配置类
+ * @author wdhcr
+ */
+@Data
+@ConfigurationProperties(prefix = "api-decrypt")
+public class ApiDecryptProperties {
+
+ /**
+ * 加密开关
+ */
+ private Boolean enabled;
+
+ /**
+ * 头部标识
+ */
+ private String headerFlag;
+
+ /**
+ * 响应加密公钥
+ */
+ private String publicKey;
+
+ /**
+ * 请求解密私钥
+ */
+ private String privateKey;
+
+}
diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/properties/EncryptorProperties.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/properties/EncryptorProperties.java
new file mode 100644
index 0000000..ba445c1
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/properties/EncryptorProperties.java
@@ -0,0 +1,48 @@
+package org.dromara.common.encrypt.properties;
+
+import org.dromara.common.encrypt.enumd.AlgorithmType;
+import org.dromara.common.encrypt.enumd.EncodeType;
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * 加解密属性配置类
+ *
+ * @author 老马
+ * @version 4.6.0
+ */
+@Data
+@ConfigurationProperties(prefix = "mybatis-encryptor")
+public class EncryptorProperties {
+
+ /**
+ * 过滤开关
+ */
+ private Boolean enable;
+
+ /**
+ * 默认算法
+ */
+ private AlgorithmType algorithm;
+
+ /**
+ * 安全秘钥
+ */
+ private String password;
+
+ /**
+ * 公钥
+ */
+ private String publicKey;
+
+ /**
+ * 私钥
+ */
+ private String privateKey;
+
+ /**
+ * 编码方式,base64/hex
+ */
+ private EncodeType encode;
+
+}
diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/utils/EncryptUtils.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/utils/EncryptUtils.java
new file mode 100644
index 0000000..8e34843
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/utils/EncryptUtils.java
@@ -0,0 +1,311 @@
+package org.dromara.common.encrypt.utils;
+
+import cn.hutool.core.codec.Base64;
+import cn.hutool.core.util.ArrayUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.crypto.SecureUtil;
+import cn.hutool.crypto.SmUtil;
+import cn.hutool.crypto.asymmetric.KeyType;
+import cn.hutool.crypto.asymmetric.RSA;
+import cn.hutool.crypto.asymmetric.SM2;
+
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 安全相关工具类
+ *
+ * @author 老马
+ */
+public class EncryptUtils {
+ /**
+ * 公钥
+ */
+ public static final String PUBLIC_KEY = "publicKey";
+ /**
+ * 私钥
+ */
+ public static final String PRIVATE_KEY = "privateKey";
+
+ /**
+ * Base64加密
+ *
+ * @param data 待加密数据
+ * @return 加密后字符串
+ */
+ public static String encryptByBase64(String data) {
+ return Base64.encode(data, StandardCharsets.UTF_8);
+ }
+
+ /**
+ * Base64解密
+ *
+ * @param data 待解密数据
+ * @return 解密后字符串
+ */
+ public static String decryptByBase64(String data) {
+ return Base64.decodeStr(data, StandardCharsets.UTF_8);
+ }
+
+ /**
+ * AES加密
+ *
+ * @param data 待解密数据
+ * @param password 秘钥字符串
+ * @return 加密后字符串, 采用Base64编码
+ */
+ public static String encryptByAes(String data, String password) {
+ if (StrUtil.isBlank(password)) {
+ throw new IllegalArgumentException("AES需要传入秘钥信息");
+ }
+ // aes算法的秘钥要求是16位、24位、32位
+ int[] array = {16, 24, 32};
+ if (!ArrayUtil.contains(array, password.length())) {
+ throw new IllegalArgumentException("AES秘钥长度要求为16位、24位、32位");
+ }
+ return SecureUtil.aes(password.getBytes(StandardCharsets.UTF_8)).encryptBase64(data, StandardCharsets.UTF_8);
+ }
+
+ /**
+ * AES加密
+ *
+ * @param data 待解密数据
+ * @param password 秘钥字符串
+ * @return 加密后字符串, 采用Hex编码
+ */
+ public static String encryptByAesHex(String data, String password) {
+ if (StrUtil.isBlank(password)) {
+ throw new IllegalArgumentException("AES需要传入秘钥信息");
+ }
+ // aes算法的秘钥要求是16位、24位、32位
+ int[] array = {16, 24, 32};
+ if (!ArrayUtil.contains(array, password.length())) {
+ throw new IllegalArgumentException("AES秘钥长度要求为16位、24位、32位");
+ }
+ return SecureUtil.aes(password.getBytes(StandardCharsets.UTF_8)).encryptHex(data, StandardCharsets.UTF_8);
+ }
+
+ /**
+ * AES解密
+ *
+ * @param data 待解密数据
+ * @param password 秘钥字符串
+ * @return 解密后字符串
+ */
+ public static String decryptByAes(String data, String password) {
+ if (StrUtil.isBlank(password)) {
+ throw new IllegalArgumentException("AES需要传入秘钥信息");
+ }
+ // aes算法的秘钥要求是16位、24位、32位
+ int[] array = {16, 24, 32};
+ if (!ArrayUtil.contains(array, password.length())) {
+ throw new IllegalArgumentException("AES秘钥长度要求为16位、24位、32位");
+ }
+ return SecureUtil.aes(password.getBytes(StandardCharsets.UTF_8)).decryptStr(data, StandardCharsets.UTF_8);
+ }
+
+ /**
+ * sm4加密
+ *
+ * @param data 待加密数据
+ * @param password 秘钥字符串
+ * @return 加密后字符串, 采用Base64编码
+ */
+ public static String encryptBySm4(String data, String password) {
+ if (StrUtil.isBlank(password)) {
+ throw new IllegalArgumentException("SM4需要传入秘钥信息");
+ }
+ // sm4算法的秘钥要求是16位长度
+ int sm4PasswordLength = 16;
+ if (sm4PasswordLength != password.length()) {
+ throw new IllegalArgumentException("SM4秘钥长度要求为16位");
+ }
+ return SmUtil.sm4(password.getBytes(StandardCharsets.UTF_8)).encryptBase64(data, StandardCharsets.UTF_8);
+ }
+
+ /**
+ * sm4加密
+ *
+ * @param data 待加密数据
+ * @param password 秘钥字符串
+ * @return 加密后字符串, 采用Base64编码
+ */
+ public static String encryptBySm4Hex(String data, String password) {
+ if (StrUtil.isBlank(password)) {
+ throw new IllegalArgumentException("SM4需要传入秘钥信息");
+ }
+ // sm4算法的秘钥要求是16位长度
+ int sm4PasswordLength = 16;
+ if (sm4PasswordLength != password.length()) {
+ throw new IllegalArgumentException("SM4秘钥长度要求为16位");
+ }
+ return SmUtil.sm4(password.getBytes(StandardCharsets.UTF_8)).encryptHex(data, StandardCharsets.UTF_8);
+ }
+
+ /**
+ * sm4解密
+ *
+ * @param data 待解密数据
+ * @param password 秘钥字符串
+ * @return 解密后字符串
+ */
+ public static String decryptBySm4(String data, String password) {
+ if (StrUtil.isBlank(password)) {
+ throw new IllegalArgumentException("SM4需要传入秘钥信息");
+ }
+ // sm4算法的秘钥要求是16位长度
+ int sm4PasswordLength = 16;
+ if (sm4PasswordLength != password.length()) {
+ throw new IllegalArgumentException("SM4秘钥长度要求为16位");
+ }
+ return SmUtil.sm4(password.getBytes(StandardCharsets.UTF_8)).decryptStr(data, StandardCharsets.UTF_8);
+ }
+
+ /**
+ * 产生sm2加解密需要的公钥和私钥
+ *
+ * @return 公私钥Map
+ */
+ public static Map generateSm2Key() {
+ Map keyMap = new HashMap<>(2);
+ SM2 sm2 = SmUtil.sm2();
+ keyMap.put(PRIVATE_KEY, sm2.getPrivateKeyBase64());
+ keyMap.put(PUBLIC_KEY, sm2.getPublicKeyBase64());
+ return keyMap;
+ }
+
+ /**
+ * sm2公钥加密
+ *
+ * @param data 待加密数据
+ * @param publicKey 公钥
+ * @return 加密后字符串, 采用Base64编码
+ */
+ public static String encryptBySm2(String data, String publicKey) {
+ if (StrUtil.isBlank(publicKey)) {
+ throw new IllegalArgumentException("SM2需要传入公钥进行加密");
+ }
+ SM2 sm2 = SmUtil.sm2(null, publicKey);
+ return sm2.encryptBase64(data, StandardCharsets.UTF_8, KeyType.PublicKey);
+ }
+
+ /**
+ * sm2公钥加密
+ *
+ * @param data 待加密数据
+ * @param publicKey 公钥
+ * @return 加密后字符串, 采用Hex编码
+ */
+ public static String encryptBySm2Hex(String data, String publicKey) {
+ if (StrUtil.isBlank(publicKey)) {
+ throw new IllegalArgumentException("SM2需要传入公钥进行加密");
+ }
+ SM2 sm2 = SmUtil.sm2(null, publicKey);
+ return sm2.encryptHex(data, StandardCharsets.UTF_8, KeyType.PublicKey);
+ }
+
+ /**
+ * sm2私钥解密
+ *
+ * @param data 待加密数据
+ * @param privateKey 私钥
+ * @return 解密后字符串
+ */
+ public static String decryptBySm2(String data, String privateKey) {
+ if (StrUtil.isBlank(privateKey)) {
+ throw new IllegalArgumentException("SM2需要传入私钥进行解密");
+ }
+ SM2 sm2 = SmUtil.sm2(privateKey, null);
+ return sm2.decryptStr(data, KeyType.PrivateKey, StandardCharsets.UTF_8);
+ }
+
+ /**
+ * 产生RSA加解密需要的公钥和私钥
+ *
+ * @return 公私钥Map
+ */
+ public static Map generateRsaKey() {
+ Map keyMap = new HashMap<>(2);
+ RSA rsa = SecureUtil.rsa();
+ keyMap.put(PRIVATE_KEY, rsa.getPrivateKeyBase64());
+ keyMap.put(PUBLIC_KEY, rsa.getPublicKeyBase64());
+ return keyMap;
+ }
+
+ /**
+ * rsa公钥加密
+ *
+ * @param data 待加密数据
+ * @param publicKey 公钥
+ * @return 加密后字符串, 采用Base64编码
+ */
+ public static String encryptByRsa(String data, String publicKey) {
+ if (StrUtil.isBlank(publicKey)) {
+ throw new IllegalArgumentException("RSA需要传入公钥进行加密");
+ }
+ RSA rsa = SecureUtil.rsa(null, publicKey);
+ return rsa.encryptBase64(data, StandardCharsets.UTF_8, KeyType.PublicKey);
+ }
+
+ /**
+ * rsa公钥加密
+ *
+ * @param data 待加密数据
+ * @param publicKey 公钥
+ * @return 加密后字符串, 采用Hex编码
+ */
+ public static String encryptByRsaHex(String data, String publicKey) {
+ if (StrUtil.isBlank(publicKey)) {
+ throw new IllegalArgumentException("RSA需要传入公钥进行加密");
+ }
+ RSA rsa = SecureUtil.rsa(null, publicKey);
+ return rsa.encryptHex(data, StandardCharsets.UTF_8, KeyType.PublicKey);
+ }
+
+ /**
+ * rsa私钥解密
+ *
+ * @param data 待加密数据
+ * @param privateKey 私钥
+ * @return 解密后字符串
+ */
+ public static String decryptByRsa(String data, String privateKey) {
+ if (StrUtil.isBlank(privateKey)) {
+ throw new IllegalArgumentException("RSA需要传入私钥进行解密");
+ }
+ RSA rsa = SecureUtil.rsa(privateKey, null);
+ return rsa.decryptStr(data, KeyType.PrivateKey, StandardCharsets.UTF_8);
+ }
+
+ /**
+ * md5加密
+ *
+ * @param data 待加密数据
+ * @return 加密后字符串, 采用Hex编码
+ */
+ public static String encryptByMd5(String data) {
+ return SecureUtil.md5(data);
+ }
+
+ /**
+ * sha256加密
+ *
+ * @param data 待加密数据
+ * @return 加密后字符串, 采用Hex编码
+ */
+ public static String encryptBySha256(String data) {
+ return SecureUtil.sha256(data);
+ }
+
+ /**
+ * sm3加密
+ *
+ * @param data 待加密数据
+ * @return 加密后字符串, 采用Hex编码
+ */
+ public static String encryptBySm3(String data) {
+ return SmUtil.sm3(data);
+ }
+
+}
diff --git a/ruoyi-common/ruoyi-common-excel/pom.xml b/ruoyi-common/ruoyi-common-excel/pom.xml
new file mode 100644
index 0000000..dd4a5ee
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-excel/pom.xml
@@ -0,0 +1,30 @@
+
+
+
+ org.dromara
+ ruoyi-common
+ ${revision}
+
+ 4.0.0
+
+ ruoyi-common-excel
+
+
+ ruoyi-common-excel
+
+
+
+
+ org.dromara
+ ruoyi-common-json
+
+
+
+ com.alibaba
+ easyexcel
+
+
+
+
diff --git a/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/annotation/CellMerge.java b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/annotation/CellMerge.java
new file mode 100644
index 0000000..bbdaaa1
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/annotation/CellMerge.java
@@ -0,0 +1,24 @@
+package org.dromara.common.excel.annotation;
+
+import org.dromara.common.excel.core.CellMergeStrategy;
+
+import java.lang.annotation.*;
+
+/**
+ * excel 列单元格合并(合并列相同项)
+ *
+ * 需搭配 {@link CellMergeStrategy} 策略使用
+ *
+ * @author Lion Li
+ */
+@Target(ElementType.FIELD)
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface CellMerge {
+
+ /**
+ * col index
+ */
+ int index() default -1;
+
+}
diff --git a/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/annotation/ExcelDictFormat.java b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/annotation/ExcelDictFormat.java
new file mode 100644
index 0000000..5c51842
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/annotation/ExcelDictFormat.java
@@ -0,0 +1,32 @@
+package org.dromara.common.excel.annotation;
+
+import org.dromara.common.core.utils.StringUtils;
+
+import java.lang.annotation.*;
+
+/**
+ * 字典格式化
+ *
+ * @author Lion Li
+ */
+@Target({ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface ExcelDictFormat {
+
+ /**
+ * 如果是字典类型,请设置字典的type值 (如: sys_user_sex)
+ */
+ String dictType() default "";
+
+ /**
+ * 读取内容转表达式 (如: 0=男,1=女,2=未知)
+ */
+ String readConverterExp() default "";
+
+ /**
+ * 分隔符,读取字符串组内容
+ */
+ String separator() default StringUtils.SEPARATOR;
+
+}
diff --git a/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/annotation/ExcelEnumFormat.java b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/annotation/ExcelEnumFormat.java
new file mode 100644
index 0000000..290379d
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/annotation/ExcelEnumFormat.java
@@ -0,0 +1,30 @@
+package org.dromara.common.excel.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * 枚举格式化
+ *
+ * @author Liang
+ */
+@Target({ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface ExcelEnumFormat {
+
+ /**
+ * 字典枚举类型
+ */
+ Class extends Enum>> enumClass();
+
+ /**
+ * 字典枚举类中对应的code属性名称,默认为code
+ */
+ String codeField() default "code";
+
+ /**
+ * 字典枚举类中对应的text属性名称,默认为text
+ */
+ String textField() default "text";
+
+}
diff --git a/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/convert/ExcelBigNumberConvert.java b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/convert/ExcelBigNumberConvert.java
new file mode 100644
index 0000000..07cc4c4
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/convert/ExcelBigNumberConvert.java
@@ -0,0 +1,52 @@
+package org.dromara.common.excel.convert;
+
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.util.ObjectUtil;
+import com.alibaba.excel.converters.Converter;
+import com.alibaba.excel.enums.CellDataTypeEnum;
+import com.alibaba.excel.metadata.GlobalConfiguration;
+import com.alibaba.excel.metadata.data.ReadCellData;
+import com.alibaba.excel.metadata.data.WriteCellData;
+import com.alibaba.excel.metadata.property.ExcelContentProperty;
+import lombok.extern.slf4j.Slf4j;
+
+import java.math.BigDecimal;
+
+/**
+ * 大数值转换
+ * Excel 数值长度位15位 大于15位的数值转换位字符串
+ *
+ * @author Lion Li
+ */
+@Slf4j
+public class ExcelBigNumberConvert implements Converter {
+
+ @Override
+ public Class supportJavaTypeKey() {
+ return Long.class;
+ }
+
+ @Override
+ public CellDataTypeEnum supportExcelTypeKey() {
+ return CellDataTypeEnum.STRING;
+ }
+
+ @Override
+ public Long convertToJavaData(ReadCellData> cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
+ return Convert.toLong(cellData.getData());
+ }
+
+ @Override
+ public WriteCellData