Compare commits

62 Commits

Author SHA1 Message Date
疯狂的狮子Li
88f871002c !816 fix 修复 判断条件写反问题
Merge pull request !816 from 疯狂的狮子Li/dev
2025-12-24 05:27:11 +00:00
疯狂的狮子Li
874ad7c9b7 fix 修复 判断条件写反问题 2025-12-24 13:10:47 +08:00
miracle-bean
89d6f6f247 !815 fix websocket 多线程下IO阻塞的问题
* fix websocket 多线程下IO阻塞的问题
2025-12-23 07:55:24 +00:00
疯狂的狮子Li
1324a1cb16 update 优化 增加 HandlerMethodValidationException 参数校验异常连接 2025-12-23 15:30:32 +08:00
疯狂的狮子Li
b6eacfa5a8 !813 紧急修复Ip2Region InputStream读取函数导致的OOM问题
Merge pull request !813 from 疯狂的狮子Li/dev
2025-12-23 06:44:24 +00:00
ColorDreams
961bca462e fix 临时修复Ip2Region InputStream读取函数导致的OOM问题 2025-12-23 14:32:48 +08:00
疯狂的狮子Li
496df8494e update 优化 翻译实现类逻辑 2025-12-23 10:38:18 +08:00
疯狂的狮子Li
da4dffcfce !812 发布 5.5.2 版本 2025年最后一版
Merge pull request !812 from 疯狂的狮子Li/dev
2025-12-23 01:38:19 +00:00
疯狂的狮子Li
2f1f9689e0 🧨🧨🧨发布 5.5.2 版本 2025年最后一版 2025-12-23 09:28:20 +08:00
疯狂的狮子Li
8110413fdf update 删除错误的配置 2025-12-23 09:22:42 +08:00
疯狂的狮子Li
c1f64d3450 update anyline 8.7.2-20250603 => 8.7.3-20251210 2025-12-22 13:11:38 +08:00
疯狂的狮子Li
cb00f4c9c1 update snailjob 1.8.0 => 1.9.0 2025-12-22 09:42:56 +08:00
疯狂的狮子Li
79512c69b2 fix 修复 创建租户同步工作流数据 在没有流程定义的情况下不会复制流程类别的问题 2025-12-19 19:38:45 +08:00
疯狂的狮子Li
a5fb128f11 update springboot 3.5.8 => 3.5.9 2025-12-19 11:31:53 +08:00
dr5hx
8a04e3c88f !811 feat(excel): 增强单元格合并处理逻辑
* feat(excel): 增强单元格合并处理逻辑
2025-12-19 01:48:31 +00:00
疯狂的狮子Li
dac447b76f fix 修复 listenerVariable.getVariable() 获取null问题 2025-12-19 09:36:22 +08:00
AprilWind
35a9e4c8e8 update 增加高安全脱敏方法 2025-12-18 19:19:01 +08:00
AprilWind
0d87c12d3c update 优化灵活脱敏方法 2025-12-18 17:30:32 +08:00
AprilWind
f20a0c4342 update 优化构建流程参数 2025-12-16 18:27:21 +08:00
AprilWind
6c8d637bd2 update 优化报错信息提示 2025-12-16 17:05:18 +08:00
马铃薯头
20e9957db2 !807 update 代码生成字典类型字段新增更新验证策略
* update 代码生成字典类型字段新增更新验证策略
2025-12-16 08:36:03 +00:00
AprilWind
9baded9326 update 测试单表和测试树表增加搜索条件 2025-12-16 14:16:13 +08:00
疯狂的狮子Li
b5902debb6 update 优化 删除无用配置类代码 2025-12-15 15:44:54 +08:00
AprilWind
bcd5bb0f86 !805 update 优化我的待办时间展示
* update 优化我的待办时间展示
2025-12-15 06:19:29 +00:00
AprilWind
1a461f7d3d !804 update 优化登录提示语
* update 优化登录提示语
2025-12-15 03:10:49 +00:00
疯狂的狮子Li
e23d99d85b fix 修复 form_path 输入空字符串导致的问题 2025-12-15 09:36:12 +08:00
秋辞未寒
f07c20afab update Ip2Region version to 3.3.1,使用新的xdb文件加载函数优化xdb数据库的加载流程 2025-12-12 22:50:23 +08:00
疯狂的狮子Li
420553eaa6 fix 修复 工作流类别 顶节点父级可以被修改导致无法加载的问题 2025-12-12 17:15:35 +08:00
疯狂的狮子Li
1d8d93eaa3 fix 修复 微软三方对接参数缺失 2025-12-12 11:40:51 +08:00
疯狂的狮子Li
5f0d09fd45 update 优化 日志输出内容 2025-12-12 09:32:07 +08:00
AprilWind
1c2b7d7017 update 优化Ip2Region初始化打印日志 2025-12-12 09:25:18 +08:00
AprilWind
5fb2890167 update 增加对IPv6地址库的支持,优化Ip2Region初始化逻辑 2025-12-11 19:57:27 +08:00
秋辞未寒
1165c8dc06 !803 feat IP地址行政区域离线查询支持IPv6(已提供相关代码,开发者自行决定是否使用)
* update IP地址行政区域助手类重命名以匹配其工具类的功能定位
* feat IP地址行政区域离线查询支持IPv6(已提供相关代码,开发者自行决定是否使用)
2025-12-11 05:23:09 +00:00
AprilWind
ee09377997 !802 update 添加 ID 生成工具类,支持多种 ID 生成方式
* update 使用 IdGeneratorUtil 替代主键生成
* update 添加 ID 生成工具类,支持多种 ID 生成方式
2025-12-11 02:22:49 +00:00
疯狂的狮子Li
1921b22a57 fix 修复 获取可驳回节点重复问题(感谢 搬砖的小庄) 2025-12-11 10:19:11 +08:00
疯狂的狮子Li
8718989c52 update 优化 任务执行监听器 传递任务的相关数据 不传递实例相关数据了(避免并行节点覆盖问题) 2025-12-11 09:43:29 +08:00
疯狂的狮子Li
36069cd0e4 update 优化 加签判断逻辑 2025-12-11 09:17:58 +08:00
疯狂的狮子Li
b58085fde1 !779 发布 5.5.1 正式版 日常依赖升级bug修复
Merge pull request !779 from 疯狂的狮子Li/dev
2025-10-28 03:21:27 +00:00
疯狂的狮子Li
b30ffa952f !766 发布 5.5.0 喜迎国庆
Merge pull request !766 from 疯狂的狮子Li/dev
2025-09-22 03:17:27 +00:00
疯狂的狮子Li
fd5d028e95 fix 修复 有某些无聊人士 对一个demo案例提漏洞 CVE-2025-6925
Signed-off-by: 疯狂的狮子Li <15040126243@163.com>
2025-07-04 01:10:49 +00:00
疯狂的狮子Li
64100cf1ff !712 发布 5.4.1 小步迭代修复问题
Merge pull request !712 from 疯狂的狮子Li/dev
2025-07-01 01:12:39 +00:00
疯狂的狮子Li
7e7d857ba5 Merge remote-tracking branch 'origin/dev' into 5.X 2025-05-29 18:18:20 +08:00
疯狂的狮子Li
d22b2a10df update 优化 PermissionService 无实现类也可以启动服务 2025-05-29 16:28:56 +08:00
疯狂的狮子Li
957a4d1fcd fix 修复 监听器 flowParams 为null报错问题 2025-05-29 16:28:56 +08:00
疯狂的狮子Li
49ef8378fe !691 发布 5.4.0 正式版
Merge pull request !691 from 疯狂的狮子Li/dev
2025-05-29 03:14:59 +00:00
疯狂的狮子Li
57dd6831d3 !664 发布 5.3.1 正式版
Merge pull request !664 from 疯狂的狮子Li/dev
2025-03-27 02:54:00 +00:00
疯狂的狮子Li
8aa60abb1f !663 回退 'Pull Request !662 : 发布 5.3.1 正式版'
* 回退 'Pull Request !662 : 发布 5.3.1 正式版'
2025-03-27 02:53:23 +00:00
疯狂的狮子Li
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
疯狂的狮子Li
159e30c982 !661 发布 5.3.1-BETA2 公测版本
Merge pull request !661 from 疯狂的狮子Li/dev
2025-03-21 07:25:25 +00:00
疯狂的狮子Li
7334d91d6b !652 发布 5.3.1-BETA 公测版本
Merge pull request !652 from 疯狂的狮子Li/dev
2025-03-13 05:27:36 +00:00
疯狂的狮子Li
95c01301f6 !644 同步修复一些问题
Merge pull request !644 from 疯狂的狮子Li/dev
2025-02-07 06:19:28 +00:00
疯狂的狮子Li
296466fa13 !640 发布 5.3.0 新春版 祝大家新年快乐
Merge pull request !640 from 疯狂的狮子Li/dev
2025-01-24 05:08:28 +00:00
疯狂的狮子Li
3c8d864b5f !639 发布 5.3.0-BETA 公测版本
Merge pull request !639 from 疯狂的狮子Li/dev
2025-01-20 03:35:45 +00:00
疯狂的狮子Li
ea50a57602 update 优化 xss包装器 Parameter 处理 兼容某些容器不允许改参数的情况 2024-11-21 10:17:34 +08:00
疯狂的狮子Li
7e14b98676 reset 回滚错误修改
Signed-off-by: 疯狂的狮子Li <15040126243@163.com>
2024-10-28 09:46:28 +00:00
疯狂的狮子Li
015b406001 !591 发布 5.2.3 正式版
Merge pull request !591 from 疯狂的狮子Li/dev
2024-10-25 03:09:23 +00:00
疯狂的狮子Li
098d3347a0 !577 发布 5.2.2 正式版 安全性提升
Merge pull request !577 from 疯狂的狮子Li/dev
2024-08-26 03:43:59 +00:00
疯狂的狮子Li
08d4493994 update 优化 bug 模板 2024-07-15 15:19:22 +08:00
疯狂的狮子Li
367d739e2d Merge remote-tracking branch 'origin/5.X' into 5.X 2024-07-09 16:38:43 +08:00
疯狂的狮子Li
d6688a367d !562 ♥️发布 5.2.1 正式版本
Merge pull request !562 from 疯狂的狮子Li/dev
2024-07-09 02:42:40 +00:00
疯狂的狮子Li
0b331796e2 !551 ♥️发布 5.2.0 正式版本
Merge pull request !551 from 疯狂的狮子Li/dev
2024-06-20 02:10:15 +00:00
疯狂的狮子Li
456620b638 !549 ♥️发布 5.2.0-BETA2 公测版本
Merge pull request !549 from 疯狂的狮子Li/dev
2024-06-06 03:13:46 +00:00
44 changed files with 544 additions and 176 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.5.1" />
<option name="imageTag" value="ruoyi/ruoyi-monitor-admin:5.5.2" />
<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.5.1" />
<option name="imageTag" value="ruoyi/ruoyi-server:5.5.2" />
<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.5.1" />
<option name="imageTag" value="ruoyi/ruoyi-snailjob-server:5.5.2" />
<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.5.1-success.svg)](https://gitee.com/dromara/RuoYi-Vue-Plus)
[![RuoYi-Vue-Plus](https://img.shields.io/badge/RuoYi_Vue_Plus-5.5.2-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)]()

10
pom.xml
View File

@@ -13,8 +13,8 @@
<description>Dromara RuoYi-Vue-Plus多租户管理系统</description>
<properties>
<revision>5.5.1</revision>
<spring-boot.version>3.5.8</spring-boot.version>
<revision>5.5.2</revision>
<spring-boot.version>3.5.9</spring-boot.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>17</java.version>
@@ -31,14 +31,14 @@
<redisson.version>3.52.0</redisson.version>
<lock4j.version>2.2.7</lock4j.version>
<dynamic-ds.version>4.3.1</dynamic-ds.version>
<snailjob.version>1.8.0</snailjob.version>
<snailjob.version>1.9.0</snailjob.version>
<mapstruct-plus.version>1.5.0</mapstruct-plus.version>
<mapstruct-plus.lombok.version>0.2.0</mapstruct-plus.lombok.version>
<lombok.version>1.18.40</lombok.version>
<bouncycastle.version>1.80</bouncycastle.version>
<justauth.version>1.16.7</justauth.version>
<!-- 离线IP地址定位库 -->
<ip2region.version>2.7.0</ip2region.version>
<ip2region.version>3.3.1</ip2region.version>
<!-- OSS 配置 -->
<aws.sdk.version>2.28.22</aws.sdk.version>
<!-- SMS 配置 -->
@@ -46,7 +46,7 @@
<!-- 限制框架中的fastjson版本 -->
<fastjson.version>1.2.83</fastjson.version>
<!-- 面向运行时的D-ORM依赖 -->
<anyline.version>8.7.2-20250603</anyline.version>
<anyline.version>8.7.3-20251210</anyline.version>
<!-- 工作流配置 -->
<warm-flow.version>1.8.4</warm-flow.version>

View File

@@ -48,6 +48,7 @@ import org.springframework.web.bind.annotation.*;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -106,7 +107,7 @@ public class AuthController {
Long userId = LoginHelper.getUserId();
scheduledExecutorService.schedule(() -> {
SseMessageDto dto = new SseMessageDto();
dto.setMessage("欢迎登录RuoYi-Vue-Plus后台管理系统");
dto.setMessage(DateUtils.getTodayHour(new Date()) + "好,欢迎登录 RuoYi-Vue-Plus 后台管理系统");
dto.setUserIds(List.of(userId));
SseMessageUtils.publishMessage(dto);
}, 5, TimeUnit.SECONDS);
@@ -147,8 +148,8 @@ public class AuthController {
StpUtil.checkLogin();
// 获取第三方登录信息
AuthResponse<AuthUser> response = SocialUtils.loginAuth(
loginBody.getSource(), loginBody.getSocialCode(),
loginBody.getSocialState(), socialProperties);
loginBody.getSource(), loginBody.getSocialCode(),
loginBody.getSocialState(), socialProperties);
AuthUser authUserData = response.getData();
// 判断授权响应是否成功
if (!response.ok()) {

View File

@@ -14,7 +14,7 @@
</description>
<properties>
<revision>5.5.1</revision>
<revision>5.5.2</revision>
</properties>
<dependencyManagement>

View File

@@ -3,10 +3,8 @@ package org.dromara.common.core.config;
import jakarta.annotation.PreDestroy;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import org.dromara.common.core.config.properties.ThreadPoolProperties;
import org.dromara.common.core.utils.SpringUtils;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.core.task.VirtualThreadTaskExecutor;
@@ -19,7 +17,6 @@ import java.util.concurrent.*;
**/
@Slf4j
@AutoConfiguration
@EnableConfigurationProperties(ThreadPoolProperties.class)
public class ThreadPoolConfig {
/**

View File

@@ -1,30 +0,0 @@
package org.dromara.common.core.config.properties;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* 线程池 配置属性
*
* @author Lion Li
*/
@Data
@ConfigurationProperties(prefix = "thread-pool")
public class ThreadPoolProperties {
/**
* 是否开启线程池
*/
private boolean enabled;
/**
* 队列最大长度
*/
private int queueCapacity;
/**
* 线程池维护线程所允许的空闲时间
*/
private int keepAliveSeconds;
}

View File

@@ -1,5 +1,7 @@
package org.dromara.common.core.utils;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.dromara.common.core.enums.FormatsType;
import org.dromara.common.core.exception.ServiceException;
@@ -297,4 +299,80 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils {
}
}
/**
* 根据指定日期时间获取时间段(凌晨 / 上午 / 中午 / 下午 / 晚上)
*
* @param date 日期时间
* @return 时间段描述
*/
public static String getTodayHour(Date date) {
int hour = DateUtil.hour(date, true);
if (hour <= 6) {
return "凌晨";
} else if (hour < 12) {
return "上午";
} else if (hour == 12) {
return "中午";
} else if (hour <= 18) {
return "下午";
} else {
return "晚上";
}
}
/**
* 将日期格式化为仿微信的友好时间
* <p>
* 规则说明:
* 1. 未来时间yyyy-MM-dd HH:mm
* 2. 今天:
* - 1 分钟内:刚刚
* - 1 小时内X 分钟前
* - 超过 1 小时:凌晨/上午/中午/下午/晚上 HH:mm
* 3. 昨天:昨天 HH:mm
* 4. 本周周X HH:mm
* 5. 今年内MM-dd HH:mm
* 6. 非今年yyyy-MM-dd HH:mm
*
* @param date 日期时间
* @return 格式化后的时间描述
*/
public static String formatFriendlyTime(Date date) {
if (date == null) {
return "";
}
Date now = DateUtil.date();
// 未来时间或非今年
if (date.after(now) || DateUtil.year(date) != DateUtil.year(now)) {
return parseDateToStr(FormatsType.YYYY_MM_DD_HH_MM, date);
}
// 今天
if (DateUtil.isSameDay(date, now)) {
long minutes = DateUtil.between(date, now, DateUnit.MINUTE);
if (minutes < 1) {
return "刚刚";
}
if (minutes < 60) {
return minutes + "分钟前";
}
return getTodayHour(date) + " " + DateUtil.format(date, "HH:mm");
}
// 昨天
if (DateUtil.isSameDay(date, DateUtil.yesterday())) {
return "昨天 " + DateUtil.format(date, "HH:mm");
}
// 本周
if (DateUtil.isSameWeek(date, now, true)) {
return DateUtil.dayOfWeekEnum(date).toChinese("")
+ " " + DateUtil.format(date, "HH:mm");
}
// 今年内其它时间
return DateUtil.format(date, "MM-dd HH:mm");
}
}

View File

@@ -28,27 +28,60 @@ public class DesensitizedUtils extends DesensitizedUtil {
}
int len = value.length();
int prefixMaskLimit = prefixVisible + maskLength;
int fullLimit = prefixMaskLimit + suffixVisible;
// 总长度小于等于前后可见长度 → 全掩码
if (len <= prefixVisible + suffixVisible) {
// 规则 1长度 <= 中间掩码长度 → 全掩码
if (len <= maskLength) {
return StrUtil.repeat('*', len);
}
String mask = StrUtil.repeat('*', maskLength);
// 规则 2长度 <= 前缀 + 中间掩码
if (len <= prefixMaskLimit) {
return value.substring(0, len - maskLength) + mask;
}
String prefix = value.substring(0, prefixVisible);
// 规则 3长度 <= 前缀 + 中间掩码 + 后缀
if (len <= fullLimit) {
int suffixLen = len - prefixMaskLimit;
return prefix + mask + value.substring(len - suffixLen);
}
// 规则 4标准形态
return prefix + mask + value.substring(len - suffixVisible);
}
/**
* 高安全级别脱敏方法Token / 私钥)
*
* @param value 原始字符串
* @param prefixVisible 前面可见长度推荐0~4
* @param suffixVisible 后面可见长度推荐0~4
* @return 脱敏后字符串
*/
public static String maskHighSecurity(String value, int prefixVisible, int suffixVisible) {
if (StrUtil.isBlank(value)) {
return value;
}
int len = value.length();
// 规则1长度 <= 前缀可见长度 → 全部掩码
if (len <= prefixVisible) {
return StrUtil.repeat('*', len);
}
// 可用长度 = 总长度 - 前后可见长度
int available = len - prefixVisible - suffixVisible;
// 规则2长度 <= 前缀 + 后缀可见长度 → 优先掩码后面
if (len <= prefixVisible + suffixVisible) {
return value.substring(0, len - prefixVisible) + StrUtil.repeat('*', prefixVisible);
}
// 中间掩码长度不能超过可用长度
int actualMaskLength = Math.min(maskLength, available);
// 剩余字符尽量显示在中间掩码旁
int remaining = available - actualMaskLength;
String middleChars = remaining > 0 ? value.substring(prefixVisible, prefixVisible + remaining) : "";
String middleMask = StrUtil.repeat('*', actualMaskLength);
String prefix = value.substring(0, prefixVisible);
String suffix = value.substring(len - suffixVisible);
return prefix + middleChars + middleMask + suffix;
// 规则3标准形态 → 前后可见,中间全部掩码
return value.substring(0, prefixVisible)
+ StrUtil.repeat('*', len - prefixVisible - suffixVisible)
+ value.substring(len - suffixVisible);
}
}

View File

@@ -20,51 +20,24 @@ public class AddressUtils {
public static final String UNKNOWN_IP = "XX XX";
// 内网地址
public static final String LOCAL_ADDRESS = "内网IP";
// 未知地址
public static final String UNKNOWN_ADDRESS = "未知";
public static String getRealAddressByIP(String ip) {
// 处理空串并过滤HTML标签
ip = HtmlUtil.cleanHtmlTag(StringUtils.blankToDefault(ip,""));
// 判断是否为IPv4
if (NetUtils.isIPv4(ip)) {
return resolverIPv4Region(ip);
}
boolean isIPv4 = NetUtils.isIPv4(ip);
// 判断是否为IPv6
if (NetUtils.isIPv6(ip)) {
return resolverIPv6Region(ip);
}
boolean isIPv6 = NetUtils.isIPv6(ip);
// 如果不是IPv4或IPv6则返回未知IP
return UNKNOWN_IP;
}
/**
* 根据IPv4地址查询IP归属行政区域
* @param ip ipv4地址
* @return 归属行政区域
*/
private static String resolverIPv4Region(String ip){
if (!isIPv4 && !isIPv6) {
return UNKNOWN_IP;
}
// 内网不查询
if (NetUtils.isInnerIP(ip)) {
if ((isIPv4 && NetUtils.isInnerIP(ip)) || (isIPv6 && NetUtils.isInnerIPv6(ip))) {
return LOCAL_ADDRESS;
}
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;
// TipsIp2Region 提供了精简的IPv6地址库精简的IPv6地址库并不能完全支持IPv6地址的查询且准确度上可能会存在问题如需要准确的IPv6地址查询建议自行实现
return RegionUtils.getRegion(ip);
}
}

View File

@@ -1,50 +1,158 @@
package org.dromara.common.core.utils.ip;
import cn.hutool.core.io.resource.NoResourceException;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.resource.ResourceUtil;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.StringUtils;
import org.lionsoul.ip2region.xdb.Searcher;
import org.lionsoul.ip2region.service.Config;
import org.lionsoul.ip2region.service.Ip2Region;
import org.lionsoul.ip2region.xdb.Util;
import java.io.File;
import java.io.InputStream;
import java.time.Duration;
/**
* 根据ip地址定位工具类离线方式
* 参考地址:<a href="https://gitee.com/lionsoul/ip2region/tree/master/binding/java">集成 ip2region 实现离线IP地址定位库</a>
* IP地址行政区域工具类
* 参考地址:<a href="https://gitee.com/lionsoul/ip2region/tree/master/binding/java">ip2region xdb java 查询客户端实现</a>
* xdb数据库文件下载<a href="https://gitee.com/lionsoul/ip2region/tree/master/data">ip2region data</a>
*
* @author lishuyan
* @author 秋辞未寒
*/
@Slf4j
public class RegionUtils {
// IP地址库文件名称
public static final String IP_XDB_FILENAME = "ip2region.xdb";
// 默认IPv4地址库文件路径
// 下载地址https://gitee.com/lionsoul/ip2region/blob/master/data/ip2region_v4.xdb
public static final String DEFAULT_IPV4_XDB_PATH = "ip2region_v4.xdb";
private static final Searcher SEARCHER;
// 默认IPv6地址库文件路径
// 下载地址https://gitee.com/lionsoul/ip2region/blob/master/data/ip2region_v6.xdb
public static final String DEFAULT_IPV6_XDB_PATH = "ip2region_v6.xdb";
// 未知地址
public static final String UNKNOWN_ADDRESS = "未知";
// Ip2Region服务实例
private static Ip2Region ip2Region;
// 初始化Ip2Region服务实例
static {
try {
// 1、将 ip2region 数据库文件 xdb 从 ClassPath 加载到内存。
// 2、基于加载到内存的 xdb 数据创建一个 Searcher 查询对象
SEARCHER = Searcher.newWithBuffer(ResourceUtil.readBytes(IP_XDB_FILENAME));
log.info("RegionUtils初始化成功加载IP地址库数据成功");
} catch (NoResourceException e) {
throw new ServiceException("RegionUtils初始化失败原因IP地址库数据不存在");
// 注意Ip2Region 的xdb文件加载策略 CachePolicy 有三种分别是BufferCache全量读取xdb到内存中、VIndexCache默认策略按需读取并缓存、NoCache实时读取
// 本项目工具使用的 CachePolicy 为 BufferCacheBufferCache会加载整个xdb文件到内存中setXdbInputStream 仅支持 BufferCache 策略
// 因为加载整个xdb文件会耗费非常大的内存如果你不希望加载整个xdb到内存中更推荐使用 VIndexCache 或 NoCache即实时读取文件策略和 setXdbPath/setXdbFile 加载方法需要注意的一点setXdbPath 和 setXdbFile 不支持读取ClassPath即源码和resource目录中的文件
// 一般而言更建议把xdb数据库放到一个指定的文件目录中即不打包进jar包中然后使用 NoCache + 配合SearcherPool的并发池读取数据更方便随时更新xdb数据库
// TODO 2025年12月23日 Ip2Region封装的 InputStream 读取函数 Searcher.loadContentFromInputStream 在Linux环境下会申请过大的byte[]空间而导致OOM这里先用临时文件的方案解决等后续 Ip2Region 更新解决方案
// 创建临时文件
File v4TempXdb = FileUtil.writeFromStream(ResourceUtil.getStream(DEFAULT_IPV4_XDB_PATH), FileUtil.createTempFile());
// IPv4配置
Config v4Config = Config.custom()
.setCachePolicy(Config.BufferCache)
.setXdbFile(v4TempXdb)
// .setXdbInputStream(ResourceUtil.getStream(DEFAULT_IPV4_XDB_PATH))
.asV4();
// 删除临时文件
v4TempXdb.delete();
// IPv6配置
Config v6Config = null;
InputStream v6XdbInputStream = ResourceUtil.getStreamSafe(DEFAULT_IPV6_XDB_PATH);
if (v6XdbInputStream == null) {
log.warn("未加载 IPv6 地址库:未在类路径下找到文件 {}。当前仅启用 IPv4 查询。如需启用 IPv6请将 ip2region_v6.xdb 放置到 resources 目录", DEFAULT_IPV6_XDB_PATH);
} else {
// 创建临时文件
File v6TempXdb = FileUtil.writeFromStream(ResourceUtil.getStream(DEFAULT_IPV4_XDB_PATH), FileUtil.createTempFile());
v6Config = Config.custom()
.setCachePolicy(Config.BufferCache)
.setXdbFile(v6TempXdb)
// .setXdbInputStream(v6XdbInputStream)
.asV6();
// 删除临时文件
v6TempXdb.delete();
}
// 初始化Ip2Region实例
RegionUtils.ip2Region = Ip2Region.create(v4Config, v6Config);
log.debug("IP工具初始化成功加载IP地址库数据成功");
} catch (Exception e) {
throw new ServiceException("RegionUtils初始化失败原因" + e.getMessage());
throw new ServiceException("RegionUtils初始化失败原因{}", e.getMessage());
}
}
/**
* 根据IP地址离线获取城市
*
* @param ipString ip地址字符串
*/
public static String getCityInfo(String ip) {
public static String getRegion(String ipString) {
try {
// 3、执行查询
String region = SEARCHER.search(StringUtils.trim(ip));
return region.replace("0|", "").replace("|0", "");
String region = ip2Region.search(ipString);
if (StringUtils.isBlank(region)) {
region = UNKNOWN_ADDRESS;
}
return region;
} catch (Exception e) {
log.error("IP地址离线获取城市异常 {}", ip);
return "未知";
log.error("IP地址离线获取城市异常 {}", ipString);
return UNKNOWN_ADDRESS;
}
}
/**
* 根据IP地址离线获取城市
*
* @param ipBytes ip地址字节数组
*/
public static String getRegion(byte[] ipBytes) {
try {
String region = ip2Region.search(ipBytes);
if (StringUtils.isBlank(region)) {
region = UNKNOWN_ADDRESS;
}
return region;
} catch (Exception e) {
log.error("IP地址离线获取城市异常 {}", Util.ipToString(ipBytes));
return UNKNOWN_ADDRESS;
}
}
/**
* 关闭Ip2Region服务
*/
public static void close() {
if (ip2Region == null) {
return;
}
try {
ip2Region.close(10000);
} catch (Exception e) {
log.error("Ip2Region服务关闭异常", e);
}
}
/**
* 关闭Ip2Region服务
*
* @param timeout 关闭超时时间
*/
public static void close(final Duration timeout) {
if (ip2Region == null) {
return;
}
if (timeout == null) {
close();
return;
}
try {
ip2Region.close(timeout.toMillis());
} catch (Exception e) {
log.error("Ip2Region服务关闭异常", e);
}
}

View File

@@ -29,7 +29,10 @@ public class CellMergeHandler {
// 行合并开始下标
this.rowIndex = hasTitle ? 1 : 0;
}
private CellMergeHandler(final boolean hasTitle, final int rowIndex) {
this.hasTitle = hasTitle;
this.rowIndex = hasTitle ? rowIndex : 0;
}
@SneakyThrows
public List<CellRangeAddress> handle(List<?> rows) {
// 如果入参为空集合则返回空集
@@ -103,6 +106,10 @@ public class CellMergeHandler {
}
if (isAddResult && i > current) {
//如果是同一行,则跳过合并
if (current + rowIndex == lastRow) {
continue;
}
result.add(new CellRangeAddress(current + rowIndex, lastRow, colNum, colNum));
}
}
@@ -177,6 +184,16 @@ public class CellMergeHandler {
return new FieldColumnIndex(colIndex, cellMerge);
}
}
/**
* 创建一个单元格合并处理器实例
*
* @param hasTitle 是否合并标题
* @param rowIndex 行索引
* @return 单元格合并处理器
*/
public static CellMergeHandler of(final boolean hasTitle, final int rowIndex) {
return new CellMergeHandler(hasTitle, rowIndex);
}
/**
* 创建一个单元格合并处理器实例

View File

@@ -31,6 +31,10 @@ public class CellMergeStrategy extends AbstractMergeStrategy implements SheetWri
this.cellList = CellMergeHandler.of(hasTitle).handle(list);
}
public CellMergeStrategy(List<?> list, boolean hasTitle, int rowIndex) {
this.cellList = CellMergeHandler.of(hasTitle, rowIndex).handle(list);
}
@Override
protected void merge(Sheet sheet, Cell cell, Head head, Integer relativeRowIndex) {
if (CollUtil.isEmpty(cellList)) {

View File

@@ -0,0 +1,129 @@
package org.dromara.common.mybatis.utils;
import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.dromara.common.core.utils.SpringUtils;
/**
* ID 生成工具类
*
* @author AprilWind
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class IdGeneratorUtil {
private static final IdentifierGenerator GENERATOR = SpringUtils.getBean(IdentifierGenerator.class);
/**
* 生成字符串类型主键 ID
* <p>
* 调用 {@link IdentifierGenerator#nextId(Object)},返回 String 格式 ID。
* </p>
*
* @return 字符串格式主键 ID
*/
public static String nextId() {
return GENERATOR.nextId(null).toString();
}
/**
* 生成 Long 类型主键 ID
* <p>
* 自动将生成的数字型主键转换为 Long 类型
* </p>
*
* @return Long 类型主键 ID
*/
public static Long nextLongId() {
return GENERATOR.nextId(null).longValue();
}
/**
* 生成 Number 类型主键 ID
* <p>
* 推荐在需要保留原始 Number 类型时使用
* </p>
*
* @return Number 类型主键 ID
*/
public static Number nextNumberId() {
return GENERATOR.nextId(null);
}
/**
* 根据实体生成数字型主键 ID
* <p>
* 若自定义的 {@link IdentifierGenerator} 根据实体内容生成 ID则可以使用本方法
* </p>
*
* @param entity 实体对象
* @return Number 类型主键 ID
*/
public static Number nextId(Object entity) {
return GENERATOR.nextId(entity);
}
/**
* 根据实体生成字符串主键 ID
* <p>
* 与 {@link #nextId(Object)} 类似,但返回 String 类型
* </p>
*
* @param entity 实体对象
* @return 字符串格式主键 ID
*/
public static String nextStringId(Object entity) {
return GENERATOR.nextId(entity).toString();
}
/**
* 生成 32 位 UUID
* <p>
* 底层使用 {@link IdWorker#get32UUID()}
* </p>
*
* @return 32 位 UUID 字符串
*/
public static String nextUUID() {
return IdWorker.get32UUID();
}
/**
* 根据实体生成 32 位 UUID
* <p>
* 默认 {@link IdentifierGenerator#nextUUID(Object)} 实现忽略实体,但保留该方法便于扩展。
* </p>
*
* @param entity 实体对象
* @return 32 位 UUID 字符串
*/
public static String nextUUID(Object entity) {
return GENERATOR.nextUUID(entity);
}
/**
* 生成带指定前缀的字符串主键 ID
* <p>
* 示例prefix = "ORD",生成结果形如:{@code ORD20251211000123}
* </p>
*
* @param prefix 自定义前缀
* @return 带前缀的字符串主键 ID
*/
public static String nextIdWithPrefix(String prefix) {
return prefix + nextId();
}
/**
* 生成带前缀的 UUID
*
* @param prefix 前缀
* @return prefix + UUID
*/
public static String nextUUIDWithPrefix(String prefix) {
return prefix + nextUUID();
}
}

View File

@@ -88,6 +88,11 @@ public enum SensitiveStrategy {
*/
STRING_MASK(s -> DesensitizedUtils.mask(s, 4, 4, 4)),
/**
* 高安全级别脱敏Token / 私钥前2位可见后2位可见中间全部掩码
*/
MASK_HIGH_SECURITY(s -> DesensitizedUtils.maskHighSecurity(s, 2, 2)),
/**
* 清空为""
*/

View File

@@ -28,9 +28,14 @@ public class SocialLoginConfigProperties {
private String redirectUri;
/**
* 是否获取unionId
* 是否需要申请unionid目前只针对qq登录
*/
private boolean unionId;
private Boolean unionId;
/**
* Microsoft Entra ID原微软 AAD中的租户 ID
*/
private String tenantId;
/**
* Coding 企业名称

View File

@@ -57,7 +57,7 @@ public class SocialUtils {
case "taobao" -> new AuthTaobaoRequest(builder.build(), STATE_CACHE);
case "douyin" -> new AuthDouyinRequest(builder.build(), STATE_CACHE);
case "linkedin" -> new AuthLinkedinRequest(builder.build(), STATE_CACHE);
case "microsoft" -> new AuthMicrosoftRequest(builder.build(), STATE_CACHE);
case "microsoft" -> new AuthMicrosoftRequest(builder.tenantId(obj.getTenantId()).build(), STATE_CACHE);
case "renren" -> new AuthRenrenRequest(builder.build(), STATE_CACHE);
case "stack_overflow" -> new AuthStackOverflowRequest(builder.stackOverflowKey(obj.getStackOverflowKey()).build(), STATE_CACHE);
case "huawei" -> new AuthHuaweiV3Request(builder.build(), STATE_CACHE);

View File

@@ -20,7 +20,7 @@ public class NicknameTranslationImpl implements TranslationInterface<String> {
@Override
public String translation(Object key, String other) {
if (key instanceof Long id) {
return userService.selectNicknameByIds(id.toString());
return userService.selectNicknameById(id);
} else if (key instanceof String ids) {
return userService.selectNicknameByIds(ids);
}

View File

@@ -1,5 +1,6 @@
package org.dromara.common.translation.core.impl;
import cn.hutool.core.convert.Convert;
import org.dromara.common.core.service.UserService;
import org.dromara.common.translation.annotation.TranslationType;
import org.dromara.common.translation.constant.TransConstant;
@@ -19,9 +20,6 @@ public class UserNameTranslationImpl implements TranslationInterface<String> {
@Override
public String translation(Object key, String other) {
if (key instanceof Long id) {
return userService.selectUserNameById(id);
}
return null;
return userService.selectUserNameById(Convert.toLong(key));
}
}

View File

@@ -14,6 +14,7 @@ import org.dromara.common.core.exception.SseException;
import org.dromara.common.core.exception.base.BaseException;
import org.dromara.common.core.utils.StreamUtils;
import org.dromara.common.json.utils.JsonUtils;
import org.springframework.context.MessageSourceResolvable;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.expression.ExpressionException;
import org.springframework.http.converter.HttpMessageNotReadableException;
@@ -25,6 +26,7 @@ import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.async.AsyncRequestTimeoutException;
import org.springframework.web.method.annotation.HandlerMethodValidationException;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.servlet.NoHandlerFoundException;
@@ -191,6 +193,16 @@ public class GlobalExceptionHandler {
return R.fail(message);
}
/**
* 方法参数校验异常 用于处理 @Validated 注解
*/
@ExceptionHandler(HandlerMethodValidationException.class)
public R<Void> handlerMethodValidationException(HandlerMethodValidationException e) {
log.error(e.getMessage());
String message = StreamUtils.join(e.getAllErrors(), MessageSourceResolvable::getDefaultMessage, ", ");
return R.fail(message);
}
/**
* JSON 解析异常Jackson 在处理 JSON 格式出错时抛出)
* 可能是请求体格式非法,也可能是服务端反序列化失败

View File

@@ -8,6 +8,7 @@ import org.dromara.common.websocket.holder.WebSocketSessionHolder;
import org.dromara.common.websocket.utils.WebSocketUtils;
import org.springframework.web.socket.*;
import org.springframework.web.socket.handler.AbstractWebSocketHandler;
import org.springframework.web.socket.handler.ConcurrentWebSocketSessionDecorator;
import java.io.IOException;
import java.util.List;
@@ -33,7 +34,7 @@ public class PlusWebSocketHandler extends AbstractWebSocketHandler {
log.info("[connect] invalid token received. sessionId: {}", session.getId());
return;
}
WebSocketSessionHolder.addSession(loginUser.getUserId(), session);
WebSocketSessionHolder.addSession(loginUser.getUserId(), new ConcurrentWebSocketSessionDecorator(session, 10 * 1000, 64000));
log.info("[connect] sessionId: {},userId:{},userType:{}", session.getId(), loginUser.getUserId(), loginUser.getUserType());
}

View File

@@ -113,7 +113,7 @@ public class WebSocketUtils {
* @param session WebSocket会话
* @param message 要发送的WebSocket消息对象
*/
private synchronized static void sendMessage(WebSocketSession session, WebSocketMessage<?> message) {
private static void sendMessage(WebSocketSession session, WebSocketMessage<?> message) {
if (session == null || !session.isOpen()) {
log.warn("[send] session会话已经关闭");
} else {

View File

@@ -62,6 +62,8 @@ public class TestDemoServiceImpl implements ITestDemoService {
private LambdaQueryWrapper<TestDemo> buildQueryWrapper(TestDemoBo bo) {
Map<String, Object> params = bo.getParams();
LambdaQueryWrapper<TestDemo> lqw = Wrappers.lambdaQuery();
lqw.eq(bo.getDeptId() != null, TestDemo::getDeptId, bo.getDeptId());
lqw.eq(bo.getUserId() != null, TestDemo::getUserId, bo.getUserId());
lqw.like(StringUtils.isNotBlank(bo.getTestKey()), TestDemo::getTestKey, bo.getTestKey());
lqw.eq(StringUtils.isNotBlank(bo.getValue()), TestDemo::getValue, bo.getValue());
lqw.between(params.get("beginCreateTime") != null && params.get("endCreateTime") != null,

View File

@@ -2,6 +2,7 @@ package org.dromara.demo.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.demo.domain.TestTree;
@@ -9,7 +10,6 @@ import org.dromara.demo.domain.bo.TestTreeBo;
import org.dromara.demo.domain.vo.TestTreeVo;
import org.dromara.demo.mapper.TestTreeMapper;
import org.dromara.demo.service.ITestTreeService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.Collection;
@@ -44,6 +44,8 @@ public class TestTreeServiceImpl implements ITestTreeService {
private LambdaQueryWrapper<TestTree> buildQueryWrapper(TestTreeBo bo) {
Map<String, Object> params = bo.getParams();
LambdaQueryWrapper<TestTree> lqw = Wrappers.lambdaQuery();
lqw.eq(bo.getDeptId() != null, TestTree::getDeptId, bo.getDeptId());
lqw.eq(bo.getUserId() != null, TestTree::getUserId, bo.getUserId());
lqw.like(StringUtils.isNotBlank(bo.getTreeName()), TestTree::getTreeName, bo.getTreeName());
lqw.between(params.get("beginCreateTime") != null && params.get("endCreateTime") != null,
TestTree::getCreateTime, params.get("beginCreateTime"), params.get("endCreateTime"));

View File

@@ -4,13 +4,12 @@ import com.baomidou.mybatisplus.annotation.FieldStrategy;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.apache.ibatis.type.JdbcType;
import jakarta.validation.constraints.NotBlank;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mybatis.core.domain.BaseEntity;
/**
* 代码生成业务字段表 gen_table_column
@@ -115,6 +114,7 @@ public class GenTableColumn extends BaseEntity {
/**
* 字典类型
*/
@TableField(updateStrategy = FieldStrategy.ALWAYS, jdbcType = JdbcType.VARCHAR)
private String dictType;
/**

View File

@@ -8,7 +8,6 @@ import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.dynamic.datasource.annotation.DSTransactional;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.RequiredArgsConstructor;
@@ -28,6 +27,7 @@ import org.dromara.common.core.utils.file.FileUtils;
import org.dromara.common.json.utils.JsonUtils;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.mybatis.utils.IdGeneratorUtil;
import org.dromara.generator.constant.GenConstants;
import org.dromara.generator.domain.GenTable;
import org.dromara.generator.domain.GenTableColumn;
@@ -60,7 +60,6 @@ public class GenTableServiceImpl implements IGenTableService {
private final GenTableMapper baseMapper;
private final GenTableColumnMapper genTableColumnMapper;
private final IdentifierGenerator identifierGenerator;
private static final String[] TABLE_IGNORE = new String[]{"sj_", "flow_", "gen_"};
@@ -322,7 +321,7 @@ public class GenTableServiceImpl implements IGenTableService {
GenTable table = baseMapper.selectGenTableById(tableId);
List<Long> menuIds = new ArrayList<>();
for (int i = 0; i < 6; i++) {
menuIds.add(identifierGenerator.nextId(null).longValue());
menuIds.add(IdGeneratorUtil.nextLongId());
}
table.setMenuIds(menuIds);
// 设置主键列信息
@@ -468,7 +467,7 @@ public class GenTableServiceImpl implements IGenTableService {
GenTable table = baseMapper.selectGenTableById(tableId);
List<Long> menuIds = new ArrayList<>();
for (int i = 0; i < 6; i++) {
menuIds.add(identifierGenerator.nextId(null).longValue());
menuIds.add(IdGeneratorUtil.nextLongId());
}
table.setMenuIds(menuIds);
// 设置主键列信息

View File

@@ -92,4 +92,20 @@ public interface FlowConstant {
* 业务编码
*/
String BUSINESS_CODE = "businessCode";
/**
* 忽略-办理权限校验true忽略false不忽略
*/
String VAR_IGNORE = "ignore";
/**
* 忽略-委派处理true忽略false不忽略
*/
String VAR_IGNORE_DEPUTE = "ignoreDepute";
/**
* 忽略-会签票签处理true忽略false不忽略
*/
String VAR_IGNORE_COOPERATE = "ignoreCooperate";
}

View File

@@ -253,4 +253,8 @@ public class FlowHisTaskVo implements Serializable {
this.cooperateTypeName = CooperateType.getValueByKey(cooperateType);
}
public String getCreateTime() {
return DateUtils.formatFriendlyTime(createTime);
}
}

View File

@@ -1,6 +1,7 @@
package org.dromara.workflow.domain.vo;
import lombok.Data;
import org.dromara.common.core.utils.DateUtils;
import org.dromara.common.translation.annotation.Translation;
import org.dromara.common.translation.constant.TransConstant;
import org.dromara.warm.flow.core.entity.User;
@@ -8,7 +9,6 @@ import org.dromara.workflow.common.constant.FlowConstant;
import java.io.Serial;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import java.util.Map;
@@ -212,4 +212,8 @@ public class FlowTaskVo implements Serializable {
private String businessTitle;
//业务扩展信息结束
public String getCreateTime() {
return DateUtils.formatFriendlyTime(createTime);
}
}

View File

@@ -7,6 +7,7 @@ import org.dromara.common.core.domain.event.ProcessEvent;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.tenant.helper.TenantHelper;
import org.dromara.warm.flow.core.entity.Instance;
import org.dromara.warm.flow.core.entity.Task;
import org.dromara.workflow.common.ConditionalOnEnable;
import org.springframework.stereotype.Component;
@@ -55,22 +56,22 @@ public class FlowProcessEventHandler {
*
* @param flowCode 流程定义编码
* @param instance 实例数据
* @param taskId 任务id
* @param nextTask 任务
* @param params 上一个任务的办理参数
*/
public void processTaskHandler(String flowCode, Instance instance, Long taskId, Map<String, Object> params) {
public void processTaskHandler(String flowCode, Instance instance, Task nextTask, Map<String, Object> params) {
String tenantId = TenantHelper.getTenantId();
log.info("【流程任务事件发布】租户ID: {}, 流程编码: {}, 业务ID: {}, 节点类型: {}, 节点编码: {}, 节点名称: {}, 任务ID: {}",
tenantId, flowCode, instance.getBusinessId(), instance.getNodeType(), instance.getNodeCode(), instance.getNodeName(), taskId);
tenantId, flowCode, instance.getBusinessId(), nextTask.getNodeType(), nextTask.getNodeCode(), nextTask.getNodeName(), nextTask.getId());
ProcessTaskEvent processTaskEvent = new ProcessTaskEvent();
processTaskEvent.setTenantId(tenantId);
processTaskEvent.setFlowCode(flowCode);
processTaskEvent.setInstanceId(instance.getId());
processTaskEvent.setBusinessId(instance.getBusinessId());
processTaskEvent.setNodeType(instance.getNodeType());
processTaskEvent.setNodeCode(instance.getNodeCode());
processTaskEvent.setNodeName(instance.getNodeName());
processTaskEvent.setTaskId(taskId);
processTaskEvent.setNodeType(nextTask.getNodeType());
processTaskEvent.setNodeCode(nextTask.getNodeCode());
processTaskEvent.setNodeName(nextTask.getNodeName());
processTaskEvent.setTaskId(nextTask.getId());
processTaskEvent.setStatus(instance.getFlowStatus());
processTaskEvent.setParams(params);
SpringUtils.context().publishEvent(processTaskEvent);

View File

@@ -74,6 +74,9 @@ public class WorkflowGlobalListener implements GlobalListener {
String ext = listenerVariable.getNode().getExt();
if (StringUtils.isNotBlank(ext)) {
Map<String, Object> variable = listenerVariable.getVariable();
if (CollUtil.isEmpty(variable)) {
variable = new HashMap<>();
}
NodeExtVo nodeExt = nodeExtService.parseNodeExt(ext, variable);
Set<String> copyList = nodeExt.getCopySettings();
if (CollUtil.isNotEmpty(copyList)) {
@@ -208,7 +211,7 @@ public class WorkflowGlobalListener implements GlobalListener {
//发布任务事件
if (CollUtil.isNotEmpty(nextTasks)) {
for (Task nextTask : nextTasks) {
flowProcessEventHandler.processTaskHandler(definition.getFlowCode(), instance, nextTask.getId(), params);
flowProcessEventHandler.processTaskHandler(definition.getFlowCode(), instance, nextTask, params);
}
}
if (ObjectUtil.isNull(flowParams)) {

View File

@@ -206,6 +206,9 @@ public class FlwCategoryServiceImpl implements IFlwCategoryService, CategoryServ
if (ObjectUtil.isNull(oldCategory)) {
throw new ServiceException("流程分类不存在,无法修改");
}
if (oldCategory.getParentId() == 0L && category.getParentId() != 0L) {
throw new ServiceException("不允许修改顶级分类的父级节点");
}
if (!oldCategory.getParentId().equals(category.getParentId())) {
FlowCategory newParentCategory = baseMapper.selectById(category.getParentId());
if (ObjectUtil.isNotNull(newParentCategory)) {

View File

@@ -208,10 +208,6 @@ public class FlwDefinitionServiceImpl implements IFlwDefinitionService {
@Override
@Transactional(rollbackFor = Exception.class)
public void syncDef(String tenantId) {
List<FlowDefinition> flowDefinitions = flowDefinitionMapper.selectList(new LambdaQueryWrapper<FlowDefinition>().eq(FlowDefinition::getTenantId, DEFAULT_TENANT_ID));
if (CollUtil.isEmpty(flowDefinitions)) {
return;
}
FlowCategory flowCategory = flwCategoryMapper.selectOne(new LambdaQueryWrapper<FlowCategory>()
.eq(FlowCategory::getTenantId, DEFAULT_TENANT_ID)
.eq(FlowCategory::getCategoryId, FlowConstant.FLOW_CATEGORY_ID));
@@ -223,6 +219,11 @@ public class FlwDefinitionServiceImpl implements IFlwDefinitionService {
flowCategory.setUpdateBy(null);
flowCategory.setUpdateTime(null);
flwCategoryMapper.insert(flowCategory);
List<FlowDefinition> flowDefinitions = flowDefinitionMapper.selectList(new LambdaQueryWrapper<FlowDefinition>().eq(FlowDefinition::getTenantId, DEFAULT_TENANT_ID));
if (CollUtil.isEmpty(flowDefinitions)) {
return;
}
List<Long> defIds = StreamUtils.toList(flowDefinitions, FlowDefinition::getId);
List<FlowNode> flowNodes = flowNodeMapper.selectList(new LambdaQueryWrapper<FlowNode>().in(FlowNode::getDefinitionId, defIds));
List<FlowSkip> flowSkips = flowSkipMapper.selectList(new LambdaQueryWrapper<FlowSkip>().in(FlowSkip::getDefinitionId, defIds));

View File

@@ -9,7 +9,6 @@ import cn.hutool.core.util.StrUtil;
import com.baomidou.lock.annotation.Lock4j;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.RequiredArgsConstructor;
@@ -27,6 +26,7 @@ import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.json.utils.JsonUtils;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.mybatis.utils.IdGeneratorUtil;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.warm.flow.core.FlowEngine;
import org.dromara.warm.flow.core.dto.FlowParams;
@@ -86,7 +86,6 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
private final FlowInstanceMapper flowInstanceMapper;
private final FlowTaskMapper flowTaskMapper;
private final FlowHisTaskMapper flowHisTaskMapper;
private final IdentifierGenerator identifierGenerator;
private final UserService userService;
private final FlwTaskMapper flwTaskMapper;
private final FlwCategoryMapper flwCategoryMapper;
@@ -233,6 +232,9 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
FlowParams flowParams = FlowParams.build()
.handler(completeTaskBo.getHandler())
.variable(variables)
.ignore(Convert.toBool(variables.getOrDefault(VAR_IGNORE, false)))
.ignoreDepute(Convert.toBool(variables.getOrDefault(VAR_IGNORE_DEPUTE, false)))
.ignoreCooperate(Convert.toBool(variables.getOrDefault(VAR_IGNORE_COOPERATE, false)))
.skipType(SkipType.PASS.getKey())
.message(completeTaskBo.getMessage())
.flowStatus(BusinessStatusEnum.WAITING.getStatus())
@@ -332,7 +334,7 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
flowNode.setNodeCode(flowHisTask.getTargetNodeCode());
flowNode.setNodeName(flowHisTask.getTargetNodeName());
//生成新的任务id
long taskId = identifierGenerator.nextId(null).longValue();
long taskId = IdGeneratorUtil.nextLongId();
task.setId(taskId);
task.setNodeName("【抄送】" + task.getNodeName());
Date updateTime = new Date(flowHisTask.getUpdateTime().getTime() - 1000);
@@ -527,20 +529,22 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
}
//获取可驳回的前置节点
List<Node> nodes = nodeService.previousNodeList(task.getDefinitionId(), nowNodeCode);
List<HisTask> taskList = hisTaskService.getByInsId(task.getInstanceId());
List<HisTask> hisTaskList = hisTaskService.getByInsId(task.getInstanceId());
Map<String, Node> nodeMap = StreamUtils.toIdentityMap(nodes, Node::getNodeCode);
Set<String> added = new HashSet<>();
List<Node> backNodeList = new ArrayList<>();
for (HisTask hisTask : taskList) {
for (HisTask hisTask : hisTaskList) {
Node nodeValue = nodeMap.get(hisTask.getNodeCode());
if (nodeValue != null) {
if (nodeValue != null
&& NodeType.BETWEEN.getKey().equals(nodeValue.getNodeType())
&& added.add(nodeValue.getNodeCode())) {
backNodeList.add(nodeValue);
}
}
if (CollUtil.isNotEmpty(backNodeList)) {
List<Node> prefixOrSuffixNodes = StreamUtils.filter(backNodeList, e -> NodeType.BETWEEN.getKey().equals(e.getNodeType()));
Collections.reverse(prefixOrSuffixNodes);
return prefixOrSuffixNodes;
Collections.reverse(backNodeList);
return backNodeList;
}
return nodes;
}
@@ -744,8 +748,8 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
Task task = taskService.getById(taskId);
FlowNode flowNode = getByNodeCode(task.getNodeCode(), task.getDefinitionId());
if (ADD_SIGNATURE.equals(taskOperation) || REDUCTION_SIGNATURE.equals(taskOperation)) {
if (!CooperateType.isCountersign(flowNode.getNodeRatio())) {
throw new ServiceException(task.getNodeName() + "不是会签节点!");
if (CooperateType.isOrSign(flowNode.getNodeRatio())) {
throw new ServiceException(task.getNodeName() + "不是会签或票签节点!");
}
}
// 设置任务状态并执行对应的任务操作

View File

@@ -27,7 +27,10 @@
d.flow_code,
d.form_custom,
d.category,
COALESCE(t.form_path, d.form_path) as form_path,
COALESCE(
NULLIF(TRIM(t.form_path), ''),
NULLIF(TRIM(d.form_path), '')
) AS form_path,
d.version,
uu.processed_by,
uu.type,

View File

@@ -99,7 +99,7 @@ services:
network_mode: "host"
ruoyi-server1:
image: ruoyi/ruoyi-server:5.5.1
image: ruoyi/ruoyi-server:5.5.2
container_name: ruoyi-server1
environment:
# 时区上海
@@ -115,7 +115,7 @@ services:
network_mode: "host"
ruoyi-server2:
image: ruoyi/ruoyi-server:5.5.1
image: ruoyi/ruoyi-server:5.5.2
container_name: ruoyi-server2
environment:
# 时区上海
@@ -131,7 +131,7 @@ services:
network_mode: "host"
ruoyi-monitor-admin:
image: ruoyi/ruoyi-monitor-admin:5.5.1
image: ruoyi/ruoyi-monitor-admin:5.5.2
container_name: ruoyi-monitor-admin
environment:
# 时区上海
@@ -143,7 +143,7 @@ services:
network_mode: "host"
ruoyi-snailjob-server:
image: ruoyi/ruoyi-snailjob-server:5.5.1
image: ruoyi/ruoyi-snailjob-server:5.5.2
container_name: ruoyi-snailjob-server
environment:
# 时区上海

View File

@@ -106,11 +106,6 @@ http {
proxy_buffering off;
# 禁用代理缓存
proxy_cache off;
# 按 IP 限制连接数(防 CC 攻击) 小型站10~20 就够 中型站50~100
limit_conn perip 20;
# 按 Server 限制总并发连接数 根据服务器的最大并发处理能力来定 太小会限制合法用户访问,太大会占满服务器资源
limit_conn perserver 500;
proxy_pass http://server/;
}

View File

@@ -738,7 +738,7 @@ CREATE TABLE sj_retry_summary
id number GENERATED ALWAYS AS IDENTITY,
namespace_id varchar2(64) DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' NULL,
group_name varchar2(64) DEFAULT '' NULL,
scene_name varchar2(50) DEFAULT '' NULL,
scene_name varchar2(64) DEFAULT '' NULL,
trigger_at date DEFAULT CURRENT_TIMESTAMP NOT NULL,
running_num number DEFAULT 0 NOT NULL,
finish_num number DEFAULT 0 NOT NULL,

View File

@@ -684,7 +684,7 @@ CREATE TABLE sj_retry_summary
id bigserial PRIMARY KEY,
namespace_id varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a',
group_name varchar(64) NOT NULL DEFAULT '',
scene_name varchar(50) NOT NULL DEFAULT '',
scene_name varchar(64) NOT NULL DEFAULT '',
trigger_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
running_num int NOT NULL DEFAULT 0,
finish_num int NOT NULL DEFAULT 0,

View File

@@ -423,7 +423,7 @@ CREATE TABLE `sj_retry_summary`
`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
`namespace_id` VARCHAR(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' COMMENT '命名空间id',
`group_name` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '组名称',
`scene_name` VARCHAR(50) NOT NULL DEFAULT '' COMMENT '场景名称',
`scene_name` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '场景名称',
`trigger_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '统计时间',
`running_num` int NOT NULL DEFAULT 0 COMMENT '重试中-日志数量',
`finish_num` int NOT NULL DEFAULT 0 COMMENT '重试完成-日志数量',

View File

@@ -2248,7 +2248,7 @@ CREATE TABLE sj_retry_summary
id bigint NOT NULL PRIMARY KEY IDENTITY,
namespace_id nvarchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a',
group_name nvarchar(64) NOT NULL DEFAULT '',
scene_name nvarchar(50) NOT NULL DEFAULT '',
scene_name nvarchar(64) NOT NULL DEFAULT '',
trigger_at datetime2 NOT NULL DEFAULT CURRENT_TIMESTAMP,
running_num int NOT NULL DEFAULT 0,
finish_num int NOT NULL DEFAULT 0,