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 mapper) { + return splitTo(str, SEPARATOR, mapper); + } + + /** + * 切分字符串自定义转换 + * + * @param str 被切分的字符串 + * @param separator 分隔符 + * @param mapper 自定义转换 + * @return 分割后的数据列表 + */ + public static List splitTo(String str, String separator, Function 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[] 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 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> 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 convertToExcelData(Long object, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) { + if (ObjectUtil.isNotNull(object)) { + String str = Convert.toStr(object); + if (str.length() > 15) { + return new WriteCellData<>(str); + } + } + WriteCellData cellData = new WriteCellData<>(new BigDecimal(object)); + cellData.setType(CellDataTypeEnum.NUMBER); + return cellData; + } + +} diff --git a/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/convert/ExcelDictConvert.java b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/convert/ExcelDictConvert.java new file mode 100644 index 0000000..61eeabf --- /dev/null +++ b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/convert/ExcelDictConvert.java @@ -0,0 +1,73 @@ +package org.dromara.common.excel.convert; + +import cn.hutool.core.annotation.AnnotationUtil; +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 org.dromara.common.excel.annotation.ExcelDictFormat; +import org.dromara.common.core.service.DictService; +import org.dromara.common.core.utils.SpringUtils; +import org.dromara.common.core.utils.StringUtils; +import org.dromara.common.excel.utils.ExcelUtil; +import lombok.extern.slf4j.Slf4j; + +import java.lang.reflect.Field; + +/** + * 字典格式化转换处理 + * + * @author Lion Li + */ +@Slf4j +public class ExcelDictConvert implements Converter { + + @Override + public Class supportJavaTypeKey() { + return Object.class; + } + + @Override + public CellDataTypeEnum supportExcelTypeKey() { + return null; + } + + @Override + public Object convertToJavaData(ReadCellData cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) { + ExcelDictFormat anno = getAnnotation(contentProperty.getField()); + String type = anno.dictType(); + String label = cellData.getStringValue(); + String value; + if (StringUtils.isBlank(type)) { + value = ExcelUtil.reverseByExp(label, anno.readConverterExp(), anno.separator()); + } else { + value = SpringUtils.getBean(DictService.class).getDictValue(type, label, anno.separator()); + } + return Convert.convert(contentProperty.getField().getType(), value); + } + + @Override + public WriteCellData convertToExcelData(Object object, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) { + if (ObjectUtil.isNull(object)) { + return new WriteCellData<>(""); + } + ExcelDictFormat anno = getAnnotation(contentProperty.getField()); + String type = anno.dictType(); + String value = Convert.toStr(object); + String label; + if (StringUtils.isBlank(type)) { + label = ExcelUtil.convertByExp(value, anno.readConverterExp(), anno.separator()); + } else { + label = SpringUtils.getBean(DictService.class).getDictLabel(type, value, anno.separator()); + } + return new WriteCellData<>(label); + } + + private ExcelDictFormat getAnnotation(Field field) { + return AnnotationUtil.getAnnotation(field, ExcelDictFormat.class); + } +} diff --git a/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/convert/ExcelEnumConvert.java b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/convert/ExcelEnumConvert.java new file mode 100644 index 0000000..b948ea7 --- /dev/null +++ b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/convert/ExcelEnumConvert.java @@ -0,0 +1,87 @@ +package org.dromara.common.excel.convert; + +import cn.hutool.core.annotation.AnnotationUtil; +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 org.dromara.common.core.utils.reflect.ReflectUtils; +import org.dromara.common.excel.annotation.ExcelEnumFormat; +import lombok.extern.slf4j.Slf4j; + +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.Map; + +/** + * 枚举格式化转换处理 + * + * @author Liang + */ +@Slf4j +public class ExcelEnumConvert implements Converter { + + @Override + public Class supportJavaTypeKey() { + return Object.class; + } + + @Override + public CellDataTypeEnum supportExcelTypeKey() { + return null; + } + + @Override + public Object convertToJavaData(ReadCellData cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) { + cellData.checkEmpty(); + // Excel中填入的是枚举中指定的描述 + Object textValue = switch (cellData.getType()) { + case STRING, DIRECT_STRING, RICH_TEXT_STRING -> cellData.getStringValue(); + case NUMBER -> cellData.getNumberValue(); + case BOOLEAN -> cellData.getBooleanValue(); + default -> throw new IllegalArgumentException("单元格类型异常!"); + }; + // 如果是空值 + if (ObjectUtil.isNull(textValue)) { + return null; + } + Map enumCodeToTextMap = beforeConvert(contentProperty); + // 从Java输出至Excel是code转text + // 因此从Excel转Java应该将text与code对调 + Map enumTextToCodeMap = new HashMap<>(); + enumCodeToTextMap.forEach((key, value) -> enumTextToCodeMap.put(value, key)); + // 应该从text -> code中查找 + Object codeValue = enumTextToCodeMap.get(textValue); + return Convert.convert(contentProperty.getField().getType(), codeValue); + } + + @Override + public WriteCellData convertToExcelData(Object object, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) { + if (ObjectUtil.isNull(object)) { + return new WriteCellData<>(""); + } + Map enumValueMap = beforeConvert(contentProperty); + String value = Convert.toStr(enumValueMap.get(object), ""); + return new WriteCellData<>(value); + } + + private Map beforeConvert(ExcelContentProperty contentProperty) { + ExcelEnumFormat anno = getAnnotation(contentProperty.getField()); + Map enumValueMap = new HashMap<>(); + Enum[] enumConstants = anno.enumClass().getEnumConstants(); + for (Enum enumConstant : enumConstants) { + Object codeValue = ReflectUtils.invokeGetter(enumConstant, anno.codeField()); + String textValue = ReflectUtils.invokeGetter(enumConstant, anno.textField()); + enumValueMap.put(codeValue, textValue); + } + return enumValueMap; + } + + private ExcelEnumFormat getAnnotation(Field field) { + return AnnotationUtil.getAnnotation(field, ExcelEnumFormat.class); + } +} diff --git a/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/CellMergeStrategy.java b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/CellMergeStrategy.java new file mode 100644 index 0000000..bcb5be7 --- /dev/null +++ b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/CellMergeStrategy.java @@ -0,0 +1,142 @@ +package org.dromara.common.excel.core; + +import cn.hutool.core.collection.CollUtil; +import com.alibaba.excel.annotation.ExcelProperty; +import com.alibaba.excel.metadata.Head; +import com.alibaba.excel.write.merge.AbstractMergeStrategy; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.util.CellRangeAddress; +import org.dromara.common.core.utils.reflect.ReflectUtils; +import org.dromara.common.excel.annotation.CellMerge; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 列值重复合并策略 + * + * @author Lion Li + */ +@Slf4j +public class CellMergeStrategy extends AbstractMergeStrategy { + + private final List cellList; + private final boolean hasTitle; + private int rowIndex; + + public CellMergeStrategy(List list, boolean hasTitle) { + this.hasTitle = hasTitle; + // 行合并开始下标 + this.rowIndex = hasTitle ? 1 : 0; + this.cellList = handle(list, hasTitle); + } + + @Override + protected void merge(Sheet sheet, Cell cell, Head head, Integer relativeRowIndex) { + // judge the list is not null + if (CollUtil.isNotEmpty(cellList)) { + // the judge is necessary + if (cell.getRowIndex() == rowIndex && cell.getColumnIndex() == 0) { + for (CellRangeAddress item : cellList) { + sheet.addMergedRegion(item); + } + } + } + } + + @SneakyThrows + private List handle(List list, boolean hasTitle) { + List cellList = new ArrayList<>(); + if (CollUtil.isEmpty(list)) { + return cellList; + } + Field[] fields = ReflectUtils.getFields(list.get(0).getClass(), field -> !"serialVersionUID".equals(field.getName())); + + // 有注解的字段 + List mergeFields = new ArrayList<>(); + List mergeFieldsIndex = new ArrayList<>(); + for (int i = 0; i < fields.length; i++) { + Field field = fields[i]; + if (field.isAnnotationPresent(CellMerge.class)) { + CellMerge cm = field.getAnnotation(CellMerge.class); + mergeFields.add(field); + mergeFieldsIndex.add(cm.index() == -1 ? i : cm.index()); + if (hasTitle) { + ExcelProperty property = field.getAnnotation(ExcelProperty.class); + rowIndex = Math.max(rowIndex, property.value().length); + } + } + } + + Map map = new HashMap<>(); + // 生成两两合并单元格 + for (int i = 0; i < list.size(); i++) { + for (int j = 0; j < mergeFields.size(); j++) { + Field field = mergeFields.get(j); + Object val = ReflectUtils.invokeGetter(list.get(i), field.getName()); + + int colNum = mergeFieldsIndex.get(j); + if (!map.containsKey(field)) { + map.put(field, new RepeatCell(val, i)); + } else { + RepeatCell repeatCell = map.get(field); + Object cellValue = repeatCell.getValue(); + if (cellValue == null || "".equals(cellValue)) { + // 空值跳过不合并 + continue; + } + if (!cellValue.equals(val)) { + if (i - repeatCell.getCurrent() > 1) { + cellList.add(new CellRangeAddress(repeatCell.getCurrent() + rowIndex, i + rowIndex - 1, colNum, colNum)); + } + map.put(field, new RepeatCell(val, i)); + } else if (j == 0) { + if (i == list.size() - 1) { + if (i > repeatCell.getCurrent()) { + cellList.add(new CellRangeAddress(repeatCell.getCurrent() + rowIndex, i + rowIndex, colNum, colNum)); + } + } + } else { + // 判断前面的是否合并了 + RepeatCell firstCell = map.get(mergeFields.get(0)); + if (repeatCell.getCurrent() != firstCell.getCurrent()) { + if (i == list.size() - 1) { + if (i > repeatCell.getCurrent()) { + cellList.add(new CellRangeAddress(repeatCell.getCurrent() + rowIndex, i + rowIndex, colNum, colNum)); + } + } else if (repeatCell.getCurrent() < firstCell.getCurrent()) { + if (i - repeatCell.getCurrent() > 1) { + cellList.add(new CellRangeAddress(repeatCell.getCurrent() + rowIndex, i + rowIndex - 1, colNum, colNum)); + } + map.put(field, new RepeatCell(val, i)); + } + } else if (i == list.size() - 1) { + if (i > repeatCell.getCurrent()) { + cellList.add(new CellRangeAddress(repeatCell.getCurrent() + rowIndex, i + rowIndex, colNum, colNum)); + } + } + } + } + } + } + return cellList; + } + + @Data + @AllArgsConstructor + static class RepeatCell { + + private Object value; + + private int current; + + } +} diff --git a/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/DefaultExcelListener.java b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/DefaultExcelListener.java new file mode 100644 index 0000000..b6fa0b4 --- /dev/null +++ b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/DefaultExcelListener.java @@ -0,0 +1,104 @@ +package org.dromara.common.excel.core; + +import cn.hutool.core.util.StrUtil; +import com.alibaba.excel.context.AnalysisContext; +import com.alibaba.excel.event.AnalysisEventListener; +import com.alibaba.excel.exception.ExcelAnalysisException; +import com.alibaba.excel.exception.ExcelDataConvertException; +import org.dromara.common.core.utils.StreamUtils; +import org.dromara.common.core.utils.ValidatorUtils; +import org.dromara.common.json.utils.JsonUtils; +import jakarta.validation.ConstraintViolation; +import jakarta.validation.ConstraintViolationException; +import lombok.NoArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import java.util.Map; +import java.util.Set; + +/** + * Excel 导入监听 + * + * @author Yjoioooo + * @author Lion Li + */ +@Slf4j +@NoArgsConstructor +public class DefaultExcelListener extends AnalysisEventListener implements ExcelListener { + + /** + * 是否Validator检验,默认为是 + */ + private Boolean isValidate = Boolean.TRUE; + + /** + * excel 表头数据 + */ + private Map headMap; + + /** + * 导入回执 + */ + private ExcelResult excelResult; + + public DefaultExcelListener(boolean isValidate) { + this.excelResult = new DefaultExcelResult<>(); + this.isValidate = isValidate; + } + + /** + * 处理异常 + * + * @param exception ExcelDataConvertException + * @param context Excel 上下文 + */ + @Override + public void onException(Exception exception, AnalysisContext context) throws Exception { + String errMsg = null; + if (exception instanceof ExcelDataConvertException excelDataConvertException) { + // 如果是某一个单元格的转换异常 能获取到具体行号 + Integer rowIndex = excelDataConvertException.getRowIndex(); + Integer columnIndex = excelDataConvertException.getColumnIndex(); + errMsg = StrUtil.format("第{}行-第{}列-表头{}: 解析异常
", + rowIndex + 1, columnIndex + 1, headMap.get(columnIndex)); + if (log.isDebugEnabled()) { + log.error(errMsg); + } + } + if (exception instanceof ConstraintViolationException constraintViolationException) { + Set> constraintViolations = constraintViolationException.getConstraintViolations(); + String constraintViolationsMsg = StreamUtils.join(constraintViolations, ConstraintViolation::getMessage, ", "); + errMsg = StrUtil.format("第{}行数据校验异常: {}", context.readRowHolder().getRowIndex() + 1, constraintViolationsMsg); + if (log.isDebugEnabled()) { + log.error(errMsg); + } + } + excelResult.getErrorList().add(errMsg); + throw new ExcelAnalysisException(errMsg); + } + + @Override + public void invokeHeadMap(Map headMap, AnalysisContext context) { + this.headMap = headMap; + log.debug("解析到一条表头数据: {}", JsonUtils.toJsonString(headMap)); + } + + @Override + public void invoke(T data, AnalysisContext context) { + if (isValidate) { + ValidatorUtils.validate(data); + } + excelResult.getList().add(data); + } + + @Override + public void doAfterAllAnalysed(AnalysisContext context) { + log.debug("所有数据解析完成!"); + } + + @Override + public ExcelResult getExcelResult() { + return excelResult; + } + +} diff --git a/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/DefaultExcelResult.java b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/DefaultExcelResult.java new file mode 100644 index 0000000..84340a6 --- /dev/null +++ b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/DefaultExcelResult.java @@ -0,0 +1,89 @@ +package org.dromara.common.excel.core; + +import cn.hutool.core.util.StrUtil; +import lombok.Data; +import lombok.Getter; +import lombok.Setter; + +import java.util.ArrayList; +import java.util.List; + +/** + * 默认excel返回对象 + * + * @author Yjoioooo + * @author Lion Li + */ + +public class DefaultExcelResult implements ExcelResult { + + /** + * 数据对象list + */ + @Setter + private List list; + + /** + * 错误信息列表 + */ + @Setter + private List errorList; + + + + private Integer allRecords; + + + public DefaultExcelResult() { + this.list = new ArrayList<>(); + this.errorList = new ArrayList<>(); + } + + public DefaultExcelResult(List list, List errorList) { + this.list = list; + this.errorList = errorList; + } + + public DefaultExcelResult(ExcelResult excelResult) { + this.list = excelResult.getList(); + this.errorList = excelResult.getErrorList(); + } + + @Override + public List getList() { + return list; + } + + @Override + public List getErrorList() { + return errorList; + } + + public Integer getAllRecords() { + return allRecords; + } + + public void setAllRecords(Integer allRecords) { + this.allRecords = allRecords; + } + + /** + * 获取导入回执 + * + * @return 导入回执 + */ + @Override + public String getAnalysis() { + int successCount = list.size(); + int errorCount = errorList.size(); + if (successCount == 0) { + return "读取失败,未解析到数据"; + } else { + if (errorCount == 0) { + return StrUtil.format("恭喜您,全部读取成功!共{}条", successCount); + } else { + return ""; + } + } + } +} diff --git a/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/DropDownOptions.java b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/DropDownOptions.java new file mode 100644 index 0000000..8b53a0c --- /dev/null +++ b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/DropDownOptions.java @@ -0,0 +1,149 @@ +package org.dromara.common.excel.core; + +import cn.hutool.core.util.StrUtil; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.dromara.common.core.exception.ServiceException; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + *

Excel下拉可选项

+ * 注意:为确保下拉框解析正确,传值务必使用createOptionValue()做为值的拼接 + * + * @author Emil.Zhang + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@SuppressWarnings("unused") +public class DropDownOptions { + /** + * 一级下拉所在列index,从0开始算 + */ + private int index = 0; + /** + * 二级下拉所在的index,从0开始算,不能与一级相同 + */ + private int nextIndex = 0; + /** + * 一级下拉所包含的数据 + */ + private List options = new ArrayList<>(); + /** + * 二级下拉所包含的数据Map + *

以每一个一级选项值为Key,每个一级选项对应的二级数据为Value

+ */ + private Map> nextOptions = new HashMap<>(); + /** + * 分隔符 + */ + private static final String DELIMITER = "_"; + + /** + * 创建只有一级的下拉选 + */ + public DropDownOptions(int index, List options) { + this.index = index; + this.options = options; + } + + /** + *

创建每个选项可选值

+ *

注意:不能以数字,特殊符号开头,选项中不可以包含任何运算符号

+ * + * @param vars 可选值内包含的参数 + * @return 合规的可选值 + */ + public static String createOptionValue(Object... vars) { + StringBuilder stringBuffer = new StringBuilder(); + String regex = "^[\\S\\d\\u4e00-\\u9fa5]+$"; + for (int i = 0; i < vars.length; i++) { + String var = StrUtil.trimToEmpty(String.valueOf(vars[i])); + if (!var.matches(regex)) { + throw new ServiceException("选项数据不符合规则,仅允许使用中英文字符以及数字"); + } + stringBuffer.append(var); + if (i < vars.length - 1) { + // 直至最后一个前,都以_作为切割线 + stringBuffer.append(DELIMITER); + } + } + if (stringBuffer.toString().matches("^\\d_*$")) { + throw new ServiceException("禁止以数字开头"); + } + return stringBuffer.toString(); + } + + /** + * 将处理后合理的可选值解析为原始的参数 + * + * @param option 经过处理后的合理的可选项 + * @return 原始的参数 + */ + public static List analyzeOptionValue(String option) { + return StrUtil.split(option, DELIMITER, true, true); + } + + /** + * 创建级联下拉选项 + * + * @param parentList 父实体可选项原始数据 + * @param parentIndex 父下拉选位置 + * @param sonList 子实体可选项原始数据 + * @param sonIndex 子下拉选位置 + * @param parentHowToGetIdFunction 父类如何获取唯一标识 + * @param sonHowToGetParentIdFunction 子类如何获取父类的唯一标识 + * @param howToBuildEveryOption 如何生成下拉选内容 + * @return 级联下拉选项 + */ + public static DropDownOptions buildLinkedOptions(List parentList, + int parentIndex, + List sonList, + int sonIndex, + Function parentHowToGetIdFunction, + Function sonHowToGetParentIdFunction, + Function howToBuildEveryOption) { + DropDownOptions parentLinkSonOptions = new DropDownOptions(); + // 先创建父类的下拉 + parentLinkSonOptions.setIndex(parentIndex); + parentLinkSonOptions.setOptions( + parentList.stream() + .map(howToBuildEveryOption) + .collect(Collectors.toList()) + ); + // 提取父-子级联下拉 + Map> sonOptions = new HashMap<>(); + // 父级依据自己的ID分组 + Map> parentGroupByIdMap = + parentList.stream().collect(Collectors.groupingBy(parentHowToGetIdFunction)); + // 遍历每个子集,提取到Map中 + sonList.forEach(everySon -> { + if (parentGroupByIdMap.containsKey(sonHowToGetParentIdFunction.apply(everySon))) { + // 找到对应的上级 + T parentObj = parentGroupByIdMap.get(sonHowToGetParentIdFunction.apply(everySon)).get(0); + // 提取名称和ID作为Key + String key = howToBuildEveryOption.apply(parentObj); + // Key对应的Value + List thisParentSonOptionList; + if (sonOptions.containsKey(key)) { + thisParentSonOptionList = sonOptions.get(key); + } else { + thisParentSonOptionList = new ArrayList<>(); + sonOptions.put(key, thisParentSonOptionList); + } + // 往Value中添加当前子集选项 + thisParentSonOptionList.add(howToBuildEveryOption.apply(everySon)); + } + }); + parentLinkSonOptions.setNextIndex(sonIndex); + parentLinkSonOptions.setNextOptions(sonOptions); + return parentLinkSonOptions; + } +} diff --git a/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/ExcelDownHandler.java b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/ExcelDownHandler.java new file mode 100644 index 0000000..3b791ea --- /dev/null +++ b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/ExcelDownHandler.java @@ -0,0 +1,371 @@ +package org.dromara.common.excel.core; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.EnumUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import com.alibaba.excel.metadata.FieldCache; +import com.alibaba.excel.metadata.FieldWrapper; +import com.alibaba.excel.util.ClassUtils; +import com.alibaba.excel.write.handler.SheetWriteHandler; +import com.alibaba.excel.write.metadata.holder.WriteSheetHolder; +import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder; +import lombok.extern.slf4j.Slf4j; +import org.apache.poi.ss.usermodel.*; +import org.apache.poi.ss.util.CellRangeAddressList; +import org.apache.poi.ss.util.WorkbookUtil; +import org.apache.poi.xssf.usermodel.XSSFDataValidation; +import org.dromara.common.core.exception.ServiceException; +import org.dromara.common.core.service.DictService; +import org.dromara.common.core.utils.SpringUtils; +import org.dromara.common.core.utils.StreamUtils; +import org.dromara.common.excel.annotation.ExcelDictFormat; +import org.dromara.common.excel.annotation.ExcelEnumFormat; + +import java.lang.reflect.Field; +import java.util.*; + +/** + *

Excel表格下拉选操作

+ * 考虑到下拉选过多可能导致Excel打开缓慢的问题,只校验前1000行 + *

+ * 即只有前1000行的数据可以用下拉框,超出的自行通过限制数据量的形式,第二次输出 + * + * @author Emil.Zhang + */ +@Slf4j +public class ExcelDownHandler implements SheetWriteHandler { + + /** + * Excel表格中的列名英文 + * 仅为了解析列英文,禁止修改 + */ + private static final String EXCEL_COLUMN_NAME = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + /** + * 单选数据Sheet名 + */ + private static final String OPTIONS_SHEET_NAME = "options"; + /** + * 联动选择数据Sheet名的头 + */ + private static final String LINKED_OPTIONS_SHEET_NAME = "linkedOptions"; + /** + * 下拉可选项 + */ + private final List dropDownOptions; + /** + * 当前单选进度 + */ + private int currentOptionsColumnIndex; + /** + * 当前联动选择进度 + */ + private int currentLinkedOptionsSheetIndex; + private final DictService dictService; + + public ExcelDownHandler(List options) { + this.dropDownOptions = options; + this.currentOptionsColumnIndex = 0; + this.currentLinkedOptionsSheetIndex = 0; + this.dictService = SpringUtils.getBean(DictService.class); + } + + /** + *

开始创建下拉数据

+ * 1.通过解析传入的@ExcelProperty同级是否标注有@DropDown选项 + * 如果有且设置了value值,则将其直接置为下拉可选项 + *

+ * 2.或者在调用ExcelUtil时指定了可选项,将依据传入的可选项做下拉 + *

+ * 3.二者并存,注意调用方式 + */ + @Override + public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) { + Sheet sheet = writeSheetHolder.getSheet(); + // 开始设置下拉框 HSSFWorkbook + DataValidationHelper helper = sheet.getDataValidationHelper(); + Workbook workbook = writeWorkbookHolder.getWorkbook(); + FieldCache fieldCache = ClassUtils.declaredFields(writeWorkbookHolder.getClazz(), writeWorkbookHolder); + for (Map.Entry entry : fieldCache.getSortedFieldMap().entrySet()) { + Integer index = entry.getKey(); + FieldWrapper wrapper = entry.getValue(); + Field field = wrapper.getField(); + // 循环实体中的每个属性 + // 可选的下拉值 + List options = new ArrayList<>(); + if (field.isAnnotationPresent(ExcelDictFormat.class)) { + // 如果指定了@ExcelDictFormat,则使用字典的逻辑 + ExcelDictFormat format = field.getDeclaredAnnotation(ExcelDictFormat.class); + String dictType = format.dictType(); + String converterExp = format.readConverterExp(); + if (StrUtil.isNotBlank(dictType)) { + // 如果传递了字典名,则依据字典建立下拉 + Collection values = Optional.ofNullable(dictService.getAllDictByDictType(dictType)) + .orElseThrow(() -> new ServiceException(String.format("字典 %s 不存在", dictType))) + .values(); + options = new ArrayList<>(values); + } else if (StrUtil.isNotBlank(converterExp)) { + // 如果指定了确切的值,则直接解析确切的值 + options = StrUtil.split(converterExp, format.separator(), true, true); + } + } else if (field.isAnnotationPresent(ExcelEnumFormat.class)) { + // 否则如果指定了@ExcelEnumFormat,则使用枚举的逻辑 + ExcelEnumFormat format = field.getDeclaredAnnotation(ExcelEnumFormat.class); + List values = EnumUtil.getFieldValues(format.enumClass(), format.textField()); + options = StreamUtils.toList(values, String::valueOf); + } + if (ObjectUtil.isNotEmpty(options)) { + // 仅当下拉可选项不为空时执行 + if (options.size() > 20) { + // 这里限制如果可选项大于20,则使用额外表形式 + dropDownWithSheet(helper, workbook, sheet, index, options); + } else { + // 否则使用固定值形式 + dropDownWithSimple(helper, sheet, index, options); + } + } + } + if (CollUtil.isEmpty(dropDownOptions)) { + return; + } + dropDownOptions.forEach(everyOptions -> { + // 如果传递了下拉框选择器参数 + if (!everyOptions.getNextOptions().isEmpty()) { + // 当二级选项不为空时,使用额外关联表的形式 + dropDownLinkedOptions(helper, workbook, sheet, everyOptions); + } else if (everyOptions.getOptions().size() > 10) { + // 当一级选项参数个数大于10,使用额外表的形式 + dropDownWithSheet(helper, workbook, sheet, everyOptions.getIndex(), everyOptions.getOptions()); + } else if (everyOptions.getOptions().size() != 0) { + // 当一级选项个数不为空,使用默认形式 + dropDownWithSimple(helper, sheet, everyOptions.getIndex(), everyOptions.getOptions()); + } + }); + } + + /** + *

简单下拉框

+ * 直接将可选项拼接为指定列的数据校验值 + * + * @param celIndex 列index + * @param value 下拉选可选值 + */ + private void dropDownWithSimple(DataValidationHelper helper, Sheet sheet, Integer celIndex, List value) { + if (ObjectUtil.isEmpty(value)) { + return; + } + this.markOptionsToSheet(helper, sheet, celIndex, helper.createExplicitListConstraint(ArrayUtil.toArray(value, String.class))); + } + + /** + *

额外表格形式的级联下拉框

+ * + * @param options 额外表格形式存储的下拉可选项 + */ + private void dropDownLinkedOptions(DataValidationHelper helper, Workbook workbook, Sheet sheet, DropDownOptions options) { + String linkedOptionsSheetName = String.format("%s_%d", LINKED_OPTIONS_SHEET_NAME, currentLinkedOptionsSheetIndex); + // 创建联动下拉数据表 + Sheet linkedOptionsDataSheet = workbook.createSheet(WorkbookUtil.createSafeSheetName(linkedOptionsSheetName)); + // 将下拉表隐藏 + workbook.setSheetHidden(workbook.getSheetIndex(linkedOptionsDataSheet), true); + // 完善横向的一级选项数据表 + List firstOptions = options.getOptions(); + Map> secoundOptionsMap = options.getNextOptions(); + + // 创建名称管理器 + Name name = workbook.createName(); + // 设置名称管理器的别名 + name.setNameName(linkedOptionsSheetName); + // 以横向第一行创建一级下拉拼接引用位置 + String firstOptionsFunction = String.format("%s!$%s$1:$%s$1", + linkedOptionsSheetName, + getExcelColumnName(0), + getExcelColumnName(firstOptions.size()) + ); + // 设置名称管理器的引用位置 + name.setRefersToFormula(firstOptionsFunction); + // 设置数据校验为序列模式,引用的是名称管理器中的别名 + this.markOptionsToSheet(helper, sheet, options.getIndex(), helper.createFormulaListConstraint(linkedOptionsSheetName)); + + for (int columIndex = 0; columIndex < firstOptions.size(); columIndex++) { + // 先提取主表中一级下拉的列名 + String firstOptionsColumnName = getExcelColumnName(columIndex); + // 一次循环是每一个一级选项 + int finalI = columIndex; + // 本次循环的一级选项值 + String thisFirstOptionsValue = firstOptions.get(columIndex); + // 创建第一行的数据 + Optional.ofNullable(linkedOptionsDataSheet.getRow(0)) + // 如果不存在则创建第一行 + .orElseGet(() -> linkedOptionsDataSheet.createRow(finalI)) + // 第一行当前列 + .createCell(columIndex) + // 设置值为当前一级选项值 + .setCellValue(thisFirstOptionsValue); + + // 第二行开始,设置第二级别选项参数 + List secondOptions = secoundOptionsMap.get(thisFirstOptionsValue); + if (CollUtil.isEmpty(secondOptions)) { + // 必须保证至少有一个关联选项,否则将导致Excel解析错误 + secondOptions = Collections.singletonList("暂无_0"); + } + + // 以该一级选项值创建子名称管理器 + Name sonName = workbook.createName(); + // 设置名称管理器的别名 + sonName.setNameName(thisFirstOptionsValue); + // 以第二行该列数据拼接引用位置 + String sonFunction = String.format("%s!$%s$2:$%s$%d", + linkedOptionsSheetName, + firstOptionsColumnName, + firstOptionsColumnName, + secondOptions.size() + 1 + ); + // 设置名称管理器的引用位置 + sonName.setRefersToFormula(sonFunction); + // 数据验证为序列模式,引用到每一个主表中的二级选项位置 + // 创建子项的名称管理器,只是为了使得Excel可以识别到数据 + String mainSheetFirstOptionsColumnName = getExcelColumnName(options.getIndex()); + for (int i = 0; i < 100; i++) { + // 以一级选项对应的主体所在位置创建二级下拉 + String secondOptionsFunction = String.format("=INDIRECT(%s%d)", mainSheetFirstOptionsColumnName, i + 1); + // 二级只能主表每一行的每一列添加二级校验 + markLinkedOptionsToSheet(helper, sheet, i, options.getNextIndex(), helper.createFormulaListConstraint(secondOptionsFunction)); + } + + for (int rowIndex = 0; rowIndex < secondOptions.size(); rowIndex++) { + // 从第二行开始填充二级选项 + int finalRowIndex = rowIndex + 1; + int finalColumIndex = columIndex; + + Row row = Optional.ofNullable(linkedOptionsDataSheet.getRow(finalRowIndex)) + // 没有则创建 + .orElseGet(() -> linkedOptionsDataSheet.createRow(finalRowIndex)); + Optional + // 在本级一级选项所在的列 + .ofNullable(row.getCell(finalColumIndex)) + // 不存在则创建 + .orElseGet(() -> row.createCell(finalColumIndex)) + // 设置二级选项值 + .setCellValue(secondOptions.get(rowIndex)); + } + } + + currentLinkedOptionsSheetIndex++; + } + + /** + *

额外表格形式的普通下拉框

+ * 由于下拉框可选值数量过多,为提升Excel打开效率,使用额外表格形式做下拉 + * + * @param celIndex 下拉选 + * @param value 下拉选可选值 + */ + private void dropDownWithSheet(DataValidationHelper helper, Workbook workbook, Sheet sheet, Integer celIndex, List value) { + // 创建下拉数据表 + Sheet simpleDataSheet = Optional.ofNullable(workbook.getSheet(WorkbookUtil.createSafeSheetName(OPTIONS_SHEET_NAME))) + .orElseGet(() -> workbook.createSheet(WorkbookUtil.createSafeSheetName(OPTIONS_SHEET_NAME))); + // 将下拉表隐藏 + workbook.setSheetHidden(workbook.getSheetIndex(simpleDataSheet), true); + // 完善纵向的一级选项数据表 + for (int i = 0; i < value.size(); i++) { + int finalI = i; + // 获取每一选项行,如果没有则创建 + Row row = Optional.ofNullable(simpleDataSheet.getRow(i)) + .orElseGet(() -> simpleDataSheet.createRow(finalI)); + // 获取本级选项对应的选项列,如果没有则创建 + Cell cell = Optional.ofNullable(row.getCell(currentOptionsColumnIndex)) + .orElseGet(() -> row.createCell(currentOptionsColumnIndex)); + // 设置值 + cell.setCellValue(value.get(i)); + } + + // 创建名称管理器 + Name name = workbook.createName(); + // 设置名称管理器的别名 + String nameName = String.format("%s_%d", OPTIONS_SHEET_NAME, celIndex); + name.setNameName(nameName); + // 以纵向第一列创建一级下拉拼接引用位置 + String function = String.format("%s!$%s$1:$%s$%d", + OPTIONS_SHEET_NAME, + getExcelColumnName(currentOptionsColumnIndex), + getExcelColumnName(currentOptionsColumnIndex), + value.size()); + // 设置名称管理器的引用位置 + name.setRefersToFormula(function); + // 设置数据校验为序列模式,引用的是名称管理器中的别名 + this.markOptionsToSheet(helper, sheet, celIndex, helper.createFormulaListConstraint(nameName)); + currentOptionsColumnIndex++; + } + + /** + * 挂载下拉的列,仅限一级选项 + */ + private void markOptionsToSheet(DataValidationHelper helper, Sheet sheet, Integer celIndex, + DataValidationConstraint constraint) { + // 设置数据有效性加载在哪个单元格上,四个参数分别是:起始行、终止行、起始列、终止列 + CellRangeAddressList addressList = new CellRangeAddressList(1, 1000, celIndex, celIndex); + markDataValidationToSheet(helper, sheet, constraint, addressList); + } + + /** + * 挂载下拉的列,仅限二级选项 + */ + private void markLinkedOptionsToSheet(DataValidationHelper helper, Sheet sheet, Integer rowIndex, + Integer celIndex, DataValidationConstraint constraint) { + // 设置数据有效性加载在哪个单元格上,四个参数分别是:起始行、终止行、起始列、终止列 + CellRangeAddressList addressList = new CellRangeAddressList(rowIndex, rowIndex, celIndex, celIndex); + markDataValidationToSheet(helper, sheet, constraint, addressList); + } + + /** + * 应用数据校验 + */ + private void markDataValidationToSheet(DataValidationHelper helper, Sheet sheet, + DataValidationConstraint constraint, CellRangeAddressList addressList) { + // 数据有效性对象 + DataValidation dataValidation = helper.createValidation(constraint, addressList); + // 处理Excel兼容性问题 + if (dataValidation instanceof XSSFDataValidation) { + //数据校验 + dataValidation.setSuppressDropDownArrow(true); + //错误提示 + dataValidation.setErrorStyle(DataValidation.ErrorStyle.STOP); + dataValidation.createErrorBox("提示", "此值与单元格定义数据不一致"); + dataValidation.setShowErrorBox(true); + //选定提示 + dataValidation.createPromptBox("填写说明:", "填写内容只能为下拉中数据,其他数据将导致导入失败"); + dataValidation.setShowPromptBox(true); + sheet.addValidationData(dataValidation); + } else { + dataValidation.setSuppressDropDownArrow(false); + } + sheet.addValidationData(dataValidation); + } + + /** + *

依据列index获取列名英文

+ * 依据列index转换为Excel中的列名英文 + *

例如第1列,index为0,解析出来为A列

+ * 第27列,index为26,解析为AA列 + *

第28列,index为27,解析为AB列

+ * + * @param columnIndex 列index + * @return 列index所在得英文名 + */ + private String getExcelColumnName(int columnIndex) { + // 26一循环的次数 + int columnCircleCount = columnIndex / 26; + // 26一循环内的位置 + int thisCircleColumnIndex = columnIndex % 26; + // 26一循环的次数大于0,则视为栏名至少两位 + String columnPrefix = columnCircleCount == 0 + ? StrUtil.EMPTY + : StrUtil.subWithLength(EXCEL_COLUMN_NAME, columnCircleCount - 1, 1); + // 从26一循环内取对应的栏位名 + String columnNext = StrUtil.subWithLength(EXCEL_COLUMN_NAME, thisCircleColumnIndex, 1); + // 将二者拼接即为最终的栏位名 + return columnPrefix + columnNext; + } +} diff --git a/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/ExcelListener.java b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/ExcelListener.java new file mode 100644 index 0000000..2d0340f --- /dev/null +++ b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/ExcelListener.java @@ -0,0 +1,14 @@ +package org.dromara.common.excel.core; + +import com.alibaba.excel.read.listener.ReadListener; + +/** + * Excel 导入监听 + * + * @author Lion Li + */ +public interface ExcelListener extends ReadListener { + + ExcelResult getExcelResult(); + +} diff --git a/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/ExcelResult.java b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/ExcelResult.java new file mode 100644 index 0000000..0c2a418 --- /dev/null +++ b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/ExcelResult.java @@ -0,0 +1,26 @@ +package org.dromara.common.excel.core; + +import java.util.List; + +/** + * excel返回对象 + * + * @author Lion Li + */ +public interface ExcelResult { + + /** + * 对象列表 + */ + List getList(); + + /** + * 错误列表 + */ + List getErrorList(); + + /** + * 导入回执 + */ + String getAnalysis(); +} diff --git a/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/IExcelListener.java b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/IExcelListener.java new file mode 100644 index 0000000..8486c62 --- /dev/null +++ b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/IExcelListener.java @@ -0,0 +1,14 @@ +package org.dromara.common.excel.core; + +import com.alibaba.excel.read.listener.ReadListener; + +/** + * Excel 导入监听 + * + * @author Lion Li + */ +public interface IExcelListener extends ReadListener { + + ExcelResult getExcelResult(); + +} diff --git a/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/utils/ExcelUtil.java b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/utils/ExcelUtil.java new file mode 100644 index 0000000..d47e59d --- /dev/null +++ b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/utils/ExcelUtil.java @@ -0,0 +1,566 @@ +package org.dromara.common.excel.utils; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.io.resource.ClassPathResource; +import cn.hutool.core.util.IdUtil; +import com.alibaba.excel.EasyExcel; +import com.alibaba.excel.ExcelWriter; +import com.alibaba.excel.annotation.ExcelProperty; +import com.alibaba.excel.write.builder.ExcelWriterSheetBuilder; +import com.alibaba.excel.write.metadata.WriteSheet; +import com.alibaba.excel.write.metadata.fill.FillConfig; +import com.alibaba.excel.write.metadata.fill.FillWrapper; +import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy; +import jakarta.servlet.ServletOutputStream; +import jakarta.servlet.http.HttpServletResponse; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.apache.poi.ss.formula.functions.T; +import org.dromara.common.core.domain.R; +import org.dromara.common.core.utils.StringUtils; +import org.dromara.common.core.utils.file.FileUtils; +import org.dromara.common.excel.convert.ExcelBigNumberConvert; +import org.dromara.common.excel.core.*; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.*; +import java.util.stream.Collectors; + +/** + * Excel相关处理 + * + * @author Lion Li + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class ExcelUtil { + + /** + * 同步导入(适用于小数据量) + * + * @param is 输入流 + * @return 转换后集合 + */ + public static List importExcel(InputStream is, Class clazz) { + return EasyExcel.read(is).head(clazz).autoCloseStream(false).sheet().doReadSync(); + } + + + /** + * 使用校验监听器 异步导入 同步返回 + * + * @param is 输入流 + * @param clazz 对象类型 + * @param isValidate 是否 Validator 检验 默认为是 + * @return 转换后集合 + */ + public static ExcelResult importExcel(InputStream is, Class clazz, boolean isValidate) { + DefaultExcelListener listener = new DefaultExcelListener<>(isValidate); + EasyExcel.read(is, clazz, listener).sheet().doRead(); + return listener.getExcelResult(); + } + + /** + * 使用自定义监听器 异步导入 自定义返回 + * + * @param is 输入流 + * @param clazz 对象类型 + * @param listener 自定义监听器 + * @return 转换后集合 + */ + public static ExcelResult importExcel(InputStream is, Class clazz, ExcelListener listener) { + EasyExcel.read(is, clazz, listener).sheet().doRead(); + return listener.getExcelResult(); + } + + /** + * 使用自定义监听器 异步导入 自定义返回 + * + * @param is 输入流 + * @param listener 自定义监听器 + * @return 转换后集合 + */ + public static ExcelResult importExcel(InputStream is, IExcelListener listener) { + EasyExcel.read(is, listener).sheet().doRead(); + return listener.getExcelResult(); + } + + /** + * 导出excel + * + * @param list 导出数据集合 + * @param sheetName 工作表的名称 + * @param clazz 实体类 + * @param response 响应体 + */ + public static void exportExcel(List list, String sheetName, Class clazz, HttpServletResponse response) { + try { + resetResponse(sheetName, response); + ServletOutputStream os = response.getOutputStream(); + exportExcel(list, sheetName, clazz, false, os, null); + } catch (IOException e) { + throw new RuntimeException("导出Excel异常"); + } + } + + + public static void exportExcelInclude(List list, String sheetName, Class clazz, HttpServletResponse response,ListinColumnFieldNames) { + try { + resetResponse(sheetName, response); + ServletOutputStream os = response.getOutputStream(); + exportExcel(list, sheetName, clazz, false, os, null,inColumnFieldNames); + } catch (IOException e) { + throw new RuntimeException("导出Excel异常"); + } + } + + /** + * 导出excel + * + * @param list 导出数据集合 + * @param sheetName 工作表的名称 + * @param clazz 实体类 + * @param response 响应体 + * @param options 级联下拉选 + */ + public static void exportExcel(List list, String sheetName, Class clazz, HttpServletResponse response, List options) { + try { + resetResponse(sheetName, response); + ServletOutputStream os = response.getOutputStream(); + exportExcel(list, sheetName, clazz, false, os, options); + } catch (IOException e) { + throw new RuntimeException("导出Excel异常"); + } + } + + /** + * 导出excel + * + * @param list 导出数据集合 + * @param sheetName 工作表的名称 + * @param clazz 实体类 + * @param merge 是否合并单元格 + * @param response 响应体 + */ + public static void exportExcel(List list, String sheetName, Class clazz, boolean merge, HttpServletResponse response) { + try { + resetResponse(sheetName, response); + ServletOutputStream os = response.getOutputStream(); + exportExcel(list, sheetName, clazz, merge, os, null); + } catch (IOException e) { + throw new RuntimeException("导出Excel异常"); + } + } + + /** + * 导出excel + * + * @param list 导出数据集合 + * @param sheetName 工作表的名称 + * @param clazz 实体类 + * @param merge 是否合并单元格 + * @param response 响应体 + * @param options 级联下拉选 + */ + public static void exportExcel(List list, String sheetName, Class clazz, boolean merge, HttpServletResponse response, List options) { + try { + resetResponse(sheetName, response); + ServletOutputStream os = response.getOutputStream(); + exportExcel(list, sheetName, clazz, merge, os, options); + } catch (IOException e) { + throw new RuntimeException("导出Excel异常"); + } + } + + /** + * 导出excel + * + * @param list 导出数据集合 + * @param sheetName 工作表的名称 + * @param clazz 实体类 + * @param os 输出流 + */ + public static void exportExcel(List list, String sheetName, Class clazz, OutputStream os) { + exportExcel(list, sheetName, clazz, false, os, null); + } + + /** + * 导出excel + * + * @param list 导出数据集合 + * @param sheetName 工作表的名称 + * @param clazz 实体类 + * @param os 输出流 + * @param options 级联下拉选内容 + */ + public static void exportExcel(List list, String sheetName, Class clazz, OutputStream os, List options) { + exportExcel(list, sheetName, clazz, false, os, options); + } + + + /** + * 导出excel + * + * @param list 导出数据集合 + * @param sheetName 工作表的名称 + * @param clazz 实体类 + * @param merge 是否合并单元格 + * @param os 输出流 + */ + public static void exportExcel(List list, String sheetName, Class clazz, boolean merge, + OutputStream os, List options,List includeField ) { + ExcelWriterSheetBuilder builder = EasyExcel.write(os, clazz) +// .head(head(clazz,includeField)) + .includeColumnFieldNames(includeField) + .autoCloseStream(false) + + // 自动适配 + .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()) + // 大数值自动转换 防止失真 + .registerConverter(new ExcelBigNumberConvert()) + .sheet(sheetName); + if (merge) { + // 合并处理器 + builder.registerWriteHandler(new CellMergeStrategy(list, true)); + } + + builder.orderByIncludeColumn(true); + // 添加下拉框操作 + builder.registerWriteHandler(new ExcelDownHandler(options)); + builder.doWrite(list); + } + + + /** + * 设置表格信息 + * + * @param dataList 查询出的数据 + * @param column 需要显示的字段 + * @return + */ + private static List> dataList(Collection dataList, Collection column) { + List> list = new ArrayList<>(); + for (Object person : dataList) { + List data = new ArrayList<>(); + for (String fieldName : column) { + // 通过反射根据需要显示的字段,获取对应的属性值 + data.add(getFieldValue(fieldName, person)); + } + list.add(data); + } + return list; + } + + /** + * 根据传入的字段获取对应的get方法,如name,对应的getName方法 + * + * @param fieldName 字段名 + * @param obj 对象 + * @return + */ + private static Object getFieldValue(String fieldName, Object obj) { + try { + String firstUpper = fieldName.substring(0, 1).toUpperCase(); + String getter = "get" + firstUpper + fieldName.substring(1); + Method method = obj.getClass().getMethod(getter); + return method.invoke(obj); + } catch (Exception e) { + return null; + } + + } + + /** + * 将注解中的头按传入列的顺序返回 + * + * @param clazz 指定类 + * @param columnList 传入的列顺序 + * @return + */ + public static List> head(Class clazz, Collection columnList) { + List> list = new ArrayList<>(); + + for (String column : columnList) { + try { + Field field = clazz.getDeclaredField(column); + if(field != null){ + ExcelProperty ep = field.getAnnotation(ExcelProperty.class); + if(ep != null){ + String[] value = ep.value(); + List collect = Arrays.stream(value).collect(Collectors.toList()); + list.add(collect); + } + } + } catch (NoSuchFieldException e) { + e.printStackTrace(); + } + } + return list; + } + + + /** + * 导出excel + * + * @param list 导出数据集合 + * @param sheetName 工作表的名称 + * @param clazz 实体类 + * @param merge 是否合并单元格 + * @param os 输出流 + */ + public static void exportExcel(List list, String sheetName, Class clazz, boolean merge, + OutputStream os, List options) { + ExcelWriterSheetBuilder builder = EasyExcel.write(os, clazz) + + .autoCloseStream(false) + // 自动适配 + .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()) + // 大数值自动转换 防止失真 + .registerConverter(new ExcelBigNumberConvert()) + .sheet(sheetName); + if (merge) { + // 合并处理器 + builder.registerWriteHandler(new CellMergeStrategy(list, true)); + } + // 添加下拉框操作 + builder.registerWriteHandler(new ExcelDownHandler(options)); + builder.doWrite(list); + } + + /** + * 单表多数据模板导出 模板格式为 {.属性} + * + * @param filename 文件名 + * @param templatePath 模板路径 resource 目录下的路径包括模板文件名 + * 例如: excel/temp.xlsx + * 重点: 模板文件必须放置到启动类对应的 resource 目录下 + * @param data 模板需要的数据 + * @param response 响应体 + */ + public static void exportTemplate(List data, String filename, String templatePath, HttpServletResponse response) { + try { + resetResponse(filename, response); + ServletOutputStream os = response.getOutputStream(); + exportTemplate(data, templatePath, os); + } catch (IOException e) { + throw new RuntimeException("导出Excel异常"); + } + } + + /** + * 单表多数据模板导出 模板格式为 {.属性} + * + * @param templatePath 模板路径 resource 目录下的路径包括模板文件名 + * 例如: excel/temp.xlsx + * 重点: 模板文件必须放置到启动类对应的 resource 目录下 + * @param data 模板需要的数据 + * @param os 输出流 + */ + public static void exportTemplate(List data, String templatePath, OutputStream os) { + ClassPathResource templateResource = new ClassPathResource(templatePath); + ExcelWriter excelWriter = EasyExcel.write(os) + .withTemplate(templateResource.getStream()) + .autoCloseStream(false) + // 大数值自动转换 防止失真 + .registerConverter(new ExcelBigNumberConvert()) + .build(); + WriteSheet writeSheet = EasyExcel.writerSheet().build(); + if (CollUtil.isEmpty(data)) { + throw new IllegalArgumentException("数据为空"); + } + // 单表多数据导出 模板格式为 {.属性} + for (Object d : data) { + excelWriter.fill(d, writeSheet); + } + excelWriter.finish(); + } + + /** + * 多表多数据模板导出 模板格式为 {key.属性} + * + * @param filename 文件名 + * @param templatePath 模板路径 resource 目录下的路径包括模板文件名 + * 例如: excel/temp.xlsx + * 重点: 模板文件必须放置到启动类对应的 resource 目录下 + * @param data 模板需要的数据 + * @param response 响应体 + */ + public static void exportTemplateMultiList(Map data, String filename, String templatePath, HttpServletResponse response) { + try { + resetResponse(filename, response); + ServletOutputStream os = response.getOutputStream(); + exportTemplateMultiList(data, templatePath, os); + } catch (IOException e) { + throw new RuntimeException("导出Excel异常"); + } + } + + /** + * 多sheet模板导出 模板格式为 {key.属性} + * + * @param filename 文件名 + * @param templatePath 模板路径 resource 目录下的路径包括模板文件名 + * 例如: excel/temp.xlsx + * 重点: 模板文件必须放置到启动类对应的 resource 目录下 + * @param data 模板需要的数据 + * @param response 响应体 + */ + public static void exportTemplateMultiSheet(List> data, String filename, String templatePath, HttpServletResponse response) { + try { + resetResponse(filename, response); + ServletOutputStream os = response.getOutputStream(); + exportTemplateMultiSheet(data, templatePath, os); + } catch (IOException e) { + throw new RuntimeException("导出Excel异常"); + } + } + + /** + * 多表多数据模板导出 模板格式为 {key.属性} + * + * @param templatePath 模板路径 resource 目录下的路径包括模板文件名 + * 例如: excel/temp.xlsx + * 重点: 模板文件必须放置到启动类对应的 resource 目录下 + * @param data 模板需要的数据 + * @param os 输出流 + */ + public static void exportTemplateMultiList(Map data, String templatePath, OutputStream os) { + ClassPathResource templateResource = new ClassPathResource(templatePath); + ExcelWriter excelWriter = EasyExcel.write(os) + .withTemplate(templateResource.getStream()) + .autoCloseStream(false) + // 大数值自动转换 防止失真 + .registerConverter(new ExcelBigNumberConvert()) + .build(); + WriteSheet writeSheet = EasyExcel.writerSheet().build(); + if (CollUtil.isEmpty(data)) { + throw new IllegalArgumentException("数据为空"); + } + for (Map.Entry map : data.entrySet()) { + // 设置列表后续还有数据 + FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build(); + if (map.getValue() instanceof Collection) { + // 多表导出必须使用 FillWrapper + excelWriter.fill(new FillWrapper(map.getKey(), (Collection) map.getValue()), fillConfig, writeSheet); + } else { + excelWriter.fill(map.getValue(), writeSheet); + } + } + excelWriter.finish(); + } + + /** + * 多sheet模板导出 模板格式为 {key.属性} + * + * @param templatePath 模板路径 resource 目录下的路径包括模板文件名 + * 例如: excel/temp.xlsx + * 重点: 模板文件必须放置到启动类对应的 resource 目录下 + * @param data 模板需要的数据 + * @param os 输出流 + */ + public static void exportTemplateMultiSheet(List> data, String templatePath, OutputStream os) { + ClassPathResource templateResource = new ClassPathResource(templatePath); + ExcelWriter excelWriter = EasyExcel.write(os) + .withTemplate(templateResource.getStream()) + .autoCloseStream(false) + // 大数值自动转换 防止失真 + .registerConverter(new ExcelBigNumberConvert()) + .build(); + if (CollUtil.isEmpty(data)) { + throw new IllegalArgumentException("数据为空"); + } + for (int i = 0; i < data.size(); i++) { + WriteSheet writeSheet = EasyExcel.writerSheet(i).build(); + for (Map.Entry map : data.get(i).entrySet()) { + // 设置列表后续还有数据 + FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build(); + if (map.getValue() instanceof Collection) { + // 多表导出必须使用 FillWrapper + excelWriter.fill(new FillWrapper(map.getKey(), (Collection) map.getValue()), fillConfig, writeSheet); + } else { + excelWriter.fill(map.getValue(), writeSheet); + } + } + } + excelWriter.finish(); + } + + /** + * 重置响应体 + */ + private static void resetResponse(String sheetName, HttpServletResponse response) throws UnsupportedEncodingException { + String filename = encodingFilename(sheetName); + FileUtils.setAttachmentResponseHeader(response, filename); + response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8"); + } + + /** + * 解析导出值 0=男,1=女,2=未知 + * + * @param propertyValue 参数值 + * @param converterExp 翻译注解 + * @param separator 分隔符 + * @return 解析后值 + */ + public static String convertByExp(String propertyValue, String converterExp, String separator) { + StringBuilder propertyString = new StringBuilder(); + String[] convertSource = converterExp.split(StringUtils.SEPARATOR); + for (String item : convertSource) { + String[] itemArray = item.split("="); + if (StringUtils.containsAny(propertyValue, separator)) { + for (String value : propertyValue.split(separator)) { + if (itemArray[0].equals(value)) { + propertyString.append(itemArray[1] + separator); + break; + } + } + } else { + if (itemArray[0].equals(propertyValue)) { + return itemArray[1]; + } + } + } + return StringUtils.stripEnd(propertyString.toString(), separator); + } + + /** + * 反向解析值 男=0,女=1,未知=2 + * + * @param propertyValue 参数值 + * @param converterExp 翻译注解 + * @param separator 分隔符 + * @return 解析后值 + */ + public static String reverseByExp(String propertyValue, String converterExp, String separator) { + StringBuilder propertyString = new StringBuilder(); + String[] convertSource = converterExp.split(StringUtils.SEPARATOR); + for (String item : convertSource) { + String[] itemArray = item.split("="); + if (StringUtils.containsAny(propertyValue, separator)) { + for (String value : propertyValue.split(separator)) { + if (itemArray[1].equals(value)) { + propertyString.append(itemArray[0] + separator); + break; + } + } + } else { + if (itemArray[1].equals(propertyValue)) { + return itemArray[0]; + } + } + } + return StringUtils.stripEnd(propertyString.toString(), separator); + } + + /** + * 编码文件名 + */ + public static String encodingFilename(String filename) { + return IdUtil.fastSimpleUUID() + "_" + filename + ".xlsx"; + } + +} diff --git a/ruoyi-common/ruoyi-common-idempotent/pom.xml b/ruoyi-common/ruoyi-common-idempotent/pom.xml new file mode 100644 index 0000000..64418b4 --- /dev/null +++ b/ruoyi-common/ruoyi-common-idempotent/pom.xml @@ -0,0 +1,41 @@ + + + + org.dromara + ruoyi-common + ${revision} + + 4.0.0 + + ruoyi-common-idempotent + + + ruoyi-common-idempotent 幂等功能 + + + + + org.dromara + ruoyi-common-json + + + + org.dromara + ruoyi-common-redis + + + + cn.hutool + hutool-crypto + + + + cn.dev33 + sa-token-core + + + + + diff --git a/ruoyi-common/ruoyi-common-idempotent/src/main/java/org/dromara/common/idempotent/annotation/RepeatSubmit.java b/ruoyi-common/ruoyi-common-idempotent/src/main/java/org/dromara/common/idempotent/annotation/RepeatSubmit.java new file mode 100644 index 0000000..42ae802 --- /dev/null +++ b/ruoyi-common/ruoyi-common-idempotent/src/main/java/org/dromara/common/idempotent/annotation/RepeatSubmit.java @@ -0,0 +1,29 @@ +package org.dromara.common.idempotent.annotation; + +import java.lang.annotation.*; +import java.util.concurrent.TimeUnit; + +/** + * 自定义注解防止表单重复提交 + * + * @author Lion Li + */ +@Inherited +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface RepeatSubmit { + + /** + * 间隔时间(ms),小于此时间视为重复提交 + */ + int interval() default 5000; + + TimeUnit timeUnit() default TimeUnit.MILLISECONDS; + + /** + * 提示消息 支持国际化 格式为 {code} + */ + String message() default "{repeat.submit.message}"; + +} diff --git a/ruoyi-common/ruoyi-common-idempotent/src/main/java/org/dromara/common/idempotent/aspectj/RepeatSubmitAspect.java b/ruoyi-common/ruoyi-common-idempotent/src/main/java/org/dromara/common/idempotent/aspectj/RepeatSubmitAspect.java new file mode 100644 index 0000000..fc53b1b --- /dev/null +++ b/ruoyi-common/ruoyi-common-idempotent/src/main/java/org/dromara/common/idempotent/aspectj/RepeatSubmitAspect.java @@ -0,0 +1,146 @@ +package org.dromara.common.idempotent.aspectj; + +import cn.dev33.satoken.SaManager; +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.crypto.SecureUtil; +import org.dromara.common.core.constant.GlobalConstants; +import org.dromara.common.core.domain.R; +import org.dromara.common.core.exception.ServiceException; +import org.dromara.common.core.utils.MessageUtils; +import org.dromara.common.core.utils.ServletUtils; +import org.dromara.common.core.utils.StringUtils; +import org.dromara.common.idempotent.annotation.RepeatSubmit; +import org.dromara.common.json.utils.JsonUtils; +import org.dromara.common.redis.utils.RedisUtils; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.AfterReturning; +import org.aspectj.lang.annotation.AfterThrowing; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.springframework.validation.BindingResult; +import org.springframework.web.multipart.MultipartFile; + +import java.time.Duration; +import java.util.Collection; +import java.util.Map; +import java.util.StringJoiner; + +/** + * 防止重复提交(参考美团GTIS防重系统) + * + * @author Lion Li + */ +@Aspect +public class RepeatSubmitAspect { + + private static final ThreadLocal KEY_CACHE = new ThreadLocal<>(); + + @Before("@annotation(repeatSubmit)") + public void doBefore(JoinPoint point, RepeatSubmit repeatSubmit) throws Throwable { + // 如果注解不为0 则使用注解数值 + long interval = repeatSubmit.timeUnit().toMillis(repeatSubmit.interval()); + + if (interval < 1000) { + throw new ServiceException("重复提交间隔时间不能小于'1'秒"); + } + HttpServletRequest request = ServletUtils.getRequest(); + String nowParams = argsArrayToString(point.getArgs()); + + // 请求地址(作为存放cache的key值) + String url = request.getRequestURI(); + + // 唯一值(没有消息头则使用请求地址) + String submitKey = StringUtils.trimToEmpty(request.getHeader(SaManager.getConfig().getTokenName())); + + submitKey = SecureUtil.md5(submitKey + ":" + nowParams); + // 唯一标识(指定key + url + 消息头) + String cacheRepeatKey = GlobalConstants.REPEAT_SUBMIT_KEY + url + submitKey; + if (RedisUtils.setObjectIfAbsent(cacheRepeatKey, "", Duration.ofMillis(interval))) { + KEY_CACHE.set(cacheRepeatKey); + } else { + String message = repeatSubmit.message(); + if (StringUtils.startsWith(message, "{") && StringUtils.endsWith(message, "}")) { + message = MessageUtils.message(StringUtils.substring(message, 1, message.length() - 1)); + } + throw new ServiceException(message); + } + } + + /** + * 处理完请求后执行 + * + * @param joinPoint 切点 + */ + @AfterReturning(pointcut = "@annotation(repeatSubmit)", returning = "jsonResult") + public void doAfterReturning(JoinPoint joinPoint, RepeatSubmit repeatSubmit, Object jsonResult) { + if (jsonResult instanceof R r) { + try { + // 成功则不删除redis数据 保证在有效时间内无法重复提交 + if (r.getCode() == R.SUCCESS) { + return; + } + RedisUtils.deleteObject(KEY_CACHE.get()); + } finally { + KEY_CACHE.remove(); + } + } + } + + /** + * 拦截异常操作 + * + * @param joinPoint 切点 + * @param e 异常 + */ + @AfterThrowing(value = "@annotation(repeatSubmit)", throwing = "e") + public void doAfterThrowing(JoinPoint joinPoint, RepeatSubmit repeatSubmit, Exception e) { + RedisUtils.deleteObject(KEY_CACHE.get()); + KEY_CACHE.remove(); + } + + /** + * 参数拼装 + */ + private String argsArrayToString(Object[] paramsArray) { + StringJoiner params = new StringJoiner(" "); + if (ArrayUtil.isEmpty(paramsArray)) { + return params.toString(); + } + for (Object o : paramsArray) { + if (ObjectUtil.isNotNull(o) && !isFilterObject(o)) { + params.add(JsonUtils.toJsonString(o)); + } + } + return params.toString(); + } + + /** + * 判断是否需要过滤的对象。 + * + * @param o 对象信息。 + * @return 如果是需要过滤的对象,则返回true;否则返回false。 + */ + @SuppressWarnings("rawtypes") + public boolean isFilterObject(final Object o) { + Class clazz = o.getClass(); + if (clazz.isArray()) { + return clazz.getComponentType().isAssignableFrom(MultipartFile.class); + } else if (Collection.class.isAssignableFrom(clazz)) { + Collection collection = (Collection) o; + for (Object value : collection) { + return value instanceof MultipartFile; + } + } else if (Map.class.isAssignableFrom(clazz)) { + Map map = (Map) o; + for (Object value : map.values()) { + return value instanceof MultipartFile; + } + } + return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse + || o instanceof BindingResult; + } + +} diff --git a/ruoyi-common/ruoyi-common-idempotent/src/main/java/org/dromara/common/idempotent/config/IdempotentAutoConfiguration.java b/ruoyi-common/ruoyi-common-idempotent/src/main/java/org/dromara/common/idempotent/config/IdempotentAutoConfiguration.java new file mode 100644 index 0000000..e8b785c --- /dev/null +++ b/ruoyi-common/ruoyi-common-idempotent/src/main/java/org/dromara/common/idempotent/config/IdempotentAutoConfiguration.java @@ -0,0 +1,21 @@ +package org.dromara.common.idempotent.config; + +import org.dromara.common.idempotent.aspectj.RepeatSubmitAspect; +import org.dromara.common.redis.config.RedisConfiguration; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.context.annotation.Bean; + +/** + * 幂等功能配置 + * + * @author Lion Li + */ +@AutoConfiguration(after = RedisConfiguration.class) +public class IdempotentAutoConfiguration { + + @Bean + public RepeatSubmitAspect repeatSubmitAspect() { + return new RepeatSubmitAspect(); + } + +} diff --git a/ruoyi-common/ruoyi-common-job/pom.xml b/ruoyi-common/ruoyi-common-job/pom.xml new file mode 100644 index 0000000..3f5e630 --- /dev/null +++ b/ruoyi-common/ruoyi-common-job/pom.xml @@ -0,0 +1,64 @@ + + + + org.dromara + ruoyi-common + ${revision} + + 4.0.0 + + ruoyi-common-job + + + ruoyi-common-job 定时任务 + + + + + + org.springframework.boot + spring-boot-autoconfigure + + + + + org.springframework.cloud + spring-cloud-commons + + + + + tech.powerjob + powerjob-worker + + + powerjob-remote-impl-akka + tech.powerjob + + + + + tech.powerjob + powerjob-official-processors + + + + + com.xuxueli + xxl-job-core + + + + org.projectlombok + lombok + + + + org.dromara + ruoyi-common-core + + + + diff --git a/ruoyi-common/ruoyi-common-job/src/main/java/org/dromara/common/job/config/PowerJobConfig.java b/ruoyi-common/ruoyi-common-job/src/main/java/org/dromara/common/job/config/PowerJobConfig.java new file mode 100644 index 0000000..b451fd1 --- /dev/null +++ b/ruoyi-common/ruoyi-common-job/src/main/java/org/dromara/common/job/config/PowerJobConfig.java @@ -0,0 +1,108 @@ +package org.dromara.common.job.config; + +import cn.hutool.core.collection.CollUtil; +import org.dromara.common.core.utils.StreamUtils; +import org.dromara.common.core.utils.StringUtils; +import org.dromara.common.job.config.properties.PowerJobProperties; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.discovery.DiscoveryClient; +import org.springframework.context.annotation.Bean; +import tech.powerjob.common.utils.CommonUtils; +import tech.powerjob.common.utils.NetUtils; +import tech.powerjob.worker.PowerJobSpringWorker; +import tech.powerjob.worker.common.PowerJobWorkerConfig; + +import java.util.Arrays; +import java.util.List; + +/** + * Autoconfiguration class for PowerJob-worker. + * + * @author songyinyin + * @since 2020/7/26 16:37 + */ +@AutoConfiguration +@EnableConfigurationProperties(PowerJobProperties.class) +//@ConditionalOnProperty(prefix = "powerjob.worker", name = "enabled", havingValue = "true", matchIfMissing = true) +@ConditionalOnProperty(prefix = "powerjob.worker", name = "enabled", havingValue = "true") +public class PowerJobConfig{ + + @Bean + public PowerJobSpringWorker initPowerJob(PowerJobProperties properties, DiscoveryClient discoveryClient) { + + PowerJobProperties.Worker worker = properties.getWorker(); + + /* + * Address of PowerJob-server node(s). Do not mistake for ActorSystem port. Do not add + * any prefix, i.e. http://. + */ + List serverAddress; + if (StringUtils.isNotBlank(worker.getServerName())) { + List instances = discoveryClient.getInstances(worker.getServerName()); + if (CollUtil.isEmpty(instances)) { + throw new RuntimeException("调度中心不存在!"); + } + serverAddress = StreamUtils.toList(instances, instance -> + String.format("%s:%s", instance.getHost(), instance.getPort())); + } else { + CommonUtils.requireNonNull(worker.getServerAddress(), "serverAddress can't be empty! " + + "if you don't want to enable powerjob, please config program arguments: powerjob.worker.enabled=false"); + serverAddress = Arrays.asList(worker.getServerAddress().split(",")); + } + /* + * Create OhMyConfig object for setting properties. + */ + PowerJobWorkerConfig config = new PowerJobWorkerConfig(); + /* + * Configuration of worker port. Random port is enabled when port is set with non-positive number. + */ + if (worker.getPort() != null) { + config.setPort(worker.getPort()); + } else { + int port = worker.getAkkaPort(); + if (port <= 0) { + port = NetUtils.getRandomPort(); + } + config.setPort(port); + } + /* + * appName, name of the application. Applications should be registered in advance to prevent + * error. This property should be the same with what you entered for appName when getting + * registered. + */ + config.setAppName(worker.getAppName()); + config.setServerAddress(serverAddress); + config.setProtocol(worker.getProtocol()); + /* + * For non-Map/MapReduce tasks, {@code memory} is recommended for speeding up calculation. + * Map/MapReduce tasks may produce batches of subtasks, which could lead to OutOfMemory + * exception or error, {@code disk} should be applied. + */ + config.setStoreStrategy(worker.getStoreStrategy()); + /* + * When enabledTestMode is set as true, PowerJob-worker no longer connects to PowerJob-server + * or validate appName. + */ + config.setAllowLazyConnectServer(worker.isAllowLazyConnectServer()); + /* + * Max length of appended workflow context . Appended workflow context value that is longer than the value will be ignored. + */ + config.setMaxAppendedWfContextLength(worker.getMaxAppendedWfContextLength()); + + config.setTag(worker.getTag()); + + config.setMaxHeavyweightTaskNum(worker.getMaxHeavyweightTaskNum()); + + config.setMaxLightweightTaskNum(worker.getMaxLightweightTaskNum()); + + config.setHealthReportInterval(worker.getHealthReportInterval()); + /* + * Create PowerJobSpringWorker object and set properties. + */ + return new PowerJobSpringWorker(config); + } + +} diff --git a/ruoyi-common/ruoyi-common-job/src/main/java/org/dromara/common/job/config/XxlJobConfig.java b/ruoyi-common/ruoyi-common-job/src/main/java/org/dromara/common/job/config/XxlJobConfig.java new file mode 100644 index 0000000..9d59951 --- /dev/null +++ b/ruoyi-common/ruoyi-common-job/src/main/java/org/dromara/common/job/config/XxlJobConfig.java @@ -0,0 +1,61 @@ +package org.dromara.common.job.config; + +import cn.hutool.core.collection.CollUtil; +import com.xxl.job.core.executor.impl.XxlJobSpringExecutor; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.dromara.common.core.utils.StreamUtils; +import org.dromara.common.job.config.properties.XxlJobProperties; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.discovery.DiscoveryClient; +import org.springframework.context.annotation.Bean; + +import java.util.List; + +/** + * xxl-job config + * + * @author Lion Li + */ +@Slf4j +@AutoConfiguration +@EnableConfigurationProperties(XxlJobProperties.class) +@AllArgsConstructor +@ConditionalOnProperty(prefix = "xxl.job", name = "enabled", havingValue = "true") +public class XxlJobConfig { + + private final XxlJobProperties xxlJobProperties; + + private final DiscoveryClient discoveryClient; + + @Bean + public XxlJobSpringExecutor xxlJobExecutor() { + log.info(">>>>>>>>>>> xxl-job config init."); + XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor(); + if (StringUtils.isNotBlank(xxlJobProperties.getAdminAppname())) { + List instances = discoveryClient.getInstances(xxlJobProperties.getAdminAppname()); + if (CollUtil.isEmpty(instances)) { + throw new RuntimeException("调度中心不存在!"); + } + String serverList = StreamUtils.join(instances, instance -> + String.format("http://%s:%s", instance.getHost(), instance.getPort())); + xxlJobSpringExecutor.setAdminAddresses(serverList); + } else { + xxlJobSpringExecutor.setAdminAddresses(xxlJobProperties.getAdminAddresses()); + } + xxlJobSpringExecutor.setAccessToken(xxlJobProperties.getAccessToken()); + XxlJobProperties.Executor executor = xxlJobProperties.getExecutor(); + xxlJobSpringExecutor.setAppname(executor.getAppname()); + xxlJobSpringExecutor.setAddress(executor.getAddress()); + xxlJobSpringExecutor.setIp(executor.getIp()); + xxlJobSpringExecutor.setPort(executor.getPort()); + xxlJobSpringExecutor.setLogPath(executor.getLogPath()); + xxlJobSpringExecutor.setLogRetentionDays(executor.getLogRetentionDays()); + return xxlJobSpringExecutor; + } + +} diff --git a/ruoyi-common/ruoyi-common-job/src/main/java/org/dromara/common/job/config/properties/PowerJobProperties.java b/ruoyi-common/ruoyi-common-job/src/main/java/org/dromara/common/job/config/properties/PowerJobProperties.java new file mode 100644 index 0000000..df5f285 --- /dev/null +++ b/ruoyi-common/ruoyi-common-job/src/main/java/org/dromara/common/job/config/properties/PowerJobProperties.java @@ -0,0 +1,109 @@ +package org.dromara.common.job.config.properties; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import tech.powerjob.common.RemoteConstant; +import tech.powerjob.common.enums.Protocol; +import tech.powerjob.worker.common.constants.StoreStrategy; +import tech.powerjob.worker.core.processor.ProcessResult; +import tech.powerjob.worker.core.processor.WorkflowContext; + +/** + * PowerJob properties configuration class. + * + * @author songyinyin + * @since 2020/7/26 16:37 + */ +@ConfigurationProperties(prefix = "powerjob") +public class PowerJobProperties { + + private final Worker worker = new Worker(); + + public Worker getWorker() { + return worker; + } + + /** + * Powerjob worker configuration properties. + */ + @Setter + @Getter + public static class Worker { + + /** + * Whether to enable PowerJob Worker + */ + private boolean enabled = true; + + /** + * Name of application, String type. Total length of this property should be no more than 255 + * characters. This is one of the required properties when registering a new application. This + * property should be assigned with the same value as what you entered for the appName. + */ + private String appName; + /** + * Akka port of Powerjob-worker, optional value. Default value of this property is 27777. + * If multiple PowerJob-worker nodes were deployed, different, unique ports should be assigned. + * Deprecated, please use 'port' + */ + @Deprecated + private int akkaPort = RemoteConstant.DEFAULT_WORKER_PORT; + /** + * port + */ + private Integer port; + /** + * Address(es) of Powerjob-server node(s). Ip:port or domain. + * Example of single Powerjob-server node: + *

+ * 127.0.0.1:7700 + *

+ * Example of Powerjob-server cluster: + *

+ * 192.168.0.10:7700,192.168.0.11:7700,192.168.0.12:7700 + *

+ */ + private String serverAddress; + + private String serverName; + /** + * Protocol for communication between WORKER and server + */ + private Protocol protocol = Protocol.AKKA; + /** + * Local store strategy for H2 database. {@code disk} or {@code memory}. + */ + private StoreStrategy storeStrategy = StoreStrategy.DISK; + /** + * Max length of response result. Result that is longer than the value will be truncated. + * {@link ProcessResult} max length for #msg + */ + private int maxResultLength = 8192; + /** + * If allowLazyConnectServer is set as true, PowerJob worker allows launching without a direct connection to the server. + * allowLazyConnectServer is used for conditions that your have no powerjob-server in your develop env so you can't startup the application + */ + private boolean allowLazyConnectServer = false; + /** + * Max length of appended workflow context value length. Appended workflow context value that is longer than the value will be ignored. + * {@link WorkflowContext} max length for #appendedContextData + */ + private int maxAppendedWfContextLength = 8192; + + private String tag; + /** + * Max numbers of LightTaskTacker + */ + private Integer maxLightweightTaskNum = 1024; + /** + * Max numbers of HeavyTaskTacker + */ + private Integer maxHeavyweightTaskNum = 64; + /** + * Interval(s) of worker health report + */ + private Integer healthReportInterval = 10; + + } +} diff --git a/ruoyi-common/ruoyi-common-job/src/main/java/org/dromara/common/job/config/properties/XxlJobProperties.java b/ruoyi-common/ruoyi-common-job/src/main/java/org/dromara/common/job/config/properties/XxlJobProperties.java new file mode 100644 index 0000000..48769d5 --- /dev/null +++ b/ruoyi-common/ruoyi-common-job/src/main/java/org/dromara/common/job/config/properties/XxlJobProperties.java @@ -0,0 +1,42 @@ +package org.dromara.common.job.config.properties; + +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * xxljob配置类 + * + * @author Lion Li + */ +@Data +@ConfigurationProperties(prefix = "xxl.job") +public class XxlJobProperties { + + private Boolean enabled; + + private String adminAddresses; + + private String adminAppname; + + private String accessToken; + + private Executor executor; + + @Data + @NoArgsConstructor + public static class Executor { + + private String appname; + + private String address; + + private String ip; + + private int port; + + private String logPath; + + private int logRetentionDays; + } +} diff --git a/ruoyi-common/ruoyi-common-job/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/ruoyi-common/ruoyi-common-job/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..b1b1441 --- /dev/null +++ b/ruoyi-common/ruoyi-common-job/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1,2 @@ +org.dromara.common.job.config.PowerJobConfig +org.dromara.common.job.config.XxlJobConfig diff --git a/ruoyi-common/ruoyi-common-json/pom.xml b/ruoyi-common/ruoyi-common-json/pom.xml new file mode 100644 index 0000000..870df5c --- /dev/null +++ b/ruoyi-common/ruoyi-common-json/pom.xml @@ -0,0 +1,37 @@ + + + + org.dromara + ruoyi-common + ${revision} + + 4.0.0 + + ruoyi-common-json + + + ruoyi-common-json 序列化模块 + + + + + org.dromara + ruoyi-common-core + + + + + com.fasterxml.jackson.core + jackson-databind + + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + + + + + diff --git a/ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/config/JacksonConfig.java b/ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/config/JacksonConfig.java new file mode 100644 index 0000000..8f5a45d --- /dev/null +++ b/ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/config/JacksonConfig.java @@ -0,0 +1,47 @@ +package org.dromara.common.json.config; + +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; +import org.dromara.common.json.handler.BigNumberSerializer; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; +import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; +import org.springframework.context.annotation.Bean; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.TimeZone; + +/** + * jackson 配置 + * + * @author Lion Li + */ +@Slf4j +@AutoConfiguration(before = JacksonAutoConfiguration.class) +public class JacksonConfig { + + @Bean + public Jackson2ObjectMapperBuilderCustomizer customizer() { + return builder -> { + // 全局配置序列化返回 JSON 处理 + JavaTimeModule javaTimeModule = new JavaTimeModule(); + javaTimeModule.addSerializer(Long.class, BigNumberSerializer.INSTANCE); + javaTimeModule.addSerializer(Long.TYPE, BigNumberSerializer.INSTANCE); + javaTimeModule.addSerializer(BigInteger.class, BigNumberSerializer.INSTANCE); + javaTimeModule.addSerializer(BigDecimal.class, ToStringSerializer.instance); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(formatter)); + javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(formatter)); + builder.modules(javaTimeModule); + builder.timeZone(TimeZone.getDefault()); + log.info("初始化 jackson 配置"); + }; + } + +} diff --git a/ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/handler/BigNumberSerializer.java b/ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/handler/BigNumberSerializer.java new file mode 100644 index 0000000..f2a7c2d --- /dev/null +++ b/ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/handler/BigNumberSerializer.java @@ -0,0 +1,42 @@ +package org.dromara.common.json.handler; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.annotation.JacksonStdImpl; +import com.fasterxml.jackson.databind.ser.std.NumberSerializer; + +import java.io.IOException; + +/** + * 超出 JS 最大最小值 处理 + * + * @author Lion Li + */ +@JacksonStdImpl +public class BigNumberSerializer extends NumberSerializer { + + /** + * 根据 JS Number.MAX_SAFE_INTEGER 与 Number.MIN_SAFE_INTEGER 得来 + */ + private static final long MAX_SAFE_INTEGER = 9007199254740991L; + private static final long MIN_SAFE_INTEGER = -9007199254740991L; + + /** + * 提供实例 + */ + public static final BigNumberSerializer INSTANCE = new BigNumberSerializer(Number.class); + + public BigNumberSerializer(Class rawType) { + super(rawType); + } + + @Override + public void serialize(Number value, JsonGenerator gen, SerializerProvider provider) throws IOException { + // 超出范围 序列化位字符串 + if (value.longValue() > MIN_SAFE_INTEGER && value.longValue() < MAX_SAFE_INTEGER) { + super.serialize(value, gen, provider); + } else { + gen.writeString(value.toString()); + } + } +} diff --git a/ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/utils/JsonUtils.java b/ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/utils/JsonUtils.java new file mode 100644 index 0000000..42af8da --- /dev/null +++ b/ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/utils/JsonUtils.java @@ -0,0 +1,113 @@ +package org.dromara.common.json.utils; + +import cn.hutool.core.lang.Dict; +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.ObjectUtil; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.exc.MismatchedInputException; +import org.dromara.common.core.utils.SpringUtils; +import org.dromara.common.core.utils.StringUtils; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * JSON 工具类 + * + * @author 芋道源码 + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class JsonUtils { + + private static final ObjectMapper OBJECT_MAPPER = SpringUtils.getBean(ObjectMapper.class); + + public static ObjectMapper getObjectMapper() { + return OBJECT_MAPPER; + } + + public static String toJsonString(Object object) { + if (ObjectUtil.isNull(object)) { + return null; + } + try { + return OBJECT_MAPPER.writeValueAsString(object); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } + + public static T parseObject(String text, Class clazz) { + if (StringUtils.isEmpty(text)) { + return null; + } + try { + return OBJECT_MAPPER.readValue(text, clazz); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public static T parseObject(byte[] bytes, Class clazz) { + if (ArrayUtil.isEmpty(bytes)) { + return null; + } + try { + return OBJECT_MAPPER.readValue(bytes, clazz); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public static T parseObject(String text, TypeReference typeReference) { + if (StringUtils.isBlank(text)) { + return null; + } + try { + return OBJECT_MAPPER.readValue(text, typeReference); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public static Dict parseMap(String text) { + if (StringUtils.isBlank(text)) { + return null; + } + try { + return OBJECT_MAPPER.readValue(text, OBJECT_MAPPER.getTypeFactory().constructType(Dict.class)); + } catch (MismatchedInputException e) { + // 类型不匹配说明不是json + return null; + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public static List parseArrayMap(String text) { + if (StringUtils.isBlank(text)) { + return null; + } + try { + return OBJECT_MAPPER.readValue(text, OBJECT_MAPPER.getTypeFactory().constructCollectionType(List.class, Dict.class)); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public static List parseArray(String text, Class clazz) { + if (StringUtils.isEmpty(text)) { + return new ArrayList<>(); + } + try { + return OBJECT_MAPPER.readValue(text, OBJECT_MAPPER.getTypeFactory().constructCollectionType(List.class, clazz)); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + +} diff --git a/ruoyi-common/ruoyi-common-loadbalancer/pom.xml b/ruoyi-common/ruoyi-common-loadbalancer/pom.xml new file mode 100644 index 0000000..1509a44 --- /dev/null +++ b/ruoyi-common/ruoyi-common-loadbalancer/pom.xml @@ -0,0 +1,40 @@ + + + + org.dromara + ruoyi-common + ${revision} + + 4.0.0 + + ruoyi-common-loadbalancer + + + ruoyi-common-loadbalancer 自定义负载均衡(多团队开发使用) + + + + + org.springframework.cloud + spring-cloud-starter-loadbalancer + + + org.apache.dubbo + dubbo-spring-boot-starter + provided + + + + org.projectlombok + lombok + + + + org.dromara + ruoyi-common-core + + + + diff --git a/ruoyi-common/ruoyi-common-loadbalancer/src/main/java/org/dromara/common/loadbalance/config/CustomEnvironmentPostProcessor.java b/ruoyi-common/ruoyi-common-loadbalancer/src/main/java/org/dromara/common/loadbalance/config/CustomEnvironmentPostProcessor.java new file mode 100644 index 0000000..7811238 --- /dev/null +++ b/ruoyi-common/ruoyi-common-loadbalancer/src/main/java/org/dromara/common/loadbalance/config/CustomEnvironmentPostProcessor.java @@ -0,0 +1,25 @@ +package org.dromara.common.loadbalance.config; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.env.EnvironmentPostProcessor; +import org.springframework.core.Ordered; +import org.springframework.core.env.ConfigurableEnvironment; + +/** + * dubbo自定义负载均衡配置注入 + * + * @author Lion Li + */ +public class CustomEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered { + + @Override + public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { + System.setProperty("dubbo.consumer.loadbalance", "customDubboLoadBalancer"); + } + + @Override + public int getOrder() { + return Ordered.HIGHEST_PRECEDENCE; + } + +} diff --git a/ruoyi-common/ruoyi-common-loadbalancer/src/main/java/org/dromara/common/loadbalance/core/CustomDubboLoadBalancer.java b/ruoyi-common/ruoyi-common-loadbalancer/src/main/java/org/dromara/common/loadbalance/core/CustomDubboLoadBalancer.java new file mode 100644 index 0000000..1d3337f --- /dev/null +++ b/ruoyi-common/ruoyi-common-loadbalancer/src/main/java/org/dromara/common/loadbalance/core/CustomDubboLoadBalancer.java @@ -0,0 +1,30 @@ +package org.dromara.common.loadbalance.core; + +import cn.hutool.core.net.NetUtil; +import lombok.extern.slf4j.Slf4j; +import org.apache.dubbo.common.URL; +import org.apache.dubbo.rpc.Invocation; +import org.apache.dubbo.rpc.Invoker; +import org.apache.dubbo.rpc.cluster.loadbalance.AbstractLoadBalance; + +import java.util.List; +import java.util.concurrent.ThreadLocalRandom; + +/** + * 自定义 Dubbo 负载均衡算法 + * + * @author Lion Li + */ +@Slf4j +public class CustomDubboLoadBalancer extends AbstractLoadBalance { + + @Override + protected Invoker doSelect(List> invokers, URL url, Invocation invocation) { + for (Invoker invoker : invokers) { + if (NetUtil.localIpv4s().contains(invoker.getUrl().getHost())) { + return invoker; + } + } + return invokers.get(ThreadLocalRandom.current().nextInt(invokers.size())); + } +} diff --git a/ruoyi-common/ruoyi-common-loadbalancer/src/main/java/org/dromara/common/loadbalance/core/CustomSpringCloudLoadBalancer.java b/ruoyi-common/ruoyi-common-loadbalancer/src/main/java/org/dromara/common/loadbalance/core/CustomSpringCloudLoadBalancer.java new file mode 100644 index 0000000..4e3d16d --- /dev/null +++ b/ruoyi-common/ruoyi-common-loadbalancer/src/main/java/org/dromara/common/loadbalance/core/CustomSpringCloudLoadBalancer.java @@ -0,0 +1,64 @@ +package org.dromara.common.loadbalance.core; + +import cn.hutool.core.net.NetUtil; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.loadbalancer.DefaultResponse; +import org.springframework.cloud.client.loadbalancer.EmptyResponse; +import org.springframework.cloud.client.loadbalancer.Request; +import org.springframework.cloud.client.loadbalancer.Response; +import org.springframework.cloud.loadbalancer.core.NoopServiceInstanceListSupplier; +import org.springframework.cloud.loadbalancer.core.ReactorServiceInstanceLoadBalancer; +import org.springframework.cloud.loadbalancer.core.SelectedInstanceCallback; +import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier; +import reactor.core.publisher.Mono; + +import java.util.List; +import java.util.concurrent.ThreadLocalRandom; + +/** + * 自定义 SpringCloud 负载均衡算法 + * + * @author Lion Li + */ +@Slf4j +@AllArgsConstructor +public class CustomSpringCloudLoadBalancer implements ReactorServiceInstanceLoadBalancer { + + private final String serviceId; + + private final ObjectProvider serviceInstanceListSupplierProvider; + + @Override + public Mono> choose(Request request) { + ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider.getIfAvailable(NoopServiceInstanceListSupplier::new); + return supplier.get(request).next().map(serviceInstances -> processInstanceResponse(supplier, serviceInstances)); + } + + private Response processInstanceResponse(ServiceInstanceListSupplier supplier, + List serviceInstances) { + Response serviceInstanceResponse = getInstanceResponse(serviceInstances); + if (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) { + ((SelectedInstanceCallback) supplier).selectedServiceInstance(serviceInstanceResponse.getServer()); + } + return serviceInstanceResponse; + } + + private Response getInstanceResponse(List instances) { + if (instances.isEmpty()) { + if (log.isWarnEnabled()) { + log.warn("No servers available for service: " + serviceId); + } + return new EmptyResponse(); + } + for (ServiceInstance instance : instances) { + if (NetUtil.localIpv4s().contains(instance.getHost())) { + return new DefaultResponse(instance); + } + } + return new DefaultResponse(instances.get(ThreadLocalRandom.current().nextInt(instances.size()))); + } + +} diff --git a/ruoyi-common/ruoyi-common-loadbalancer/src/main/resources/META-INF/dubbo/org.apache.dubbo.rpc.cluster.LoadBalance b/ruoyi-common/ruoyi-common-loadbalancer/src/main/resources/META-INF/dubbo/org.apache.dubbo.rpc.cluster.LoadBalance new file mode 100644 index 0000000..f40caf6 --- /dev/null +++ b/ruoyi-common/ruoyi-common-loadbalancer/src/main/resources/META-INF/dubbo/org.apache.dubbo.rpc.cluster.LoadBalance @@ -0,0 +1 @@ +customDubboLoadBalancer=org.dromara.common.loadbalance.core.CustomDubboLoadBalancer diff --git a/ruoyi-common/ruoyi-common-loadbalancer/src/main/resources/META-INF/spring.factories b/ruoyi-common/ruoyi-common-loadbalancer/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000..18edf43 --- /dev/null +++ b/ruoyi-common/ruoyi-common-loadbalancer/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.env.EnvironmentPostProcessor=\ + org.dromara.common.loadbalance.config.CustomEnvironmentPostProcessor diff --git a/ruoyi-common/ruoyi-common-log/pom.xml b/ruoyi-common/ruoyi-common-log/pom.xml new file mode 100644 index 0000000..cbda66f --- /dev/null +++ b/ruoyi-common/ruoyi-common-log/pom.xml @@ -0,0 +1,43 @@ + + + + org.dromara + ruoyi-common + ${revision} + + 4.0.0 + + ruoyi-common-log + + + ruoyi-common-log 日志记录 + + + + + + org.dromara + ruoyi-common-satoken + + + + org.dromara + ruoyi-common-json + + + + com.alibaba + transmittable-thread-local + + + + org.apache.dubbo + dubbo-spring-boot-starter + provided + + + + + diff --git a/ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/annotation/Log.java b/ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/annotation/Log.java new file mode 100644 index 0000000..2dced97 --- /dev/null +++ b/ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/annotation/Log.java @@ -0,0 +1,48 @@ +package org.dromara.common.log.annotation; + +import org.dromara.common.log.enums.BusinessType; +import org.dromara.common.log.enums.OperatorType; + +import java.lang.annotation.*; + +/** + * 自定义操作日志记录注解 + * + * @author ruoyi + */ +@Target({ElementType.PARAMETER, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface Log { + /** + * 模块 + */ + String title() default ""; + + /** + * 功能 + */ + BusinessType businessType() default BusinessType.OTHER; + + /** + * 操作人类别 + */ + OperatorType operatorType() default OperatorType.MANAGE; + + /** + * 是否保存请求的参数 + */ + boolean isSaveRequestData() default true; + + /** + * 是否保存响应的参数 + */ + boolean isSaveResponseData() default true; + + + /** + * 排除指定的请求参数 + */ + String[] excludeParamNames() default {}; + +} diff --git a/ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/aspect/LogAspect.java b/ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/aspect/LogAspect.java new file mode 100644 index 0000000..f2356e9 --- /dev/null +++ b/ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/aspect/LogAspect.java @@ -0,0 +1,234 @@ +package org.dromara.common.log.aspect; + +import cn.hutool.core.lang.Dict; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.ObjectUtil; +import com.alibaba.ttl.TransmittableThreadLocal; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.time.StopWatch; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.AfterReturning; +import org.aspectj.lang.annotation.AfterThrowing; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.dromara.common.core.utils.ServletUtils; +import org.dromara.common.core.utils.SpringUtils; +import org.dromara.common.core.utils.StringUtils; +import org.dromara.common.json.utils.JsonUtils; +import org.dromara.common.log.annotation.Log; +import org.dromara.common.log.enums.BusinessStatus; +import org.dromara.common.log.event.OperLogEvent; +import org.dromara.common.satoken.utils.LoginHelper; +import org.dromara.common.satoken.utils.MpLoginHelper; +import org.dromara.map.api.model.LoginMpUser; +import org.dromara.system.api.model.LoginUser; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.http.HttpMethod; +import org.springframework.validation.BindingResult; +import org.springframework.web.multipart.MultipartFile; + +import java.util.Collection; +import java.util.Map; +import java.util.StringJoiner; + +/** + * 操作日志记录处理 + * + * @author Lion Li + */ +@Slf4j +@Aspect +@AutoConfiguration +public class LogAspect { + + /** + * 排除敏感属性字段 + */ + public static final String[] EXCLUDE_PROPERTIES = { "password", "oldPassword", "newPassword", "confirmPassword" }; + + + /** + * 计算操作消耗时间 + */ + private static final ThreadLocal TIME_THREADLOCAL = new TransmittableThreadLocal<>(); + + /** + * 处理请求前执行 + */ + @Before(value = "@annotation(controllerLog)") + public void boBefore(JoinPoint joinPoint, Log controllerLog) { + StopWatch stopWatch = new StopWatch(); + TIME_THREADLOCAL.set(stopWatch); + stopWatch.start(); + } + + /** + * 处理完请求后执行 + * + * @param joinPoint 切点 + */ + @AfterReturning(pointcut = "@annotation(controllerLog)", returning = "jsonResult") + public void doAfterReturning(JoinPoint joinPoint, Log controllerLog, Object jsonResult) { + handleLog(joinPoint, controllerLog, null, jsonResult); + } + + /** + * 拦截异常操作 + * + * @param joinPoint 切点 + * @param e 异常 + */ + @AfterThrowing(value = "@annotation(controllerLog)", throwing = "e") + public void doAfterThrowing(JoinPoint joinPoint, Log controllerLog, Exception e) { + handleLog(joinPoint, controllerLog, e, null); + } + + protected void handleLog(final JoinPoint joinPoint, Log controllerLog, final Exception e, Object jsonResult) { + try { + + // *========数据库日志=========*// + OperLogEvent operLog = new OperLogEvent(); + operLog.setTenantId(LoginHelper.getTenantId()); + operLog.setStatus(BusinessStatus.SUCCESS.ordinal()); + // 请求的地址 + String ip = ServletUtils.getClientIP(); + operLog.setOperIp(ip); + operLog.setOperUrl(StringUtils.substring(ServletUtils.getRequest().getRequestURI(), 0, 255)); + LoginUser loginUser = LoginHelper.getLoginUser(); + if(loginUser != null){ + operLog.setOperName(loginUser.getUsername()); + operLog.setDeptName(loginUser.getDeptName()); + }else{ + LoginMpUser mpUser = MpLoginHelper.getLoginUser(); + if(mpUser != null){ + operLog.setOperName(mpUser.getPhone()); + operLog.setDeptName(mpUser.getDeptName()); + } + + } + + + if (e != null) { + operLog.setStatus(BusinessStatus.FAIL.ordinal()); + operLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 2000)); + } + // 设置方法名称 + String className = joinPoint.getTarget().getClass().getName(); + String methodName = joinPoint.getSignature().getName(); + operLog.setMethod(className + "." + methodName + "()"); + // 设置请求方式 + operLog.setRequestMethod(ServletUtils.getRequest().getMethod()); + // 处理设置注解上的参数 + getControllerMethodDescription(joinPoint, controllerLog, operLog, jsonResult); + // 设置消耗时间 + StopWatch stopWatch = TIME_THREADLOCAL.get(); + stopWatch.stop(); + operLog.setCostTime(stopWatch.getTime()); + // 发布事件保存数据库 + SpringUtils.context().publishEvent(operLog); + } catch (Exception exp) { + // 记录本地异常日志 + log.error("异常信息:{}", exp.getMessage()); + exp.printStackTrace(); + } finally { + TIME_THREADLOCAL.remove(); + } + } + + /** + * 获取注解中对方法的描述信息 用于Controller层注解 + * + * @param log 日志 + * @param operLog 操作日志 + * @throws Exception + */ + public void getControllerMethodDescription(JoinPoint joinPoint, Log log, OperLogEvent operLog, Object jsonResult) throws Exception { + // 设置action动作 + operLog.setBusinessType(log.businessType().ordinal()); + // 设置标题 + operLog.setTitle(log.title()); + // 设置操作人类别 + operLog.setOperatorType(log.operatorType().ordinal()); + // 是否需要保存request,参数和值 + if (log.isSaveRequestData()) { + // 获取参数的信息,传入到数据库中。 + setRequestValue(joinPoint, operLog, log.excludeParamNames()); + } + // 是否需要保存response,参数和值 + if (log.isSaveResponseData() && ObjectUtil.isNotNull(jsonResult)) { + operLog.setJsonResult(StringUtils.substring(JsonUtils.toJsonString(jsonResult), 0, 2000)); + } + } + + /** + * 获取请求的参数,放到log中 + * + * @param operLog 操作日志 + * @throws Exception 异常 + */ + private void setRequestValue(JoinPoint joinPoint, OperLogEvent operLog, String[] excludeParamNames) throws Exception { + Map paramsMap = ServletUtils.getParamMap(ServletUtils.getRequest()); + String requestMethod = operLog.getRequestMethod(); + if (MapUtil.isEmpty(paramsMap) + && HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod)) { + String params = argsArrayToString(joinPoint.getArgs(), excludeParamNames); + operLog.setOperParam(StringUtils.substring(params, 0, 2000)); + } else { + MapUtil.removeAny(paramsMap, EXCLUDE_PROPERTIES); + MapUtil.removeAny(paramsMap, excludeParamNames); + operLog.setOperParam(StringUtils.substring(JsonUtils.toJsonString(paramsMap), 0, 2000)); + } + } + + /** + * 参数拼装 + */ + private String argsArrayToString(Object[] paramsArray, String[] excludeParamNames) { + StringJoiner params = new StringJoiner(" "); + if (ArrayUtil.isEmpty(paramsArray)) { + return params.toString(); + } + for (Object o : paramsArray) { + if (ObjectUtil.isNotNull(o) && !isFilterObject(o)) { + String str = JsonUtils.toJsonString(o); + Dict dict = JsonUtils.parseMap(str); + if (MapUtil.isNotEmpty(dict)) { + MapUtil.removeAny(dict, EXCLUDE_PROPERTIES); + MapUtil.removeAny(dict, excludeParamNames); + str = JsonUtils.toJsonString(dict); + } + params.add(str); + } + } + return params.toString(); + } + + /** + * 判断是否需要过滤的对象。 + * + * @param o 对象信息。 + * @return 如果是需要过滤的对象,则返回true;否则返回false。 + */ + @SuppressWarnings("rawtypes") + public boolean isFilterObject(final Object o) { + Class clazz = o.getClass(); + if (clazz.isArray()) { + return clazz.getComponentType().isAssignableFrom(MultipartFile.class); + } else if (Collection.class.isAssignableFrom(clazz)) { + Collection collection = (Collection) o; + for (Object value : collection) { + return value instanceof MultipartFile; + } + } else if (Map.class.isAssignableFrom(clazz)) { + Map map = (Map) o; + for (Object value : map.values()) { + return value instanceof MultipartFile; + } + } + return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse + || o instanceof BindingResult; + } +} diff --git a/ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/enums/BusinessStatus.java b/ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/enums/BusinessStatus.java new file mode 100644 index 0000000..d303dc3 --- /dev/null +++ b/ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/enums/BusinessStatus.java @@ -0,0 +1,18 @@ +package org.dromara.common.log.enums; + +/** + * 操作状态 + * + * @author ruoyi + */ +public enum BusinessStatus { + /** + * 成功 + */ + SUCCESS, + + /** + * 失败 + */ + FAIL, +} diff --git a/ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/enums/BusinessType.java b/ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/enums/BusinessType.java new file mode 100644 index 0000000..2305b3e --- /dev/null +++ b/ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/enums/BusinessType.java @@ -0,0 +1,61 @@ +package org.dromara.common.log.enums; + +/** + * 业务操作类型 + * + * @author ruoyi + */ +public enum BusinessType { + /** + * 其它 + */ + OTHER, + + /** + * 新增 + */ + INSERT, + + /** + * 修改 + */ + UPDATE, + + /** + * 删除 + */ + DELETE, + + /** + * 授权 + */ + GRANT, + + /** + * 导出 + */ + EXPORT, + + /** + * 导入 + */ + IMPORT, + + /** + * 强退 + */ + FORCE, + + /** + * 生成代码 + */ + GENCODE, + + /** + * 清空数据 + */ + CLEAN, + + + SELECT, +} diff --git a/ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/enums/OperatorType.java b/ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/enums/OperatorType.java new file mode 100644 index 0000000..de9328b --- /dev/null +++ b/ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/enums/OperatorType.java @@ -0,0 +1,23 @@ +package org.dromara.common.log.enums; + +/** + * 操作人类别 + * + * @author ruoyi + */ +public enum OperatorType { + /** + * 其它 + */ + OTHER, + + /** + * 后台用户 + */ + MANAGE, + + /** + * 手机端用户 + */ + MOBILE +} diff --git a/ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/event/LogEventListener.java b/ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/event/LogEventListener.java new file mode 100644 index 0000000..3697e58 --- /dev/null +++ b/ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/event/LogEventListener.java @@ -0,0 +1,103 @@ +package org.dromara.common.log.event; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.http.useragent.UserAgent; +import cn.hutool.http.useragent.UserAgentUtil; +import jakarta.servlet.http.HttpServletRequest; +import lombok.extern.slf4j.Slf4j; +import org.apache.dubbo.config.annotation.DubboReference; +import org.dromara.common.core.constant.Constants; +import org.dromara.common.core.utils.ServletUtils; +import org.dromara.common.core.utils.StringUtils; +import org.dromara.common.core.utils.ip.AddressUtils; +import org.dromara.common.satoken.utils.LoginHelper; +import org.dromara.system.api.RemoteClientService; +import org.dromara.system.api.RemoteLogService; +import org.dromara.system.api.domain.bo.RemoteLogininforBo; +import org.dromara.system.api.domain.bo.RemoteOperLogBo; +import org.dromara.system.api.domain.vo.RemoteClientVo; +import org.springframework.context.event.EventListener; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; + +/** + * 异步调用日志服务 + * + * @author ruoyi + */ +@Component +@Slf4j +public class LogEventListener { + + @DubboReference + private RemoteLogService remoteLogService; + @DubboReference + private RemoteClientService remoteClientService; + + /** + * 保存系统日志记录 + */ + @Async + @EventListener + public void saveLog(OperLogEvent operLogEvent) { + RemoteOperLogBo sysOperLog = BeanUtil.toBean(operLogEvent, RemoteOperLogBo.class); + remoteLogService.saveLog(sysOperLog); + } + + @Async + @EventListener + public void saveLogininfor(LogininforEvent logininforEvent) { + HttpServletRequest request = logininforEvent.getRequest(); + final UserAgent userAgent = UserAgentUtil.parse(request.getHeader("User-Agent")); + final String ip = ServletUtils.getClientIP(request); + // 客户端信息 + String clientid = request.getHeader(LoginHelper.CLIENT_KEY); + RemoteClientVo clientVo = null; + if (StringUtils.isNotBlank(clientid)) { + clientVo = remoteClientService.queryByClientId(clientid); + } + + String address = AddressUtils.getRealAddressByIP(ip); + StringBuilder s = new StringBuilder(); + s.append(getBlock(ip)); + s.append(address); + s.append(getBlock(logininforEvent.getUsername())); + s.append(getBlock(logininforEvent.getStatus())); + s.append(getBlock(logininforEvent.getMessage())); + // 打印信息到日志 + log.info(s.toString(), logininforEvent.getArgs()); + // 获取客户端操作系统 + String os = userAgent.getOs().getName(); + // 获取客户端浏览器 + String browser = userAgent.getBrowser().getName(); + // 封装对象 + RemoteLogininforBo logininfor = new RemoteLogininforBo(); + logininfor.setTenantId(logininforEvent.getTenantId()); + logininfor.setUserName(logininforEvent.getUsername()); + if (ObjectUtil.isNotNull(clientVo)) { + logininfor.setClientKey(clientVo.getClientKey()); + logininfor.setDeviceType(clientVo.getDeviceType()); + } + logininfor.setIpaddr(ip); + logininfor.setLoginLocation(address); + logininfor.setBrowser(browser); + logininfor.setOs(os); + logininfor.setMsg(logininforEvent.getMessage()); + // 日志状态 + if (StringUtils.equalsAny(logininforEvent.getStatus(), Constants.LOGIN_SUCCESS, Constants.LOGOUT, Constants.REGISTER)) { + logininfor.setStatus(Constants.SUCCESS); + } else if (Constants.LOGIN_FAIL.equals(logininforEvent.getStatus())) { + logininfor.setStatus(Constants.FAIL); + } + remoteLogService.saveLogininfor(logininfor); + } + + private String getBlock(Object msg) { + if (msg == null) { + msg = ""; + } + return "[" + msg + "]"; + } + +} diff --git a/ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/event/LogininforEvent.java b/ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/event/LogininforEvent.java new file mode 100644 index 0000000..938eaad --- /dev/null +++ b/ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/event/LogininforEvent.java @@ -0,0 +1,52 @@ +package org.dromara.common.log.event; + +import lombok.Data; + +import jakarta.servlet.http.HttpServletRequest; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 登录事件 + * + * @author Lion Li + */ + +@Data +public class LogininforEvent implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 租户ID + */ + private String tenantId; + + /** + * 用户账号 + */ + private String username; + + /** + * 登录状态 0成功 1失败 + */ + private String status; + + /** + * 提示消息 + */ + private String message; + + /** + * 请求体 + */ + private HttpServletRequest request; + + /** + * 其他参数 + */ + private Object[] args; + +} diff --git a/ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/event/OperLogEvent.java b/ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/event/OperLogEvent.java new file mode 100644 index 0000000..0386192 --- /dev/null +++ b/ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/event/OperLogEvent.java @@ -0,0 +1,115 @@ +package org.dromara.common.log.event; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + +/** + * 操作日志事件 + * + * @author Lion Li + */ + +@Data +public class OperLogEvent implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 日志主键 + */ + private Long operId; + + /** + * 租户ID + */ + private String tenantId; + + /** + * 操作模块 + */ + private String title; + + /** + * 业务类型(0其它 1新增 2修改 3删除) + */ + private Integer businessType; + + /** + * 业务类型数组 + */ + private Integer[] businessTypes; + + /** + * 请求方法 + */ + private String method; + + /** + * 请求方式 + */ + private String requestMethod; + + /** + * 操作类别(0其它 1后台用户 2手机端用户) + */ + private Integer operatorType; + + /** + * 操作人员 + */ + private String operName; + + /** + * 部门名称 + */ + private String deptName; + + /** + * 请求url + */ + private String operUrl; + + /** + * 操作地址 + */ + private String operIp; + + /** + * 操作地点 + */ + private String operLocation; + + /** + * 请求参数 + */ + private String operParam; + + /** + * 返回参数 + */ + private String jsonResult; + + /** + * 操作状态(0正常 1异常) + */ + private Integer status; + + /** + * 错误消息 + */ + private String errorMsg; + + /** + * 操作时间 + */ + private Date operTime; + + /** + * 消耗时间 + */ + private Long costTime; +} diff --git a/ruoyi-common/ruoyi-common-log/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/ruoyi-common/ruoyi-common-log/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..42b8812 --- /dev/null +++ b/ruoyi-common/ruoyi-common-log/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1,2 @@ +org.dromara.common.log.event.LogEventListener +org.dromara.common.log.aspect.LogAspect diff --git a/ruoyi-common/ruoyi-common-logstash/pom.xml b/ruoyi-common/ruoyi-common-logstash/pom.xml new file mode 100644 index 0000000..36ad275 --- /dev/null +++ b/ruoyi-common/ruoyi-common-logstash/pom.xml @@ -0,0 +1,24 @@ + + + + org.dromara + ruoyi-common + ${revision} + + 4.0.0 + + ruoyi-common-logstash + + + ruoyi-common-logstash logstash日志推送模块 + + + + + net.logstash.logback + logstash-logback-encoder + + + diff --git a/ruoyi-common/ruoyi-common-logstash/src/main/resources/logback-logstash.xml b/ruoyi-common/ruoyi-common-logstash/src/main/resources/logback-logstash.xml new file mode 100644 index 0000000..d25f752 --- /dev/null +++ b/ruoyi-common/ruoyi-common-logstash/src/main/resources/logback-logstash.xml @@ -0,0 +1,19 @@ + + + + + + + + + + ${logstash.address} + + {"spring.application.name":"${appName}"} + + + + + + + diff --git a/ruoyi-common/ruoyi-common-mail/pom.xml b/ruoyi-common/ruoyi-common-mail/pom.xml new file mode 100644 index 0000000..c0e1b2e --- /dev/null +++ b/ruoyi-common/ruoyi-common-mail/pom.xml @@ -0,0 +1,34 @@ + + + + org.dromara + ruoyi-common + ${revision} + + 4.0.0 + + ruoyi-common-mail + + + ruoyi-common-mail 邮件模块 + + + + + org.dromara + ruoyi-common-core + + + + jakarta.mail + jakarta.mail-api + + + org.eclipse.angus + jakarta.mail + + + + diff --git a/ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/config/MailConfig.java b/ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/config/MailConfig.java new file mode 100644 index 0000000..1b51c27 --- /dev/null +++ b/ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/config/MailConfig.java @@ -0,0 +1,37 @@ +package org.dromara.common.mail.config; + +import org.dromara.common.mail.config.properties.MailProperties; +import org.dromara.common.mail.utils.MailAccount; +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; + +/** + * JavaMail 配置 + * + * @author Michelle.Chung + */ +@AutoConfiguration +@EnableConfigurationProperties(MailProperties.class) +public class MailConfig { + + @Bean + @ConditionalOnProperty(value = "mail.enabled", havingValue = "true") + public MailAccount mailAccount(MailProperties mailProperties) { + MailAccount account = new MailAccount(); + account.setHost(mailProperties.getHost()); + account.setPort(mailProperties.getPort()); + account.setAuth(mailProperties.getAuth()); + account.setFrom(mailProperties.getFrom()); + account.setUser(mailProperties.getUser()); + account.setPass(mailProperties.getPass()); + account.setSocketFactoryPort(mailProperties.getPort()); + account.setStarttlsEnable(mailProperties.getStarttlsEnable()); + account.setSslEnable(mailProperties.getSslEnable()); + account.setTimeout(mailProperties.getTimeout()); + account.setConnectionTimeout(mailProperties.getConnectionTimeout()); + return account; + } + +} diff --git a/ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/config/MailConfiguration.java b/ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/config/MailConfiguration.java new file mode 100644 index 0000000..6410122 --- /dev/null +++ b/ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/config/MailConfiguration.java @@ -0,0 +1,37 @@ +package org.dromara.common.mail.config; + +import org.dromara.common.mail.utils.MailAccount; +import org.dromara.common.mail.config.properties.MailProperties; +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; + +/** + * JavaMail 配置 + * + * @author Michelle.Chung + */ +@AutoConfiguration +@EnableConfigurationProperties(MailProperties.class) +public class MailConfiguration { + + @Bean + @ConditionalOnProperty(value = "mail.enabled", havingValue = "true") + public MailAccount mailAccount(MailProperties mailProperties) { + MailAccount account = new MailAccount(); + account.setHost(mailProperties.getHost()); + account.setPort(mailProperties.getPort()); + account.setAuth(mailProperties.getAuth()); + account.setFrom(mailProperties.getFrom()); + account.setUser(mailProperties.getUser()); + account.setPass(mailProperties.getPass()); + account.setSocketFactoryPort(mailProperties.getPort()); + account.setStarttlsEnable(mailProperties.getStarttlsEnable()); + account.setSslEnable(mailProperties.getSslEnable()); + account.setTimeout(mailProperties.getTimeout()); + account.setConnectionTimeout(mailProperties.getConnectionTimeout()); + return account; + } + +} diff --git a/ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/config/properties/MailProperties.java b/ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/config/properties/MailProperties.java new file mode 100644 index 0000000..d0e78a2 --- /dev/null +++ b/ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/config/properties/MailProperties.java @@ -0,0 +1,69 @@ +package org.dromara.common.mail.config.properties; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * JavaMail 配置属性 + * + * @author Michelle.Chung + */ +@Data +@ConfigurationProperties(prefix = "mail") +public class MailProperties { + + /** + * 过滤开关 + */ + private Boolean enabled; + + /** + * SMTP服务器域名 + */ + private String host; + + /** + * SMTP服务端口 + */ + private Integer port; + + /** + * 是否需要用户名密码验证 + */ + private Boolean auth; + + /** + * 用户名 + */ + private String user; + + /** + * 密码 + */ + private String pass; + + /** + * 发送方,遵循RFC-822标准 + */ + private String from; + + /** + * 使用 STARTTLS安全连接,STARTTLS是对纯文本通信协议的扩展。它将纯文本连接升级为加密连接(TLS或SSL), 而不是使用一个单独的加密通信端口。 + */ + private Boolean starttlsEnable; + + /** + * 使用 SSL安全连接 + */ + private Boolean sslEnable; + + /** + * SMTP超时时长,单位毫秒,缺省值不超时 + */ + private Long timeout; + + /** + * Socket连接超时值,单位毫秒,缺省值不超时 + */ + private Long connectionTimeout; +} diff --git a/ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/GlobalMailAccount.java b/ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/GlobalMailAccount.java new file mode 100644 index 0000000..fdae869 --- /dev/null +++ b/ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/GlobalMailAccount.java @@ -0,0 +1,46 @@ +package org.dromara.common.mail.utils; + +import cn.hutool.core.io.IORuntimeException; + +/** + * 全局邮件帐户,依赖于邮件配置文件{@link MailAccount#MAIL_SETTING_PATHS} + * + * @author looly + */ +public enum GlobalMailAccount { + INSTANCE; + + private final MailAccount mailAccount; + + /** + * 构造 + */ + GlobalMailAccount() { + mailAccount = createDefaultAccount(); + } + + /** + * 获得邮件帐户 + * + * @return 邮件帐户 + */ + public MailAccount getAccount() { + return this.mailAccount; + } + + /** + * 创建默认帐户 + * + * @return MailAccount + */ + private MailAccount createDefaultAccount() { + for (String mailSettingPath : MailAccount.MAIL_SETTING_PATHS) { + try { + return new MailAccount(mailSettingPath); + } catch (IORuntimeException ignore) { + //ignore + } + } + return null; + } +} diff --git a/ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/InternalMailUtil.java b/ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/InternalMailUtil.java new file mode 100644 index 0000000..b755e73 --- /dev/null +++ b/ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/InternalMailUtil.java @@ -0,0 +1,108 @@ +package org.dromara.common.mail.utils; + +import cn.hutool.core.util.ArrayUtil; +import jakarta.mail.internet.AddressException; +import jakarta.mail.internet.InternetAddress; +import jakarta.mail.internet.MimeUtility; + +import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * 邮件内部工具类 + * + * @author looly + * @since 3.2.3 + */ +public class InternalMailUtil { + + /** + * 将多个字符串邮件地址转为{@link InternetAddress}列表
+ * 单个字符串地址可以是多个地址合并的字符串 + * + * @param addrStrs 地址数组 + * @param charset 编码(主要用于中文用户名的编码) + * @return 地址数组 + * @since 4.0.3 + */ + public static InternetAddress[] parseAddressFromStrs(String[] addrStrs, Charset charset) { + final List resultList = new ArrayList<>(addrStrs.length); + InternetAddress[] addrs; + for (String addrStr : addrStrs) { + addrs = parseAddress(addrStr, charset); + if (ArrayUtil.isNotEmpty(addrs)) { + Collections.addAll(resultList, addrs); + } + } + return resultList.toArray(new InternetAddress[0]); + } + + /** + * 解析第一个地址 + * + * @param address 地址字符串 + * @param charset 编码,{@code null}表示使用系统属性定义的编码或系统编码 + * @return 地址列表 + */ + public static InternetAddress parseFirstAddress(String address, Charset charset) { + final InternetAddress[] internetAddresses = parseAddress(address, charset); + if (ArrayUtil.isEmpty(internetAddresses)) { + try { + return new InternetAddress(address); + } catch (AddressException e) { + throw new MailException(e); + } + } + return internetAddresses[0]; + } + + /** + * 将一个地址字符串解析为多个地址
+ * 地址间使用" "、","、";"分隔 + * + * @param address 地址字符串 + * @param charset 编码,{@code null}表示使用系统属性定义的编码或系统编码 + * @return 地址列表 + */ + public static InternetAddress[] parseAddress(String address, Charset charset) { + InternetAddress[] addresses; + try { + addresses = InternetAddress.parse(address); + } catch (AddressException e) { + throw new MailException(e); + } + //编码用户名 + if (ArrayUtil.isNotEmpty(addresses)) { + final String charsetStr = null == charset ? null : charset.name(); + for (InternetAddress internetAddress : addresses) { + try { + internetAddress.setPersonal(internetAddress.getPersonal(), charsetStr); + } catch (UnsupportedEncodingException e) { + throw new MailException(e); + } + } + } + + return addresses; + } + + /** + * 编码中文字符
+ * 编码失败返回原字符串 + * + * @param text 被编码的文本 + * @param charset 编码 + * @return 编码后的结果 + */ + public static String encodeText(String text, Charset charset) { + try { + return MimeUtility.encodeText(text, charset.name(), null); + } catch (UnsupportedEncodingException e) { + // ignore + } + return text; + } +} diff --git a/ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/Mail.java b/ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/Mail.java new file mode 100644 index 0000000..6ca4b69 --- /dev/null +++ b/ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/Mail.java @@ -0,0 +1,483 @@ +package org.dromara.common.mail.utils; + +import cn.hutool.core.builder.Builder; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.IORuntimeException; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import jakarta.activation.DataHandler; +import jakarta.activation.DataSource; +import jakarta.activation.FileDataSource; +import jakarta.activation.FileTypeMap; +import jakarta.mail.*; +import jakarta.mail.internet.MimeBodyPart; +import jakarta.mail.internet.MimeMessage; +import jakarta.mail.internet.MimeMultipart; +import jakarta.mail.internet.MimeUtility; +import jakarta.mail.util.ByteArrayDataSource; + +import java.io.*; +import java.nio.charset.Charset; +import java.util.Date; + +/** + * 邮件发送客户端 + * + * @author looly + * @since 3.2.0 + */ +public class Mail implements Builder { + @Serial + private static final long serialVersionUID = 1L; + + /** + * 邮箱帐户信息以及一些客户端配置信息 + */ + private final MailAccount mailAccount; + /** + * 收件人列表 + */ + private String[] tos; + /** + * 抄送人列表(carbon copy) + */ + private String[] ccs; + /** + * 密送人列表(blind carbon copy) + */ + private String[] bccs; + /** + * 回复地址(reply-to) + */ + private String[] reply; + /** + * 标题 + */ + private String title; + /** + * 内容 + */ + private String content; + /** + * 是否为HTML + */ + private boolean isHtml; + /** + * 正文、附件和图片的混合部分 + */ + private final Multipart multipart = new MimeMultipart(); + /** + * 是否使用全局会话,默认为false + */ + private boolean useGlobalSession = false; + + /** + * debug输出位置,可以自定义debug日志 + */ + private PrintStream debugOutput; + + /** + * 创建邮件客户端 + * + * @param mailAccount 邮件帐号 + * @return Mail + */ + public static Mail create(MailAccount mailAccount) { + return new Mail(mailAccount); + } + + /** + * 创建邮件客户端,使用全局邮件帐户 + * + * @return Mail + */ + public static Mail create() { + return new Mail(); + } + + // --------------------------------------------------------------- Constructor start + + /** + * 构造,使用全局邮件帐户 + */ + public Mail() { + this(GlobalMailAccount.INSTANCE.getAccount()); + } + + /** + * 构造 + * + * @param mailAccount 邮件帐户,如果为null使用默认配置文件的全局邮件配置 + */ + public Mail(MailAccount mailAccount) { + mailAccount = (null != mailAccount) ? mailAccount : GlobalMailAccount.INSTANCE.getAccount(); + this.mailAccount = mailAccount.defaultIfEmpty(); + } + // --------------------------------------------------------------- Constructor end + + // --------------------------------------------------------------- Getters and Setters start + + /** + * 设置收件人 + * + * @param tos 收件人列表 + * @return this + * @see #setTos(String...) + */ + public Mail to(String... tos) { + return setTos(tos); + } + + /** + * 设置多个收件人 + * + * @param tos 收件人列表 + * @return this + */ + public Mail setTos(String... tos) { + this.tos = tos; + return this; + } + + /** + * 设置多个抄送人(carbon copy) + * + * @param ccs 抄送人列表 + * @return this + * @since 4.0.3 + */ + public Mail setCcs(String... ccs) { + this.ccs = ccs; + return this; + } + + /** + * 设置多个密送人(blind carbon copy) + * + * @param bccs 密送人列表 + * @return this + * @since 4.0.3 + */ + public Mail setBccs(String... bccs) { + this.bccs = bccs; + return this; + } + + /** + * 设置多个回复地址(reply-to) + * + * @param reply 回复地址(reply-to)列表 + * @return this + * @since 4.6.0 + */ + public Mail setReply(String... reply) { + this.reply = reply; + return this; + } + + /** + * 设置标题 + * + * @param title 标题 + * @return this + */ + public Mail setTitle(String title) { + this.title = title; + return this; + } + + /** + * 设置正文
+ * 正文可以是普通文本也可以是HTML(默认普通文本),可以通过调用{@link #setHtml(boolean)} 设置是否为HTML + * + * @param content 正文 + * @return this + */ + public Mail setContent(String content) { + this.content = content; + return this; + } + + /** + * 设置是否是HTML + * + * @param isHtml 是否为HTML + * @return this + */ + public Mail setHtml(boolean isHtml) { + this.isHtml = isHtml; + return this; + } + + /** + * 设置正文 + * + * @param content 正文内容 + * @param isHtml 是否为HTML + * @return this + */ + public Mail setContent(String content, boolean isHtml) { + setContent(content); + return setHtml(isHtml); + } + + /** + * 设置文件类型附件,文件可以是图片文件,此时自动设置cid(正文中引用图片),默认cid为文件名 + * + * @param files 附件文件列表 + * @return this + */ + public Mail setFiles(File... files) { + if (ArrayUtil.isEmpty(files)) { + return this; + } + + final DataSource[] attachments = new DataSource[files.length]; + for (int i = 0; i < files.length; i++) { + attachments[i] = new FileDataSource(files[i]); + } + return setAttachments(attachments); + } + + /** + * 增加附件或图片,附件使用{@link DataSource} 形式表示,可以使用{@link FileDataSource}包装文件表示文件附件 + * + * @param attachments 附件列表 + * @return this + * @since 4.0.9 + */ + public Mail setAttachments(DataSource... attachments) { + if (ArrayUtil.isNotEmpty(attachments)) { + final Charset charset = this.mailAccount.getCharset(); + MimeBodyPart bodyPart; + String nameEncoded; + try { + for (DataSource attachment : attachments) { + bodyPart = new MimeBodyPart(); + bodyPart.setDataHandler(new DataHandler(attachment)); + nameEncoded = attachment.getName(); + if (this.mailAccount.isEncodefilename()) { + nameEncoded = InternalMailUtil.encodeText(nameEncoded, charset); + } + // 普通附件文件名 + bodyPart.setFileName(nameEncoded); + if (StrUtil.startWith(attachment.getContentType(), "image/")) { + // 图片附件,用于正文中引用图片 + bodyPart.setContentID(nameEncoded); + } + this.multipart.addBodyPart(bodyPart); + } + } catch (MessagingException e) { + throw new MailException(e); + } + } + return this; + } + + /** + * 增加图片,图片的键对应到邮件模板中的占位字符串,图片类型默认为"image/jpeg" + * + * @param cid 图片与占位符,占位符格式为cid:${cid} + * @param imageStream 图片文件 + * @return this + * @since 4.6.3 + */ + public Mail addImage(String cid, InputStream imageStream) { + return addImage(cid, imageStream, null); + } + + /** + * 增加图片,图片的键对应到邮件模板中的占位字符串 + * + * @param cid 图片与占位符,占位符格式为cid:${cid} + * @param imageStream 图片流,不关闭 + * @param contentType 图片类型,null赋值默认的"image/jpeg" + * @return this + * @since 4.6.3 + */ + public Mail addImage(String cid, InputStream imageStream, String contentType) { + ByteArrayDataSource imgSource; + try { + imgSource = new ByteArrayDataSource(imageStream, ObjectUtil.defaultIfNull(contentType, "image/jpeg")); + } catch (IOException e) { + throw new IORuntimeException(e); + } + imgSource.setName(cid); + return setAttachments(imgSource); + } + + /** + * 增加图片,图片的键对应到邮件模板中的占位字符串 + * + * @param cid 图片与占位符,占位符格式为cid:${cid} + * @param imageFile 图片文件 + * @return this + * @since 4.6.3 + */ + public Mail addImage(String cid, File imageFile) { + InputStream in = null; + try { + in = FileUtil.getInputStream(imageFile); + return addImage(cid, in, FileTypeMap.getDefaultFileTypeMap().getContentType(imageFile)); + } finally { + IoUtil.close(in); + } + } + + /** + * 设置字符集编码 + * + * @param charset 字符集编码 + * @return this + * @see MailAccount#setCharset(Charset) + */ + public Mail setCharset(Charset charset) { + this.mailAccount.setCharset(charset); + return this; + } + + /** + * 设置是否使用全局会话,默认为true + * + * @param isUseGlobalSession 是否使用全局会话,默认为true + * @return this + * @since 4.0.2 + */ + public Mail setUseGlobalSession(boolean isUseGlobalSession) { + this.useGlobalSession = isUseGlobalSession; + return this; + } + + /** + * 设置debug输出位置,可以自定义debug日志 + * + * @param debugOutput debug输出位置 + * @return this + * @since 5.5.6 + */ + public Mail setDebugOutput(PrintStream debugOutput) { + this.debugOutput = debugOutput; + return this; + } + // --------------------------------------------------------------- Getters and Setters end + + @Override + public MimeMessage build() { + try { + return buildMsg(); + } catch (MessagingException e) { + throw new MailException(e); + } + } + + /** + * 发送 + * + * @return message-id + * @throws MailException 邮件发送异常 + */ + public String send() throws MailException { + try { + return doSend(); + } catch (MessagingException e) { + if (e instanceof SendFailedException) { + // 当地址无效时,显示更加详细的无效地址信息 + final Address[] invalidAddresses = ((SendFailedException) e).getInvalidAddresses(); + final String msg = StrUtil.format("Invalid Addresses: {}", ArrayUtil.toString(invalidAddresses)); + throw new MailException(msg, e); + } + throw new MailException(e); + } + } + + // --------------------------------------------------------------- Private method start + + /** + * 执行发送 + * + * @return message-id + * @throws MessagingException 发送异常 + */ + private String doSend() throws MessagingException { + final MimeMessage mimeMessage = buildMsg(); + Transport.send(mimeMessage); + return mimeMessage.getMessageID(); + } + + /** + * 构建消息 + * + * @return {@link MimeMessage}消息 + * @throws MessagingException 消息异常 + */ + private MimeMessage buildMsg() throws MessagingException { + final Charset charset = this.mailAccount.getCharset(); + final MimeMessage msg = new MimeMessage(getSession()); + // 发件人 + final String from = this.mailAccount.getFrom(); + if (StrUtil.isEmpty(from)) { + // 用户未提供发送方,则从Session中自动获取 + msg.setFrom(); + } else { + msg.setFrom(InternalMailUtil.parseFirstAddress(from, charset)); + } + // 标题 + msg.setSubject(this.title, (null == charset) ? null : charset.name()); + // 发送时间 + msg.setSentDate(new Date()); + // 内容和附件 + msg.setContent(buildContent(charset)); + // 收件人 + msg.setRecipients(MimeMessage.RecipientType.TO, InternalMailUtil.parseAddressFromStrs(this.tos, charset)); + // 抄送人 + if (ArrayUtil.isNotEmpty(this.ccs)) { + msg.setRecipients(MimeMessage.RecipientType.CC, InternalMailUtil.parseAddressFromStrs(this.ccs, charset)); + } + // 密送人 + if (ArrayUtil.isNotEmpty(this.bccs)) { + msg.setRecipients(MimeMessage.RecipientType.BCC, InternalMailUtil.parseAddressFromStrs(this.bccs, charset)); + } + // 回复地址(reply-to) + if (ArrayUtil.isNotEmpty(this.reply)) { + msg.setReplyTo(InternalMailUtil.parseAddressFromStrs(this.reply, charset)); + } + + return msg; + } + + /** + * 构建邮件信息主体 + * + * @param charset 编码,{@code null}则使用{@link MimeUtility#getDefaultJavaCharset()} + * @return 邮件信息主体 + * @throws MessagingException 消息异常 + */ + private Multipart buildContent(Charset charset) throws MessagingException { + final String charsetStr = null != charset ? charset.name() : MimeUtility.getDefaultJavaCharset(); + // 正文 + final MimeBodyPart body = new MimeBodyPart(); + body.setContent(content, StrUtil.format("text/{}; charset={}", isHtml ? "html" : "plain", charsetStr)); + this.multipart.addBodyPart(body); + + return this.multipart; + } + + /** + * 获取默认邮件会话
+ * 如果为全局单例的会话,则全局只允许一个邮件帐号,否则每次发送邮件会新建一个新的会话 + * + * @return 邮件会话 {@link Session} + */ + private Session getSession() { + final Session session = MailUtils.getSession(this.mailAccount, this.useGlobalSession); + + if (null != this.debugOutput) { + session.setDebugOut(debugOutput); + } + + return session; + } + // --------------------------------------------------------------- Private method end +} diff --git a/ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/MailAccount.java b/ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/MailAccount.java new file mode 100644 index 0000000..2a732a1 --- /dev/null +++ b/ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/MailAccount.java @@ -0,0 +1,659 @@ +package org.dromara.common.mail.utils; + +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.setting.Setting; + +import java.io.Serial; +import java.io.Serializable; +import java.nio.charset.Charset; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +/** + * 邮件账户对象 + * + * @author Luxiaolei + */ +public class MailAccount implements Serializable { + @Serial + private static final long serialVersionUID = -6937313421815719204L; + + private static final String MAIL_PROTOCOL = "mail.transport.protocol"; + private static final String SMTP_HOST = "mail.smtp.host"; + private static final String SMTP_PORT = "mail.smtp.port"; + private static final String SMTP_AUTH = "mail.smtp.auth"; + private static final String SMTP_TIMEOUT = "mail.smtp.timeout"; + private static final String SMTP_CONNECTION_TIMEOUT = "mail.smtp.connectiontimeout"; + private static final String SMTP_WRITE_TIMEOUT = "mail.smtp.writetimeout"; + + // SSL + private static final String STARTTLS_ENABLE = "mail.smtp.starttls.enable"; + private static final String SSL_ENABLE = "mail.smtp.ssl.enable"; + private static final String SSL_PROTOCOLS = "mail.smtp.ssl.protocols"; + private static final String SOCKET_FACTORY = "mail.smtp.socketFactory.class"; + private static final String SOCKET_FACTORY_FALLBACK = "mail.smtp.socketFactory.fallback"; + private static final String SOCKET_FACTORY_PORT = "smtp.socketFactory.port"; + + // System Properties + private static final String SPLIT_LONG_PARAMS = "mail.mime.splitlongparameters"; + //private static final String ENCODE_FILE_NAME = "mail.mime.encodefilename"; + //private static final String CHARSET = "mail.mime.charset"; + + // 其他 + private static final String MAIL_DEBUG = "mail.debug"; + + public static final String[] MAIL_SETTING_PATHS = new String[]{"config/mail.setting", "config/mailAccount.setting", "mail.setting"}; + + /** + * SMTP服务器域名 + */ + private String host; + /** + * SMTP服务端口 + */ + private Integer port; + /** + * 是否需要用户名密码验证 + */ + private Boolean auth; + /** + * 用户名 + */ + private String user; + /** + * 密码 + */ + private String pass; + /** + * 发送方,遵循RFC-822标准 + */ + private String from; + + /** + * 是否打开调试模式,调试模式会显示与邮件服务器通信过程,默认不开启 + */ + private boolean debug; + /** + * 编码用于编码邮件正文和发送人、收件人等中文 + */ + private Charset charset = CharsetUtil.CHARSET_UTF_8; + /** + * 对于超长参数是否切分为多份,默认为false(国内邮箱附件不支持切分的附件名) + */ + private boolean splitlongparameters = false; + /** + * 对于文件名是否使用{@link #charset}编码,默认为 {@code true} + */ + private boolean encodefilename = true; + + /** + * 使用 STARTTLS安全连接,STARTTLS是对纯文本通信协议的扩展。它将纯文本连接升级为加密连接(TLS或SSL), 而不是使用一个单独的加密通信端口。 + */ + private boolean starttlsEnable = false; + /** + * 使用 SSL安全连接 + */ + private Boolean sslEnable; + + /** + * SSL协议,多个协议用空格分隔 + */ + private String sslProtocols; + + /** + * 指定实现javax.net.SocketFactory接口的类的名称,这个类将被用于创建SMTP的套接字 + */ + private String socketFactoryClass = "javax.net.ssl.SSLSocketFactory"; + /** + * 如果设置为true,未能创建一个套接字使用指定的套接字工厂类将导致使用java.net.Socket创建的套接字类, 默认值为true + */ + private boolean socketFactoryFallback; + /** + * 指定的端口连接到在使用指定的套接字工厂。如果没有设置,将使用默认端口 + */ + private int socketFactoryPort = 465; + + /** + * SMTP超时时长,单位毫秒,缺省值不超时 + */ + private long timeout; + /** + * Socket连接超时值,单位毫秒,缺省值不超时 + */ + private long connectionTimeout; + /** + * Socket写出超时值,单位毫秒,缺省值不超时 + */ + private long writeTimeout; + + /** + * 自定义的其他属性,此自定义属性会覆盖默认属性 + */ + private final Map customProperty = new HashMap<>(); + + // -------------------------------------------------------------- Constructor start + + /** + * 构造,所有参数需自行定义或保持默认值 + */ + public MailAccount() { + } + + /** + * 构造 + * + * @param settingPath 配置文件路径 + */ + public MailAccount(String settingPath) { + this(new Setting(settingPath)); + } + + /** + * 构造 + * + * @param setting 配置文件 + */ + public MailAccount(Setting setting) { + setting.toBean(this); + } + + // -------------------------------------------------------------- Constructor end + + /** + * 获得SMTP服务器域名 + * + * @return SMTP服务器域名 + */ + public String getHost() { + return host; + } + + /** + * 设置SMTP服务器域名 + * + * @param host SMTP服务器域名 + * @return this + */ + public MailAccount setHost(String host) { + this.host = host; + return this; + } + + /** + * 获得SMTP服务端口 + * + * @return SMTP服务端口 + */ + public Integer getPort() { + return port; + } + + /** + * 设置SMTP服务端口 + * + * @param port SMTP服务端口 + * @return this + */ + public MailAccount setPort(Integer port) { + this.port = port; + return this; + } + + /** + * 是否需要用户名密码验证 + * + * @return 是否需要用户名密码验证 + */ + public Boolean isAuth() { + return auth; + } + + /** + * 设置是否需要用户名密码验证 + * + * @param isAuth 是否需要用户名密码验证 + * @return this + */ + public MailAccount setAuth(boolean isAuth) { + this.auth = isAuth; + return this; + } + + /** + * 获取用户名 + * + * @return 用户名 + */ + public String getUser() { + return user; + } + + /** + * 设置用户名 + * + * @param user 用户名 + * @return this + */ + public MailAccount setUser(String user) { + this.user = user; + return this; + } + + /** + * 获取密码 + * + * @return 密码 + */ + public String getPass() { + return pass; + } + + /** + * 设置密码 + * + * @param pass 密码 + * @return this + */ + public MailAccount setPass(String pass) { + this.pass = pass; + return this; + } + + /** + * 获取发送方,遵循RFC-822标准 + * + * @return 发送方,遵循RFC-822标准 + */ + public String getFrom() { + return from; + } + + /** + * 设置发送方,遵循RFC-822标准
+ * 发件人可以是以下形式: + * + *
+     * 1. user@xxx.xx
+     * 2.  name <user@xxx.xx>
+     * 
+ * + * @param from 发送方,遵循RFC-822标准 + * @return this + */ + public MailAccount setFrom(String from) { + this.from = from; + return this; + } + + /** + * 是否打开调试模式,调试模式会显示与邮件服务器通信过程,默认不开启 + * + * @return 是否打开调试模式,调试模式会显示与邮件服务器通信过程,默认不开启 + * @since 4.0.2 + */ + public boolean isDebug() { + return debug; + } + + /** + * 设置是否打开调试模式,调试模式会显示与邮件服务器通信过程,默认不开启 + * + * @param debug 是否打开调试模式,调试模式会显示与邮件服务器通信过程,默认不开启 + * @return this + * @since 4.0.2 + */ + public MailAccount setDebug(boolean debug) { + this.debug = debug; + return this; + } + + /** + * 获取字符集编码 + * + * @return 编码,可能为{@code null} + */ + public Charset getCharset() { + return charset; + } + + /** + * 设置字符集编码,此选项不会修改全局配置,若修改全局配置,请设置此项为{@code null}并设置: + *
+     * 	System.setProperty("mail.mime.charset", charset);
+     * 
+ * + * @param charset 字符集编码,{@code null} 则表示使用全局设置的默认编码,全局编码为mail.mime.charset系统属性 + * @return this + */ + public MailAccount setCharset(Charset charset) { + this.charset = charset; + return this; + } + + /** + * 对于超长参数是否切分为多份,默认为false(国内邮箱附件不支持切分的附件名) + * + * @return 对于超长参数是否切分为多份 + */ + public boolean isSplitlongparameters() { + return splitlongparameters; + } + + /** + * 设置对于超长参数是否切分为多份,默认为false(国内邮箱附件不支持切分的附件名)
+ * 注意此项为全局设置,此项会调用 + *
+     * System.setProperty("mail.mime.splitlongparameters", true)
+     * 
+ * + * @param splitlongparameters 对于超长参数是否切分为多份 + */ + public void setSplitlongparameters(boolean splitlongparameters) { + this.splitlongparameters = splitlongparameters; + } + + /** + * 对于文件名是否使用{@link #charset}编码,默认为 {@code true} + * + * @return 对于文件名是否使用{@link #charset}编码,默认为 {@code true} + * @since 5.7.16 + */ + public boolean isEncodefilename() { + + return encodefilename; + } + + /** + * 设置对于文件名是否使用{@link #charset}编码,此选项不会修改全局配置
+ * 如果此选项设置为{@code false},则是否编码取决于两个系统属性: + *
    + *
  • mail.mime.encodefilename 是否编码附件文件名
  • + *
  • mail.mime.charset 编码文件名的编码
  • + *
+ * + * @param encodefilename 对于文件名是否使用{@link #charset}编码 + * @since 5.7.16 + */ + public void setEncodefilename(boolean encodefilename) { + this.encodefilename = encodefilename; + } + + /** + * 是否使用 STARTTLS安全连接,STARTTLS是对纯文本通信协议的扩展。它将纯文本连接升级为加密连接(TLS或SSL), 而不是使用一个单独的加密通信端口。 + * + * @return 是否使用 STARTTLS安全连接 + */ + public boolean isStarttlsEnable() { + return this.starttlsEnable; + } + + /** + * 设置是否使用STARTTLS安全连接,STARTTLS是对纯文本通信协议的扩展。它将纯文本连接升级为加密连接(TLS或SSL), 而不是使用一个单独的加密通信端口。 + * + * @param startttlsEnable 是否使用STARTTLS安全连接 + * @return this + */ + public MailAccount setStarttlsEnable(boolean startttlsEnable) { + this.starttlsEnable = startttlsEnable; + return this; + } + + /** + * 是否使用 SSL安全连接 + * + * @return 是否使用 SSL安全连接 + */ + public Boolean isSslEnable() { + return this.sslEnable; + } + + /** + * 设置是否使用SSL安全连接 + * + * @param sslEnable 是否使用SSL安全连接 + * @return this + */ + public MailAccount setSslEnable(Boolean sslEnable) { + this.sslEnable = sslEnable; + return this; + } + + /** + * 获取SSL协议,多个协议用空格分隔 + * + * @return SSL协议,多个协议用空格分隔 + * @since 5.5.7 + */ + public String getSslProtocols() { + return sslProtocols; + } + + /** + * 设置SSL协议,多个协议用空格分隔 + * + * @param sslProtocols SSL协议,多个协议用空格分隔 + * @since 5.5.7 + */ + public void setSslProtocols(String sslProtocols) { + this.sslProtocols = sslProtocols; + } + + /** + * 获取指定实现javax.net.SocketFactory接口的类的名称,这个类将被用于创建SMTP的套接字 + * + * @return 指定实现javax.net.SocketFactory接口的类的名称, 这个类将被用于创建SMTP的套接字 + */ + public String getSocketFactoryClass() { + return socketFactoryClass; + } + + /** + * 设置指定实现javax.net.SocketFactory接口的类的名称,这个类将被用于创建SMTP的套接字 + * + * @param socketFactoryClass 指定实现javax.net.SocketFactory接口的类的名称,这个类将被用于创建SMTP的套接字 + * @return this + */ + public MailAccount setSocketFactoryClass(String socketFactoryClass) { + this.socketFactoryClass = socketFactoryClass; + return this; + } + + /** + * 如果设置为true,未能创建一个套接字使用指定的套接字工厂类将导致使用java.net.Socket创建的套接字类, 默认值为true + * + * @return 如果设置为true, 未能创建一个套接字使用指定的套接字工厂类将导致使用java.net.Socket创建的套接字类, 默认值为true + */ + public boolean isSocketFactoryFallback() { + return socketFactoryFallback; + } + + /** + * 如果设置为true,未能创建一个套接字使用指定的套接字工厂类将导致使用java.net.Socket创建的套接字类, 默认值为true + * + * @param socketFactoryFallback 如果设置为true,未能创建一个套接字使用指定的套接字工厂类将导致使用java.net.Socket创建的套接字类, 默认值为true + * @return this + */ + public MailAccount setSocketFactoryFallback(boolean socketFactoryFallback) { + this.socketFactoryFallback = socketFactoryFallback; + return this; + } + + /** + * 获取指定的端口连接到在使用指定的套接字工厂。如果没有设置,将使用默认端口 + * + * @return 指定的端口连接到在使用指定的套接字工厂。如果没有设置,将使用默认端口 + */ + public int getSocketFactoryPort() { + return socketFactoryPort; + } + + /** + * 指定的端口连接到在使用指定的套接字工厂。如果没有设置,将使用默认端口 + * + * @param socketFactoryPort 指定的端口连接到在使用指定的套接字工厂。如果没有设置,将使用默认端口 + * @return this + */ + public MailAccount setSocketFactoryPort(int socketFactoryPort) { + this.socketFactoryPort = socketFactoryPort; + return this; + } + + /** + * 设置SMTP超时时长,单位毫秒,缺省值不超时 + * + * @param timeout SMTP超时时长,单位毫秒,缺省值不超时 + * @return this + * @since 4.1.17 + */ + public MailAccount setTimeout(long timeout) { + this.timeout = timeout; + return this; + } + + /** + * 设置Socket连接超时值,单位毫秒,缺省值不超时 + * + * @param connectionTimeout Socket连接超时值,单位毫秒,缺省值不超时 + * @return this + * @since 4.1.17 + */ + public MailAccount setConnectionTimeout(long connectionTimeout) { + this.connectionTimeout = connectionTimeout; + return this; + } + + /** + * 设置Socket写出超时值,单位毫秒,缺省值不超时 + * + * @param writeTimeout Socket写出超时值,单位毫秒,缺省值不超时 + * @return this + * @since 5.8.3 + */ + public MailAccount setWriteTimeout(long writeTimeout) { + this.writeTimeout = writeTimeout; + return this; + } + + /** + * 获取自定义属性列表 + * + * @return 自定义参数列表 + * @since 5.6.4 + */ + public Map getCustomProperty() { + return customProperty; + } + + /** + * 设置自定义属性,如mail.smtp.ssl.socketFactory + * + * @param key 属性名,空白被忽略 + * @param value 属性值, null被忽略 + * @return this + * @since 5.6.4 + */ + public MailAccount setCustomProperty(String key, Object value) { + if (StrUtil.isNotBlank(key) && ObjectUtil.isNotNull(value)) { + this.customProperty.put(key, value); + } + return this; + } + + /** + * 获得SMTP相关信息 + * + * @return {@link Properties} + */ + public Properties getSmtpProps() { + //全局系统参数 + System.setProperty(SPLIT_LONG_PARAMS, String.valueOf(this.splitlongparameters)); + + final Properties p = new Properties(); + p.put(MAIL_PROTOCOL, "smtp"); + p.put(SMTP_HOST, this.host); + p.put(SMTP_PORT, String.valueOf(this.port)); + p.put(SMTP_AUTH, String.valueOf(this.auth)); + if (this.timeout > 0) { + p.put(SMTP_TIMEOUT, String.valueOf(this.timeout)); + } + if (this.connectionTimeout > 0) { + p.put(SMTP_CONNECTION_TIMEOUT, String.valueOf(this.connectionTimeout)); + } + // issue#2355 + if (this.writeTimeout > 0) { + p.put(SMTP_WRITE_TIMEOUT, String.valueOf(this.writeTimeout)); + } + + p.put(MAIL_DEBUG, String.valueOf(this.debug)); + + if (this.starttlsEnable) { + //STARTTLS是对纯文本通信协议的扩展。它将纯文本连接升级为加密连接(TLS或SSL), 而不是使用一个单独的加密通信端口。 + p.put(STARTTLS_ENABLE, "true"); + + if (null == this.sslEnable) { + //为了兼容旧版本,当用户没有此项配置时,按照starttlsEnable开启状态时对待 + this.sslEnable = true; + } + } + + // SSL + if (null != this.sslEnable && this.sslEnable) { + p.put(SSL_ENABLE, "true"); + p.put(SOCKET_FACTORY, socketFactoryClass); + p.put(SOCKET_FACTORY_FALLBACK, String.valueOf(this.socketFactoryFallback)); + p.put(SOCKET_FACTORY_PORT, String.valueOf(this.socketFactoryPort)); + // issue#IZN95@Gitee,在Linux下需自定义SSL协议版本 + if (StrUtil.isNotBlank(this.sslProtocols)) { + p.put(SSL_PROTOCOLS, this.sslProtocols); + } + } + + // 补充自定义属性,允许自定属性覆盖已经设置的值 + p.putAll(this.customProperty); + + return p; + } + + /** + * 如果某些值为null,使用默认值 + * + * @return this + */ + public MailAccount defaultIfEmpty() { + // 去掉发件人的姓名部分 + final String fromAddress = InternalMailUtil.parseFirstAddress(this.from, this.charset).getAddress(); + + if (StrUtil.isBlank(this.host)) { + // 如果SMTP地址为空,默认使用smtp.<发件人邮箱后缀> + this.host = StrUtil.format("smtp.{}", StrUtil.subSuf(fromAddress, fromAddress.indexOf('@') + 1)); + } + if (StrUtil.isBlank(user)) { + // 如果用户名为空,默认为发件人(issue#I4FYVY@Gitee) + //this.user = StrUtil.subPre(fromAddress, fromAddress.indexOf('@')); + this.user = fromAddress; + } + if (null == this.auth) { + // 如果密码非空白,则使用认证模式 + this.auth = (false == StrUtil.isBlank(this.pass)); + } + if (null == this.port) { + // 端口在SSL状态下默认与socketFactoryPort一致,非SSL状态下默认为25 + this.port = (null != this.sslEnable && this.sslEnable) ? this.socketFactoryPort : 25; + } + if (null == this.charset) { + // 默认UTF-8编码 + this.charset = CharsetUtil.CHARSET_UTF_8; + } + + return this; + } + + @Override + public String toString() { + return "MailAccount [host=" + host + ", port=" + port + ", auth=" + auth + ", user=" + user + ", pass=" + (StrUtil.isEmpty(this.pass) ? "" : "******") + ", from=" + from + ", startttlsEnable=" + + starttlsEnable + ", socketFactoryClass=" + socketFactoryClass + ", socketFactoryFallback=" + socketFactoryFallback + ", socketFactoryPort=" + socketFactoryPort + "]"; + } +} diff --git a/ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/MailException.java b/ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/MailException.java new file mode 100644 index 0000000..cc199d4 --- /dev/null +++ b/ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/MailException.java @@ -0,0 +1,40 @@ +package org.dromara.common.mail.utils; + +import cn.hutool.core.exceptions.ExceptionUtil; +import cn.hutool.core.util.StrUtil; + +import java.io.Serial; + +/** + * 邮件异常 + * + * @author xiaoleilu + */ +public class MailException extends RuntimeException { + @Serial + private static final long serialVersionUID = 8247610319171014183L; + + public MailException(Throwable e) { + super(ExceptionUtil.getMessage(e), e); + } + + public MailException(String message) { + super(message); + } + + public MailException(String messageTemplate, Object... params) { + super(StrUtil.format(messageTemplate, params)); + } + + public MailException(String message, Throwable throwable) { + super(message, throwable); + } + + public MailException(String message, Throwable throwable, boolean enableSuppression, boolean writableStackTrace) { + super(message, throwable, enableSuppression, writableStackTrace); + } + + public MailException(Throwable throwable, String messageTemplate, Object... params) { + super(StrUtil.format(messageTemplate, params), throwable); + } +} diff --git a/ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/MailUtils.java b/ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/MailUtils.java new file mode 100644 index 0000000..040cc57 --- /dev/null +++ b/ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/MailUtils.java @@ -0,0 +1,467 @@ +package org.dromara.common.mail.utils; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.CharUtil; +import cn.hutool.core.util.StrUtil; +import jakarta.mail.Authenticator; +import jakarta.mail.Session; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.dromara.common.core.utils.SpringUtils; +import org.dromara.common.core.utils.StringUtils; + +import java.io.File; +import java.io.InputStream; +import java.util.Collection; +import java.util.List; +import java.util.Map; + + +/** + * 邮件工具类 + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class MailUtils { + + private static final MailAccount ACCOUNT = SpringUtils.getBean(MailAccount.class); + + /** + * 获取邮件发送实例 + */ + public static MailAccount getMailAccount() { + return ACCOUNT; + } + + /** + * 获取邮件发送实例 (自定义发送人以及授权码) + * + * @param user 发送人 + * @param pass 授权码 + */ + public static MailAccount getMailAccount(String from, String user, String pass) { + ACCOUNT.setFrom(StringUtils.blankToDefault(from, ACCOUNT.getFrom())); + ACCOUNT.setUser(StringUtils.blankToDefault(user, ACCOUNT.getUser())); + ACCOUNT.setPass(StringUtils.blankToDefault(pass, ACCOUNT.getPass())); + return ACCOUNT; + } + + /** + * 使用配置文件中设置的账户发送文本邮件,发送给单个或多个收件人
+ * 多个收件人可以使用逗号“,”分隔,也可以通过分号“;”分隔 + * + * @param to 收件人 + * @param subject 标题 + * @param content 正文 + * @param files 附件列表 + * @return message-id + * @since 3.2.0 + */ + public static String sendText(String to, String subject, String content, File... files) { + return send(to, subject, content, false, files); + } + + /** + * 使用配置文件中设置的账户发送HTML邮件,发送给单个或多个收件人
+ * 多个收件人可以使用逗号“,”分隔,也可以通过分号“;”分隔 + * + * @param to 收件人 + * @param subject 标题 + * @param content 正文 + * @param files 附件列表 + * @return message-id + * @since 3.2.0 + */ + public static String sendHtml(String to, String subject, String content, File... files) { + return send(to, subject, content, true, files); + } + + /** + * 使用配置文件中设置的账户发送邮件,发送单个或多个收件人
+ * 多个收件人可以使用逗号“,”分隔,也可以通过分号“;”分隔 + * + * @param to 收件人 + * @param subject 标题 + * @param content 正文 + * @param isHtml 是否为HTML + * @param files 附件列表 + * @return message-id + */ + public static String send(String to, String subject, String content, boolean isHtml, File... files) { + return send(splitAddress(to), subject, content, isHtml, files); + } + + /** + * 使用配置文件中设置的账户发送邮件,发送单个或多个收件人
+ * 多个收件人、抄送人、密送人可以使用逗号“,”分隔,也可以通过分号“;”分隔 + * + * @param to 收件人,可以使用逗号“,”分隔,也可以通过分号“;”分隔 + * @param cc 抄送人,可以使用逗号“,”分隔,也可以通过分号“;”分隔 + * @param bcc 密送人,可以使用逗号“,”分隔,也可以通过分号“;”分隔 + * @param subject 标题 + * @param content 正文 + * @param isHtml 是否为HTML + * @param files 附件列表 + * @return message-id + * @since 4.0.3 + */ + public static String send(String to, String cc, String bcc, String subject, String content, boolean isHtml, File... files) { + return send(splitAddress(to), splitAddress(cc), splitAddress(bcc), subject, content, isHtml, files); + } + + /** + * 使用配置文件中设置的账户发送文本邮件,发送给多人 + * + * @param tos 收件人列表 + * @param subject 标题 + * @param content 正文 + * @param files 附件列表 + * @return message-id + */ + public static String sendText(Collection tos, String subject, String content, File... files) { + return send(tos, subject, content, false, files); + } + + /** + * 使用配置文件中设置的账户发送HTML邮件,发送给多人 + * + * @param tos 收件人列表 + * @param subject 标题 + * @param content 正文 + * @param files 附件列表 + * @return message-id + * @since 3.2.0 + */ + public static String sendHtml(Collection tos, String subject, String content, File... files) { + return send(tos, subject, content, true, files); + } + + /** + * 使用配置文件中设置的账户发送邮件,发送给多人 + * + * @param tos 收件人列表 + * @param subject 标题 + * @param content 正文 + * @param isHtml 是否为HTML + * @param files 附件列表 + * @return message-id + */ + public static String send(Collection tos, String subject, String content, boolean isHtml, File... files) { + return send(tos, null, null, subject, content, isHtml, files); + } + + /** + * 使用配置文件中设置的账户发送邮件,发送给多人 + * + * @param tos 收件人列表 + * @param ccs 抄送人列表,可以为null或空 + * @param bccs 密送人列表,可以为null或空 + * @param subject 标题 + * @param content 正文 + * @param isHtml 是否为HTML + * @param files 附件列表 + * @return message-id + * @since 4.0.3 + */ + public static String send(Collection tos, Collection ccs, Collection bccs, String subject, String content, boolean isHtml, File... files) { + return send(getMailAccount(), true, tos, ccs, bccs, subject, content, null, isHtml, files); + } + + // ------------------------------------------------------------------------------------------------------------------------------- Custom MailAccount + + /** + * 发送邮件给多人 + * + * @param mailAccount 邮件认证对象 + * @param to 收件人,多个收件人逗号或者分号隔开 + * @param subject 标题 + * @param content 正文 + * @param isHtml 是否为HTML格式 + * @param files 附件列表 + * @return message-id + * @since 3.2.0 + */ + public static String send(MailAccount mailAccount, String to, String subject, String content, boolean isHtml, File... files) { + return send(mailAccount, splitAddress(to), subject, content, isHtml, files); + } + + /** + * 发送邮件给多人 + * + * @param mailAccount 邮件帐户信息 + * @param tos 收件人列表 + * @param subject 标题 + * @param content 正文 + * @param isHtml 是否为HTML格式 + * @param files 附件列表 + * @return message-id + */ + public static String send(MailAccount mailAccount, Collection tos, String subject, String content, boolean isHtml, File... files) { + return send(mailAccount, tos, null, null, subject, content, isHtml, files); + } + + /** + * 发送邮件给多人 + * + * @param mailAccount 邮件帐户信息 + * @param tos 收件人列表 + * @param ccs 抄送人列表,可以为null或空 + * @param bccs 密送人列表,可以为null或空 + * @param subject 标题 + * @param content 正文 + * @param isHtml 是否为HTML格式 + * @param files 附件列表 + * @return message-id + * @since 4.0.3 + */ + public static String send(MailAccount mailAccount, Collection tos, Collection ccs, Collection bccs, String subject, String content, boolean isHtml, File... files) { + return send(mailAccount, false, tos, ccs, bccs, subject, content, null, isHtml, files); + } + + /** + * 使用配置文件中设置的账户发送HTML邮件,发送给单个或多个收件人
+ * 多个收件人可以使用逗号“,”分隔,也可以通过分号“;”分隔 + * + * @param to 收件人 + * @param subject 标题 + * @param content 正文 + * @param imageMap 图片与占位符,占位符格式为cid:$IMAGE_PLACEHOLDER + * @param files 附件列表 + * @return message-id + * @since 3.2.0 + */ + public static String sendHtml(String to, String subject, String content, Map imageMap, File... files) { + return send(to, subject, content, imageMap, true, files); + } + + /** + * 使用配置文件中设置的账户发送邮件,发送单个或多个收件人
+ * 多个收件人可以使用逗号“,”分隔,也可以通过分号“;”分隔 + * + * @param to 收件人 + * @param subject 标题 + * @param content 正文 + * @param imageMap 图片与占位符,占位符格式为cid:$IMAGE_PLACEHOLDER + * @param isHtml 是否为HTML + * @param files 附件列表 + * @return message-id + */ + public static String send(String to, String subject, String content, Map imageMap, boolean isHtml, File... files) { + return send(splitAddress(to), subject, content, imageMap, isHtml, files); + } + + /** + * 使用配置文件中设置的账户发送邮件,发送单个或多个收件人
+ * 多个收件人、抄送人、密送人可以使用逗号“,”分隔,也可以通过分号“;”分隔 + * + * @param to 收件人,可以使用逗号“,”分隔,也可以通过分号“;”分隔 + * @param cc 抄送人,可以使用逗号“,”分隔,也可以通过分号“;”分隔 + * @param bcc 密送人,可以使用逗号“,”分隔,也可以通过分号“;”分隔 + * @param subject 标题 + * @param content 正文 + * @param imageMap 图片与占位符,占位符格式为cid:$IMAGE_PLACEHOLDER + * @param isHtml 是否为HTML + * @param files 附件列表 + * @return message-id + * @since 4.0.3 + */ + public static String send(String to, String cc, String bcc, String subject, String content, Map imageMap, boolean isHtml, File... files) { + return send(splitAddress(to), splitAddress(cc), splitAddress(bcc), subject, content, imageMap, isHtml, files); + } + + /** + * 使用配置文件中设置的账户发送HTML邮件,发送给多人 + * + * @param tos 收件人列表 + * @param subject 标题 + * @param content 正文 + * @param imageMap 图片与占位符,占位符格式为cid:$IMAGE_PLACEHOLDER + * @param files 附件列表 + * @return message-id + * @since 3.2.0 + */ + public static String sendHtml(Collection tos, String subject, String content, Map imageMap, File... files) { + return send(tos, subject, content, imageMap, true, files); + } + + /** + * 使用配置文件中设置的账户发送邮件,发送给多人 + * + * @param tos 收件人列表 + * @param subject 标题 + * @param content 正文 + * @param imageMap 图片与占位符,占位符格式为cid:$IMAGE_PLACEHOLDER + * @param isHtml 是否为HTML + * @param files 附件列表 + * @return message-id + */ + public static String send(Collection tos, String subject, String content, Map imageMap, boolean isHtml, File... files) { + return send(tos, null, null, subject, content, imageMap, isHtml, files); + } + + /** + * 使用配置文件中设置的账户发送邮件,发送给多人 + * + * @param tos 收件人列表 + * @param ccs 抄送人列表,可以为null或空 + * @param bccs 密送人列表,可以为null或空 + * @param subject 标题 + * @param content 正文 + * @param imageMap 图片与占位符,占位符格式为cid:$IMAGE_PLACEHOLDER + * @param isHtml 是否为HTML + * @param files 附件列表 + * @return message-id + * @since 4.0.3 + */ + public static String send(Collection tos, Collection ccs, Collection bccs, String subject, String content, Map imageMap, boolean isHtml, File... files) { + return send(getMailAccount(), true, tos, ccs, bccs, subject, content, imageMap, isHtml, files); + } + + // ------------------------------------------------------------------------------------------------------------------------------- Custom MailAccount + + /** + * 发送邮件给多人 + * + * @param mailAccount 邮件认证对象 + * @param to 收件人,多个收件人逗号或者分号隔开 + * @param subject 标题 + * @param content 正文 + * @param imageMap 图片与占位符,占位符格式为cid:$IMAGE_PLACEHOLDER + * @param isHtml 是否为HTML格式 + * @param files 附件列表 + * @return message-id + * @since 3.2.0 + */ + public static String send(MailAccount mailAccount, String to, String subject, String content, Map imageMap, boolean isHtml, File... files) { + return send(mailAccount, splitAddress(to), subject, content, imageMap, isHtml, files); + } + + /** + * 发送邮件给多人 + * + * @param mailAccount 邮件帐户信息 + * @param tos 收件人列表 + * @param subject 标题 + * @param content 正文 + * @param imageMap 图片与占位符,占位符格式为cid:$IMAGE_PLACEHOLDER + * @param isHtml 是否为HTML格式 + * @param files 附件列表 + * @return message-id + * @since 4.6.3 + */ + public static String send(MailAccount mailAccount, Collection tos, String subject, String content, Map imageMap, boolean isHtml, File... files) { + return send(mailAccount, tos, null, null, subject, content, imageMap, isHtml, files); + } + + /** + * 发送邮件给多人 + * + * @param mailAccount 邮件帐户信息 + * @param tos 收件人列表 + * @param ccs 抄送人列表,可以为null或空 + * @param bccs 密送人列表,可以为null或空 + * @param subject 标题 + * @param content 正文 + * @param imageMap 图片与占位符,占位符格式为cid:$IMAGE_PLACEHOLDER + * @param isHtml 是否为HTML格式 + * @param files 附件列表 + * @return message-id + * @since 4.6.3 + */ + public static String send(MailAccount mailAccount, Collection tos, Collection ccs, Collection bccs, String subject, String content, Map imageMap, + boolean isHtml, File... files) { + return send(mailAccount, false, tos, ccs, bccs, subject, content, imageMap, isHtml, files); + } + + /** + * 根据配置文件,获取邮件客户端会话 + * + * @param mailAccount 邮件账户配置 + * @param isSingleton 是否单例(全局共享会话) + * @return {@link Session} + * @since 5.5.7 + */ + public static Session getSession(MailAccount mailAccount, boolean isSingleton) { + Authenticator authenticator = null; + if (mailAccount.isAuth()) { + authenticator = new UserPassAuthenticator(mailAccount.getUser(), mailAccount.getPass()); + } + + return isSingleton ? Session.getDefaultInstance(mailAccount.getSmtpProps(), authenticator) // + : Session.getInstance(mailAccount.getSmtpProps(), authenticator); + } + + // ------------------------------------------------------------------------------------------------------------------------ Private method start + + /** + * 发送邮件给多人 + * + * @param mailAccount 邮件帐户信息 + * @param useGlobalSession 是否全局共享Session + * @param tos 收件人列表 + * @param ccs 抄送人列表,可以为null或空 + * @param bccs 密送人列表,可以为null或空 + * @param subject 标题 + * @param content 正文 + * @param imageMap 图片与占位符,占位符格式为cid:${cid} + * @param isHtml 是否为HTML格式 + * @param files 附件列表 + * @return message-id + * @since 4.6.3 + */ + private static String send(MailAccount mailAccount, boolean useGlobalSession, Collection tos, Collection ccs, Collection bccs, String subject, String content, + Map imageMap, boolean isHtml, File... files) { + final Mail mail = Mail.create(mailAccount).setUseGlobalSession(useGlobalSession); + + // 可选抄送人 + if (CollUtil.isNotEmpty(ccs)) { + mail.setCcs(ccs.toArray(new String[0])); + } + // 可选密送人 + if (CollUtil.isNotEmpty(bccs)) { + mail.setBccs(bccs.toArray(new String[0])); + } + + mail.setTos(tos.toArray(new String[0])); + mail.setTitle(subject); + mail.setContent(content); + mail.setHtml(isHtml); + mail.setFiles(files); + + // 图片 + if (MapUtil.isNotEmpty(imageMap)) { + for (Map.Entry entry : imageMap.entrySet()) { + mail.addImage(entry.getKey(), entry.getValue()); + // 关闭流 + IoUtil.close(entry.getValue()); + } + } + + return mail.send(); + } + + /** + * 将多个联系人转为列表,分隔符为逗号或者分号 + * + * @param addresses 多个联系人,如果为空返回null + * @return 联系人列表 + */ + private static List splitAddress(String addresses) { + if (StrUtil.isBlank(addresses)) { + return null; + } + + List result; + if (StrUtil.contains(addresses, CharUtil.COMMA)) { + result = StrUtil.splitTrim(addresses, CharUtil.COMMA); + } else if (StrUtil.contains(addresses, ';')) { + result = StrUtil.splitTrim(addresses, ';'); + } else { + result = CollUtil.newArrayList(addresses); + } + return result; + } + // ------------------------------------------------------------------------------------------------------------------------ Private method end + +} diff --git a/ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/UserPassAuthenticator.java b/ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/UserPassAuthenticator.java new file mode 100644 index 0000000..fbbe5e3 --- /dev/null +++ b/ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/UserPassAuthenticator.java @@ -0,0 +1,33 @@ +package org.dromara.common.mail.utils; + +import jakarta.mail.Authenticator; +import jakarta.mail.PasswordAuthentication; + +/** + * 用户名密码验证器 + * + * @author looly + * @since 3.1.2 + */ +public class UserPassAuthenticator extends Authenticator { + + private final String user; + private final String pass; + + /** + * 构造 + * + * @param user 用户名 + * @param pass 密码 + */ + public UserPassAuthenticator(String user, String pass) { + this.user = user; + this.pass = pass; + } + + @Override + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication(this.user, this.pass); + } + +} diff --git a/ruoyi-common/ruoyi-common-mybatis/pom.xml b/ruoyi-common/ruoyi-common-mybatis/pom.xml new file mode 100644 index 0000000..64876fb --- /dev/null +++ b/ruoyi-common/ruoyi-common-mybatis/pom.xml @@ -0,0 +1,82 @@ + + + + org.dromara + ruoyi-common + ${revision} + + 4.0.0 + + ruoyi-common-mybatis + + + ruoyi-common-mybatis 数据库服务 + + + + + org.dromara + ruoyi-common-satoken + + + + org.dromara + ruoyi-common-dubbo + true + + + + org.mybatis.spring.boot + mybatis-spring-boot-starter + + + + com.baomidou + mybatis-plus-boot-starter + + + org.mybatis + mybatis-spring + + + + + + + p6spy + p6spy + + + + + com.baomidou + dynamic-datasource-spring-boot3-starter + ${dynamic-ds.version} + + + + + com.mysql + mysql-connector-j + + + + com.oracle.database.jdbc + ojdbc8 + + + + org.postgresql + postgresql + + + + com.microsoft.sqlserver + mssql-jdbc + + + + + diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/annotation/DataColumn.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/annotation/DataColumn.java new file mode 100644 index 0000000..aca470f --- /dev/null +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/annotation/DataColumn.java @@ -0,0 +1,28 @@ +package org.dromara.common.mybatis.annotation; + +import java.lang.annotation.*; + +/** + * 数据权限 + * + * 一个注解只能对应一个模板 + * + * @author Lion Li + * @version 3.5.0 + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface DataColumn { + + /** + * 占位符关键字 + */ + String[] key() default "deptName"; + + /** + * 占位符替换值 + */ + String[] value() default "dept_id"; + +} diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/annotation/DataPermission.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/annotation/DataPermission.java new file mode 100644 index 0000000..f4351e3 --- /dev/null +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/annotation/DataPermission.java @@ -0,0 +1,18 @@ +package org.dromara.common.mybatis.annotation; + +import java.lang.annotation.*; + +/** + * 数据权限组 + * + * @author Lion Li + * @version 3.5.0 + */ +@Target({ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface DataPermission { + + DataColumn[] value(); + +} diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/config/MybatisPlusConfiguration.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/config/MybatisPlusConfiguration.java new file mode 100644 index 0000000..e0ec34b --- /dev/null +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/config/MybatisPlusConfiguration.java @@ -0,0 +1,116 @@ +package org.dromara.common.mybatis.config; + +import cn.hutool.core.net.NetUtil; +import com.baomidou.mybatisplus.autoconfigure.DdlApplicationRunner; +import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration; +import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; +import com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator; +import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator; +import com.baomidou.mybatisplus.extension.ddl.IDdl; +import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; +import org.dromara.common.core.factory.YmlPropertySourceFactory; +import org.dromara.common.mybatis.handler.InjectionMetaObjectHandler; +import org.dromara.common.mybatis.interceptor.PlusDataPermissionInterceptor; +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.PropertySource; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +import java.util.List; + +/** + * mybatis-plus配置类(下方注释有插件介绍) + * + * @author Lion Li + */ +@EnableTransactionManagement(proxyTargetClass = true) +@AutoConfiguration(before = MybatisPlusAutoConfiguration.class) +@MapperScan("${mybatis-plus.mapperPackage}") +@PropertySource(value = "classpath:common-mybatis.yml", factory = YmlPropertySourceFactory.class) +public class MybatisPlusConfiguration { + + @Bean + public MybatisPlusInterceptor mybatisPlusInterceptor() { + MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); + // 数据权限处理 + interceptor.addInnerInterceptor(dataPermissionInterceptor()); + // 分页插件 + interceptor.addInnerInterceptor(paginationInnerInterceptor()); + // 乐观锁插件 + interceptor.addInnerInterceptor(optimisticLockerInnerInterceptor()); + return interceptor; + } + + /** + * 数据权限拦截器 + */ + public PlusDataPermissionInterceptor dataPermissionInterceptor() { + return new PlusDataPermissionInterceptor(); + } + + /** + * 分页插件,自动识别数据库类型 + */ + public PaginationInnerInterceptor paginationInnerInterceptor() { + PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(); + // 设置最大单页限制数量,默认 500 条,-1 不受限制 + paginationInnerInterceptor.setMaxLimit(-1L); + // 分页合理化 + paginationInnerInterceptor.setOverflow(true); + return paginationInnerInterceptor; + } + + /** + * 乐观锁插件 + */ + public OptimisticLockerInnerInterceptor optimisticLockerInnerInterceptor() { + return new OptimisticLockerInnerInterceptor(); + } + + /** + * 元对象字段填充控制器 + */ + @Bean + public MetaObjectHandler metaObjectHandler() { + return new InjectionMetaObjectHandler(); + } + + /** + * 使用网卡信息绑定雪花生成器 + * 防止集群雪花ID重复 + */ + @Bean + public IdentifierGenerator idGenerator() { + return new DefaultIdentifierGenerator(NetUtil.getLocalhost()); + } + + /** + * PaginationInnerInterceptor 分页插件,自动识别数据库类型 + * https://baomidou.com/pages/97710a/ + * OptimisticLockerInnerInterceptor 乐观锁插件 + * https://baomidou.com/pages/0d93c0/ + * MetaObjectHandler 元对象字段填充控制器 + * https://baomidou.com/pages/4c6bcf/ + * ISqlInjector sql注入器 + * https://baomidou.com/pages/42ea4a/ + * BlockAttackInnerInterceptor 如果是对全表的删除或更新操作,就会终止该操作 + * https://baomidou.com/pages/f9a237/ + * IllegalSQLInnerInterceptor sql性能规范插件(垃圾SQL拦截) + * IdentifierGenerator 自定义主键策略 + * https://baomidou.com/pages/568eb2/ + * TenantLineInnerInterceptor 多租户插件 + * https://baomidou.com/pages/aef2f2/ + * DynamicTableNameInnerInterceptor 动态表名插件 + * https://baomidou.com/pages/2a45ff/ + */ + + @Bean + public DdlApplicationRunner ddlApplicationRunner(@Autowired(required = false) List ddlList) { + return new DdlApplicationRunner(ddlList); + } + +} diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/domain/BaseEntity.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/domain/BaseEntity.java new file mode 100644 index 0000000..820b49a --- /dev/null +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/domain/BaseEntity.java @@ -0,0 +1,71 @@ +package org.dromara.common.mybatis.core.domain; + +import com.baomidou.mybatisplus.annotation.FieldFill; +import com.baomidou.mybatisplus.annotation.TableField; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +/** + * Entity基类 + * + * @author Lion Li + */ + +@Data +public class BaseEntity implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 搜索值 + */ + @JsonIgnore + @TableField(exist = false) + private String searchValue; + + /** + * 创建部门 + */ + @TableField(fill = FieldFill.INSERT) + private Long createDept; + + /** + * 创建者 + */ + @TableField(fill = FieldFill.INSERT) + private Long createBy; + + /** + * 创建时间 + */ + @TableField(fill = FieldFill.INSERT) + private Date createTime; + + /** + * 更新者 + */ + @TableField(fill = FieldFill.INSERT_UPDATE) + private Long updateBy; + + /** + * 更新时间 + */ + @TableField(fill = FieldFill.INSERT_UPDATE) + private Date updateTime; + + /** + * 请求参数 + */ + @JsonInclude(JsonInclude.Include.NON_EMPTY) + @TableField(exist = false) + private Map params = new HashMap<>(); + +} diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/mapper/BaseMapperPlus.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/mapper/BaseMapperPlus.java new file mode 100644 index 0000000..b14e2d2 --- /dev/null +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/mapper/BaseMapperPlus.java @@ -0,0 +1,198 @@ +package org.dromara.common.mybatis.core.mapper; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.conditions.Wrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.core.toolkit.ReflectionKit; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.toolkit.Db; +import org.apache.ibatis.logging.Log; +import org.apache.ibatis.logging.LogFactory; +import org.dromara.common.core.utils.MapstructUtils; + +import java.io.Serializable; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * 自定义 Mapper 接口, 实现 自定义扩展 + * + * @param table 泛型 + * @param vo 泛型 + * @author Lion Li + * @since 2021-05-13 + */ +@SuppressWarnings("unchecked") +public interface BaseMapperPlus extends BaseMapper { + + Log log = LogFactory.getLog(BaseMapperPlus.class); + + default Class currentVoClass() { + return (Class) ReflectionKit.getSuperClassGenericType(this.getClass(), BaseMapperPlus.class, 1); + } + + default Class currentModelClass() { + return (Class) ReflectionKit.getSuperClassGenericType(this.getClass(), BaseMapperPlus.class, 0); + } + + default List selectList() { + return this.selectList(new QueryWrapper<>()); + } + + /** + * 批量插入 + */ + default boolean insertBatch(Collection entityList) { + return Db.saveBatch(entityList); + } + + /** + * 批量更新 + */ + default boolean updateBatchById(Collection entityList) { + return Db.updateBatchById(entityList); + } + + /** + * 批量插入或更新 + */ + default boolean insertOrUpdateBatch(Collection entityList) { + return Db.saveOrUpdateBatch(entityList); + } + + /** + * 批量插入(包含限制条数) + */ + default boolean insertBatch(Collection entityList, int batchSize) { + return Db.saveBatch(entityList, batchSize); + } + + /** + * 批量更新(包含限制条数) + */ + default boolean updateBatchById(Collection entityList, int batchSize) { + return Db.updateBatchById(entityList, batchSize); + } + + /** + * 批量插入或更新(包含限制条数) + */ + default boolean insertOrUpdateBatch(Collection entityList, int batchSize) { + return Db.saveOrUpdateBatch(entityList, batchSize); + } + + /** + * 插入或更新(包含限制条数) + */ + default boolean insertOrUpdate(T entity) { + return Db.saveOrUpdate(entity); + } + + default V selectVoById(Serializable id) { + return selectVoById(id, this.currentVoClass()); + } + + /** + * 根据 ID 查询 + */ + default C selectVoById(Serializable id, Class voClass) { + T obj = this.selectById(id); + if (ObjectUtil.isNull(obj)) { + return null; + } + return MapstructUtils.convert(obj, voClass); + } + + default List selectVoBatchIds(Collection idList) { + return selectVoBatchIds(idList, this.currentVoClass()); + } + + /** + * 查询(根据ID 批量查询) + */ + default List selectVoBatchIds(Collection idList, Class voClass) { + List list = this.selectBatchIds(idList); + if (CollUtil.isEmpty(list)) { + return CollUtil.newArrayList(); + } + return MapstructUtils.convert(list, voClass); + } + + default List selectVoByMap(Map map) { + return selectVoByMap(map, this.currentVoClass()); + } + + /** + * 查询(根据 columnMap 条件) + */ + default List selectVoByMap(Map map, Class voClass) { + List list = this.selectByMap(map); + if (CollUtil.isEmpty(list)) { + return CollUtil.newArrayList(); + } + return MapstructUtils.convert(list, voClass); + } + + default V selectVoOne(Wrapper wrapper) { + return selectVoOne(wrapper, this.currentVoClass()); + } + + /** + * 根据 entity 条件,查询一条记录 + */ + default C selectVoOne(Wrapper wrapper, Class voClass) { + T obj = this.selectOne(wrapper); + if (ObjectUtil.isNull(obj)) { + return null; + } + return MapstructUtils.convert(obj, voClass); + } + + default List selectVoList() { + return selectVoList(new QueryWrapper<>(), this.currentVoClass()); + } + + default List selectVoList(Wrapper wrapper) { + return selectVoList(wrapper, this.currentVoClass()); + } + + /** + * 根据 entity 条件,查询全部记录 + */ + default List selectVoList(Wrapper wrapper, Class voClass) { + List list = this.selectList(wrapper); + if (CollUtil.isEmpty(list)) { + return CollUtil.newArrayList(); + } + return MapstructUtils.convert(list, voClass); + } + + default

> P selectVoPage(IPage page, Wrapper wrapper) { + return selectVoPage(page, wrapper, this.currentVoClass()); + } + + /** + * 分页查询VO + */ + default > P selectVoPage(IPage page, Wrapper wrapper, Class voClass) { + List list = this.selectList(page, wrapper); + IPage voPage = new Page<>(page.getCurrent(), page.getSize(), page.getTotal()); + if (CollUtil.isEmpty(list)) { + return (P) voPage; + } + voPage.setRecords(MapstructUtils.convert(list, voClass)); + return (P) voPage; + } + + default List selectObjs(Wrapper wrapper, Function mapper) { + return this.selectObjs(wrapper).stream().filter(Objects::nonNull).map(mapper).collect(Collectors.toList()); + } + +} diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/page/PageQuery.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/page/PageQuery.java new file mode 100644 index 0000000..8ef4a57 --- /dev/null +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/page/PageQuery.java @@ -0,0 +1,114 @@ +package org.dromara.common.mybatis.core.page; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.metadata.OrderItem; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import org.dromara.common.core.exception.ServiceException; +import org.dromara.common.core.utils.StringUtils; +import org.dromara.common.core.utils.sql.SqlUtil; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * 分页查询实体类 + * + * @author Lion Li + */ + +@Data +public class PageQuery implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 分页大小 + */ + private Integer pageSize; + + /** + * 当前页数 + */ + private Integer pageNum; + + /** + * 排序列 + */ + private String orderByColumn; + + /** + * 排序的方向desc或者asc + */ + private String isAsc; + + /** + * 当前记录起始索引 默认值 + */ + public static final int DEFAULT_PAGE_NUM = 1; + + /** + * 每页显示记录数 默认值 默认查全部 + */ + public static final int DEFAULT_PAGE_SIZE = Integer.MAX_VALUE; + + public Page build() { + Integer pageNum = ObjectUtil.defaultIfNull(getPageNum(), DEFAULT_PAGE_NUM); + Integer pageSize = ObjectUtil.defaultIfNull(getPageSize(), DEFAULT_PAGE_SIZE); + if (pageNum <= 0) { + pageNum = DEFAULT_PAGE_NUM; + } + Page page = new Page<>(pageNum, pageSize); + List orderItems = buildOrderItem(); + if (CollUtil.isNotEmpty(orderItems)) { + page.addOrder(orderItems); + } + return page; + } + + /** + * 构建排序 + * + * 支持的用法如下: + * {isAsc:"asc",orderByColumn:"id"} order by id asc + * {isAsc:"asc",orderByColumn:"id,createTime"} order by id asc,create_time asc + * {isAsc:"desc",orderByColumn:"id,createTime"} order by id desc,create_time desc + * {isAsc:"asc,desc",orderByColumn:"id,createTime"} order by id asc,create_time desc + */ + private List buildOrderItem() { + if (StringUtils.isBlank(orderByColumn) || StringUtils.isBlank(isAsc)) { + return null; + } + String orderBy = SqlUtil.escapeOrderBySql(orderByColumn); + orderBy = StringUtils.toUnderScoreCase(orderBy); + + // 兼容前端排序类型 + isAsc = StringUtils.replaceEach(isAsc, new String[]{"ascending", "descending"}, new String[]{"asc", "desc"}); + + String[] orderByArr = orderBy.split(StringUtils.SEPARATOR); + String[] isAscArr = isAsc.split(StringUtils.SEPARATOR); + if (isAscArr.length != 1 && isAscArr.length != orderByArr.length) { + throw new ServiceException("排序参数有误"); + } + + List list = new ArrayList<>(); + // 每个字段各自排序 + for (int i = 0; i < orderByArr.length; i++) { + String orderByStr = orderByArr[i]; + String isAscStr = isAscArr.length == 1 ? isAscArr[0] : isAscArr[i]; + if ("asc".equals(isAscStr)) { + list.add(OrderItem.asc(orderByStr)); + } else if ("desc".equals(isAscStr)) { + list.add(OrderItem.desc(orderByStr)); + } else { + throw new ServiceException("排序参数有误"); + } + } + return list; + } + +} diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/page/TableDataInfo.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/page/TableDataInfo.java new file mode 100644 index 0000000..a4b6799 --- /dev/null +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/page/TableDataInfo.java @@ -0,0 +1,81 @@ +package org.dromara.common.mybatis.core.page; + +import cn.hutool.http.HttpStatus; +import com.baomidou.mybatisplus.core.metadata.IPage; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +/** + * 表格分页数据对象 + * + * @author Lion Li + */ + +@Data +@NoArgsConstructor +public class TableDataInfo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 总记录数 + */ + private long total; + + /** + * 列表数据 + */ + private List rows; + + /** + * 消息状态码 + */ + private int code; + + /** + * 消息内容 + */ + private String msg; + + /** + * 分页 + * + * @param list 列表数据 + * @param total 总记录数 + */ + public TableDataInfo(List list, long total) { + this.rows = list; + this.total = total; + } + + public static TableDataInfo build(IPage page) { + TableDataInfo rspData = new TableDataInfo<>(); + rspData.setCode(HttpStatus.HTTP_OK); + rspData.setMsg("查询成功"); + rspData.setRows(page.getRecords()); + rspData.setTotal(page.getTotal()); + return rspData; + } + + public static TableDataInfo build(List list) { + TableDataInfo rspData = new TableDataInfo<>(); + rspData.setCode(HttpStatus.HTTP_OK); + rspData.setMsg("查询成功"); + rspData.setRows(list); + rspData.setTotal(list.size()); + return rspData; + } + + public static TableDataInfo build() { + TableDataInfo rspData = new TableDataInfo<>(); + rspData.setCode(HttpStatus.HTTP_OK); + rspData.setMsg("查询成功"); + return rspData; + } + +} diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/enums/DataBaseType.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/enums/DataBaseType.java new file mode 100644 index 0000000..93487e9 --- /dev/null +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/enums/DataBaseType.java @@ -0,0 +1,49 @@ +package org.dromara.common.mybatis.enums; + +import org.dromara.common.core.utils.StringUtils; +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 数据库类型 + * + * @author Lion Li + */ +@Getter +@AllArgsConstructor +public enum DataBaseType { + + /** + * MySQL + */ + MY_SQL("MySQL"), + + /** + * Oracle + */ + ORACLE("Oracle"), + + /** + * PostgreSQL + */ + POSTGRE_SQL("PostgreSQL"), + + /** + * SQL Server + */ + SQL_SERVER("Microsoft SQL Server"); + + private final String type; + + public static DataBaseType find(String databaseProductName) { + if (StringUtils.isBlank(databaseProductName)) { + return null; + } + for (DataBaseType type : values()) { + if (type.getType().equals(databaseProductName)) { + return type; + } + } + return null; + } +} diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/enums/DataScopeType.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/enums/DataScopeType.java new file mode 100644 index 0000000..9ea66b0 --- /dev/null +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/enums/DataScopeType.java @@ -0,0 +1,73 @@ +package org.dromara.common.mybatis.enums; + +import org.dromara.common.core.utils.StringUtils; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.dromara.common.mybatis.helper.DataPermissionHelper; + +/** + * 数据权限类型 + *

+ * 语法支持 spel 模板表达式 + *

+ * 内置数据 user 当前用户 内容参考 LoginUser + * 如需扩展数据 可使用 {@link DataPermissionHelper} 操作 + * 内置服务 sdss 系统数据权限服务 内容参考 SysDataScopeService + * 如需扩展更多自定义服务 可以参考 sdss 自行编写 + * + * @author Lion Li + * @version 3.5.0 + */ +@Getter +@AllArgsConstructor +public enum DataScopeType { + + /** + * 全部数据权限 + */ + ALL("1", "", ""), + + /** + * 自定数据权限 + */ + CUSTOM("2", " #{#deptName} IN ( #{@sdss.getRoleCustom( #user.roleId )} ) ", " 1 = 0 "), + + /** + * 部门数据权限 + */ + DEPT("3", " #{#deptName} = #{#user.deptId} ", " 1 = 0 "), + + /** + * 部门及以下数据权限 + */ + DEPT_AND_CHILD("4", " #{#deptName} IN ( #{@sdss.getDeptAndChild( #user.deptId )} )", " 1 = 0 "), + + /** + * 仅本人数据权限 + */ + SELF("5", " #{#userName} = #{#user.userId} ", " 1 = 0 "); + + private final String code; + + /** + * 语法 采用 spel 模板表达式 + */ + private final String sqlTemplate; + + /** + * 不满足 sqlTemplate 则填充 + */ + private final String elseSql; + + public static DataScopeType findCode(String code) { + if (StringUtils.isBlank(code)) { + return null; + } + for (DataScopeType type : values()) { + if (type.getCode().equals(code)) { + return type; + } + } + return null; + } +} diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/filter/DubboDataPermissionFilter.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/filter/DubboDataPermissionFilter.java new file mode 100644 index 0000000..61e549d --- /dev/null +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/filter/DubboDataPermissionFilter.java @@ -0,0 +1,28 @@ +package org.dromara.common.mybatis.filter; + +import org.dromara.common.mybatis.helper.DataPermissionHelper; +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 java.util.Map; + +/** + * dubbo 数据权限参数传递 + * + * @author Lion Li + */ +@Slf4j +@Activate(group = {CommonConstants.CONSUMER}) +public class DubboDataPermissionFilter implements Filter { + + @Override + public Result invoke(Invoker invoker, Invocation invocation) throws RpcException { + RpcServiceContext context = RpcContext.getServiceContext(); + Map dataPermissionContext = DataPermissionHelper.getContext(); + context.setObjectAttachment(DataPermissionHelper.DATA_PERMISSION_KEY, dataPermissionContext); + return invoker.invoke(invocation); + } + +} diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/handler/InjectionMetaObjectHandler.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/handler/InjectionMetaObjectHandler.java new file mode 100644 index 0000000..c4ff3bf --- /dev/null +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/handler/InjectionMetaObjectHandler.java @@ -0,0 +1,82 @@ +package org.dromara.common.mybatis.handler; + +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.http.HttpStatus; +import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; +import lombok.extern.slf4j.Slf4j; +import org.apache.ibatis.reflection.MetaObject; +import org.dromara.common.core.exception.ServiceException; +import org.dromara.common.mybatis.core.domain.BaseEntity; +import org.dromara.common.satoken.utils.LoginHelper; +import org.dromara.system.api.model.LoginUser; + +import java.util.Date; + +/** + * MP注入处理器 + * + * @author Lion Li + */ +@Slf4j +public class InjectionMetaObjectHandler implements MetaObjectHandler { + + @Override + public void insertFill(MetaObject metaObject) { + try { + if (ObjectUtil.isNotNull(metaObject) + && metaObject.getOriginalObject() instanceof BaseEntity baseEntity) { + Date current = ObjectUtil.isNotNull(baseEntity.getCreateTime()) + ? baseEntity.getCreateTime() : new Date(); + baseEntity.setCreateTime(current); + baseEntity.setUpdateTime(current); + LoginUser loginUser = getLoginUser(); + if (ObjectUtil.isNotNull(loginUser)) { + Long userId = ObjectUtil.isNotNull(baseEntity.getCreateBy()) + ? baseEntity.getCreateBy() : loginUser.getUserId(); + // 当前已登录 且 创建人为空 则填充 + baseEntity.setCreateBy(userId); + // 当前已登录 且 更新人为空 则填充 + baseEntity.setUpdateBy(userId); + baseEntity.setCreateDept(ObjectUtil.isNotNull(baseEntity.getCreateDept()) + ? baseEntity.getCreateDept() : loginUser.getDeptId()); + } + } + } catch (Exception e) { + throw new ServiceException("自动注入异常 => " + e.getMessage(), HttpStatus.HTTP_UNAUTHORIZED); + } + } + + @Override + public void updateFill(MetaObject metaObject) { + try { + if (ObjectUtil.isNotNull(metaObject) + && metaObject.getOriginalObject() instanceof BaseEntity baseEntity) { + Date current = new Date(); + // 更新时间填充(不管为不为空) + baseEntity.setUpdateTime(current); + LoginUser loginUser = getLoginUser(); + // 当前已登录 更新人填充(不管为不为空) + if (ObjectUtil.isNotNull(loginUser)) { + baseEntity.setUpdateBy(loginUser.getUserId()); + } + } + } catch (Exception e) { + throw new ServiceException("自动注入异常 => " + e.getMessage(), HttpStatus.HTTP_UNAUTHORIZED); + } + } + + /** + * 获取登录用户 + */ + private LoginUser getLoginUser() { + LoginUser loginUser; + try { + loginUser = LoginHelper.getLoginUser(); + } catch (Exception e) { + log.warn("自动注入警告 => 用户未登录"); + return null; + } + return loginUser; + } + +} diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/handler/MybatisExceptionHandler.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/handler/MybatisExceptionHandler.java new file mode 100644 index 0000000..45d474f --- /dev/null +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/handler/MybatisExceptionHandler.java @@ -0,0 +1,46 @@ +package org.dromara.common.mybatis.handler; + +import org.dromara.common.core.domain.R; +import lombok.extern.slf4j.Slf4j; +import org.mybatis.spring.MyBatisSystemException; +import org.springframework.dao.DuplicateKeyException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +import jakarta.servlet.http.HttpServletRequest; + +/** + * Mybatis异常处理器 + * + * @author Lion Li + */ +@Slf4j +@RestControllerAdvice +public class MybatisExceptionHandler { + + /** + * 主键或UNIQUE索引,数据重复异常 + */ + @ExceptionHandler(DuplicateKeyException.class) + public R handleDuplicateKeyException(DuplicateKeyException e, HttpServletRequest request) { + String requestURI = request.getRequestURI(); + log.error("请求地址'{}',数据库中已存在记录'{}'", requestURI, e.getMessage()); + return R.fail("数据库中已存在该记录,请联系管理员确认"); + } + + /** + * Mybatis系统异常 通用处理 + */ + @ExceptionHandler(MyBatisSystemException.class) + public R handleCannotFindDataSourceException(MyBatisSystemException e, HttpServletRequest request) { + String requestURI = request.getRequestURI(); + String message = e.getMessage(); + if (message != null && "CannotFindDataSourceException".contains(message)) { + log.error("请求地址'{}', 未找到数据源", requestURI); + return R.fail("未找到数据源,请联系管理员确认"); + } + log.error("请求地址'{}', Mybatis系统异常", requestURI, e); + return R.fail(message); + } + +} diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/handler/PlusDataPermissionHandler.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/handler/PlusDataPermissionHandler.java new file mode 100644 index 0000000..43c9e1b --- /dev/null +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/handler/PlusDataPermissionHandler.java @@ -0,0 +1,240 @@ +package org.dromara.common.mybatis.handler; + +import cn.hutool.core.annotation.AnnotationUtil; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ClassUtil; +import cn.hutool.core.util.ObjectUtil; +import lombok.extern.slf4j.Slf4j; +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.expression.Expression; +import net.sf.jsqlparser.expression.Parenthesis; +import net.sf.jsqlparser.expression.operators.conditional.AndExpression; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import org.dromara.common.core.enums.BusRole; +import org.dromara.common.core.exception.ServiceException; +import org.dromara.common.core.utils.SpringUtils; +import org.dromara.common.core.utils.StreamUtils; +import org.dromara.common.core.utils.StringUtils; +import org.dromara.common.mybatis.annotation.DataColumn; +import org.dromara.common.mybatis.annotation.DataPermission; +import org.dromara.common.mybatis.enums.DataScopeType; +import org.dromara.common.mybatis.helper.DataPermissionHelper; +import org.dromara.common.satoken.utils.LoginHelper; +import org.dromara.system.api.model.LoginUser; +import org.dromara.system.api.model.RoleDTO; +import org.springframework.context.expression.BeanFactoryResolver; +import org.springframework.expression.BeanResolver; +import org.springframework.expression.ExpressionParser; +import org.springframework.expression.ParserContext; +import org.springframework.expression.common.TemplateParserContext; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.expression.spel.support.StandardEvaluationContext; + +import java.lang.reflect.Method; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; + +/** + * 数据权限过滤 + * + * @author Lion Li + * @version 3.5.0 + */ +@Slf4j +public class PlusDataPermissionHandler { + + /** + * 方法或类(名称) 与 注解的映射关系缓存 + */ + private final Map dataPermissionCacheMap = new ConcurrentHashMap<>(); + + /** + * spel 解析器 + */ + private final ExpressionParser parser = new SpelExpressionParser(); + private final ParserContext parserContext = new TemplateParserContext(); + /** + * bean解析器 用于处理 spel 表达式中对 bean 的调用 + */ + private final BeanResolver beanResolver = new BeanFactoryResolver(SpringUtils.getBeanFactory()); + + + public Expression getSqlSegment(Expression where, String mappedStatementId, boolean isSelect) { + DataColumn[] dataColumns = findAnnotation(mappedStatementId); + LoginUser currentUser = DataPermissionHelper.getVariable("user"); + if (ObjectUtil.isNull(currentUser)) { + currentUser = LoginHelper.getLoginUser(); + DataPermissionHelper.setVariable("user", currentUser); + } + + if(LoginHelper.getBusRole() == null){ + return where; + } + + // 如果是超级管理员或租户管理员,则不过滤数据 + if (LoginHelper.isSuperAdmin() || LoginHelper.isTenantAdmin()) { + return where; + } + + + if( BusRole.PLATFORM.getId() == LoginHelper.getBusRole().intValue()){ + return where; + } + + String dataFilterSql = buildDataFilter(dataColumns, isSelect); + if (StringUtils.isBlank(dataFilterSql)) { + return where; + } + try { + Expression expression = CCJSqlParserUtil.parseExpression(dataFilterSql); + // 数据权限使用单独的括号 防止与其他条件冲突 + Parenthesis parenthesis = new Parenthesis(expression); + if (ObjectUtil.isNotNull(where)) { + return new AndExpression(where, parenthesis); + } else { + return parenthesis; + } + } catch (JSQLParserException e) { + throw new ServiceException("数据权限解析异常 => " + e.getMessage()); + } + } + + /** + * 构造数据过滤sql + */ + private String buildDataFilter(DataColumn[] dataColumns, boolean isSelect) { + if(dataColumns == null || dataColumns.length == 0){ + return ""; + } + // 更新或删除需满足所有条件 + String joinStr = isSelect ? " OR " : " AND "; + LoginUser user = DataPermissionHelper.getVariable("user"); + if (user != null){ + StandardEvaluationContext context = new StandardEvaluationContext(); + context.setBeanResolver(beanResolver); + DataPermissionHelper.getContext().forEach(context::setVariable); + Set conditions = new HashSet<>(); + context.setVariable("busNo",user.getBusNo()); + String sqlTemp =""; + if(user.getBusRole() != null){ + if(BusRole.AGENT.getId() == user.getBusRole().intValue()){ + sqlTemp = " #{#colomField} like '#{#busNo}%' "; + }else if(BusRole.OPERATOR.getId() == user.getBusRole().intValue()){ + sqlTemp = " #{#colomField} like '#{#busNo}%' "; + }else if(BusRole.SERVICE.getId() == user.getBusRole().intValue()){ + + }else if(BusRole.MERCHANT.getId() == user.getBusRole().intValue()){ + sqlTemp = " #{#colomField} = '#{#busNo}' "; + } + } + + for (DataColumn dataColumn : dataColumns) { + if (dataColumn.key().length != dataColumn.value().length) { + throw new ServiceException("角色数据范围异常 => key与value长度不匹配"); + } + // 不包含 key 变量 则不处理 + if (!StringUtils.containsAny(sqlTemp, + Arrays.stream(dataColumn.key()).map(key -> "#" + key).toArray(String[]::new) + )) { + continue; + } + // 设置注解变量 key 为表达式变量 value 为变量值 + for (int i = 0; i < dataColumn.key().length; i++) { + context.setVariable(dataColumn.key()[i], dataColumn.value()[i]); + } + + // 解析sql模板并填充 + String sql = parser.parseExpression(sqlTemp, parserContext).getValue(context, String.class); + conditions.add(joinStr + sql); + } + + + + for (RoleDTO role : user.getRoles()) { + user.setRoleId(role.getRoleId()); + // 获取角色权限泛型 + DataScopeType type = DataScopeType.findCode(role.getDataScope()); + if (ObjectUtil.isNull(type)) { + throw new ServiceException("角色数据范围异常 => " + role.getDataScope()); + } + // 全部数据权限直接返回 + if (type == DataScopeType.ALL) { + break; +// return ""; + } + boolean isSuccess = false; + for (DataColumn dataColumn : dataColumns) { + if (dataColumn.key().length != dataColumn.value().length) { + throw new ServiceException("角色数据范围异常 => key与value长度不匹配"); + } + // 不包含 key 变量 则不处理 + if (!StringUtils.containsAny(type.getSqlTemplate(), + Arrays.stream(dataColumn.key()).map(key -> "#" + key).toArray(String[]::new) + )) { + continue; + } + // 设置注解变量 key 为表达式变量 value 为变量值 + for (int i = 0; i < dataColumn.key().length; i++) { + context.setVariable(dataColumn.key()[i], dataColumn.value()[i]); + } + + // 解析sql模板并填充 + String sql = parser.parseExpression(type.getSqlTemplate(), parserContext).getValue(context, String.class); + conditions.add(joinStr + sql); + isSuccess = true; + } + // 未处理成功则填充兜底方案 + if (!isSuccess && StringUtils.isNotBlank(type.getElseSql())) { + conditions.add(joinStr + type.getElseSql()); + } + } + + if (CollUtil.isNotEmpty(conditions)) { + String sql = StreamUtils.join(conditions, Function.identity(), ""); + return sql.substring(joinStr.length()); + } + } + return ""; + } + + public DataColumn[] findAnnotation(String mappedStatementId) { + StringBuilder sb = new StringBuilder(mappedStatementId); + int index = sb.lastIndexOf("."); + String clazzName = sb.substring(0, index); + String methodName = sb.substring(index + 1, sb.length()); + Class clazz; + try { + clazz = ClassUtil.loadClass(clazzName); + } catch (Exception e) { + return null; + } + List methods = Arrays.stream(ClassUtil.getDeclaredMethods(clazz)) + .filter(method -> method.getName().equals(methodName)).toList(); + DataPermission dataPermission; + // 获取方法注解 + for (Method method : methods) { + dataPermission = dataPermissionCacheMap.get(mappedStatementId); + if (ObjectUtil.isNotNull(dataPermission)) { + return dataPermission.value(); + } + if (AnnotationUtil.hasAnnotation(method, DataPermission.class)) { + dataPermission = AnnotationUtil.getAnnotation(method, DataPermission.class); + dataPermissionCacheMap.put(mappedStatementId, dataPermission); + return dataPermission.value(); + } + } + dataPermission = dataPermissionCacheMap.get(clazz.getName()); + if (ObjectUtil.isNotNull(dataPermission)) { + return dataPermission.value(); + } + // 获取类注解 + if (AnnotationUtil.hasAnnotation(clazz, DataPermission.class)) { + dataPermission = AnnotationUtil.getAnnotation(clazz, DataPermission.class); + dataPermissionCacheMap.put(clazz.getName(), dataPermission); + return dataPermission.value(); + } + return null; + } + +} diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/helper/DataBaseHelper.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/helper/DataBaseHelper.java new file mode 100644 index 0000000..4a29383 --- /dev/null +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/helper/DataBaseHelper.java @@ -0,0 +1,82 @@ +package org.dromara.common.mybatis.helper; + +import cn.hutool.core.convert.Convert; +import com.baomidou.dynamic.datasource.DynamicRoutingDataSource; +import org.dromara.common.core.exception.ServiceException; +import org.dromara.common.core.utils.SpringUtils; +import org.dromara.common.mybatis.enums.DataBaseType; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +/** + * 数据库助手 + * + * @author Lion Li + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class DataBaseHelper { + + private static final DynamicRoutingDataSource DS = SpringUtils.getBean(DynamicRoutingDataSource.class); + + /** + * 获取当前数据库类型 + */ + public static DataBaseType getDataBaseType() { + DataSource dataSource = DS.determineDataSource(); + try (Connection conn = dataSource.getConnection()) { + DatabaseMetaData metaData = conn.getMetaData(); + String databaseProductName = metaData.getDatabaseProductName(); + return DataBaseType.find(databaseProductName); + } catch (SQLException e) { + throw new ServiceException(e.getMessage()); + } + } + + public static boolean isMySql() { + return DataBaseType.MY_SQL == getDataBaseType(); + } + + public static boolean isOracle() { + return DataBaseType.ORACLE == getDataBaseType(); + } + + public static boolean isPostgerSql() { + return DataBaseType.POSTGRE_SQL == getDataBaseType(); + } + + public static boolean isSqlServer() { + return DataBaseType.SQL_SERVER == getDataBaseType(); + } + + public static String findInSet(Object var1, String var2) { + DataBaseType dataBasyType = getDataBaseType(); + String var = Convert.toStr(var1); + if (dataBasyType == DataBaseType.SQL_SERVER) { + // charindex(',100,' , ',0,100,101,') <> 0 + return "charindex(',%s,' , ','+%s+',') <> 0".formatted(var, var2); + } else if (dataBasyType == DataBaseType.POSTGRE_SQL) { + // (select position(',100,' in ',0,100,101,')) <> 0 + return "(select position(',%s,' in ','||%s||',')) <> 0".formatted(var, var2); + } else if (dataBasyType == DataBaseType.ORACLE) { + // instr(',0,100,101,' , ',100,') <> 0 + return "instr(','||%s||',' , ',%s,') <> 0".formatted(var2, var); + } + // find_in_set('100' , '0,100,101') + return "find_in_set('%s' , %s) <> 0".formatted(var, var2); + } + + /** + * 获取当前加载的数据库名 + */ + public static List getDataSourceNameList() { + return new ArrayList<>(DS.getDataSources().keySet()); + } +} diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/helper/DataPermissionHelper.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/helper/DataPermissionHelper.java new file mode 100644 index 0000000..0beae4d --- /dev/null +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/helper/DataPermissionHelper.java @@ -0,0 +1,93 @@ +package org.dromara.common.mybatis.helper; + +import cn.dev33.satoken.context.SaHolder; +import cn.dev33.satoken.context.model.SaStorage; +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.plugins.IgnoreStrategy; +import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Supplier; + +/** + * 数据权限助手 + * + * @author Lion Li + * @version 3.5.0 + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +@SuppressWarnings("unchecked cast") +public class DataPermissionHelper { + + public static final String DATA_PERMISSION_KEY = "data:permission"; + + public static T getVariable(String key) { + Map context = getContext(); + return (T) context.get(key); + } + + + public static void setVariable(String key, Object value) { + Map context = getContext(); + context.put(key, value); + } + + public static Map getContext() { + SaStorage saStorage = SaHolder.getStorage(); + Object attribute = saStorage.get(DATA_PERMISSION_KEY); + if (ObjectUtil.isNull(attribute)) { + saStorage.set(DATA_PERMISSION_KEY, new HashMap<>()); + attribute = saStorage.get(DATA_PERMISSION_KEY); + } + if (attribute instanceof Map map) { + return map; + } + throw new NullPointerException("data permission context type exception"); + } + + /** + * 开启忽略数据权限(开启后需手动调用 {@link #disableIgnore()} 关闭) + */ + public static void enableIgnore() { + InterceptorIgnoreHelper.handle(IgnoreStrategy.builder().dataPermission(true).build()); + } + + /** + * 关闭忽略数据权限 + */ + public static void disableIgnore() { + InterceptorIgnoreHelper.clearIgnoreStrategy(); + } + + /** + * 在忽略数据权限中执行 + * + * @param handle 处理执行方法 + */ + public static void ignore(Runnable handle) { + enableIgnore(); + try { + handle.run(); + } finally { + disableIgnore(); + } + } + + /** + * 在忽略数据权限中执行 + * + * @param handle 处理执行方法 + */ + public static T ignore(Supplier handle) { + enableIgnore(); + try { + return handle.get(); + } finally { + disableIgnore(); + } + } + +} diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/interceptor/PlusDataPermissionInterceptor.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/interceptor/PlusDataPermissionInterceptor.java new file mode 100644 index 0000000..0ab0c11 --- /dev/null +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/interceptor/PlusDataPermissionInterceptor.java @@ -0,0 +1,129 @@ +package org.dromara.common.mybatis.interceptor; + +import cn.hutool.core.collection.ConcurrentHashSet; +import cn.hutool.core.util.ArrayUtil; +import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper; +import com.baomidou.mybatisplus.core.toolkit.PluginUtils; +import com.baomidou.mybatisplus.extension.parser.JsqlParserSupport; +import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor; +import org.dromara.common.mybatis.annotation.DataColumn; +import org.dromara.common.mybatis.handler.PlusDataPermissionHandler; +import net.sf.jsqlparser.expression.Expression; +import net.sf.jsqlparser.statement.delete.Delete; +import net.sf.jsqlparser.statement.select.PlainSelect; +import net.sf.jsqlparser.statement.select.Select; +import net.sf.jsqlparser.statement.select.SelectBody; +import net.sf.jsqlparser.statement.select.SetOperationList; +import net.sf.jsqlparser.statement.update.Update; +import org.apache.ibatis.executor.Executor; +import org.apache.ibatis.executor.statement.StatementHandler; +import org.apache.ibatis.mapping.BoundSql; +import org.apache.ibatis.mapping.MappedStatement; +import org.apache.ibatis.mapping.SqlCommandType; +import org.apache.ibatis.session.ResultHandler; +import org.apache.ibatis.session.RowBounds; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.List; +import java.util.Set; + +/** + * 数据权限拦截器 + * + * @author Lion Li + * @version 3.5.0 + */ +public class PlusDataPermissionInterceptor extends JsqlParserSupport implements InnerInterceptor { + + private final PlusDataPermissionHandler dataPermissionHandler = new PlusDataPermissionHandler(); + /** + * 无效注解方法缓存用于快速返回 + */ + private final Set invalidCacheSet = new ConcurrentHashSet<>(); + + @Override + public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { + // 检查忽略注解 + if (InterceptorIgnoreHelper.willIgnoreDataPermission(ms.getId())) { + return; + } + // 检查是否无效 无数据权限注解 + if (invalidCacheSet.contains(ms.getId())) { + return; + } + DataColumn[] dataColumns = dataPermissionHandler.findAnnotation(ms.getId()); + if (ArrayUtil.isEmpty(dataColumns)) { + invalidCacheSet.add(ms.getId()); + return; + } + // 解析 sql 分配对应方法 + PluginUtils.MPBoundSql mpBs = PluginUtils.mpBoundSql(boundSql); + mpBs.sql(parserSingle(mpBs.sql(), ms.getId())); + } + + @Override + public void beforePrepare(StatementHandler sh, Connection connection, Integer transactionTimeout) { + PluginUtils.MPStatementHandler mpSh = PluginUtils.mpStatementHandler(sh); + MappedStatement ms = mpSh.mappedStatement(); + SqlCommandType sct = ms.getSqlCommandType(); + if (sct == SqlCommandType.UPDATE || sct == SqlCommandType.DELETE) { + if (InterceptorIgnoreHelper.willIgnoreDataPermission(ms.getId())) { + return; + } + // 检查是否无效 无数据权限注解 + if (invalidCacheSet.contains(ms.getId())) { + return; + } + DataColumn[] dataColumns = dataPermissionHandler.findAnnotation(ms.getId()); + if (ArrayUtil.isEmpty(dataColumns)) { + invalidCacheSet.add(ms.getId()); + return; + } + PluginUtils.MPBoundSql mpBs = mpSh.mPBoundSql(); + mpBs.sql(parserMulti(mpBs.sql(), ms.getId())); + } + } + + @Override + protected void processSelect(Select select, int index, String sql, Object obj) { + SelectBody selectBody = select.getSelectBody(); + if (selectBody instanceof PlainSelect plainSelect) { + this.setWhere(plainSelect, (String) obj); + } else if (selectBody instanceof SetOperationList setOperationList) { + List selectBodyList = setOperationList.getSelects(); + selectBodyList.forEach(s -> this.setWhere((PlainSelect) s, (String) obj)); + } + } + + @Override + protected void processUpdate(Update update, int index, String sql, Object obj) { + Expression sqlSegment = dataPermissionHandler.getSqlSegment(update.getWhere(), (String) obj, false); + if (null != sqlSegment) { + update.setWhere(sqlSegment); + } + } + + @Override + protected void processDelete(Delete delete, int index, String sql, Object obj) { + Expression sqlSegment = dataPermissionHandler.getSqlSegment(delete.getWhere(), (String) obj, false); + if (null != sqlSegment) { + delete.setWhere(sqlSegment); + } + } + + /** + * 设置 where 条件 + * + * @param plainSelect 查询对象 + * @param mappedStatementId 执行方法id + */ + protected void setWhere(PlainSelect plainSelect, String mappedStatementId) { + Expression sqlSegment = dataPermissionHandler.getSqlSegment(plainSelect.getWhere(), mappedStatementId, true); + if (null != sqlSegment) { + plainSelect.setWhere(sqlSegment); + } + } + +} + diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/service/SysDataScopeService.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/service/SysDataScopeService.java new file mode 100644 index 0000000..0c187f6 --- /dev/null +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/service/SysDataScopeService.java @@ -0,0 +1,28 @@ +package org.dromara.common.mybatis.service; + +import org.dromara.system.api.RemoteDataScopeService; +import org.apache.dubbo.config.annotation.DubboReference; +import org.springframework.stereotype.Service; + +/** + * 数据权限 实现 + *

+ * 注意: 此Service内不允许调用标注`数据权限`注解的方法 + * 例如: deptMapper.selectList 此 selectList 方法标注了`数据权限`注解 会出现循环解析的问题 + * + * @author Lion Li + */ +@Service("sdss") +public class SysDataScopeService { + + @DubboReference + private RemoteDataScopeService remoteDataScopeService; + + public String getRoleCustom(Long roleId) { + return remoteDataScopeService.getRoleCustom(roleId); + } + + public String getDeptAndChild(Long deptId) { + return remoteDataScopeService.getDeptAndChild(deptId); + } +} diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/resources/META-INF/dubbo/org.apache.dubbo.rpc.Filter b/ruoyi-common/ruoyi-common-mybatis/src/main/resources/META-INF/dubbo/org.apache.dubbo.rpc.Filter new file mode 100644 index 0000000..6d8ff88 --- /dev/null +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/resources/META-INF/dubbo/org.apache.dubbo.rpc.Filter @@ -0,0 +1 @@ +dubboDataPermissionFilter=org.dromara.common.mybatis.filter.DubboDataPermissionFilter diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/resources/common-mybatis.yml b/ruoyi-common/ruoyi-common-mybatis/src/main/resources/common-mybatis.yml new file mode 100644 index 0000000..f5dc637 --- /dev/null +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/resources/common-mybatis.yml @@ -0,0 +1,33 @@ +# 内置配置 不允许修改 如需修改请在 nacos 上写相同配置覆盖 +# MyBatisPlus配置 +# https://baomidou.com/config/ +mybatis-plus: + # 启动时是否检查 MyBatis XML 文件的存在,默认不检查 + checkConfigLocation: false + configuration: + # 自动驼峰命名规则(camel case)映射 + mapUnderscoreToCamelCase: true + # MyBatis 自动映射策略 + # NONE:不启用 PARTIAL:只对非嵌套 resultMap 自动映射 FULL:对所有 resultMap 自动映射 + autoMappingBehavior: FULL + # MyBatis 自动映射时未知列或未知属性处理策 + # NONE:不做处理 WARNING:打印相关警告 FAILING:抛出异常和详细信息 + autoMappingUnknownColumnBehavior: NONE + # 更详细的日志输出 会有性能损耗 org.apache.ibatis.logging.stdout.StdOutImpl + # 关闭日志记录 (可单纯使用 p6spy 分析) org.apache.ibatis.logging.nologging.NoLoggingImpl + # 默认日志输出 org.apache.ibatis.logging.slf4j.Slf4jImpl + logImpl: org.apache.ibatis.logging.nologging.NoLoggingImpl + global-config: + # 是否打印 Logo banner + banner: true + dbConfig: + # 主键类型 + # AUTO 自增 NONE 空 INPUT 用户输入 ASSIGN_ID 雪花 ASSIGN_UUID 唯一 UUID + idType: ASSIGN_ID + # 逻辑已删除值(框架表均使用此值 禁止随意修改) + logicDeleteValue: 2 + # 逻辑未删除值 + logicNotDeleteValue: 0 + insertStrategy: NOT_NULL + updateStrategy: NOT_NULL + whereStrategy: NOT_NULL diff --git a/ruoyi-common/ruoyi-common-oss/pom.xml b/ruoyi-common/ruoyi-common-oss/pom.xml new file mode 100644 index 0000000..8e7afff --- /dev/null +++ b/ruoyi-common/ruoyi-common-oss/pom.xml @@ -0,0 +1,35 @@ + + + + org.dromara + ruoyi-common + ${revision} + + 4.0.0 + + ruoyi-common-oss + + + ruoyi-common-oss oss服务 + + + + + org.dromara + ruoyi-common-json + + + + org.dromara + ruoyi-common-redis + + + + com.amazonaws + aws-java-sdk-s3 + + + + diff --git a/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/constant/OssConstant.java b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/constant/OssConstant.java new file mode 100644 index 0000000..9d8db93 --- /dev/null +++ b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/constant/OssConstant.java @@ -0,0 +1,40 @@ +package org.dromara.common.oss.constant; + +import org.dromara.common.core.constant.GlobalConstants; + +import java.util.Arrays; +import java.util.List; + +/** + * 对象存储常量 + * + * @author Lion Li + */ +public interface OssConstant { + + /** + * 默认配置KEY + */ + String DEFAULT_CONFIG_KEY = GlobalConstants.GLOBAL_REDIS_KEY + "sys_oss:default_config"; + + /** + * 预览列表资源开关Key + */ + String PEREVIEW_LIST_RESOURCE_KEY = "sys.oss.previewListResource"; + + /** + * 系统数据ids + */ + List SYSTEM_DATA_IDS = Arrays.asList(1L, 2L, 3L, 4L); + + /** + * 云服务商 + */ + String[] CLOUD_SERVICE = new String[] {"aliyun", "qcloud", "qiniu", "obs"}; + + /** + * https 状态 + */ + String IS_HTTPS = "Y"; + +} diff --git a/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/core/OssClient.java b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/core/OssClient.java new file mode 100644 index 0000000..53e05c9 --- /dev/null +++ b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/core/OssClient.java @@ -0,0 +1,262 @@ +package org.dromara.common.oss.core; + +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.util.IdUtil; +import com.amazonaws.ClientConfiguration; +import com.amazonaws.HttpMethod; +import com.amazonaws.Protocol; +import com.amazonaws.auth.AWSCredentials; +import com.amazonaws.auth.AWSCredentialsProvider; +import com.amazonaws.auth.AWSStaticCredentialsProvider; +import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.client.builder.AwsClientBuilder; +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.AmazonS3Client; +import com.amazonaws.services.s3.AmazonS3ClientBuilder; +import com.amazonaws.services.s3.model.*; +import org.dromara.common.core.utils.DateUtils; +import org.dromara.common.core.utils.StringUtils; +import org.dromara.common.oss.constant.OssConstant; +import org.dromara.common.oss.entity.UploadResult; +import org.dromara.common.oss.enumd.AccessPolicyType; +import org.dromara.common.oss.enumd.PolicyType; +import org.dromara.common.oss.exception.OssException; +import org.dromara.common.oss.properties.OssProperties; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.InputStream; +import java.net.URL; +import java.util.Date; + +/** + * S3 存储协议 所有兼容S3协议的云厂商均支持 + * 阿里云 腾讯云 七牛云 minio + * + * @author Lion Li + */ +public class OssClient { + + private final String configKey; + + private final OssProperties properties; + + private final AmazonS3 client; + + public OssClient(String configKey, OssProperties ossProperties) { + this.configKey = configKey; + this.properties = ossProperties; + try { + AwsClientBuilder.EndpointConfiguration endpointConfig = + new AwsClientBuilder.EndpointConfiguration(properties.getEndpoint(), properties.getRegion()); + + AWSCredentials credentials = new BasicAWSCredentials(properties.getAccessKey(), properties.getSecretKey()); + AWSCredentialsProvider credentialsProvider = new AWSStaticCredentialsProvider(credentials); + ClientConfiguration clientConfig = new ClientConfiguration(); + if (OssConstant.IS_HTTPS.equals(properties.getIsHttps())) { + clientConfig.setProtocol(Protocol.HTTPS); + } else { + clientConfig.setProtocol(Protocol.HTTP); + } + AmazonS3ClientBuilder build = AmazonS3Client.builder() + .withEndpointConfiguration(endpointConfig) + .withClientConfiguration(clientConfig) + .withCredentials(credentialsProvider) + .disableChunkedEncoding(); + if (!StringUtils.containsAny(properties.getEndpoint(), OssConstant.CLOUD_SERVICE)) { + // minio 使用https限制使用域名访问 需要此配置 站点填域名 + build.enablePathStyleAccess(); + } + this.client = build.build(); + + createBucket(); + } catch (Exception e) { + if (e instanceof OssException) { + throw e; + } + throw new OssException("配置错误! 请检查系统配置:[" + e.getMessage() + "]"); + } + } + + public void createBucket() { + try { + String bucketName = properties.getBucketName(); + if (client.doesBucketExistV2(bucketName)) { + return; + } + CreateBucketRequest createBucketRequest = new CreateBucketRequest(bucketName); + AccessPolicyType accessPolicy = getAccessPolicy(); + createBucketRequest.setCannedAcl(accessPolicy.getAcl()); + client.createBucket(createBucketRequest); + client.setBucketPolicy(bucketName, getPolicy(bucketName, accessPolicy.getPolicyType())); + } catch (Exception e) { + throw new OssException("创建Bucket失败, 请核对配置信息:[" + e.getMessage() + "]"); + } + } + + public UploadResult upload(byte[] data, String path, String contentType) { + return upload(new ByteArrayInputStream(data), path, contentType); + } + + public UploadResult upload(InputStream inputStream, String path, String contentType) { + if (!(inputStream instanceof ByteArrayInputStream)) { + inputStream = new ByteArrayInputStream(IoUtil.readBytes(inputStream)); + } + try { + ObjectMetadata metadata = new ObjectMetadata(); + metadata.setContentType(contentType); + metadata.setContentLength(inputStream.available()); + PutObjectRequest putObjectRequest = new PutObjectRequest(properties.getBucketName(), path, inputStream, metadata); + // 设置上传对象的 Acl 为公共读 + putObjectRequest.setCannedAcl(getAccessPolicy().getAcl()); + client.putObject(putObjectRequest); + } catch (Exception e) { + throw new OssException("上传文件失败,请检查配置信息:[" + e.getMessage() + "]"); + } + return UploadResult.builder().url(getUrl() + "/" + path).filename(path).build(); + } + + public UploadResult upload(File file, String path) { + try { + PutObjectRequest putObjectRequest = new PutObjectRequest(properties.getBucketName(), path, file); + // 设置上传对象的 Acl 为公共读 + putObjectRequest.setCannedAcl(getAccessPolicy().getAcl()); + client.putObject(putObjectRequest); + } catch (Exception e) { + throw new OssException("上传文件失败,请检查配置信息:[" + e.getMessage() + "]"); + } + return UploadResult.builder().url(getUrl() + "/" + path).filename(path).build(); + } + + public void delete(String path) { + path = path.replace(getUrl() + "/", ""); + try { + client.deleteObject(properties.getBucketName(), path); + } catch (Exception e) { + throw new OssException("删除文件失败,请检查配置信息:[" + e.getMessage() + "]"); + } + } + + public UploadResult uploadSuffix(byte[] data, String suffix, String contentType) { + return upload(data, getPath(properties.getPrefix(), suffix), contentType); + } + + public UploadResult uploadSuffix(InputStream inputStream, String suffix, String contentType) { + return upload(inputStream, getPath(properties.getPrefix(), suffix), contentType); + } + + public UploadResult uploadSuffix(File file, String suffix) { + return upload(file, getPath(properties.getPrefix(), suffix)); + } + + /** + * 获取文件元数据 + * + * @param path 完整文件路径 + */ + public ObjectMetadata getObjectMetadata(String path) { + path = path.replace(getUrl() + "/", ""); + S3Object object = client.getObject(properties.getBucketName(), path); + return object.getObjectMetadata(); + } + + public InputStream getObjectContent(String path) { + path = path.replace(getUrl() + "/", ""); + S3Object object = client.getObject(properties.getBucketName(), path); + return object.getObjectContent(); + } + + public String getUrl() { + String domain = properties.getDomain(); + String endpoint = properties.getEndpoint(); + String header = OssConstant.IS_HTTPS.equals(properties.getIsHttps()) ? "https://" : "http://"; + // 云服务商直接返回 + if (StringUtils.containsAny(endpoint, OssConstant.CLOUD_SERVICE)) { + if (StringUtils.isNotBlank(domain)) { + return header + domain; + } + return header + properties.getBucketName() + "." + endpoint; + } + // minio 单独处理 + if (StringUtils.isNotBlank(domain)) { + return header + domain + "/" + properties.getBucketName(); + } + return header + endpoint + "/" + properties.getBucketName(); + } + + public String getPath(String prefix, String suffix) { + // 生成uuid + String uuid = IdUtil.fastSimpleUUID(); + // 文件路径 + String path = DateUtils.datePath() + "/" + uuid; + if (StringUtils.isNotBlank(prefix)) { + path = prefix + "/" + path; + } + return path + suffix; + } + + + public String getConfigKey() { + return configKey; + } + + /** + * 获取私有URL链接 + * + * @param objectKey 对象KEY + * @param second 授权时间 + */ + public String getPrivateUrl(String objectKey, Integer second) { + GeneratePresignedUrlRequest generatePresignedUrlRequest = + new GeneratePresignedUrlRequest(properties.getBucketName(), objectKey) + .withMethod(HttpMethod.GET) + .withExpiration(new Date(System.currentTimeMillis() + 1000L * second)); + URL url = client.generatePresignedUrl(generatePresignedUrlRequest); + return url.toString(); + } + + /** + * 检查配置是否相同 + */ + public boolean checkPropertiesSame(OssProperties properties) { + return this.properties.equals(properties); + } + + /** + * 获取当前桶权限类型 + * + * @return 当前桶权限类型code + */ + public AccessPolicyType getAccessPolicy() { + return AccessPolicyType.getByType(properties.getAccessPolicy()); + } + + private static String getPolicy(String bucketName, PolicyType policyType) { + StringBuilder builder = new StringBuilder(); + builder.append("{\n\"Statement\": [\n{\n\"Action\": [\n"); + builder.append(switch (policyType) { + case WRITE -> "\"s3:GetBucketLocation\",\n\"s3:ListBucketMultipartUploads\"\n"; + case READ_WRITE -> "\"s3:GetBucketLocation\",\n\"s3:ListBucket\",\n\"s3:ListBucketMultipartUploads\"\n"; + default -> "\"s3:GetBucketLocation\"\n"; + }); + builder.append("],\n\"Effect\": \"Allow\",\n\"Principal\": \"*\",\n\"Resource\": \"arn:aws:s3:::"); + builder.append(bucketName); + builder.append("\"\n},\n"); + if (policyType == PolicyType.READ) { + builder.append("{\n\"Action\": [\n\"s3:ListBucket\"\n],\n\"Effect\": \"Deny\",\n\"Principal\": \"*\",\n\"Resource\": \"arn:aws:s3:::"); + builder.append(bucketName); + builder.append("\"\n},\n"); + } + builder.append("{\n\"Action\": "); + builder.append(switch (policyType) { + case WRITE -> "[\n\"s3:AbortMultipartUpload\",\n\"s3:DeleteObject\",\n\"s3:ListMultipartUploadParts\",\n\"s3:PutObject\"\n],\n"; + case READ_WRITE -> "[\n\"s3:AbortMultipartUpload\",\n\"s3:DeleteObject\",\n\"s3:GetObject\",\n\"s3:ListMultipartUploadParts\",\n\"s3:PutObject\"\n],\n"; + default -> "\"s3:GetObject\",\n"; + }); + builder.append("\"Effect\": \"Allow\",\n\"Principal\": \"*\",\n\"Resource\": \"arn:aws:s3:::"); + builder.append(bucketName); + builder.append("/*\"\n}\n],\n\"Version\": \"2012-10-17\"\n}\n"); + return builder.toString(); + } + +} diff --git a/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/entity/UploadResult.java b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/entity/UploadResult.java new file mode 100644 index 0000000..a6f57e5 --- /dev/null +++ b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/entity/UploadResult.java @@ -0,0 +1,24 @@ +package org.dromara.common.oss.entity; + +import lombok.Builder; +import lombok.Data; + +/** + * 上传返回体 + * + * @author Lion Li + */ +@Data +@Builder +public class UploadResult { + + /** + * 文件路径 + */ + private String url; + + /** + * 文件名 + */ + private String filename; +} diff --git a/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/enumd/AccessPolicyType.java b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/enumd/AccessPolicyType.java new file mode 100644 index 0000000..9074d72 --- /dev/null +++ b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/enumd/AccessPolicyType.java @@ -0,0 +1,55 @@ +package org.dromara.common.oss.enumd; + +import com.amazonaws.services.s3.model.CannedAccessControlList; +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 桶访问策略配置 + * + * @author 陈賝 + */ +@Getter +@AllArgsConstructor +public enum AccessPolicyType { + + /** + * private + */ + PRIVATE("0", CannedAccessControlList.Private, PolicyType.WRITE), + + /** + * public + */ + PUBLIC("1", CannedAccessControlList.PublicRead, PolicyType.READ), + + /** + * custom + */ + CUSTOM("2",CannedAccessControlList.PublicRead, PolicyType.READ); + + /** + * 桶 权限类型 + */ + private final String type; + + /** + * 文件对象 权限类型 + */ + private final CannedAccessControlList acl; + + /** + * 桶策略类型 + */ + private final PolicyType policyType; + + public static AccessPolicyType getByType(String type) { + for (AccessPolicyType value : values()) { + if (value.getType().equals(type)) { + return value; + } + } + throw new RuntimeException("'type' not found By " + type); + } + +} diff --git a/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/enumd/PolicyType.java b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/enumd/PolicyType.java new file mode 100644 index 0000000..fe96341 --- /dev/null +++ b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/enumd/PolicyType.java @@ -0,0 +1,35 @@ +package org.dromara.common.oss.enumd; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * minio策略配置 + * + * @author Lion Li + */ +@Getter +@AllArgsConstructor +public enum PolicyType { + + /** + * 只读 + */ + READ("read-only"), + + /** + * 只写 + */ + WRITE("write-only"), + + /** + * 读写 + */ + READ_WRITE("read-write"); + + /** + * 类型 + */ + private final String type; + +} diff --git a/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/exception/OssException.java b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/exception/OssException.java new file mode 100644 index 0000000..52e9623 --- /dev/null +++ b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/exception/OssException.java @@ -0,0 +1,19 @@ +package org.dromara.common.oss.exception; + +import java.io.Serial; + +/** + * OSS异常类 + * + * @author Lion Li + */ +public class OssException extends RuntimeException { + + @Serial + private static final long serialVersionUID = 1L; + + public OssException(String msg) { + super(msg); + } + +} diff --git a/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/factory/OssFactory.java b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/factory/OssFactory.java new file mode 100644 index 0000000..763b090 --- /dev/null +++ b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/factory/OssFactory.java @@ -0,0 +1,65 @@ +package org.dromara.common.oss.factory; + +import org.dromara.common.core.constant.CacheNames; +import org.dromara.common.core.utils.StringUtils; +import org.dromara.common.json.utils.JsonUtils; +import org.dromara.common.oss.constant.OssConstant; +import org.dromara.common.oss.core.OssClient; +import org.dromara.common.oss.exception.OssException; +import org.dromara.common.oss.properties.OssProperties; +import org.dromara.common.redis.utils.CacheUtils; +import org.dromara.common.redis.utils.RedisUtils; +import lombok.extern.slf4j.Slf4j; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 文件上传Factory + * + * @author Lion Li + */ +@Slf4j +public class OssFactory { + + private static final Map CLIENT_CACHE = new ConcurrentHashMap<>(); + + /** + * 获取默认实例 + */ + public static OssClient instance() { + // 获取redis 默认类型 + String configKey = RedisUtils.getCacheObject(OssConstant.DEFAULT_CONFIG_KEY); + if (StringUtils.isEmpty(configKey)) { + throw new OssException("文件存储服务类型无法找到!"); + } + return instance(configKey); + } + + /** + * 根据类型获取实例 + */ + public static synchronized OssClient instance(String configKey) { + String json = CacheUtils.get(CacheNames.SYS_OSS_CONFIG, configKey); + if (json == null) { + throw new OssException("系统异常, '" + configKey + "'配置信息不存在!"); + } + OssProperties properties = JsonUtils.parseObject(json, OssProperties.class); + // 使用租户标识避免多个租户相同key实例覆盖 + String key = properties.getTenantId() + ":" + configKey; + OssClient client = CLIENT_CACHE.get(key); + if (client == null) { + CLIENT_CACHE.put(key, new OssClient(configKey, properties)); + log.info("创建OSS实例 key => {}", configKey); + return CLIENT_CACHE.get(key); + } + // 配置不相同则重新构建 + if (!client.checkPropertiesSame(properties)) { + CLIENT_CACHE.put(key, new OssClient(configKey, properties)); + log.info("重载OSS实例 key => {}", configKey); + return CLIENT_CACHE.get(key); + } + return client; + } + +} diff --git a/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/properties/OssProperties.java b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/properties/OssProperties.java new file mode 100644 index 0000000..cb37206 --- /dev/null +++ b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/properties/OssProperties.java @@ -0,0 +1,63 @@ +package org.dromara.common.oss.properties; + +import lombok.Data; + +/** + * OSS对象存储 配置属性 + * + * @author Lion Li + */ +@Data +public class OssProperties { + + /** + * 租户id + */ + private String tenantId; + + /** + * 访问站点 + */ + private String endpoint; + + /** + * 自定义域名 + */ + private String domain; + + /** + * 前缀 + */ + private String prefix; + + /** + * ACCESS_KEY + */ + private String accessKey; + + /** + * SECRET_KEY + */ + private String secretKey; + + /** + * 存储空间名 + */ + private String bucketName; + + /** + * 存储区域 + */ + private String region; + + /** + * 是否https(Y=是,N=否) + */ + private String isHttps; + + /** + * 桶权限类型(0private 1public 2custom) + */ + private String accessPolicy; + +} diff --git a/ruoyi-common/ruoyi-common-prometheus/pom.xml b/ruoyi-common/ruoyi-common-prometheus/pom.xml new file mode 100644 index 0000000..319bed4 --- /dev/null +++ b/ruoyi-common/ruoyi-common-prometheus/pom.xml @@ -0,0 +1,28 @@ + + + + org.dromara + ruoyi-common + ${revision} + + 4.0.0 + + ruoyi-common-prometheus + + + ruoyi-common-prometheus prometheus监控 + + + + + org.springframework.boot + spring-boot-starter-actuator + + + io.micrometer + micrometer-registry-prometheus + + + diff --git a/ruoyi-common/ruoyi-common-prometheus/src/main/java/org/dromara/common/prometheus/config/PrometheusConfiguration.java b/ruoyi-common/ruoyi-common-prometheus/src/main/java/org/dromara/common/prometheus/config/PrometheusConfiguration.java new file mode 100644 index 0000000..c5cfbf5 --- /dev/null +++ b/ruoyi-common/ruoyi-common-prometheus/src/main/java/org/dromara/common/prometheus/config/PrometheusConfiguration.java @@ -0,0 +1,22 @@ +package org.dromara.common.prometheus.config; + +import io.micrometer.core.instrument.MeterRegistry; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.actuate.autoconfigure.metrics.MeterRegistryCustomizer; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.context.annotation.Bean; + +/** + * prometheus 配置 + * + * @author Lion Li + */ +@AutoConfiguration +public class PrometheusConfiguration { + + @Bean + public MeterRegistryCustomizer configurer(@Value("${spring.application.name}") String applicationName) { + return (registry) -> registry.config().commonTags("application", applicationName); + } + +} diff --git a/ruoyi-common/ruoyi-common-ratelimiter/pom.xml b/ruoyi-common/ruoyi-common-ratelimiter/pom.xml new file mode 100644 index 0000000..bbde940 --- /dev/null +++ b/ruoyi-common/ruoyi-common-ratelimiter/pom.xml @@ -0,0 +1,30 @@ + + + + org.dromara + ruoyi-common + ${revision} + + 4.0.0 + + ruoyi-common-ratelimiter + + + ruoyi-common-ratelimiter 限流功能 + + + + + org.dromara + ruoyi-common-core + + + + org.dromara + ruoyi-common-redis + + + + diff --git a/ruoyi-common/ruoyi-common-ratelimiter/src/main/java/org/dromara/common/ratelimiter/annotation/RateLimiter.java b/ruoyi-common/ruoyi-common-ratelimiter/src/main/java/org/dromara/common/ratelimiter/annotation/RateLimiter.java new file mode 100644 index 0000000..de09752 --- /dev/null +++ b/ruoyi-common/ruoyi-common-ratelimiter/src/main/java/org/dromara/common/ratelimiter/annotation/RateLimiter.java @@ -0,0 +1,41 @@ +package org.dromara.common.ratelimiter.annotation; + +import org.dromara.common.ratelimiter.enums.LimitType; + +import java.lang.annotation.*; + +/** + * 限流注解 + * + * @author Lion Li + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface RateLimiter { + /** + * 限流key,支持使用Spring el表达式来动态获取方法上的参数值 + * 格式类似于 #code.id #{#code} + */ + String key() default ""; + + /** + * 限流时间,单位秒 + */ + int time() default 60; + + /** + * 限流次数 + */ + int count() default 100; + + /** + * 限流类型 + */ + LimitType limitType() default LimitType.DEFAULT; + + /** + * 提示消息 支持国际化 格式为 {code} + */ + String message() default "{rate.limiter.message}"; +} diff --git a/ruoyi-common/ruoyi-common-ratelimiter/src/main/java/org/dromara/common/ratelimiter/aspectj/RateLimiterAspect.java b/ruoyi-common/ruoyi-common-ratelimiter/src/main/java/org/dromara/common/ratelimiter/aspectj/RateLimiterAspect.java new file mode 100644 index 0000000..8f3a5ca --- /dev/null +++ b/ruoyi-common/ruoyi-common-ratelimiter/src/main/java/org/dromara/common/ratelimiter/aspectj/RateLimiterAspect.java @@ -0,0 +1,127 @@ +package org.dromara.common.ratelimiter.aspectj; + +import cn.hutool.core.util.ArrayUtil; +import org.dromara.common.core.constant.GlobalConstants; +import org.dromara.common.core.exception.ServiceException; +import org.dromara.common.core.utils.MessageUtils; +import org.dromara.common.core.utils.ServletUtils; +import org.dromara.common.core.utils.StringUtils; +import org.dromara.common.ratelimiter.annotation.RateLimiter; +import org.dromara.common.ratelimiter.enums.LimitType; +import org.dromara.common.redis.utils.RedisUtils; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.aspectj.lang.reflect.MethodSignature; +import org.redisson.api.RateType; +import org.springframework.core.DefaultParameterNameDiscoverer; +import org.springframework.core.ParameterNameDiscoverer; +import org.springframework.expression.EvaluationContext; +import org.springframework.expression.Expression; +import org.springframework.expression.ExpressionParser; +import org.springframework.expression.ParserContext; +import org.springframework.expression.common.TemplateParserContext; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.expression.spel.support.StandardEvaluationContext; + +import java.lang.reflect.Method; + +/** + * 限流处理 + * + * @author Lion Li + */ +@Slf4j +@Aspect +public class RateLimiterAspect { + + /** + * 定义spel表达式解析器 + */ + private final ExpressionParser parser = new SpelExpressionParser(); + /** + * 定义spel解析模版 + */ + private final ParserContext parserContext = new TemplateParserContext(); + /** + * 定义spel上下文对象进行解析 + */ + private final EvaluationContext context = new StandardEvaluationContext(); + /** + * 方法参数解析器 + */ + private final ParameterNameDiscoverer pnd = new DefaultParameterNameDiscoverer(); + + @Before("@annotation(rateLimiter)") + public void doBefore(JoinPoint point, RateLimiter rateLimiter) throws Throwable { + int time = rateLimiter.time(); + int count = rateLimiter.count(); + String combineKey = getCombineKey(rateLimiter, point); + try { + RateType rateType = RateType.OVERALL; + if (rateLimiter.limitType() == LimitType.CLUSTER) { + rateType = RateType.PER_CLIENT; + } + long number = RedisUtils.rateLimiter(combineKey, rateType, count, time); + if (number == -1) { + String message = rateLimiter.message(); + if (StringUtils.startsWith(message, "{") && StringUtils.endsWith(message, "}")) { + message = MessageUtils.message(StringUtils.substring(message, 1, message.length() - 1)); + } + throw new ServiceException(message); + } + log.info("限制令牌 => {}, 剩余令牌 => {}, 缓存key => '{}'", count, number, combineKey); + } catch (Exception e) { + if (e instanceof ServiceException) { + throw e; + } else { + throw new RuntimeException("服务器限流异常,请稍候再试"); + } + } + } + + public String getCombineKey(RateLimiter rateLimiter, JoinPoint point) { + String key = rateLimiter.key(); + // 获取方法(通过方法签名来获取) + MethodSignature signature = (MethodSignature) point.getSignature(); + Method method = signature.getMethod(); + Class targetClass = method.getDeclaringClass(); + // 判断是否是spel格式 + if (StringUtils.containsAny(key, "#")) { + // 获取参数值 + Object[] args = point.getArgs(); + // 获取方法上参数的名称 + String[] parameterNames = pnd.getParameterNames(method); + if (ArrayUtil.isEmpty(parameterNames)) { + throw new ServiceException("限流key解析异常!请联系管理员!"); + } + for (int i = 0; i < parameterNames.length; i++) { + context.setVariable(parameterNames[i], args[i]); + } + // 解析返回给key + try { + Expression expression; + if (StringUtils.startsWith(key, parserContext.getExpressionPrefix()) + && StringUtils.endsWith(key, parserContext.getExpressionSuffix())) { + expression = parser.parseExpression(key, parserContext); + } else { + expression = parser.parseExpression(key); + } + key = expression.getValue(context, String.class) + ":"; + } catch (Exception e) { + throw new ServiceException("限流key解析异常!请联系管理员!"); + } + } + StringBuilder stringBuffer = new StringBuilder(GlobalConstants.RATE_LIMIT_KEY); + stringBuffer.append(ServletUtils.getRequest().getRequestURI()).append(":"); + if (rateLimiter.limitType() == LimitType.IP) { + // 获取请求ip + stringBuffer.append(ServletUtils.getClientIP()).append(":"); + } else if (rateLimiter.limitType() == LimitType.CLUSTER) { + // 获取客户端实例id + stringBuffer.append(RedisUtils.getClient().getId()).append(":"); + } + return stringBuffer.append(key).toString(); + } +} diff --git a/ruoyi-common/ruoyi-common-ratelimiter/src/main/java/org/dromara/common/ratelimiter/config/RateLimiterConfig.java b/ruoyi-common/ruoyi-common-ratelimiter/src/main/java/org/dromara/common/ratelimiter/config/RateLimiterConfig.java new file mode 100644 index 0000000..4b7e5b7 --- /dev/null +++ b/ruoyi-common/ruoyi-common-ratelimiter/src/main/java/org/dromara/common/ratelimiter/config/RateLimiterConfig.java @@ -0,0 +1,20 @@ +package org.dromara.common.ratelimiter.config; + +import org.dromara.common.ratelimiter.aspectj.RateLimiterAspect; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.data.redis.connection.RedisConfiguration; + +/** + * @author guangxin + * @date 2023/1/18 + */ +@AutoConfiguration(after = RedisConfiguration.class) +public class RateLimiterConfig { + + @Bean + public RateLimiterAspect rateLimiterAspect() { + return new RateLimiterAspect(); + } + +} diff --git a/ruoyi-common/ruoyi-common-ratelimiter/src/main/java/org/dromara/common/ratelimiter/enums/LimitType.java b/ruoyi-common/ruoyi-common-ratelimiter/src/main/java/org/dromara/common/ratelimiter/enums/LimitType.java new file mode 100644 index 0000000..b7f059f --- /dev/null +++ b/ruoyi-common/ruoyi-common-ratelimiter/src/main/java/org/dromara/common/ratelimiter/enums/LimitType.java @@ -0,0 +1,24 @@ +package org.dromara.common.ratelimiter.enums; + +/** + * 限流类型 + * + * @author ruoyi + */ + +public enum LimitType { + /** + * 默认策略全局限流 + */ + DEFAULT, + + /** + * 根据请求者IP进行限流 + */ + IP, + + /** + * 实例限流(集群多后端实例) + */ + CLUSTER +} diff --git a/ruoyi-common/ruoyi-common-redis/pom.xml b/ruoyi-common/ruoyi-common-redis/pom.xml new file mode 100644 index 0000000..5e5c277 --- /dev/null +++ b/ruoyi-common/ruoyi-common-redis/pom.xml @@ -0,0 +1,37 @@ + + + + org.dromara + ruoyi-common + ${revision} + + 4.0.0 + + ruoyi-common-redis + + + ruoyi-common-redis 缓存服务 + + + + + + org.dromara + ruoyi-common-core + + + + + org.redisson + redisson-spring-boot-starter + + + + com.baomidou + lock4j-redisson-spring-boot-starter + + + + diff --git a/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/config/RedisConfiguration.java b/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/config/RedisConfiguration.java new file mode 100644 index 0000000..8264423 --- /dev/null +++ b/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/config/RedisConfiguration.java @@ -0,0 +1,144 @@ +package org.dromara.common.redis.config; + +import cn.hutool.core.util.ObjectUtil; +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.PropertyAccessor; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator; +import lombok.extern.slf4j.Slf4j; +import org.dromara.common.redis.config.properties.RedissonProperties; +import org.dromara.common.redis.handler.KeyPrefixHandler; +import org.dromara.common.redis.manager.PlusSpringCacheManager; +import org.redisson.client.codec.StringCodec; +import org.redisson.codec.CompositeCodec; +import org.redisson.codec.TypedJsonJacksonCodec; +import org.redisson.spring.starter.RedissonAutoConfigurationCustomizer; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.cache.CacheManager; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.context.annotation.Bean; + +/** + * redis配置 + * + * @author Lion Li + */ +@Slf4j +@AutoConfiguration +@EnableCaching +@EnableConfigurationProperties(RedissonProperties.class) +public class RedisConfiguration { + + @Autowired + private RedissonProperties redissonProperties; + + @Autowired + private ObjectMapper objectMapper; + + @Bean + public RedissonAutoConfigurationCustomizer redissonCustomizer() { + return config -> { + ObjectMapper om = objectMapper.copy(); + om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); + // 指定序列化输入的类型,类必须是非final修饰的。序列化时将对象全类名一起保存下来 + om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL); + TypedJsonJacksonCodec jsonCodec = new TypedJsonJacksonCodec(Object.class, om); + // 组合序列化 key 使用 String 内容使用通用 json 格式 + CompositeCodec codec = new CompositeCodec(StringCodec.INSTANCE, jsonCodec, jsonCodec); + config.setThreads(redissonProperties.getThreads()) + .setNettyThreads(redissonProperties.getNettyThreads()) + // 缓存 Lua 脚本 减少网络传输(redisson 大部分的功能都是基于 Lua 脚本实现) + .setUseScriptCache(true) + .setCodec(codec); + RedissonProperties.SingleServerConfig singleServerConfig = redissonProperties.getSingleServerConfig(); + if (ObjectUtil.isNotNull(singleServerConfig)) { + // 使用单机模式 + config.useSingleServer() + //设置redis key前缀 + .setNameMapper(new KeyPrefixHandler(redissonProperties.getKeyPrefix())) + .setTimeout(singleServerConfig.getTimeout()) + .setClientName(singleServerConfig.getClientName()) + .setIdleConnectionTimeout(singleServerConfig.getIdleConnectionTimeout()) + .setSubscriptionConnectionPoolSize(singleServerConfig.getSubscriptionConnectionPoolSize()) + .setConnectionMinimumIdleSize(singleServerConfig.getConnectionMinimumIdleSize()) + .setConnectionPoolSize(singleServerConfig.getConnectionPoolSize()); + } + // 集群配置方式 参考下方注释 + RedissonProperties.ClusterServersConfig clusterServersConfig = redissonProperties.getClusterServersConfig(); + if (ObjectUtil.isNotNull(clusterServersConfig)) { + config.useClusterServers() + //设置redis key前缀 + .setNameMapper(new KeyPrefixHandler(redissonProperties.getKeyPrefix())) + .setTimeout(clusterServersConfig.getTimeout()) + .setClientName(clusterServersConfig.getClientName()) + .setIdleConnectionTimeout(clusterServersConfig.getIdleConnectionTimeout()) + .setSubscriptionConnectionPoolSize(clusterServersConfig.getSubscriptionConnectionPoolSize()) + .setMasterConnectionMinimumIdleSize(clusterServersConfig.getMasterConnectionMinimumIdleSize()) + .setMasterConnectionPoolSize(clusterServersConfig.getMasterConnectionPoolSize()) + .setSlaveConnectionMinimumIdleSize(clusterServersConfig.getSlaveConnectionMinimumIdleSize()) + .setSlaveConnectionPoolSize(clusterServersConfig.getSlaveConnectionPoolSize()) + .setReadMode(clusterServersConfig.getReadMode()) + .setSubscriptionMode(clusterServersConfig.getSubscriptionMode()); + } + log.info("初始化 redis 配置"); + }; + } + + /** + * 自定义缓存管理器 整合spring-cache + */ + @Bean + public CacheManager cacheManager() { + return new PlusSpringCacheManager(); + } + + /** + * redis集群配置 yml + * + * --- # redis 集群配置(单机与集群只能开启一个另一个需要注释掉) + * spring.data: + * redis: + * cluster: + * nodes: + * - 192.168.0.100:6379 + * - 192.168.0.101:6379 + * - 192.168.0.102:6379 + * # 密码 + * password: + * # 连接超时时间 + * timeout: 10s + * # 是否开启ssl + * ssl.enabled: false + * + * redisson: + * # 线程池数量 + * threads: 16 + * # Netty线程池数量 + * nettyThreads: 32 + * # 集群配置 + * clusterServersConfig: + * # 客户端名称 + * clientName: ${ruoyi.name} + * # master最小空闲连接数 + * masterConnectionMinimumIdleSize: 32 + * # master连接池大小 + * masterConnectionPoolSize: 64 + * # slave最小空闲连接数 + * slaveConnectionMinimumIdleSize: 32 + * # slave连接池大小 + * slaveConnectionPoolSize: 64 + * # 连接空闲超时,单位:毫秒 + * idleConnectionTimeout: 10000 + * # 命令等待超时,单位:毫秒 + * timeout: 3000 + * # 发布和订阅连接池大小 + * subscriptionConnectionPoolSize: 50 + * # 读取模式 + * readMode: "SLAVE" + * # 订阅模式 + * subscriptionMode: "MASTER" + */ + +} diff --git a/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/config/properties/RedissonProperties.java b/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/config/properties/RedissonProperties.java new file mode 100644 index 0000000..ebec786 --- /dev/null +++ b/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/config/properties/RedissonProperties.java @@ -0,0 +1,135 @@ +package org.dromara.common.redis.config.properties; + +import lombok.Data; +import lombok.NoArgsConstructor; +import org.redisson.config.ReadMode; +import org.redisson.config.SubscriptionMode; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * Redisson 配置属性 + * + * @author Lion Li + */ +@Data +@ConfigurationProperties(prefix = "redisson") +public class RedissonProperties { + + /** + * redis缓存key前缀 + */ + private String keyPrefix; + + /** + * 线程池数量,默认值 = 当前处理核数量 * 2 + */ + private int threads; + + /** + * Netty线程池数量,默认值 = 当前处理核数量 * 2 + */ + private int nettyThreads; + + /** + * 单机服务配置 + */ + private SingleServerConfig singleServerConfig; + + /** + * 集群服务配置 + */ + private ClusterServersConfig clusterServersConfig; + + @Data + @NoArgsConstructor + public static class SingleServerConfig { + + /** + * 客户端名称 + */ + private String clientName; + + /** + * 最小空闲连接数 + */ + private int connectionMinimumIdleSize; + + /** + * 连接池大小 + */ + private int connectionPoolSize; + + /** + * 连接空闲超时,单位:毫秒 + */ + private int idleConnectionTimeout; + + /** + * 命令等待超时,单位:毫秒 + */ + private int timeout; + + /** + * 发布和订阅连接池大小 + */ + private int subscriptionConnectionPoolSize; + + } + + @Data + @NoArgsConstructor + public static class ClusterServersConfig { + + /** + * 客户端名称 + */ + private String clientName; + + /** + * master最小空闲连接数 + */ + private int masterConnectionMinimumIdleSize; + + /** + * master连接池大小 + */ + private int masterConnectionPoolSize; + + /** + * slave最小空闲连接数 + */ + private int slaveConnectionMinimumIdleSize; + + /** + * slave连接池大小 + */ + private int slaveConnectionPoolSize; + + /** + * 连接空闲超时,单位:毫秒 + */ + private int idleConnectionTimeout; + + /** + * 命令等待超时,单位:毫秒 + */ + private int timeout; + + /** + * 发布和订阅连接池大小 + */ + private int subscriptionConnectionPoolSize; + + /** + * 读取模式 + */ + private ReadMode readMode; + + /** + * 订阅模式 + */ + private SubscriptionMode subscriptionMode; + + } + +} diff --git a/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/handler/KeyPrefixHandler.java b/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/handler/KeyPrefixHandler.java new file mode 100644 index 0000000..3bf3e34 --- /dev/null +++ b/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/handler/KeyPrefixHandler.java @@ -0,0 +1,50 @@ +package org.dromara.common.redis.handler; + +import org.dromara.common.core.utils.StringUtils; +import org.redisson.api.NameMapper; + +/** + * redis缓存key前缀处理 + * + * @author ye + * @date 2022/7/14 17:44 + * @since 4.3.0 + */ +public class KeyPrefixHandler implements NameMapper { + + private final String keyPrefix; + + public KeyPrefixHandler(String keyPrefix) { + //前缀为空 则返回空前缀 + this.keyPrefix = StringUtils.isBlank(keyPrefix) ? "" : keyPrefix + ":"; + } + + /** + * 增加前缀 + */ + @Override + public String map(String name) { + if (StringUtils.isBlank(name)) { + return null; + } + if (StringUtils.isNotBlank(keyPrefix) && !name.startsWith(keyPrefix)) { + return keyPrefix + name; + } + return name; + } + + /** + * 去除前缀 + */ + @Override + public String unmap(String name) { + if (StringUtils.isBlank(name)) { + return null; + } + if (StringUtils.isNotBlank(keyPrefix) && name.startsWith(keyPrefix)) { + return name.substring(keyPrefix.length()); + } + return name; + } + +} diff --git a/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/manager/PlusSpringCacheManager.java b/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/manager/PlusSpringCacheManager.java new file mode 100644 index 0000000..8c84889 --- /dev/null +++ b/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/manager/PlusSpringCacheManager.java @@ -0,0 +1,192 @@ +/** + * Copyright (c) 2013-2021 Nikita Koksharov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dromara.common.redis.manager; + +import org.dromara.common.redis.utils.RedisUtils; +import org.redisson.api.RMap; +import org.redisson.api.RMapCache; +import org.redisson.spring.cache.CacheConfig; +import org.redisson.spring.cache.RedissonCache; +import org.springframework.boot.convert.DurationStyle; +import org.springframework.cache.Cache; +import org.springframework.cache.CacheManager; +import org.springframework.cache.transaction.TransactionAwareCacheDecorator; +import org.springframework.util.StringUtils; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * A {@link CacheManager} implementation + * backed by Redisson instance. + *

+ * 修改 RedissonSpringCacheManager 源码 + * 重写 cacheName 处理方法 支持多参数 + * + * @author Nikita Koksharov + * + */ +@SuppressWarnings("unchecked") +public class PlusSpringCacheManager implements CacheManager { + + private boolean dynamic = true; + + private boolean allowNullValues = true; + + private boolean transactionAware = true; + + Map configMap = new ConcurrentHashMap<>(); + ConcurrentMap instanceMap = new ConcurrentHashMap<>(); + + /** + * Creates CacheManager supplied by Redisson instance + */ + public PlusSpringCacheManager() { + } + + + /** + * Defines possibility of storing {@code null} values. + *

+ * Default is true + * + * @param allowNullValues stores if true + */ + public void setAllowNullValues(boolean allowNullValues) { + this.allowNullValues = allowNullValues; + } + + /** + * Defines if cache aware of Spring-managed transactions. + * If {@code true} put/evict operations are executed only for successful transaction in after-commit phase. + *

+ * Default is false + * + * @param transactionAware cache is transaction aware if true + */ + public void setTransactionAware(boolean transactionAware) { + this.transactionAware = transactionAware; + } + + /** + * Defines 'fixed' cache names. + * A new cache instance will not be created in dynamic for non-defined names. + *

+ * `null` parameter setups dynamic mode + * + * @param names of caches + */ + public void setCacheNames(Collection names) { + if (names != null) { + for (String name : names) { + getCache(name); + } + dynamic = false; + } else { + dynamic = true; + } + } + + /** + * Set cache config mapped by cache name + * + * @param config object + */ + public void setConfig(Map config) { + this.configMap = (Map) config; + } + + protected CacheConfig createDefaultConfig() { + return new CacheConfig(); + } + + @Override + public Cache getCache(String name) { + // 重写 cacheName 支持多参数 + String[] array = StringUtils.delimitedListToStringArray(name, "#"); + name = array[0]; + + Cache cache = instanceMap.get(name); + if (cache != null) { + return cache; + } + if (!dynamic) { + return cache; + } + + CacheConfig config = configMap.get(name); + if (config == null) { + config = createDefaultConfig(); + configMap.put(name, config); + } + + if (array.length > 1) { + config.setTTL(DurationStyle.detectAndParse(array[1]).toMillis()); + } + if (array.length > 2) { + config.setMaxIdleTime(DurationStyle.detectAndParse(array[2]).toMillis()); + } + if (array.length > 3) { + config.setMaxSize(Integer.parseInt(array[3])); + } + + if (config.getMaxIdleTime() == 0 && config.getTTL() == 0 && config.getMaxSize() == 0) { + return createMap(name, config); + } + + return createMapCache(name, config); + } + + private Cache createMap(String name, CacheConfig config) { + RMap map = RedisUtils.getClient().getMap(name); + + Cache cache = new RedissonCache(map, allowNullValues); + if (transactionAware) { + cache = new TransactionAwareCacheDecorator(cache); + } + Cache oldCache = instanceMap.putIfAbsent(name, cache); + if (oldCache != null) { + cache = oldCache; + } + return cache; + } + + private Cache createMapCache(String name, CacheConfig config) { + RMapCache map = RedisUtils.getClient().getMapCache(name); + + Cache cache = new RedissonCache(map, config, allowNullValues); + if (transactionAware) { + cache = new TransactionAwareCacheDecorator(cache); + } + Cache oldCache = instanceMap.putIfAbsent(name, cache); + if (oldCache != null) { + cache = oldCache; + } else { + map.setMaxSize(config.getMaxSize()); + } + return cache; + } + + @Override + public Collection getCacheNames() { + return Collections.unmodifiableSet(configMap.keySet()); + } + + +} diff --git a/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/utils/CacheUtils.java b/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/utils/CacheUtils.java new file mode 100644 index 0000000..42a88d6 --- /dev/null +++ b/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/utils/CacheUtils.java @@ -0,0 +1,75 @@ +package org.dromara.common.redis.utils; + +import org.dromara.common.core.utils.SpringUtils; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.redisson.api.RMap; +import org.springframework.cache.Cache; +import org.springframework.cache.CacheManager; + +import java.util.Set; + +/** + * 缓存操作工具类 {@link } + * + * @author Michelle.Chung + * @date 2022/8/13 + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +@SuppressWarnings(value = {"unchecked"}) +public class CacheUtils { + + private static final CacheManager CACHE_MANAGER = SpringUtils.getBean(CacheManager.class); + + /** + * 获取缓存组内所有的KEY + * + * @param cacheNames 缓存组名称 + */ + public static Set keys(String cacheNames) { + RMap rmap = (RMap) CACHE_MANAGER.getCache(cacheNames).getNativeCache(); + return rmap.keySet(); + } + + /** + * 获取缓存值 + * + * @param cacheNames 缓存组名称 + * @param key 缓存key + */ + public static T get(String cacheNames, Object key) { + Cache.ValueWrapper wrapper = CACHE_MANAGER.getCache(cacheNames).get(key); + return wrapper != null ? (T) wrapper.get() : null; + } + + /** + * 保存缓存值 + * + * @param cacheNames 缓存组名称 + * @param key 缓存key + * @param value 缓存值 + */ + public static void put(String cacheNames, Object key, Object value) { + CACHE_MANAGER.getCache(cacheNames).put(key, value); + } + + /** + * 删除缓存值 + * + * @param cacheNames 缓存组名称 + * @param key 缓存key + */ + public static void evict(String cacheNames, Object key) { + CACHE_MANAGER.getCache(cacheNames).evict(key); + } + + /** + * 清空缓存值 + * + * @param cacheNames 缓存组名称 + */ + public static void clear(String cacheNames) { + CACHE_MANAGER.getCache(cacheNames).clear(); + } + +} diff --git a/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/utils/RedisUtils.java b/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/utils/RedisUtils.java new file mode 100644 index 0000000..bc14e84 --- /dev/null +++ b/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/utils/RedisUtils.java @@ -0,0 +1,538 @@ +package org.dromara.common.redis.utils; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.dromara.common.core.utils.SpringUtils; +import org.redisson.api.*; +import org.springframework.dao.DataAccessException; +import org.springframework.data.redis.connection.RedisConnection; +import org.springframework.data.redis.core.RedisCallback; + +import java.time.Duration; +import java.util.*; +import java.util.function.Consumer; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * redis 工具类 + * + * @author Lion Li + * @version 3.1.0 新增 + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +@SuppressWarnings(value = {"unchecked", "rawtypes"}) +public class RedisUtils { + + private static final RedissonClient CLIENT = SpringUtils.getBean(RedissonClient.class); + + /** + * 限流 + * + * @param key 限流key + * @param rateType 限流类型 + * @param rate 速率 + * @param rateInterval 速率间隔 + * @return -1 表示失败 + */ + public static long rateLimiter(String key, RateType rateType, int rate, int rateInterval) { + RRateLimiter rateLimiter = CLIENT.getRateLimiter(key); + rateLimiter.trySetRate(rateType, rate, rateInterval, RateIntervalUnit.SECONDS); + if (rateLimiter.tryAcquire()) { + return rateLimiter.availablePermits(); + } else { + return -1L; + } + } + + /** + * 获取客户端实例 + */ + public static RedissonClient getClient() { + return CLIENT; + } + + /** + * 发布通道消息 + * + * @param channelKey 通道key + * @param msg 发送数据 + * @param consumer 自定义处理 + */ + public static void publish(String channelKey, T msg, Consumer consumer) { + RTopic topic = CLIENT.getTopic(channelKey); + topic.publish(msg); + consumer.accept(msg); + } + + public static void publish(String channelKey, T msg) { + RTopic topic = CLIENT.getTopic(channelKey); + topic.publish(msg); + } + + /** + * 订阅通道接收消息 + * + * @param channelKey 通道key + * @param clazz 消息类型 + * @param consumer 自定义处理 + */ + public static void subscribe(String channelKey, Class clazz, Consumer consumer) { + RTopic topic = CLIENT.getTopic(channelKey); + topic.addListener(clazz, (channel, msg) -> consumer.accept(msg)); + } + + /** + * 缓存基本的对象,Integer、String、实体类等 + * + * @param key 缓存的键值 + * @param value 缓存的值 + */ + public static void setCacheObject(final String key, final T value) { + setCacheObject(key, value, false); + } + + /** + * 缓存基本的对象,保留当前对象 TTL 有效期 + * + * @param key 缓存的键值 + * @param value 缓存的值 + * @param isSaveTtl 是否保留TTL有效期(例如: set之前ttl剩余90 set之后还是为90) + * @since Redis 6.X 以上使用 setAndKeepTTL 兼容 5.X 方案 + */ + public static void setCacheObject(final String key, final T value, final boolean isSaveTtl) { + RBucket bucket = CLIENT.getBucket(key); + if (isSaveTtl) { + try { + bucket.setAndKeepTTL(value); + } catch (Exception e) { + long timeToLive = bucket.remainTimeToLive(); + setCacheObject(key, value, Duration.ofMillis(timeToLive)); + } + } else { + bucket.set(value); + } + } + + /** + * 缓存基本的对象,Integer、String、实体类等 + * + * @param key 缓存的键值 + * @param value 缓存的值 + * @param duration 时间 + */ + public static void setCacheObject(final String key, final T value, final Duration duration) { + RBatch batch = CLIENT.createBatch(); + RBucketAsync bucket = batch.getBucket(key); + bucket.setAsync(value); + bucket.expireAsync(duration); + batch.execute(); + } + + /** + * 如果不存在则设置 并返回 true 如果存在则返回 false + * + * @param key 缓存的键值 + * @param value 缓存的值 + * @return set成功或失败 + */ + public static boolean setObjectIfAbsent(final String key, final T value, final Duration duration) { + RBucket bucket = CLIENT.getBucket(key); + return bucket.setIfAbsent(value, duration); + } + + /** + * 如果存在则设置 并返回 true 如果存在则返回 false + * + * @param key 缓存的键值 + * @param value 缓存的值 + * @return set成功或失败 + */ + public static boolean setObjectIfExists(final String key, final T value, final Duration duration) { + RBucket bucket = CLIENT.getBucket(key); + return bucket.setIfExists(value, duration); + } + + /** + * 注册对象监听器 + *

+ * key 监听器需开启 `notify-keyspace-events` 等 redis 相关配置 + * + * @param key 缓存的键值 + * @param listener 监听器配置 + */ + public static void addObjectListener(final String key, final ObjectListener listener) { + RBucket result = CLIENT.getBucket(key); + result.addListener(listener); + } + + /** + * 设置有效时间 + * + * @param key Redis键 + * @param timeout 超时时间 + * @return true=设置成功;false=设置失败 + */ + public static boolean expire(final String key, final long timeout) { + return expire(key, Duration.ofSeconds(timeout)); + } + + /** + * 设置有效时间 + * + * @param key Redis键 + * @param duration 超时时间 + * @return true=设置成功;false=设置失败 + */ + public static boolean expire(final String key, final Duration duration) { + RBucket rBucket = CLIENT.getBucket(key); + return rBucket.expire(duration); + } + + /** + * 获得缓存的基本对象。 + * + * @param key 缓存键值 + * @return 缓存键值对应的数据 + */ + public static T getCacheObject(final String key) { + RBucket rBucket = CLIENT.getBucket(key); + return rBucket.get(); + } + + /** + * 获得key剩余存活时间 + * + * @param key 缓存键值 + * @return 剩余存活时间 + */ + public static long getTimeToLive(final String key) { + RBucket rBucket = CLIENT.getBucket(key); + return rBucket.remainTimeToLive(); + } + + /** + * 删除单个对象 + * + * @param key 缓存的键值 + */ + public static boolean deleteObject(final String key) { + return CLIENT.getBucket(key).delete(); + } + + /** + * 删除集合对象 + * + * @param collection 多个对象 + */ + public static void deleteObject(final Collection collection) { + RBatch batch = CLIENT.createBatch(); + collection.forEach(t -> { + batch.getBucket(t.toString()).deleteAsync(); + }); + batch.execute(); + } + + /** + * 检查缓存对象是否存在 + * + * @param key 缓存的键值 + */ + public static boolean isExistsObject(final String key) { + return CLIENT.getBucket(key).isExists(); + } + + /** + * 缓存List数据 + * + * @param key 缓存的键值 + * @param dataList 待缓存的List数据 + * @return 缓存的对象 + */ + public static boolean setCacheList(final String key, final List dataList) { + RList rList = CLIENT.getList(key); + return rList.addAll(dataList); + } + + /** + * 追加缓存List数据 + * + * @param key 缓存的键值 + * @param data 待缓存的数据 + * @return 缓存的对象 + */ + public static boolean addCacheList(final String key, final T data) { + RList rList = CLIENT.getList(key); + return rList.add(data); + } + + /** + * 注册List监听器 + *

+ * key 监听器需开启 `notify-keyspace-events` 等 redis 相关配置 + * + * @param key 缓存的键值 + * @param listener 监听器配置 + */ + public static void addListListener(final String key, final ObjectListener listener) { + RList rList = CLIENT.getList(key); + rList.addListener(listener); + } + + /** + * 获得缓存的list对象 + * + * @param key 缓存的键值 + * @return 缓存键值对应的数据 + */ + public static List getCacheList(final String key) { + RList rList = CLIENT.getList(key); + return rList.readAll(); + } + + /** + * 获得缓存的list对象(范围) + * + * @param key 缓存的键值 + * @param form 起始下标 + * @param to 截止下标 + * @return 缓存键值对应的数据 + */ + public static List getCacheListRange(final String key, int form, int to) { + RList rList = CLIENT.getList(key); + return rList.range(form, to); + } + + /** + * 缓存Set + * + * @param key 缓存键值 + * @param dataSet 缓存的数据 + * @return 缓存数据的对象 + */ + public static boolean setCacheSet(final String key, final Set dataSet) { + RSet rSet = CLIENT.getSet(key); + return rSet.addAll(dataSet); + } + + /** + * 追加缓存Set数据 + * + * @param key 缓存的键值 + * @param data 待缓存的数据 + * @return 缓存的对象 + */ + public static boolean addCacheSet(final String key, final T data) { + RSet rSet = CLIENT.getSet(key); + return rSet.add(data); + } + + /** + * 注册Set监听器 + *

+ * key 监听器需开启 `notify-keyspace-events` 等 redis 相关配置 + * + * @param key 缓存的键值 + * @param listener 监听器配置 + */ + public static void addSetListener(final String key, final ObjectListener listener) { + RSet rSet = CLIENT.getSet(key); + rSet.addListener(listener); + } + + /** + * 获得缓存的set + * + * @param key 缓存的key + * @return set对象 + */ + public static Set getCacheSet(final String key) { + RSet rSet = CLIENT.getSet(key); + return rSet.readAll(); + } + + /** + * 缓存Map + * + * @param key 缓存的键值 + * @param dataMap 缓存的数据 + */ + public static void setCacheMap(final String key, final Map dataMap) { + if (dataMap != null) { + RMap rMap = CLIENT.getMap(key); + rMap.putAll(dataMap); + } + } + + /** + * 注册Map监听器 + *

+ * key 监听器需开启 `notify-keyspace-events` 等 redis 相关配置 + * + * @param key 缓存的键值 + * @param listener 监听器配置 + */ + public static void addMapListener(final String key, final ObjectListener listener) { + RMap rMap = CLIENT.getMap(key); + rMap.addListener(listener); + } + + /** + * 获得缓存的Map + * + * @param key 缓存的键值 + * @return map对象 + */ + public static Map getCacheMap(final String key) { + RMap rMap = CLIENT.getMap(key); + return rMap.getAll(rMap.keySet()); + } + + /** + * 获得缓存Map的key列表 + * + * @param key 缓存的键值 + * @return key列表 + */ + public static Set getCacheMapKeySet(final String key) { + RMap rMap = CLIENT.getMap(key); + return rMap.keySet(); + } + + /** + * 往Hash中存入数据 + * + * @param key Redis键 + * @param hKey Hash键 + * @param value 值 + */ + public static void setCacheMapValue(final String key, final String hKey, final T value) { + RMap rMap = CLIENT.getMap(key); + rMap.put(hKey, value); + } + + /** + * 获取Hash中的数据 + * + * @param key Redis键 + * @param hKey Hash键 + * @return Hash中的对象 + */ + public static T getCacheMapValue(final String key, final String hKey) { + RMap rMap = CLIENT.getMap(key); + return rMap.get(hKey); + } + + /** + * 删除Hash中的数据 + * + * @param key Redis键 + * @param hKey Hash键 + * @return Hash中的对象 + */ + public static T delCacheMapValue(final String key, final String hKey) { + RMap rMap = CLIENT.getMap(key); + return rMap.remove(hKey); + } + + /** + * 删除Hash中的数据 + * + * @param key Redis键 + * @param hKeys Hash键 + */ + public static void delMultiCacheMapValue(final String key, final Set hKeys) { + RBatch batch = CLIENT.createBatch(); + RMapAsync rMap = batch.getMap(key); + for (String hKey : hKeys) { + rMap.removeAsync(hKey); + } + batch.execute(); + } + + /** + * 获取多个Hash中的数据 + * + * @param key Redis键 + * @param hKeys Hash键集合 + * @return Hash对象集合 + */ + public static Map getMultiCacheMapValue(final String key, final Set hKeys) { + RMap rMap = CLIENT.getMap(key); + return rMap.getAll(hKeys); + } + + /** + * 设置原子值 + * + * @param key Redis键 + * @param value 值 + */ + public static void setAtomicValue(String key, long value) { + RAtomicLong atomic = CLIENT.getAtomicLong(key); + atomic.set(value); + } + + /** + * 获取原子值 + * + * @param key Redis键 + * @return 当前值 + */ + public static long getAtomicValue(String key) { + RAtomicLong atomic = CLIENT.getAtomicLong(key); + return atomic.get(); + } + + /** + * 递增原子值 + * + * @param key Redis键 + * @return 当前值 + */ + public static long incrAtomicValue(String key) { + RAtomicLong atomic = CLIENT.getAtomicLong(key); + return atomic.incrementAndGet(); + } + + /** + * 递减原子值 + * + * @param key Redis键 + * @return 当前值 + */ + public static long decrAtomicValue(String key) { + RAtomicLong atomic = CLIENT.getAtomicLong(key); + return atomic.decrementAndGet(); + } + + /** + * 获得缓存的基本对象列表 + * + * @param pattern 字符串前缀 + * @return 对象列表 + */ + public static Collection keys(final String pattern) { + Stream stream = CLIENT.getKeys().getKeysStreamByPattern(pattern); + return stream.collect(Collectors.toList()); + } + + /** + * 删除缓存的基本对象列表 + * + * @param pattern 字符串前缀 + */ + public static void deleteKeys(final String pattern) { + CLIENT.getKeys().deleteByPattern(pattern); + } + + /** + * 检查redis中是否存在key + * + * @param key 键 + */ + public static Boolean hasKey(String key) { + RKeys rKeys = CLIENT.getKeys(); + return rKeys.countExists(key) > 0; + } +} diff --git a/ruoyi-common/ruoyi-common-satoken/pom.xml b/ruoyi-common/ruoyi-common-satoken/pom.xml new file mode 100644 index 0000000..61e1269 --- /dev/null +++ b/ruoyi-common/ruoyi-common-satoken/pom.xml @@ -0,0 +1,62 @@ + + + + org.dromara + ruoyi-common + ${revision} + + 4.0.0 + + ruoyi-common-satoken + + + ruoyi-common-satoken + + + + + + cn.dev33 + sa-token-core + + + + + cn.dev33 + sa-token-jwt + ${satoken.version} + + + cn.hutool + hutool-all + + + + + + cn.hutool + hutool-jwt + + + + + org.dromara + ruoyi-api-system + + + + org.dromara + ruoyi-api-mp + + + + + org.dromara + ruoyi-common-redis + + + + + diff --git a/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/config/SaTokenConfiguration.java b/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/config/SaTokenConfiguration.java new file mode 100644 index 0000000..981cada --- /dev/null +++ b/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/config/SaTokenConfiguration.java @@ -0,0 +1,44 @@ +package org.dromara.common.satoken.config; + +import cn.dev33.satoken.dao.SaTokenDao; +import cn.dev33.satoken.jwt.StpLogicJwtForSimple; +import cn.dev33.satoken.stp.StpInterface; +import cn.dev33.satoken.stp.StpLogic; +import org.dromara.common.core.factory.YmlPropertySourceFactory; +import org.dromara.common.satoken.core.dao.PlusSaTokenDao; +import org.dromara.common.satoken.core.service.SaPermissionImpl; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.PropertySource; + +/** + * Sa-Token 配置 + * + * @author Lion Li + */ +@AutoConfiguration +@PropertySource(value = "classpath:common-satoken.yml", factory = YmlPropertySourceFactory.class) +public class SaTokenConfiguration { + + @Bean + public StpLogic getStpLogicJwt() { + return new StpLogicJwtForSimple(); + } + + /** + * 权限接口实现(使用bean注入方便用户替换) + */ + @Bean + public StpInterface stpInterface() { + return new SaPermissionImpl(); + } + + /** + * 自定义dao层存储 + */ + @Bean + public SaTokenDao saTokenDao() { + return new PlusSaTokenDao(); + } + +} diff --git a/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/core/dao/PlusSaTokenDao.java b/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/core/dao/PlusSaTokenDao.java new file mode 100644 index 0000000..d885962 --- /dev/null +++ b/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/core/dao/PlusSaTokenDao.java @@ -0,0 +1,148 @@ +package org.dromara.common.satoken.core.dao; + +import cn.dev33.satoken.dao.SaTokenDao; +import cn.dev33.satoken.util.SaFoxUtil; +import org.dromara.common.redis.utils.RedisUtils; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * Sa-Token持久层接口(使用框架自带RedisUtils实现 协议统一) + * + * @author Lion Li + */ +public class PlusSaTokenDao implements SaTokenDao { + + /** + * 获取Value,如无返空 + */ + @Override + public String get(String key) { + return RedisUtils.getCacheObject(key); + } + + /** + * 写入Value,并设定存活时间 (单位: 秒) + */ + @Override + public void set(String key, String value, long timeout) { + if (timeout == 0 || timeout <= NOT_VALUE_EXPIRE) { + return; + } + // 判断是否为永不过期 + if (timeout == NEVER_EXPIRE) { + RedisUtils.setCacheObject(key, value); + } else { + RedisUtils.setCacheObject(key, value, Duration.ofSeconds(timeout)); + } + } + + /** + * 修修改指定key-value键值对 (过期时间不变) + */ + @Override + public void update(String key, String value) { + if (RedisUtils.hasKey(key)) { + RedisUtils.setCacheObject(key, value, true); + } + } + + /** + * 删除Value + */ + @Override + public void delete(String key) { + RedisUtils.deleteObject(key); + } + + /** + * 获取Value的剩余存活时间 (单位: 秒) + */ + @Override + public long getTimeout(String key) { + long timeout = RedisUtils.getTimeToLive(key); + return timeout < 0 ? timeout : timeout / 1000; + } + + /** + * 修改Value的剩余存活时间 (单位: 秒) + */ + @Override + public void updateTimeout(String key, long timeout) { + RedisUtils.expire(key, Duration.ofSeconds(timeout)); + } + + + /** + * 获取Object,如无返空 + */ + @Override + public Object getObject(String key) { + return RedisUtils.getCacheObject(key); + } + + /** + * 写入Object,并设定存活时间 (单位: 秒) + */ + @Override + public void setObject(String key, Object object, long timeout) { + if (timeout == 0 || timeout <= NOT_VALUE_EXPIRE) { + return; + } + // 判断是否为永不过期 + if (timeout == NEVER_EXPIRE) { + RedisUtils.setCacheObject(key, object); + } else { + RedisUtils.setCacheObject(key, object, Duration.ofSeconds(timeout)); + } + } + + /** + * 更新Object (过期时间不变) + */ + @Override + public void updateObject(String key, Object object) { + if (RedisUtils.hasKey(key)) { + RedisUtils.setCacheObject(key, object, true); + } + } + + /** + * 删除Object + */ + @Override + public void deleteObject(String key) { + RedisUtils.deleteObject(key); + } + + /** + * 获取Object的剩余存活时间 (单位: 秒) + */ + @Override + public long getObjectTimeout(String key) { + long timeout = RedisUtils.getTimeToLive(key); + return timeout < 0 ? timeout : timeout / 1000; + } + + /** + * 修改Object的剩余存活时间 (单位: 秒) + */ + @Override + public void updateObjectTimeout(String key, long timeout) { + RedisUtils.expire(key, Duration.ofSeconds(timeout)); + } + + + /** + * 搜索数据 + */ + @Override + public List searchData(String prefix, String keyword, int start, int size, boolean sortType) { + Collection keys = RedisUtils.keys(prefix + "*" + keyword + "*"); + List list = new ArrayList<>(keys); + return SaFoxUtil.searchList(list, start, size, sortType); + } +} diff --git a/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/core/service/SaPermissionImpl.java b/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/core/service/SaPermissionImpl.java new file mode 100644 index 0000000..257922b --- /dev/null +++ b/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/core/service/SaPermissionImpl.java @@ -0,0 +1,47 @@ +package org.dromara.common.satoken.core.service; + +import cn.dev33.satoken.stp.StpInterface; +import org.dromara.common.core.enums.UserType; +import org.dromara.common.satoken.utils.LoginHelper; +import org.dromara.system.api.model.LoginUser; + +import java.util.ArrayList; +import java.util.List; + +/** + * sa-token 权限管理实现类 + * + * @author Lion Li + */ +public class SaPermissionImpl implements StpInterface { + + /** + * 获取菜单权限列表 + */ + @Override + public List getPermissionList(Object loginId, String loginType) { + LoginUser loginUser = LoginHelper.getLoginUser(); + UserType userType = UserType.getUserType(loginUser.getUserType()); + if (userType == UserType.SYS_USER) { + return new ArrayList<>(loginUser.getMenuPermission()); + } else if (userType == UserType.APP_USER) { + // 其他端 自行根据业务编写 + } + return new ArrayList<>(); + } + + /** + * 获取角色权限列表 + */ + @Override + public List getRoleList(Object loginId, String loginType) { + LoginUser loginUser = LoginHelper.getLoginUser(); + UserType userType = UserType.getUserType(loginUser.getUserType()); + if (userType == UserType.SYS_USER) { + return new ArrayList<>(loginUser.getRolePermission()); + } else if (userType == UserType.APP_USER) { + // 其他端 自行根据业务编写 + } + return new ArrayList<>(); + } +} diff --git a/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/utils/LoginHelper.java b/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/utils/LoginHelper.java new file mode 100644 index 0000000..1eceb88 --- /dev/null +++ b/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/utils/LoginHelper.java @@ -0,0 +1,231 @@ +package org.dromara.common.satoken.utils; + +import cn.dev33.satoken.context.SaHolder; +import cn.dev33.satoken.context.model.SaStorage; +import cn.dev33.satoken.session.SaSession; +import cn.dev33.satoken.stp.SaLoginModel; +import cn.dev33.satoken.stp.StpUtil; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.util.ObjectUtil; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.dromara.common.core.constant.TenantConstants; +import org.dromara.common.core.constant.UserConstants; +import org.dromara.common.core.enums.UserType; +import org.dromara.system.api.model.LoginUser; + +import java.util.Set; +import java.util.function.Supplier; + +/** + * 登录鉴权助手 + *

+ * user_type 为 用户类型 同一个用户表 可以有多种用户类型 例如 pc,app + * deivce 为 设备类型 同一个用户类型 可以有 多种设备类型 例如 web,ios + * 可以组成 用户类型与设备类型多对多的 权限灵活控制 + *

+ * 多用户体系 针对 多种用户类型 但权限控制不一致 + * 可以组成 多用户类型表与多设备类型 分别控制权限 + * + * @author Lion Li + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class LoginHelper { + + public static final String LOGIN_USER_KEY = "loginUser"; + public static final String TENANT_KEY = "tenantId"; + + //获取主体ID + public static final String BUS_ID = "bus_id"; + public static final String USER_KEY = "userId"; + + public static final String BUS_ROLE = "bus_role"; + public static final String BUS_NO = "bus_no"; + public static final String DEPT_KEY = "deptId"; + public static final String CLIENT_KEY = "clientid"; + public static final String TENANT_ADMIN_KEY = "isTenantAdmin"; + + /** + * 登录系统 基于 设备类型 + * 针对相同用户体系不同设备 + * + * @param loginUser 登录用户信息 + * @param model 配置参数 + */ + public static void login(LoginUser loginUser, SaLoginModel model) { + SaStorage storage = SaHolder.getStorage(); + storage.set(LOGIN_USER_KEY, loginUser); + storage.set(TENANT_KEY, loginUser.getTenantId()); + storage.set(USER_KEY, loginUser.getUserId()); + storage.set(DEPT_KEY, loginUser.getDeptId()); + storage.set(BUS_NO,loginUser.getBusNo()); + storage.set(BUS_ID,loginUser.getBusId()); + storage.set(BUS_ROLE,loginUser.getBusRole()); + model = ObjectUtil.defaultIfNull(model, new SaLoginModel()); + StpUtil.login(loginUser.getLoginId(), + model.setExtra(TENANT_KEY, loginUser.getTenantId()) + .setExtra(USER_KEY, loginUser.getUserId()) + .setExtra(BUS_NO, loginUser.getBusNo()) + .setExtra(BUS_ID, loginUser.getBusId()) + .setExtra(BUS_ROLE, loginUser.getBusRole()) + .setExtra(DEPT_KEY, loginUser.getDeptId())); + StpUtil.getTokenSession().set(LOGIN_USER_KEY, loginUser); + } + + /** + * 更新登录用户信息 + * @param loginUser + */ + public static void updateLoginUser(LoginUser loginUser) { + StpUtil.getTokenSession().set(LOGIN_USER_KEY, loginUser); + } + + /** + * 获取用户(多级缓存) + */ + public static LoginUser getLoginUser() { + return (LoginUser) getStorageIfAbsentSet(LOGIN_USER_KEY, () -> { + SaSession session = StpUtil.getTokenSession(); + if (ObjectUtil.isNull(session)) { + return null; + } + return session.get(LOGIN_USER_KEY); + }); + } + + /** + * 获取用户基于token + */ + public static LoginUser getLoginUser(String token) { + SaSession session = StpUtil.getTokenSessionByToken(token); + if (ObjectUtil.isNull(session)) { + return null; + } + return (LoginUser) session.get(LOGIN_USER_KEY); + } + + /** + * 获取用户id + */ + public static Long getUserId() { + return Convert.toLong(getExtra(USER_KEY)); + } + + /** + * 获取租户ID + */ + public static String getTenantId() { + return Convert.toStr(getExtra(TENANT_KEY)); + } + + + + + /** + * @Param + * @Return + * @Author sunzexing + * @Date 2024-04-20 10:54 + * 说明:获取登录用户企业编码 + **/ + + public static String getNo(){return Convert.toStr(getExtra(BUS_NO));} + + + + /** + * @Param + * @Return + * @Author sunzexing + * @Date 2024-04-20 10:54 + * 说明:获取登录用户角色id(5:平台 6:服务商 7:运营商 8:代理商 9:商户) + **/ + + public static Integer getBusRole(){return Convert.toInt(getExtra(BUS_ROLE));} + + + + /** + * @Param + * @Return + * @Author sunzexing + * @Date 2024-04-20 10:54 + * 说明:获取登录用户主体id(结算中心id或运营商id或渠道商id或商户id) + **/ + public static Long getBusId(){return Convert.toLong(getExtra(BUS_ID));} + + + /** + * 获取部门ID + */ + public static Long getDeptId() { + return Convert.toLong(getExtra(DEPT_KEY)); + } + + private static Object getExtra(String key) { + return getStorageIfAbsentSet(key, () -> StpUtil.getExtra(key)); + } + + /** + * 获取用户账户 + */ + public static String getUsername() { + return getLoginUser().getUsername(); + } + + /** + * 获取用户类型 + */ + public static UserType getUserType() { + String loginId = StpUtil.getLoginIdAsString(); + return UserType.getUserType(loginId); + } + + /** + * 是否为超级管理员 + * + * @param userId 用户ID + * @return 结果 + */ + public static boolean isSuperAdmin(Long userId) { + return UserConstants.SUPER_ADMIN_ID.equals(userId); + } + + public static boolean isSuperAdmin() { + return isSuperAdmin(getUserId()); + } + + /** + * 是否为超级管理员 + * + * @param rolePermission 角色权限标识组 + * @return 结果 + */ + public static boolean isTenantAdmin(Set rolePermission) { + return rolePermission.contains(TenantConstants.TENANT_ADMIN_ROLE_KEY); + } + + public static boolean isTenantAdmin() { + Object value = getStorageIfAbsentSet(TENANT_ADMIN_KEY, () -> { + return isTenantAdmin(getLoginUser().getRolePermission()); + }); + return value != null ? Convert.toBool(value) : false; + } + + public static boolean isLogin() { + return getLoginUser() != null; + } + + public static Object getStorageIfAbsentSet(String key, Supplier handle) { + try { + Object obj = SaHolder.getStorage().get(key); + if (ObjectUtil.isNull(obj)) { + obj = handle.get(); + SaHolder.getStorage().set(key, obj); + } + return obj; + } catch (Exception e) { + return null; + } + } +} diff --git a/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/utils/MpLoginHelper.java b/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/utils/MpLoginHelper.java new file mode 100644 index 0000000..3dcbdd2 --- /dev/null +++ b/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/utils/MpLoginHelper.java @@ -0,0 +1,198 @@ +package org.dromara.common.satoken.utils; + +import cn.dev33.satoken.context.SaHolder; +import cn.dev33.satoken.context.model.SaStorage; +import cn.dev33.satoken.session.SaSession; +import cn.dev33.satoken.stp.SaLoginModel; +import cn.dev33.satoken.stp.StpUtil; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.util.ObjectUtil; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.dromara.common.core.constant.TenantConstants; +import org.dromara.common.core.constant.UserConstants; +import org.dromara.common.core.enums.UserType; +import org.dromara.map.api.domain.RemoteUserWorkerVo; +import org.dromara.map.api.model.LoginMpUser; +import org.dromara.system.api.model.LoginUser; + +import java.util.Set; +import java.util.function.Supplier; + +/** + * 登录鉴权助手 + *

+ * user_type 为 用户类型 同一个用户表 可以有多种用户类型 例如 pc,app + * deivce 为 设备类型 同一个用户类型 可以有 多种设备类型 例如 web,ios + * 可以组成 用户类型与设备类型多对多的 权限灵活控制 + *

+ * 多用户体系 针对 多种用户类型 但权限控制不一致 + * 可以组成 多用户类型表与多设备类型 分别控制权限 + * + * @author Lion Li + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class MpLoginHelper { + + public static final String LOGIN_USER_KEY = "mpLoginUser"; + public static final String TENANT_KEY = "tenantId"; + public static final String USER_KEY = "userId"; + public static final String DEPT_KEY = "deptId"; + public static final String CLIENT_KEY = "clientid"; + public static final String TENANT_ADMIN_KEY = "isTenantAdmin"; + + /** + * 登录系统 基于 设备类型 + * 针对相同用户体系不同设备 + * + * @param loginUser 登录用户信息 + * @param model 配置参数 + */ + public static void login(LoginMpUser loginUser, String tenatId, SaLoginModel model) { + SaStorage storage = SaHolder.getStorage(); + storage.set(LOGIN_USER_KEY, loginUser); + storage.set(TENANT_KEY, tenatId); + storage.set(USER_KEY, loginUser.getUserId()); + model = ObjectUtil.defaultIfNull(model, new SaLoginModel()); +// StpUtil.login(loginUser.getUserId(), +// model.setExtra(TENANT_KEY, tenatId) +// .setExtra("PHONE",loginUser.getPhone()) +// .setExtra(USER_KEY, loginUser.getUserId())); + StpUtil.login(loginUser.getUserId(),model.setExtra(USER_KEY, loginUser.getUserId())); + StpUtil.getTokenSession().set(LOGIN_USER_KEY, loginUser); + } + + + /** + * 登录系统 基于 设备类型 + * 针对相同用户体系不同设备 + * + * @param loginUser 登录用户信息 + */ + public static void setLoginInfo(LoginMpUser loginUser) { + SaStorage storage = SaHolder.getStorage(); + storage.set(LOGIN_USER_KEY, loginUser); +// storage.set(TENANT_KEY, tenatId); + storage.set(USER_KEY, loginUser.getUserId()); +// model = ObjectUtil.defaultIfNull(model, new SaLoginModel()); +// StpUtil.login(loginUser.getUserId(), +// model.setExtra(TENANT_KEY, tenatId) +// .setExtra("PHONE",loginUser.getPhone()) +// .setExtra(USER_KEY, loginUser.getUserId())); +// StpUtil.login(loginUser.getUserId()); + StpUtil.getTokenSession().set(LOGIN_USER_KEY, loginUser); + } + + + /** + * 获取用户(多级缓存) + */ + public static LoginMpUser getLoginUser() { + return (LoginMpUser) getStorageIfAbsentSet(LOGIN_USER_KEY, () -> { + SaSession session = StpUtil.getTokenSession(); + if (ObjectUtil.isNull(session)) { + return null; + } + return session.get(LOGIN_USER_KEY); + }); + } + + /** + * 获取用户基于token + */ + public static LoginMpUser getLoginUser(String token) { + SaSession session = StpUtil.getTokenSessionByToken(token); + if (ObjectUtil.isNull(session)) { + return null; + } + return (LoginMpUser) session.get(LOGIN_USER_KEY); + } + + /** + * 获取用户id + */ + public static Long getUserId() { + return Convert.toLong(getExtra(USER_KEY)); + } + + /** + * 获取租户ID + */ + public static String getTenantId() { + return Convert.toStr(getExtra(TENANT_KEY)); + } + + /** + * 获取部门ID + */ + public static Long getDeptId() { + return Convert.toLong(getExtra(DEPT_KEY)); + } + + public static Object getExtra(String key) { + return getStorageIfAbsentSet(key, () -> StpUtil.getExtra(key)); + } + + /** + * 获取用户账户 + */ +// public static String getUsername() { +// return getLoginUser().getUsername(); +// } + + /** + * 获取用户类型 + */ + public static UserType getUserType() { + String loginId = StpUtil.getLoginIdAsString(); + return UserType.getUserType(loginId); + } + + /** + * 是否为超级管理员 + * + * @param userId 用户ID + * @return 结果 + */ + public static boolean isSuperAdmin(Long userId) { + return UserConstants.SUPER_ADMIN_ID.equals(userId); + } + + public static boolean isSuperAdmin() { + return isSuperAdmin(getUserId()); + } + + /** + * 是否为超级管理员 + * + * @param rolePermission 角色权限标识组 + * @return 结果 + */ + public static boolean isTenantAdmin(Set rolePermission) { + return rolePermission.contains(TenantConstants.TENANT_ADMIN_ROLE_KEY); + } + +// public static boolean isTenantAdmin() { +// Object value = getStorageIfAbsentSet(TENANT_ADMIN_KEY, () -> { +// return isTenantAdmin(getLoginUser().getRolePermission()); +// }); +// return Convert.toBool(value); +// } + + public static boolean isLogin() { + return getLoginUser() != null; + } + + public static Object getStorageIfAbsentSet(String key, Supplier handle) { + try { + Object obj = SaHolder.getStorage().get(key); + if (ObjectUtil.isNull(obj)) { + obj = handle.get(); + SaHolder.getStorage().set(key, obj); + } + return obj; + } catch (Exception e) { + return null; + } + } +} diff --git a/ruoyi-common/ruoyi-common-satoken/src/main/resources/common-satoken.yml b/ruoyi-common/ruoyi-common-satoken/src/main/resources/common-satoken.yml new file mode 100644 index 0000000..95e1b41 --- /dev/null +++ b/ruoyi-common/ruoyi-common-satoken/src/main/resources/common-satoken.yml @@ -0,0 +1,13 @@ +# 内置配置 不允许修改 如需修改请在 nacos 上写相同配置覆盖 +# Sa-Token配置 +sa-token: + # 允许动态设置 token 有效期 + dynamic-active-timeout: true + # 允许从 请求参数 读取 token + is-read-body: true + # 允许从 header 读取 token + is-read-header: true + # 关闭 cookie 鉴权 从根源杜绝 csrf 漏洞风险 + is-read-cookie: false + # token前缀 + token-prefix: "Bearer" diff --git a/ruoyi-common/ruoyi-common-seata/pom.xml b/ruoyi-common/ruoyi-common-seata/pom.xml new file mode 100644 index 0000000..b2b51f8 --- /dev/null +++ b/ruoyi-common/ruoyi-common-seata/pom.xml @@ -0,0 +1,53 @@ + + + + org.dromara + ruoyi-common + ${revision} + + 4.0.0 + + ruoyi-common-seata + + + ruoyi-common-seata 分布式事务 + + + + + + org.dromara + ruoyi-common-core + + + + org.apache.dubbo.extensions + dubbo-filter-seata + 1.0.1 + + + io.seata + seata-core + + + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-seata + + + org.apache.logging.log4j + * + + + org.apache.dubbo.extensions + dubbo-filter-seata + + + + + diff --git a/ruoyi-common/ruoyi-common-seata/src/main/java/org/dromara/common/seata/config/SeataConfiguration.java b/ruoyi-common/ruoyi-common-seata/src/main/java/org/dromara/common/seata/config/SeataConfiguration.java new file mode 100644 index 0000000..6b4ddb2 --- /dev/null +++ b/ruoyi-common/ruoyi-common-seata/src/main/java/org/dromara/common/seata/config/SeataConfiguration.java @@ -0,0 +1,16 @@ +package org.dromara.common.seata.config; + +import org.dromara.common.core.factory.YmlPropertySourceFactory; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.context.annotation.PropertySource; + +/** + * seata 配置 + * + * @author Lion Li + */ +@AutoConfiguration +@PropertySource(value = "classpath:common-seata.yml", factory = YmlPropertySourceFactory.class) +public class SeataConfiguration { + +} diff --git a/ruoyi-common/ruoyi-common-seata/src/main/resources/common-seata.yml b/ruoyi-common/ruoyi-common-seata/src/main/resources/common-seata.yml new file mode 100644 index 0000000..698f2dd --- /dev/null +++ b/ruoyi-common/ruoyi-common-seata/src/main/resources/common-seata.yml @@ -0,0 +1,19 @@ +# 内置配置 不允许修改 如需修改请在 nacos 上写相同配置覆盖 +# seata配置 +seata: + config: + type: nacos + nacos: + server-addr: ${spring.cloud.nacos.server-addr} + group: ${spring.cloud.nacos.config.group} + namespace: ${spring.profiles.active} + data-id: seata-server.properties + registry: + type: nacos + nacos: + application: ruoyi-seata-server + server-addr: ${spring.cloud.nacos.server-addr} + group: ${spring.cloud.nacos.discovery.group} + namespace: ${spring.profiles.active} + # 关闭自动代理 + enable-auto-data-source-proxy: false diff --git a/ruoyi-common/ruoyi-common-security/pom.xml b/ruoyi-common/ruoyi-common-security/pom.xml new file mode 100644 index 0000000..ee499f1 --- /dev/null +++ b/ruoyi-common/ruoyi-common-security/pom.xml @@ -0,0 +1,32 @@ + + + + org.dromara + ruoyi-common + ${revision} + + 4.0.0 + + ruoyi-common-security + + + ruoyi-common-security 安全模块 + + + + + org.dromara + ruoyi-common-satoken + + + + + cn.dev33 + sa-token-spring-boot3-starter + + + + + diff --git a/ruoyi-common/ruoyi-common-security/src/main/java/org/dromara/common/security/config/SecurityConfiguration.java b/ruoyi-common/ruoyi-common-security/src/main/java/org/dromara/common/security/config/SecurityConfiguration.java new file mode 100644 index 0000000..efd34fc --- /dev/null +++ b/ruoyi-common/ruoyi-common-security/src/main/java/org/dromara/common/security/config/SecurityConfiguration.java @@ -0,0 +1,47 @@ +package org.dromara.common.security.config; + +import cn.dev33.satoken.SaManager; +import cn.dev33.satoken.filter.SaServletFilter; +import cn.dev33.satoken.interceptor.SaInterceptor; +import cn.dev33.satoken.same.SaSameUtil; +import cn.dev33.satoken.util.SaResult; +import org.dromara.common.core.constant.HttpStatus; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +/** + * 权限安全配置 + * + * @author Lion Li + */ +@AutoConfiguration +public class SecurityConfiguration implements WebMvcConfigurer { + + /** + * 注册sa-token的拦截器 + */ + @Override + public void addInterceptors(InterceptorRegistry registry) { + // 注册路由拦截器,自定义验证规则 + registry.addInterceptor(new SaInterceptor()).addPathPatterns("/**"); + } + + /** + * 校验是否从网关转发 + */ + @Bean + public SaServletFilter getSaServletFilter() { + return new SaServletFilter() + .addInclude("/**") + .addExclude("/actuator/**") + .setAuth(obj -> { + if (SaManager.getConfig().getCheckSameToken()) { + SaSameUtil.checkCurrentRequestToken(); + } + }) + .setError(e -> SaResult.error("认证失败,无法访问系统资源").setCode(HttpStatus.UNAUTHORIZED)); + } + +} diff --git a/ruoyi-common/ruoyi-common-security/src/main/java/org/dromara/common/security/handler/GlobalExceptionHandler.java b/ruoyi-common/ruoyi-common-security/src/main/java/org/dromara/common/security/handler/GlobalExceptionHandler.java new file mode 100644 index 0000000..8fb1f76 --- /dev/null +++ b/ruoyi-common/ruoyi-common-security/src/main/java/org/dromara/common/security/handler/GlobalExceptionHandler.java @@ -0,0 +1,175 @@ +package org.dromara.common.security.handler; + +import cn.dev33.satoken.exception.NotLoginException; +import cn.dev33.satoken.exception.NotPermissionException; +import cn.dev33.satoken.exception.NotRoleException; +import cn.dev33.satoken.exception.SameTokenInvalidException; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.http.HttpStatus; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.ConstraintViolation; +import jakarta.validation.ConstraintViolationException; +import lombok.extern.slf4j.Slf4j; +import org.dromara.common.core.domain.R; +import org.dromara.common.core.exception.ServiceException; +import org.dromara.common.core.exception.base.BaseException; +import org.dromara.common.core.utils.StreamUtils; +import org.springframework.context.support.DefaultMessageSourceResolvable; +import org.springframework.validation.BindException; +import org.springframework.web.HttpRequestMethodNotSupportedException; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.MissingPathVariableException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; + +/** + * 全局异常处理器 + * + * @author Lion Li + */ +@Slf4j +@RestControllerAdvice +public class GlobalExceptionHandler { + + /** + * 权限码异常 + */ + @ExceptionHandler(NotPermissionException.class) + public R handleNotPermissionException(NotPermissionException e, HttpServletRequest request) { + String requestURI = request.getRequestURI(); + log.error("请求地址'{}',权限码校验失败'{}'", requestURI, e.getMessage()); + return R.fail(HttpStatus.HTTP_FORBIDDEN, "没有访问权限,请联系管理员授权"); + } + + /** + * 角色权限异常 + */ + @ExceptionHandler(NotRoleException.class) + public R handleNotRoleException(NotRoleException e, HttpServletRequest request) { + String requestURI = request.getRequestURI(); + log.error("请求地址'{}',角色权限校验失败'{}'", requestURI, e.getMessage()); + return R.fail(HttpStatus.HTTP_FORBIDDEN, "没有访问权限,请联系管理员授权"); + } + + /** + * 认证失败 + */ + @ExceptionHandler(NotLoginException.class) + public R handleNotLoginException(NotLoginException e, HttpServletRequest request) { + String requestURI = request.getRequestURI(); + log.error("请求地址'{}',认证失败'{}',无法访问系统资源", requestURI, e.getMessage()); + return R.fail(HttpStatus.HTTP_UNAUTHORIZED, "认证失败,无法访问系统资源"); + } + + /** + * 无效认证 + */ + @ExceptionHandler(SameTokenInvalidException.class) + public R handleSameTokenInvalidException(SameTokenInvalidException e, HttpServletRequest request) { + String requestURI = request.getRequestURI(); + log.error("请求地址'{}',内网认证失败'{}',无法访问系统资源", requestURI, e.getMessage()); + return R.fail(HttpStatus.HTTP_UNAUTHORIZED, "认证失败,无法访问系统资源"); + } + + /** + * 请求方式不支持 + */ + @ExceptionHandler(HttpRequestMethodNotSupportedException.class) + public R handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e, + HttpServletRequest request) { + String requestURI = request.getRequestURI(); + log.error("请求地址'{}',不支持'{}'请求", requestURI, e.getMethod()); + return R.fail(e.getMessage()); + } + + /** + * 业务异常 + */ + @ExceptionHandler(ServiceException.class) + public R handleServiceException(ServiceException e, HttpServletRequest request) { + log.error(e.getMessage()); + Integer code = e.getCode(); + return ObjectUtil.isNotNull(code) ? R.fail(code, e.getMessage()) : R.fail(e.getMessage()); + } + + /** + * 业务异常 + */ + @ExceptionHandler(BaseException.class) + public R handleBaseException(BaseException e, HttpServletRequest request) { + log.error(e.getMessage()); + return R.fail(e.getMessage()); + } + + /** + * 请求路径中缺少必需的路径变量 + */ + @ExceptionHandler(MissingPathVariableException.class) + public R handleMissingPathVariableException(MissingPathVariableException e, HttpServletRequest request) { + String requestURI = request.getRequestURI(); + log.error("请求路径中缺少必需的路径变量'{}',发生系统异常.", requestURI); + return R.fail(String.format("请求路径中缺少必需的路径变量[%s]", e.getVariableName())); + } + + /** + * 请求参数类型不匹配 + */ + @ExceptionHandler(MethodArgumentTypeMismatchException.class) + public R handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e, HttpServletRequest request) { + String requestURI = request.getRequestURI(); + log.error("请求参数类型不匹配'{}',发生系统异常.", requestURI); + return R.fail(String.format("请求参数类型不匹配,参数[%s]要求类型为:'%s',但输入值为:'%s'", e.getName(), e.getRequiredType().getName(), e.getValue())); + } + + /** + * 拦截未知的运行时异常 + */ + @ExceptionHandler(RuntimeException.class) + public R handleRuntimeException(RuntimeException e, HttpServletRequest request) { + String requestURI = request.getRequestURI(); + log.error("请求地址'{}',发生未知异常.", requestURI, e); + return R.fail(e.getMessage()); + } + + /** + * 系统异常 + */ + @ExceptionHandler(Exception.class) + public R handleException(Exception e, HttpServletRequest request) { + String requestURI = request.getRequestURI(); + log.error("请求地址'{}',发生系统异常.", requestURI, e); + return R.fail(e.getMessage()); + } + + /** + * 自定义验证异常 + */ + @ExceptionHandler(BindException.class) + public R handleBindException(BindException e) { + log.error(e.getMessage()); + String message = StreamUtils.join(e.getAllErrors(), DefaultMessageSourceResolvable::getDefaultMessage, ", "); + return R.fail(message); + } + + /** + * 自定义验证异常 + */ + @ExceptionHandler(ConstraintViolationException.class) + public R constraintViolationException(ConstraintViolationException e) { + log.error(e.getMessage()); + String message = StreamUtils.join(e.getConstraintViolations(), ConstraintViolation::getMessage, ", "); + return R.fail(message); + } + + /** + * 自定义验证异常 + */ + @ExceptionHandler(MethodArgumentNotValidException.class) + public R handleMethodArgumentNotValidException(MethodArgumentNotValidException e) { + log.error(e.getMessage()); + String message = e.getBindingResult().getFieldError().getDefaultMessage(); + return R.fail(message); + } + +} diff --git a/ruoyi-common/ruoyi-common-sensitive/pom.xml b/ruoyi-common/ruoyi-common-sensitive/pom.xml new file mode 100644 index 0000000..fecdf09 --- /dev/null +++ b/ruoyi-common/ruoyi-common-sensitive/pom.xml @@ -0,0 +1,25 @@ + + + + org.dromara + ruoyi-common + ${revision} + + 4.0.0 + + ruoyi-common-sensitive + + + ruoyi-common-sensitive 脱敏模块 + + + + + org.dromara + ruoyi-common-json + + + + diff --git a/ruoyi-common/ruoyi-common-sensitive/src/main/java/org/dromara/common/sensitive/annotation/Sensitive.java b/ruoyi-common/ruoyi-common-sensitive/src/main/java/org/dromara/common/sensitive/annotation/Sensitive.java new file mode 100644 index 0000000..1dfc896 --- /dev/null +++ b/ruoyi-common/ruoyi-common-sensitive/src/main/java/org/dromara/common/sensitive/annotation/Sensitive.java @@ -0,0 +1,28 @@ +package org.dromara.common.sensitive.annotation; + +import com.fasterxml.jackson.annotation.JacksonAnnotationsInside; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import org.dromara.common.sensitive.core.SensitiveStrategy; +import org.dromara.common.sensitive.handler.SensitiveHandler; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 数据脱敏注解 + * + * @author zhujie + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +@JacksonAnnotationsInside +@JsonSerialize(using = SensitiveHandler.class) +public @interface Sensitive { + SensitiveStrategy strategy(); + + String roleKey() default ""; + + String perms() default ""; +} diff --git a/ruoyi-common/ruoyi-common-sensitive/src/main/java/org/dromara/common/sensitive/core/SensitiveService.java b/ruoyi-common/ruoyi-common-sensitive/src/main/java/org/dromara/common/sensitive/core/SensitiveService.java new file mode 100644 index 0000000..7b5264b --- /dev/null +++ b/ruoyi-common/ruoyi-common-sensitive/src/main/java/org/dromara/common/sensitive/core/SensitiveService.java @@ -0,0 +1,18 @@ +package org.dromara.common.sensitive.core; + +/** + * 脱敏服务 + * 默认管理员不过滤 + * 需自行根据业务重写实现 + * + * @author Lion Li + * @version 3.6.0 + */ +public interface SensitiveService { + + /** + * 是否脱敏 + */ + boolean isSensitive(String roleKey, String perms); + +} diff --git a/ruoyi-common/ruoyi-common-sensitive/src/main/java/org/dromara/common/sensitive/core/SensitiveStrategy.java b/ruoyi-common/ruoyi-common-sensitive/src/main/java/org/dromara/common/sensitive/core/SensitiveStrategy.java new file mode 100644 index 0000000..9d1978a --- /dev/null +++ b/ruoyi-common/ruoyi-common-sensitive/src/main/java/org/dromara/common/sensitive/core/SensitiveStrategy.java @@ -0,0 +1,49 @@ +package org.dromara.common.sensitive.core; + +import cn.hutool.core.util.DesensitizedUtil; +import lombok.AllArgsConstructor; + +import java.util.function.Function; + +/** + * 脱敏策略 + * + * @author Yjoioooo + * @version 3.6.0 + */ +@AllArgsConstructor +public enum SensitiveStrategy { + + /** + * 身份证脱敏 + */ + ID_CARD(s -> DesensitizedUtil.idCardNum(s, 3, 4)), + + /** + * 手机号脱敏 + */ + PHONE(DesensitizedUtil::mobilePhone), + + /** + * 地址脱敏 + */ + ADDRESS(s -> DesensitizedUtil.address(s, 8)), + + /** + * 邮箱脱敏 + */ + EMAIL(DesensitizedUtil::email), + + /** + * 银行卡 + */ + BANK_CARD(DesensitizedUtil::bankCard); + + //可自行添加其他脱敏策略 + + private final Function desensitizer; + + public Function desensitizer() { + return desensitizer; + } +} diff --git a/ruoyi-common/ruoyi-common-sensitive/src/main/java/org/dromara/common/sensitive/handler/SensitiveHandler.java b/ruoyi-common/ruoyi-common-sensitive/src/main/java/org/dromara/common/sensitive/handler/SensitiveHandler.java new file mode 100644 index 0000000..c76c83a --- /dev/null +++ b/ruoyi-common/ruoyi-common-sensitive/src/main/java/org/dromara/common/sensitive/handler/SensitiveHandler.java @@ -0,0 +1,58 @@ +package org.dromara.common.sensitive.handler; + +import cn.hutool.core.util.ObjectUtil; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.BeanProperty; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.ser.ContextualSerializer; +import org.dromara.common.core.utils.SpringUtils; +import org.dromara.common.sensitive.annotation.Sensitive; +import org.dromara.common.sensitive.core.SensitiveService; +import org.dromara.common.sensitive.core.SensitiveStrategy; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.BeansException; + +import java.io.IOException; +import java.util.Objects; + +/** + * 数据脱敏json序列化工具 + * + * @author Yjoioooo + */ +@Slf4j +public class SensitiveHandler extends JsonSerializer implements ContextualSerializer { + + private SensitiveStrategy strategy; + private String roleKey; + private String perms; + + @Override + public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException { + try { + SensitiveService sensitiveService = SpringUtils.getBean(SensitiveService.class); + if (ObjectUtil.isNotNull(sensitiveService) && sensitiveService.isSensitive(roleKey, perms)) { + gen.writeString(strategy.desensitizer().apply(value)); + } else { + gen.writeString(value); + } + } catch (BeansException e) { + log.error("脱敏实现不存在, 采用默认处理 => {}", e.getMessage()); + gen.writeString(value); + } + } + + @Override + public JsonSerializer createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException { + Sensitive annotation = property.getAnnotation(Sensitive.class); + if (Objects.nonNull(annotation) && Objects.equals(String.class, property.getType().getRawClass())) { + this.strategy = annotation.strategy(); + this.roleKey = annotation.roleKey(); + this.perms = annotation.perms(); + return this; + } + return prov.findValueSerializer(property.getType(), property); + } +} diff --git a/ruoyi-common/ruoyi-common-sentinel/pom.xml b/ruoyi-common/ruoyi-common-sentinel/pom.xml new file mode 100644 index 0000000..3185baf --- /dev/null +++ b/ruoyi-common/ruoyi-common-sentinel/pom.xml @@ -0,0 +1,51 @@ + + + + org.dromara + ruoyi-common + ${revision} + + 4.0.0 + + ruoyi-common-sentinel + + + ruoyi-common-sentinel 限流模块 + + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-sentinel + + + + + com.alibaba.csp + sentinel-datasource-nacos + + + com.alibaba.csp + sentinel-apache-dubbo3-adapter + + + + com.fasterxml.jackson.dataformat + jackson-dataformat-xml + true + + + + org.dromara + ruoyi-common-core + + + + org.springframework.boot + spring-boot-autoconfigure + + + + diff --git a/ruoyi-common/ruoyi-common-sentinel/src/main/java/com/alibaba/cloud/sentinel/custom/SentinelAutoConfiguration.java b/ruoyi-common/ruoyi-common-sentinel/src/main/java/com/alibaba/cloud/sentinel/custom/SentinelAutoConfiguration.java new file mode 100644 index 0000000..df23c49 --- /dev/null +++ b/ruoyi-common/ruoyi-common-sentinel/src/main/java/com/alibaba/cloud/sentinel/custom/SentinelAutoConfiguration.java @@ -0,0 +1,265 @@ +/* + * Copyright 2013-2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.alibaba.cloud.sentinel.custom; + +import com.alibaba.cloud.commons.lang.StringUtils; +import com.alibaba.cloud.sentinel.SentinelProperties; +import com.alibaba.cloud.sentinel.datasource.converter.JsonConverter; +import com.alibaba.cloud.sentinel.datasource.converter.XmlConverter; +import com.alibaba.csp.sentinel.annotation.aspectj.SentinelResourceAspect; +import com.alibaba.csp.sentinel.config.SentinelConfig; +import com.alibaba.csp.sentinel.init.InitExecutor; +import com.alibaba.csp.sentinel.log.LogBase; +import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRule; +import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; +import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; +import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule; +import com.alibaba.csp.sentinel.slots.system.SystemRule; +import com.alibaba.csp.sentinel.transport.config.TransportConfig; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.xml.XmlMapper; +import jakarta.annotation.PostConstruct; +import org.dromara.common.core.utils.StreamUtils; +import org.dromara.common.sentinel.config.properties.SentinelCustomProperties; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.discovery.DiscoveryClient; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; + +import java.util.List; + +import static com.alibaba.cloud.sentinel.SentinelConstants.BLOCK_PAGE_URL_CONF_KEY; +import static com.alibaba.csp.sentinel.config.SentinelConfig.setConfig; + +/** + * 改造sentinel自动配置 支持服务名注册 + * + * @author Lion Li + * + * @author xiaojing + * @author jiashuai.xie + * @author Jim + */ +@Configuration(proxyBeanMethods = false) +@ConditionalOnProperty(name = "spring.cloud.sentinel.enabled", matchIfMissing = true) +@EnableConfigurationProperties({SentinelProperties.class, SentinelCustomProperties.class}) +public class SentinelAutoConfiguration { + + @Value("${project.name:${spring.application.name:}}") + private String projectName; + + @Autowired + private SentinelProperties properties; + @Autowired + private SentinelCustomProperties customProperties; + @Autowired + private DiscoveryClient discoveryClient; + + @PostConstruct + private void init() { + if (StringUtils.isEmpty(System.getProperty(LogBase.LOG_DIR)) + && StringUtils.isNotBlank(properties.getLog().getDir())) { + System.setProperty(LogBase.LOG_DIR, properties.getLog().getDir()); + } + if (StringUtils.isEmpty(System.getProperty(LogBase.LOG_NAME_USE_PID)) + && properties.getLog().isSwitchPid()) { + System.setProperty(LogBase.LOG_NAME_USE_PID, + String.valueOf(properties.getLog().isSwitchPid())); + } + if (StringUtils.isEmpty(System.getProperty(SentinelConfig.APP_NAME_PROP_KEY)) + && StringUtils.isNotBlank(projectName)) { + System.setProperty(SentinelConfig.APP_NAME_PROP_KEY, projectName); + } + if (StringUtils.isEmpty(System.getProperty(TransportConfig.SERVER_PORT)) + && StringUtils.isNotBlank(properties.getTransport().getPort())) { + System.setProperty(TransportConfig.SERVER_PORT, + properties.getTransport().getPort()); + } + + if (StringUtils.isNotBlank(customProperties.getServerName())) { + List instances = discoveryClient.getInstances(customProperties.getServerName()); + String serverList = StreamUtils.join(instances, instance -> + String.format("http://%s:%s", instance.getHost(), instance.getPort())); + System.setProperty(TransportConfig.CONSOLE_SERVER, serverList); + } else { + if (StringUtils.isEmpty(System.getProperty(TransportConfig.CONSOLE_SERVER)) + && StringUtils.isNotBlank(properties.getTransport().getDashboard())) { + System.setProperty(TransportConfig.CONSOLE_SERVER, + properties.getTransport().getDashboard()); + } + } + + if (StringUtils.isEmpty(System.getProperty(TransportConfig.HEARTBEAT_INTERVAL_MS)) + && StringUtils + .isNotBlank(properties.getTransport().getHeartbeatIntervalMs())) { + System.setProperty(TransportConfig.HEARTBEAT_INTERVAL_MS, + properties.getTransport().getHeartbeatIntervalMs()); + } + if (StringUtils.isEmpty(System.getProperty(TransportConfig.HEARTBEAT_CLIENT_IP)) + && StringUtils.isNotBlank(properties.getTransport().getClientIp())) { + System.setProperty(TransportConfig.HEARTBEAT_CLIENT_IP, + properties.getTransport().getClientIp()); + } + if (StringUtils.isEmpty(System.getProperty(SentinelConfig.CHARSET)) + && StringUtils.isNotBlank(properties.getMetric().getCharset())) { + System.setProperty(SentinelConfig.CHARSET, + properties.getMetric().getCharset()); + } + if (StringUtils + .isEmpty(System.getProperty(SentinelConfig.SINGLE_METRIC_FILE_SIZE)) + && StringUtils.isNotBlank(properties.getMetric().getFileSingleSize())) { + System.setProperty(SentinelConfig.SINGLE_METRIC_FILE_SIZE, + properties.getMetric().getFileSingleSize()); + } + if (StringUtils + .isEmpty(System.getProperty(SentinelConfig.TOTAL_METRIC_FILE_COUNT)) + && StringUtils.isNotBlank(properties.getMetric().getFileTotalCount())) { + System.setProperty(SentinelConfig.TOTAL_METRIC_FILE_COUNT, + properties.getMetric().getFileTotalCount()); + } + if (StringUtils.isEmpty(System.getProperty(SentinelConfig.COLD_FACTOR)) + && StringUtils.isNotBlank(properties.getFlow().getColdFactor())) { + System.setProperty(SentinelConfig.COLD_FACTOR, + properties.getFlow().getColdFactor()); + } + if (StringUtils.isNotBlank(properties.getBlockPage())) { + setConfig(BLOCK_PAGE_URL_CONF_KEY, properties.getBlockPage()); + } + + // earlier initialize + if (properties.isEager()) { + InitExecutor.doInit(); + } + + } + + @Bean + @ConditionalOnMissingBean + public SentinelResourceAspect sentinelResourceAspect() { + return new SentinelResourceAspect(); + } + + @Bean + @ConditionalOnMissingBean + @ConditionalOnClass(name = "org.springframework.web.client.RestTemplate") + @ConditionalOnProperty(name = "resttemplate.sentinel.enabled", havingValue = "true", + matchIfMissing = true) + public SentinelBeanPostProcessor sentinelBeanPostProcessor( + ApplicationContext applicationContext) { + return new SentinelBeanPostProcessor(applicationContext); + } + + @Bean + @ConditionalOnMissingBean + public SentinelDataSourceHandler sentinelDataSourceHandler( + DefaultListableBeanFactory beanFactory, SentinelProperties sentinelProperties, + Environment env) { + return new SentinelDataSourceHandler(beanFactory, sentinelProperties, env); + } + + @ConditionalOnClass(ObjectMapper.class) + @Configuration(proxyBeanMethods = false) + protected static class SentinelConverterConfiguration { + + @Configuration(proxyBeanMethods = false) + protected static class SentinelJsonConfiguration { + + private ObjectMapper objectMapper = new ObjectMapper(); + + public SentinelJsonConfiguration() { + objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, + false); + } + + @Bean("sentinel-json-flow-converter") + public JsonConverter jsonFlowConverter() { + return new JsonConverter(objectMapper, FlowRule.class); + } + + @Bean("sentinel-json-degrade-converter") + public JsonConverter jsonDegradeConverter() { + return new JsonConverter(objectMapper, DegradeRule.class); + } + + @Bean("sentinel-json-system-converter") + public JsonConverter jsonSystemConverter() { + return new JsonConverter(objectMapper, SystemRule.class); + } + + @Bean("sentinel-json-authority-converter") + public JsonConverter jsonAuthorityConverter() { + return new JsonConverter(objectMapper, AuthorityRule.class); + } + + @Bean("sentinel-json-param-flow-converter") + public JsonConverter jsonParamFlowConverter() { + return new JsonConverter(objectMapper, ParamFlowRule.class); + } + + } + + @ConditionalOnClass(XmlMapper.class) + @Configuration(proxyBeanMethods = false) + protected static class SentinelXmlConfiguration { + + private XmlMapper xmlMapper = new XmlMapper(); + + public SentinelXmlConfiguration() { + xmlMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, + false); + } + + @Bean("sentinel-xml-flow-converter") + public XmlConverter xmlFlowConverter() { + return new XmlConverter(xmlMapper, FlowRule.class); + } + + @Bean("sentinel-xml-degrade-converter") + public XmlConverter xmlDegradeConverter() { + return new XmlConverter(xmlMapper, DegradeRule.class); + } + + @Bean("sentinel-xml-system-converter") + public XmlConverter xmlSystemConverter() { + return new XmlConverter(xmlMapper, SystemRule.class); + } + + @Bean("sentinel-xml-authority-converter") + public XmlConverter xmlAuthorityConverter() { + return new XmlConverter(xmlMapper, AuthorityRule.class); + } + + @Bean("sentinel-xml-param-flow-converter") + public XmlConverter xmlParamFlowConverter() { + return new XmlConverter(xmlMapper, ParamFlowRule.class); + } + + } + + } + +} diff --git a/ruoyi-common/ruoyi-common-sentinel/src/main/java/org/dromara/common/sentinel/config/properties/SentinelCustomProperties.java b/ruoyi-common/ruoyi-common-sentinel/src/main/java/org/dromara/common/sentinel/config/properties/SentinelCustomProperties.java new file mode 100644 index 0000000..934cd36 --- /dev/null +++ b/ruoyi-common/ruoyi-common-sentinel/src/main/java/org/dromara/common/sentinel/config/properties/SentinelCustomProperties.java @@ -0,0 +1,17 @@ +package org.dromara.common.sentinel.config.properties; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * sentinel自定义配置类 + * + * @author Lion Li + */ +@Data +@ConfigurationProperties(prefix = "spring.cloud.sentinel.transport") +public class SentinelCustomProperties { + + private String serverName; + +} diff --git a/ruoyi-common/ruoyi-common-skylog/pom.xml b/ruoyi-common/ruoyi-common-skylog/pom.xml new file mode 100644 index 0000000..212bcf0 --- /dev/null +++ b/ruoyi-common/ruoyi-common-skylog/pom.xml @@ -0,0 +1,29 @@ + + + + org.dromara + ruoyi-common + ${revision} + + 4.0.0 + + ruoyi-common-skylog + + + ruoyi-common-skylog skywalking日志收集模块 + + + + + + org.apache.skywalking + apm-toolkit-logback-1.x + + + org.apache.skywalking + apm-toolkit-trace + + + diff --git a/ruoyi-common/ruoyi-common-skylog/src/main/resources/logback-skylog.xml b/ruoyi-common/ruoyi-common-skylog/src/main/resources/logback-skylog.xml new file mode 100644 index 0000000..7e64cc5 --- /dev/null +++ b/ruoyi-common/ruoyi-common-skylog/src/main/resources/logback-skylog.xml @@ -0,0 +1,29 @@ + + + + + + + + + [%tid] ${console.log.pattern} + + utf-8 + + + + + + + + [%tid] ${console.log.pattern} + + utf-8 + + + + + + + + diff --git a/ruoyi-common/ruoyi-common-sms/pom.xml b/ruoyi-common/ruoyi-common-sms/pom.xml new file mode 100644 index 0000000..44934d3 --- /dev/null +++ b/ruoyi-common/ruoyi-common-sms/pom.xml @@ -0,0 +1,39 @@ + + + + org.dromara + ruoyi-common + ${revision} + + 4.0.0 + + ruoyi-common-sms + + + ruoyi-common-sms 短信模块 + + + + + + org.dromara.sms4j + sms4j-spring-boot-starter + + + + com.alibaba + fastjson + + + + + + org.dromara + ruoyi-common-core + + + + + diff --git a/ruoyi-common/ruoyi-common-sms/src/main/java/org/dromara/common/sms/config/SmsAutoConfiguration.java b/ruoyi-common/ruoyi-common-sms/src/main/java/org/dromara/common/sms/config/SmsAutoConfiguration.java new file mode 100644 index 0000000..da3872b --- /dev/null +++ b/ruoyi-common/ruoyi-common-sms/src/main/java/org/dromara/common/sms/config/SmsAutoConfiguration.java @@ -0,0 +1,16 @@ +package org.dromara.common.sms.config; + +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.context.properties.EnableConfigurationProperties; + +/** + * 短信配置类(暂时没用 预留扩展) + * + * @author Lion Li + * @version 4.2.0 + */ +@AutoConfiguration +@EnableConfigurationProperties(SmsProperties.class) +public class SmsAutoConfiguration { + +} diff --git a/ruoyi-common/ruoyi-common-sms/src/main/java/org/dromara/common/sms/config/SmsProperties.java b/ruoyi-common/ruoyi-common-sms/src/main/java/org/dromara/common/sms/config/SmsProperties.java new file mode 100644 index 0000000..230f620 --- /dev/null +++ b/ruoyi-common/ruoyi-common-sms/src/main/java/org/dromara/common/sms/config/SmsProperties.java @@ -0,0 +1,29 @@ +package org.dromara.common.sms.config; + +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.mail.MailProperties; +import org.springframework.boot.context.properties.ConfigurationProperties; +import lombok.Data; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Configuration; +import org.springframework.stereotype.Component; + +/** + * @author sunzexing + * @version 1.0 + * @title SmsProperties + * @description + * @create 2024-08-13 15:16 + */ + + +@Data +@Component + +@ConfigurationProperties(prefix = "sms") +public class SmsProperties { + + private String type; + + +} diff --git a/ruoyi-common/ruoyi-common-sms/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/ruoyi-common/ruoyi-common-sms/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..5919ce3 --- /dev/null +++ b/ruoyi-common/ruoyi-common-sms/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +org.dromara.common.sms.config.SmsAutoConfiguration diff --git a/ruoyi-common/ruoyi-common-social/pom.xml b/ruoyi-common/ruoyi-common-social/pom.xml new file mode 100644 index 0000000..887a8e0 --- /dev/null +++ b/ruoyi-common/ruoyi-common-social/pom.xml @@ -0,0 +1,35 @@ + + + + org.dromara + ruoyi-common + ${revision} + + 4.0.0 + + ruoyi-common-social + + + ruoyi-common-social 授权认证 + + + + + me.zhyd.oauth + JustAuth + + + + org.dromara + ruoyi-common-json + + + + org.dromara + ruoyi-common-redis + + + + diff --git a/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/config/SocialAutoConfiguration.java b/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/config/SocialAutoConfiguration.java new file mode 100644 index 0000000..19b39d8 --- /dev/null +++ b/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/config/SocialAutoConfiguration.java @@ -0,0 +1,23 @@ +package org.dromara.common.social.config; + +import me.zhyd.oauth.cache.AuthStateCache; +import org.dromara.common.social.config.properties.SocialProperties; +import org.dromara.common.social.utils.AuthRedisStateCache; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; + +/** + * Social 配置属性 + * @author thiszhc + */ +@AutoConfiguration +@EnableConfigurationProperties(SocialProperties.class) +public class SocialAutoConfiguration { + + @Bean + public AuthStateCache authStateCache() { + return new AuthRedisStateCache(); + } + +} diff --git a/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/config/properties/SocialLoginConfigProperties.java b/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/config/properties/SocialLoginConfigProperties.java new file mode 100644 index 0000000..76be234 --- /dev/null +++ b/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/config/properties/SocialLoginConfigProperties.java @@ -0,0 +1,68 @@ +package org.dromara.common.social.config.properties; + +import lombok.Data; + +/** + * 社交登录配置 + * + * @author thiszhc + */ +@Data +public class SocialLoginConfigProperties { + + /** + * 应用 ID + */ + private String clientId; + + /** + * 应用密钥 + */ + private String clientSecret; + + /** + * 回调地址 + */ + private String redirectUri; + + /** + * 是否获取unionId + */ + private boolean unionId; + + /** + * Coding 企业名称 + */ + private String codingGroupName; + + /** + * 支付宝公钥 + */ + private String alipayPublicKey; + + /** + * 企业微信应用ID + */ + private String agentId; + + /** + * stackoverflow api key + */ + private String stackOverflowKey; + + /** + * 设备ID + */ + private String deviceId; + + /** + * 客户端系统类型 + */ + private String clientOsType; + + /** + * maxkey 服务器地址 + */ + private String serverUrl; + +} diff --git a/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/config/properties/SocialProperties.java b/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/config/properties/SocialProperties.java new file mode 100644 index 0000000..1beb7d0 --- /dev/null +++ b/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/config/properties/SocialProperties.java @@ -0,0 +1,29 @@ +package org.dromara.common.social.config.properties; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +import java.util.Map; + +/** + * Social 配置属性 + * + * @author thiszhc + */ +@Data +@Component +@ConfigurationProperties(prefix = "justauth") +public class SocialProperties { + + /** + * 是否启用 + */ + private Boolean enabled; + + /** + * 授权类型 + */ + private Map type; + +} diff --git a/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/maxkey/AuthMaxKeyRequest.java b/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/maxkey/AuthMaxKeyRequest.java new file mode 100644 index 0000000..b95c19e --- /dev/null +++ b/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/maxkey/AuthMaxKeyRequest.java @@ -0,0 +1,80 @@ +package org.dromara.common.social.maxkey; + +import cn.hutool.core.lang.Dict; +import me.zhyd.oauth.cache.AuthStateCache; +import me.zhyd.oauth.config.AuthConfig; +import me.zhyd.oauth.exception.AuthException; +import me.zhyd.oauth.model.AuthCallback; +import me.zhyd.oauth.model.AuthToken; +import me.zhyd.oauth.model.AuthUser; +import me.zhyd.oauth.request.AuthDefaultRequest; +import org.dromara.common.core.utils.SpringUtils; +import org.dromara.common.json.utils.JsonUtils; + +/** + * @author 长春叭哥 2023年03月26日 + */ +public class AuthMaxKeyRequest extends AuthDefaultRequest { + + public static final String SERVER_URL = SpringUtils.getProperty("justauth.type.maxkey.server-url"); + + /** + * 设定归属域 + */ + public AuthMaxKeyRequest(AuthConfig config) { + super(config, AuthMaxKeySource.MAXKEY); + } + + public AuthMaxKeyRequest(AuthConfig config, AuthStateCache authStateCache) { + super(config, AuthMaxKeySource.MAXKEY, authStateCache); + } + + @Override + protected AuthToken getAccessToken(AuthCallback authCallback) { + String body = doPostAuthorizationCode(authCallback.getCode()); + Dict object = JsonUtils.parseMap(body); + // oauth/token 验证异常 + if (object.containsKey("error")) { + throw new AuthException(object.getStr("error_description")); + } + // user 验证异常 + if (object.containsKey("message")) { + throw new AuthException(object.getStr("message")); + } + return AuthToken.builder() + .accessToken(object.getStr("access_token")) + .refreshToken(object.getStr("refresh_token")) + .idToken(object.getStr("id_token")) + .tokenType(object.getStr("token_type")) + .scope(object.getStr("scope")) + .build(); + } + + @Override + protected AuthUser getUserInfo(AuthToken authToken) { + String body = doGetUserInfo(authToken); + Dict object = JsonUtils.parseMap(body); + // oauth/token 验证异常 + if (object.containsKey("error")) { + throw new AuthException(object.getStr("error_description")); + } + // user 验证异常 + if (object.containsKey("message")) { + throw new AuthException(object.getStr("message")); + } + return AuthUser.builder() + .uuid(object.getStr("userId")) + .username(object.getStr("username")) + .nickname(object.getStr("displayName")) + .avatar(object.getStr("avatar_url")) + .blog(object.getStr("web_url")) + .company(object.getStr("organization")) + .location(object.getStr("location")) + .email(object.getStr("email")) + .remark(object.getStr("bio")) + .token(authToken) + .source(source.toString()) + .build(); + } + +} diff --git a/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/maxkey/AuthMaxKeySource.java b/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/maxkey/AuthMaxKeySource.java new file mode 100644 index 0000000..1ff57f7 --- /dev/null +++ b/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/maxkey/AuthMaxKeySource.java @@ -0,0 +1,52 @@ +package org.dromara.common.social.maxkey; + +import me.zhyd.oauth.config.AuthSource; +import me.zhyd.oauth.request.AuthDefaultRequest; + +/** + * Oauth2 默认接口说明 + * + * @author 长春叭哥 2023年03月26日 + * + */ +public enum AuthMaxKeySource implements AuthSource { + + /** + * 自己搭建的 maxkey 私服 + */ + MAXKEY { + + /** + * 授权的api + */ + @Override + public String authorize() { + return AuthMaxKeyRequest.SERVER_URL + "/sign/authz/oauth/v20/authorize"; + } + + /** + * 获取accessToken的api + */ + @Override + public String accessToken() { + return AuthMaxKeyRequest.SERVER_URL + "/sign/authz/oauth/v20/token"; + } + + /** + * 获取用户信息的api + */ + @Override + public String userInfo() { + return AuthMaxKeyRequest.SERVER_URL + "/sign/api/oauth/v20/me"; + } + + /** + * 平台对应的 AuthRequest 实现类,必须继承自 {@link AuthDefaultRequest} + */ + @Override + public Class getTargetClass() { + return AuthMaxKeyRequest.class; + } + + } +} diff --git a/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/utils/AuthRedisStateCache.java b/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/utils/AuthRedisStateCache.java new file mode 100644 index 0000000..0b6ec20 --- /dev/null +++ b/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/utils/AuthRedisStateCache.java @@ -0,0 +1,61 @@ +package org.dromara.common.social.utils; + +import lombok.AllArgsConstructor; +import me.zhyd.oauth.cache.AuthStateCache; +import org.dromara.common.core.constant.GlobalConstants; +import org.dromara.common.redis.utils.RedisUtils; + +import java.time.Duration; + +/** + * 授权状态缓存 + */ +@AllArgsConstructor +public class AuthRedisStateCache implements AuthStateCache { + + /** + * 存入缓存 + * + * @param key 缓存key + * @param value 缓存内容 + */ + @Override + public void cache(String key, String value) { + // 授权超时时间 默认三分钟 + RedisUtils.setCacheObject(GlobalConstants.SOCIAL_AUTH_CODE_KEY + key, value, Duration.ofMinutes(3)); + } + + /** + * 存入缓存 + * + * @param key 缓存key + * @param value 缓存内容 + * @param timeout 指定缓存过期时间(毫秒) + */ + @Override + public void cache(String key, String value, long timeout) { + RedisUtils.setCacheObject(GlobalConstants.SOCIAL_AUTH_CODE_KEY + key, value, Duration.ofMillis(timeout)); + } + + /** + * 获取缓存内容 + * + * @param key 缓存key + * @return 缓存内容 + */ + @Override + public String get(String key) { + return RedisUtils.getCacheObject(GlobalConstants.SOCIAL_AUTH_CODE_KEY + key); + } + + /** + * 是否存在key,如果对应key的value值已过期,也返回false + * + * @param key 缓存key + * @return true:存在key,并且value没过期;false:key不存在或者已过期 + */ + @Override + public boolean containsKey(String key) { + return RedisUtils.hasKey(GlobalConstants.SOCIAL_AUTH_CODE_KEY + key); + } +} diff --git a/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/utils/SocialUtils.java b/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/utils/SocialUtils.java new file mode 100644 index 0000000..a5ed6f1 --- /dev/null +++ b/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/utils/SocialUtils.java @@ -0,0 +1,70 @@ +package org.dromara.common.social.utils; + +import cn.hutool.core.util.ObjectUtil; +import me.zhyd.oauth.config.AuthConfig; +import me.zhyd.oauth.exception.AuthException; +import me.zhyd.oauth.model.AuthCallback; +import me.zhyd.oauth.model.AuthResponse; +import me.zhyd.oauth.model.AuthUser; +import me.zhyd.oauth.request.*; +import org.dromara.common.core.utils.SpringUtils; +import org.dromara.common.social.config.properties.SocialLoginConfigProperties; +import org.dromara.common.social.config.properties.SocialProperties; +import org.dromara.common.social.maxkey.AuthMaxKeyRequest; + +/** + * 认证授权工具类 + * + * @author thiszhc + */ +public class SocialUtils { + + private static final AuthRedisStateCache STATE_CACHE = SpringUtils.getBean(AuthRedisStateCache.class); + + @SuppressWarnings("unchecked") + public static AuthResponse loginAuth(String source, String code, String state, SocialProperties socialProperties) throws AuthException { + AuthRequest authRequest = getAuthRequest(source, socialProperties); + AuthCallback callback = new AuthCallback(); + callback.setCode(code); + callback.setState(state); + return authRequest.login(callback); + } + + public static AuthRequest getAuthRequest(String source, SocialProperties socialProperties) throws AuthException { + SocialLoginConfigProperties obj = socialProperties.getType().get(source); + if (ObjectUtil.isNull(obj)) { + throw new AuthException("不支持的第三方登录类型"); + } + final AuthConfig.AuthConfigBuilder builder = AuthConfig.builder() + .clientId(obj.getClientId()) + .clientSecret(obj.getClientSecret()) + .redirectUri(obj.getRedirectUri()); + return switch (source.toLowerCase()) { + case "dingtalk" -> new AuthDingTalkRequest(builder.build(), STATE_CACHE); + case "baidu" -> new AuthBaiduRequest(builder.build(), STATE_CACHE); + case "github" -> new AuthGithubRequest(builder.build(), STATE_CACHE); + case "gitee" -> new AuthGiteeRequest(builder.build(), STATE_CACHE); + case "weibo" -> new AuthWeiboRequest(builder.build(), STATE_CACHE); + case "coding" -> new AuthCodingRequest(builder.build(), STATE_CACHE); + case "oschina" -> new AuthOschinaRequest(builder.build(), STATE_CACHE); + // 支付宝在创建回调地址时,不允许使用localhost或者127.0.0.1,所以这儿的回调地址使用的局域网内的ip + case "alipay_wallet" -> new AuthAlipayRequest(builder.build(), socialProperties.getType().get("alipay_wallet").getAlipayPublicKey(), STATE_CACHE); + case "qq" -> new AuthQqRequest(builder.build(), STATE_CACHE); + case "wechat_open" -> new AuthWeChatOpenRequest(builder.build(), STATE_CACHE); + case "taobao" -> new AuthTaobaoRequest(builder.build(), STATE_CACHE); + case "douyin" -> new AuthDouyinRequest(builder.build(), STATE_CACHE); + case "linkedin" -> new AuthLinkedinRequest(builder.build(), STATE_CACHE); + case "microsoft" -> new AuthMicrosoftRequest(builder.build(), STATE_CACHE); + case "renren" -> new AuthRenrenRequest(builder.build(), STATE_CACHE); + case "stack_overflow" -> new AuthStackOverflowRequest(builder.stackOverflowKey("").build(), STATE_CACHE); + case "huawei" -> new AuthHuaweiRequest(builder.build(), STATE_CACHE); + case "wechat_enterprise" -> new AuthWeChatEnterpriseQrcodeRequest(builder.agentId("").build(), STATE_CACHE); + case "gitlab" -> new AuthGitlabRequest(builder.build(), STATE_CACHE); + case "wechat_mp" -> new AuthWeChatMpRequest(builder.build(), STATE_CACHE); + case "aliyun" -> new AuthAliyunRequest(builder.build(), STATE_CACHE); + case "maxkey" -> new AuthMaxKeyRequest(builder.build(), STATE_CACHE); + default -> throw new AuthException("未获取到有效的Auth配置"); + }; + } +} + diff --git a/ruoyi-common/ruoyi-common-tenant/pom.xml b/ruoyi-common/ruoyi-common-tenant/pom.xml new file mode 100644 index 0000000..2b87bf9 --- /dev/null +++ b/ruoyi-common/ruoyi-common-tenant/pom.xml @@ -0,0 +1,36 @@ + + + + org.dromara + ruoyi-common + ${revision} + + 4.0.0 + + ruoyi-common-tenant + + + ruoyi-common-tenant 租户模块 + + + + + org.dromara + ruoyi-common-mybatis + + + + org.dromara + ruoyi-common-redis + + + + com.alibaba + transmittable-thread-local + + + + + diff --git a/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/config/TenantConfiguration.java b/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/config/TenantConfiguration.java new file mode 100644 index 0000000..f2c708a --- /dev/null +++ b/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/config/TenantConfiguration.java @@ -0,0 +1,106 @@ +package org.dromara.common.tenant.config; + +import cn.dev33.satoken.dao.SaTokenDao; +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor; +import org.dromara.common.core.utils.reflect.ReflectUtils; +import org.dromara.common.mybatis.config.MybatisPlusConfiguration; +import org.dromara.common.redis.config.RedisConfiguration; +import org.dromara.common.redis.config.properties.RedissonProperties; +import org.dromara.common.tenant.core.TenantSaTokenDao; +import org.dromara.common.tenant.handle.PlusTenantLineHandler; +import org.dromara.common.tenant.handle.TenantKeyPrefixHandler; +import org.dromara.common.tenant.manager.TenantSpringCacheManager; +import org.dromara.common.tenant.properties.TenantProperties; +import org.redisson.config.ClusterServersConfig; +import org.redisson.config.SingleServerConfig; +import org.redisson.spring.starter.RedissonAutoConfigurationCustomizer; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.cache.CacheManager; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Primary; + +import java.util.ArrayList; +import java.util.List; + +/** + * 租户配置类 + * + * @author Lion Li + */ +@EnableConfigurationProperties(TenantProperties.class) +@AutoConfiguration(after = {RedisConfiguration.class}) +@ConditionalOnProperty(value = "tenant.enable", havingValue = "true") +public class TenantConfiguration { + + @ConditionalOnBean(MybatisPlusConfiguration.class) + @AutoConfiguration(after = {MybatisPlusConfiguration.class}) + static class MybatisPlusConfig { + + /** + * 初始化租户配置 + */ + @Bean + public boolean tenantInit(MybatisPlusInterceptor mybatisPlusInterceptor, + TenantProperties tenantProperties) { + List interceptors = new ArrayList<>(); + // 多租户插件 必须放到第一位 + interceptors.add(tenantLineInnerInterceptor(tenantProperties)); + interceptors.addAll(mybatisPlusInterceptor.getInterceptors()); + mybatisPlusInterceptor.setInterceptors(interceptors); + return true; + } + + /** + * 多租户插件 + */ + public TenantLineInnerInterceptor tenantLineInnerInterceptor(TenantProperties tenantProperties) { + return new TenantLineInnerInterceptor(new PlusTenantLineHandler(tenantProperties)); + } + } + + @Bean + public RedissonAutoConfigurationCustomizer tenantRedissonCustomizer(RedissonProperties redissonProperties) { + return config -> { + TenantKeyPrefixHandler nameMapper = new TenantKeyPrefixHandler(redissonProperties.getKeyPrefix()); + SingleServerConfig singleServerConfig = ReflectUtils.invokeGetter(config, "singleServerConfig"); + if (ObjectUtil.isNotNull(singleServerConfig)) { + // 使用单机模式 + // 设置多租户 redis key前缀 + singleServerConfig.setNameMapper(nameMapper); + ReflectUtils.invokeSetter(config, "singleServerConfig", singleServerConfig); + } + ClusterServersConfig clusterServersConfig = ReflectUtils.invokeGetter(config, "clusterServersConfig"); + // 集群配置方式 参考下方注释 + if (ObjectUtil.isNotNull(clusterServersConfig)) { + // 设置多租户 redis key前缀 + clusterServersConfig.setNameMapper(nameMapper); + ReflectUtils.invokeSetter(config, "clusterServersConfig", clusterServersConfig); + } + }; + } + + /** + * 多租户缓存管理器 + */ + @Primary + @Bean + public CacheManager tenantCacheManager() { + return new TenantSpringCacheManager(); + } + + /** + * 多租户鉴权dao实现 + */ + @Primary + @Bean + public SaTokenDao tenantSaTokenDao() { + return new TenantSaTokenDao(); + } + +} diff --git a/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/core/TenantEntity.java b/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/core/TenantEntity.java new file mode 100644 index 0000000..8ad0d2c --- /dev/null +++ b/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/core/TenantEntity.java @@ -0,0 +1,21 @@ +package org.dromara.common.tenant.core; + +import org.dromara.common.mybatis.core.domain.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 租户基类 + * + * @author Michelle.Chung + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class TenantEntity extends BaseEntity { + + /** + * 租户编号 + */ + private String tenantId; + +} diff --git a/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/core/TenantSaTokenDao.java b/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/core/TenantSaTokenDao.java new file mode 100644 index 0000000..b8da28e --- /dev/null +++ b/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/core/TenantSaTokenDao.java @@ -0,0 +1,148 @@ +package org.dromara.common.tenant.core; + +import org.dromara.common.core.constant.GlobalConstants; +import org.dromara.common.redis.utils.RedisUtils; +import org.dromara.common.satoken.core.dao.PlusSaTokenDao; + +import java.time.Duration; +import java.util.List; + +/** + * SaToken 认证数据持久层 适配多租户 + * + * @author Lion Li + */ +public class TenantSaTokenDao extends PlusSaTokenDao { + + @Override + public String get(String key) { + return super.get(GlobalConstants.GLOBAL_REDIS_KEY + key); + } + + @Override + public void set(String key, String value, long timeout) { + super.set(GlobalConstants.GLOBAL_REDIS_KEY + key, value, timeout); + } + + /** + * 修修改指定key-value键值对 (过期时间不变) + */ + @Override + public void update(String key, String value) { + long expire = getTimeout(key); + // -2 = 无此键 + if (expire == NOT_VALUE_EXPIRE) { + return; + } + this.set(key, value, expire); + } + + /** + * 删除Value + */ + @Override + public void delete(String key) { + super.delete(GlobalConstants.GLOBAL_REDIS_KEY + key); + } + + /** + * 获取Value的剩余存活时间 (单位: 秒) + */ + @Override + public long getTimeout(String key) { + return super.getTimeout(GlobalConstants.GLOBAL_REDIS_KEY + key); + } + + /** + * 修改Value的剩余存活时间 (单位: 秒) + */ + @Override + public void updateTimeout(String key, long timeout) { + // 判断是否想要设置为永久 + if (timeout == NEVER_EXPIRE) { + long expire = getTimeout(key); + if (expire == NEVER_EXPIRE) { + // 如果其已经被设置为永久,则不作任何处理 + } else { + // 如果尚未被设置为永久,那么再次set一次 + this.set(key, this.get(key), timeout); + } + return; + } + RedisUtils.expire(GlobalConstants.GLOBAL_REDIS_KEY + key, Duration.ofSeconds(timeout)); + } + + + /** + * 获取Object,如无返空 + */ + @Override + public Object getObject(String key) { + return super.getObject(GlobalConstants.GLOBAL_REDIS_KEY + key); + } + + /** + * 写入Object,并设定存活时间 (单位: 秒) + */ + @Override + public void setObject(String key, Object object, long timeout) { + super.setObject(GlobalConstants.GLOBAL_REDIS_KEY + key, object, timeout); + } + + /** + * 更新Object (过期时间不变) + */ + @Override + public void updateObject(String key, Object object) { + long expire = getObjectTimeout(key); + // -2 = 无此键 + if (expire == NOT_VALUE_EXPIRE) { + return; + } + this.setObject(key, object, expire); + } + + /** + * 删除Object + */ + @Override + public void deleteObject(String key) { + super.deleteObject(GlobalConstants.GLOBAL_REDIS_KEY + key); + } + + /** + * 获取Object的剩余存活时间 (单位: 秒) + */ + @Override + public long getObjectTimeout(String key) { + return super.getObjectTimeout(GlobalConstants.GLOBAL_REDIS_KEY + key); + } + + /** + * 修改Object的剩余存活时间 (单位: 秒) + */ + @Override + public void updateObjectTimeout(String key, long timeout) { + // 判断是否想要设置为永久 + if (timeout == NEVER_EXPIRE) { + long expire = getObjectTimeout(key); + if (expire == NEVER_EXPIRE) { + // 如果其已经被设置为永久,则不作任何处理 + } else { + // 如果尚未被设置为永久,那么再次set一次 + this.setObject(key, this.getObject(key), timeout); + } + return; + } + RedisUtils.expire(GlobalConstants.GLOBAL_REDIS_KEY + key, Duration.ofSeconds(timeout)); + } + + + /** + * 搜索数据 + */ + @Override + public List searchData(String prefix, String keyword, int start, int size, boolean sortType) { + return super.searchData(GlobalConstants.GLOBAL_REDIS_KEY + prefix, keyword, start, size, sortType); + } +} diff --git a/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/exception/TenantException.java b/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/exception/TenantException.java new file mode 100644 index 0000000..ee2bc97 --- /dev/null +++ b/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/exception/TenantException.java @@ -0,0 +1,20 @@ +package org.dromara.common.tenant.exception; + +import org.dromara.common.core.exception.base.BaseException; + +import java.io.Serial; + +/** + * 租户异常类 + * + * @author Lion Li + */ +public class TenantException extends BaseException { + + @Serial + private static final long serialVersionUID = 1L; + + public TenantException(String code, Object... args) { + super("tenant", code, args, null); + } +} diff --git a/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/handle/PlusTenantLineHandler.java b/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/handle/PlusTenantLineHandler.java new file mode 100644 index 0000000..6c93ee5 --- /dev/null +++ b/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/handle/PlusTenantLineHandler.java @@ -0,0 +1,56 @@ +package org.dromara.common.tenant.handle; + +import cn.hutool.core.collection.ListUtil; +import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import net.sf.jsqlparser.expression.Expression; +import net.sf.jsqlparser.expression.NullValue; +import net.sf.jsqlparser.expression.StringValue; +import org.dromara.common.core.utils.StringUtils; +import org.dromara.common.tenant.helper.TenantHelper; +import org.dromara.common.tenant.properties.TenantProperties; + +import java.util.List; + +/** + * 自定义租户处理器 + * + * @author Lion Li + */ +@Slf4j +@AllArgsConstructor +public class PlusTenantLineHandler implements TenantLineHandler { + + private final TenantProperties tenantProperties; + + @Override + public Expression getTenantId() { + String tenantId = TenantHelper.getTenantId(); + if (StringUtils.isBlank(tenantId)) { + log.error("无法获取有效的租户id -> Null"); + return new NullValue(); + } + // 返回固定租户 + return new StringValue(tenantId); + } + + @Override + public boolean ignoreTable(String tableName) { + String tenantId = TenantHelper.getTenantId(); + // 判断是否有租户 + if (StringUtils.isNotBlank(tenantId)) { + // 不需要过滤租户的表 + List excludes = tenantProperties.getExcludes(); + // 非业务表 + List tables = ListUtil.toList( + "gen_table", + "gen_table_column" + ); + tables.addAll(excludes); + return tables.contains(tableName); + } + return true; + } + +} diff --git a/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/handle/TenantKeyPrefixHandler.java b/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/handle/TenantKeyPrefixHandler.java new file mode 100644 index 0000000..14dad1a --- /dev/null +++ b/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/handle/TenantKeyPrefixHandler.java @@ -0,0 +1,66 @@ +package org.dromara.common.tenant.handle; + +import lombok.extern.slf4j.Slf4j; +import org.dromara.common.core.constant.GlobalConstants; +import org.dromara.common.core.utils.StringUtils; +import org.dromara.common.redis.handler.KeyPrefixHandler; +import org.dromara.common.tenant.helper.TenantHelper; + +/** + * 多租户redis缓存key前缀处理 + * + * @author Lion Li + */ +@Slf4j +public class TenantKeyPrefixHandler extends KeyPrefixHandler { + + public TenantKeyPrefixHandler(String keyPrefix) { + super(keyPrefix); + } + + /** + * 增加前缀 + */ + @Override + public String map(String name) { + if (StringUtils.isBlank(name)) { + return null; + } + if (StringUtils.contains(name, GlobalConstants.GLOBAL_REDIS_KEY)) { + return super.map(name); + } + String tenantId = TenantHelper.getTenantId(); + if (StringUtils.isBlank(tenantId)) { + log.error("无法获取有效的租户id -> Null"); + } + if (StringUtils.startsWith(name, tenantId + "")) { + // 如果存在则直接返回 + return super.map(name); + } + return super.map(tenantId + ":" + name); + } + + /** + * 去除前缀 + */ + @Override + public String unmap(String name) { + String unmap = super.unmap(name); + if (StringUtils.isBlank(unmap)) { + return null; + } + if (StringUtils.contains(name, GlobalConstants.GLOBAL_REDIS_KEY)) { + return super.unmap(name); + } + String tenantId = TenantHelper.getTenantId(); + if (StringUtils.isBlank(tenantId)) { + log.error("无法获取有效的租户id -> Null"); + } + if (StringUtils.startsWith(unmap, tenantId + "")) { + // 如果存在则删除 + return unmap.substring((tenantId + ":").length()); + } + return unmap; + } + +} diff --git a/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/helper/TenantHelper.java b/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/helper/TenantHelper.java new file mode 100644 index 0000000..e830c19 --- /dev/null +++ b/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/helper/TenantHelper.java @@ -0,0 +1,189 @@ +package org.dromara.common.tenant.helper; + +import cn.dev33.satoken.context.SaHolder; +import cn.dev33.satoken.stp.StpUtil; +import cn.hutool.core.convert.Convert; +import com.alibaba.ttl.TransmittableThreadLocal; +import com.baomidou.mybatisplus.core.plugins.IgnoreStrategy; +import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.dromara.common.core.constant.GlobalConstants; +import org.dromara.common.core.utils.SpringUtils; +import org.dromara.common.core.utils.StringUtils; +import org.dromara.common.redis.utils.RedisUtils; +import org.dromara.common.satoken.utils.LoginHelper; + +import java.util.function.Supplier; + +/** + * 租户助手 + * + * @author Lion Li + */ +@Slf4j +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class TenantHelper { + + private static final String DYNAMIC_TENANT_KEY = GlobalConstants.GLOBAL_REDIS_KEY + "dynamicTenant"; + + private static final ThreadLocal TEMP_DYNAMIC_TENANT = new TransmittableThreadLocal<>(); + + /** + * 租户功能是否启用 + */ + public static boolean isEnable() { + return Convert.toBool(SpringUtils.getProperty("tenant.enable"), false); + } + + /** + * 开启忽略租户(开启后需手动调用 {@link #disableIgnore()} 关闭) + */ + public static void enableIgnore() { + InterceptorIgnoreHelper.handle(IgnoreStrategy.builder().tenantLine(true).build()); + } + + /** + * 关闭忽略租户 + */ + public static void disableIgnore() { + InterceptorIgnoreHelper.clearIgnoreStrategy(); + } + + /** + * 在忽略租户中执行 + * + * @param handle 处理执行方法 + */ + public static void ignore(Runnable handle) { + enableIgnore(); + try { + handle.run(); + } finally { + disableIgnore(); + } + } + + /** + * 在忽略租户中执行 + * + * @param handle 处理执行方法 + */ + public static T ignore(Supplier handle) { + enableIgnore(); + try { + return handle.get(); + } finally { + disableIgnore(); + } + } + + /** + * 设置动态租户(一直有效 需要手动清理) + *

+ * 如果为未登录状态下 那么只在当前线程内生效 + */ + public static void setDynamic(String tenantId) { + if (!isEnable()) { + return; + } + if (!isLogin()) { + TEMP_DYNAMIC_TENANT.set(tenantId); + return; + } + String cacheKey = DYNAMIC_TENANT_KEY + ":" + LoginHelper.getUserId(); + RedisUtils.setCacheObject(cacheKey, tenantId); + SaHolder.getStorage().set(cacheKey, tenantId); + } + + /** + * 获取动态租户(一直有效 需要手动清理) + *

+ * 如果为未登录状态下 那么只在当前线程内生效 + */ + public static String getDynamic() { + if (!isEnable()) { + return null; + } + if (!isLogin()) { + return TEMP_DYNAMIC_TENANT.get(); + } + String cacheKey = DYNAMIC_TENANT_KEY + ":" + LoginHelper.getUserId(); + String tenantId = (String) SaHolder.getStorage().get(cacheKey); + if (StringUtils.isNotBlank(tenantId)) { + return tenantId; + } + tenantId = RedisUtils.getCacheObject(cacheKey); + SaHolder.getStorage().set(cacheKey, tenantId); + return tenantId; + } + + /** + * 清除动态租户 + */ + public static void clearDynamic() { + if (!isEnable()) { + return; + } + if (!isLogin()) { + TEMP_DYNAMIC_TENANT.remove(); + return; + } + String cacheKey = DYNAMIC_TENANT_KEY + ":" + LoginHelper.getUserId(); + RedisUtils.deleteObject(cacheKey); + SaHolder.getStorage().delete(cacheKey); + } + + /** + * 在动态租户中执行 + * + * @param handle 处理执行方法 + */ + public static void dynamic(String tenantId, Runnable handle) { + setDynamic(tenantId); + try { + handle.run(); + } finally { + clearDynamic(); + } + } + + /** + * 在动态租户中执行 + * + * @param handle 处理执行方法 + */ + public static T dynamic(String tenantId, Supplier handle) { + setDynamic(tenantId); + try { + return handle.get(); + } finally { + clearDynamic(); + } + } + + /** + * 获取当前租户id(动态租户优先) + */ + public static String getTenantId() { + if (!isEnable()) { + return null; + } + String tenantId = TenantHelper.getDynamic(); + if (StringUtils.isBlank(tenantId)) { + tenantId = LoginHelper.getTenantId(); + } + return tenantId; + } + + private static boolean isLogin() { + try { + StpUtil.checkLogin(); + return true; + } catch (Exception e) { + return false; + } + } + +} diff --git a/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/manager/TenantSpringCacheManager.java b/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/manager/TenantSpringCacheManager.java new file mode 100644 index 0000000..d230afc --- /dev/null +++ b/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/manager/TenantSpringCacheManager.java @@ -0,0 +1,32 @@ +package org.dromara.common.tenant.manager; + +import org.dromara.common.core.constant.GlobalConstants; +import org.dromara.common.core.utils.StringUtils; +import org.dromara.common.redis.manager.PlusSpringCacheManager; +import org.dromara.common.tenant.helper.TenantHelper; +import org.springframework.cache.Cache; + +/** + * 重写 cacheName 处理方法 支持多租户 + * + * @author Lion Li + */ +public class TenantSpringCacheManager extends PlusSpringCacheManager { + + public TenantSpringCacheManager() { + } + + @Override + public Cache getCache(String name) { + if (StringUtils.contains(name, GlobalConstants.GLOBAL_REDIS_KEY)) { + return super.getCache(name); + } + String tenantId = TenantHelper.getTenantId(); + if (StringUtils.startsWith(name, tenantId)) { + // 如果存在则直接返回 + return super.getCache(name); + } + return super.getCache(tenantId + ":" + name); + } + +} diff --git a/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/properties/TenantProperties.java b/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/properties/TenantProperties.java new file mode 100644 index 0000000..1675ccf --- /dev/null +++ b/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/properties/TenantProperties.java @@ -0,0 +1,27 @@ +package org.dromara.common.tenant.properties; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; + +import java.util.List; + +/** + * 租户 配置属性 + * + * @author Lion Li + */ +@Data +@ConfigurationProperties(prefix = "tenant") +public class TenantProperties { + + /** + * 是否启用 + */ + private Boolean enable; + + /** + * 排除表 + */ + private List excludes; + +} diff --git a/ruoyi-common/ruoyi-common-translation/pom.xml b/ruoyi-common/ruoyi-common-translation/pom.xml new file mode 100644 index 0000000..030dd7d --- /dev/null +++ b/ruoyi-common/ruoyi-common-translation/pom.xml @@ -0,0 +1,42 @@ + + + + org.dromara + ruoyi-common + ${revision} + + 4.0.0 + + ruoyi-common-translation + + + ruoyi-common-translation 通用翻译功能 + + + + + + org.dromara + ruoyi-common-json + + + + org.dromara + ruoyi-common-dict + + + + org.dromara + ruoyi-common-dubbo + + + + org.dromara + ruoyi-api-resource + + + + + diff --git a/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/annotation/Translation.java b/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/annotation/Translation.java new file mode 100644 index 0000000..6c1227f --- /dev/null +++ b/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/annotation/Translation.java @@ -0,0 +1,39 @@ +package org.dromara.common.translation.annotation; + +import com.fasterxml.jackson.annotation.JacksonAnnotationsInside; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import org.dromara.common.translation.core.handler.TranslationHandler; + +import java.lang.annotation.*; + +/** + * 通用翻译注解 + * + * @author Lion Li + */ +@Inherited +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD, ElementType.METHOD}) +@Documented +@JacksonAnnotationsInside +@JsonSerialize(using = TranslationHandler.class) +public @interface Translation { + + /** + * 类型 (需与实现类上的 {@link org.dromara.common.translation.annotation.TranslationType} 注解type对应) + *

+ * 默认取当前字段的值 如果设置了 @{@link Translation#mapper()} 则取映射字段的值 + */ + String type(); + + /** + * 映射字段 (如果不为空则取此字段的值) + */ + String mapper() default ""; + + /** + * 其他条件 例如: 字典type(sys_user_sex) + */ + String other() default ""; + +} diff --git a/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/annotation/TranslationType.java b/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/annotation/TranslationType.java new file mode 100644 index 0000000..43bfab0 --- /dev/null +++ b/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/annotation/TranslationType.java @@ -0,0 +1,23 @@ +package org.dromara.common.translation.annotation; + +import org.dromara.common.translation.core.TranslationInterface; + +import java.lang.annotation.*; + +/** + * 翻译类型注解 (标注到{@link TranslationInterface} 的实现类) + * + * @author Lion Li + */ +@Inherited +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE}) +@Documented +public @interface TranslationType { + + /** + * 类型 + */ + String type(); + +} diff --git a/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/config/TranslationConfig.java b/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/config/TranslationConfig.java new file mode 100644 index 0000000..5dcd0c1 --- /dev/null +++ b/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/config/TranslationConfig.java @@ -0,0 +1,50 @@ +package org.dromara.common.translation.config; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.dromara.common.translation.annotation.TranslationType; +import org.dromara.common.translation.core.TranslationInterface; +import org.dromara.common.translation.core.handler.TranslationBeanSerializerModifier; +import org.dromara.common.translation.core.handler.TranslationHandler; +import jakarta.annotation.PostConstruct; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.AutoConfiguration; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 翻译模块配置类 + * + * @author Lion Li + */ +@Slf4j +@AutoConfiguration +public class TranslationConfig { + + @Autowired + private List> list; + + @Autowired + private ObjectMapper objectMapper; + + @PostConstruct + public void init() { + Map> map = new HashMap<>(list.size()); + for (TranslationInterface trans : list) { + if (trans.getClass().isAnnotationPresent(TranslationType.class)) { + TranslationType annotation = trans.getClass().getAnnotation(TranslationType.class); + map.put(annotation.type(), trans); + } else { + log.warn(trans.getClass().getName() + " 翻译实现类未标注 TranslationType 注解!"); + } + } + TranslationHandler.TRANSLATION_MAPPER.putAll(map); + // 设置 Bean 序列化修改器 + objectMapper.setSerializerFactory( + objectMapper.getSerializerFactory() + .withSerializerModifier(new TranslationBeanSerializerModifier())); + } + +} diff --git a/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/constant/TransConstant.java b/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/constant/TransConstant.java new file mode 100644 index 0000000..a6ecb2d --- /dev/null +++ b/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/constant/TransConstant.java @@ -0,0 +1,36 @@ +package org.dromara.common.translation.constant; + +/** + * 翻译常量 + * + * @author Lion Li + */ +public interface TransConstant { + + /** + * 用户id转账号 + */ + String USER_ID_TO_NAME = "user_id_to_name"; + + /** + * 用户id转用户昵称 + */ + String USER_ID_TO_NICKNAME = "user_id_to_nickname"; + + + /** + * 部门id转名称 + */ + String DEPT_ID_TO_NAME = "dept_id_to_name"; + + /** + * 字典type转label + */ + String DICT_TYPE_TO_LABEL = "dict_type_to_label"; + + /** + * ossId转url + */ + String OSS_ID_TO_URL = "oss_id_to_url"; + +} diff --git a/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/TranslationInterface.java b/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/TranslationInterface.java new file mode 100644 index 0000000..e4d6dd3 --- /dev/null +++ b/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/TranslationInterface.java @@ -0,0 +1,20 @@ +package org.dromara.common.translation.core; + +import org.dromara.common.translation.annotation.TranslationType; + +/** + * 翻译接口 (实现类需标注 {@link TranslationType} 注解标明翻译类型) + * + * @author Lion Li + */ +public interface TranslationInterface { + + /** + * 翻译 + * + * @param key 需要被翻译的键(不为空) + * @param other 其他参数 + * @return 返回键对应的值 + */ + T translation(Object key, String other); +} diff --git a/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/handler/TranslationHandler.java b/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/handler/TranslationHandler.java new file mode 100644 index 0000000..bb9615b --- /dev/null +++ b/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/handler/TranslationHandler.java @@ -0,0 +1,65 @@ +package org.dromara.common.translation.core.handler; + +import cn.hutool.core.util.ObjectUtil; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.BeanProperty; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.ser.ContextualSerializer; +import org.dromara.common.core.utils.StringUtils; +import org.dromara.common.core.utils.reflect.ReflectUtils; +import org.dromara.common.translation.annotation.Translation; +import org.dromara.common.translation.core.TranslationInterface; +import lombok.extern.slf4j.Slf4j; + +import java.io.IOException; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 翻译处理器 + * + * @author Lion Li + */ +@Slf4j +public class TranslationHandler extends JsonSerializer implements ContextualSerializer { + + /** + * 全局翻译实现类映射器 + */ + public static final Map> TRANSLATION_MAPPER = new ConcurrentHashMap<>(); + + private Translation translation; + + @Override + public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException { + TranslationInterface trans = TRANSLATION_MAPPER.get(translation.type()); + if (ObjectUtil.isNotNull(trans)) { + // 如果映射字段不为空 则取映射字段的值 + if (StringUtils.isNotBlank(translation.mapper())) { + value = ReflectUtils.invokeGetter(gen.getCurrentValue(), translation.mapper()); + } + // 如果为 null 直接写出 + if (ObjectUtil.isNull(value)) { + gen.writeNull(); + return; + } + Object result = trans.translation(value, translation.other()); + gen.writeObject(result); + } else { + gen.writeObject(value); + } + } + + @Override + public JsonSerializer createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException { + Translation translation = property.getAnnotation(Translation.class); + if (Objects.nonNull(translation)) { + this.translation = translation; + return this; + } + return prov.findValueSerializer(property.getType(), property); + } +} diff --git a/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/impl/DeptNameTranslationImpl.java b/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/impl/DeptNameTranslationImpl.java new file mode 100644 index 0000000..b88af57 --- /dev/null +++ b/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/impl/DeptNameTranslationImpl.java @@ -0,0 +1,26 @@ +package org.dromara.common.translation.core.impl; + +import org.dromara.common.translation.annotation.TranslationType; +import org.dromara.common.translation.constant.TransConstant; +import org.dromara.common.translation.core.TranslationInterface; +import org.dromara.system.api.RemoteDeptService; +import lombok.AllArgsConstructor; +import org.apache.dubbo.config.annotation.DubboReference; + +/** + * 部门翻译实现 + * + * @author Lion Li + */ +@AllArgsConstructor +@TranslationType(type = TransConstant.DEPT_ID_TO_NAME) +public class DeptNameTranslationImpl implements TranslationInterface { + + @DubboReference + private RemoteDeptService remoteDeptService; + + @Override + public String translation(Object key, String other) { + return remoteDeptService.selectDeptNameByIds(key.toString()); + } +} diff --git a/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/impl/DictTypeTranslationImpl.java b/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/impl/DictTypeTranslationImpl.java new file mode 100644 index 0000000..538d932 --- /dev/null +++ b/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/impl/DictTypeTranslationImpl.java @@ -0,0 +1,28 @@ +package org.dromara.common.translation.core.impl; + +import org.dromara.common.core.service.DictService; +import org.dromara.common.core.utils.StringUtils; +import org.dromara.common.translation.annotation.TranslationType; +import org.dromara.common.translation.constant.TransConstant; +import org.dromara.common.translation.core.TranslationInterface; +import lombok.AllArgsConstructor; + +/** + * 字典翻译实现 + * + * @author Lion Li + */ +@AllArgsConstructor +@TranslationType(type = TransConstant.DICT_TYPE_TO_LABEL) +public class DictTypeTranslationImpl implements TranslationInterface { + + private final DictService dictService; + + @Override + public String translation(Object key, String other) { + if (key instanceof String && StringUtils.isNotBlank(other)) { + return dictService.getDictLabel(other, key.toString()); + } + return null; + } +} diff --git a/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/impl/NicknameTranslationImpl.java b/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/impl/NicknameTranslationImpl.java new file mode 100644 index 0000000..b73478e --- /dev/null +++ b/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/impl/NicknameTranslationImpl.java @@ -0,0 +1,26 @@ +package org.dromara.common.translation.core.impl; + +import lombok.AllArgsConstructor; +import org.apache.dubbo.config.annotation.DubboReference; +import org.dromara.common.translation.annotation.TranslationType; +import org.dromara.common.translation.constant.TransConstant; +import org.dromara.common.translation.core.TranslationInterface; +import org.dromara.system.api.RemoteUserService; + +/** + * 用户昵称翻译实现 + * + * @author may + */ +@AllArgsConstructor +@TranslationType(type = TransConstant.USER_ID_TO_NICKNAME) +public class NicknameTranslationImpl implements TranslationInterface { + + @DubboReference + private RemoteUserService remoteUserService; + + @Override + public String translation(Object key, String other) { + return remoteUserService.selectNicknameById((Long) key); + } +} diff --git a/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/impl/OssUrlTranslationImpl.java b/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/impl/OssUrlTranslationImpl.java new file mode 100644 index 0000000..b9fe839 --- /dev/null +++ b/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/impl/OssUrlTranslationImpl.java @@ -0,0 +1,26 @@ +package org.dromara.common.translation.core.impl; + +import org.dromara.common.translation.annotation.TranslationType; +import org.dromara.common.translation.constant.TransConstant; +import org.dromara.common.translation.core.TranslationInterface; +import org.dromara.resource.api.RemoteFileService; +import lombok.AllArgsConstructor; +import org.apache.dubbo.config.annotation.DubboReference; + +/** + * OSS翻译实现 + * + * @author Lion Li + */ +@AllArgsConstructor +@TranslationType(type = TransConstant.OSS_ID_TO_URL) +public class OssUrlTranslationImpl implements TranslationInterface { + + @DubboReference(mock = "true") + private RemoteFileService ossService; + + @Override + public String translation(Object key, String other) { + return ossService.selectUrlByIds(key.toString()); + } +} diff --git a/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/impl/UserNameTranslationImpl.java b/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/impl/UserNameTranslationImpl.java new file mode 100644 index 0000000..c442302 --- /dev/null +++ b/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/impl/UserNameTranslationImpl.java @@ -0,0 +1,26 @@ +package org.dromara.common.translation.core.impl; + +import org.dromara.common.translation.annotation.TranslationType; +import org.dromara.common.translation.constant.TransConstant; +import org.dromara.common.translation.core.TranslationInterface; +import org.dromara.system.api.RemoteUserService; +import lombok.AllArgsConstructor; +import org.apache.dubbo.config.annotation.DubboReference; + +/** + * 用户名翻译实现 + * + * @author Lion Li + */ +@AllArgsConstructor +@TranslationType(type = TransConstant.USER_ID_TO_NAME) +public class UserNameTranslationImpl implements TranslationInterface { + + @DubboReference + private RemoteUserService remoteUserService; + + @Override + public String translation(Object key, String other) { + return remoteUserService.selectUserNameById((Long) key); + } +} diff --git a/ruoyi-common/ruoyi-common-web/pom.xml b/ruoyi-common/ruoyi-common-web/pom.xml new file mode 100644 index 0000000..5ef7e92 --- /dev/null +++ b/ruoyi-common/ruoyi-common-web/pom.xml @@ -0,0 +1,74 @@ + + + + org.dromara + ruoyi-common + ${revision} + + 4.0.0 + + ruoyi-common-web + + + ruoyi-common-web web服务 + + + + + + org.dromara + ruoyi-common-core + + + + + org.springframework.boot + spring-boot-starter-web + + + spring-boot-starter-tomcat + org.springframework.boot + + + + + + org.springframework.boot + spring-boot-starter-undertow + + + + + org.springframework.boot + spring-boot-starter-actuator + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-discovery + true + + + + net.dreamlu + mica-metrics + 2.7.6 + + + net.dreamlu + mica-core + + + + + + net.dreamlu + mica-core + 2.7.6 + provided + + + + diff --git a/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/I18nConfig.java b/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/I18nConfig.java new file mode 100644 index 0000000..4e212cb --- /dev/null +++ b/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/I18nConfig.java @@ -0,0 +1,22 @@ +package org.dromara.common.web.config; + +import org.dromara.common.web.core.I18nLocaleResolver; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.web.servlet.LocaleResolver; + +/** + * 国际化配置 + * + * @author Lion Li + */ +@AutoConfiguration(before = WebMvcAutoConfiguration.class) +public class I18nConfig { + + @Bean + public LocaleResolver localeResolver() { + return new I18nLocaleResolver(); + } + +} diff --git a/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/UndertowConfig.java b/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/UndertowConfig.java new file mode 100644 index 0000000..421ce6d --- /dev/null +++ b/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/UndertowConfig.java @@ -0,0 +1,30 @@ +package org.dromara.common.web.config; + +import io.undertow.server.DefaultByteBufferPool; +import io.undertow.websockets.jsr.WebSocketDeploymentInfo; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory; +import org.springframework.boot.web.server.WebServerFactoryCustomizer; + +/** + * Undertow 自定义配置 + * + * @author Lion Li + */ +@AutoConfiguration +public class UndertowConfig implements WebServerFactoryCustomizer { + + /** + * 设置 Undertow 的 websocket 缓冲池 + */ + @Override + public void customize(UndertowServletWebServerFactory factory) { + // 默认不直接分配内存 如果项目中使用了 websocket 建议直接分配 + factory.addDeploymentInfoCustomizers(deploymentInfo -> { + WebSocketDeploymentInfo webSocketDeploymentInfo = new WebSocketDeploymentInfo(); + webSocketDeploymentInfo.setBuffers(new DefaultByteBufferPool(false, 512)); + deploymentInfo.addServletContextAttribute("io.undertow.websockets.jsr.WebSocketDeploymentInfo", webSocketDeploymentInfo); + }); + } + +} diff --git a/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/core/BaseController.java b/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/core/BaseController.java new file mode 100644 index 0000000..2808fa8 --- /dev/null +++ b/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/core/BaseController.java @@ -0,0 +1,32 @@ +package org.dromara.common.web.core; + +import org.dromara.common.core.domain.R; + +/** + * web层通用数据处理 + * + * @author Lion Li + */ +public class BaseController { + + /** + * 响应返回结果 + * + * @param rows 影响行数 + * @return 操作结果 + */ + protected R toAjax(int rows) { + return rows > 0 ? R.ok() : R.fail(); + } + + /** + * 响应返回结果 + * + * @param result 结果 + * @return 操作结果 + */ + protected R toAjax(boolean result) { + return result ? R.ok() : R.fail(); + } + +} diff --git a/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/core/I18nLocaleResolver.java b/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/core/I18nLocaleResolver.java new file mode 100644 index 0000000..98ddd06 --- /dev/null +++ b/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/core/I18nLocaleResolver.java @@ -0,0 +1,31 @@ +package org.dromara.common.web.core; + +import org.springframework.web.servlet.LocaleResolver; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.util.Locale; + +/** + * 获取请求头国际化信息 + * + * @author Lion Li + */ +public class I18nLocaleResolver implements LocaleResolver { + + @Override + public Locale resolveLocale(HttpServletRequest httpServletRequest) { + String language = httpServletRequest.getHeader("content-language"); + Locale locale = Locale.getDefault(); + if (language != null && language.length() > 0) { + String[] split = language.split("_"); + locale = new Locale(split[0], split[1]); + } + return locale; + } + + @Override + public void setLocale(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Locale locale) { + + } +} diff --git a/ruoyi-common/ruoyi-common-web/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/ruoyi-common/ruoyi-common-web/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..cdad79a --- /dev/null +++ b/ruoyi-common/ruoyi-common-web/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1,2 @@ +org.dromara.common.web.config.I18nConfig +org.dromara.common.web.config.UndertowConfig diff --git a/ruoyi-common/ruoyi-common-web/src/main/resources/logback-common.xml b/ruoyi-common/ruoyi-common-web/src/main/resources/logback-common.xml new file mode 100644 index 0000000..89eaa97 --- /dev/null +++ b/ruoyi-common/ruoyi-common-web/src/main/resources/logback-common.xml @@ -0,0 +1,97 @@ + + + + + + + + + ${log.path}/console.log + + + ${log.path}/console.%d{yyyy-MM-dd}.log + + 1 + + + ${log.pattern} + utf-8 + + + + INFO + + + + + + ${log.path}/info.log + + + + ${log.path}/info.%d{yyyy-MM-dd}.log + + 60 + + + ${log.pattern} + + + + INFO + + ACCEPT + + DENY + + + + + ${log.path}/error.log + + + + ${log.path}/error.%d{yyyy-MM-dd}.log + + 60 + + + ${log.pattern} + + + + ERROR + + ACCEPT + + DENY + + + + + + + 0 + + 512 + + + + + + + + 0 + + 512 + + + + + + + + + + + diff --git a/ruoyi-common/ruoyi-common-websocket/pom.xml b/ruoyi-common/ruoyi-common-websocket/pom.xml new file mode 100644 index 0000000..db86dcb --- /dev/null +++ b/ruoyi-common/ruoyi-common-websocket/pom.xml @@ -0,0 +1,40 @@ + + + + org.dromara + ruoyi-common + ${revision} + + 4.0.0 + + ruoyi-common-websocket + + + ruoyi-common-websocket 模块 + + + + + org.dromara + ruoyi-common-core + + + org.dromara + ruoyi-common-redis + + + org.dromara + ruoyi-common-satoken + + + org.dromara + ruoyi-common-json + + + org.springframework.boot + spring-boot-starter-websocket + + + diff --git a/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/config/WebSocketConfig.java b/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/config/WebSocketConfig.java new file mode 100644 index 0000000..30d109e --- /dev/null +++ b/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/config/WebSocketConfig.java @@ -0,0 +1,60 @@ +package org.dromara.common.websocket.config; + +import cn.hutool.core.util.StrUtil; +import org.dromara.common.websocket.config.properties.WebSocketProperties; +import org.dromara.common.websocket.handler.PlusWebSocketHandler; +import org.dromara.common.websocket.interceptor.PlusWebSocketInterceptor; +import org.dromara.common.websocket.listener.WebSocketTopicListener; +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; +import org.springframework.web.socket.WebSocketHandler; +import org.springframework.web.socket.config.annotation.EnableWebSocket; +import org.springframework.web.socket.config.annotation.WebSocketConfigurer; +import org.springframework.web.socket.server.HandshakeInterceptor; + +/** + * WebSocket 配置 + * + * @author zendwang + */ +@AutoConfiguration +@ConditionalOnProperty(value = "websocket.enabled", havingValue = "true") +@EnableConfigurationProperties(WebSocketProperties.class) +@EnableWebSocket +public class WebSocketConfig { + + @Bean + public WebSocketConfigurer webSocketConfigurer(HandshakeInterceptor handshakeInterceptor, + WebSocketHandler webSocketHandler, + WebSocketProperties webSocketProperties) { + if (StrUtil.isBlank(webSocketProperties.getPath())) { + webSocketProperties.setPath("/websocket"); + } + + if (StrUtil.isBlank(webSocketProperties.getAllowedOrigins())) { + webSocketProperties.setAllowedOrigins("*"); + } + + return registry -> registry + .addHandler(webSocketHandler, webSocketProperties.getPath()) + .addInterceptors(handshakeInterceptor) + .setAllowedOrigins(webSocketProperties.getAllowedOrigins()); + } + + @Bean + public HandshakeInterceptor handshakeInterceptor() { + return new PlusWebSocketInterceptor(); + } + + @Bean + public WebSocketHandler webSocketHandler() { + return new PlusWebSocketHandler(); + } + + @Bean + public WebSocketTopicListener topicListener() { + return new WebSocketTopicListener(); + } +} diff --git a/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/config/properties/WebSocketProperties.java b/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/config/properties/WebSocketProperties.java new file mode 100644 index 0000000..d629fe5 --- /dev/null +++ b/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/config/properties/WebSocketProperties.java @@ -0,0 +1,26 @@ +package org.dromara.common.websocket.config.properties; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * WebSocket 配置项 + * + * @author zendwang + */ +@ConfigurationProperties("websocket") +@Data +public class WebSocketProperties { + + private Boolean enabled; + + /** + * 路径 + */ + private String path; + + /** + * 设置访问源地址 + */ + private String allowedOrigins; +} diff --git a/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/constant/WebSocketConstants.java b/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/constant/WebSocketConstants.java new file mode 100644 index 0000000..54eb447 --- /dev/null +++ b/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/constant/WebSocketConstants.java @@ -0,0 +1,28 @@ +package org.dromara.common.websocket.constant; + +/** + * websocket的常量配置 + * + * @author zendwang + */ +public interface WebSocketConstants { + /** + * websocketSession中的参数的key + */ + String LOGIN_USER_KEY = "loginUser"; + + /** + * 订阅的频道 + */ + String WEB_SOCKET_TOPIC = "global:websocket"; + + /** + * 前端心跳检查的命令 + */ + String PING = "ping"; + + /** + * 服务端心跳恢复的字符串 + */ + String PONG = "pong"; +} diff --git a/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/dto/WebSocketMessageDto.java b/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/dto/WebSocketMessageDto.java new file mode 100644 index 0000000..e2d4456 --- /dev/null +++ b/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/dto/WebSocketMessageDto.java @@ -0,0 +1,29 @@ +package org.dromara.common.websocket.dto; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +/** + * 消息的dto + * + * @author zendwang + */ +@Data +public class WebSocketMessageDto implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 需要推送到的session key 列表 + */ + private List sessionKeys; + + /** + * 需要发送的消息 + */ + private String message; +} diff --git a/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/handler/PlusWebSocketHandler.java b/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/handler/PlusWebSocketHandler.java new file mode 100644 index 0000000..b92f0cf --- /dev/null +++ b/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/handler/PlusWebSocketHandler.java @@ -0,0 +1,102 @@ +package org.dromara.common.websocket.handler; + +import lombok.extern.slf4j.Slf4j; +import org.dromara.common.websocket.dto.WebSocketMessageDto; +import org.dromara.common.websocket.holder.WebSocketSessionHolder; +import org.dromara.common.websocket.utils.WebSocketUtils; +import org.dromara.system.api.model.LoginUser; +import org.springframework.web.socket.*; +import org.springframework.web.socket.handler.AbstractWebSocketHandler; + +import java.util.List; + +import static org.dromara.common.websocket.constant.WebSocketConstants.LOGIN_USER_KEY; + +/** + * WebSocketHandler 实现类 + * + * @author zendwang + */ +@Slf4j +public class PlusWebSocketHandler extends AbstractWebSocketHandler { + + /** + * 连接成功后 + */ + @Override + public void afterConnectionEstablished(WebSocketSession session) { + LoginUser loginUser = (LoginUser) session.getAttributes().get(LOGIN_USER_KEY); + WebSocketSessionHolder.addSession(loginUser.getUserId(), session); + log.info("[connect] sessionId: {},userId:{},userType:{}", session.getId(), loginUser.getUserId(), loginUser.getUserType()); + } + + /** + * 处理发送来的文本消息 + * + * @param session + * @param message + * @throws Exception + */ + @Override + protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { + LoginUser loginUser = (LoginUser) session.getAttributes().get(LOGIN_USER_KEY); + List userIds = List.of(loginUser.getUserId()); + WebSocketMessageDto webSocketMessageDto = new WebSocketMessageDto(); + webSocketMessageDto.setSessionKeys(userIds); + webSocketMessageDto.setMessage(message.getPayload()); + WebSocketUtils.publishMessage(webSocketMessageDto); + } + + @Override + protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message) throws Exception { + super.handleBinaryMessage(session, message); + } + + /** + * 心跳监测的回复 + * + * @param session + * @param message + * @throws Exception + */ + @Override + protected void handlePongMessage(WebSocketSession session, PongMessage message) throws Exception { + WebSocketUtils.sendPongMessage(session); + } + + /** + * 连接出错时 + * + * @param session + * @param exception + * @throws Exception + */ + @Override + public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception { + log.error("[transport error] sessionId: {} , exception:{}", session.getId(), exception.getMessage()); + } + + /** + * 连接关闭后 + * + * @param session + * @param status + */ + @Override + public void afterConnectionClosed(WebSocketSession session, CloseStatus status) { + LoginUser loginUser = (LoginUser) session.getAttributes().get(LOGIN_USER_KEY); + WebSocketSessionHolder.removeSession(loginUser.getUserId()); + log.info("[disconnect] sessionId: {},userId:{},userType:{}", session.getId(), loginUser.getUserId(), loginUser.getUserType()); + } + + /** + * 是否支持分片消息 + * + * @return + */ + @Override + public boolean supportsPartialMessages() { + return false; + } + +} diff --git a/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/holder/WebSocketSessionHolder.java b/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/holder/WebSocketSessionHolder.java new file mode 100644 index 0000000..de8c5a7 --- /dev/null +++ b/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/holder/WebSocketSessionHolder.java @@ -0,0 +1,42 @@ +package org.dromara.common.websocket.holder; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.springframework.web.socket.WebSocketSession; + +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +/** + * WebSocketSession 用于保存当前所有在线的会话信息 + * + * @author zendwang + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class WebSocketSessionHolder { + + private static final Map USER_SESSION_MAP = new ConcurrentHashMap<>(); + + public static void addSession(Long sessionKey, WebSocketSession session) { + USER_SESSION_MAP.put(sessionKey, session); + } + + public static void removeSession(Long sessionKey) { + if (USER_SESSION_MAP.containsKey(sessionKey)) { + USER_SESSION_MAP.remove(sessionKey); + } + } + + public static WebSocketSession getSessions(Long sessionKey) { + return USER_SESSION_MAP.get(sessionKey); + } + + public static Set getSessionsAll() { + return USER_SESSION_MAP.keySet(); + } + + public static Boolean existSession(Long sessionKey) { + return USER_SESSION_MAP.containsKey(sessionKey); + } +} diff --git a/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/interceptor/PlusWebSocketInterceptor.java b/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/interceptor/PlusWebSocketInterceptor.java new file mode 100644 index 0000000..fda4b59 --- /dev/null +++ b/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/interceptor/PlusWebSocketInterceptor.java @@ -0,0 +1,51 @@ +package org.dromara.common.websocket.interceptor; + +import lombok.extern.slf4j.Slf4j; +import org.dromara.common.satoken.utils.LoginHelper; +import org.dromara.system.api.model.LoginUser; +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.http.server.ServerHttpResponse; +import org.springframework.web.socket.WebSocketHandler; +import org.springframework.web.socket.server.HandshakeInterceptor; + +import java.util.Map; + +import static org.dromara.common.websocket.constant.WebSocketConstants.LOGIN_USER_KEY; + +/** + * WebSocket握手请求的拦截器 + * + * @author zendwang + */ +@Slf4j +public class PlusWebSocketInterceptor implements HandshakeInterceptor { + + /** + * 握手前 + * + * @param request request + * @param response response + * @param wsHandler wsHandler + * @param attributes attributes + * @return 是否握手成功 + */ + @Override + public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map attributes) { + LoginUser loginUser = LoginHelper.getLoginUser(); + attributes.put(LOGIN_USER_KEY, loginUser); + return true; + } + + /** + * 握手后 + * + * @param request request + * @param response response + * @param wsHandler wsHandler + * @param exception 异常 + */ + @Override + public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) { + + } +} diff --git a/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/listener/WebSocketTopicListener.java b/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/listener/WebSocketTopicListener.java new file mode 100644 index 0000000..01528d0 --- /dev/null +++ b/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/listener/WebSocketTopicListener.java @@ -0,0 +1,43 @@ +package org.dromara.common.websocket.listener; + +import cn.hutool.core.collection.CollUtil; +import org.dromara.common.websocket.holder.WebSocketSessionHolder; +import org.dromara.common.websocket.utils.WebSocketUtils; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.core.Ordered; + +/** + * WebSocket 主题订阅监听器 + * + * @author zendwang + */ +@Slf4j +public class WebSocketTopicListener implements ApplicationRunner, Ordered { + + @Override + public void run(ApplicationArguments args) throws Exception { + WebSocketUtils.subscribeMessage((message) -> { + log.info("WebSocket主题订阅收到消息session keys={} message={}", message.getSessionKeys(), message.getMessage()); + // 如果key不为空就按照key发消息 如果为空就群发 + if (CollUtil.isNotEmpty(message.getSessionKeys())) { + message.getSessionKeys().forEach(key -> { + if (WebSocketSessionHolder.existSession(key)) { + WebSocketUtils.sendMessage(key, message.getMessage()); + } + }); + } else { + WebSocketSessionHolder.getSessionsAll().forEach(key -> { + WebSocketUtils.sendMessage(key, message.getMessage()); + }); + } + }); + log.info("初始化WebSocket主题订阅监听器成功"); + } + + @Override + public int getOrder() { + return -1; + } +} diff --git a/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/utils/WebSocketUtils.java b/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/utils/WebSocketUtils.java new file mode 100644 index 0000000..99bb845 --- /dev/null +++ b/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/utils/WebSocketUtils.java @@ -0,0 +1,110 @@ +package org.dromara.common.websocket.utils; + +import cn.hutool.core.collection.CollUtil; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.dromara.common.redis.utils.RedisUtils; +import org.dromara.common.websocket.dto.WebSocketMessageDto; +import org.dromara.common.websocket.holder.WebSocketSessionHolder; +import org.springframework.web.socket.PongMessage; +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketMessage; +import org.springframework.web.socket.WebSocketSession; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +import static org.dromara.common.websocket.constant.WebSocketConstants.WEB_SOCKET_TOPIC; + +/** + * 工具类 + * + * @author zendwang + */ +@Slf4j +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class WebSocketUtils { + + /** + * 发送消息 + * + * @param sessionKey session主键 一般为用户id + * @param message 消息文本 + */ + public static void sendMessage(Long sessionKey, String message) { + WebSocketSession session = WebSocketSessionHolder.getSessions(sessionKey); + sendMessage(session, message); + } + + /** + * 订阅消息 + * + * @param consumer 自定义处理 + */ + public static void subscribeMessage(Consumer consumer) { + RedisUtils.subscribe(WEB_SOCKET_TOPIC, WebSocketMessageDto.class, consumer); + } + + /** + * 发布订阅的消息 + * + * @param webSocketMessage 消息对象 + */ + public static void publishMessage(WebSocketMessageDto webSocketMessage) { + List unsentSessionKeys = new ArrayList<>(); + // 当前服务内session,直接发送消息 + for (Long sessionKey : webSocketMessage.getSessionKeys()) { + if (WebSocketSessionHolder.existSession(sessionKey)) { + WebSocketUtils.sendMessage(sessionKey, webSocketMessage.getMessage()); + continue; + } + unsentSessionKeys.add(sessionKey); + } + // 不在当前服务内session,发布订阅消息 + if (CollUtil.isNotEmpty(unsentSessionKeys)) { + WebSocketMessageDto broadcastMessage = new WebSocketMessageDto(); + broadcastMessage.setMessage(webSocketMessage.getMessage()); + broadcastMessage.setSessionKeys(unsentSessionKeys); + RedisUtils.publish(WEB_SOCKET_TOPIC, broadcastMessage, consumer -> { + log.info("WebSocket发送主题订阅消息topic:{} session keys:{} message:{}", + WEB_SOCKET_TOPIC, unsentSessionKeys, webSocketMessage.getMessage()); + }); + } + } + + /** + * 发布订阅的消息(群发) + * + * @param message 消息内容 + */ + public static void publishAll(String message) { + WebSocketMessageDto broadcastMessage = new WebSocketMessageDto(); + broadcastMessage.setMessage(message); + RedisUtils.publish(WEB_SOCKET_TOPIC, broadcastMessage, consumer -> { + log.info("WebSocket发送主题订阅消息topic:{} message:{}", WEB_SOCKET_TOPIC, message); + }); + } + + public static void sendPongMessage(WebSocketSession session) { + sendMessage(session, new PongMessage()); + } + + public static void sendMessage(WebSocketSession session, String message) { + sendMessage(session, new TextMessage(message)); + } + + private static void sendMessage(WebSocketSession session, WebSocketMessage message) { + if (session == null || !session.isOpen()) { + log.warn("[send] session会话已经关闭"); + } else { + try { + session.sendMessage(message); + } catch (IOException e) { + log.error("[send] session({}) 发送消息({}) 异常", session, message, e); + } + } + } +} diff --git a/ruoyi-gateway/src/main/resources/application.yml b/ruoyi-gateway/src/main/resources/application.yml index 3e2def6..6f41721 100644 --- a/ruoyi-gateway/src/main/resources/application.yml +++ b/ruoyi-gateway/src/main/resources/application.yml @@ -22,13 +22,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-modules/pom.xml b/ruoyi-modules/pom.xml index e482762..4498f53 100644 --- a/ruoyi-modules/pom.xml +++ b/ruoyi-modules/pom.xml @@ -33,6 +33,7 @@ ruoyi-common-loadbalancer + diff --git a/ruoyi-modules/ruoyi-flexible/src/main/resources/application.yml b/ruoyi-modules/ruoyi-flexible/src/main/resources/application.yml index fd66dbb..af1170a 100644 --- a/ruoyi-modules/ruoyi-flexible/src/main/resources/application.yml +++ b/ruoyi-modules/ruoyi-flexible/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-modules/ruoyi-gen/src/main/resources/application.yml b/ruoyi-modules/ruoyi-gen/src/main/resources/application.yml index 253639b..c6aadbd 100644 --- a/ruoyi-modules/ruoyi-gen/src/main/resources/application.yml +++ b/ruoyi-modules/ruoyi-gen/src/main/resources/application.yml @@ -20,14 +20,14 @@ 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-modules/ruoyi-job/src/main/resources/application.yml b/ruoyi-modules/ruoyi-job/src/main/resources/application.yml index 77fda0c..61a9eb5 100644 --- a/ruoyi-modules/ruoyi-job/src/main/resources/application.yml +++ b/ruoyi-modules/ruoyi-job/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-modules/ruoyi-mp/src/main/java/org/dromara/mp/controller/UserWorkerController.java b/ruoyi-modules/ruoyi-mp/src/main/java/org/dromara/mp/controller/UserWorkerController.java index 5eea04a..45df26f 100644 --- a/ruoyi-modules/ruoyi-mp/src/main/java/org/dromara/mp/controller/UserWorkerController.java +++ b/ruoyi-modules/ruoyi-mp/src/main/java/org/dromara/mp/controller/UserWorkerController.java @@ -47,7 +47,7 @@ public class UserWorkerController extends BaseController { /** * 查询自雇者列表 */ - @SaCheckPermission("mp:worker:list") +// @SaCheckPermission("mp:worker:list") @GetMapping("/list") @Log(title = "查询自雇者列表", businessType = BusinessType.SELECT,operatorType= OperatorType.MOBILE) public TableDataInfo list(UserWorkerBo bo, PageQuery pageQuery) { @@ -57,7 +57,7 @@ public class UserWorkerController extends BaseController { /** * 导出自雇者列表 */ - @SaCheckPermission("mp:worker:export") +// @SaCheckPermission("mp:worker:export") @Log(title = "自雇者", businessType = BusinessType.EXPORT) @PostMapping("/export") public void export(UserWorkerBo bo, HttpServletResponse response) { @@ -70,7 +70,7 @@ public class UserWorkerController extends BaseController { * * @param id 主键 */ - @SaCheckPermission("mp:worker:query") +// @SaCheckPermission("mp:worker:query") @GetMapping("/{id}") @Log(title = "获取自雇者详细信息", businessType = BusinessType.SELECT,operatorType= OperatorType.MOBILE) public R getInfo(@NotNull(message = "主键不能为空") @@ -81,7 +81,7 @@ public class UserWorkerController extends BaseController { /** * 新增自雇者 */ - @SaCheckPermission("mp:worker:add") +// @SaCheckPermission("mp:worker:add") @Log(title = "自雇者", businessType = BusinessType.INSERT) @PostMapping() public R add(@Validated(AddGroup.class) @RequestBody UserWorkerBo bo) { @@ -91,7 +91,7 @@ public class UserWorkerController extends BaseController { /** * 修改自雇者 */ - @SaCheckPermission("mp:worker:edit") +// @SaCheckPermission("mp:worker:edit") @Log(title = "自雇者", businessType = BusinessType.UPDATE) @PutMapping() public R edit(@Validated(EditGroup.class) @RequestBody UserWorkerBo bo) { @@ -103,7 +103,7 @@ public class UserWorkerController extends BaseController { * * @param ids 主键串 */ - @SaCheckPermission("mp:worker:remove") +// @SaCheckPermission("mp:worker:remove") @Log(title = "自雇者", businessType = BusinessType.DELETE) @DeleteMapping("/{ids}") public R remove(@NotEmpty(message = "主键不能为空") diff --git a/ruoyi-modules/ruoyi-mp/src/main/resources/application.yml b/ruoyi-modules/ruoyi-mp/src/main/resources/application.yml index 03e3a4c..ced8659 100644 --- a/ruoyi-modules/ruoyi-mp/src/main/resources/application.yml +++ b/ruoyi-modules/ruoyi-mp/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-modules/ruoyi-payment/pom.xml b/ruoyi-modules/ruoyi-payment/pom.xml index 4a058e5..155807e 100644 --- a/ruoyi-modules/ruoyi-payment/pom.xml +++ b/ruoyi-modules/ruoyi-payment/pom.xml @@ -153,6 +153,20 @@ spring-boot-starter-test test + + + javax.validation + validation-api + 2.0.1.Final + + + + org.dromara + ruoyi-system + 2.1.2 + compile + + diff --git a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/bill/controller/MerBillController.java b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/bill/controller/MerBillController.java index 4492bd1..db0ec91 100644 --- a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/bill/controller/MerBillController.java +++ b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/bill/controller/MerBillController.java @@ -32,6 +32,11 @@ import org.dromara.payment.task.domain.vo.MerTaskEnrollVo; import org.dromara.payment.task.domain.vo.MerTaskVo; import org.dromara.payment.task.service.IMerTaskEnrollService; import org.dromara.payment.task.service.IMerTaskService; +import org.dromara.payment.worker.domain.UserWorkerAccount; +import org.dromara.payment.worker.domain.bo.UserWorkerAccountBo; +import org.dromara.payment.worker.domain.vo.UserWorkerVo; +import org.dromara.payment.worker.service.impl.UserWorkerAccountServiceImpl; +import org.dromara.payment.worker.service.impl.UserWorkerServiceImpl; import org.dromara.system.api.model.LoginUser; import org.springframework.cloud.stream.function.StreamBridge; import org.springframework.http.MediaType; @@ -76,6 +81,10 @@ public class MerBillController extends BaseController { private final StreamBridge streamBridge; + private final UserWorkerAccountServiceImpl userWorkerAccountServiceImpl; + + private final UserWorkerServiceImpl userWorkerServiceImpl; + private void bindDataPermission(MerBillBo bo) { LoginUser loginUser = LoginHelper.getLoginUser(); if(loginUser.getCurRoleId() == BusRole.SERVICE.getId()){ @@ -308,6 +317,25 @@ public class MerBillController extends BaseController { public R> importBillData(@RequestPart("file") MultipartFile file,AddBillBo bo) throws Exception { this.merBillService.validateSysSyd(bo); ExcelResult result = ExcelUtil.importExcel(file.getInputStream(),new BillImportListener(bo)); + + List list = result.getList(); + for (MerBillDetailBo merBillDetailBo : list) { + + UserWorkerVo userWorkerVo = userWorkerServiceImpl.queryByName(merBillDetailBo.getWorkerName()); + + UserWorkerAccountBo account = new UserWorkerAccountBo(); + account.setWorkId(userWorkerVo.getId()); + account.setBankCard(merBillDetailBo.getBankCard()); + + + + + + + userWorkerAccountServiceImpl.updateByBo(account); + } + + return R.ok(result); } diff --git a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/bill/domain/bo/MerBillDetailBo.java b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/bill/domain/bo/MerBillDetailBo.java index 5a3362d..25d4c1c 100644 --- a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/bill/domain/bo/MerBillDetailBo.java +++ b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/bill/domain/bo/MerBillDetailBo.java @@ -107,6 +107,7 @@ public class MerBillDetailBo extends BusId { */ @NotNull(message = "账单金额(个人应得)不能为空", groups = { AddGroup.class, EditGroup.class }) private BigDecimal moneyDecimal; + private Long money; /** diff --git a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/bill/service/impl/MerBillServiceImpl.java b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/bill/service/impl/MerBillServiceImpl.java index 0d83d99..42a1fc1 100644 --- a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/bill/service/impl/MerBillServiceImpl.java +++ b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/bill/service/impl/MerBillServiceImpl.java @@ -87,6 +87,8 @@ import org.dromara.payment.task.domain.MerTaskEnroll; import org.dromara.payment.task.domain.vo.MerTaskVo; import org.dromara.payment.task.mapper.MerTaskEnrollMapper; import org.dromara.payment.task.mapper.MerTaskMapper; +import org.dromara.payment.thirdPlatform.base.domain.SysThirdPlatform; +import org.dromara.payment.thirdPlatform.base.mapper.SysThirdPlatformMapper; import org.dromara.payment.thirdPlatform.xinyujian.service.IXyjService; import org.dromara.payment.util.MoneyUtils; import org.dromara.payment.worker.domain.UserWorker; @@ -212,6 +214,9 @@ public class MerBillServiceImpl extends BaseService implements IMerBillService { @Autowired private IXyjService xyjService; + + @Autowired + private SysThirdPlatformMapper sysThirdPlatformMapper; /** * 查询账单 */ @@ -1074,7 +1079,13 @@ public class MerBillServiceImpl extends BaseService implements IMerBillService { MerTask merTask = taskMapper.selectById(bill.getTaskId()); BusMerchantAccount account = merchantAccountMapper.selectOne(new LambdaQueryWrapper().eq(BusMerchantAccount::getMerId,bill.getMerId()).eq(BusMerchantAccount::getSydId,bill.getSydId()).eq(BusMerchantAccount::getBankType,bill.getPassage()).eq(BusMerchantAccount::getStatus,1)); List merBillDetails = billDetailMapper.selectList(new LambdaQueryWrapper().eq(MerBillDetail::getBillId,bill.getId())); - if(false){ + SysThirdPlatform sysThirdPlatform = sysThirdPlatformMapper.selectThirdAppInfo(sysSyd.getThreeId()); + + + + + + if(Objects.nonNull(sysThirdPlatform) && sysThirdPlatform.getPlatformName().equals("薪遇见")){ return xyjService.settleBatch(bill, merBillDetails, sysSyd, merchant, merTask, account); }else{ return baseMapper.updateById(update) > 0; diff --git a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/channel/controller/BusChannelController.java b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/channel/controller/BusChannelController.java index 6e2f8bf..fea3844 100644 --- a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/channel/controller/BusChannelController.java +++ b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/channel/controller/BusChannelController.java @@ -56,7 +56,7 @@ public class BusChannelController extends BaseController { /** * 查询代理商列表 */ -// @SaCheckPermission("payment:channel:listConfig") + @SaCheckPermission("payment:channel:listConfig") @GetMapping("/listConfig") public TableDataInfo listConfig(BusChannelBo bo, PageQuery pageQuery) { return busChannelService.queryPageListProConfig(bo, pageQuery); @@ -136,7 +136,6 @@ public class BusChannelController extends BaseController { /** * 审核代理商 */ -// @SaCheckPermission("payment:channel:check") @Log(title = "审核代理商", businessType = BusinessType.UPDATE) @PostMapping("/chk") @SaCheckPermission("payment:channel:check") diff --git a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/channel/domain/convert/BusChannelAccountSynVoConvert.java b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/channel/domain/convert/BusChannelAccountSynVoConvert.java index a667eb6..a5dda67 100644 --- a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/channel/domain/convert/BusChannelAccountSynVoConvert.java +++ b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/channel/domain/convert/BusChannelAccountSynVoConvert.java @@ -1,7 +1,8 @@ package org.dromara.payment.channel.domain.convert; import io.github.linpeilie.BaseMapper; -import org.dromara.payment.channel.api.domain.RemoteBusChannelAccountSynVo; + +import org.dromara.payment.channel.domain.RemoteBusChannelAccountSynVo; import org.dromara.payment.channel.domain.vo.BusChannelAccountSynVo; import org.mapstruct.Mapper; import org.mapstruct.MappingConstants; diff --git a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/channel/domain/convert/BusChannelAccountVoConvert.java b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/channel/domain/convert/BusChannelAccountVoConvert.java index a65621c..09d58d1 100644 --- a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/channel/domain/convert/BusChannelAccountVoConvert.java +++ b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/channel/domain/convert/BusChannelAccountVoConvert.java @@ -1,7 +1,8 @@ package org.dromara.payment.channel.domain.convert; import io.github.linpeilie.BaseMapper; -import org.dromara.payment.channel.api.domain.RemoteBusChannelAccountVo; + +import org.dromara.payment.channel.domain.RemoteBusChannelAccountVo; import org.dromara.payment.channel.domain.vo.BusChannelAccountVo; import org.mapstruct.Mapper; import org.mapstruct.MappingConstants; diff --git a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/channel/domain/convert/BusChannelCashoutVoConvert.java b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/channel/domain/convert/BusChannelCashoutVoConvert.java index ec83107..7a30f83 100644 --- a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/channel/domain/convert/BusChannelCashoutVoConvert.java +++ b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/channel/domain/convert/BusChannelCashoutVoConvert.java @@ -1,7 +1,8 @@ package org.dromara.payment.channel.domain.convert; import io.github.linpeilie.BaseMapper; -import org.dromara.payment.channel.api.domain.RemoteBusChannelCashoutVo; + +import org.dromara.payment.channel.domain.RemoteBusChannelCashoutVo; import org.dromara.payment.channel.domain.vo.BusChannelCashoutVo; import org.mapstruct.Mapper; import org.mapstruct.MappingConstants; diff --git a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/channel/domain/convert/BusChannelFlowsVoConvert.java b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/channel/domain/convert/BusChannelFlowsVoConvert.java index 342e01d..1af08e2 100644 --- a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/channel/domain/convert/BusChannelFlowsVoConvert.java +++ b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/channel/domain/convert/BusChannelFlowsVoConvert.java @@ -1,7 +1,8 @@ package org.dromara.payment.channel.domain.convert; import io.github.linpeilie.BaseMapper; -import org.dromara.payment.channel.api.domain.RemoteBusChannelFlowsVo; + +import org.dromara.payment.channel.domain.RemoteBusChannelFlowsVo; import org.dromara.payment.channel.domain.vo.BusChannelFlowsVo; import org.mapstruct.Mapper; import org.mapstruct.MappingConstants; diff --git a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/channel/domain/convert/BusChannelProductVoConvert.java b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/channel/domain/convert/BusChannelProductVoConvert.java index 2370304..80c8105 100644 --- a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/channel/domain/convert/BusChannelProductVoConvert.java +++ b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/channel/domain/convert/BusChannelProductVoConvert.java @@ -1,7 +1,8 @@ package org.dromara.payment.channel.domain.convert; import io.github.linpeilie.BaseMapper; -import org.dromara.payment.channel.api.domain.RemoteBusChannelProductVo; + +import org.dromara.payment.channel.domain.RemoteBusChannelProductVo; import org.dromara.payment.channel.domain.vo.BusChannelProductVo; import org.mapstruct.Mapper; import org.mapstruct.MappingConstants; diff --git a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/channel/domain/convert/BusChannelSalesmanVoConvert.java b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/channel/domain/convert/BusChannelSalesmanVoConvert.java index 72b4e29..8c0b754 100644 --- a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/channel/domain/convert/BusChannelSalesmanVoConvert.java +++ b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/channel/domain/convert/BusChannelSalesmanVoConvert.java @@ -1,7 +1,8 @@ package org.dromara.payment.channel.domain.convert; import io.github.linpeilie.BaseMapper; -import org.dromara.payment.channel.api.domain.RemoteBusChannelSalesmanVo; + +import org.dromara.payment.channel.domain.RemoteBusChannelSalesmanVo; import org.dromara.payment.channel.domain.vo.BusChannelSalesmanVo; import org.mapstruct.Mapper; import org.mapstruct.MappingConstants; diff --git a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/channel/domain/convert/BusChannelVoConvert.java b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/channel/domain/convert/BusChannelVoConvert.java index cb625cd..b3d668d 100644 --- a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/channel/domain/convert/BusChannelVoConvert.java +++ b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/channel/domain/convert/BusChannelVoConvert.java @@ -1,7 +1,8 @@ package org.dromara.payment.channel.domain.convert; import io.github.linpeilie.BaseMapper; -import org.dromara.payment.channel.api.domain.RemoteBusChannelVo; + +import org.dromara.payment.channel.domain.RemoteBusChannelVo; import org.dromara.payment.channel.domain.vo.BusChannelVo; import org.mapstruct.Mapper; import org.mapstruct.MappingConstants; diff --git a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/channel/service/impl/BusChannelProductServiceImpl.java b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/channel/service/impl/BusChannelProductServiceImpl.java index 5020319..8b91d82 100644 --- a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/channel/service/impl/BusChannelProductServiceImpl.java +++ b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/channel/service/impl/BusChannelProductServiceImpl.java @@ -19,6 +19,8 @@ import org.dromara.common.satoken.utils.LoginHelper; import org.dromara.payment.merchant.domain.BusMerchantProduct; import org.dromara.payment.merchant.domain.bo.BusMerchantProductBo; import org.dromara.payment.merchant.mapper.BusMerchantProductMapper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import org.dromara.payment.channel.domain.bo.BusChannelProductBo; import org.dromara.payment.channel.domain.vo.BusChannelProductVo; @@ -40,6 +42,7 @@ import java.util.*; @Service public class BusChannelProductServiceImpl extends BaseService implements IBusChannelProductService { + private static final Logger log = LoggerFactory.getLogger(BusChannelProductServiceImpl.class); private final BusChannelProductMapper baseMapper; private final BusMerchantProductMapper busMerchantProductMapper; @@ -66,14 +69,19 @@ public class BusChannelProductServiceImpl extends BaseService implements IBusCha bo.setChannelId(LoginHelper.getBusId()); int burRole = LoginHelper.getLoginUser().getBusRole(); Page result = null; + if(burRole == BusRole.AGENT.getId()){ result = baseMapper.selectAgentProList(bo,pageQuery.build()); }else if(burRole == BusRole.OPERATOR.getId()){ result = baseMapper.selectOperAgentProList(bo,pageQuery.build()); + }else if(LoginHelper.isSuperAdmin()){ + result = baseMapper.selectOperAgentProList(bo,pageQuery.build()); + } + if (result != null) { + result.getRecords().forEach(item -> { + item.setUnId(IdUtil.getSnowflakeNextId()); + }); } - result.getRecords().forEach(item -> { - item.setUnId(IdUtil.getSnowflakeNextId()); - }); return TableDataInfo.build(result); } diff --git a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/channel/service/impl/BusChannelServiceImpl.java b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/channel/service/impl/BusChannelServiceImpl.java index b9da659..0bdcd74 100644 --- a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/channel/service/impl/BusChannelServiceImpl.java +++ b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/channel/service/impl/BusChannelServiceImpl.java @@ -124,18 +124,19 @@ public class BusChannelServiceImpl extends BaseService implements IBusChannelSer @Override public TableDataInfo queryPageList(BusChannelBo bo, PageQuery pageQuery) { QueryWrapper lqw = Wrappers.query(); - lqw.between(bo.getStartTime() != null,"t.create_time",bo.getStartTime(),bo.getEndTime()); - lqw.eq(bo.getDwType() != null,"t.dw_type",bo.getDwType()); - lqw.eq(bo.getDwLevel() != null,"t.dw_level",bo.getDwLevel()); - lqw.like(StringUtils.isNotEmpty(bo.getName()),"t.name",bo.getName()); - lqw.like(StringUtils.isNotEmpty(bo.getNo()),"t.no",bo.getNo()); - lqw.eq(bo.getVerifyStatus() != null,"t.verify_status",bo.getVerifyStatus()); - lqw.ne(StringUtils.isNotEmpty(LoginHelper.getNo()),"t.no",LoginHelper.getNo() ); - if(bo.getIsProduct() != null && bo.getIsProduct() == 1){ - lqw.eq("t.create_unit_id",LoginHelper.getBusId()); + if (!LoginHelper.isSuperAdmin()) { + lqw.between(bo.getStartTime() != null, "t.create_time", bo.getStartTime(), bo.getEndTime()); + lqw.eq(bo.getDwType() != null, "t.dw_type", bo.getDwType()); + lqw.eq(bo.getDwLevel() != null, "t.dw_level", bo.getDwLevel()); + lqw.like(StringUtils.isNotEmpty(bo.getName()), "t.name", bo.getName()); + lqw.like(StringUtils.isNotEmpty(bo.getNo()), "t.no", bo.getNo()); + lqw.eq(bo.getVerifyStatus() != null, "t.verify_status", bo.getVerifyStatus()); + lqw.ne(StringUtils.isNotEmpty(LoginHelper.getNo()), "t.no", LoginHelper.getNo()); + if (bo.getIsProduct() != null && bo.getIsProduct() == 1) { + lqw.eq("t.create_unit_id", LoginHelper.getBusId()); + } } - lqw.orderByDesc("t.create_time"); Page result = baseMapper.selBusChannelLists(pageQuery.build(), lqw); return TableDataInfo.build(result); diff --git a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/common/domain/convert/BusOperLogVoConvert.java b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/common/domain/convert/BusOperLogVoConvert.java index f509002..6f7c426 100644 --- a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/common/domain/convert/BusOperLogVoConvert.java +++ b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/common/domain/convert/BusOperLogVoConvert.java @@ -1,7 +1,7 @@ package org.dromara.payment.common.domain.convert; import io.github.linpeilie.BaseMapper; -import org.dromara.payment.common.api.domain.RemoteBusOperLogVo; +import org.dromara.payment.api.common.domain.RemoteBusOperLogVo; import org.dromara.payment.common.domain.vo.BusOperLogVo; import org.mapstruct.Mapper; import org.mapstruct.MappingConstants; diff --git a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/common/domain/convert/TranLogVoConvert.java b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/common/domain/convert/TranLogVoConvert.java index a27ba49..b65686f 100644 --- a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/common/domain/convert/TranLogVoConvert.java +++ b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/common/domain/convert/TranLogVoConvert.java @@ -1,7 +1,7 @@ package org.dromara.payment.common.domain.convert; import io.github.linpeilie.BaseMapper; -import org.dromara.payment.common.api.domain.RemoteTranLogVo; +import org.dromara.payment.api.common.domain.RemoteTranLogVo; import org.dromara.payment.common.domain.vo.TranLogVo; import org.mapstruct.Mapper; import org.mapstruct.MappingConstants; diff --git a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/earlyWarn/controller/LgEarlyWarnController.java b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/earlyWarn/controller/LgEarlyWarnController.java new file mode 100644 index 0000000..0900178 --- /dev/null +++ b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/earlyWarn/controller/LgEarlyWarnController.java @@ -0,0 +1,107 @@ +package org.dromara.payment.earlyWarn.controller; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.RequiredArgsConstructor; +import org.dromara.common.core.domain.R; +import org.dromara.common.core.validate.AddGroup; +import org.dromara.common.core.validate.EditGroup; +import org.dromara.common.excel.utils.ExcelUtil; +import org.dromara.common.idempotent.annotation.RepeatSubmit; +import org.dromara.common.log.annotation.Log; +import org.dromara.common.log.enums.BusinessType; +import org.dromara.common.mybatis.core.page.PageQuery; +import org.dromara.common.mybatis.core.page.TableDataInfo; +import org.dromara.common.web.core.BaseController; +import org.dromara.payment.earlyWarn.domain.bo.LgEarlyWarnBo; +import org.dromara.payment.earlyWarn.domain.vo.LgEarlyWarnVo; +import org.dromara.payment.earlyWarn.service.ILgEarlyWarnService; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 风控预警设置 + * 前端访问路由地址为:/system/earlyWarn + * + * @author LionLi + * @date 2025-06-10 + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/earlyWarn") +public class LgEarlyWarnController extends BaseController { + + private final ILgEarlyWarnService lgEarlyWarnService; + + /** + * 查询风控预警设置列表 + */ + @SaCheckPermission("system:earlyWarn:list") + @GetMapping("/list") + public TableDataInfo list(LgEarlyWarnBo bo, PageQuery pageQuery) { + return lgEarlyWarnService.queryPageList(bo, pageQuery); + } + + /** + * 导出风控预警设置列表 + */ + @SaCheckPermission("system:earlyWarn:export") + @Log(title = "风控预警设置", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(LgEarlyWarnBo bo, HttpServletResponse response) { + List list = lgEarlyWarnService.queryList(bo); + ExcelUtil.exportExcel(list, "风控预警设置", LgEarlyWarnVo.class, response); + } + + /** + * 获取风控预警设置详细信息 + * + * @param id 主键 + */ + @SaCheckPermission("system:earlyWarn:query") + @GetMapping("/{id}") + public R getInfo(@NotNull(message = "主键不能为空") + @PathVariable Long id) { + return R.ok(lgEarlyWarnService.queryById(id)); + } + + /** + * 新增风控预警设置 + */ + @SaCheckPermission("system:earlyWarn:add") + @Log(title = "风控预警设置", businessType = BusinessType.INSERT) + @RepeatSubmit() + @PostMapping() + public R add(@Validated(AddGroup.class) @RequestBody LgEarlyWarnBo bo) { + return toAjax(lgEarlyWarnService.insertByBo(bo)); + } + + /** + * 修改风控预警设置 + */ + @SaCheckPermission("system:earlyWarn:edit") + @Log(title = "风控预警设置", businessType = BusinessType.UPDATE) + @RepeatSubmit() + @PutMapping() + public R edit(@Validated(EditGroup.class) @RequestBody LgEarlyWarnBo bo) { + return toAjax(lgEarlyWarnService.updateByBo(bo)); + } + + /** + * 删除风控预警设置 + * + * @param ids 主键串 + */ + @SaCheckPermission("system:earlyWarn:remove") + @Log(title = "风控预警设置", businessType = BusinessType.DELETE) + @DeleteMapping("/{ids}") + public R remove(@NotEmpty(message = "主键不能为空") + @PathVariable Long[] ids) { + return toAjax(lgEarlyWarnService.deleteWithValidByIds(List.of(ids), true)); + } +} diff --git a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/earlyWarn/controller/LgEarlyWarnDtlController.java b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/earlyWarn/controller/LgEarlyWarnDtlController.java new file mode 100644 index 0000000..b59d1f3 --- /dev/null +++ b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/earlyWarn/controller/LgEarlyWarnDtlController.java @@ -0,0 +1,107 @@ +package org.dromara.payment.earlyWarn.controller; + +import java.util.List; + +import lombok.RequiredArgsConstructor; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.constraints.*; +import cn.dev33.satoken.annotation.SaCheckPermission; +import org.dromara.common.mybatis.core.page.TableDataInfo; +import org.dromara.payment.earlyWarn.domain.bo.LgEarlyWarnDtlBo; +import org.dromara.payment.earlyWarn.domain.vo.LgEarlyWarnDtlVo; +import org.dromara.payment.earlyWarn.service.ILgEarlyWarnDtlService; +import org.springframework.web.bind.annotation.*; +import org.springframework.validation.annotation.Validated; +import org.dromara.common.idempotent.annotation.RepeatSubmit; +import org.dromara.common.log.annotation.Log; +import org.dromara.common.web.core.BaseController; +import org.dromara.common.mybatis.core.page.PageQuery; +import org.dromara.common.core.domain.R; +import org.dromara.common.core.validate.AddGroup; +import org.dromara.common.core.validate.EditGroup; +import org.dromara.common.log.enums.BusinessType; +import org.dromara.common.excel.utils.ExcelUtil; + + +/** + * 风控预警设置明细 + * 前端访问路由地址为:/system/earlyWarnDtl + * + * @author LionLi + * @date 2025-06-10 + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/earlyWarnDtl") +public class LgEarlyWarnDtlController extends BaseController { + + private final ILgEarlyWarnDtlService lgEarlyWarnDtlService; + + /** + * 查询风控预警设置明细列表 + */ + @SaCheckPermission("system:earlyWarnDtl:list") + @GetMapping("/list") + public TableDataInfo list(LgEarlyWarnDtlBo bo, PageQuery pageQuery) { + return lgEarlyWarnDtlService.queryPageList(bo, pageQuery); + } + + /** + * 导出风控预警设置明细列表 + */ + @SaCheckPermission("system:earlyWarnDtl:export") + @Log(title = "风控预警设置明细", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(LgEarlyWarnDtlBo bo, HttpServletResponse response) { + List list = lgEarlyWarnDtlService.queryList(bo); + ExcelUtil.exportExcel(list, "风控预警设置明细", LgEarlyWarnDtlVo.class, response); + } + + /** + * 获取风控预警设置明细详细信息 + * + * @param id 主键 + */ + @SaCheckPermission("system:earlyWarnDtl:query") + @GetMapping("/{id}") + public R getInfo(@NotNull(message = "主键不能为空") + @PathVariable Long id) { + return R.ok(lgEarlyWarnDtlService.queryById(id)); + } + + /** + * 新增风控预警设置明细 + */ + @SaCheckPermission("system:earlyWarnDtl:add") + @Log(title = "风控预警设置明细", businessType = BusinessType.INSERT) + @RepeatSubmit() + @PostMapping() + public R add(@Validated(AddGroup.class) @RequestBody LgEarlyWarnDtlBo bo) { + return toAjax(lgEarlyWarnDtlService.insertByBo(bo)); + } + + /** + * 修改风控预警设置明细 + */ + @SaCheckPermission("system:earlyWarnDtl:edit") + @Log(title = "风控预警设置明细", businessType = BusinessType.UPDATE) + @RepeatSubmit() + @PutMapping() + public R edit(@Validated(EditGroup.class) @RequestBody LgEarlyWarnDtlBo bo) { + return toAjax(lgEarlyWarnDtlService.updateByBo(bo)); + } + + /** + * 删除风控预警设置明细 + * + * @param ids 主键串 + */ + @SaCheckPermission("system:earlyWarnDtl:remove") + @Log(title = "风控预警设置明细", businessType = BusinessType.DELETE) + @DeleteMapping("/{ids}") + public R remove(@NotEmpty(message = "主键不能为空") + @PathVariable Long[] ids) { + return toAjax(lgEarlyWarnDtlService.deleteWithValidByIds(List.of(ids), true)); + } +} diff --git a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/earlyWarn/domain/LgEarlyWarn.java b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/earlyWarn/domain/LgEarlyWarn.java new file mode 100644 index 0000000..b750cb0 --- /dev/null +++ b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/earlyWarn/domain/LgEarlyWarn.java @@ -0,0 +1,148 @@ +package org.dromara.payment.earlyWarn.domain; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.dromara.common.mybatis.core.domain.BaseEntity; + +import java.io.Serial; +import java.math.BigDecimal; + +/** + * 风控预警设置对象 lg_early_warn + * + * @author LionLi + * @date 2025-06-10 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("lg_early_warn") +public class LgEarlyWarn extends BaseEntity { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 自增主键 + */ + @TableId(value = "id") + 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-modules/ruoyi-payment/src/main/java/org/dromara/payment/earlyWarn/domain/LgEarlyWarnDtl.java b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/earlyWarn/domain/LgEarlyWarnDtl.java new file mode 100644 index 0000000..46b06f9 --- /dev/null +++ b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/earlyWarn/domain/LgEarlyWarnDtl.java @@ -0,0 +1,152 @@ +package org.dromara.payment.earlyWarn.domain; + +import org.dromara.common.mybatis.core.domain.BaseEntity; +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import lombok.EqualsAndHashCode; +import java.math.BigDecimal; + +import java.io.Serial; + +/** + * 风控预警设置明细对象 lg_early_warn_dtl + * + * @author LionLi + * @date 2025-06-10 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("lg_early_warn_dtl") +public class LgEarlyWarnDtl extends BaseEntity { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 自增主键 + */ + @TableId(value = "id") + 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-modules/ruoyi-payment/src/main/java/org/dromara/payment/earlyWarn/domain/bo/LgEarlyWarnBo.java b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/earlyWarn/domain/bo/LgEarlyWarnBo.java new file mode 100644 index 0000000..d8a8b1b --- /dev/null +++ b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/earlyWarn/domain/bo/LgEarlyWarnBo.java @@ -0,0 +1,171 @@ +package org.dromara.payment.earlyWarn.domain.bo; + +import io.github.linpeilie.annotations.AutoMapper; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.dromara.common.core.validate.AddGroup; +import org.dromara.common.core.validate.EditGroup; +import org.dromara.common.mybatis.core.domain.BaseEntity; +import org.dromara.payment.earlyWarn.domain.LgEarlyWarn; + +import java.math.BigDecimal; + +/** + * 风控预警设置业务对象 lg_early_warn + * + * @author LionLi + * @date 2025-06-10 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@AutoMapper(target = LgEarlyWarn.class, reverseConvertGenerate = false) +public class LgEarlyWarnBo extends BaseEntity { + + /** + * 自增主键 + */ + @NotNull(message = "自增主键不能为空", groups = { EditGroup.class }) + private Long id; + + /** + * 风险类型[1:单人单笔发放金额预警 2:批次内单笔金额占比预警 3:批次内发放金额相似预警 4:单人每年发放金额预警 5:单人连续月份发放金额相似预警 6:企业风险预警 7:发放年龄预警 8:企业高管身份预警] + */ + @NotNull(message = "风险类型[1:单人单笔发放金额预警 2:批次内单笔金额占比预警 3:批次内发放金额相似预警 4:单人每年发放金额预警 5:单人连续月份发放金额相似预警 6:企业风险预警 7:发放年龄预警 8:企业高管身份预警]不能为空", groups = { AddGroup.class, EditGroup.class }) + private Integer riskType; + + /** + * 触发规则[1 单人单笔金额 2 批次占比 3 离散度 4 单人每年金额 5 单人连续月份 6 企业信息异常(经营异常、行政处罚、违法欠税等)7 发放人员年龄 8 发放人员姓名命中企业高管名单] + */ + @NotNull(message = "触发规则[1 单人单笔金额 2 批次占比 3 离散度 4 单人每年金额 5 单人连续月份 6 企业信息异常(经营异常、行政处罚、违法欠税等)7 发放人员年龄 8 发放人员姓名命中企业高管名单]不能为空", groups = { AddGroup.class, EditGroup.class }) + private Integer triggerRule; + + /** + * 单人单笔金额 + */ + @NotNull(message = "单人单笔金额不能为空", groups = { AddGroup.class, EditGroup.class }) + private BigDecimal singleAmount; + + /** + * 批次总笔数 + */ + @NotNull(message = "批次总笔数不能为空", groups = { AddGroup.class, EditGroup.class }) + private BigDecimal totalAmountBatch; + + /** + * 单笔发放金额起始值 + */ + @NotNull(message = "单笔发放金额起始值不能为空", groups = { AddGroup.class, EditGroup.class }) + private BigDecimal singlePaymentAmountStart; + + /** + * 单笔发放金额结束值 + */ + @NotNull(message = "单笔发放金额结束值不能为空", groups = { AddGroup.class, EditGroup.class }) + private BigDecimal singlePaymentAmountEnd; + + /** + * 占比总数 + */ + @NotNull(message = "占比总数不能为空", groups = { AddGroup.class, EditGroup.class }) + private BigDecimal proportionTotal; + + /** + * 离散度 + */ + @NotNull(message = "离散度不能为空", groups = { AddGroup.class, EditGroup.class }) + private BigDecimal dispersion; + + /** + * 单人每年金额 + */ + @NotNull(message = "单人每年金额不能为空", groups = { AddGroup.class, EditGroup.class }) + private BigDecimal singleYearAmount; + + /** + * 累计发放金额波动起始值 + */ + @NotNull(message = "累计发放金额波动起始值不能为空", groups = { AddGroup.class, EditGroup.class }) + private BigDecimal waveAmountStart; + + /** + * 累计发放金额波动结束值 + */ + @NotNull(message = "累计发放金额波动结束值不能为空", groups = { AddGroup.class, EditGroup.class }) + private BigDecimal waveAmountEnd; + + /** + * 累计发放金额 + */ + @NotNull(message = "累计发放金额不能为空", groups = { AddGroup.class, EditGroup.class }) + private BigDecimal waveAmount; + + /** + * 单人连续月份 + */ + @NotNull(message = "单人连续月份不能为空", groups = { AddGroup.class, EditGroup.class }) + private Long singleContinuousMonth; + + /** + * 发放年龄 + */ + @NotNull(message = "发放年龄不能为空", groups = { AddGroup.class, EditGroup.class }) + private Long issuingAge; + + /** + * 处理频率[1 每次提交发放 2 每月最后一天] + */ + @NotNull(message = "处理频率[1 每次提交发放 2 每月最后一天]不能为空", groups = { AddGroup.class, EditGroup.class }) + private Integer handleFrequency; + + /** + * 处理方式[1 发放需审核 2 仅风险预警] + */ + @NotNull(message = "处理方式[1 发放需审核 2 仅风险预警]不能为空", groups = { AddGroup.class, EditGroup.class }) + private Integer handleWay; + + /** + * 风险原因 + */ + @NotBlank(message = "风险原因不能为空", groups = { AddGroup.class, EditGroup.class }) + private String riskReasons; + + /** + * 是否已删除[0:否,1:是] + */ + @NotNull(message = "是否已删除[0:否,1:是]不能为空", groups = { AddGroup.class, EditGroup.class }) + private Integer isDelete; + + /** + * 创建者user_id + */ + @NotNull(message = "创建者user_id不能为空", groups = { AddGroup.class, EditGroup.class }) + private Long createUserId; + + /** + * 更新者user_id + */ + @NotNull(message = "更新者user_id不能为空", groups = { AddGroup.class, EditGroup.class }) + private Long updateUserId; + + /** + * 备注 + */ + @NotBlank(message = "备注不能为空", groups = { AddGroup.class, EditGroup.class }) + private String remark; + + /** + * 单人单月限额 + */ + @NotNull(message = "单人单月限额不能为空", groups = { AddGroup.class, EditGroup.class }) + private Long singleMonthLimit; + + /** + * 单人月限额 + */ + @NotNull(message = "单人月限额不能为空", groups = { AddGroup.class, EditGroup.class }) + private Long singleMonthLimitAmount; + + +} diff --git a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/earlyWarn/domain/bo/LgEarlyWarnDtlBo.java b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/earlyWarn/domain/bo/LgEarlyWarnDtlBo.java new file mode 100644 index 0000000..7189c20 --- /dev/null +++ b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/earlyWarn/domain/bo/LgEarlyWarnDtlBo.java @@ -0,0 +1,176 @@ +package org.dromara.payment.earlyWarn.domain.bo; + +import org.dromara.common.mybatis.core.domain.BaseEntity; +import org.dromara.common.core.validate.AddGroup; +import org.dromara.common.core.validate.EditGroup; +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import lombok.EqualsAndHashCode; +import jakarta.validation.constraints.*; +import org.dromara.payment.earlyWarn.domain.LgEarlyWarnDtl; + +import java.math.BigDecimal; + +/** + * 风控预警设置明细业务对象 lg_early_warn_dtl + * + * @author LionLi + * @date 2025-06-10 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@AutoMapper(target = LgEarlyWarnDtl.class, reverseConvertGenerate = false) +public class LgEarlyWarnDtlBo extends BaseEntity { + + /** + * 自增主键 + */ + @NotNull(message = "自增主键不能为空", groups = { EditGroup.class }) + private Long id; + + /** + * 风控预警ID + */ + @NotNull(message = "风控预警ID不能为空", groups = { AddGroup.class, EditGroup.class }) + private Long earlyWarnId; + + /** + * 所属税源地 + */ + @NotNull(message = "所属税源地不能为空", groups = { AddGroup.class, EditGroup.class }) + private Long taxSourcesId; + + /** + * 风险类型[1:单人单笔发放金额预警 2:批次内单笔金额占比预警 3:批次内发放金额相似预警 4:单人每年发放金额预警 5:单人连续月份发放金额相似预警 6:企业风险预警 7:发放年龄预警 8:企业高管身份预警] + */ + @NotNull(message = "风险类型[1:单人单笔发放金额预警 2:批次内单笔金额占比预警 3:批次内发放金额相似预警 4:单人每年发放金额预警 5:单人连续月份发放金额相似预警 6:企业风险预警 7:发放年龄预警 8:企业高管身份预警]不能为空", groups = { AddGroup.class, EditGroup.class }) + private Integer riskType; + + /** + * 触发规则[1 单人单笔金额 2 批次占比 3 离散度 4 单人每年金额 5 单人连续月份 6 企业信息异常(经营异常、行政处罚、违法欠税等)7 发放人员年龄 8 发放人员姓名命中企业高管名单] + */ + @NotNull(message = "触发规则[1 单人单笔金额 2 批次占比 3 离散度 4 单人每年金额 5 单人连续月份 6 企业信息异常(经营异常、行政处罚、违法欠税等)7 发放人员年龄 8 发放人员姓名命中企业高管名单]不能为空", groups = { AddGroup.class, EditGroup.class }) + private Integer triggerRule; + + /** + * 单人单笔金额 + */ + @NotNull(message = "单人单笔金额不能为空", groups = { AddGroup.class, EditGroup.class }) + private BigDecimal singleAmount; + + /** + * 批次总笔数 + */ + @NotNull(message = "批次总笔数不能为空", groups = { AddGroup.class, EditGroup.class }) + private BigDecimal totalAmountBatch; + + /** + * 单笔发放金额起始值 + */ + @NotNull(message = "单笔发放金额起始值不能为空", groups = { AddGroup.class, EditGroup.class }) + private BigDecimal singlePaymentAmountStart; + + /** + * 单笔发放金额结束值 + */ + @NotNull(message = "单笔发放金额结束值不能为空", groups = { AddGroup.class, EditGroup.class }) + private BigDecimal singlePaymentAmountEnd; + + /** + * 占比总数 + */ + @NotNull(message = "占比总数不能为空", groups = { AddGroup.class, EditGroup.class }) + private BigDecimal proportionTotal; + + /** + * 离散度 + */ + @NotNull(message = "离散度不能为空", groups = { AddGroup.class, EditGroup.class }) + private BigDecimal dispersion; + + /** + * 单人每年金额 + */ + @NotNull(message = "单人每年金额不能为空", groups = { AddGroup.class, EditGroup.class }) + private BigDecimal singleYearAmount; + + /** + * 累计发放金额波动起始值 + */ + @NotNull(message = "累计发放金额波动起始值不能为空", groups = { AddGroup.class, EditGroup.class }) + private BigDecimal waveAmountStart; + + /** + * 累计发放金额波动结束值 + */ + @NotNull(message = "累计发放金额波动结束值不能为空", groups = { AddGroup.class, EditGroup.class }) + private BigDecimal waveAmountEnd; + + /** + * 累计发放金额 + */ + @NotNull(message = "累计发放金额不能为空", groups = { AddGroup.class, EditGroup.class }) + private BigDecimal waveAmount; + + /** + * 单人连续月份 + */ + @NotNull(message = "单人连续月份不能为空", groups = { AddGroup.class, EditGroup.class }) + private Long singleContinuousMonth; + + /** + * 发放年龄 + */ + @NotNull(message = "发放年龄不能为空", groups = { AddGroup.class, EditGroup.class }) + private Long issuingAge; + + /** + * 处理频率[1 每次提交发放 2 每月最后一天] + */ + @NotNull(message = "处理频率[1 每次提交发放 2 每月最后一天]不能为空", groups = { AddGroup.class, EditGroup.class }) + private Integer handleFrequency; + + /** + * 处理方式[1 发放需审核 2 仅风险预警] + */ + @NotNull(message = "处理方式[1 发放需审核 2 仅风险预警]不能为空", groups = { AddGroup.class, EditGroup.class }) + private Integer handleWay; + + /** + * 风险原因 + */ + @NotBlank(message = "风险原因不能为空", groups = { AddGroup.class, EditGroup.class }) + private String riskReasons; + + /** + * 是否已删除[0:否,1:是] + */ + @NotNull(message = "是否已删除[0:否,1:是]不能为空", groups = { AddGroup.class, EditGroup.class }) + private Integer isDelete; + + /** + * 创建者user_id + */ + @NotNull(message = "创建者user_id不能为空", groups = { AddGroup.class, EditGroup.class }) + private Long createUserId; + + /** + * 更新者user_id + */ + @NotNull(message = "更新者user_id不能为空", groups = { AddGroup.class, EditGroup.class }) + private Long updateUserId; + + /** + * 备注 + */ + @NotBlank(message = "备注不能为空", groups = { AddGroup.class, EditGroup.class }) + private String remark; + + /** + * + */ + @NotNull(message = "不能为空", groups = { AddGroup.class, EditGroup.class }) + private Long singleMonthLimitAmount; + + +} diff --git a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/earlyWarn/domain/convert/LgEarlyWarnDtlVoConvert.java b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/earlyWarn/domain/convert/LgEarlyWarnDtlVoConvert.java new file mode 100644 index 0000000..983b38e --- /dev/null +++ b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/earlyWarn/domain/convert/LgEarlyWarnDtlVoConvert.java @@ -0,0 +1,20 @@ +package org.dromara.payment.earlyWarn.domain.convert; + + +import io.github.linpeilie.BaseMapper; +import org.dromara.payment.api.earlyWarn.domain.RemoteLgEarlyWarnDtlVo; +import org.dromara.payment.earlyWarn.domain.vo.LgEarlyWarnDtlVo; +import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; +import org.mapstruct.ReportingPolicy; + +/** + * 风控预警设置明细Convert接口 + * + * @author LionLi + * @date 2025-06-10 + */ +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, unmappedTargetPolicy = ReportingPolicy.IGNORE) +public interface LgEarlyWarnDtlVoConvert extends BaseMapper { + +} diff --git a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/earlyWarn/domain/convert/LgEarlyWarnVoConvert.java b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/earlyWarn/domain/convert/LgEarlyWarnVoConvert.java new file mode 100644 index 0000000..4cedc95 --- /dev/null +++ b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/earlyWarn/domain/convert/LgEarlyWarnVoConvert.java @@ -0,0 +1,20 @@ +package org.dromara.payment.earlyWarn.domain.convert; + + +import io.github.linpeilie.BaseMapper; +import org.dromara.payment.api.earlyWarn.domain.RemoteLgEarlyWarnVo; +import org.dromara.payment.earlyWarn.domain.vo.LgEarlyWarnVo; +import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; +import org.mapstruct.ReportingPolicy; + +/** + * 风控预警设置Convert接口 + * + * @author LionLi + * @date 2025-06-10 + */ +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, unmappedTargetPolicy = ReportingPolicy.IGNORE) +public interface LgEarlyWarnVoConvert extends BaseMapper { + +} diff --git a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/earlyWarn/domain/vo/LgEarlyWarnDtlVo.java b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/earlyWarn/domain/vo/LgEarlyWarnDtlVo.java new file mode 100644 index 0000000..a331e34 --- /dev/null +++ b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/earlyWarn/domain/vo/LgEarlyWarnDtlVo.java @@ -0,0 +1,184 @@ +package org.dromara.payment.earlyWarn.domain.vo; + +import java.math.BigDecimal; +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import org.dromara.common.excel.annotation.ExcelDictFormat; +import org.dromara.common.excel.convert.ExcelDictConvert; +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import org.dromara.payment.earlyWarn.domain.LgEarlyWarnDtl; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + + + +/** + * 风控预警设置明细视图对象 lg_early_warn_dtl + * + * @author LionLi + * @date 2025-06-10 + */ +@Data +@ExcelIgnoreUnannotated +@AutoMapper(target = LgEarlyWarnDtl.class) +public class LgEarlyWarnDtlVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 自增主键 + */ + @ExcelProperty(value = "自增主键") + private Long id; + + /** + * 风控预警ID + */ + @ExcelProperty(value = "风控预警ID") + private Long earlyWarnId; + + /** + * 所属税源地 + */ + @ExcelProperty(value = "所属税源地") + private Long taxSourcesId; + + /** + * 风险类型[1:单人单笔发放金额预警 2:批次内单笔金额占比预警 3:批次内发放金额相似预警 4:单人每年发放金额预警 5:单人连续月份发放金额相似预警 6:企业风险预警 7:发放年龄预警 8:企业高管身份预警] + */ + @ExcelProperty(value = "风险类型[1:单人单笔发放金额预警 2:批次内单笔金额占比预警 3:批次内发放金额相似预警 4:单人每年发放金额预警 5:单人连续月份发放金额相似预警 6:企业风险预警 7:发放年龄预警 8:企业高管身份预警]") + private Integer riskType; + + /** + * 触发规则[1 单人单笔金额 2 批次占比 3 离散度 4 单人每年金额 5 单人连续月份 6 企业信息异常(经营异常、行政处罚、违法欠税等)7 发放人员年龄 8 发放人员姓名命中企业高管名单] + */ + @ExcelProperty(value = "触发规则[1 单人单笔金额 2 批次占比 3 离散度 4 单人每年金额 5 单人连续月份 6 企业信息异常", converter = ExcelDictConvert.class) + @ExcelDictFormat(readConverterExp = "经=营异常、行政处罚、违法欠税等") + private Integer triggerRule; + + /** + * 单人单笔金额 + */ + @ExcelProperty(value = "单人单笔金额") + private BigDecimal singleAmount; + + /** + * 批次总笔数 + */ + @ExcelProperty(value = "批次总笔数") + private BigDecimal totalAmountBatch; + + /** + * 单笔发放金额起始值 + */ + @ExcelProperty(value = "单笔发放金额起始值") + private BigDecimal singlePaymentAmountStart; + + /** + * 单笔发放金额结束值 + */ + @ExcelProperty(value = "单笔发放金额结束值") + private BigDecimal singlePaymentAmountEnd; + + /** + * 占比总数 + */ + @ExcelProperty(value = "占比总数") + private BigDecimal proportionTotal; + + /** + * 离散度 + */ + @ExcelProperty(value = "离散度") + private BigDecimal dispersion; + + /** + * 单人每年金额 + */ + @ExcelProperty(value = "单人每年金额") + private BigDecimal singleYearAmount; + + /** + * 累计发放金额波动起始值 + */ + @ExcelProperty(value = "累计发放金额波动起始值") + private BigDecimal waveAmountStart; + + /** + * 累计发放金额波动结束值 + */ + @ExcelProperty(value = "累计发放金额波动结束值") + private BigDecimal waveAmountEnd; + + /** + * 累计发放金额 + */ + @ExcelProperty(value = "累计发放金额") + private BigDecimal waveAmount; + + /** + * 单人连续月份 + */ + @ExcelProperty(value = "单人连续月份") + private Long singleContinuousMonth; + + /** + * 发放年龄 + */ + @ExcelProperty(value = "发放年龄") + private Long issuingAge; + + /** + * 处理频率[1 每次提交发放 2 每月最后一天] + */ + @ExcelProperty(value = "处理频率[1 每次提交发放 2 每月最后一天]") + private Integer handleFrequency; + + /** + * 处理方式[1 发放需审核 2 仅风险预警] + */ + @ExcelProperty(value = "处理方式[1 发放需审核 2 仅风险预警]") + private Integer handleWay; + + /** + * 风险原因 + */ + @ExcelProperty(value = "风险原因") + private String riskReasons; + + /** + * 是否已删除[0:否,1:是] + */ + @ExcelProperty(value = "是否已删除[0:否,1:是]") + private Integer isDelete; + + /** + * 创建者user_id + */ + @ExcelProperty(value = "创建者user_id") + private Long createUserId; + + /** + * 更新者user_id + */ + @ExcelProperty(value = "更新者user_id") + private Long updateUserId; + + /** + * 备注 + */ + @ExcelProperty(value = "备注") + private String remark; + + /** + * + */ + @ExcelProperty(value = "") + private Long singleMonthLimitAmount; + + +} diff --git a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/earlyWarn/domain/vo/LgEarlyWarnVo.java b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/earlyWarn/domain/vo/LgEarlyWarnVo.java new file mode 100644 index 0000000..9fe7f16 --- /dev/null +++ b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/earlyWarn/domain/vo/LgEarlyWarnVo.java @@ -0,0 +1,177 @@ +package org.dromara.payment.earlyWarn.domain.vo; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import org.dromara.common.excel.annotation.ExcelDictFormat; +import org.dromara.common.excel.convert.ExcelDictConvert; +import org.dromara.payment.earlyWarn.domain.LgEarlyWarn; + + +import java.io.Serial; +import java.io.Serializable; +import java.math.BigDecimal; + + +/** + * 风控预警设置视图对象 lg_early_warn + * + * @author LionLi + * @date 2025-06-10 + */ +@Data +@ExcelIgnoreUnannotated +@AutoMapper(target = LgEarlyWarn.class) +public class LgEarlyWarnVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 自增主键 + */ + @ExcelProperty(value = "自增主键") + private Long id; + + /** + * 风险类型[1:单人单笔发放金额预警 2:批次内单笔金额占比预警 3:批次内发放金额相似预警 4:单人每年发放金额预警 5:单人连续月份发放金额相似预警 6:企业风险预警 7:发放年龄预警 8:企业高管身份预警] + */ + @ExcelProperty(value = "风险类型[1:单人单笔发放金额预警 2:批次内单笔金额占比预警 3:批次内发放金额相似预警 4:单人每年发放金额预警 5:单人连续月份发放金额相似预警 6:企业风险预警 7:发放年龄预警 8:企业高管身份预警]") + private Integer riskType; + + /** + * 触发规则[1 单人单笔金额 2 批次占比 3 离散度 4 单人每年金额 5 单人连续月份 6 企业信息异常(经营异常、行政处罚、违法欠税等)7 发放人员年龄 8 发放人员姓名命中企业高管名单] + */ + @ExcelProperty(value = "触发规则[1 单人单笔金额 2 批次占比 3 离散度 4 单人每年金额 5 单人连续月份 6 企业信息异常", converter = ExcelDictConvert.class) + @ExcelDictFormat(readConverterExp = "经=营异常、行政处罚、违法欠税等") + private Integer triggerRule; + + /** + * 单人单笔金额 + */ + @ExcelProperty(value = "单人单笔金额") + private BigDecimal singleAmount; + + /** + * 批次总笔数 + */ + @ExcelProperty(value = "批次总笔数") + private BigDecimal totalAmountBatch; + + /** + * 单笔发放金额起始值 + */ + @ExcelProperty(value = "单笔发放金额起始值") + private BigDecimal singlePaymentAmountStart; + + /** + * 单笔发放金额结束值 + */ + @ExcelProperty(value = "单笔发放金额结束值") + private BigDecimal singlePaymentAmountEnd; + + /** + * 占比总数 + */ + @ExcelProperty(value = "占比总数") + private BigDecimal proportionTotal; + + /** + * 离散度 + */ + @ExcelProperty(value = "离散度") + private BigDecimal dispersion; + + /** + * 单人每年金额 + */ + @ExcelProperty(value = "单人每年金额") + private BigDecimal singleYearAmount; + + /** + * 累计发放金额波动起始值 + */ + @ExcelProperty(value = "累计发放金额波动起始值") + private BigDecimal waveAmountStart; + + /** + * 累计发放金额波动结束值 + */ + @ExcelProperty(value = "累计发放金额波动结束值") + private BigDecimal waveAmountEnd; + + /** + * 累计发放金额 + */ + @ExcelProperty(value = "累计发放金额") + private BigDecimal waveAmount; + + /** + * 单人连续月份 + */ + @ExcelProperty(value = "单人连续月份") + private Long singleContinuousMonth; + + /** + * 发放年龄 + */ + @ExcelProperty(value = "发放年龄") + private Long issuingAge; + + /** + * 处理频率[1 每次提交发放 2 每月最后一天] + */ + @ExcelProperty(value = "处理频率[1 每次提交发放 2 每月最后一天]") + private Integer handleFrequency; + + /** + * 处理方式[1 发放需审核 2 仅风险预警] + */ + @ExcelProperty(value = "处理方式[1 发放需审核 2 仅风险预警]") + private Integer handleWay; + + /** + * 风险原因 + */ + @ExcelProperty(value = "风险原因") + private String riskReasons; + + /** + * 是否已删除[0:否,1:是] + */ + @ExcelProperty(value = "是否已删除[0:否,1:是]") + private Integer isDelete; + + /** + * 创建者user_id + */ + @ExcelProperty(value = "创建者user_id") + private Long createUserId; + + /** + * 更新者user_id + */ + @ExcelProperty(value = "更新者user_id") + private Long updateUserId; + + /** + * 备注 + */ + @ExcelProperty(value = "备注") + private String remark; + + /** + * 单人单月限额 + */ + @ExcelProperty(value = "单人单月限额") + private Long singleMonthLimit; + + /** + * 单人月限额 + */ + @ExcelProperty(value = "单人月限额") + private Long singleMonthLimitAmount; + + +} diff --git a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/earlyWarn/mapper/LgEarlyWarnDtlMapper.java b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/earlyWarn/mapper/LgEarlyWarnDtlMapper.java new file mode 100644 index 0000000..f7fb977 --- /dev/null +++ b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/earlyWarn/mapper/LgEarlyWarnDtlMapper.java @@ -0,0 +1,16 @@ +package org.dromara.payment.earlyWarn.mapper; + + +import org.dromara.common.mybatis.core.mapper.BaseMapperPlus; +import org.dromara.payment.earlyWarn.domain.LgEarlyWarnDtl; +import org.dromara.payment.earlyWarn.domain.vo.LgEarlyWarnDtlVo; + +/** + * 风控预警设置明细Mapper接口 + * + * @author LionLi + * @date 2025-06-10 + */ +public interface LgEarlyWarnDtlMapper extends BaseMapperPlus { + +} diff --git a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/earlyWarn/mapper/LgEarlyWarnMapper.java b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/earlyWarn/mapper/LgEarlyWarnMapper.java new file mode 100644 index 0000000..de7bf3b --- /dev/null +++ b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/earlyWarn/mapper/LgEarlyWarnMapper.java @@ -0,0 +1,16 @@ +package org.dromara.payment.earlyWarn.mapper; + + +import org.dromara.common.mybatis.core.mapper.BaseMapperPlus; +import org.dromara.payment.earlyWarn.domain.LgEarlyWarn; +import org.dromara.payment.earlyWarn.domain.vo.LgEarlyWarnVo; + +/** + * 风控预警设置Mapper接口 + * + * @author LionLi + * @date 2025-06-10 + */ +public interface LgEarlyWarnMapper extends BaseMapperPlus { + +} diff --git a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/earlyWarn/service/ILgEarlyWarnDtlService.java b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/earlyWarn/service/ILgEarlyWarnDtlService.java new file mode 100644 index 0000000..2cbd7cb --- /dev/null +++ b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/earlyWarn/service/ILgEarlyWarnDtlService.java @@ -0,0 +1,50 @@ +package org.dromara.payment.earlyWarn.service; + + +import org.dromara.common.mybatis.core.page.PageQuery; +import org.dromara.common.mybatis.core.page.TableDataInfo; +import org.dromara.payment.earlyWarn.domain.bo.LgEarlyWarnDtlBo; +import org.dromara.payment.earlyWarn.domain.vo.LgEarlyWarnDtlVo; + +import java.util.Collection; +import java.util.List; + + +/** + * 风控预警设置明细Service接口 + * + * @author LionLi + * @date 2025-06-10 + */ +public interface ILgEarlyWarnDtlService { + + /** + * 查询风控预警设置明细 + */ + LgEarlyWarnDtlVo queryById(Long id); + + /** + * 查询风控预警设置明细列表 + */ + TableDataInfo queryPageList(LgEarlyWarnDtlBo bo, PageQuery pageQuery); + + /** + * 查询风控预警设置明细列表 + */ + List queryList(LgEarlyWarnDtlBo bo); + + /** + * 新增风控预警设置明细 + */ + Boolean insertByBo(LgEarlyWarnDtlBo bo); + + /** + * 修改风控预警设置明细 + */ + Boolean updateByBo(LgEarlyWarnDtlBo bo); + + /** + * 校验并批量删除风控预警设置明细信息 + */ + Boolean deleteWithValidByIds(Collection ids, Boolean isValid); +} diff --git a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/earlyWarn/service/ILgEarlyWarnService.java b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/earlyWarn/service/ILgEarlyWarnService.java new file mode 100644 index 0000000..b78e193 --- /dev/null +++ b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/earlyWarn/service/ILgEarlyWarnService.java @@ -0,0 +1,49 @@ +package org.dromara.payment.earlyWarn.service; + +import org.dromara.common.mybatis.core.page.PageQuery; +import org.dromara.common.mybatis.core.page.TableDataInfo; +import org.dromara.payment.earlyWarn.domain.bo.LgEarlyWarnBo; +import org.dromara.payment.earlyWarn.domain.vo.LgEarlyWarnVo; + + +import java.util.Collection; +import java.util.List; + +/** + * 风控预警设置Service接口 + * + * @author LionLi + * @date 2025-06-10 + */ +public interface ILgEarlyWarnService { + + /** + * 查询风控预警设置 + */ + LgEarlyWarnVo queryById(Long id); + + /** + * 查询风控预警设置列表 + */ + TableDataInfo queryPageList(LgEarlyWarnBo bo, PageQuery pageQuery); + + /** + * 查询风控预警设置列表 + */ + List queryList(LgEarlyWarnBo bo); + + /** + * 新增风控预警设置 + */ + Boolean insertByBo(LgEarlyWarnBo bo); + + /** + * 修改风控预警设置 + */ + Boolean updateByBo(LgEarlyWarnBo bo); + + /** + * 校验并批量删除风控预警设置信息 + */ + Boolean deleteWithValidByIds(Collection ids, Boolean isValid); +} diff --git a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/earlyWarn/service/impl/LgEarlyWarnDtlServiceImpl.java b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/earlyWarn/service/impl/LgEarlyWarnDtlServiceImpl.java new file mode 100644 index 0000000..f251fe9 --- /dev/null +++ b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/earlyWarn/service/impl/LgEarlyWarnDtlServiceImpl.java @@ -0,0 +1,130 @@ +package org.dromara.payment.earlyWarn.service.impl; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import org.dromara.common.core.utils.MapstructUtils; +import org.dromara.common.core.utils.StringUtils; +import org.dromara.common.mybatis.core.page.TableDataInfo; +import org.dromara.common.mybatis.core.page.PageQuery; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import lombok.RequiredArgsConstructor; +import org.dromara.payment.earlyWarn.domain.LgEarlyWarnDtl; +import org.dromara.payment.earlyWarn.domain.bo.LgEarlyWarnDtlBo; +import org.dromara.payment.earlyWarn.domain.vo.LgEarlyWarnDtlVo; +import org.dromara.payment.earlyWarn.mapper.LgEarlyWarnDtlMapper; +import org.dromara.payment.earlyWarn.service.ILgEarlyWarnDtlService; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Map; +import java.util.Collection; + +/** + * 风控预警设置明细Service业务层处理 + * + * @author LionLi + * @date 2025-06-10 + */ +@RequiredArgsConstructor +@Service +public class LgEarlyWarnDtlServiceImpl implements ILgEarlyWarnDtlService { + + private final LgEarlyWarnDtlMapper baseMapper; + + /** + * 查询风控预警设置明细 + */ + @Override + public LgEarlyWarnDtlVo queryById(Long id){ + return baseMapper.selectVoById(id); + } + + /** + * 查询风控预警设置明细列表 + */ + @Override + public TableDataInfo queryPageList(LgEarlyWarnDtlBo bo, PageQuery pageQuery) { + QueryWrapper lqw = buildQueryWrapper(bo); + Page result = baseMapper.selectVoPage(pageQuery.build(), lqw); + return TableDataInfo.build(result); + } + + /** + * 查询风控预警设置明细列表 + */ + @Override + public List queryList(LgEarlyWarnDtlBo bo) { + QueryWrapper lqw = buildQueryWrapper(bo); + return baseMapper.selectVoList(lqw); + } + + private QueryWrapper buildQueryWrapper(LgEarlyWarnDtlBo bo) { + Map params = bo.getParams(); + QueryWrapper lqw = new QueryWrapper<>(); + lqw.eq(bo.getEarlyWarnId() != null, "early_warn_id", bo.getEarlyWarnId()); + lqw.eq(bo.getTaxSourcesId() != null, "tax_sources_id", bo.getTaxSourcesId()); + lqw.eq(bo.getRiskType() != null, "risk_type", bo.getRiskType()); + lqw.eq(bo.getTriggerRule() != null, "trigger_rule", bo.getTriggerRule()); + lqw.eq(bo.getSingleAmount() != null, "single_amount", bo.getSingleAmount()); + lqw.eq(bo.getTotalAmountBatch() != null, "total_amount_batch", bo.getTotalAmountBatch()); + lqw.eq(bo.getSinglePaymentAmountStart() != null, "single_payment_amount_start", bo.getSinglePaymentAmountStart()); + lqw.eq(bo.getSinglePaymentAmountEnd() != null, "single_payment_amount_end", bo.getSinglePaymentAmountEnd()); + lqw.eq(bo.getProportionTotal() != null, "proportion_total", bo.getProportionTotal()); + lqw.eq(bo.getDispersion() != null, "dispersion", bo.getDispersion()); + lqw.eq(bo.getSingleYearAmount() != null, "single_year_amount", bo.getSingleYearAmount()); + lqw.eq(bo.getWaveAmountStart() != null, "wave_amount_start", bo.getWaveAmountStart()); + lqw.eq(bo.getWaveAmountEnd() != null, "wave_amount_end", bo.getWaveAmountEnd()); + lqw.eq(bo.getWaveAmount() != null, "wave_amount", bo.getWaveAmount()); + lqw.eq(bo.getSingleContinuousMonth() != null, "single_continuous_month", bo.getSingleContinuousMonth()); + lqw.eq(bo.getIssuingAge() != null, "issuing_age", bo.getIssuingAge()); + lqw.eq(bo.getHandleFrequency() != null, "handle_frequency", bo.getHandleFrequency()); + lqw.eq(bo.getHandleWay() != null, "handle_way", bo.getHandleWay()); + lqw.eq(StringUtils.isNotBlank(bo.getRiskReasons()), "risk_reasons", bo.getRiskReasons()); + lqw.eq(bo.getIsDelete() != null, "is_delete", bo.getIsDelete()); + lqw.eq(bo.getCreateUserId() != null, "create_user_id", bo.getCreateUserId()); + lqw.eq(bo.getUpdateUserId() != null, "update_user_id", bo.getUpdateUserId()); + lqw.eq(bo.getSingleMonthLimitAmount() != null, "single_month_limit_amount", bo.getSingleMonthLimitAmount()); + return lqw; + } + + /** + * 新增风控预警设置明细 + */ + @Override + public Boolean insertByBo(LgEarlyWarnDtlBo bo) { + LgEarlyWarnDtl add = MapstructUtils.convert(bo, LgEarlyWarnDtl.class); + validEntityBeforeSave(add); + boolean flag = baseMapper.insert(add) > 0; + if (flag) { + bo.setId(add.getId()); + } + return flag; + } + + /** + * 修改风控预警设置明细 + */ + @Override + public Boolean updateByBo(LgEarlyWarnDtlBo bo) { + LgEarlyWarnDtl update = MapstructUtils.convert(bo, LgEarlyWarnDtl.class); + validEntityBeforeSave(update); + return baseMapper.updateById(update) > 0; + } + + /** + * 保存前的数据校验 + */ + private void validEntityBeforeSave(LgEarlyWarnDtl entity){ + //TODO 做一些数据校验,如唯一约束 + } + + /** + * 批量删除风控预警设置明细 + */ + @Override + public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { + if(isValid){ + //TODO 做一些业务上的校验,判断是否需要校验 + } + return baseMapper.deleteBatchIds(ids) > 0; + } +} diff --git a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/earlyWarn/service/impl/LgEarlyWarnServiceImpl.java b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/earlyWarn/service/impl/LgEarlyWarnServiceImpl.java new file mode 100644 index 0000000..0143735 --- /dev/null +++ b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/earlyWarn/service/impl/LgEarlyWarnServiceImpl.java @@ -0,0 +1,130 @@ +package org.dromara.payment.earlyWarn.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import lombok.RequiredArgsConstructor; +import org.dromara.common.core.utils.MapstructUtils; +import org.dromara.common.core.utils.StringUtils; +import org.dromara.common.mybatis.core.page.PageQuery; +import org.dromara.common.mybatis.core.page.TableDataInfo; + +import org.dromara.payment.earlyWarn.domain.LgEarlyWarn; +import org.dromara.payment.earlyWarn.domain.bo.LgEarlyWarnBo; +import org.dromara.payment.earlyWarn.domain.vo.LgEarlyWarnVo; +import org.dromara.payment.earlyWarn.mapper.LgEarlyWarnMapper; +import org.dromara.payment.earlyWarn.service.ILgEarlyWarnService; +import org.springframework.stereotype.Service; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/** + * 风控预警设置Service业务层处理 + * + * @author LionLi + * @date 2025-06-10 + */ +@RequiredArgsConstructor +@Service +public class LgEarlyWarnServiceImpl implements ILgEarlyWarnService { + + private final LgEarlyWarnMapper baseMapper; + + /** + * 查询风控预警设置 + */ + @Override + public LgEarlyWarnVo queryById(Long id){ + return baseMapper.selectVoById(id); + } + + /** + * 查询风控预警设置列表 + */ + @Override + public TableDataInfo queryPageList(LgEarlyWarnBo bo, PageQuery pageQuery) { + QueryWrapper lqw = buildQueryWrapper(bo); + Page result = baseMapper.selectVoPage(pageQuery.build(), lqw); + return TableDataInfo.build(result); + } + + /** + * 查询风控预警设置列表 + */ + @Override + public List queryList(LgEarlyWarnBo bo) { + QueryWrapper lqw = buildQueryWrapper(bo); + return baseMapper.selectVoList(lqw); + } + + private QueryWrapper buildQueryWrapper(LgEarlyWarnBo bo) { + Map params = bo.getParams(); + QueryWrapper lqw = new QueryWrapper<>(); + lqw.eq(bo.getRiskType() != null, "risk_type", bo.getRiskType()); + lqw.eq(bo.getTriggerRule() != null, "trigger_rule", bo.getTriggerRule()); + lqw.eq(bo.getSingleAmount() != null, "single_amount", bo.getSingleAmount()); + lqw.eq(bo.getTotalAmountBatch() != null, "total_amount_batch", bo.getTotalAmountBatch()); + lqw.eq(bo.getSinglePaymentAmountStart() != null, "single_payment_amount_start", bo.getSinglePaymentAmountStart()); + lqw.eq(bo.getSinglePaymentAmountEnd() != null, "single_payment_amount_end", bo.getSinglePaymentAmountEnd()); + lqw.eq(bo.getProportionTotal() != null, "proportion_total", bo.getProportionTotal()); + lqw.eq(bo.getDispersion() != null, "dispersion", bo.getDispersion()); + lqw.eq(bo.getSingleYearAmount() != null, "single_year_amount", bo.getSingleYearAmount()); + lqw.eq(bo.getWaveAmountStart() != null, "wave_amount_start", bo.getWaveAmountStart()); + lqw.eq(bo.getWaveAmountEnd() != null, "wave_amount_end", bo.getWaveAmountEnd()); + lqw.eq(bo.getWaveAmount() != null, "wave_amount", bo.getWaveAmount()); + lqw.eq(bo.getSingleContinuousMonth() != null, "single_continuous_month", bo.getSingleContinuousMonth()); + lqw.eq(bo.getIssuingAge() != null, "issuing_age", bo.getIssuingAge()); + lqw.eq(bo.getHandleFrequency() != null, "handle_frequency", bo.getHandleFrequency()); + lqw.eq(bo.getHandleWay() != null, "handle_way", bo.getHandleWay()); + lqw.eq(StringUtils.isNotBlank(bo.getRiskReasons()), "risk_reasons", bo.getRiskReasons()); + lqw.eq(bo.getIsDelete() != null, "is_delete", bo.getIsDelete()); + lqw.eq(bo.getCreateUserId() != null, "create_user_id", bo.getCreateUserId()); + lqw.eq(bo.getUpdateUserId() != null, "update_user_id", bo.getUpdateUserId()); + lqw.eq(bo.getSingleMonthLimit() != null, "single_month_limit", bo.getSingleMonthLimit()); + lqw.eq(bo.getSingleMonthLimitAmount() != null, "single_month_limit_amount", bo.getSingleMonthLimitAmount()); + return lqw; + } + + /** + * 新增风控预警设置 + */ + @Override + public Boolean insertByBo(LgEarlyWarnBo bo) { + LgEarlyWarn add = MapstructUtils.convert(bo, LgEarlyWarn.class); + validEntityBeforeSave(add); + boolean flag = baseMapper.insert(add) > 0; + if (flag) { + bo.setId(add.getId()); + } + return flag; + } + + /** + * 修改风控预警设置 + */ + @Override + public Boolean updateByBo(LgEarlyWarnBo bo) { + LgEarlyWarn update = MapstructUtils.convert(bo, LgEarlyWarn.class); + validEntityBeforeSave(update); + return baseMapper.updateById(update) > 0; + } + + /** + * 保存前的数据校验 + */ + private void validEntityBeforeSave(LgEarlyWarn entity){ + //TODO 做一些数据校验,如唯一约束 + } + + /** + * 批量删除风控预警设置 + */ + @Override + public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { + if(isValid){ + //TODO 做一些业务上的校验,判断是否需要校验 + } + return baseMapper.deleteBatchIds(ids) > 0; + } +} diff --git a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/exception/CustomException.java b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/exception/CustomException.java new file mode 100644 index 0000000..b92a0c8 --- /dev/null +++ b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/exception/CustomException.java @@ -0,0 +1,31 @@ +package org.dromara.payment.exception; + +public class CustomException extends RuntimeException +{ + private static final long serialVersionUID = 1L; + private Integer code; + private String message; + + public CustomException(final String message) { + this.message = message; + } + + public CustomException(final String message, final Integer code) { + this.message = message; + this.code = code; + } + + public CustomException(final String message, final Throwable e) { + super(message, e); + this.message = message; + } + + @Override + public String getMessage() { + return this.message; + } + + public Integer getCode() { + return this.code; + } +} diff --git a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/invoice/domain/bo/MerInvoiceBo.java b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/invoice/domain/bo/MerInvoiceBo.java index 8ecb797..0ca9ee4 100644 --- a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/invoice/domain/bo/MerInvoiceBo.java +++ b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/invoice/domain/bo/MerInvoiceBo.java @@ -260,4 +260,9 @@ public class MerInvoiceBo extends BaseEntity { * 发票相关的代发批次列表 */ private List bills; + + + private String categoryOne; + + private String categoryTwo; } diff --git a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/invoice/domain/convert/MerInvoiceAddressVoConvert.java b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/invoice/domain/convert/MerInvoiceAddressVoConvert.java index e3ecabf..81b23d0 100644 --- a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/invoice/domain/convert/MerInvoiceAddressVoConvert.java +++ b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/invoice/domain/convert/MerInvoiceAddressVoConvert.java @@ -1,7 +1,8 @@ package org.dromara.payment.invoice.domain.convert; + import io.github.linpeilie.BaseMapper; -import org.dromara.payment.invoice.api.domain.RemoteMerInvoiceAddressVo; +import org.dromara.payment.api.invoice.domain.RemoteMerInvoiceAddressVo; import org.dromara.payment.invoice.domain.vo.MerInvoiceAddressVo; import org.mapstruct.Mapper; import org.mapstruct.MappingConstants; diff --git a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/invoice/domain/convert/MerInvoiceBillVoConvert.java b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/invoice/domain/convert/MerInvoiceBillVoConvert.java index b26c488..525a847 100644 --- a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/invoice/domain/convert/MerInvoiceBillVoConvert.java +++ b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/invoice/domain/convert/MerInvoiceBillVoConvert.java @@ -1,7 +1,8 @@ package org.dromara.payment.invoice.domain.convert; + import io.github.linpeilie.BaseMapper; -import org.dromara.payment.invoice.api.domain.RemoteMerInvoiceBillVo; +import org.dromara.payment.api.invoice.domain.RemoteMerInvoiceBillVo; import org.dromara.payment.invoice.domain.vo.MerInvoiceBillVo; import org.mapstruct.Mapper; import org.mapstruct.MappingConstants; diff --git a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/invoice/domain/convert/MerInvoiceDetailVoConvert.java b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/invoice/domain/convert/MerInvoiceDetailVoConvert.java index 9ef1b6a..94239b3 100644 --- a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/invoice/domain/convert/MerInvoiceDetailVoConvert.java +++ b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/invoice/domain/convert/MerInvoiceDetailVoConvert.java @@ -1,7 +1,7 @@ package org.dromara.payment.invoice.domain.convert; import io.github.linpeilie.BaseMapper; -import org.dromara.payment.invoice.api.domain.RemoteMerInvoiceDetailVo; +import org.dromara.payment.api.invoice.domain.RemoteMerInvoiceDetailVo; import org.dromara.payment.invoice.domain.vo.MerInvoiceDetailVo; import org.mapstruct.Mapper; import org.mapstruct.MappingConstants; diff --git a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/invoice/domain/convert/MerInvoiceTypeVoConvert.java b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/invoice/domain/convert/MerInvoiceTypeVoConvert.java index 07ae382..66fa3e8 100644 --- a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/invoice/domain/convert/MerInvoiceTypeVoConvert.java +++ b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/invoice/domain/convert/MerInvoiceTypeVoConvert.java @@ -1,7 +1,7 @@ package org.dromara.payment.invoice.domain.convert; import io.github.linpeilie.BaseMapper; -import org.dromara.payment.invoice.api.domain.RemoteMerInvoiceTypeVo; +import org.dromara.payment.api.invoice.domain.RemoteMerInvoiceTypeVo; import org.dromara.payment.invoice.domain.vo.MerInvoiceTypeVo; import org.mapstruct.Mapper; import org.mapstruct.MappingConstants; diff --git a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/invoice/domain/convert/MerInvoiceVoConvert.java b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/invoice/domain/convert/MerInvoiceVoConvert.java index 6a5fa1e..1cd4f5d 100644 --- a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/invoice/domain/convert/MerInvoiceVoConvert.java +++ b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/invoice/domain/convert/MerInvoiceVoConvert.java @@ -1,7 +1,7 @@ package org.dromara.payment.invoice.domain.convert; import io.github.linpeilie.BaseMapper; -import org.dromara.payment.invoice.api.domain.RemoteMerInvoiceVo; +import org.dromara.payment.api.invoice.domain.RemoteMerInvoiceVo; import org.dromara.payment.invoice.domain.vo.MerInvoiceVo; import org.mapstruct.Mapper; import org.mapstruct.MappingConstants; diff --git a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/invoice/domain/vo/MerInvoiceVo.java b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/invoice/domain/vo/MerInvoiceVo.java index dd3480f..9fab5b9 100644 --- a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/invoice/domain/vo/MerInvoiceVo.java +++ b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/invoice/domain/vo/MerInvoiceVo.java @@ -310,4 +310,9 @@ public class MerInvoiceVo implements Serializable { * 发票列表 */ private List details; + + + private String categoryOne; + + private String categoryTwo; } diff --git a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/invoice/service/impl/MerInvoiceServiceImpl.java b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/invoice/service/impl/MerInvoiceServiceImpl.java index ffd732b..01e92a2 100644 --- a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/invoice/service/impl/MerInvoiceServiceImpl.java +++ b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/invoice/service/impl/MerInvoiceServiceImpl.java @@ -46,6 +46,8 @@ import org.dromara.payment.sys.mapper.SysSydAccountMapper; import org.dromara.payment.sys.mapper.SysSydFlowsMapper; import org.dromara.payment.sys.mapper.SysSydMapper; import org.dromara.payment.sys.service.ISysSydAccountService; +import org.dromara.payment.thirdPlatform.base.domain.SysThirdPlatform; +import org.dromara.payment.thirdPlatform.base.mapper.SysThirdPlatformMapper; import org.dromara.payment.thirdPlatform.xinyujian.service.IXyjService; import org.springframework.stereotype.Service; import org.dromara.payment.invoice.domain.bo.MerInvoiceBo; @@ -95,6 +97,8 @@ public class MerInvoiceServiceImpl implements IMerInvoiceService { private final IXyjService xyjService; private final BusMerchantMapper merchantMapper; + + private final SysThirdPlatformMapper sysThirdPlatformMapper; /** * 查询商户发票记录 */ @@ -244,7 +248,10 @@ public class MerInvoiceServiceImpl implements IMerInvoiceService { BusMerchant busMerchant = merchantMapper.selectById(add.getMerId()); boolean flag; - if(false){ + SysThirdPlatform sysThirdPlatform = sysThirdPlatformMapper.selectThirdAppInfo(sysSyd.getThreeId()); + + + if(Objects.nonNull(sysThirdPlatform) && sysThirdPlatform.getPlatformName().equals("薪遇见")){ flag = xyjService.applyInvoice(busMerchant,sysSyd,add); }else{ flag = baseMapper.insert(add) > 0; diff --git a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/merchant/controller/BusMerchantProductController.java b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/merchant/controller/BusMerchantProductController.java index b99faaf..44d6369 100644 --- a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/merchant/controller/BusMerchantProductController.java +++ b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/merchant/controller/BusMerchantProductController.java @@ -81,7 +81,7 @@ public class BusMerchantProductController extends BaseController { /** * 新增商户产品表 */ -// @SaCheckPermission("payment:merchantProduct:add") + @SaCheckPermission("payment:merchantProduct:add") @Log(title = "商户产品表", businessType = BusinessType.INSERT) @RepeatSubmit() @PostMapping() diff --git a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/merchant/domain/BusMerchant.java b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/merchant/domain/BusMerchant.java index 6bbf369..be82702 100644 --- a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/merchant/domain/BusMerchant.java +++ b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/merchant/domain/BusMerchant.java @@ -357,7 +357,15 @@ public class BusMerchant extends BaseEntity { * 是否验证资金账户(0:否 1:是) */ private Integer isValiAcc; + /** + * 注册资本 + */ + private String registeredAssets; + /** + * 注册时间 + */ + private Date regdate; private String agentNo; diff --git a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/merchant/domain/vo/BusMerchantProductVo.java b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/merchant/domain/vo/BusMerchantProductVo.java index fb74387..8012621 100644 --- a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/merchant/domain/vo/BusMerchantProductVo.java +++ b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/merchant/domain/vo/BusMerchantProductVo.java @@ -191,5 +191,7 @@ public class BusMerchantProductVo implements Serializable { private BigDecimal mdyServiceCharge; + + private String unId; } diff --git a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/merchant/service/impl/BusMerchantProductServiceImpl.java b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/merchant/service/impl/BusMerchantProductServiceImpl.java index dfb4a85..836b437 100644 --- a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/merchant/service/impl/BusMerchantProductServiceImpl.java +++ b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/merchant/service/impl/BusMerchantProductServiceImpl.java @@ -4,6 +4,8 @@ import cn.hutool.core.util.IdUtil; import cn.hutool.json.JSONUtil; import com.alibaba.fastjson.JSONObject; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.ObjectUtils; +import org.dromara.common.core.enums.BusRole; import org.dromara.common.core.service.BaseService; import org.dromara.common.core.utils.MapstructUtils; import org.dromara.common.core.utils.StringUtils; @@ -20,7 +22,13 @@ import org.dromara.payment.channel.domain.vo.BusChannelProductVo; import org.dromara.payment.channel.mapper.BusChannelProductMapper; import org.dromara.payment.merchant.domain.BusMerchant; import org.dromara.payment.merchant.mapper.BusMerchantMapper; +import org.dromara.payment.sys.domain.SysSyd; +import org.dromara.payment.sys.mapper.SysSydMapper; +import org.dromara.payment.thirdPlatform.base.domain.SysThirdPlatform; +import org.dromara.payment.thirdPlatform.base.mapper.SysThirdPlatformMapper; import org.dromara.payment.thirdPlatform.xinyujian.service.IXyjService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import org.dromara.payment.merchant.domain.bo.BusMerchantProductBo; import org.dromara.payment.merchant.domain.vo.BusMerchantProductVo; @@ -41,6 +49,7 @@ import java.util.*; @Service public class BusMerchantProductServiceImpl extends BaseService implements IBusMerchantProductService { + private static final Logger log = LoggerFactory.getLogger(BusMerchantProductServiceImpl.class); private final BusMerchantProductMapper baseMapper; private final BusChannelProductMapper busChannelProductMapper; @@ -49,6 +58,10 @@ public class BusMerchantProductServiceImpl extends BaseService implements IBusMe private final IXyjService xyjService; + private final SysSydMapper sydMapper; + + private final SysThirdPlatformMapper thirdPlatformMapper; + /** * 查询商户产品表 */ @@ -71,12 +84,16 @@ public class BusMerchantProductServiceImpl extends BaseService implements IBusMe this.invalidationParamsException("企业ID不能为空"); } + QueryWrapper lqw = Wrappers.query(); lqw.eq("t1.bus_type",bo.getBusType()); lqw.eq("t.mer_id",bo.getMerId()); lqw.eq("t3.pro_status",1); lqw.eq("t1.pro_status",1); - lqw.eq("t1.channel_id",LoginHelper.getBusId()); + if(!LoginHelper.isSuperAdmin()){ + lqw.eq("t1.channel_id",LoginHelper.getBusId()); + } + Page result = baseMapper.selBusMerProByTypeList(pageQuery.build(), lqw); @@ -163,7 +180,11 @@ public class BusMerchantProductServiceImpl extends BaseService implements IBusMe List voList = JSONUtil.toList(boListStr, BusMerchantProductBo.class); List insertList = new ArrayList(); for(BusMerchantProductBo vo :voList){ - if (vo.getProNo().equals("P9490811577")){ + SysSyd sysSyd = sydMapper.selectSysSydById(vo.getSydId()); + log.info("sydId:"+sysSyd.getId()+"!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + SysThirdPlatform sysThirdPlatform = thirdPlatformMapper.selectThirdAppInfo(sysSyd.getId()); + log.info("sysThirdPlatform:"+sysThirdPlatform.getPlatformName()); + if (ObjectUtils.isNotEmpty(sysThirdPlatform)&&sysThirdPlatform.getPlatformName().equals("薪遇见")){ BusMerchant busMerchant = busMerchantMapper.selectById(mer_id); xyjService.addMerchant(busMerchant,vo.getSydId()); } @@ -184,7 +205,7 @@ public class BusMerchantProductServiceImpl extends BaseService implements IBusMe // vo.setInvoiceItems(agentVo.getInvoiceItems()); vo.setCostServiceCharge(agentVo.getServiceCharge()); - ValidatorUtils.validate(vo, AddGroup.class); +// ValidatorUtils.validate(vo, AddGroup.class); if(vo.getServiceCharge() != null && vo.getServiceCharge().compareTo(vo.getCostServiceCharge()) < 0){ vo.setServiceCharge(vo.getCostServiceCharge()); } diff --git a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/merchant/service/impl/BusMerchantServiceImpl.java b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/merchant/service/impl/BusMerchantServiceImpl.java index 08ca499..3045eba 100644 --- a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/merchant/service/impl/BusMerchantServiceImpl.java +++ b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/merchant/service/impl/BusMerchantServiceImpl.java @@ -81,17 +81,18 @@ public class BusMerchantServiceImpl extends BaseService implements IBusMerchantS @Override public TableDataInfo queryPageList(BusMerchantBo bo, PageQuery pageQuery) { QueryWrapper lqw = Wrappers.query(); - - lqw.between(bo.getStartTime() != null,"t.create_time",bo.getStartTime(),bo.getEndTime()); - lqw.like(StringUtils.isNotEmpty(bo.getName()),"t.name",bo.getName()); - lqw.like(StringUtils.isNotEmpty(bo.getNo()),"t.no",bo.getNo()); - lqw.like(StringUtils.isNotEmpty(bo.getScaleName()),"s.name",bo.getScaleName()); - if(bo.getIsProduct() != null && bo.getIsProduct() == 1){ - lqw.eq("t.create_unit_id",LoginHelper.getBusId()); - } - int busRole = LoginHelper.getBusRole(); - if(busRole == BusRole.SERVICE.getId()){ - lqw.inSql("t.id","select distinct mer_id from bus_merchant_product tt where tt.syd_id ="+LoginHelper.getBusId()); + if (!LoginHelper.isSuperAdmin()){ + lqw.between(bo.getStartTime() != null,"t.create_time",bo.getStartTime(),bo.getEndTime()); + lqw.like(StringUtils.isNotEmpty(bo.getName()),"t.name",bo.getName()); + lqw.like(StringUtils.isNotEmpty(bo.getNo()),"t.no",bo.getNo()); + lqw.like(StringUtils.isNotEmpty(bo.getScaleName()),"s.name",bo.getScaleName()); + if(bo.getIsProduct() != null && bo.getIsProduct() == 1){ + lqw.eq("t.create_unit_id",LoginHelper.getBusId()); + } + int busRole = LoginHelper.getBusRole(); + if(busRole == BusRole.SERVICE.getId()){ + lqw.inSql("t.id","select distinct mer_id from bus_merchant_product tt where tt.syd_id ="+LoginHelper.getBusId()); + } } lqw.orderByDesc("t.create_time"); diff --git a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/sys/controller/LgEarlyWarnController.java b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/sys/controller/LgEarlyWarnController.java deleted file mode 100644 index 752fef0..0000000 --- a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/sys/controller/LgEarlyWarnController.java +++ /dev/null @@ -1,53 +0,0 @@ -//package org.dromara.payment.sys.controller; -// -// -//import org.dromara.common.log.enums.BusinessType; -//import org.springframework.beans.factory.annotation.Autowired; -//import org.springframework.web.bind.annotation.*; -// -//@RestController -//@RequestMapping({"/business/early_warn"}) -//public class LgEarlyWarnController extends BaseController { -// @Autowired -// private ILgEarlyWarnService lgEarlyWarnService; -// -// @ApiOperation(value = "后台-查询风控预警设置列表", response = LgEarlyWarnListDtlVO.class) -// @PreAuthorize("@ss.hasPermi('business:early_warn:list')") -// @GetMapping({"/list"}) -// public TableDataInfo list(LgEarlyWarnListQuery query) { -// YFListVO list = this.lgEarlyWarnService.selectLgEarlyWarnList(query); -// return getDataTable(list.getList(), list.getTotal().longValue()); -// } -// -// @ApiOperation(value = "后台-获取风控预警设置详细信息", response = LgEarlyWarnInfoVO.class) -// @PreAuthorize("@ss.hasPermi('business:early_warn:query')") -// @GetMapping({"/getInfo/{id}"}) -// public AjaxResult getInfo(@PathVariable("id") Integer id) { -// return AjaxResult.success(this.lgEarlyWarnService.selectLgEarlyWarnById(id)); -// } -// -// @ApiOperation("后台-新增风控预警设置") -// @PreAuthorize("@ss.hasPermi('business:early_warn:add')") -// @Log(title = "后台-风控预警设置", businessType = BusinessType.INSERT) -// @PostMapping({"/add"}) -// public AjaxResult add(@RequestBody LgEarlyWarnSaveDTO dto) { -// this.lgEarlyWarnService.insertLgEarlyWarn(dto); -// return AjaxResult.success(); -// } -// -// @ApiOperation("后台-修改风控预警设置") -// @PreAuthorize("@ss.hasPermi('business:early_warn:edit')") -// @Log(title = "后台-风控预警设置", businessType = BusinessType.UPDATE) -// @PutMapping({"/edit"}) -// public AjaxResult edit(@RequestBody LgEarlyWarnUpdateDTO lgEarlyWarn) { -// return toAjax(this.lgEarlyWarnService.updateLgEarlyWarn(lgEarlyWarn)); -// } -// -// @ApiOperation("删除风控预警设置") -// @PreAuthorize("@ss.hasPermi('business:early_warn:remove')") -// @Log(title = "风控预警设置", businessType = BusinessType.DELETE) -// @DeleteMapping({"/remove/{ids}"}) -// public AjaxResult remove(@PathVariable Long[] ids) { -// return toAjax(this.lgEarlyWarnService.deleteLgEarlyWarnByIds(ids)); -// } -//} diff --git a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/sys/controller/LgEarlyWarnDtlController.java b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/sys/controller/LgEarlyWarnDtlController.java deleted file mode 100644 index 1a8bbbe..0000000 --- a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/sys/controller/LgEarlyWarnDtlController.java +++ /dev/null @@ -1,16 +0,0 @@ -//package org.dromara.payment.sys.controller; -// -//import com.ruoyi.business.service.ILgEarlyWarnDtlService; -//import com.ruoyi.common.core.controller.BaseController; -//import io.swagger.annotations.Api; -//import org.springframework.beans.factory.annotation.Autowired; -//import org.springframework.web.bind.annotation.RequestMapping; -//import org.springframework.web.bind.annotation.RestController; -// -//@RestController -//@RequestMapping({"/business/early_warn_dtl"}) -//@Api(tags = {"风控预警设置明细"}) -//public class LgEarlyWarnDtlController extends BaseController { -// @Autowired -// private ILgEarlyWarnDtlService lgEarlyWarnDtlService; -//} diff --git a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/sys/controller/ProInfoCommonController.java b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/sys/controller/ProInfoCommonController.java index 1cdd04f..e9efc18 100644 --- a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/sys/controller/ProInfoCommonController.java +++ b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/sys/controller/ProInfoCommonController.java @@ -1,6 +1,5 @@ package org.dromara.payment.sys.controller; -import cn.dev33.satoken.annotation.SaCheckPermission; import lombok.RequiredArgsConstructor; import org.dromara.common.core.domain.R; import org.dromara.common.log.annotation.Log; @@ -9,10 +8,7 @@ import org.dromara.common.mybatis.core.page.PageQuery; import org.dromara.common.mybatis.core.page.TableDataInfo; import org.dromara.common.web.core.BaseController; import org.dromara.payment.merchant.domain.vo.BusMerchantVo; -import org.dromara.payment.sys.domain.SysSyd; -import org.dromara.payment.sys.domain.bo.AccAccountsBo; import org.dromara.payment.sys.domain.bo.ProInfoCommonBo; -import org.dromara.payment.sys.domain.vo.AccAccountsVo; import org.dromara.payment.sys.domain.vo.ProInfoCommonVo; import org.dromara.payment.sys.domain.vo.SysSydVo; import org.dromara.payment.sys.service.IProInfoCommonService; diff --git a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/sys/controller/SysSydAccountController.java b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/sys/controller/SysSydAccountController.java index fc75286..c2f8261 100644 --- a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/sys/controller/SysSydAccountController.java +++ b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/sys/controller/SysSydAccountController.java @@ -9,15 +9,12 @@ import jakarta.validation.constraints.*; import cn.dev33.satoken.annotation.SaCheckPermission; import org.apache.dubbo.config.annotation.DubboReference; import org.dromara.common.satoken.utils.LoginHelper; -import org.dromara.payment.merchant.domain.vo.BusMerchantAccountVo; import org.dromara.payment.sys.domain.vo.SysSydAccountVo; import org.dromara.payment.sys.domain.vo.SysSydVo; import org.dromara.payment.sys.service.ISysSydAccountService; import org.dromara.payment.sys.service.ISysSydService; import org.dromara.settlement.api.RemoteAccountService; -import org.dromara.settlement.api.domain.Result; import org.dromara.system.api.model.LoginUser; -import org.redisson.api.RList; import org.springframework.web.bind.annotation.*; import org.springframework.validation.annotation.Validated; import org.dromara.common.idempotent.annotation.RepeatSubmit; diff --git a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/sys/controller/SysSydCashoutController.java b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/sys/controller/SysSydCashoutController.java index 7022cb1..bebf458 100644 --- a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/sys/controller/SysSydCashoutController.java +++ b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/sys/controller/SysSydCashoutController.java @@ -10,7 +10,6 @@ import org.dromara.common.satoken.utils.LoginHelper; import org.dromara.payment.sys.domain.bo.SysSydCashoutBo; import org.dromara.payment.sys.domain.vo.SysSydAccountVo; import org.dromara.payment.sys.domain.vo.SysSydCashoutVo; -import org.dromara.payment.sys.domain.vo.SysSydVo; import org.dromara.payment.sys.service.ISysSydAccountService; import org.dromara.payment.sys.service.ISysSydCashoutService; import org.dromara.payment.sys.service.ISysSydService; diff --git a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/sys/controller/SysSydConfigController.java b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/sys/controller/SysSydConfigController.java index 5a6db6f..8b0996b 100644 --- a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/sys/controller/SysSydConfigController.java +++ b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/sys/controller/SysSydConfigController.java @@ -4,10 +4,6 @@ import java.util.List; import java.util.Map; import com.alibaba.fastjson.JSONObject; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.JsonMappingException; -import com.fasterxml.jackson.databind.ObjectMapper; import lombok.RequiredArgsConstructor; import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.constraints.*; diff --git a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/sys/controller/SysSydController.java b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/sys/controller/SysSydController.java index 8c9232e..e9364a5 100644 --- a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/sys/controller/SysSydController.java +++ b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/sys/controller/SysSydController.java @@ -8,7 +8,6 @@ import jakarta.validation.constraints.*; import cn.dev33.satoken.annotation.SaCheckPermission; import org.dromara.common.satoken.utils.LoginHelper; import org.dromara.payment.sys.domain.SysSydSignContent; -import org.dromara.payment.sys.domain.vo.SelectSydVo; import org.dromara.payment.sys.domain.vo.SysSydVo; import org.dromara.payment.sys.service.ISysSydService; import org.dromara.settlement.api.domain.SignZhifubao; diff --git a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/sys/controller/SysSydFlowsController.java b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/sys/controller/SysSydFlowsController.java index 902f9ca..3131dcd 100644 --- a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/sys/controller/SysSydFlowsController.java +++ b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/sys/controller/SysSydFlowsController.java @@ -13,8 +13,6 @@ import org.dromara.common.core.utils.StreamUtils; import org.dromara.common.core.utils.StringUtils; import org.dromara.common.core.utils.file.FileUtils; import org.dromara.common.satoken.utils.LoginHelper; -import org.dromara.payment.bill.domain.bo.MerBillDetailBo; -import org.dromara.payment.bill.domain.vo.MerBillDetailVo; import org.dromara.payment.sys.domain.vo.SysSydFlowsVo; import org.dromara.payment.sys.service.ISysSydFlowsService; import org.dromara.system.api.model.LoginUser; diff --git a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/sys/controller/SysSydProductController.java b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/sys/controller/SysSydProductController.java index 9005a3b..bc5e14b 100644 --- a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/sys/controller/SysSydProductController.java +++ b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/sys/controller/SysSydProductController.java @@ -5,12 +5,10 @@ import java.util.List; import lombok.RequiredArgsConstructor; import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.constraints.*; -import cn.dev33.satoken.annotation.SaCheckPermission; import org.dromara.common.satoken.utils.LoginHelper; import org.dromara.payment.sys.domain.vo.SysSydProductVo; import org.dromara.payment.sys.service.ISysSydProductService; import org.dromara.system.api.model.LoginUser; -import org.redisson.api.RList; import org.springframework.web.bind.annotation.*; import org.springframework.validation.annotation.Validated; import org.dromara.common.idempotent.annotation.RepeatSubmit; diff --git a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/sys/domain/SysSyd.java b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/sys/domain/SysSyd.java index 22f2357..f57c09e 100644 --- a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/sys/domain/SysSyd.java +++ b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/sys/domain/SysSyd.java @@ -220,4 +220,15 @@ public class SysSyd extends BaseEntity { private Long threeId; private Long parkId; + + private Long createDept; + + private Long createBy; + + private Date createTime; + + private Long updateBy; + + private Date updateTime; + } diff --git a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/sys/domain/bo/SysSydBo.java b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/sys/domain/bo/SysSydBo.java index 53dea2e..cf6cf08 100644 --- a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/sys/domain/bo/SysSydBo.java +++ b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/sys/domain/bo/SysSydBo.java @@ -21,7 +21,8 @@ import java.util.Date; @Data @EqualsAndHashCode(callSuper = true) @AutoMapper(target = SysSyd.class, reverseConvertGenerate = false) -public class SysSydBo extends BaseEntity { +public class +SysSydBo extends BaseEntity { /** * 主键 diff --git a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/sys/domain/vo/LgEarlyWarnDtlInfoVO.java b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/sys/domain/vo/LgEarlyWarnDtlInfoVO.java new file mode 100644 index 0000000..e9c5f10 --- /dev/null +++ b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/sys/domain/vo/LgEarlyWarnDtlInfoVO.java @@ -0,0 +1,298 @@ +package org.dromara.payment.sys.domain.vo; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.Date; + +@EqualsAndHashCode +public class LgEarlyWarnDtlInfoVO implements Serializable { + private static final long serialVersionUID = 1L; + + private Long id; + + private Long earlyWarnId; + + private Long taxSourcesId; + + private String taxSourcesIdDisplay; + + private Integer riskType; + + 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 Integer singleContinuousMonth; + + private Integer issuingAge; + + private Integer handleFrequency; + + private Integer handleWay; + + private String riskReasons; + + private Integer isDelete; + + private Long createUserId; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date createTime; + + private Long updateUserId; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date updateTime; + + private String remark; + + public void setId(Long id) { + this.id = id; + } + + public void setEarlyWarnId(Long earlyWarnId) { + this.earlyWarnId = earlyWarnId; + } + + public void setTaxSourcesId(Long taxSourcesId) { + this.taxSourcesId = taxSourcesId; + } + + public void setTaxSourcesIdDisplay(String taxSourcesIdDisplay) { + this.taxSourcesIdDisplay = taxSourcesIdDisplay; + } + + public void setRiskType(Integer riskType) { + this.riskType = riskType; + } + + public void setTriggerRule(Integer triggerRule) { + this.triggerRule = triggerRule; + } + + public void setSingleAmount(BigDecimal singleAmount) { + this.singleAmount = singleAmount; + } + + public void setTotalAmountBatch(BigDecimal totalAmountBatch) { + this.totalAmountBatch = totalAmountBatch; + } + + public void setSinglePaymentAmountStart(BigDecimal singlePaymentAmountStart) { + this.singlePaymentAmountStart = singlePaymentAmountStart; + } + + public void setSinglePaymentAmountEnd(BigDecimal singlePaymentAmountEnd) { + this.singlePaymentAmountEnd = singlePaymentAmountEnd; + } + + public void setProportionTotal(BigDecimal proportionTotal) { + this.proportionTotal = proportionTotal; + } + + public void setDispersion(BigDecimal dispersion) { + this.dispersion = dispersion; + } + + public void setSingleYearAmount(BigDecimal singleYearAmount) { + this.singleYearAmount = singleYearAmount; + } + + public void setWaveAmountStart(BigDecimal waveAmountStart) { + this.waveAmountStart = waveAmountStart; + } + + public void setWaveAmountEnd(BigDecimal waveAmountEnd) { + this.waveAmountEnd = waveAmountEnd; + } + + public void setWaveAmount(BigDecimal waveAmount) { + this.waveAmount = waveAmount; + } + + public void setSingleContinuousMonth(Integer singleContinuousMonth) { + this.singleContinuousMonth = singleContinuousMonth; + } + + public void setIssuingAge(Integer issuingAge) { + this.issuingAge = issuingAge; + } + + public void setHandleFrequency(Integer handleFrequency) { + this.handleFrequency = handleFrequency; + } + + public void setHandleWay(Integer handleWay) { + this.handleWay = handleWay; + } + + public void setRiskReasons(String riskReasons) { + this.riskReasons = riskReasons; + } + + public void setIsDelete(Integer isDelete) { + this.isDelete = isDelete; + } + + public void setCreateUserId(Long createUserId) { + this.createUserId = createUserId; + } + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + public void setCreateTime(Date createTime) { + this.createTime = createTime; + } + + public void setUpdateUserId(Long updateUserId) { + this.updateUserId = updateUserId; + } + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + public void setUpdateTime(Date updateTime) { + this.updateTime = updateTime; + } + + public void setRemark(String remark) { + this.remark = remark; + } + + + protected boolean canEqual(Object other) { + return other instanceof LgEarlyWarnDtlInfoVO; + } + + + + public String toString() { + return "LgEarlyWarnDtlInfoVO(id=" + getId() + ", earlyWarnId=" + getEarlyWarnId() + ", taxSourcesId=" + getTaxSourcesId() + ", taxSourcesIdDisplay=" + getTaxSourcesIdDisplay() + ", riskType=" + getRiskType() + ", triggerRule=" + getTriggerRule() + ", singleAmount=" + getSingleAmount() + ", totalAmountBatch=" + getTotalAmountBatch() + ", singlePaymentAmountStart=" + getSinglePaymentAmountStart() + ", singlePaymentAmountEnd=" + getSinglePaymentAmountEnd() + ", proportionTotal=" + getProportionTotal() + ", dispersion=" + getDispersion() + ", singleYearAmount=" + getSingleYearAmount() + ", waveAmountStart=" + getWaveAmountStart() + ", waveAmountEnd=" + getWaveAmountEnd() + ", waveAmount=" + getWaveAmount() + ", singleContinuousMonth=" + getSingleContinuousMonth() + ", issuingAge=" + getIssuingAge() + ", handleFrequency=" + getHandleFrequency() + ", handleWay=" + getHandleWay() + ", riskReasons=" + getRiskReasons() + ", isDelete=" + getIsDelete() + ", createUserId=" + getCreateUserId() + ", createTime=" + getCreateTime() + ", updateUserId=" + getUpdateUserId() + ", updateTime=" + getUpdateTime() + ", remark=" + getRemark() + ")"; + } + + public Long getId() { + return this.id; + } + + public Long getEarlyWarnId() { + return this.earlyWarnId; + } + + public Long getTaxSourcesId() { + return this.taxSourcesId; + } + + public String getTaxSourcesIdDisplay() { + return this.taxSourcesIdDisplay; + } + + public Integer getRiskType() { + return this.riskType; + } + + public Integer getTriggerRule() { + return this.triggerRule; + } + + public BigDecimal getSingleAmount() { + return this.singleAmount; + } + + public BigDecimal getTotalAmountBatch() { + return this.totalAmountBatch; + } + + public BigDecimal getSinglePaymentAmountStart() { + return this.singlePaymentAmountStart; + } + + public BigDecimal getSinglePaymentAmountEnd() { + return this.singlePaymentAmountEnd; + } + + public BigDecimal getProportionTotal() { + return this.proportionTotal; + } + + public BigDecimal getDispersion() { + return this.dispersion; + } + + public BigDecimal getSingleYearAmount() { + return this.singleYearAmount; + } + + public BigDecimal getWaveAmountStart() { + return this.waveAmountStart; + } + + public BigDecimal getWaveAmountEnd() { + return this.waveAmountEnd; + } + + public BigDecimal getWaveAmount() { + return this.waveAmount; + } + + public Integer getSingleContinuousMonth() { + return this.singleContinuousMonth; + } + + public Integer getIssuingAge() { + return this.issuingAge; + } + + public Integer getHandleFrequency() { + return this.handleFrequency; + } + + public Integer getHandleWay() { + return this.handleWay; + } + + public String getRiskReasons() { + return this.riskReasons; + } + + public Integer getIsDelete() { + return this.isDelete; + } + + public Long getCreateUserId() { + return this.createUserId; + } + + public Date getCreateTime() { + return this.createTime; + } + + public Long getUpdateUserId() { + return this.updateUserId; + } + + public Date getUpdateTime() { + return this.updateTime; + } + + public String getRemark() { + return this.remark; + } +} diff --git a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/sys/domain/vo/LgEarlyWarnInfoVO.java b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/sys/domain/vo/LgEarlyWarnInfoVO.java new file mode 100644 index 0000000..e15fb0b --- /dev/null +++ b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/sys/domain/vo/LgEarlyWarnInfoVO.java @@ -0,0 +1,281 @@ +package org.dromara.payment.sys.domain.vo; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.EqualsAndHashCode; + +import javax.validation.constraints.NotEmpty; +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.Date; +import java.util.List; + +@EqualsAndHashCode +public class LgEarlyWarnInfoVO implements Serializable { + private static final long serialVersionUID = 1L; + + private Long id; + + @NotEmpty(message = "所属税源地 不可为空") + private List taxSourcesIdList; + + private Integer riskType; + + 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 Integer singleContinuousMonth; + + private Integer issuingAge; + + private Integer handleFrequency; + + private Integer handleWay; + + private String riskReasons; + + private Integer isDelete; + + private Long createUserId; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date createTime; + + private Long updateUserId; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date updateTime; + + private String remark; + + public void setId(Long id) { + this.id = id; + } + + public void setTaxSourcesIdList(List taxSourcesIdList) { + this.taxSourcesIdList = taxSourcesIdList; + } + + public void setRiskType(Integer riskType) { + this.riskType = riskType; + } + + public void setTriggerRule(Integer triggerRule) { + this.triggerRule = triggerRule; + } + + public void setSingleAmount(BigDecimal singleAmount) { + this.singleAmount = singleAmount; + } + + public void setTotalAmountBatch(BigDecimal totalAmountBatch) { + this.totalAmountBatch = totalAmountBatch; + } + + public void setSinglePaymentAmountStart(BigDecimal singlePaymentAmountStart) { + this.singlePaymentAmountStart = singlePaymentAmountStart; + } + + public void setSinglePaymentAmountEnd(BigDecimal singlePaymentAmountEnd) { + this.singlePaymentAmountEnd = singlePaymentAmountEnd; + } + + public void setProportionTotal(BigDecimal proportionTotal) { + this.proportionTotal = proportionTotal; + } + + public void setDispersion(BigDecimal dispersion) { + this.dispersion = dispersion; + } + + public void setSingleYearAmount(BigDecimal singleYearAmount) { + this.singleYearAmount = singleYearAmount; + } + + public void setWaveAmountStart(BigDecimal waveAmountStart) { + this.waveAmountStart = waveAmountStart; + } + + public void setWaveAmountEnd(BigDecimal waveAmountEnd) { + this.waveAmountEnd = waveAmountEnd; + } + + public void setWaveAmount(BigDecimal waveAmount) { + this.waveAmount = waveAmount; + } + + public void setSingleContinuousMonth(Integer singleContinuousMonth) { + this.singleContinuousMonth = singleContinuousMonth; + } + + public void setIssuingAge(Integer issuingAge) { + this.issuingAge = issuingAge; + } + + public void setHandleFrequency(Integer handleFrequency) { + this.handleFrequency = handleFrequency; + } + + public void setHandleWay(Integer handleWay) { + this.handleWay = handleWay; + } + + public void setRiskReasons(String riskReasons) { + this.riskReasons = riskReasons; + } + + public void setIsDelete(Integer isDelete) { + this.isDelete = isDelete; + } + + public void setCreateUserId(Long createUserId) { + this.createUserId = createUserId; + } + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + public void setCreateTime(Date createTime) { + this.createTime = createTime; + } + + public void setUpdateUserId(Long updateUserId) { + this.updateUserId = updateUserId; + } + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + public void setUpdateTime(Date updateTime) { + this.updateTime = updateTime; + } + + public void setRemark(String remark) { + this.remark = remark; + } + + + protected boolean canEqual(Object other) { + return other instanceof LgEarlyWarnInfoVO; + } + + + + public String toString() { + return "LgEarlyWarnInfoVO(id=" + getId() + ", taxSourcesIdList=" + getTaxSourcesIdList() + ", riskType=" + getRiskType() + ", triggerRule=" + getTriggerRule() + ", singleAmount=" + getSingleAmount() + ", totalAmountBatch=" + getTotalAmountBatch() + ", singlePaymentAmountStart=" + getSinglePaymentAmountStart() + ", singlePaymentAmountEnd=" + getSinglePaymentAmountEnd() + ", proportionTotal=" + getProportionTotal() + ", dispersion=" + getDispersion() + ", singleYearAmount=" + getSingleYearAmount() + ", waveAmountStart=" + getWaveAmountStart() + ", waveAmountEnd=" + getWaveAmountEnd() + ", waveAmount=" + getWaveAmount() + ", singleContinuousMonth=" + getSingleContinuousMonth() + ", issuingAge=" + getIssuingAge() + ", handleFrequency=" + getHandleFrequency() + ", handleWay=" + getHandleWay() + ", riskReasons=" + getRiskReasons() + ", isDelete=" + getIsDelete() + ", createUserId=" + getCreateUserId() + ", createTime=" + getCreateTime() + ", updateUserId=" + getUpdateUserId() + ", updateTime=" + getUpdateTime() + ", remark=" + getRemark() + ")"; + } + + public Long getId() { + return this.id; + } + + public List getTaxSourcesIdList() { + return this.taxSourcesIdList; + } + + public Integer getRiskType() { + return this.riskType; + } + + public Integer getTriggerRule() { + return this.triggerRule; + } + + public BigDecimal getSingleAmount() { + return this.singleAmount; + } + + public BigDecimal getTotalAmountBatch() { + return this.totalAmountBatch; + } + + public BigDecimal getSinglePaymentAmountStart() { + return this.singlePaymentAmountStart; + } + + public BigDecimal getSinglePaymentAmountEnd() { + return this.singlePaymentAmountEnd; + } + + public BigDecimal getProportionTotal() { + return this.proportionTotal; + } + + public BigDecimal getDispersion() { + return this.dispersion; + } + + public BigDecimal getSingleYearAmount() { + return this.singleYearAmount; + } + + public BigDecimal getWaveAmountStart() { + return this.waveAmountStart; + } + + public BigDecimal getWaveAmountEnd() { + return this.waveAmountEnd; + } + + public BigDecimal getWaveAmount() { + return this.waveAmount; + } + + public Integer getSingleContinuousMonth() { + return this.singleContinuousMonth; + } + + public Integer getIssuingAge() { + return this.issuingAge; + } + + public Integer getHandleFrequency() { + return this.handleFrequency; + } + + public Integer getHandleWay() { + return this.handleWay; + } + + public String getRiskReasons() { + return this.riskReasons; + } + + public Integer getIsDelete() { + return this.isDelete; + } + + public Long getCreateUserId() { + return this.createUserId; + } + + public Date getCreateTime() { + return this.createTime; + } + + public Long getUpdateUserId() { + return this.updateUserId; + } + + public Date getUpdateTime() { + return this.updateTime; + } + + public String getRemark() { + return this.remark; + } +} diff --git a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/sys/domain/vo/LgEarlyWarnListDtlVO.java b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/sys/domain/vo/LgEarlyWarnListDtlVO.java new file mode 100644 index 0000000..b3bb13a --- /dev/null +++ b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/sys/domain/vo/LgEarlyWarnListDtlVO.java @@ -0,0 +1,329 @@ +package org.dromara.payment.sys.domain.vo; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.Date; +import java.util.List; + +@EqualsAndHashCode +public class LgEarlyWarnListDtlVO implements Serializable { + private static final long serialVersionUID = 1L; + + private Long id; + + private List taxSourcesIdList; + + private String taxSourcesIdListDisplay; + + private Integer riskType; + + private String riskTypeDisplay; + + private Integer triggerRule; + + private String triggerRuleDisplay; + + 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 Integer singleContinuousMonth; + + private Integer issuingAge; + + private Integer handleFrequency; + + private String handleFrequencyDisplay; + + private Integer handleWay; + + private String handleWayDisplay; + + private String riskReasons; + + private Integer isDelete; + + private Long createUserId; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date createTime; + + private Long updateUserId; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date updateTime; + + private String remark; + + public void setId(Long id) { + this.id = id; + } + + public void setTaxSourcesIdList(List taxSourcesIdList) { + this.taxSourcesIdList = taxSourcesIdList; + } + + public void setTaxSourcesIdListDisplay(String taxSourcesIdListDisplay) { + this.taxSourcesIdListDisplay = taxSourcesIdListDisplay; + } + + public void setRiskType(Integer riskType) { + this.riskType = riskType; + } + + public void setRiskTypeDisplay(String riskTypeDisplay) { + this.riskTypeDisplay = riskTypeDisplay; + } + + public void setTriggerRule(Integer triggerRule) { + this.triggerRule = triggerRule; + } + + public void setTriggerRuleDisplay(String triggerRuleDisplay) { + this.triggerRuleDisplay = triggerRuleDisplay; + } + + public void setSingleAmount(BigDecimal singleAmount) { + this.singleAmount = singleAmount; + } + + public void setTotalAmountBatch(BigDecimal totalAmountBatch) { + this.totalAmountBatch = totalAmountBatch; + } + + public void setSinglePaymentAmountStart(BigDecimal singlePaymentAmountStart) { + this.singlePaymentAmountStart = singlePaymentAmountStart; + } + + public void setSinglePaymentAmountEnd(BigDecimal singlePaymentAmountEnd) { + this.singlePaymentAmountEnd = singlePaymentAmountEnd; + } + + public void setProportionTotal(BigDecimal proportionTotal) { + this.proportionTotal = proportionTotal; + } + + public void setDispersion(BigDecimal dispersion) { + this.dispersion = dispersion; + } + + public void setSingleYearAmount(BigDecimal singleYearAmount) { + this.singleYearAmount = singleYearAmount; + } + + public void setWaveAmountStart(BigDecimal waveAmountStart) { + this.waveAmountStart = waveAmountStart; + } + + public void setWaveAmountEnd(BigDecimal waveAmountEnd) { + this.waveAmountEnd = waveAmountEnd; + } + + public void setWaveAmount(BigDecimal waveAmount) { + this.waveAmount = waveAmount; + } + + public void setSingleContinuousMonth(Integer singleContinuousMonth) { + this.singleContinuousMonth = singleContinuousMonth; + } + + public void setIssuingAge(Integer issuingAge) { + this.issuingAge = issuingAge; + } + + public void setHandleFrequency(Integer handleFrequency) { + this.handleFrequency = handleFrequency; + } + + public void setHandleFrequencyDisplay(String handleFrequencyDisplay) { + this.handleFrequencyDisplay = handleFrequencyDisplay; + } + + public void setHandleWay(Integer handleWay) { + this.handleWay = handleWay; + } + + public void setHandleWayDisplay(String handleWayDisplay) { + this.handleWayDisplay = handleWayDisplay; + } + + public void setRiskReasons(String riskReasons) { + this.riskReasons = riskReasons; + } + + public void setIsDelete(Integer isDelete) { + this.isDelete = isDelete; + } + + public void setCreateUserId(Long createUserId) { + this.createUserId = createUserId; + } + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + public void setCreateTime(Date createTime) { + this.createTime = createTime; + } + + public void setUpdateUserId(Long updateUserId) { + this.updateUserId = updateUserId; + } + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + public void setUpdateTime(Date updateTime) { + this.updateTime = updateTime; + } + + public void setRemark(String remark) { + this.remark = remark; + } + + + protected boolean canEqual(Object other) { + return other instanceof LgEarlyWarnListDtlVO; + } + + + + public String toString() { + return "LgEarlyWarnListDtlVO(id=" + getId() + ", taxSourcesIdList=" + getTaxSourcesIdList() + ", taxSourcesIdListDisplay=" + getTaxSourcesIdListDisplay() + ", riskType=" + getRiskType() + ", riskTypeDisplay=" + getRiskTypeDisplay() + ", triggerRule=" + getTriggerRule() + ", triggerRuleDisplay=" + getTriggerRuleDisplay() + ", singleAmount=" + getSingleAmount() + ", totalAmountBatch=" + getTotalAmountBatch() + ", singlePaymentAmountStart=" + getSinglePaymentAmountStart() + ", singlePaymentAmountEnd=" + getSinglePaymentAmountEnd() + ", proportionTotal=" + getProportionTotal() + ", dispersion=" + getDispersion() + ", singleYearAmount=" + getSingleYearAmount() + ", waveAmountStart=" + getWaveAmountStart() + ", waveAmountEnd=" + getWaveAmountEnd() + ", waveAmount=" + getWaveAmount() + ", singleContinuousMonth=" + getSingleContinuousMonth() + ", issuingAge=" + getIssuingAge() + ", handleFrequency=" + getHandleFrequency() + ", handleFrequencyDisplay=" + getHandleFrequencyDisplay() + ", handleWay=" + getHandleWay() + ", handleWayDisplay=" + getHandleWayDisplay() + ", riskReasons=" + getRiskReasons() + ", isDelete=" + getIsDelete() + ", createUserId=" + getCreateUserId() + ", createTime=" + getCreateTime() + ", updateUserId=" + getUpdateUserId() + ", updateTime=" + getUpdateTime() + ", remark=" + getRemark() + ")"; + } + + public Long getId() { + return this.id; + } + + public List getTaxSourcesIdList() { + return this.taxSourcesIdList; + } + + public String getTaxSourcesIdListDisplay() { + return this.taxSourcesIdListDisplay; + } + + public Integer getRiskType() { + return this.riskType; + } + + public String getRiskTypeDisplay() { + return this.riskTypeDisplay; + } + + public Integer getTriggerRule() { + return this.triggerRule; + } + + public String getTriggerRuleDisplay() { + return this.triggerRuleDisplay; + } + + public BigDecimal getSingleAmount() { + return this.singleAmount; + } + + public BigDecimal getTotalAmountBatch() { + return this.totalAmountBatch; + } + + public BigDecimal getSinglePaymentAmountStart() { + return this.singlePaymentAmountStart; + } + + public BigDecimal getSinglePaymentAmountEnd() { + return this.singlePaymentAmountEnd; + } + + public BigDecimal getProportionTotal() { + return this.proportionTotal; + } + + public BigDecimal getDispersion() { + return this.dispersion; + } + + public BigDecimal getSingleYearAmount() { + return this.singleYearAmount; + } + + public BigDecimal getWaveAmountStart() { + return this.waveAmountStart; + } + + public BigDecimal getWaveAmountEnd() { + return this.waveAmountEnd; + } + + public BigDecimal getWaveAmount() { + return this.waveAmount; + } + + public Integer getSingleContinuousMonth() { + return this.singleContinuousMonth; + } + + public Integer getIssuingAge() { + return this.issuingAge; + } + + public Integer getHandleFrequency() { + return this.handleFrequency; + } + + public String getHandleFrequencyDisplay() { + return this.handleFrequencyDisplay; + } + + public Integer getHandleWay() { + return this.handleWay; + } + + public String getHandleWayDisplay() { + return this.handleWayDisplay; + } + + public String getRiskReasons() { + return this.riskReasons; + } + + public Integer getIsDelete() { + return this.isDelete; + } + + public Long getCreateUserId() { + return this.createUserId; + } + + public Date getCreateTime() { + return this.createTime; + } + + public Long getUpdateUserId() { + return this.updateUserId; + } + + public Date getUpdateTime() { + return this.updateTime; + } + + public String getRemark() { + return this.remark; + } +} diff --git a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/sys/service/impl/ProInfoCommonServiceImpl.java b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/sys/service/impl/ProInfoCommonServiceImpl.java index d46c8d1..4777871 100644 --- a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/sys/service/impl/ProInfoCommonServiceImpl.java +++ b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/sys/service/impl/ProInfoCommonServiceImpl.java @@ -101,6 +101,8 @@ public class ProInfoCommonServiceImpl extends BaseService implements IProInfoCom }else if(busRole == BusRole.SERVICE.getId()){ lqw.eq(StringUtils.isNotEmpty(LoginHelper.getNo()),"t.syd_id",LoginHelper.getBusId()); result =this.proInfoCommnoMapper.selectOperBusType(lqw); + }else if(busRole == BusRole.PLATFORM.getId()){ + result = this.proInfoCommnoMapper.selectOperBusType(lqw); } return result; } diff --git a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/task/domain/bo/MerTaskBo.java b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/task/domain/bo/MerTaskBo.java index 4c216ac..9f9d162 100644 --- a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/task/domain/bo/MerTaskBo.java +++ b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/task/domain/bo/MerTaskBo.java @@ -266,6 +266,18 @@ public class MerTaskBo extends BaseEntity { */ private String sydVerifyBy; + private String itemTypeId; + + private String taskTypeId; + + private String itemTypeName; + + private String taskTypeName; + + private String itemId; + + private String taskId; + /** * 结算中心驳回原因 */ diff --git a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/task/domain/vo/MerTaskVo.java b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/task/domain/vo/MerTaskVo.java index 9c821e5..da53bef 100644 --- a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/task/domain/vo/MerTaskVo.java +++ b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/task/domain/vo/MerTaskVo.java @@ -319,6 +319,18 @@ public class MerTaskVo implements Serializable { private String sydVerifyReason; + private String itemTypeId; + + private String taskTypeId; + + private String itemTypeName; + + private String taskTypeName; + + private String itemId; + + private String taskId; + //运营商 private String opName; diff --git a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/task/service/impl/MerTaskServiceImpl.java b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/task/service/impl/MerTaskServiceImpl.java index 5c3374a..04e5a41 100644 --- a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/task/service/impl/MerTaskServiceImpl.java +++ b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/task/service/impl/MerTaskServiceImpl.java @@ -22,12 +22,15 @@ import org.dromara.payment.merchant.domain.bo.BusMerchantBo; import org.dromara.payment.merchant.domain.vo.BusOperAndAgentAndMer; import org.dromara.payment.merchant.mapper.BusMerchantConfigMapper; import org.dromara.payment.merchant.mapper.BusMerchantMapper; +import org.dromara.payment.sys.domain.SysSyd; import org.dromara.payment.sys.domain.vo.SysSydVo; import org.dromara.payment.sys.mapper.SysSydMapper; import org.dromara.payment.task.domain.MerTaskDetail; import org.dromara.payment.task.domain.MerTaskEnroll; import org.dromara.payment.task.domain.vo.MerTaskEnrollVo; import org.dromara.payment.task.mapper.MerTaskEnrollMapper; +import org.dromara.payment.thirdPlatform.base.domain.SysThirdPlatform; +import org.dromara.payment.thirdPlatform.base.mapper.SysThirdPlatformMapper; import org.dromara.payment.thirdPlatform.xinyujian.service.IXyjService; import org.dromara.system.api.model.LoginUser; import org.springframework.beans.BeanUtils; @@ -42,10 +45,7 @@ import org.dromara.payment.task.service.IMerTaskService; import javax.annotation.Resource; import java.security.SecureRandom; -import java.util.Date; -import java.util.List; -import java.util.Map; -import java.util.Collection; +import java.util.*; /** * 项目Service业务层处理 @@ -80,6 +80,9 @@ public class MerTaskServiceImpl extends BaseService implements IMerTaskService @Resource private final IXyjService xyjService; + @Resource + private final SysThirdPlatformMapper sysThirdPlatformMapper; + /** * 查询项目 */ @@ -365,14 +368,22 @@ public class MerTaskServiceImpl extends BaseService implements IMerTaskService } } } - boolean flag = false; - if(false){ - BusMerchant merchant = new BusMerchant(); - BeanUtils.copyProperties(merchant, bo); + boolean flag; + + + SysSyd sysSyd = sysSydMapper.selectSysSydById(add.getSydId()); + + + SysThirdPlatform sysThirdPlatform = sysThirdPlatformMapper.selectThirdAppInfo(sysSyd.getId()); + + + if(Objects.nonNull(sysThirdPlatform) && sysThirdPlatform.getPlatformName().equals("薪遇见")){ + BusMerchant merchant = busMerchantMapper.selectById(bo.getMerId()); flag = xyjService.createProjectTask(add, merchant); - }else { - flag = this.insertTask(add) > 0; + }else{ + flag = baseMapper.insert(add) > 0; } + if (flag) { if(1 == add.getTaskStatus()){ merLog.setBusId(add.getId()); diff --git a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/taxRecords/controller/SwMonitorController.java b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/taxRecords/controller/SwMonitorController.java index 1863082..33fa38a 100644 --- a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/taxRecords/controller/SwMonitorController.java +++ b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/taxRecords/controller/SwMonitorController.java @@ -1,21 +1,16 @@ package org.dromara.payment.taxRecords.controller; +import cn.dev33.satoken.annotation.SaCheckPermission; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import org.dromara.common.core.domain.R; -import org.dromara.common.excel.utils.ExcelUtil; import org.dromara.common.log.annotation.Log; import org.dromara.common.log.enums.BusinessType; import org.dromara.common.mybatis.core.page.PageQuery; import org.dromara.common.mybatis.core.page.TableDataInfo; import org.dromara.common.web.core.BaseController; -import org.dromara.payment.common.domain.CommonSelConditionEntity; -import org.dromara.payment.task.domain.MerTaskEnroll; import org.dromara.payment.task.domain.vo.MerTaskEnrollVo; -import org.dromara.payment.taxRecords.domain.bo.MerTaxRecordsDetailBo; import org.dromara.payment.taxRecords.domain.bo.SwMonitorBo; -import org.dromara.payment.taxRecords.domain.vo.MerTaxRecordsDetailVo; -import org.dromara.payment.taxRecords.domain.vo.MerTaxRecordsVo; import org.dromara.payment.taxRecords.domain.vo.SwMonitorVo; import org.dromara.payment.taxRecords.service.ISwMonitorService; import org.springframework.validation.annotation.Validated; @@ -23,9 +18,8 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; - import javax.annotation.Resource; -import java.util.List; + /** * @author sunzexing @@ -69,7 +63,7 @@ public class SwMonitorController extends BaseController { /** * 导出发票 */ -// @SaCheckPermission("taxRecords:taxRecordsDetail:export") + // @SaCheckPermission("taxRecords:taxRecordsDetail:export") @Log(title = "下载发票", businessType = BusinessType.EXPORT) @PostMapping("/exportInv") public void export(String bo, HttpServletResponse response)throws Exception { diff --git a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/taxRecords/service/impl/MerTaxRecordsServiceImpl.java b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/taxRecords/service/impl/MerTaxRecordsServiceImpl.java index 8b0df58..24c4565 100644 --- a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/taxRecords/service/impl/MerTaxRecordsServiceImpl.java +++ b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/taxRecords/service/impl/MerTaxRecordsServiceImpl.java @@ -18,6 +18,8 @@ import org.dromara.payment.merchant.domain.BusMerchant; import org.dromara.payment.merchant.mapper.BusMerchantMapper; import org.dromara.payment.sys.domain.SysSyd; import org.dromara.payment.sys.mapper.SysSydMapper; +import org.dromara.payment.thirdPlatform.base.domain.SysThirdPlatform; +import org.dromara.payment.thirdPlatform.base.mapper.SysThirdPlatformMapper; import org.dromara.payment.thirdPlatform.xinyujian.service.IXyjService; import org.springframework.stereotype.Service; import org.dromara.payment.taxRecords.domain.bo.MerTaxRecordsBo; @@ -27,10 +29,7 @@ import org.dromara.payment.taxRecords.mapper.MerTaxRecordsMapper; import org.dromara.payment.taxRecords.service.IMerTaxRecordsService; import javax.annotation.Resource; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Collection; +import java.util.*; /** * 完税证明Service业务层处理 @@ -54,6 +53,9 @@ public class MerTaxRecordsServiceImpl implements IMerTaxRecordsService { @Resource private BusMerchantMapper merchantMapper; + @Resource + private SysThirdPlatformMapper sysThirdPlatformMapper; + /** * 查询完税证明 */ @@ -125,7 +127,8 @@ public class MerTaxRecordsServiceImpl implements IMerTaxRecordsService { SysSyd sysSyd = sydMapper.selectSysSydById(add.getSydId()); BusMerchant busMerchant = merchantMapper.selectById(add.getMerId()); boolean flag; - if(false) { + SysThirdPlatform sysThirdPlatform = sysThirdPlatformMapper.selectThirdAppInfo(sysSyd.getThreeId()); + if(Objects.nonNull(sysThirdPlatform) && sysThirdPlatform.getPlatformName().equals("薪遇见")){ flag = xyjService.taxpayDownLoad(busMerchant,sysSyd,bo.getMonth()); }else { flag = baseMapper.insert(add) > 0; diff --git a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/taxRecords/service/impl/SwMonitorServiceImpl.java b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/taxRecords/service/impl/SwMonitorServiceImpl.java index 0716c19..303ec45 100644 --- a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/taxRecords/service/impl/SwMonitorServiceImpl.java +++ b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/taxRecords/service/impl/SwMonitorServiceImpl.java @@ -89,9 +89,11 @@ public class SwMonitorServiceImpl extends BaseService implements ISwMonitorServi lqw.eq("t.pay_status",1); lqw.eq(bo.getBusType() !=null,"t.bus_type",bo.getBusType()); int busRole = LoginHelper.getBusRole(); - if(busRole == BusRole.OPERATOR.getId()){ - lqw.eq("t.op_id",LoginHelper.getBusId()); + if(busRole != BusRole.PLATFORM.getId()){ + // lqw.eq("t.op_id",LoginHelper.getBusId()); + lqw.eq("t1.id",LoginHelper.getBusId ()); } + lqw.orderByDesc("t.create_time"); Page result = baseMapper.selSwMonitorList(pageQuery.build(), lqw); return TableDataInfo.build(result); diff --git a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/thirdPlatform/base/controller/SysThirdPlatformController.java b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/thirdPlatform/base/controller/SysThirdPlatformController.java index df96345..f9d8b50 100644 --- a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/thirdPlatform/base/controller/SysThirdPlatformController.java +++ b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/thirdPlatform/base/controller/SysThirdPlatformController.java @@ -18,12 +18,10 @@ public class SysThirdPlatformController { private SysThirdPlatformMapper sysThirdPlatformMapper; @GetMapping("/list") - public List list(){ + public List list(){ List sysThirdPlatforms = this.sysThirdPlatformMapper.selectList(); - List platformNames = sysThirdPlatforms.stream() - .map(SysThirdPlatform::getPlatformName) - .collect(Collectors.toList()); - return platformNames; + + return sysThirdPlatforms; } } diff --git a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/thirdPlatform/utils/PaymentConfigUtils.java b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/thirdPlatform/utils/PaymentConfigUtils.java index e783829..12e4e18 100644 --- a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/thirdPlatform/utils/PaymentConfigUtils.java +++ b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/thirdPlatform/utils/PaymentConfigUtils.java @@ -1,7 +1,6 @@ package org.dromara.payment.thirdPlatform.utils; - import cn.hutool.core.lang.Assert; import org.dromara.payment.thirdPlatform.xinyujian.config.XinyujianAccountBean; import org.dromara.payment.thirdPlatform.xinyujian.config.XinyujianConfig; @@ -18,30 +17,27 @@ import java.util.Map; @Service public class PaymentConfigUtils { - private static final Logger log = LoggerFactory.getLogger(PaymentConfigUtils.class); + private static final Logger log = LoggerFactory.getLogger(PaymentConfigUtils.class); + @Resource + private XinyujianConfig xinyujianConfig; - @Resource - private XinyujianConfig xinyujianConfig; - - - - public XinyujianAccountBean getXyjAccountBean(Long paymentConfigId) { - XinyujianAccountBean accountBean = (XinyujianAccountBean)this.xinyujianConfig.getAccounts().get(paymentConfigId); - Assert.isNull(accountBean, "益零工配置不存在"); - accountBean.setPaymentConfigId(paymentConfigId); - return accountBean; - } - - public List listXyjAccountBeans() { - List list = new ArrayList<>(); - Map map = this.xinyujianConfig.getAccounts(); - for (Long paymentConfigId : map.keySet()) { - XinyujianAccountBean bean = map.get(paymentConfigId); - bean.setPaymentConfigId(paymentConfigId); - list.add(bean); + public XinyujianAccountBean getXyjAccountBean(Long paymentConfigId) { + XinyujianAccountBean accountBean = (XinyujianAccountBean) this.xinyujianConfig.getAccounts().get(paymentConfigId); + Assert.isNull(accountBean, "益零工配置不存在"); + accountBean.setPaymentConfigId(paymentConfigId); + return accountBean; + } + + public List listXyjAccountBeans() { + List list = new ArrayList<>(); + Map map = this.xinyujianConfig.getAccounts(); + for (Long paymentConfigId : map.keySet()) { + XinyujianAccountBean bean = map.get(paymentConfigId); + bean.setPaymentConfigId(paymentConfigId); + list.add(bean); + } + return list; } - return list; - } } diff --git a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/thirdPlatform/xinyujian/config/XyjConfig.java b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/thirdPlatform/xinyujian/config/XyjConfig.java index afc70f3..dde5827 100644 --- a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/thirdPlatform/xinyujian/config/XyjConfig.java +++ b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/thirdPlatform/xinyujian/config/XyjConfig.java @@ -24,4 +24,6 @@ public class XyjConfig { private String baseUrl; + + } diff --git a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/thirdPlatform/xinyujian/domain/vo/ThirdItemTaskTypesVo.java b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/thirdPlatform/xinyujian/domain/vo/ThirdItemTaskTypesVo.java index 982d540..ab750ca 100644 --- a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/thirdPlatform/xinyujian/domain/vo/ThirdItemTaskTypesVo.java +++ b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/thirdPlatform/xinyujian/domain/vo/ThirdItemTaskTypesVo.java @@ -96,5 +96,8 @@ public class ThirdItemTaskTypesVo implements Serializable { @ExcelProperty(value = "是否可用 1是,0否") private Integer isUse; + @ExcelProperty(value = "") + private Long createDept; + } diff --git a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/thirdPlatform/xinyujian/service/XyjServerImpl.java b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/thirdPlatform/xinyujian/service/XyjServerImpl.java index cec7765..2a3f89e 100644 --- a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/thirdPlatform/xinyujian/service/XyjServerImpl.java +++ b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/thirdPlatform/xinyujian/service/XyjServerImpl.java @@ -6,8 +6,10 @@ import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONObject; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.esotericsoftware.kryo.kryo5.minlog.Log; +import com.google.gson.Gson; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.RandomStringUtils; @@ -52,6 +54,7 @@ import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; import java.math.BigDecimal; +import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; @@ -118,43 +121,54 @@ public class XyjServerImpl extends BaseService implements IXyjService { //企业名称 data.put("companyName", busMerchant.getName()); //企业社会统一信用代码 - data.put("creditCode", busMerchant.getName()); + data.put("creditCode", busMerchant.getNsrSbh()); //企业地址 - data.put("companyAddr", busMerchant.getName()); + data.put("companyAddr", busMerchant.getAddress()); //企业电话 - data.put("companyMobile", busMerchant.getName()); + data.put("companyMobile", busMerchant.getFrSjh()); //灵活用工场景编码 - data.put("serviceTypeId", busMerchant.getName()); + data.put("serviceTypeId", busMerchant.getIndustryCode()); + + //注册资本(单位:万 //元) - data.put("registeredAssets", busMerchant.getName()); + data.put("registeredAssets", busMerchant.getRegisteredAssets()); + + Date date = busMerchant.getRegdate(); // 或从其他来源获取Date对象 + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); + String formattedDate = sdf.format(date); + + //注册时间 - data.put("regdate", busMerchant.getName()); + data.put("regdate", formattedDate); + + + //企业开户行 - data.put("bankName", busMerchant.getName()); + data.put("bankName", busMerchant.getKhh()); //企业银行账户 - data.put("bankAcct", busMerchant.getName()); + data.put("bankAcct", busMerchant.getBankCard()); //企业联系人 - data.put("contactName", busMerchant.getName()); + data.put("contactName", busMerchant.getWtr()); //企业联系人手机号 - data.put("contactMobile", busMerchant.getName()); + data.put("contactMobile", busMerchant.getWtrSjh()); //法人姓名 - data.put("legalPersonName", busMerchant.getName()); + data.put("legalPersonName", busMerchant.getFr()); //法人身份证号 - data.put("idNo", busMerchant.getName()); + data.put("idNo", busMerchant.getFrSfz()); //营业执照照片 - data.put("businessLicense", busMerchant.getName()); + data.put("businessLicense", busMerchant.getYyzz()); //基本存款账号信息表 data.put("accountLicense", busMerchant.getName()); //办公场地门头照 //注册时间小于6个月必传 - data.put("houseNumberImg", busMerchant.getName()); + data.put("houseNumberImg", busMerchant.getMtqjzPic()); //办公场地场所照 //注册时间小于6个月必传 - data.put("officeSpaceImg", busMerchant.getName()); + data.put("officeSpaceImg", busMerchant.getBgzp1Pic()); //办公场地前台照 //注册时间小于6个月必传 - data.put("receptionImg", busMerchant.getName()); + data.put("receptionImg", busMerchant.getBgzp2Pic()); //入驻回调通知URL data.put("callBackUrl", busMerchant.getName()); //来账通知回调地址 @@ -168,10 +182,16 @@ public class XyjServerImpl extends BaseService implements IXyjService { log.info("企业注入新遇见平台,参数:{}", paramStr); log.info("企业注入新遇见平台,加密后信息:{}",sendMsg); String result = HttpUtil.post(config.getBaseUrl()+"/interface/company/register",sendMsg); + log.info("企业注入新遇见平台,返回信息:{}",result); BaseDataVo vo = decryptAndVerify(config.getOtherPublicKey(), config.getMyPrivateKey(), result); log.info("企业注入新遇见平台,result:{}", JSON.toJSONString(vo)); - JSONObject resultJson = (JSONObject)vo.getData(); + + log.info("企业注入新遇见平台,返回信息:{}", vo.getData()); + + + + JSONObject resultJson = JSON.parseObject(vo.getData().toString()); if(resultJson != null){ String status = resultJson.getString("status"); String businessId = resultJson.getString("businessId"); @@ -181,7 +201,7 @@ public class XyjServerImpl extends BaseService implements IXyjService { busMerchant1.setBusinessStatus(status); this.busMerchantMapper.updateById(busMerchant1); if("11".equals(status)){ - this.addMerchantResultSel(businessId, sydId); + this. addMerchantResultSel(businessId, sydId); } } @@ -189,35 +209,40 @@ public class XyjServerImpl extends BaseService implements IXyjService { } - - - @Override public boolean createProjectTask(MerTask task,BusMerchant merchant) { XyjConfig config = this.getXyjAppInfo(task.getSydId()); + this.queryPark(merchant.getBusinessId(), task.getSydId()); SysSyd sysSyd = sysSydMapper.selectSysSydById(task.getSydId()); JSONObject jsonObject = new JSONObject(); jsonObject.put("businessId",merchant.getBusinessId()); jsonObject.put("parkId",sysSyd.getParkId()); - jsonObject.put("itemTitle",task.getName()); - jsonObject.put("itemTypeId",task.getItemTypeId()); jsonObject.put("commissionType","04"); jsonObject.put("commissionLow",task.getCost()); jsonObject.put("commissionHigh",task.getCostMax()); jsonObject.put("itemPeriod","04"); - jsonObject.put("checkRequire","02"); + jsonObject.put("checkRequire","01"); + jsonObject.put("checkPeriod","1"); List taskObjectList = new ArrayList<>(); + + ThirdItemTaskTypes thirdItemTaskTypes = + thirdItemTaskTypesMapper.selectOne(new QueryWrapper().eq("business_id", merchant.getBusinessId()).eq("park_id", sysSyd.getParkId())); + jsonObject.put("itemTitle",task.getName()); + jsonObject.put("itemTypeId",thirdItemTaskTypes.getItemType()); JSONObject taskObject = new JSONObject(); taskObject.put("taskTitle",task.getName()); - taskObject.put("taskTypeId",task.getTaskTypeId()); + taskObject.put("taskTypeId",thirdItemTaskTypes.getTaskType()); taskObject.put("commissionType","01"); taskObject.put("commissionLow",task.getCost()); taskObject.put("commissionHigh",task.getCostMax()); taskObject.put("taskPeriod","04"); - taskObject.put("checkRequire","02"); + taskObject.put("checkRequire","01"); + taskObject.put("checkPeriod","1"); + taskObject.put("recruitType","2"); taskObject.put("deliveryType","2"); taskObjectList.add(taskObject); jsonObject.put("taskInfo",taskObjectList); + log.info("企业注入新遇见平台创建项目和任务,参数:{}", jsonObject.toString()); String sendMsg = createSendMsg(config.getAppid(), config.getOtherPublicKey(), config.getMyPrivateKey(),JSON.toJSONString(jsonObject)); log.info("企业注入新遇见平台创建项目和任务,加密后信息:{}",sendMsg); String result = HttpUtil.post(config.getBaseUrl()+"/interface/item/create",sendMsg); @@ -227,7 +252,8 @@ public class XyjServerImpl extends BaseService implements IXyjService { if(!this.checkSuccess(vo)){ throw new RuntimeException("创建项目失败"); } - JSONObject resultJson = (JSONObject)vo.getData(); + + JSONObject resultJson = JSON.parseObject(vo.getData().toString()); if(resultJson != null){ String itemId = resultJson.getString("itemId"); JSONArray taskInfo = resultJson.getJSONArray("taskInfo"); @@ -721,9 +747,19 @@ public class XyjServerImpl extends BaseService implements IXyjService { String result = HttpUtil.post(config.getBaseUrl()+"/interface/baseInfo/queryParks",sendMsg); BaseDataVo bdvo = this.decryptAndVerify(config.getOtherPublicKey(), config.getMyPrivateKey(), result); log.info("查询企业[{}]的税源地信息结果:{}", businessId, JSON.toJSONString(bdvo)); - JSONObject dataObj = (JSONObject)bdvo.getData(); - if(dataObj != null){ + String jsonString = JSON.toJSONString(bdvo); + JSONObject Obj = JSON.parseObject(jsonString); + String dataStr = Obj.getString("data"); + JSONArray dataArray = JSON.parseArray(dataStr); + if(dataArray != null){ + for(int i=0;i itemTaskList = this.thirdItemTaskTypesService.queryList(itemTaskTypesBo); @@ -737,6 +773,7 @@ public class XyjServerImpl extends BaseService implements IXyjService { itemType.forEach(item->{ JSONObject itemObj = (JSONObject)item; ThirdItemTaskTypes tTaskTypes = new ThirdItemTaskTypes(); + tTaskTypes.setBusinessId(businessId); tTaskTypes.setParkId(Long.valueOf(parkId)); String itemTypeId = itemObj.getString("itemTypeId");//项目类型编号 String itemTypeName = itemObj.getString("itemTypeName");//项目类型名称 @@ -747,6 +784,7 @@ public class XyjServerImpl extends BaseService implements IXyjService { JSONArray taskType = itemObj.getJSONArray("taskType");//任务类型 if(taskType != null){ taskType.forEach(task->{ + log.info("查询企业的税源地信息结果task:{}", JSON.toJSONString(task)); JSONObject taskObj = (JSONObject)task; String taskTypeId = taskObj.getString("taskTypeId");//任务类型编号 String taskTypeName = taskObj.getString("taskTypeName");//任务类型名称 @@ -759,10 +797,13 @@ public class XyjServerImpl extends BaseService implements IXyjService { itemTaskTypess.add(tTaskTypes); }); if(!itemTaskTypess.isEmpty()){ + log.info("查询企业[{}]的税源地信息结果taskType:{}", businessId, JSON.toJSONString(itemTaskTypess)); this.thirdItemTaskTypesMapper.insertBatch(itemTaskTypess); } } - } + } + } + return true; } diff --git a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/thirdPlatform/xinyujian/sign/BaseDataVo.java b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/thirdPlatform/xinyujian/sign/BaseDataVo.java index 169b3c2..a347b81 100644 --- a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/thirdPlatform/xinyujian/sign/BaseDataVo.java +++ b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/thirdPlatform/xinyujian/sign/BaseDataVo.java @@ -58,13 +58,4 @@ public class BaseDataVo implements Serializable { */ private Boolean verify; - - public static void main(String[] args) { - - - - - - - } } diff --git a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/util/TaxCalculatorUtils.java b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/util/TaxCalculatorUtils.java new file mode 100644 index 0000000..40c98f6 --- /dev/null +++ b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/util/TaxCalculatorUtils.java @@ -0,0 +1,153 @@ +package org.dromara.payment.util; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.ArrayList; +import java.util.List; + +/** + * 个人所得税计算工具类(综合所得年度税率表版) + * 支持根据累计收入、月份、已预缴税额计算当月应预扣税额 + */ +public class TaxCalculatorUtils { + + // 每月减除费用(5000元,年度累计为 5000 * n) + private static final BigDecimal MONTHLY_DEDUCTION = new BigDecimal("5000"); + // 收入减除比例(20%,适用于劳务报酬等综合所得) + private static final BigDecimal INCOME_DEDUCTION_RATIO = new BigDecimal("0.2"); + + // 定义年度税率表(数组形式,每个元素包含:级距上限、税率、速算扣除数) + private static final List ANNUAL_TAX_RATES = new ArrayList<>(); + static { + ANNUAL_TAX_RATES.add(new TaxRate(new BigDecimal("36000"), new BigDecimal("0.03"), new BigDecimal("0"))); + ANNUAL_TAX_RATES.add(new TaxRate(new BigDecimal("144000"), new BigDecimal("0.10"), new BigDecimal("2520"))); + ANNUAL_TAX_RATES.add(new TaxRate(new BigDecimal("300000"), new BigDecimal("0.20"), new BigDecimal("16920"))); + ANNUAL_TAX_RATES.add(new TaxRate(new BigDecimal("420000"), new BigDecimal("0.25"), new BigDecimal("31920"))); + ANNUAL_TAX_RATES.add(new TaxRate(new BigDecimal("660000"), new BigDecimal("0.30"), new BigDecimal("52920"))); + ANNUAL_TAX_RATES.add(new TaxRate(new BigDecimal("960000"), new BigDecimal("0.35"), new BigDecimal("85920"))); + ANNUAL_TAX_RATES.add(new TaxRate(null, new BigDecimal("0.45"), new BigDecimal("181920"))); // 超过960000元的部分 + } + + /** + * 计算当月应预扣税额 + * + * @param cumulativeIncome 累计收入(税前) + * @param taxMonth 当前月份(1-12) + * @param cumulativePrepaidTax 累计已预缴税额 + * @return 当月应预扣税额(BigDecimal,保留2位小数) + */ + public static BigDecimal calculateMonthlyWithholdingTax( + BigDecimal cumulativeIncome, + int taxMonth, + BigDecimal cumulativePrepaidTax + ) { + // 1. 参数校验 + validateParams(cumulativeIncome, taxMonth, cumulativePrepaidTax); + + // 2. 计算累计减除费用:5000 * n(n为月份) + BigDecimal cumulativeDeduction = MONTHLY_DEDUCTION.multiply(new BigDecimal(taxMonth)); + + // 3. 计算应纳税所得额:累计收入 × (1 - 20%) - 累计减除费用 + BigDecimal taxableIncome = cumulativeIncome + .multiply(BigDecimal.ONE.subtract(INCOME_DEDUCTION_RATIO)) + .subtract(cumulativeDeduction) + .setScale(2, RoundingMode.HALF_UP); // 保留2位小数 + + // 4. 如果应纳税所得额≤0,当月无需缴税 + if (taxableIncome.compareTo(BigDecimal.ZERO) <= 0) { + return BigDecimal.ZERO; + } + + // 5. 匹配税率和速算扣除数 + TaxRate matchedRate = matchTaxRate(taxableIncome); + + // 6. 计算当月应预扣税额:应纳税所得额 × 税率 - 速算扣除数 - 已预缴税额 + BigDecimal withholdingTax = taxableIncome + .multiply(matchedRate.rate) + .subtract(matchedRate.quickDeduction) + .subtract(cumulativePrepaidTax) + .setScale(2, RoundingMode.HALF_UP); // 保留2位小数 + + // 7. 确保税额≥0(避免出现负数) + return withholdingTax.max(BigDecimal.ZERO); + } + + /** + * 匹配税率(根据应纳税所得额找到对应的税率和速算扣除数) + * + * @param taxableIncome 应纳税所得额 + * @return 匹配的税率信息(TaxRate对象) + */ + private static TaxRate matchTaxRate(BigDecimal taxableIncome) { + for (TaxRate rate : ANNUAL_TAX_RATES) { + if (rate.upperLimit == null) { // 最后一级(超过960000元的部分) + return rate; + } + if (taxableIncome.compareTo(rate.upperLimit) <= 0) { + return rate; + } + } + return ANNUAL_TAX_RATES.get(ANNUAL_TAX_RATES.size() - 1); // 兜底(理论上不会触发) + } + + /** + * 参数校验 + * + * @param cumulativeIncome 累计收入 + * @param taxMonth 当前月份 + * @param cumulativePrepaidTax 累计已预缴税额 + */ + private static void validateParams( + BigDecimal cumulativeIncome, + int taxMonth, + BigDecimal cumulativePrepaidTax + ) { + if (cumulativeIncome == null || cumulativeIncome.compareTo(BigDecimal.ZERO) < 0) { + throw new IllegalArgumentException("累计收入不能为负数或空!"); + } + if (taxMonth < 1 || taxMonth > 12) { + throw new IllegalArgumentException("月份必须在1-12之间!"); + } + if (cumulativePrepaidTax == null || cumulativePrepaidTax.compareTo(BigDecimal.ZERO) < 0) { + throw new IllegalArgumentException("累计已预缴税额不能为负数或空!"); + } + } + + /** + * 税率实体类(内部静态类) + */ + private static class TaxRate { + private final BigDecimal upperLimit; // 级距上限(最后一级为null) + private final BigDecimal rate; // 税率 + private final BigDecimal quickDeduction; // 速算扣除数 + + public TaxRate(BigDecimal upperLimit, BigDecimal rate, BigDecimal quickDeduction) { + this.upperLimit = upperLimit; + this.rate = rate; + this.quickDeduction = quickDeduction; + } + } + + // 示例:测试工具类 + public static void main(String[] args) { + // 测试用例1:累计收入 100000元,第7个月,已预缴税额 1500元 + BigDecimal cumulativeIncome = new BigDecimal("100000"); + int taxMonth = 7; + BigDecimal cumulativePrepaidTax = new BigDecimal("1500"); + + BigDecimal tax = calculateMonthlyWithholdingTax(cumulativeIncome, taxMonth, cumulativePrepaidTax); + System.out.println("测试用例1:"); + System.out.println("累计收入:" + cumulativeIncome + "元,月份:" + taxMonth + ",已预缴税额:" + cumulativePrepaidTax + "元"); + System.out.println("当月应预扣税额:" + tax + "元\n"); + + // 测试用例2:累计收入 400000元,第10个月,已预缴税额 30000元 + cumulativeIncome = new BigDecimal("400000"); + taxMonth = 10; + cumulativePrepaidTax = new BigDecimal("30000"); + + tax = calculateMonthlyWithholdingTax(cumulativeIncome, taxMonth, cumulativePrepaidTax); + System.out.println("测试用例2:"); + System.out.println("累计收入:" + cumulativeIncome + "元,月份:" + taxMonth + ",已预缴税额:" + cumulativePrepaidTax + "元"); + System.out.println("当月应预扣税额:" + tax + "元"); + } +} diff --git a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/util/ValidatorUtils.java b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/util/ValidatorUtils.java new file mode 100644 index 0000000..c46e6c4 --- /dev/null +++ b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/util/ValidatorUtils.java @@ -0,0 +1,39 @@ +package org.dromara.payment.util; + + + + + +import org.apache.commons.lang3.StringUtils; +import org.dromara.payment.exception.CustomException; + +import javax.validation.ConstraintViolation; +import javax.validation.Validation; +import javax.validation.Validator; + +import java.util.Set; + +public class ValidatorUtils { + private static Validator validator = Validation.buildDefaultValidatorFactory().getValidator(); + + public static void validateEntity(Object object, Class... groups) throws CustomException { + Set> constraintViolations = validator.validate(object, groups); + if (!constraintViolations.isEmpty()) { + StringBuilder msg = new StringBuilder(); + for (ConstraintViolation constraint : constraintViolations) + msg.append(";").append(constraint.getMessage()); + throw new CustomException(msg.substring(1)); + } + } + + public static String validateEntityNotError(Object object, Class... groups) throws CustomException { + Set> constraintViolations = validator.validate(object, groups); + StringBuilder msg = new StringBuilder(); + if (!constraintViolations.isEmpty()) { + msg = new StringBuilder(); + for (ConstraintViolation constraint : constraintViolations) + msg.append(";").append(constraint.getMessage()); + } + return StringUtils.isNotBlank(msg) ? String.valueOf(msg.substring(1)) : ""; + } +} diff --git a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/worker/controller/UserWorkerBusinessController.java b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/worker/controller/UserWorkerBusinessController.java index e819a0b..9229fcb 100644 --- a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/worker/controller/UserWorkerBusinessController.java +++ b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/worker/controller/UserWorkerBusinessController.java @@ -43,7 +43,8 @@ import javax.annotation.Resource; @RequiredArgsConstructor @RestController @RequestMapping("/workerBusiness") -public class UserWorkerBusinessController extends BaseController { +public class +UserWorkerBusinessController extends BaseController { private final IUserWorkerBusinessService userWorkerBusinessService; diff --git a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/worker/controller/UserWorkerController.java b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/worker/controller/UserWorkerController.java index 1fd1088..45c93c2 100644 --- a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/worker/controller/UserWorkerController.java +++ b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/worker/controller/UserWorkerController.java @@ -40,7 +40,7 @@ public class UserWorkerController extends BaseController { /** * 查询自雇者列表 */ - @SaCheckPermission("userWorker:Userworker:list") + // @SaCheckPermission("userWorker:Userworker:list") @GetMapping("/list") public TableDataInfo list(UserWorkerBo bo, PageQuery pageQuery) { return userWorkerService.queryPageList(bo, pageQuery); @@ -49,7 +49,7 @@ public class UserWorkerController extends BaseController { /** * 导出自雇者列表 */ - @SaCheckPermission("userWorker:Userworker:export") +// @SaCheckPermission("userWorker:Userworker:export") @Log(title = "自雇者", businessType = BusinessType.EXPORT) @PostMapping("/export") public void export(UserWorkerBo bo, HttpServletResponse response) { @@ -62,7 +62,7 @@ public class UserWorkerController extends BaseController { * * @param id 主键 */ - @SaCheckPermission("userWorker:Userworker:query") + // @SaCheckPermission("userWorker:Userworker:query") @GetMapping("/{id}") public R getInfo(@NotNull(message = "主键不能为空") @PathVariable Long id) { @@ -72,7 +72,7 @@ public class UserWorkerController extends BaseController { /** * 新增自雇者 */ - @SaCheckPermission("userWorker:Userworker:add") + // @SaCheckPermission("userWorker:Userworker:add") @Log(title = "自雇者", businessType = BusinessType.INSERT) @RepeatSubmit() @PostMapping() @@ -83,7 +83,7 @@ public class UserWorkerController extends BaseController { /** * 修改自雇者 */ - @SaCheckPermission("userWorker:Userworker:edit") + // @SaCheckPermission("userWorker:Userworker:edit") @Log(title = "自雇者", businessType = BusinessType.UPDATE) @RepeatSubmit() @PutMapping() @@ -96,7 +96,7 @@ public class UserWorkerController extends BaseController { * * @param ids 主键串 */ - @SaCheckPermission("userWorker:Userworker:remove") +// @SaCheckPermission("userWorker:Userworker:remove") @Log(title = "自雇者", businessType = BusinessType.DELETE) @DeleteMapping("/{ids}") public R remove(@NotEmpty(message = "主键不能为空") diff --git a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/worker/service/IUserWorkerService.java b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/worker/service/IUserWorkerService.java index 1a83f91..99eafc6 100644 --- a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/worker/service/IUserWorkerService.java +++ b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/worker/service/IUserWorkerService.java @@ -22,6 +22,11 @@ public interface IUserWorkerService { */ UserWorkerVo queryById(Long id); + /** + * 查询自雇者 + */ + UserWorkerVo queryByName(String name); + /** * 查询自雇者列表 */ diff --git a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/worker/service/impl/UserWorkerBusinessServiceImpl.java b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/worker/service/impl/UserWorkerBusinessServiceImpl.java index 5058b24..be8556f 100644 --- a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/worker/service/impl/UserWorkerBusinessServiceImpl.java +++ b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/worker/service/impl/UserWorkerBusinessServiceImpl.java @@ -282,13 +282,10 @@ public class UserWorkerBusinessServiceImpl extends BaseService implements IUserW this.invalidationParamsException("服务商id不能为空"); } - - if(StringUtils.isEmpty(sydNo)){ this.invalidationParamsException("服务商编码不能为空"); } - if(StringUtils.isEmpty(code)){ this.invalidationParamsException("域名不能为空"); } @@ -308,10 +305,7 @@ public class UserWorkerBusinessServiceImpl extends BaseService implements IUserW } - try{ - - BusMerchantBo bo1 = new BusMerchantBo(); bo1.setNo(merNo); BusOperAndAgentAndMer busOperAndAgentAndMer = this.busMerchantMapper.selOpAndAgentInfosByMerInfo(bo1); diff --git a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/worker/service/impl/UserWorkerServiceImpl.java b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/worker/service/impl/UserWorkerServiceImpl.java index f17de6e..93d5734 100644 --- a/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/worker/service/impl/UserWorkerServiceImpl.java +++ b/ruoyi-modules/ruoyi-payment/src/main/java/org/dromara/payment/worker/service/impl/UserWorkerServiceImpl.java @@ -40,6 +40,16 @@ public class UserWorkerServiceImpl implements IUserWorkerService { return baseMapper.selectVoById(id); } + /** + * 查询自雇者 + * + * @param name + */ + @Override + public UserWorkerVo queryByName(String name) { + return baseMapper.selectVoOne(Wrappers.query().eq("name", name)); + } + /** * 查询自雇者列表 */ diff --git a/ruoyi-modules/ruoyi-payment/src/main/resources/application.yml b/ruoyi-modules/ruoyi-payment/src/main/resources/application.yml index 8ea15bf..fe6fea3 100644 --- a/ruoyi-modules/ruoyi-payment/src/main/resources/application.yml +++ b/ruoyi-modules/ruoyi-payment/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-modules/ruoyi-payment/src/main/resources/mapper/payment/earlyWarn/LgEarlyWarnMapper.xml b/ruoyi-modules/ruoyi-payment/src/main/resources/mapper/payment/earlyWarn/LgEarlyWarnMapper.xml new file mode 100644 index 0000000..cb11552 --- /dev/null +++ b/ruoyi-modules/ruoyi-payment/src/main/resources/mapper/payment/earlyWarn/LgEarlyWarnMapper.xml @@ -0,0 +1,7 @@ + + + + + diff --git a/ruoyi-modules/ruoyi-payment/src/main/resources/mapper/payment/operator/BusOperatorProductMapper.xml b/ruoyi-modules/ruoyi-payment/src/main/resources/mapper/payment/operator/BusOperatorProductMapper.xml index 49baccf..84e1f96 100644 --- a/ruoyi-modules/ruoyi-payment/src/main/resources/mapper/payment/operator/BusOperatorProductMapper.xml +++ b/ruoyi-modules/ruoyi-payment/src/main/resources/mapper/payment/operator/BusOperatorProductMapper.xml @@ -84,10 +84,26 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + + + + + + - - update bus_channel_product set cost_service_charge = #{item.cost_service_charge} where op_id =#{item.op_id} and pro_id= #{item.pro_id} and up_is_op = 1 + UPDATE bus_channel_product + SET cost_service_charge = CASE + + WHEN op_id = #{item.op_id} AND pro_id = #{item.pro_id} AND up_is_op = 1 + THEN #{item.cost_service_charge} + ELSE cost_service_charge + END + WHERE (op_id, pro_id, up_is_op) IN ( + + (#{item.op_id}, #{item.pro_id}, 1) + + ) diff --git a/ruoyi-modules/ruoyi-payment/src/main/resources/mapper/payment/syd/SysSydMapper.xml b/ruoyi-modules/ruoyi-payment/src/main/resources/mapper/payment/syd/SysSydMapper.xml index 59f9786..f78d7ed 100644 --- a/ruoyi-modules/ruoyi-payment/src/main/resources/mapper/payment/syd/SysSydMapper.xml +++ b/ruoyi-modules/ruoyi-payment/src/main/resources/mapper/payment/syd/SysSydMapper.xml @@ -39,7 +39,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" - select id, gsmc, abridge, kfd, kfh, bank_type, bank_card, jbsl, quote_num, add_time, add_by, editer, province_id, province_name, city_id, city_name, county_id, county_name, nsr_sbh, fr, fr_cardid, fr_phone, con_name, con_phone, con_cardid, con_email, address, business from sys_syd + select id, gsmc, abridge, kfd, kfh, bank_type, bank_card, jbsl, quote_num, add_time, add_by, editer, province_id, province_name, city_id, city_name, county_id, county_name, nsr_sbh, fr, fr_cardid, fr_phone, con_name, con_phone, con_cardid, con_email, address, business,park_id,three_id from sys_syd @@ -101,6 +101,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" limit_amount = #{limitAmount}, sign_mode = #{signMode}, sign_valid_time = #{signValidTime}, + park_id = #{parkId}, + three_id = #{threeId}, where id = #{id} diff --git a/ruoyi-modules/ruoyi-payment/src/main/resources/mapper/payment/thirdPlatform/SysThirdPlatformMapper.xml b/ruoyi-modules/ruoyi-payment/src/main/resources/mapper/payment/thirdPlatform/SysThirdPlatformMapper.xml index 3c0d4d8..d8ac496 100644 --- a/ruoyi-modules/ruoyi-payment/src/main/resources/mapper/payment/thirdPlatform/SysThirdPlatformMapper.xml +++ b/ruoyi-modules/ruoyi-payment/src/main/resources/mapper/payment/thirdPlatform/SysThirdPlatformMapper.xml @@ -6,7 +6,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" diff --git a/ruoyi-modules/ruoyi-resource/src/main/resources/application.yml b/ruoyi-modules/ruoyi-resource/src/main/resources/application.yml index 92e3701..5e8d4d5 100644 --- a/ruoyi-modules/ruoyi-resource/src/main/resources/application.yml +++ b/ruoyi-modules/ruoyi-resource/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-modules/ruoyi-settlement/src/main/resources/application.yml b/ruoyi-modules/ruoyi-settlement/src/main/resources/application.yml index 8758f15..a8daf2a 100644 --- a/ruoyi-modules/ruoyi-settlement/src/main/resources/application.yml +++ b/ruoyi-modules/ruoyi-settlement/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-modules/ruoyi-system/src/main/resources/application.yml b/ruoyi-modules/ruoyi-system/src/main/resources/application.yml index 4000023..ef8a2ec 100644 --- a/ruoyi-modules/ruoyi-system/src/main/resources/application.yml +++ b/ruoyi-modules/ruoyi-system/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: