11 Commits

Author SHA1 Message Date
疯狂的狮子Li
296466fa13 !640 发布 5.3.0 新春版 祝大家新年快乐
Merge pull request !640 from 疯狂的狮子Li/dev
2025-01-24 05:08:28 +00:00
疯狂的狮子Li
3c8d864b5f !639 发布 5.3.0-BETA 公测版本
Merge pull request !639 from 疯狂的狮子Li/dev
2025-01-20 03:35:45 +00:00
疯狂的狮子Li
ea50a57602 update 优化 xss包装器 Parameter 处理 兼容某些容器不允许改参数的情况 2024-11-21 10:17:34 +08:00
疯狂的狮子Li
7e14b98676 reset 回滚错误修改
Signed-off-by: 疯狂的狮子Li <15040126243@163.com>
2024-10-28 09:46:28 +00:00
疯狂的狮子Li
015b406001 !591 发布 5.2.3 正式版
Merge pull request !591 from 疯狂的狮子Li/dev
2024-10-25 03:09:23 +00:00
疯狂的狮子Li
098d3347a0 !577 发布 5.2.2 正式版 安全性提升
Merge pull request !577 from 疯狂的狮子Li/dev
2024-08-26 03:43:59 +00:00
疯狂的狮子Li
08d4493994 update 优化 bug 模板 2024-07-15 15:19:22 +08:00
疯狂的狮子Li
367d739e2d Merge remote-tracking branch 'origin/5.X' into 5.X 2024-07-09 16:38:43 +08:00
疯狂的狮子Li
d6688a367d !562 ♥️发布 5.2.1 正式版本
Merge pull request !562 from 疯狂的狮子Li/dev
2024-07-09 02:42:40 +00:00
疯狂的狮子Li
0b331796e2 !551 ♥️发布 5.2.0 正式版本
Merge pull request !551 from 疯狂的狮子Li/dev
2024-06-20 02:10:15 +00:00
疯狂的狮子Li
456620b638 !549 ♥️发布 5.2.0-BETA2 公测版本
Merge pull request !549 from 疯狂的狮子Li/dev
2024-06-06 03:13:46 +00:00
193 changed files with 1744 additions and 3625 deletions

View File

@@ -2,7 +2,7 @@
<configuration default="false" name="ruoyi-monitor-admin" type="docker-deploy" factoryName="dockerfile" server-name="Docker"> <configuration default="false" name="ruoyi-monitor-admin" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
<deployment type="dockerfile"> <deployment type="dockerfile">
<settings> <settings>
<option name="imageTag" value="ruoyi/ruoyi-monitor-admin:5.3.1" /> <option name="imageTag" value="ruoyi/ruoyi-monitor-admin:5.3.0" />
<option name="buildOnly" value="true" /> <option name="buildOnly" value="true" />
<option name="sourceFilePath" value="ruoyi-extend/ruoyi-monitor-admin/Dockerfile" /> <option name="sourceFilePath" value="ruoyi-extend/ruoyi-monitor-admin/Dockerfile" />
</settings> </settings>

View File

@@ -2,7 +2,7 @@
<configuration default="false" name="ruoyi-server" type="docker-deploy" factoryName="dockerfile" server-name="Docker"> <configuration default="false" name="ruoyi-server" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
<deployment type="dockerfile"> <deployment type="dockerfile">
<settings> <settings>
<option name="imageTag" value="ruoyi/ruoyi-server:5.3.1" /> <option name="imageTag" value="ruoyi/ruoyi-server:5.3.0" />
<option name="buildOnly" value="true" /> <option name="buildOnly" value="true" />
<option name="sourceFilePath" value="ruoyi-admin/Dockerfile" /> <option name="sourceFilePath" value="ruoyi-admin/Dockerfile" />
</settings> </settings>

View File

@@ -2,7 +2,7 @@
<configuration default="false" name="ruoyi-snailjob-server" type="docker-deploy" factoryName="dockerfile" server-name="Docker"> <configuration default="false" name="ruoyi-snailjob-server" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
<deployment type="dockerfile"> <deployment type="dockerfile">
<settings> <settings>
<option name="imageTag" value="ruoyi/ruoyi-snailjob-server:5.3.1" /> <option name="imageTag" value="ruoyi/ruoyi-snailjob-server:5.3.0" />
<option name="buildOnly" value="true" /> <option name="buildOnly" value="true" />
<option name="sourceFilePath" value="ruoyi-extend/ruoyi-snailjob-server/Dockerfile" /> <option name="sourceFilePath" value="ruoyi-extend/ruoyi-snailjob-server/Dockerfile" />
</settings> </settings>

View File

