Compare commits
109 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
5476f543f3 | ||
![]() |
c820bb4e74 | ||
![]() |
d4f30e7de7 | ||
![]() |
384a3f9b92 | ||
![]() |
1e9abcb3f5 | ||
![]() |
ee3948d8e3 | ||
![]() |
4117bd6cc5 | ||
![]() |
0cbeb5b2df | ||
![]() |
335f806566 | ||
![]() |
8655030c7b | ||
![]() |
2635d74af5 | ||
![]() |
da91600948 | ||
![]() |
08cda7b792 | ||
![]() |
a0957a76c1 | ||
![]() |
fe227e62cd | ||
![]() |
502b692fee | ||
![]() |
d28e5fadb5 | ||
![]() |
935da2f093 | ||
![]() |
e78c09ddbe | ||
![]() |
ae9c718ddd | ||
![]() |
844aa24b43 | ||
![]() |
d86def6036 | ||
![]() |
108bd8e7c4 | ||
![]() |
c8738f6eb0 | ||
![]() |
6174ca90e1 | ||
![]() |
3d7fd649fd | ||
![]() |
4224ffdc64 | ||
![]() |
0b3f82660f | ||
![]() |
8230404035 | ||
![]() |
4c5bb90361 | ||
![]() |
15b8251601 | ||
![]() |
0e86f9a111 | ||
![]() |
5de6e70ca1 | ||
![]() |
ee3272918b | ||
![]() |
2219f02a17 | ||
![]() |
4606755e42 | ||
![]() |
d057d654fa | ||
![]() |
8263a252f1 | ||
![]() |
2c98a64d17 | ||
![]() |
2c721c0a09 | ||
![]() |
8555e6d684 | ||
![]() |
70d2864488 | ||
![]() |
6517cdd084 | ||
![]() |
226d18b627 | ||
![]() |
0d91f15061 | ||
![]() |
54b134e942 | ||
![]() |
a53709481c | ||
![]() |
7a67dce923 | ||
![]() |
048fb6abcb | ||
![]() |
3573a71af3 | ||
![]() |
41ea876a96 | ||
![]() |
d494c551d5 | ||
![]() |
445ef5763d | ||
![]() |
92b3efb87d | ||
![]() |
b07033ba51 | ||
![]() |
fad4f8e19e | ||
![]() |
0b99d54edf | ||
![]() |
596cf10a1c | ||
![]() |
659caa1d0e | ||
![]() |
4358633176 | ||
![]() |
9660f65dad | ||
![]() |
f57b1092e4 | ||
![]() |
00e3ee95c3 | ||
![]() |
e5b182a8ef | ||
![]() |
458f60be07 | ||
![]() |
32fd0311b3 | ||
![]() |
d60ca890a2 | ||
![]() |
9d6fc4f539 | ||
![]() |
f3b3bec8b0 | ||
![]() |
bcd06f3d2b | ||
![]() |
f8c699ebbd | ||
![]() |
c91f9686d7 | ||
![]() |
7fb5c26cee | ||
![]() |
c1fb7b4484 | ||
![]() |
918d67abef | ||
![]() |
a2e1acb66c | ||
![]() |
050a48b134 | ||
![]() |
b8ab413eab | ||
![]() |
7c649bb5b2 | ||
![]() |
c9cb308abf | ||
![]() |
0974e65c0a | ||
![]() |
fa75c7155a | ||
![]() |
2c180c89a9 | ||
![]() |
f31983c2c7 | ||
![]() |
62c8e1c877 | ||
![]() |
3bedaa7ddc | ||
![]() |
e7b6dac49f | ||
![]() |
d151c053a7 | ||
![]() |
fc2eab2a6d | ||
![]() |
e28a2a404e | ||
![]() |
773e257fa1 | ||
![]() |
651da9a92a | ||
![]() |
e5ca734018 | ||
![]() |
5b131ad3fd | ||
![]() |
fe848f418a | ||
![]() |
6d484c0bda | ||
![]() |
f366e430f1 | ||
![]() |
7e9ba78376 | ||
![]() |
5a814b947c | ||
![]() |
0af60f5ac5 | ||
![]() |
6b8f3e2ed1 | ||
![]() |
3f829e271d | ||
![]() |
8027f5c2d8 | ||
![]() |
29a620eb41 | ||
![]() |
885918d11f | ||
![]() |
6d31a7107b | ||
![]() |
29ec129069 | ||
![]() |
3f3f3d793a | ||
![]() |
2b7ba78eb1 |
@ -1,13 +1,13 @@
|
|||||||
<p align="center">
|
<p align="center">
|
||||||
<img alt="logo" src="https://gitee.com/dataprince/ruoyi-flex/raw/master/image/ruoyi-flex-logo.png">
|
<img alt="logo" src="https://gitee.com/dataprince/ruoyi-flex/raw/master/image/ruoyi-flex-logo.png">
|
||||||
</p>
|
</p>
|
||||||
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">Ruoyi-Flex V5.1.0</h1>
|
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">Ruoyi-Flex V5.2.0-SNAPSHOT</h1>
|
||||||
<h4 align="center">Ruoyi-Flex是基于JDK21、Spring Boot V3.2.X+平台 前后端分离的未来8年更快的Java开发框架</h4>
|
<h4 align="center">Ruoyi-Flex是基于JDK21、Spring Boot V3.2.X+平台 前后端分离的未来8年更快的Java开发框架</h4>
|
||||||
|
|
||||||
|
|
||||||
## 1、平台简介
|
## 1、平台简介
|
||||||
|
|
||||||
Ruoyi-Flex是一套全部开源的快速开发平台,针对”分布式集群与多租户“场景全方位升级,使用MIT开源许可协议,毫无保留给个人及企业免费使用。基于RuoYi-Vue、RuoYi-Vue-Plus,集成MyBatis-Flex、JDK21、SpringBootV3.2.X+、Lombok、Sa-Token、SpringDoc、Hutool、SpringBoot Admin、PowerJob、Vue3、Element-Plus、MinIO等优秀开源软件,支持PostgreSQL、MySQL开源数据库及其衍生分布式数据库。
|
Ruoyi-Flex是一套全部开源的快速开发平台,针对”分布式集群与多租户“场景全方位升级,使用MIT开源许可协议,毫无保留给个人及企业免费使用。基于RuoYi-Vue、RuoYi-Vue-Plus,集成MyBatis-Flex、JDK21、SpringBootV3.2.X+、Lombok、Sa-Token、SpringDoc、Hutool、SpringBoot Admin、EasyRetry、PowerJob、Vue3、Element-Plus、AntDesign-Vben、MinIO、Flowable等优秀开源软件,支持PostgreSQL、MySQL开源数据库及其衍生分布式数据库。
|
||||||
|
|
||||||
## 2、系统特色
|
## 2、系统特色
|
||||||
Ruoyi-Flex秉承“写的更少、性能更好、出错更低、交流通畅、快速入门” 的理念,为您带来全方位的赋能与提升:
|
Ruoyi-Flex秉承“写的更少、性能更好、出错更低、交流通畅、快速入门” 的理念,为您带来全方位的赋能与提升:
|
||||||
|
Binary file not shown.
BIN
doc/~$oyi-Flex-Guide.docx
Normal file
BIN
doc/~$oyi-Flex-Guide.docx
Normal file
Binary file not shown.
93
pom.xml
93
pom.xml
@ -13,47 +13,50 @@
|
|||||||
<description>Ruoyi-Flex管理系统</description>
|
<description>Ruoyi-Flex管理系统</description>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<revision>5.1.0</revision>
|
<revision>5.2.0-SNAPSHOT</revision>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||||
<java.version>21</java.version>
|
<java.version>21</java.version>
|
||||||
<spring-boot.version>3.2.2</spring-boot.version>
|
<spring-boot.version>3.2.5</spring-boot.version>
|
||||||
<mybatis-flex.version>1.7.9</mybatis-flex.version>
|
<mybatis-flex.version>1.9.4</mybatis-flex.version>
|
||||||
<satoken.version>1.37.0</satoken.version>
|
<satoken.version>1.38.0</satoken.version>
|
||||||
<HikariCP.version>5.0.1</HikariCP.version>
|
<HikariCP.version>5.1.0</HikariCP.version>
|
||||||
<bitwalker.version>1.21</bitwalker.version>
|
<bitwalker.version>1.21</bitwalker.version>
|
||||||
|
<caffeine.version>3.1.8</caffeine.version>
|
||||||
<kaptcha.version>2.3.3</kaptcha.version>
|
<kaptcha.version>2.3.3</kaptcha.version>
|
||||||
<pagehelper.version>6.1.0</pagehelper.version>
|
<pagehelper.version>6.1.0</pagehelper.version>
|
||||||
<fastjson.version>2.0.43</fastjson.version>
|
<fastjson.version>2.0.43</fastjson.version>
|
||||||
<oshi.version>6.4.8</oshi.version>
|
<oshi.version>6.4.8</oshi.version>
|
||||||
<commons.collections.version>3.2.2</commons.collections.version>
|
<commons.collections.version>3.2.2</commons.collections.version>
|
||||||
<poi.version>5.2.3</poi.version>
|
<poi.version>5.2.5</poi.version>
|
||||||
<easyexcel.version>3.3.3</easyexcel.version>
|
<easyexcel.version>3.3.3</easyexcel.version>
|
||||||
<velocity.version>2.3</velocity.version>
|
<velocity.version>2.3</velocity.version>
|
||||||
<jwt.version>0.9.1</jwt.version>
|
<jwt.version>0.9.1</jwt.version>
|
||||||
<servlet-api.version>6.0.0</servlet-api.version>
|
<servlet-api.version>6.0.0</servlet-api.version>
|
||||||
<guava.version>32.1.1-jre</guava.version>
|
<guava.version>32.1.1-jre</guava.version>
|
||||||
<springdoc.version>2.3.0</springdoc.version>
|
<springdoc.version>2.4.0</springdoc.version>
|
||||||
<springdoc-openapi-starter-common.version>2.3.0</springdoc-openapi-starter-common.version>
|
<springdoc-openapi-starter-common.version>2.4.0</springdoc-openapi-starter-common.version>
|
||||||
<therapi-runtime-javadoc.version>0.15.0</therapi-runtime-javadoc.version>
|
<therapi-runtime-javadoc.version>0.15.0</therapi-runtime-javadoc.version>
|
||||||
<snakeyaml.version>2.2</snakeyaml.version>
|
<snakeyaml.version>2.2</snakeyaml.version>
|
||||||
<lombok.version>1.18.30</lombok.version>
|
<lombok.version>1.18.30</lombok.version>
|
||||||
<mapstruct-plus.version>1.3.6</mapstruct-plus.version>
|
<mapstruct-plus.version>1.3.6</mapstruct-plus.version>
|
||||||
<mapstruct-plus.lombok.version>0.2.0</mapstruct-plus.lombok.version>
|
<mapstruct-plus.lombok.version>0.2.0</mapstruct-plus.lombok.version>
|
||||||
<hutool.version>5.8.25</hutool.version>
|
<hutool.version>5.8.27</hutool.version>
|
||||||
<redisson.version>3.26.0</redisson.version>
|
<redisson.version>3.27.2</redisson.version>
|
||||||
<lock4j.version>2.2.7</lock4j.version>
|
<lock4j.version>2.2.7</lock4j.version>
|
||||||
<alibaba-ttl.version>2.14.4</alibaba-ttl.version>
|
<alibaba-ttl.version>2.14.4</alibaba-ttl.version>
|
||||||
<spring-boot-admin.version>3.2.1</spring-boot-admin.version>
|
<spring-boot-admin.version>3.2.3</spring-boot-admin.version>
|
||||||
<powerjob.version>4.3.6</powerjob.version>
|
<powerjob.version>4.3.6</powerjob.version>
|
||||||
|
<easyretry.version>3.2.0</easyretry.version>
|
||||||
<!-- 离线IP地址定位库 -->
|
<!-- 离线IP地址定位库 -->
|
||||||
<ip2region.version>2.7.0</ip2region.version>
|
<ip2region.version>2.7.0</ip2region.version>
|
||||||
<!-- OSS 配置 -->
|
<!-- OSS 配置 -->
|
||||||
<aws-java-sdk-s3.version>1.12.600</aws-java-sdk-s3.version>
|
<aws.sdk.version>2.25.15</aws.sdk.version>
|
||||||
|
<aws.crt.version>0.29.13</aws.crt.version>
|
||||||
<!-- 加解密依赖库 -->
|
<!-- 加解密依赖库 -->
|
||||||
<bcprov-jdk.version>1.77</bcprov-jdk.version>
|
<bcprov-jdk.version>1.77</bcprov-jdk.version>
|
||||||
<!-- SMS 配置 -->
|
<!-- SMS 配置 -->
|
||||||
<sms4j.version>3.1.1</sms4j.version>
|
<sms4j.version>3.2.0</sms4j.version>
|
||||||
<!-- findbugs消除打包警告 -->
|
<!-- findbugs消除打包警告 -->
|
||||||
<jsr305.version>3.0.2</jsr305.version>
|
<jsr305.version>3.0.2</jsr305.version>
|
||||||
<!-- 三方授权认证 -->
|
<!-- 三方授权认证 -->
|
||||||
@ -65,6 +68,9 @@
|
|||||||
<maven-compiler-plugin.verison>3.11.0</maven-compiler-plugin.verison>
|
<maven-compiler-plugin.verison>3.11.0</maven-compiler-plugin.verison>
|
||||||
<maven-surefire-plugin.version>3.1.2</maven-surefire-plugin.version>
|
<maven-surefire-plugin.version>3.1.2</maven-surefire-plugin.version>
|
||||||
<flatten-maven-plugin.version>1.5.0</flatten-maven-plugin.version>
|
<flatten-maven-plugin.version>1.5.0</flatten-maven-plugin.version>
|
||||||
|
|
||||||
|
<!--工作流配置-->
|
||||||
|
<flowable.version>7.0.0</flowable.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<profiles>
|
<profiles>
|
||||||
@ -117,6 +123,15 @@
|
|||||||
<scope>import</scope>
|
<scope>import</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- flowable 的依赖配置-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.flowable</groupId>
|
||||||
|
<artifactId>flowable-bom</artifactId>
|
||||||
|
<version>${flowable.version}</version>
|
||||||
|
<type>pom</type>
|
||||||
|
<scope>import</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- common 的依赖配置-->
|
<!-- common 的依赖配置-->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.ruoyi</groupId>
|
<groupId>com.ruoyi</groupId>
|
||||||
@ -172,6 +187,13 @@
|
|||||||
<version>${satoken.version}</version>
|
<version>${satoken.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- caffeine缓存 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.github.ben-manes.caffeine</groupId>
|
||||||
|
<artifactId>caffeine</artifactId>
|
||||||
|
<version>${caffeine.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- servlet包 -->
|
<!-- servlet包 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>jakarta.servlet</groupId>
|
<groupId>jakarta.servlet</groupId>
|
||||||
@ -332,11 +354,23 @@
|
|||||||
<version>${ip2region.version}</version>
|
<version>${ip2region.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- OSS 配置 -->
|
<!-- AWS SDK for Java 2.x -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.amazonaws</groupId>
|
<groupId>software.amazon.awssdk</groupId>
|
||||||
<artifactId>aws-java-sdk-s3</artifactId>
|
<artifactId>s3</artifactId>
|
||||||
<version>${aws-java-sdk-s3.version}</version>
|
<version>${aws.sdk.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<!-- 使用AWS基于 CRT 的 S3 客户端 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>software.amazon.awssdk.crt</groupId>
|
||||||
|
<artifactId>aws-crt</artifactId>
|
||||||
|
<version>${aws.crt.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<!-- 基于 AWS CRT 的 S3 客户端的性能增强的 S3 传输管理器 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>software.amazon.awssdk</groupId>
|
||||||
|
<artifactId>s3-transfer-manager</artifactId>
|
||||||
|
<version>${aws.sdk.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- 加解密依赖库 -->
|
<!-- 加解密依赖库 -->
|
||||||
@ -411,6 +445,23 @@
|
|||||||
<version>${revision}</version>
|
<version>${revision}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- EasyRetry Client -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.aizuda</groupId>
|
||||||
|
<artifactId>easy-retry-client-starter</artifactId>
|
||||||
|
<version>${easyretry.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.aizuda</groupId>
|
||||||
|
<artifactId>easy-retry-client-core</artifactId>
|
||||||
|
<version>${easyretry.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.aizuda</groupId>
|
||||||
|
<artifactId>easy-retry-client-job-core</artifactId>
|
||||||
|
<version>${easyretry.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- 代码生成-->
|
<!-- 代码生成-->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.ruoyi</groupId>
|
<groupId>com.ruoyi</groupId>
|
||||||
@ -425,7 +476,6 @@
|
|||||||
<version>${revision}</version>
|
<version>${revision}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
<!-- demo模块 -->
|
<!-- demo模块 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.ruoyi</groupId>
|
<groupId>com.ruoyi</groupId>
|
||||||
@ -433,6 +483,13 @@
|
|||||||
<version>${revision}</version>
|
<version>${revision}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- 工作流模块 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.ruoyi</groupId>
|
||||||
|
<artifactId>ruoyi-workflow</artifactId>
|
||||||
|
<version>${revision}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</dependencyManagement>
|
</dependencyManagement>
|
||||||
|
|
||||||
|
8
ruoyi-admin/Dockerfile
Normal file
8
ruoyi-admin/Dockerfile
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# 使用官方的 Java 运行时作为父镜像
|
||||||
|
FROM registry.cn-qingdao.aliyuncs.com/yuzl1/jdk:21
|
||||||
|
|
||||||
|
# 将本地文件复制到容器中
|
||||||
|
COPY target/ruoyi-admin.jar /ruoyi-admin.jar
|
||||||
|
|
||||||
|
# 运行应用
|
||||||
|
ENTRYPOINT ["java","-jar","/ruoyi-admin.jar"]
|
@ -78,11 +78,18 @@
|
|||||||
<artifactId>spring-boot-admin-starter-client</artifactId>
|
<artifactId>spring-boot-admin-starter-client</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- powerjob 客户端 -->
|
<dependency>
|
||||||
<!-- <dependency>-->
|
<groupId>com.aizuda</groupId>
|
||||||
<!-- <groupId>tech.powerjob</groupId>-->
|
<artifactId>easy-retry-client-starter</artifactId>
|
||||||
<!-- <artifactId>powerjob-worker-spring-boot-starter</artifactId>-->
|
</dependency>
|
||||||
<!-- </dependency>-->
|
<dependency>
|
||||||
|
<groupId>com.aizuda</groupId>
|
||||||
|
<artifactId>easy-retry-client-core</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.aizuda</groupId>
|
||||||
|
<artifactId>easy-retry-client-job-core</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ package com.ruoyi;
|
|||||||
import org.springframework.boot.SpringApplication;
|
import org.springframework.boot.SpringApplication;
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
import org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration;
|
import org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration;
|
||||||
|
import org.springframework.boot.context.metrics.buffering.BufferingApplicationStartup;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 启动程序
|
* 启动程序
|
||||||
@ -14,7 +15,9 @@ public class RuoYiApplication
|
|||||||
{
|
{
|
||||||
public static void main(String[] args)
|
public static void main(String[] args)
|
||||||
{
|
{
|
||||||
SpringApplication.run(RuoYiApplication.class, args);
|
SpringApplication application = new SpringApplication(RuoYiApplication.class);
|
||||||
|
application.setApplicationStartup(new BufferingApplicationStartup(2048));
|
||||||
|
application.run(args);
|
||||||
System.out.println("(♥◠‿◠)ノ゙ RuoYi-Flex-Boot启动成功 ლ(´ڡ`ლ)゙ \n" +
|
System.out.println("(♥◠‿◠)ノ゙ RuoYi-Flex-Boot启动成功 ლ(´ڡ`ლ)゙ \n" +
|
||||||
" ███████ ██ ██ ██ ████████ ██ \n" +
|
" ███████ ██ ██ ██ ████████ ██ \n" +
|
||||||
"░██░░░░██ ░░██ ██ ░░ ░██░░░░░ ░██ \n" +
|
"░██░░░░██ ░░██ ██ ░░ ░██░░░░░ ░██ \n" +
|
||||||
|
@ -5,8 +5,6 @@ import cn.hutool.core.collection.CollUtil;
|
|||||||
import cn.hutool.core.util.ObjectUtil;
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
import com.mybatisflex.core.query.QueryWrapper;
|
import com.mybatisflex.core.query.QueryWrapper;
|
||||||
import com.ruoyi.common.core.constant.UserConstants;
|
import com.ruoyi.common.core.constant.UserConstants;
|
||||||
import com.ruoyi.common.core.core.domain.AjaxResult;
|
|
||||||
import com.ruoyi.common.core.core.domain.model.LoginUser;
|
|
||||||
import com.ruoyi.common.core.core.domain.model.SocialLoginBody;
|
import com.ruoyi.common.core.core.domain.model.SocialLoginBody;
|
||||||
import com.ruoyi.common.core.utils.*;
|
import com.ruoyi.common.core.utils.*;
|
||||||
import com.ruoyi.common.encrypt.annotation.ApiEncrypt;
|
import com.ruoyi.common.encrypt.annotation.ApiEncrypt;
|
||||||
@ -15,12 +13,11 @@ import com.ruoyi.common.security.utils.LoginHelper;
|
|||||||
import com.ruoyi.common.social.config.properties.SocialLoginConfigProperties;
|
import com.ruoyi.common.social.config.properties.SocialLoginConfigProperties;
|
||||||
import com.ruoyi.common.social.config.properties.SocialProperties;
|
import com.ruoyi.common.social.config.properties.SocialProperties;
|
||||||
import com.ruoyi.common.social.utils.SocialUtils;
|
import com.ruoyi.common.social.utils.SocialUtils;
|
||||||
import com.ruoyi.common.tenant.helper.TenantHelper;
|
import com.ruoyi.common.websocket.dto.WebSocketMessageDto;
|
||||||
import com.ruoyi.common.websocket.utils.WebSocketUtils;
|
import com.ruoyi.common.websocket.utils.WebSocketUtils;
|
||||||
import com.ruoyi.system.domain.bo.SysTenantBo;
|
import com.ruoyi.system.domain.bo.SysTenantBo;
|
||||||
import com.ruoyi.system.domain.vo.SysClientVo;
|
import com.ruoyi.system.domain.vo.SysClientVo;
|
||||||
import com.ruoyi.system.domain.vo.SysTenantVo;
|
import com.ruoyi.system.domain.vo.SysTenantVo;
|
||||||
import com.ruoyi.system.domain.vo.SysUserVo;
|
|
||||||
import com.ruoyi.system.service.*;
|
import com.ruoyi.system.service.*;
|
||||||
import com.ruoyi.web.domain.vo.LoginTenantVo;
|
import com.ruoyi.web.domain.vo.LoginTenantVo;
|
||||||
import com.ruoyi.web.domain.vo.LoginVo;
|
import com.ruoyi.web.domain.vo.LoginVo;
|
||||||
@ -33,7 +30,6 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
import com.ruoyi.common.core.core.domain.R;
|
import com.ruoyi.common.core.core.domain.R;
|
||||||
import com.ruoyi.common.core.core.domain.model.LoginBody;
|
import com.ruoyi.common.core.core.domain.model.LoginBody;
|
||||||
import com.ruoyi.common.core.core.domain.model.RegisterBody;
|
import com.ruoyi.common.core.core.domain.model.RegisterBody;
|
||||||
import com.ruoyi.system.domain.SysClient;
|
|
||||||
import com.ruoyi.web.service.IAuthStrategy;
|
import com.ruoyi.web.service.IAuthStrategy;
|
||||||
import com.ruoyi.web.service.SysLoginService;
|
import com.ruoyi.web.service.SysLoginService;
|
||||||
import me.zhyd.oauth.model.AuthResponse;
|
import me.zhyd.oauth.model.AuthResponse;
|
||||||
@ -110,7 +106,10 @@ public class AuthController {
|
|||||||
|
|
||||||
Long userId = LoginHelper.getUserId();
|
Long userId = LoginHelper.getUserId();
|
||||||
scheduledExecutorService.schedule(() -> {
|
scheduledExecutorService.schedule(() -> {
|
||||||
WebSocketUtils.sendMessage(userId, "欢迎登录RuoYi-Flex多租户管理系统");
|
WebSocketMessageDto dto = new WebSocketMessageDto();
|
||||||
|
dto.setMessage("欢迎登录RuoYi-Flex多租户管理系统");
|
||||||
|
dto.setSessionKeys(List.of(userId));
|
||||||
|
WebSocketUtils.publishMessage(dto);
|
||||||
}, 3, TimeUnit.SECONDS);
|
}, 3, TimeUnit.SECONDS);
|
||||||
|
|
||||||
return R.ok(loginVo);
|
return R.ok(loginVo);
|
||||||
|
@ -2,7 +2,10 @@ package com.ruoyi.web.controller;
|
|||||||
|
|
||||||
import cn.dev33.satoken.annotation.SaIgnore;
|
import cn.dev33.satoken.annotation.SaIgnore;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
import cn.hutool.captcha.AbstractCaptcha;
|
import cn.hutool.captcha.AbstractCaptcha;
|
||||||
import cn.hutool.captcha.generator.CodeGenerator;
|
import cn.hutool.captcha.generator.CodeGenerator;
|
||||||
@ -10,11 +13,13 @@ import cn.hutool.core.util.IdUtil;
|
|||||||
import cn.hutool.core.util.RandomUtil;
|
import cn.hutool.core.util.RandomUtil;
|
||||||
import com.ruoyi.common.core.annotation.RateLimiter;
|
import com.ruoyi.common.core.annotation.RateLimiter;
|
||||||
import com.ruoyi.common.core.constant.GlobalConstants;
|
import com.ruoyi.common.core.constant.GlobalConstants;
|
||||||
|
import com.ruoyi.common.core.core.domain.AjaxResult;
|
||||||
import com.ruoyi.common.core.enums.LimitType;
|
import com.ruoyi.common.core.enums.LimitType;
|
||||||
import com.ruoyi.common.core.utils.StringUtils;
|
import com.ruoyi.common.core.utils.StringUtils;
|
||||||
import com.ruoyi.common.core.utils.reflect.ReflectUtils;
|
import com.ruoyi.common.core.utils.reflect.ReflectUtils;
|
||||||
import com.ruoyi.common.core.utils.SpringUtils;
|
import com.ruoyi.common.core.utils.SpringUtils;
|
||||||
import com.ruoyi.common.core.core.domain.R;
|
import com.ruoyi.common.core.core.domain.R;
|
||||||
|
import com.ruoyi.common.encrypt.utils.RSAUtils;
|
||||||
import com.ruoyi.common.mail.config.properties.MailProperties;
|
import com.ruoyi.common.mail.config.properties.MailProperties;
|
||||||
import com.ruoyi.common.mail.utils.MailUtils;
|
import com.ruoyi.common.mail.utils.MailUtils;
|
||||||
import com.ruoyi.common.redis.utils.RedisUtils;
|
import com.ruoyi.common.redis.utils.RedisUtils;
|
||||||
@ -74,6 +79,7 @@ public class CaptchaController
|
|||||||
AbstractCaptcha captcha = SpringUtils.getBean(captchaProperties.getCategory().getClazz());
|
AbstractCaptcha captcha = SpringUtils.getBean(captchaProperties.getCategory().getClazz());
|
||||||
captcha.setGenerator(codeGenerator);
|
captcha.setGenerator(codeGenerator);
|
||||||
captcha.createCode();
|
captcha.createCode();
|
||||||
|
// 如果是数学验证码,使用SpEL表达式处理验证码结果
|
||||||
String code = captcha.getCode();
|
String code = captcha.getCode();
|
||||||
if (isMath) {
|
if (isMath) {
|
||||||
ExpressionParser parser = new SpelExpressionParser();
|
ExpressionParser parser = new SpelExpressionParser();
|
||||||
@ -133,4 +139,27 @@ public class CaptchaController
|
|||||||
}
|
}
|
||||||
return R.ok();
|
return R.ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/genKeyPair")
|
||||||
|
public R genKeyPair() {
|
||||||
|
Map<String,String> map=new HashMap<>();
|
||||||
|
try {
|
||||||
|
log.info("开始生产rsa秘钥");
|
||||||
|
|
||||||
|
Map<String, Object> keyPair = RSAUtils.genKeyPair();
|
||||||
|
String publicKey = RSAUtils.getPublicKey(keyPair);
|
||||||
|
String privateKey = RSAUtils.getPrivateKey(keyPair);
|
||||||
|
log.info("privateKey:"+privateKey);
|
||||||
|
String uuid="ruoyi_"+ UUID.randomUUID().toString().replace("-","");
|
||||||
|
RedisUtils.setCacheMapValue("loginRsa",uuid,privateKey);
|
||||||
|
RedisUtils.expire("loginRsa",60*60);
|
||||||
|
log.info("写入redis完成");
|
||||||
|
|
||||||
|
map.put("uuidPrivateKey",uuid);
|
||||||
|
map.put("RSA_PUBLIC_KEY",publicKey);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return R.fail("生成RSA秘钥失败,"+e.getMessage());
|
||||||
|
}
|
||||||
|
return R.ok(map);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,10 +13,19 @@ import lombok.Data;
|
|||||||
@AutoMapper(target = SysTenantVo.class)
|
@AutoMapper(target = SysTenantVo.class)
|
||||||
public class TenantListVo {
|
public class TenantListVo {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 租户编号
|
||||||
|
*/
|
||||||
private String tenantId;
|
private String tenantId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 企业名称
|
||||||
|
*/
|
||||||
private String companyName;
|
private String companyName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 域名
|
||||||
|
*/
|
||||||
private String domain;
|
private String domain;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,26 +1,30 @@
|
|||||||
package com.ruoyi.common.security.listener;
|
package com.ruoyi.web.listener;
|
||||||
|
|
||||||
import cn.dev33.satoken.config.SaTokenConfig;
|
import cn.dev33.satoken.config.SaTokenConfig;
|
||||||
import cn.dev33.satoken.listener.SaTokenListener;
|
import cn.dev33.satoken.listener.SaTokenListener;
|
||||||
import cn.dev33.satoken.stp.SaLoginModel;
|
import cn.dev33.satoken.stp.SaLoginModel;
|
||||||
import cn.hutool.http.useragent.UserAgent;
|
import cn.hutool.http.useragent.UserAgent;
|
||||||
import cn.hutool.http.useragent.UserAgentUtil;
|
import cn.hutool.http.useragent.UserAgentUtil;
|
||||||
import com.ruoyi.common.core.constant.CacheConstants;
|
|
||||||
import com.ruoyi.common.core.core.domain.dto.UserOnlineDTO;
|
|
||||||
import com.ruoyi.common.core.core.domain.model.LoginUser;
|
|
||||||
import com.ruoyi.common.core.enums.UserType;
|
|
||||||
import com.ruoyi.common.redis.utils.RedisUtils;
|
|
||||||
import com.ruoyi.common.security.utils.LoginHelper;
|
|
||||||
import com.ruoyi.common.core.utils.ip.AddressUtils;
|
|
||||||
import com.ruoyi.common.core.utils.ServletUtils;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import com.ruoyi.common.core.constant.CacheConstants;
|
||||||
|
import com.ruoyi.common.core.constant.Constants;
|
||||||
|
import com.ruoyi.common.core.core.domain.dto.UserOnlineDTO;
|
||||||
|
import com.ruoyi.common.core.utils.MessageUtils;
|
||||||
|
import com.ruoyi.common.core.utils.ServletUtils;
|
||||||
|
import com.ruoyi.common.core.utils.SpringUtils;
|
||||||
|
import com.ruoyi.common.core.utils.ip.AddressUtils;
|
||||||
|
import com.ruoyi.common.log.event.LogininforEvent;
|
||||||
|
import com.ruoyi.common.redis.utils.RedisUtils;
|
||||||
|
import com.ruoyi.common.security.utils.LoginHelper;
|
||||||
|
import com.ruoyi.common.tenant.helper.TenantHelper;
|
||||||
|
import com.ruoyi.web.service.SysLoginService;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户行为 自定义侦听器
|
* 用户行为 侦听器的实现
|
||||||
*
|
*
|
||||||
* @author Lion Li
|
* @author Lion Li
|
||||||
*/
|
*/
|
||||||
@ -30,35 +34,46 @@ import java.time.Duration;
|
|||||||
public class UserActionListener implements SaTokenListener {
|
public class UserActionListener implements SaTokenListener {
|
||||||
|
|
||||||
private final SaTokenConfig tokenConfig;
|
private final SaTokenConfig tokenConfig;
|
||||||
|
private final SysLoginService loginService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 每次登录时触发
|
* 每次登录时触发
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void doLogin(String loginType, Object loginId, String tokenValue, SaLoginModel loginModel) {
|
public void doLogin(String loginType, Object loginId, String tokenValue, SaLoginModel loginModel) {
|
||||||
UserType userType = UserType.getUserType(loginId.toString());
|
UserAgent userAgent = UserAgentUtil.parse(ServletUtils.getRequest().getHeader("User-Agent"));
|
||||||
if (userType == UserType.SYS_USER) {
|
String ip = ServletUtils.getClientIP();
|
||||||
UserAgent userAgent = UserAgentUtil.parse(ServletUtils.getRequest().getHeader("User-Agent"));
|
UserOnlineDTO dto = new UserOnlineDTO();
|
||||||
String ip = ServletUtils.getClientIP();
|
dto.setIpaddr(ip);
|
||||||
LoginUser user = LoginHelper.getLoginUser();
|
dto.setLoginLocation(AddressUtils.getRealAddressByIP(ip));
|
||||||
UserOnlineDTO dto = new UserOnlineDTO();
|
dto.setBrowser(userAgent.getBrowser().getName());
|
||||||
dto.setIpaddr(ip);
|
dto.setOs(userAgent.getOs().getName());
|
||||||
dto.setLoginLocation(AddressUtils.getRealAddressByIP(ip));
|
dto.setLoginTime(System.currentTimeMillis());
|
||||||
dto.setBrowser(userAgent.getBrowser().getName());
|
dto.setTokenId(tokenValue);
|
||||||
dto.setOs(userAgent.getOs().getName());
|
String username = (String) loginModel.getExtra(LoginHelper.USER_NAME_KEY);
|
||||||
dto.setLoginTime(System.currentTimeMillis());
|
Long tenantId = (Long) loginModel.getExtra(LoginHelper.TENANT_KEY);
|
||||||
dto.setTokenId(tokenValue);
|
dto.setUserName(username);
|
||||||
dto.setUserName(user.getUsername());
|
dto.setClientKey((String) loginModel.getExtra(LoginHelper.CLIENT_KEY));
|
||||||
dto.setDeptName(user.getDeptName());
|
dto.setDeviceType(loginModel.getDevice());
|
||||||
|
dto.setDeptName((String) loginModel.getExtra(LoginHelper.DEPT_NAME_KEY));
|
||||||
|
TenantHelper.dynamic(tenantId, () -> {
|
||||||
if(tokenConfig.getTimeout() == -1) {
|
if(tokenConfig.getTimeout() == -1) {
|
||||||
RedisUtils.setCacheObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue, dto);
|
RedisUtils.setCacheObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue, dto);
|
||||||
} else {
|
} else {
|
||||||
RedisUtils.setCacheObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue, dto, Duration.ofSeconds(tokenConfig.getTimeout()));
|
RedisUtils.setCacheObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue, dto, Duration.ofSeconds(tokenConfig.getTimeout()));
|
||||||
}
|
}
|
||||||
log.info("user doLogin, userId:{}, token:{}", loginId, tokenValue);
|
});
|
||||||
} else if (userType == UserType.APP_USER) {
|
// 记录登录日志
|
||||||
// app端 自行根据业务编写
|
LogininforEvent logininforEvent = new LogininforEvent();
|
||||||
}
|
logininforEvent.setTenantId(tenantId);
|
||||||
|
logininforEvent.setUsername(username);
|
||||||
|
logininforEvent.setStatus(Constants.LOGIN_SUCCESS);
|
||||||
|
logininforEvent.setMessage(MessageUtils.message("user.login.success"));
|
||||||
|
logininforEvent.setRequest(ServletUtils.getRequest());
|
||||||
|
SpringUtils.context().publishEvent(logininforEvent);
|
||||||
|
// 更新登录信息
|
||||||
|
loginService.recordLoginInfo((Long) loginModel.getExtra(LoginHelper.USER_KEY), ip);
|
||||||
|
log.info("user doLogin, userId:{}, token:{}", loginId, tokenValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
@ -5,7 +5,6 @@ import cn.dev33.satoken.stp.StpUtil;
|
|||||||
import cn.hutool.core.bean.BeanUtil;
|
import cn.hutool.core.bean.BeanUtil;
|
||||||
import cn.hutool.core.collection.CollUtil;
|
import cn.hutool.core.collection.CollUtil;
|
||||||
import cn.hutool.core.util.ObjectUtil;
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
import com.mybatisflex.core.tenant.TenantManager;
|
|
||||||
import com.ruoyi.common.core.constant.*;
|
import com.ruoyi.common.core.constant.*;
|
||||||
import com.ruoyi.common.core.core.domain.dto.RoleDTO;
|
import com.ruoyi.common.core.core.domain.dto.RoleDTO;
|
||||||
import com.ruoyi.common.core.enums.LoginType;
|
import com.ruoyi.common.core.enums.LoginType;
|
||||||
@ -21,21 +20,15 @@ import com.ruoyi.common.tenant.helper.TenantHelper;
|
|||||||
import com.ruoyi.system.domain.SysUser;
|
import com.ruoyi.system.domain.SysUser;
|
||||||
import com.ruoyi.system.domain.bo.SysSocialBo;
|
import com.ruoyi.system.domain.bo.SysSocialBo;
|
||||||
import com.ruoyi.system.domain.bo.SysUserBo;
|
import com.ruoyi.system.domain.bo.SysUserBo;
|
||||||
import com.ruoyi.system.domain.vo.SysSocialVo;
|
import com.ruoyi.system.domain.vo.*;
|
||||||
import com.ruoyi.system.domain.vo.SysTenantVo;
|
import com.ruoyi.system.service.*;
|
||||||
import com.ruoyi.system.domain.vo.SysUserVo;
|
|
||||||
import com.ruoyi.system.service.ISysPermissionService;
|
|
||||||
import com.ruoyi.system.service.ISysSocialService;
|
|
||||||
import com.ruoyi.system.service.ISysTenantService;
|
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import me.zhyd.oauth.model.AuthUser;
|
import me.zhyd.oauth.model.AuthUser;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import com.ruoyi.common.core.core.domain.model.LoginUser;
|
import com.ruoyi.common.core.core.domain.model.LoginUser;
|
||||||
import com.ruoyi.common.core.utils.DateUtils;
|
import com.ruoyi.common.core.utils.DateUtils;
|
||||||
import com.ruoyi.common.core.utils.MessageUtils;
|
import com.ruoyi.common.core.utils.MessageUtils;
|
||||||
import com.ruoyi.system.service.ISysUserService;
|
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
|
||||||
@ -70,6 +63,12 @@ public class SysLoginService {
|
|||||||
@Resource
|
@Resource
|
||||||
private ISysUserService userService;
|
private ISysUserService userService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ISysDeptService deptService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ISysRoleService roleService;
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private ISysTenantService tenantService;
|
private ISysTenantService tenantService;
|
||||||
|
|
||||||
@ -244,11 +243,17 @@ public class SysLoginService {
|
|||||||
* @param userId 用户ID
|
* @param userId 用户ID
|
||||||
*/
|
*/
|
||||||
public void recordLoginInfo(Long userId, String ip) {
|
public void recordLoginInfo(Long userId, String ip) {
|
||||||
|
SysUserVo sysUserVo = userService.selectUserById(userId);
|
||||||
|
if (ObjectUtil.isNull(sysUserVo)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
SysUser sysUser = new SysUser();
|
SysUser sysUser = new SysUser();
|
||||||
sysUser.setUserId(userId);
|
sysUser.setUserId(userId);
|
||||||
sysUser.setLoginIp(ip);
|
sysUser.setLoginIp(ip);
|
||||||
sysUser.setLoginDate(DateUtils.getNowDate());
|
sysUser.setLoginDate(DateUtils.getNowDate());
|
||||||
sysUser.setUpdateBy(userId);
|
sysUser.setUpdateBy(userId);
|
||||||
|
sysUser.setVersion(sysUserVo.getVersion());
|
||||||
userService.updateById(sysUser);
|
userService.updateById(sysUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,7 +5,6 @@ import cn.dev33.satoken.stp.StpUtil;
|
|||||||
import cn.hutool.core.util.ObjectUtil;
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
import com.ruoyi.common.core.core.domain.model.EmailLoginBody;
|
import com.ruoyi.common.core.core.domain.model.EmailLoginBody;
|
||||||
import com.ruoyi.common.json.utils.JsonUtils;
|
import com.ruoyi.common.json.utils.JsonUtils;
|
||||||
import com.ruoyi.common.tenant.helper.TenantHelper;
|
|
||||||
import com.ruoyi.system.domain.vo.SysClientVo;
|
import com.ruoyi.system.domain.vo.SysClientVo;
|
||||||
import com.ruoyi.system.domain.vo.SysUserVo;
|
import com.ruoyi.system.domain.vo.SysUserVo;
|
||||||
import com.ruoyi.system.service.ISysUserService;
|
import com.ruoyi.system.service.ISysUserService;
|
||||||
@ -70,9 +69,6 @@ public class EmailAuthStrategy implements IAuthStrategy {
|
|||||||
// 生成token
|
// 生成token
|
||||||
LoginHelper.login(loginUser, model);
|
LoginHelper.login(loginUser, model);
|
||||||
|
|
||||||
// loginService.recordLogininfor(loginUser.getTenantId(), user.getUserName(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
|
|
||||||
// loginService.recordLoginInfo(user.getUserId());
|
|
||||||
|
|
||||||
LoginVo loginVo = new LoginVo();
|
LoginVo loginVo = new LoginVo();
|
||||||
loginVo.setAccessToken(StpUtil.getTokenValue());
|
loginVo.setAccessToken(StpUtil.getTokenValue());
|
||||||
loginVo.setExpireIn(StpUtil.getTokenTimeout());
|
loginVo.setExpireIn(StpUtil.getTokenTimeout());
|
||||||
|
@ -6,7 +6,6 @@ import cn.dev33.satoken.stp.StpUtil;
|
|||||||
import cn.hutool.core.util.ObjectUtil;
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
import com.ruoyi.common.core.core.domain.model.PasswordLoginBody;
|
import com.ruoyi.common.core.core.domain.model.PasswordLoginBody;
|
||||||
import com.ruoyi.common.json.utils.JsonUtils;
|
import com.ruoyi.common.json.utils.JsonUtils;
|
||||||
import com.ruoyi.common.tenant.helper.TenantHelper;
|
|
||||||
import com.ruoyi.system.domain.vo.SysClientVo;
|
import com.ruoyi.system.domain.vo.SysClientVo;
|
||||||
import com.ruoyi.system.service.ISysUserService;
|
import com.ruoyi.system.service.ISysUserService;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
@ -26,7 +25,6 @@ import com.ruoyi.common.core.utils.ValidatorUtils;
|
|||||||
import com.ruoyi.common.redis.utils.RedisUtils;
|
import com.ruoyi.common.redis.utils.RedisUtils;
|
||||||
import com.ruoyi.common.security.utils.LoginHelper;
|
import com.ruoyi.common.security.utils.LoginHelper;
|
||||||
import com.ruoyi.common.web.config.properties.CaptchaProperties;
|
import com.ruoyi.common.web.config.properties.CaptchaProperties;
|
||||||
import com.ruoyi.system.domain.SysClient;
|
|
||||||
import com.ruoyi.system.domain.vo.SysUserVo;
|
import com.ruoyi.system.domain.vo.SysUserVo;
|
||||||
import com.ruoyi.web.domain.vo.LoginVo;
|
import com.ruoyi.web.domain.vo.LoginVo;
|
||||||
import com.ruoyi.web.service.IAuthStrategy;
|
import com.ruoyi.web.service.IAuthStrategy;
|
||||||
@ -81,9 +79,6 @@ public class PasswordAuthStrategy implements IAuthStrategy {
|
|||||||
// 生成token
|
// 生成token
|
||||||
LoginHelper.login(loginUser, model);
|
LoginHelper.login(loginUser, model);
|
||||||
|
|
||||||
// loginService.recordLogininfor(loginUser.getTenantId(), username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
|
|
||||||
// loginService.recordLoginInfo(user.getUserId(),user.getVersion());
|
|
||||||
|
|
||||||
LoginVo loginVo = new LoginVo();
|
LoginVo loginVo = new LoginVo();
|
||||||
loginVo.setAccessToken(StpUtil.getTokenValue());
|
loginVo.setAccessToken(StpUtil.getTokenValue());
|
||||||
loginVo.setExpireIn(StpUtil.getTokenTimeout());
|
loginVo.setExpireIn(StpUtil.getTokenTimeout());
|
||||||
|
@ -3,10 +3,7 @@ package com.ruoyi.web.service.impl;
|
|||||||
import cn.dev33.satoken.stp.SaLoginModel;
|
import cn.dev33.satoken.stp.SaLoginModel;
|
||||||
import cn.dev33.satoken.stp.StpUtil;
|
import cn.dev33.satoken.stp.StpUtil;
|
||||||
import cn.hutool.core.collection.CollUtil;
|
import cn.hutool.core.collection.CollUtil;
|
||||||
import cn.hutool.core.map.MapUtil;
|
|
||||||
import cn.hutool.core.util.ObjectUtil;
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
import cn.hutool.http.HttpUtil;
|
|
||||||
import cn.hutool.http.Method;
|
|
||||||
import com.ruoyi.system.service.ISysUserService;
|
import com.ruoyi.system.service.ISysUserService;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
@ -23,11 +20,9 @@ import com.ruoyi.common.json.utils.JsonUtils;
|
|||||||
import com.ruoyi.common.security.utils.LoginHelper;
|
import com.ruoyi.common.security.utils.LoginHelper;
|
||||||
import com.ruoyi.common.social.config.properties.SocialProperties;
|
import com.ruoyi.common.social.config.properties.SocialProperties;
|
||||||
import com.ruoyi.common.social.utils.SocialUtils;
|
import com.ruoyi.common.social.utils.SocialUtils;
|
||||||
import com.ruoyi.common.tenant.helper.TenantHelper;
|
|
||||||
import com.ruoyi.system.domain.vo.SysClientVo;
|
import com.ruoyi.system.domain.vo.SysClientVo;
|
||||||
import com.ruoyi.system.domain.vo.SysSocialVo;
|
import com.ruoyi.system.domain.vo.SysSocialVo;
|
||||||
import com.ruoyi.system.domain.vo.SysUserVo;
|
import com.ruoyi.system.domain.vo.SysUserVo;
|
||||||
import com.ruoyi.system.mapper.SysUserMapper;
|
|
||||||
import com.ruoyi.system.service.ISysSocialService;
|
import com.ruoyi.system.service.ISysSocialService;
|
||||||
import com.ruoyi.web.domain.vo.LoginVo;
|
import com.ruoyi.web.domain.vo.LoginVo;
|
||||||
import com.ruoyi.web.service.IAuthStrategy;
|
import com.ruoyi.web.service.IAuthStrategy;
|
||||||
|
@ -12,7 +12,6 @@ import com.ruoyi.common.core.enums.UserStatus;
|
|||||||
import com.ruoyi.common.core.utils.ValidatorUtils;
|
import com.ruoyi.common.core.utils.ValidatorUtils;
|
||||||
import com.ruoyi.common.json.utils.JsonUtils;
|
import com.ruoyi.common.json.utils.JsonUtils;
|
||||||
import com.ruoyi.common.security.utils.LoginHelper;
|
import com.ruoyi.common.security.utils.LoginHelper;
|
||||||
import com.ruoyi.system.domain.SysClient;
|
|
||||||
import com.ruoyi.system.domain.vo.SysClientVo;
|
import com.ruoyi.system.domain.vo.SysClientVo;
|
||||||
import com.ruoyi.system.domain.vo.SysUserVo;
|
import com.ruoyi.system.domain.vo.SysUserVo;
|
||||||
import com.ruoyi.web.domain.vo.LoginVo;
|
import com.ruoyi.web.domain.vo.LoginVo;
|
||||||
|
@ -29,15 +29,15 @@ mybatis-flex:
|
|||||||
# rewriteBatchedStatements=true 批处理优化 大幅提升批量插入更新删除性能(对数据库有性能损耗 使用批量操作应考虑性能问题)
|
# rewriteBatchedStatements=true 批处理优化 大幅提升批量插入更新删除性能(对数据库有性能损耗 使用批量操作应考虑性能问题)
|
||||||
type: ${spring.datasource.type}
|
type: ${spring.datasource.type}
|
||||||
# mysql数据库
|
# mysql数据库
|
||||||
# driver-class-name: com.mysql.cj.jdbc.Driver
|
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||||
# url: jdbc:mysql://localhost:3306/ruoyi-flex?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true
|
url: jdbc:mysql://localhost:3306/ruoyi-flex?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true
|
||||||
# username: root
|
username: root
|
||||||
# password: Root@369
|
password: Root@369
|
||||||
#postgresql数据库
|
#postgresql数据库
|
||||||
driver-class-name: org.postgresql.Driver
|
# driver-class-name: org.postgresql.Driver
|
||||||
url: jdbc:postgresql://localhost:5432/ruoyi-flex?useUnicode=true&characterEncoding=utf8&useSSL=true&autoReconnect=true&reWriteBatchedInserts=true
|
# url: jdbc:postgresql://localhost:5432/ruoyi-flex?useUnicode=true&characterEncoding=utf8&useSSL=true&autoReconnect=true&reWriteBatchedInserts=true
|
||||||
username: postgres
|
# username: postgres
|
||||||
password: postgres@369
|
# password: postgres@369
|
||||||
|
|
||||||
# redis 单机配置(单机与集群只能开启一个另一个需要注释掉)
|
# redis 单机配置(单机与集群只能开启一个另一个需要注释掉)
|
||||||
spring.data:
|
spring.data:
|
||||||
@ -80,7 +80,7 @@ redisson:
|
|||||||
--- # 监控中心客户端配置
|
--- # 监控中心客户端配置
|
||||||
spring.boot.admin.client:
|
spring.boot.admin.client:
|
||||||
# 增加客户端开关
|
# 增加客户端开关
|
||||||
enabled: true
|
enabled: false
|
||||||
url: http://localhost:9090/admin
|
url: http://localhost:9090/admin
|
||||||
instance:
|
instance:
|
||||||
service-host-type: IP
|
service-host-type: IP
|
||||||
@ -91,7 +91,7 @@ spring.boot.admin.client:
|
|||||||
powerjob:
|
powerjob:
|
||||||
worker:
|
worker:
|
||||||
# 如何开启调度中心请查看文档教程
|
# 如何开启调度中心请查看文档教程
|
||||||
enabled: true
|
enabled: false
|
||||||
# 需要先在 powerjob 登录页执行应用注册后才能使用
|
# 需要先在 powerjob 登录页执行应用注册后才能使用
|
||||||
app-name: ruoyi-worker
|
app-name: ruoyi-worker
|
||||||
# 28080 端口 随着主应用端口飘逸 避免集群冲突
|
# 28080 端口 随着主应用端口飘逸 避免集群冲突
|
||||||
@ -103,6 +103,18 @@ powerjob:
|
|||||||
max-appended-wf-context-length: 4096
|
max-appended-wf-context-length: 4096
|
||||||
max-result-length: 4096
|
max-result-length: 4096
|
||||||
|
|
||||||
|
--- # easy-retry 配置
|
||||||
|
easy-retry:
|
||||||
|
enabled: false
|
||||||
|
# 需要在EasyRetry后台组管理创建对应名称的组,然后创建任务的时候选择对应的组,才能正确分派任务
|
||||||
|
group-name: "ruoyi_group"
|
||||||
|
# EasyRetry接入验证令牌 详见 script/sql/easy_retry.sql `er_group_config` 表
|
||||||
|
token: "ER_cKqBTPzCsWA3VyuCfFoccmuIEGXjr5KT"
|
||||||
|
server:
|
||||||
|
host: 127.0.0.1
|
||||||
|
port: 1788
|
||||||
|
# 详见 script/sql/easy_retry.sql `er_namespace` 表
|
||||||
|
namespace: ${spring.profiles.active}
|
||||||
|
|
||||||
--- # mail 邮件发送
|
--- # mail 邮件发送
|
||||||
mail:
|
mail:
|
||||||
|
@ -29,30 +29,15 @@ mybatis-flex:
|
|||||||
# rewriteBatchedStatements=true 批处理优化 大幅提升批量插入更新删除性能(对数据库有性能损耗 使用批量操作应考虑性能问题)
|
# rewriteBatchedStatements=true 批处理优化 大幅提升批量插入更新删除性能(对数据库有性能损耗 使用批量操作应考虑性能问题)
|
||||||
type: ${spring.datasource.type}
|
type: ${spring.datasource.type}
|
||||||
# mysql数据库
|
# mysql数据库
|
||||||
# driver-class-name: com.mysql.cj.jdbc.Driver
|
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||||
# url: jdbc:mysql://localhost:3306/ruoyi-flex?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true
|
url: jdbc:mysql://localhost:3306/ruoyi-flex?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true
|
||||||
# username: root
|
username: root
|
||||||
# password: Root@369
|
password: Root@369
|
||||||
#postgresql数据库
|
#postgresql数据库
|
||||||
driver-class-name: org.postgresql.Driver
|
# driver-class-name: org.postgresql.Driver
|
||||||
url: jdbc:postgresql://localhost:5432/ruoyi-flex?useUnicode=true&characterEncoding=utf8&useSSL=true&autoReconnect=true&reWriteBatchedInserts=true
|
# url: jdbc:postgresql://localhost:5432/ruoyi-flex?useUnicode=true&characterEncoding=utf8&useSSL=true&autoReconnect=true&reWriteBatchedInserts=true
|
||||||
username: postgres
|
# username: postgres
|
||||||
password: postgres@369
|
# password: postgres@369
|
||||||
|
|
||||||
# # 数据源-2
|
|
||||||
# ds2:
|
|
||||||
# # 指定为HikariDataSource
|
|
||||||
# type: ${spring.datasource.type}
|
|
||||||
# # mysql数据库
|
|
||||||
# driver-class-name: com.mysql.cj.jdbc.Driver
|
|
||||||
# url: jdbc:mysql://localhost:3306/ruoyi-flex?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true
|
|
||||||
# username: root
|
|
||||||
# password: Root@369
|
|
||||||
# #postgresql数据库
|
|
||||||
## driver-class-name: org.postgresql.Driver
|
|
||||||
## url: jdbc:postgresql://localhost:5432/ruoyi-flex?useUnicode=true&characterEncoding=utf8&useSSL=true&autoReconnect=true&reWriteBatchedInserts=true
|
|
||||||
## username: postgres
|
|
||||||
## password: postgres@369
|
|
||||||
|
|
||||||
# redis 单机配置(单机与集群只能开启一个另一个需要注释掉)
|
# redis 单机配置(单机与集群只能开启一个另一个需要注释掉)
|
||||||
spring.data:
|
spring.data:
|
||||||
@ -118,6 +103,19 @@ powerjob:
|
|||||||
max-appended-wf-context-length: 4096
|
max-appended-wf-context-length: 4096
|
||||||
max-result-length: 4096
|
max-result-length: 4096
|
||||||
|
|
||||||
|
--- # easy-retry 配置
|
||||||
|
easy-retry:
|
||||||
|
enabled: false
|
||||||
|
# 需要在EasyRetry后台组管理创建对应名称的组,然后创建任务的时候选择对应的组,才能正确分派任务
|
||||||
|
group-name: "ruoyi_group"
|
||||||
|
# EasyRetry接入验证令牌 详见 script/sql/easy_retry.sql `er_group_config` 表
|
||||||
|
token: "ER_cKqBTPzCsWA3VyuCfFoccmuIEGXjr5KT"
|
||||||
|
server:
|
||||||
|
host: 127.0.0.1
|
||||||
|
port: 1788
|
||||||
|
# 详见 script/sql/easy_retry.sql `er_namespace` 表
|
||||||
|
namespace: ${spring.profiles.active}
|
||||||
|
|
||||||
--- # mail 邮件发送
|
--- # mail 邮件发送
|
||||||
mail:
|
mail:
|
||||||
enabled: false
|
enabled: false
|
||||||
|
@ -9,7 +9,7 @@ ruoyi:
|
|||||||
# 实例演示开关
|
# 实例演示开关
|
||||||
demoEnabled: true
|
demoEnabled: true
|
||||||
# 文件路径 示例( Windows配置D:/ruoyi/uploadPath,Linux配置 /home/ruoyi/uploadPath)
|
# 文件路径 示例( Windows配置D:/ruoyi/uploadPath,Linux配置 /home/ruoyi/uploadPath)
|
||||||
profile: D:/ruoyi/uploadPath
|
profile: /home/ruoyi/uploadPath
|
||||||
# 获取ip地址开关
|
# 获取ip地址开关
|
||||||
addressEnabled: false
|
addressEnabled: false
|
||||||
|
|
||||||
@ -124,7 +124,7 @@ pagehelper:
|
|||||||
mybatis-flex:
|
mybatis-flex:
|
||||||
# 搜索指定包别名
|
# 搜索指定包别名
|
||||||
type-aliases-package: com.ruoyi.**.domain
|
type-aliases-package: com.ruoyi.**.domain
|
||||||
# 不支持多包, 如有需要可在注解配置 或 提升扫包等级:com.**.**.mapper
|
# 多包名使用 例如 org.dromara.**.mapper,org.xxx.**.mapper
|
||||||
mapper-package: com.ruoyi.**.mapper
|
mapper-package: com.ruoyi.**.mapper
|
||||||
# 配置mapper的扫描,找到所有的mapper.xml映射文件
|
# 配置mapper的扫描,找到所有的mapper.xml映射文件
|
||||||
mapper-locations: classpath*:mapper/**/*Mapper.xml
|
mapper-locations: classpath*:mapper/**/*Mapper.xml
|
||||||
@ -191,14 +191,12 @@ api-decrypt:
|
|||||||
|
|
||||||
# SpringDoc配置
|
# SpringDoc配置
|
||||||
springdoc:
|
springdoc:
|
||||||
#需要扫描的包,可以配置多个,使用逗号分割
|
|
||||||
packages-to-scan: com.ruoyi
|
|
||||||
paths-to-exclude: #配置不包含在swagger文档中的api
|
paths-to-exclude: #配置不包含在swagger文档中的api
|
||||||
- /api/test/**
|
- /api/test/**
|
||||||
- /api/mockito/data
|
- /api/mockito/data
|
||||||
swagger-ui:
|
swagger-ui:
|
||||||
enabled: true #开启/禁止swagger,prod可以设置为false
|
enabled: true #开启/禁止swagger,prod可以设置为false
|
||||||
version: 5.10.3 #指定swagger-ui的版本号
|
version: 5.11.8 #指定swagger-ui的版本号
|
||||||
disable-swagger-default-url: true #禁用default petstore url
|
disable-swagger-default-url: true #禁用default petstore url
|
||||||
path: /swagger-ui.html #swagger页面
|
path: /swagger-ui.html #swagger页面
|
||||||
persistAuthorization: true # 持久化认证数据,如果设置为 true,它会保留授权数据并且不会在浏览器关闭/刷新时丢失
|
persistAuthorization: true # 持久化认证数据,如果设置为 true,它会保留授权数据并且不会在浏览器关闭/刷新时丢失
|
||||||
@ -221,20 +219,24 @@ springdoc:
|
|||||||
email: 738981257@qq.com
|
email: 738981257@qq.com
|
||||||
url: https://gitee.com/dataprince/ruoyi-flex
|
url: https://gitee.com/dataprince/ruoyi-flex
|
||||||
components:
|
components:
|
||||||
# 鉴权方式配置
|
# 鉴权方式配置
|
||||||
security-schemes:
|
security-schemes:
|
||||||
apiKey:
|
apiKey:
|
||||||
type: APIKEY
|
type: APIKEY
|
||||||
in: HEADER
|
in: HEADER
|
||||||
name: ${sa-token.token-name}
|
name: ${sa-token.token-name}
|
||||||
group-configs:
|
group-configs:
|
||||||
- group: 1.演示模块
|
- group: 1.web模块
|
||||||
packages-to-scan: com.ruoyi.demo
|
packages-to-scan: com.ruoyi.web
|
||||||
- group: 2.通用模块
|
- group: 2.演示模块
|
||||||
|
packages-to-scan:
|
||||||
|
- com.ruoyi.demo
|
||||||
|
- com.ruoyi.mf
|
||||||
|
- group: 3.通用模块
|
||||||
packages-to-scan: com.ruoyi.common
|
packages-to-scan: com.ruoyi.common
|
||||||
- group: 3.系统模块
|
- group: 4.系统模块
|
||||||
packages-to-scan: com.ruoyi.system
|
packages-to-scan: com.ruoyi.system
|
||||||
- group: 4.代码生成模块
|
- group: 5.代码生成模块
|
||||||
packages-to-scan: com.ruoyi.generator
|
packages-to-scan: com.ruoyi.generator
|
||||||
|
|
||||||
# 防止XSS攻击
|
# 防止XSS攻击
|
||||||
@ -298,6 +300,8 @@ security:
|
|||||||
- /captchaImage
|
- /captchaImage
|
||||||
- /captcha/get
|
- /captcha/get
|
||||||
- /captcha/check
|
- /captcha/check
|
||||||
|
- /genKeyPair
|
||||||
|
- /job/**
|
||||||
|
|
||||||
--- # Actuator 监控端点的配置项
|
--- # Actuator 监控端点的配置项
|
||||||
management:
|
management:
|
||||||
|
Binary file not shown.
@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<configuration>
|
<configuration>
|
||||||
<!-- 日志存放路径 -->
|
<!-- 日志存放路径 -->
|
||||||
<property name="log.path" value="/home/ruoyi/logs" />
|
<property name="log.path" value="./home/ruoyi/logs" />
|
||||||
<!-- 日志输出格式 -->
|
<!-- 日志输出格式 -->
|
||||||
<property name="log.pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] - %msg%n" />
|
<property name="log.pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] - %msg%n" />
|
||||||
|
|
||||||
@ -11,7 +11,7 @@
|
|||||||
<pattern>${log.pattern}</pattern>
|
<pattern>${log.pattern}</pattern>
|
||||||
</encoder>
|
</encoder>
|
||||||
</appender>
|
</appender>
|
||||||
|
|
||||||
<!-- 系统日志输出 -->
|
<!-- 系统日志输出 -->
|
||||||
<appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
<appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||||
<file>${log.path}/sys-info.log</file>
|
<file>${log.path}/sys-info.log</file>
|
||||||
@ -34,7 +34,7 @@
|
|||||||
<onMismatch>DENY</onMismatch>
|
<onMismatch>DENY</onMismatch>
|
||||||
</filter>
|
</filter>
|
||||||
</appender>
|
</appender>
|
||||||
|
|
||||||
<appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
<appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||||
<file>${log.path}/sys-error.log</file>
|
<file>${log.path}/sys-error.log</file>
|
||||||
<!-- 循环政策:基于时间创建日志文件 -->
|
<!-- 循环政策:基于时间创建日志文件 -->
|
||||||
@ -56,7 +56,7 @@
|
|||||||
<onMismatch>DENY</onMismatch>
|
<onMismatch>DENY</onMismatch>
|
||||||
</filter>
|
</filter>
|
||||||
</appender>
|
</appender>
|
||||||
|
|
||||||
<!-- 用户访问日志输出 -->
|
<!-- 用户访问日志输出 -->
|
||||||
<appender name="sys-user" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
<appender name="sys-user" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||||
<file>${log.path}/sys-user.log</file>
|
<file>${log.path}/sys-user.log</file>
|
||||||
@ -70,7 +70,7 @@
|
|||||||
<pattern>${log.pattern}</pattern>
|
<pattern>${log.pattern}</pattern>
|
||||||
</encoder>
|
</encoder>
|
||||||
</appender>
|
</appender>
|
||||||
|
|
||||||
<!-- 系统模块日志级别控制 -->
|
<!-- 系统模块日志级别控制 -->
|
||||||
<logger name="com.ruoyi" level="info" />
|
<logger name="com.ruoyi" level="info" />
|
||||||
<!-- Spring日志级别控制 -->
|
<!-- Spring日志级别控制 -->
|
||||||
@ -79,15 +79,15 @@
|
|||||||
<root level="info">
|
<root level="info">
|
||||||
<appender-ref ref="console" />
|
<appender-ref ref="console" />
|
||||||
</root>
|
</root>
|
||||||
|
|
||||||
<!--系统操作日志-->
|
<!--系统操作日志-->
|
||||||
<root level="info">
|
<root level="info">
|
||||||
<appender-ref ref="file_info" />
|
<appender-ref ref="file_info" />
|
||||||
<appender-ref ref="file_error" />
|
<appender-ref ref="file_error" />
|
||||||
</root>
|
</root>
|
||||||
|
|
||||||
<!--系统用户操作日志-->
|
<!--系统用户操作日志-->
|
||||||
<logger name="sys-user" level="info">
|
<logger name="sys-user" level="info">
|
||||||
<appender-ref ref="sys-user"/>
|
<appender-ref ref="sys-user"/>
|
||||||
</logger>
|
</logger>
|
||||||
</configuration>
|
</configuration>
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
</description>
|
</description>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<revision>5.1.0</revision>
|
<revision>5.2.0-SNAPSHOT</revision>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencyManagement>
|
<dependencyManagement>
|
||||||
|
@ -149,6 +149,11 @@
|
|||||||
<artifactId>jsr305</artifactId>
|
<artifactId>jsr305</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba</groupId>
|
||||||
|
<artifactId>transmittable-thread-local</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
|
@ -6,6 +6,7 @@ import com.ruoyi.common.core.utils.SpringUtils;
|
|||||||
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
|
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
|
||||||
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.core.task.VirtualThreadTaskExecutor;
|
||||||
import org.springframework.scheduling.annotation.AsyncConfigurer;
|
import org.springframework.scheduling.annotation.AsyncConfigurer;
|
||||||
import org.springframework.scheduling.annotation.EnableAsync;
|
import org.springframework.scheduling.annotation.EnableAsync;
|
||||||
|
|
||||||
@ -14,10 +15,10 @@ import java.util.concurrent.Executor;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 异步配置
|
* 异步配置
|
||||||
*
|
* <p>
|
||||||
|
* 如果未使用虚拟线程则生效
|
||||||
* @author Lion Li
|
* @author Lion Li
|
||||||
*/
|
*/
|
||||||
@ConditionalOnProperty(prefix = "spring.threads.virtual", name = "enabled", havingValue = "false")
|
|
||||||
@AutoConfiguration
|
@AutoConfiguration
|
||||||
public class AsyncConfig implements AsyncConfigurer {
|
public class AsyncConfig implements AsyncConfigurer {
|
||||||
|
|
||||||
@ -26,6 +27,9 @@ public class AsyncConfig implements AsyncConfigurer {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Executor getAsyncExecutor() {
|
public Executor getAsyncExecutor() {
|
||||||
|
if(SpringUtils.isVirtual()) {
|
||||||
|
return new VirtualThreadTaskExecutor("async-");
|
||||||
|
}
|
||||||
return SpringUtils.getBean("scheduledExecutorService");
|
return SpringUtils.getBean("scheduledExecutorService");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,6 +35,11 @@ public interface CacheNames {
|
|||||||
*/
|
*/
|
||||||
String SYS_TENANT = GlobalConstants.GLOBAL_REDIS_KEY + "sys_tenant#30d";
|
String SYS_TENANT = GlobalConstants.GLOBAL_REDIS_KEY + "sys_tenant#30d";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 客户端
|
||||||
|
*/
|
||||||
|
String SYS_CLIENT = GlobalConstants.GLOBAL_REDIS_KEY + "sys_client#30d";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户账户
|
* 用户账户
|
||||||
*/
|
*/
|
||||||
|
@ -0,0 +1,49 @@
|
|||||||
|
package com.ruoyi.common.core.constant;
|
||||||
|
|
||||||
|
import cn.hutool.core.lang.RegexPool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 常用正则表达式字符串
|
||||||
|
* <p>
|
||||||
|
* 常用正则表达式集合,更多正则见: https://any86.github.io/any-rule/
|
||||||
|
*
|
||||||
|
* @author Feng
|
||||||
|
*/
|
||||||
|
public interface RegexConstants extends RegexPool {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字典类型必须以字母开头,且只能为(小写字母,数字,下滑线)
|
||||||
|
*/
|
||||||
|
public static final String DICTIONARY_TYPE = "^[a-z][a-z0-9_]*$";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 身份证号码(后6位)
|
||||||
|
*/
|
||||||
|
public static final String ID_CARD_LAST_6 = "^(([0-2][1-9])|10|20|30|31)\\d{3}[0-9Xx]$";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* QQ号码
|
||||||
|
*/
|
||||||
|
public static final String QQ_NUMBER = "^[1-9][0-9]\\d{4,9}$";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 邮政编码
|
||||||
|
*/
|
||||||
|
public static final String POSTAL_CODE = "^[1-9]\\d{5}$";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注册账号
|
||||||
|
*/
|
||||||
|
public static final String ACCOUNT = "^[a-zA-Z][a-zA-Z0-9_]{4,15}$";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 密码:包含至少8个字符,包括大写字母、小写字母、数字和特殊字符
|
||||||
|
*/
|
||||||
|
public static final String PASSWORD = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@$!%*?&])[A-Za-z\\d@$!%*?&]{8,}$";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通用状态(0表示正常,1表示停用)
|
||||||
|
*/
|
||||||
|
public static final String STATUS = "^[01]$";
|
||||||
|
|
||||||
|
}
|
@ -34,6 +34,16 @@ public class UserOnlineDTO implements Serializable {
|
|||||||
*/
|
*/
|
||||||
private String userName;
|
private String userName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 客户端
|
||||||
|
*/
|
||||||
|
private String clientKey;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设备类型
|
||||||
|
*/
|
||||||
|
private String deviceType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 登录IP地址
|
* 登录IP地址
|
||||||
*/
|
*/
|
||||||
|
@ -0,0 +1,52 @@
|
|||||||
|
package com.ruoyi.common.core.factory;
|
||||||
|
|
||||||
|
import cn.hutool.core.lang.PatternPool;
|
||||||
|
import com.ruoyi.common.core.constant.RegexConstants;
|
||||||
|
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 正则表达式模式池工厂
|
||||||
|
* <p>初始化的时候将正则表达式加入缓存池当中</p>
|
||||||
|
* <p>提高正则表达式的性能,避免重复编译相同的正则表达式</p>
|
||||||
|
*
|
||||||
|
* @author 21001
|
||||||
|
*/
|
||||||
|
public class RegexPatternPoolFactory extends PatternPool {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字典类型必须以字母开头,且只能为(小写字母,数字,下滑线)
|
||||||
|
*/
|
||||||
|
public static final Pattern DICTIONARY_TYPE = get(RegexConstants.DICTIONARY_TYPE);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 身份证号码(后6位)
|
||||||
|
*/
|
||||||
|
public static final Pattern ID_CARD_LAST_6 = get(RegexConstants.ID_CARD_LAST_6);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* QQ号码
|
||||||
|
*/
|
||||||
|
public static final Pattern QQ_NUMBER = get(RegexConstants.QQ_NUMBER);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 邮政编码
|
||||||
|
*/
|
||||||
|
public static final Pattern POSTAL_CODE = get(RegexConstants.POSTAL_CODE);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注册账号
|
||||||
|
*/
|
||||||
|
public static final Pattern ACCOUNT = get(RegexConstants.ACCOUNT);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 密码:包含至少8个字符,包括大写字母、小写字母、数字和特殊字符
|
||||||
|
*/
|
||||||
|
public static final Pattern PASSWORD = get(RegexConstants.PASSWORD);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通用状态(0表示正常,1表示停用)
|
||||||
|
*/
|
||||||
|
public static final Pattern STATUS = get(RegexConstants.STATUS);
|
||||||
|
|
||||||
|
}
|
@ -3,7 +3,9 @@ package com.ruoyi.common.core.utils;
|
|||||||
import cn.hutool.extra.spring.SpringUtil;
|
import cn.hutool.extra.spring.SpringUtil;
|
||||||
import org.springframework.aop.framework.AopContext;
|
import org.springframework.aop.framework.AopContext;
|
||||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||||
|
import org.springframework.boot.autoconfigure.thread.Threading;
|
||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
|
import org.springframework.core.env.Environment;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -98,4 +100,8 @@ public final class SpringUtils extends SpringUtil
|
|||||||
return StringUtils.isNotEmpty(activeProfiles) ? activeProfiles[0] : null;
|
return StringUtils.isNotEmpty(activeProfiles) ? activeProfiles[0] : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean isVirtual() {
|
||||||
|
return Threading.VIRTUAL.isActive(getBean(Environment.class));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,8 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils
|
|||||||
{
|
{
|
||||||
public static final String SEPARATOR = ",";
|
public static final String SEPARATOR = ",";
|
||||||
|
|
||||||
|
public static final String SLASH = "/";
|
||||||
|
|
||||||
/** 空字符串 */
|
/** 空字符串 */
|
||||||
private static final String NULLSTR = "";
|
private static final String NULLSTR = "";
|
||||||
|
|
||||||
|
@ -0,0 +1,30 @@
|
|||||||
|
package com.ruoyi.common.core.utils.regex;
|
||||||
|
|
||||||
|
|
||||||
|
import cn.hutool.core.util.ReUtil;
|
||||||
|
import com.ruoyi.common.core.constant.RegexConstants;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 正则相关工具类
|
||||||
|
*
|
||||||
|
* @author Feng
|
||||||
|
*/
|
||||||
|
public final class RegexUtils extends ReUtil {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从输入字符串中提取匹配的部分,如果没有匹配则返回默认值
|
||||||
|
*
|
||||||
|
* @param input 要提取的输入字符串
|
||||||
|
* @param regex 用于匹配的正则表达式,可以使用 {@link RegexConstants} 中定义的常量
|
||||||
|
* @param defaultInput 如果没有匹配时返回的默认值
|
||||||
|
* @return 如果找到匹配的部分,则返回匹配的部分,否则返回默认值
|
||||||
|
*/
|
||||||
|
public static String extractFromString(String input, String regex, String defaultInput) {
|
||||||
|
try {
|
||||||
|
return ReUtil.get(regex, input, 1);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return defaultInput;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,105 @@
|
|||||||
|
package com.ruoyi.common.core.utils.regex;
|
||||||
|
|
||||||
|
import cn.hutool.core.exceptions.ValidateException;
|
||||||
|
import cn.hutool.core.lang.Validator;
|
||||||
|
import com.ruoyi.common.core.factory.RegexPatternPoolFactory;
|
||||||
|
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 正则字段校验器
|
||||||
|
* 主要验证字段非空、是否为满足指定格式等
|
||||||
|
*
|
||||||
|
* @author Feng
|
||||||
|
*/
|
||||||
|
public class RegexValidator extends Validator {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字典类型必须以字母开头,且只能为(小写字母,数字,下滑线)
|
||||||
|
*/
|
||||||
|
public static final Pattern DICTIONARY_TYPE = RegexPatternPoolFactory.DICTIONARY_TYPE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 身份证号码(后6位)
|
||||||
|
*/
|
||||||
|
public static final Pattern ID_CARD_LAST_6 = RegexPatternPoolFactory.ID_CARD_LAST_6;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* QQ号码
|
||||||
|
*/
|
||||||
|
public static final Pattern QQ_NUMBER = RegexPatternPoolFactory.QQ_NUMBER;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 邮政编码
|
||||||
|
*/
|
||||||
|
public static final Pattern POSTAL_CODE = RegexPatternPoolFactory.POSTAL_CODE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注册账号
|
||||||
|
*/
|
||||||
|
public static final Pattern ACCOUNT = RegexPatternPoolFactory.ACCOUNT;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 密码:包含至少8个字符,包括大写字母、小写字母、数字和特殊字符
|
||||||
|
*/
|
||||||
|
public static final Pattern PASSWORD = RegexPatternPoolFactory.PASSWORD;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通用状态(0表示正常,1表示停用)
|
||||||
|
*/
|
||||||
|
public static final Pattern STATUS = RegexPatternPoolFactory.STATUS;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查输入的账号是否匹配预定义的规则
|
||||||
|
*
|
||||||
|
* @param value 要验证的账号
|
||||||
|
* @return 如果账号符合规则,返回 true;否则,返回 false。
|
||||||
|
*/
|
||||||
|
public static boolean isAccount(CharSequence value) {
|
||||||
|
return isMatchRegex(ACCOUNT, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证输入的账号是否符合规则,如果不符合,则抛出 ValidateException 异常
|
||||||
|
*
|
||||||
|
* @param value 要验证的账号
|
||||||
|
* @param errorMsg 验证失败时抛出的异常消息
|
||||||
|
* @param <T> CharSequence 的子类型
|
||||||
|
* @return 如果验证通过,返回输入的账号
|
||||||
|
* @throws ValidateException 如果验证失败
|
||||||
|
*/
|
||||||
|
public static <T extends CharSequence> T validateAccount(T value, String errorMsg) throws ValidateException {
|
||||||
|
if (!isAccount(value)) {
|
||||||
|
throw new ValidateException(errorMsg);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查输入的状态是否匹配预定义的规则
|
||||||
|
*
|
||||||
|
* @param value 要验证的状态
|
||||||
|
* @return 如果状态符合规则,返回 true;否则,返回 false。
|
||||||
|
*/
|
||||||
|
public static boolean isStatus(CharSequence value) {
|
||||||
|
return isMatchRegex(STATUS, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证输入的状态是否符合规则,如果不符合,则抛出 ValidateException 异常
|
||||||
|
*
|
||||||
|
* @param value 要验证的状态
|
||||||
|
* @param errorMsg 验证失败时抛出的异常消息
|
||||||
|
* @param <T> CharSequence 的子类型
|
||||||
|
* @return 如果验证通过,返回输入的状态
|
||||||
|
* @throws ValidateException 如果验证失败
|
||||||
|
*/
|
||||||
|
public static <T extends CharSequence> T validateStatus(T value, String errorMsg) throws ValidateException {
|
||||||
|
if (!isStatus(value)) {
|
||||||
|
throw new ValidateException(errorMsg);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
package com.ruoyi.common.encrypt.core;
|
package com.ruoyi.common.encrypt.core;
|
||||||
|
|
||||||
import cn.hutool.core.util.ReflectUtil;
|
import cn.hutool.core.util.ReflectUtil;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import com.ruoyi.common.encrypt.annotation.EncryptField;
|
import com.ruoyi.common.encrypt.annotation.EncryptField;
|
||||||
|
|
||||||
@ -19,6 +20,7 @@ import java.util.stream.Collectors;
|
|||||||
* @version 4.6.0
|
* @version 4.6.0
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
|
@NoArgsConstructor
|
||||||
public class EncryptorManager {
|
public class EncryptorManager {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -36,8 +36,9 @@ public class CryptoFilter implements Filter {
|
|||||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
|
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
|
||||||
HttpServletRequest servletRequest = (HttpServletRequest) request;
|
HttpServletRequest servletRequest = (HttpServletRequest) request;
|
||||||
HttpServletResponse servletResponse = (HttpServletResponse) response;
|
HttpServletResponse servletResponse = (HttpServletResponse) response;
|
||||||
|
// 获取加密注解
|
||||||
boolean responseFlag = false;
|
ApiEncrypt apiEncrypt = this.getApiEncryptAnnotation(servletRequest);
|
||||||
|
boolean responseFlag = apiEncrypt != null && apiEncrypt.response();
|
||||||
ServletRequest requestWrapper = null;
|
ServletRequest requestWrapper = null;
|
||||||
ServletResponse responseWrapper = null;
|
ServletResponse responseWrapper = null;
|
||||||
EncryptResponseBodyWrapper responseBodyWrapper = null;
|
EncryptResponseBodyWrapper responseBodyWrapper = null;
|
||||||
@ -48,12 +49,9 @@ public class CryptoFilter implements Filter {
|
|||||||
if (HttpMethod.PUT.matches(servletRequest.getMethod()) || HttpMethod.POST.matches(servletRequest.getMethod())) {
|
if (HttpMethod.PUT.matches(servletRequest.getMethod()) || HttpMethod.POST.matches(servletRequest.getMethod())) {
|
||||||
// 是否存在加密标头
|
// 是否存在加密标头
|
||||||
String headerValue = servletRequest.getHeader(properties.getHeaderFlag());
|
String headerValue = servletRequest.getHeader(properties.getHeaderFlag());
|
||||||
// 获取加密注解
|
|
||||||
ApiEncrypt apiEncrypt = this.getApiEncryptAnnotation(servletRequest);
|
|
||||||
responseFlag = apiEncrypt != null && apiEncrypt.response();
|
|
||||||
if (StringUtils.isNotBlank(headerValue)) {
|
if (StringUtils.isNotBlank(headerValue)) {
|
||||||
// 请求解密
|
// 请求解密
|
||||||
requestWrapper = new DecryptRequestBodyWrapper(servletRequest, properties.getPrivateKey(), properties.getHeaderFlag());
|
requestWrapper = new DecryptRequestBodyWrapper(servletRequest, headerValue);
|
||||||
} else {
|
} else {
|
||||||
// 是否有注解,有就报错,没有放行
|
// 是否有注解,有就报错,没有放行
|
||||||
if (ObjectUtil.isNotNull(apiEncrypt)) {
|
if (ObjectUtil.isNotNull(apiEncrypt)) {
|
||||||
@ -64,13 +62,13 @@ public class CryptoFilter implements Filter {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 判断是否响应加密
|
|
||||||
if (responseFlag) {
|
|
||||||
responseBodyWrapper = new EncryptResponseBodyWrapper(servletResponse);
|
|
||||||
responseWrapper = responseBodyWrapper;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 判断是否响应加密
|
||||||
|
if (responseFlag) {
|
||||||
|
responseBodyWrapper = new EncryptResponseBodyWrapper(servletResponse);
|
||||||
|
responseWrapper = responseBodyWrapper;
|
||||||
|
}
|
||||||
|
|
||||||
chain.doFilter(
|
chain.doFilter(
|
||||||
ObjectUtil.defaultIfNull(requestWrapper, request),
|
ObjectUtil.defaultIfNull(requestWrapper, request),
|
||||||
|
@ -2,12 +2,14 @@ package com.ruoyi.common.encrypt.filter;
|
|||||||
|
|
||||||
import cn.hutool.core.io.IoUtil;
|
import cn.hutool.core.io.IoUtil;
|
||||||
import com.ruoyi.common.encrypt.utils.EncryptUtils;
|
import com.ruoyi.common.encrypt.utils.EncryptUtils;
|
||||||
|
import com.ruoyi.common.redis.utils.RedisUtils;
|
||||||
import jakarta.servlet.ReadListener;
|
import jakarta.servlet.ReadListener;
|
||||||
import jakarta.servlet.ServletInputStream;
|
import jakarta.servlet.ServletInputStream;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletRequestWrapper;
|
import jakarta.servlet.http.HttpServletRequestWrapper;
|
||||||
import com.ruoyi.common.core.constant.Constants;
|
import com.ruoyi.common.core.constant.Constants;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.web.util.UriUtils;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
@ -24,22 +26,20 @@ public class DecryptRequestBodyWrapper extends HttpServletRequestWrapper {
|
|||||||
|
|
||||||
private final byte[] body;
|
private final byte[] body;
|
||||||
|
|
||||||
public DecryptRequestBodyWrapper(HttpServletRequest request, String privateKey, String headerFlag) throws IOException {
|
public DecryptRequestBodyWrapper(HttpServletRequest request, String privateKey) throws IOException {
|
||||||
super(request);
|
super(request);
|
||||||
// 获取 AES 密码 采用 RSA 加密
|
// 获取 AES 密码 采用 RSA 加密
|
||||||
String headerRsa = request.getHeader(headerFlag);
|
String privateKeyValue = RedisUtils.getCacheMapValue("loginRsa", privateKey);
|
||||||
String decryptAes = EncryptUtils.decryptByRsa(headerRsa, privateKey);
|
|
||||||
// 解密 AES 密码
|
|
||||||
String aesPassword = EncryptUtils.decryptByBase64(decryptAes);
|
|
||||||
request.setCharacterEncoding(Constants.UTF8);
|
|
||||||
byte[] readBytes = IoUtil.readBytes(request.getInputStream(), false);
|
|
||||||
String requestBody = new String(readBytes, StandardCharsets.UTF_8);
|
|
||||||
// 解密 body 采用 AES 加密
|
|
||||||
String decryptBody = EncryptUtils.decryptByAes(requestBody, aesPassword);
|
|
||||||
body = decryptBody.getBytes(StandardCharsets.UTF_8);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
byte[] readBytes = IoUtil.readBytes(request.getInputStream(), false);
|
||||||
|
String requestBody = new String(readBytes, StandardCharsets.UTF_8);
|
||||||
|
// 解密 body 采用 AES 加密
|
||||||
|
String decryptBody = EncryptUtils.decryptByRsa(requestBody, privateKeyValue);
|
||||||
|
body = UriUtils.decode(decryptBody, StandardCharsets.UTF_8).getBytes(StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
public BufferedReader getReader() {
|
public BufferedReader getReader() {
|
||||||
return new BufferedReader(new InputStreamReader(getInputStream()));
|
return new BufferedReader(new InputStreamReader(getInputStream()));
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,363 @@
|
|||||||
|
package com.ruoyi.common.encrypt.utils;
|
||||||
|
|
||||||
|
import org.apache.commons.codec.binary.Base64;
|
||||||
|
|
||||||
|
import javax.crypto.Cipher;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.security.*;
|
||||||
|
import java.security.interfaces.RSAPrivateKey;
|
||||||
|
import java.security.interfaces.RSAPublicKey;
|
||||||
|
import java.security.spec.PKCS8EncodedKeySpec;
|
||||||
|
import java.security.spec.X509EncodedKeySpec;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @NAME: RSAEncryptUtils
|
||||||
|
* @AUTHOR: gaoly
|
||||||
|
* @DATE: 2021/1/26 15:15
|
||||||
|
* @DES:
|
||||||
|
**/
|
||||||
|
public class RSAUtils {
|
||||||
|
/** */
|
||||||
|
/**
|
||||||
|
* 加密算法RSA
|
||||||
|
*/
|
||||||
|
public static final String KEY_ALGORITHM = "RSA";
|
||||||
|
|
||||||
|
/** */
|
||||||
|
/**
|
||||||
|
* 签名算法
|
||||||
|
*/
|
||||||
|
public static final String SIGNATURE_ALGORITHM = "MD5withRSA";
|
||||||
|
|
||||||
|
/** */
|
||||||
|
/**
|
||||||
|
* 获取公钥的key
|
||||||
|
*/
|
||||||
|
private static final String PUBLIC_KEY = "RSAPublicKey";
|
||||||
|
|
||||||
|
/** */
|
||||||
|
/**
|
||||||
|
* 获取私钥的key
|
||||||
|
*/
|
||||||
|
private static final String PRIVATE_KEY = "RSAPrivateKey";
|
||||||
|
|
||||||
|
/** */
|
||||||
|
/**
|
||||||
|
* RSA最大加密明文大小
|
||||||
|
*/
|
||||||
|
private static final int MAX_ENCRYPT_BLOCK = 117;
|
||||||
|
|
||||||
|
/** */
|
||||||
|
/**
|
||||||
|
* RSA最大解密密文大小
|
||||||
|
*/
|
||||||
|
private static final int MAX_DECRYPT_BLOCK = 128;
|
||||||
|
|
||||||
|
/** */
|
||||||
|
/**
|
||||||
|
* RSA 位数 如果采用2048 上面最大加密和最大解密则须填写: 245 256
|
||||||
|
*/
|
||||||
|
private static final int INITIALIZE_LENGTH = 1024;
|
||||||
|
|
||||||
|
/** */
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* 生成密钥对(公钥和私钥)
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public static Map<String, Object> genKeyPair() throws Exception {
|
||||||
|
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM);
|
||||||
|
keyPairGen.initialize(INITIALIZE_LENGTH);
|
||||||
|
KeyPair keyPair = keyPairGen.generateKeyPair();
|
||||||
|
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
|
||||||
|
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
|
||||||
|
Map<String, Object> keyMap = new HashMap<String, Object>(2);
|
||||||
|
keyMap.put(PUBLIC_KEY, publicKey);
|
||||||
|
keyMap.put(PRIVATE_KEY, privateKey);
|
||||||
|
return keyMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** */
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* 用私钥对信息生成数字签名
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param data
|
||||||
|
* 已加密数据
|
||||||
|
* @param privateKey
|
||||||
|
* 私钥(BASE64编码)
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public static String sign(byte[] data, String privateKey) throws Exception {
|
||||||
|
byte[] keyBytes = Base64.decodeBase64(privateKey);
|
||||||
|
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
|
||||||
|
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
|
||||||
|
PrivateKey privateK = keyFactory.generatePrivate(pkcs8KeySpec);
|
||||||
|
Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
|
||||||
|
signature.initSign(privateK);
|
||||||
|
signature.update(data);
|
||||||
|
return Base64.encodeBase64String(signature.sign());
|
||||||
|
}
|
||||||
|
|
||||||
|
/** */
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* 校验数字签名
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param data
|
||||||
|
* 已加密数据
|
||||||
|
* @param publicKey
|
||||||
|
* 公钥(BASE64编码)
|
||||||
|
* @param sign
|
||||||
|
* 数字签名
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* @throws Exception
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public static boolean verify(byte[] data, String publicKey, String sign) throws Exception {
|
||||||
|
byte[] keyBytes = Base64.decodeBase64(publicKey);
|
||||||
|
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
|
||||||
|
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
|
||||||
|
PublicKey publicK = keyFactory.generatePublic(keySpec);
|
||||||
|
Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
|
||||||
|
signature.initVerify(publicK);
|
||||||
|
signature.update(data);
|
||||||
|
return signature.verify(Base64.decodeBase64(sign));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** */
|
||||||
|
/**
|
||||||
|
* <P>
|
||||||
|
* 私钥解密
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param encryptedData
|
||||||
|
* 已加密数据
|
||||||
|
* @param privateKey
|
||||||
|
* 私钥(BASE64编码)
|
||||||
|
* @return
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public static byte[] decryptByPrivateKey(byte[] encryptedData, String privateKey) throws Exception {
|
||||||
|
byte[] keyBytes = Base64.decodeBase64(privateKey);
|
||||||
|
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
|
||||||
|
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
|
||||||
|
Key privateK = keyFactory.generatePrivate(pkcs8KeySpec);
|
||||||
|
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
|
||||||
|
cipher.init(Cipher.DECRYPT_MODE, privateK);
|
||||||
|
int inputLen = encryptedData.length;
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
|
int offSet = 0;
|
||||||
|
byte[] cache;
|
||||||
|
int i = 0;
|
||||||
|
// 对数据分段解密
|
||||||
|
while (inputLen - offSet > 0) {
|
||||||
|
if (inputLen - offSet > MAX_DECRYPT_BLOCK) {
|
||||||
|
cache = cipher.doFinal(encryptedData, offSet, MAX_DECRYPT_BLOCK);
|
||||||
|
} else {
|
||||||
|
cache = cipher.doFinal(encryptedData, offSet, inputLen - offSet);
|
||||||
|
}
|
||||||
|
out.write(cache, 0, cache.length);
|
||||||
|
i++;
|
||||||
|
offSet = i * MAX_DECRYPT_BLOCK;
|
||||||
|
}
|
||||||
|
byte[] decryptedData = out.toByteArray();
|
||||||
|
out.close();
|
||||||
|
return decryptedData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** */
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* 公钥解密
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param encryptedData
|
||||||
|
* 已加密数据
|
||||||
|
* @param publicKey
|
||||||
|
* 公钥(BASE64编码)
|
||||||
|
* @return
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public static byte[] decryptByPublicKey(byte[] encryptedData, String publicKey) throws Exception {
|
||||||
|
byte[] keyBytes = Base64.decodeBase64(publicKey);
|
||||||
|
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
|
||||||
|
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
|
||||||
|
Key publicK = keyFactory.generatePublic(x509KeySpec);
|
||||||
|
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
|
||||||
|
cipher.init(Cipher.DECRYPT_MODE, publicK);
|
||||||
|
int inputLen = encryptedData.length;
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
|
int offSet = 0;
|
||||||
|
byte[] cache;
|
||||||
|
int i = 0;
|
||||||
|
// 对数据分段解密
|
||||||
|
while (inputLen - offSet > 0) {
|
||||||
|
if (inputLen - offSet > MAX_DECRYPT_BLOCK) {
|
||||||
|
cache = cipher.doFinal(encryptedData, offSet, MAX_DECRYPT_BLOCK);
|
||||||
|
} else {
|
||||||
|
cache = cipher.doFinal(encryptedData, offSet, inputLen - offSet);
|
||||||
|
}
|
||||||
|
out.write(cache, 0, cache.length);
|
||||||
|
i++;
|
||||||
|
offSet = i * MAX_DECRYPT_BLOCK;
|
||||||
|
}
|
||||||
|
byte[] decryptedData = out.toByteArray();
|
||||||
|
out.close();
|
||||||
|
return decryptedData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** */
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* 公钥加密
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param data
|
||||||
|
* 源数据
|
||||||
|
* @param publicKey
|
||||||
|
* 公钥(BASE64编码)
|
||||||
|
* @return
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public static byte[] encryptByPublicKey(byte[] data, String publicKey) throws Exception {
|
||||||
|
byte[] keyBytes = Base64.decodeBase64(publicKey);
|
||||||
|
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
|
||||||
|
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
|
||||||
|
Key publicK = keyFactory.generatePublic(x509KeySpec);
|
||||||
|
// 对数据加密
|
||||||
|
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
|
||||||
|
cipher.init(Cipher.ENCRYPT_MODE, publicK);
|
||||||
|
int inputLen = data.length;
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
|
int offSet = 0;
|
||||||
|
byte[] cache;
|
||||||
|
int i = 0;
|
||||||
|
// 对数据分段加密
|
||||||
|
while (inputLen - offSet > 0) {
|
||||||
|
if (inputLen - offSet > MAX_ENCRYPT_BLOCK) {
|
||||||
|
cache = cipher.doFinal(data, offSet, MAX_ENCRYPT_BLOCK);
|
||||||
|
} else {
|
||||||
|
cache = cipher.doFinal(data, offSet, inputLen - offSet);
|
||||||
|
}
|
||||||
|
out.write(cache, 0, cache.length);
|
||||||
|
i++;
|
||||||
|
offSet = i * MAX_ENCRYPT_BLOCK;
|
||||||
|
}
|
||||||
|
byte[] encryptedData = out.toByteArray();
|
||||||
|
out.close();
|
||||||
|
return encryptedData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** */
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* 私钥加密
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param data
|
||||||
|
* 源数据
|
||||||
|
* @param privateKey
|
||||||
|
* 私钥(BASE64编码)
|
||||||
|
* @return
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public static byte[] encryptByPrivateKey(byte[] data, String privateKey) throws Exception {
|
||||||
|
byte[] keyBytes = Base64.decodeBase64(privateKey);
|
||||||
|
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
|
||||||
|
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
|
||||||
|
Key privateK = keyFactory.generatePrivate(pkcs8KeySpec);
|
||||||
|
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
|
||||||
|
cipher.init(Cipher.ENCRYPT_MODE, privateK);
|
||||||
|
int inputLen = data.length;
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
|
int offSet = 0;
|
||||||
|
byte[] cache;
|
||||||
|
int i = 0;
|
||||||
|
// 对数据分段加密
|
||||||
|
while (inputLen - offSet > 0) {
|
||||||
|
if (inputLen - offSet > MAX_ENCRYPT_BLOCK) {
|
||||||
|
cache = cipher.doFinal(data, offSet, MAX_ENCRYPT_BLOCK);
|
||||||
|
} else {
|
||||||
|
cache = cipher.doFinal(data, offSet, inputLen - offSet);
|
||||||
|
}
|
||||||
|
out.write(cache, 0, cache.length);
|
||||||
|
i++;
|
||||||
|
offSet = i * MAX_ENCRYPT_BLOCK;
|
||||||
|
}
|
||||||
|
byte[] encryptedData = out.toByteArray();
|
||||||
|
out.close();
|
||||||
|
return encryptedData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** */
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* 获取私钥
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param keyMap
|
||||||
|
* 密钥对
|
||||||
|
* @return
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public static String getPrivateKey(Map<String, Object> keyMap) throws Exception {
|
||||||
|
Key key = (Key) keyMap.get(PRIVATE_KEY);
|
||||||
|
return Base64.encodeBase64String(key.getEncoded());
|
||||||
|
}
|
||||||
|
|
||||||
|
/** */
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* 获取公钥
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param keyMap
|
||||||
|
* 密钥对
|
||||||
|
* @return
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public static String getPublicKey(Map<String, Object> keyMap) throws Exception {
|
||||||
|
Key key = (Key) keyMap.get(PUBLIC_KEY);
|
||||||
|
return Base64.encodeBase64String(key.getEncoded());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* java端公钥加密
|
||||||
|
*/
|
||||||
|
public static String encryptedDataOnJava(String data, String PUBLICKEY) {
|
||||||
|
try {
|
||||||
|
data = Base64.encodeBase64String(encryptByPublicKey(data.getBytes(), PUBLICKEY));
|
||||||
|
} catch (Exception e) {
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* java端私钥解密
|
||||||
|
*/
|
||||||
|
public static String decryptDataOnJava(String data, String PRIVATEKEY) {
|
||||||
|
String temp = "";
|
||||||
|
try {
|
||||||
|
byte[] rs = Base64.decodeBase64(data);
|
||||||
|
temp = new String(RSAUtils.decryptByPrivateKey(rs, PRIVATEKEY),"UTF-8");
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
}
|
||||||
|
return temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) throws Exception{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -11,6 +11,7 @@ import com.alibaba.excel.util.ClassUtils;
|
|||||||
import com.alibaba.excel.write.handler.SheetWriteHandler;
|
import com.alibaba.excel.write.handler.SheetWriteHandler;
|
||||||
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
|
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
|
||||||
import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;
|
import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;
|
||||||
|
import com.ruoyi.common.core.utils.StringUtils;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.poi.ss.usermodel.*;
|
import org.apache.poi.ss.usermodel.*;
|
||||||
import org.apache.poi.ss.util.CellRangeAddressList;
|
import org.apache.poi.ss.util.CellRangeAddressList;
|
||||||
@ -99,15 +100,16 @@ public class ExcelDownHandler implements SheetWriteHandler {
|
|||||||
ExcelDictFormat format = field.getDeclaredAnnotation(ExcelDictFormat.class);
|
ExcelDictFormat format = field.getDeclaredAnnotation(ExcelDictFormat.class);
|
||||||
String dictType = format.dictType();
|
String dictType = format.dictType();
|
||||||
String converterExp = format.readConverterExp();
|
String converterExp = format.readConverterExp();
|
||||||
if (StrUtil.isNotBlank(dictType)) {
|
if (StringUtils.isNotBlank(dictType)) {
|
||||||
// 如果传递了字典名,则依据字典建立下拉
|
// 如果传递了字典名,则依据字典建立下拉
|
||||||
Collection<String> values = Optional.ofNullable(dictService.getAllDictByDictType(dictType))
|
Collection<String> values = Optional.ofNullable(dictService.getAllDictByDictType(dictType))
|
||||||
.orElseThrow(() -> new ServiceException(String.format("字典 %s 不存在", dictType)))
|
.orElseThrow(() -> new ServiceException(String.format("字典 %s 不存在", dictType)))
|
||||||
.values();
|
.values();
|
||||||
options = new ArrayList<>(values);
|
options = new ArrayList<>(values);
|
||||||
} else if (StrUtil.isNotBlank(converterExp)) {
|
} else if (StringUtils.isNotBlank(converterExp)) {
|
||||||
// 如果指定了确切的值,则直接解析确切的值
|
// 如果指定了确切的值,则直接解析确切的值
|
||||||
options = StrUtil.split(converterExp, format.separator(), true, true);
|
List<String> strList = StringUtils.splitList(converterExp, format.separator());
|
||||||
|
options = StreamUtils.toList(strList, s -> StringUtils.split(s, "=")[1]);
|
||||||
}
|
}
|
||||||
} else if (field.isAnnotationPresent(ExcelEnumFormat.class)) {
|
} else if (field.isAnnotationPresent(ExcelEnumFormat.class)) {
|
||||||
// 否则如果指定了@ExcelEnumFormat,则使用枚举的逻辑
|
// 否则如果指定了@ExcelEnumFormat,则使用枚举的逻辑
|
||||||
|
@ -38,6 +38,20 @@
|
|||||||
<artifactId>powerjob-official-processors</artifactId>
|
<artifactId>powerjob-official-processors</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- EasyRetry client -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.aizuda</groupId>
|
||||||
|
<artifactId>easy-retry-client-starter</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.aizuda</groupId>
|
||||||
|
<artifactId>easy-retry-client-core</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.aizuda</groupId>
|
||||||
|
<artifactId>easy-retry-client-job-core</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.projectlombok</groupId>
|
<groupId>org.projectlombok</groupId>
|
||||||
<artifactId>lombok</artifactId>
|
<artifactId>lombok</artifactId>
|
||||||
|
@ -0,0 +1,37 @@
|
|||||||
|
package com.ruoyi.common.job.config;
|
||||||
|
|
||||||
|
import ch.qos.logback.classic.Logger;
|
||||||
|
import ch.qos.logback.classic.LoggerContext;
|
||||||
|
import ch.qos.logback.classic.spi.ILoggingEvent;
|
||||||
|
import com.aizuda.easy.retry.client.common.appender.EasyRetryLogbackAppender;
|
||||||
|
import com.aizuda.easy.retry.client.common.event.EasyRetryStartingEvent;
|
||||||
|
import com.aizuda.easy.retry.client.starter.EnableEasyRetry;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
|
import org.springframework.context.event.EventListener;
|
||||||
|
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启动定时任务
|
||||||
|
*
|
||||||
|
* @author dhb52
|
||||||
|
* @since 2024/3/12
|
||||||
|
*/
|
||||||
|
@AutoConfiguration
|
||||||
|
@ConditionalOnProperty(prefix = "easy-retry", name = "enabled", havingValue = "true")
|
||||||
|
@EnableScheduling
|
||||||
|
@EnableEasyRetry(group = "${easy-retry.group-name}")
|
||||||
|
public class EasyRetryConfig {
|
||||||
|
|
||||||
|
@EventListener(EasyRetryStartingEvent.class)
|
||||||
|
public void onStarting(EasyRetryStartingEvent event) {
|
||||||
|
LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
|
||||||
|
EasyRetryLogbackAppender<ILoggingEvent> ca = new EasyRetryLogbackAppender<>();
|
||||||
|
ca.setName("easy_log_appender");
|
||||||
|
ca.start();
|
||||||
|
Logger rootLogger = lc.getLogger(Logger.ROOT_LOGGER_NAME);
|
||||||
|
rootLogger.addAppender(ca);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,2 @@
|
|||||||
|
com.ruoyi.common.job.config.PowerJobConfig
|
||||||
|
com.ruoyi.common.job.config.EasyRetryConfig
|
@ -27,11 +27,6 @@
|
|||||||
<artifactId>ruoyi-common-json</artifactId>
|
<artifactId>ruoyi-common-json</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.alibaba</groupId>
|
|
||||||
<artifactId>transmittable-thread-local</artifactId>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
|
@ -204,7 +204,7 @@ public class LogAspect {
|
|||||||
public boolean isFilterObject(final Object o) {
|
public boolean isFilterObject(final Object o) {
|
||||||
Class<?> clazz = o.getClass();
|
Class<?> clazz = o.getClass();
|
||||||
if (clazz.isArray()) {
|
if (clazz.isArray()) {
|
||||||
return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
|
return MultipartFile.class.isAssignableFrom(clazz.getComponentType());
|
||||||
} else if (Collection.class.isAssignableFrom(clazz)) {
|
} else if (Collection.class.isAssignableFrom(clazz)) {
|
||||||
Collection collection = (Collection) o;
|
Collection collection = (Collection) o;
|
||||||
for (Object value : collection) {
|
for (Object value : collection) {
|
||||||
|
@ -21,6 +21,11 @@
|
|||||||
<artifactId>ruoyi-common-core</artifactId>
|
<artifactId>ruoyi-common-core</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.ruoyi</groupId>
|
||||||
|
<artifactId>ruoyi-common-excel</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.ruoyi</groupId>
|
<groupId>com.ruoyi</groupId>
|
||||||
<artifactId>ruoyi-common-security</artifactId>
|
<artifactId>ruoyi-common-security</artifactId>
|
||||||
|
@ -4,6 +4,8 @@ import com.github.pagehelper.PageInterceptor;
|
|||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pagehelper分页,兼用老项目
|
* Pagehelper分页,兼用老项目
|
||||||
*
|
*
|
||||||
@ -14,6 +16,11 @@ public class PagehelperConfig {
|
|||||||
@Bean
|
@Bean
|
||||||
public PageInterceptor pageInterceptor(){
|
public PageInterceptor pageInterceptor(){
|
||||||
PageInterceptor pageInterceptor = new PageInterceptor();
|
PageInterceptor pageInterceptor = new PageInterceptor();
|
||||||
|
Properties properties = new Properties();
|
||||||
|
properties.setProperty("supportMethodsArguments","true");
|
||||||
|
properties.setProperty("autoRuntimeDialect","true");
|
||||||
|
pageInterceptor.setProperties(properties);
|
||||||
|
|
||||||
return pageInterceptor;
|
return pageInterceptor;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package com.ruoyi.common.orm.core.domain;
|
package com.ruoyi.common.orm.core.domain;
|
||||||
|
|
||||||
|
import com.alibaba.excel.annotation.ExcelProperty;
|
||||||
import com.mybatisflex.annotation.Column;
|
import com.mybatisflex.annotation.Column;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
import java.io.Serial;
|
import java.io.Serial;
|
||||||
@ -19,18 +21,21 @@ public class TreeEntity extends BaseEntity
|
|||||||
@Serial
|
@Serial
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
/** 父菜单名称 */
|
/** 父级名称 */
|
||||||
@Column(ignore = true)
|
@Column(ignore = true)
|
||||||
private String parentName;
|
private String parentName;
|
||||||
|
|
||||||
/** 父菜单ID */
|
/** 父亲ID */
|
||||||
|
@ExcelProperty(value = "上级编号")
|
||||||
|
@NotNull(message = "上级编号不能为空")
|
||||||
private Long parentId;
|
private Long parentId;
|
||||||
|
|
||||||
/** 显示顺序 */
|
/** 显示顺序 */
|
||||||
|
@ExcelProperty(value = "显示顺序")
|
||||||
private Integer orderNum;
|
private Integer orderNum;
|
||||||
|
|
||||||
/** 祖级列表 */
|
/** 祖级列表 */
|
||||||
@Column(ignore = true)
|
//@Column(ignore = true)
|
||||||
private String ancestors;
|
private String ancestors;
|
||||||
|
|
||||||
/** 子部门 */
|
/** 子部门 */
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package com.ruoyi.common.orm.handler;
|
package com.ruoyi.common.orm.handler;
|
||||||
|
|
||||||
import com.ruoyi.common.core.core.domain.R;
|
import com.ruoyi.common.core.core.domain.R;
|
||||||
|
import com.ruoyi.common.core.utils.StringUtils;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.mybatis.spring.MyBatisSystemException;
|
import org.mybatis.spring.MyBatisSystemException;
|
||||||
import org.springframework.dao.DuplicateKeyException;
|
import org.springframework.dao.DuplicateKeyException;
|
||||||
@ -35,7 +36,7 @@ public class MybatisExceptionHandler {
|
|||||||
public R<Void> handleCannotFindDataSourceException(MyBatisSystemException e, HttpServletRequest request) {
|
public R<Void> handleCannotFindDataSourceException(MyBatisSystemException e, HttpServletRequest request) {
|
||||||
String requestURI = request.getRequestURI();
|
String requestURI = request.getRequestURI();
|
||||||
String message = e.getMessage();
|
String message = e.getMessage();
|
||||||
if ("CannotFindDataSourceException".contains(message)) {
|
if (StringUtils.contains("CannotFindDataSourceException", message)) {
|
||||||
log.error("请求地址'{}', 未找到数据源", requestURI);
|
log.error("请求地址'{}', 未找到数据源", requestURI);
|
||||||
return R.fail("未找到数据源,请联系管理员确认");
|
return R.fail("未找到数据源,请联系管理员确认");
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,183 @@
|
|||||||
|
package com.ruoyi.common.orm.helper;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 监听器管理
|
||||||
|
* <p>
|
||||||
|
* 考虑任务调度、三方接口回调等可自由控制审计字段
|
||||||
|
*
|
||||||
|
* @author Ice
|
||||||
|
* @version 1.0
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class ListenerManager {
|
||||||
|
|
||||||
|
private ListenerManager() {}
|
||||||
|
|
||||||
|
private static ThreadLocal<Boolean> ignoreInsertListenerTl = ThreadLocal.withInitial(() -> Boolean.FALSE);
|
||||||
|
|
||||||
|
private static ThreadLocal<Boolean> ignoreUpdateListenerTl = ThreadLocal.withInitial(() -> Boolean.FALSE);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置 InsertListenerThreadLocal
|
||||||
|
* @param tl ThreadLocal
|
||||||
|
*/
|
||||||
|
public static synchronized void setInsertListenerTl(ThreadLocal<Boolean> tl) {
|
||||||
|
ignoreInsertListenerTl = tl;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置 UpdateListenerThreadLocal
|
||||||
|
* @param tl ThreadLocal
|
||||||
|
*/
|
||||||
|
public static synchronized void setUpdateListenerTl(ThreadLocal<Boolean> tl) {
|
||||||
|
ignoreUpdateListenerTl = tl;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否执行 InsertListener
|
||||||
|
* @return 是否执行
|
||||||
|
*/
|
||||||
|
public static boolean isDoInsertListener() {
|
||||||
|
return !ignoreInsertListenerTl.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否执行 UpdateListener
|
||||||
|
* @return 是否执行
|
||||||
|
*/
|
||||||
|
public static boolean isDoUpdateListener() {
|
||||||
|
return !ignoreUpdateListenerTl.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 忽略 Listener
|
||||||
|
*/
|
||||||
|
public static <T> T withoutListener(Supplier<T> supplier) {
|
||||||
|
try {
|
||||||
|
ignoreListener();
|
||||||
|
return supplier.get();
|
||||||
|
} finally {
|
||||||
|
restoreListener();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 忽略 Listener
|
||||||
|
*/
|
||||||
|
public static void withoutListener(Runnable runnable) {
|
||||||
|
try {
|
||||||
|
ignoreListener();
|
||||||
|
runnable.run();
|
||||||
|
} finally {
|
||||||
|
restoreListener();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 忽略 Listener
|
||||||
|
*/
|
||||||
|
public static void ignoreListener() {
|
||||||
|
ignoreInsertListenerTl.set(Boolean.TRUE);
|
||||||
|
ignoreUpdateListenerTl.set(Boolean.TRUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 恢复 Listener
|
||||||
|
*/
|
||||||
|
public static void restoreListener() {
|
||||||
|
ignoreInsertListenerTl.remove();
|
||||||
|
ignoreUpdateListenerTl.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 忽略 InsertListener
|
||||||
|
*/
|
||||||
|
public static <T> T withoutInsertListener(Supplier<T> supplier) {
|
||||||
|
try {
|
||||||
|
ignoreInsertListener();
|
||||||
|
return supplier.get();
|
||||||
|
} finally {
|
||||||
|
restoreInsertListener();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 忽略 InsertListener
|
||||||
|
*/
|
||||||
|
public static void withoutInsertListener(Runnable runnable) {
|
||||||
|
try {
|
||||||
|
ignoreInsertListener();
|
||||||
|
runnable.run();
|
||||||
|
} finally {
|
||||||
|
restoreInsertListener();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 忽略 InsertListener
|
||||||
|
*/
|
||||||
|
public static void ignoreInsertListener() {
|
||||||
|
ignoreInsertListenerTl.set(Boolean.TRUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 恢复 InsertListener
|
||||||
|
*/
|
||||||
|
public static void restoreInsertListener() {
|
||||||
|
ignoreInsertListenerTl.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 忽略 UpdateListener
|
||||||
|
*/
|
||||||
|
public static <T> T withoutUpdateListener(Supplier<T> supplier) {
|
||||||
|
try {
|
||||||
|
ignoreUpdateListener();
|
||||||
|
return supplier.get();
|
||||||
|
} finally {
|
||||||
|
restoreUpdateListener();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 忽略 UpdateListener
|
||||||
|
*/
|
||||||
|
public static void withoutUpdateListener(Runnable runnable) {
|
||||||
|
try {
|
||||||
|
ignoreUpdateListener();
|
||||||
|
runnable.run();
|
||||||
|
} finally {
|
||||||
|
restoreUpdateListener();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 忽略 UpdateListener
|
||||||
|
*/
|
||||||
|
public static void ignoreUpdateListener() {
|
||||||
|
ignoreUpdateListenerTl.set(Boolean.TRUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 恢复 UpdateListener
|
||||||
|
*/
|
||||||
|
public static void restoreUpdateListener() {
|
||||||
|
ignoreUpdateListenerTl.remove();
|
||||||
|
}
|
||||||
|
}
|
@ -5,6 +5,7 @@ import cn.hutool.http.HttpStatus;
|
|||||||
import com.mybatisflex.annotation.InsertListener;
|
import com.mybatisflex.annotation.InsertListener;
|
||||||
import com.ruoyi.common.core.exception.ServiceException;
|
import com.ruoyi.common.core.exception.ServiceException;
|
||||||
import com.ruoyi.common.orm.core.domain.BaseEntity;
|
import com.ruoyi.common.orm.core.domain.BaseEntity;
|
||||||
|
import com.ruoyi.common.orm.helper.ListenerManager;
|
||||||
import com.ruoyi.common.security.utils.LoginHelper;
|
import com.ruoyi.common.security.utils.LoginHelper;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
@ -19,7 +20,7 @@ public class EntityInsertListener implements InsertListener {
|
|||||||
@Override
|
@Override
|
||||||
public void onInsert(Object entity) {
|
public void onInsert(Object entity) {
|
||||||
try {
|
try {
|
||||||
if (ObjectUtil.isNotNull(entity) && (entity instanceof BaseEntity)) {
|
if (ListenerManager.isDoInsertListener() && ObjectUtil.isNotNull(entity) && (entity instanceof BaseEntity)) {
|
||||||
BaseEntity baseEntity = (BaseEntity) entity;
|
BaseEntity baseEntity = (BaseEntity) entity;
|
||||||
|
|
||||||
Long loginUserId = LoginHelper.getUserId();
|
Long loginUserId = LoginHelper.getUserId();
|
||||||
|
@ -5,6 +5,7 @@ import cn.hutool.http.HttpStatus;
|
|||||||
import com.mybatisflex.annotation.UpdateListener;
|
import com.mybatisflex.annotation.UpdateListener;
|
||||||
import com.ruoyi.common.core.exception.ServiceException;
|
import com.ruoyi.common.core.exception.ServiceException;
|
||||||
import com.ruoyi.common.orm.core.domain.BaseEntity;
|
import com.ruoyi.common.orm.core.domain.BaseEntity;
|
||||||
|
import com.ruoyi.common.orm.helper.ListenerManager;
|
||||||
import com.ruoyi.common.security.utils.LoginHelper;
|
import com.ruoyi.common.security.utils.LoginHelper;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
@ -18,7 +19,7 @@ public class EntityUpdateListener implements UpdateListener {
|
|||||||
@Override
|
@Override
|
||||||
public void onUpdate(Object entity) {
|
public void onUpdate(Object entity) {
|
||||||
try {
|
try {
|
||||||
if (ObjectUtil.isNotNull(entity) && (entity instanceof BaseEntity)) {
|
if (ListenerManager.isDoUpdateListener() && ObjectUtil.isNotNull(entity) && (entity instanceof BaseEntity)) {
|
||||||
BaseEntity baseEntity = (BaseEntity) entity;
|
BaseEntity baseEntity = (BaseEntity) entity;
|
||||||
baseEntity.setUpdateBy(LoginHelper.getUserId());
|
baseEntity.setUpdateBy(LoginHelper.getUserId());
|
||||||
baseEntity.setUpdateTime(new Date());
|
baseEntity.setUpdateTime(new Date());
|
||||||
|
@ -31,9 +31,44 @@
|
|||||||
<artifactId>ruoyi-common-redis</artifactId>
|
<artifactId>ruoyi-common-redis</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- AWS SDK for Java 2.x -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.amazonaws</groupId>
|
<groupId>software.amazon.awssdk</groupId>
|
||||||
<artifactId>aws-java-sdk-s3</artifactId>
|
<artifactId>s3</artifactId>
|
||||||
|
<exclusions>
|
||||||
|
<!-- 将基于 Netty 的 HTTP 客户端从类路径中移除 -->
|
||||||
|
<exclusion>
|
||||||
|
<groupId>software.amazon.awssdk</groupId>
|
||||||
|
<artifactId>netty-nio-client</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
<!-- 将基于 CRT 的 HTTP 客户端从类路径中移除 -->
|
||||||
|
<exclusion>
|
||||||
|
<groupId>software.amazon.awssdk</groupId>
|
||||||
|
<artifactId>aws-crt-client</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
<!-- 将基于 Apache 的 HTTP 客户端从类路径中移除 -->
|
||||||
|
<exclusion>
|
||||||
|
<groupId>software.amazon.awssdk</groupId>
|
||||||
|
<artifactId>apache-client</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
<!-- 将配置基于 URL 连接的 HTTP 客户端从类路径中移除 -->
|
||||||
|
<exclusion>
|
||||||
|
<groupId>software.amazon.awssdk</groupId>
|
||||||
|
<artifactId>url-connection-client</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- 使用AWS基于 CRT 的 S3 客户端 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>software.amazon.awssdk.crt</groupId>
|
||||||
|
<artifactId>aws-crt</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- 基于 AWS CRT 的 S3 客户端的性能增强的 S3 传输管理器 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>software.amazon.awssdk</groupId>
|
||||||
|
<artifactId>s3-transfer-manager</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
@ -2,73 +2,117 @@ package com.ruoyi.common.oss.core;
|
|||||||
|
|
||||||
import cn.hutool.core.io.IoUtil;
|
import cn.hutool.core.io.IoUtil;
|
||||||
import cn.hutool.core.util.IdUtil;
|
import cn.hutool.core.util.IdUtil;
|
||||||
import com.amazonaws.ClientConfiguration;
|
import com.ruoyi.common.core.constant.Constants;
|
||||||
import com.amazonaws.HttpMethod;
|
|
||||||
import com.amazonaws.Protocol;
|
|
||||||
import com.amazonaws.auth.AWSCredentials;
|
|
||||||
import com.amazonaws.auth.AWSCredentialsProvider;
|
|
||||||
import com.amazonaws.auth.AWSStaticCredentialsProvider;
|
|
||||||
import com.amazonaws.auth.BasicAWSCredentials;
|
|
||||||
import com.amazonaws.client.builder.AwsClientBuilder;
|
|
||||||
import com.amazonaws.services.s3.AmazonS3;
|
|
||||||
import com.amazonaws.services.s3.AmazonS3Client;
|
|
||||||
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
|
|
||||||
import com.amazonaws.services.s3.model.*;
|
|
||||||
import com.ruoyi.common.core.utils.DateUtils;
|
import com.ruoyi.common.core.utils.DateUtils;
|
||||||
import com.ruoyi.common.core.utils.StringUtils;
|
import com.ruoyi.common.core.utils.StringUtils;
|
||||||
|
import com.ruoyi.common.core.utils.file.FileUtils;
|
||||||
import com.ruoyi.common.oss.constant.OssConstant;
|
import com.ruoyi.common.oss.constant.OssConstant;
|
||||||
import com.ruoyi.common.oss.entity.UploadResult;
|
import com.ruoyi.common.oss.entity.UploadResult;
|
||||||
import com.ruoyi.common.oss.enumd.AccessPolicyType;
|
import com.ruoyi.common.oss.enumd.AccessPolicyType;
|
||||||
import com.ruoyi.common.oss.enumd.PolicyType;
|
import com.ruoyi.common.oss.enumd.PolicyType;
|
||||||
import com.ruoyi.common.oss.exception.OssException;
|
import com.ruoyi.common.oss.exception.OssException;
|
||||||
import com.ruoyi.common.oss.properties.OssProperties;
|
import com.ruoyi.common.oss.properties.OssProperties;
|
||||||
|
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
|
||||||
|
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
|
||||||
|
import software.amazon.awssdk.core.async.AsyncRequestBody;
|
||||||
|
import software.amazon.awssdk.core.async.BlockingInputStreamAsyncRequestBody;
|
||||||
|
import software.amazon.awssdk.regions.Region;
|
||||||
|
import software.amazon.awssdk.services.s3.S3AsyncClient;
|
||||||
|
import software.amazon.awssdk.services.s3.S3Configuration;
|
||||||
|
import software.amazon.awssdk.services.s3.model.NoSuchBucketException;
|
||||||
|
import software.amazon.awssdk.services.s3.model.S3Exception;
|
||||||
|
import software.amazon.awssdk.services.s3.presigner.S3Presigner;
|
||||||
|
import software.amazon.awssdk.transfer.s3.S3TransferManager;
|
||||||
|
import software.amazon.awssdk.transfer.s3.model.*;
|
||||||
|
import software.amazon.awssdk.transfer.s3.progress.LoggingTransferListener;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.net.URI;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.Date;
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.time.Duration;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* S3 存储协议 所有兼容S3协议的云厂商均支持
|
* S3 存储协议 所有兼容S3协议的云厂商均支持
|
||||||
* 阿里云 腾讯云 七牛云 minio
|
* 阿里云 腾讯云 七牛云 minio
|
||||||
*
|
*
|
||||||
* @author Lion Li
|
* @author AprilWind
|
||||||
*/
|
*/
|
||||||
public class OssClient {
|
public class OssClient {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 服务商
|
||||||
|
*/
|
||||||
private final String configKey;
|
private final String configKey;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置属性
|
||||||
|
*/
|
||||||
private final OssProperties properties;
|
private final OssProperties properties;
|
||||||
|
|
||||||
private final AmazonS3 client;
|
/**
|
||||||
|
* Amazon S3 异步客户端
|
||||||
|
*/
|
||||||
|
private final S3AsyncClient client;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用于管理 S3 数据传输的高级工具
|
||||||
|
*/
|
||||||
|
private final S3TransferManager transferManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AWS S3 预签名 URL 的生成器
|
||||||
|
*/
|
||||||
|
private final S3Presigner presigner;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造方法
|
||||||
|
*
|
||||||
|
* @param configKey 配置键
|
||||||
|
* @param ossProperties Oss配置属性
|
||||||
|
*/
|
||||||
public OssClient(String configKey, OssProperties ossProperties) {
|
public OssClient(String configKey, OssProperties ossProperties) {
|
||||||
this.configKey = configKey;
|
this.configKey = configKey;
|
||||||
this.properties = ossProperties;
|
this.properties = ossProperties;
|
||||||
try {
|
try {
|
||||||
AwsClientBuilder.EndpointConfiguration endpointConfig =
|
// 创建 AWS 认证信息
|
||||||
new AwsClientBuilder.EndpointConfiguration(properties.getEndpoint(), properties.getRegion());
|
StaticCredentialsProvider credentialsProvider = StaticCredentialsProvider.create(
|
||||||
|
AwsBasicCredentials.create(properties.getAccessKey(), properties.getSecretKey()));
|
||||||
|
|
||||||
AWSCredentials credentials = new BasicAWSCredentials(properties.getAccessKey(), properties.getSecretKey());
|
//MinIO 使用 HTTPS 限制使用域名访问,站点填域名。需要启用路径样式访问
|
||||||
AWSCredentialsProvider credentialsProvider = new AWSStaticCredentialsProvider(credentials);
|
boolean isStyle = !StringUtils.containsAny(properties.getEndpoint(), OssConstant.CLOUD_SERVICE);
|
||||||
ClientConfiguration clientConfig = new ClientConfiguration();
|
|
||||||
if (OssConstant.IS_HTTPS.equals(properties.getIsHttps())) {
|
|
||||||
clientConfig.setProtocol(Protocol.HTTPS);
|
|
||||||
} else {
|
|
||||||
clientConfig.setProtocol(Protocol.HTTP);
|
|
||||||
}
|
|
||||||
AmazonS3ClientBuilder build = AmazonS3Client.builder()
|
|
||||||
.withEndpointConfiguration(endpointConfig)
|
|
||||||
.withClientConfiguration(clientConfig)
|
|
||||||
.withCredentials(credentialsProvider)
|
|
||||||
.disableChunkedEncoding();
|
|
||||||
if (!StringUtils.containsAny(properties.getEndpoint(), OssConstant.CLOUD_SERVICE)) {
|
|
||||||
// minio 使用https限制使用域名访问 需要此配置 站点填域名
|
|
||||||
build.enablePathStyleAccess();
|
|
||||||
}
|
|
||||||
this.client = build.build();
|
|
||||||
|
|
||||||
|
//创建AWS基于 CRT 的 S3 客户端
|
||||||
|
this.client = S3AsyncClient.crtBuilder()
|
||||||
|
.credentialsProvider(credentialsProvider)
|
||||||
|
.endpointOverride(URI.create(getEndpoint()))
|
||||||
|
.region(of())
|
||||||
|
.targetThroughputInGbps(20.0)
|
||||||
|
.minimumPartSizeInBytes(10 * 1025 * 1024L)
|
||||||
|
.checksumValidationEnabled(false)
|
||||||
|
.forcePathStyle(isStyle)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
//AWS基于 CRT 的 S3 AsyncClient 实例用作 S3 传输管理器的底层客户端
|
||||||
|
this.transferManager = S3TransferManager.builder().s3Client(this.client).build();
|
||||||
|
|
||||||
|
// 创建 S3 配置对象
|
||||||
|
S3Configuration config = S3Configuration.builder().chunkedEncodingEnabled(false)
|
||||||
|
.pathStyleAccessEnabled(isStyle).build();
|
||||||
|
|
||||||
|
// 创建 预签名 URL 的生成器 实例,用于生成 S3 预签名 URL
|
||||||
|
this.presigner = S3Presigner.builder()
|
||||||
|
.region(of())
|
||||||
|
.credentialsProvider(credentialsProvider)
|
||||||
|
.endpointOverride(URI.create(getDomain()))
|
||||||
|
.serviceConfiguration(config)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// 创建存储桶
|
||||||
createBucket();
|
createBucket();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
if (e instanceof OssException) {
|
if (e instanceof OssException) {
|
||||||
@ -78,126 +122,158 @@ public class OssClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 同步创建存储桶
|
||||||
|
* 如果存储桶不存在,会进行创建;如果存储桶存在,不执行任何操作
|
||||||
|
*
|
||||||
|
* @throws OssException 当创建存储桶时发生异常时抛出
|
||||||
|
*/
|
||||||
public void createBucket() {
|
public void createBucket() {
|
||||||
|
String bucketName = properties.getBucketName();
|
||||||
try {
|
try {
|
||||||
String bucketName = properties.getBucketName();
|
// 尝试获取存储桶的信息
|
||||||
if (client.doesBucketExistV2(bucketName)) {
|
client.headBucket(
|
||||||
return;
|
x -> x.bucket(bucketName)
|
||||||
|
.build())
|
||||||
|
.join();
|
||||||
|
} catch (Exception ex) {
|
||||||
|
if (ex.getCause() instanceof NoSuchBucketException) {
|
||||||
|
try {
|
||||||
|
// 存储桶不存在,尝试创建存储桶
|
||||||
|
client.createBucket(
|
||||||
|
x -> x.bucket(bucketName))
|
||||||
|
.join();
|
||||||
|
|
||||||
|
// 设置存储桶的访问策略(Bucket Policy)
|
||||||
|
client.putBucketPolicy(
|
||||||
|
x -> x.bucket(bucketName)
|
||||||
|
.policy(getPolicy(bucketName, getAccessPolicy().getPolicyType())))
|
||||||
|
.join();
|
||||||
|
} catch (S3Exception e) {
|
||||||
|
// 存储桶创建或策略设置失败
|
||||||
|
throw new OssException("创建Bucket失败, 请核对配置信息:[" + e.getMessage() + "]");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new OssException("判断Bucket是否存在失败,请核对配置信息:[" + ex.getMessage() + "]");
|
||||||
}
|
}
|
||||||
CreateBucketRequest createBucketRequest = new CreateBucketRequest(bucketName);
|
|
||||||
AccessPolicyType accessPolicy = getAccessPolicy();
|
|
||||||
createBucketRequest.setCannedAcl(accessPolicy.getAcl());
|
|
||||||
client.createBucket(createBucketRequest);
|
|
||||||
client.setBucketPolicy(bucketName, getPolicy(bucketName, accessPolicy.getPolicyType()));
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new OssException("创建Bucket失败, 请核对配置信息:[" + e.getMessage() + "]");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public UploadResult upload(byte[] data, String path, String contentType) {
|
/**
|
||||||
return upload(new ByteArrayInputStream(data), path, contentType);
|
* 上传文件到 Amazon S3,并返回上传结果
|
||||||
|
*
|
||||||
|
* @param filePath 本地文件路径
|
||||||
|
* @param key 在 Amazon S3 中的对象键
|
||||||
|
* @param md5Digest 本地文件的 MD5 哈希值(可选)
|
||||||
|
* @return UploadResult 包含上传后的文件信息
|
||||||
|
* @throws OssException 如果上传失败,抛出自定义异常
|
||||||
|
*/
|
||||||
|
public UploadResult upload(Path filePath, String key, String md5Digest) {
|
||||||
|
try {
|
||||||
|
// 构建上传请求对象
|
||||||
|
FileUpload fileUpload = transferManager.uploadFile(
|
||||||
|
x -> x.putObjectRequest(
|
||||||
|
y -> y.bucket(properties.getBucketName())
|
||||||
|
.key(key)
|
||||||
|
.contentMD5(StringUtils.isNotEmpty(md5Digest) ? md5Digest : null)
|
||||||
|
.build())
|
||||||
|
.addTransferListener(LoggingTransferListener.create())
|
||||||
|
.source(filePath).build());
|
||||||
|
|
||||||
|
// 等待上传完成并获取上传结果
|
||||||
|
CompletedFileUpload uploadResult = fileUpload.completionFuture().join();
|
||||||
|
String eTag = uploadResult.response().eTag();
|
||||||
|
|
||||||
|
// 提取上传结果中的 ETag,并构建一个自定义的 UploadResult 对象
|
||||||
|
return UploadResult.builder().url(getUrl() + StringUtils.SLASH + key).filename(key).eTag(eTag).build();
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 捕获异常并抛出自定义异常
|
||||||
|
throw new OssException("上传文件失败,请检查配置信息:[" + e.getMessage() + "]");
|
||||||
|
} finally {
|
||||||
|
// 无论上传是否成功,最终都会删除临时文件
|
||||||
|
FileUtils.del(filePath);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public UploadResult upload(InputStream inputStream, String path, String contentType) {
|
/**
|
||||||
|
* 上传 InputStream 到 Amazon S3
|
||||||
|
*
|
||||||
|
* @param inputStream 要上传的输入流
|
||||||
|
* @param key 在 Amazon S3 中的对象键
|
||||||
|
* @param length 输入流的长度
|
||||||
|
* @return UploadResult 包含上传后的文件信息
|
||||||
|
* @throws OssException 如果上传失败,抛出自定义异常
|
||||||
|
*/
|
||||||
|
public UploadResult upload(InputStream inputStream, String key, Long length) {
|
||||||
|
// 如果输入流不是 ByteArrayInputStream,则将其读取为字节数组再创建 ByteArrayInputStream
|
||||||
if (!(inputStream instanceof ByteArrayInputStream)) {
|
if (!(inputStream instanceof ByteArrayInputStream)) {
|
||||||
inputStream = new ByteArrayInputStream(IoUtil.readBytes(inputStream));
|
inputStream = new ByteArrayInputStream(IoUtil.readBytes(inputStream));
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
ObjectMetadata metadata = new ObjectMetadata();
|
// 创建异步请求体(length如果为空会报错)
|
||||||
metadata.setContentType(contentType);
|
BlockingInputStreamAsyncRequestBody body = AsyncRequestBody.forBlockingInputStream(length);
|
||||||
metadata.setContentLength(inputStream.available());
|
|
||||||
PutObjectRequest putObjectRequest = new PutObjectRequest(properties.getBucketName(), path, inputStream, metadata);
|
// 使用 transferManager 进行上传
|
||||||
// 设置上传对象的 Acl 为公共读
|
Upload upload = transferManager.upload(
|
||||||
putObjectRequest.setCannedAcl(getAccessPolicy().getAcl());
|
x -> x.requestBody(body)
|
||||||
client.putObject(putObjectRequest);
|
.putObjectRequest(
|
||||||
|
y -> y.bucket(properties.getBucketName())
|
||||||
|
.key(key)
|
||||||
|
.build())
|
||||||
|
.build());
|
||||||
|
|
||||||
|
// 将输入流写入请求体
|
||||||
|
body.writeInputStream(inputStream);
|
||||||
|
|
||||||
|
// 等待文件上传操作完成
|
||||||
|
CompletedUpload uploadResult = upload.completionFuture().join();
|
||||||
|
String eTag = uploadResult.response().eTag();
|
||||||
|
|
||||||
|
// 提取上传结果中的 ETag,并构建一个自定义的 UploadResult 对象
|
||||||
|
return UploadResult.builder().url(getUrl() + StringUtils.SLASH + key).filename(key).eTag(eTag).build();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new OssException("上传文件失败,请检查配置信息:[" + e.getMessage() + "]");
|
throw new OssException("上传文件失败,请检查配置信息:[" + e.getMessage() + "]");
|
||||||
}
|
}
|
||||||
return UploadResult.builder().url(getUrl() + "/" + path).filename(path).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
public UploadResult upload(File file, String path) {
|
|
||||||
try {
|
|
||||||
PutObjectRequest putObjectRequest = new PutObjectRequest(properties.getBucketName(), path, file);
|
|
||||||
// 设置上传对象的 Acl 为公共读
|
|
||||||
putObjectRequest.setCannedAcl(getAccessPolicy().getAcl());
|
|
||||||
client.putObject(putObjectRequest);
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new OssException("上传文件失败,请检查配置信息:[" + e.getMessage() + "]");
|
|
||||||
}
|
|
||||||
return UploadResult.builder().url(getUrl() + "/" + path).filename(path).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void delete(String path) {
|
|
||||||
path = path.replace(getUrl() + "/", "");
|
|
||||||
try {
|
|
||||||
client.deleteObject(properties.getBucketName(), path);
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new OssException("删除文件失败,请检查配置信息:[" + e.getMessage() + "]");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public UploadResult uploadSuffix(byte[] data, String suffix, String contentType) {
|
|
||||||
return upload(data, getPath(properties.getPrefix(), suffix), contentType);
|
|
||||||
}
|
|
||||||
|
|
||||||
public UploadResult uploadSuffix(InputStream inputStream, String suffix, String contentType) {
|
|
||||||
return upload(inputStream, getPath(properties.getPrefix(), suffix), contentType);
|
|
||||||
}
|
|
||||||
|
|
||||||
public UploadResult uploadSuffix(File file, String suffix) {
|
|
||||||
return upload(file, getPath(properties.getPrefix(), suffix));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取文件元数据
|
* 下载文件从 Amazon S3 到临时目录
|
||||||
*
|
*
|
||||||
* @param path 完整文件路径
|
* @param path 文件在 Amazon S3 中的对象键
|
||||||
|
* @return 下载后的文件在本地的临时路径
|
||||||
|
* @throws OssException 如果下载失败,抛出自定义异常
|
||||||
*/
|
*/
|
||||||
public ObjectMetadata getObjectMetadata(String path) {
|
public Path fileDownload(String path) {
|
||||||
path = path.replace(getUrl() + "/", "");
|
// 构建临时文件
|
||||||
S3Object object = client.getObject(properties.getBucketName(), path);
|
Path tempFilePath = FileUtils.createTempFile().toPath();
|
||||||
return object.getObjectMetadata();
|
// 使用 S3TransferManager 下载文件
|
||||||
|
FileDownload downloadFile = transferManager.downloadFile(
|
||||||
|
x -> x.getObjectRequest(
|
||||||
|
y -> y.bucket(properties.getBucketName())
|
||||||
|
.key(removeBaseUrl(path))
|
||||||
|
.build())
|
||||||
|
.addTransferListener(LoggingTransferListener.create())
|
||||||
|
.destination(tempFilePath)
|
||||||
|
.build());
|
||||||
|
// 等待文件下载操作完成
|
||||||
|
downloadFile.completionFuture().join();
|
||||||
|
return tempFilePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
public InputStream getObjectContent(String path) {
|
/**
|
||||||
path = path.replace(getUrl() + "/", "");
|
* 删除云存储服务中指定路径下文件
|
||||||
S3Object object = client.getObject(properties.getBucketName(), path);
|
*
|
||||||
return object.getObjectContent();
|
* @param path 指定路径
|
||||||
}
|
*/
|
||||||
|
public void delete(String path) {
|
||||||
public String getUrl() {
|
try {
|
||||||
String domain = properties.getDomain();
|
client.deleteObject(
|
||||||
String endpoint = properties.getEndpoint();
|
x -> x.bucket(properties.getBucketName())
|
||||||
String header = OssConstant.IS_HTTPS.equals(properties.getIsHttps()) ? "https://" : "http://";
|
.key(removeBaseUrl(path))
|
||||||
// 云服务商直接返回
|
.build());
|
||||||
if (StringUtils.containsAny(endpoint, OssConstant.CLOUD_SERVICE)) {
|
} catch (Exception e) {
|
||||||
if (StringUtils.isNotBlank(domain)) {
|
throw new OssException("删除文件失败,请检查配置信息:[" + e.getMessage() + "]");
|
||||||
return header + domain;
|
|
||||||
}
|
|
||||||
return header + properties.getBucketName() + "." + endpoint;
|
|
||||||
}
|
}
|
||||||
// minio 单独处理
|
|
||||||
if (StringUtils.isNotBlank(domain)) {
|
|
||||||
return header + domain + "/" + properties.getBucketName();
|
|
||||||
}
|
|
||||||
return header + endpoint + "/" + properties.getBucketName();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getPath(String prefix, String suffix) {
|
|
||||||
// 生成uuid
|
|
||||||
String uuid = IdUtil.fastSimpleUUID();
|
|
||||||
// 文件路径
|
|
||||||
String path = DateUtils.datePath() + "/" + uuid;
|
|
||||||
if (StringUtils.isNotBlank(prefix)) {
|
|
||||||
path = prefix + "/" + path;
|
|
||||||
}
|
|
||||||
return path + suffix;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public String getConfigKey() {
|
|
||||||
return configKey;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -207,14 +283,189 @@ public class OssClient {
|
|||||||
* @param second 授权时间
|
* @param second 授权时间
|
||||||
*/
|
*/
|
||||||
public String getPrivateUrl(String objectKey, Integer second) {
|
public String getPrivateUrl(String objectKey, Integer second) {
|
||||||
GeneratePresignedUrlRequest generatePresignedUrlRequest =
|
// 使用 AWS S3 预签名 URL 的生成器 获取对象的预签名 URL
|
||||||
new GeneratePresignedUrlRequest(properties.getBucketName(), objectKey)
|
URL url = presigner.presignGetObject(
|
||||||
.withMethod(HttpMethod.GET)
|
x -> x.signatureDuration(Duration.ofSeconds(second))
|
||||||
.withExpiration(new Date(System.currentTimeMillis() + 1000L * second));
|
.getObjectRequest(
|
||||||
URL url = client.generatePresignedUrl(generatePresignedUrlRequest);
|
y -> y.bucket(properties.getBucketName())
|
||||||
|
.key(objectKey)
|
||||||
|
.build())
|
||||||
|
.build())
|
||||||
|
.url();
|
||||||
return url.toString();
|
return url.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传 byte[] 数据到 Amazon S3,使用指定的后缀构造对象键。
|
||||||
|
*
|
||||||
|
* @param data 要上传的 byte[] 数据
|
||||||
|
* @param suffix 对象键的后缀
|
||||||
|
* @return UploadResult 包含上传后的文件信息
|
||||||
|
* @throws OssException 如果上传失败,抛出自定义异常
|
||||||
|
*/
|
||||||
|
public UploadResult uploadSuffix(byte[] data, String suffix) {
|
||||||
|
return upload(new ByteArrayInputStream(data), getPath(properties.getPrefix(), suffix), Long.valueOf(data.length));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传 InputStream 到 Amazon S3,使用指定的后缀构造对象键。
|
||||||
|
*
|
||||||
|
* @param inputStream 要上传的输入流
|
||||||
|
* @param suffix 对象键的后缀
|
||||||
|
* @param length 输入流的长度
|
||||||
|
* @return UploadResult 包含上传后的文件信息
|
||||||
|
* @throws OssException 如果上传失败,抛出自定义异常
|
||||||
|
*/
|
||||||
|
public UploadResult uploadSuffix(InputStream inputStream, String suffix, Long length) {
|
||||||
|
return upload(inputStream, getPath(properties.getPrefix(), suffix), length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传文件到 Amazon S3,使用指定的后缀构造对象键
|
||||||
|
*
|
||||||
|
* @param file 要上传的文件
|
||||||
|
* @param suffix 对象键的后缀
|
||||||
|
* @return UploadResult 包含上传后的文件信息
|
||||||
|
* @throws OssException 如果上传失败,抛出自定义异常
|
||||||
|
*/
|
||||||
|
public UploadResult uploadSuffix(File file, String suffix) {
|
||||||
|
return upload(file.toPath(), getPath(properties.getPrefix(), suffix), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取文件输入流
|
||||||
|
*
|
||||||
|
* @param path 完整文件路径
|
||||||
|
* @return 输入流
|
||||||
|
*/
|
||||||
|
public InputStream getObjectContent(String path) throws IOException {
|
||||||
|
// 下载文件到临时目录
|
||||||
|
Path tempFilePath = fileDownload(path);
|
||||||
|
// 创建输入流
|
||||||
|
InputStream inputStream = Files.newInputStream(tempFilePath);
|
||||||
|
// 删除临时文件
|
||||||
|
FileUtils.del(tempFilePath);
|
||||||
|
// 返回对象内容的输入流
|
||||||
|
return inputStream;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取 S3 客户端的终端点 URL
|
||||||
|
*
|
||||||
|
* @return 终端点 URL
|
||||||
|
*/
|
||||||
|
public String getEndpoint() {
|
||||||
|
// 根据配置文件中的是否使用 HTTPS,设置协议头部
|
||||||
|
String header = getIsHttps();
|
||||||
|
// 拼接协议头部和终端点,得到完整的终端点 URL
|
||||||
|
return header + properties.getEndpoint();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取 S3 客户端的终端点 URL(自定义域名)
|
||||||
|
*
|
||||||
|
* @return 终端点 URL
|
||||||
|
*/
|
||||||
|
public String getDomain() {
|
||||||
|
// 从配置中获取域名、终端点、是否使用 HTTPS 等信息
|
||||||
|
String domain = properties.getDomain();
|
||||||
|
String endpoint = properties.getEndpoint();
|
||||||
|
String header = getIsHttps();
|
||||||
|
|
||||||
|
// 如果是云服务商,直接返回域名或终端点
|
||||||
|
if (StringUtils.containsAny(endpoint, OssConstant.CLOUD_SERVICE)) {
|
||||||
|
return StringUtils.isNotEmpty(domain) ? header + domain : header + endpoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果是 MinIO,处理域名并返回
|
||||||
|
if (StringUtils.isNotEmpty(domain)) {
|
||||||
|
return domain.startsWith(Constants.HTTPS) || domain.startsWith(Constants.HTTP) ? domain : header + domain;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回终端点
|
||||||
|
return header + endpoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据传入的 region 参数返回相应的 AWS 区域
|
||||||
|
* 如果 region 参数非空,使用 Region.of 方法创建并返回对应的 AWS 区域对象
|
||||||
|
* 如果 region 参数为空,返回一个默认的 AWS 区域(例如,us-east-1),作为广泛支持的区域
|
||||||
|
*
|
||||||
|
* @return 对应的 AWS 区域对象,或者默认的广泛支持的区域(us-east-1)
|
||||||
|
*/
|
||||||
|
public Region of() {
|
||||||
|
//AWS 区域字符串
|
||||||
|
String region = properties.getRegion();
|
||||||
|
// 如果 region 参数非空,使用 Region.of 方法创建对应的 AWS 区域对象,否则返回默认区域
|
||||||
|
return StringUtils.isNotEmpty(region) ? Region.of(region) : Region.US_EAST_1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取云存储服务的URL
|
||||||
|
*
|
||||||
|
* @return 文件路径
|
||||||
|
*/
|
||||||
|
public String getUrl() {
|
||||||
|
String domain = properties.getDomain();
|
||||||
|
String endpoint = properties.getEndpoint();
|
||||||
|
String header = getIsHttps();
|
||||||
|
// 云服务商直接返回
|
||||||
|
if (StringUtils.containsAny(endpoint, OssConstant.CLOUD_SERVICE)) {
|
||||||
|
return header + (StringUtils.isNotEmpty(domain) ? domain : properties.getBucketName() + "." + endpoint);
|
||||||
|
}
|
||||||
|
// MinIO 单独处理
|
||||||
|
if (StringUtils.isNotEmpty(domain)) {
|
||||||
|
// 如果 domain 以 "https://" 或 "http://" 开头
|
||||||
|
return (domain.startsWith(Constants.HTTPS) || domain.startsWith(Constants.HTTP)) ?
|
||||||
|
domain + StringUtils.SLASH + properties.getBucketName() : header + domain + StringUtils.SLASH + properties.getBucketName();
|
||||||
|
}
|
||||||
|
return header + endpoint + StringUtils.SLASH + properties.getBucketName();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成一个符合特定规则的、唯一的文件路径。通过使用日期、UUID、前缀和后缀等元素的组合,确保了文件路径的独一无二性
|
||||||
|
*
|
||||||
|
* @param prefix 前缀
|
||||||
|
* @param suffix 后缀
|
||||||
|
* @return 文件路径
|
||||||
|
*/
|
||||||
|
public String getPath(String prefix, String suffix) {
|
||||||
|
// 生成uuid
|
||||||
|
String uuid = IdUtil.fastSimpleUUID();
|
||||||
|
// 生成日期路径
|
||||||
|
String datePath = DateUtils.datePath();
|
||||||
|
// 拼接路径
|
||||||
|
String path = StringUtils.isNotEmpty(prefix) ?
|
||||||
|
prefix + StringUtils.SLASH + datePath + StringUtils.SLASH + uuid : datePath + StringUtils.SLASH + uuid;
|
||||||
|
return path + suffix;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移除路径中的基础URL部分,得到相对路径
|
||||||
|
*
|
||||||
|
* @param path 完整的路径,包括基础URL和相对路径
|
||||||
|
* @return 去除基础URL后的相对路径
|
||||||
|
*/
|
||||||
|
public String removeBaseUrl(String path) {
|
||||||
|
return path.replace(getUrl() + StringUtils.SLASH, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 服务商
|
||||||
|
*/
|
||||||
|
public String getConfigKey() {
|
||||||
|
return configKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取是否使用 HTTPS 的配置,并返回相应的协议头部。
|
||||||
|
*
|
||||||
|
* @return 协议头部,根据是否使用 HTTPS 返回 "https://" 或 "http://"
|
||||||
|
*/
|
||||||
|
public String getIsHttps() {
|
||||||
|
return OssConstant.IS_HTTPS.equals(properties.getIsHttps()) ? Constants.HTTPS : Constants.HTTP;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 检查配置是否相同
|
* 检查配置是否相同
|
||||||
*/
|
*/
|
||||||
@ -231,32 +482,77 @@ public class OssClient {
|
|||||||
return AccessPolicyType.getByType(properties.getAccessPolicy());
|
return AccessPolicyType.getByType(properties.getAccessPolicy());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成 AWS S3 存储桶访问策略
|
||||||
|
*
|
||||||
|
* @param bucketName 存储桶
|
||||||
|
* @param policyType 桶策略类型
|
||||||
|
* @return 符合 AWS S3 存储桶访问策略格式的字符串
|
||||||
|
*/
|
||||||
private static String getPolicy(String bucketName, PolicyType policyType) {
|
private static String getPolicy(String bucketName, PolicyType policyType) {
|
||||||
StringBuilder builder = new StringBuilder();
|
String policy = switch (policyType) {
|
||||||
builder.append("{\n\"Statement\": [\n{\n\"Action\": [\n");
|
case WRITE -> """
|
||||||
builder.append(switch (policyType) {
|
{
|
||||||
case WRITE -> "\"s3:GetBucketLocation\",\n\"s3:ListBucketMultipartUploads\"\n";
|
"Version": "2012-10-17",
|
||||||
case READ_WRITE -> "\"s3:GetBucketLocation\",\n\"s3:ListBucket\",\n\"s3:ListBucketMultipartUploads\"\n";
|
"Statement": []
|
||||||
default -> "\"s3:GetBucketLocation\"\n";
|
}
|
||||||
});
|
""";
|
||||||
builder.append("],\n\"Effect\": \"Allow\",\n\"Principal\": \"*\",\n\"Resource\": \"arn:aws:s3:::");
|
case READ_WRITE -> """
|
||||||
builder.append(bucketName);
|
{
|
||||||
builder.append("\"\n},\n");
|
"Version": "2012-10-17",
|
||||||
if (policyType == PolicyType.READ) {
|
"Statement": [
|
||||||
builder.append("{\n\"Action\": [\n\"s3:ListBucket\"\n],\n\"Effect\": \"Deny\",\n\"Principal\": \"*\",\n\"Resource\": \"arn:aws:s3:::");
|
{
|
||||||
builder.append(bucketName);
|
"Effect": "Allow",
|
||||||
builder.append("\"\n},\n");
|
"Principal": "*",
|
||||||
}
|
"Action": [
|
||||||
builder.append("{\n\"Action\": ");
|
"s3:GetBucketLocation",
|
||||||
builder.append(switch (policyType) {
|
"s3:ListBucket",
|
||||||
case WRITE -> "[\n\"s3:AbortMultipartUpload\",\n\"s3:DeleteObject\",\n\"s3:ListMultipartUploadParts\",\n\"s3:PutObject\"\n],\n";
|
"s3:ListBucketMultipartUploads"
|
||||||
case READ_WRITE -> "[\n\"s3:AbortMultipartUpload\",\n\"s3:DeleteObject\",\n\"s3:GetObject\",\n\"s3:ListMultipartUploadParts\",\n\"s3:PutObject\"\n],\n";
|
],
|
||||||
default -> "\"s3:GetObject\",\n";
|
"Resource": "arn:aws:s3:::bucketName"
|
||||||
});
|
},
|
||||||
builder.append("\"Effect\": \"Allow\",\n\"Principal\": \"*\",\n\"Resource\": \"arn:aws:s3:::");
|
{
|
||||||
builder.append(bucketName);
|
"Effect": "Allow",
|
||||||
builder.append("/*\"\n}\n],\n\"Version\": \"2012-10-17\"\n}\n");
|
"Principal": "*",
|
||||||
return builder.toString();
|
"Action": [
|
||||||
|
"s3:AbortMultipartUpload",
|
||||||
|
"s3:DeleteObject",
|
||||||
|
"s3:GetObject",
|
||||||
|
"s3:ListMultipartUploadParts",
|
||||||
|
"s3:PutObject"
|
||||||
|
],
|
||||||
|
"Resource": "arn:aws:s3:::bucketName/*"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
case READ -> """
|
||||||
|
{
|
||||||
|
"Version": "2012-10-17",
|
||||||
|
"Statement": [
|
||||||
|
{
|
||||||
|
"Effect": "Allow",
|
||||||
|
"Principal": "*",
|
||||||
|
"Action": ["s3:GetBucketLocation"],
|
||||||
|
"Resource": "arn:aws:s3:::bucketName"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Effect": "Deny",
|
||||||
|
"Principal": "*",
|
||||||
|
"Action": ["s3:ListBucket"],
|
||||||
|
"Resource": "arn:aws:s3:::bucketName"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Effect": "Allow",
|
||||||
|
"Principal": "*",
|
||||||
|
"Action": "s3:GetObject",
|
||||||
|
"Resource": "arn:aws:s3:::bucketName/*"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
};
|
||||||
|
return policy.replaceAll("bucketName", bucketName);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -21,4 +21,9 @@ public class UploadResult {
|
|||||||
* 文件名
|
* 文件名
|
||||||
*/
|
*/
|
||||||
private String filename;
|
private String filename;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 已上传对象的实体标记(用来校验文件)
|
||||||
|
*/
|
||||||
|
private String eTag;
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
package com.ruoyi.common.oss.enumd;
|
package com.ruoyi.common.oss.enumd;
|
||||||
|
|
||||||
import com.amazonaws.services.s3.model.CannedAccessControlList;
|
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
import software.amazon.awssdk.services.s3.model.BucketCannedACL;
|
||||||
|
import software.amazon.awssdk.services.s3.model.ObjectCannedACL;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 桶访问策略配置
|
* 桶访问策略配置
|
||||||
@ -16,27 +17,32 @@ public enum AccessPolicyType {
|
|||||||
/**
|
/**
|
||||||
* private
|
* private
|
||||||
*/
|
*/
|
||||||
PRIVATE("0", CannedAccessControlList.Private, PolicyType.WRITE),
|
PRIVATE("0", BucketCannedACL.PRIVATE, ObjectCannedACL.PRIVATE, PolicyType.WRITE),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* public
|
* public
|
||||||
*/
|
*/
|
||||||
PUBLIC("1", CannedAccessControlList.PublicRead, PolicyType.READ),
|
PUBLIC("1", BucketCannedACL.PUBLIC_READ_WRITE, ObjectCannedACL.PUBLIC_READ_WRITE, PolicyType.READ_WRITE),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* custom
|
* custom
|
||||||
*/
|
*/
|
||||||
CUSTOM("2",CannedAccessControlList.PublicRead, PolicyType.READ);
|
CUSTOM("2", BucketCannedACL.PUBLIC_READ, ObjectCannedACL.PUBLIC_READ, PolicyType.READ);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 桶 权限类型
|
* 桶 权限类型(数据库值)
|
||||||
*/
|
*/
|
||||||
private final String type;
|
private final String type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 桶 权限类型
|
||||||
|
*/
|
||||||
|
private final BucketCannedACL bucketCannedACL;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 文件对象 权限类型
|
* 文件对象 权限类型
|
||||||
*/
|
*/
|
||||||
private final CannedAccessControlList acl;
|
private final ObjectCannedACL objectCannedACL;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 桶策略类型
|
* 桶策略类型
|
||||||
|
@ -13,6 +13,7 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 文件上传Factory
|
* 文件上传Factory
|
||||||
@ -23,6 +24,7 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||||||
public class OssFactory {
|
public class OssFactory {
|
||||||
|
|
||||||
private static final Map<String, OssClient> CLIENT_CACHE = new ConcurrentHashMap<>();
|
private static final Map<String, OssClient> CLIENT_CACHE = new ConcurrentHashMap<>();
|
||||||
|
private static final ReentrantLock LOCK = new ReentrantLock();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取默认实例
|
* 获取默认实例
|
||||||
@ -39,7 +41,7 @@ public class OssFactory {
|
|||||||
/**
|
/**
|
||||||
* 根据类型获取实例
|
* 根据类型获取实例
|
||||||
*/
|
*/
|
||||||
public static synchronized OssClient instance(String configKey) {
|
public static OssClient instance(String configKey) {
|
||||||
String json = CacheUtils.get(CacheNames.SYS_OSS_CONFIG, configKey);
|
String json = CacheUtils.get(CacheNames.SYS_OSS_CONFIG, configKey);
|
||||||
if (json == null) {
|
if (json == null) {
|
||||||
throw new OssException("系统异常, '" + configKey + "'配置信息不存在!");
|
throw new OssException("系统异常, '" + configKey + "'配置信息不存在!");
|
||||||
@ -48,16 +50,19 @@ public class OssFactory {
|
|||||||
// 使用租户标识避免多个租户相同key实例覆盖
|
// 使用租户标识避免多个租户相同key实例覆盖
|
||||||
String key = properties.getTenantId() + ":" + configKey;
|
String key = properties.getTenantId() + ":" + configKey;
|
||||||
OssClient client = CLIENT_CACHE.get(key);
|
OssClient client = CLIENT_CACHE.get(key);
|
||||||
if (client == null) {
|
// 客户端不存在或配置不相同则重新构建
|
||||||
CLIENT_CACHE.put(key, new OssClient(configKey, properties));
|
if (client == null || !client.checkPropertiesSame(properties)) {
|
||||||
log.info("创建OSS实例 key => {}", configKey);
|
LOCK.lock();
|
||||||
return CLIENT_CACHE.get(key);
|
try {
|
||||||
}
|
client = CLIENT_CACHE.get(key);
|
||||||
// 配置不相同则重新构建
|
if (client == null || !client.checkPropertiesSame(properties)) {
|
||||||
if (!client.checkPropertiesSame(properties)) {
|
CLIENT_CACHE.put(key, new OssClient(configKey, properties));
|
||||||
CLIENT_CACHE.put(key, new OssClient(configKey, properties));
|
log.info("创建OSS实例 key => {}", configKey);
|
||||||
log.info("重载OSS实例 key => {}", configKey);
|
return CLIENT_CACHE.get(key);
|
||||||
return CLIENT_CACHE.get(key);
|
}
|
||||||
|
} finally {
|
||||||
|
LOCK.unlock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return client;
|
return client;
|
||||||
}
|
}
|
||||||
|
@ -1,29 +1,29 @@
|
|||||||
package com.ruoyi.common.ratelimiter.aspectj;
|
package com.ruoyi.common.ratelimiter.aspectj;
|
||||||
|
|
||||||
import cn.hutool.core.util.ArrayUtil;
|
|
||||||
import com.ruoyi.common.core.constant.GlobalConstants;
|
|
||||||
import com.ruoyi.common.core.exception.ServiceException;
|
|
||||||
import com.ruoyi.common.core.utils.MessageUtils;
|
|
||||||
import com.ruoyi.common.core.utils.ServletUtils;
|
|
||||||
import com.ruoyi.common.core.utils.StringUtils;
|
|
||||||
import com.ruoyi.common.ratelimiter.annotation.RateLimiter;
|
|
||||||
import com.ruoyi.common.ratelimiter.enums.LimitType;
|
|
||||||
import com.ruoyi.common.redis.utils.RedisUtils;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.aspectj.lang.JoinPoint;
|
import org.aspectj.lang.JoinPoint;
|
||||||
import org.aspectj.lang.annotation.Aspect;
|
import org.aspectj.lang.annotation.Aspect;
|
||||||
import org.aspectj.lang.annotation.Before;
|
import org.aspectj.lang.annotation.Before;
|
||||||
import org.aspectj.lang.reflect.MethodSignature;
|
import org.aspectj.lang.reflect.MethodSignature;
|
||||||
|
import com.ruoyi.common.core.constant.GlobalConstants;
|
||||||
|
import com.ruoyi.common.core.exception.ServiceException;
|
||||||
|
import com.ruoyi.common.core.utils.MessageUtils;
|
||||||
|
import com.ruoyi.common.core.utils.ServletUtils;
|
||||||
|
import com.ruoyi.common.core.utils.SpringUtils;
|
||||||
|
import com.ruoyi.common.core.utils.StringUtils;
|
||||||
|
import com.ruoyi.common.ratelimiter.annotation.RateLimiter;
|
||||||
|
import com.ruoyi.common.ratelimiter.enums.LimitType;
|
||||||
|
import com.ruoyi.common.redis.utils.RedisUtils;
|
||||||
import org.redisson.api.RateType;
|
import org.redisson.api.RateType;
|
||||||
|
import org.springframework.context.expression.BeanFactoryResolver;
|
||||||
|
import org.springframework.context.expression.MethodBasedEvaluationContext;
|
||||||
import org.springframework.core.DefaultParameterNameDiscoverer;
|
import org.springframework.core.DefaultParameterNameDiscoverer;
|
||||||
import org.springframework.core.ParameterNameDiscoverer;
|
import org.springframework.core.ParameterNameDiscoverer;
|
||||||
import org.springframework.expression.EvaluationContext;
|
|
||||||
import org.springframework.expression.Expression;
|
import org.springframework.expression.Expression;
|
||||||
import org.springframework.expression.ExpressionParser;
|
import org.springframework.expression.ExpressionParser;
|
||||||
import org.springframework.expression.ParserContext;
|
import org.springframework.expression.ParserContext;
|
||||||
import org.springframework.expression.common.TemplateParserContext;
|
import org.springframework.expression.common.TemplateParserContext;
|
||||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
@ -44,21 +44,18 @@ public class RateLimiterAspect {
|
|||||||
* 定义spel解析模版
|
* 定义spel解析模版
|
||||||
*/
|
*/
|
||||||
private final ParserContext parserContext = new TemplateParserContext();
|
private final ParserContext parserContext = new TemplateParserContext();
|
||||||
/**
|
|
||||||
* 定义spel上下文对象进行解析
|
|
||||||
*/
|
|
||||||
private final EvaluationContext context = new StandardEvaluationContext();
|
|
||||||
/**
|
/**
|
||||||
* 方法参数解析器
|
* 方法参数解析器
|
||||||
*/
|
*/
|
||||||
private final ParameterNameDiscoverer pnd = new DefaultParameterNameDiscoverer();
|
private final ParameterNameDiscoverer pnd = new DefaultParameterNameDiscoverer();
|
||||||
|
|
||||||
|
|
||||||
@Before("@annotation(rateLimiter)")
|
@Before("@annotation(rateLimiter)")
|
||||||
public void doBefore(JoinPoint point, RateLimiter rateLimiter) throws Throwable {
|
public void doBefore(JoinPoint point, RateLimiter rateLimiter) {
|
||||||
int time = rateLimiter.time();
|
int time = rateLimiter.time();
|
||||||
int count = rateLimiter.count();
|
int count = rateLimiter.count();
|
||||||
String combineKey = getCombineKey(rateLimiter, point);
|
|
||||||
try {
|
try {
|
||||||
|
String combineKey = getCombineKey(rateLimiter, point);
|
||||||
RateType rateType = RateType.OVERALL;
|
RateType rateType = RateType.OVERALL;
|
||||||
if (rateLimiter.limitType() == LimitType.CLUSTER) {
|
if (rateLimiter.limitType() == LimitType.CLUSTER) {
|
||||||
rateType = RateType.PER_CLIENT;
|
rateType = RateType.PER_CLIENT;
|
||||||
@ -76,42 +73,29 @@ public class RateLimiterAspect {
|
|||||||
if (e instanceof ServiceException) {
|
if (e instanceof ServiceException) {
|
||||||
throw e;
|
throw e;
|
||||||
} else {
|
} else {
|
||||||
throw new RuntimeException("服务器限流异常,请稍候再试");
|
throw new RuntimeException("服务器限流异常,请稍候再试", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getCombineKey(RateLimiter rateLimiter, JoinPoint point) {
|
private String getCombineKey(RateLimiter rateLimiter, JoinPoint point) {
|
||||||
String key = rateLimiter.key();
|
String key = rateLimiter.key();
|
||||||
// 获取方法(通过方法签名来获取)
|
if (StringUtils.isNotBlank(key)) {
|
||||||
MethodSignature signature = (MethodSignature) point.getSignature();
|
MethodSignature signature = (MethodSignature) point.getSignature();
|
||||||
Method method = signature.getMethod();
|
Method targetMethod = signature.getMethod();
|
||||||
Class<?> targetClass = method.getDeclaringClass();
|
|
||||||
// 判断是否是spel格式
|
|
||||||
if (StringUtils.containsAny(key, "#")) {
|
|
||||||
// 获取参数值
|
|
||||||
Object[] args = point.getArgs();
|
Object[] args = point.getArgs();
|
||||||
// 获取方法上参数的名称
|
//noinspection DataFlowIssue
|
||||||
String[] parameterNames = pnd.getParameterNames(method);
|
MethodBasedEvaluationContext context =
|
||||||
if (ArrayUtil.isEmpty(parameterNames)) {
|
new MethodBasedEvaluationContext(null, targetMethod, args, pnd);
|
||||||
throw new ServiceException("限流key解析异常!请联系管理员!");
|
context.setBeanResolver(new BeanFactoryResolver(SpringUtils.getBeanFactory()));
|
||||||
}
|
Expression expression;
|
||||||
for (int i = 0; i < parameterNames.length; i++) {
|
if (StringUtils.startsWith(key, parserContext.getExpressionPrefix())
|
||||||
context.setVariable(parameterNames[i], args[i]);
|
&& StringUtils.endsWith(key, parserContext.getExpressionSuffix())) {
|
||||||
}
|
expression = parser.parseExpression(key, parserContext);
|
||||||
// 解析返回给key
|
} else {
|
||||||
try {
|
expression = parser.parseExpression(key);
|
||||||
Expression expression;
|
|
||||||
if (StringUtils.startsWith(key, parserContext.getExpressionPrefix())
|
|
||||||
&& StringUtils.endsWith(key, parserContext.getExpressionSuffix())) {
|
|
||||||
expression = parser.parseExpression(key, parserContext);
|
|
||||||
} else {
|
|
||||||
expression = parser.parseExpression(key);
|
|
||||||
}
|
|
||||||
key = expression.getValue(context, String.class) + ":";
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new ServiceException("限流key解析异常!请联系管理员!");
|
|
||||||
}
|
}
|
||||||
|
key = expression.getValue(context, String.class);
|
||||||
}
|
}
|
||||||
StringBuilder stringBuffer = new StringBuilder(GlobalConstants.RATE_LIMIT_KEY);
|
StringBuilder stringBuffer = new StringBuilder(GlobalConstants.RATE_LIMIT_KEY);
|
||||||
stringBuffer.append(ServletUtils.getRequest().getRequestURI()).append(":");
|
stringBuffer.append(ServletUtils.getRequest().getRequestURI()).append(":");
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"com.ruoyi.common.ratelimiter.annotation.RateLimiter@key": {
|
||||||
|
"method": {
|
||||||
|
"parameters": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -46,6 +46,12 @@
|
|||||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- caffeine缓存 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.github.ben-manes.caffeine</groupId>
|
||||||
|
<artifactId>caffeine</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
|
@ -0,0 +1,45 @@
|
|||||||
|
package com.ruoyi.common.redis.config;
|
||||||
|
|
||||||
|
import com.github.benmanes.caffeine.cache.Cache;
|
||||||
|
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||||
|
import com.ruoyi.common.redis.manager.FlexSpringCacheManager;
|
||||||
|
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||||
|
import org.springframework.cache.CacheManager;
|
||||||
|
import org.springframework.cache.annotation.EnableCaching;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 缓存配置
|
||||||
|
*
|
||||||
|
* @author Lion Li
|
||||||
|
*/
|
||||||
|
@AutoConfiguration
|
||||||
|
@EnableCaching
|
||||||
|
public class CacheConfig {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* caffeine 本地缓存处理器
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public Cache<Object, Object> caffeine() {
|
||||||
|
return Caffeine.newBuilder()
|
||||||
|
// 设置最后一次写入或访问后经过固定时间过期
|
||||||
|
.expireAfterWrite(30, TimeUnit.SECONDS)
|
||||||
|
// 初始的缓存空间大小
|
||||||
|
.initialCapacity(100)
|
||||||
|
// 缓存的最大条数
|
||||||
|
.maximumSize(1000)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定义缓存管理器 整合spring-cache
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public CacheManager cacheManager() {
|
||||||
|
return new FlexSpringCacheManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -5,8 +5,9 @@ import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
|||||||
import com.fasterxml.jackson.annotation.PropertyAccessor;
|
import com.fasterxml.jackson.annotation.PropertyAccessor;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
|
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
|
||||||
|
import com.ruoyi.common.core.utils.SpringUtils;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import com.ruoyi.common.redis.handler.KeyPrefixHandler;
|
import com.ruoyi.common.redis.handler.KeyPrefixHandler;
|
||||||
import com.ruoyi.common.redis.manager.FlexSpringCacheManager;
|
|
||||||
import com.ruoyi.common.redis.config.properties.RedissonProperties;
|
import com.ruoyi.common.redis.config.properties.RedissonProperties;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import org.redisson.client.codec.StringCodec;
|
import org.redisson.client.codec.StringCodec;
|
||||||
@ -16,22 +17,18 @@ import org.redisson.spring.starter.RedissonAutoConfigurationCustomizer;
|
|||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
import org.springframework.cache.CacheManager;
|
|
||||||
import org.springframework.cache.annotation.EnableCaching;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.slf4j.Logger;
|
import org.springframework.core.task.VirtualThreadTaskExecutor;
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* redis配置
|
* redis配置
|
||||||
*
|
*
|
||||||
* @author Lion Li
|
* @author Lion Li
|
||||||
*/
|
*/
|
||||||
|
@Slf4j
|
||||||
@AutoConfiguration
|
@AutoConfiguration
|
||||||
@EnableCaching
|
|
||||||
@EnableConfigurationProperties(RedissonProperties.class)
|
@EnableConfigurationProperties(RedissonProperties.class)
|
||||||
public class RedisConfig {
|
public class RedisConfig {
|
||||||
private static final Logger log = LoggerFactory.getLogger(RedisConfig.class);
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private RedissonProperties redissonProperties;
|
private RedissonProperties redissonProperties;
|
||||||
@Resource
|
@Resource
|
||||||
@ -52,6 +49,9 @@ public class RedisConfig {
|
|||||||
// 缓存 Lua 脚本 减少网络传输(redisson 大部分的功能都是基于 Lua 脚本实现)
|
// 缓存 Lua 脚本 减少网络传输(redisson 大部分的功能都是基于 Lua 脚本实现)
|
||||||
.setUseScriptCache(true)
|
.setUseScriptCache(true)
|
||||||
.setCodec(codec);
|
.setCodec(codec);
|
||||||
|
if (SpringUtils.isVirtual()) {
|
||||||
|
config.setNettyExecutor(new VirtualThreadTaskExecutor("redisson-"));
|
||||||
|
}
|
||||||
RedissonProperties.SingleServerConfig singleServerConfig = redissonProperties.getSingleServerConfig();
|
RedissonProperties.SingleServerConfig singleServerConfig = redissonProperties.getSingleServerConfig();
|
||||||
if (ObjectUtil.isNotNull(singleServerConfig)) {
|
if (ObjectUtil.isNotNull(singleServerConfig)) {
|
||||||
// 使用单机模式
|
// 使用单机模式
|
||||||
@ -86,14 +86,6 @@ public class RedisConfig {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 自定义缓存管理器 整合spring-cache
|
|
||||||
*/
|
|
||||||
@Bean
|
|
||||||
public CacheManager cacheManager() {
|
|
||||||
return new FlexSpringCacheManager();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* redis集群配置 yml
|
* redis集群配置 yml
|
||||||
*
|
*
|
||||||
|
@ -0,0 +1,88 @@
|
|||||||
|
package com.ruoyi.common.redis.manager;
|
||||||
|
|
||||||
|
import com.ruoyi.common.core.utils.SpringUtils;
|
||||||
|
import org.springframework.cache.Cache;
|
||||||
|
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cache 装饰器模式(用于扩展 Caffeine 一级缓存)
|
||||||
|
*
|
||||||
|
* @author LionLi
|
||||||
|
*/
|
||||||
|
public class CaffeineCacheDecorator implements Cache {
|
||||||
|
|
||||||
|
private static final com.github.benmanes.caffeine.cache.Cache<Object, Object>
|
||||||
|
CAFFEINE = SpringUtils.getBean("caffeine");
|
||||||
|
|
||||||
|
private final Cache cache;
|
||||||
|
|
||||||
|
public CaffeineCacheDecorator(Cache cache) {
|
||||||
|
this.cache = cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return cache.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getNativeCache() {
|
||||||
|
return cache.getNativeCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUniqueKey(Object key) {
|
||||||
|
return cache.getName() + ":" + key;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ValueWrapper get(Object key) {
|
||||||
|
Object o = CAFFEINE.get(getUniqueKey(key), k -> cache.get(key));
|
||||||
|
return (ValueWrapper) o;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public <T> T get(Object key, Class<T> type) {
|
||||||
|
Object o = CAFFEINE.get(getUniqueKey(key), k -> cache.get(key, type));
|
||||||
|
return (T) o;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void put(Object key, Object value) {
|
||||||
|
cache.put(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValueWrapper putIfAbsent(Object key, Object value) {
|
||||||
|
return cache.putIfAbsent(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void evict(Object key) {
|
||||||
|
evictIfPresent(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean evictIfPresent(Object key) {
|
||||||
|
boolean b = cache.evictIfPresent(key);
|
||||||
|
if (b) {
|
||||||
|
CAFFEINE.invalidate(getUniqueKey(key));
|
||||||
|
}
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clear() {
|
||||||
|
cache.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean invalidate() {
|
||||||
|
return cache.invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@Override
|
||||||
|
public <T> T get(Object key, Callable<T> valueLoader) {
|
||||||
|
Object o = CAFFEINE.get(getUniqueKey(key), k -> cache.get(key, valueLoader));
|
||||||
|
return (T) o;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -33,7 +33,7 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||||||
import java.util.concurrent.ConcurrentMap;
|
import java.util.concurrent.ConcurrentMap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link org.springframework.cache.CacheManager} implementation
|
* A {@link CacheManager} implementation
|
||||||
* backed by Redisson instance.
|
* backed by Redisson instance.
|
||||||
* <p>
|
* <p>
|
||||||
* 修改 RedissonSpringCacheManager 源码
|
* 修改 RedissonSpringCacheManager 源码
|
||||||
@ -156,7 +156,7 @@ public class FlexSpringCacheManager implements CacheManager {
|
|||||||
private Cache createMap(String name, CacheConfig config) {
|
private Cache createMap(String name, CacheConfig config) {
|
||||||
RMap<Object, Object> map = RedisUtils.getClient().getMap(name);
|
RMap<Object, Object> map = RedisUtils.getClient().getMap(name);
|
||||||
|
|
||||||
Cache cache = new RedissonCache(map, allowNullValues);
|
Cache cache = new CaffeineCacheDecorator(new RedissonCache(map, allowNullValues));
|
||||||
if (transactionAware) {
|
if (transactionAware) {
|
||||||
cache = new TransactionAwareCacheDecorator(cache);
|
cache = new TransactionAwareCacheDecorator(cache);
|
||||||
}
|
}
|
||||||
@ -170,7 +170,7 @@ public class FlexSpringCacheManager implements CacheManager {
|
|||||||
private Cache createMapCache(String name, CacheConfig config) {
|
private Cache createMapCache(String name, CacheConfig config) {
|
||||||
RMapCache<Object, Object> map = RedisUtils.getClient().getMapCache(name);
|
RMapCache<Object, Object> map = RedisUtils.getClient().getMapCache(name);
|
||||||
|
|
||||||
Cache cache = new RedissonCache(map, config, allowNullValues);
|
Cache cache = new CaffeineCacheDecorator(new RedissonCache(map, config, allowNullValues));
|
||||||
if (transactionAware) {
|
if (transactionAware) {
|
||||||
cache = new TransactionAwareCacheDecorator(cache);
|
cache = new TransactionAwareCacheDecorator(cache);
|
||||||
}
|
}
|
||||||
|
@ -1 +1,2 @@
|
|||||||
com.ruoyi.common.redis.config.RedisConfig
|
com.ruoyi.common.redis.config.RedisConfig
|
||||||
|
com.ruoyi.common.redis.config.CacheConfig
|
||||||
|
@ -41,6 +41,12 @@
|
|||||||
<artifactId>sa-token-jwt</artifactId>
|
<artifactId>sa-token-jwt</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- caffeine缓存 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.github.ben-manes.caffeine</groupId>
|
||||||
|
<artifactId>caffeine</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
|
@ -2,12 +2,15 @@ package com.ruoyi.common.security.core.dao;
|
|||||||
|
|
||||||
import cn.dev33.satoken.dao.SaTokenDao;
|
import cn.dev33.satoken.dao.SaTokenDao;
|
||||||
import cn.dev33.satoken.util.SaFoxUtil;
|
import cn.dev33.satoken.util.SaFoxUtil;
|
||||||
|
import com.github.benmanes.caffeine.cache.Cache;
|
||||||
|
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||||
import com.ruoyi.common.redis.utils.RedisUtils;
|
import com.ruoyi.common.redis.utils.RedisUtils;
|
||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sa-Token持久层接口(使用框架自带RedisUtils实现 协议统一)
|
* Sa-Token持久层接口(使用框架自带RedisUtils实现 协议统一)
|
||||||
@ -16,12 +19,22 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
public class FlexSaTokenDao implements SaTokenDao {
|
public class FlexSaTokenDao implements SaTokenDao {
|
||||||
|
|
||||||
|
private static final Cache<String, Object> CAFFEINE = Caffeine.newBuilder()
|
||||||
|
// 设置最后一次写入或访问后经过固定时间过期
|
||||||
|
.expireAfterWrite(5, TimeUnit.SECONDS)
|
||||||
|
// 初始的缓存空间大小
|
||||||
|
.initialCapacity(100)
|
||||||
|
// 缓存的最大条数
|
||||||
|
.maximumSize(1000)
|
||||||
|
.build();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取Value,如无返空
|
* 获取Value,如无返空
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public String get(String key) {
|
public String get(String key) {
|
||||||
return RedisUtils.getCacheObject(key);
|
Object o = CAFFEINE.get(key, k -> RedisUtils.getCacheObject(key));
|
||||||
|
return (String) o;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -38,6 +51,7 @@ public class FlexSaTokenDao implements SaTokenDao {
|
|||||||
} else {
|
} else {
|
||||||
RedisUtils.setCacheObject(key, value, Duration.ofSeconds(timeout));
|
RedisUtils.setCacheObject(key, value, Duration.ofSeconds(timeout));
|
||||||
}
|
}
|
||||||
|
CAFFEINE.put(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -47,6 +61,7 @@ public class FlexSaTokenDao implements SaTokenDao {
|
|||||||
public void update(String key, String value) {
|
public void update(String key, String value) {
|
||||||
if (RedisUtils.hasKey(key)) {
|
if (RedisUtils.hasKey(key)) {
|
||||||
RedisUtils.setCacheObject(key, value, true);
|
RedisUtils.setCacheObject(key, value, true);
|
||||||
|
CAFFEINE.put(key, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,7 +96,8 @@ public class FlexSaTokenDao implements SaTokenDao {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Object getObject(String key) {
|
public Object getObject(String key) {
|
||||||
return RedisUtils.getCacheObject(key);
|
Object o = CAFFEINE.get(key, k -> RedisUtils.getCacheObject(key));
|
||||||
|
return o;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -98,6 +114,7 @@ public class FlexSaTokenDao implements SaTokenDao {
|
|||||||
} else {
|
} else {
|
||||||
RedisUtils.setCacheObject(key, object, Duration.ofSeconds(timeout));
|
RedisUtils.setCacheObject(key, object, Duration.ofSeconds(timeout));
|
||||||
}
|
}
|
||||||
|
CAFFEINE.put(key, object);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -107,6 +124,7 @@ public class FlexSaTokenDao implements SaTokenDao {
|
|||||||
public void updateObject(String key, Object object) {
|
public void updateObject(String key, Object object) {
|
||||||
if (RedisUtils.hasKey(key)) {
|
if (RedisUtils.hasKey(key)) {
|
||||||
RedisUtils.setCacheObject(key, object, true);
|
RedisUtils.setCacheObject(key, object, true);
|
||||||
|
CAFFEINE.put(key, object);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,10 +157,14 @@ public class FlexSaTokenDao implements SaTokenDao {
|
|||||||
/**
|
/**
|
||||||
* 搜索数据
|
* 搜索数据
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
@Override
|
@Override
|
||||||
public List<String> searchData(String prefix, String keyword, int start, int size, boolean sortType) {
|
public List<String> searchData(String prefix, String keyword, int start, int size, boolean sortType) {
|
||||||
Collection<String> keys = RedisUtils.keys(prefix + "*" + keyword + "*");
|
String keyStr = prefix + "*" + keyword + "*";
|
||||||
List<String> list = new ArrayList<>(keys);
|
return (List<String>) CAFFEINE.get(keyStr, k -> {
|
||||||
return SaFoxUtil.searchList(list, start, size, sortType);
|
Collection<String> keys = RedisUtils.keys(keyStr);
|
||||||
|
List<String> list = new ArrayList<>(keys);
|
||||||
|
return SaFoxUtil.searchList(list, start, size, sortType);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,6 @@ import com.ruoyi.common.core.core.domain.model.LoginUser;
|
|||||||
import com.ruoyi.common.core.enums.UserType;
|
import com.ruoyi.common.core.enums.UserType;
|
||||||
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.Supplier;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 登录鉴权助手
|
* 登录鉴权助手
|
||||||
@ -35,9 +34,10 @@ public class LoginHelper {
|
|||||||
public static final String LOGIN_USER_KEY = "loginUser";
|
public static final String LOGIN_USER_KEY = "loginUser";
|
||||||
public static final String TENANT_KEY = "tenantId";
|
public static final String TENANT_KEY = "tenantId";
|
||||||
public static final String USER_KEY = "userId";
|
public static final String USER_KEY = "userId";
|
||||||
|
public static final String USER_NAME_KEY = "userName";
|
||||||
public static final String DEPT_KEY = "deptId";
|
public static final String DEPT_KEY = "deptId";
|
||||||
|
public static final String DEPT_NAME_KEY = "deptName";
|
||||||
public static final String CLIENT_KEY = "clientid";
|
public static final String CLIENT_KEY = "clientid";
|
||||||
public static final String TENANT_ADMIN_KEY = "isTenantAdmin";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 登录系统 基于 设备类型
|
* 登录系统 基于 设备类型
|
||||||
@ -57,7 +57,10 @@ public class LoginHelper {
|
|||||||
StpUtil.login(loginUser.getLoginId(),
|
StpUtil.login(loginUser.getLoginId(),
|
||||||
model.setExtra(TENANT_KEY, loginUser.getTenantId())
|
model.setExtra(TENANT_KEY, loginUser.getTenantId())
|
||||||
.setExtra(USER_KEY, loginUser.getUserId())
|
.setExtra(USER_KEY, loginUser.getUserId())
|
||||||
.setExtra(DEPT_KEY, loginUser.getDeptId()));
|
.setExtra(USER_NAME_KEY, loginUser.getUsername())
|
||||||
|
.setExtra(DEPT_KEY, loginUser.getDeptId())
|
||||||
|
.setExtra(DEPT_NAME_KEY, loginUser.getDeptName())
|
||||||
|
);
|
||||||
StpUtil.getTokenSession().set(LOGIN_USER_KEY, loginUser);
|
StpUtil.getTokenSession().set(LOGIN_USER_KEY, loginUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,13 +68,11 @@ public class LoginHelper {
|
|||||||
* 获取用户(多级缓存)
|
* 获取用户(多级缓存)
|
||||||
*/
|
*/
|
||||||
public static LoginUser getLoginUser() {
|
public static LoginUser getLoginUser() {
|
||||||
return (LoginUser) getStorageIfAbsentSet(LOGIN_USER_KEY, () -> {
|
SaSession session = StpUtil.getTokenSession();
|
||||||
SaSession session = StpUtil.getTokenSession();
|
if (ObjectUtil.isNull(session)) {
|
||||||
if (ObjectUtil.isNull(session)) {
|
return null;
|
||||||
return null;
|
}
|
||||||
}
|
return (LoginUser) session.get(LOGIN_USER_KEY);
|
||||||
return session.get(LOGIN_USER_KEY);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -89,7 +90,7 @@ public class LoginHelper {
|
|||||||
* 获取用户id
|
* 获取用户id
|
||||||
*/
|
*/
|
||||||
public static Long getUserId() {
|
public static Long getUserId() {
|
||||||
return Convert.toLong(getExtra(USER_KEY));
|
return Convert.toLong(getExtra(USER_KEY));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -106,8 +107,18 @@ public class LoginHelper {
|
|||||||
return Convert.toLong(getExtra(DEPT_KEY));
|
return Convert.toLong(getExtra(DEPT_KEY));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前 Token 的扩展信息
|
||||||
|
*
|
||||||
|
* @param key 键值
|
||||||
|
* @return 对应的扩展数据
|
||||||
|
*/
|
||||||
private static Object getExtra(String key) {
|
private static Object getExtra(String key) {
|
||||||
return getStorageIfAbsentSet(key, () -> StpUtil.getExtra(key));
|
try {
|
||||||
|
return StpUtil.getExtra(key);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -135,12 +146,17 @@ public class LoginHelper {
|
|||||||
return UserConstants.SUPER_ADMIN_ID.equals(userId);
|
return UserConstants.SUPER_ADMIN_ID.equals(userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否为超级管理员
|
||||||
|
*
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
public static boolean isSuperAdmin() {
|
public static boolean isSuperAdmin() {
|
||||||
return isSuperAdmin(getUserId());
|
return isSuperAdmin(getUserId());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 是否为超级管理员
|
* 是否为租户管理员
|
||||||
*
|
*
|
||||||
* @param rolePermission 角色权限标识组
|
* @param rolePermission 角色权限标识组
|
||||||
* @return 结果
|
* @return 结果
|
||||||
@ -150,27 +166,16 @@ public class LoginHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isTenantAdmin() {
|
public static boolean isTenantAdmin() {
|
||||||
Object value = getStorageIfAbsentSet(TENANT_ADMIN_KEY, () -> {
|
return Convert.toBool(isTenantAdmin(getLoginUser().getRolePermission()));
|
||||||
return isTenantAdmin(getLoginUser().getRolePermission());
|
|
||||||
});
|
|
||||||
return Convert.toBool(value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查当前用户是否已登录
|
||||||
|
*
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
public static boolean isLogin() {
|
public static boolean isLogin() {
|
||||||
return getLoginUser() != null;
|
return getLoginUser() != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Object getStorageIfAbsentSet(String key, Supplier<Object> handle) {
|
|
||||||
try {
|
|
||||||
Object obj = SaHolder.getStorage().get(key);
|
|
||||||
if (ObjectUtil.isNull(obj)) {
|
|
||||||
obj = handle.get();
|
|
||||||
SaHolder.getStorage().set(key, obj);
|
|
||||||
}
|
|
||||||
return obj;
|
|
||||||
} catch (Exception e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.ruoyi</groupId>
|
<groupId>com.ruoyi</groupId>
|
||||||
<artifactId>ruoyi-common-orm</artifactId>
|
<artifactId>ruoyi-common-orm</artifactId>
|
||||||
|
<optional>true</optional>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
@ -26,11 +27,6 @@
|
|||||||
<artifactId>ruoyi-common-redis</artifactId>
|
<artifactId>ruoyi-common-redis</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.alibaba</groupId>
|
|
||||||
<artifactId>transmittable-thread-local</artifactId>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
|
@ -5,12 +5,14 @@ import com.mybatisflex.core.tenant.TenantFactory;
|
|||||||
import com.ruoyi.common.security.utils.LoginHelper;
|
import com.ruoyi.common.security.utils.LoginHelper;
|
||||||
import com.ruoyi.common.tenant.helper.TenantHelper;
|
import com.ruoyi.common.tenant.helper.TenantHelper;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 自定义租户工厂
|
* 自定义租户工厂
|
||||||
*
|
*
|
||||||
* @author 数据小王子
|
* @author 数据小王子
|
||||||
*/
|
*/
|
||||||
|
@Slf4j
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public class MyTenantFactory implements TenantFactory {
|
public class MyTenantFactory implements TenantFactory {
|
||||||
|
|
||||||
|
@ -73,10 +73,6 @@
|
|||||||
<artifactId>hutool-crypto</artifactId>
|
<artifactId>hutool-crypto</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.alibaba</groupId>
|
|
||||||
<artifactId>transmittable-thread-local</artifactId>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
|
@ -127,7 +127,7 @@ public class RepeatSubmitAspect {
|
|||||||
public boolean isFilterObject(final Object o) {
|
public boolean isFilterObject(final Object o) {
|
||||||
Class<?> clazz = o.getClass();
|
Class<?> clazz = o.getClass();
|
||||||
if (clazz.isArray()) {
|
if (clazz.isArray()) {
|
||||||
return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
|
return MultipartFile.class.isAssignableFrom(clazz.getComponentType());
|
||||||
} else if (Collection.class.isAssignableFrom(clazz)) {
|
} else if (Collection.class.isAssignableFrom(clazz)) {
|
||||||
Collection collection = (Collection) o;
|
Collection collection = (Collection) o;
|
||||||
for (Object value : collection) {
|
for (Object value : collection) {
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
<modules>
|
<modules>
|
||||||
<module>ruoyi-monitor</module>
|
<module>ruoyi-monitor</module>
|
||||||
<module>ruoyi-powerjob-server</module>
|
<module>ruoyi-powerjob-server</module>
|
||||||
|
<module>ruoyi-easyretry-server</module>
|
||||||
</modules>
|
</modules>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
|
18
ruoyi-extra/ruoyi-easyretry-server/Dockerfile
Normal file
18
ruoyi-extra/ruoyi-easyretry-server/Dockerfile
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# 使用官方的 Java 运行时作为父镜像
|
||||||
|
FROM registry.cn-qingdao.aliyuncs.com/yuzl1/jdk:21
|
||||||
|
|
||||||
|
MAINTAINER Lion Li
|
||||||
|
|
||||||
|
RUN mkdir -p /ruoyi/easyretry/logs
|
||||||
|
|
||||||
|
WORKDIR /ruoyi/easyretry
|
||||||
|
|
||||||
|
ENV LANG=C.UTF-8 LC_ALL=C.UTF-8 JAVA_OPTS="-Xms512m -Xmx1024m"
|
||||||
|
|
||||||
|
EXPOSE 8800
|
||||||
|
EXPOSE 1788
|
||||||
|
|
||||||
|
ADD ./target/ruoyi-easyretry-server.jar ./app.jar
|
||||||
|
|
||||||
|
ENTRYPOINT java -Djava.security.egd=file:/dev/./urandom -jar app.jar \
|
||||||
|
-XX:+HeapDumpOnOutOfMemoryError -Xlog:gc*,:time,tags,level -XX:+UseZGC ${JAVA_OPTS}
|
46
ruoyi-extra/ruoyi-easyretry-server/pom.xml
Normal file
46
ruoyi-extra/ruoyi-easyretry-server/pom.xml
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
<?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.ruoyi</groupId>
|
||||||
|
<artifactId>ruoyi-extra</artifactId>
|
||||||
|
<version>${revision}</version>
|
||||||
|
</parent>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
<artifactId>ruoyi-easyretry-server</artifactId>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.aizuda</groupId>
|
||||||
|
<artifactId>easy-retry-server-starter</artifactId>
|
||||||
|
<version>${easyretry.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>de.codecentric</groupId>
|
||||||
|
<artifactId>spring-boot-admin-starter-client</artifactId>
|
||||||
|
<version>${spring-boot-admin.version}</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<finalName>${project.artifactId}</finalName>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
<version>${spring-boot.version}</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<goals>
|
||||||
|
<goal>repackage</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
</project>
|
@ -0,0 +1,18 @@
|
|||||||
|
package com.ruoyi.easyretry;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* EasyRetry Server 启动程序
|
||||||
|
*
|
||||||
|
* @author dhb52
|
||||||
|
*/
|
||||||
|
@SpringBootApplication
|
||||||
|
public class EasyRetryServerApplication {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(com.aizuda.easy.retry.server.EasyRetryServerApplication.class, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,56 @@
|
|||||||
|
spring:
|
||||||
|
datasource:
|
||||||
|
type: com.zaxxer.hikari.HikariDataSource
|
||||||
|
# mysql数据库
|
||||||
|
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||||
|
url: jdbc:mysql://localhost:3306/ruoyi-flex?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
|
||||||
|
username: root
|
||||||
|
password: Root@369
|
||||||
|
# postgresql数据库
|
||||||
|
# driver-class-name: org.postgresql.Driver
|
||||||
|
# url: jdbc:postgresql://localhost:5432/ruoyi-flex?useUnicode=true&characterEncoding=utf8&useSSL=true&autoReconnect=true&reWriteBatchedInserts=true
|
||||||
|
# username: postgres
|
||||||
|
# password: postgres@369
|
||||||
|
hikari:
|
||||||
|
connection-timeout: 30000
|
||||||
|
validation-timeout: 5000
|
||||||
|
minimum-idle: 10
|
||||||
|
maximum-pool-size: 20
|
||||||
|
idle-timeout: 600000
|
||||||
|
max-lifetime: 900000
|
||||||
|
keepaliveTime: 30000
|
||||||
|
|
||||||
|
--- # easy-retry 服务端配置
|
||||||
|
easy-retry:
|
||||||
|
# 拉取重试数据的每批次的大小
|
||||||
|
retry-pull-page-size: 1000
|
||||||
|
# 拉取重试数据的每批次的大小
|
||||||
|
job-pull-page-size: 1000
|
||||||
|
# 服务端 netty 端口
|
||||||
|
netty-port: 1788
|
||||||
|
# 重试和死信表的分区总数
|
||||||
|
total-partition: 2
|
||||||
|
# 一个客户端每秒最多接收的重试数量指令
|
||||||
|
limiter: 1000
|
||||||
|
# 号段模式下步长配置
|
||||||
|
step: 100
|
||||||
|
# 日志保存时间(单位: day)
|
||||||
|
log-storage: 90
|
||||||
|
# 回调配置
|
||||||
|
callback:
|
||||||
|
#回调最大执行次数
|
||||||
|
max-count: 288
|
||||||
|
#间隔时间
|
||||||
|
trigger-interval: 900
|
||||||
|
mode: all
|
||||||
|
retry-max-pull-count: 10
|
||||||
|
|
||||||
|
--- # 监控中心配置
|
||||||
|
spring.boot.admin.client:
|
||||||
|
# 增加客户端开关
|
||||||
|
enabled: true
|
||||||
|
url: http://localhost:9090/admin
|
||||||
|
instance:
|
||||||
|
service-host-type: IP
|
||||||
|
username: ruoyi
|
||||||
|
password: 123456
|
@ -0,0 +1,56 @@
|
|||||||
|
spring:
|
||||||
|
datasource:
|
||||||
|
type: com.zaxxer.hikari.HikariDataSource
|
||||||
|
# mysql数据库
|
||||||
|
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||||
|
url: jdbc:mysql://localhost:3306/ruoyi-flex?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
|
||||||
|
username: root
|
||||||
|
password: Root@369
|
||||||
|
# postgresql数据库
|
||||||
|
#driver-class-name: org.postgresql.Driver
|
||||||
|
#url: jdbc:postgresql://localhost:5432/ruoyi-flex?useUnicode=true&characterEncoding=utf8&useSSL=true&autoReconnect=true&reWriteBatchedInserts=true
|
||||||
|
#username: postgres
|
||||||
|
#password: postgres@369
|
||||||
|
hikari:
|
||||||
|
connection-timeout: 30000
|
||||||
|
validation-timeout: 5000
|
||||||
|
minimum-idle: 10
|
||||||
|
maximum-pool-size: 20
|
||||||
|
idle-timeout: 600000
|
||||||
|
max-lifetime: 900000
|
||||||
|
keepaliveTime: 30000
|
||||||
|
|
||||||
|
--- # easy-retry 服务端配置
|
||||||
|
easy-retry:
|
||||||
|
# 拉取重试数据的每批次的大小
|
||||||
|
retry-pull-page-size: 1000
|
||||||
|
# 拉取重试数据的每批次的大小
|
||||||
|
job-pull-page-size: 1000
|
||||||
|
# 服务端 netty 端口
|
||||||
|
netty-port: 1788
|
||||||
|
# 重试和死信表的分区总数
|
||||||
|
total-partition: 2
|
||||||
|
# 一个客户端每秒最多接收的重试数量指令
|
||||||
|
limiter: 1000
|
||||||
|
# 号段模式下步长配置
|
||||||
|
step: 100
|
||||||
|
# 日志保存时间(单位: day)
|
||||||
|
log-storage: 90
|
||||||
|
# 回调配置
|
||||||
|
callback:
|
||||||
|
#回调最大执行次数
|
||||||
|
max-count: 288
|
||||||
|
#间隔时间
|
||||||
|
trigger-interval: 900
|
||||||
|
mode: all
|
||||||
|
retry-max-pull-count: 10
|
||||||
|
|
||||||
|
--- # 监控中心配置
|
||||||
|
spring.boot.admin.client:
|
||||||
|
# 增加客户端开关
|
||||||
|
enabled: true
|
||||||
|
url: http://localhost:9090/admin
|
||||||
|
instance:
|
||||||
|
service-host-type: IP
|
||||||
|
username: ruoyi
|
||||||
|
password: 123456
|
@ -0,0 +1,40 @@
|
|||||||
|
server:
|
||||||
|
port: 8800
|
||||||
|
servlet:
|
||||||
|
context-path: /easy-retry
|
||||||
|
|
||||||
|
spring:
|
||||||
|
application:
|
||||||
|
name: ruoyi-easyretry-server
|
||||||
|
profiles:
|
||||||
|
active: @profiles.active@
|
||||||
|
web:
|
||||||
|
resources:
|
||||||
|
static-locations: classpath:admin/
|
||||||
|
|
||||||
|
mybatis-plus:
|
||||||
|
typeAliasesPackage: com.aizuda.easy.retry.template.datasource.persistence.po
|
||||||
|
global-config:
|
||||||
|
db-config:
|
||||||
|
table-prefix: er_
|
||||||
|
where-strategy: NOT_EMPTY
|
||||||
|
capital-mode: false
|
||||||
|
logic-delete-value: 1
|
||||||
|
logic-not-delete-value: 0
|
||||||
|
configuration:
|
||||||
|
map-underscore-to-camel-case: true
|
||||||
|
cache-enabled: true
|
||||||
|
|
||||||
|
logging:
|
||||||
|
config: classpath:logback-plus.xml
|
||||||
|
|
||||||
|
management:
|
||||||
|
endpoints:
|
||||||
|
web:
|
||||||
|
exposure:
|
||||||
|
include: '*'
|
||||||
|
endpoint:
|
||||||
|
health:
|
||||||
|
show-details: ALWAYS
|
||||||
|
logfile:
|
||||||
|
external-file: ./logs/ruoyi-easyretry-server/console.log
|
@ -0,0 +1,92 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<configuration>
|
||||||
|
<property name="log.path" value="./logs/ruoyi-easyretry-server" />
|
||||||
|
<property name="console.log.pattern"
|
||||||
|
value="%red(%d{yyyy-MM-dd HH:mm:ss}) %green([%thread]) %highlight(%-5level) %boldMagenta(%logger{36}%n) - %msg%n"/>
|
||||||
|
<property name="log.pattern" value="%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"/>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- 控制台输出 -->
|
||||||
|
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
|
<encoder>
|
||||||
|
<pattern>${console.log.pattern}</pattern>
|
||||||
|
<charset>utf-8</charset>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<!-- 控制台输出 -->
|
||||||
|
<appender name="file_console" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||||
|
<file>${log.path}/console.log</file>
|
||||||
|
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||||
|
<!-- 日志文件名格式 -->
|
||||||
|
<fileNamePattern>${log.path}/console.%d{yyyy-MM-dd}.log</fileNamePattern>
|
||||||
|
<!-- 日志最大 1天 -->
|
||||||
|
<maxHistory>1</maxHistory>
|
||||||
|
</rollingPolicy>
|
||||||
|
<encoder>
|
||||||
|
<pattern>${log.pattern}</pattern>
|
||||||
|
<charset>utf-8</charset>
|
||||||
|
</encoder>
|
||||||
|
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
|
||||||
|
<!-- 过滤的级别 -->
|
||||||
|
<level>INFO</level>
|
||||||
|
</filter>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||||
|
<file>${log.path}/info.log</file>
|
||||||
|
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||||
|
<FileNamePattern>${log.path}/info.%d{yyyy-MM-dd}.log</FileNamePattern>
|
||||||
|
<MaxHistory>60</MaxHistory>
|
||||||
|
</rollingPolicy>
|
||||||
|
<encoder>
|
||||||
|
<pattern>${log.pattern}</pattern>
|
||||||
|
</encoder>
|
||||||
|
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||||
|
<level>INFO</level>
|
||||||
|
<onMatch>ACCEPT</onMatch>
|
||||||
|
<onMismatch>DENY</onMismatch>
|
||||||
|
</filter>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||||
|
<file>${log.path}/error.log</file>
|
||||||
|
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||||
|
<FileNamePattern>${log.path}/error.%d{yyyy-MM-dd}.log
|
||||||
|
</FileNamePattern>
|
||||||
|
<MaxHistory>60</MaxHistory>
|
||||||
|
</rollingPolicy>
|
||||||
|
<encoder>
|
||||||
|
<pattern>${log.pattern}</pattern>
|
||||||
|
</encoder>
|
||||||
|
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||||
|
<level>ERROR</level>
|
||||||
|
<onMatch>ACCEPT</onMatch>
|
||||||
|
<onMismatch>DENY</onMismatch>
|
||||||
|
</filter>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<appender name ="async_info" class= "ch.qos.logback.classic.AsyncAppender">
|
||||||
|
<discardingThreshold >100</discardingThreshold>
|
||||||
|
<queueSize>1024</queueSize>
|
||||||
|
<appender-ref ref ="file_info"/>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<appender name ="async_error" class= "ch.qos.logback.classic.AsyncAppender">
|
||||||
|
<discardingThreshold >100</discardingThreshold>
|
||||||
|
<queueSize>1024</queueSize>
|
||||||
|
<appender-ref ref ="file_error"/>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<!-- EasyRetry appender -->
|
||||||
|
<appender name="easy_log_server_appender" class="com.aizuda.easy.retry.server.common.appender.EasyRetryServerLogbackAppender">
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<!-- 控制台输出日志级别 -->
|
||||||
|
<root level="info">
|
||||||
|
<appender-ref ref="console" />
|
||||||
|
<appender-ref ref="async_info" />
|
||||||
|
<appender-ref ref="async_error" />
|
||||||
|
<appender-ref ref="easy_log_server_appender" />
|
||||||
|
</root>
|
||||||
|
</configuration>
|
8
ruoyi-extra/ruoyi-monitor/Dockerfile
Normal file
8
ruoyi-extra/ruoyi-monitor/Dockerfile
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# 使用官方的 Java 运行时作为父镜像
|
||||||
|
FROM registry.cn-qingdao.aliyuncs.com/yuzl1/jdk:21
|
||||||
|
|
||||||
|
# 将本地文件复制到容器中
|
||||||
|
COPY target/ruoyi-monitor.jar /ruoyi-monitor.jar
|
||||||
|
|
||||||
|
# 运行应用
|
||||||
|
ENTRYPOINT ["java","-jar","/ruoyi-monitor.jar"]
|
8
ruoyi-extra/ruoyi-powerjob-server/Dockerfile
Normal file
8
ruoyi-extra/ruoyi-powerjob-server/Dockerfile
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# 使用官方的 Java 运行时作为父镜像
|
||||||
|
FROM registry.cn-qingdao.aliyuncs.com/yuzl1/jdk:21
|
||||||
|
|
||||||
|
# 将本地文件复制到容器中
|
||||||
|
COPY target/ruoyi-powerjob-server.jar /ruoyi-powerjob-server.jar
|
||||||
|
|
||||||
|
# 运行应用
|
||||||
|
ENTRYPOINT ["java","-jar","/ruoyi-powerjob-server.jar"]
|
@ -2,15 +2,15 @@ oms.env=dev
|
|||||||
|
|
||||||
####### Database properties(Configure according to the the environment) #######
|
####### Database properties(Configure according to the the environment) #######
|
||||||
spring.datasource.remote.hibernate.properties.hibernate.dialect=tech.powerjob.server.persistence.config.dialect.PowerJobPGDialect
|
spring.datasource.remote.hibernate.properties.hibernate.dialect=tech.powerjob.server.persistence.config.dialect.PowerJobPGDialect
|
||||||
spring.datasource.core.driver-class-name=org.postgresql.Driver
|
#spring.datasource.core.driver-class-name=org.postgresql.Driver
|
||||||
spring.datasource.core.jdbc-url=jdbc:postgresql://localhost:5432/ruoyi-flex?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
|
#spring.datasource.core.jdbc-url=jdbc:postgresql://localhost:5432/ruoyi-flex?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
|
||||||
spring.datasource.core.username=postgres
|
#spring.datasource.core.username=postgres
|
||||||
spring.datasource.core.password=postgres@369
|
#spring.datasource.core.password=postgres@369
|
||||||
## MySQL数据库连接参数
|
## MySQL数据库连接参数
|
||||||
#spring.datasource.core.driver-class-name=com.mysql.cj.jdbc.Driver
|
spring.datasource.core.driver-class-name=com.mysql.cj.jdbc.Driver
|
||||||
#spring.datasource.core.jdbc-url=jdbc:mysql://localhost:3306/ruoyi-flex?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
|
spring.datasource.core.jdbc-url=jdbc:mysql://localhost:3306/ruoyi-flex?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
|
||||||
#spring.datasource.core.username=root
|
spring.datasource.core.username=root
|
||||||
#spring.datasource.core.password=Root@369
|
spring.datasource.core.password=Root@369
|
||||||
spring.datasource.core.maximum-pool-size=20
|
spring.datasource.core.maximum-pool-size=20
|
||||||
spring.datasource.core.minimum-idle=5
|
spring.datasource.core.minimum-idle=5
|
||||||
|
|
||||||
|
@ -1,6 +1,12 @@
|
|||||||
oms.env=prod
|
oms.env=prod
|
||||||
|
|
||||||
####### Database properties(Configure according to the the environment) #######
|
####### Database properties(Configure according to the the environment) #######
|
||||||
|
#spring.datasource.remote.hibernate.properties.hibernate.dialect=tech.powerjob.server.persistence.config.dialect.PowerJobPGDialect
|
||||||
|
#spring.datasource.core.driver-class-name=org.postgresql.Driver
|
||||||
|
#spring.datasource.core.jdbc-url=jdbc:postgresql://localhost:5432/ruoyi-flex?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
|
||||||
|
#spring.datasource.core.username=postgres
|
||||||
|
#spring.datasource.core.password=postgres@369
|
||||||
|
## MySQL数据库连接参数
|
||||||
spring.datasource.core.driver-class-name=com.mysql.cj.jdbc.Driver
|
spring.datasource.core.driver-class-name=com.mysql.cj.jdbc.Driver
|
||||||
spring.datasource.core.jdbc-url=jdbc:mysql://localhost:3306/ruoyi-flex?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
|
spring.datasource.core.jdbc-url=jdbc:mysql://localhost:3306/ruoyi-flex?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
|
||||||
spring.datasource.core.username=root
|
spring.datasource.core.username=root
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# Http server port
|
# Http server port
|
||||||
server.port=7700
|
server.port=7070
|
||||||
|
|
||||||
spring.profiles.active=@profiles.active@
|
spring.profiles.active=@profiles.active@
|
||||||
spring.main.banner-mode=log
|
spring.main.banner-mode=log
|
||||||
|
@ -1,11 +1,14 @@
|
|||||||
package com.ruoyi.mf.controller;
|
package com.ruoyi.mf.controller;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import jakarta.validation.constraints.*;
|
||||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.validation.annotation.Validated;
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
import com.ruoyi.common.excel.core.ExcelResult;
|
||||||
import com.ruoyi.common.core.core.domain.R;
|
import com.ruoyi.common.core.core.domain.R;
|
||||||
import com.ruoyi.common.excel.utils.ExcelUtil;
|
import com.ruoyi.common.excel.utils.ExcelUtil;
|
||||||
import com.ruoyi.common.log.annotation.Log;
|
import com.ruoyi.common.log.annotation.Log;
|
||||||
@ -14,15 +17,18 @@ import com.ruoyi.common.web.annotation.RepeatSubmit;
|
|||||||
import com.ruoyi.common.web.core.BaseController;
|
import com.ruoyi.common.web.core.BaseController;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import com.ruoyi.mf.domain.vo.MfProductVo;
|
import com.ruoyi.mf.domain.vo.MfProductVo;
|
||||||
|
import com.ruoyi.mf.domain.vo.MfProductImportVo;
|
||||||
import com.ruoyi.mf.domain.bo.MfProductBo;
|
import com.ruoyi.mf.domain.bo.MfProductBo;
|
||||||
|
import com.ruoyi.mf.listener.MfProductImportListener;
|
||||||
import com.ruoyi.mf.service.IMfProductService;
|
import com.ruoyi.mf.service.IMfProductService;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 产品树Controller
|
* 产品树Controller
|
||||||
*
|
*
|
||||||
* @author 数据小王子
|
* @author 数据小王子
|
||||||
* 2024-01-06
|
* 2024-04-12
|
||||||
*/
|
*/
|
||||||
@Validated
|
@Validated
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@ -56,6 +62,26 @@ public class MfProductController extends BaseController
|
|||||||
ExcelUtil.exportExcel(list, "产品树", MfProductVo.class, response);
|
ExcelUtil.exportExcel(list, "产品树", MfProductVo.class, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导入数据
|
||||||
|
*
|
||||||
|
* @param file 导入文件
|
||||||
|
* @param updateSupport 是否更新已存在数据
|
||||||
|
*/
|
||||||
|
@Log(title = "产品树", businessType = BusinessType.IMPORT)
|
||||||
|
@SaCheckPermission("mf:product:import")
|
||||||
|
@PostMapping("/importData")
|
||||||
|
public R<Void> importData(MultipartFile file, boolean updateSupport) throws Exception {
|
||||||
|
ExcelResult<MfProductImportVo> result = ExcelUtil.importExcel(file.getInputStream(), MfProductImportVo.class, new MfProductImportListener(updateSupport));
|
||||||
|
return R.ok(result.getAnalysis());
|
||||||
|
}
|
||||||
|
|
||||||
|
@SaCheckPermission("mf:product:import")
|
||||||
|
@PostMapping("/importTemplate")
|
||||||
|
public void importTemplate(HttpServletResponse response) {
|
||||||
|
ExcelUtil.exportExcel(new ArrayList<>(), "产品树", MfProductImportVo.class, response);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取产品树详细信息
|
* 获取产品树详细信息
|
||||||
*/
|
*/
|
||||||
|
@ -1,11 +1,14 @@
|
|||||||
package com.ruoyi.mf.controller;
|
package com.ruoyi.mf.controller;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import jakarta.validation.constraints.*;
|
||||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.validation.annotation.Validated;
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
import com.ruoyi.common.excel.core.ExcelResult;
|
||||||
import com.ruoyi.common.core.core.domain.R;
|
import com.ruoyi.common.core.core.domain.R;
|
||||||
import com.ruoyi.common.excel.utils.ExcelUtil;
|
import com.ruoyi.common.excel.utils.ExcelUtil;
|
||||||
import com.ruoyi.common.log.annotation.Log;
|
import com.ruoyi.common.log.annotation.Log;
|
||||||
@ -14,8 +17,11 @@ import com.ruoyi.common.web.annotation.RepeatSubmit;
|
|||||||
import com.ruoyi.common.web.core.BaseController;
|
import com.ruoyi.common.web.core.BaseController;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import com.ruoyi.mf.domain.vo.MfStudentVo;
|
import com.ruoyi.mf.domain.vo.MfStudentVo;
|
||||||
|
import com.ruoyi.mf.domain.vo.MfStudentImportVo;
|
||||||
import com.ruoyi.mf.domain.bo.MfStudentBo;
|
import com.ruoyi.mf.domain.bo.MfStudentBo;
|
||||||
|
import com.ruoyi.mf.listener.MfStudentImportListener;
|
||||||
import com.ruoyi.mf.service.IMfStudentService;
|
import com.ruoyi.mf.service.IMfStudentService;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import com.ruoyi.common.orm.core.page.TableDataInfo;
|
import com.ruoyi.common.orm.core.page.TableDataInfo;
|
||||||
|
|
||||||
@ -23,7 +29,7 @@ import com.ruoyi.common.orm.core.page.TableDataInfo;
|
|||||||
* 学生信息表Controller
|
* 学生信息表Controller
|
||||||
*
|
*
|
||||||
* @author 数据小王子
|
* @author 数据小王子
|
||||||
* 2024-01-05
|
* 2024-04-12
|
||||||
*/
|
*/
|
||||||
@Validated
|
@Validated
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@ -56,6 +62,26 @@ public class MfStudentController extends BaseController
|
|||||||
ExcelUtil.exportExcel(list, "学生信息表", MfStudentVo.class, response);
|
ExcelUtil.exportExcel(list, "学生信息表", MfStudentVo.class, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导入数据
|
||||||
|
*
|
||||||
|
* @param file 导入文件
|
||||||
|
* @param updateSupport 是否更新已存在数据
|
||||||
|
*/
|
||||||
|
@Log(title = "学生信息表", businessType = BusinessType.IMPORT)
|
||||||
|
@SaCheckPermission("mf:student:import")
|
||||||
|
@PostMapping("/importData")
|
||||||
|
public R<Void> importData(MultipartFile file, boolean updateSupport) throws Exception {
|
||||||
|
ExcelResult<MfStudentImportVo> result = ExcelUtil.importExcel(file.getInputStream(), MfStudentImportVo.class, new MfStudentImportListener(updateSupport));
|
||||||
|
return R.ok(result.getAnalysis());
|
||||||
|
}
|
||||||
|
|
||||||
|
@SaCheckPermission("mf:student:import")
|
||||||
|
@PostMapping("/importTemplate")
|
||||||
|
public void importTemplate(HttpServletResponse response) {
|
||||||
|
ExcelUtil.exportExcel(new ArrayList<>(), "学生信息表", MfStudentImportVo.class, response);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取学生信息表详细信息
|
* 获取学生信息表详细信息
|
||||||
*/
|
*/
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package com.ruoyi.mf.domain;
|
package com.ruoyi.mf.domain;
|
||||||
|
|
||||||
|
|
||||||
import com.mybatisflex.annotation.Column;
|
import com.mybatisflex.annotation.Column;
|
||||||
import com.mybatisflex.annotation.Id;
|
import com.mybatisflex.annotation.Id;
|
||||||
import com.mybatisflex.annotation.Table;
|
import com.mybatisflex.annotation.Table;
|
||||||
@ -13,7 +12,7 @@ import com.ruoyi.common.orm.core.domain.TreeEntity;
|
|||||||
* 产品树对象 mf_product
|
* 产品树对象 mf_product
|
||||||
*
|
*
|
||||||
* @author 数据小王子
|
* @author 数据小王子
|
||||||
* 2024-01-06
|
* 2024-04-12
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
@EqualsAndHashCode(callSuper = true)
|
@EqualsAndHashCode(callSuper = true)
|
||||||
@ -23,7 +22,7 @@ public class MfProduct extends TreeEntity
|
|||||||
@Serial
|
@Serial
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
/** 产品id */
|
/** 产品编号 */
|
||||||
@Id
|
@Id
|
||||||
private Long productId;
|
private Long productId;
|
||||||
|
|
||||||
|
@ -2,7 +2,6 @@ package com.ruoyi.mf.domain;
|
|||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
|
||||||
import com.mybatisflex.annotation.Column;
|
import com.mybatisflex.annotation.Column;
|
||||||
import com.mybatisflex.annotation.Id;
|
import com.mybatisflex.annotation.Id;
|
||||||
import com.mybatisflex.annotation.Table;
|
import com.mybatisflex.annotation.Table;
|
||||||
@ -15,7 +14,7 @@ import com.ruoyi.common.orm.core.domain.BaseEntity;
|
|||||||
* 学生信息表对象 mf_student
|
* 学生信息表对象 mf_student
|
||||||
*
|
*
|
||||||
* @author 数据小王子
|
* @author 数据小王子
|
||||||
* 2024-01-06
|
* 2024-04-12
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
@EqualsAndHashCode(callSuper = true)
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@ -11,7 +11,7 @@ import com.ruoyi.common.orm.core.domain.TreeEntity;
|
|||||||
* 产品树业务对象 mf_product
|
* 产品树业务对象 mf_product
|
||||||
*
|
*
|
||||||
* @author 数据小王子
|
* @author 数据小王子
|
||||||
* @date 2024-01-06
|
* @date 2024-04-12
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
@EqualsAndHashCode(callSuper = true)
|
@EqualsAndHashCode(callSuper = true)
|
||||||
@ -20,7 +20,7 @@ public class MfProductBo extends TreeEntity
|
|||||||
{
|
{
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 产品id
|
* 产品编号
|
||||||
*/
|
*/
|
||||||
private Long productId;
|
private Long productId;
|
||||||
|
|
||||||
@ -36,4 +36,5 @@ public class MfProductBo extends TreeEntity
|
|||||||
@NotBlank(message = "产品状态(0正常 1停用)不能为空")
|
@NotBlank(message = "产品状态(0正常 1停用)不能为空")
|
||||||
private String status;
|
private String status;
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ import com.ruoyi.common.orm.core.domain.BaseEntity;
|
|||||||
* 学生信息表业务对象 mf_student
|
* 学生信息表业务对象 mf_student
|
||||||
*
|
*
|
||||||
* @author 数据小王子
|
* @author 数据小王子
|
||||||
* @date 2024-01-05
|
* @date 2024-04-12
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
@EqualsAndHashCode(callSuper = true)
|
@EqualsAndHashCode(callSuper = true)
|
||||||
@ -63,4 +63,5 @@ public class MfStudentBo extends BaseEntity
|
|||||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||||
private Date studentBirthday;
|
private Date studentBirthday;
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,52 @@
|
|||||||
|
package com.ruoyi.mf.domain.vo;
|
||||||
|
|
||||||
|
import com.alibaba.excel.annotation.ExcelProperty;
|
||||||
|
import com.ruoyi.common.excel.annotation.ExcelDictFormat;
|
||||||
|
import com.ruoyi.common.excel.convert.ExcelDictConvert;
|
||||||
|
import lombok.Data;
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 产品树导入视图对象 mf_product
|
||||||
|
*
|
||||||
|
* @author 数据小王子
|
||||||
|
* @date 2024-04-12
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class MfProductImportVo implements Serializable
|
||||||
|
{
|
||||||
|
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/** 产品编号 */
|
||||||
|
@ExcelProperty(value = "产品编号")
|
||||||
|
private Long productId;
|
||||||
|
|
||||||
|
/** 上级编号 */
|
||||||
|
@ExcelProperty(value = "上级编号")
|
||||||
|
private Long parentId;
|
||||||
|
|
||||||
|
/** 产品名称 */
|
||||||
|
@ExcelProperty(value = "产品名称")
|
||||||
|
private String productName;
|
||||||
|
|
||||||
|
/** 显示顺序 */
|
||||||
|
@ExcelProperty(value = "显示顺序")
|
||||||
|
private Integer orderNum;
|
||||||
|
|
||||||
|
/** 产品状态(0正常 1停用) */
|
||||||
|
@ExcelProperty(value = "产品状态", converter = ExcelDictConvert.class)
|
||||||
|
@ExcelDictFormat(dictType = "sys_student_status")
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
/** 逻辑删除标志(0代表存在 1代表删除) */
|
||||||
|
@ExcelProperty(value = "逻辑删除标志(0代表存在 1代表删除)")
|
||||||
|
private Integer delFlag;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -17,7 +17,7 @@ import com.ruoyi.common.orm.core.domain.TreeEntity;
|
|||||||
* 产品树视图对象 mf_product
|
* 产品树视图对象 mf_product
|
||||||
*
|
*
|
||||||
* @author 数据小王子
|
* @author 数据小王子
|
||||||
* @date 2024-01-06
|
* @date 2024-04-12
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
@ExcelIgnoreUnannotated
|
@ExcelIgnoreUnannotated
|
||||||
@ -29,8 +29,8 @@ public class MfProductVo extends TreeEntity implements Serializable
|
|||||||
@Serial
|
@Serial
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
/** 产品id */
|
/** 产品编号 */
|
||||||
@ExcelProperty(value = "产品id")
|
@ExcelProperty(value = "产品编号")
|
||||||
private Long productId;
|
private Long productId;
|
||||||
|
|
||||||
/** 产品名称 */
|
/** 产品名称 */
|
||||||
@ -46,4 +46,6 @@ public class MfProductVo extends TreeEntity implements Serializable
|
|||||||
@ExcelProperty(value = "逻辑删除标志(0代表存在 1代表删除)")
|
@ExcelProperty(value = "逻辑删除标志(0代表存在 1代表删除)")
|
||||||
private Integer delFlag;
|
private Integer delFlag;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,61 @@
|
|||||||
|
package com.ruoyi.mf.domain.vo;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
import com.alibaba.excel.annotation.ExcelProperty;
|
||||||
|
import com.ruoyi.common.excel.annotation.ExcelDictFormat;
|
||||||
|
import com.ruoyi.common.excel.convert.ExcelDictConvert;
|
||||||
|
import lombok.Data;
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 学生信息表导入视图对象 mf_student
|
||||||
|
*
|
||||||
|
* @author 数据小王子
|
||||||
|
* @date 2024-04-12
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class MfStudentImportVo implements Serializable
|
||||||
|
{
|
||||||
|
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
|
||||||
|
/** 学生名称 */
|
||||||
|
@ExcelProperty(value = "学生名称")
|
||||||
|
private String studentName;
|
||||||
|
|
||||||
|
/** 年龄 */
|
||||||
|
@ExcelProperty(value = "年龄")
|
||||||
|
private Integer studentAge;
|
||||||
|
|
||||||
|
/** 爱好(0代码 1音乐 2电影) */
|
||||||
|
@ExcelProperty(value = "爱好", converter = ExcelDictConvert.class)
|
||||||
|
@ExcelDictFormat(dictType = "sys_student_hobby")
|
||||||
|
private String studentHobby;
|
||||||
|
|
||||||
|
/** 性别(1男 2女 3未知) */
|
||||||
|
@ExcelProperty(value = "性别", converter = ExcelDictConvert.class)
|
||||||
|
@ExcelDictFormat(dictType = "sys_user_gender")
|
||||||
|
private String studentGender;
|
||||||
|
|
||||||
|
/** 状态(0正常 1停用) */
|
||||||
|
@ExcelProperty(value = "状态", converter = ExcelDictConvert.class)
|
||||||
|
@ExcelDictFormat(dictType = "sys_student_status")
|
||||||
|
private String studentStatus;
|
||||||
|
|
||||||
|
/** 生日 */
|
||||||
|
@ExcelProperty(value = "生日")
|
||||||
|
private Date studentBirthday;
|
||||||
|
|
||||||
|
/** 逻辑删除标志(0代表存在 1代表删除) */
|
||||||
|
@ExcelProperty(value = "逻辑删除标志(0代表存在 1代表删除)")
|
||||||
|
private Integer delFlag;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -19,7 +19,7 @@ import com.ruoyi.common.orm.core.domain.BaseEntity;
|
|||||||
* 学生信息表视图对象 mf_student
|
* 学生信息表视图对象 mf_student
|
||||||
*
|
*
|
||||||
* @author 数据小王子
|
* @author 数据小王子
|
||||||
* @date 2024-01-05
|
* @date 2024-04-12
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
@ExcelIgnoreUnannotated
|
@ExcelIgnoreUnannotated
|
||||||
@ -66,4 +66,6 @@ public class MfStudentVo extends BaseEntity implements Serializable
|
|||||||
@ExcelProperty(value = "逻辑删除标志(0代表存在 1代表删除)")
|
@ExcelProperty(value = "逻辑删除标志(0代表存在 1代表删除)")
|
||||||
private Integer delFlag;
|
private Integer delFlag;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,118 @@
|
|||||||
|
package com.ruoyi.mf.listener;
|
||||||
|
|
||||||
|
import cn.hutool.core.bean.BeanUtil;
|
||||||
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
|
import com.alibaba.excel.context.AnalysisContext;
|
||||||
|
import com.alibaba.excel.event.AnalysisEventListener;
|
||||||
|
import com.ruoyi.common.core.exception.ServiceException;
|
||||||
|
import com.ruoyi.common.core.utils.SpringUtils;
|
||||||
|
import com.ruoyi.common.core.utils.ValidatorUtils;
|
||||||
|
import com.ruoyi.common.excel.core.ExcelListener;
|
||||||
|
import com.ruoyi.common.excel.core.ExcelResult;
|
||||||
|
import com.ruoyi.mf.domain.bo.MfProductBo;
|
||||||
|
import com.ruoyi.mf.domain.vo.*;
|
||||||
|
import com.ruoyi.mf.service.*;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 产品树自定义导入
|
||||||
|
*
|
||||||
|
* @author 数据小王子
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class MfProductImportListener extends AnalysisEventListener<MfProductImportVo> implements ExcelListener<MfProductImportVo> {
|
||||||
|
private final IMfProductService mfProductService;
|
||||||
|
|
||||||
|
private final Boolean isUpdateSupport;
|
||||||
|
private int successNum = 0;
|
||||||
|
private int failureNum = 0;
|
||||||
|
private final StringBuilder successMsg = new StringBuilder();
|
||||||
|
private final StringBuilder failureMsg = new StringBuilder();
|
||||||
|
|
||||||
|
public MfProductImportListener(Boolean isUpdateSupport) {
|
||||||
|
this.mfProductService = SpringUtils.getBean(IMfProductService.class);
|
||||||
|
this.isUpdateSupport = isUpdateSupport;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void invoke(MfProductImportVo mfProductVo, AnalysisContext context) {
|
||||||
|
try {
|
||||||
|
|
||||||
|
MfProductBo mfProductBo = BeanUtil.toBean(mfProductVo, MfProductBo.class);
|
||||||
|
|
||||||
|
//TODO:根据某个字段,查询数据库表中是否存在记录,不存在就新增,存在就更新
|
||||||
|
MfProductVo mfProductVo1 = null;
|
||||||
|
|
||||||
|
mfProductVo1 = mfProductService.selectById(mfProductVo.getProductId());
|
||||||
|
if (ObjectUtil.isNull(mfProductVo1)) {
|
||||||
|
//不存在就新增
|
||||||
|
mfProductBo.setVersion(0);
|
||||||
|
ValidatorUtils.validate(mfProductBo);
|
||||||
|
boolean inserted = mfProductService.insertWithPk(mfProductBo);//树表需要前台传来主键值
|
||||||
|
|
||||||
|
if (inserted) {
|
||||||
|
successNum++;
|
||||||
|
successMsg.append("<br/>").append(successNum).append("、产品树 记录导入成功");
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
failureNum++;
|
||||||
|
failureMsg.append("<br/>").append(failureNum).append("、产品树 记录导入失败");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if (isUpdateSupport) {
|
||||||
|
//存在就更新
|
||||||
|
mfProductBo.setProductId(mfProductVo1.getProductId());//主键
|
||||||
|
mfProductBo.setVersion(mfProductVo1.getVersion());
|
||||||
|
boolean updated = mfProductService.update(mfProductBo);
|
||||||
|
if (updated) {
|
||||||
|
successNum++;
|
||||||
|
successMsg.append("<br/>").append(successNum).append("、产品树 记录更新成功");
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
failureNum++;
|
||||||
|
failureMsg.append("<br/>").append(failureNum).append("、产品树 记录更新失败");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
failureNum++;
|
||||||
|
String msg = "<br/>" + failureNum + "、产品树 记录导入失败:";
|
||||||
|
failureMsg.append(msg).append(e.getMessage());
|
||||||
|
log.error(msg, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void doAfterAllAnalysed(AnalysisContext context) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ExcelResult<MfProductImportVo> getExcelResult() {
|
||||||
|
return new ExcelResult<>() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getAnalysis() {
|
||||||
|
if (failureNum > 0) {
|
||||||
|
failureMsg.insert(0, "很抱歉,导入失败!共 " + failureNum + " 条数据没有成功导入,错误如下:");
|
||||||
|
throw new ServiceException(failureMsg.toString());
|
||||||
|
} else {
|
||||||
|
successMsg.insert(0, "恭喜您,数据已全部导入成功!共 " + successNum + " 条,数据如下:");
|
||||||
|
}
|
||||||
|
return successMsg.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<MfProductImportVo> getList() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getErrorList() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,118 @@
|
|||||||
|
package com.ruoyi.mf.listener;
|
||||||
|
|
||||||
|
import cn.hutool.core.bean.BeanUtil;
|
||||||
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
|
import com.alibaba.excel.context.AnalysisContext;
|
||||||
|
import com.alibaba.excel.event.AnalysisEventListener;
|
||||||
|
import com.ruoyi.common.core.exception.ServiceException;
|
||||||
|
import com.ruoyi.common.core.utils.SpringUtils;
|
||||||
|
import com.ruoyi.common.core.utils.ValidatorUtils;
|
||||||
|
import com.ruoyi.common.excel.core.ExcelListener;
|
||||||
|
import com.ruoyi.common.excel.core.ExcelResult;
|
||||||
|
import com.ruoyi.mf.domain.bo.MfStudentBo;
|
||||||
|
import com.ruoyi.mf.domain.vo.*;
|
||||||
|
import com.ruoyi.mf.service.*;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 学生信息表自定义导入
|
||||||
|
*
|
||||||
|
* @author 数据小王子
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class MfStudentImportListener extends AnalysisEventListener<MfStudentImportVo> implements ExcelListener<MfStudentImportVo> {
|
||||||
|
private final IMfStudentService mfStudentService;
|
||||||
|
|
||||||
|
private final Boolean isUpdateSupport;
|
||||||
|
private int successNum = 0;
|
||||||
|
private int failureNum = 0;
|
||||||
|
private final StringBuilder successMsg = new StringBuilder();
|
||||||
|
private final StringBuilder failureMsg = new StringBuilder();
|
||||||
|
|
||||||
|
public MfStudentImportListener(Boolean isUpdateSupport) {
|
||||||
|
this.mfStudentService = SpringUtils.getBean(IMfStudentService.class);
|
||||||
|
this.isUpdateSupport = isUpdateSupport;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void invoke(MfStudentImportVo mfStudentVo, AnalysisContext context) {
|
||||||
|
try {
|
||||||
|
|
||||||
|
MfStudentBo mfStudentBo = BeanUtil.toBean(mfStudentVo, MfStudentBo.class);
|
||||||
|
|
||||||
|
//TODO:根据某个字段,查询数据库表中是否存在记录,不存在就新增,存在就更新
|
||||||
|
MfStudentVo mfStudentVo1 = null;
|
||||||
|
|
||||||
|
//mfStudentVo1 = mfStudentService.selectBySomefield(mfStudentVo.getSomefield());
|
||||||
|
if (ObjectUtil.isNull(mfStudentVo1)) {
|
||||||
|
//不存在就新增
|
||||||
|
mfStudentBo.setVersion(0);
|
||||||
|
ValidatorUtils.validate(mfStudentBo);
|
||||||
|
boolean inserted = mfStudentService.insert(mfStudentBo);
|
||||||
|
|
||||||
|
if (inserted) {
|
||||||
|
successNum++;
|
||||||
|
successMsg.append("<br/>").append(successNum).append("、学生信息表 记录导入成功");
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
failureNum++;
|
||||||
|
failureMsg.append("<br/>").append(failureNum).append("、学生信息表 记录导入失败");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if (isUpdateSupport) {
|
||||||
|
//存在就更新
|
||||||
|
mfStudentBo.setStudentId(mfStudentVo1.getStudentId());//主键
|
||||||
|
mfStudentBo.setVersion(mfStudentVo1.getVersion());
|
||||||
|
boolean updated = mfStudentService.update(mfStudentBo);
|
||||||
|
if (updated) {
|
||||||
|
successNum++;
|
||||||
|
successMsg.append("<br/>").append(successNum).append("、学生信息表 记录更新成功");
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
failureNum++;
|
||||||
|
failureMsg.append("<br/>").append(failureNum).append("、学生信息表 记录更新失败");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
failureNum++;
|
||||||
|
String msg = "<br/>" + failureNum + "、学生信息表 记录导入失败:";
|
||||||
|
failureMsg.append(msg).append(e.getMessage());
|
||||||
|
log.error(msg, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void doAfterAllAnalysed(AnalysisContext context) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ExcelResult<MfStudentImportVo> getExcelResult() {
|
||||||
|
return new ExcelResult<>() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getAnalysis() {
|
||||||
|
if (failureNum > 0) {
|
||||||
|
failureMsg.insert(0, "很抱歉,导入失败!共 " + failureNum + " 条数据没有成功导入,错误如下:");
|
||||||
|
throw new ServiceException(failureMsg.toString());
|
||||||
|
} else {
|
||||||
|
successMsg.insert(0, "恭喜您,数据已全部导入成功!共 " + successNum + " 条,数据如下:");
|
||||||
|
}
|
||||||
|
return successMsg.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<MfStudentImportVo> getList() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getErrorList() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -8,7 +8,7 @@ import com.ruoyi.mf.domain.MfProduct;
|
|||||||
* 产品树Mapper接口
|
* 产品树Mapper接口
|
||||||
*
|
*
|
||||||
* @author 数据小王子
|
* @author 数据小王子
|
||||||
* 2024-01-06
|
* 2024-04-12
|
||||||
*/
|
*/
|
||||||
@Mapper
|
@Mapper
|
||||||
public interface MfProductMapper extends BaseMapper<MfProduct>
|
public interface MfProductMapper extends BaseMapper<MfProduct>
|
||||||
|
@ -8,7 +8,7 @@ import com.ruoyi.mf.domain.MfStudent;
|
|||||||
* 学生信息表Mapper接口
|
* 学生信息表Mapper接口
|
||||||
*
|
*
|
||||||
* @author 数据小王子
|
* @author 数据小王子
|
||||||
* 2023-11-22
|
* 2024-04-12
|
||||||
*/
|
*/
|
||||||
@Mapper
|
@Mapper
|
||||||
public interface MfStudentMapper extends BaseMapper<MfStudent>
|
public interface MfStudentMapper extends BaseMapper<MfStudent>
|
||||||
|
@ -10,7 +10,7 @@ import com.ruoyi.common.orm.core.service.IBaseService;
|
|||||||
* 产品树Service接口
|
* 产品树Service接口
|
||||||
*
|
*
|
||||||
* @author 数据小王子
|
* @author 数据小王子
|
||||||
* 2024-01-06
|
* 2024-04-12
|
||||||
*/
|
*/
|
||||||
public interface IMfProductService extends IBaseService<MfProduct>
|
public interface IMfProductService extends IBaseService<MfProduct>
|
||||||
{
|
{
|
||||||
@ -39,6 +39,14 @@ public interface IMfProductService extends IBaseService<MfProduct>
|
|||||||
*/
|
*/
|
||||||
boolean insert(MfProductBo mfProductBo);
|
boolean insert(MfProductBo mfProductBo);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增产品树,前台提供主键值,一般用于导入的场合
|
||||||
|
*
|
||||||
|
* @param mfProductBo 产品树Bo
|
||||||
|
* @return 结果:true 操作成功,false 操作失败
|
||||||
|
*/
|
||||||
|
boolean insertWithPk(MfProductBo mfProductBo);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 修改产品树
|
* 修改产品树
|
||||||
*
|
*
|
||||||
|
@ -11,7 +11,7 @@ import com.ruoyi.common.orm.core.page.TableDataInfo;
|
|||||||
* 学生信息表Service接口
|
* 学生信息表Service接口
|
||||||
*
|
*
|
||||||
* @author 数据小王子
|
* @author 数据小王子
|
||||||
* 2024-01-05
|
* 2024-04-12
|
||||||
*/
|
*/
|
||||||
public interface IMfStudentService extends IBaseService<MfStudent>
|
public interface IMfStudentService extends IBaseService<MfStudent>
|
||||||
{
|
{
|
||||||
@ -47,6 +47,14 @@ public interface IMfStudentService extends IBaseService<MfStudent>
|
|||||||
*/
|
*/
|
||||||
boolean insert(MfStudentBo mfStudentBo);
|
boolean insert(MfStudentBo mfStudentBo);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增学生信息表,前台提供主键值,一般用于导入的场合
|
||||||
|
*
|
||||||
|
* @param mfStudentBo 学生信息表Bo
|
||||||
|
* @return 结果:true 操作成功,false 操作失败
|
||||||
|
*/
|
||||||
|
boolean insertWithPk(MfStudentBo mfStudentBo);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 修改学生信息表
|
* 修改学生信息表
|
||||||
*
|
*
|
||||||
|
@ -2,10 +2,18 @@ package com.ruoyi.mf.service.impl;
|
|||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import cn.hutool.core.util.ObjectUtil;
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
|
import com.mybatisflex.core.paginate.Page;
|
||||||
|
import com.mybatisflex.core.query.QueryMethods;
|
||||||
import com.mybatisflex.core.query.QueryWrapper;
|
import com.mybatisflex.core.query.QueryWrapper;
|
||||||
|
import com.mybatisflex.core.update.UpdateChain;
|
||||||
import com.ruoyi.common.core.utils.MapstructUtils;
|
import com.ruoyi.common.core.utils.MapstructUtils;
|
||||||
|
import com.ruoyi.common.core.utils.StringUtils;
|
||||||
|
import com.ruoyi.common.orm.core.page.PageQuery;
|
||||||
|
import com.ruoyi.common.orm.core.page.TableDataInfo;
|
||||||
import com.ruoyi.common.orm.core.service.impl.BaseServiceImpl;
|
import com.ruoyi.common.orm.core.service.impl.BaseServiceImpl;
|
||||||
|
import com.ruoyi.common.core.utils.DateUtils;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
@ -20,7 +28,7 @@ import static com.ruoyi.mf.domain.table.MfProductTableDef.MF_PRODUCT;
|
|||||||
* 产品树Service业务层处理
|
* 产品树Service业务层处理
|
||||||
*
|
*
|
||||||
* @author 数据小王子
|
* @author 数据小王子
|
||||||
* 2024-01-06
|
* 2024-04-12
|
||||||
*/
|
*/
|
||||||
@Service
|
@Service
|
||||||
public class MfProductServiceImpl extends BaseServiceImpl<MfProductMapper, MfProduct> implements IMfProductService
|
public class MfProductServiceImpl extends BaseServiceImpl<MfProductMapper, MfProduct> implements IMfProductService
|
||||||
@ -80,9 +88,49 @@ public class MfProductServiceImpl extends BaseServiceImpl<MfProductMapper, MfPro
|
|||||||
{
|
{
|
||||||
MfProduct mfProduct = MapstructUtils.convert(mfProductBo, MfProduct.class);
|
MfProduct mfProduct = MapstructUtils.convert(mfProductBo, MfProduct.class);
|
||||||
|
|
||||||
|
//获取祖级列表字段
|
||||||
|
Long parentId = mfProduct.getParentId();
|
||||||
|
if (parentId == 0) {
|
||||||
|
mfProduct.setAncestors("0");
|
||||||
|
} else {
|
||||||
|
MfProductVo parentMfProduct = selectById(mfProductBo.getParentId());
|
||||||
|
if (ObjectUtil.isNotNull(parentMfProduct)) {
|
||||||
|
mfProduct.setAncestors(parentMfProduct.getAncestors()+"," +parentId);
|
||||||
|
} else {
|
||||||
|
mfProduct.setAncestors("0");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return this.save(mfProduct);//使用全局配置的雪花算法主键生成器生成ID值
|
return this.save(mfProduct);//使用全局配置的雪花算法主键生成器生成ID值
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增产品树,前台提供主键值,一般用于导入的场合
|
||||||
|
*
|
||||||
|
* @param mfProductBo 产品树Bo
|
||||||
|
* @return 结果:true 操作成功,false 操作失败
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean insertWithPk(MfProductBo mfProductBo)
|
||||||
|
{
|
||||||
|
MfProduct mfProduct = MapstructUtils.convert(mfProductBo, MfProduct.class);
|
||||||
|
|
||||||
|
//获取祖级列表字段
|
||||||
|
Long parentId = mfProduct.getParentId();
|
||||||
|
if (parentId == 0) {
|
||||||
|
mfProduct.setAncestors("0");
|
||||||
|
} else {
|
||||||
|
MfProductVo parentMfProduct = selectById(mfProductBo.getParentId());
|
||||||
|
if (ObjectUtil.isNotNull(parentMfProduct)) {
|
||||||
|
mfProduct.setAncestors(parentMfProduct.getAncestors()+"," +parentId);
|
||||||
|
} else {
|
||||||
|
mfProduct.setAncestors("0");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return mfProductMapper.insertWithPk(mfProduct) > 0;//前台传来主键值
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 修改产品树
|
* 修改产品树
|
||||||
*
|
*
|
||||||
@ -94,12 +142,46 @@ public class MfProductServiceImpl extends BaseServiceImpl<MfProductMapper, MfPro
|
|||||||
{
|
{
|
||||||
MfProduct mfProduct = MapstructUtils.convert(mfProductBo, MfProduct.class);
|
MfProduct mfProduct = MapstructUtils.convert(mfProductBo, MfProduct.class);
|
||||||
if(ObjectUtil.isNotNull(mfProduct) && ObjectUtil.isNotNull(mfProduct.getProductId())) {
|
if(ObjectUtil.isNotNull(mfProduct) && ObjectUtil.isNotNull(mfProduct.getProductId())) {
|
||||||
|
//更新祖级列表字段
|
||||||
|
MfProductVo newParentMfProduct = selectById(mfProduct.getParentId());
|
||||||
|
MfProductVo oldMfProduct = selectById(mfProduct.getProductId());
|
||||||
|
if ( ObjectUtil.isNotNull(newParentMfProduct) && ObjectUtil.isNotNull(oldMfProduct) ) {
|
||||||
|
String newAncestors = newParentMfProduct.getAncestors() + "," + newParentMfProduct.getProductId();
|
||||||
|
String oldAncestors = oldMfProduct.getAncestors();
|
||||||
|
mfProduct.setAncestors(newAncestors);
|
||||||
|
updateMfProductChildren(mfProduct.getProductId(), newAncestors, oldAncestors);
|
||||||
|
}
|
||||||
boolean updated = this.updateById(mfProduct);
|
boolean updated = this.updateById(mfProduct);
|
||||||
return updated;
|
return updated;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改子元素关系
|
||||||
|
*
|
||||||
|
* @param productId 主键ID
|
||||||
|
* @param newAncestors 新的父ID集合
|
||||||
|
* @param oldAncestors 旧的父ID集合
|
||||||
|
*/
|
||||||
|
@Transactional
|
||||||
|
public void updateMfProductChildren(Long productId, String newAncestors, String oldAncestors) {
|
||||||
|
QueryWrapper queryWrapper = QueryWrapper.create()
|
||||||
|
.from(MF_PRODUCT)
|
||||||
|
.where(QueryMethods.findInSet(QueryMethods.number(productId), MF_PRODUCT.ANCESTORS).gt(0));
|
||||||
|
|
||||||
|
List<MfProductVo> children = this.listAs(queryWrapper, MfProductVo.class);
|
||||||
|
|
||||||
|
for (MfProductVo child : children) {
|
||||||
|
child.setAncestors(child.getAncestors().replaceFirst(oldAncestors, newAncestors));
|
||||||
|
|
||||||
|
UpdateChain.of(MfProduct.class)
|
||||||
|
.set(MfProduct::getAncestors, child.getAncestors())
|
||||||
|
.where(MfProduct::getProductId).eq(child.getProductId())
|
||||||
|
.update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 批量删除产品树
|
* 批量删除产品树
|
||||||
*
|
*
|
||||||
|
@ -2,13 +2,18 @@ package com.ruoyi.mf.service.impl;
|
|||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import cn.hutool.core.util.ObjectUtil;
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
import com.mybatisflex.core.paginate.Page;
|
import com.mybatisflex.core.paginate.Page;
|
||||||
|
import com.mybatisflex.core.query.QueryMethods;
|
||||||
import com.mybatisflex.core.query.QueryWrapper;
|
import com.mybatisflex.core.query.QueryWrapper;
|
||||||
|
import com.mybatisflex.core.update.UpdateChain;
|
||||||
import com.ruoyi.common.core.utils.MapstructUtils;
|
import com.ruoyi.common.core.utils.MapstructUtils;
|
||||||
|
import com.ruoyi.common.core.utils.StringUtils;
|
||||||
import com.ruoyi.common.orm.core.page.PageQuery;
|
import com.ruoyi.common.orm.core.page.PageQuery;
|
||||||
import com.ruoyi.common.orm.core.page.TableDataInfo;
|
import com.ruoyi.common.orm.core.page.TableDataInfo;
|
||||||
import com.ruoyi.common.orm.core.service.impl.BaseServiceImpl;
|
import com.ruoyi.common.orm.core.service.impl.BaseServiceImpl;
|
||||||
|
import com.ruoyi.common.core.utils.DateUtils;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
@ -23,7 +28,7 @@ import static com.ruoyi.mf.domain.table.MfStudentTableDef.MF_STUDENT;
|
|||||||
* 学生信息表Service业务层处理
|
* 学生信息表Service业务层处理
|
||||||
*
|
*
|
||||||
* @author 数据小王子
|
* @author 数据小王子
|
||||||
* 2024-01-05
|
* 2024-04-12
|
||||||
*/
|
*/
|
||||||
@Service
|
@Service
|
||||||
public class MfStudentServiceImpl extends BaseServiceImpl<MfStudentMapper, MfStudent> implements IMfStudentService
|
public class MfStudentServiceImpl extends BaseServiceImpl<MfStudentMapper, MfStudent> implements IMfStudentService
|
||||||
@ -95,9 +100,25 @@ public class MfStudentServiceImpl extends BaseServiceImpl<MfStudentMapper, MfStu
|
|||||||
{
|
{
|
||||||
MfStudent mfStudent = MapstructUtils.convert(mfStudentBo, MfStudent.class);
|
MfStudent mfStudent = MapstructUtils.convert(mfStudentBo, MfStudent.class);
|
||||||
|
|
||||||
|
|
||||||
return this.save(mfStudent);//使用全局配置的雪花算法主键生成器生成ID值
|
return this.save(mfStudent);//使用全局配置的雪花算法主键生成器生成ID值
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增学生信息表,前台提供主键值,一般用于导入的场合
|
||||||
|
*
|
||||||
|
* @param mfStudentBo 学生信息表Bo
|
||||||
|
* @return 结果:true 操作成功,false 操作失败
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean insertWithPk(MfStudentBo mfStudentBo)
|
||||||
|
{
|
||||||
|
MfStudent mfStudent = MapstructUtils.convert(mfStudentBo, MfStudent.class);
|
||||||
|
|
||||||
|
|
||||||
|
return mfStudentMapper.insertWithPk(mfStudent) > 0;//前台传来主键值
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 修改学生信息表
|
* 修改学生信息表
|
||||||
*
|
*
|
||||||
@ -115,6 +136,7 @@ public class MfStudentServiceImpl extends BaseServiceImpl<MfStudentMapper, MfStu
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 批量删除学生信息表
|
* 批量删除学生信息表
|
||||||
*
|
*
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user