20 Commits

Author SHA1 Message Date
d22b2a10df update 优化 PermissionService 无实现类也可以启动服务 2025-05-29 16:28:56 +08:00
957a4d1fcd fix 修复 监听器 flowParams 为null报错问题 2025-05-29 16:28:56 +08:00
49ef8378fe !691 发布 5.4.0 正式版
Merge pull request !691 from 疯狂的狮子Li/dev
2025-05-29 03:14:59 +00:00
57dd6831d3 !664 发布 5.3.1 正式版
Merge pull request !664 from 疯狂的狮子Li/dev
2025-03-27 02:54:00 +00:00
8aa60abb1f !663 回退 'Pull Request !662 : 发布 5.3.1 正式版'
* 回退 'Pull Request !662 : 发布 5.3.1 正式版'
2025-03-27 02:53:23 +00:00
7a9f51fc7a !662 发布 5.3.1 正式版
* 🐳发布 5.3.1 正式版
* update 优化 删除无用配置
* fix 修复 excel模板导出数据被覆盖的问题
* update 优化 统一用户密码校验长度
* update mybatis-plus 3.5.10.1 => 3.5.11
* fix 修复 跨域未设置请求头问题(cloud版本不需要 vue版本需要)
2025-03-27 02:51:57 +00:00
159e30c982 !661 发布 5.3.1-BETA2 公测版本
Merge pull request !661 from 疯狂的狮子Li/dev
2025-03-21 07:25:25 +00:00
7334d91d6b !652 发布 5.3.1-BETA 公测版本
Merge pull request !652 from 疯狂的狮子Li/dev
2025-03-13 05:27:36 +00:00
95c01301f6 !644 同步修复一些问题
Merge pull request !644 from 疯狂的狮子Li/dev
2025-02-07 06:19:28 +00:00
296466fa13 !640 发布 5.3.0 新春版 祝大家新年快乐
Merge pull request !640 from 疯狂的狮子Li/dev
2025-01-24 05:08:28 +00:00
3c8d864b5f !639 发布 5.3.0-BETA 公测版本
Merge pull request !639 from 疯狂的狮子Li/dev
2025-01-20 03:35:45 +00:00
ea50a57602 update 优化 xss包装器 Parameter 处理 兼容某些容器不允许改参数的情况 2024-11-21 10:17:34 +08:00
7e14b98676 reset 回滚错误修改
Signed-off-by: 疯狂的狮子Li <15040126243@163.com>
2024-10-28 09:46:28 +00:00
015b406001 !591 发布 5.2.3 正式版
Merge pull request !591 from 疯狂的狮子Li/dev
2024-10-25 03:09:23 +00:00
098d3347a0 !577 发布 5.2.2 正式版 安全性提升
Merge pull request !577 from 疯狂的狮子Li/dev
2024-08-26 03:43:59 +00:00
08d4493994 update 优化 bug 模板 2024-07-15 15:19:22 +08:00
367d739e2d Merge remote-tracking branch 'origin/5.X' into 5.X 2024-07-09 16:38:43 +08:00
d6688a367d !562 ♥️发布 5.2.1 正式版本
Merge pull request !562 from 疯狂的狮子Li/dev
2024-07-09 02:42:40 +00:00
0b331796e2 !551 ♥️发布 5.2.0 正式版本
Merge pull request !551 from 疯狂的狮子Li/dev
2024-06-20 02:10:15 +00:00
456620b638 !549 ♥️发布 5.2.0-BETA2 公测版本
Merge pull request !549 from 疯狂的狮子Li/dev
2024-06-06 03:13:46 +00:00
236 changed files with 2409 additions and 5497 deletions

View File

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

View File

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

View File

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

View File

@ -10,7 +10,7 @@
[![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://gitee.com/dromara/RuoYi-Vue-Plus/blob/5.X/LICENSE)
[![使用IntelliJ IDEA开发维护](https://img.shields.io/badge/IntelliJ%20IDEA-提供支持-blue.svg)](https://www.jetbrains.com/?from=RuoYi-Vue-Plus)
<br>
[![RuoYi-Vue-Plus](https://img.shields.io/badge/RuoYi_Vue_Plus-5.4.1-success.svg)](https://gitee.com/dromara/RuoYi-Vue-Plus)
[![RuoYi-Vue-Plus](https://img.shields.io/badge/RuoYi_Vue_Plus-5.4.0-success.svg)](https://gitee.com/dromara/RuoYi-Vue-Plus)
[![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-21](https://img.shields.io/badge/JDK-21-green.svg)]()
@ -27,7 +27,7 @@
> 成员前端项目地址: 基于soybean [ruoyi-plus-soybean](https://gitee.com/xlsea/ruoyi-plus-soybean)<br>
> 成员项目地址: 删除多租户与工作流 [RuoYi-Vue-Plus-Single](https://gitee.com/ColorDreams/RuoYi-Vue-Plus-Single)<br>
> 文档地址: [plus-doc](https://plus-doc.dromara.org) 国内加速: [plus-doc.top](https://plus-doc.top)
> 文档地址: [plus-doc](https://plus-doc.dromara.org) 文档在华为云上如果打不开大概率是DNS问题 可以尝试切换网络等方式(或者科学上网)
## 赞助商
@ -37,10 +37,7 @@ CCFlow 驰聘低代码-流程-表单 - https://gitee.com/opencc/RuoYi-JFlow <br>
引迈信息 软件开发平台 - https://www.jnpfsoft.com/index.html?from=plus-doc <br>
<font color="red">**启山商城系统 多租户商城源码可免费商用可二次开发 - https://www.73app.cn/** </font><br>
Mall4J 高质量Java商城系统 - https://www.mall4j.com/cn/?statId=11 <br>
aizuda flowlong 工作流 - https://gitee.com/aizuda/flowlong <br>
Ruoyi-Plus-Uniapp - https://ruoyi.plus <br>
[如何成为赞助商 加群联系作者详谈 每日PV2500-3000 IP1700-2500](https://plus-doc.dromara.org/#/common/add_group)
[如何成为赞助商 加群联系作者详谈](https://plus-doc.dromara.org/#/common/add_group)
# 本框架与RuoYi的功能差异

69
pom.xml
View File

@ -13,32 +13,33 @@
<description>Dromara RuoYi-Vue-Plus多租户管理系统</description>
<properties>
<revision>5.4.1</revision>
<spring-boot.version>3.5.4</spring-boot.version>
<revision>5.4.0</revision>
<spring-boot.version>3.4.6</spring-boot.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>17</java.version>
<mybatis.version>3.5.16</mybatis.version>
<springdoc.version>2.8.9</springdoc.version>
<springdoc.version>2.8.8</springdoc.version>
<therapi-javadoc.version>0.15.0</therapi-javadoc.version>
<fastexcel.version>1.2.0</fastexcel.version>
<velocity.version>2.3</velocity.version>
<satoken.version>1.44.0</satoken.version>
<satoken.version>1.42.0</satoken.version>
<mybatis-plus.version>3.5.12</mybatis-plus.version>
<p6spy.version>3.9.1</p6spy.version>
<hutool.version>5.8.38</hutool.version>
<spring-boot-admin.version>3.5.1</spring-boot-admin.version>
<redisson.version>3.50.0</redisson.version>
<hutool.version>5.8.35</hutool.version>
<spring-boot-admin.version>3.4.7</spring-boot-admin.version>
<redisson.version>3.45.1</redisson.version>
<lock4j.version>2.2.7</lock4j.version>
<dynamic-ds.version>4.3.1</dynamic-ds.version>
<snailjob.version>1.7.2</snailjob.version>
<snailjob.version>1.5.0</snailjob.version>
<mapstruct-plus.version>1.4.8</mapstruct-plus.version>
<mapstruct-plus.lombok.version>0.2.0</mapstruct-plus.lombok.version>
<lombok.version>1.18.38</lombok.version>
<lombok.version>1.18.36</lombok.version>
<bouncycastle.version>1.80</bouncycastle.version>
<justauth.version>1.16.7</justauth.version>
<!-- 离线IP地址定位库 -->
<ip2region.version>2.7.0</ip2region.version>
<!-- OSS 配置 -->
<aws.sdk.version>2.28.22</aws.sdk.version>
<!-- SMS 配置 -->
@ -46,15 +47,15 @@
<!-- 限制框架中的fastjson版本 -->
<fastjson.version>1.2.83</fastjson.version>
<!-- 面向运行时的D-ORM依赖 -->
<anyline.version>8.7.2-20250603</anyline.version>
<anyline.version>8.7.2-20250101</anyline.version>
<!-- 工作流配置 -->
<warm-flow.version>1.8.0</warm-flow.version>
<warm-flow.version>1.7.3</warm-flow.version>
<!-- 插件版本 -->
<maven-jar-plugin.version>3.4.2</maven-jar-plugin.version>
<maven-war-plugin.version>3.4.0</maven-war-plugin.version>
<maven-compiler-plugin.version>3.14.0</maven-compiler-plugin.version>
<maven-surefire-plugin.version>3.5.3</maven-surefire-plugin.version>
<maven-jar-plugin.version>3.2.2</maven-jar-plugin.version>
<maven-war-plugin.version>3.2.2</maven-war-plugin.version>
<maven-compiler-plugin.version>3.11.0</maven-compiler-plugin.version>
<maven-surefire-plugin.version>3.1.2</maven-surefire-plugin.version>
<flatten-maven-plugin.version>1.3.0</flatten-maven-plugin.version>
<!-- 打包默认跳过测试 -->
<skipTests>true</skipTests>
@ -118,6 +119,25 @@
<scope>import</scope>
</dependency>
<!-- Warm-Flow国产工作流引擎, 在线文档http://warm-flow.cn/ -->
<dependency>
<groupId>org.dromara.warm</groupId>
<artifactId>warm-flow-mybatis-plus-sb3-starter</artifactId>
<version>${warm-flow.version}</version>
</dependency>
<dependency>
<groupId>org.dromara.warm</groupId>
<artifactId>warm-flow-plugin-ui-sb-web</artifactId>
<version>${warm-flow.version}</version>
</dependency>
<!-- JustAuth 的依赖配置-->
<dependency>
<groupId>me.zhyd.oauth</groupId>
<artifactId>JustAuth</artifactId>
<version>${justauth.version}</version>
</dependency>
<!-- common 的依赖配置-->
<dependency>
<groupId>org.dromara</groupId>
@ -294,25 +314,6 @@
<version>${mapstruct-plus.version}</version>
</dependency>
<!-- Warm-Flow国产工作流引擎, 在线文档http://warm-flow.cn/ -->
<dependency>
<groupId>org.dromara.warm</groupId>
<artifactId>warm-flow-mybatis-plus-sb3-starter</artifactId>
<version>${warm-flow.version}</version>
</dependency>
<dependency>
<groupId>org.dromara.warm</groupId>
<artifactId>warm-flow-plugin-ui-sb-web</artifactId>
<version>${warm-flow.version}</version>
</dependency>
<!-- JustAuth 的依赖配置-->
<dependency>
<groupId>me.zhyd.oauth</groupId>
<artifactId>JustAuth</artifactId>
<version>${justauth.version}</version>
</dependency>
<!-- 离线IP地址定位库 ip2region -->
<dependency>
<groupId>org.lionsoul</groupId>

View File

@ -1,6 +1,6 @@
# 贝尔实验室 Spring 官方推荐镜像 JDK下载地址 https://bell-sw.com/pages/downloads/
FROM bellsoft/liberica-openjdk-rocky:17.0.16-cds
#FROM bellsoft/liberica-openjdk-rocky:21.0.8-cds
FROM bellsoft/liberica-openjdk-debian:17.0.11-cds
#FROM bellsoft/liberica-openjdk-debian:21.0.5-cds
#FROM findepi/graalvm:java17-native
LABEL maintainer="Lion Li"

View File

@ -21,8 +21,6 @@ import org.dromara.common.core.domain.model.SocialLoginBody;
import org.dromara.common.core.utils.*;
import org.dromara.common.encrypt.annotation.ApiEncrypt;
import org.dromara.common.json.utils.JsonUtils;
import org.dromara.common.ratelimiter.annotation.RateLimiter;
import org.dromara.common.ratelimiter.enums.LimitType;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.common.social.config.properties.SocialLoginConfigProperties;
import org.dromara.common.social.config.properties.SocialProperties;
@ -200,7 +198,6 @@ public class AuthController {
*
* @return 租户列表
*/
@RateLimiter(time = 60, count = 20, limitType = LimitType.IP)
@GetMapping("/tenant/list")
public R<LoginTenantVo> tenantList(HttpServletRequest request) throws Exception {
// 返回对象

View File

@ -158,6 +158,6 @@ public class UserActionListener implements SaTokenListener {
* 每次Token续期时触发
*/
@Override
public void doRenewTimeout(String loginType, Object loginId, String tokenValue, long timeout) {
public void doRenewTimeout(String tokenValue, Object loginId, long timeout) {
}
}

View File

@ -87,7 +87,7 @@ public class SysRegisterService {
recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
throw new CaptchaExpireException();
}
if (!StringUtils.equalsIgnoreCase(code, captcha)) {
if (!code.equalsIgnoreCase(captcha)) {
recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error"));
throw new CaptchaException();
}

View File

@ -102,7 +102,7 @@ public class PasswordAuthStrategy implements IAuthStrategy {
loginService.recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
throw new CaptchaExpireException();
}
if (!StringUtils.equalsIgnoreCase(code, captcha)) {
if (!code.equalsIgnoreCase(captcha)) {
loginService.recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error"));
throw new CaptchaException();
}

View File

@ -3,7 +3,10 @@ package org.dromara.web.service.impl;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.http.Method;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.zhyd.oauth.model.AuthResponse;
@ -65,6 +68,15 @@ public class SocialAuthStrategy implements IAuthStrategy {
throw new ServiceException(response.getMsg());
}
AuthUser authUserData = response.getData();
if ("GITEE".equals(authUserData.getSource())) {
// 如用户使用 gitee 登录顺手 star 给作者一点支持 拒绝白嫖
HttpUtil.createRequest(Method.PUT, "https://gitee.com/api/v5/user/starred/dromara/RuoYi-Vue-Plus")
.formStr(MapUtil.of("access_token", authUserData.getToken().getAccessToken()))
.executeAsync();
HttpUtil.createRequest(Method.PUT, "https://gitee.com/api/v5/user/starred/dromara/RuoYi-Cloud-Plus")
.formStr(MapUtil.of("access_token", authUserData.getToken().getAccessToken()))
.executeAsync();
}
List<SysSocialVo> list = sysSocialService.selectByAuthId(authUserData.getSource() + authUserData.getUuid());
if (CollUtil.isEmpty(list)) {

View File

@ -27,6 +27,8 @@ snail-job:
port: 2${server.port}
# 客户端ip指定
host:
# RPC类型: netty, grpc
rpc-type: grpc
--- # 数据源配置
spring:

View File

@ -30,6 +30,8 @@ snail-job:
port: 2${server.port}
# 客户端ip指定
host:
# RPC类型: netty, grpc
rpc-type: grpc
--- # 数据源配置
spring:

View File

@ -57,13 +57,6 @@ spring:
# 开启虚拟线程 仅jdk21可用
virtual:
enabled: false
task:
execution:
# 从 springboot 3.5 开始 spring自带线程池
# 不再需要 AsyncConfig与ThreadPoolConfig 可直接注入线程池使用
thread-name-prefix: async-
# 由spring自己初始化线程池
mode: force
# 资源信息
messages:
# 国际化资源文件路径
@ -134,7 +127,6 @@ tenant:
- sys_user_role
- sys_client
- sys_oss_config
- flow_spel
# MyBatisPlus配置
# https://baomidou.com/config/
@ -191,12 +183,19 @@ springdoc:
# 描述
description: '描述:用于管理集团旗下公司的人员信息,具体包括XXX,XXX模块...'
# 版本
version: '版本号: ${project.version}'
version: '版本号: ${ruoyi.version}'
# 作者信息
contact:
name: Lion Li
email: crazylionli@163.com
url: https://gitee.com/dromara/RuoYi-Vue-Plus
components:
# 鉴权方式配置
security-schemes:
apiKey:
type: APIKEY
in: HEADER
name: ${sa-token.token-name}
#这里定义了两个分组,可定义多个,也可以不定义
group-configs:
- group: 1.演示模块
@ -214,10 +213,20 @@ springdoc:
xss:
# 过滤开关
enabled: true
# 排除链接
# 排除链接(多个用逗号分隔)
excludeUrls:
- /system/notice
# 全局线程池相关配置
# 如使用JDK21请直接使用虚拟线程 不要开启此配置
thread-pool:
# 是否开启线程池
enabled: false
# 队列最大长度
queueCapacity: 128
# 线程池维护线程所允许的空闲时间
keepAliveSeconds: 300
--- # 分布式锁 lock4j 全局配置
lock4j:
# 获取分布式锁超时时间,默认为 3000 毫秒
@ -257,8 +266,6 @@ warm-flow:
enabled: true
# 是否开启设计器ui
ui: true
# 是否显示流程图顶部文字
top-text-show: true
# 默认Authorization如果有多个token用逗号分隔
token-name: ${sa-token.token-name},clientid
# 流程状态对应的三元色

View File

@ -17,7 +17,6 @@ user.username.length.valid=账户长度必须在{min}到{max}个字符之间
user.password.not.blank=用户密码不能为空
user.password.length.valid=用户密码长度必须在{min}到{max}个字符之间
user.password.not.valid=* 5-50个字符
user.password.format.valid=密码必须包含大写字母、小写字母、数字和特殊字符
user.email.not.valid=邮箱格式错误
user.email.not.blank=邮箱不能为空
user.phonenumber.not.blank=用户手机号不能为空

View File

@ -17,7 +17,6 @@ user.username.length.valid=Account length must be between {min} and {max} charac
user.password.not.blank=Password cannot be empty
user.password.length.valid=Password length must be between {min} and {max} characters
user.password.not.valid=* 5-50 characters
user.password.format.valid=Password must contain uppercase, lowercase, digit, and special character
user.email.not.valid=Mailbox format error
user.email.not.blank=Mailbox cannot be blank
user.phonenumber.not.blank=Phone number cannot be blank

View File

@ -17,7 +17,6 @@ user.username.length.valid=账户长度必须在{min}到{max}个字符之间
user.password.not.blank=用户密码不能为空
user.password.length.valid=用户密码长度必须在{min}到{max}个字符之间
user.password.not.valid=* 5-50个字符
user.password.format.valid=密码必须包含大写字母、小写字母、数字和特殊字符
user.email.not.valid=邮箱格式错误
user.email.not.blank=邮箱不能为空
user.phonenumber.not.blank=用户手机号不能为空

View File

@ -14,7 +14,7 @@
</description>
<properties>
<revision>5.4.1</revision>
<revision>5.4.0</revision>
</properties>
<dependencyManagement>

View File

@ -0,0 +1,52 @@
package org.dromara.common.core.config;
import cn.hutool.core.util.ArrayUtil;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.SpringUtils;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.core.task.VirtualThreadTaskExecutor;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import java.util.Arrays;
import java.util.concurrent.Executor;
/**
* 异步配置
* <p>
* 如果未使用虚拟线程则生效
*
* @author Lion Li
*/
@AutoConfiguration
public class AsyncConfig implements AsyncConfigurer {
/**
* 自定义 @Async 注解使用系统线程池
*/
@Override
public Executor getAsyncExecutor() {
if(SpringUtils.isVirtual()) {
return new VirtualThreadTaskExecutor("async-");
}
return SpringUtils.getBean("scheduledExecutorService");
}
/**
* 异步执行异常处理
*/
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return (throwable, method, objects) -> {
throwable.printStackTrace();
StringBuilder sb = new StringBuilder();
sb.append("Exception message - ").append(throwable.getMessage())
.append(", Method name - ").append(method.getName());
if (ArrayUtil.isNotEmpty(objects)) {
sb.append(", Parameter value - ").append(Arrays.toString(objects));
}
throw new ServiceException(sb.toString());
};
}
}

View File

@ -7,9 +7,11 @@ import org.dromara.common.core.config.properties.ThreadPoolProperties;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.core.utils.Threads;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.core.task.VirtualThreadTaskExecutor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
@ -32,6 +34,18 @@ public class ThreadPoolConfig {
private ScheduledExecutorService scheduledExecutorService;
@Bean(name = "threadPoolTaskExecutor")
@ConditionalOnProperty(prefix = "thread-pool", name = "enabled", havingValue = "true")
public ThreadPoolTaskExecutor threadPoolTaskExecutor(ThreadPoolProperties threadPoolProperties) {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(core);
executor.setMaxPoolSize(core * 2);
executor.setQueueCapacity(threadPoolProperties.getQueueCapacity());
executor.setKeepAliveSeconds(threadPoolProperties.getKeepAliveSeconds());
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
return executor;
}
/**
* 执行周期性或定时任务
*/

View File

@ -77,9 +77,4 @@ public interface SystemConstants {
*/
String ROOT_DEPT_ANCESTORS = "0";
/**
* 默认部门 ID
*/
Long DEFAULT_DEPT_ID = 100L;
}

View File

@ -50,11 +50,6 @@ public class CompleteTaskDTO implements Serializable {
*/
private String notice;
/**
* 办理人(可不填 用于覆盖当前节点办理人)
*/
private String handler;
/**
* 流程变量
*/

View File

@ -30,11 +30,6 @@ public class StartProcessDTO implements Serializable {
*/
private String flowCode;
/**
* 办理人(可不填 用于覆盖当前节点办理人)
*/
private String handler;
/**
* 流程变量,前端会提交一个元素{'entity': {业务详情数据对象}}
*/

View File

@ -52,17 +52,17 @@ public class TaskAssigneeDTO implements Serializable {
*/
public static <T> List<TaskHandler> convertToHandlerList(
List<T> sourceList,
Function<T, String> storageId,
Function<T, Long> storageId,
Function<T, String> handlerCode,
Function<T, String> handlerName,
Function<T, String> groupName,
Function<T, Long> groupName,
Function<T, Date> createTimeMapper) {
return sourceList.stream()
.map(item -> new TaskHandler(
storageId.apply(item),
String.valueOf(storageId.apply(item)),
handlerCode.apply(item),
handlerName.apply(item),
groupName.apply(item),
groupName != null ? String.valueOf(groupName.apply(item)) : null,
createTimeMapper.apply(item)
)).collect(Collectors.toList());
}

View File

@ -27,11 +27,6 @@ public class ProcessEvent implements Serializable {
*/
private String flowCode;
/**
* 实例id
*/
private Long instanceId;
/**
* 业务id
*/

View File

@ -4,7 +4,6 @@ import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Map;
/**
* 流程任务监听
@ -47,11 +46,6 @@ public class ProcessTaskEvent implements Serializable {
*/
private Long taskId;
/**
* 实例id
*/
private Long instanceId;
/**
* 业务id
*/
@ -62,9 +56,4 @@ public class ProcessTaskEvent implements Serializable {
*/
private String status;
/**
* 办理参数
*/
private Map<String, Object> params;
}

View File

@ -26,7 +26,6 @@ public class PasswordLoginBody extends LoginBody {
*/
@NotBlank(message = "{user.password.not.blank}")
@Length(min = 5, max = 30, message = "{user.password.length.valid}")
// @Pattern(regexp = RegexConstants.PASSWORD, message = "{user.password.format.valid}")
private String password;
}

View File

@ -26,12 +26,8 @@ public class RegisterBody extends LoginBody {
*/
@NotBlank(message = "{user.password.not.blank}")
@Length(min = 5, max = 30, message = "{user.password.length.valid}")
// @Pattern(regexp = RegexConstants.PASSWORD, message = "{user.password.format.valid}")
private String password;
/**
* 用户类型
*/
private String userType;
}

View File

@ -1,15 +1,11 @@
package org.dromara.common.core.exception;
import cn.hutool.core.text.StrFormatter;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.*;
import java.io.Serial;
/**
* 业务异常(支持占位符 {}
* 业务异常
*
* @author ruoyi
*/
@ -46,10 +42,6 @@ public final class ServiceException extends RuntimeException {
this.code = code;
}
public ServiceException(String message, Object... args) {
this.message = StrFormatter.format(message, args);
}
@Override
public String getMessage() {
return message;

View File

@ -3,7 +3,6 @@ package org.dromara.common.core.service;
import org.dromara.common.core.domain.dto.DeptDTO;
import java.util.List;
import java.util.Map;
/**
* 通用 部门服务
@ -35,12 +34,4 @@ public interface DeptService {
*/
List<DeptDTO> selectDeptsByList();
/**
* 根据部门 ID 列表查询部门名称映射关系
*
* @param deptIds 部门 ID 列表
* @return Map其中 key 为部门 IDvalue 为对应的部门名称
*/
Map<Long, String> selectDeptNamesByIds(List<Long> deptIds);
}

View File

@ -1,8 +1,5 @@
package org.dromara.common.core.service;
import java.util.List;
import java.util.Map;
/**
* 通用 岗位服务
*
@ -10,12 +7,4 @@ import java.util.Map;
*/
public interface PostService {
/**
* 根据岗位 ID 列表查询岗位名称映射关系
*
* @param postIds 岗位 ID 列表
* @return Map其中 key 为岗位 IDvalue 为对应的岗位名称
*/
Map<Long, String> selectPostNamesByIds(List<Long> postIds);
}

View File

@ -1,8 +1,5 @@
package org.dromara.common.core.service;
import java.util.List;
import java.util.Map;
/**
* 通用 角色服务
*
@ -10,12 +7,4 @@ import java.util.Map;
*/
public interface RoleService {
/**
* 根据角色 ID 列表查询角色名称映射关系
*
* @param roleIds 角色 ID 列表
* @return Map其中 key 为角色 IDvalue 为对应的角色名称
*/
Map<Long, String> selectRoleNamesByIds(List<Long> roleIds);
}

View File

@ -100,4 +100,28 @@ public interface UserService {
*/
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

@ -82,7 +82,6 @@ public interface WorkflowService {
* completeTask.getVariables().put("ignore", true);
*
* @param completeTask 参数
* @return 结果
*/
boolean completeTask(CompleteTaskDTO completeTask);
@ -91,15 +90,6 @@ public interface WorkflowService {
*
* @param taskId 任务ID
* @param message 办理意见
* @return 结果
*/
boolean completeTask(Long taskId, String message);
/**
* 启动流程并办理第一个任务
*
* @param startProcess 参数
* @return 结果
*/
boolean startCompleteTask(StartProcessDTO startProcess);
}

View File

@ -293,7 +293,7 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils {
// 校验时间跨度不超过最大限制
if (diff > maxValue) {
throw new ServiceException("最大时间跨度为 {} {}", maxValue, unit.toString().toLowerCase());
throw new ServiceException("最大时间跨度为 " + maxValue + " " + unit.toString().toLowerCase());
}
}

View File

@ -115,7 +115,7 @@ public class ServletUtils extends JakartaServletUtil {
public static Map<String, String> getParamMap(ServletRequest request) {
Map<String, String> params = new HashMap<>();
for (Map.Entry<String, String[]> entry : getParams(request).entrySet()) {
params.put(entry.getKey(), StringUtils.joinComma(entry.getValue()));
params.put(entry.getKey(), StringUtils.join(entry.getValue(), StringUtils.SEPARATOR));
}
return params;
}

View File

@ -7,6 +7,7 @@ import lombok.NoArgsConstructor;
import java.util.*;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;

View File

@ -260,13 +260,13 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils {
if (s != null) {
final int len = s.length();
if (s.length() <= size) {
sb.append(Convert.toStr(c).repeat(size - len));
sb.append(String.valueOf(c).repeat(size - len));
sb.append(s);
} else {
return s.substring(len - size, len);
}
} else {
sb.append(Convert.toStr(c).repeat(Math.max(0, size)));
sb.append(String.valueOf(c).repeat(Math.max(0, size)));
}
return sb.toString();
}
@ -361,24 +361,5 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils {
return input;
}
}
/**
* 将可迭代对象中的元素使用逗号拼接成字符串
*
* @param iterable 可迭代对象,如 List、Set 等
* @return 拼接后的字符串
*/
public static String joinComma(Iterable<?> iterable) {
return StringUtils.join(iterable, SEPARATOR);
}
/**
* 将数组中的元素使用逗号拼接成字符串
*
* @param array 任意类型的数组
* @return 拼接后的字符串
*/
public static String joinComma(Object[] array) {
return StringUtils.join(array, SEPARATOR);
}
}

View File

@ -10,8 +10,6 @@ import lombok.NoArgsConstructor;
import org.dromara.common.core.utils.reflect.ReflectUtils;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@ -62,31 +60,6 @@ public class TreeBuildUtils extends TreeUtil {
return TreeUtil.build(list, parentId, DEFAULT_CONFIG, nodeParser);
}
/**
* 构建多根节点的树结构(支持多个顶级节点)
*
* @param list 原始数据列表
* @param getId 获取节点 ID 的方法引用例如node -> node.getId()
* @param getParentId 获取节点父级 ID 的方法引用例如node -> node.getParentId()
* @param parser 树节点属性映射器,用于将原始节点 T 转为 Tree 节点
* @param <T> 原始数据类型如实体类、DTO 等)
* @param <K> 节点 ID 类型(如 Long、String
* @return 构建完成的树形结构(可能包含多个顶级根节点)
*/
public static <T, K> List<Tree<K>> buildMultiRoot(List<T> list, Function<T, K> getId, Function<T, K> getParentId, NodeParser<T, K> parser) {
if (CollUtil.isEmpty(list)) {
return CollUtil.newArrayList();
}
Set<K> rootParentIds = StreamUtils.toSet(list, getParentId);
rootParentIds.removeAll(StreamUtils.toSet(list, getId));
// 构建每一个根 parentId 下的树,并合并成最终结果列表
return rootParentIds.stream()
.flatMap(rootParentId -> TreeUtil.build(list, rootParentId, parser).stream())
.collect(Collectors.toList());
}
/**
* 获取节点列表中所有节点的叶子节点
*

View File

@ -16,55 +16,28 @@ import org.dromara.common.core.utils.StringUtils;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class AddressUtils {
// 未知IP
public static final String UNKNOWN_IP = "XX XX";
// 内网地址
public static final String LOCAL_ADDRESS = "内网IP";
// 未知地址
public static final String UNKNOWN_ADDRESS = "未知";
public static final String UNKNOWN = "XX XX";
public static String getRealAddressByIP(String ip) {
// 处理空串并过滤HTML标签
ip = HtmlUtil.cleanHtmlTag(StringUtils.blankToDefault(ip,""));
// 判断是否为IPv4
if (NetUtils.isIPv4(ip)) {
return resolverIPv4Region(ip);
boolean isIPv6 = NetUtils.isIPv6(ip);
// 判断是否为IPv4或IPv6如果不是则返回未知地址
if (!NetUtils.isIPv4(ip) && !isIPv6) {
return UNKNOWN;
}
// 判断是否为IPv6
if (NetUtils.isIPv6(ip)) {
return resolverIPv6Region(ip);
}
// 如果不是IPv4或IPv6则返回未知IP
return UNKNOWN_IP;
}
/**
* 根据IPv4地址查询IP归属行政区域
* @param ip ipv4地址
* @return 归属行政区域
*/
private static String resolverIPv4Region(String ip){
// 内网不查询
if (NetUtils.isInnerIP(ip)) {
return LOCAL_ADDRESS;
if (NetUtils.isInnerIPv6(ip) || NetUtils.isInnerIP(ip)) {
return "内网IP";
}
// 不支持IPv6不再进行没有必要的IP地址信息的解析直接返回
if (isIPv6) {
log.warn("ip2region不支持IPV6地址解析{}", ip);
// 如有需要可自行实现IPv6地址信息解析逻辑并在这里返回
return "未知";
}
return RegionUtils.getCityInfo(ip);
}
/**
* 根据IPv6地址查询IP归属行政区域
* @param ip ipv6地址
* @return 归属行政区域
*/
private static String resolverIPv6Region(String ip){
// 内网不查询
if (NetUtils.isInnerIPv6(ip)) {
return LOCAL_ADDRESS;
}
log.warn("ip2region不支持IPV6地址解析{}", ip);
// 不支持IPv6不再进行没有必要的IP地址信息的解析直接返回
// 如有需要可自行实现IPv6地址信息解析逻辑并在这里返回
return UNKNOWN_ADDRESS;
}
}

View File

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

View File

@ -54,15 +54,14 @@ public class SpringDocConfig {
openApi.externalDocs(properties.getExternalDocs());
openApi.tags(properties.getTags());
openApi.paths(properties.getPaths());
if (properties.getComponents() != null) {
openApi.components(properties.getComponents());
Set<String> keySet = properties.getComponents().getSecuritySchemes().keySet();
List<SecurityRequirement> list = new ArrayList<>();
SecurityRequirement securityRequirement = new SecurityRequirement();
keySet.forEach(securityRequirement::addList);
list.add(securityRequirement);
openApi.security(list);
}
openApi.components(properties.getComponents());
Set<String> keySet = properties.getComponents().getSecuritySchemes().keySet();
List<SecurityRequirement> list = new ArrayList<>();
SecurityRequirement securityRequirement = new SecurityRequirement();
keySet.forEach(securityRequirement::addList);
list.add(securityRequirement);
openApi.security(list);
return openApi;
}

View File

@ -6,7 +6,6 @@ import org.dromara.common.encrypt.properties.ApiDecryptProperties;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistration;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
@ -21,14 +20,13 @@ import org.springframework.context.annotation.Bean;
public class ApiDecryptAutoConfiguration {
@Bean
@FilterRegistration(
name = "cryptoFilter",
urlPatterns = "/*",
order = FilterRegistrationBean.HIGHEST_PRECEDENCE,
dispatcherTypes = DispatcherType.REQUEST
)
public CryptoFilter cryptoFilter(ApiDecryptProperties properties) {
return new CryptoFilter(properties);
public FilterRegistrationBean<CryptoFilter> cryptoFilterRegistration(ApiDecryptProperties properties) {
FilterRegistrationBean<CryptoFilter> registration = new FilterRegistrationBean<>();
registration.setDispatcherTypes(DispatcherType.REQUEST);
registration.setFilter(new CryptoFilter(properties));
registration.addUrlPatterns("/*");
registration.setName("cryptoFilter");
registration.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE);
return registration;
}
}

View File

@ -5,7 +5,6 @@ import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ObjectUtil;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.plugin.*;
import org.dromara.common.core.utils.StringUtils;
@ -40,23 +39,12 @@ public class MybatisDecryptInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 开始进行参数解密
ResultSetHandler resultSetHandler = (ResultSetHandler) invocation.getTarget();
Field parameterHandlerField = resultSetHandler.getClass().getDeclaredField("parameterHandler");
parameterHandlerField.setAccessible(true);
Object target = parameterHandlerField.get(resultSetHandler);
if (target instanceof ParameterHandler parameterHandler) {
Object parameterObject = parameterHandler.getParameterObject();
if (ObjectUtil.isNotNull(parameterObject) && !(parameterObject instanceof String)) {
this.decryptHandler(parameterObject);
}
}
// 获取执行mysql执行结果
Object result = invocation.proceed();
if (result == null) {
return null;
}
this.decryptHandler(result);
decryptHandler(result);
return result;
}

View File

@ -6,13 +6,17 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 批注 此注解仅用于单表头 不支持多层级表头
* 批注
* @author guzhouyanyu
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ExcelNotation {
/**
* col index
*/
int index() default -1;
/**
* 批注内容
*/

View File

@ -8,13 +8,17 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 是否必填 此注解仅用于单表头 不支持多层级表头
* 是否必填
* @author guzhouyanyu
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ExcelRequired {
/**
* col index
*/
int index() default -1;
/**
* 字体颜色
*/

View File

@ -1,152 +0,0 @@
package org.dromara.common.excel.core;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import cn.idev.excel.annotation.ExcelIgnore;
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import lombok.SneakyThrows;
import org.apache.poi.ss.util.CellRangeAddress;
import org.dromara.common.core.utils.reflect.ReflectUtils;
import org.dromara.common.excel.annotation.CellMerge;
import java.lang.reflect.Field;
import java.util.*;
/**
* 单元格合并处理器
*
* @author Lion Li
*/
public class CellMergeHandler {
private final boolean hasTitle;
private int rowIndex;
private CellMergeHandler(final boolean hasTitle) {
this.hasTitle = hasTitle;
// 行合并开始下标
this.rowIndex = hasTitle ? 1 : 0;
}
@SneakyThrows
public List<CellRangeAddress> handle(List<?> list) {
List<CellRangeAddress> cellList = new ArrayList<>();
if (CollUtil.isEmpty(list)) {
return cellList;
}
Class<?> clazz = list.get(0).getClass();
boolean annotationPresent = clazz.isAnnotationPresent(ExcelIgnoreUnannotated.class);
Field[] fields = ReflectUtils.getFields(clazz, field -> {
if ("serialVersionUID".equals(field.getName())) {
return false;
}
if (field.isAnnotationPresent(ExcelIgnore.class)) {
return false;
}
return !annotationPresent || field.isAnnotationPresent(ExcelProperty.class);
});
// 有注解的字段
List<Field> mergeFields = new ArrayList<>();
List<Integer> mergeFieldsIndex = new ArrayList<>();
for (int i = 0; i < fields.length; i++) {
Field field = fields[i];
if (field.isAnnotationPresent(CellMerge.class)) {
CellMerge cm = field.getAnnotation(CellMerge.class);
mergeFields.add(field);
mergeFieldsIndex.add(cm.index() == -1 ? i : cm.index());
if (hasTitle) {
ExcelProperty property = field.getAnnotation(ExcelProperty.class);
rowIndex = Math.max(rowIndex, property.value().length);
}
}
}
Map<Field, RepeatCell> map = new HashMap<>();
// 生成两两合并单元格
for (int i = 0; i < list.size(); i++) {
Object rowObj = list.get(i);
for (int j = 0; j < mergeFields.size(); j++) {
Field field = mergeFields.get(j);
Object val = ReflectUtils.invokeGetter(rowObj, field.getName());
int colNum = mergeFieldsIndex.get(j);
if (!map.containsKey(field)) {
map.put(field, new RepeatCell(val, i));
} else {
RepeatCell repeatCell = map.get(field);
Object cellValue = repeatCell.value();
if (cellValue == null || "".equals(cellValue)) {
// 空值跳过不合并
continue;
}
if (!cellValue.equals(val)) {
if ((i - repeatCell.current() > 1)) {
cellList.add(new CellRangeAddress(repeatCell.current() + rowIndex, i + rowIndex - 1, colNum, colNum));
}
map.put(field, new RepeatCell(val, i));
} else if (i == list.size() - 1) {
if (!isMerge(list, i, field)) {
// 如果最后一行不能合并,检查之前的数据是否需要合并
if (i - repeatCell.current() > 1) {
cellList.add(new CellRangeAddress(repeatCell.current() + rowIndex, i + rowIndex - 1, colNum, colNum));
}
} else if (i > repeatCell.current()) {
// 如果最后一行可以合并,则直接合并到最后
cellList.add(new CellRangeAddress(repeatCell.current() + rowIndex, i + rowIndex, colNum, colNum));
}
} else if (!isMerge(list, i, field)) {
if ((i - repeatCell.current() > 1)) {
cellList.add(new CellRangeAddress(repeatCell.current() + rowIndex, i + rowIndex - 1, colNum, colNum));
}
map.put(field, new RepeatCell(val, i));
}
}
}
}
return cellList;
}
private boolean isMerge(List<?> list, int i, Field field) {
boolean isMerge = true;
CellMerge cm = field.getAnnotation(CellMerge.class);
final String[] mergeBy = cm.mergeBy();
if (StrUtil.isAllNotBlank(mergeBy)) {
//比对当前list(i)和list(i - 1)的各个属性值一一比对 如果全为真 则为真
for (String fieldName : mergeBy) {
final Object valCurrent = ReflectUtil.getFieldValue(list.get(i), fieldName);
final Object valPre = ReflectUtil.getFieldValue(list.get(i - 1), fieldName);
if (!Objects.equals(valPre, valCurrent)) {
//依赖字段如有任一不等值,则标记为不可合并
isMerge = false;
}
}
}
return isMerge;
}
record RepeatCell(Object value, int current) {}
/**
* 创建一个单元格合并处理器实例
*
* @param hasTitle 是否合并标题
* @return 单元格合并处理器
*/
public static CellMergeHandler of(final boolean hasTitle) {
return new CellMergeHandler(hasTitle);
}
/**
* 创建一个单元格合并处理器实例(默认不合并标题)
*
* @return 单元格合并处理器
*/
public static CellMergeHandler of() {
return new CellMergeHandler(false);
}
}

View File

@ -1,15 +1,24 @@
package org.dromara.common.excel.core;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import cn.idev.excel.annotation.ExcelProperty;
import cn.idev.excel.metadata.Head;
import cn.idev.excel.write.handler.WorkbookWriteHandler;
import cn.idev.excel.write.handler.context.WorkbookWriteHandlerContext;
import cn.idev.excel.write.merge.AbstractMergeStrategy;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.util.CellRangeAddress;
import org.dromara.common.core.utils.reflect.ReflectUtils;
import org.dromara.common.excel.annotation.CellMerge;
import java.lang.reflect.Field;
import java.util.*;
/**
@ -21,39 +30,134 @@ import java.util.*;
public class CellMergeStrategy extends AbstractMergeStrategy implements WorkbookWriteHandler {
private final List<CellRangeAddress> cellList;
public CellMergeStrategy(List<CellRangeAddress> cellList) {
this.cellList = cellList;
}
private final boolean hasTitle;
private int rowIndex;
public CellMergeStrategy(List<?> list, boolean hasTitle) {
this.cellList = CellMergeHandler.of(hasTitle).handle(list);
this.hasTitle = hasTitle;
// 行合并开始下标
this.rowIndex = hasTitle ? 1 : 0;
this.cellList = handle(list, hasTitle);
}
@Override
protected void merge(Sheet sheet, Cell cell, Head head, Integer relativeRowIndex) {
if (CollUtil.isEmpty(cellList)){
return;
}
//单元格写入了,遍历合并区域,如果该Cell在区域内,但非首行,则清空
final int rowIndex = cell.getRowIndex();
for (CellRangeAddress cellAddresses : cellList) {
final int firstRow = cellAddresses.getFirstRow();
if (cellAddresses.isInRange(cell) && rowIndex != firstRow){
cell.setBlank();
if (CollUtil.isNotEmpty(cellList)){
for (CellRangeAddress cellAddresses : cellList) {
final int firstRow = cellAddresses.getFirstRow();
if (cellAddresses.isInRange(cell) && rowIndex != firstRow){
cell.setBlank();
}
}
}
}
@Override
public void afterWorkbookDispose(final WorkbookWriteHandlerContext context) {
if (CollUtil.isEmpty(cellList)){
return;
}
//当前表格写完后,统一写入
for (CellRangeAddress item : cellList) {
context.getWriteContext().writeSheetHolder().getSheet().addMergedRegion(item);
if (CollUtil.isNotEmpty(cellList)){
for (CellRangeAddress item : cellList) {
context.getWriteContext().writeSheetHolder().getSheet().addMergedRegion(item);
}
}
}
@SneakyThrows
private List<CellRangeAddress> handle(List<?> list, boolean hasTitle) {
List<CellRangeAddress> cellList = new ArrayList<>();
if (CollUtil.isEmpty(list)) {
return cellList;
}
Field[] fields = ReflectUtils.getFields(list.get(0).getClass(), field -> !"serialVersionUID".equals(field.getName()));
// 有注解的字段
List<Field> mergeFields = new ArrayList<>();
List<Integer> mergeFieldsIndex = new ArrayList<>();
for (int i = 0; i < fields.length; i++) {
Field field = fields[i];
if (field.isAnnotationPresent(CellMerge.class)) {
CellMerge cm = field.getAnnotation(CellMerge.class);
mergeFields.add(field);
mergeFieldsIndex.add(cm.index() == -1 ? i : cm.index());
if (hasTitle) {
ExcelProperty property = field.getAnnotation(ExcelProperty.class);
rowIndex = Math.max(rowIndex, property.value().length);
}
}
}
Map<Field, RepeatCell> map = new HashMap<>();
// 生成两两合并单元格
for (int i = 0; i < list.size(); i++) {
for (int j = 0; j < mergeFields.size(); j++) {
Field field = mergeFields.get(j);
Object val = ReflectUtils.invokeGetter(list.get(i), field.getName());
int colNum = mergeFieldsIndex.get(j);
if (!map.containsKey(field)) {
map.put(field, new RepeatCell(val, i));
} else {
RepeatCell repeatCell = map.get(field);
Object cellValue = repeatCell.getValue();
if (cellValue == null || "".equals(cellValue)) {
// 空值跳过不合并
continue;
}
if (!cellValue.equals(val)) {
if ((i - repeatCell.getCurrent() > 1)) {
cellList.add(new CellRangeAddress(repeatCell.getCurrent() + rowIndex, i + rowIndex - 1, colNum, colNum));
}
map.put(field, new RepeatCell(val, i));
} else if (i == list.size() - 1) {
if (!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));
}
} else if (!isMerge(list, i, field)) {
if ((i - repeatCell.getCurrent() > 1)) {
cellList.add(new CellRangeAddress(repeatCell.getCurrent() + rowIndex, i + rowIndex - 1, colNum, colNum));
}
map.put(field, new RepeatCell(val, i));
}
}
}
}
return cellList;
}
private boolean isMerge(List<?> list, int i, Field field) {
boolean isMerge = true;
CellMerge cm = field.getAnnotation(CellMerge.class);
final String[] mergeBy = cm.mergeBy();
if (StrUtil.isAllNotBlank(mergeBy)) {
//比对当前list(i)和list(i - 1)的各个属性值一一比对 如果全为真 则为真
for (String fieldName : mergeBy) {
final Object valCurrent = ReflectUtil.getFieldValue(list.get(i), fieldName);
final Object valPre = ReflectUtil.getFieldValue(list.get(i - 1), fieldName);
if (!Objects.equals(valPre, valCurrent)) {
//依赖字段如有任一不等值,则标记为不可合并
isMerge = false;
}
}
}
return isMerge;
}
@Data
@AllArgsConstructor
static class RepeatCell {
private Object value;
private int current;
}
}

View File

@ -1,6 +1,5 @@
package org.dromara.common.excel.core;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.StrUtil;
import lombok.AllArgsConstructor;
import lombok.Data;
@ -66,7 +65,7 @@ public class DropDownOptions {
StringBuilder stringBuffer = new StringBuilder();
String regex = "^[\\S\\d\\u4e00-\\u9fa5]+$";
for (int i = 0; i < vars.length; i++) {
String var = StrUtil.trimToEmpty(Convert.toStr(vars[i]));
String var = StrUtil.trimToEmpty(String.valueOf(vars[i]));
if (!var.matches(regex)) {
throw new ServiceException("选项数据不符合规则,仅允许使用中英文字符以及数字");
}

View File

@ -1,7 +1,6 @@
package org.dromara.common.excel.core;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.EnumUtil;
import cn.hutool.core.util.ObjectUtil;
@ -104,7 +103,7 @@ public class ExcelDownHandler implements SheetWriteHandler {
if (StringUtils.isNotBlank(dictType)) {
// 如果传递了字典名,则依据字典建立下拉
Collection<String> values = Optional.ofNullable(dictService.getAllDictByDictType(dictType))
.orElseThrow(() -> new ServiceException("字典 {} 不存在", dictType))
.orElseThrow(() -> new ServiceException(String.format("字典 %s 不存在", dictType)))
.values();
options = new ArrayList<>(values);
} else if (StringUtils.isNotBlank(converterExp)) {
@ -116,7 +115,7 @@ public class ExcelDownHandler implements SheetWriteHandler {
// 否则如果指定了@ExcelEnumFormat则使用枚举的逻辑
ExcelEnumFormat format = field.getDeclaredAnnotation(ExcelEnumFormat.class);
List<Object> values = EnumUtil.getFieldValues(format.enumClass(), format.textField());
options = StreamUtils.toList(values, Convert::toStr);
options = StreamUtils.toList(values, String::valueOf);
}
if (ObjectUtil.isNotEmpty(options)) {
// 仅当下拉可选项不为空时执行

View File

@ -1,7 +1,6 @@
package org.dromara.common.excel.handler;
import cn.hutool.core.collection.CollUtil;
import cn.idev.excel.annotation.ExcelProperty;
import cn.idev.excel.metadata.data.DataFormatData;
import cn.idev.excel.metadata.data.WriteCellData;
import cn.idev.excel.util.StyleUtil;
@ -14,6 +13,7 @@ import cn.idev.excel.write.metadata.style.WriteFont;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFClientAnchor;
import org.apache.poi.xssf.usermodel.XSSFRichTextString;
import org.dromara.common.core.utils.reflect.ReflectUtils;
import org.dromara.common.excel.annotation.ExcelNotation;
import org.dromara.common.excel.annotation.ExcelRequired;
@ -31,12 +31,12 @@ public class DataWriteHandler implements SheetWriteHandler, CellWriteHandler {
/**
* 批注
*/
private final Map<String, String> notationMap;
private final Map<Integer, String> notationMap;
/**
* 头列字体颜色
*/
private final Map<String, Short> headColumnMap;
private final Map<Integer, Short> headColumnMap;
public DataWriteHandler(Class<?> clazz) {
@ -49,16 +49,15 @@ public class DataWriteHandler implements SheetWriteHandler, CellWriteHandler {
if (CollUtil.isEmpty(notationMap) && CollUtil.isEmpty(headColumnMap)) {
return;
}
// 第一行
WriteCellData<?> cellData = context.getFirstCellData();
// 第一个格子
WriteCellStyle writeCellStyle = cellData.getOrCreateStyle();
DataFormatData dataFormatData = new DataFormatData();
// 单元格设置为文本格式
dataFormatData.setIndex((short) 49);
writeCellStyle.setDataFormatData(dataFormatData);
if (context.getHead()) {
DataFormatData dataFormatData = new DataFormatData();
// 单元格设置为文本格式
dataFormatData.setIndex((short) 49);
writeCellStyle.setDataFormatData(dataFormatData);
Cell cell = context.getCell();
WriteSheetHolder writeSheetHolder = context.getWriteSheetHolder();
Sheet sheet = writeSheetHolder.getSheet();
@ -68,17 +67,17 @@ public class DataWriteHandler implements SheetWriteHandler, CellWriteHandler {
WriteFont headWriteFont = new WriteFont();
// 加粗
headWriteFont.setBold(true);
if (CollUtil.isNotEmpty(headColumnMap) && headColumnMap.containsKey(cell.getStringCellValue())) {
if (CollUtil.isNotEmpty(headColumnMap) && headColumnMap.containsKey(cell.getColumnIndex())) {
// 设置字体颜色
headWriteFont.setColor(headColumnMap.get(cell.getStringCellValue()));
headWriteFont.setColor(headColumnMap.get(cell.getColumnIndex()));
}
writeCellStyle.setWriteFont(headWriteFont);
CellStyle cellStyle = StyleUtil.buildCellStyle(workbook, null, writeCellStyle);
cell.setCellStyle(cellStyle);
if (CollUtil.isNotEmpty(notationMap) && notationMap.containsKey(cell.getStringCellValue())) {
if (CollUtil.isNotEmpty(notationMap) && notationMap.containsKey(cell.getColumnIndex())) {
// 批注内容
String notationContext = notationMap.get(cell.getStringCellValue());
String notationContext = notationMap.get(cell.getColumnIndex());
// 创建绘图对象
Comment comment = drawing.createCellComment(new XSSFClientAnchor(0, 0, 0, 0, (short) cell.getColumnIndex(), 0, (short) 5, 5));
comment.setString(new XSSFRichTextString(notationContext));
@ -90,16 +89,23 @@ public class DataWriteHandler implements SheetWriteHandler, CellWriteHandler {
/**
* 获取必填列
*/
private static Map<String, Short> getRequiredMap(Class<?> clazz) {
Map<String, Short> requiredMap = new HashMap<>();
private static Map<Integer, Short> getRequiredMap(Class<?> clazz) {
Map<Integer, Short> requiredMap = new HashMap<>();
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
// 检查 fields 数组是否为空
if (fields.length == 0) {
return requiredMap;
}
Field[] filteredFields = ReflectUtils.getFields(clazz, field -> !"serialVersionUID".equals(field.getName()));
for (int i = 0; i < filteredFields.length; i++) {
Field field = filteredFields[i];
if (!field.isAnnotationPresent(ExcelRequired.class)) {
continue;
}
ExcelRequired excelRequired = field.getAnnotation(ExcelRequired.class);
ExcelProperty excelProperty = field.getAnnotation(ExcelProperty.class);
requiredMap.put(excelProperty.value()[0], excelRequired.fontColor().getIndex());
int columnIndex = excelRequired.index() == -1 ? i : excelRequired.index();
requiredMap.put(columnIndex, excelRequired.fontColor().getIndex());
}
return requiredMap;
}
@ -107,16 +113,22 @@ public class DataWriteHandler implements SheetWriteHandler, CellWriteHandler {
/**
* 获取批注
*/
private static Map<String, String> getNotationMap(Class<?> clazz) {
Map<String, String> notationMap = new HashMap<>();
private static Map<Integer, String> getNotationMap(Class<?> clazz) {
Map<Integer, String> notationMap = new HashMap<>();
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
// 检查 fields 数组是否为空
if (fields.length == 0) {
return notationMap;
}
Field[] filteredFields = ReflectUtils.getFields(clazz, field -> !"serialVersionUID".equals(field.getName()));
for (int i = 0; i < filteredFields.length; i++) {
Field field = filteredFields[i];
if (!field.isAnnotationPresent(ExcelNotation.class)) {
continue;
}
ExcelNotation excelNotation = field.getAnnotation(ExcelNotation.class);
ExcelProperty excelProperty = field.getAnnotation(ExcelProperty.class);
notationMap.put(excelProperty.value()[0], excelNotation.value());
int columnIndex = excelNotation.index() == -1 ? i : excelNotation.index();
notationMap.put(columnIndex, excelNotation.value());
}
return notationMap;
}

View File

@ -27,7 +27,9 @@ import org.springframework.http.HttpMethod;
import org.springframework.validation.BindingResult;
import org.springframework.web.multipart.MultipartFile;
import java.util.*;
import java.util.Collection;
import java.util.Map;
import java.util.StringJoiner;
/**
* 操作日志记录处理
@ -174,28 +176,14 @@ public class LogAspect {
if (ArrayUtil.isEmpty(paramsArray)) {
return params.toString();
}
String[] exclude = ArrayUtil.addAll(excludeParamNames, EXCLUDE_PROPERTIES);
for (Object o : paramsArray) {
if (ObjectUtil.isNotNull(o) && !isFilterObject(o)) {
String str = "";
if (o instanceof List<?> list) {
List<Dict> list1 = new ArrayList<>();
for (Object obj : list) {
String str1 = JsonUtils.toJsonString(obj);
Dict dict = JsonUtils.parseMap(str1);
if (MapUtil.isNotEmpty(dict)) {
MapUtil.removeAny(dict, exclude);
list1.add(dict);
}
}
str = JsonUtils.toJsonString(list1);
} else {
str = JsonUtils.toJsonString(o);
Dict dict = JsonUtils.parseMap(str);
if (MapUtil.isNotEmpty(dict)) {
MapUtil.removeAny(dict, exclude);
str = JsonUtils.toJsonString(dict);
}
String str = JsonUtils.toJsonString(o);
Dict dict = JsonUtils.parseMap(str);
if (MapUtil.isNotEmpty(dict)) {
MapUtil.removeAny(dict, EXCLUDE_PROPERTIES);
MapUtil.removeAny(dict, excludeParamNames);
str = JsonUtils.toJsonString(dict);
}
params.add(str);
}

View File

@ -1,54 +0,0 @@
package org.dromara.common.mybatis.aspect;
import lombok.extern.slf4j.Slf4j;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.dromara.common.mybatis.annotation.DataPermission;
import org.dromara.common.mybatis.helper.DataPermissionHelper;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 数据权限注解Advice
*
* @author 秋辞未寒
*/
@Slf4j
public class DataPermissionAdvice implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
Object target = invocation.getThis();
Method method = invocation.getMethod();
Object[] args = invocation.getArguments();
// 设置权限注解
DataPermissionHelper.setPermission(getDataPermissionAnnotation(target, method, args));
try {
// 执行代理方法
return invocation.proceed();
} finally {
// 清除权限注解
DataPermissionHelper.removePermission();
}
}
/**
* 获取数据权限注解
*/
private DataPermission getDataPermissionAnnotation(Object target, Method method,Object[] args){
DataPermission dataPermission = method.getAnnotation(DataPermission.class);
// 优先获取方法上的注解
if (dataPermission != null) {
return dataPermission;
}
// 方法上没有注解,则获取类上的注解
Class<?> targetClass = target.getClass();
// 如果是 JDK 动态代理则获取真实的Class实例
if (Proxy.isProxyClass(targetClass)) {
targetClass = targetClass.getInterfaces()[0];
}
dataPermission = targetClass.getAnnotation(DataPermission.class);
return dataPermission;
}
}

View File

@ -0,0 +1,50 @@
package org.dromara.common.mybatis.aspect;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.dromara.common.mybatis.annotation.DataPermission;
import org.dromara.common.mybatis.helper.DataPermissionHelper;
/**
* 数据权限处理
*
* @author Lion Li
*/
@Slf4j
@Aspect
public class DataPermissionAspect {
/**
* 处理请求前执行
*/
@Before(value = "@annotation(dataPermission)")
public void doBefore(JoinPoint joinPoint, DataPermission dataPermission) {
DataPermissionHelper.setPermission(dataPermission);
}
/**
* 处理完请求后执行
*
* @param joinPoint 切点
*/
@AfterReturning(pointcut = "@annotation(dataPermission)")
public void doAfterReturning(JoinPoint joinPoint, DataPermission dataPermission) {
DataPermissionHelper.removePermission();
}
/**
* 拦截异常操作
*
* @param joinPoint 切点
* @param e 异常
*/
@AfterThrowing(value = "@annotation(dataPermission)", throwing = "e")
public void doAfterThrowing(JoinPoint joinPoint, DataPermission dataPermission, Exception e) {
DataPermissionHelper.removePermission();
}
}

View File

@ -1,39 +0,0 @@
package org.dromara.common.mybatis.aspect;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.mybatis.annotation.DataPermission;
import org.springframework.aop.support.StaticMethodMatcherPointcut;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 数据权限匹配切点
*
* @author 秋辞未寒
*/
@Slf4j
@SuppressWarnings("all")
public class DataPermissionPointcut extends StaticMethodMatcherPointcut {
@Override
public boolean matches(Method method, Class<?> targetClass) {
// 优先匹配方法
// 数据权限注解不对继承生效,所以检查当前方法是否有注解即可,不再往上匹配父类或接口
if (method.isAnnotationPresent(DataPermission.class)) {
return true;
}
// MyBatis 的 Mapper 就是通过 JDK 动态代理实现的,所以这里需要检查是否匹配 JDK 的动态代理
Class<?> targetClassRef = targetClass;
if (Proxy.isProxyClass(targetClassRef)) {
// 数据权限注解不对继承生效,但由于 SpringIOC 容器拿到的实际上是 MyBatis 代理过后的 Mapper而 targetClass.isAnnotationPresent 实际匹配的是 Proxy 类的注解,不会查找代理类。
// 所以这里不能用 targetClass.isAnnotationPresent只能用 AnnotatedElementUtils.hasAnnotation 或 targetClass.getInterfaces()[0].isAnnotationPresent 去做匹配,以检查被代理的 MapperClass 是否具有注解
// 原理JDK 动态代理本质上就是对接口进行实现然后对具体的接口实现做代理,所以直接通过接口可以拿到实际的 MapperClass
targetClassRef = targetClass.getInterfaces()[0];
}
return targetClassRef.isAnnotationPresent(DataPermission.class);
}
}

View File

@ -1,33 +0,0 @@
package org.dromara.common.mybatis.aspect;
import org.aopalliance.aop.Advice;
import org.springframework.aop.Pointcut;
import org.springframework.aop.support.AbstractPointcutAdvisor;
/**
* 数据权限注解切面定义
*
* @author 秋辞未寒
*/
@SuppressWarnings("all")
public class DataPermissionPointcutAdvisor extends AbstractPointcutAdvisor {
private final Advice advice;
private final Pointcut pointcut;
public DataPermissionPointcutAdvisor() {
this.advice = new DataPermissionAdvice();
this.pointcut = new DataPermissionPointcut();
}
@Override
public Pointcut getPointcut() {
return this.pointcut;
}
@Override
public Advice getAdvice() {
return this.advice;
}
}

View File

@ -11,17 +11,15 @@ import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerIntercept
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
import org.dromara.common.core.factory.YmlPropertySourceFactory;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.mybatis.aspect.DataPermissionPointcutAdvisor;
import org.dromara.common.mybatis.aspect.DataPermissionAspect;
import org.dromara.common.mybatis.handler.InjectionMetaObjectHandler;
import org.dromara.common.mybatis.handler.MybatisExceptionHandler;
import org.dromara.common.mybatis.handler.PlusPostInitTableInfoHandler;
import org.dromara.common.mybatis.interceptor.PlusDataPermissionInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.annotation.Role;
import org.springframework.transaction.annotation.EnableTransactionManagement;
/**
@ -29,7 +27,6 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
*
* @author Lion Li
*/
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
@EnableTransactionManagement(proxyTargetClass = true)
@MapperScan("${mybatis-plus.mapperPackage}")
@PropertySource(value = "classpath:common-mybatis.yml", factory = YmlPropertySourceFactory.class)
@ -57,16 +54,15 @@ public class MybatisPlusConfig {
* 数据权限拦截器
*/
public PlusDataPermissionInterceptor dataPermissionInterceptor() {
return new PlusDataPermissionInterceptor();
return new PlusDataPermissionInterceptor(SpringUtils.getProperty("mybatis-plus.mapperPackage"));
}
/**
* 数据权限切面处理器
*/
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public DataPermissionPointcutAdvisor dataPermissionPointcutAdvisor() {
return new DataPermissionPointcutAdvisor();
public DataPermissionAspect dataPermissionAspect() {
return new DataPermissionAspect();
}
/**

View File

@ -6,11 +6,9 @@ import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.baomidou.mybatisplus.core.toolkit.reflect.GenericTypeUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.toolkit.Db;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;
import org.dromara.common.core.utils.MapstructUtils;
@ -132,7 +130,7 @@ public interface BaseMapperPlus<T, V> extends BaseMapper<T> {
* @return 查询到的单个VO对象
*/
default V selectVoById(Serializable id) {
return this.selectVoById(id, this.currentVoClass());
return selectVoById(id, this.currentVoClass());
}
/**
@ -158,7 +156,7 @@ public interface BaseMapperPlus<T, V> extends BaseMapper<T> {
* @return 查询到的VO对象列表
*/
default List<V> selectVoByIds(Collection<? extends Serializable> idList) {
return this.selectVoByIds(idList, this.currentVoClass());
return selectVoByIds(idList, this.currentVoClass());
}
/**
@ -184,7 +182,7 @@ public interface BaseMapperPlus<T, V> extends BaseMapper<T> {
* @return 查询到的VO对象列表
*/
default List<V> selectVoByMap(Map<String, Object> map) {
return this.selectVoByMap(map, this.currentVoClass());
return selectVoByMap(map, this.currentVoClass());
}
/**
@ -210,7 +208,7 @@ public interface BaseMapperPlus<T, V> extends BaseMapper<T> {
* @return 查询到的单个VO对象
*/
default V selectVoOne(Wrapper<T> wrapper) {
return this.selectVoOne(wrapper, this.currentVoClass());
return selectVoOne(wrapper, this.currentVoClass());
}
/**
@ -221,12 +219,11 @@ public interface BaseMapperPlus<T, V> extends BaseMapper<T> {
* @return 查询到的单个VO对象
*/
default V selectVoOne(Wrapper<T> wrapper, boolean throwEx) {
return this.selectVoOne(wrapper, this.currentVoClass(), throwEx);
return selectVoOne(wrapper, this.currentVoClass(), throwEx);
}
/**
* 根据条件查询单个VO对象并指定返回的VO对象的类型(自动拼接 limit 1)
* 注意不要再自己添加 limit 1 做限制了
* 根据条件查询单个VO对象并指定返回的VO对象的类型
*
* @param wrapper 查询条件Wrapper
* @param voClass 返回的VO对象的Class对象
@ -234,12 +231,11 @@ public interface BaseMapperPlus<T, V> extends BaseMapper<T> {
* @return 查询到的单个VO对象经过类型转换为指定的VO类后返回
*/
default <C> C selectVoOne(Wrapper<T> wrapper, Class<C> voClass) {
return this.selectVoOne(wrapper, voClass, true);
return selectVoOne(wrapper, voClass, true);
}
/**
* 根据条件查询单个实体对象并将其转换为指定的VO对象(自动拼接 limit 1)
* 注意不要再自己添加 limit 1 做限制了
* 根据条件查询单个实体对象并将其转换为指定的VO对象
*
* @param wrapper 查询条件Wrapper
* @param voClass 要转换的VO类的Class对象
@ -255,33 +251,13 @@ public interface BaseMapperPlus<T, V> extends BaseMapper<T> {
return MapstructUtils.convert(obj, voClass);
}
/**
* 根据条件查询单条记录(自动拼接 limit 1 限制返回 1 条数据,不依赖 {@code throwEx} 参数)
* 注意不要再自己添加 limit 1 做限制了
* <p>
* <strong>注意:</strong>
* 1. 使用 {@code Page<>(1, 1)} 强制分页查询,确保 SQL 自动添加 {@code LIMIT 1},因此 {@code throwEx} 参数不再生效
* 2. 原方法的 {@code throwEx} 逻辑(多条数据抛异常)已被优化掉,因为分页查询不会返回多条记录
* </p>
*
* @param queryWrapper 查询条件(可为 null
* @param throwEx <del>是否抛出异常(已弃用,此参数不再生效)</del>
* @return 单条记录或无数据时返回 null
*/
@Override
default T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper, boolean throwEx) {
// 强制分页查询LIMIT 1确保最多返回 1 条记录
List<T> list = this.selectList(new Page<>(1, 1), queryWrapper);
return CollUtil.isEmpty(list) ? null : list.get(0);
}
/**
* 查询所有VO对象列表
*
* @return 查询到的VO对象列表
*/
default List<V> selectVoList() {
return this.selectVoList(new QueryWrapper<>(), this.currentVoClass());
return selectVoList(new QueryWrapper<>(), this.currentVoClass());
}
/**
@ -318,7 +294,7 @@ public interface BaseMapperPlus<T, V> extends BaseMapper<T> {
* @return 查询到的VO对象分页列表
*/
default <P extends IPage<V>> P selectVoPage(IPage<T> page, Wrapper<T> wrapper) {
return this.selectVoPage(page, wrapper, this.currentVoClass());
return selectVoPage(page, wrapper, this.currentVoClass());
}
/**

View File

@ -42,46 +42,17 @@ public enum DataBaseType {
* 根据数据库产品名称查找对应的数据库类型
*
* @param databaseProductName 数据库产品名称
* @return 对应的数据库类型枚举值
* @return 对应的数据库类型枚举值,如果未找到则返回 null
*/
public static DataBaseType find(String databaseProductName) {
if (StringUtils.isBlank(databaseProductName)) {
return MY_SQL;
return null;
}
for (DataBaseType type : values()) {
if (type.getType().equals(databaseProductName)) {
return type;
}
}
return MY_SQL;
return null;
}
/**
* 判断是否为 MySQL 类型
*/
public boolean isMySql() {
return this == MY_SQL;
}
/**
* 判断是否为 Oracle 类型
*/
public boolean isOracle() {
return this == ORACLE;
}
/**
* 判断是否为 PostgreSQL 类型
*/
public boolean isPostgreSql() {
return this == POSTGRE_SQL;
}
/**
* 判断是否为 SQL Server 类型
*/
public boolean isSqlServer() {
return this == SQL_SERVER;
}
}

View File

@ -22,11 +22,6 @@ import java.util.Date;
@Slf4j
public class InjectionMetaObjectHandler implements MetaObjectHandler {
/**
* 如果用户不存在默认注入-1代表无用户
*/
private static final Long DEFAULT_USER_ID = -1L;
/**
* 插入填充方法,用于在插入数据时自动填充实体对象中的创建时间、更新时间、创建人、更新人等信息
*
@ -50,11 +45,6 @@ public class InjectionMetaObjectHandler implements MetaObjectHandler {
baseEntity.setCreateBy(userId);
baseEntity.setUpdateBy(userId);
baseEntity.setCreateDept(ObjectUtils.notNull(baseEntity.getCreateDept(), loginUser.getDeptId()));
} else {
// 填充创建人、更新人和创建部门信息
baseEntity.setCreateBy(DEFAULT_USER_ID);
baseEntity.setUpdateBy(DEFAULT_USER_ID);
baseEntity.setCreateDept(ObjectUtils.notNull(baseEntity.getCreateDept(), DEFAULT_USER_ID));
}
}
} else {
@ -84,8 +74,6 @@ public class InjectionMetaObjectHandler implements MetaObjectHandler {
Long userId = LoginHelper.getUserId();
if (ObjectUtil.isNotNull(userId)) {
baseEntity.setUpdateBy(userId);
} else {
baseEntity.setUpdateBy(DEFAULT_USER_ID);
}
} else {
this.strictUpdateFill(metaObject, "updateTime", Date.class, new Date());
@ -105,6 +93,7 @@ public class InjectionMetaObjectHandler implements MetaObjectHandler {
try {
loginUser = LoginHelper.getLoginUser();
} catch (Exception e) {
log.warn("自动注入警告 => 用户未登录");
return null;
}
return loginUser;

View File

@ -1,5 +1,6 @@
package org.dromara.common.mybatis.handler;
import cn.hutool.core.annotation.AnnotationUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import lombok.AllArgsConstructor;
@ -9,6 +10,7 @@ import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.expression.operators.relational.ParenthesedExpressionList;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import org.apache.ibatis.io.Resources;
import org.dromara.common.core.domain.dto.RoleDTO;
import org.dromara.common.core.domain.model.LoginUser;
import org.dromara.common.core.exception.ServiceException;
@ -20,13 +22,22 @@ import org.dromara.common.mybatis.annotation.DataPermission;
import org.dromara.common.mybatis.enums.DataScopeType;
import org.dromara.common.mybatis.helper.DataPermissionHelper;
import org.dromara.common.satoken.utils.LoginHelper;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.expression.BeanFactoryResolver;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.ClassMetadata;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.expression.*;
import org.springframework.expression.common.TemplateParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.util.ClassUtils;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
/**
@ -38,6 +49,11 @@ import java.util.function.Function;
@Slf4j
public class PlusDataPermissionHandler {
/**
* 类名称与注解的映射关系缓存(由于aop无法拦截mybatis接口类上的注解 只能通过启动预扫描的方式进行)
*/
private final Map<String, DataPermission> dataPermissionCacheMap = new ConcurrentHashMap<>();
/**
* spel 解析器
*/
@ -48,17 +64,27 @@ public class PlusDataPermissionHandler {
*/
private final BeanResolver beanResolver = new BeanFactoryResolver(SpringUtils.getBeanFactory());
/**
* 构造方法,扫描指定包下的 Mapper 类并初始化缓存
*
* @param mapperPackage Mapper 类所在的包路径
*/
public PlusDataPermissionHandler(String mapperPackage) {
scanMapperClasses(mapperPackage);
}
/**
* 获取数据过滤条件的 SQL 片段
*
* @param where 原始的查询条件表达式
* @param mappedStatementId Mapper 方法的 ID
* @param isSelect 是否为查询语句
* @return 数据过滤条件的 SQL 片段
*/
public Expression getSqlSegment(Expression where, boolean isSelect) {
public Expression getSqlSegment(Expression where, String mappedStatementId, boolean isSelect) {
try {
// 获取数据权限配置
DataPermission dataPermission = getDataPermission();
DataPermission dataPermission = getDataPermission(mappedStatementId);
// 获取当前登录用户信息
LoginUser currentUser = DataPermissionHelper.getVariable("user");
if (ObjectUtil.isNull(currentUser)) {
@ -180,22 +206,92 @@ public class PlusDataPermissionHandler {
return StringUtils.EMPTY;
}
/**
* 扫描指定包下的 Mapper 类,并查找其中带有特定注解的方法或类
*
* @param mapperPackage Mapper 类所在的包路径
*/
private void scanMapperClasses(String mapperPackage) {
// 创建资源解析器和元数据读取工厂
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
// 将 Mapper 包路径按分隔符拆分为数组
String[] packagePatternArray = StringUtils.splitPreserveAllTokens(mapperPackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
String classpath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX;
try {
for (String packagePattern : packagePatternArray) {
// 将包路径转换为资源路径
String path = ClassUtils.convertClassNameToResourcePath(packagePattern);
// 获取指定路径下的所有 .class 文件资源
Resource[] resources = resolver.getResources(classpath + path + "/*.class");
for (Resource resource : resources) {
// 获取资源的类元数据
ClassMetadata classMetadata = factory.getMetadataReader(resource).getClassMetadata();
// 获取资源对应的类对象
Class<?> clazz = Resources.classForName(classMetadata.getClassName());
// 查找类中的特定注解
findAnnotation(clazz);
}
}
} catch (Exception e) {
log.error("初始化数据安全缓存时出错:{}", e.getMessage());
}
}
/**
* 在指定的类中查找特定的注解 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 注解对象
*
* @param mapperId 映射语句 ID
* @return DataPermission 注解对象,如果不存在则返回 null
*/
public DataPermission getDataPermission() {
return DataPermissionHelper.getPermission();
public DataPermission getDataPermission(String mapperId) {
// 检查上下文中是否包含映射语句 ID 对应的 DataPermission 注解对象
if (DataPermissionHelper.getPermission() != null) {
return DataPermissionHelper.getPermission();
}
// 检查缓存中是否包含映射语句 ID 对应的 DataPermission 注解对象
if (dataPermissionCacheMap.containsKey(mapperId)) {
return dataPermissionCacheMap.get(mapperId);
}
// 如果缓存中不包含映射语句 ID 对应的 DataPermission 注解对象,则尝试使用类名作为键查找
String clazzName = mapperId.substring(0, mapperId.lastIndexOf("."));
if (dataPermissionCacheMap.containsKey(clazzName)) {
return dataPermissionCacheMap.get(clazzName);
}
return null;
}
/**
* 检查给定的映射语句 ID 是否有效,即是否能够找到对应的 DataPermission 注解对象
*
* @param mapperId 映射语句 ID
* @return 如果找到对应的 DataPermission 注解对象,则返回 false否则返回 true
*/
public boolean invalid() {
return getDataPermission() == null;
public boolean invalid(String mapperId) {
return getDataPermission(mapperId) == null;
}
/**

View File

@ -26,14 +26,7 @@ public class DataBaseHelper {
private static final DynamicRoutingDataSource DS = SpringUtils.getBean(DynamicRoutingDataSource.class);
/**
* 获取当前数据源对应的数据库类型
* <p>
* 通过 DynamicRoutingDataSource 获取当前线程绑定的数据源,
* 然后从数据源获取数据库连接,利用连接的元数据获取数据库产品名称,
* 最后调用 DataBaseType.find 方法将数据库名称转换为对应的枚举类型
*
* @return 当前数据库对应的 DataBaseType 枚举,找不到时默认返回 MY_SQL
* @throws ServiceException 当获取数据库连接或元数据出现异常时抛出业务异常
* 获取当前数据库类型
*/
public static DataBaseType getDataBaseType() {
DataSource dataSource = DS.determineDataSource();
@ -46,31 +39,37 @@ public class DataBaseHelper {
}
}
/**
* 根据当前数据库类型,生成兼容的 FIND_IN_SET 语句片段
* <p>
* 用于判断指定值是否存在于逗号分隔的字符串列中SQL写法根据不同数据库方言自动切换
* - Oracle 使用 instr 函数
* - PostgreSQL 使用 strpos 函数
* - SQL Server 使用 charindex 函数
* - 其他默认使用 MySQL 的 find_in_set 函数
*
* @param var1 要查找的值(支持任意类型,内部会转换成字符串)
* @param var2 存储逗号分隔值的数据库列名
* @return 适用于当前数据库的 SQL 条件字符串,通常用于 where 或 apply 中拼接
*/
public static boolean isMySql() {
return DataBaseType.MY_SQL == getDataBaseType();
}
public static boolean isOracle() {
return DataBaseType.ORACLE == getDataBaseType();
}
public static boolean isPostgerSql() {
return DataBaseType.POSTGRE_SQL == getDataBaseType();
}
public static boolean isSqlServer() {
return DataBaseType.SQL_SERVER == getDataBaseType();
}
public static String findInSet(Object var1, String var2) {
DataBaseType dataBasyType = getDataBaseType();
String var = Convert.toStr(var1);
return switch (getDataBaseType()) {
// instr(',0,100,101,' , ',100,') <> 0
case ORACLE -> "instr(','||%s||',' , ',%s,') <> 0".formatted(var2, var);
// (select strpos(',0,100,101,' , ',100,')) <> 0
case POSTGRE_SQL -> "(select strpos(','||%s||',' , ',%s,')) <> 0".formatted(var2, var);
if (dataBasyType == DataBaseType.SQL_SERVER) {
// charindex(',100,' , ',0,100,101,') <> 0
case SQL_SERVER -> "charindex(',%s,' , ','+%s+',') <> 0".formatted(var, var2);
// find_in_set(100 , '0,100,101')
default -> "find_in_set('%s' , %s) <> 0".formatted(var, var2);
};
return "charindex(',%s,' , ','+%s+',') <> 0".formatted(var, var2);
} else if (dataBasyType == DataBaseType.POSTGRE_SQL) {
// (select strpos(',0,100,101,' , ',100,')) <> 0
return "(select strpos(','||%s||',' , ',%s,')) <> 0".formatted(var2, var);
} else if (dataBasyType == DataBaseType.ORACLE) {
// instr(',0,100,101,' , ',100,') <> 0
return "instr(','||%s||',' , ',%s,') <> 0".formatted(var2, var);
}
// find_in_set(100 , '0,100,101')
return "find_in_set('%s' , %s) <> 0".formatted(var, var2);
}
/**

View File

@ -35,7 +35,16 @@ import java.util.List;
@Slf4j
public class PlusDataPermissionInterceptor extends BaseMultiTableInnerInterceptor implements InnerInterceptor {
private final PlusDataPermissionHandler dataPermissionHandler = new PlusDataPermissionHandler();
private final PlusDataPermissionHandler dataPermissionHandler;
/**
* 构造函数,初始化 PlusDataPermissionHandler 实例
*
* @param mapperPackage 扫描的映射器包
*/
public PlusDataPermissionInterceptor(String mapperPackage) {
this.dataPermissionHandler = new PlusDataPermissionHandler(mapperPackage);
}
/**
* 在执行查询之前,检查并处理数据权限相关逻辑
@ -55,7 +64,7 @@ public class PlusDataPermissionInterceptor extends BaseMultiTableInnerIntercepto
return;
}
// 检查是否缺少有效的数据权限注解
if (dataPermissionHandler.invalid()) {
if (dataPermissionHandler.invalid(ms.getId())) {
return;
}
// 解析 sql 分配对应方法
@ -83,7 +92,7 @@ public class PlusDataPermissionInterceptor extends BaseMultiTableInnerIntercepto
return;
}
// 检查是否缺少有效的数据权限注解
if (dataPermissionHandler.invalid()) {
if (dataPermissionHandler.invalid(ms.getId())) {
return;
}
PluginUtils.MPBoundSql mpBs = mpSh.mPBoundSql();
@ -119,7 +128,7 @@ public class PlusDataPermissionInterceptor extends BaseMultiTableInnerIntercepto
*/
@Override
protected void processUpdate(Update update, int index, String sql, Object obj) {
Expression sqlSegment = dataPermissionHandler.getSqlSegment(update.getWhere(), false);
Expression sqlSegment = dataPermissionHandler.getSqlSegment(update.getWhere(), (String) obj, false);
if (null != sqlSegment) {
update.setWhere(sqlSegment);
}
@ -135,7 +144,7 @@ public class PlusDataPermissionInterceptor extends BaseMultiTableInnerIntercepto
*/
@Override
protected void processDelete(Delete delete, int index, String sql, Object obj) {
Expression sqlSegment = dataPermissionHandler.getSqlSegment(delete.getWhere(), false);
Expression sqlSegment = dataPermissionHandler.getSqlSegment(delete.getWhere(), (String) obj, false);
if (null != sqlSegment) {
delete.setWhere(sqlSegment);
}
@ -148,7 +157,7 @@ public class PlusDataPermissionInterceptor extends BaseMultiTableInnerIntercepto
* @param mappedStatementId 映射语句的 ID
*/
protected void setWhere(PlainSelect plainSelect, String mappedStatementId) {
Expression sqlSegment = dataPermissionHandler.getSqlSegment(plainSelect.getWhere(), true);
Expression sqlSegment = dataPermissionHandler.getSqlSegment(plainSelect.getWhere(), mappedStatementId, true);
if (null != sqlSegment) {
plainSelect.setWhere(sqlSegment);
}

View File

@ -2,7 +2,6 @@ package org.dromara.common.oss.core;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.IdUtil;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.constant.Constants;
import org.dromara.common.core.utils.DateUtils;
import org.dromara.common.core.utils.StringUtils;
@ -14,7 +13,9 @@ import org.dromara.common.oss.exception.OssException;
import org.dromara.common.oss.properties.OssProperties;
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import software.amazon.awssdk.core.async.*;
import software.amazon.awssdk.core.ResponseInputStream;
import software.amazon.awssdk.core.async.AsyncResponseTransformer;
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.services.s3.S3AsyncClient;
@ -28,12 +29,9 @@ import software.amazon.awssdk.transfer.s3.progress.LoggingTransferListener;
import java.io.*;
import java.net.URI;
import java.net.URL;
import java.nio.channels.Channels;
import java.nio.channels.WritableByteChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Duration;
import java.util.Optional;
import java.util.function.Consumer;
/**
@ -42,7 +40,6 @@ import java.util.function.Consumer;
*
* @author AprilWind
*/
@Slf4j
public class OssClient {
/**
@ -180,12 +177,12 @@ public class OssClient {
// 创建异步请求体length如果为空会报错
BlockingInputStreamAsyncRequestBody body = BlockingInputStreamAsyncRequestBody.builder()
.contentLength(length)
.subscribeTimeout(Duration.ofSeconds(120))
.subscribeTimeout(Duration.ofSeconds(30))
.build();
// 使用 transferManager 进行上传
Upload upload = transferManager.upload(
x -> x.requestBody(body).addTransferListener(LoggingTransferListener.create())
x -> x.requestBody(body)
.putObjectRequest(
y -> y.bucket(properties.getBucketName())
.key(key)
@ -240,61 +237,30 @@ public class OssClient {
* @param key 文件在 Amazon S3 中的对象键
* @param out 输出流
* @param consumer 自定义处理逻辑
* @return 输出流中写入的字节数(长度)
* @throws OssException 如果下载失败,抛出自定义异常
*/
public void download(String key, OutputStream out, Consumer<Long> consumer) {
try {
this.download(key, consumer).writeTo(out);
} catch (Exception e) {
throw new OssException("文件下载失败,错误信息:[" + e.getMessage() + "]");
}
}
/**
* 下载文件从 Amazon S3 到 输出流
*
* @param key 文件在 Amazon S3 中的对象键
* @param contentLengthConsumer 文件大小消费者函数
* @return 写出订阅器
* @throws OssException 如果下载失败,抛出自定义异常
*/
public WriteOutSubscriber<OutputStream> download(String key, Consumer<Long> contentLengthConsumer) {
try {
// 构建下载请求
DownloadRequest<ResponsePublisher<GetObjectResponse>> publisherDownloadRequest = DownloadRequest.builder()
DownloadRequest<ResponseInputStream<GetObjectResponse>> downloadRequest = DownloadRequest.builder()
// 文件对象
.getObjectRequest(y -> y.bucket(properties.getBucketName())
.key(key)
.build())
.addTransferListener(LoggingTransferListener.create())
// 使用发布订阅转换器
.responseTransformer(AsyncResponseTransformer.toPublisher())
// 使用订阅转换器
.responseTransformer(AsyncResponseTransformer.toBlockingInputStream())
.build();
// 使用 S3TransferManager 下载文件
Download<ResponsePublisher<GetObjectResponse>> publisherDownload = transferManager.download(publisherDownloadRequest);
// 获取下载发布订阅转换器
ResponsePublisher<GetObjectResponse> publisher = publisherDownload.completionFuture().join().result();
// 执行文件大小消费者函数
Optional.ofNullable(contentLengthConsumer)
.ifPresent(lengthConsumer -> lengthConsumer.accept(publisher.response().contentLength()));
// 构建写出订阅器对象
return out -> {
// 创建可写入的字节通道
try(WritableByteChannel channel = Channels.newChannel(out)){
// 订阅数据
publisher.subscribe(byteBuffer -> {
while (byteBuffer.hasRemaining()) {
try {
channel.write(byteBuffer);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}).join();
Download<ResponseInputStream<GetObjectResponse>> responseFuture = transferManager.download(downloadRequest);
// 输出到流中
try (ResponseInputStream<GetObjectResponse> responseStream = responseFuture.completionFuture().join().result()) { // auto-closeable stream
if (consumer != null) {
consumer.accept(responseStream.response().contentLength());
}
};
responseStream.transferTo(out); // 阻塞调用线程 blocks the calling thread
}
} catch (Exception e) {
throw new OssException("文件下载失败,错误信息:[" + e.getMessage() + "]");
}

View File

@ -1,15 +0,0 @@
package org.dromara.common.oss.core;
import java.io.IOException;
/**
* 写出订阅器
*
* @author 秋辞未寒
*/
@FunctionalInterface
public interface WriteOutSubscriber<T> {
void writeTo(T out) throws IOException;
}

View File

@ -16,9 +16,7 @@ import java.util.function.Function;
*
* @author Lion Li
* @version 3.6.0 新增
* @deprecated redisson 新版本已经将队列功能标记删除 一些技术问题无法解决 建议搭建MQ使用
*/
@Deprecated
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class QueueUtils {

View File

@ -129,9 +129,9 @@ public class RedisUtils {
} catch (Exception e) {
long timeToLive = bucket.remainTimeToLive();
if (timeToLive == -1) {
bucket.set(value);
setCacheObject(key, value);
} else {
bucket.set(value, Duration.ofMillis(timeToLive));
setCacheObject(key, value, Duration.ofMillis(timeToLive));
}
}
} else {
@ -147,8 +147,11 @@ public class RedisUtils {
* @param duration 时间
*/
public static <T> void setCacheObject(final String key, final T value, final Duration duration) {
RBucket<T> bucket = CLIENT.getBucket(key);
bucket.set(value, duration);
RBatch batch = CLIENT.createBatch();
RBucketAsync<T> bucket = batch.getBucket(key);
bucket.setAsync(value);
bucket.expireAsync(duration);
batch.execute();
}
/**

View File

@ -1,7 +1,7 @@
package org.dromara.common.redis.utils;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.dromara.common.core.utils.SpringUtils;
@ -10,10 +10,6 @@ import org.redisson.api.RIdGenerator;
import org.redisson.api.RedissonClient;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAccessor;
/**
* 发号器工具类
@ -27,12 +23,12 @@ public class SequenceUtils {
/**
* 默认初始值
*/
public static final long DEFAULT_INIT_VALUE = 1L;
public static final Long DEFAULT_INIT_VALUE = 1L;
/**
* 默认步长
*/
public static final long DEFAULT_STEP_VALUE = 1L;
public static final Long DEFAULT_STEP_VALUE = 1L;
/**
* 默认过期时间-天
@ -44,11 +40,6 @@ public class SequenceUtils {
*/
public static final Duration DEFAULT_EXPIRE_TIME_MINUTE = Duration.ofMinutes(1);
/**
* 默认最小ID容量位数 - 6位数即至少可以生成的ID为999999个
*/
public static final int DEFAULT_MIN_ID_CAPACITY_BITS = 6;
/**
* 获取Redisson客户端实例
*/
@ -63,11 +54,14 @@ public class SequenceUtils {
* @param stepValue ID步长
* @return ID生成器
*/
public static RIdGenerator getIdGenerator(String key, Duration expireTime, long initValue, long stepValue) {
private static RIdGenerator getIdGenerator(String key, Duration expireTime, Long initValue, Long stepValue) {
if (initValue == null || initValue <= 0) {
initValue = DEFAULT_INIT_VALUE;
}
if (stepValue == null || stepValue <= 0) {
stepValue = DEFAULT_STEP_VALUE;
}
RIdGenerator idGenerator = REDISSON_CLIENT.getIdGenerator(key);
// 初始值和步长不能小于等于0
initValue = initValue <= 0 ? DEFAULT_INIT_VALUE : initValue;
stepValue = stepValue <= 0 ? DEFAULT_STEP_VALUE : stepValue;
// 设置初始值和步长
idGenerator.tryInit(initValue, stepValue);
// 设置过期时间
@ -75,17 +69,6 @@ public class SequenceUtils {
return idGenerator;
}
/**
* 获取ID生成器
*
* @param key 业务key
* @param expireTime 过期时间
* @return ID生成器
*/
public static RIdGenerator getIdGenerator(String key, Duration expireTime) {
return getIdGenerator(key, expireTime, DEFAULT_INIT_VALUE, DEFAULT_STEP_VALUE);
}
/**
* 获取指定业务key的唯一id
*
@ -95,21 +78,10 @@ public class SequenceUtils {
* @param stepValue ID步长
* @return 唯一id
*/
public static long getNextId(String key, Duration expireTime, long initValue, long stepValue) {
public static long nextId(String key, Duration expireTime, Long initValue, Long stepValue) {
return getIdGenerator(key, expireTime, initValue, stepValue).nextId();
}
/**
* 获取指定业务key的唯一id (ID初始值=1,ID步长=1)
*
* @param key 业务key
* @param expireTime 过期时间
* @return 唯一id
*/
public static long getNextId(String key, Duration expireTime) {
return getIdGenerator(key, expireTime).nextId();
}
/**
* 获取指定业务key的唯一id字符串
*
@ -119,8 +91,19 @@ public class SequenceUtils {
* @param stepValue ID步长
* @return 唯一id
*/
public static String getNextIdString(String key, Duration expireTime, long initValue, long stepValue) {
return Convert.toStr(getNextId(key, expireTime, initValue, stepValue));
public static String nextIdStr(String key, Duration expireTime, Long initValue, Long stepValue) {
return String.valueOf(nextId(key, expireTime, initValue, stepValue));
}
/**
* 获取指定业务key的唯一id (ID初始值=1,ID步长=1)
*
* @param key 业务key
* @param expireTime 过期时间
* @return 唯一id
*/
public static long nextId(String key, Duration expireTime) {
return getIdGenerator(key, expireTime, DEFAULT_INIT_VALUE, DEFAULT_STEP_VALUE).nextId();
}
/**
@ -130,8 +113,8 @@ public class SequenceUtils {
* @param expireTime 过期时间
* @return 唯一id
*/
public static String getNextIdString(String key, Duration expireTime) {
return Convert.toStr(getNextId(key, expireTime));
public static String nextIdStr(String key, Duration expireTime) {
return String.valueOf(nextId(key, expireTime));
}
/**
@ -142,210 +125,56 @@ public class SequenceUtils {
* @param width 位数不足左补0
* @return 补零后的唯一id字符串
*/
public static String getPaddedNextIdString(String key, Duration expireTime, Integer width) {
return StringUtils.leftPad(getNextIdString(key, expireTime), width, '0');
public static String nextPaddedIdStr(String key, Duration expireTime, Integer width) {
return StringUtils.leftPad(nextIdStr(key, expireTime), width, '0');
}
/**
* 获取 yyyyMMdd 格式的唯一id
* 获取 yyyyMMdd 开头的唯一id
*
* @return 唯一id
* @deprecated 请使用 {@link #getDateId(String)} 或 {@link #getDateId(String, boolean)}、{@link #getDateId(String, boolean, int)}确保不同业务的ID连续性
*/
@Deprecated
public static String getDateId() {
return getDateId("");
public static String nextIdDate() {
return nextIdDate("");
}
/**
* 获取 prefix + yyyyMMdd 格式的唯一id
* 获取 prefix + yyyyMMdd 开头的唯一id
*
* @param prefix 业务前缀
* @return 唯一id
*/
public static String getDateId(String prefix) {
return getDateId(prefix, true);
public static String nextIdDate(String prefix) {
// 前缀+日期 构建 prefixKey
String prefixKey = StringUtils.format("{}{}", StringUtils.blankToDefault(prefix, ""), DateUtil.format(DateUtil.date(), DatePattern.PURE_DATE_FORMATTER));
// 获取下一个id
long nextId = getIdGenerator(prefixKey, DEFAULT_EXPIRE_TIME_DAY, DEFAULT_INIT_VALUE, DEFAULT_STEP_VALUE).nextId();
// 返回完整id
return StringUtils.format("{}{}", prefixKey, nextId);
}
/**
* 获取 prefix + yyyyMMdd 格式的唯一id
*
* @param prefix 业务前缀
* @param isWithPrefix id是否携带业务前缀
* @return 唯一id
*/
public static String getDateId(String prefix, boolean isWithPrefix) {
return getDateId(prefix, isWithPrefix, -1);
}
/**
* 获取 prefix + yyyyMMdd 格式的唯一id (启用ID补位补位长度 = {@link #DEFAULT_MIN_ID_CAPACITY_BITS})}
*
* @param prefix 业务前缀
* @param isWithPrefix id是否携带业务前缀
* @return 唯一id
*/
public static String getPaddedDateId(String prefix, boolean isWithPrefix) {
return getDateId(prefix, isWithPrefix, DEFAULT_MIN_ID_CAPACITY_BITS);
}
/**
* 获取 prefix + yyyyMMdd 格式的唯一id
*
* @param prefix 业务前缀
* @param isWithPrefix id是否携带业务前缀
* @param minIdCapacityBits 最小ID容量位数小于该位数的ID左补0小于等于0表示不启用补位
* @return 唯一id
*/
public static String getDateId(String prefix, boolean isWithPrefix, int minIdCapacityBits) {
return getDateId(prefix, isWithPrefix, minIdCapacityBits, LocalDate.now());
}
/**
* 获取 prefix + yyyyMMdd 格式的唯一id
*
* @param prefix 业务前缀
* @param isWithPrefix id是否携带业务前缀
* @param minIdCapacityBits 最小ID容量位数小于该位数的ID左补0小于等于0表示不启用补位
* @param time 时间
* @return 唯一id
*/
public static String getDateId(String prefix, boolean isWithPrefix, int minIdCapacityBits, LocalDate time) {
return getDateId(prefix, isWithPrefix, minIdCapacityBits, time, DEFAULT_INIT_VALUE, DEFAULT_STEP_VALUE);
}
/**
* 获取 prefix + yyyyMMdd 格式的唯一id
*
* @param prefix 业务前缀
* @param isWithPrefix id是否携带业务前缀
* @param minIdCapacityBits 最小ID容量位数小于该位数的ID左补0小于等于0表示不启用补位
* @param time 时间
* @param initValue ID初始值
* @param stepValue ID步长
* @return 唯一id
*/
public static String getDateId(String prefix, boolean isWithPrefix, int minIdCapacityBits, LocalDate time, long initValue, long stepValue) {
return getDatePatternId(prefix, isWithPrefix, minIdCapacityBits, time, DatePattern.PURE_DATE_FORMATTER, DEFAULT_EXPIRE_TIME_DAY, initValue, stepValue);
}
/**
* 获取 yyyyMMddHHmmss 格式的唯一id
* 获取 yyyyMMddHHmmss 开头的唯一id
*
* @return 唯一id
* @deprecated 请使用 {@link #getDateTimeId(String)} 或 {@link #getDateTimeId(String, boolean)}、{@link #getDateTimeId(String, boolean, int)}确保不同业务的ID连续性
*/
@Deprecated
public static String getDateTimeId() {
return getDateTimeId("", false);
public static String nextIdDateTime() {
return nextIdDateTime("");
}
/**
* 获取 prefix + yyyyMMddHHmmss 格式的唯一id
* 获取 prefix + yyyyMMddHHmmss 开头的唯一id
*
* @param prefix 业务前缀
* @return 唯一id
*/
public static String getDateTimeId(String prefix) {
return getDateTimeId(prefix, true);
public static String nextIdDateTime(String prefix) {
// 前缀+日期时间 构建 prefixKey
String prefixKey = StringUtils.format("{}{}", StringUtils.blankToDefault(prefix, ""), DateUtil.format(DateUtil.date(), DatePattern.PURE_DATETIME_FORMATTER));
// 获取下一个id
long nextId = getIdGenerator(prefixKey, DEFAULT_EXPIRE_TIME_MINUTE, DEFAULT_INIT_VALUE, DEFAULT_STEP_VALUE).nextId();
// 返回完整id
return StringUtils.format("{}{}", prefixKey, nextId);
}
/**
* 获取 prefix + yyyyMMddHHmmss 格式的唯一id
*
* @param prefix 业务前缀
* @param isWithPrefix id是否携带业务前缀
* @return 唯一id
*/
public static String getDateTimeId(String prefix, boolean isWithPrefix) {
return getDateTimeId(prefix, isWithPrefix, -1);
}
/**
* 获取 prefix + yyyyMMddHHmmss 格式的唯一id (启用ID补位补位长度 = {@link #DEFAULT_MIN_ID_CAPACITY_BITS})}
*
* @param prefix 业务前缀
* @param isWithPrefix id是否携带业务前缀
* @return 唯一id
*/
public static String getPaddedDateTimeId(String prefix, boolean isWithPrefix) {
return getDateTimeId(prefix, isWithPrefix, DEFAULT_MIN_ID_CAPACITY_BITS);
}
/**
* 获取 prefix + yyyyMMddHHmmss 格式的唯一id
*
* @param prefix 业务前缀
* @param isWithPrefix id是否携带业务前缀
* @param minIdCapacityBits 最小ID容量位数小于该位数的ID左补0小于等于0表示不启用补位
* @return 唯一id
*/
public static String getDateTimeId(String prefix, boolean isWithPrefix, int minIdCapacityBits) {
return getDateTimeId(prefix, isWithPrefix, minIdCapacityBits, LocalDateTime.now());
}
/**
* 获取 prefix + yyyyMMddHHmmss 格式的唯一id
*
* @param prefix 业务前缀
* @param isWithPrefix id是否携带业务前缀
* @param minIdCapacityBits 最小ID容量位数小于该位数的ID左补0小于等于0表示不启用补位
* @param time 时间
* @return 唯一id
*/
public static String getDateTimeId(String prefix, boolean isWithPrefix, int minIdCapacityBits, LocalDateTime time) {
return getDateTimeId(prefix, isWithPrefix, minIdCapacityBits, time, DEFAULT_INIT_VALUE, DEFAULT_STEP_VALUE);
}
/**
* 获取 prefix + yyyyMMddHHmmss 格式的唯一id
*
* @param prefix 业务前缀
* @param isWithPrefix id是否携带业务前缀
* @param minIdCapacityBits 最小ID容量位数小于该位数的ID左补0小于等于0表示不启用补位
* @param initValue ID初始值
* @param stepValue ID步长
* @return 唯一id
*/
public static String getDateTimeId(String prefix, boolean isWithPrefix, int minIdCapacityBits, LocalDateTime time, long initValue, long stepValue) {
return getDatePatternId(prefix, isWithPrefix, minIdCapacityBits, time, DatePattern.PURE_DATETIME_FORMATTER, DEFAULT_EXPIRE_TIME_MINUTE, initValue, stepValue);
}
/**
* 获取指定业务key的指定时间格式的ID
*
* @param prefix 业务前缀
* @param isWithPrefix id是否携带业务前缀
* @param minIdCapacityBits 最小ID容量位数小于该位数的ID左补0小于等于0表示不启用补位
* @param temporalAccessor 时间访问器
* @param timeFormatter 时间格式
* @param expireTime 过期时间
* @param initValue ID初始值
* @param stepValue ID步长
* @return 唯一id
*/
private static String getDatePatternId(String prefix, boolean isWithPrefix, int minIdCapacityBits, TemporalAccessor temporalAccessor, DateTimeFormatter timeFormatter, Duration expireTime, long initValue, long stepValue) {
// 时间前缀
String timePrefix = timeFormatter.format(temporalAccessor);
// 业务前缀 + 时间前缀 构建 prefixKey
String prefixKey = StringUtils.format("{}{}", StringUtils.blankToDefault(prefix, ""), timePrefix);
// 获取id例 -> 1
String nextId = getNextIdString(prefixKey, expireTime, initValue, stepValue);
// minIdCapacityBits 大于0且 nextId 的长度小于 minIdCapacityBits则左补0
if (minIdCapacityBits > 0 && nextId.length() < minIdCapacityBits) {
nextId = StringUtils.leftPad(nextId, minIdCapacityBits, '0');
}
// 是否携带业务前缀
if (isWithPrefix) {
// 例 -> P202507031
// 其中 P 为业务前缀202507031 为 yyyyMMdd 格式时间, 1 为nextId
return StringUtils.format("{}{}", prefixKey, nextId);
}
// 例 -> 202507031
// 其中 202507031 为 yyyyMMdd 格式时间, 1 为nextId
return StringUtils.format("{}{}", timePrefix, nextId);
}
}

View File

@ -53,7 +53,11 @@ public class PlusSaTokenDao implements SaTokenDaoBySessionFollowObject {
if (timeout == NEVER_EXPIRE) {
RedisUtils.setCacheObject(key, value);
} else {
RedisUtils.setCacheObject(key, value, Duration.ofSeconds(timeout));
if (RedisUtils.hasKey(key)) {
RedisUtils.setCacheObject(key, value, true);
} else {
RedisUtils.setCacheObject(key, value, Duration.ofSeconds(timeout));
}
}
CAFFEINE.invalidate(key);
}
@ -74,9 +78,7 @@ public class PlusSaTokenDao implements SaTokenDaoBySessionFollowObject {
*/
@Override
public void delete(String key) {
if (RedisUtils.deleteObject(key)) {
CAFFEINE.invalidate(key);
}
RedisUtils.deleteObject(key);
}
/**
@ -132,7 +134,11 @@ public class PlusSaTokenDao implements SaTokenDaoBySessionFollowObject {
if (timeout == NEVER_EXPIRE) {
RedisUtils.setCacheObject(key, object);
} else {
RedisUtils.setCacheObject(key, object, Duration.ofSeconds(timeout));
if (RedisUtils.hasKey(key)) {
RedisUtils.setCacheObject(key, object, true);
} else {
RedisUtils.setCacheObject(key, object, Duration.ofSeconds(timeout));
}
}
CAFFEINE.invalidate(key);
}
@ -153,9 +159,7 @@ public class PlusSaTokenDao implements SaTokenDaoBySessionFollowObject {
*/
@Override
public void deleteObject(String key) {
if (RedisUtils.deleteObject(key)) {
CAFFEINE.invalidate(key);
}
RedisUtils.deleteObject(key);
}
/**

View File

@ -1,7 +1,6 @@
package org.dromara.common.satoken.core.service;
import cn.dev33.satoken.stp.StpInterface;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import org.dromara.common.core.domain.model.LoginUser;
import org.dromara.common.core.enums.UserType;
@ -40,12 +39,8 @@ public class SaPermissionImpl implements StpInterface {
if (userType == UserType.APP_USER) {
// 其他端 自行根据业务编写
}
if (CollUtil.isNotEmpty(loginUser.getMenuPermission())) {
// SYS_USER 默认返回权限
return new ArrayList<>(loginUser.getMenuPermission());
} else {
return new ArrayList<>();
}
// SYS_USER 默认返回权限
return new ArrayList<>(loginUser.getMenuPermission());
}
/**
@ -67,12 +62,8 @@ public class SaPermissionImpl implements StpInterface {
if (userType == UserType.APP_USER) {
// 其他端 自行根据业务编写
}
if (CollUtil.isNotEmpty(loginUser.getRolePermission())) {
// SYS_USER 默认返回权限
return new ArrayList<>(loginUser.getRolePermission());
} else {
return new ArrayList<>();
}
// SYS_USER 默认返回权限
return new ArrayList<>(loginUser.getRolePermission());
}
private PermissionService getPermissionService() {

View File

@ -207,8 +207,7 @@ public class LoginHelper {
*/
public static boolean isLogin() {
try {
StpUtil.checkLogin();
return true;
return getLoginUser() != null;
} catch (Exception e) {
return false;
}

View File

@ -1,6 +1,5 @@
package org.dromara.common.sensitive.core;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.DesensitizedUtil;
import lombok.AllArgsConstructor;
@ -53,7 +52,7 @@ public enum SensitiveStrategy {
/**
* 用户ID
*/
USER_ID(s -> Convert.toStr(DesensitizedUtil.userId())),
USER_ID(s -> String.valueOf(DesensitizedUtil.userId())),
/**
* 密码

View File

@ -1,154 +0,0 @@
package me.zhyd.oauth.request;
import com.alibaba.fastjson.JSONObject;
import me.zhyd.oauth.cache.AuthStateCache;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.config.AuthSource;
import me.zhyd.oauth.enums.AuthResponseStatus;
import me.zhyd.oauth.enums.AuthUserGender;
import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthToken;
import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.utils.HttpUtils;
import me.zhyd.oauth.utils.StringUtils;
import me.zhyd.oauth.utils.UrlBuilder;
/**
* <p>
* 企业微信登录父类
* </p>
*
* @author liguanhua (347826496(a)qq.com)
* @since 1.15.9
*/
public abstract class AbstractAuthWeChatEnterpriseRequest extends AuthDefaultRequest {
public AbstractAuthWeChatEnterpriseRequest(AuthConfig config, AuthSource source) {
super(config,source);
}
public AbstractAuthWeChatEnterpriseRequest(AuthConfig config, AuthSource source, AuthStateCache authStateCache) {
super(config, source, authStateCache);
}
@Override
public AuthToken getAccessToken(AuthCallback authCallback) {
String response = doGetAuthorizationCode(accessTokenUrl(null));
JSONObject object = this.checkResponse(response);
return AuthToken.builder()
.accessToken(object.getString("access_token"))
.expireIn(object.getIntValue("expires_in"))
.code(authCallback.getCode())
.build();
}
@Override
public AuthUser getUserInfo(AuthToken authToken) {
String response = doGetUserInfo(authToken);
JSONObject object = this.checkResponse(response);
// 返回 OpenId 或其他,均代表非当前企业用户,不支持
// https://github.com/justauth/JustAuth/issues/227 修复bug
if (!object.containsKey("userid")) {
throw new AuthException(AuthResponseStatus.UNIDENTIFIED_PLATFORM, source);
}
String userId = object.getString("userid");
String userTicket = object.getString("user_ticket");
JSONObject userDetail = getUserDetail(authToken.getAccessToken(), userId, userTicket);
return AuthUser.builder()
.rawUserInfo(userDetail)
.username(userDetail.getString("name"))
.nickname(userDetail.getString("alias"))
.avatar(userDetail.getString("avatar"))
.location(userDetail.getString("address"))
.email(userDetail.getString("email"))
.uuid(userId)
.gender(AuthUserGender.getWechatRealGender(userDetail.getString("gender")))
.token(authToken)
.source(source.toString())
.build();
}
/**
* 校验请求结果
*
* @param response 请求结果
* @return 如果请求结果正常则返回JSONObject
*/
private JSONObject checkResponse(String response) {
JSONObject object = JSONObject.parseObject(response);
if (object.containsKey("errcode") && object.getIntValue("errcode") != 0) {
throw new AuthException(object.getString("errmsg"), source);
}
return object;
}
/**
* 返回获取accessToken的url
*
* @param code 授权码
* @return 返回获取accessToken的url
*/
@Override
protected String accessTokenUrl(String code) {
return UrlBuilder.fromBaseUrl(source.accessToken())
.queryParam("corpid", config.getClientId())
.queryParam("corpsecret", config.getClientSecret())
.build();
}
/**
* 返回获取userInfo的url
*
* @param authToken 用户授权后的token
* @return 返回获取userInfo的url
*/
@Override
protected String userInfoUrl(AuthToken authToken) {
return UrlBuilder.fromBaseUrl(source.userInfo())
.queryParam("access_token", authToken.getAccessToken())
.queryParam("code", authToken.getCode())
.build();
}
/**
* 用户详情
*
* @param accessToken accessToken
* @param userId 企业内用户id
* @param userTicket 成员票据,用于获取用户信息或敏感信息
* @return 用户详情
*/
private JSONObject getUserDetail(String accessToken, String userId, String userTicket) {
// 用户基础信息
String userInfoUrl = UrlBuilder.fromBaseUrl("https://qyapi.weixin.qq.com/cgi-bin/user/get")
.queryParam("access_token", accessToken)
.queryParam("userid", userId)
.build();
String userInfoResponse = new HttpUtils(config.getHttpConfig()).get(userInfoUrl).getBody();
JSONObject userInfo = checkResponse(userInfoResponse);
// 用户敏感信息
if (StringUtils.isNotEmpty(userTicket)) {
String userDetailUrl = UrlBuilder.fromBaseUrl("https://qyapi.weixin.qq.com/cgi-bin/auth/getuserdetail")
.queryParam("access_token", accessToken)
.build();
JSONObject param = new JSONObject();
param.put("user_ticket", userTicket);
String userDetailResponse = new HttpUtils(config.getHttpConfig()).post(userDetailUrl, param.toJSONString()).getBody();
JSONObject userDetail = checkResponse(userDetailResponse);
userInfo.putAll(userDetail);
}
return userInfo;
}
}

View File

@ -6,6 +6,7 @@ import lombok.RequiredArgsConstructor;
import org.dromara.common.core.domain.R;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.common.sse.core.SseEmitterManager;
import org.dromara.common.sse.dto.SseMessageDto;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.http.MediaType;
@ -13,6 +14,8 @@ import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.util.List;
/**
* SSE 控制器
*
@ -30,9 +33,7 @@ public class SseController implements DisposableBean {
*/
@GetMapping(value = "${sse.path}", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter connect() {
if (!StpUtil.isLogin()) {
return null;
}
StpUtil.checkLogin();
String tokenValue = StpUtil.getTokenValue();
Long userId = LoginHelper.getUserId();
return sseEmitterManager.connect(userId, tokenValue);
@ -50,32 +51,31 @@ public class SseController implements DisposableBean {
return R.ok();
}
// 以下为demo仅供参考 禁止使用 请在业务逻辑中使用工具发送而不是用接口发送
// /**
// * 向特定用户发送消息
// *
// * @param userId 目标用户的 ID
// * @param msg 要发送的消息内容
// */
// @GetMapping(value = "${sse.path}/send")
// public R<Void> send(Long userId, String msg) {
// SseMessageDto dto = new SseMessageDto();
// dto.setUserIds(List.of(userId));
// dto.setMessage(msg);
// sseEmitterManager.publishMessage(dto);
// return R.ok();
// }
//
// /**
// * 向所有用户发送消息
// *
// * @param msg 要发送的消息内容
// */
// @GetMapping(value = "${sse.path}/sendAll")
// public R<Void> send(String msg) {
// sseEmitterManager.publishAll(msg);
// return R.ok();
// }
/**
* 向特定用户发送消息
*
* @param userId 目标用户的 ID
* @param msg 要发送的消息内容
*/
@GetMapping(value = "${sse.path}/send")
public R<Void> send(Long userId, String msg) {
SseMessageDto dto = new SseMessageDto();
dto.setUserIds(List.of(userId));
dto.setMessage(msg);
sseEmitterManager.publishMessage(dto);
return R.ok();
}
/**
* 向所有用户发送消息
*
* @param msg 要发送的消息内容
*/
@GetMapping(value = "${sse.path}/sendAll")
public R<Void> send(String msg) {
sseEmitterManager.publishAll(msg);
return R.ok();
}
/**
* 清理资源。此方法目前不执行任何操作,但避免因未实现而导致错误

View File

@ -38,8 +38,8 @@ public class SseEmitterManager {
// 每个用户可以有多个 SSE 连接,通过 token 进行区分
Map<String, SseEmitter> emitters = USER_TOKEN_EMITTERS.computeIfAbsent(userId, k -> new ConcurrentHashMap<>());
// 创建一个新的 SseEmitter 实例,超时时间设置为一天 避免连接之后直接关闭浏览器导致连接停滞
SseEmitter emitter = new SseEmitter(86400000L);
// 创建一个新的 SseEmitter 实例,超时时间设置为 0 表示无限制
SseEmitter emitter = new SseEmitter(0L);
emitters.put(token, emitter);

View File

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

View File

@ -21,6 +21,11 @@
<artifactId>ruoyi-common-json</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-redis</artifactId>
</dependency>
<!-- SpringBoot Web容器 -->
<dependency>
<groupId>org.springframework.boot</groupId>

View File

@ -7,7 +7,6 @@ import org.dromara.common.web.filter.XssFilter;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistration;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
@ -22,20 +21,24 @@ public class FilterConfig {
@Bean
@ConditionalOnProperty(value = "xss.enabled", havingValue = "true")
@FilterRegistration(
name = "xssFilter",
urlPatterns = "/*",
order = FilterRegistrationBean.HIGHEST_PRECEDENCE + 1,
dispatcherTypes = DispatcherType.REQUEST
)
public XssFilter xssFilter() {
return new XssFilter();
public FilterRegistrationBean<XssFilter> xssFilterRegistration() {
FilterRegistrationBean<XssFilter> registration = new FilterRegistrationBean<>();
registration.setDispatcherTypes(DispatcherType.REQUEST);
registration.setFilter(new XssFilter());
registration.addUrlPatterns("/*");
registration.setName("xssFilter");
registration.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE + 1);
return registration;
}
@Bean
@FilterRegistration(name = "repeatableFilter", urlPatterns = "/*")
public RepeatableFilter repeatableFilter() {
return new RepeatableFilter();
public FilterRegistrationBean<RepeatableFilter> someFilterRegistration() {
FilterRegistrationBean<RepeatableFilter> registration = new FilterRegistrationBean<>();
registration.setFilter(new RepeatableFilter());
registration.addUrlPatterns("/*");
registration.setName("repeatableFilter");
registration.setOrder(FilterRegistrationBean.LOWEST_PRECEDENCE);
return registration;
}
}

View File

@ -1,12 +1,9 @@
package org.dromara.common.web.config;
import cn.hutool.core.date.DateUtil;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.web.handler.GlobalExceptionHandler;
import org.dromara.common.web.interceptor.PlusWebInvokeTimeInterceptor;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.format.FormatterRegistry;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
@ -14,8 +11,6 @@ import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.Date;
/**
* 通用配置
*
@ -30,17 +25,6 @@ public class ResourcesConfig implements WebMvcConfigurer {
registry.addInterceptor(new PlusWebInvokeTimeInterceptor());
}
@Override
public void addFormatters(FormatterRegistry registry) {
// 全局日期格式转换配置
registry.addConverter(String.class, Date.class, source -> {
if (StringUtils.isBlank(source)) {
return null;
}
return DateUtil.parse(source);
});
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
}

View File

@ -1,6 +1,6 @@
# 贝尔实验室 Spring 官方推荐镜像 JDK下载地址 https://bell-sw.com/pages/downloads/
FROM bellsoft/liberica-openjdk-rocky:17.0.16-cds
#FROM bellsoft/liberica-openjdk-rocky:21.0.8-cds
FROM bellsoft/liberica-openjdk-debian:17.0.11-cds
#FROM bellsoft/liberica-openjdk-debian:21.0.5-cds
#FROM findepi/graalvm:java17-native
LABEL maintainer="Lion Li"

View File

@ -1,6 +1,5 @@
package org.dromara.monitor.admin;
import de.codecentric.boot.admin.server.config.EnableAdminServer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@ -9,7 +8,6 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
*
* @author Lion Li
*/
@EnableAdminServer
@SpringBootApplication
public class MonitorAdminApplication {

View File

@ -0,0 +1,31 @@
package org.dromara.monitor.admin.config;
import de.codecentric.boot.admin.server.config.EnableAdminServer;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration;
import org.springframework.boot.task.ThreadPoolTaskExecutorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
/**
* springboot-admin server配置类
*
* @author Lion Li
*/
@Configuration
@EnableAdminServer
public class AdminServerConfig {
@Lazy
@Bean(name = TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME)
@ConditionalOnMissingBean(Executor.class)
public ThreadPoolTaskExecutor applicationTaskExecutor(ThreadPoolTaskExecutorBuilder builder) {
return builder.build();
}
}

View File

@ -10,7 +10,7 @@ import org.springframework.security.config.annotation.web.configurers.AbstractHt
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
/**
* admin 监控 安全配置
@ -32,14 +32,14 @@ public class SecurityConfig {
SavedRequestAwareAuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
successHandler.setTargetUrlParameter("redirectTo");
successHandler.setDefaultTargetUrl(adminContextPath + "/");
PathPatternRequestMatcher.Builder mvc = PathPatternRequestMatcher.withDefaults();
return httpSecurity
.headers((header) ->
header.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable))
.authorizeHttpRequests((authorize) ->
authorize.requestMatchers(
mvc.matcher(adminContextPath + "/assets/**"),
mvc.matcher(adminContextPath + "/login")
new AntPathRequestMatcher(adminContextPath + "/assets/**"),
new AntPathRequestMatcher(adminContextPath + "/login")
).permitAll()
.anyRequest().authenticated())
.formLogin((formLogin) ->

View File

@ -20,9 +20,6 @@ spring:
ui:
title: RuoYi-Vue-Plus服务监控中心
context-path: /admin
# 忽略无用警告
thymeleaf:
check-template-location: false
--- # Actuator 监控端点的配置项
management:

View File

@ -1,6 +1,6 @@
# 贝尔实验室 Spring 官方推荐镜像 JDK下载地址 https://bell-sw.com/pages/downloads/
FROM bellsoft/liberica-openjdk-rocky:17.0.16-cds
#FROM bellsoft/liberica-openjdk-rocky:21.0.8-cds
FROM bellsoft/liberica-openjdk-debian:17.0.11-cds
#FROM bellsoft/liberica-openjdk-debian:21.0.5-cds
#FROM findepi/graalvm:java17-native
LABEL maintainer="Lion Li"
@ -9,7 +9,7 @@ RUN mkdir -p /ruoyi/snailjob/logs
WORKDIR /ruoyi/snailjob
ENV LANG=C.UTF-8 LC_ALL=C.UTF-8 JAVA_OPTS=""
ENV LANG=C.UTF-8 LC_ALL=C.UTF-8 JAVA_OPTS="-Xms512m -Xmx1024m"
EXPOSE 8800
EXPOSE 17888

View File

@ -1,146 +0,0 @@
package com.aizuda.snailjob.server.common.register;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.aizuda.snailjob.common.core.enums.NodeTypeEnum;
import com.aizuda.snailjob.common.core.util.JsonUtil;
import com.aizuda.snailjob.common.core.util.NetUtil;
import com.aizuda.snailjob.common.core.util.SnailJobVersion;
import com.aizuda.snailjob.common.core.util.StreamUtils;
import com.aizuda.snailjob.common.log.SnailJobLog;
import com.aizuda.snailjob.server.common.cache.CacheConsumerGroup;
import com.aizuda.snailjob.server.common.config.SystemProperties;
import com.aizuda.snailjob.server.common.convert.RegisterNodeInfoConverter;
import com.aizuda.snailjob.server.common.dto.ServerNodeExtAttrs;
import com.aizuda.snailjob.server.common.handler.InstanceManager;
import com.aizuda.snailjob.template.datasource.persistence.po.ServerNode;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.google.common.collect.Lists;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* 服务端注册
*
* @author opensnail
* @date 2023-06-07
* @since 1.6.0
*/
@Component(ServerRegister.BEAN_NAME)
@RequiredArgsConstructor
public class ServerRegister extends AbstractRegister {
public static final String BEAN_NAME = "serverRegister";
private final ScheduledExecutorService serverRegisterNode = Executors.newSingleThreadScheduledExecutor(r -> new Thread(r, "server-register-node"));
public static final int DELAY_TIME = 30;
public static final String CURRENT_CID;
public static final String GROUP_NAME = "DEFAULT_SERVER";
public static final String NAMESPACE_ID = "DEFAULT_SERVER_NAMESPACE_ID";
private final InstanceManager instanceManager;
private final SystemProperties systemProperties;
private final ServerProperties serverProperties;
static {
CURRENT_CID = IdUtil.getSnowflakeNextIdStr();
}
@Override
public boolean supports(int type) {
return getNodeType().equals(type);
}
@Override
protected void beforeProcessor(RegisterContext context) {
// 新增扩展参数
ServerNodeExtAttrs serverNodeExtAttrs = new ServerNodeExtAttrs();
serverNodeExtAttrs.setWebPort(serverProperties.getPort());
serverNodeExtAttrs.setSystemVersion(SnailJobVersion.getVersion());
context.setGroupName(GROUP_NAME);
context.setHostId(CURRENT_CID);
String serverHost = systemProperties.getServerHost();
if (StrUtil.isEmptyIfStr(serverHost)) {
serverHost = NetUtil.getLocalIpStr();
}
context.setHostIp(serverHost);
context.setHostPort(systemProperties.getServerPort());
context.setContextPath(Optional.ofNullable(serverProperties.getServlet().getContextPath()).orElse(StrUtil.EMPTY));
context.setNamespaceId(NAMESPACE_ID);
context.setExtAttrs(JsonUtil.toJsonString(serverNodeExtAttrs));
}
@Override
protected LocalDateTime getExpireAt() {
return LocalDateTime.now().plusSeconds(DELAY_TIME);
}
@Override
protected boolean doRegister(RegisterContext context, ServerNode serverNode) {
refreshExpireAt(Lists.newArrayList(serverNode));
return Boolean.TRUE;
}
@Override
protected void afterProcessor(final ServerNode serverNode) {
try {
// 同步当前POD消费的组的节点信息
// netty的client只会注册到一个服务端若组分配的和client连接的不是一个POD则会导致当前POD没有其他客户端的注册信息
ConcurrentMap<String /*groupName*/, Set<String>/*namespaceId*/> allConsumerGroupName = CacheConsumerGroup.getAllConsumerGroupName();
if (CollUtil.isNotEmpty(allConsumerGroupName)) {
Set<String> namespaceIdSets = StreamUtils.toSetByFlatMap(allConsumerGroupName.values(), Set::stream);
if (CollUtil.isEmpty(namespaceIdSets)) {
return;
}
List<ServerNode> serverNodes = serverNodeMapper.selectList(
new LambdaQueryWrapper<ServerNode>()
.eq(ServerNode::getNodeType, NodeTypeEnum.CLIENT.getType())
.in(ServerNode::getNamespaceId, namespaceIdSets)
.in(ServerNode::getGroupName, allConsumerGroupName.keySet()));
for (final ServerNode node : serverNodes) {
// 刷新全量本地缓存
instanceManager.registerOrUpdate(RegisterNodeInfoConverter.INSTANCE.toRegisterNodeInfo(node));
// 刷新过期时间
CacheConsumerGroup.addOrUpdate(node.getGroupName(), node.getNamespaceId());
}
}
} catch (Exception e) {
SnailJobLog.LOCAL.error("Client refresh failed", e);
}
}
@Override
protected Integer getNodeType() {
return NodeTypeEnum.SERVER.getType();
}
@Override
public void start() {
SnailJobLog.LOCAL.info("ServerRegister start");
serverRegisterNode.scheduleAtFixedRate(() -> {
try {
this.register(new RegisterContext());
} catch (Exception e) {
SnailJobLog.LOCAL.error("Server-side registration failed", e);
}
}, 0, DELAY_TIME * 2 / 3, TimeUnit.SECONDS);
}
@Override
public void close() {
SnailJobLog.LOCAL.info("ServerRegister close");
}
}

View File

@ -16,26 +16,15 @@ spring:
--- # snail-job 服务端配置
snail-job:
# 服务端节点IP(默认按照`NetUtil.getLocalIpStr()`)
server-host:
# 服务端netty的端口号
# 拉取重试数据的每批次的大小
retry-pull-page-size: 1000
# 拉取重试数据的每批次的大小
job-pull-page-size: 1000
# 服务器端口
server-port: 17888
# 合并日志默认保存天数
merge-Log-days: 1
# 合并日志默认的条数
merge-Log-num: 500
# 配置每批次拉取重试数据的大小
retry-pull-page-size: 100
# 配置日志保存时间(单位:天)
# 日志保存时间(单位: day)
log-storage: 7
# bucket的总数量
bucket-total: 128
# Dashboard 任务容错天数
summary-day: 7
# 配置负载均衡周期时间
load-balance-cycle-time: 10
# 重试任务拉取的并行度
retry-max-pull-parallel: 2
rpc-type: grpc
--- # 监控中心配置
spring.boot.admin.client:

View File

@ -16,26 +16,15 @@ spring:
--- # snail-job 服务端配置
snail-job:
# 服务端节点IP(默认按照`NetUtil.getLocalIpStr()`)
server-host:
# 服务端netty的端口号
# 拉取重试数据的每批次的大小
retry-pull-page-size: 1000
# 拉取重试数据的每批次的大小
job-pull-page-size: 1000
# 服务器端口
server-port: 17888
# 合并日志默认保存天数
merge-Log-days: 1
# 合并日志默认的条数
merge-Log-num: 500
# 配置每批次拉取重试数据的大小
retry-pull-page-size: 100
# 配置日志保存时间(单位:天)
# 日志保存时间(单位: day)
log-storage: 7
# bucket的总数量
bucket-total: 128
# Dashboard 任务容错天数
summary-day: 7
# 配置负载均衡周期时间
load-balance-cycle-time: 10
# 重试任务拉取的并行度
retry-max-pull-parallel: 2
rpc-type: grpc
--- # 监控中心配置
spring.boot.admin.client:

View File

@ -85,7 +85,6 @@
<!-- 控制台输出日志级别 -->
<root level="info">
<appender-ref ref="console" />
<appender-ref ref="file_console" />
<appender-ref ref="async_info" />
<appender-ref ref="async_error" />
<appender-ref ref="snail_log_server_appender" />

View File

@ -1,5 +1,6 @@
package org.dromara.demo.controller;
import cn.dev33.satoken.annotation.SaIgnore;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.domain.R;
import org.dromara.common.mail.utils.MailUtils;
@ -17,11 +18,12 @@ import java.util.Arrays;
*
* @author Michelle.Chung
*/
@SaIgnore
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/demo/mail")
public class MailSendController {
public class MailController {
/**
* 发送邮件
@ -42,11 +44,11 @@ public class MailSendController {
* @param to 接收人
* @param subject 标题
* @param text 内容
* @param filePath 附件路径
*/
@GetMapping("/sendMessageWithAttachment")
public R<Void> sendMessageWithAttachment(String to, String subject, String text) {
// 附件路径 禁止前端传递 有任意读取系统文件风险
MailUtils.sendText(to, subject, text, new File("/xxx/xxx"));
public R<Void> sendMessageWithAttachment(String to, String subject, String text, String filePath) {
MailUtils.sendText(to, subject, text, new File(filePath));
return R.ok();
}
@ -56,11 +58,10 @@ public class MailSendController {
* @param to 接收人
* @param subject 标题
* @param text 内容
* @param paths 附件路径
*/
@GetMapping("/sendMessageWithAttachments")
public R<Void> sendMessageWithAttachments(String to, String subject, String text) {
// 附件路径 禁止前端传递 有任意读取系统文件风险
String[] paths = new String[]{"/xxx/xxx", "/xxx/xxx"};
public R<Void> sendMessageWithAttachments(String to, String subject, String text, String[] paths) {
File[] array = Arrays.stream(paths).map(File::new).toArray(File[]::new);
MailUtils.sendText(to, subject, text, array);
return R.ok();

View File

@ -1,9 +1,11 @@
package org.dromara.demo.controller.queue;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import cn.dev33.satoken.annotation.SaIgnore;
import org.dromara.common.core.domain.R;
import org.dromara.common.redis.utils.QueueUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RBoundedBlockingQueue;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@ -18,9 +20,7 @@ import org.springframework.web.bind.annotation.RestController;
*
* @author Lion Li
* @version 3.6.0
* @deprecated redisson 新版本已经将队列功能标记删除 一些技术问题无法解决 建议搭建MQ使用
*/
@Deprecated
@Slf4j
@RequiredArgsConstructor
@RestController

View File

@ -1,5 +1,6 @@
package org.dromara.demo.controller.queue;
import cn.dev33.satoken.annotation.SaIgnore;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.domain.R;
@ -22,9 +23,8 @@ import java.util.concurrent.TimeUnit;
*
* @author Lion Li
* @version 3.6.0
* @deprecated redisson 新版本已经将队列功能标记删除 一些技术问题无法解决 建议搭建MQ使用
*/
@Deprecated
@SaIgnore
@Slf4j
@RequiredArgsConstructor
@RestController

View File

@ -8,9 +8,7 @@ import lombok.NoArgsConstructor;
*
* @author Lion Li
* @version 3.6.0
* @deprecated redisson 新版本已经将队列功能标记删除 一些技术问题无法解决 建议搭建MQ使用
*/
@Deprecated
@Data
@NoArgsConstructor
public class PriorityDemo implements Comparable<PriorityDemo> {

View File

@ -19,9 +19,7 @@ import org.springframework.web.bind.annotation.RestController;
*
* @author Lion Li
* @version 3.6.0
* @deprecated redisson 新版本已经将队列功能标记删除 一些技术问题无法解决 建议搭建MQ使用
*/
@Deprecated
@Slf4j
@RequiredArgsConstructor
@RestController

View File

@ -59,9 +59,4 @@ public class TestDemoBo extends BaseEntity {
@NotBlank(message = "值不能为空", groups = {AddGroup.class, EditGroup.class})
private String value;
/**
* 版本
*/
private Long version;
}

View File

@ -2,7 +2,6 @@ package org.dromara.demo.domain.vo;
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import cn.idev.excel.annotation.format.DateTimeFormat;
import org.dromara.common.excel.annotation.ExcelNotation;
import org.dromara.common.excel.annotation.ExcelRequired;
import org.dromara.common.translation.annotation.Translation;
@ -47,7 +46,7 @@ public class TestDemoVo implements Serializable {
* 用户id
*/
@ExcelRequired
@ExcelProperty(value = "用户id", index = 5)
@ExcelProperty(value = "用户id")
private Long userId;
/**
@ -74,8 +73,6 @@ public class TestDemoVo implements Serializable {
/**
* 创建时间
*/
@ExcelRequired
@DateTimeFormat("yyyy-MM-dd HH:mm:ss")
@ExcelProperty(value = "创建时间")
private Date createTime;
@ -111,9 +108,4 @@ public class TestDemoVo implements Serializable {
@ExcelProperty(value = "更新人账号")
private String updateByName;
/**
* 版本
*/
private Long version;
}

View File

@ -42,11 +42,6 @@
<artifactId>ruoyi-common-log</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-idempotent</artifactId>
</dependency>
<!--velocity代码生成使用模板 -->
<dependency>
<groupId>org.apache.velocity</groupId>

View File

@ -3,23 +3,21 @@ package org.dromara.generator.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.io.IoUtil;
import com.baomidou.lock.annotation.Lock4j;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.domain.R;
import org.dromara.common.idempotent.annotation.RepeatSubmit;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.mybatis.helper.DataBaseHelper;
import org.dromara.common.web.core.BaseController;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.generator.domain.GenTable;
import org.dromara.generator.domain.GenTableColumn;
import org.dromara.generator.service.IGenTableService;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
@ -52,7 +50,6 @@ public class GenController extends BaseController {
*
* @param tableId 表ID
*/
@RepeatSubmit()
@SaCheckPermission("tool:gen:query")
@GetMapping(value = "/{tableId}")
public R<Map<String, Object>> getInfo(@PathVariable Long tableId) {
@ -83,8 +80,11 @@ public class GenController extends BaseController {
@SaCheckPermission("tool:gen:list")
@GetMapping(value = "/column/{tableId}")
public TableDataInfo<GenTableColumn> columnList(@PathVariable("tableId") Long tableId) {
TableDataInfo<GenTableColumn> dataInfo = new TableDataInfo<>();
List<GenTableColumn> list = genTableService.selectGenTableColumnListByTableId(tableId);
return TableDataInfo.build(list);
dataInfo.setRows(list);
dataInfo.setTotal(list.size());
return dataInfo;
}
/**
@ -94,7 +94,6 @@ public class GenController extends BaseController {
*/
@SaCheckPermission("tool:gen:import")
@Log(title = "代码生成", businessType = BusinessType.IMPORT)
@RepeatSubmit()
@PostMapping("/importTable")
public R<Void> importTableSave(String tables, String dataName) {
String[] tableNames = Convert.toStrArray(tables);
@ -109,7 +108,6 @@ public class GenController extends BaseController {
*/
@SaCheckPermission("tool:gen:edit")
@Log(title = "代码生成", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping
public R<Void> editSave(@Validated @RequestBody GenTable genTable) {
genTableService.validateEdit(genTable);
@ -175,7 +173,6 @@ public class GenController extends BaseController {
*/
@SaCheckPermission("tool:gen:edit")
@Log(title = "代码生成", businessType = BusinessType.UPDATE)
@Lock4j
@GetMapping("/synchDb/{tableId}")
public R<Void> synchDb(@PathVariable("tableId") Long tableId) {
genTableService.synchDb(tableId);

View File

@ -297,13 +297,13 @@ public class GenTableServiceImpl implements IGenTableService {
List<GenTableColumn> tableColumns = new ArrayList<>();
columns.forEach((columnName, column) -> {
GenTableColumn tableColumn = new GenTableColumn();
tableColumn.setIsPk(column.isPrimaryKey() ? "1" : "0");
tableColumn.setIsPk(String.valueOf(column.isPrimaryKey()));
tableColumn.setColumnName(column.getName());
tableColumn.setColumnComment(column.getComment());
tableColumn.setColumnType(column.getOriginType().toLowerCase());
tableColumn.setSort(column.getPosition());
tableColumn.setIsRequired(column.isNullable() ? "0" : "1");
tableColumn.setIsIncrement(column.isAutoIncrement() ? "1" : "0");
tableColumn.setIsRequired(column.isNullable() == 0 ? "1" : "0");
tableColumn.setIsIncrement(column.isAutoIncrement() == -1 ? "0" : "1");
tableColumns.add(tableColumn);
});
return tableColumns;

View File

@ -3,7 +3,6 @@ package org.dromara.generator.util;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.lang.Dict;
import org.dromara.common.mybatis.enums.DataBaseType;
import org.dromara.generator.constant.GenConstants;
import org.dromara.common.core.utils.DateUtils;
import org.dromara.common.core.utils.StringUtils;
@ -119,12 +118,11 @@ public class VelocityUtils {
templates.add("vm/java/serviceImpl.java.vm");
templates.add("vm/java/controller.java.vm");
templates.add("vm/xml/mapper.xml.vm");
DataBaseType dataBaseType = DataBaseHelper.getDataBaseType();
if (dataBaseType.isOracle()) {
if (DataBaseHelper.isOracle()) {
templates.add("vm/sql/oracle/sql.vm");
} else if (dataBaseType.isPostgreSql()) {
} else if (DataBaseHelper.isPostgerSql()) {
templates.add("vm/sql/postgres/sql.vm");
} else if (dataBaseType.isSqlServer()) {
} else if (DataBaseHelper.isSqlServer()) {
templates.add("vm/sql/sqlserver/sql.vm");
} else {
templates.add("vm/sql/sql.vm");

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