!247 发布 2.5.2 版本 2025年最后一版

Merge pull request !247 from 疯狂的狮子Li/dev
This commit is contained in:
疯狂的狮子Li 2025-12-23 01:40:03 +00:00 committed by Gitee
commit 1be37bae6b
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
113 changed files with 1415 additions and 666 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2,7 +2,7 @@
<configuration default="false" name="ruoyi-seata-server" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
<deployment type="dockerfile">
<settings>
<option name="imageTag" value="ruoyi/ruoyi-seata-server:2.5.1" />
<option name="imageTag" value="ruoyi/ruoyi-seata-server:2.5.2" />
<option name="buildOnly" value="true" />
<option name="sourceFilePath" value="ruoyi-visual/ruoyi-seata-server/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:2.5.1" />
<option name="imageTag" value="ruoyi/ruoyi-snailjob-server:2.5.2" />
<option name="buildOnly" value="true" />
<option name="sourceFilePath" value="ruoyi-visual/ruoyi-snailjob-server/Dockerfile" />
</settings>

View File

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

View File

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

View File

@ -10,7 +10,7 @@
[![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://gitee.com/dromara/RuoYi-Cloud-Plus/blob/2.X/LICENSE)
[![使用IntelliJ IDEA开发维护](https://img.shields.io/badge/IntelliJ%20IDEA-提供支持-blue.svg)](https://www.jetbrains.com/?from=RuoYi-Cloud-Plus)
<br>
[![RuoYi-Cloud-Plus](https://img.shields.io/badge/RuoYi_Cloud_Plus-2.5.1-success.svg)](https://gitee.com/dromara/RuoYi-Cloud-Plus)
[![RuoYi-Cloud-Plus](https://img.shields.io/badge/RuoYi_Cloud_Plus-2.5.2-success.svg)](https://gitee.com/dromara/RuoYi-Cloud-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)]()
@ -39,6 +39,7 @@ CCFlow 驰聘低代码-流程-表单 - https://gitee.com/opencc/RuoYi-JFlow <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>
Topiam IAM/IDaaS身份管理平台 - https://www.topiam.cn/ <br>
[如何成为赞助商 加群联系作者详谈 每日PV2500-3000 IP1700-2500](https://plus-doc.dromara.org/#/common/add_group)

20
pom.xml
View File

@ -13,26 +13,26 @@
<description>Dromara RuoYi-Cloud-Plus微服务系统</description>
<properties>
<revision>2.5.1</revision>
<revision>2.5.2</revision>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>17</java.version>
<spring-boot.version>3.5.7</spring-boot.version>
<spring-cloud.version>2025.0.0</spring-cloud.version>
<spring-boot.version>3.5.9</spring-boot.version>
<spring-cloud.version>2025.0.1</spring-cloud.version>
<spring-boot-admin.version>3.5.5</spring-boot-admin.version>
<mybatis.version>3.5.16</mybatis.version>
<mybatis-plus.version>3.5.14</mybatis-plus.version>
<p6spy.version>3.9.1</p6spy.version>
<dynamic-ds.version>4.3.1</dynamic-ds.version>
<velocity.version>2.3</velocity.version>
<swagger.core.version>2.2.36</swagger.core.version>
<springdoc.version>2.8.13</springdoc.version>
<swagger.core.version>2.2.38</swagger.core.version>
<springdoc.version>2.8.14</springdoc.version>
<therapi-javadoc.version>0.15.0</therapi-javadoc.version>
<fastexcel.version>1.3.0</fastexcel.version>
<hutool.version>5.8.40</hutool.version>
<redisson.version>3.51.0</redisson.version>
<redisson.version>3.52.0</redisson.version>
<lock4j.version>2.2.7</lock4j.version>
<snailjob.version>1.8.0</snailjob.version>
<snailjob.version>1.9.0</snailjob.version>
<satoken.version>1.44.0</satoken.version>
<lombok.version>1.18.40</lombok.version>
<logstash.version>7.4</logstash.version>
@ -43,7 +43,7 @@
<mapstruct-plus.lombok.version>0.2.0</mapstruct-plus.lombok.version>
<justauth.version>1.16.7</justauth.version>
<!-- 离线IP地址定位库 -->
<ip2region.version>2.7.0</ip2region.version>
<ip2region.version>3.3.1</ip2region.version>
<!-- 临时修复 fastjson 漏洞 -->
<fastjson.version>1.2.83</fastjson.version>
<!-- OSS 配置 -->
@ -51,9 +51,9 @@
<!-- SMS 配置 -->
<sms4j.version>3.3.4</sms4j.version>
<!-- 面向运行时的D-ORM依赖 -->
<anyline.version>8.7.2-20250603</anyline.version>
<anyline.version>8.7.3-20251210</anyline.version>
<!-- 工作流配置 -->
<warm-flow.version>1.8.2</warm-flow.version>
<warm-flow.version>1.8.4</warm-flow.version>
<!-- mq配置 -->
<rocketmq.version>2.3.4</rocketmq.version>

View File

@ -15,7 +15,7 @@
</description>
<properties>
<revision>2.5.1</revision>
<revision>2.5.2</revision>
</properties>
<dependencyManagement>

View File

@ -1,5 +1,11 @@
package org.dromara.system.api;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.lang.Dict;
import java.math.BigDecimal;
import java.util.List;
/**
* 配置服务
*
@ -14,4 +20,88 @@ public interface RemoteConfigService {
*/
boolean selectRegisterEnabled(String tenantId);
/**
* 根据参数 key 获取参数值
*
* @param configKey 参数 key
* @return 参数值
*/
String getConfigValue(String configKey);
/**
* 根据参数 key 获取布尔值
*
* @param configKey 参数 key
* @return Boolean
*/
default Boolean getConfigBool(String configKey) {
return Convert.toBool(getConfigValue(configKey));
}
/**
* 根据参数 key 获取整数值
*
* @param configKey 参数 key
* @return Integer
*/
default Integer getConfigInt(String configKey) {
return Convert.toInt(getConfigValue(configKey));
}
/**
* 根据参数 key 获取长整型值
*
* @param configKey 参数 key
* @return Long
*/
default Long getConfigLong(String configKey) {
return Convert.toLong(getConfigValue(configKey));
}
/**
* 根据参数 key 获取 BigDecimal
*
* @param configKey 参数 key
* @return BigDecimal
*/
default BigDecimal getConfigDecimal(String configKey) {
return Convert.toBigDecimal(getConfigValue(configKey));
}
/**
* 根据参数 key 获取 Map 类型的配置
*
* @param configKey 参数 key
* @return Dict 对象如果配置为空或无法解析返回空 Dict
*/
Dict getConfigMap(String configKey);
/**
* 根据参数 key 获取 Map 类型的配置列表
*
* @param configKey 参数 key
* @return Dict 列表如果配置为空或无法解析返回空列表
*/
List<Dict> getConfigArrayMap(String configKey);
/**
* 根据参数 key 获取指定类型的配置对象
*
* @param configKey 参数 key
* @param clazz 目标对象类型
* @param <T> 目标对象泛型
* @return 对象实例如果配置为空或无法解析返回 null
*/
<T> T getConfigObject(String configKey, Class<T> clazz);
/**
* 根据参数 key 获取指定类型的配置列表
*
* @param configKey 参数 key
* @param clazz 目标元素类型
* @param <T> 元素类型泛型
* @return 指定类型列表如果配置为空或无法解析返回空列表
*/
<T> List<T> getConfigArray(String configKey, Class<T> clazz);
}

View File

@ -21,7 +21,7 @@ public interface RemoteWorkflowService {
* @param businessIds 业务id
* @return 结果
*/
boolean deleteInstance(List<Long> businessIds);
boolean deleteInstance(List<String> businessIds);
/**
* 获取当前流程状态

View File

@ -17,7 +17,7 @@ import java.util.Map;
public class RemoteWorkflowServiceMock implements RemoteWorkflowService {
@Override
public boolean deleteInstance(List<Long> businessIds) {
public boolean deleteInstance(List<String> businessIds) {
log.warn("服务调用异常 -> 降级处理");
return false;
}

View File

@ -43,6 +43,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;
@ -104,7 +105,7 @@ public class TokenController {
Long userId = LoginHelper.getUserId();
scheduledExecutorService.schedule(() -> {
remoteMessageService.publishMessage(List.of(userId), "欢迎登录RuoYi-Cloud-Plus微服务管理系统");
remoteMessageService.publishMessage(List.of(userId), DateUtils.getTodayHour(new Date()) + "好,欢迎登录 RuoYi-Cloud-Plus 后台管理系统");
}, 5, TimeUnit.SECONDS);
return R.ok(loginVo);
}

View File

@ -14,11 +14,11 @@
</description>
<properties>
<revision>2.5.1</revision>
<spring-cloud-alibaba.version>2023.0.3.4</spring-cloud-alibaba.version>
<revision>2.5.2</revision>
<spring-cloud-alibaba.version>2025.0.0.0</spring-cloud-alibaba.version>
<seata.version>2.5.0</seata.version>
<nacos.client.version>2.5.1</nacos.client.version>
<dubbo.version>3.3.5</dubbo.version>
<dubbo.version>3.3.6</dubbo.version>
<dubbo-extensions.version>3.3.1</dubbo-extensions.version>
</properties>
<dependencyManagement>

View File

@ -14,7 +14,7 @@
</description>
<properties>
<revision>2.5.1</revision>
<revision>2.5.2</revision>
</properties>
<dependencyManagement>

View File

@ -71,4 +71,10 @@ public interface SystemConstants {
* 根部门祖级列表
*/
String ROOT_DEPT_ANCESTORS = "0";
/**
* 排除敏感属性字段
*/
String[] EXCLUDE_PROPERTIES = { "password", "oldPassword", "newPassword", "confirmPassword" };
}

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

@ -0,0 +1,87 @@
package org.dromara.common.core.utils;
import cn.hutool.core.util.DesensitizedUtil;
import cn.hutool.core.util.StrUtil;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
/**
* 脱敏工具类
*
* @author AprilWind
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class DesensitizedUtils extends DesensitizedUtil {
/**
* 灵活脱敏方法
*
* @param value 原始字符串
* @param prefixVisible 前面可见长度
* @param suffixVisible 后面可见长度
* @param maskLength 中间掩码长度固定显示多少 *如果总长度不足则自动缩减
* @return 脱敏后字符串
*/
public static String mask(String value, int prefixVisible, int suffixVisible, int maskLength) {
if (StrUtil.isBlank(value)) {
return value;
}
int len = value.length();
int prefixMaskLimit = prefixVisible + maskLength;
int fullLimit = prefixMaskLimit + 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);
}
// 规则2长度 <= 前缀 + 后缀可见长度 优先掩码后面
if (len <= prefixVisible + suffixVisible) {
return value.substring(0, len - prefixVisible) + StrUtil.repeat('*', prefixVisible);
}
// 规则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,142 @@
package org.dromara.common.core.utils.ip;
import cn.hutool.core.io.resource.NoResourceException;
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.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数据库
// IPv4配置
Config v4Config = Config.custom()
.setCachePolicy(Config.BufferCache)
.setXdbInputStream(ResourceUtil.getStream(DEFAULT_IPV4_XDB_PATH))
.asV4();
// 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 {
v6Config = Config.custom()
.setCachePolicy(Config.BufferCache)
.setXdbInputStream(v6XdbInputStream)
.asV6();
}
// 初始化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

@ -0,0 +1,22 @@
package org.dromara.common.excel.annotation;
import org.dromara.common.excel.core.ExcelOptionsProvider;
import java.lang.annotation.*;
/**
* Excel动态下拉选项注解
*
* @author Angus
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface ExcelDynamicOptions {
/**
* 提供者类全限定名
* 实现org.dromara.common.excel.service.ExcelOptionsProvider实现类接口
*/
Class<? extends ExcelOptionsProvider> providerClass();
}

View File

@ -30,6 +30,11 @@ 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 +108,10 @@ public class CellMergeHandler {
}
if (isAddResult && i > current) {
//如果是同一行则跳过合并
if (current + rowIndex == lastRow) {
continue;
}
result.add(new CellRangeAddress(current + rowIndex, lastRow, colNum, colNum));
}
}
@ -147,12 +156,12 @@ public class CellMergeHandler {
private boolean isMerge(Object currentRow, Object preRow, CellMerge cellMerge) {
final String[] mergeBy = cellMerge.mergeBy();
if (StrUtil.isAllNotBlank(mergeBy)) {
//比对当前行和上一行的各个属性值一一比对 如果全为真 则为真
// 比对当前行和上一行的各个属性值一一比对 如果全为真 则为真
for (String fieldName : mergeBy) {
final Object valCurrent = ReflectUtil.getFieldValue(currentRow, fieldName);
final Object valPre = ReflectUtil.getFieldValue(preRow, fieldName);
if (!Objects.equals(valPre, valCurrent)) {
//依赖字段如有任一不等值,则标记为不可合并
// 依赖字段如有任一不等值,则标记为不可合并
return false;
}
}
@ -178,6 +187,17 @@ public class CellMergeHandler {
}
}
/**
* 创建一个单元格合并处理器实例
*
* @param hasTitle 是否合并标题
* @param rowIndex 行索引
* @return 单元格合并处理器
*/
public static CellMergeHandler of(final boolean hasTitle, final int rowIndex) {
return new CellMergeHandler(hasTitle, rowIndex);
}
/**
* 创建一个单元格合并处理器实例
*

View File

@ -2,15 +2,16 @@ package org.dromara.common.excel.core;
import cn.hutool.core.collection.CollUtil;
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.handler.SheetWriteHandler;
import cn.idev.excel.write.merge.AbstractMergeStrategy;
import cn.idev.excel.write.metadata.holder.WriteSheetHolder;
import cn.idev.excel.write.metadata.holder.WriteWorkbookHolder;
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 java.util.*;
import java.util.List;
/**
* 列值重复合并策略
@ -18,7 +19,7 @@ import java.util.*;
* @author Lion Li
*/
@Slf4j
public class CellMergeStrategy extends AbstractMergeStrategy implements WorkbookWriteHandler {
public class CellMergeStrategy extends AbstractMergeStrategy implements SheetWriteHandler {
private final List<CellRangeAddress> cellList;
@ -30,29 +31,34 @@ public class CellMergeStrategy extends AbstractMergeStrategy implements Workbook
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)){
if (CollUtil.isEmpty(cellList)) {
return;
}
//单元格写入了,遍历合并区域,如果该Cell在区域内,但非首行,则清空
// 单元格写入了,遍历合并区域,如果该Cell在区域内,但非首行,则清空
final int rowIndex = cell.getRowIndex();
for (CellRangeAddress cellAddresses : cellList) {
final int firstRow = cellAddresses.getFirstRow();
if (cellAddresses.isInRange(cell) && rowIndex != firstRow){
if (cellAddresses.isInRange(cell) && rowIndex != firstRow) {
cell.setBlank();
}
}
}
@Override
public void afterWorkbookDispose(final WorkbookWriteHandlerContext context) {
if (CollUtil.isEmpty(cellList)){
public void afterSheetCreate(final WriteWorkbookHolder writeWorkbookHolder, final WriteSheetHolder writeSheetHolder) {
if (CollUtil.isEmpty(cellList)) {
return;
}
//当前表格写完后统一写入
// Sheet 创建时提前写入合并区域后续写入只会影响首格不会移除合并
final Sheet sheet = writeSheetHolder.getSheet();
for (CellRangeAddress item : cellList) {
context.getWriteContext().writeSheetHolder().getSheet().addMergedRegion(item);
sheet.addMergedRegion(item);
}
}

View File

@ -23,6 +23,7 @@ import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.core.utils.StreamUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.excel.annotation.ExcelDictFormat;
import org.dromara.common.excel.annotation.ExcelDynamicOptions;
import org.dromara.common.excel.annotation.ExcelEnumFormat;
import java.lang.reflect.Field;
@ -117,6 +118,15 @@ public class ExcelDownHandler implements SheetWriteHandler {
ExcelEnumFormat format = field.getDeclaredAnnotation(ExcelEnumFormat.class);
List<Object> values = EnumUtil.getFieldValues(format.enumClass(), format.textField());
options = StreamUtils.toList(values, Convert::toStr);
} else if (field.isAnnotationPresent(ExcelDynamicOptions.class)) {
// 处理动态下拉选项
ExcelDynamicOptions dynamicOptions = field.getDeclaredAnnotation(ExcelDynamicOptions.class);
// 获取提供者实例
ExcelOptionsProvider provider = SpringUtils.getBean(dynamicOptions.providerClass());
Set<String> providerOptions = provider.getOptions();
if (CollUtil.isNotEmpty(providerOptions)) {
options = new ArrayList<>(providerOptions);
}
}
if (ObjectUtil.isNotEmpty(options)) {
// 仅当下拉可选项不为空时执行

View File

@ -0,0 +1,19 @@
package org.dromara.common.excel.core;
import java.util.Set;
/**
* Excel下拉选项数据提供接口
*
* @author Angus
*/
public interface ExcelOptionsProvider {
/**
* 获取下拉选项数据
*
* @return 下拉选项列表
*/
Set<String> getOptions();
}

View File

@ -13,6 +13,7 @@ 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.core.constant.SystemConstants;
import org.dromara.common.core.utils.ServletUtils;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.core.utils.StringUtils;
@ -39,12 +40,6 @@ import java.util.*;
@AutoConfiguration
public class LogAspect {
/**
* 排除敏感属性字段
*/
public static final String[] EXCLUDE_PROPERTIES = { "password", "oldPassword", "newPassword", "confirmPassword" };
/**
* 计时 key
*/
@ -161,7 +156,7 @@ public class LogAspect {
String params = argsArrayToString(joinPoint.getArgs(), excludeParamNames);
operLog.setOperParam(StringUtils.substring(params, 0, 3800));
} else {
MapUtil.removeAny(paramsMap, EXCLUDE_PROPERTIES);
MapUtil.removeAny(paramsMap, SystemConstants.EXCLUDE_PROPERTIES);
MapUtil.removeAny(paramsMap, excludeParamNames);
operLog.setOperParam(StringUtils.substring(JsonUtils.toJsonString(paramsMap), 0, 3800));
}
@ -175,7 +170,7 @@ public class LogAspect {
if (ArrayUtil.isEmpty(paramsArray)) {
return params.toString();
}
String[] exclude = ArrayUtil.addAll(excludeParamNames, EXCLUDE_PROPERTIES);
String[] exclude = ArrayUtil.addAll(excludeParamNames, SystemConstants.EXCLUDE_PROPERTIES);
for (Object o : paramsArray) {
if (ObjectUtil.isNotNull(o) && !isFilterObject(o)) {
String str = "";

View File

@ -42,7 +42,7 @@ public class DataBaseHelper {
String databaseProductName = metaData.getDatabaseProductName();
return DataBaseType.find(databaseProductName);
} catch (SQLException e) {
throw new ServiceException(e.getMessage());
throw new RuntimeException("获取数据库类型失败", e);
}
}

View File

@ -115,7 +115,7 @@ public class DataPermissionHelper {
/**
* 开启忽略数据权限(开启后需手动调用 {@link #disableIgnore()} 关闭)
*/
public static void enableIgnore() {
private static void enableIgnore() {
IgnoreStrategy ignoreStrategy = getIgnoreStrategy();
if (ObjectUtil.isNull(ignoreStrategy)) {
InterceptorIgnoreHelper.handle(IgnoreStrategy.builder().dataPermission(true).build());
@ -129,7 +129,7 @@ public class DataPermissionHelper {
/**
* 关闭忽略数据权限
*/
public static void disableIgnore() {
private static void disableIgnore() {
IgnoreStrategy ignoreStrategy = getIgnoreStrategy();
if (ObjectUtil.isNotNull(ignoreStrategy)) {
boolean noOtherIgnoreStrategy = !Boolean.TRUE.equals(ignoreStrategy.getDynamicTableName())

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

@ -33,6 +33,7 @@ import java.nio.channels.WritableByteChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Duration;
import java.util.Map;
import java.util.Optional;
import java.util.function.Consumer;
@ -94,7 +95,11 @@ public class OssClient {
.region(of())
.forcePathStyle(isStyle)
.httpClient(NettyNioAsyncHttpClient.builder()
.connectionTimeout(Duration.ofSeconds(60)).build())
.connectionTimeout(Duration.ofSeconds(60))
.connectionAcquisitionTimeout(Duration.ofSeconds(30))
.maxConcurrency(100)
.maxPendingConnectionAcquires(1000)
.build())
.build();
//AWS基于 CRT S3 AsyncClient 实例用作 S3 传输管理器的底层客户端
@ -317,13 +322,13 @@ public class OssClient {
}
/**
* 获取私有URL链接
* 创建下载请求的预签名URL
*
* @param objectKey 对象KEY
* @param expiredTime 链接授权到期时间
*/
public String getPrivateUrl(String objectKey, Duration expiredTime) {
// 使用 AWS S3 预签名 URL 的生成器 获取对象的预签名 URL
public String createPresignedGetUrl(String objectKey, Duration expiredTime) {
// 使用 AWS S3 预签名 URL 的生成器 获取下载对象的预签名 URL
URL url = presigner.presignGetObject(
x -> x.signatureDuration(expiredTime)
.getObjectRequest(
@ -332,7 +337,28 @@ public class OssClient {
.build())
.build())
.url();
return url.toString();
return url.toExternalForm();
}
/**
* 创建上传请求的预签名URL
*
* @param objectKey 对象KEY
* @param expiredTime 链接授权到期时间
* @param metadata 元数据
*/
public String createPresignedPutUrl(String objectKey, Duration expiredTime, Map<String, String> metadata) {
// 使用 AWS S3 预签名 URL 的生成器 获取上传文件对象的预签名 URL
URL url = presigner.presignPutObject(
x -> x.signatureDuration(expiredTime)
.putObjectRequest(
y -> y.bucket(properties.getBucketName())
.key(objectKey)
.metadata(metadata)
.build())
.build())
.url();
return url.toExternalForm();
}
/**

View File

@ -43,16 +43,12 @@
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
<!-- &lt;!&ndash; redis序列化替代方案 比json快无数的跨语言二进制序列化 &ndash;&gt;-->
<!-- <dependency>-->
<!-- <groupId>org.apache.fury</groupId>-->
<!-- <artifactId>fury-core</artifactId>-->
<!-- <version>0.9.0</version>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>org.slf4j</groupId>-->
<!-- <artifactId>slf4j-api</artifactId>-->
<!-- </dependency>-->
<!-- redis序列化替代方案 比json快无数的跨语言二进制序列化 -->
<dependency>
<groupId>org.apache.fory</groupId>
<artifactId>fory-core</artifactId>
<version>0.13.1</version>
</dependency>
</dependencies>

View File

@ -53,9 +53,10 @@ public class RedisConfiguration {
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
// 指定序列化输入的类型类必须是非final修饰的序列化时将对象全类名一起保存下来
om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
// LoggerFactory.useSlf4jLogging(true);
// FuryCodec furyCodec = new FuryCodec();
// CompositeCodec codec = new CompositeCodec(StringCodec.INSTANCE, furyCodec, furyCodec);
// org.apache.fory.logging.LoggerFactory 包别引入错了
// LoggerFactory.useSlf4jLogging(true);
// ForyCodec foryCodec = new ForyCodec();
// CompositeCodec codec = new CompositeCodec(StringCodec.INSTANCE, foryCodec, foryCodec);
TypedJsonJacksonCodec jsonCodec = new TypedJsonJacksonCodec(Object.class, om);
// 组合序列化 key 使用 String 内容使用通用 json 格式
CompositeCodec codec = new CompositeCodec(StringCodec.INSTANCE, jsonCodec, jsonCodec);

View File

@ -3,6 +3,7 @@ package org.dromara.common.sensitive.core;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.DesensitizedUtil;
import lombok.AllArgsConstructor;
import org.dromara.common.core.utils.DesensitizedUtils;
import java.util.function.Function;
@ -80,6 +81,18 @@ public enum SensitiveStrategy {
*/
FIRST_MASK(DesensitizedUtil::firstMask),
/**
* 通用字符串脱敏
* 可配置前后可见长度和中间掩码长度
* 默认示例前4位可见后4位可见中间固定4个*
*/
STRING_MASK(s -> DesensitizedUtils.mask(s, 4, 4, 4)),
/**
* 高安全级别脱敏Token / 私钥前2位可见后2位可见中间全部掩码
*/
MASK_HIGH_SECURITY(s -> DesensitizedUtils.maskHighSecurity(s, 2, 2)),
/**
* 清空为null
*/

View File

@ -1,51 +0,0 @@
package org.dromara.common.core.utils;
import org.dromara.common.core.constant.CacheNames;
import org.dromara.common.redis.utils.CacheUtils;
import org.dromara.system.api.domain.vo.RemoteDictDataVo;
import java.util.List;
/**
* 字典工具类
*
* @author ruoyi
*/
public class DictUtils {
/**
* 设置字典缓存
*
* @param key 参数键
* @param dictDatas 字典数据列表
*/
public static void setDictCache(String key, List<RemoteDictDataVo> dictDatas) {
CacheUtils.put(CacheNames.SYS_DICT, key, dictDatas);
}
/**
* 获取字典缓存
*
* @param key 参数键
* @return dictDatas 字典数据列表
*/
public static List<RemoteDictDataVo> getDictCache(String key) {
return CacheUtils.get(CacheNames.SYS_DICT, key);
}
/**
* 删除指定字典缓存
*
* @param key 字典键
*/
public static void removeDictCache(String key) {
CacheUtils.evict(CacheNames.SYS_DICT, key);
}
/**
* 清空字典缓存
*/
public static void clearDictCache() {
CacheUtils.clear(CacheNames.SYS_DICT);
}
}

View File

@ -52,10 +52,11 @@ public abstract class AbstractAuthWeChatEnterpriseRequest extends AuthDefaultReq
JSONObject object = this.checkResponse(response);
// 返回 OpenId 或其他均代表非当前企业用户不支持
if (!object.containsKey("UserId")) {
// 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 userId = object.getString("userid");
String userTicket = object.getString("user_ticket");
JSONObject userDetail = getUserDetail(authToken.getAccessToken(), userId, userTicket);

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

@ -55,7 +55,7 @@ public class TenantHelper {
/**
* 开启忽略租户(开启后需手动调用 {@link #disableIgnore()} 关闭)
*/
public static void enableIgnore() {
private static void enableIgnore() {
IgnoreStrategy ignoreStrategy = getIgnoreStrategy();
if (ObjectUtil.isNull(ignoreStrategy)) {
InterceptorIgnoreHelper.handle(IgnoreStrategy.builder().tenantLine(true).build());
@ -69,7 +69,7 @@ public class TenantHelper {
/**
* 关闭忽略租户
*/
public static void disableIgnore() {
private static void disableIgnore() {
IgnoreStrategy ignoreStrategy = getIgnoreStrategy();
if (ObjectUtil.isNotNull(ignoreStrategy)) {
boolean noOtherIgnoreStrategy = !Boolean.TRUE.equals(ignoreStrategy.getDynamicTableName())

View File

@ -15,6 +15,7 @@ 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.support.DefaultMessageSourceResolvable;
import org.springframework.expression.ExpressionException;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
@ -43,7 +44,7 @@ public class GlobalExceptionHandler {
*/
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public R<Void> handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e,
HttpServletRequest request) {
HttpServletRequest request) {
String requestURI = request.getRequestURI();
log.error("请求地址'{}',不支持'{}'请求", requestURI, e.getMethod());
return R.fail(HttpStatus.HTTP_BAD_METHOD, e.getMessage());
@ -210,4 +211,13 @@ public class GlobalExceptionHandler {
return R.fail(HttpStatus.HTTP_BAD_REQUEST, "请求参数格式错误:" + e.getMostSpecificCause().getMessage());
}
/**
* SpEL 表达式相关异常
*/
@ExceptionHandler(ExpressionException.class)
public R<Void> handleSpelException(ExpressionException e, HttpServletRequest request) {
log.error("请求地址'{}'SpEL解析异常: {}", request.getRequestURI(), e.getMessage());
return R.fail(HttpStatus.HTTP_INTERNAL_ERROR, "SpEL解析失败" + e.getMessage());
}
}

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

@ -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

@ -1,8 +1,16 @@
package org.dromara.gateway.filter;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.constant.SystemConstants;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.json.utils.JsonUtils;
import org.dromara.gateway.config.properties.ApiDecryptProperties;
import org.dromara.gateway.config.properties.CustomGatewayProperties;
@ -13,10 +21,14 @@ import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.HashSet;
import java.util.Set;
/**
* 全局日志过滤器
* <p>
@ -35,6 +47,7 @@ public class GlobalLogFilter implements GlobalFilter, Ordered {
private static final String START_TIME = "startTime";
@SneakyThrows
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
if (!customGatewayProperties.getRequestLog()) {
@ -51,12 +64,20 @@ public class GlobalLogFilter implements GlobalFilter, Ordered {
log.info("[PLUS]开始请求 => URL[{}],参数类型[encrypt]", url);
} else {
String jsonParam = WebFluxUtils.resolveBodyFromCacheRequest(exchange);
if (StringUtils.isNotBlank(jsonParam)) {
ObjectMapper objectMapper = JsonUtils.getObjectMapper();
JsonNode rootNode = objectMapper.readTree(jsonParam);
removeSensitiveFields(rootNode, SystemConstants.EXCLUDE_PROPERTIES);
jsonParam = rootNode.toString();
}
log.info("[PLUS]开始请求 => URL[{}],参数类型[json],参数:[{}]", url, jsonParam);
}
} else {
MultiValueMap<String, String> parameterMap = request.getQueryParams();
if (MapUtil.isNotEmpty(parameterMap)) {
String parameters = JsonUtils.toJsonString(parameterMap);
LinkedMultiValueMap<String, String> map = new LinkedMultiValueMap<>(parameterMap);
MapUtil.removeAny(map, SystemConstants.EXCLUDE_PROPERTIES);
String parameters = JsonUtils.toJsonString(map);
log.info("[PLUS]开始请求 => URL[{}],参数类型[param],参数:[{}]", url, parameters);
} else {
log.info("[PLUS]开始请求 => URL[{}],无参数", url);
@ -73,6 +94,30 @@ public class GlobalLogFilter implements GlobalFilter, Ordered {
}));
}
private void removeSensitiveFields(JsonNode node, String[] excludeProperties) {
if (node == null) {
return;
}
if (node.isObject()) {
ObjectNode objectNode = (ObjectNode) node;
// 收集要删除的字段名避免 ConcurrentModification
Set<String> fieldsToRemove = new HashSet<>();
objectNode.fieldNames().forEachRemaining(fieldName -> {
if (ArrayUtil.contains(excludeProperties, fieldName)) {
fieldsToRemove.add(fieldName);
}
});
fieldsToRemove.forEach(objectNode::remove);
// 递归处理子节点
objectNode.elements().forEachRemaining(child -> removeSensitiveFields(child, excludeProperties));
} else if (node.isArray()) {
ArrayNode arrayNode = (ArrayNode) node;
for (JsonNode child : arrayNode) {
removeSensitiveFields(child, excludeProperties);
}
}
}
@Override
public int getOrder() {
// 日志处理器在负载均衡器之后执行 负载均衡器会导致线程切换 无法获取上下文内容

View File

@ -91,10 +91,12 @@ public class GenController extends BaseController {
* 导入表结构保存
*
* @param tables 表名串
* @param dataName 数据源名称
*/
@RepeatSubmit()
@SaCheckPermission("tool:gen:import")
@Log(title = "代码生成", businessType = BusinessType.IMPORT)
@Lock4j(keys = {"#dataName"}, acquireTimeout = 10000)
@RepeatSubmit()
@PostMapping("/importTable")
public R<Void> importTableSave(String tables, String dataName) {
String[] tableNames = Convert.toStrArray(tables);
@ -107,9 +109,9 @@ public class GenController extends BaseController {
/**
* 修改保存代码生成业务
*/
@RepeatSubmit()
@SaCheckPermission("tool:gen:edit")
@Log(title = "代码生成", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping
public R<Void> editSave(@Validated @RequestBody GenTable genTable) {
genTableService.validateEdit(genTable);
@ -175,7 +177,7 @@ public class GenController extends BaseController {
*/
@SaCheckPermission("tool:gen:edit")
@Log(title = "代码生成", businessType = BusinessType.UPDATE)
@Lock4j
@Lock4j(keys = {"#tableId"}, acquireTimeout = 5000)
@GetMapping("/synchDb/{tableId}")
public R<Void> synchDb(@PathVariable("tableId") Long tableId) {
genTableService.synchDb(tableId);

View File

@ -114,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.gen.constant.GenConstants;
import org.dromara.gen.domain.GenTable;
import org.dromara.gen.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_", "act_", "flw_", "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

@ -2,15 +2,16 @@ package org.dromara.resource.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.hutool.core.util.ObjectUtil;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.NotEmpty;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.validate.QueryGroup;
import org.dromara.common.web.core.BaseController;
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.web.core.BaseController;
import org.dromara.resource.domain.bo.SysOssBo;
import org.dromara.resource.domain.vo.SysOssUploadVo;
import org.dromara.resource.domain.vo.SysOssVo;
@ -20,8 +21,6 @@ import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.NotEmpty;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
@ -69,9 +68,6 @@ public class SysOssController extends BaseController {
@Log(title = "OSS对象存储", businessType = BusinessType.INSERT)
@PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public R<SysOssUploadVo> upload(@RequestPart("file") MultipartFile file) {
if (ObjectUtil.isNull(file)) {
return R.fail("上传文件不能为空");
}
SysOssVo oss = iSysOssService.upload(file);
SysOssUploadVo uploadVo = new SysOssUploadVo();
uploadVo.setUrl(oss.getUrl());

View File

@ -170,6 +170,9 @@ public class SysOssServiceImpl implements ISysOssService {
*/
@Override
public SysOssVo upload(MultipartFile file) {
if (ObjectUtil.isNull(file) || file.isEmpty()) {
throw new ServiceException("上传文件不能为空");
}
String originalfileName = file.getOriginalFilename();
String suffix = StringUtils.substring(originalfileName, originalfileName.lastIndexOf("."), originalfileName.length());
OssClient storage = OssFactory.instance();
@ -194,12 +197,16 @@ public class SysOssServiceImpl implements ISysOssService {
*/
@Override
public SysOssVo upload(File file) {
if (ObjectUtil.isNull(file) || !file.isFile() || file.length() <= 0) {
throw new ServiceException("上传文件不能为空");
}
String originalfileName = file.getName();
String suffix = StringUtils.substring(originalfileName, originalfileName.lastIndexOf("."), originalfileName.length());
OssClient storage = OssFactory.instance();
long length = file.length();
UploadResult uploadResult = storage.uploadSuffix(file, suffix);
SysOssExt ext1 = new SysOssExt();
ext1.setFileSize(file.length());
ext1.setFileSize(length);
// 保存文件信息
return buildResultEntity(originalfileName, suffix, storage.getConfigKey(), uploadResult, ext1);
}
@ -263,7 +270,7 @@ public class SysOssServiceImpl implements ISysOssService {
OssClient storage = OssFactory.instance(oss.getService());
// 仅修改桶类型为 private 的URL临时URL时长为120s
if (AccessPolicyType.PRIVATE == storage.getAccessPolicy()) {
oss.setUrl(storage.getPrivateUrl(oss.getFileName(), Duration.ofSeconds(120)));
oss.setUrl(storage.createPresignedGetUrl(oss.getFileName(), Duration.ofSeconds(120)));
}
return oss;
}

View File

@ -76,6 +76,9 @@ public class SysClientController extends BaseController {
@RepeatSubmit()
@PostMapping()
public R<Void> add(@Validated(AddGroup.class) @RequestBody SysClientBo bo) {
if (!sysClientService.checkClickKeyUnique(bo)) {
return R.fail("新增客户端'" + bo.getClientKey() + "'失败客户端key已存在");
}
return toAjax(sysClientService.insertByBo(bo));
}
@ -87,6 +90,9 @@ public class SysClientController extends BaseController {
@RepeatSubmit()
@PutMapping()
public R<Void> edit(@Validated(EditGroup.class) @RequestBody SysClientBo bo) {
if (!sysClientService.checkClickKeyUnique(bo)) {
return R.fail("修改客户端'" + bo.getClientKey() + "'失败客户端key已存在");
}
return toAjax(sysClientService.updateByBo(bo));
}

View File

@ -2,6 +2,7 @@ package org.dromara.system.controller.system;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.crypto.digest.BCrypt;
import lombok.RequiredArgsConstructor;
import org.apache.dubbo.config.annotation.DubboReference;
@ -120,7 +121,7 @@ public class SysProfileController extends BaseController {
@Log(title = "用户头像", businessType = BusinessType.UPDATE)
@PostMapping(value = "/avatar", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public R<AvatarVo> avatar(@RequestPart("avatarfile") MultipartFile avatarfile) throws IOException {
if (!avatarfile.isEmpty()) {
if (ObjectUtil.isNotNull(avatarfile) && !avatarfile.isEmpty()) {
String extension = FileUtil.extName(avatarfile.getOriginalFilename());
if (!StringUtils.equalsAnyIgnoreCase(extension, MimeTypeUtils.IMAGE_EXTENSION)) {
return R.fail("文件格式不正确,请上传" + Arrays.toString(MimeTypeUtils.IMAGE_EXTENSION) + "格式");

View File

@ -9,6 +9,7 @@ import org.mapstruct.ReportingPolicy;
/**
* 字典数据转换器
*
* @author zhujie
*/
@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, unmappedTargetPolicy = ReportingPolicy.IGNORE)

View File

@ -0,0 +1,18 @@
package org.dromara.system.domain.convert;
import io.github.linpeilie.BaseMapper;
import org.dromara.system.api.domain.vo.RemoteDictTypeVo;
import org.dromara.system.domain.vo.SysDictTypeVo;
import org.mapstruct.Mapper;
import org.mapstruct.MappingConstants;
import org.mapstruct.ReportingPolicy;
/**
* 字典类型转换器
*
* @author liyaoheng
*/
@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface SysDictTypeVoConvert extends BaseMapper<SysDictTypeVo, RemoteDictTypeVo> {
}

View File

@ -23,6 +23,8 @@ public class RemoteClientServiceImpl implements RemoteClientService {
/**
* 根据客户端id获取客户端详情
*
* @see org.dromara.system.domain.convert.SysClientVoConvert
*/
@Override
public RemoteClientVo queryByClientId(String clientId) {

View File

@ -1,11 +1,15 @@
package org.dromara.system.dubbo;
import cn.hutool.core.lang.Dict;
import lombok.RequiredArgsConstructor;
import org.apache.dubbo.config.annotation.DubboService;
import org.dromara.common.json.utils.JsonUtils;
import org.dromara.system.api.RemoteConfigService;
import org.dromara.system.service.ISysConfigService;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* 配置服务
*
@ -26,4 +30,60 @@ public class RemoteConfigServiceImpl implements RemoteConfigService {
return configService.selectRegisterEnabled(tenantId);
}
@Override
public String getConfigValue(String configKey) {
return configService.selectConfigByKey(configKey);
}
/**
* 根据参数 key 获取 Map 类型的配置
*
* @param configKey 参数 key
* @return Dict 对象如果配置为空或无法解析返回空 Dict
*/
@Override
public Dict getConfigMap(String configKey) {
String configValue = getConfigValue(configKey);
return JsonUtils.parseMap(configValue);
}
/**
* 根据参数 key 获取 Map 类型的配置列表
*
* @param configKey 参数 key
* @return Dict 列表如果配置为空或无法解析返回空列表
*/
@Override
public List<Dict> getConfigArrayMap(String configKey) {
String configValue = getConfigValue(configKey);
return JsonUtils.parseArrayMap(configValue);
}
/**
* 根据参数 key 获取指定类型的配置对象
*
* @param configKey 参数 key
* @param clazz 目标对象类型
* @return 对象实例如果配置为空或无法解析返回 null
*/
@Override
public <T> T getConfigObject(String configKey, Class<T> clazz) {
String configValue = getConfigValue(configKey);
return JsonUtils.parseObject(configValue, clazz);
}
/**
* 根据参数 key 获取指定类型的配置列表=
*
* @param configKey 参数 key
* @param clazz 目标元素类型
* @return 指定类型列表如果配置为空或无法解析返回空列表
*/
@Override
public <T> List<T> getConfigArray(String configKey, Class<T> clazz) {
String configValue = getConfigValue(configKey);
return JsonUtils.parseArray(configValue, clazz);
}
}

View File

@ -25,6 +25,13 @@ public class RemoteDictServiceImpl implements RemoteDictService {
private final ISysDictTypeService sysDictTypeService;
/**
* remote根据字典类型查询字典
*
* @param dictType 字典类型
* @return RemoteDictTypeVo
* @see org.dromara.system.domain.convert.SysDictTypeVoConvert
*/
@Override
public RemoteDictTypeVo selectDictTypeByType(String dictType) {
SysDictTypeVo vo = sysDictTypeService.selectDictTypeByType(dictType);
@ -32,10 +39,11 @@ public class RemoteDictServiceImpl implements RemoteDictService {
}
/**
* 根据字典类型查询字典数据
* remote根据字典类型查询字典数据
*
* @param dictType 字典类型
* @return 字典数据集合信息
* @see org.dromara.system.domain.convert.SysDictDataVoConvert
*/
@Override
public List<RemoteDictDataVo> selectDictDataByType(String dictType) {

View File

@ -24,6 +24,8 @@ public class RemoteTenantServiceImpl implements RemoteTenantService {
/**
* 根据租户id获取租户详情
*
* @see org.dromara.system.domain.convert.SysTenantVoConvert
*/
@Override
public RemoteTenantVo queryByTenantId(String tenantId) {

View File

@ -305,6 +305,7 @@ public class RemoteUserServiceImpl implements RemoteUserService {
*
* @param userIds 用户ids
* @return 用户列表
* @see org.dromara.system.domain.convert.SysUserVoConvert
*/
@Override
public List<RemoteUserVo> selectListByIds(List<Long> userIds) {

View File

@ -55,4 +55,12 @@ public interface ISysClientService {
*/
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
/**
* 校验客户端key是否唯一
*
* @param client 客户端信息
* @return 结果
*/
boolean checkClickKeyUnique(SysClientBo client);
}

View File

@ -1,6 +1,7 @@
package org.dromara.system.service.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.crypto.SecureUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
@ -135,4 +136,19 @@ public class SysClientServiceImpl implements ISysClientService {
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
return baseMapper.deleteByIds(ids) > 0;
}
/**
* 校验客户端key是否唯一
*
* @param client 客户端信息
* @return 结果
*/
@Override
public boolean checkClickKeyUnique(SysClientBo client) {
boolean exist = baseMapper.exists(new LambdaQueryWrapper<SysClient>()
.eq(SysClient::getClientKey, client.getClientKey())
.ne(ObjectUtil.isNotNull(client.getId()), SysClient::getId, client.getId()));
return !exist;
}
}

View File

@ -93,4 +93,19 @@ 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

@ -187,6 +187,7 @@ public class FlwDefinitionController extends BaseController {
@RepeatSubmit()
@PutMapping("/active/{id}")
@Transactional(rollbackFor = Exception.class)
@Log(title = "流程定义", businessType = BusinessType.UPDATE)
public R<Boolean> active(@PathVariable Long id, @RequestParam boolean active) {
return R.ok(active ? defService.active(id) : defService.unActive(id));
}

View File

@ -1,7 +1,9 @@
package org.dromara.workflow.controller;
import cn.hutool.core.convert.Convert;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.utils.StreamUtils;
import org.dromara.common.idempotent.annotation.RepeatSubmit;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessType;
@ -75,8 +77,9 @@ public class FlwInstanceController extends BaseController {
* @param businessIds 业务id
*/
@DeleteMapping("/deleteByBusinessIds/{businessIds}")
@Log(title = "流程实例管理", businessType = BusinessType.DELETE)
public R<Void> deleteByBusinessIds(@PathVariable List<Long> businessIds) {
return toAjax(flwInstanceService.deleteByBusinessIds(businessIds));
return toAjax(flwInstanceService.deleteByBusinessIds(StreamUtils.toList(businessIds, Convert::toStr)));
}
/**
@ -85,6 +88,7 @@ public class FlwInstanceController extends BaseController {
* @param instanceIds 实例id
*/
@DeleteMapping("/deleteByInstanceIds/{instanceIds}")
@Log(title = "流程实例管理", businessType = BusinessType.DELETE)
public R<Void> deleteByInstanceIds(@PathVariable List<Long> instanceIds) {
return toAjax(flwInstanceService.deleteByInstanceIds(instanceIds));
}
@ -95,6 +99,7 @@ public class FlwInstanceController extends BaseController {
* @param instanceIds 实例id
*/
@DeleteMapping("/deleteHisByInstanceIds/{instanceIds}")
@Log(title = "流程实例管理", businessType = BusinessType.DELETE)
public R<Void> deleteHisByInstanceIds(@PathVariable List<Long> instanceIds) {
return toAjax(flwInstanceService.deleteHisByInstanceIds(instanceIds));
}
@ -106,6 +111,7 @@ public class FlwInstanceController extends BaseController {
*/
@RepeatSubmit()
@PutMapping("/cancelProcessApply")
@Log(title = "流程实例管理", businessType = BusinessType.UPDATE)
public R<Void> cancelProcessApply(@RequestBody FlowCancelBo bo) {
return toAjax(flwInstanceService.cancelProcessApply(bo));
}
@ -118,6 +124,7 @@ public class FlwInstanceController extends BaseController {
*/
@RepeatSubmit()
@PutMapping("/active/{id}")
@Log(title = "流程实例管理", businessType = BusinessType.UPDATE)
public R<Boolean> active(@PathVariable Long id, @RequestParam boolean active) {
return R.ok(active ? insService.active(id) : insService.unActive(id));
}
@ -160,6 +167,7 @@ public class FlwInstanceController extends BaseController {
*/
@RepeatSubmit()
@PutMapping("/updateVariable")
@Log(title = "流程实例管理", businessType = BusinessType.UPDATE)
public R<Void> updateVariable(@Validated @RequestBody FlowVariableBo bo) {
return toAjax(flwInstanceService.updateVariable(bo));
}

View File

@ -23,7 +23,7 @@ import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 流程spel达式定义
* 流程spel达式定义
*
* @author Michelle.Chung
* @date 2025-07-04
@ -38,7 +38,7 @@ public class FlwSpelController extends BaseController {
private final IFlwSpelService flwSpelService;
/**
* 查询流程spel达式定义列表
* 查询流程spel达式定义列表
*/
@SaCheckPermission("workflow:spel:list")
@GetMapping("/list")
@ -47,7 +47,7 @@ public class FlwSpelController extends BaseController {
}
/**
* 获取流程spel达式定义详细信息
* 获取流程spel达式定义详细信息
*
* @param id 主键
*/
@ -58,10 +58,10 @@ public class FlwSpelController extends BaseController {
}
/**
* 新增流程spel达式定义
* 新增流程spel达式定义
*/
@SaCheckPermission("workflow:spel:add")
@Log(title = "流程spel达式定义", businessType = BusinessType.INSERT)
@Log(title = "流程spel达式定义", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping()
public R<Void> add(@Validated(AddGroup.class) @RequestBody FlowSpelBo bo) {
@ -69,10 +69,10 @@ public class FlwSpelController extends BaseController {
}
/**
* 修改流程spel达式定义
* 修改流程spel达式定义
*/
@SaCheckPermission("workflow:spel:edit")
@Log(title = "流程spel达式定义", businessType = BusinessType.UPDATE)
@Log(title = "流程spel达式定义", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping()
public R<Void> edit(@Validated(EditGroup.class) @RequestBody FlowSpelBo bo) {
@ -80,12 +80,12 @@ public class FlwSpelController extends BaseController {
}
/**
* 删除流程spel达式定义
* 删除流程spel达式定义
*
* @param ids 主键串
*/
@SaCheckPermission("workflow:spel:remove")
@Log(title = "流程spel达式定义", businessType = BusinessType.DELETE)
@Log(title = "流程spel达式定义", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public R<Void> remove(@NotEmpty(message = "主键不能为空") @PathVariable Long[] ids) {
return toAjax(flwSpelService.deleteWithValidByIds(List.of(ids), true));

View File

@ -216,6 +216,7 @@ public class FlwTaskController extends BaseController {
* @return 结果
*/
@PostMapping("/urgeTask")
@Log(title = "任务管理", businessType = BusinessType.INSERT)
public R<Void> urgeTask(@RequestBody FlowUrgeTaskBo bo) {
return toAjax(flwTaskService.urgeTask(bo));
}

View File

@ -10,7 +10,7 @@ import org.dromara.common.mybatis.core.domain.BaseEntity;
import java.io.Serial;
/**
* 流程spel达式定义对象 flow_spel
* 流程spel达式定义对象 flow_spel
*
* @author Michelle.Chung
* @date 2025-07-04

View File

@ -50,6 +50,6 @@ public class FlowInstanceBo implements Serializable {
/**
* 申请人Ids
*/
private List<Long> createByIds;
private List<String> createByIds;
}

View File

@ -10,7 +10,7 @@ import org.dromara.common.mybatis.core.domain.BaseEntity;
import org.dromara.workflow.domain.FlowSpel;
/**
* 流程spel达式定义业务对象 flow_spel
* 流程spel达式定义业务对象 flow_spel
*
* @author Michelle.Chung
* @date 2025-07-04

View File

@ -1,10 +1,13 @@
package org.dromara.workflow.domain.bo;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 任务请求对象
@ -42,6 +45,11 @@ public class FlowTaskBo implements Serializable {
*/
private Long instanceId;
/**
* 流程状态
*/
private String flowStatus;
/**
* 权限列表
*/
@ -52,4 +60,10 @@ public class FlowTaskBo implements Serializable {
*/
private List<Long> createByIds;
/**
* 请求参数
*/
@JsonInclude(JsonInclude.Include.NON_EMPTY)
private Map<String, Object> params = new HashMap<>();
}

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

@ -78,7 +78,7 @@ public class FlowInstanceVo {
private String variable;
/**
* 流程状态0待提交 1审批中 2 审批通过 3自动通过 8已完成 9已退回 10失效
* 流程状态
*/
private String flowStatus;

View File

@ -14,7 +14,7 @@ import java.util.Date;
/**
* 流程spel达式定义视图对象 flow_spel
* 流程spel达式定义视图对象 flow_spel
*
* @author Michelle.Chung
* @date 2025-07-04

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;
@ -163,7 +163,7 @@ public class FlowTaskVo implements Serializable {
/**
* 流程签署比例值 大于0为票签会签
*/
private BigDecimal nodeRatio;
private String nodeRatio;
/**
* 申请人id
@ -212,4 +212,8 @@ public class FlowTaskVo implements Serializable {
private String businessTitle;
//业务扩展信息结束
public String getCreateTime() {
return DateUtils.formatFriendlyTime(createTime);
}
}

View File

@ -24,7 +24,7 @@ public class RemoteWorkflowServiceImpl implements RemoteWorkflowService {
private final WorkflowService workflowService;
@Override
public boolean deleteInstance(List<Long> businessIds) {
public boolean deleteInstance(List<String> businessIds) {
return workflowService.deleteInstance(businessIds);
}

View File

@ -4,6 +4,7 @@ import lombok.extern.slf4j.Slf4j;
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.api.event.ProcessDeleteEvent;
import org.dromara.workflow.api.event.ProcessEvent;
import org.dromara.workflow.api.event.ProcessTaskEvent;
@ -53,24 +54,24 @@ public class FlowProcessEventHandler {
/**
* 执行创建任务监听
*
* @param flowCode 流程定义编码
* @param instance 实例数据
* @param taskId 任务id
* @param flowCode 流程定义编码
* @param instance 实例数据
* @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

@ -5,6 +5,7 @@ import cn.hutool.core.convert.Convert;
import cn.hutool.core.lang.TypeReference;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
@ -76,6 +77,9 @@ public class WorkflowGlobalListener implements GlobalListener {
String ext = listenerVariable.getNode().getExt();
if (StringUtils.isNotBlank(ext)) {
Map<String, Object> variable = listenerVariable.getVariable();
if (CollUtil.isNotEmpty(variable)) {
variable = new HashMap<>();
}
NodeExtVo nodeExt = nodeExtService.parseNodeExt(ext, variable);
Set<String> copyList = nodeExt.getCopySettings();
if (CollUtil.isNotEmpty(copyList)) {
@ -107,20 +111,61 @@ public class WorkflowGlobalListener implements GlobalListener {
Definition definition = listenerVariable.getDefinition();
Instance instance = listenerVariable.getInstance();
String applyNodeCode = flwCommonService.applyNodeCode(definition.getId());
String hisStatus = flowParams != null ? flowParams.getHisStatus() : null;
for (Task flowTask : nextTasks) {
// 如果办理或者退回并行存在需要指定办理人则直接覆盖办理人
if (variable.containsKey(flowTask.getNodeCode()) && TaskStatusEnum.isPassOrBack(flowParams.getHisStatus())) {
String userIds = variable.get(flowTask.getNodeCode()).toString();
flowTask.setPermissionList(List.of(userIds.split(StringUtils.SEPARATOR)));
variable.remove(flowTask.getNodeCode());
String nodeCode = flowTask.getNodeCode();
// 处理办理或退回时指定办理人的情况
if (TaskStatusEnum.PASS.getStatus().equals(hisStatus)) {
processTaskPermission(variable, flowTask, hisStatus);
} else if (TaskStatusEnum.BACK.getStatus().equals(hisStatus)) {
processTaskPermission(variable, flowTask, hisStatus);
}
// 如果是申请节点则把启动人添加到办理人
if (flowTask.getNodeCode().equals(applyNodeCode)) {
if (nodeCode.equals(applyNodeCode) && StringUtils.isNotBlank(instance.getCreateBy())) {
flowTask.setPermissionList(List.of(instance.getCreateBy()));
}
}
}
/**
* 处理任务权限设置
*
* @param variable 变量集合
* @param flowTask 流程任务
* @param taskStatus 任务状态
*/
private void processTaskPermission(Map<String, Object> variable, Task flowTask, String taskStatus) {
String nodeKey = taskStatus + StrUtil.COLON + flowTask.getNodeCode();
// 检查是否存在状态相关的变量
if (!variable.containsKey(nodeKey)) {
return;
}
// 获取用户ID字符串
Object userIdsObj = variable.get(nodeKey);
if (userIdsObj == null) {
return;
}
String userIds = userIdsObj.toString();
if (StringUtils.isBlank(userIds)) {
return;
}
// 分割用户ID并设置权限列表
String[] userIdArray = userIds.split(StringUtils.SEPARATOR);
if (userIdArray.length > 0) {
flowTask.setPermissionList(List.of(userIdArray));
// 移除已处理的状态变量
variable.remove(nodeKey);
FlowEngine.insService().removeVariables(flowTask.getInstanceId(),nodeKey);
}
}
/**
* 完成监听器当前任务完成后执行
*
@ -147,7 +192,8 @@ public class WorkflowGlobalListener implements GlobalListener {
//申请人提交事件
Boolean submit = MapUtil.getBool(variable, FlowConstant.SUBMIT);
if (submit != null && submit) {
flowProcessEventHandler.processHandler(definition.getFlowCode(), instance, instance.getFlowStatus(), variable, true);
String status = determineFlowStatus(instance);
flowProcessEventHandler.processHandler(definition.getFlowCode(), instance, status, variable, true);
} else {
// 判断流程状态发布撤销退回作废终止已完成事件
String status = determineFlowStatus(instance);
@ -168,7 +214,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

@ -5,7 +5,7 @@ import org.dromara.workflow.domain.FlowSpel;
import org.dromara.workflow.domain.vo.FlowSpelVo;
/**
* 流程spel达式定义Mapper接口
* 流程spel达式定义Mapper接口
*
* @author Michelle.Chung
* @date 2025-07-04

View File

@ -75,7 +75,7 @@ public interface IFlwInstanceService {
* @param businessIds 业务id
* @return 结果
*/
boolean deleteByBusinessIds(List<Long> businessIds);
boolean deleteByBusinessIds(List<String> businessIds);
/**
* 按照实例id删除流程实例

View File

@ -12,7 +12,7 @@ import java.util.List;
import java.util.Map;
/**
* 流程spel达式定义Service接口
* 流程spel达式定义Service接口
*
* @author Michelle.Chung
* @date 2025-07-04
@ -20,48 +20,48 @@ import java.util.Map;
public interface IFlwSpelService {
/**
* 查询流程spel达式定义
* 查询流程spel达式定义
*
* @param id 主键
* @return 流程spel达式定义
* @return 流程spel达式定义
*/
FlowSpelVo queryById(Long id);
/**
* 分页查询流程spel达式定义列表
* 分页查询流程spel达式定义列表
*
* @param bo 查询条件
* @param pageQuery 分页参数
* @return 流程spel达式定义分页列表
* @return 流程spel达式定义分页列表
*/
TableDataInfo<FlowSpelVo> queryPageList(FlowSpelBo bo, PageQuery pageQuery);
/**
* 查询符合条件的流程spel达式定义列表
* 查询符合条件的流程spel达式定义列表
*
* @param bo 查询条件
* @return 流程spel达式定义列表
* @return 流程spel达式定义列表
*/
List<FlowSpelVo> queryList(FlowSpelBo bo);
/**
* 新增流程spel达式定义
* 新增流程spel达式定义
*
* @param bo 流程spel达式定义
* @param bo 流程spel达式定义
* @return 是否新增成功
*/
Boolean insertByBo(FlowSpelBo bo);
/**
* 修改流程spel达式定义
* 修改流程spel达式定义
*
* @param bo 流程spel达式定义
* @param bo 流程spel达式定义
* @return 是否修改成功
*/
Boolean updateByBo(FlowSpelBo bo);
/**
* 校验并批量删除流程spel达式定义信息
* 校验并批量删除流程spel达式定义信息
*
* @param ids 待删除的主键集合
* @param isValid 是否进行有效性校验

View File

@ -20,7 +20,7 @@ public interface WorkflowService {
* @param businessIds 业务id
* @return 结果
*/
boolean deleteInstance(List<Long> businessIds);
boolean deleteInstance(List<String> businessIds);
/**
* 获取当前流程状态

View File

@ -213,6 +213,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

@ -92,17 +92,23 @@ public class FlwCommonServiceImpl implements IFlwCommonService {
if (ObjectUtil.isEmpty(messageTypeEnum)) {
continue;
}
switch (messageTypeEnum) {
case SYSTEM_MESSAGE -> {
remoteMessageService.publishMessage(userIds, message);
try {
switch (messageTypeEnum) {
case SYSTEM_MESSAGE -> {
remoteMessageService.publishMessage(userIds, message);
}
case EMAIL_MESSAGE -> {
remoteMailService.send(emails, subject, message);
}
case SMS_MESSAGE -> {
// TODO: 补充短信发送逻辑
log.info("【短信发送 - TODO】用户数量={} 内容={}", userList.size(), message);
}
default -> log.warn("【消息发送】未处理的消息类型:{}", messageTypeEnum);
}
case EMAIL_MESSAGE -> {
remoteMailService.send(emails, subject, message);
}
case SMS_MESSAGE -> {
//todo 短信发送
}
default -> throw new IllegalStateException("Unexpected value: " + messageTypeEnum);
} catch (Exception ex) {
// 记录错误但不抛出确保主逻辑不受影响
log.error("【消息发送失败】类型={},原因={}", messageTypeEnum, ex.getMessage(), ex);
}
}
}

View File

@ -210,10 +210,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));
@ -225,6 +221,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

@ -181,8 +181,8 @@ public class FlwInstanceServiceImpl implements IFlwInstanceService {
*/
@Override
@Transactional(rollbackFor = Exception.class)
public boolean deleteByBusinessIds(List<Long> businessIds) {
List<FlowInstance> flowInstances = flowInstanceMapper.selectList(new LambdaQueryWrapper<FlowInstance>().in(FlowInstance::getBusinessId, StreamUtils.toList(businessIds, Convert::toStr)));
public boolean deleteByBusinessIds(List<String> businessIds) {
List<FlowInstance> flowInstances = flowInstanceMapper.selectList(new LambdaQueryWrapper<FlowInstance>().in(FlowInstance::getBusinessId, businessIds));
if (CollUtil.isEmpty(flowInstances)) {
log.warn("未找到对应的流程实例信息,无法执行删除操作。");
return false;
@ -211,27 +211,17 @@ public class FlwInstanceServiceImpl implements IFlwInstanceService {
Function.identity()
);
try {
// 逐一触发删除事件
instances.forEach(instance -> {
Definition definition = definitionMap.get(instance.getDefinitionId());
if (ObjectUtil.isNull(definition)) {
log.warn("实例 ID: {} 对应的流程定义信息未找到,跳过删除事件触发。", instance.getId());
return;
}
flowProcessEventHandler.processDeleteHandler(definition.getFlowCode(), instance.getBusinessId());
});
// 删除实例
boolean remove = insService.remove(instanceIds);
if (!remove) {
log.warn("删除流程实例失败!");
throw new ServiceException("删除流程实例失败");
// 逐一触发删除事件
instances.forEach(instance -> {
Definition definition = definitionMap.get(instance.getDefinitionId());
if (ObjectUtil.isNull(definition)) {
log.warn("实例 ID: {} 对应的流程定义信息未找到,跳过删除事件触发。", instance.getId());
return;
}
} catch (Exception e) {
log.warn("操作失败!{}", e.getMessage());
throw new ServiceException(e.getMessage());
}
return true;
flowProcessEventHandler.processDeleteHandler(definition.getFlowCode(), instance.getBusinessId());
});
// 删除实例
return insService.remove(instanceIds);
}
/**
@ -254,27 +244,22 @@ public class FlwInstanceServiceImpl implements IFlwInstanceService {
Definition::getId,
Function.identity()
);
try {
// 逐一触发删除事件
instances.forEach(instance -> {
Definition definition = definitionMap.get(instance.getDefinitionId());
if (ObjectUtil.isNull(definition)) {
log.warn("实例 ID: {} 对应的流程定义信息未找到,跳过删除事件触发。", instance.getId());
return;
}
flowProcessEventHandler.processDeleteHandler(definition.getFlowCode(), instance.getBusinessId());
});
List<FlowTask> flowTaskList = flwTaskService.selectByInstIds(instanceIds);
if (CollUtil.isNotEmpty(flowTaskList)) {
FlowEngine.userService().deleteByTaskIds(StreamUtils.toList(flowTaskList, FlowTask::getId));
// 逐一触发删除事件
instances.forEach(instance -> {
Definition definition = definitionMap.get(instance.getDefinitionId());
if (ObjectUtil.isNull(definition)) {
log.warn("实例 ID: {} 对应的流程定义信息未找到,跳过删除事件触发。", instance.getId());
return;
}
FlowEngine.taskService().deleteByInsIds(instanceIds);
FlowEngine.hisTaskService().deleteByInsIds(instanceIds);
FlowEngine.insService().removeByIds(instanceIds);
} catch (Exception e) {
log.warn("操作失败!{}", e.getMessage());
throw new ServiceException(e.getMessage());
flowProcessEventHandler.processDeleteHandler(definition.getFlowCode(), instance.getBusinessId());
});
List<FlowTask> flowTaskList = flwTaskService.selectByInstIds(instanceIds);
if (CollUtil.isNotEmpty(flowTaskList)) {
FlowEngine.userService().deleteByTaskIds(StreamUtils.toList(flowTaskList, FlowTask::getId));
}
FlowEngine.taskService().deleteByInsIds(instanceIds);
FlowEngine.hisTaskService().deleteByInsIds(instanceIds);
FlowEngine.insService().removeByIds(instanceIds);
return true;
}
@ -286,29 +271,24 @@ public class FlwInstanceServiceImpl implements IFlwInstanceService {
@Override
@Transactional(rollbackFor = Exception.class)
public boolean cancelProcessApply(FlowCancelBo bo) {
try {
Instance instance = selectInstByBusinessId(bo.getBusinessId());
if (instance == null) {
throw new ServiceException(ExceptionCons.NOT_FOUNT_INSTANCE);
}
Definition definition = defService.getById(instance.getDefinitionId());
if (definition == null) {
throw new ServiceException(ExceptionCons.NOT_FOUNT_DEF);
}
String message = bo.getMessage();
String userIdStr = LoginHelper.getUserIdStr();
BusinessStatusEnum.checkCancelStatus(instance.getFlowStatus());
FlowParams flowParams = FlowParams.build()
.message(message)
.flowStatus(BusinessStatusEnum.CANCEL.getStatus())
.hisStatus(BusinessStatusEnum.CANCEL.getStatus())
.handler(userIdStr)
.ignore(true);
taskService.revoke(instance.getId(), flowParams);
} catch (Exception e) {
log.error("撤销失败: {}", e.getMessage(), e);
throw new ServiceException(e.getMessage());
Instance instance = selectInstByBusinessId(bo.getBusinessId());
if (instance == null) {
throw new ServiceException(ExceptionCons.NOT_FOUNT_INSTANCE);
}
Definition definition = defService.getById(instance.getDefinitionId());
if (definition == null) {
throw new ServiceException(ExceptionCons.NOT_FOUNT_DEF);
}
String message = bo.getMessage();
String userIdStr = LoginHelper.getUserIdStr();
BusinessStatusEnum.checkCancelStatus(instance.getFlowStatus());
FlowParams flowParams = FlowParams.build()
.message(message)
.flowStatus(BusinessStatusEnum.CANCEL.getStatus())
.hisStatus(BusinessStatusEnum.CANCEL.getStatus())
.handler(userIdStr)
.ignore(true);
taskService.revoke(instance.getId(), flowParams);
return true;
}
@ -422,20 +402,14 @@ public class FlwInstanceServiceImpl implements IFlwInstanceService {
if (flowInstance == null) {
throw new ServiceException(ExceptionCons.NOT_FOUNT_INSTANCE);
}
try {
Map<String, Object> variableMap = new HashMap<>(Optional.ofNullable(flowInstance.getVariableMap()).orElse(Collections.emptyMap()));
if (!variableMap.containsKey(bo.getKey())) {
log.error("变量不存在: {}", bo.getKey());
return false;
}
variableMap.put(bo.getKey(), bo.getValue());
flowInstance.setVariable(FlowEngine.jsonConvert.objToStr(variableMap));
flowInstanceMapper.updateById(flowInstance);
} catch (Exception e) {
log.error("设置流程变量失败: {}", e.getMessage(), e);
throw new ServiceException(e.getMessage());
Map<String, Object> variableMap = new HashMap<>(Optional.ofNullable(flowInstance.getVariableMap()).orElse(Collections.emptyMap()));
if (!variableMap.containsKey(bo.getKey())) {
log.error("变量不存在: {}", bo.getKey());
return false;
}
return true;
variableMap.put(bo.getKey(), bo.getValue());
flowInstance.setVariable(FlowEngine.jsonConvert.objToStr(variableMap));
return flowInstanceMapper.updateById(flowInstance) > 0;
}
/**
@ -480,21 +454,16 @@ public class FlwInstanceServiceImpl implements IFlwInstanceService {
@Override
@Transactional(rollbackFor = Exception.class)
public boolean processInvalid(FlowInvalidBo bo) {
try {
Instance instance = insService.getById(bo.getId());
if (instance != null) {
BusinessStatusEnum.checkInvalidStatus(instance.getFlowStatus());
}
FlowParams flowParams = FlowParams.build()
.message(bo.getComment())
.flowStatus(BusinessStatusEnum.INVALID.getStatus())
.hisStatus(TaskStatusEnum.INVALID.getStatus())
.ignore(true);
taskService.terminationByInsId(bo.getId(), flowParams);
return true;
} catch (Exception e) {
log.error(e.getMessage(), e);
throw new ServiceException(e.getMessage());
Instance instance = insService.getById(bo.getId());
if (instance != null) {
BusinessStatusEnum.checkInvalidStatus(instance.getFlowStatus());
}
FlowParams flowParams = FlowParams.build()
.message(bo.getComment())
.flowStatus(BusinessStatusEnum.INVALID.getStatus())
.hisStatus(TaskStatusEnum.INVALID.getStatus())
.ignore(true);
taskService.terminationByInsId(bo.getId(), flowParams);
return true;
}
}

View File

@ -28,7 +28,7 @@ import java.util.List;
import java.util.Map;
/**
* 流程spel达式定义Service业务层处理
* 流程spel达式定义Service业务层处理
*
* @author Michelle.Chung
* @date 2025-07-04
@ -42,10 +42,10 @@ public class FlwSpelServiceImpl implements IFlwSpelService {
private final FlwSpelMapper baseMapper;
/**
* 查询流程spel达式定义
* 查询流程spel达式定义
*
* @param id 主键
* @return 流程spel达式定义
* @return 流程spel达式定义
*/
@Override
public FlowSpelVo queryById(Long id){
@ -53,11 +53,11 @@ public class FlwSpelServiceImpl implements IFlwSpelService {
}
/**
* 分页查询流程spel达式定义列表
* 分页查询流程spel达式定义列表
*
* @param bo 查询条件
* @param pageQuery 分页参数
* @return 流程spel达式定义分页列表
* @return 流程spel达式定义分页列表
*/
@Override
public TableDataInfo<FlowSpelVo> queryPageList(FlowSpelBo bo, PageQuery pageQuery) {
@ -67,10 +67,10 @@ public class FlwSpelServiceImpl implements IFlwSpelService {
}
/**
* 查询符合条件的流程spel达式定义列表
* 查询符合条件的流程spel达式定义列表
*
* @param bo 查询条件
* @return 流程spel达式定义列表
* @return 流程spel达式定义列表
*/
@Override
public List<FlowSpelVo> queryList(FlowSpelBo bo) {
@ -92,9 +92,9 @@ public class FlwSpelServiceImpl implements IFlwSpelService {
}
/**
* 新增流程spel达式定义
* 新增流程spel达式定义
*
* @param bo 流程spel达式定义
* @param bo 流程spel达式定义
* @return 是否新增成功
*/
@Override
@ -109,9 +109,9 @@ public class FlwSpelServiceImpl implements IFlwSpelService {
}
/**
* 修改流程spel达式定义
* 修改流程spel达式定义
*
* @param bo 流程spel达式定义
* @param bo 流程spel达式定义
* @return 是否修改成功
*/
@Override
@ -129,7 +129,7 @@ public class FlwSpelServiceImpl implements IFlwSpelService {
}
/**
* 校验并批量删除流程spel达式定义信息
* 校验并批量删除流程spel达式定义信息
*
* @param ids 待删除的主键集合
* @param isValid 是否进行有效性校验

View File

@ -5,9 +5,10 @@ import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.ObjectUtil;
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;
@ -23,12 +24,14 @@ 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.system.api.RemoteUserService;
import org.dromara.system.api.domain.vo.RemoteUserVo;
import org.dromara.warm.flow.core.FlowEngine;
import org.dromara.warm.flow.core.dto.FlowParams;
import org.dromara.warm.flow.core.entity.*;
import org.dromara.warm.flow.core.enums.CooperateType;
import org.dromara.warm.flow.core.enums.NodeType;
import org.dromara.warm.flow.core.enums.SkipType;
import org.dromara.warm.flow.core.enums.UserType;
@ -61,7 +64,6 @@ import org.dromara.workflow.service.IFlwTaskService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.util.*;
import static org.dromara.workflow.common.constant.FlowConstant.*;
@ -85,7 +87,6 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
private final FlowInstanceMapper flowInstanceMapper;
private final FlowTaskMapper flowTaskMapper;
private final FlowHisTaskMapper flowHisTaskMapper;
private final IdentifierGenerator identifierGenerator;
private final FlwTaskMapper flwTaskMapper;
private final FlwCategoryMapper flwCategoryMapper;
private final FlowNodeMapper flowNodeMapper;
@ -104,6 +105,7 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
*/
@Override
@Transactional(rollbackFor = Exception.class)
@Lock4j(keys = {"#startProcessBo.flowCode + #startProcessBo.businessId"})
public RemoteStartProcessReturn startWorkFlow(StartProcessBo startProcessBo) {
String businessId = startProcessBo.getBusinessId();
if (StringUtils.isBlank(businessId)) {
@ -140,6 +142,9 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
// 将流程定义内的扩展参数设置到变量中
Definition definition = FlowEngine.defService().getPublishByFlowCode(startProcessBo.getFlowCode());
if (ObjectUtil.isNull(definition)) {
throw new ServiceException("流程【" + startProcessBo.getFlowCode() + "】未发布,请先在流程设计器中发布流程定义");
}
Dict dict = JsonUtils.parseMap(definition.getExt());
boolean autoPass = !ObjectUtil.isNull(dict) && dict.getBool(FlowConstant.AUTO_PASS);
variables.put(FlowConstant.AUTO_PASS, autoPass);
@ -149,12 +154,7 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
.flowCode(startProcessBo.getFlowCode())
.variable(startProcessBo.getVariables())
.flowStatus(BusinessStatusEnum.DRAFT.getStatus());
Instance instance;
try {
instance = insService.start(businessId, flowParams);
} catch (Exception e) {
throw new ServiceException(e.getMessage());
}
Instance instance = insService.start(businessId, flowParams);
// 保存流程实例业务信息
this.buildFlowInstanceBizExt(instance, bizExt);
// 申请人执行流程
@ -200,52 +200,51 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
*/
@Override
@Transactional(rollbackFor = Exception.class)
@Lock4j(keys = {"#completeTaskBo.taskId"})
public boolean completeTask(CompleteTaskBo completeTaskBo) {
try {
// 获取任务ID并查询对应的流程任务和实例信息
Long taskId = completeTaskBo.getTaskId();
List<String> messageType = completeTaskBo.getMessageType();
String notice = completeTaskBo.getNotice();
// 获取抄送人
List<FlowCopyBo> flowCopyList = completeTaskBo.getFlowCopyList();
// 设置抄送人
Map<String, Object> variables = completeTaskBo.getVariables();
variables.put(FlowConstant.FLOW_COPY_LIST, flowCopyList);
// 消息类型
variables.put(FlowConstant.MESSAGE_TYPE, messageType);
// 消息通知
variables.put(FlowConstant.MESSAGE_NOTICE, notice);
// 获取任务ID并查询对应的流程任务和实例信息
Long taskId = completeTaskBo.getTaskId();
List<String> messageType = completeTaskBo.getMessageType();
String notice = completeTaskBo.getNotice();
// 获取抄送人
List<FlowCopyBo> flowCopyList = completeTaskBo.getFlowCopyList();
// 设置抄送人
Map<String, Object> variables = completeTaskBo.getVariables();
variables.put(FlowConstant.FLOW_COPY_LIST, flowCopyList);
// 消息类型
variables.put(FlowConstant.MESSAGE_TYPE, messageType);
// 消息通知
variables.put(FlowConstant.MESSAGE_NOTICE, notice);
FlowTask flowTask = flowTaskMapper.selectById(taskId);
if (ObjectUtil.isNull(flowTask)) {
throw new ServiceException("流程任务不存在或任务已审批!");
}
Instance ins = insService.getById(flowTask.getInstanceId());
// 检查流程状态是否为草稿已撤销或已退回状态若是则执行流程提交监听
if (BusinessStatusEnum.isDraftOrCancelOrBack(ins.getFlowStatus())) {
variables.put(FlowConstant.SUBMIT, true);
}
// 设置弹窗处理人
Map<String, Object> assigneeMap = setPopAssigneeMap(completeTaskBo.getAssigneeMap(), ins.getVariableMap());
if (CollUtil.isNotEmpty(assigneeMap)) {
variables.putAll(assigneeMap);
}
// 构建流程参数包括变量跳转类型消息处理人权限等信息
FlowParams flowParams = FlowParams.build()
.handler(completeTaskBo.getHandler())
.variable(variables)
.skipType(SkipType.PASS.getKey())
.message(completeTaskBo.getMessage())
.flowStatus(BusinessStatusEnum.WAITING.getStatus())
.hisStatus(TaskStatusEnum.PASS.getStatus())
.hisTaskExt(completeTaskBo.getFileId());
Boolean autoPass = Convert.toBool(variables.getOrDefault(AUTO_PASS, false));
skipTask(taskId, flowParams, flowTask.getInstanceId(), autoPass);
return true;
} catch (Exception e) {
log.error(e.getMessage(), e);
throw new ServiceException(e.getMessage());
FlowTask flowTask = flowTaskMapper.selectById(taskId);
if (ObjectUtil.isNull(flowTask)) {
throw new ServiceException("流程任务不存在或任务已审批!");
}
Instance ins = insService.getById(flowTask.getInstanceId());
// 检查流程状态是否为草稿已撤销或已退回状态若是则执行流程提交监听
if (BusinessStatusEnum.isDraftOrCancelOrBack(ins.getFlowStatus())) {
variables.put(FlowConstant.SUBMIT, true);
}
// 设置弹窗处理人
Map<String, Object> assigneeMap = setPopAssigneeMap(completeTaskBo.getAssigneeMap(), ins.getVariableMap());
if (CollUtil.isNotEmpty(assigneeMap)) {
variables.putAll(assigneeMap);
}
// 构建流程参数包括变量跳转类型消息处理人权限等信息
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())
.hisStatus(TaskStatusEnum.PASS.getStatus())
.hisTaskExt(completeTaskBo.getFileId());
Boolean autoPass = Convert.toBool(variables.getOrDefault(AUTO_PASS, false));
skipTask(taskId, flowParams, flowTask.getInstanceId(), autoPass);
return true;
}
/**
@ -307,10 +306,12 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
List<String> variableUserIds = Arrays.asList(userIds.split(StringUtils.SEPARATOR));
hashSet.addAll(popUserIds);
hashSet.addAll(variableUserIds);
map.put(entry.getKey(), StringUtils.joinComma(hashSet));
map.put(TaskStatusEnum.PASS.getStatus() + StrUtil.COLON + entry.getKey(), StringUtils.joinComma(hashSet));
map.put(TaskStatusEnum.BACK.getStatus() + StrUtil.COLON + entry.getKey(), StringUtils.joinComma(hashSet));
}
} else {
map.put(entry.getKey(), entry.getValue());
map.put(TaskStatusEnum.PASS.getStatus() + StrUtil.COLON + entry.getKey(), entry.getValue());
map.put(TaskStatusEnum.BACK.getStatus() + StrUtil.COLON + entry.getKey(), entry.getValue());
}
}
return map;
@ -335,7 +336,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);
@ -449,15 +450,19 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
}
private QueryWrapper<FlowTaskBo> buildQueryWrapper(FlowTaskBo flowTaskBo) {
Map<String, Object> params = flowTaskBo.getParams();
QueryWrapper<FlowTaskBo> wrapper = Wrappers.query();
wrapper.like(StringUtils.isNotBlank(flowTaskBo.getNodeName()), "t.node_name", flowTaskBo.getNodeName());
wrapper.like(StringUtils.isNotBlank(flowTaskBo.getFlowName()), "t.flow_name", flowTaskBo.getFlowName());
wrapper.like(StringUtils.isNotBlank(flowTaskBo.getFlowCode()), "t.flow_code", flowTaskBo.getFlowCode());
wrapper.like(StringUtils.isNotBlank(flowTaskBo.getFlowStatus()), "t.flow_status", flowTaskBo.getFlowStatus());
wrapper.in(CollUtil.isNotEmpty(flowTaskBo.getCreateByIds()), "t.create_by", flowTaskBo.getCreateByIds());
if (StringUtils.isNotBlank(flowTaskBo.getCategory())) {
List<Long> categoryIds = flwCategoryMapper.selectCategoryIdsByParentId(Convert.toLong(flowTaskBo.getCategory()));
wrapper.in("t.category", StreamUtils.toList(categoryIds, Convert::toStr));
}
wrapper.between(params.get("beginTime") != null && params.get("endTime") != null,
"t.create_time", params.get("beginTime"), params.get("endTime"));
wrapper.orderByDesc("t.create_time").orderByDesc("t.update_time");
return wrapper;
}
@ -470,40 +475,35 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
@Override
@Transactional(rollbackFor = Exception.class)
public boolean backProcess(BackProcessBo bo) {
try {
Long taskId = bo.getTaskId();
String notice = bo.getNotice();
List<String> messageType = bo.getMessageType();
String message = bo.getMessage();
FlowTask task = flowTaskMapper.selectById(taskId);
if (ObjectUtil.isNull(task)) {
throw new ServiceException("任务不存在!");
}
Instance inst = insService.getById(task.getInstanceId());
BusinessStatusEnum.checkBackStatus(inst.getFlowStatus());
Long definitionId = task.getDefinitionId();
String applyNodeCode = flwCommonService.applyNodeCode(definitionId);
Map<String, Object> variable = new HashMap<>();
// 消息类型
variable.put(FlowConstant.MESSAGE_TYPE, messageType);
// 消息通知
variable.put(FlowConstant.MESSAGE_NOTICE, notice);
FlowParams flowParams = FlowParams.build()
.nodeCode(bo.getNodeCode())
.variable(variable)
.message(message)
.skipType(SkipType.REJECT.getKey())
.flowStatus(applyNodeCode.equals(bo.getNodeCode()) ? TaskStatusEnum.BACK.getStatus() : TaskStatusEnum.WAITING.getStatus())
.hisStatus(TaskStatusEnum.BACK.getStatus())
.hisTaskExt(bo.getFileId());
taskService.skip(task.getId(), flowParams);
return true;
} catch (Exception e) {
log.error(e.getMessage(), e);
throw new ServiceException(e.getMessage());
Long taskId = bo.getTaskId();
String notice = bo.getNotice();
List<String> messageType = bo.getMessageType();
String message = bo.getMessage();
FlowTask task = flowTaskMapper.selectById(taskId);
if (ObjectUtil.isNull(task)) {
throw new ServiceException("任务不存在!");
}
Instance inst = insService.getById(task.getInstanceId());
BusinessStatusEnum.checkBackStatus(inst.getFlowStatus());
Long definitionId = task.getDefinitionId();
String applyNodeCode = flwCommonService.applyNodeCode(definitionId);
Map<String, Object> variable = new HashMap<>();
// 消息类型
variable.put(FlowConstant.MESSAGE_TYPE, messageType);
// 消息通知
variable.put(FlowConstant.MESSAGE_NOTICE, notice);
FlowParams flowParams = FlowParams.build()
.nodeCode(bo.getNodeCode())
.variable(variable)
.message(message)
.skipType(SkipType.REJECT.getKey())
.flowStatus(applyNodeCode.equals(bo.getNodeCode()) ? TaskStatusEnum.BACK.getStatus() : TaskStatusEnum.WAITING.getStatus())
.hisStatus(TaskStatusEnum.BACK.getStatus())
.hisTaskExt(bo.getFileId());
taskService.skip(task.getId(), flowParams);
return true;
}
/**
@ -531,8 +531,22 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
}
//获取可驳回的前置节点
List<Node> nodes = nodeService.previousNodeList(task.getDefinitionId(), nowNodeCode);
if (CollUtil.isNotEmpty(nodes)) {
return StreamUtils.filter(nodes, e -> NodeType.BETWEEN.getKey().equals(e.getNodeType()));
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 : hisTaskList) {
Node nodeValue = nodeMap.get(hisTask.getNodeCode());
if (nodeValue != null
&& NodeType.BETWEEN.getKey().equals(nodeValue.getNodeType())
&& added.add(nodeValue.getNodeCode())) {
backNodeList.add(nodeValue);
}
}
if (CollUtil.isNotEmpty(backNodeList)) {
Collections.reverse(backNodeList);
return backNodeList;
}
return nodes;
}
@ -545,26 +559,21 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
@Override
@Transactional(rollbackFor = Exception.class)
public boolean terminationTask(FlowTerminationBo bo) {
try {
Long taskId = bo.getTaskId();
Task task = taskService.getById(taskId);
if (task == null) {
throw new ServiceException("任务不存在!");
}
Instance instance = insService.getById(task.getInstanceId());
if (ObjectUtil.isNotNull(instance)) {
BusinessStatusEnum.checkInvalidStatus(instance.getFlowStatus());
}
FlowParams flowParams = FlowParams.build()
.message(bo.getComment())
.flowStatus(BusinessStatusEnum.TERMINATION.getStatus())
.hisStatus(TaskStatusEnum.TERMINATION.getStatus());
taskService.termination(taskId, flowParams);
return true;
} catch (Exception e) {
log.error(e.getMessage(), e);
throw new ServiceException(e.getMessage());
Long taskId = bo.getTaskId();
Task task = taskService.getById(taskId);
if (task == null) {
throw new ServiceException("任务不存在!");
}
Instance instance = insService.getById(task.getInstanceId());
if (ObjectUtil.isNotNull(instance)) {
BusinessStatusEnum.checkInvalidStatus(instance.getFlowStatus());
}
FlowParams flowParams = FlowParams.build()
.message(bo.getComment())
.flowStatus(BusinessStatusEnum.TERMINATION.getStatus())
.hisStatus(TaskStatusEnum.TERMINATION.getStatus());
taskService.termination(taskId, flowParams);
return true;
}
/**
@ -741,8 +750,8 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
Task task = taskService.getById(taskId);
FlowNode flowNode = getByNodeCode(task.getNodeCode(), task.getDefinitionId());
if ("addSignature".equals(taskOperation) || "reductionSignature".equals(taskOperation)) {
if (flowNode.getNodeRatio().compareTo(BigDecimal.ZERO) == 0) {
throw new ServiceException(task.getNodeName() + "不是会签节点!");
if (CooperateType.isOrSign(flowNode.getNodeRatio())) {
throw new ServiceException(task.getNodeName() + "不是会签或票签节点!");
}
}
// 设置任务状态并执行对应的任务操作
@ -786,23 +795,18 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
if (CollUtil.isEmpty(taskIdList)) {
return false;
}
try {
List<FlowTask> flowTasks = this.selectByIdList(taskIdList);
// 批量删除现有任务的办理人记录
if (CollUtil.isNotEmpty(flowTasks)) {
FlowEngine.userService().deleteByTaskIds(StreamUtils.toList(flowTasks, FlowTask::getId));
List<User> userList = StreamUtils.toList(flowTasks, flowTask ->
new FlowUser()
.setType(TaskAssigneeType.APPROVER.getCode())
.setProcessedBy(userId)
.setAssociated(flowTask.getId()));
if (CollUtil.isNotEmpty(userList)) {
FlowEngine.userService().saveBatch(userList);
}
List<FlowTask> flowTasks = this.selectByIdList(taskIdList);
// 批量删除现有任务的办理人记录
if (CollUtil.isNotEmpty(flowTasks)) {
FlowEngine.userService().deleteByTaskIds(StreamUtils.toList(flowTasks, FlowTask::getId));
List<User> userList = StreamUtils.toList(flowTasks, flowTask ->
new FlowUser()
.setType(TaskAssigneeType.APPROVER.getCode())
.setProcessedBy(userId)
.setAssociated(flowTask.getId()));
if (CollUtil.isNotEmpty(userList)) {
FlowEngine.userService().saveBatch(userList);
}
} catch (Exception e) {
log.error(e.getMessage(), e);
throw new ServiceException(e.getMessage());
}
return true;
}
@ -842,21 +846,16 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
*/
@Override
public boolean urgeTask(FlowUrgeTaskBo bo) {
try {
if (CollUtil.isEmpty(bo.getTaskIdList())) {
return false;
}
List<RemoteUserVo> userList = this.currentTaskAllUser(bo.getTaskIdList());
if (CollUtil.isEmpty(userList)) {
return false;
}
List<String> messageType = bo.getMessageType();
String message = bo.getMessage();
flwCommonService.sendMessage(messageType, message, "单据审批提醒", userList);
} catch (Exception e) {
log.error(e.getMessage(), e);
throw new ServiceException(e.getMessage());
if (CollUtil.isEmpty(bo.getTaskIdList())) {
return false;
}
List<RemoteUserVo> userList = this.currentTaskAllUser(bo.getTaskIdList());
if (CollUtil.isEmpty(userList)) {
return false;
}
List<String> messageType = bo.getMessageType();
String message = bo.getMessage();
flwCommonService.sendMessage(messageType, message, "单据审批提醒", userList);
return true;
}

View File

@ -13,6 +13,7 @@ import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.enums.BusinessStatusEnum;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StreamUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import org.dromara.common.mybatis.core.page.PageQuery;
@ -168,7 +169,7 @@ public class TestLeaveServiceImpl implements ITestLeaveService {
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean deleteWithValidByIds(List<Long> ids) {
workflowService.deleteInstance(ids);
workflowService.deleteInstance(StreamUtils.toList(ids, Convert::toStr));
return baseMapper.deleteByIds(ids) > 0;
}
@ -201,7 +202,10 @@ public class TestLeaveServiceImpl implements ITestLeaveService {
testLeave.setApplyCode(businessCode);
}
testLeave.setStatus(BusinessStatusEnum.WAITING.getStatus());
log.info("申请人提交");
}
String status = BusinessStatusEnum.findByStatus(processEvent.getStatus());
log.info("当前流程状态为{}", status);
baseMapper.updateById(testLeave);
});
}

View File

@ -3,7 +3,6 @@ package org.dromara.workflow.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ObjectUtil;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.warm.flow.orm.entity.FlowInstance;
import org.dromara.workflow.api.domain.RemoteCompleteTask;
@ -45,7 +44,7 @@ public class WorkflowServiceImpl implements WorkflowService {
* @return 结果
*/
@Override
public boolean deleteInstance(List<Long> businessIds) {
public boolean deleteInstance(List<String> businessIds) {
return flwInstanceService.deleteByBusinessIds(businessIds);
}
@ -159,28 +158,19 @@ public class WorkflowServiceImpl implements WorkflowService {
*/
@Override
public boolean startCompleteTask(RemoteStartProcess startProcess) {
try {
StartProcessBo processBo = new StartProcessBo();
processBo.setBusinessId(startProcess.getBusinessId());
processBo.setFlowCode(startProcess.getFlowCode());
processBo.setVariables(startProcess.getVariables());
processBo.setHandler(startProcess.getHandler());
processBo.setBizExt(BeanUtil.toBean(startProcess.getBizExt(), FlowInstanceBizExt.class));
StartProcessBo processBo = new StartProcessBo();
processBo.setBusinessId(startProcess.getBusinessId());
processBo.setFlowCode(startProcess.getFlowCode());
processBo.setVariables(startProcess.getVariables());
processBo.setHandler(startProcess.getHandler());
processBo.setBizExt(BeanUtil.toBean(startProcess.getBizExt(), FlowInstanceBizExt.class));
RemoteStartProcessReturn result = flwTaskService.startWorkFlow(processBo);
CompleteTaskBo taskBo = new CompleteTaskBo();
taskBo.setTaskId(result.getTaskId());
taskBo.setMessageType(Collections.singletonList(MessageTypeEnum.SYSTEM_MESSAGE.getCode()));
taskBo.setVariables(startProcess.getVariables());
taskBo.setHandler(startProcess.getHandler());
boolean flag = flwTaskService.completeTask(taskBo);
if (!flag) {
throw new ServiceException("流程发起异常");
}
return true;
} catch (Exception e) {
throw new ServiceException(e.getMessage());
}
RemoteStartProcessReturn result = flwTaskService.startWorkFlow(processBo);
CompleteTaskBo taskBo = new CompleteTaskBo();
taskBo.setTaskId(result.getTaskId());
taskBo.setMessageType(Collections.singletonList(MessageTypeEnum.SYSTEM_MESSAGE.getCode()));
taskBo.setVariables(startProcess.getVariables());
taskBo.setHandler(startProcess.getHandler());
return flwTaskService.completeTask(taskBo);
}
}

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

@ -27,7 +27,7 @@ services:
network_mode: "host"
nacos:
image: ruoyi/ruoyi-nacos:2.5.1
image: ruoyi/ruoyi-nacos:2.5.2
container_name: nacos
ports:
- "8848:8848"
@ -95,7 +95,7 @@ services:
network_mode: "host"
seata-server:
image: ruoyi/ruoyi-seata-server:2.5.1
image: ruoyi/ruoyi-seata-server:2.5.2
container_name: seata-server
ports:
- "7091:7091"
@ -134,7 +134,7 @@ services:
network_mode: "host"
ruoyi-monitor:
image: ruoyi/ruoyi-monitor:2.5.1
image: ruoyi/ruoyi-monitor:2.5.2
container_name: ruoyi-monitor
environment:
# 时区上海
@ -150,7 +150,7 @@ services:
network_mode: "host"
ruoyi-snailjob-server:
image: ruoyi/ruoyi-snailjob-server:2.5.1
image: ruoyi/ruoyi-snailjob-server:2.5.2
container_name: ruoyi-snailjob-server
environment:
# 时区上海
@ -164,7 +164,7 @@ services:
network_mode: "host"
ruoyi-gateway:
image: ruoyi/ruoyi-gateway:2.5.1
image: ruoyi/ruoyi-gateway:2.5.2
container_name: ruoyi-gateway
environment:
# 时区上海
@ -180,7 +180,7 @@ services:
network_mode: "host"
ruoyi-auth:
image: ruoyi/ruoyi-auth:2.5.1
image: ruoyi/ruoyi-auth:2.5.2
container_name: ruoyi-auth
environment:
# 时区上海
@ -196,7 +196,7 @@ services:
network_mode: "host"
ruoyi-system:
image: ruoyi/ruoyi-system:2.5.1
image: ruoyi/ruoyi-system:2.5.2
container_name: ruoyi-system
environment:
# 时区上海
@ -212,7 +212,7 @@ services:
network_mode: "host"
ruoyi-gen:
image: ruoyi/ruoyi-gen:2.5.1
image: ruoyi/ruoyi-gen:2.5.2
container_name: ruoyi-gen
environment:
# 时区上海
@ -228,7 +228,7 @@ services:
network_mode: "host"
ruoyi-job:
image: ruoyi/ruoyi-job:2.5.1
image: ruoyi/ruoyi-job:2.5.2
container_name: ruoyi-job
environment:
# 时区上海
@ -246,7 +246,7 @@ services:
network_mode: "host"
ruoyi-resource:
image: ruoyi/ruoyi-resource:2.5.1
image: ruoyi/ruoyi-resource:2.5.2
container_name: ruoyi-resource
environment:
# 时区上海
@ -262,7 +262,7 @@ services:
network_mode: "host"
ruoyi-workflow:
image: ruoyi/ruoyi-workflow:2.5.1
image: ruoyi/ruoyi-workflow:2.5.2
container_name: ruoyi-workflow
environment:
# 时区上海
@ -395,56 +395,31 @@ services:
- /docker/rabbitmq/data:/var/lib/rabbitmq
network_mode: "host"
zookeeper:
image: 'bitnami/zookeeper:3.8.0'
container_name: zookeeper
ports:
- "2181:2181"
environment:
TZ: Asia/Shanghai
ALLOW_ANONYMOUS_LOGIN: "yes"
ZOO_SERVER_ID: 1
ZOO_PORT_NUMBER: 2181
# 自带的控制台 一般用不上可自行开启
ZOO_ENABLE_ADMIN_SERVER: "no"
# 自带控制台的端口
ZOO_ADMIN_SERVER_PORT_NUMBER: 8080
network_mode: "host"
kafka:
image: 'bitnami/kafka:3.6.2'
image: apache/kafka:3.9.1
container_name: kafka
ports:
- "9092:9092"
- "9093:9093"
environment:
TZ: Asia/Shanghai
# 更多变量 查看文档 https://github.com/bitnami/bitnami-docker-kafka/blob/master/README.md
KAFKA_BROKER_ID: 1
KAFKA_NODE_ID: 1
KAFKA_PROCESS_ROLES: broker,controller
# 监听端口
KAFKA_CFG_LISTENERS: PLAINTEXT://:9092
KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:9092,CONTROLLER://0.0.0.0:9093
# 实际访问ip 本地用 127 内网用 192 外网用 外网ip
KAFKA_CFG_ADVERTISED_LISTENERS: PLAINTEXT://192.168.31.165:9092
KAFKA_CFG_ZOOKEEPER_CONNECT: 127.0.0.1:2181
ALLOW_PLAINTEXT_LISTENER: "yes"
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://192.168.31.165:9092
KAFKA_CONTROLLER_QUORUM_VOTERS: 1@localhost:9093
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT
KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT
KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER
KAFKA_LOG_DIRS: /var/lib/kafka/data
KAFKA_AUTO_CREATE_TOPICS_ENABLE: "true"
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
KAFKA_LOG_RETENTION_HOURS: 168
KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS: 0
CLUSTER_ID: "Mk3OEYBSD34fcwNTJENDM2Qk" # 使用 kafka-storage.sh random-uuid 生成
volumes:
- /docker/kafka/data:/bitnami/kafka/data
depends_on:
- zookeeper
network_mode: "host"
kafka-manager:
image: sheepkiller/kafka-manager:latest
container_name: kafka-manager
ports:
- "19092:19092"
environment:
ZK_HOSTS: 127.0.0.1:2181
APPLICATION_SECRET: letmein
KAFKA_MANAGER_USERNAME: ruoyi
KAFKA_MANAGER_PASSWORD: ruoyi123
KM_ARGS: -Dhttp.port=19092
depends_on:
- kafka
- /docker/kafka/data:/var/lib/kafka/data
network_mode: "host"
sky-oap:

View File

@ -99,11 +99,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

@ -600,11 +600,11 @@ INSERT INTO sys_menu VALUES ('11642', '请假申请删除', '11638', '4', '#', '
INSERT INTO sys_menu VALUES ('11643', '请假申请导出', '11638', '5', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:leave:export', '#', 103, 1, SYSDATE, NULL, NULL, '');
INSERT INTO sys_menu VALUES ('11801', '流程表达式', '11616', 2, 'spel', 'workflow/spel/index', '', 1, 0, 'C', '0', '0', 'workflow:spel:list', 'input', 103, 1, SYSDATE, 1, SYSDATE, '流程达式定义菜单');
INSERT INTO sys_menu VALUES ('11802', '流程spel达式定义查询', '11801', 1, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:query', '#', 103, 1, SYSDATE, NULL, NULL, '');
INSERT INTO sys_menu VALUES ('11803', '流程spel达式定义新增', '11801', 2, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:add', '#', 103, 1, SYSDATE, NULL, NULL, '');
INSERT INTO sys_menu VALUES ('11804', '流程spel达式定义修改', '11801', 3, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:edit', '#', 103, 1, SYSDATE, NULL, NULL, '');
INSERT INTO sys_menu VALUES ('11805', '流程spel达式定义删除', '11801', 4, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:remove', '#', 103, 1, SYSDATE, NULL, NULL, '');
INSERT INTO sys_menu VALUES ('11806', '流程spel达式定义导出', '11801', 5, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:export', '#', 103, 1, SYSDATE, NULL, NULL, '');
INSERT INTO sys_menu VALUES ('11802', '流程spel达式定义查询', '11801', 1, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:query', '#', 103, 1, SYSDATE, NULL, NULL, '');
INSERT INTO sys_menu VALUES ('11803', '流程spel达式定义新增', '11801', 2, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:add', '#', 103, 1, SYSDATE, NULL, NULL, '');
INSERT INTO sys_menu VALUES ('11804', '流程spel达式定义修改', '11801', 3, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:edit', '#', 103, 1, SYSDATE, NULL, NULL, '');
INSERT INTO sys_menu VALUES ('11805', '流程spel达式定义删除', '11801', 4, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:remove', '#', 103, 1, SYSDATE, NULL, NULL, '');
INSERT INTO sys_menu VALUES ('11806', '流程spel达式定义导出', '11801', 5, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:export', '#', 103, 1, SYSDATE, NULL, NULL, '');
-- ----------------------------
-- 6、用户和角色关联表 用户N-1角色

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

@ -55,13 +55,12 @@ create table FLOW_NODE
DEFINITION_ID NUMBER(20) not null,
NODE_CODE VARCHAR2(100) not null,
NODE_NAME VARCHAR2(100),
NODE_RATIO NUMBER(6, 3),
PERMISSION_FLAG VARCHAR2(200),
NODE_RATIO VARCHAR2(200),
COORDINATE VARCHAR2(100),
ANY_NODE_SKIP VARCHAR2(100),
LISTENER_TYPE VARCHAR2(100),
LISTENER_PATH VARCHAR2(500),
HANDLER_TYPE VARCHAR2(100),
HANDLER_PATH VARCHAR2(400),
FORM_CUSTOM VARCHAR2(1) default 'N',
FORM_PATH VARCHAR2(100),
VERSION VARCHAR2(20),
@ -71,8 +70,7 @@ create table FLOW_NODE
UPDATE_BY VARCHAR2(64) default '',
EXT CLOB,
DEL_FLAG VARCHAR2(1) default '0',
TENANT_ID VARCHAR2(40),
PERMISSION_FLAG VARCHAR2(200)
TENANT_ID VARCHAR2(40)
);
alter table FLOW_NODE
@ -89,8 +87,6 @@ comment on column FLOW_NODE.COORDINATE is '坐标';
comment on column FLOW_NODE.ANY_NODE_SKIP is '任意结点跳转';
comment on column FLOW_NODE.LISTENER_TYPE is '监听器类型';
comment on column FLOW_NODE.LISTENER_PATH is '监听器路径';
comment on column FLOW_NODE.HANDLER_TYPE is '处理器类型';
comment on column FLOW_NODE.HANDLER_PATH is '处理器路径';
comment on column FLOW_NODE.FORM_CUSTOM is '审批表单是否自定义 (Y是 N否)';
comment on column FLOW_NODE.FORM_PATH is '审批表单路径';
comment on column FLOW_NODE.VERSION is '版本';

View File

@ -599,11 +599,11 @@ INSERT INTO sys_menu VALUES ('11642', '请假申请删除', '11638', '4', '#', '
INSERT INTO sys_menu VALUES ('11643', '请假申请导出', '11638', '5', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:leave:export', '#', 103, 1, now(), NULL, NULL, '');
INSERT INTO sys_menu VALUES ('11801', '流程表达式', '11616', 2, 'spel', 'workflow/spel/index', '', 1, 0, 'C', '0', '0', 'workflow:spel:list', 'input', 103, 1, now(), 1, now(), '流程达式定义菜单');
INSERT INTO sys_menu VALUES ('11802', '流程spel达式定义查询', '11801', 1, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:query', '#', 103, 1, now(), NULL, NULL, '');
INSERT INTO sys_menu VALUES ('11803', '流程spel达式定义新增', '11801', 2, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:add', '#', 103, 1, now(), NULL, NULL, '');
INSERT INTO sys_menu VALUES ('11804', '流程spel达式定义修改', '11801', 3, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:edit', '#', 103, 1, now(), NULL, NULL, '');
INSERT INTO sys_menu VALUES ('11805', '流程spel达式定义删除', '11801', 4, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:remove', '#', 103, 1, now(), NULL, NULL, '');
INSERT INTO sys_menu VALUES ('11806', '流程spel达式定义导出', '11801', 5, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:export', '#', 103, 1, now(), NULL, NULL, '');
INSERT INTO sys_menu VALUES ('11802', '流程spel达式定义查询', '11801', 1, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:query', '#', 103, 1, now(), NULL, NULL, '');
INSERT INTO sys_menu VALUES ('11803', '流程spel达式定义新增', '11801', 2, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:add', '#', 103, 1, now(), NULL, NULL, '');
INSERT INTO sys_menu VALUES ('11804', '流程spel达式定义修改', '11801', 3, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:edit', '#', 103, 1, now(), NULL, NULL, '');
INSERT INTO sys_menu VALUES ('11805', '流程spel达式定义删除', '11801', 4, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:remove', '#', 103, 1, now(), NULL, NULL, '');
INSERT INTO sys_menu VALUES ('11806', '流程spel达式定义导出', '11801', 5, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:export', '#', 103, 1, now(), NULL, NULL, '');
-- ----------------------------
-- 6、用户和角色关联表 用户N-1角色

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,

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