@@ -7,10 +7,10 @@
[![码云Gitee](https://gitee.com/dromara/RuoYi-Vue-Plus/badge/star.svg?theme=blue)](https://gitee.com/dromara/RuoYi-Vue-Plus) [![码云Gitee](https://gitee.com/dromara/RuoYi-Vue-Plus/badge/star.svg?theme=blue)](https://gitee.com/dromara/RuoYi-Vue-Plus)
[![GitHub](https://img.shields.io/github/stars/dromara/RuoYi-Vue-Plus.svg?style=social&label=Stars)](https://github.com/dromara/RuoYi-Vue-Plus) [![GitHub](https://img.shields.io/github/stars/dromara/RuoYi-Vue-Plus.svg?style=social&label=Stars)](https://github.com/dromara/RuoYi-Vue-Plus)
[![Star](https://gitcode.com/dromara/RuoYi-Vue-Plus/star/badge.svg)](https://gitcode.com/dromara/RuoYi-Vue-Plus) [![Star](https://gitcode.com/dromara/RuoYi-Vue-Plus/star/badge.svg)](https://gitcode.com/dromara/RuoYi-Vue-Plus)
[![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://gitee.com/dromara/RuoYi-Vue-Plus/blob/5.X/LICENSE) [![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://gitee.com/dromara/RuoYi-Vue-Plus/blob/master/LICENSE)
[![使用IntelliJ IDEA开发维护](https://img.shields.io/badge/IntelliJ%20IDEA-提供支持-blue.svg)](https://www.jetbrains.com/?from=RuoYi-Vue-Plus) [![使用IntelliJ IDEA开发维护](https://img.shields.io/badge/IntelliJ%20IDEA-提供支持-blue.svg)](https://www.jetbrains.com/?from=RuoYi-Vue-Plus)
<br> <br>
[![RuoYi-Vue-Plus](https://img.shields.io/badge/RuoYi_Vue_Plus-5.3.1-success.svg)](https://gitee.com/dromara/RuoYi-Vue-Plus) [![RuoYi-Vue-Plus](https://img.shields.io/badge/RuoYi_Vue_Plus-5.3.0-success.svg)](https://gitee.com/dromara/RuoYi-Vue-Plus)
[![Spring Boot](https://img.shields.io/badge/Spring%20Boot-3.4-blue.svg)]() [![Spring Boot](https://img.shields.io/badge/Spring%20Boot-3.4-blue.svg)]()
[![JDK-17](https://img.shields.io/badge/JDK-17-green.svg)]() [![JDK-17](https://img.shields.io/badge/JDK-17-green.svg)]()
[![JDK-21](https://img.shields.io/badge/JDK-21-green.svg)]() [![JDK-21](https://img.shields.io/badge/JDK-21-green.svg)]()
@@ -22,10 +22,10 @@
> 系统演示: [传送门](https://plus-doc.dromara.org/#/common/demo_system) > 系统演示: [传送门](https://plus-doc.dromara.org/#/common/demo_system)
> 官方前端项目地址: [gitee](https://gitee.com/JavaLionLi/plus-ui) - [github](https://github.com/JavaLionLi/plus-ui) - [gitcode](https://gitcode.com/dromara/plus-ui)<br> > 官方前端项目地址: [plus-ui](https://gitee.com/JavaLionLi/plus-ui)<br>
> 成员前端项目地址: 基于vben5 [ruoyi-plus-vben5](https://gitee.com/dapppp/ruoyi-plus-vben5) > 成员前端项目地址: 基于vben5 [ruoyi-plus-vben5](https://gitee.com/dapppp/ruoyi-plus-vben5)
> 文档地址: [plus-doc](https://plus-doc.dromara.org) 文档在华为云上如果打不开大概率是DNS问题 可以尝试切换网络等方式(或者科学上网) > 文档地址: [plus-doc](https://plus-doc.dromara.org)
## 赞助商 ## 赞助商
@@ -34,7 +34,6 @@ CCFlow 驰聘低代码-流程-表单 - https://gitee.com/opencc/RuoYi-JFlow <br>
数舵科技 软件定制开发APP小程序等 - http://www.shuduokeji.com/ <br> 数舵科技 软件定制开发APP小程序等 - http://www.shuduokeji.com/ <br>
引迈信息 软件开发平台 - https://www.jnpfsoft.com/index.html?from=plus-doc <br> 引迈信息 软件开发平台 - https://www.jnpfsoft.com/index.html?from=plus-doc <br>
<font color="red">**启山商城系统 多租户商城源码可免费商用可二次开发 - https://www.73app.cn/** </font><br> <font color="red">**启山商城系统 多租户商城源码可免费商用可二次开发 - https://www.73app.cn/** </font><br>
Mall4J 高质量Java商城系统 - https://www.mall4j.com/cn/?statId=11 <br>
[如何成为赞助商 加群联系作者详谈](https://plus-doc.dromara.org/#/common/add_group) [如何成为赞助商 加群联系作者详谈](https://plus-doc.dromara.org/#/common/add_group)
# 本框架与RuoYi的功能差异 # 本框架与RuoYi的功能差异
@@ -76,7 +75,7 @@ Mall4J 高质量Java商城系统 - https://www.mall4j.com/cn/?statId=11 <br>
| 邮件 | 采用 mail-api 通用协议支持大部分邮件厂商 | 不支持 | | 邮件 | 采用 mail-api 通用协议支持大部分邮件厂商 | 不支持 |
| 接口文档 | 采用 SpringDoc、javadoc 无注解零入侵基于java注释<br/>只需把注释写好 无需再写一大堆的文档注解了 | 采用 Springfox 已停止维护 需要编写大量的注解来支持文档生成 | | 接口文档 | 采用 SpringDoc、javadoc 无注解零入侵基于java注释<br/>只需把注释写好 无需再写一大堆的文档注解了 | 采用 Springfox 已停止维护 需要编写大量的注解来支持文档生成 |
| 校验框架 | 采用 Validation 支持注解与工具类校验 注解支持国际化 | 仅支持注解 且注解不支持国际化 | | 校验框架 | 采用 Validation 支持注解与工具类校验 注解支持国际化 | 仅支持注解 且注解不支持国际化 |
| Excel框架 | 采用 FastExcel(原Alibaba EasyExcel) 基于插件化<br/>框架对其增加了很多功能 例如 自动合并相同内容 自动排列布局 字典翻译等 | 基于 POI 手写实现 功能有限 复杂 扩展性差 | | Excel框架 | 采用 Alibaba EasyExcel 基于插件化<br/>框架对其增加了很多功能 例如 自动合并相同内容 自动排列布局 字典翻译等 | 基于 POI 手写实现 功能有限 复杂 扩展性差 |
| 工作流支持 | 支持各种复杂审批 转办 委派 加减签 会签 或签 票签 等功能 | 无 | | 工作流支持 | 支持各种复杂审批 转办 委派 加减签 会签 或签 票签 等功能 | 无 |
| 工具类框架 | 采用 Hutool、Lombok 上百种工具覆盖90%的使用需求 基于注解自动生成 get set 等简化框架大量代码 | 手写工具稳定性差易出问题 工具数量有限 代码臃肿需自己手写 get set 等 | | 工具类框架 | 采用 Hutool、Lombok 上百种工具覆盖90%的使用需求 基于注解自动生成 get set 等简化框架大量代码 | 手写工具稳定性差易出问题 工具数量有限 代码臃肿需自己手写 get set 等 |
| 监控框架 | 采用 SpringBoot-Admin 基于SpringBoot官方 actuator 探针机制<br/>实时监控服务状态 框架还为其扩展了在线日志查看监控 | 无 | | 监控框架 | 采用 SpringBoot-Admin 基于SpringBoot官方 actuator 探针机制<br/>实时监控服务状态 框架还为其扩展了在线日志查看监控 | 无 |
@@ -114,6 +113,7 @@ Mall4J 高质量Java商城系统 - https://www.mall4j.com/cn/?statId=11 <br>
| 系统接口 | 根据业务代码自动生成相关的api接口文档 | 支持 | 支持 | | 系统接口 | 根据业务代码自动生成相关的api接口文档 | 支持 | 支持 |
| 服务监控 | 监视集群系统CPU、内存、磁盘、堆栈、在线日志、Spring相关配置等 | 支持 | 仅支持单机CPU、内存、磁盘监控 | | 服务监控 | 监视集群系统CPU、内存、磁盘、堆栈、在线日志、Spring相关配置等 | 支持 | 仅支持单机CPU、内存、磁盘监控 |
| 缓存监控 | 对系统的缓存信息查询,命令统计等。 | 支持 | 支持 | | 缓存监控 | 对系统的缓存信息查询,命令统计等。 | 支持 | 支持 |
| 在线构建器 | 拖动表单元素生成相应的HTML代码。 | 支持 | 支持 |
| 使用案例 | 系统的一些功能案例 | 支持 | 不支持 | | 使用案例 | 系统的一些功能案例 | 支持 | 不支持 |
## 参考文档 ## 参考文档

51
pom.xml
View File

@@ -13,43 +13,44 @@
<description>Dromara RuoYi-Vue-Plus多租户管理系统</description> <description>Dromara RuoYi-Vue-Plus多租户管理系统</description>
<properties> <properties>
<revision>5.3.1</revision> <revision>5.3.0</revision>
<spring-boot.version>3.4.5</spring-boot.version> <spring-boot.version>3.4.1</spring-boot.version>
<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>17</java.version> <java.version>17</java.version>
<mybatis.version>3.5.16</mybatis.version> <mybatis.version>3.5.16</mybatis.version>
<springdoc.version>2.8.5</springdoc.version> <springdoc.version>2.8.3</springdoc.version>
<therapi-javadoc.version>0.15.0</therapi-javadoc.version> <therapi-javadoc.version>0.15.0</therapi-javadoc.version>
<fastexcel.version>1.2.0</fastexcel.version> <easyexcel.version>4.0.3</easyexcel.version>
<velocity.version>2.3</velocity.version> <velocity.version>2.3</velocity.version>
<satoken.version>1.42.0</satoken.version> <satoken.version>1.39.0</satoken.version>
<mybatis-plus.version>3.5.11</mybatis-plus.version> <mybatis-plus.version>3.5.10</mybatis-plus.version>
<p6spy.version>3.9.1</p6spy.version> <p6spy.version>3.9.1</p6spy.version>
<hutool.version>5.8.35</hutool.version> <hutool.version>5.8.35</hutool.version>
<spring-boot-admin.version>3.4.5</spring-boot-admin.version> <spring-boot-admin.version>3.4.1</spring-boot-admin.version>
<redisson.version>3.45.1</redisson.version> <redisson.version>3.43.0</redisson.version>
<lock4j.version>2.2.7</lock4j.version> <lock4j.version>2.2.7</lock4j.version>
<dynamic-ds.version>4.3.1</dynamic-ds.version> <dynamic-ds.version>4.3.1</dynamic-ds.version>
<snailjob.version>1.4.0</snailjob.version> <snailjob.version>1.3.0</snailjob.version>
<mapstruct-plus.version>1.4.6</mapstruct-plus.version> <mapstruct-plus.version>1.4.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>
<lombok.version>1.18.36</lombok.version> <lombok.version>1.18.36</lombok.version>
<bouncycastle.version>1.80</bouncycastle.version> <bouncycastle.version>1.76</bouncycastle.version>
<justauth.version>1.16.7</justauth.version> <justauth.version>1.16.7</justauth.version>
<!-- 离线IP地址定位库 --> <!-- 离线IP地址定位库 -->
<ip2region.version>2.7.0</ip2region.version> <ip2region.version>2.7.0</ip2region.version>
<!-- OSS 配置 --> <!-- OSS 配置 -->
<aws.sdk.version>2.28.22</aws.sdk.version> <aws.sdk.version>2.28.22</aws.sdk.version>
<aws.crt.version>0.31.3</aws.crt.version>
<!-- SMS 配置 --> <!-- SMS 配置 -->
<sms4j.version>3.3.4</sms4j.version> <sms4j.version>3.3.3</sms4j.version>
<!-- 限制框架中的fastjson版本 --> <!-- 限制框架中的fastjson版本 -->
<fastjson.version>1.2.83</fastjson.version> <fastjson.version>1.2.83</fastjson.version>
<!-- 面向运行时的D-ORM依赖 --> <!-- 面向运行时的D-ORM依赖 -->
<anyline.version>8.7.2-20250101</anyline.version> <anyline.version>8.7.2-20250101</anyline.version>
<!--工作流配置--> <!--工作流配置-->
<warm-flow.version>1.7.0</warm-flow.version> <warm-flow.version>1.6.6</warm-flow.version>
<!-- 插件版本 --> <!-- 插件版本 -->
<maven-jar-plugin.version>3.2.2</maven-jar-plugin.version> <maven-jar-plugin.version>3.2.2</maven-jar-plugin.version>
@@ -57,8 +58,6 @@
<maven-compiler-plugin.version>3.11.0</maven-compiler-plugin.version> <maven-compiler-plugin.version>3.11.0</maven-compiler-plugin.version>
<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.3.0</flatten-maven-plugin.version> <flatten-maven-plugin.version>1.3.0</flatten-maven-plugin.version>
<!-- 打包默认跳过测试 -->
<skipTests>true</skipTests>
</properties> </properties>
<profiles> <profiles>
@@ -166,9 +165,9 @@
</dependency> </dependency>
<dependency> <dependency>
<groupId>cn.idev.excel</groupId> <groupId>com.alibaba</groupId>
<artifactId>fastexcel</artifactId> <artifactId>easyexcel</artifactId>
<version>${fastexcel.version}</version> <version>${easyexcel.version}</version>
</dependency> </dependency>
<!-- velocity代码生成使用模板 --> <!-- velocity代码生成使用模板 -->
@@ -246,18 +245,18 @@
<artifactId>s3</artifactId> <artifactId>s3</artifactId>
<version>${aws.sdk.version}</version> <version>${aws.sdk.version}</version>
</dependency> </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 传输管理器 --> <!-- 基于 AWS CRT 的 S3 客户端的性能增强的 S3 传输管理器 -->
<dependency> <dependency>
<groupId>software.amazon.awssdk</groupId> <groupId>software.amazon.awssdk</groupId>
<artifactId>s3-transfer-manager</artifactId> <artifactId>s3-transfer-manager</artifactId>
<version>${aws.sdk.version}</version> <version>${aws.sdk.version}</version>
</dependency> </dependency>
<!-- 将基于 Netty 的 HTTP 客户端从类路径中移除 -->
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>netty-nio-client</artifactId>
<version>${aws.sdk.version}</version>
</dependency>
<!--短信sms4j--> <!--短信sms4j-->
<dependency> <dependency>
<groupId>org.dromara.sms4j</groupId> <groupId>org.dromara.sms4j</groupId>
@@ -321,6 +320,12 @@
<version>${ip2region.version}</version> <version>${ip2region.version}</version>
</dependency> </dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.15.0</version>
</dependency>
<dependency> <dependency>
<groupId>com.alibaba</groupId> <groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId> <artifactId>fastjson</artifactId>

View File

@@ -11,11 +11,9 @@ RUN mkdir -p /ruoyi/server/logs \
WORKDIR /ruoyi/server WORKDIR /ruoyi/server
ENV SERVER_PORT=8080 SNAIL_PORT=28080 LANG=C.UTF-8 LC_ALL=C.UTF-8 JAVA_OPTS="" ENV SERVER_PORT=8080 LANG=C.UTF-8 LC_ALL=C.UTF-8 JAVA_OPTS=""
EXPOSE ${SERVER_PORT} EXPOSE ${SERVER_PORT}
# 暴露 snail job 客户端端口 用于定时任务调度中心通信
EXPOSE ${SNAIL_PORT}
ADD ./target/ruoyi-admin.jar ./app.jar ADD ./target/ruoyi-admin.jar ./app.jar
# 工作流字体文件 # 工作流字体文件
@@ -24,7 +22,6 @@ ADD ./zhFonts/ /usr/share/fonts/zhFonts/
SHELL ["/bin/bash", "-c"] SHELL ["/bin/bash", "-c"]
ENTRYPOINT java -Djava.security.egd=file:/dev/./urandom -Dserver.port=${SERVER_PORT} \ ENTRYPOINT java -Djava.security.egd=file:/dev/./urandom -Dserver.port=${SERVER_PORT} \
-Dsnail-job.port=${SNAIL_PORT} \
# 应用名称 如果想区分集群节点监控 改成不同的名称即可 # 应用名称 如果想区分集群节点监控 改成不同的名称即可
#-Dskywalking.agent.service_name=ruoyi-server \ #-Dskywalking.agent.service_name=ruoyi-server \
#-javaagent:/ruoyi/skywalking/agent/skywalking-agent.jar \ #-javaagent:/ruoyi/skywalking/agent/skywalking-agent.jar \

View File

@@ -11,7 +11,6 @@ import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.constant.Constants; import org.dromara.common.core.constant.Constants;
import org.dromara.common.core.constant.GlobalConstants; import org.dromara.common.core.constant.GlobalConstants;
import org.dromara.common.core.domain.R; import org.dromara.common.core.domain.R;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.SpringUtils; import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.core.utils.StringUtils; import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.core.utils.reflect.ReflectUtils; import org.dromara.common.core.utils.reflect.ReflectUtils;
@@ -80,21 +79,12 @@ public class CaptchaController {
* *
* @param email 邮箱 * @param email 邮箱
*/ */
@RateLimiter(key = "#email", time = 60, count = 1)
@GetMapping("/resource/email/code") @GetMapping("/resource/email/code")
public R<Void> emailCode(@NotBlank(message = "{user.email.not.blank}") String email) { public R<Void> emailCode(@NotBlank(message = "{user.email.not.blank}") String email) {
if (!mailProperties.getEnabled()) { if (!mailProperties.getEnabled()) {
return R.fail("当前系统没有开启邮箱功能!"); return R.fail("当前系统没有开启邮箱功能!");
} }
SpringUtils.getAopProxy(this).emailCodeImpl(email);
return R.ok();
}
/**
* 邮箱验证码
* 独立方法避免验证码关闭之后仍然走限流
*/
@RateLimiter(key = "#email", time = 60, count = 1)
public void emailCodeImpl(String email) {
String key = GlobalConstants.CAPTCHA_CODE_KEY + email; String key = GlobalConstants.CAPTCHA_CODE_KEY + email;
String code = RandomUtil.randomNumbers(4); String code = RandomUtil.randomNumbers(4);
RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION)); RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
@@ -102,30 +92,23 @@ public class CaptchaController {
MailUtils.sendText(email, "登录验证码", "您本次验证码为:" + code + ",有效性为" + Constants.CAPTCHA_EXPIRATION + "分钟,请尽快填写。"); MailUtils.sendText(email, "登录验证码", "您本次验证码为:" + code + ",有效性为" + Constants.CAPTCHA_EXPIRATION + "分钟,请尽快填写。");
} catch (Exception e) { } catch (Exception e) {
log.error("验证码短信发送异常 => {}", e.getMessage()); log.error("验证码短信发送异常 => {}", e.getMessage());
throw new ServiceException(e.getMessage()); return R.fail(e.getMessage());
} }
return R.ok();
} }
/** /**
* 生成验证码 * 生成验证码
*/ */
@RateLimiter(time = 60, count = 10, limitType = LimitType.IP)
@GetMapping("/auth/code") @GetMapping("/auth/code")
public R<CaptchaVo> getCode() { public R<CaptchaVo> getCode() {
CaptchaVo captchaVo = new CaptchaVo();
boolean captchaEnabled = captchaProperties.getEnable(); boolean captchaEnabled = captchaProperties.getEnable();
if (!captchaEnabled) { if (!captchaEnabled) {
CaptchaVo captchaVo = new CaptchaVo();
captchaVo.setCaptchaEnabled(false); captchaVo.setCaptchaEnabled(false);
return R.ok(captchaVo); return R.ok(captchaVo);
} }
return R.ok(SpringUtils.getAopProxy(this).getCodeImpl());
}
/**
* 生成验证码
* 独立方法避免验证码关闭之后仍然走限流
*/
@RateLimiter(time = 60, count = 10, limitType = LimitType.IP)
public CaptchaVo getCodeImpl() {
// 保存验证码信息 // 保存验证码信息
String uuid = IdUtil.simpleUUID(); String uuid = IdUtil.simpleUUID();
String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + uuid; String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + uuid;
@@ -145,10 +128,9 @@ public class CaptchaController {
code = exp.getValue(String.class); code = exp.getValue(String.class);
} }
RedisUtils.setCacheObject(verifyKey, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION)); RedisUtils.setCacheObject(verifyKey, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
CaptchaVo captchaVo = new CaptchaVo();
captchaVo.setUuid(uuid); captchaVo.setUuid(uuid);
captchaVo.setImg(captcha.getImageBase64()); captchaVo.setImg(captcha.getImageBase64());
return captchaVo; return R.ok(captchaVo);
} }
} }

View File

@@ -1,9 +1,9 @@
package org.dromara.web.controller; package org.dromara.web.controller;
import cn.dev33.satoken.annotation.SaIgnore; import cn.dev33.satoken.annotation.SaIgnore;
import lombok.RequiredArgsConstructor; import org.dromara.common.core.config.RuoYiConfig;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.core.utils.StringUtils; import org.dromara.common.core.utils.StringUtils;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
@@ -17,12 +17,16 @@ import org.springframework.web.bind.annotation.RestController;
@RestController @RestController
public class IndexController { public class IndexController {
/**
* 系统基础配置
*/
private final RuoYiConfig ruoyiConfig;
/** /**
* 访问首页,提示语 * 访问首页,提示语
*/ */
@GetMapping("/") @GetMapping("/")
public String index() { public String index() {
return StringUtils.format("欢迎使用{}后台管理框架,请通过前端地址访问。", SpringUtils.getApplicationName()); return StringUtils.format("欢迎使用{}后台管理框架,当前版本v{}请通过前端地址访问。", ruoyiConfig.getName(), ruoyiConfig.getVersion());
} }
} }

View File

@@ -1,8 +1,9 @@
package org.dromara.web.listener; package org.dromara.web.listener;
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.StpUtil; import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
import cn.hutool.core.convert.Convert; import cn.hutool.core.convert.Convert;
import cn.hutool.http.useragent.UserAgent; import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil; import cn.hutool.http.useragent.UserAgentUtil;
@@ -34,13 +35,14 @@ import java.time.Duration;
@Slf4j @Slf4j
public class UserActionListener implements SaTokenListener { public class UserActionListener implements SaTokenListener {
private final SaTokenConfig tokenConfig;
private final SysLoginService loginService; private final SysLoginService loginService;
/** /**
* 每次登录时触发 * 每次登录时触发
*/ */
@Override @Override
public void doLogin(String loginType, Object loginId, String tokenValue, SaLoginParameter loginParameter) { public void doLogin(String loginType, Object loginId, String tokenValue, SaLoginModel loginModel) {
UserAgent userAgent = UserAgentUtil.parse(ServletUtils.getRequest().getHeader("User-Agent")); UserAgent userAgent = UserAgentUtil.parse(ServletUtils.getRequest().getHeader("User-Agent"));
String ip = ServletUtils.getClientIP(); String ip = ServletUtils.getClientIP();
UserOnlineDTO dto = new UserOnlineDTO(); UserOnlineDTO dto = new UserOnlineDTO();
@@ -50,17 +52,17 @@ public class UserActionListener implements SaTokenListener {
dto.setOs(userAgent.getOs().getName()); dto.setOs(userAgent.getOs().getName());
dto.setLoginTime(System.currentTimeMillis()); dto.setLoginTime(System.currentTimeMillis());
dto.setTokenId(tokenValue); dto.setTokenId(tokenValue);
String username = (String) loginParameter.getExtra(LoginHelper.USER_NAME_KEY); String username = (String) loginModel.getExtra(LoginHelper.USER_NAME_KEY);
String tenantId = (String) loginParameter.getExtra(LoginHelper.TENANT_KEY); String tenantId = (String) loginModel.getExtra(LoginHelper.TENANT_KEY);
dto.setUserName(username); dto.setUserName(username);
dto.setClientKey((String) loginParameter.getExtra(LoginHelper.CLIENT_KEY)); dto.setClientKey((String) loginModel.getExtra(LoginHelper.CLIENT_KEY));
dto.setDeviceType(loginParameter.getDeviceType()); dto.setDeviceType(loginModel.getDevice());
dto.setDeptName((String) loginParameter.getExtra(LoginHelper.DEPT_NAME_KEY)); dto.setDeptName((String) loginModel.getExtra(LoginHelper.DEPT_NAME_KEY));
TenantHelper.dynamic(tenantId, () -> { TenantHelper.dynamic(tenantId, () -> {
if(loginParameter.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(loginParameter.getTimeout())); RedisUtils.setCacheObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue, dto, Duration.ofSeconds(tokenConfig.getTimeout()));
} }
}); });
// 记录登录日志 // 记录登录日志
@@ -72,7 +74,7 @@ public class UserActionListener implements SaTokenListener {
logininforEvent.setRequest(ServletUtils.getRequest()); logininforEvent.setRequest(ServletUtils.getRequest());
SpringUtils.context().publishEvent(logininforEvent); SpringUtils.context().publishEvent(logininforEvent);
// 更新登录信息 // 更新登录信息
loginService.recordLoginInfo((Long) loginParameter.getExtra(LoginHelper.USER_KEY), ip); loginService.recordLoginInfo((Long) loginModel.getExtra(LoginHelper.USER_KEY), ip);
log.info("user doLogin, userId:{}, token:{}", loginId, tokenValue); log.info("user doLogin, userId:{}, token:{}", loginId, tokenValue);
} }

View File

@@ -1,6 +1,6 @@
package org.dromara.web.service; package org.dromara.web.service;
import cn.hutool.crypto.digest.BCrypt; import cn.dev33.satoken.secure.BCrypt;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.dromara.common.core.constant.Constants; import org.dromara.common.core.constant.Constants;

View File

@@ -1,7 +1,7 @@
package org.dromara.web.service.impl; package org.dromara.web.service.impl;
import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.StpUtil; import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
@@ -58,8 +58,8 @@ public class EmailAuthStrategy implements IAuthStrategy {
}); });
loginUser.setClientKey(client.getClientKey()); loginUser.setClientKey(client.getClientKey());
loginUser.setDeviceType(client.getDeviceType()); loginUser.setDeviceType(client.getDeviceType());
SaLoginParameter model = new SaLoginParameter(); SaLoginModel model = new SaLoginModel();
model.setDeviceType(client.getDeviceType()); model.setDevice(client.getDeviceType());
// 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置 // 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
// 例如: 后台用户30分钟过期 app用户1天过期 // 例如: 后台用户30分钟过期 app用户1天过期
model.setTimeout(client.getTimeout()); model.setTimeout(client.getTimeout());

View File

@@ -1,9 +1,9 @@
package org.dromara.web.service.impl; package org.dromara.web.service.impl;
import cn.dev33.satoken.secure.BCrypt;
import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.StpUtil; import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
import cn.hutool.crypto.digest.BCrypt;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@@ -70,8 +70,8 @@ public class PasswordAuthStrategy implements IAuthStrategy {
}); });
loginUser.setClientKey(client.getClientKey()); loginUser.setClientKey(client.getClientKey());
loginUser.setDeviceType(client.getDeviceType()); loginUser.setDeviceType(client.getDeviceType());
SaLoginParameter model = new SaLoginParameter(); SaLoginModel model = new SaLoginModel();
model.setDeviceType(client.getDeviceType()); model.setDevice(client.getDeviceType());
// 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置 // 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
// 例如: 后台用户30分钟过期 app用户1天过期 // 例如: 后台用户30分钟过期 app用户1天过期
model.setTimeout(client.getTimeout()); model.setTimeout(client.getTimeout());

View File

@@ -1,7 +1,7 @@
package org.dromara.web.service.impl; package org.dromara.web.service.impl;
import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.StpUtil; import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
@@ -58,8 +58,8 @@ public class SmsAuthStrategy implements IAuthStrategy {
}); });
loginUser.setClientKey(client.getClientKey()); loginUser.setClientKey(client.getClientKey());
loginUser.setDeviceType(client.getDeviceType()); loginUser.setDeviceType(client.getDeviceType());
SaLoginParameter model = new SaLoginParameter(); SaLoginModel model = new SaLoginModel();
model.setDeviceType(client.getDeviceType()); model.setDevice(client.getDeviceType());
// 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置 // 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
// 例如: 后台用户30分钟过期 app用户1天过期 // 例如: 后台用户30分钟过期 app用户1天过期
model.setTimeout(client.getTimeout()); model.setTimeout(client.getTimeout());

View File

@@ -1,7 +1,7 @@
package org.dromara.web.service.impl; package org.dromara.web.service.impl;
import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.StpUtil; import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil; import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
@@ -99,8 +99,8 @@ public class SocialAuthStrategy implements IAuthStrategy {
}); });
loginUser.setClientKey(client.getClientKey()); loginUser.setClientKey(client.getClientKey());
loginUser.setDeviceType(client.getDeviceType()); loginUser.setDeviceType(client.getDeviceType());
SaLoginParameter model = new SaLoginParameter(); SaLoginModel model = new SaLoginModel();
model.setDeviceType(client.getDeviceType()); model.setDevice(client.getDeviceType());
// 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置 // 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
// 例如: 后台用户30分钟过期 app用户1天过期 // 例如: 后台用户30分钟过期 app用户1天过期
model.setTimeout(client.getTimeout()); model.setTimeout(client.getTimeout());

View File

@@ -1,7 +1,7 @@
package org.dromara.web.service.impl; package org.dromara.web.service.impl;
import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.StpUtil; import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@@ -76,8 +76,8 @@ public class XcxAuthStrategy implements IAuthStrategy {
loginUser.setDeviceType(client.getDeviceType()); loginUser.setDeviceType(client.getDeviceType());
loginUser.setOpenid(openid); loginUser.setOpenid(openid);
SaLoginParameter model = new SaLoginParameter(); SaLoginModel model = new SaLoginModel();
model.setDeviceType(client.getDeviceType()); model.setDevice(client.getDeviceType());
// 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置 // 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
// 例如: 后台用户30分钟过期 app用户1天过期 // 例如: 后台用户30分钟过期 app用户1天过期
model.setTimeout(client.getTimeout()); model.setTimeout(client.getTimeout());

View File

@@ -2,7 +2,7 @@
spring.boot.admin.client: spring.boot.admin.client:
# 增加客户端开关 # 增加客户端开关
enabled: true enabled: true
url: http://localhost:9090/admin url: http://localhost:9090
instance: instance:
service-host-type: IP service-host-type: IP
metadata: metadata:
@@ -120,8 +120,8 @@ redisson:
nettyThreads: 8 nettyThreads: 8
# 单节点配置 # 单节点配置
singleServerConfig: singleServerConfig:
# 客户端名称 不能用中文 # 客户端名称
clientName: RuoYi-Vue-Plus clientName: ${ruoyi.name}
# 最小空闲连接数 # 最小空闲连接数
connectionMinimumIdleSize: 8 connectionMinimumIdleSize: 8
# 连接池大小 # 连接池大小

View File

@@ -5,7 +5,7 @@ spring.servlet.multipart.location: /ruoyi/server/temp
spring.boot.admin.client: spring.boot.admin.client:
# 增加客户端开关 # 增加客户端开关
enabled: true enabled: true
url: http://localhost:9090/admin url: http://localhost:9090
instance: instance:
service-host-type: IP service-host-type: IP
metadata: metadata:
@@ -123,8 +123,8 @@ redisson:
nettyThreads: 32 nettyThreads: 32
# 单节点配置 # 单节点配置
singleServerConfig: singleServerConfig:
# 客户端名称 不能用中文 # 客户端名称
clientName: RuoYi-Vue-Plus clientName: ${ruoyi.name}
# 最小空闲连接数 # 最小空闲连接数
connectionMinimumIdleSize: 32 connectionMinimumIdleSize: 32
# 连接池大小 # 连接池大小

View File

@@ -1,3 +1,24 @@
# 项目相关配置
ruoyi:
# 名称
name: RuoYi-Vue-Plus
# 版本
version: ${revision}
# 版权年份
copyrightYear: 2024
captcha:
enable: true
# 页面 <参数设置> 可开启关闭 验证码校验
# 验证码类型 math 数组计算 char 字符验证
type: MATH
# line 线段干扰 circle 圆圈干扰 shear 扭曲干扰
category: CIRCLE
# 数字验证码位数
numberLength: 1
# 字符验证码长度
charLength: 4
# 开发环境配置 # 开发环境配置
server: server:
# 服务器的HTTP端口默认为8080 # 服务器的HTTP端口默认为8080
@@ -20,18 +41,6 @@ server:
# 阻塞任务线程池, 当执行类似servlet请求阻塞操作, undertow会从这个线程池中取得线程,它的值设置取决于系统的负载 # 阻塞任务线程池, 当执行类似servlet请求阻塞操作, undertow会从这个线程池中取得线程,它的值设置取决于系统的负载
worker: 256 worker: 256
captcha:
enable: true
# 页面 <参数设置> 可开启关闭 验证码校验
# 验证码类型 math 数组计算 char 字符验证
type: MATH
# line 线段干扰 circle 圆圈干扰 shear 扭曲干扰
category: CIRCLE
# 数字验证码位数
numberLength: 1
# 字符验证码长度
charLength: 4
# 日志配置 # 日志配置
logging: logging:
level: level:
@@ -52,7 +61,7 @@ user:
# Spring配置 # Spring配置
spring: spring:
application: application:
name: RuoYi-Vue-Plus name: ${ruoyi.name}
threads: threads:
# 开启虚拟线程 仅jdk21可用 # 开启虚拟线程 仅jdk21可用
virtual: virtual:
@@ -177,9 +186,12 @@ springdoc:
api-docs: api-docs:
# 是否开启接口文档 # 是否开启接口文档
enabled: true enabled: true
# swagger-ui:
# # 持久化认证数据
# persistAuthorization: true
info: info:
# 标题 # 标题
title: '标题:RuoYi-Vue-Plus多租户管理系统_接口文档' title: '标题:${ruoyi.name}多租户管理系统_接口文档'
# 描述 # 描述
description: '描述:用于管理集团旗下公司的人员信息,具体包括XXX,XXX模块...' description: '描述:用于管理集团旗下公司的人员信息,具体包括XXX,XXX模块...'
# 版本 # 版本
@@ -216,6 +228,7 @@ xss:
# 排除链接(多个用逗号分隔) # 排除链接(多个用逗号分隔)
excludeUrls: excludeUrls:
- /system/notice - /system/notice
- /warm-flow/save-xml
# 全局线程池相关配置 # 全局线程池相关配置
# 如使用JDK21请直接使用虚拟线程 不要开启此配置 # 如使用JDK21请直接使用虚拟线程 不要开启此配置
@@ -268,11 +281,3 @@ warm-flow:
ui: true ui: true
# 默认Authorization如果有多个token用逗号分隔 # 默认Authorization如果有多个token用逗号分隔
token-name: ${sa-token.token-name},clientid token-name: ${sa-token.token-name},clientid
# 流程状态对应的三元色
chart-status-color:
## 未办理
- 62,62,62
## 待办理
- 255,205,23
## 已办理
- 157,255,0

View File

@@ -1,6 +1,6 @@
package org.dromara.test; package org.dromara.test;
import org.dromara.common.web.config.properties.CaptchaProperties; import org.dromara.common.core.config.RuoYiConfig;
import org.junit.jupiter.api.*; import org.junit.jupiter.api.*;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
@@ -17,19 +17,19 @@ import java.util.concurrent.TimeUnit;
public class DemoUnitTest { public class DemoUnitTest {
@Autowired @Autowired
private CaptchaProperties captchaProperties; private RuoYiConfig ruoYiConfig;
@DisplayName("测试 @SpringBootTest @Test @DisplayName 注解") @DisplayName("测试 @SpringBootTest @Test @DisplayName 注解")
@Test @Test
public void testTest() { public void testTest() {
System.out.println(captchaProperties); System.out.println(ruoYiConfig);
} }
@Disabled @Disabled
@DisplayName("测试 @Disabled 注解") @DisplayName("测试 @Disabled 注解")
@Test @Test
public void testDisabled() { public void testDisabled() {
System.out.println(captchaProperties); System.out.println(ruoYiConfig);
} }
@Timeout(value = 2L, unit = TimeUnit.SECONDS) @Timeout(value = 2L, unit = TimeUnit.SECONDS)
@@ -37,7 +37,7 @@ public class DemoUnitTest {
@Test @Test
public void testTimeout() throws InterruptedException { public void testTimeout() throws InterruptedException {
Thread.sleep(3000); Thread.sleep(3000);
System.out.println(captchaProperties); System.out.println(ruoYiConfig);
} }

View File

@@ -14,7 +14,7 @@
</description> </description>
<properties> <properties>
<revision>5.3.1</revision> <revision>5.3.0</revision>
</properties> </properties>
<dependencyManagement> <dependencyManagement>

View File

@@ -0,0 +1,33 @@
package org.dromara.common.core.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* 读取项目相关配置
*
* @author Lion Li
*/
@Data
@Component
@ConfigurationProperties(prefix = "ruoyi")
public class RuoYiConfig {
/**
* 项目名称
*/
private String name;
/**
* 版本
*/
private String version;
/**
* 版权年份
*/
private String copyrightYear;
}

View File

@@ -3,7 +3,6 @@ package org.dromara.common.core.config;
import jakarta.validation.Validator; import jakarta.validation.Validator;
import org.hibernate.validator.HibernateValidator; import org.hibernate.validator.HibernateValidator;
import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration;
import org.springframework.context.MessageSource; import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
@@ -15,11 +14,11 @@ import java.util.Properties;
* *
* @author Lion Li * @author Lion Li
*/ */
@AutoConfiguration(before = ValidationAutoConfiguration.class) @AutoConfiguration
public class ValidatorConfig { public class ValidatorConfig {
/** /**
* 配置校验框架 快速失败模式 * 配置校验框架 快速返回模式
*/ */
@Bean @Bean
public Validator validator(MessageSource messageSource) { public Validator validator(MessageSource messageSource) {
@@ -29,7 +28,7 @@ public class ValidatorConfig {
// 设置使用 HibernateValidator 校验器 // 设置使用 HibernateValidator 校验器
factoryBean.setProviderClass(HibernateValidator.class); factoryBean.setProviderClass(HibernateValidator.class);
Properties properties = new Properties(); Properties properties = new Properties();
// 设置快速失败模式fail-fast即校验过程中一旦遇到失败立即停止并返回错误 // 设置 快速异常返回
properties.setProperty("hibernate.validator.fail_fast", "true"); properties.setProperty("hibernate.validator.fail_fast", "true");
factoryBean.setValidationProperties(properties); factoryBean.setValidationProperties(properties);
// 加载配置 // 加载配置

View File

@@ -3,14 +3,13 @@ package org.dromara.common.core.constant;
/** /**
* 缓存组名称常量 * 缓存组名称常量
* <p> * <p>
* key 格式为 cacheNames#ttl#maxIdleTime#maxSize#local * key 格式为 cacheNames#ttl#maxIdleTime#maxSize
* <p> * <p>
* ttl 过期时间 如果设置为0则不过期 默认为0 * ttl 过期时间 如果设置为0则不过期 默认为0
* maxIdleTime 最大空闲时间 根据LRU算法清理空闲数据 如果设置为0则不检测 默认为0 * maxIdleTime 最大空闲时间 根据LRU算法清理空闲数据 如果设置为0则不检测 默认为0
* maxSize 组最大长度 根据LRU算法清理溢出数据 如果设置为0则无限长 默认为0 * maxSize 组最大长度 根据LRU算法清理溢出数据 如果设置为0则无限长 默认为0
* local 默认开启本地缓存为1 关闭本地缓存为0
* <p> * <p>
* 例子: test#60s、test#0#60s、test#0#1m#1000、test#1h#0#500、test#1h#0#500#0 * 例子: test#60s、test#0#60s、test#0#1m#1000、test#1h#0#500
* *
* @author Lion Li * @author Lion Li
*/ */
@@ -31,11 +30,6 @@ public interface CacheNames {
*/ */
String SYS_DICT = "sys_dict"; String SYS_DICT = "sys_dict";
/**
* 数据字典类型
*/
String SYS_DICT_TYPE = "sys_dict_type";
/** /**
* 租户 * 租户
*/ */

View File

@@ -17,14 +17,9 @@ public interface RegexConstants extends RegexPool {
String DICTIONARY_TYPE = "^[a-z][a-z0-9_]*$"; String DICTIONARY_TYPE = "^[a-z][a-z0-9_]*$";
/** /**
* 权限标识必须符合以下格式: * 权限标识必须符合 tool:build:list 格式,或者空字符串
* 1. 标准格式xxx:yyy:zzz
* - 第一部分xxx只能包含字母、数字和下划线_不能使用 `*`
* - 第二部分yyy可以包含字母、数字、下划线_和 `*`
* - 第三部分zzz可以包含字母、数字、下划线_和 `*`
* 2. 允许空字符串(""),表示没有权限标识
*/ */
String PERMISSION_STRING = "^$|^[a-zA-Z0-9_]+:[a-zA-Z0-9_*]+:[a-zA-Z0-9_*]+$"; String PERMISSION_STRING = "^(|^[a-zA-Z0-9_]+:[a-zA-Z0-9_]+:[a-zA-Z0-9_]+)$";
/** /**
* 身份证号码后6位 * 身份证号码后6位

View File

@@ -72,9 +72,4 @@ public interface SystemConstants {
*/ */
Long SUPER_ADMIN_ID = 1L; Long SUPER_ADMIN_ID = 1L;
/**
* 根部门祖级列表
*/
String ROOT_DEPT_ANCESTORS = "0";
} }

View File

@@ -11,6 +11,7 @@ import java.io.Serializable;
* *
* @author AprilWind * @author AprilWind
*/ */
@Data @Data
@NoArgsConstructor @NoArgsConstructor
public class DeptDTO implements Serializable { public class DeptDTO implements Serializable {

View File

@@ -1,41 +0,0 @@
package org.dromara.common.core.domain.dto;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
/**
* 字典数据DTO
*
* @author AprilWind
*/
@Data
@NoArgsConstructor
public class DictDataDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 字典标签
*/
private String dictLabel;
/**
* 字典键值
*/
private String dictValue;
/**
* 是否默认Y是 N否
*/
private String isDefault;
/**
* 备注
*/
private String remark;
}

View File

@@ -1,41 +0,0 @@
package org.dromara.common.core.domain.dto;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
/**
* 字典类型DTO
*
* @author AprilWind
*/
@Data
@NoArgsConstructor
public class DictTypeDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 字典主键
*/
private Long dictId;
/**
* 字典名称
*/
private String dictName;
/**
* 字典类型
*/
private String dictType;
/**
* 备注
*/
private String remark;
}

View File

@@ -35,7 +35,7 @@ public class RoleDTO implements Serializable {
private String roleKey; private String roleKey;
/** /**
* 数据范围1全部数据权限 2自定数据权限 3本部门数据权限 4本部门及以下数据权限 5仅本人数据权限 6部门及以下或本人数据权限 * 数据范围1所有数据权限2自定数据权限3本部门数据权限4本部门及以下数据权限5仅本人数据权限
*/ */
private String dataScope; private String dataScope;

View File

@@ -33,22 +33,7 @@ public class ProcessEvent implements Serializable {
private String businessId; private String businessId;
/** /**
* 节点类型0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关 * 状态
*/
private Integer nodeType;
/**
* 流程节点编码
*/
private String nodeCode;
/**
* 流程节点名称
*/
private String nodeName;
/**
* 流程状态
*/ */
private String status; private String status;
@@ -60,6 +45,6 @@ public class ProcessEvent implements Serializable {
/** /**
* 当为true时为申请人节点办理 * 当为true时为申请人节点办理
*/ */
private Boolean submit; private boolean submit;
} }

View File

@@ -6,12 +6,12 @@ import java.io.Serial;
import java.io.Serializable; import java.io.Serializable;
/** /**
* 流程创建任务监听 * 流程办理监听
* *
* @author may * @author may
*/ */
@Data @Data
public class ProcessCreateTaskEvent implements Serializable { public class ProcessTaskEvent implements Serializable {
@Serial @Serial
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@@ -27,20 +27,10 @@ public class ProcessCreateTaskEvent implements Serializable {
private String flowCode; private String flowCode;
/** /**
* 节点类型0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关 * 审批节点编码
*/
private Integer nodeType;
/**
* 流程节点编码
*/ */
private String nodeCode; private String nodeCode;
/**
* 流程节点名称
*/
private String nodeName;
/** /**
* 任务id * 任务id
*/ */

View File

@@ -18,14 +18,14 @@ public class PasswordLoginBody extends LoginBody {
* 用户名 * 用户名
*/ */
@NotBlank(message = "{user.username.not.blank}") @NotBlank(message = "{user.username.not.blank}")
@Length(min = 2, max = 30, message = "{user.username.length.valid}") @Length(min = 2, max = 20, message = "{user.username.length.valid}")
private String username; private String username;
/** /**
* 用户密码 * 用户密码
*/ */
@NotBlank(message = "{user.password.not.blank}") @NotBlank(message = "{user.password.not.blank}")
@Length(min = 5, max = 30, message = "{user.password.length.valid}") @Length(min = 5, max = 20, message = "{user.password.length.valid}")
private String password; private String password;
} }

View File

@@ -18,14 +18,14 @@ public class RegisterBody extends LoginBody {
* 用户名 * 用户名
*/ */
@NotBlank(message = "{user.username.not.blank}") @NotBlank(message = "{user.username.not.blank}")
@Length(min = 2, max = 30, message = "{user.username.length.valid}") @Length(min = 2, max = 20, message = "{user.username.length.valid}")
private String username; private String username;
/** /**
* 用户密码 * 用户密码
*/ */
@NotBlank(message = "{user.password.not.blank}") @NotBlank(message = "{user.password.not.blank}")
@Length(min = 5, max = 30, message = "{user.password.length.valid}") @Length(min = 5, max = 20, message = "{user.password.length.valid}")
private String password; private String password;
private String userType; private String userType;

View File

@@ -5,6 +5,7 @@ import lombok.Getter;
/** /**
* 设备类型 * 设备类型
* 针对一套 用户体系
* *
* @author Lion Li * @author Lion Li
*/ */
@@ -28,12 +29,9 @@ public enum DeviceType {
XCX("xcx"), XCX("xcx"),
/** /**
* 第三方社交登录平台 * social第三方端
*/ */
SOCIAL("social"); SOCIAL("social");
/**
* 设备标识
*/
private final String device; private final String device;
} }

View File

@@ -1,11 +1,12 @@
package org.dromara.common.core.enums; package org.dromara.common.core.enums;
import org.dromara.common.core.utils.StringUtils;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
import org.dromara.common.core.utils.StringUtils;
/** /**
* 用户类型 * 设备类型
* 针对多套 用户体系
* *
* @author Lion Li * @author Lion Li
*/ */
@@ -14,18 +15,15 @@ import org.dromara.common.core.utils.StringUtils;
public enum UserType { public enum UserType {
/** /**
* 后台系统用户 * pc端
*/ */
SYS_USER("sys_user"), SYS_USER("sys_user"),
/** /**
* 移动客户端用户 * app端
*/ */
APP_USER("app_user"); APP_USER("app_user");
/**
* 用户类型标识(用于 token、权限识别等
*/
private final String userType; private final String userType;
public static UserType getUserType(String str) { public static UserType getUserType(String str) {

View File

@@ -1,9 +1,5 @@
package org.dromara.common.core.service; package org.dromara.common.core.service;
import org.dromara.common.core.domain.dto.DictDataDTO;
import org.dromara.common.core.domain.dto.DictTypeDTO;
import java.util.List;
import java.util.Map; import java.util.Map;
/** /**
@@ -68,20 +64,4 @@ public interface DictService {
*/ */
Map<String, String> getAllDictByDictType(String dictType); Map<String, String> getAllDictByDictType(String dictType);
/**
* 根据字典类型查询详细信息
*
* @param dictType 字典类型
* @return 字典类型详细信息
*/
DictTypeDTO getDictType(String dictType);
/**
* 根据字典类型查询字典数据列表
*
* @param dictType 字典类型
* @return 字典数据列表
*/
List<DictDataDTO> getDictData(String dictType);
} }

View File

@@ -3,7 +3,6 @@ package org.dromara.common.core.service;
import org.dromara.common.core.domain.dto.UserDTO; import org.dromara.common.core.domain.dto.UserDTO;
import java.util.List; import java.util.List;
import java.util.Map;
/** /**
* 通用 用户服务 * 通用 用户服务
@@ -92,36 +91,4 @@ public interface UserService {
*/ */
List<UserDTO> selectUsersByPostIds(List<Long> postIds); List<UserDTO> selectUsersByPostIds(List<Long> postIds);
/**
* 根据用户 ID 列表查询用户名称映射关系
*
* @param userIds 用户 ID 列表
* @return Map其中 key 为用户 IDvalue 为对应的用户名称
*/
Map<Long, String> selectUserNamesByIds(List<Long> userIds);
/**
* 根据角色 ID 列表查询角色名称映射关系
*
* @param roleIds 角色 ID 列表
* @return Map其中 key 为角色 IDvalue 为对应的角色名称
*/
Map<Long, String> selectRoleNamesByIds(List<Long> roleIds);
/**
* 根据部门 ID 列表查询部门名称映射关系
*
* @param deptIds 部门 ID 列表
* @return Map其中 key 为部门 IDvalue 为对应的部门名称
*/
Map<Long, String> selectDeptNamesByIds(List<Long> deptIds);
/**
* 根据岗位 ID 列表查询岗位名称映射关系
*
* @param postIds 岗位 ID 列表
* @return Map其中 key 为岗位 IDvalue 为对应的岗位名称
*/
Map<Long, String> selectPostNamesByIds(List<Long> postIds);
} }

View File

@@ -78,18 +78,9 @@ public interface WorkflowService {
/** /**
* 办理任务 * 办理任务
* 系统后台发起审批 无用户信息 需要忽略权限
* completeTask.getVariables().put("ignore", true);
* *
* @param completeTask 参数 * @param completeTask 参数
* @return 结果
*/ */
boolean completeTask(CompleteTaskDTO completeTask); boolean completeTask(CompleteTaskDTO completeTask);
/**
* 办理任务
*
* @param taskId 任务ID
* @param message 办理意见
*/
boolean completeTask(Long taskId, String message);
} }

View File

@@ -175,27 +175,14 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils {
} }
/** /**
* 计算两个时间之间的时间差,并以指定单位返回(绝对值 * 计算两个日期之间的天数差(以毫秒为单位
* *
* @param start 起始时间 * @param date1 第一个日期
* @param end 结束时间 * @param date2 第二个日期
* @param unit 所需返回的时间单位DAYS、HOURS、MINUTES、SECONDS、MILLISECONDS、MICROSECONDS、NANOSECONDS * @return 两个日期之间的天数差的绝对值
* @return 时间差的绝对值,以指定单位表示
*/ */
public static long difference(Date start, Date end, TimeUnit unit) { public static int differentDaysByMillisecond(Date date1, Date date2) {
// 计算时间差,单位为毫秒,取绝对值避免负数 return Math.abs((int) ((date2.getTime() - date1.getTime()) / (1000 * 3600 * 24)));
long diffInMillis = Math.abs(end.getTime() - start.getTime());
// 根据目标单位转换时间差
return switch (unit) {
case DAYS -> diffInMillis / TimeUnit.DAYS.toMillis(1);
case HOURS -> diffInMillis / TimeUnit.HOURS.toMillis(1);
case MINUTES -> diffInMillis / TimeUnit.MINUTES.toMillis(1);
case SECONDS -> diffInMillis / TimeUnit.SECONDS.toMillis(1);
case MILLISECONDS -> diffInMillis;
case MICROSECONDS -> TimeUnit.MILLISECONDS.toMicros(diffInMillis);
case NANOSECONDS -> TimeUnit.MILLISECONDS.toNanos(diffInMillis);
};
} }
/** /**

View File

@@ -1,84 +0,0 @@
package org.dromara.common.core.utils;
import cn.hutool.core.lang.PatternPool;
import cn.hutool.core.net.NetUtil;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.utils.regex.RegexUtils;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
/**
* 增强网络相关工具类
*
* @author 秋辞未寒
*/
@Slf4j
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class NetUtils extends NetUtil {
/**
* 判断是否为IPv6地址
*
* @param ip IP地址
* @return 是否为IPv6地址
*/
public static boolean isIPv6(String ip) {
try {
// 判断是否为IPv6地址
return InetAddress.getByName(ip) instanceof Inet6Address;
} catch (UnknownHostException e) {
return false;
}
}
/**
* 判断IPv6地址是否为内网地址
* <br><br>
* 以下地址将归类为本地地址,如有业务场景有需要,请根据需求自行处理:
* <pre>
* 通配符地址 0:0:0:0:0:0:0:0
* 链路本地地址 fe80::/10
* 唯一本地地址 fec0::/10
* 环回地址 ::1
* </pre>
*
* @param ip IP地址
* @return 是否为内网地址
*/
public static boolean isInnerIPv6(String ip) {
try {
// 判断是否为IPv6地址
if (InetAddress.getByName(ip) instanceof Inet6Address inet6Address) {
// isAnyLocalAddress 判断是否为通配符地址,通常不会将其视为内网地址,根据业务场景自行处理判断
// isLinkLocalAddress 判断是否为链路本地地址,通常不算内网地址,是否划分归属于内网需要根据业务场景自行处理判断
// isLoopbackAddress 判断是否为环回地址与IPv4的 127.0.0.1 同理,用于表示本机
// isSiteLocalAddress 判断是否为本地站点地址IPv6唯一本地地址Unique Local Addresses简称ULA
if (inet6Address.isAnyLocalAddress()
|| inet6Address.isLinkLocalAddress()
|| inet6Address.isLoopbackAddress()
|| inet6Address.isSiteLocalAddress()) {
return true;
}
}
} catch (UnknownHostException e) {
// 注意isInnerIPv6方法和isIPv6方法的适用范围不同所以此处不能忽略其异常信息。
throw new IllegalArgumentException("Invalid IPv6 address!", e);
}
return false;
}
/**
* 判断是否为IPv4地址
*
* @param ip IP地址
* @return 是否为IPv4地址
*/
public static boolean isIPv4(String ip) {
return RegexUtils.isMatch(PatternPool.IPV4, ip);
}
}

View File

@@ -6,7 +6,6 @@ import cn.hutool.core.lang.Validator;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import org.springframework.util.AntPathMatcher; import org.springframework.util.AntPathMatcher;
import java.nio.charset.Charset;
import java.util.*; import java.util.*;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -319,7 +318,6 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils {
.stream() .stream()
.filter(Objects::nonNull) .filter(Objects::nonNull)
.map(mapper) .map(mapper)
.filter(Objects::nonNull)
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
@@ -340,26 +338,4 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils {
return false; return false;
} }
/**
* 将字符串从源字符集转换为目标字符集
*
* @param input 原始字符串
* @param fromCharset 源字符集
* @param toCharset 目标字符集
* @return 转换后的字符串
*/
public static String convert(String input, Charset fromCharset, Charset toCharset) {
if (isBlank(input)) {
return input;
}
try {
// 从源字符集获取字节
byte[] bytes = input.getBytes(fromCharset);
// 使用目标字符集解码
return new String(bytes, toCharset);
} catch (Exception e) {
return input;
}
}
} }

View File

@@ -1,11 +1,11 @@
package org.dromara.common.core.utils.ip; package org.dromara.common.core.utils.ip;
import cn.hutool.core.net.NetUtil;
import cn.hutool.http.HtmlUtil; import cn.hutool.http.HtmlUtil;
import org.dromara.common.core.utils.StringUtils;
import lombok.AccessLevel; import lombok.AccessLevel;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.utils.NetUtils;
import org.dromara.common.core.utils.StringUtils;
/** /**
* 获取地址类 * 获取地址类
@@ -20,24 +20,14 @@ public class AddressUtils {
public static final String UNKNOWN = "XX XX"; public static final String UNKNOWN = "XX XX";
public static String getRealAddressByIP(String ip) { public static String getRealAddressByIP(String ip) {
// 处理空串并过滤HTML标签 if (StringUtils.isBlank(ip)) {
ip = HtmlUtil.cleanHtmlTag(StringUtils.blankToDefault(ip,""));
boolean isIPv6 = NetUtils.isIPv6(ip);
// 判断是否为IPv4或IPv6如果不是则返回未知地址
if (!NetUtils.isIPv4(ip) && !isIPv6) {
return UNKNOWN; return UNKNOWN;
} }
// 内网不查询 // 内网不查询
if (NetUtils.isInnerIPv6(ip) || NetUtils.isInnerIP(ip)) { ip = StringUtils.contains(ip, "0:0:0:0:0:0:0:1") ? "127.0.0.1" : HtmlUtil.cleanHtmlTag(ip);
if (NetUtil.isInnerIP(ip)) {
return "内网IP"; return "内网IP";
} }
// 不支持IPv6不再进行没有必要的IP地址信息的解析直接返回
if (isIPv6) {
log.warn("ip2region不支持IPV6地址解析{}", ip);
// 如有需要可自行实现IPv6地址信息解析逻辑并在这里返回
return "未知";
}
return RegionUtils.getCityInfo(ip); return RegionUtils.getCityInfo(ip);
} }
} }

View File

@@ -1,12 +1,15 @@
package org.dromara.common.core.utils.ip; package org.dromara.common.core.utils.ip;
import cn.hutool.core.io.resource.NoResourceException; import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.resource.ResourceUtil; import cn.hutool.core.io.resource.ClassPathResource;
import lombok.extern.slf4j.Slf4j; import cn.hutool.core.util.ObjectUtil;
import org.dromara.common.core.exception.ServiceException; import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.StringUtils; import org.dromara.common.core.utils.file.FileUtils;
import lombok.extern.slf4j.Slf4j;
import org.lionsoul.ip2region.xdb.Searcher; import org.lionsoul.ip2region.xdb.Searcher;
import java.io.File;
/** /**
* 根据ip地址定位工具类离线方式 * 根据ip地址定位工具类离线方式
* 参考地址:<a href="https://gitee.com/lionsoul/ip2region/tree/master/binding/java">集成 ip2region 实现离线IP地址定位库</a> * 参考地址:<a href="https://gitee.com/lionsoul/ip2region/tree/master/binding/java">集成 ip2region 实现离线IP地址定位库</a>
@@ -16,19 +19,31 @@ import org.lionsoul.ip2region.xdb.Searcher;
@Slf4j @Slf4j
public class RegionUtils { public class RegionUtils {
// IP地址库文件名称
public static final String IP_XDB_FILENAME = "ip2region.xdb";
private static final Searcher SEARCHER; private static final Searcher SEARCHER;
static { static {
try { String fileName = "/ip2region.xdb";
// 1、将 ip2region 数据库文件 xdb 从 ClassPath 加载到内存。 File existFile = FileUtils.file(FileUtil.getTmpDir() + FileUtil.FILE_SEPARATOR + fileName);
// 2、基于加载到内存的 xdb 数据创建一个 Searcher 查询对象。 if (!FileUtils.exist(existFile)) {
SEARCHER = Searcher.newWithBuffer(ResourceUtil.readBytes(IP_XDB_FILENAME)); ClassPathResource fileStream = new ClassPathResource(fileName);
log.info("RegionUtils初始化成功加载IP地址库数据成功"); if (ObjectUtil.isEmpty(fileStream.getStream())) {
} catch (NoResourceException e) {
throw new ServiceException("RegionUtils初始化失败原因IP地址库数据不存在"); throw new ServiceException("RegionUtils初始化失败原因IP地址库数据不存在");
}
FileUtils.writeFromStream(fileStream.getStream(), existFile);
}
String dbPath = existFile.getPath();
// 1、从 dbPath 加载整个 xdb 到内存。
byte[] cBuff;
try {
cBuff = Searcher.loadContentFromFile(dbPath);
} catch (Exception e) {
throw new ServiceException("RegionUtils初始化失败原因从ip2region.xdb文件加载内容失败" + e.getMessage());
}
// 2、使用上述的 cBuff 创建一个完全基于内存的查询对象。
try {
SEARCHER = Searcher.newWithBuffer(cBuff);
} catch (Exception e) { } catch (Exception e) {
throw new ServiceException("RegionUtils初始化失败原因" + e.getMessage()); throw new ServiceException("RegionUtils初始化失败原因" + e.getMessage());
} }
@@ -39,8 +54,9 @@ public class RegionUtils {
*/ */
public static String getCityInfo(String ip) { public static String getCityInfo(String ip) {
try { try {
ip = ip.trim();
// 3、执行查询 // 3、执行查询
String region = SEARCHER.search(StringUtils.trim(ip)); String region = SEARCHER.search(ip);
return region.replace("0|", "").replace("|0", ""); return region.replace("0|", "").replace("|0", "");
} catch (Exception e) { } catch (Exception e) {
log.error("IP地址离线获取城市异常 {}", ip); log.error("IP地址离线获取城市异常 {}", ip);

View File

@@ -1,5 +1,6 @@
org.dromara.common.core.config.ApplicationConfig org.dromara.common.core.config.ApplicationConfig
org.dromara.common.core.config.AsyncConfig org.dromara.common.core.config.AsyncConfig
org.dromara.common.core.config.RuoYiConfig
org.dromara.common.core.config.ThreadPoolConfig org.dromara.common.core.config.ThreadPoolConfig
org.dromara.common.core.config.ValidatorConfig org.dromara.common.core.config.ValidatorConfig
org.dromara.common.core.utils.SpringUtils org.dromara.common.core.utils.SpringUtils

View File

@@ -30,7 +30,7 @@ import java.util.Optional;
import java.util.Set; import java.util.Set;
/** /**
* 接口文档配置 * Swagger 文档配置
* *
* @author Lion Li * @author Lion Li
*/ */

View File

@@ -76,14 +76,12 @@ public class EncryptResponseBodyWrapper extends HttpServletResponseWrapper {
String encryptPassword = EncryptUtils.encryptByRsa(encryptAes, publicKey); String encryptPassword = EncryptUtils.encryptByRsa(encryptAes, publicKey);
// 设置响应头 // 设置响应头
// vue版本需要设置
servletResponse.addHeader("Access-Control-Expose-Headers", headerFlag); servletResponse.addHeader("Access-Control-Expose-Headers", headerFlag);
servletResponse.setHeader(headerFlag, encryptPassword);
servletResponse.setHeader("Access-Control-Allow-Origin", "*"); servletResponse.setHeader("Access-Control-Allow-Origin", "*");
servletResponse.setHeader("Access-Control-Allow-Methods", "*"); servletResponse.setHeader("Access-Control-Allow-Methods", "*");
servletResponse.setHeader(headerFlag, encryptPassword);
servletResponse.setCharacterEncoding(StandardCharsets.UTF_8.toString()); servletResponse.setCharacterEncoding(StandardCharsets.UTF_8.toString());
// 获取原始内容 // 获取原始内容
String originalBody = this.getContent(); String originalBody = this.getContent();
// 对内容进行加密 // 对内容进行加密

View File

@@ -108,7 +108,7 @@ public class EncryptUtils {
} }
/** /**
* SM4加密Base64编码 * sm4加密
* *
* @param data 待加密数据 * @param data 待加密数据
* @param password 秘钥字符串 * @param password 秘钥字符串
@@ -127,11 +127,11 @@ public class EncryptUtils {
} }
/** /**
* SM4加密Hex编码 * sm4加密
* *
* @param data 待加密数据 * @param data 待加密数据
* @param password 秘钥字符串 * @param password 秘钥字符串
* @return 加密后字符串, 采用Hex编码 * @return 加密后字符串, 采用Base64编码
*/ */
public static String encryptBySm4Hex(String data, String password) { public static String encryptBySm4Hex(String data, String password) {
if (StrUtil.isBlank(password)) { if (StrUtil.isBlank(password)) {
@@ -148,7 +148,7 @@ public class EncryptUtils {
/** /**
* sm4解密 * sm4解密
* *
* @param data 待解密数据可以是Base64或Hex编码 * @param data 待解密数据
* @param password 秘钥字符串 * @param password 秘钥字符串
* @return 解密后字符串 * @return 解密后字符串
*/ */

View File

@@ -22,8 +22,8 @@
</dependency> </dependency>
<dependency> <dependency>
<groupId>cn.idev.excel</groupId> <groupId>com.alibaba</groupId>
<artifactId>fastexcel</artifactId> <artifactId>easyexcel</artifactId>
</dependency> </dependency>
</dependencies> </dependencies>

View File

@@ -2,12 +2,12 @@ package org.dromara.common.excel.convert;
import cn.hutool.core.convert.Convert; import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
import cn.idev.excel.converters.Converter; import com.alibaba.excel.converters.Converter;
import cn.idev.excel.enums.CellDataTypeEnum; import com.alibaba.excel.enums.CellDataTypeEnum;
import cn.idev.excel.metadata.GlobalConfiguration; import com.alibaba.excel.metadata.GlobalConfiguration;
import cn.idev.excel.metadata.data.ReadCellData; import com.alibaba.excel.metadata.data.ReadCellData;
import cn.idev.excel.metadata.data.WriteCellData; import com.alibaba.excel.metadata.data.WriteCellData;
import cn.idev.excel.metadata.property.ExcelContentProperty; import com.alibaba.excel.metadata.property.ExcelContentProperty;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import java.math.BigDecimal; import java.math.BigDecimal;

View File

@@ -3,12 +3,12 @@ package org.dromara.common.excel.convert;
import cn.hutool.core.annotation.AnnotationUtil; import cn.hutool.core.annotation.AnnotationUtil;
import cn.hutool.core.convert.Convert; import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
import cn.idev.excel.converters.Converter; import com.alibaba.excel.converters.Converter;
import cn.idev.excel.enums.CellDataTypeEnum; import com.alibaba.excel.enums.CellDataTypeEnum;
import cn.idev.excel.metadata.GlobalConfiguration; import com.alibaba.excel.metadata.GlobalConfiguration;
import cn.idev.excel.metadata.data.ReadCellData; import com.alibaba.excel.metadata.data.ReadCellData;
import cn.idev.excel.metadata.data.WriteCellData; import com.alibaba.excel.metadata.data.WriteCellData;
import cn.idev.excel.metadata.property.ExcelContentProperty; import com.alibaba.excel.metadata.property.ExcelContentProperty;
import org.dromara.common.excel.annotation.ExcelDictFormat; import org.dromara.common.excel.annotation.ExcelDictFormat;
import org.dromara.common.core.service.DictService; import org.dromara.common.core.service.DictService;
import org.dromara.common.core.utils.SpringUtils; import org.dromara.common.core.utils.SpringUtils;

View File

@@ -3,12 +3,12 @@ package org.dromara.common.excel.convert;
import cn.hutool.core.annotation.AnnotationUtil; import cn.hutool.core.annotation.AnnotationUtil;
import cn.hutool.core.convert.Convert; import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
import cn.idev.excel.converters.Converter; import com.alibaba.excel.converters.Converter;
import cn.idev.excel.enums.CellDataTypeEnum; import com.alibaba.excel.enums.CellDataTypeEnum;
import cn.idev.excel.metadata.GlobalConfiguration; import com.alibaba.excel.metadata.GlobalConfiguration;
import cn.idev.excel.metadata.data.ReadCellData; import com.alibaba.excel.metadata.data.ReadCellData;
import cn.idev.excel.metadata.data.WriteCellData; import com.alibaba.excel.metadata.data.WriteCellData;
import cn.idev.excel.metadata.property.ExcelContentProperty; import com.alibaba.excel.metadata.property.ExcelContentProperty;
import org.dromara.common.core.utils.reflect.ReflectUtils; import org.dromara.common.core.utils.reflect.ReflectUtils;
import org.dromara.common.excel.annotation.ExcelEnumFormat; import org.dromara.common.excel.annotation.ExcelEnumFormat;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;

View File

@@ -3,11 +3,11 @@ package org.dromara.common.excel.core;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ReflectUtil; import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import cn.idev.excel.annotation.ExcelProperty; import com.alibaba.excel.annotation.ExcelProperty;
import cn.idev.excel.metadata.Head; import com.alibaba.excel.metadata.Head;
import cn.idev.excel.write.handler.WorkbookWriteHandler; import com.alibaba.excel.write.handler.WorkbookWriteHandler;
import cn.idev.excel.write.handler.context.WorkbookWriteHandlerContext; import com.alibaba.excel.write.handler.context.WorkbookWriteHandlerContext;
import cn.idev.excel.write.merge.AbstractMergeStrategy; import com.alibaba.excel.write.merge.AbstractMergeStrategy;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
import lombok.SneakyThrows; import lombok.SneakyThrows;
@@ -112,13 +112,7 @@ public class CellMergeStrategy extends AbstractMergeStrategy implements Workbook
} }
map.put(field, new RepeatCell(val, i)); map.put(field, new RepeatCell(val, i));
} else if (i == list.size() - 1) { } else if (i == list.size() - 1) {
if (!isMerge(list, i, field)) { if (i > repeatCell.getCurrent() && isMerge(list, i, field)) {
// 如果最后一行不能合并,检查之前的数据是否需要合并
if (i - repeatCell.getCurrent() > 1) {
cellList.add(new CellRangeAddress(repeatCell.getCurrent() + rowIndex, i + rowIndex - 1, colNum, colNum));
}
} else if (i > repeatCell.getCurrent()) {
// 如果最后一行可以合并,则直接合并到最后
cellList.add(new CellRangeAddress(repeatCell.getCurrent() + rowIndex, i + rowIndex, colNum, colNum)); cellList.add(new CellRangeAddress(repeatCell.getCurrent() + rowIndex, i + rowIndex, colNum, colNum));
} }
} else if (!isMerge(list, i, field)) { } else if (!isMerge(list, i, field)) {

View File

@@ -1,10 +1,10 @@
package org.dromara.common.excel.core; package org.dromara.common.excel.core;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import cn.idev.excel.context.AnalysisContext; import com.alibaba.excel.context.AnalysisContext;
import cn.idev.excel.event.AnalysisEventListener; import com.alibaba.excel.event.AnalysisEventListener;
import cn.idev.excel.exception.ExcelAnalysisException; import com.alibaba.excel.exception.ExcelAnalysisException;
import cn.idev.excel.exception.ExcelDataConvertException; import com.alibaba.excel.exception.ExcelDataConvertException;
import org.dromara.common.core.utils.StreamUtils; import org.dromara.common.core.utils.StreamUtils;
import org.dromara.common.core.utils.ValidatorUtils; import org.dromara.common.core.utils.ValidatorUtils;
import org.dromara.common.json.utils.JsonUtils; import org.dromara.common.json.utils.JsonUtils;

View File

@@ -5,12 +5,12 @@ import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.EnumUtil; import cn.hutool.core.util.EnumUtil;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import cn.idev.excel.metadata.FieldCache; import com.alibaba.excel.metadata.FieldCache;
import cn.idev.excel.metadata.FieldWrapper; import com.alibaba.excel.metadata.FieldWrapper;
import cn.idev.excel.util.ClassUtils; import com.alibaba.excel.util.ClassUtils;
import cn.idev.excel.write.handler.SheetWriteHandler; import com.alibaba.excel.write.handler.SheetWriteHandler;
import cn.idev.excel.write.metadata.holder.WriteSheetHolder; import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import cn.idev.excel.write.metadata.holder.WriteWorkbookHolder; import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;
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;
@@ -175,7 +175,7 @@ public class ExcelDownHandler implements SheetWriteHandler {
List<String> firstOptions = options.getOptions(); List<String> firstOptions = options.getOptions();
Map<String, List<String>> secoundOptionsMap = options.getNextOptions(); Map<String, List<String>> secoundOptionsMap = options.getNextOptions();
// 采用按行填充数据的方式,避免出现数据无法写入的问题 // 采用按行填充数据的方式,避免EasyExcel出现数据无法写入的问题
// Attempting to write a row in the range that is already written to disk // Attempting to write a row in the range that is already written to disk
// 使用ArrayList记载数据防止乱序 // 使用ArrayList记载数据防止乱序
@@ -291,11 +291,9 @@ public class ExcelDownHandler implements SheetWriteHandler {
* @param value 下拉选可选值 * @param value 下拉选可选值
*/ */
private void dropDownWithSheet(DataValidationHelper helper, Workbook workbook, Sheet sheet, Integer celIndex, List<String> value) { private void dropDownWithSheet(DataValidationHelper helper, Workbook workbook, Sheet sheet, Integer celIndex, List<String> value) {
//由于poi的写出相关问题超过100个会被临时写进硬盘导致后续内存合并会出Attempting to write a row[] in the range [] that is already written to disk
String tmpOptionsSheetName = OPTIONS_SHEET_NAME + "_" + currentOptionsColumnIndex;
// 创建下拉数据表 // 创建下拉数据表
Sheet simpleDataSheet = Optional.ofNullable(workbook.getSheet(WorkbookUtil.createSafeSheetName(tmpOptionsSheetName))) Sheet simpleDataSheet = Optional.ofNullable(workbook.getSheet(WorkbookUtil.createSafeSheetName(OPTIONS_SHEET_NAME)))
.orElseGet(() -> workbook.createSheet(WorkbookUtil.createSafeSheetName(tmpOptionsSheetName))); .orElseGet(() -> workbook.createSheet(WorkbookUtil.createSafeSheetName(OPTIONS_SHEET_NAME)));
// 将下拉表隐藏 // 将下拉表隐藏
workbook.setSheetHidden(workbook.getSheetIndex(simpleDataSheet), true); workbook.setSheetHidden(workbook.getSheetIndex(simpleDataSheet), true);
// 完善纵向的一级选项数据表 // 完善纵向的一级选项数据表
@@ -304,9 +302,9 @@ public class ExcelDownHandler implements SheetWriteHandler {
// 获取每一选项行,如果没有则创建 // 获取每一选项行,如果没有则创建
Row row = Optional.ofNullable(simpleDataSheet.getRow(i)) Row row = Optional.ofNullable(simpleDataSheet.getRow(i))
.orElseGet(() -> simpleDataSheet.createRow(finalI)); .orElseGet(() -> simpleDataSheet.createRow(finalI));
// 获取本级选项对应的选项列,如果没有则创建。上述采用多个sheet,默认索引为1列 // 获取本级选项对应的选项列,如果没有则创建
Cell cell = Optional.ofNullable(row.getCell(0)) Cell cell = Optional.ofNullable(row.getCell(currentOptionsColumnIndex))
.orElseGet(() -> row.createCell(0)); .orElseGet(() -> row.createCell(currentOptionsColumnIndex));
// 设置值 // 设置值
cell.setCellValue(value.get(i)); cell.setCellValue(value.get(i));
} }
@@ -314,13 +312,13 @@ public class ExcelDownHandler implements SheetWriteHandler {
// 创建名称管理器 // 创建名称管理器
Name name = workbook.createName(); Name name = workbook.createName();
// 设置名称管理器的别名 // 设置名称管理器的别名
String nameName = String.format("%s_%d", tmpOptionsSheetName, celIndex); String nameName = String.format("%s_%d", OPTIONS_SHEET_NAME, celIndex);
name.setNameName(nameName); name.setNameName(nameName);
// 以纵向第一列创建一级下拉拼接引用位置 // 以纵向第一列创建一级下拉拼接引用位置
String function = String.format("%s!$%s$1:$%s$%d", String function = String.format("%s!$%s$1:$%s$%d",
tmpOptionsSheetName, OPTIONS_SHEET_NAME,
getExcelColumnName(0), getExcelColumnName(currentOptionsColumnIndex),
getExcelColumnName(0), getExcelColumnName(currentOptionsColumnIndex),
value.size()); value.size());
// 设置名称管理器的引用位置 // 设置名称管理器的引用位置
name.setRefersToFormula(function); name.setRefersToFormula(function);

View File

@@ -1,6 +1,6 @@
package org.dromara.common.excel.core; package org.dromara.common.excel.core;
import cn.idev.excel.read.listener.ReadListener; import com.alibaba.excel.read.listener.ReadListener;
/** /**
* Excel 导入监听 * Excel 导入监听

View File

@@ -1,15 +1,15 @@
package org.dromara.common.excel.handler; package org.dromara.common.excel.handler;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.idev.excel.metadata.data.DataFormatData; import com.alibaba.excel.metadata.data.DataFormatData;
import cn.idev.excel.metadata.data.WriteCellData; import com.alibaba.excel.metadata.data.WriteCellData;
import cn.idev.excel.util.StyleUtil; import com.alibaba.excel.util.StyleUtil;
import cn.idev.excel.write.handler.CellWriteHandler; import com.alibaba.excel.write.handler.CellWriteHandler;
import cn.idev.excel.write.handler.SheetWriteHandler; import com.alibaba.excel.write.handler.SheetWriteHandler;
import cn.idev.excel.write.handler.context.CellWriteHandlerContext; import com.alibaba.excel.write.handler.context.CellWriteHandlerContext;
import cn.idev.excel.write.metadata.holder.WriteSheetHolder; import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import cn.idev.excel.write.metadata.style.WriteCellStyle; import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import cn.idev.excel.write.metadata.style.WriteFont; import com.alibaba.excel.write.metadata.style.WriteFont;
import org.apache.poi.ss.usermodel.*; import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFClientAnchor; import org.apache.poi.xssf.usermodel.XSSFClientAnchor;
import org.apache.poi.xssf.usermodel.XSSFRichTextString; import org.apache.poi.xssf.usermodel.XSSFRichTextString;

View File

@@ -3,13 +3,13 @@ package org.dromara.common.excel.utils;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.resource.ClassPathResource; import cn.hutool.core.io.resource.ClassPathResource;
import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.IdUtil;
import cn.idev.excel.FastExcel; import com.alibaba.excel.EasyExcel;
import cn.idev.excel.ExcelWriter; import com.alibaba.excel.ExcelWriter;
import cn.idev.excel.write.builder.ExcelWriterSheetBuilder; import com.alibaba.excel.write.builder.ExcelWriterSheetBuilder;
import cn.idev.excel.write.metadata.WriteSheet; import com.alibaba.excel.write.metadata.WriteSheet;
import cn.idev.excel.write.metadata.fill.FillConfig; import com.alibaba.excel.write.metadata.fill.FillConfig;
import cn.idev.excel.write.metadata.fill.FillWrapper; import com.alibaba.excel.write.metadata.fill.FillWrapper;
import cn.idev.excel.write.style.column.LongestMatchColumnWidthStyleStrategy; import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
import jakarta.servlet.ServletOutputStream; import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import lombok.AccessLevel; import lombok.AccessLevel;
@@ -43,7 +43,7 @@ public class ExcelUtil {
* @return 转换后集合 * @return 转换后集合
*/ */
public static <T> List<T> importExcel(InputStream is, Class<T> clazz) { public static <T> List<T> importExcel(InputStream is, Class<T> clazz) {
return FastExcel.read(is).head(clazz).autoCloseStream(false).sheet().doReadSync(); return EasyExcel.read(is).head(clazz).autoCloseStream(false).sheet().doReadSync();
} }
@@ -57,7 +57,7 @@ public class ExcelUtil {
*/ */
public static <T> ExcelResult<T> importExcel(InputStream is, Class<T> clazz, boolean isValidate) { public static <T> ExcelResult<T> importExcel(InputStream is, Class<T> clazz, boolean isValidate) {
DefaultExcelListener<T> listener = new DefaultExcelListener<>(isValidate); DefaultExcelListener<T> listener = new DefaultExcelListener<>(isValidate);
FastExcel.read(is, clazz, listener).sheet().doRead(); EasyExcel.read(is, clazz, listener).sheet().doRead();
return listener.getExcelResult(); return listener.getExcelResult();
} }
@@ -70,7 +70,7 @@ public class ExcelUtil {
* @return 转换后集合 * @return 转换后集合
*/ */
public static <T> ExcelResult<T> importExcel(InputStream is, Class<T> clazz, ExcelListener<T> listener) { public static <T> ExcelResult<T> importExcel(InputStream is, Class<T> clazz, ExcelListener<T> listener) {
FastExcel.read(is, clazz, listener).sheet().doRead(); EasyExcel.read(is, clazz, listener).sheet().doRead();
return listener.getExcelResult(); return listener.getExcelResult();
} }
@@ -186,7 +186,7 @@ public class ExcelUtil {
*/ */
public static <T> void exportExcel(List<T> list, String sheetName, Class<T> clazz, boolean merge, public static <T> void exportExcel(List<T> list, String sheetName, Class<T> clazz, boolean merge,
OutputStream os, List<DropDownOptions> options) { OutputStream os, List<DropDownOptions> options) {
ExcelWriterSheetBuilder builder = FastExcel.write(os, clazz) ExcelWriterSheetBuilder builder = EasyExcel.write(os, clazz)
.autoCloseStream(false) .autoCloseStream(false)
// 自动适配 // 自动适配
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()) .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
@@ -215,9 +215,6 @@ public class ExcelUtil {
*/ */
public static <T> void exportTemplate(List<T> data, String filename, String templatePath, HttpServletResponse response) { public static <T> void exportTemplate(List<T> data, String filename, String templatePath, HttpServletResponse response) {
try { try {
if (CollUtil.isEmpty(data)) {
throw new IllegalArgumentException("数据为空");
}
resetResponse(filename, response); resetResponse(filename, response);
ServletOutputStream os = response.getOutputStream(); ServletOutputStream os = response.getOutputStream();
exportTemplate(data, templatePath, os); exportTemplate(data, templatePath, os);
@@ -236,19 +233,21 @@ public class ExcelUtil {
* @param os 输出流 * @param os 输出流
*/ */
public static <T> void exportTemplate(List<T> data, String templatePath, OutputStream os) { public static <T> void exportTemplate(List<T> data, String templatePath, OutputStream os) {
if (CollUtil.isEmpty(data)) {
throw new IllegalArgumentException("数据为空");
}
ClassPathResource templateResource = new ClassPathResource(templatePath); ClassPathResource templateResource = new ClassPathResource(templatePath);
ExcelWriter excelWriter = FastExcel.write(os) ExcelWriter excelWriter = EasyExcel.write(os)
.withTemplate(templateResource.getStream()) .withTemplate(templateResource.getStream())
.autoCloseStream(false) .autoCloseStream(false)
// 大数值自动转换 防止失真 // 大数值自动转换 防止失真
.registerConverter(new ExcelBigNumberConvert()) .registerConverter(new ExcelBigNumberConvert())
.registerWriteHandler(new DataWriteHandler(data.get(0).getClass())) .registerWriteHandler(new DataWriteHandler(data.get(0).getClass()))
.build(); .build();
WriteSheet writeSheet = FastExcel.writerSheet().build(); WriteSheet writeSheet = EasyExcel.writerSheet().build();
FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
// 单表多数据导出 模板格式为 {.属性} // 单表多数据导出 模板格式为 {.属性}
for (T d : data) { for (T d : data) {
excelWriter.fill(d, fillConfig, writeSheet); excelWriter.fill(d, writeSheet);
} }
excelWriter.finish(); excelWriter.finish();
} }
@@ -265,9 +264,6 @@ public class ExcelUtil {
*/ */
public static void exportTemplateMultiList(Map<String, Object> data, String filename, String templatePath, HttpServletResponse response) { public static void exportTemplateMultiList(Map<String, Object> data, String filename, String templatePath, HttpServletResponse response) {
try { try {
if (CollUtil.isEmpty(data)) {
throw new IllegalArgumentException("数据为空");
}
resetResponse(filename, response); resetResponse(filename, response);
ServletOutputStream os = response.getOutputStream(); ServletOutputStream os = response.getOutputStream();
exportTemplateMultiList(data, templatePath, os); exportTemplateMultiList(data, templatePath, os);
@@ -288,9 +284,6 @@ public class ExcelUtil {
*/ */
public static void exportTemplateMultiSheet(List<Map<String, Object>> data, String filename, String templatePath, HttpServletResponse response) { public static void exportTemplateMultiSheet(List<Map<String, Object>> data, String filename, String templatePath, HttpServletResponse response) {
try { try {
if (CollUtil.isEmpty(data)) {
throw new IllegalArgumentException("数据为空");
}
resetResponse(filename, response); resetResponse(filename, response);
ServletOutputStream os = response.getOutputStream(); ServletOutputStream os = response.getOutputStream();
exportTemplateMultiSheet(data, templatePath, os); exportTemplateMultiSheet(data, templatePath, os);
@@ -309,14 +302,17 @@ public class ExcelUtil {
* @param os 输出流 * @param os 输出流
*/ */
public static void exportTemplateMultiList(Map<String, Object> data, String templatePath, OutputStream os) { public static void exportTemplateMultiList(Map<String, Object> data, String templatePath, OutputStream os) {
if (CollUtil.isEmpty(data)) {
throw new IllegalArgumentException("数据为空");
}
ClassPathResource templateResource = new ClassPathResource(templatePath); ClassPathResource templateResource = new ClassPathResource(templatePath);
ExcelWriter excelWriter = FastExcel.write(os) ExcelWriter excelWriter = EasyExcel.write(os)
.withTemplate(templateResource.getStream()) .withTemplate(templateResource.getStream())
.autoCloseStream(false) .autoCloseStream(false)
// 大数值自动转换 防止失真 // 大数值自动转换 防止失真
.registerConverter(new ExcelBigNumberConvert()) .registerConverter(new ExcelBigNumberConvert())
.build(); .build();
WriteSheet writeSheet = FastExcel.writerSheet().build(); WriteSheet writeSheet = EasyExcel.writerSheet().build();
for (Map.Entry<String, Object> map : data.entrySet()) { for (Map.Entry<String, Object> map : data.entrySet()) {
// 设置列表后续还有数据 // 设置列表后续还有数据
FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build(); FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
@@ -324,7 +320,7 @@ public class ExcelUtil {
// 多表导出必须使用 FillWrapper // 多表导出必须使用 FillWrapper
excelWriter.fill(new FillWrapper(map.getKey(), (Collection<?>) map.getValue()), fillConfig, writeSheet); excelWriter.fill(new FillWrapper(map.getKey(), (Collection<?>) map.getValue()), fillConfig, writeSheet);
} else { } else {
excelWriter.fill(map.getValue(), fillConfig, writeSheet); excelWriter.fill(map.getValue(), writeSheet);
} }
} }
excelWriter.finish(); excelWriter.finish();
@@ -340,15 +336,18 @@ public class ExcelUtil {
* @param os 输出流 * @param os 输出流
*/ */
public static void exportTemplateMultiSheet(List<Map<String, Object>> data, String templatePath, OutputStream os) { public static void exportTemplateMultiSheet(List<Map<String, Object>> data, String templatePath, OutputStream os) {
if (CollUtil.isEmpty(data)) {
throw new IllegalArgumentException("数据为空");
}
ClassPathResource templateResource = new ClassPathResource(templatePath); ClassPathResource templateResource = new ClassPathResource(templatePath);
ExcelWriter excelWriter = FastExcel.write(os) ExcelWriter excelWriter = EasyExcel.write(os)
.withTemplate(templateResource.getStream()) .withTemplate(templateResource.getStream())
.autoCloseStream(false) .autoCloseStream(false)
// 大数值自动转换 防止失真 // 大数值自动转换 防止失真
.registerConverter(new ExcelBigNumberConvert()) .registerConverter(new ExcelBigNumberConvert())
.build(); .build();
for (int i = 0; i < data.size(); i++) { for (int i = 0; i < data.size(); i++) {
WriteSheet writeSheet = FastExcel.writerSheet(i).build(); WriteSheet writeSheet = EasyExcel.writerSheet(i).build();
for (Map.Entry<String, Object> map : data.get(i).entrySet()) { for (Map.Entry<String, Object> map : data.get(i).entrySet()) {
// 设置列表后续还有数据 // 设置列表后续还有数据
FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build(); FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();

View File

@@ -1,6 +1,5 @@
package org.dromara.common.mybatis.handler; package org.dromara.common.mybatis.handler;
import cn.hutool.http.HttpStatus;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.domain.R; import org.dromara.common.core.domain.R;
@@ -26,7 +25,7 @@ public class MybatisExceptionHandler {
public R<Void> handleDuplicateKeyException(DuplicateKeyException e, HttpServletRequest request) { public R<Void> handleDuplicateKeyException(DuplicateKeyException e, HttpServletRequest request) {
String requestURI = request.getRequestURI(); String requestURI = request.getRequestURI();
log.error("请求地址'{}',数据库中已存在记录'{}'", requestURI, e.getMessage()); log.error("请求地址'{}',数据库中已存在记录'{}'", requestURI, e.getMessage());
return R.fail(HttpStatus.HTTP_CONFLICT, "数据库中已存在该记录,请联系管理员确认"); return R.fail("数据库中已存在该记录,请联系管理员确认");
} }
/** /**
@@ -36,12 +35,12 @@ 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 (StringUtils.contains(message, "CannotFindDataSourceException")) { if (StringUtils.contains("CannotFindDataSourceException", message)) {
log.error("请求地址'{}', 未找到数据源", requestURI); log.error("请求地址'{}', 未找到数据源", requestURI);
return R.fail(HttpStatus.HTTP_INTERNAL_ERROR, "未找到数据源,请联系管理员确认"); return R.fail("未找到数据源,请联系管理员确认");
} }
log.error("请求地址'{}', Mybatis系统异常", requestURI, e); log.error("请求地址'{}', Mybatis系统异常", requestURI, e);
return R.fail(HttpStatus.HTTP_INTERNAL_ERROR, message); return R.fail(message);
} }
} }

View File

@@ -35,7 +35,6 @@ import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
import java.lang.reflect.Method;
import java.util.*; import java.util.*;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function; import java.util.function.Function;
@@ -230,7 +229,10 @@ public class PlusDataPermissionHandler {
// 获取资源对应的类对象 // 获取资源对应的类对象
Class<?> clazz = Resources.classForName(classMetadata.getClassName()); Class<?> clazz = Resources.classForName(classMetadata.getClassName());
// 查找类中的特定注解 // 查找类中的特定注解
findAnnotation(clazz); if (AnnotationUtil.hasAnnotation(clazz, DataPermission.class)) {
DataPermission dataPermission = AnnotationUtil.getAnnotation(clazz, DataPermission.class);
dataPermissionCacheMap.put(clazz.getName(), dataPermission);
}
} }
} }
} catch (Exception e) { } catch (Exception e) {
@@ -238,29 +240,6 @@ public class PlusDataPermissionHandler {
} }
} }
/**
* 在指定的类中查找特定的注解 DataPermission并将带有这个注解的方法或类存储到 dataPermissionCacheMap 中
*
* @param clazz 要查找的类
*/
private void findAnnotation(Class<?> clazz) {
DataPermission dataPermission;
for (Method method : clazz.getMethods()) {
if (method.isDefault() || method.isVarArgs()) {
continue;
}
String mappedStatementId = clazz.getName() + "." + method.getName();
if (AnnotationUtil.hasAnnotation(method, DataPermission.class)) {
dataPermission = AnnotationUtil.getAnnotation(method, DataPermission.class);
dataPermissionCacheMap.put(mappedStatementId, dataPermission);
}
}
if (AnnotationUtil.hasAnnotation(clazz, DataPermission.class)) {
dataPermission = AnnotationUtil.getAnnotation(clazz, DataPermission.class);
dataPermissionCacheMap.put(clazz.getName(), dataPermission);
}
}
/** /**
* 根据映射语句 ID 或类名获取对应的 DataPermission 注解对象 * 根据映射语句 ID 或类名获取对应的 DataPermission 注解对象
* *
@@ -272,10 +251,6 @@ public class PlusDataPermissionHandler {
if (DataPermissionHelper.getPermission() != null) { if (DataPermissionHelper.getPermission() != null) {
return DataPermissionHelper.getPermission(); return DataPermissionHelper.getPermission();
} }
// 检查缓存中是否包含映射语句 ID 对应的 DataPermission 注解对象
if (dataPermissionCacheMap.containsKey(mapperId)) {
return dataPermissionCacheMap.get(mapperId);
}
// 如果缓存中不包含映射语句 ID 对应的 DataPermission 注解对象,则尝试使用类名作为键查找 // 如果缓存中不包含映射语句 ID 对应的 DataPermission 注解对象,则尝试使用类名作为键查找
String clazzName = mapperId.substring(0, mapperId.lastIndexOf(".")); String clazzName = mapperId.substring(0, mapperId.lastIndexOf("."));
if (dataPermissionCacheMap.containsKey(clazzName)) { if (dataPermissionCacheMap.containsKey(clazzName)) {

View File

@@ -31,6 +31,11 @@
<groupId>software.amazon.awssdk</groupId> <groupId>software.amazon.awssdk</groupId>
<artifactId>s3</artifactId> <artifactId>s3</artifactId>
<exclusions> <exclusions>
<!-- 将基于 Netty 的 HTTP 客户端从类路径中移除 -->
<exclusion>
<groupId>software.amazon.awssdk</groupId>
<artifactId>netty-nio-client</artifactId>
</exclusion>
<!-- 将基于 CRT 的 HTTP 客户端从类路径中移除 --> <!-- 将基于 CRT 的 HTTP 客户端从类路径中移除 -->
<exclusion> <exclusion>
<groupId>software.amazon.awssdk</groupId> <groupId>software.amazon.awssdk</groupId>
@@ -49,10 +54,10 @@
</exclusions> </exclusions>
</dependency> </dependency>
<!-- 将基于 Netty 的 HTTP 客户端从类路径中移除 --> <!-- 使用AWS基于 CRT 的 S3 客户端 -->
<dependency> <dependency>
<groupId>software.amazon.awssdk</groupId> <groupId>software.amazon.awssdk.crt</groupId>
<artifactId>netty-nio-client</artifactId> <artifactId>aws-crt</artifactId>
</dependency> </dependency>
<!-- 基于 AWS CRT 的 S3 客户端的性能增强的 S3 传输管理器 --> <!-- 基于 AWS CRT 的 S3 客户端的性能增强的 S3 传输管理器 -->

View File

@@ -8,7 +8,7 @@ import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.core.utils.file.FileUtils; import org.dromara.common.core.utils.file.FileUtils;
import org.dromara.common.oss.constant.OssConstant; import org.dromara.common.oss.constant.OssConstant;
import org.dromara.common.oss.entity.UploadResult; import org.dromara.common.oss.entity.UploadResult;
import org.dromara.common.oss.enums.AccessPolicyType; import org.dromara.common.oss.enumd.AccessPolicyType;
import org.dromara.common.oss.exception.OssException; import org.dromara.common.oss.exception.OssException;
import org.dromara.common.oss.properties.OssProperties; import org.dromara.common.oss.properties.OssProperties;
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
@@ -16,10 +16,10 @@ import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import software.amazon.awssdk.core.ResponseInputStream; import software.amazon.awssdk.core.ResponseInputStream;
import software.amazon.awssdk.core.async.AsyncResponseTransformer; import software.amazon.awssdk.core.async.AsyncResponseTransformer;
import software.amazon.awssdk.core.async.BlockingInputStreamAsyncRequestBody; import software.amazon.awssdk.core.async.BlockingInputStreamAsyncRequestBody;
import software.amazon.awssdk.http.nio.netty.NettyNioAsyncHttpClient;
import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3AsyncClient; import software.amazon.awssdk.services.s3.S3AsyncClient;
import software.amazon.awssdk.services.s3.S3Configuration; import software.amazon.awssdk.services.s3.S3Configuration;
import software.amazon.awssdk.services.s3.crt.S3CrtHttpConfiguration;
import software.amazon.awssdk.services.s3.model.GetObjectResponse; import software.amazon.awssdk.services.s3.model.GetObjectResponse;
import software.amazon.awssdk.services.s3.presigner.S3Presigner; import software.amazon.awssdk.services.s3.presigner.S3Presigner;
import software.amazon.awssdk.transfer.s3.S3TransferManager; import software.amazon.awssdk.transfer.s3.S3TransferManager;
@@ -84,14 +84,18 @@ public class OssClient {
// MinIO 使用 HTTPS 限制使用域名访问,站点填域名。需要启用路径样式访问 // MinIO 使用 HTTPS 限制使用域名访问,站点填域名。需要启用路径样式访问
boolean isStyle = !StringUtils.containsAny(properties.getEndpoint(), OssConstant.CLOUD_SERVICE); boolean isStyle = !StringUtils.containsAny(properties.getEndpoint(), OssConstant.CLOUD_SERVICE);
// 创建AWS基于 Netty 的 S3 客户端 // 创建AWS基于 CRT 的 S3 客户端
this.client = S3AsyncClient.builder() this.client = S3AsyncClient.crtBuilder()
.credentialsProvider(credentialsProvider) .credentialsProvider(credentialsProvider)
.endpointOverride(URI.create(getEndpoint())) .endpointOverride(URI.create(getEndpoint()))
.region(of()) .region(of())
.targetThroughputInGbps(20.0)
.minimumPartSizeInBytes(10 * 1025 * 1024L)
.checksumValidationEnabled(false)
.forcePathStyle(isStyle) .forcePathStyle(isStyle)
.httpClient(NettyNioAsyncHttpClient.builder() .httpConfiguration(S3CrtHttpConfiguration.builder()
.connectionTimeout(Duration.ofSeconds(60)).build()) .connectionTimeout(Duration.ofSeconds(60)) // 设置连接超时
.build())
.build(); .build();
//AWS基于 CRT 的 S3 AsyncClient 实例用作 S3 传输管理器的底层客户端 //AWS基于 CRT 的 S3 AsyncClient 实例用作 S3 传输管理器的底层客户端

View File

@@ -1,4 +1,4 @@
package org.dromara.common.oss.enums; package org.dromara.common.oss.enumd;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;

View File

@@ -78,7 +78,6 @@ public class CaffeineCacheDecorator implements Cache {
@Override @Override
public void clear() { public void clear() {
CAFFEINE.invalidateAll();
cache.clear(); cache.clear();
} }

View File

@@ -145,25 +145,18 @@ public class PlusSpringCacheManager implements CacheManager {
if (array.length > 3) { if (array.length > 3) {
config.setMaxSize(Integer.parseInt(array[3])); config.setMaxSize(Integer.parseInt(array[3]));
} }
int local = 1;
if (array.length > 4) {
local = Integer.parseInt(array[4]);
}
if (config.getMaxIdleTime() == 0 && config.getTTL() == 0 && config.getMaxSize() == 0) { if (config.getMaxIdleTime() == 0 && config.getTTL() == 0 && config.getMaxSize() == 0) {
return createMap(name, config, local); return createMap(name, config);
} }
return createMapCache(name, config, local); return createMapCache(name, config);
} }
private Cache createMap(String name, CacheConfig config, int local) { 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(name, new RedissonCache(map, allowNullValues));
if (local == 1) {
cache = new CaffeineCacheDecorator(name, cache);
}
if (transactionAware) { if (transactionAware) {
cache = new TransactionAwareCacheDecorator(cache); cache = new TransactionAwareCacheDecorator(cache);
} }
@@ -174,13 +167,10 @@ public class PlusSpringCacheManager implements CacheManager {
return cache; return cache;
} }
private Cache createMapCache(String name, CacheConfig config, int local) { 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(name, new RedissonCache(map, config, allowNullValues));
if (local == 1) {
cache = new CaffeineCacheDecorator(name, cache);
}
if (transactionAware) { if (transactionAware) {
cache = new TransactionAwareCacheDecorator(cache); cache = new TransactionAwareCacheDecorator(cache);
} }

View File

@@ -1,6 +1,6 @@
package org.dromara.common.satoken.core.dao; package org.dromara.common.satoken.core.dao;
import cn.dev33.satoken.dao.auto.SaTokenDaoBySessionFollowObject; 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.Cache;
import com.github.benmanes.caffeine.cache.Caffeine; import com.github.benmanes.caffeine.cache.Caffeine;
@@ -16,12 +16,10 @@ import java.util.concurrent.TimeUnit;
* Sa-Token持久层接口(使用框架自带RedisUtils实现 协议统一) * Sa-Token持久层接口(使用框架自带RedisUtils实现 协议统一)
* <p> * <p>
* 采用 caffeine + redis 多级缓存 优化并发查询效率 * 采用 caffeine + redis 多级缓存 优化并发查询效率
* <p>
* SaTokenDaoBySessionFollowObject 是 SaTokenDao 子集简化了session方法处理
* *
* @author Lion Li * @author Lion Li
*/ */
public class PlusSaTokenDao implements SaTokenDaoBySessionFollowObject { public class PlusSaTokenDao implements SaTokenDao {
private static final Cache<String, Object> CAFFEINE = Caffeine.newBuilder() private static final Cache<String, Object> CAFFEINE = Caffeine.newBuilder()
// 设置最后一次写入或访问后经过固定时间过期 // 设置最后一次写入或访问后经过固定时间过期
@@ -87,8 +85,7 @@ public class PlusSaTokenDao implements SaTokenDaoBySessionFollowObject {
@Override @Override
public long getTimeout(String key) { public long getTimeout(String key) {
long timeout = RedisUtils.getTimeToLive(key); long timeout = RedisUtils.getTimeToLive(key);
// 加1的目的 解决sa-token使用秒 redis是毫秒导致1秒的精度问题 手动补偿 return timeout < 0 ? timeout : timeout / 1000;
return timeout < 0 ? timeout : timeout / 1000 + 1;
} }
/** /**
@@ -109,19 +106,6 @@ public class PlusSaTokenDao implements SaTokenDaoBySessionFollowObject {
return o; return o;
} }
/**
* 获取 Object (指定反序列化类型),如无返空
*
* @param key 键名称
* @return object
*/
@SuppressWarnings("unchecked cast")
@Override
public <T> T getObject(String key, Class<T> classType) {
Object o = CAFFEINE.get(key, k -> RedisUtils.getCacheObject(key));
return (T) o;
}
/** /**
* 写入Object并设定存活时间 (单位: 秒) * 写入Object并设定存活时间 (单位: 秒)
*/ */
@@ -168,8 +152,7 @@ public class PlusSaTokenDao implements SaTokenDaoBySessionFollowObject {
@Override @Override
public long getObjectTimeout(String key) { public long getObjectTimeout(String key) {
long timeout = RedisUtils.getTimeToLive(key); long timeout = RedisUtils.getTimeToLive(key);
// 加1的目的 解决sa-token使用秒 redis是毫秒导致1秒的精度问题 手动补偿 return timeout < 0 ? timeout : timeout / 1000;
return timeout < 0 ? timeout : timeout / 1000 + 1;
} }
/** /**
@@ -180,6 +163,7 @@ public class PlusSaTokenDao implements SaTokenDaoBySessionFollowObject {
RedisUtils.expire(key, Duration.ofSeconds(timeout)); RedisUtils.expire(key, Duration.ofSeconds(timeout));
} }
/** /**
* 搜索数据 * 搜索数据
*/ */

View File

@@ -1,8 +1,8 @@
package org.dromara.common.satoken.utils; package org.dromara.common.satoken.utils;
import cn.dev33.satoken.session.SaSession; import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.StpUtil; import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert; import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
@@ -47,8 +47,8 @@ public class LoginHelper {
* @param loginUser 登录用户信息 * @param loginUser 登录用户信息
* @param model 配置参数 * @param model 配置参数
*/ */
public static void login(LoginUser loginUser, SaLoginParameter model) { public static void login(LoginUser loginUser, SaLoginModel model) {
model = ObjectUtil.defaultIfNull(model, new SaLoginParameter()); model = ObjectUtil.defaultIfNull(model, new SaLoginModel());
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())
@@ -63,25 +63,23 @@ public class LoginHelper {
/** /**
* 获取用户(多级缓存) * 获取用户(多级缓存)
*/ */
@SuppressWarnings("unchecked cast") public static LoginUser getLoginUser() {
public static <T extends LoginUser> T getLoginUser() {
SaSession session = StpUtil.getTokenSession(); SaSession session = StpUtil.getTokenSession();
if (ObjectUtil.isNull(session)) { if (ObjectUtil.isNull(session)) {
return null; return null;
} }
return (T) session.get(LOGIN_USER_KEY); return (LoginUser) session.get(LOGIN_USER_KEY);
} }
/** /**
* 获取用户基于token * 获取用户基于token
*/ */
@SuppressWarnings("unchecked cast") public static LoginUser getLoginUser(String token) {
public static <T extends LoginUser> T getLoginUser(String token) {
SaSession session = StpUtil.getTokenSessionByToken(token); SaSession session = StpUtil.getTokenSessionByToken(token);
if (ObjectUtil.isNull(session)) { if (ObjectUtil.isNull(session)) {
return null; return null;
} }
return (T) session.get(LOGIN_USER_KEY); return (LoginUser) session.get(LOGIN_USER_KEY);
} }
/** /**
@@ -193,11 +191,7 @@ public class LoginHelper {
* @return 结果 * @return 结果
*/ */
public static boolean isTenantAdmin() { public static boolean isTenantAdmin() {
LoginUser loginUser = getLoginUser(); return Convert.toBool(isTenantAdmin(getLoginUser().getRolePermission()));
if (loginUser == null) {
return false;
}
return Convert.toBool(isTenantAdmin(loginUser.getRolePermission()));
} }
/** /**

View File

@@ -11,13 +11,13 @@ import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.constant.HttpStatus; import org.dromara.common.core.constant.HttpStatus;
import org.dromara.common.core.exception.SseException;
import org.dromara.common.core.utils.ServletUtils; import org.dromara.common.core.utils.ServletUtils;
import org.dromara.common.core.utils.SpringUtils; import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.core.utils.StringUtils; import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.satoken.utils.LoginHelper; import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.common.security.config.properties.SecurityProperties; import org.dromara.common.security.config.properties.SecurityProperties;
import org.dromara.common.security.handler.AllUrlHandler; import org.dromara.common.security.handler.AllUrlHandler;
import org.springframework.beans.factory.annotation.Value;
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.context.annotation.Bean; import org.springframework.context.annotation.Bean;
@@ -37,8 +37,6 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
public class SecurityConfig implements WebMvcConfigurer { public class SecurityConfig implements WebMvcConfigurer {
private final SecurityProperties securityProperties; private final SecurityProperties securityProperties;
@Value("${sse.path}")
private String ssePath;
/** /**
* 注册sa-token的拦截器 * 注册sa-token的拦截器
@@ -56,7 +54,15 @@ public class SecurityConfig implements WebMvcConfigurer {
.check(() -> { .check(() -> {
HttpServletRequest request = ServletUtils.getRequest(); HttpServletRequest request = ServletUtils.getRequest();
// 检查是否登录 是否有token // 检查是否登录 是否有token
try {
StpUtil.checkLogin(); StpUtil.checkLogin();
} catch (NotLoginException e) {
if (request.getRequestURI().contains("sse")) {
throw new SseException(e.getMessage(), e.getCode());
} else {
throw e;
}
}
// 检查 header 与 param 里的 clientid 与 token 里的是否一致 // 检查 header 与 param 里的 clientid 与 token 里的是否一致
String headerCid = request.getHeader(LoginHelper.CLIENT_KEY); String headerCid = request.getHeader(LoginHelper.CLIENT_KEY);
@@ -78,8 +84,7 @@ public class SecurityConfig implements WebMvcConfigurer {
}); });
})).addPathPatterns("/**") })).addPathPatterns("/**")
// 排除不需要拦截的路径 // 排除不需要拦截的路径
.excludePathPatterns(securityProperties.getExcludes()) .excludePathPatterns(securityProperties.getExcludes());
.excludePathPatterns(ssePath);
} }
/** /**

View File

@@ -33,7 +33,6 @@ public class SseController implements DisposableBean {
*/ */
@GetMapping(value = "${sse.path}", produces = MediaType.TEXT_EVENT_STREAM_VALUE) @GetMapping(value = "${sse.path}", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter connect() { public SseEmitter connect() {
StpUtil.checkLogin();
String tokenValue = StpUtil.getTokenValue(); String tokenValue = StpUtil.getTokenValue();
Long userId = LoginHelper.getUserId(); Long userId = LoginHelper.getUserId();
return sseEmitterManager.connect(userId, tokenValue); return sseEmitterManager.connect(userId, tokenValue);

View File

@@ -44,24 +44,9 @@ public class SseEmitterManager {
emitters.put(token, emitter); emitters.put(token, emitter);
// 当 emitter 完成、超时或发生错误时,从映射表中移除对应的 token // 当 emitter 完成、超时或发生错误时,从映射表中移除对应的 token
emitter.onCompletion(() -> { emitter.onCompletion(() -> emitters.remove(token));
SseEmitter remove = emitters.remove(token); emitter.onTimeout(() -> emitters.remove(token));
if (remove != null) { emitter.onError((e) -> emitters.remove(token));
remove.complete();
}
});
emitter.onTimeout(() -> {
SseEmitter remove = emitters.remove(token);
if (remove != null) {
remove.complete();
}
});
emitter.onError((e) -> {
SseEmitter remove = emitters.remove(token);
if (remove != null) {
remove.complete();
}
});
try { try {
// 向客户端发送一条连接成功的事件 // 向客户端发送一条连接成功的事件
@@ -80,9 +65,6 @@ public class SseEmitterManager {
* @param token 用户的唯一令牌,用于识别具体的连接 * @param token 用户的唯一令牌,用于识别具体的连接
*/ */
public void disconnect(Long userId, String token) { public void disconnect(Long userId, String token) {
if (userId == null || token == null) {
return;
}
Map<String, SseEmitter> emitters = USER_TOKEN_EMITTERS.get(userId); Map<String, SseEmitter> emitters = USER_TOKEN_EMITTERS.get(userId);
if (MapUtil.isNotEmpty(emitters)) { if (MapUtil.isNotEmpty(emitters)) {
try { try {
@@ -121,10 +103,7 @@ public class SseEmitterManager {
.name("message") .name("message")
.data(message)); .data(message));
} catch (Exception e) { } catch (Exception e) {
SseEmitter remove = emitters.remove(entry.getKey()); emitters.remove(entry.getKey());
if (remove != null) {
remove.complete();
}
} }
} }
} else { } else {

View File

@@ -26,7 +26,7 @@ public class SseMessageUtils {
} }
/** /**
* 向指定的SSE会话发送消息 * 向指定的WebSocket会话发送消息
* *
* @param userId 要发送消息的用户id * @param userId 要发送消息的用户id
* @param message 要发送的消息内容 * @param message 要发送的消息内容

View File

@@ -81,17 +81,6 @@ public class TenantSaTokenDao extends PlusSaTokenDao {
return super.getObject(GlobalConstants.GLOBAL_REDIS_KEY + key); return super.getObject(GlobalConstants.GLOBAL_REDIS_KEY + key);
} }
/**
* 获取 Object (指定反序列化类型),如无返空
*
* @param key 键名称
* @return object
*/
@Override
public <T> T getObject(String key, Class<T> classType) {
return super.getObject(GlobalConstants.GLOBAL_REDIS_KEY + key, classType);
}
/** /**
* 写入Object并设定存活时间 (单位: 秒) * 写入Object并设定存活时间 (单位: 秒)
*/ */
@@ -148,6 +137,7 @@ public class TenantSaTokenDao extends PlusSaTokenDao {
RedisUtils.expire(GlobalConstants.GLOBAL_REDIS_KEY + key, Duration.ofSeconds(timeout)); RedisUtils.expire(GlobalConstants.GLOBAL_REDIS_KEY + key, Duration.ofSeconds(timeout));
} }
/** /**
* 搜索数据 * 搜索数据
*/ */

View File

@@ -48,7 +48,7 @@ public class PlusTenantLineHandler implements TenantLineHandler {
"gen_table_column" "gen_table_column"
); );
tables.addAll(excludes); tables.addAll(excludes);
return StringUtils.containsAnyIgnoreCase(tableName, tables.toArray(new String[0])); return tables.contains(tableName);
} }
return true; return true;
} }

View File

@@ -59,6 +59,9 @@
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId> <artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version> <version>${spring-boot.version}</version>
<configuration>
<!-- <fork>true</fork> &lt;!&ndash; 如果没有该配置devtools不会生效 &ndash;&gt;-->
</configuration>
<executions> <executions>
<execution> <execution>
<goals> <goals>

View File

@@ -19,7 +19,6 @@ spring:
admin: admin:
ui: ui:
title: RuoYi-Vue-Plus服务监控中心 title: RuoYi-Vue-Plus服务监控中心
context-path: /admin
--- # Actuator 监控端点的配置项 --- # Actuator 监控端点的配置项
management: management:
@@ -38,7 +37,7 @@ spring.boot.admin.client:
# 增加客户端开关 # 增加客户端开关
enabled: true enabled: true
# 设置 Spring Boot Admin Server 地址 # 设置 Spring Boot Admin Server 地址
url: http://localhost:9090/admin url: http://localhost:9090
instance: instance:
service-host-type: IP service-host-type: IP
metadata: metadata:

View File

@@ -44,7 +44,7 @@ public class ActuatorAuthFilter implements Filter {
return; return;
} }
// 验证用户名和密码 // 验证用户名和密码
if (!username.equals(split[0]) || !password.equals(split[1])) { if (!username.equals(split[0]) && password.equals(split[1])) {
response.setHeader("WWW-Authenticate", "Basic realm=\"realm\""); response.setHeader("WWW-Authenticate", "Basic realm=\"realm\"");
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized"); response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
return; return;

View File

@@ -22,15 +22,28 @@ snail-job:
job-pull-page-size: 1000 job-pull-page-size: 1000
# 服务器端口 # 服务器端口
server-port: 17888 server-port: 17888
# 一个客户端每秒最多接收的重试数量指令
limiter: 1000
# 号段模式下步长配置
step: 100
# 日志保存时间(单位: day) # 日志保存时间(单位: day)
log-storage: 7 log-storage: 90
# 回调配置
callback:
#回调最大执行次数
max-count: 288
#间隔时间
trigger-interval: 900
# 重试每次拉取的次数
retry-max-pull-count: 10
# RPC通讯类型: netty,grpc
rpc-type: grpc rpc-type: grpc
--- # 监控中心配置 --- # 监控中心配置
spring.boot.admin.client: spring.boot.admin.client:
# 增加客户端开关 # 增加客户端开关
enabled: true enabled: true
url: http://localhost:9090/admin url: http://localhost:9090
instance: instance:
service-host-type: IP service-host-type: IP
metadata: metadata:

View File

@@ -22,15 +22,28 @@ snail-job:
job-pull-page-size: 1000 job-pull-page-size: 1000
# 服务器端口 # 服务器端口
server-port: 17888 server-port: 17888
# 一个客户端每秒最多接收的重试数量指令
limiter: 1000
# 号段模式下步长配置
step: 100
# 日志保存时间(单位: day) # 日志保存时间(单位: day)
log-storage: 7 log-storage: 90
# 回调配置
callback:
#回调最大执行次数
max-count: 288
#间隔时间
trigger-interval: 900
# 重试每次拉取的次数
retry-max-pull-count: 10
# RPC通讯类型: netty,grpc
rpc-type: grpc rpc-type: grpc
--- # 监控中心配置 --- # 监控中心配置
spring.boot.admin.client: spring.boot.admin.client:
# 增加客户端开关 # 增加客户端开关
enabled: true enabled: true
url: http://localhost:9090/admin url: http://localhost:9090
instance: instance:
service-host-type: IP service-host-type: IP
metadata: metadata:

View File

@@ -40,7 +40,7 @@ public class RedisCacheController {
* <p> * <p>
* cacheNames 命名规则 查看 {@link CacheNames} 注释 支持多参数 * cacheNames 命名规则 查看 {@link CacheNames} 注释 支持多参数
*/ */
@Cacheable(cacheNames = "demo:cache#60s#10m#20#1", key = "#key", condition = "#key != null") @Cacheable(cacheNames = "demo:cache#60s#10m#20", key = "#key", condition = "#key != null")
@GetMapping("/test1") @GetMapping("/test1")
public R<String> test1(String key, String value) { public R<String> test1(String key, String value) {
return R.ok("操作成功", value); return R.ok("操作成功", value);

View File

@@ -1,6 +1,6 @@
package org.dromara.demo.domain.bo; package org.dromara.demo.domain.bo;
import cn.idev.excel.annotation.ExcelProperty; import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data; import lombok.Data;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;

View File

@@ -1,7 +1,7 @@
package org.dromara.demo.domain.vo; package org.dromara.demo.domain.vo;
import cn.idev.excel.annotation.ExcelIgnoreUnannotated; import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty; import com.alibaba.excel.annotation.ExcelProperty;
import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;

View File

@@ -1,7 +1,7 @@
package org.dromara.demo.domain.vo; package org.dromara.demo.domain.vo;
import cn.idev.excel.annotation.ExcelIgnoreUnannotated; import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty; import com.alibaba.excel.annotation.ExcelProperty;
import org.dromara.common.excel.annotation.ExcelNotation; import org.dromara.common.excel.annotation.ExcelNotation;
import org.dromara.common.excel.annotation.ExcelRequired; import org.dromara.common.excel.annotation.ExcelRequired;
import org.dromara.common.translation.annotation.Translation; import org.dromara.common.translation.annotation.Translation;

View File

@@ -1,7 +1,7 @@
package org.dromara.demo.domain.vo; package org.dromara.demo.domain.vo;
import cn.idev.excel.annotation.ExcelIgnoreUnannotated; import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty; import com.alibaba.excel.annotation.ExcelProperty;
import org.dromara.demo.domain.TestTree; import org.dromara.demo.domain.TestTree;
import io.github.linpeilie.annotations.AutoMapper; import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data; import lombok.Data;

View File

@@ -1,7 +1,7 @@
package org.dromara.demo.listener; package org.dromara.demo.listener;
import cn.hutool.core.util.NumberUtil; import cn.hutool.core.util.NumberUtil;
import cn.idev.excel.context.AnalysisContext; import com.alibaba.excel.context.AnalysisContext;
import org.dromara.common.core.utils.ValidatorUtils; import org.dromara.common.core.utils.ValidatorUtils;
import org.dromara.common.core.validate.AddGroup; import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup; import org.dromara.common.core.validate.EditGroup;

View File

@@ -4,8 +4,8 @@ package ${packageName}.domain.vo;
import ${import}; import ${import};
#end #end
import ${packageName}.domain.${ClassName}; import ${packageName}.domain.${ClassName};
import cn.idev.excel.annotation.ExcelIgnoreUnannotated; import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty; import com.alibaba.excel.annotation.ExcelProperty;
import org.dromara.common.excel.annotation.ExcelDictFormat; import org.dromara.common.excel.annotation.ExcelDictFormat;
import org.dromara.common.excel.convert.ExcelDictConvert; import org.dromara.common.excel.convert.ExcelDictConvert;
import io.github.linpeilie.annotations.AutoMapper; import io.github.linpeilie.annotations.AutoMapper;

View File

@@ -1,30 +0,0 @@
package org.dromara.job.entity;
import lombok.Data;
import java.math.BigDecimal;
@Data
public class BillDto {
/**
* 账单ID
*/
private Long billId;
/**
* 账单渠道
*/
private String billChannel;
/**
* 账单日期
*/
private String billDate;
/**
* 账单金额
*/
private BigDecimal billAmount;
}

View File

@@ -1,42 +0,0 @@
package org.dromara.job.snailjob;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.aizuda.snailjob.client.job.core.annotation.JobExecutor;
import com.aizuda.snailjob.client.job.core.dto.JobArgs;
import com.aizuda.snailjob.client.model.ExecuteResult;
import com.aizuda.snailjob.common.log.SnailJobLog;
import org.dromara.common.json.utils.JsonUtils;
import org.dromara.job.entity.BillDto;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
/**
* DAG工作流任务-模拟支付宝账单任务
* <a href="https://juejin.cn/post/7487860254114644019"></a>
*
* @author 老马
*/
@Component
@JobExecutor(name = "alipayBillTask")
public class AlipayBillTask {
public ExecuteResult jobExecute(JobArgs jobArgs) throws InterruptedException {
BillDto billDto = new BillDto();
billDto.setBillId(23456789L);
billDto.setBillChannel("alipay");
// 设置清算日期
String settlementDate = (String) jobArgs.getWfContext().get("settlementDate");
if (StrUtil.equals(settlementDate, "sysdate")) {
settlementDate = DateUtil.today();
}
billDto.setBillDate(settlementDate);
billDto.setBillAmount(new BigDecimal("2345.67"));
// 把billDto对象放入上下文进行传递
jobArgs.appendContext("alipay", JsonUtils.toJsonString(billDto));
SnailJobLog.REMOTE.info("上下文: {}", jobArgs.getWfContext());
return ExecuteResult.success(billDto);
}
}

View File

@@ -1,45 +0,0 @@
package org.dromara.job.snailjob;
import cn.hutool.core.util.StrUtil;
import com.aizuda.snailjob.client.job.core.annotation.JobExecutor;
import com.aizuda.snailjob.client.job.core.dto.JobArgs;
import com.aizuda.snailjob.client.model.ExecuteResult;
import com.aizuda.snailjob.common.log.SnailJobLog;
import org.dromara.common.json.utils.JsonUtils;
import org.dromara.job.entity.BillDto;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
/**
* DAG工作流任务-模拟汇总账单任务
* <a href="https://juejin.cn/post/7487860254114644019"></a>
*
* @author 老马
*/
@Component
@JobExecutor(name = "summaryBillTask")
public class SummaryBillTask {
public ExecuteResult jobExecute(JobArgs jobArgs) throws InterruptedException {
// 获得微信账单
BigDecimal wechatAmount = BigDecimal.valueOf(0);
String wechat = (String) jobArgs.getWfContext("wechat");
if (StrUtil.isNotBlank(wechat)) {
BillDto wechatBillDto = JsonUtils.parseObject(wechat, BillDto.class);
wechatAmount = wechatBillDto.getBillAmount();
}
// 获得支付宝账单
BigDecimal alipayAmount = BigDecimal.valueOf(0);
String alipay = (String) jobArgs.getWfContext("alipay");
if (StrUtil.isNotBlank(alipay)) {
BillDto alipayBillDto = JsonUtils.parseObject(alipay, BillDto.class);
alipayAmount = alipayBillDto.getBillAmount();
}
// 汇总账单
BigDecimal totalAmount = wechatAmount.add(alipayAmount);
SnailJobLog.REMOTE.info("总金额: {}", totalAmount);
return ExecuteResult.success(totalAmount);
}
}

View File

@@ -8,10 +8,8 @@ import com.aizuda.snailjob.common.log.SnailJobLog;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
/** /**
* 正常任务 * @author opensnail
* <a href="https://juejin.cn/post/7418074037392293914"></a> * @date 2024-05-17
*
* @author 老马
*/ */
@Component @Component
@JobExecutor(name = "testJobExecutor") @JobExecutor(name = "testJobExecutor")

View File

@@ -1,37 +0,0 @@
package org.dromara.job.snailjob;
import cn.hutool.core.util.RandomUtil;
import com.aizuda.snailjob.client.job.core.annotation.JobExecutor;
import com.aizuda.snailjob.client.job.core.dto.JobArgs;
import com.aizuda.snailjob.client.model.ExecuteResult;
import com.aizuda.snailjob.common.log.SnailJobLog;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
* 广播任务
* <a href="https://juejin.cn/post/7422948006150438950"></a>
*
* @author 老马
*/
@Slf4j
@Component
@JobExecutor(name = "testBroadcastJob")
public class TestBroadcastJob {
@Value("${snail-job.port}")
private int clientPort;
public ExecuteResult jobExecute(JobArgs jobArgs) {
int randomInt = RandomUtil.randomInt(100);
log.info("随机数: {}", randomInt);
SnailJobLog.REMOTE.info("随机数: {},客户端端口:{}", randomInt, clientPort);
if (randomInt < 50) {
throw new RuntimeException("随机数小于50收集日志任务执行失败");
}
// 获得jobArgs 中传入的相加的两个数
return ExecuteResult.success("随机数大于50收集日志任务执行成功");
}
}

View File

@@ -1,53 +0,0 @@
package org.dromara.job.snailjob;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.aizuda.snailjob.client.job.core.MapHandler;
import com.aizuda.snailjob.client.job.core.annotation.JobExecutor;
import com.aizuda.snailjob.client.job.core.annotation.MapExecutor;
import com.aizuda.snailjob.client.job.core.dto.MapArgs;
import com.aizuda.snailjob.client.model.ExecuteResult;
import com.aizuda.snailjob.common.log.SnailJobLog;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
/**
* Map任务 动态分配 只分片不关注结果
* <a href="https://juejin.cn/post/7446362500478894106"></a>
*
* @author 老马
*/
@Component
@JobExecutor(name = "testMapJobAnnotation")
public class TestMapJobAnnotation {
@MapExecutor
public ExecuteResult doJobMapExecute(MapArgs mapArgs, MapHandler mapHandler) {
// 生成1~200数值并分片
int partitionSize = 50;
List<List<Integer>> partition = IntStream.rangeClosed(1, 200)
.boxed()
.collect(Collectors.groupingBy(i -> (i - 1) / partitionSize))
.values()
.stream()
.toList();
SnailJobLog.REMOTE.info("端口:{}完成分配任务", SpringUtil.getProperty("server.port"));
return mapHandler.doMap(partition, "doCalc");
}
@MapExecutor(taskName = "doCalc")
public ExecuteResult doCalc(MapArgs mapArgs) {
List<Integer> sourceList = (List<Integer>) mapArgs.getMapResult();
// 遍历sourceList的每一个元素,计算出一个累加值partitionTotal
int partitionTotal = sourceList.stream().mapToInt(i -> i).sum();
// 打印日志到服务器
ThreadUtil.sleep(3, TimeUnit.SECONDS);
SnailJobLog.REMOTE.info("端口:{},partitionTotal:{}", SpringUtil.getProperty("server.port"), partitionTotal);
return ExecuteResult.success(partitionTotal);
}
}

View File

@@ -1,60 +0,0 @@
package org.dromara.job.snailjob;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.aizuda.snailjob.client.job.core.MapHandler;
import com.aizuda.snailjob.client.job.core.annotation.JobExecutor;
import com.aizuda.snailjob.client.job.core.annotation.MapExecutor;
import com.aizuda.snailjob.client.job.core.annotation.ReduceExecutor;
import com.aizuda.snailjob.client.job.core.dto.MapArgs;
import com.aizuda.snailjob.client.job.core.dto.ReduceArgs;
import com.aizuda.snailjob.client.model.ExecuteResult;
import com.aizuda.snailjob.common.log.SnailJobLog;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
/**
* MapReduce任务 动态分配 分片后合并结果
* <a href="https://juejin.cn/post/7448551286506913802"></a>
*
* @author 老马
*/
@Component
@JobExecutor(name = "testMapReduceAnnotation1")
public class TestMapReduceAnnotation1 {
@MapExecutor
public ExecuteResult rootMapExecute(MapArgs mapArgs, MapHandler mapHandler) {
int partitionSize = 50;
List<List<Integer>> partition = IntStream.rangeClosed(1, 200)
.boxed()
.collect(Collectors.groupingBy(i -> (i - 1) / partitionSize))
.values()
.stream()
.toList();
SnailJobLog.REMOTE.info("端口:{}完成分配任务", SpringUtil.getProperty("server.port"));
return mapHandler.doMap(partition, "doCalc");
}
@MapExecutor(taskName = "doCalc")
public ExecuteResult doCalc(MapArgs mapArgs) {
List<Integer> sourceList = (List<Integer>) mapArgs.getMapResult();
// 遍历sourceList的每一个元素,计算出一个累加值partitionTotal
int partitionTotal = sourceList.stream().mapToInt(i -> i).sum();
// 打印日志到服务器
ThreadUtil.sleep(3, TimeUnit.SECONDS);
SnailJobLog.REMOTE.info("端口:{},partitionTotal:{}", SpringUtil.getProperty("server.port"), partitionTotal);
return ExecuteResult.success(partitionTotal);
}
@ReduceExecutor
public ExecuteResult reduceExecute(ReduceArgs reduceArgs) {
int reduceTotal = reduceArgs.getMapResult().stream().mapToInt(i -> Integer.parseInt((String) i)).sum();
SnailJobLog.REMOTE.info("端口:{},reduceTotal:{}", SpringUtil.getProperty("server.port"), reduceTotal);
return ExecuteResult.success(reduceTotal);
}
}

View File

@@ -1,36 +0,0 @@
package org.dromara.job.snailjob;
import com.aizuda.snailjob.client.job.core.annotation.JobExecutor;
import com.aizuda.snailjob.client.job.core.dto.JobArgs;
import com.aizuda.snailjob.client.model.ExecuteResult;
import com.aizuda.snailjob.common.log.SnailJobLog;
import org.springframework.stereotype.Component;
/**
* 静态分片 根据服务端任务参数分片
* <a href="https://juejin.cn/post/7426232375703896101"></a>
*
* @author 老马
*/
@Component
@JobExecutor(name = "testStaticShardingJob")
public class TestStaticShardingJob {
public ExecuteResult jobExecute(JobArgs jobArgs) {
String jobParams = String.valueOf(jobArgs.getJobParams());
SnailJobLog.LOCAL.info("开始执行分片任务,参数:{}", jobParams);
// 获得jobArgs 中传入的开始id和结束id
String[] split = jobParams.split(",");
Long fromId = Long.parseLong(split[0]);
Long toId = Long.parseLong(split[1]);
// 模拟数据库操作,对范围id,进行加密处理
try {
SnailJobLog.REMOTE.info("开始对id范围:{}进行加密处理", fromId + "-" + toId);
Thread.sleep(3000);
SnailJobLog.REMOTE.info("对id范围:{}进行加密处理完成", fromId + "-" + toId);
} catch (InterruptedException e) {
return ExecuteResult.failure("任务执行失败");
}
return ExecuteResult.success("执行分片任务完成");
}
}

View File

@@ -1,43 +0,0 @@
package org.dromara.job.snailjob;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.aizuda.snailjob.client.job.core.annotation.JobExecutor;
import com.aizuda.snailjob.client.job.core.dto.JobArgs;
import com.aizuda.snailjob.client.model.ExecuteResult;
import com.aizuda.snailjob.common.log.SnailJobLog;
import org.dromara.common.json.utils.JsonUtils;
import org.dromara.job.entity.BillDto;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
/**
* DAG工作流任务-模拟微信账单任务
* <a href="https://juejin.cn/post/7487860254114644019"></a>
*
* @author 老马
*/
@Component
@JobExecutor(name = "wechatBillTask")
public class WechatBillTask {
public ExecuteResult jobExecute(JobArgs jobArgs) throws InterruptedException {
BillDto billDto = new BillDto();
billDto.setBillId(123456789L);
billDto.setBillChannel("wechat");
// 从上下文中获得清算日期并设置,如果上下文中清算日期
// 是sysdate设置为当前日期否则取管理页面设置的值
String settlementDate = (String) jobArgs.getWfContext().get("settlementDate");
if (StrUtil.equals(settlementDate, "sysdate")) {
settlementDate = DateUtil.today();
}
billDto.setBillDate(settlementDate);
billDto.setBillAmount(new BigDecimal("1234.56"));
// 把billDto对象放入上下文进行传递
jobArgs.appendContext("wechat", JsonUtils.toJsonString(billDto));
SnailJobLog.REMOTE.info("上下文: {}", jobArgs.getWfContext());
return ExecuteResult.success(billDto);
}
}

View File

@@ -1,9 +1,10 @@
package org.dromara.system.controller.monitor; package org.dromara.system.controller.monitor;
import cn.dev33.satoken.annotation.SaCheckPermission; import cn.dev33.satoken.annotation.SaCheckPermission;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.domain.R; import org.dromara.common.core.domain.R;
import org.dromara.common.core.utils.StringUtils; import org.dromara.common.core.utils.StringUtils;
import org.dromara.system.domain.vo.CacheListInfoVo;
import lombok.RequiredArgsConstructor;
import org.redisson.spring.data.connection.RedissonConnectionFactory; import org.redisson.spring.data.connection.RedissonConnectionFactory;
import org.springframework.data.redis.connection.RedisConnection; import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
@@ -44,11 +45,11 @@ public class CacheController {
}); });
} }
return R.ok(new CacheListInfoVo( CacheListInfoVo infoVo = new CacheListInfoVo();
connection.commands().info(), infoVo.setInfo(connection.commands().info());
connection.commands().dbSize(), pieList)); infoVo.setDbSize(connection.commands().dbSize());
infoVo.setCommandStats(pieList);
return R.ok(infoVo);
} }
public record CacheListInfoVo(Properties info, Long dbSize, List<Map<String, String>> commandStats) {}
} }

View File

@@ -15,6 +15,7 @@ import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.common.web.core.BaseController; import org.dromara.common.web.core.BaseController;
import org.dromara.system.domain.SysMenu; import org.dromara.system.domain.SysMenu;
import org.dromara.system.domain.bo.SysMenuBo; import org.dromara.system.domain.bo.SysMenuBo;
import org.dromara.system.domain.vo.MenuTreeSelectVo;
import org.dromara.system.domain.vo.RouterVo; import org.dromara.system.domain.vo.RouterVo;
import org.dromara.system.domain.vo.SysMenuVo; import org.dromara.system.domain.vo.SysMenuVo;
import org.dromara.system.service.ISysMenuService; import org.dromara.system.service.ISysMenuService;
@@ -95,9 +96,9 @@ public class SysMenuController extends BaseController {
@GetMapping(value = "/roleMenuTreeselect/{roleId}") @GetMapping(value = "/roleMenuTreeselect/{roleId}")
public R<MenuTreeSelectVo> roleMenuTreeselect(@PathVariable("roleId") Long roleId) { public R<MenuTreeSelectVo> roleMenuTreeselect(@PathVariable("roleId") Long roleId) {
List<SysMenuVo> menus = menuService.selectMenuList(LoginHelper.getUserId()); List<SysMenuVo> menus = menuService.selectMenuList(LoginHelper.getUserId());
MenuTreeSelectVo selectVo = new MenuTreeSelectVo( MenuTreeSelectVo selectVo = new MenuTreeSelectVo();
menuService.selectMenuListByRoleId(roleId), selectVo.setCheckedKeys(menuService.selectMenuListByRoleId(roleId));
menuService.buildMenuTreeSelect(menus)); selectVo.setMenus(menuService.buildMenuTreeSelect(menus));
return R.ok(selectVo); return R.ok(selectVo);
} }
@@ -111,9 +112,9 @@ public class SysMenuController extends BaseController {
@GetMapping(value = "/tenantPackageMenuTreeselect/{packageId}") @GetMapping(value = "/tenantPackageMenuTreeselect/{packageId}")
public R<MenuTreeSelectVo> tenantPackageMenuTreeselect(@PathVariable("packageId") Long packageId) { public R<MenuTreeSelectVo> tenantPackageMenuTreeselect(@PathVariable("packageId") Long packageId) {
List<SysMenuVo> menus = menuService.selectMenuList(LoginHelper.getUserId()); List<SysMenuVo> menus = menuService.selectMenuList(LoginHelper.getUserId());
MenuTreeSelectVo selectVo = new MenuTreeSelectVo( MenuTreeSelectVo selectVo = new MenuTreeSelectVo();
menuService.selectMenuListByPackageId(packageId), selectVo.setCheckedKeys(menuService.selectMenuListByPackageId(packageId));
menuService.buildMenuTreeSelect(menus)); selectVo.setMenus(menuService.buildMenuTreeSelect(menus));
return R.ok(selectVo); return R.ok(selectVo);
} }
@@ -170,7 +171,4 @@ public class SysMenuController extends BaseController {
return toAjax(menuService.deleteMenuById(menuId)); return toAjax(menuService.deleteMenuById(menuId));
} }
public record MenuTreeSelectVo(List<Long> checkedKeys, List<Tree<Long>> menus) {
}
} }

View File

@@ -1,8 +1,8 @@
package org.dromara.system.controller.system; package org.dromara.system.controller.system;
import cn.dev33.satoken.secure.BCrypt;
import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.FileUtil;
import cn.hutool.crypto.digest.BCrypt;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.dromara.common.core.domain.R; import org.dromara.common.core.domain.R;
import org.dromara.common.core.utils.StringUtils; import org.dromara.common.core.utils.StringUtils;
@@ -17,6 +17,8 @@ import org.dromara.common.web.core.BaseController;
import org.dromara.system.domain.bo.SysUserBo; import org.dromara.system.domain.bo.SysUserBo;
import org.dromara.system.domain.bo.SysUserPasswordBo; import org.dromara.system.domain.bo.SysUserPasswordBo;
import org.dromara.system.domain.bo.SysUserProfileBo; import org.dromara.system.domain.bo.SysUserProfileBo;
import org.dromara.system.domain.vo.AvatarVo;
import org.dromara.system.domain.vo.ProfileVo;
import org.dromara.system.domain.vo.SysOssVo; import org.dromara.system.domain.vo.SysOssVo;
import org.dromara.system.domain.vo.SysUserVo; import org.dromara.system.domain.vo.SysUserVo;
import org.dromara.system.service.ISysOssService; import org.dromara.system.service.ISysOssService;
@@ -48,9 +50,10 @@ public class SysProfileController extends BaseController {
@GetMapping @GetMapping
public R<ProfileVo> profile() { public R<ProfileVo> profile() {
SysUserVo user = userService.selectUserById(LoginHelper.getUserId()); SysUserVo user = userService.selectUserById(LoginHelper.getUserId());
String roleGroup = userService.selectUserRoleGroup(user.getUserId()); ProfileVo profileVo = new ProfileVo();
String postGroup = userService.selectUserPostGroup(user.getUserId()); profileVo.setUser(user);
ProfileVo profileVo = new ProfileVo(user, roleGroup, postGroup); profileVo.setRoleGroup(userService.selectUserRoleGroup(user.getUserId()));
profileVo.setPostGroup(userService.selectUserPostGroup(user.getUserId()));
return R.ok(profileVo); return R.ok(profileVo);
} }
@@ -120,14 +123,11 @@ public class SysProfileController extends BaseController {
String avatar = oss.getUrl(); String avatar = oss.getUrl();
boolean updateSuccess = DataPermissionHelper.ignore(() -> userService.updateUserAvatar(LoginHelper.getUserId(), oss.getOssId())); boolean updateSuccess = DataPermissionHelper.ignore(() -> userService.updateUserAvatar(LoginHelper.getUserId(), oss.getOssId()));
if (updateSuccess) { if (updateSuccess) {
return R.ok(new AvatarVo(avatar)); AvatarVo avatarVo = new AvatarVo();
avatarVo.setImgUrl(avatar);
return R.ok(avatarVo);
} }
} }
return R.fail("上传图片异常,请联系管理员"); return R.fail("上传图片异常,请联系管理员");
} }
public record AvatarVo(String imgUrl) {}
public record ProfileVo(SysUserVo user, String roleGroup, String postGroup) {}
} }

View File

@@ -1,7 +1,6 @@
package org.dromara.system.controller.system; package org.dromara.system.controller.system;
import cn.dev33.satoken.annotation.SaCheckPermission; import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.hutool.core.lang.tree.Tree;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.dromara.common.core.domain.R; import org.dromara.common.core.domain.R;
@@ -15,6 +14,7 @@ import org.dromara.system.domain.SysUserRole;
import org.dromara.system.domain.bo.SysDeptBo; import org.dromara.system.domain.bo.SysDeptBo;
import org.dromara.system.domain.bo.SysRoleBo; import org.dromara.system.domain.bo.SysRoleBo;
import org.dromara.system.domain.bo.SysUserBo; import org.dromara.system.domain.bo.SysUserBo;
import org.dromara.system.domain.vo.DeptTreeSelectVo;
import org.dromara.system.domain.vo.SysRoleVo; import org.dromara.system.domain.vo.SysRoleVo;
import org.dromara.system.domain.vo.SysUserVo; import org.dromara.system.domain.vo.SysUserVo;
import org.dromara.system.service.ISysDeptService; import org.dromara.system.service.ISysDeptService;
@@ -221,12 +221,9 @@ public class SysRoleController extends BaseController {
@SaCheckPermission("system:role:list") @SaCheckPermission("system:role:list")
@GetMapping(value = "/deptTree/{roleId}") @GetMapping(value = "/deptTree/{roleId}")
public R<DeptTreeSelectVo> roleDeptTreeselect(@PathVariable("roleId") Long roleId) { public R<DeptTreeSelectVo> roleDeptTreeselect(@PathVariable("roleId") Long roleId) {
DeptTreeSelectVo selectVo = new DeptTreeSelectVo( DeptTreeSelectVo selectVo = new DeptTreeSelectVo();
deptService.selectDeptListByRoleId(roleId), selectVo.setCheckedKeys(deptService.selectDeptListByRoleId(roleId));
deptService.selectDeptTreeList(new SysDeptBo())); selectVo.setDepts(deptService.selectDeptTreeList(new SysDeptBo()));
return R.ok(selectVo); return R.ok(selectVo);
} }
public record DeptTreeSelectVo(List<Long> checkedKeys, List<Tree<Long>> depts) {}
} }

View File

@@ -1,10 +1,10 @@
package org.dromara.system.controller.system; package org.dromara.system.controller.system;
import cn.dev33.satoken.annotation.SaCheckPermission; import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.secure.BCrypt;
import cn.hutool.core.lang.tree.Tree; import cn.hutool.core.lang.tree.Tree;
import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
import cn.hutool.crypto.digest.BCrypt;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;

View File

@@ -1,6 +1,5 @@
package org.dromara.system.domain; package org.dromara.system.domain;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic; import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.TableName;
@@ -9,8 +8,6 @@ import lombok.EqualsAndHashCode;
import org.dromara.common.tenant.core.TenantEntity; import org.dromara.common.tenant.core.TenantEntity;
import java.io.Serial; import java.io.Serial;
import java.util.ArrayList;
import java.util.List;
/** /**
* 部门表 sys_dept * 部门表 sys_dept
@@ -83,10 +80,4 @@ public class SysDept extends TenantEntity {
*/ */
private String ancestors; private String ancestors;
/**
* 子部门
*/
@TableField(exist = false)
private List<SysDept> children = new ArrayList<>();
} }

View File

@@ -42,11 +42,6 @@ public class SysOss extends TenantEntity {
*/ */
private String url; private String url;
/**
* 扩展字段
*/
private String ext1;
/** /**
* 服务商 * 服务商
*/ */

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