1、依赖升级:springboot 2.7.18 -> 3.4.5,springcloud 2021.x -> 2024.x ,其余包同步yudao升级;2、微服务配置简化;

This commit is contained in:
gaoqr
2025-07-22 11:39:54 +08:00
parent 799affcecb
commit 20085ffb9f
915 changed files with 3116 additions and 4561 deletions
+73 -132
View File
@@ -14,61 +14,56 @@
<properties> <properties>
<revision>2.0.0-jdk17-snapshot</revision> <revision>2.0.0-jdk17-snapshot</revision>
<flatten-maven-plugin.version>1.2.4</flatten-maven-plugin.version> <flatten-maven-plugin.version>1.6.0</flatten-maven-plugin.version>
<!-- 统一依赖管理 --> <!-- 统一依赖管理 -->
<spring.boot.version>2.7.18</spring.boot.version> <spring.boot.version>3.4.5</spring.boot.version>
<spring.cloud.version>2021.0.5</spring.cloud.version> <spring.cloud.version>2024.0.1</spring.cloud.version>
<spring.cloud.alibaba.version>2021.0.4.0</spring.cloud.alibaba.version> <spring.cloud.alibaba.version>2023.0.3.2</spring.cloud.alibaba.version>
<!-- Web 相关 --> <!-- Web 相关 -->
<servlet.versoin>2.5</servlet.versoin> <springdoc.version>2.8.3</springdoc.version>
<springdoc.version>1.6.15</springdoc.version> <knife4j.version>4.6.0</knife4j.version>
<knife4j.version>4.3.0</knife4j.version>
<!-- DB 相关 --> <!-- DB 相关 -->
<druid.version>1.2.21</druid.version> <druid.version>1.2.24</druid.version>
<mybatis-plus.version>3.5.5</mybatis-plus.version> <mybatis.version>3.5.19</mybatis.version>
<mybatis-plus-generator.version>3.5.5</mybatis-plus-generator.version> <mybatis-plus.version>3.5.10.1</mybatis-plus.version>
<dynamic-datasource.version>4.3.0</dynamic-datasource.version> <dynamic-datasource.version>4.3.1</dynamic-datasource.version>
<mybatis-plus-join.version>1.4.10</mybatis-plus-join.version> <mybatis-plus-join.version>1.4.13</mybatis-plus-join.version>
<redisson.version>3.36.0</redisson.version> <!-- Spring Boot 2.X 最多使用 3.18.0 版本,否则会报 Tuple NoClassDefFoundError --> <redisson.version>3.41.0</redisson.version>
<dm8.jdbc.version>8.1.3.62</dm8.jdbc.version> <dm8.jdbc.version>8.1.3.62</dm8.jdbc.version>
<elasticsearch.version>8.8.1</elasticsearch.version> <elasticsearch-client.version>8.8.1</elasticsearch-client.version>
<jakarta-json.version>2.1.1</jakarta-json.version>
<!-- 消息队列 --> <!-- 消息队列 -->
<rocketmq-spring.version>2.2.3</rocketmq-spring.version> <rocketmq-spring.version>2.3.2</rocketmq-spring.version>
<!-- RPC 相关 --> <!-- RPC 相关 -->
<!-- Config 配置中心相关 --> <!-- Config 配置中心相关 -->
<apollo.version>1.9.2</apollo.version>
<!-- Job 定时任务相关 --> <!-- Job 定时任务相关 -->
<xxl-job.version>2.3.1</xxl-job.version> <xxl-job.version>2.4.0</xxl-job.version>
<!-- 服务保障相关 --> <!-- 服务保障相关 -->
<lock4j.version>2.2.7</lock4j.version> <lock4j.version>2.2.7</lock4j.version>
<resilience4j.version>1.7.1</resilience4j.version>
<!-- 监控相关 --> <!-- 监控相关 -->
<skywalking.version>8.12.0</skywalking.version> <skywalking.version>9.0.0</skywalking.version>
<spring-boot-admin.version>2.7.15</spring-boot-admin.version> <spring-boot-admin.version>3.4.5</spring-boot-admin.version>
<opentracing.version>0.33.0</opentracing.version> <opentracing.version>0.33.0</opentracing.version>
<!-- Test 测试相关 --> <!-- Test 测试相关 -->
<podam.version>7.2.11.RELEASE</podam.version> <!-- Spring Boot 2.X 最多使用 7.2.11 版本 --> <podam.version>8.0.2.RELEASE</podam.version>
<jedis-mock.version>1.0.13</jedis-mock.version> <jedis-mock.version>1.1.4</jedis-mock.version>
<mockito-inline.version>4.11.0</mockito-inline.version> <mockito-inline.version>5.2.0</mockito-inline.version>
<!-- Bpm 工作流相关 -->
<flowable.version>6.8.0</flowable.version>
<!-- 工具类相关 --> <!-- 工具类相关 -->
<anji-plus-captcha.version>1.4.0</anji-plus-captcha.version> <anji-plus-captcha.version>1.4.0</anji-plus-captcha.version>
<jsoup.version>1.17.2</jsoup.version> <jsoup.version>1.18.1</jsoup.version>
<lombok.version>1.18.30</lombok.version> <lombok.version>1.18.30</lombok.version>
<mapstruct.version>1.5.5.Final</mapstruct.version> <mapstruct.version>1.6.3</mapstruct.version>
<hutool.version>5.8.25</hutool.version> <hutool-5.version>5.8.35</hutool-5.version>
<hutool-6.version>6.0.0-M19</hutool-6.version>
<easyexcel.verion>4.0.3</easyexcel.verion> <easyexcel.verion>4.0.3</easyexcel.verion>
<velocity.version>2.3</velocity.version> <velocity.version>2.4.1</velocity.version>
<screw.version>1.0.5</screw.version> <screw.version>1.0.5</screw.version>
<fastjson.version>1.2.83</fastjson.version> <fastjson.version>1.2.83</fastjson.version>
<guava.version>33.0.0-jre</guava.version> <guava.version>33.4.8-jre</guava.version>
<guice.version>5.1.0</guice.version> <guice.version>5.1.0</guice.version>
<transmittable-thread-local.version>2.14.5</transmittable-thread-local.version> <transmittable-thread-local.version>2.14.5</transmittable-thread-local.version>
<commons-net.version>3.10.0</commons-net.version> <commons-net.version>3.11.1</commons-net.version>
<jsch.version>0.1.55</jsch.version> <jsch.version>0.1.55</jsch.version>
<tika-core.version>2.9.1</tika-core.version> <tika-core.version>3.1.0</tika-core.version>
<ip2region.version>2.7.0</ip2region.version> <ip2region.version>2.7.0</ip2region.version>
<bizlog-sdk.version>3.0.6</bizlog-sdk.version> <bizlog-sdk.version>3.0.6</bizlog-sdk.version>
<reflections.version>0.10.2</reflections.version> <reflections.version>0.10.2</reflections.version>
@@ -78,14 +73,11 @@
<commons-io.version>2.17.0</commons-io.version> <commons-io.version>2.17.0</commons-io.version>
<commons-compress.version>1.27.1</commons-compress.version> <commons-compress.version>1.27.1</commons-compress.version>
<minio.version>8.5.7</minio.version> <minio.version>8.5.7</minio.version>
<aliyun-java-sdk-core.version>4.6.4</aliyun-java-sdk-core.version> <justauth.version>1.16.7</justauth.version>
<aliyun-java-sdk-dysmsapi.version>2.2.1</aliyun-java-sdk-dysmsapi.version> <justauth-starter.version>1.4.0</justauth-starter.version>
<tencentcloud-sdk-java.version>3.1.880</tencentcloud-sdk-java.version> <jimureport.version>1.9.4</jimureport.version>
<justauth.version>1.0.8</justauth.version>
<jimureport.version>1.6.6</jimureport.version>
<xercesImpl.version>2.12.2</xercesImpl.version> <xercesImpl.version>2.12.2</xercesImpl.version>
<weixin-java.version>4.6.0</weixin-java.version> <weixin-java.version>4.7.5.B</weixin-java.version>
<java.version>17</java.version>
<!-- 拼音 --> <!-- 拼音 -->
<pingyinfj.version>2.5.1</pingyinfj.version> <pingyinfj.version>2.5.1</pingyinfj.version>
@@ -94,7 +86,6 @@
<excel-poi.version>4.1.2</excel-poi.version> <excel-poi.version>4.1.2</excel-poi.version>
<!-- 语音--> <!-- 语音-->
<jacob.version>1.18</jacob.version> <jacob.version>1.18</jacob.version>
<nacos-client.version>2.3.0</nacos-client.version>
</properties> </properties>
<dependencyManagement> <dependencyManagement>
@@ -121,11 +112,6 @@
<type>pom</type> <type>pom</type>
<scope>import</scope> <scope>import</scope>
</dependency> </dependency>
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
<version>${nacos-client.version}</version>
</dependency>
<!-- 业务组件 --> <!-- 业务组件 -->
<dependency> <dependency>
@@ -209,19 +195,19 @@
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springdoc</groupId> <!-- 接口文档 UI:默认 --> <groupId>com.github.xingfudeshi</groupId> <!-- TODO 芋艿:https://github.com/xiaoymin/knife4j/issues/874 -->
<artifactId>springdoc-openapi-ui</artifactId> <artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
<version>${springdoc.version}</version> <version>${knife4j.version}</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.github.xiaoymin</groupId> <!-- 接口文档 UIknife4j --> <groupId>org.springdoc</groupId>
<artifactId>knife4j-openapi3-spring-boot-starter</artifactId> <artifactId>springdoc-openapi-starter-webmvc-api</artifactId>
<version>${knife4j.version}</version> <version>${springdoc.version}</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.github.xiaoymin</groupId> <!-- 接口文档 UI:knife4j【网关专属】 --> <groupId>com.github.xiaoymin</groupId> <!-- 接口文档 UI:knife4j【网关专属】 -->
<artifactId>knife4j-gateway-spring-boot-starter</artifactId> <artifactId>knife4j-gateway-spring-boot-starter</artifactId>
<version>${knife4j.version}</version> <version>4.5.0</version> <!-- TODO 芋艿:等 4.5.0 => 4.6.0 -->
</dependency> </dependency>
<!-- DB 相关 --> <!-- DB 相关 -->
@@ -238,22 +224,33 @@
<dependency> <dependency>
<groupId>com.alibaba</groupId> <groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId> <artifactId>druid-spring-boot-3-starter</artifactId>
<version>${druid.version}</version> <version>${druid.version}</version>
</dependency> </dependency>
<dependency>
<!-- 注意:必须声明,避免 flowable 和 mybatis-plus 引入的 mybatis 版本不一致!!! -->
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>${mybatis.version}</version>
</dependency>
<dependency> <dependency>
<groupId>com.baomidou</groupId> <groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId> <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-jsqlparser</artifactId>
<version>${mybatis-plus.version}</version> <version>${mybatis-plus.version}</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.baomidou</groupId> <groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId> <!-- 代码生成器,使用它解析表结构 --> <artifactId>mybatis-plus-generator</artifactId> <!-- 代码生成器,使用它解析表结构 -->
<version>${mybatis-plus-generator.version}</version> <version>${mybatis-plus.version}</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.baomidou</groupId> <groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId> <!-- 多数据源 --> <artifactId>dynamic-datasource-spring-boot3-starter</artifactId> <!-- 多数据源 -->
<version>${dynamic-datasource.version}</version> <version>${dynamic-datasource.version}</version>
</dependency> </dependency>
<dependency> <dependency>
@@ -272,18 +269,6 @@
<groupId>org.redisson</groupId> <groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId> <artifactId>redisson-spring-boot-starter</artifactId>
<version>${redisson.version}</version> <version>${redisson.version}</version>
<exclusions>
<exclusion>
<groupId>org.redisson</groupId>
<!-- 使用 redisson-spring-data-27 替代,解决 Tuple NoClassDefFoundError 报错 -->
<artifactId>redisson-spring-data-33</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-data-27</artifactId>
<version>${redisson.version}</version>
</dependency> </dependency>
<dependency> <dependency>
@@ -328,13 +313,6 @@
<version>${rocketmq-spring.version}</version> <version>${rocketmq-spring.version}</version>
</dependency> </dependency>
<!-- 服务保障相关 -->
<dependency>
<groupId>com.cf.imes</groupId>
<artifactId>cf-spring-boot-starter-protection</artifactId>
<version>${revision}</version>
</dependency>
<dependency> <dependency>
<groupId>com.baomidou</groupId> <groupId>com.baomidou</groupId>
<artifactId>lock4j-redisson-spring-boot-starter</artifactId> <artifactId>lock4j-redisson-spring-boot-starter</artifactId>
@@ -449,24 +427,6 @@
<version>${podam.version}</version> <version>${podam.version}</version>
</dependency> </dependency>
<!-- 工作流相关 -->
<dependency>
<groupId>com.cf.imes</groupId>
<artifactId>cf-spring-boot-starter-flowable</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter-process</artifactId>
<version>${flowable.version}</version>
</dependency>
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter-actuator</artifactId>
<version>${flowable.version}</version>
</dependency>
<!-- 工作流相关结束 -->
<!-- 工具类相关 --> <!-- 工具类相关 -->
<dependency> <dependency>
<groupId>com.cf.imes</groupId> <groupId>com.cf.imes</groupId>
@@ -505,7 +465,12 @@
<dependency> <dependency>
<groupId>cn.hutool</groupId> <groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId> <artifactId>hutool-all</artifactId>
<version>${hutool.version}</version> <version>${hutool-5.version}</version>
</dependency>
<dependency>
<groupId>org.dromara.hutool</groupId>
<artifactId>hutool-extra</artifactId>
<version>${hutool-6.version}</version>
</dependency> </dependency>
<dependency> <dependency>
@@ -677,45 +642,15 @@
<version>${stax-api.version}</version> <version>${stax-api.version}</version>
</dependency> </dependency>
<!-- SMS SDK begin -->
<dependency> <dependency>
<groupId>com.aliyun</groupId> <groupId>me.zhyd.oauth</groupId>
<artifactId>aliyun-java-sdk-core</artifactId> <artifactId>JustAuth</artifactId> <!-- 社交登陆(例如说,个人微信、企业微信等等) -->
<version>${aliyun-java-sdk-core.version}</version>
<exclusions>
<exclusion>
<artifactId>opentracing-api</artifactId>
<groupId>io.opentracing</groupId>
</exclusion>
<exclusion>
<artifactId>opentracing-util</artifactId>
<groupId>io.opentracing</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-dysmsapi</artifactId>
<version>${aliyun-java-sdk-dysmsapi.version}</version>
</dependency>
<dependency>
<groupId>com.tencentcloudapi</groupId>
<artifactId>tencentcloud-sdk-java-sms</artifactId>
<version>${tencentcloud-sdk-java.version}</version>
</dependency>
<!-- SMS SDK end -->
<dependency>
<groupId>com.xingyuv</groupId>
<artifactId>spring-boot-starter-justauth</artifactId> <!-- 社交登陆(例如说,个人微信、企业微信等等) -->
<version>${justauth.version}</version> <version>${justauth.version}</version>
<exclusions> </dependency>
<exclusion> <dependency>
<groupId>cn.hutool</groupId> <groupId>com.xkcoding.justauth</groupId>
<artifactId>hutool-core</artifactId> <artifactId>justauth-spring-boot-starter</artifactId>
</exclusion> <version>${justauth-starter.version}</version>
</exclusions>
</dependency> </dependency>
<!-- 积木报表--> <!-- 积木报表-->
@@ -741,6 +676,12 @@
<version>${jacob.version}</version> <version>${jacob.version}</version>
</dependency> </dependency>
<dependency>
<groupId>co.elastic.clients</groupId>
<artifactId>elasticsearch-java</artifactId>
<version>${elasticsearch-client.version}</version>
</dependency>
</dependencies> </dependencies>
</dependencyManagement> </dependencyManagement>
+2 -2
View File
@@ -64,8 +64,8 @@
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springdoc</groupId> <groupId>org.springdoc</groupId> <!-- 接口文档:使用最新版本的 Swagger 模型 -->
<artifactId>springdoc-openapi-ui</artifactId> <artifactId>springdoc-openapi-starter-webmvc-api</artifactId>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
@@ -3,9 +3,9 @@ package com.cf.imes.framework.common.pojo;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data; import lombok.Data;
import javax.validation.constraints.Min; import jakarta.validation.constraints.Min;
import javax.validation.constraints.Max; import jakarta.validation.constraints.Max;
import javax.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import java.io.Serializable; import java.io.Serializable;
@Schema(description="分页参数") @Schema(description="分页参数")
@@ -12,7 +12,7 @@ import org.springframework.util.StringUtils;
import org.springframework.web.util.UriComponents; import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder; import org.springframework.web.util.UriComponentsBuilder;
import javax.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import java.net.URI; import java.net.URI;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.nio.charset.Charset; import java.nio.charset.Charset;
@@ -2,16 +2,16 @@ package com.cf.imes.framework.common.util.servlet;
import cn.hutool.core.io.IoUtil; import cn.hutool.core.io.IoUtil;
import cn.hutool.core.text.CharSequenceUtil; import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.extra.servlet.ServletUtil; import cn.hutool.extra.servlet.JakartaServletUtil;
import com.cf.imes.framework.common.util.json.JsonUtils; import com.cf.imes.framework.common.util.json.JsonUtils;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes; import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.ServletRequest; import jakarta.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException; import java.io.IOException;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.util.Map; import java.util.Map;
@@ -32,7 +32,7 @@ public class ServletUtils {
@SuppressWarnings("deprecation") // 必须使用 APPLICATION_JSON_UTF8_VALUE,否则会乱码 @SuppressWarnings("deprecation") // 必须使用 APPLICATION_JSON_UTF8_VALUE,否则会乱码
public static void writeJSON(HttpServletResponse response, Object object) { public static void writeJSON(HttpServletResponse response, Object object) {
String content = JsonUtils.toJsonString(object); String content = JsonUtils.toJsonString(object);
ServletUtil.write(response, content, MediaType.APPLICATION_JSON_UTF8_VALUE); JakartaServletUtil.write(response, content, MediaType.APPLICATION_JSON_UTF8_VALUE);
} }
/** /**
@@ -85,7 +85,7 @@ public class ServletUtils {
if (request == null) { if (request == null) {
return null; return null;
} }
return ServletUtil.getClientIP(request); return JakartaServletUtil.getClientIP(request);
} }
public static boolean isJsonRequest(ServletRequest request) { public static boolean isJsonRequest(ServletRequest request) {
@@ -93,19 +93,19 @@ public class ServletUtils {
} }
public static String getBody(HttpServletRequest request) { public static String getBody(HttpServletRequest request) {
return ServletUtil.getBody(request); return JakartaServletUtil.getBody(request);
} }
public static byte[] getBodyBytes(HttpServletRequest request) { public static byte[] getBodyBytes(HttpServletRequest request) {
return ServletUtil.getBodyBytes(request); return JakartaServletUtil.getBodyBytes(request);
} }
public static String getClientIP(HttpServletRequest request) { public static String getClientIP(HttpServletRequest request) {
return ServletUtil.getClientIP(request); return JakartaServletUtil.getClientIP(request);
} }
public static Map<String, String> getParamMap(HttpServletRequest request) { public static Map<String, String> getParamMap(HttpServletRequest request) {
return ServletUtil.getParamMap(request); return JakartaServletUtil.getParamMap(request);
} }
} }
@@ -4,10 +4,10 @@ import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert; import cn.hutool.core.lang.Assert;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import javax.validation.ConstraintViolation; import jakarta.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException; import jakarta.validation.ConstraintViolationException;
import javax.validation.Validation; import jakarta.validation.Validation;
import javax.validation.Validator; import jakarta.validation.Validator;
import java.util.Set; import java.util.Set;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@@ -2,8 +2,8 @@ package com.cf.imes.framework.common.validation;
import com.cf.imes.framework.common.core.IntArrayValuable; import com.cf.imes.framework.common.core.IntArrayValuable;
import javax.validation.Constraint; import jakarta.validation.Constraint;
import javax.validation.Payload; import jakarta.validation.Payload;
import java.lang.annotation.*; import java.lang.annotation.*;
@Target({ @Target({
@@ -3,8 +3,8 @@ package com.cf.imes.framework.common.validation;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import com.cf.imes.framework.common.core.IntArrayValuable; import com.cf.imes.framework.common.core.IntArrayValuable;
import javax.validation.ConstraintValidator; import jakarta.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext; import jakarta.validation.ConstraintValidatorContext;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
@@ -2,8 +2,8 @@ package com.cf.imes.framework.common.validation;
import com.cf.imes.framework.common.core.IntArrayValuable; import com.cf.imes.framework.common.core.IntArrayValuable;
import javax.validation.ConstraintValidator; import jakarta.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext; import jakarta.validation.ConstraintValidatorContext;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@@ -1,7 +1,7 @@
package com.cf.imes.framework.common.validation; package com.cf.imes.framework.common.validation;
import javax.validation.Constraint; import jakarta.validation.Constraint;
import javax.validation.Payload; import jakarta.validation.Payload;
import java.lang.annotation.*; import java.lang.annotation.*;
@Target({ @Target({
@@ -5,8 +5,8 @@ import cn.hutool.core.util.ArrayUtil;
import com.cf.imes.framework.common.util.validation.ValidationUtils; import com.cf.imes.framework.common.util.validation.ValidationUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import javax.validation.ConstraintValidator; import jakarta.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext; import jakarta.validation.ConstraintValidatorContext;
public class MobileValidator implements ConstraintValidator<Mobile, String> { public class MobileValidator implements ConstraintValidator<Mobile, String> {
@@ -1,7 +1,7 @@
package com.cf.imes.framework.common.validation; package com.cf.imes.framework.common.validation;
import javax.validation.Constraint; import jakarta.validation.Constraint;
import javax.validation.Payload; import jakarta.validation.Payload;
import java.lang.annotation.Documented; import java.lang.annotation.Documented;
import java.lang.annotation.ElementType; import java.lang.annotation.ElementType;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
@@ -3,8 +3,8 @@ package com.cf.imes.framework.common.validation;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import javax.validation.ConstraintValidator; import jakarta.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext; import jakarta.validation.ConstraintValidatorContext;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.text.DecimalFormat; import java.text.DecimalFormat;
@@ -1,7 +1,7 @@
package com.cf.imes.framework.common.validation; package com.cf.imes.framework.common.validation;
import javax.validation.Constraint; import jakarta.validation.Constraint;
import javax.validation.Payload; import jakarta.validation.Payload;
import java.lang.annotation.*; import java.lang.annotation.*;
@Target({ @Target({
@@ -3,8 +3,8 @@ package com.cf.imes.framework.common.validation;
import cn.hutool.core.text.CharSequenceUtil; import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.PhoneUtil; import cn.hutool.core.util.PhoneUtil;
import javax.validation.ConstraintValidator; import jakarta.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext; import jakarta.validation.ConstraintValidatorContext;
public class TelephoneValidator implements ConstraintValidator<Telephone, String> { public class TelephoneValidator implements ConstraintValidator<Telephone, String> {
@@ -1,7 +1,8 @@
package com.cf.imes.framework.datapermission.config; package com.cf.imes.framework.datapermission.config;
import com.baomidou.mybatisplus.extension.plugins.inner.DataPermissionInterceptor;
import com.cf.imes.framework.datapermission.core.aop.DataPermissionAnnotationAdvisor; import com.cf.imes.framework.datapermission.core.aop.DataPermissionAnnotationAdvisor;
import com.cf.imes.framework.datapermission.core.db.DataPermissionDatabaseInterceptor; import com.cf.imes.framework.datapermission.core.db.DataPermissionRuleHandler;
import com.cf.imes.framework.datapermission.core.rule.DataPermissionRule; import com.cf.imes.framework.datapermission.core.rule.DataPermissionRule;
import com.cf.imes.framework.datapermission.core.rule.DataPermissionRuleFactory; import com.cf.imes.framework.datapermission.core.rule.DataPermissionRuleFactory;
import com.cf.imes.framework.datapermission.core.rule.DataPermissionRuleFactoryImpl; import com.cf.imes.framework.datapermission.core.rule.DataPermissionRuleFactoryImpl;
@@ -26,14 +27,15 @@ public class ChenfengDataPermissionAutoConfiguration {
} }
@Bean @Bean
public DataPermissionDatabaseInterceptor dataPermissionDatabaseInterceptor(MybatisPlusInterceptor interceptor, public DataPermissionRuleHandler dataPermissionRuleHandler(MybatisPlusInterceptor interceptor,
DataPermissionRuleFactory ruleFactory) { DataPermissionRuleFactory ruleFactory) {
// 创建 DataPermissionDatabaseInterceptor 拦截器 // 创建 DataPermissionInterceptor 拦截器
DataPermissionDatabaseInterceptor inner = new DataPermissionDatabaseInterceptor(ruleFactory); DataPermissionRuleHandler handler = new DataPermissionRuleHandler(ruleFactory);
DataPermissionInterceptor inner = new DataPermissionInterceptor(handler);
// 添加到 interceptor 中 // 添加到 interceptor 中
// 需要加在首个,主要是为了在分页插件前面。这个是 MyBatis Plus 的规定 // 需要加在首个,主要是为了在分页插件前面。这个是 MyBatis Plus 的规定
MyBatisUtils.addInterceptor(interceptor, inner, 0); MyBatisUtils.addInterceptor(interceptor, inner, 0);
return inner; return handler;
} }
@Bean @Bean
@@ -1,641 +0,0 @@
package com.cf.imes.framework.datapermission.core.db;
import cn.hutool.core.collection.CollUtil;
import com.cf.imes.framework.common.util.collection.SetUtils;
import com.cf.imes.framework.datapermission.core.rule.DataPermissionRule;
import com.cf.imes.framework.datapermission.core.rule.DataPermissionRuleFactory;
import com.cf.imes.framework.mybatis.core.util.MyBatisUtils;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
import com.baomidou.mybatisplus.extension.parser.JsqlParserSupport;
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import net.sf.jsqlparser.expression.*;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
import net.sf.jsqlparser.expression.operators.relational.ExistsExpression;
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
import net.sf.jsqlparser.expression.operators.relational.InExpression;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.delete.Delete;
import net.sf.jsqlparser.statement.select.*;
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.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
* 数据权限拦截器,通过 {@link DataPermissionRule} 数据权限规则,重写 SQL 的方式来实现
* 主要的 SQL 重写方法,可见 {@link #builderExpression(Expression, List)} 方法
*
* 整体的代码实现上,参考 {@link com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor} 实现。
* 所以每次 MyBatis Plus 升级时,需要 Review 下其具体的实现是否有变更!
*
* @author 晨丰科技
*/
@RequiredArgsConstructor
public class DataPermissionDatabaseInterceptor extends JsqlParserSupport implements InnerInterceptor {
private final DataPermissionRuleFactory ruleFactory;
@Getter
private final MappedStatementCache mappedStatementCache = new MappedStatementCache();
@Override // SELECT 场景
public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
// 获得 Mapper 对应的数据权限的规则
List<DataPermissionRule> rules = ruleFactory.getDataPermissionRule(ms.getId());
if (mappedStatementCache.noRewritable(ms, rules)) { // 如果无需重写,则跳过
return;
}
PluginUtils.MPBoundSql mpBs = PluginUtils.mpBoundSql(boundSql);
try {
// 初始化上下文
ContextHolder.init(rules);
// 处理 SQL
mpBs.sql(parserSingle(mpBs.sql(), null));
} finally {
// 添加是否需要重写的缓存
addMappedStatementCache(ms);
// 清空上下文
ContextHolder.clear();
}
}
@Override // 只处理 UPDATE / DELETE 场景,不处理 INSERT 场景(因为 INSERT 不需要数据权限)
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) {
// 获得 Mapper 对应的数据权限的规则
List<DataPermissionRule> rules = ruleFactory.getDataPermissionRule(ms.getId());
if (mappedStatementCache.noRewritable(ms, rules)) { // 如果无需重写,则跳过
return;
}
PluginUtils.MPBoundSql mpBs = mpSh.mPBoundSql();
try {
// 初始化上下文
ContextHolder.init(rules);
// 处理 SQL
mpBs.sql(parserMulti(mpBs.sql(), null));
} finally {
// 添加是否需要重写的缓存
addMappedStatementCache(ms);
// 清空上下文
ContextHolder.clear();
}
}
}
@Override
protected void processSelect(Select select, int index, String sql, Object obj) {
processSelectBody(select.getSelectBody());
List<WithItem> withItemsList = select.getWithItemsList();
if (!CollectionUtils.isEmpty(withItemsList)) {
withItemsList.forEach(this::processSelectBody);
}
}
/**
* update 语句处理
*/
@Override
protected void processUpdate(Update update, int index, String sql, Object obj) {
final Table table = update.getTable();
update.setWhere(this.builderExpression(update.getWhere(), table));
}
/**
* delete 语句处理
*/
@Override
protected void processDelete(Delete delete, int index, String sql, Object obj) {
delete.setWhere(this.builderExpression(delete.getWhere(), delete.getTable()));
}
// ========== 和 TenantLineInnerInterceptor 一致的逻辑 ==========
protected void processSelectBody(SelectBody selectBody) {
if (selectBody == null) {
return;
}
if (selectBody instanceof PlainSelect) {
processPlainSelect((PlainSelect) selectBody);
} else if (selectBody instanceof WithItem) {
WithItem withItem = (WithItem) selectBody;
processSelectBody(withItem.getSubSelect().getSelectBody());
} else {
SetOperationList operationList = (SetOperationList) selectBody;
List<SelectBody> selectBodyList = operationList.getSelects();
if (CollectionUtils.isNotEmpty(selectBodyList)) {
selectBodyList.forEach(this::processSelectBody);
}
}
}
/**
* 处理 PlainSelect
*/
protected void processPlainSelect(PlainSelect plainSelect) {
//#3087 github
List<SelectItem> selectItems = plainSelect.getSelectItems();
if (CollectionUtils.isNotEmpty(selectItems)) {
selectItems.forEach(this::processSelectItem);
}
// 处理 where 中的子查询
Expression where = plainSelect.getWhere();
processWhereSubSelect(where);
// 处理 fromItem
FromItem fromItem = plainSelect.getFromItem();
List<Table> list = processFromItem(fromItem);
List<Table> mainTables = new ArrayList<>(list);
// 处理 join
List<Join> joins = plainSelect.getJoins();
if (CollectionUtils.isNotEmpty(joins)) {
mainTables = processJoins(mainTables, joins);
}
// 当有 mainTable 时,进行 where 条件追加
if (CollectionUtils.isNotEmpty(mainTables)) {
plainSelect.setWhere(builderExpression(where, mainTables));
}
}
private List<Table> processFromItem(FromItem fromItem) {
// 处理括号括起来的表达式
while (fromItem instanceof ParenthesisFromItem) {
fromItem = ((ParenthesisFromItem) fromItem).getFromItem();
}
List<Table> mainTables = new ArrayList<>();
// 无 join 时的处理逻辑
if (fromItem instanceof Table) {
Table fromTable = (Table) fromItem;
mainTables.add(fromTable);
} else if (fromItem instanceof SubJoin) {
// SubJoin 类型则还需要添加上 where 条件
List<Table> tables = processSubJoin((SubJoin) fromItem);
mainTables.addAll(tables);
} else {
// 处理下 fromItem
processOtherFromItem(fromItem);
}
return mainTables;
}
/**
* 处理where条件内的子查询
* <p>
* 支持如下:
* 1. in
* 2. =
* 3. >
* 4. <
* 5. >=
* 6. <=
* 7. <>
* 8. EXISTS
* 9. NOT EXISTS
* <p>
* 前提条件:
* 1. 子查询必须放在小括号中
* 2. 子查询一般放在比较操作符的右边
*
* @param where where 条件
*/
protected void processWhereSubSelect(Expression where) {
if (where == null) {
return;
}
if (where instanceof FromItem) {
processOtherFromItem((FromItem) where);
return;
}
if (where.toString().indexOf("SELECT") >= 1) {
// 有子查询
if (where instanceof BinaryExpression) {
// 比较符号 , and , or , 等等
BinaryExpression expression = (BinaryExpression) where;
processWhereSubSelect(expression.getLeftExpression());
processWhereSubSelect(expression.getRightExpression());
} else if (where instanceof InExpression) {
// in
InExpression expression = (InExpression) where;
Expression inExpression = expression.getRightExpression();
if (inExpression instanceof SubSelect) {
processSelectBody(((SubSelect) inExpression).getSelectBody());
}
} else if (where instanceof ExistsExpression) {
// exists
ExistsExpression expression = (ExistsExpression) where;
processWhereSubSelect(expression.getRightExpression());
} else if (where instanceof NotExpression) {
// not exists
NotExpression expression = (NotExpression) where;
processWhereSubSelect(expression.getExpression());
} else if (where instanceof Parenthesis) {
Parenthesis expression = (Parenthesis) where;
processWhereSubSelect(expression.getExpression());
}
}
}
protected void processSelectItem(SelectItem selectItem) {
if (selectItem instanceof SelectExpressionItem) {
SelectExpressionItem selectExpressionItem = (SelectExpressionItem) selectItem;
if (selectExpressionItem.getExpression() instanceof SubSelect) {
processSelectBody(((SubSelect) selectExpressionItem.getExpression()).getSelectBody());
} else if (selectExpressionItem.getExpression() instanceof Function) {
processFunction((Function) selectExpressionItem.getExpression());
}
}
}
/**
* 处理函数
* <p>支持: 1. select fun(args..) 2. select fun1(fun2(args..),args..)<p>
* <p> fixed gitee pulls/141</p>
*
* @param function
*/
protected void processFunction(Function function) {
ExpressionList parameters = function.getParameters();
if (parameters != null) {
parameters.getExpressions().forEach(expression -> {
if (expression instanceof SubSelect) {
processSelectBody(((SubSelect) expression).getSelectBody());
} else if (expression instanceof Function) {
processFunction((Function) expression);
}
});
}
}
/**
* 处理子查询等
*/
protected void processOtherFromItem(FromItem fromItem) {
// 去除括号
while (fromItem instanceof ParenthesisFromItem) {
fromItem = ((ParenthesisFromItem) fromItem).getFromItem();
}
if (fromItem instanceof SubSelect) {
SubSelect subSelect = (SubSelect) fromItem;
if (subSelect.getSelectBody() != null) {
processSelectBody(subSelect.getSelectBody());
}
} else if (fromItem instanceof ValuesList) {
logger.debug("Perform a subQuery, if you do not give us feedback");
} else if (fromItem instanceof LateralSubSelect) {
LateralSubSelect lateralSubSelect = (LateralSubSelect) fromItem;
if (lateralSubSelect.getSubSelect() != null) {
SubSelect subSelect = lateralSubSelect.getSubSelect();
if (subSelect.getSelectBody() != null) {
processSelectBody(subSelect.getSelectBody());
}
}
}
}
/**
* 处理 sub join
*
* @param subJoin subJoin
* @return Table subJoin 中的主表
*/
private List<Table> processSubJoin(SubJoin subJoin) {
List<Table> mainTables = new ArrayList<>();
if (subJoin.getJoinList() != null) {
List<Table> list = processFromItem(subJoin.getLeft());
mainTables.addAll(list);
mainTables = processJoins(mainTables, subJoin.getJoinList());
}
return mainTables;
}
/**
* 处理 joins
*
* @param mainTables 可以为 null
* @param joins join 集合
* @return List<Table> 右连接查询的 Table 列表
*/
private List<Table> processJoins(List<Table> mainTables, List<Join> joins) {
// join 表达式中最终的主表
Table mainTable = null;
// 当前 join 的左表
Table leftTable = null;
if (mainTables == null) {
mainTables = new ArrayList<>();
} else if (mainTables.size() == 1) {
mainTable = mainTables.get(0);
leftTable = mainTable;
}
//对于 on 表达式写在最后的 join,需要记录下前面多个 on 的表名
Deque<List<Table>> onTableDeque = new LinkedList<>();
for (Join join : joins) {
// 处理 on 表达式
FromItem joinItem = join.getRightItem();
// 获取当前 join 的表,subJoint 可以看作是一张表
List<Table> joinTables = null;
if (joinItem instanceof Table) {
joinTables = new ArrayList<>();
joinTables.add((Table) joinItem);
} else if (joinItem instanceof SubJoin) {
joinTables = processSubJoin((SubJoin) joinItem);
}
if (joinTables != null) {
// 如果是隐式内连接
if (join.isSimple()) {
mainTables.addAll(joinTables);
continue;
}
// 当前表是否忽略
Table joinTable = joinTables.get(0);
List<Table> onTables = null;
// 如果不要忽略,且是右连接,则记录下当前表
if (join.isRight()) {
mainTable = joinTable;
if (leftTable != null) {
onTables = Collections.singletonList(leftTable);
}
} else if (join.isLeft()) {
onTables = Collections.singletonList(joinTable);
} else if (join.isInner()) {
if (mainTable == null) {
onTables = Collections.singletonList(joinTable);
} else {
onTables = Arrays.asList(mainTable, joinTable);
}
mainTable = null;
}
mainTables = new ArrayList<>();
if (mainTable != null) {
mainTables.add(mainTable);
}
// 获取 join 尾缀的 on 表达式列表
Collection<Expression> originOnExpressions = join.getOnExpressions();
// 正常 join on 表达式只有一个,立刻处理
if (originOnExpressions.size() == 1 && onTables != null) {
List<Expression> onExpressions = new LinkedList<>();
onExpressions.add(builderExpression(originOnExpressions.iterator().next(), onTables));
join.setOnExpressions(onExpressions);
leftTable = joinTable;
continue;
}
// 表名压栈,忽略的表压入 null,以便后续不处理
onTableDeque.push(onTables);
// 尾缀多个 on 表达式的时候统一处理
if (originOnExpressions.size() > 1) {
Collection<Expression> onExpressions = new LinkedList<>();
for (Expression originOnExpression : originOnExpressions) {
List<Table> currentTableList = onTableDeque.poll();
if (CollectionUtils.isEmpty(currentTableList)) {
onExpressions.add(originOnExpression);
} else {
onExpressions.add(builderExpression(originOnExpression, currentTableList));
}
}
join.setOnExpressions(onExpressions);
}
leftTable = joinTable;
} else {
processOtherFromItem(joinItem);
leftTable = null;
}
}
return mainTables;
}
// ========== 和 TenantLineInnerInterceptor 存在差异的逻辑:关键,实现权限条件的拼接 ==========
/**
* 处理条件
*
* @param currentExpression 当前 where 条件
* @param table 单个表
*/
protected Expression builderExpression(Expression currentExpression, Table table) {
return this.builderExpression(currentExpression, Collections.singletonList(table));
}
/**
* 处理条件
*
* @param currentExpression 当前 where 条件
* @param tables 多个表
*/
protected Expression builderExpression(Expression currentExpression, List<Table> tables) {
// 没有表需要处理直接返回
if (CollectionUtils.isEmpty(tables)) {
return currentExpression;
}
// 第一步,获得 Table 对应的数据权限条件
Expression dataPermissionExpression = null;
for (Table table : tables) {
// 构建每个表的权限 Expression 条件
Expression expression = buildDataPermissionExpression(table);
if (expression == null) {
continue;
}
// 合并到 dataPermissionExpression 中
dataPermissionExpression = dataPermissionExpression == null ? expression
: new AndExpression(dataPermissionExpression, expression);
}
// 第二步,合并多个 Expression 条件
if (dataPermissionExpression == null) {
return currentExpression;
}
if (currentExpression == null) {
return dataPermissionExpression;
}
// ① 如果表达式为 Or,则需要 (currentExpression) AND dataPermissionExpression
if (currentExpression instanceof OrExpression) {
return new AndExpression(new Parenthesis(currentExpression), dataPermissionExpression);
}
// ② 如果表达式为 And,则直接返回 where AND dataPermissionExpression
return new AndExpression(currentExpression, dataPermissionExpression);
}
/**
* 构建指定表的数据权限的 Expression 过滤条件
*
* @param table 表
* @return Expression 过滤条件
*/
private Expression buildDataPermissionExpression(Table table) {
// 生成条件
Expression allExpression = null;
for (DataPermissionRule rule : ContextHolder.getRules()) {
// 判断表名是否匹配
String tableName = MyBatisUtils.getTableName(table);
if (!rule.getTableNames().contains(tableName)) {
continue;
}
// 如果有匹配的规则,说明可重写。
// 为什么不是有 allExpression 非空才重写呢?在生成 column = value 过滤条件时,会因为 value 不存在,导致未重写。
// 这样导致第一次无 value,被标记成无需重写;但是第二次有 value,此时会需要重写。
ContextHolder.setRewrite(true);
// 单条规则的条件
Expression oneExpress = rule.getExpression(tableName, table.getAlias());
if (oneExpress == null){
continue;
}
// 拼接到 allExpression 中
allExpression = allExpression == null ? oneExpress
: new AndExpression(allExpression, oneExpress);
}
return allExpression;
}
/**
* 判断 SQL 是否重写。如果没有重写,则添加到 {@link MappedStatementCache} 中
*
* @param ms MappedStatement
*/
private void addMappedStatementCache(MappedStatement ms) {
if (ContextHolder.getRewrite()) {
return;
}
// 无重写,进行添加
mappedStatementCache.addNoRewritable(ms, ContextHolder.getRules());
}
/**
* SQL 解析上下文,方便透传 {@link DataPermissionRule} 规则
*
* @author 晨丰科技
*/
static final class ContextHolder {
/**
* 该 {@link MappedStatement} 对应的规则
*/
private static final ThreadLocal<List<DataPermissionRule>> RULES = ThreadLocal.withInitial(Collections::emptyList);
/**
* SQL 是否进行重写
*/
private static final ThreadLocal<Boolean> REWRITE = ThreadLocal.withInitial(() -> Boolean.FALSE);
public static void init(List<DataPermissionRule> rules) {
RULES.set(rules);
REWRITE.set(false);
}
public static void clear() {
RULES.remove();
REWRITE.remove();
}
public static boolean getRewrite() {
return REWRITE.get();
}
public static void setRewrite(boolean rewrite) {
REWRITE.set(rewrite);
}
public static List<DataPermissionRule> getRules() {
return RULES.get();
}
}
/**
* {@link MappedStatement} 缓存
* 目前主要用于,记录 {@link DataPermissionRule} 是否对指定 {@link MappedStatement} 无效
* 如果无效,则可以避免 SQL 的解析,加快速度
*
* @author 晨丰科技
*/
static final class MappedStatementCache {
/**
* 指定数据权限规则,对指定 MappedStatement 无需重写(不生效)的缓存
*
* value{@link MappedStatement#getId()} 编号
*/
@Getter
private final Map<Class<? extends DataPermissionRule>, Set<String>> noRewritableMappedStatements = new ConcurrentHashMap<>();
/**
* 判断是否无需重写
* ps:虽然有点中文式英语,但是容易读懂即可
*
* @param ms MappedStatement
* @param rules 数据权限规则数组
* @return 是否无需重写
*/
public boolean noRewritable(MappedStatement ms, List<DataPermissionRule> rules) {
// 如果规则为空,说明无需重写
if (CollUtil.isEmpty(rules)) {
return true;
}
// 任一规则不在 noRewritableMap 中,则说明可能需要重写
for (DataPermissionRule rule : rules) {
Set<String> mappedStatementIds = noRewritableMappedStatements.get(rule.getClass());
if (!CollUtil.contains(mappedStatementIds, ms.getId())) {
return false;
}
}
return true;
}
/**
* 添加无需重写的 MappedStatement
*
* @param ms MappedStatement
* @param rules 数据权限规则数组
*/
public void addNoRewritable(MappedStatement ms, List<DataPermissionRule> rules) {
for (DataPermissionRule rule : rules) {
Set<String> mappedStatementIds = noRewritableMappedStatements.get(rule.getClass());
if (CollUtil.isNotEmpty(mappedStatementIds)) {
mappedStatementIds.add(ms.getId());
} else {
noRewritableMappedStatements.put(rule.getClass(), SetUtils.asSet(ms.getId()));
}
}
}
/**
* 清空缓存
* 目前主要提供给单元测试
*/
public void clear() {
noRewritableMappedStatements.clear();
}
}
}
@@ -0,0 +1,63 @@
package com.cf.imes.framework.datapermission.core.db;
import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.extension.plugins.handler.MultiDataPermissionHandler;
import com.cf.imes.framework.datapermission.core.rule.DataPermissionRule;
import com.cf.imes.framework.datapermission.core.rule.DataPermissionRuleFactory;
import com.cf.imes.framework.mybatis.core.util.MyBatisUtils;
import lombok.RequiredArgsConstructor;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.schema.Table;
import java.util.List;
/**
* 基于 {@link DataPermissionRule} 的数据权限处理器
*
* 它的底层,是基于 MyBatis Plus 的 <a href="https://baomidou.com/plugins/data-permission/">数据权限插件</a>
* 核心原理:它会在 SQL 执行前拦截 SQL 语句,并根据用户权限动态添加权限相关的 SQL 片段。这样,只有用户有权限访问的数据才会被查询出来
*
* @author 芋道源码
*/
@RequiredArgsConstructor
public class DataPermissionRuleHandler implements MultiDataPermissionHandler {
private final DataPermissionRuleFactory ruleFactory;
@Override
public Expression getSqlSegment(Table table, Expression where, String mappedStatementId) {
// todo 缺少VisitTenantId 特殊:跨租户访问
// if (skipPermissionCheck()) {
// return null;
// }
// 获得 Mapper 对应的数据权限的规则
List<DataPermissionRule> rules = ruleFactory.getDataPermissionRule(mappedStatementId);
if (CollUtil.isEmpty(rules)) {
return null;
}
// 生成条件
Expression allExpression = null;
for (DataPermissionRule rule : rules) {
// 判断表名是否匹配
String tableName = MyBatisUtils.getTableName(table);
if (!rule.getTableNames().contains(tableName)) {
continue;
}
// 单条规则的条件
Expression oneExpress = rule.getExpression(tableName, table.getAlias());
if (oneExpress == null) {
continue;
}
// 拼接到 allExpression 中
allExpression = allExpression == null ? oneExpress
: new AndExpression(allExpression, oneExpress);
}
return allExpression;
}
}
@@ -21,6 +21,7 @@ import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
import net.sf.jsqlparser.expression.operators.relational.EqualsTo; import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
import net.sf.jsqlparser.expression.operators.relational.ExpressionList; import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
import net.sf.jsqlparser.expression.operators.relational.InExpression; import net.sf.jsqlparser.expression.operators.relational.InExpression;
import net.sf.jsqlparser.expression.operators.relational.ParenthesedExpressionList;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
@@ -141,7 +142,7 @@ public class DeptDataPermissionRule implements DataPermissionRule {
return deptExpression; return deptExpression;
} }
// 目前,如果有指定部门 + 可查看自己,采用 OR 条件。即,WHERE (dept_id IN ? OR user_id = ?) // 目前,如果有指定部门 + 可查看自己,采用 OR 条件。即,WHERE (dept_id IN ? OR user_id = ?)
return new Parenthesis(new OrExpression(deptExpression, userExpression)); return new ParenthesedExpressionList(new OrExpression(deptExpression, userExpression));
} }
private Expression buildDeptExpression(String tableName, Alias tableAlias, Set<Long> deptIds) { private Expression buildDeptExpression(String tableName, Alias tableAlias, Set<Long> deptIds) {
@@ -1,190 +0,0 @@
package com.cf.imes.framework.datapermission.core.db;
import com.cf.imes.framework.common.util.collection.SetUtils;
import com.cf.imes.framework.datapermission.core.rule.DataPermissionRule;
import com.cf.imes.framework.datapermission.core.rule.DataPermissionRuleFactory;
import com.cf.imes.framework.mybatis.core.util.MyBatisUtils;
import com.cf.imes.framework.test.core.ut.BaseMockitoUnitTest;
import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
import net.sf.jsqlparser.expression.Alias;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.LongValue;
import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
import net.sf.jsqlparser.schema.Column;
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.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import java.sql.Connection;
import java.util.*;
import static java.util.Collections.singletonList;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
/**
* {@link DataPermissionDatabaseInterceptor} 的单元测试
* 主要测试 {@link DataPermissionDatabaseInterceptor#beforePrepare(StatementHandler, Connection, Integer)}
* 和 {@link DataPermissionDatabaseInterceptor#beforeUpdate(Executor, MappedStatement, Object)}
* 以及在这个过程中,ContextHolder 和 MappedStatementCache
*
* @author 晨丰科技
*/
public class DataPermissionDatabaseInterceptorTest extends BaseMockitoUnitTest {
@InjectMocks
private DataPermissionDatabaseInterceptor interceptor;
@Mock
private DataPermissionRuleFactory ruleFactory;
@BeforeEach
public void setUp() {
// 清理上下文
DataPermissionDatabaseInterceptor.ContextHolder.clear();
// 清空缓存
interceptor.getMappedStatementCache().clear();
}
@Test // 不存在规则,且不匹配
public void testBeforeQuery_withoutRule() {
try (MockedStatic<PluginUtils> pluginUtilsMock = mockStatic(PluginUtils.class)) {
// 准备参数
MappedStatement mappedStatement = mock(MappedStatement.class);
BoundSql boundSql = mock(BoundSql.class);
// 调用
interceptor.beforeQuery(null, mappedStatement, null, null, null, boundSql);
// 断言
pluginUtilsMock.verify(() -> PluginUtils.mpBoundSql(boundSql), never());
}
}
@Test // 存在规则,且不匹配
public void testBeforeQuery_withMatchRule() {
try (MockedStatic<PluginUtils> pluginUtilsMock = mockStatic(PluginUtils.class)) {
// 准备参数
MappedStatement mappedStatement = mock(MappedStatement.class);
BoundSql boundSql = mock(BoundSql.class);
// mock 方法(数据权限)
when(ruleFactory.getDataPermissionRule(same(mappedStatement.getId())))
.thenReturn(singletonList(new DeptDataPermissionRule()));
// mock 方法(MPBoundSql)
PluginUtils.MPBoundSql mpBs = mock(PluginUtils.MPBoundSql.class);
pluginUtilsMock.when(() -> PluginUtils.mpBoundSql(same(boundSql))).thenReturn(mpBs);
// mock 方法(SQL)
String sql = "select * from t_user where id = 1";
when(mpBs.sql()).thenReturn(sql);
// 针对 ContextHolder 和 MappedStatementCache 暂时不 mock,主要想校验过程中,数据是否正确
// 调用
interceptor.beforeQuery(null, mappedStatement, null, null, null, boundSql);
// 断言
verify(mpBs, times(1)).sql(
eq("SELECT * FROM t_user WHERE id = 1 AND t_user.dept_id = 100"));
// 断言缓存
assertTrue(interceptor.getMappedStatementCache().getNoRewritableMappedStatements().isEmpty());
}
}
@Test // 存在规则,但不匹配
public void testBeforeQuery_withoutMatchRule() {
try (MockedStatic<PluginUtils> pluginUtilsMock = mockStatic(PluginUtils.class)) {
// 准备参数
MappedStatement mappedStatement = mock(MappedStatement.class);
BoundSql boundSql = mock(BoundSql.class);
// mock 方法(数据权限)
when(ruleFactory.getDataPermissionRule(same(mappedStatement.getId())))
.thenReturn(singletonList(new DeptDataPermissionRule()));
// mock 方法(MPBoundSql)
PluginUtils.MPBoundSql mpBs = mock(PluginUtils.MPBoundSql.class);
pluginUtilsMock.when(() -> PluginUtils.mpBoundSql(same(boundSql))).thenReturn(mpBs);
// mock 方法(SQL)
String sql = "select * from t_role where id = 1";
when(mpBs.sql()).thenReturn(sql);
// 针对 ContextHolder 和 MappedStatementCache 暂时不 mock,主要想校验过程中,数据是否正确
// 调用
interceptor.beforeQuery(null, mappedStatement, null, null, null, boundSql);
// 断言
verify(mpBs, times(1)).sql(
eq("SELECT * FROM t_role WHERE id = 1"));
// 断言缓存
assertFalse(interceptor.getMappedStatementCache().getNoRewritableMappedStatements().isEmpty());
}
}
@Test
public void testAddNoRewritable() {
// 准备参数
MappedStatement ms = mock(MappedStatement.class);
List<DataPermissionRule> rules = singletonList(new DeptDataPermissionRule());
// mock 方法
when(ms.getId()).thenReturn("selectById");
// 调用
interceptor.getMappedStatementCache().addNoRewritable(ms, rules);
// 断言
Map<Class<? extends DataPermissionRule>, Set<String>> noRewritableMappedStatements =
interceptor.getMappedStatementCache().getNoRewritableMappedStatements();
assertEquals(1, noRewritableMappedStatements.size());
assertEquals(SetUtils.asSet("selectById"), noRewritableMappedStatements.get(DeptDataPermissionRule.class));
}
@Test
public void testNoRewritable() {
// 准备参数
MappedStatement ms = mock(MappedStatement.class);
// mock 方法
when(ms.getId()).thenReturn("selectById");
// mock 数据
List<DataPermissionRule> rules = singletonList(new DeptDataPermissionRule());
interceptor.getMappedStatementCache().addNoRewritable(ms, rules);
// 场景一,rules 为空
assertTrue(interceptor.getMappedStatementCache().noRewritable(ms, null));
// 场景二,rules 非空,可重写
assertFalse(interceptor.getMappedStatementCache().noRewritable(ms, singletonList(new EmptyDataPermissionRule())));
// 场景三,rule 非空,不可重写
assertTrue(interceptor.getMappedStatementCache().noRewritable(ms, rules));
}
private static class DeptDataPermissionRule implements DataPermissionRule {
private static final String COLUMN = "dept_id";
@Override
public Set<String> getTableNames() {
return SetUtils.asSet("t_user");
}
@Override
public Expression getExpression(String tableName, Alias tableAlias) {
Column column = MyBatisUtils.buildColumn(tableName, tableAlias, COLUMN);
LongValue value = new LongValue(100L);
return new EqualsTo(column, value);
}
}
private static class EmptyDataPermissionRule implements DataPermissionRule {
@Override
public Set<String> getTableNames() {
return Collections.emptySet();
}
@Override
public Expression getExpression(String tableName, Alias tableAlias) {
return null;
}
}
}
@@ -1,5 +1,6 @@
package com.cf.imes.framework.datapermission.core.db; package com.cf.imes.framework.datapermission.core.db;
import com.baomidou.mybatisplus.extension.plugins.inner.DataPermissionInterceptor;
import com.cf.imes.framework.datapermission.core.rule.DataPermissionRule; import com.cf.imes.framework.datapermission.core.rule.DataPermissionRule;
import com.cf.imes.framework.datapermission.core.rule.DataPermissionRuleFactory; import com.cf.imes.framework.datapermission.core.rule.DataPermissionRuleFactory;
import com.cf.imes.framework.mybatis.core.util.MyBatisUtils; import com.cf.imes.framework.mybatis.core.util.MyBatisUtils;
@@ -10,6 +11,7 @@ import net.sf.jsqlparser.expression.LongValue;
import net.sf.jsqlparser.expression.operators.relational.EqualsTo; import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
import net.sf.jsqlparser.expression.operators.relational.ExpressionList; import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
import net.sf.jsqlparser.expression.operators.relational.InExpression; import net.sf.jsqlparser.expression.operators.relational.InExpression;
import net.sf.jsqlparser.expression.operators.relational.ParenthesedExpressionList;
import net.sf.jsqlparser.schema.Column; import net.sf.jsqlparser.schema.Column;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@@ -21,28 +23,34 @@ import java.util.Set;
import static com.cf.imes.framework.common.util.collection.SetUtils.asSet; import static com.cf.imes.framework.common.util.collection.SetUtils.asSet;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
/** /**
* {@link DataPermissionDatabaseInterceptor} 的单元测试 * {@link DataPermissionRuleHandler} 的单元测试
* 主要复用了 MyBatis Plus TenantLineInnerInterceptorTest 的单元测试 * 主要复用了 MyBatis Plus TenantLineInnerInterceptorTest 的单元测试
* 不过它的单元测试不是很规范考虑到是复用的所以暂时不进行修改~ * 不过它的单元测试不是很规范考虑到是复用的所以暂时不进行修改~
* *
* @author 晨丰科技 * @author 芋道源码
*/ */
public class DataPermissionDatabaseInterceptorTest2 extends BaseMockitoUnitTest { public class DataPermissionRuleHandlerTest extends BaseMockitoUnitTest {
@InjectMocks @InjectMocks
private DataPermissionDatabaseInterceptor interceptor; private DataPermissionRuleHandler handler;
@Mock @Mock
private DataPermissionRuleFactory ruleFactory; private DataPermissionRuleFactory ruleFactory;
private DataPermissionInterceptor interceptor;
@BeforeEach @BeforeEach
public void setUp() { public void setUp() {
// 组织的数据权限规则 interceptor = new DataPermissionInterceptor(handler);
// 租户的数据权限规则
DataPermissionRule tenantRule = new DataPermissionRule() { DataPermissionRule tenantRule = new DataPermissionRule() {
private static final String COLUMN = "organ_id"; private static final String COLUMN = "tenant_id";
@Override @Override
public Set<String> getTableNames() { public Set<String> getTableNames() {
@@ -71,14 +79,14 @@ public class DataPermissionDatabaseInterceptorTest2 extends BaseMockitoUnitTest
@Override @Override
public Expression getExpression(String tableName, Alias tableAlias) { public Expression getExpression(String tableName, Alias tableAlias) {
Column column = MyBatisUtils.buildColumn(tableName, tableAlias, COLUMN); Column column = MyBatisUtils.buildColumn(tableName, tableAlias, COLUMN);
ExpressionList values = new ExpressionList(new LongValue(10L), ExpressionList<LongValue> values = new ExpressionList<>(new LongValue(10L),
new LongValue(20L)); new LongValue(20L));
return new InExpression(column, values); return new InExpression(column, new ParenthesedExpressionList((values)));
} }
}; };
// 设置到上下文保证 // 设置到上下文
DataPermissionDatabaseInterceptor.ContextHolder.init(Arrays.asList(tenantRule, deptRule)); when(ruleFactory.getDataPermissionRule(any())).thenReturn(Arrays.asList(tenantRule, deptRule));
} }
@Test @Test
@@ -262,7 +270,7 @@ public class DataPermissionDatabaseInterceptorTest2 extends BaseMockitoUnitTest
"right join entity2 e2 on e1.id = e2.id", "right join entity2 e2 on e1.id = e2.id",
"SELECT * FROM entity e " + "SELECT * FROM entity e " +
"LEFT JOIN entity1 e1 ON e1.id = e.id AND e1.tenant_id = 1 " + "LEFT JOIN entity1 e1 ON e1.id = e.id AND e1.tenant_id = 1 " +
"RIGHT JOIN entity2 e2 ON e1.id = e2.id AND e1.tenant_id = 1 " + "RIGHT JOIN entity2 e2 ON e1.id = e2.id AND e.tenant_id = 1 " +
"WHERE e2.tenant_id = 1"); "WHERE e2.tenant_id = 1");
assertSql("SELECT * FROM entity e " + assertSql("SELECT * FROM entity e " +
@@ -447,7 +455,6 @@ public class DataPermissionDatabaseInterceptorTest2 extends BaseMockitoUnitTest
assertEquals(targetSql, interceptor.parserSingle(sql, null)); assertEquals(targetSql, interceptor.parserSingle(sql, null));
} }
// ========== 额外的测试 ========== // ========== 额外的测试 ==========
@Test @Test
@@ -16,7 +16,7 @@ import com.cf.imes.framework.ip.core.service.dto.IPQueryDataRespDTO;
import com.cf.imes.framework.ip.core.service.dto.IPQueryRespDTO; import com.cf.imes.framework.ip.core.service.dto.IPQueryRespDTO;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import javax.annotation.Resource; import jakarta.annotation.Resource;
/** /**
* @author Gqr * @author Gqr
@@ -8,7 +8,7 @@ import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.ActiveProfiles;
import javax.annotation.Resource; import jakarta.annotation.Resource;
import static com.cf.imes.framework.test.core.util.RandomUtils.randomString; import static com.cf.imes.framework.test.core.util.RandomUtils.randomString;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -27,9 +27,9 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource; import jakarta.annotation.Resource;
import javax.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
import java.lang.reflect.Array; import java.lang.reflect.Array;
import java.time.LocalDateTime; import java.time.LocalDateTime;
@@ -19,10 +19,10 @@ import com.cf.imes.module.system.enums.ErrorCodeConstants;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.util.AntPathMatcher; import org.springframework.util.AntPathMatcher;
import javax.servlet.FilterChain; import jakarta.servlet.FilterChain;
import javax.servlet.ServletException; import jakarta.servlet.ServletException;
import javax.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException; import java.io.IOException;
import java.util.Objects; import java.util.Objects;
@@ -4,10 +4,10 @@ import com.cf.imes.framework.organ.core.context.OrganContextHolder;
import com.cf.imes.framework.web.core.util.WebFrameworkUtils; import com.cf.imes.framework.web.core.util.WebFrameworkUtils;
import org.springframework.web.filter.OncePerRequestFilter; import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain; import jakarta.servlet.FilterChain;
import javax.servlet.ServletException; import jakarta.servlet.ServletException;
import javax.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException; import java.io.IOException;
/** /**
@@ -225,7 +225,7 @@ public class InvocableHandlerMethod extends HandlerMethod {
return new AsyncResultMethodParameter(returnValue); return new AsyncResultMethodParameter(returnValue);
} }
private class AsyncResultMethodParameter extends HandlerMethodParameter { private class AsyncResultMethodParameter extends AnnotatedMethodParameter {
@Nullable @Nullable
private final Object returnValue; private final Object returnValue;
@@ -2,7 +2,7 @@ package com.cf.imes.framework.pay.core.client;
import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonTypeInfo;
import javax.validation.Validator; import jakarta.validation.Validator;
/** /**
* 支付客户端的配置,本质是支付渠道的配置 * 支付客户端的配置,本质是支付渠道的配置
@@ -6,9 +6,9 @@ import lombok.Data;
import org.hibernate.validator.constraints.Length; import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.constraints.URL; import org.hibernate.validator.constraints.URL;
import javax.validation.constraints.DecimalMin; import jakarta.validation.constraints.DecimalMin;
import javax.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.Map; import java.util.Map;
@@ -7,9 +7,9 @@ import lombok.NoArgsConstructor;
import lombok.experimental.Accessors; import lombok.experimental.Accessors;
import org.hibernate.validator.constraints.URL; import org.hibernate.validator.constraints.URL;
import javax.validation.constraints.DecimalMin; import jakarta.validation.constraints.DecimalMin;
import javax.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
/** /**
* 统一 退款 Request DTO * 统一 退款 Request DTO
@@ -5,9 +5,9 @@ import com.cf.imes.framework.pay.core.enums.transfer.PayTransferTypeEnum;
import lombok.Data; import lombok.Data;
import org.hibernate.validator.constraints.Length; import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.Min; import jakarta.validation.constraints.Min;
import javax.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import java.util.Map; import java.util.Map;
/** /**
@@ -3,7 +3,7 @@ package com.cf.imes.framework.pay.core.client.impl;
import com.cf.imes.framework.pay.core.client.PayClientConfig; import com.cf.imes.framework.pay.core.client.PayClientConfig;
import lombok.Data; import lombok.Data;
import javax.validation.Validator; import jakarta.validation.Validator;
/** /**
* 无需任何配置 PayClientConfig 实现类 * 无需任何配置 PayClientConfig 实现类
@@ -4,9 +4,9 @@ import com.cf.imes.framework.common.util.validation.ValidationUtils;
import com.cf.imes.framework.pay.core.client.PayClientConfig; import com.cf.imes.framework.pay.core.client.PayClientConfig;
import lombok.Data; import lombok.Data;
import javax.validation.Validator; import jakarta.validation.Validator;
import javax.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
/** /**
* 支付宝的 PayClientConfig 实现类 * 支付宝的 PayClientConfig 实现类
@@ -3,7 +3,7 @@ package com.cf.imes.framework.pay.core.client.impl.mock;
import com.cf.imes.framework.pay.core.client.PayClientConfig; import com.cf.imes.framework.pay.core.client.PayClientConfig;
import lombok.Data; import lombok.Data;
import javax.validation.Validator; import jakarta.validation.Validator;
/** /**
* 模拟支付的 PayClientConfig 实现类 * 模拟支付的 PayClientConfig 实现类
@@ -5,8 +5,8 @@ import com.cf.imes.framework.common.util.validation.ValidationUtils;
import com.cf.imes.framework.pay.core.client.PayClientConfig; import com.cf.imes.framework.pay.core.client.PayClientConfig;
import lombok.Data; import lombok.Data;
import javax.validation.Validator; import jakarta.validation.Validator;
import javax.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
@@ -25,7 +25,7 @@ import org.junit.jupiter.api.Test;
import org.mockito.ArgumentMatcher; import org.mockito.ArgumentMatcher;
import org.mockito.Mock; import org.mockito.Mock;
import javax.validation.ConstraintViolationException; import jakarta.validation.ConstraintViolationException;
import java.util.Date; import java.util.Date;
import static com.cf.imes.framework.pay.core.client.impl.alipay.AlipayPayClientConfig.MODE_PUBLIC_KEY; import static com.cf.imes.framework.pay.core.client.impl.alipay.AlipayPayClientConfig.MODE_PUBLIC_KEY;
@@ -17,7 +17,6 @@
<maven.compiler.source>17</maven.compiler.source> <maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target> <maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<elasticsearch.version>8.8.1</elasticsearch.version>
<jakarta-json.version>2.1.1</jakarta-json.version> <jakarta-json.version>2.1.1</jakarta-json.version>
</properties> </properties>
<dependencies> <dependencies>
@@ -41,7 +40,7 @@
<dependency> <dependency>
<groupId>co.elastic.clients</groupId> <groupId>co.elastic.clients</groupId>
<artifactId>elasticsearch-java</artifactId> <artifactId>elasticsearch-java</artifactId>
<version>${elasticsearch.version}</version> <!-- <version>${elasticsearch.version}</version>-->
<exclusions> <exclusions>
<exclusion> <exclusion>
<groupId>jakarta.json</groupId> <groupId>jakarta.json</groupId>
@@ -5,7 +5,7 @@ import com.cf.imes.framework.es.core.valid.UpdateGroup;
import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data; import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat; import org.springframework.format.annotation.DateTimeFormat;
import javax.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import static com.cf.imes.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; import static com.cf.imes.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
import static com.cf.imes.framework.common.util.date.DateUtils.TIME_ZONE_DEFAULT; import static com.cf.imes.framework.common.util.date.DateUtils.TIME_ZONE_DEFAULT;
@@ -1,6 +1,6 @@
package com.cf.imes.framework.es.core.valid; package com.cf.imes.framework.es.core.valid;
import javax.validation.groups.Default; import jakarta.validation.groups.Default;
/** /**
* @author there * @author there
@@ -1,6 +1,6 @@
package com.cf.imes.framework.es.core.valid; package com.cf.imes.framework.es.core.valid;
import javax.validation.groups.Default; import jakarta.validation.groups.Default;
/** /**
* @author there * @author there
@@ -6,7 +6,7 @@ import lombok.SneakyThrows;
import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.ServiceInstance;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import javax.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import java.net.InetAddress; import java.net.InetAddress;
import java.util.Objects; import java.util.Objects;
@@ -5,10 +5,10 @@ import com.cf.imes.framework.env.core.context.EnvContextHolder;
import com.cf.imes.framework.env.core.util.EnvUtils; import com.cf.imes.framework.env.core.util.EnvUtils;
import org.springframework.web.filter.OncePerRequestFilter; import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain; import jakarta.servlet.FilterChain;
import javax.servlet.ServletException; import jakarta.servlet.ServletException;
import javax.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException; import java.io.IOException;
/** /**
@@ -5,7 +5,7 @@ import com.alibaba.excel.converters.longconverter.LongStringConverter;
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy; import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException; import java.io.IOException;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
@@ -1,37 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>cf-framework</artifactId>
<groupId>com.cf.imes</groupId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cf-spring-boot-starter-flowable</artifactId>
<dependencies>
<dependency>
<groupId>com.cf.imes</groupId>
<artifactId>cf-common</artifactId>
</dependency>
<!-- Web 相关 -->
<dependency>
<groupId>com.cf.imes</groupId>
<artifactId>cf-spring-boot-starter-security</artifactId>
</dependency>
<!-- flowable 工作流相关 -->
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter-process</artifactId>
</dependency>
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
</project>
@@ -1,46 +0,0 @@
package com.cf.imes.framework.flowable.config;
import com.cf.imes.framework.common.enums.WebFilterOrderEnum;
import com.cf.imes.framework.flowable.core.web.FlowableWebFilter;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.core.task.AsyncListenableTaskExecutor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
@AutoConfiguration
public class ChenfengFlowableConfiguration {
/**
* 参考 {@link org.flowable.spring.boot.FlowableJobConfiguration} 类,创建对应的 AsyncListenableTaskExecutor Bean
*
* 如果不创建,会导致项目启动时,Flowable 报错的问题
*/
@Bean(name = "applicationTaskExecutor")
@ConditionalOnMissingBean(name = "applicationTaskExecutor")
public AsyncListenableTaskExecutor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(8);
executor.setMaxPoolSize(8);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("flowable-task-Executor-");
executor.setAwaitTerminationSeconds(30);
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setAllowCoreThreadTimeOut(true);
executor.initialize();
return executor;
}
/**
* 配置 flowable Web 过滤器
*/
@Bean
public FilterRegistrationBean<FlowableWebFilter> flowableWebFilter() {
FilterRegistrationBean<FlowableWebFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new FlowableWebFilter());
registrationBean.setOrder(WebFilterOrderEnum.FLOWABLE_FILTER);
return registrationBean;
}
}
@@ -1 +0,0 @@
package com.cf.imes.framework.flowable.core;
@@ -1,309 +0,0 @@
package com.cf.imes.framework.flowable.core.util;
import cn.hutool.core.collection.CollUtil;
import org.flowable.bpmn.converter.BpmnXMLConverter;
import org.flowable.bpmn.model.Process;
import org.flowable.bpmn.model.*;
import java.util.*;
/**
* 流程模型转操作工具类
*/
public class BpmnModelUtils {
/**
* 根据节点,获取入口连线
*
* @param source 起始节点
* @return 入口连线列表
*/
public static List<SequenceFlow> getElementIncomingFlows(FlowElement source) {
if (source instanceof FlowNode) {
return ((FlowNode) source).getIncomingFlows();
}
return new ArrayList<>();
}
/**
* 根据节点,获取出口连线
*
* @param source 起始节点
* @return 出口连线列表
*/
public static List<SequenceFlow> getElementOutgoingFlows(FlowElement source) {
if (source instanceof FlowNode) {
return ((FlowNode) source).getOutgoingFlows();
}
return new ArrayList<>();
}
/**
* 获取流程元素信息
*
* @param model bpmnModel 对象
* @param flowElementId 元素 ID
* @return 元素信息
*/
public static FlowElement getFlowElementById(BpmnModel model, String flowElementId) {
Process process = model.getMainProcess();
return process.getFlowElement(flowElementId);
}
/**
* 获得 BPMN 流程中,指定的元素们
*
* @param model
* @param clazz 指定元素。例如说,{@link UserTask}、{@link Gateway} 等等
* @return 元素们
*/
public static <T extends FlowElement> List<T> getBpmnModelElements(BpmnModel model, Class<T> clazz) {
List<T> result = new ArrayList<>();
model.getProcesses().forEach(process -> {
process.getFlowElements().forEach(flowElement -> {
if (flowElement.getClass().isAssignableFrom(clazz)) {
result.add((T) flowElement);
}
});
});
return result;
}
/**
* 比较 两个bpmnModel 是否相同
* @param oldModel 老的bpmn model
* @param newModel 新的bpmn model
*/
public static boolean equals(BpmnModel oldModel, BpmnModel newModel) {
// 由于 BpmnModel 未提供 equals 方法,所以只能转成字节数组,进行比较
return Arrays.equals(getBpmnBytes(oldModel), getBpmnBytes(newModel));
}
/**
* 把 bpmnModel 转换成 byte[]
* @param model bpmnModel
*/
public static byte[] getBpmnBytes(BpmnModel model) {
if (model == null) {
return new byte[0];
}
BpmnXMLConverter converter = new BpmnXMLConverter();
return converter.convertToXML(model);
}
// ========== 遍历相关的方法 ==========
/**
* 找到 source 节点之前的所有用户任务节点
*
* @param source 起始节点
* @param hasSequenceFlow 已经经过的连线的 ID,用于判断线路是否重复
* @param userTaskList 已找到的用户任务节点
* @return 用户任务节点 数组
*/
public static List<UserTask> getPreviousUserTaskList(FlowElement source, Set<String> hasSequenceFlow, List<UserTask> userTaskList) {
userTaskList = userTaskList == null ? new ArrayList<>() : userTaskList;
hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow;
// 如果该节点为开始节点,且存在上级子节点,则顺着上级子节点继续迭代
if (source instanceof StartEvent && source.getSubProcess() != null) {
userTaskList = getPreviousUserTaskList(source.getSubProcess(), hasSequenceFlow, userTaskList);
}
// 根据类型,获取入口连线
List<SequenceFlow> sequenceFlows = getElementIncomingFlows(source);
if (sequenceFlows == null) {
return userTaskList;
}
// 循环找到目标元素
for (SequenceFlow sequenceFlow : sequenceFlows) {
// 如果发现连线重复,说明循环了,跳过这个循环
if (hasSequenceFlow.contains(sequenceFlow.getId())) {
continue;
}
// 添加已经走过的连线
hasSequenceFlow.add(sequenceFlow.getId());
// 类型为用户节点,则新增父级节点
if (sequenceFlow.getSourceFlowElement() instanceof UserTask) {
userTaskList.add((UserTask) sequenceFlow.getSourceFlowElement());
}
// 类型为子流程,则添加子流程开始节点出口处相连的节点
if (sequenceFlow.getSourceFlowElement() instanceof SubProcess) {
// 获取子流程用户任务节点
List<UserTask> childUserTaskList = findChildProcessUserTaskList((StartEvent) ((SubProcess) sequenceFlow.getSourceFlowElement()).getFlowElements().toArray()[0], null, null);
// 如果找到节点,则说明该线路找到节点,不继续向下找,反之继续
if (CollUtil.isNotEmpty(childUserTaskList)) {
userTaskList.addAll(childUserTaskList);
}
}
// 继续迭代
userTaskList = getPreviousUserTaskList(sequenceFlow.getSourceFlowElement(), hasSequenceFlow, userTaskList);
}
return userTaskList;
}
/**
* 迭代获取子流程用户任务节点
*
* @param source 起始节点
* @param hasSequenceFlow 已经经过的连线的 ID,用于判断线路是否重复
* @param userTaskList 需要撤回的用户任务列表
* @return 用户任务节点
*/
public static List<UserTask> findChildProcessUserTaskList(FlowElement source, Set<String> hasSequenceFlow, List<UserTask> userTaskList) {
hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow;
userTaskList = userTaskList == null ? new ArrayList<>() : userTaskList;
// 根据类型,获取出口连线
List<SequenceFlow> sequenceFlows = getElementOutgoingFlows(source);
if (sequenceFlows == null) {
return userTaskList;
}
// 循环找到目标元素
for (SequenceFlow sequenceFlow : sequenceFlows) {
// 如果发现连线重复,说明循环了,跳过这个循环
if (hasSequenceFlow.contains(sequenceFlow.getId())) {
continue;
}
// 添加已经走过的连线
hasSequenceFlow.add(sequenceFlow.getId());
// 如果为用户任务类型,且任务节点的 Key 正在运行的任务中存在,添加
if (sequenceFlow.getTargetFlowElement() instanceof UserTask) {
userTaskList.add((UserTask) sequenceFlow.getTargetFlowElement());
continue;
}
// 如果节点为子流程节点情况,则从节点中的第一个节点开始获取
if (sequenceFlow.getTargetFlowElement() instanceof SubProcess) {
List<UserTask> childUserTaskList = findChildProcessUserTaskList((FlowElement) (((SubProcess) sequenceFlow.getTargetFlowElement()).getFlowElements().toArray()[0]), hasSequenceFlow, null);
// 如果找到节点,则说明该线路找到节点,不继续向下找,反之继续
if (CollUtil.isNotEmpty(childUserTaskList)) {
userTaskList.addAll(childUserTaskList);
continue;
}
}
// 继续迭代
userTaskList = findChildProcessUserTaskList(sequenceFlow.getTargetFlowElement(), hasSequenceFlow, userTaskList);
}
return userTaskList;
}
/**
* 迭代从后向前扫描,判断目标节点相对于当前节点是否是串行
* 不存在直接回退到子流程中的情况,但存在从子流程出去到父流程情况
*
* @param source 起始节点
* @param target 目标节点
* @param visitedElements 已经经过的连线的 ID,用于判断线路是否重复
* @return 结果
*/
public static boolean isSequentialReachable(FlowElement source, FlowElement target, Set<String> visitedElements) {
visitedElements = visitedElements == null ? new HashSet<>() : visitedElements;
// 不能是开始事件和子流程
if (source instanceof StartEvent && isInEventSubprocess(source)) {
return false;
}
// 根据类型,获取入口连线
List<SequenceFlow> sequenceFlows = getElementIncomingFlows(source);
if (CollUtil.isEmpty(sequenceFlows)) {
return true;
}
// 循环找到目标元素
for (SequenceFlow sequenceFlow : sequenceFlows) {
// 如果发现连线重复,说明循环了,跳过这个循环
if (visitedElements.contains(sequenceFlow.getId())) {
continue;
}
// 添加已经走过的连线
visitedElements.add(sequenceFlow.getId());
// 这条线路存在目标节点,这条线路完成,进入下个线路
FlowElement sourceFlowElement = sequenceFlow.getSourceFlowElement();
if (target.getId().equals(sourceFlowElement.getId())) {
continue;
}
// 如果目标节点为并行网关,则不继续
if (sourceFlowElement instanceof ParallelGateway) {
return false;
}
// 否则就继续迭代
if (!isSequentialReachable(sourceFlowElement, target, visitedElements)) {
return false;
}
}
return true;
}
/**
* 判断当前节点是否属于不同的子流程
*
* @param flowElement 被判断的节点
* @return true 表示属于子流程
*/
private static boolean isInEventSubprocess(FlowElement flowElement) {
FlowElementsContainer flowElementsContainer = flowElement.getParentContainer();
while (flowElementsContainer != null) {
if (flowElementsContainer instanceof EventSubProcess) {
return true;
}
if (flowElementsContainer instanceof FlowElement) {
flowElementsContainer = ((FlowElement) flowElementsContainer).getParentContainer();
} else {
flowElementsContainer = null;
}
}
return false;
}
/**
* 根据正在运行的任务节点,迭代获取子级任务节点列表,向后找
*
* @param source 起始节点
* @param runTaskKeyList 正在运行的任务 Key,用于校验任务节点是否是正在运行的节点
* @param hasSequenceFlow 已经经过的连线的 ID,用于判断线路是否重复
* @param userTaskList 需要撤回的用户任务列表
* @return 子级任务节点列表
*/
public static List<UserTask> iteratorFindChildUserTasks(FlowElement source, List<String> runTaskKeyList,
Set<String> hasSequenceFlow, List<UserTask> userTaskList) {
hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow;
userTaskList = userTaskList == null ? new ArrayList<>() : userTaskList;
// 如果该节点为开始节点,且存在上级子节点,则顺着上级子节点继续迭代
if (source instanceof StartEvent && source.getSubProcess() != null) {
userTaskList = iteratorFindChildUserTasks(source.getSubProcess(), runTaskKeyList, hasSequenceFlow, userTaskList);
}
// 根据类型,获取出口连线
List<SequenceFlow> sequenceFlows = getElementOutgoingFlows(source);
if (sequenceFlows == null) {
return userTaskList;
}
// 循环找到目标元素
for (SequenceFlow sequenceFlow : sequenceFlows) {
// 如果发现连线重复,说明循环了,跳过这个循环
if (hasSequenceFlow.contains(sequenceFlow.getId())) {
continue;
}
// 添加已经走过的连线
hasSequenceFlow.add(sequenceFlow.getId());
// 如果为用户任务类型,且任务节点的 Key 正在运行的任务中存在,添加
if (sequenceFlow.getTargetFlowElement() instanceof UserTask && runTaskKeyList.contains((sequenceFlow.getTargetFlowElement()).getId())) {
userTaskList.add((UserTask) sequenceFlow.getTargetFlowElement());
continue;
}
// 如果节点为子流程节点情况,则从节点中的第一个节点开始获取
if (sequenceFlow.getTargetFlowElement() instanceof SubProcess) {
List<UserTask> childUserTaskList = iteratorFindChildUserTasks((FlowElement) (((SubProcess) sequenceFlow.getTargetFlowElement()).getFlowElements().toArray()[0]), runTaskKeyList, hasSequenceFlow, null);
// 如果找到节点,则说明该线路找到节点,不继续向下找,反之继续
if (CollUtil.isNotEmpty(childUserTaskList)) {
userTaskList.addAll(childUserTaskList);
continue;
}
}
// 继续迭代
userTaskList = iteratorFindChildUserTasks(sequenceFlow.getTargetFlowElement(), runTaskKeyList, hasSequenceFlow, userTaskList);
}
return userTaskList;
}
}
@@ -1,32 +0,0 @@
package com.cf.imes.framework.flowable.core.util;
import org.flowable.common.engine.impl.identity.Authentication;
/**
* Flowable 相关的工具方法
*
* @author 晨丰科技
*/
public class FlowableUtils {
// ========== User 相关的工具方法 ==========
public static void setAuthenticatedUserId(Long userId) {
Authentication.setAuthenticatedUserId(String.valueOf(userId));
}
public static void clearAuthenticatedUserId() {
Authentication.setAuthenticatedUserId(null);
}
// ========== Execution 相关的工具方法 ==========
public static String formatCollectionVariable(String activityId) {
return activityId + "_assignees";
}
public static String formatCollectionElementVariable(String activityId) {
return activityId + "_assignee";
}
}
@@ -1,35 +0,0 @@
package com.cf.imes.framework.flowable.core.web;
import com.cf.imes.framework.flowable.core.util.FlowableUtils;
import com.cf.imes.framework.security.core.util.SecurityFrameworkUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* flowable Web 过滤器,将 userId 设置到 {@link org.flowable.common.engine.impl.identity.Authentication} 中
*
* @author jason
*/
public class FlowableWebFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
try {
// 设置工作流的用户
Long userId = SecurityFrameworkUtils.getLoginUserId();
if (userId != null) {
FlowableUtils.setAuthenticatedUserId(userId);
}
// 过滤
chain.doFilter(request, response);
} finally {
// 清理
FlowableUtils.clearAuthenticatedUserId();
}
}
}
@@ -1 +0,0 @@
package com.cf.imes.framework.flowable;
@@ -1 +0,0 @@
com.cf.imes.framework.flowable.config.ChenfengFlowableConfiguration
@@ -4,9 +4,9 @@ import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import javax.validation.Valid; import jakarta.validation.Valid;
import javax.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
/** /**
* XXL-Job 配置类 * XXL-Job 配置类
@@ -16,7 +16,7 @@ import org.springframework.context.annotation.Bean;
* @author mashu * @author mashu
*/ */
@AutoConfiguration @AutoConfiguration
@ConditionalOnClass({BizTraceAspect.class}) @ConditionalOnClass(value = {BizTraceAspect.class}, name = "jakarta.servlet.Filter")
@EnableConfigurationProperties(TracerProperties.class) @EnableConfigurationProperties(TracerProperties.class)
@ConditionalOnProperty(prefix = "chenfeng.tracer", value = "enable", matchIfMissing = true) @ConditionalOnProperty(prefix = "chenfeng.tracer", value = "enable", matchIfMissing = true)
public class ChenfengTracerAutoConfiguration { public class ChenfengTracerAutoConfiguration {
@@ -3,10 +3,10 @@ package com.cf.imes.framework.tracer.core.filter;
import org.slf4j.MDC; import org.slf4j.MDC;
import org.springframework.web.filter.OncePerRequestFilter; import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain; import jakarta.servlet.FilterChain;
import javax.servlet.ServletException; import jakarta.servlet.ServletException;
import javax.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException; import java.io.IOException;
import java.util.UUID; import java.util.UUID;
@@ -68,11 +68,11 @@ public class ChenfengRedisMQConsumerAutoConfiguration {
@Bean @Bean
@ConditionalOnBean(AbstractRedisSimpleMessageListener.class) @ConditionalOnBean(AbstractRedisSimpleMessageListener.class)
public RedisMessageListenerContainer redisDelListenerContainer( public RedisMessageListenerContainer redisDelListenerContainer(
RedisTemplate redisTemplate, List<AbstractRedisSimpleMessageListener> listeners) { RedisMQTemplate redisMQTemplate, List<AbstractRedisSimpleMessageListener> listeners) {
// 创建 RedisMessageListenerContainer 对象 // 创建 RedisMessageListenerContainer 对象
RedisMessageListenerContainer container = new RedisMessageListenerContainer(); RedisMessageListenerContainer container = new RedisMessageListenerContainer();
// 设置 RedisConnection 工厂。 // 设置 RedisConnection 工厂。
container.setConnectionFactory(redisTemplate.getRequiredConnectionFactory()); container.setConnectionFactory(redisMQTemplate.getRedisTemplate().getRequiredConnectionFactory());
// 添加监听器 // 添加监听器
listeners.forEach(listener -> { listeners.forEach(listener -> {
container.addMessageListener(listener, listener.getTopic()); container.addMessageListener(listener, listener.getTopic());
@@ -56,15 +56,25 @@
<dependency> <dependency>
<groupId>com.alibaba</groupId> <groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId> <artifactId>druid-spring-boot-3-starter</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.baomidou</groupId> <groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId> <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.baomidou</groupId> <groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId> <!-- 多数据源 --> <artifactId>mybatis-plus-jsqlparser</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot3-starter</artifactId> <!-- 多数据源 -->
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</exclusion>
</exclusions>
</dependency> </dependency>
<dependency> <dependency>
@@ -1,7 +1,7 @@
package com.cf.imes.framework.datasource.config; package com.cf.imes.framework.datasource.config;
import com.cf.imes.framework.datasource.core.filter.DruidAdRemoveFilter; import com.cf.imes.framework.datasource.core.filter.DruidAdRemoveFilter;
import com.alibaba.druid.spring.boot.autoconfigure.properties.DruidStatProperties; import com.alibaba.druid.spring.boot3.autoconfigure.properties.DruidStatProperties;
import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
@@ -4,7 +4,6 @@ import com.baomidou.dynamic.datasource.event.DataSourceInitEvent;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceAutoConfiguration; import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceAutoConfiguration;
import com.cf.imes.framework.mybatis.core.event.ChenfengDataSourceEncryptInitEvent; import com.cf.imes.framework.mybatis.core.event.ChenfengDataSourceEncryptInitEvent;
import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
@@ -13,8 +12,7 @@ import org.springframework.context.annotation.Bean;
* @author Gqr * @author Gqr
* @since 2024/8/26 18:20 * @since 2024/8/26 18:20
*/ */
@AutoConfiguration @AutoConfiguration(before = DynamicDataSourceAutoConfiguration.class)
@AutoConfigureBefore(DynamicDataSourceAutoConfiguration.class)
@ConditionalOnProperty(name = "chenfeng.encrypt.enable", havingValue = "true") @ConditionalOnProperty(name = "chenfeng.encrypt.enable", havingValue = "true")
public class ChenfengDataSourceEncryptConfiguration { public class ChenfengDataSourceEncryptConfiguration {
@Bean @Bean
@@ -3,10 +3,10 @@ package com.cf.imes.framework.datasource.core.filter;
import com.alibaba.druid.util.Utils; import com.alibaba.druid.util.Utils;
import org.springframework.web.filter.OncePerRequestFilter; import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain; import jakarta.servlet.FilterChain;
import javax.servlet.ServletException; import jakarta.servlet.ServletException;
import javax.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException; import java.io.IOException;
/** /**
@@ -1,6 +1,7 @@
package com.cf.imes.framework.mybatis.config; package com.cf.imes.framework.mybatis.config;
import cn.hutool.core.text.CharSequenceUtil; import cn.hutool.core.text.CharSequenceUtil;
import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration;
import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator; import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.cf.imes.framework.mybatis.core.generator.SnowFlakeGenerator; import com.cf.imes.framework.mybatis.core.generator.SnowFlakeGenerator;
@@ -25,7 +26,7 @@ import org.springframework.core.env.ConfigurableEnvironment;
* *
* @author 晨丰科技 * @author 晨丰科技
*/ */
@AutoConfiguration @AutoConfiguration(before = MybatisPlusAutoConfiguration.class) // 目的:先于 MyBatis Plus 自动配置,避免 @MapperScan 可能扫描不到 Mapper 打印 warn 日志
@MapperScan(value = "${chenfeng.info.base-package}", annotationClass = Mapper.class, @MapperScan(value = "${chenfeng.info.base-package}", annotationClass = Mapper.class,
lazyInitialization = "${mybatis.lazy-initialization:false}") // Mapper 懒加载,目前仅用于单元测试 lazyInitialization = "${mybatis.lazy-initialization:false}") // Mapper 懒加载,目前仅用于单元测试
public class ChenfengMybatisAutoConfiguration { public class ChenfengMybatisAutoConfiguration {
@@ -13,8 +13,8 @@ import org.apache.ibatis.reflection.MetaObject;
import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import javax.annotation.Resource; import jakarta.annotation.Resource;
import javax.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.List; import java.util.List;
@@ -172,10 +172,6 @@ public interface BaseMapperX<T> extends MPJBaseMapper<T> {
return Db.updateBatchById(entities, size); return Db.updateBatchById(entities, size);
} }
default Boolean insertOrUpdate(T entity) {
return Db.saveOrUpdate(entity);
}
default Boolean insertOrUpdateBatch(Collection<T> collection) { default Boolean insertOrUpdateBatch(Collection<T> collection) {
return Db.saveOrUpdateBatch(collection); return Db.saveOrUpdateBatch(collection);
} }
@@ -4,6 +4,7 @@ import com.cf.imes.framework.common.util.json.JsonUtils;
import com.baomidou.mybatisplus.extension.handlers.AbstractJsonTypeHandler; import com.baomidou.mybatisplus.extension.handlers.AbstractJsonTypeHandler;
import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.core.type.TypeReference;
import java.lang.reflect.Field;
import java.util.Set; import java.util.Set;
/** /**
@@ -18,13 +19,21 @@ public class JsonLongSetTypeHandler extends AbstractJsonTypeHandler<Object> {
private static final TypeReference<Set<Long>> TYPE_REFERENCE = new TypeReference<Set<Long>>(){}; private static final TypeReference<Set<Long>> TYPE_REFERENCE = new TypeReference<Set<Long>>(){};
public JsonLongSetTypeHandler(Class<?> type, Field field) {
super(type, field);
}
public JsonLongSetTypeHandler(Class<?> type) {
super(type);
}
@Override @Override
protected Object parse(String json) { public Object parse(String json) {
return JsonUtils.parseObject(json, TYPE_REFERENCE); return JsonUtils.parseObject(json, TYPE_REFERENCE);
} }
@Override @Override
protected String toJson(Object obj) { public String toJson(Object obj) {
return JsonUtils.toJsonString(obj); return JsonUtils.toJsonString(obj);
} }
@@ -1,38 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>com.cf.imes</groupId>
<artifactId>cf-framework</artifactId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cf-spring-boot-starter-protection</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>服务保证,提供分布式锁、幂等、限流、熔断等等功能</description>
<dependencies>
<!-- DB 相关 -->
<dependency>
<groupId>com.cf.imes</groupId>
<artifactId>cf-spring-boot-starter-redis</artifactId>
</dependency>
<!-- 服务保障相关 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>lock4j-redisson-spring-boot-starter</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-spring-boot2</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>
@@ -1,40 +0,0 @@
package com.cf.imes.framework.idempotent.config;
import com.cf.imes.framework.idempotent.core.aop.IdempotentAspect;
import com.cf.imes.framework.idempotent.core.keyresolver.impl.DefaultIdempotentKeyResolver;
import com.cf.imes.framework.idempotent.core.keyresolver.impl.ExpressionIdempotentKeyResolver;
import com.cf.imes.framework.idempotent.core.keyresolver.IdempotentKeyResolver;
import com.cf.imes.framework.idempotent.core.redis.IdempotentRedisDAO;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import com.cf.imes.framework.redis.config.ChenfengRedisAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.core.StringRedisTemplate;
import java.util.List;
@AutoConfiguration(after = ChenfengRedisAutoConfiguration.class)
public class ChenfengIdempotentConfiguration {
@Bean
public IdempotentAspect idempotentAspect(List<IdempotentKeyResolver> keyResolvers, IdempotentRedisDAO idempotentRedisDAO) {
return new IdempotentAspect(keyResolvers, idempotentRedisDAO);
}
@Bean
public IdempotentRedisDAO idempotentRedisDAO(StringRedisTemplate stringRedisTemplate) {
return new IdempotentRedisDAO(stringRedisTemplate);
}
// ========== 各种 IdempotentKeyResolver Bean ==========
@Bean
public DefaultIdempotentKeyResolver defaultIdempotentKeyResolver() {
return new DefaultIdempotentKeyResolver();
}
@Bean
public ExpressionIdempotentKeyResolver expressionIdempotentKeyResolver() {
return new ExpressionIdempotentKeyResolver();
}
}
@@ -1,46 +0,0 @@
package com.cf.imes.framework.idempotent.core.annotation;
import com.cf.imes.framework.idempotent.core.keyresolver.impl.DefaultIdempotentKeyResolver;
import com.cf.imes.framework.idempotent.core.keyresolver.IdempotentKeyResolver;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.concurrent.TimeUnit;
/**
* 幂等注解
*
* @author 晨丰科技
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {
/**
* 幂等的超时时间,默认为 1 秒
*
* 注意,如果执行时间超过它,请求还是会进来
*/
int timeout() default 1;
/**
* 时间单位,默认为 SECONDS 秒
*/
TimeUnit timeUnit() default TimeUnit.SECONDS;
/**
* 提示信息,正在执行中的提示
*/
String message() default "重复请求,请稍后重试";
/**
* 使用的 Key 解析器
*/
Class<? extends IdempotentKeyResolver> keyResolver() default DefaultIdempotentKeyResolver.class;
/**
* 使用的 Key 参数
*/
String keyArg() default "";
}
@@ -1,56 +0,0 @@
package com.cf.imes.framework.idempotent.core.aop;
import com.cf.imes.framework.common.exception.ServiceException;
import com.cf.imes.framework.common.exception.enums.GlobalErrorCodeConstants;
import com.cf.imes.framework.idempotent.core.annotation.Idempotent;
import com.cf.imes.framework.idempotent.core.keyresolver.IdempotentKeyResolver;
import com.cf.imes.framework.idempotent.core.redis.IdempotentRedisDAO;
import com.cf.imes.framework.common.util.collection.CollectionUtils;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.util.Assert;
import java.util.List;
import java.util.Map;
/**
* 拦截声明了 {@link Idempotent} 注解的方法,实现幂等操作
*
* @author 晨丰科技
*/
@Aspect
@Slf4j
public class IdempotentAspect {
/**
* IdempotentKeyResolver 集合
*/
private final Map<Class<? extends IdempotentKeyResolver>, IdempotentKeyResolver> keyResolvers;
private final IdempotentRedisDAO idempotentRedisDAO;
public IdempotentAspect(List<IdempotentKeyResolver> keyResolvers, IdempotentRedisDAO idempotentRedisDAO) {
this.keyResolvers = CollectionUtils.convertMap(keyResolvers, IdempotentKeyResolver::getClass);
this.idempotentRedisDAO = idempotentRedisDAO;
}
@Before("@annotation(idempotent)")
public void beforePointCut(JoinPoint joinPoint, Idempotent idempotent) {
// 获得 IdempotentKeyResolver
IdempotentKeyResolver keyResolver = keyResolvers.get(idempotent.keyResolver());
Assert.notNull(keyResolver, "找不到对应的 IdempotentKeyResolver");
// 解析 Key
String key = keyResolver.resolver(joinPoint, idempotent);
// 锁定 Key。
boolean success = idempotentRedisDAO.setIfAbsent(key, idempotent.timeout(), idempotent.timeUnit());
// 锁定失败,抛出异常
if (!success) {
log.info("[beforePointCut][方法({}) 参数({}) 存在重复请求]", joinPoint.getSignature().toString(), joinPoint.getArgs());
throw new ServiceException(GlobalErrorCodeConstants.REPEATED_REQUESTS.getCode(), idempotent.message());
}
}
}
@@ -1,22 +0,0 @@
package com.cf.imes.framework.idempotent.core.keyresolver;
import com.cf.imes.framework.idempotent.core.annotation.Idempotent;
import org.aspectj.lang.JoinPoint;
/**
* 幂等 Key 解析器接口
*
* @author 晨丰科技
*/
public interface IdempotentKeyResolver {
/**
* 解析一个 Key
*
* @param idempotent 幂等注解
* @param joinPoint AOP 切面
* @return Key
*/
String resolver(JoinPoint joinPoint, Idempotent idempotent);
}
@@ -1,25 +0,0 @@
package com.cf.imes.framework.idempotent.core.keyresolver.impl;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.crypto.SecureUtil;
import com.cf.imes.framework.idempotent.core.annotation.Idempotent;
import com.cf.imes.framework.idempotent.core.keyresolver.IdempotentKeyResolver;
import org.aspectj.lang.JoinPoint;
/**
* 默认幂等 Key 解析器,使用方法名 + 方法参数,组装成一个 Key
*
* 为了避免 Key 过长,使用 MD5 进行“压缩”
*
* @author 晨丰科技
*/
public class DefaultIdempotentKeyResolver implements IdempotentKeyResolver {
@Override
public String resolver(JoinPoint joinPoint, Idempotent idempotent) {
String methodName = joinPoint.getSignature().toString();
String argsStr = CharSequenceUtil.join(",", joinPoint.getArgs());
return SecureUtil.md5(methodName + argsStr);
}
}
@@ -1,63 +0,0 @@
package com.cf.imes.framework.idempotent.core.keyresolver.impl;
import cn.hutool.core.util.ArrayUtil;
import com.cf.imes.framework.idempotent.core.annotation.Idempotent;
import com.cf.imes.framework.idempotent.core.keyresolver.IdempotentKeyResolver;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import java.lang.reflect.Method;
/**
* 基于 Spring EL 表达式,
*
* @author 晨丰科技
*/
public class ExpressionIdempotentKeyResolver implements IdempotentKeyResolver {
private final ParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer();
private final ExpressionParser expressionParser = new SpelExpressionParser();
@Override
public String resolver(JoinPoint joinPoint, Idempotent idempotent) {
// 获得被拦截方法参数名列表
Method method = getMethod(joinPoint);
Object[] args = joinPoint.getArgs();
String[] parameterNames = this.parameterNameDiscoverer.getParameterNames(method);
// 准备 Spring EL 表达式解析的上下文
StandardEvaluationContext evaluationContext = new StandardEvaluationContext();
if (ArrayUtil.isNotEmpty(parameterNames)) {
for (int i = 0; i < parameterNames.length; i++) {
evaluationContext.setVariable(parameterNames[i], args[i]);
}
}
// 解析参数
Expression expression = expressionParser.parseExpression(idempotent.keyArg());
return expression.getValue(evaluationContext, String.class);
}
private static Method getMethod(JoinPoint point) {
// 处理,声明在类上的情况
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
if (!method.getDeclaringClass().isInterface()) {
return method;
}
// 处理,声明在接口上的情况
try {
return point.getTarget().getClass().getDeclaredMethod(
point.getSignature().getName(), method.getParameterTypes());
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
}
@@ -1,36 +0,0 @@
package com.cf.imes.framework.idempotent.core.redis;
import lombok.AllArgsConstructor;
import org.springframework.data.redis.core.StringRedisTemplate;
import java.util.concurrent.TimeUnit;
/**
* 幂等 Redis DAO
*
* @author 晨丰科技
*/
@AllArgsConstructor
public class IdempotentRedisDAO {
/**
* 幂等操作
*
* KEY 格式:idempotent:%s // 参数为 uuid
* VALUE 格式:String
* 过期时间:不固定
*/
private static final String IDEMPOTENT = "idempotent:%s";
private final StringRedisTemplate redisTemplate;
public Boolean setIfAbsent(String key, long timeout, TimeUnit timeUnit) {
String redisKey = formatKey(key);
return redisTemplate.opsForValue().setIfAbsent(redisKey, "", timeout, timeUnit);
}
private static String formatKey(String key) {
return String.format(IDEMPOTENT, key);
}
}
@@ -1,12 +0,0 @@
/**
* 幂等组件,参考 https://github.com/it4alla/idempotent 项目实现
* 实现原理是,相同参数的方法,一段时间内,有且仅能执行一次。通过这样的方式,保证幂等性。
*
* 使用场景:例如说,用户快速的双击了某个按钮,前端没有禁用该按钮,导致发送了两次重复的请求。
*
* 和 it4alla/idempotent 组件的差异点,主要体现在两点:
* 1. 我们去掉了 @Idempotent 注解的 delKey 属性。原因是,本质上 delKey 为 true 时,实现的是分布式锁的能力
* 此时,我们偏向使用 Lock4j 组件。原则上,一个组件只提供一种单一的能力。
* 2. 考虑到组件的通用性,我们并未像 it4alla/idempotent 组件一样使用 Redisson RMap 结构,而是直接使用 Redis 的 String 数据格式。
*/
package com.cf.imes.framework.idempotent;
@@ -1,18 +0,0 @@
package com.cf.imes.framework.lock4j.config;
import com.cf.imes.framework.lock4j.core.DefaultLockFailureStrategy;
import com.baomidou.lock.spring.boot.autoconfigure.LockAutoConfiguration;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Bean;
@AutoConfiguration(before = LockAutoConfiguration.class)
@ConditionalOnClass(name = "com.baomidou.lock.annotation.Lock4j")
public class ChenfengLock4jConfiguration {
@Bean
public DefaultLockFailureStrategy lockFailureStrategy() {
return new DefaultLockFailureStrategy();
}
}
@@ -1,21 +0,0 @@
package com.cf.imes.framework.lock4j.core;
import com.cf.imes.framework.common.exception.ServiceException;
import com.cf.imes.framework.common.exception.enums.GlobalErrorCodeConstants;
import com.baomidou.lock.LockFailureStrategy;
import lombok.extern.slf4j.Slf4j;
import java.lang.reflect.Method;
/**
* 自定义获取锁失败策略,抛出 {@link ServiceException} 异常
*/
@Slf4j
public class DefaultLockFailureStrategy implements LockFailureStrategy {
@Override
public void onLockFailure(String key, Method method, Object[] arguments) {
log.debug("[onLockFailure][线程:{} 获取锁失败,key:{} 获取失败:{} ]", Thread.currentThread().getName(), key, arguments);
throw new ServiceException(GlobalErrorCodeConstants.LOCKED);
}
}
@@ -1,4 +0,0 @@
/**
* 分布式锁组件,使用 https://gitee.com/baomidou/lock4j 开源项目
*/
package com.cf.imes.framework.lock4j;
@@ -1,9 +0,0 @@
/**
* 使用 Resilience4j 组件,实现服务保障,包括:
* 1. 熔断器
* 2. 限流器
* 3. 舱壁隔离
* 4. 重试
* 5. 限时器
*/
package com.cf.imes.framework.resilience4j;
@@ -1,2 +0,0 @@
com.cf.imes.framework.idempotent.config.ChenfengIdempotentConfiguration
com.cf.imes.framework.lock4j.config.ChenfengLock4jConfiguration
@@ -26,10 +26,6 @@
<groupId>org.redisson</groupId> <groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId> <artifactId>redisson-spring-boot-starter</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-data-27</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
@@ -81,7 +81,7 @@ public class ChenfengCacheAutoConfiguration {
} }
@Bean @Bean
public RedisLockUtil lockUtil(RedisTemplate redisTemplate) { public RedisLockUtil lockUtil(RedisTemplate<String, Object> redisTemplate) {
return new RedisLockUtil(redisTemplate); return new RedisLockUtil(redisTemplate);
} }
} }
@@ -3,6 +3,7 @@ package com.cf.imes.framework.redis.config;
import cn.hutool.core.util.ReflectUtil; import cn.hutool.core.util.ReflectUtil;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.redisson.spring.starter.RedissonAutoConfigurationV2;
import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.connection.RedisConnectionFactory;
@@ -12,7 +13,7 @@ import org.springframework.data.redis.serializer.RedisSerializer;
/** /**
* Redis 配置类 * Redis 配置类
*/ */
@AutoConfiguration @AutoConfiguration(before = RedissonAutoConfigurationV2.class) // 目的:使用自己定义的 RedisTemplate Bean
public class ChenfengRedisAutoConfiguration { public class ChenfengRedisAutoConfiguration {
/** /**
@@ -10,8 +10,8 @@ import com.mzt.logapi.beans.LogRecord;
import com.mzt.logapi.service.ILogRecordService; import com.mzt.logapi.service.ILogRecordService;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import javax.annotation.Resource; import jakarta.annotation.Resource;
import javax.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import java.util.List; import java.util.List;
/** /**
@@ -4,9 +4,9 @@ import com.cf.imes.framework.web.config.WebProperties;
import org.springframework.core.Ordered; import org.springframework.core.Ordered;
import org.springframework.security.config.Customizer; import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer; import org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer;
import javax.annotation.Resource; import jakarta.annotation.Resource;
/** /**
* 自定义的 URL 的安全配置 * 自定义的 URL 的安全配置
@@ -15,7 +15,7 @@ import javax.annotation.Resource;
* @author 晨丰科技 * @author 晨丰科技
*/ */
public abstract class AuthorizeRequestsCustomizer public abstract class AuthorizeRequestsCustomizer
implements Customizer<ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry>, Ordered { implements Customizer<AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry>, Ordered {
@Resource @Resource
private WebProperties webProperties; private WebProperties webProperties;
@@ -12,6 +12,7 @@ import com.cf.imes.module.system.api.oauth2.OAuth2TokenApi;
import com.cf.imes.module.system.api.permission.PermissionApi; import com.cf.imes.module.system.api.permission.PermissionApi;
import org.springframework.beans.factory.config.MethodInvokingFactoryBean; import org.springframework.beans.factory.config.MethodInvokingFactoryBean;
import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigureOrder;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
@@ -22,7 +23,7 @@ import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.firewall.DefaultHttpFirewall; import org.springframework.security.web.firewall.DefaultHttpFirewall;
import org.springframework.security.web.firewall.HttpFirewall; import org.springframework.security.web.firewall.HttpFirewall;
import javax.annotation.Resource; import jakarta.annotation.Resource;
/** /**
* Spring Security 自动配置类,主要用于相关组件的配置 * Spring Security 自动配置类,主要用于相关组件的配置
@@ -33,6 +34,7 @@ import javax.annotation.Resource;
* @author 晨丰科技 * @author 晨丰科技
*/ */
@AutoConfiguration @AutoConfiguration
@AutoConfigureOrder(-1) // 目的:先于 Spring Security 自动配置,避免一键改包后,org.* 基础包无法生效
@EnableConfigurationProperties(SecurityProperties.class) @EnableConfigurationProperties(SecurityProperties.class)
public class ChenfengSecurityAutoConfiguration { public class ChenfengSecurityAutoConfiguration {
@@ -5,14 +5,19 @@ import com.cf.imes.framework.security.core.filter.TokenAuthenticationFilter;
import com.cf.imes.framework.web.config.WebProperties; import com.cf.imes.framework.web.config.WebProperties;
import com.google.common.collect.HashMultimap; import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap; import com.google.common.collect.Multimap;
import jakarta.servlet.DispatcherType;
import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigureOrder;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.SecurityFilterChain;
@@ -23,8 +28,8 @@ import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo; import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import javax.annotation.Resource; import jakarta.annotation.Resource;
import javax.annotation.security.PermitAll; import jakarta.annotation.security.PermitAll;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@@ -35,6 +40,7 @@ import java.util.Set;
* @author 晨丰科技 * @author 晨丰科技
*/ */
@AutoConfiguration @AutoConfiguration
@AutoConfigureOrder(-1) // 目的:先于 Spring Security 自动配置,避免一键改包后,org.* 基础包无法生效
@EnableMethodSecurity(securedEnabled = true) @EnableMethodSecurity(securedEnabled = true)
public class ChenfengWebSecurityConfigurerAdapter { public class ChenfengWebSecurityConfigurerAdapter {
@@ -101,15 +107,15 @@ public class ChenfengWebSecurityConfigurerAdapter {
// 登出 // 登出
httpSecurity httpSecurity
// 开启跨域 // 开启跨域
.cors().and() .cors(Customizer.withDefaults())
// CSRF 禁用,因为不使用 Session // CSRF 禁用,因为不使用 Session
.csrf().disable() .csrf(AbstractHttpConfigurer::disable)
// 基于 token 机制,所以不需要 Session // 基于 token 机制,所以不需要 Session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() .sessionManagement(c -> c.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.headers().frameOptions().disable().and() .headers(c -> c.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable))
// 一堆自定义的 Spring Security 处理器 // 一堆自定义的 Spring Security 处理器
.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint) .exceptionHandling(c -> c.authenticationEntryPoint(authenticationEntryPoint)
.accessDeniedHandler(accessDeniedHandler); .accessDeniedHandler(accessDeniedHandler));
// 登录、登录暂时不使用 Spring Security 的拓展点,主要考虑一方面拓展多用户、多种登录方式相对复杂,一方面用户的学习成本较高 // 登录、登录暂时不使用 Spring Security 的拓展点,主要考虑一方面拓展多用户、多种登录方式相对复杂,一方面用户的学习成本较高
// 获得 @PermitAll 带来的 URL 列表,免登录 // 获得 @PermitAll 带来的 URL 列表,免登录
@@ -117,25 +123,25 @@ public class ChenfengWebSecurityConfigurerAdapter {
// 设置每个请求的权限 // 设置每个请求的权限
httpSecurity httpSecurity
// ①:全局共享规则 // ①:全局共享规则
.authorizeRequests() .authorizeHttpRequests(c -> c
// 1.1 静态资源,可匿名访问 // 1.1 静态资源,可匿名访问
.antMatchers(HttpMethod.GET, "/*.html", "/**/*.html", "/**/*.css", "/**/*.js").permitAll() .requestMatchers(HttpMethod.GET, "/*.html", "/*.css", "/*.js").permitAll()
// 1.2 设置 @PermitAll 无需认证 // 1.2 设置 @PermitAll 无需认证
.antMatchers(HttpMethod.GET, permitAllUrls.get(HttpMethod.GET).toArray(new String[0])).permitAll() .requestMatchers(HttpMethod.GET, permitAllUrls.get(HttpMethod.GET).toArray(new String[0])).permitAll()
.antMatchers(HttpMethod.POST, permitAllUrls.get(HttpMethod.POST).toArray(new String[0])).permitAll() .requestMatchers(HttpMethod.POST, permitAllUrls.get(HttpMethod.POST).toArray(new String[0])).permitAll()
.antMatchers(HttpMethod.PUT, permitAllUrls.get(HttpMethod.PUT).toArray(new String[0])).permitAll() .requestMatchers(HttpMethod.PUT, permitAllUrls.get(HttpMethod.PUT).toArray(new String[0])).permitAll()
.antMatchers(HttpMethod.DELETE, permitAllUrls.get(HttpMethod.DELETE).toArray(new String[0])).permitAll() .requestMatchers(HttpMethod.DELETE, permitAllUrls.get(HttpMethod.DELETE).toArray(new String[0])).permitAll()
// 1.3 基于 chenfeng.security.permit-all-urls 无需认证 .requestMatchers(HttpMethod.HEAD, permitAllUrls.get(HttpMethod.HEAD).toArray(new String[0])).permitAll()
.antMatchers(securityProperties.getPermitAllUrls().toArray(new String[0])).permitAll() .requestMatchers(HttpMethod.PATCH, permitAllUrls.get(HttpMethod.PATCH).toArray(new String[0])).permitAll()
// 1.4 设置 App API 无需认证 // 1.3 基于 yudao.security.permit-all-urls 无需认证
.antMatchers(buildAppApi("/**")).permitAll() .requestMatchers(securityProperties.getPermitAllUrls().toArray(new String[0])).permitAll()
)
// ②:每个项目的自定义规则 // ②:每个项目的自定义规则
.and().authorizeRequests(registry -> // 下面,循环设置自定义规则 .authorizeHttpRequests(c -> authorizeRequestsCustomizers.forEach(customizer -> customizer.customize(c)))
authorizeRequestsCustomizers.forEach(customizer -> customizer.customize(registry)))
// ③:兜底规则,必须认证 // ③:兜底规则,必须认证
.authorizeRequests() .authorizeHttpRequests(c -> c
.anyRequest().authenticated() .dispatcherTypeMatchers(DispatcherType.ASYNC).permitAll() // WebFlux 异步请求,无需认证,目的:SSE 场景
; .anyRequest().authenticated());
// 添加 Token Filter // 添加 Token Filter
httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
@@ -4,8 +4,8 @@ import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import javax.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@@ -20,10 +20,10 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.AccessDeniedException;
import org.springframework.web.filter.OncePerRequestFilter; import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain; import jakarta.servlet.FilterChain;
import javax.servlet.ServletException; import jakarta.servlet.ServletException;
import javax.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException; import java.io.IOException;
import java.net.URLDecoder; import java.net.URLDecoder;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
@@ -9,10 +9,10 @@ import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler; import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.access.ExceptionTranslationFilter; import org.springframework.security.web.access.ExceptionTranslationFilter;
import javax.servlet.FilterChain; import jakarta.servlet.FilterChain;
import javax.servlet.ServletException; import jakarta.servlet.ServletException;
import javax.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException; import java.io.IOException;
import static com.cf.imes.framework.common.exception.enums.GlobalErrorCodeConstants.FORBIDDEN; import static com.cf.imes.framework.common.exception.enums.GlobalErrorCodeConstants.FORBIDDEN;
@@ -8,9 +8,9 @@ import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.ExceptionTranslationFilter; import org.springframework.security.web.access.ExceptionTranslationFilter;
import javax.servlet.FilterChain; import jakarta.servlet.FilterChain;
import javax.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import static com.cf.imes.framework.common.exception.enums.GlobalErrorCodeConstants.UNAUTHORIZED; import static com.cf.imes.framework.common.exception.enums.GlobalErrorCodeConstants.UNAUTHORIZED;
@@ -13,7 +13,7 @@ import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import javax.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import java.util.Collections; import java.util.Collections;
/** /**
@@ -5,7 +5,7 @@ import com.cf.imes.framework.mybatis.config.ChenfengMybatisAutoConfiguration;
import com.cf.imes.framework.redis.config.ChenfengRedisAutoConfiguration; import com.cf.imes.framework.redis.config.ChenfengRedisAutoConfiguration;
import com.cf.imes.framework.test.config.RedisTestConfiguration; import com.cf.imes.framework.test.config.RedisTestConfiguration;
import com.cf.imes.framework.test.config.SqlInitializationTestConfiguration; import com.cf.imes.framework.test.config.SqlInitializationTestConfiguration;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure; import com.alibaba.druid.spring.boot3.autoconfigure.DruidDataSourceAutoConfigure;
import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration; import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration;
import org.redisson.spring.starter.RedissonAutoConfiguration; import org.redisson.spring.starter.RedissonAutoConfiguration;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
@@ -3,7 +3,7 @@ package com.cf.imes.framework.test.core.ut;
import com.cf.imes.framework.datasource.config.ChenfengDataSourceAutoConfiguration; import com.cf.imes.framework.datasource.config.ChenfengDataSourceAutoConfiguration;
import com.cf.imes.framework.mybatis.config.ChenfengMybatisAutoConfiguration; import com.cf.imes.framework.mybatis.config.ChenfengMybatisAutoConfiguration;
import com.cf.imes.framework.test.config.SqlInitializationTestConfiguration; import com.cf.imes.framework.test.config.SqlInitializationTestConfiguration;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure; import com.alibaba.druid.spring.boot3.autoconfigure.DruidDataSourceAutoConfigure;
import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration; import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration;
import com.github.yulichang.autoconfigure.MybatisPlusJoinAutoConfiguration; import com.github.yulichang.autoconfigure.MybatisPlusJoinAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
@@ -45,12 +45,13 @@
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.github.xiaoymin</groupId> <!-- 接口文档 --> <groupId>com.github.xingfudeshi</groupId>
<artifactId>knife4j-openapi3-spring-boot-starter</artifactId> <artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springdoc</groupId> <!-- 接口文档 --> <groupId>org.springdoc</groupId> <!-- 接口文档:使用最新版本的 Swagger 模型 -->
<artifactId>springdoc-openapi-ui</artifactId> <artifactId>springdoc-openapi-starter-webmvc-api</artifactId>
<scope>provided</scope>
</dependency> </dependency>
<!-- RPC 远程调用相关 --> <!-- RPC 远程调用相关 -->
@@ -75,7 +76,7 @@
<dependency> <dependency>
<groupId>com.baomidou</groupId> <groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId> <!-- 多数据源 --> <artifactId>dynamic-datasource-spring-boot3-starter</artifactId> <!-- 多数据源 -->
</dependency> </dependency>
</dependencies> </dependencies>
@@ -11,7 +11,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import javax.servlet.Filter; import jakarta.servlet.Filter;
@AutoConfiguration(after = ChenfengWebAutoConfiguration.class) @AutoConfiguration(after = ChenfengWebAutoConfiguration.class)
public class ChenfengApiLogAutoConfiguration { public class ChenfengApiLogAutoConfiguration {
@@ -14,10 +14,10 @@ import com.cf.imes.module.infra.api.logger.ApiAccessLogApi;
import com.cf.imes.module.infra.api.logger.dto.ApiAccessLogCreateReqDTO; import com.cf.imes.module.infra.api.logger.dto.ApiAccessLogCreateReqDTO;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import javax.servlet.FilterChain; import jakarta.servlet.FilterChain;
import javax.servlet.ServletException; import jakarta.servlet.ServletException;
import javax.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException; import java.io.IOException;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit; import java.time.temporal.ChronoUnit;
@@ -4,7 +4,7 @@ import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import javax.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import java.util.List; import java.util.List;
/** /**
@@ -9,10 +9,14 @@ import io.swagger.v3.oas.models.media.StringSchema;
import io.swagger.v3.oas.models.parameters.Parameter; import io.swagger.v3.oas.models.parameters.Parameter;
import io.swagger.v3.oas.models.security.SecurityRequirement; import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme; import io.swagger.v3.oas.models.security.SecurityScheme;
import org.springdoc.core.*; import org.springdoc.core.models.GroupedOpenApi;
import org.springdoc.core.properties.SpringDocConfigProperties;
import org.springdoc.core.service.OpenAPIService;
import org.springdoc.core.service.SecurityService;
import org.springdoc.core.customizers.OpenApiBuilderCustomizer; import org.springdoc.core.customizers.OpenApiBuilderCustomizer;
import org.springdoc.core.customizers.ServerBaseUrlCustomizer; import org.springdoc.core.customizers.ServerBaseUrlCustomizer;
import org.springdoc.core.providers.JavadocProvider; import org.springdoc.core.providers.JavadocProvider;
import org.springdoc.core.utils.PropertyResolverUtils;
import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
@@ -3,7 +3,7 @@ package com.cf.imes.framework.swagger.config;
import lombok.Data; import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
import javax.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotEmpty;
/** /**
* Swagger 配置属性 * Swagger 配置属性
@@ -25,8 +25,8 @@ import org.springframework.web.filter.CorsFilter;
import org.springframework.web.servlet.config.annotation.PathMatchConfigurer; import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import javax.annotation.Resource; import jakarta.annotation.Resource;
import javax.servlet.Filter; import jakarta.servlet.Filter;
@AutoConfiguration @AutoConfiguration
@EnableConfigurationProperties(WebProperties.class) @EnableConfigurationProperties(WebProperties.class)
@@ -7,9 +7,9 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import org.springframework.web.servlet.config.annotation.PathMatchConfigurer; import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
import javax.validation.Valid; import jakarta.validation.Valid;
import javax.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
@ConfigurationProperties(prefix = "chenfeng.web") @ConfigurationProperties(prefix = "chenfeng.web")
@Validated @Validated
@@ -5,7 +5,7 @@ import com.cf.imes.framework.web.config.WebProperties;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.web.filter.OncePerRequestFilter; import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
/** /**
* 过滤 /admin-api、/app-api 等 API 请求的过滤器 * 过滤 /admin-api、/app-api 等 API 请求的过滤器

Some files were not shown because too many files have changed in this diff Show More