mirror of
https://gitee.com/dromara/RuoYi-Vue-Plus.git
synced 2026-03-24 22:34:33 +08:00
Compare commits
69 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b629c82d0f | ||
|
|
c601164ba3 | ||
|
|
867a5d084d | ||
|
|
78aab8f8cd | ||
|
|
c919572eb2 | ||
|
|
12bcc68b45 | ||
|
|
f551cd938c | ||
|
|
e1a8eea3a1 | ||
|
|
269de03d0b | ||
|
|
0bc4961694 | ||
|
|
a71541a227 | ||
|
|
334c85ed61 | ||
|
|
0dd3b8dc51 | ||
|
|
0530a9d307 | ||
|
|
2d7195c61d | ||
|
|
aaede419bc | ||
|
|
75d8d374bc | ||
|
|
07b29e06cf | ||
|
|
3cbbd0698d | ||
|
|
5312131635 | ||
|
|
91f505539e | ||
|
|
c9774e78c4 | ||
|
|
337c2f7170 | ||
|
|
f773818642 | ||
|
|
9bf8ae5583 | ||
|
|
d190b89681 | ||
|
|
d89e09b94e | ||
|
|
1452ae9685 | ||
|
|
28772b8b30 | ||
|
|
ab201f9d9e | ||
|
|
1f5f969d20 | ||
|
|
a89a124a0e | ||
|
|
536b6db527 | ||
|
|
f8e3cff83d | ||
|
|
6b3ed18c33 | ||
|
|
e669fa3210 | ||
|
|
2d116146ce | ||
|
|
ed9de38c6f | ||
|
|
28d4c88387 | ||
|
|
c656f3340d | ||
|
|
cd0ee3f016 | ||
|
|
48ea66cb1a | ||
|
|
ab6409ea28 | ||
|
|
34c3b81190 | ||
|
|
91ba3869e7 | ||
|
|
1bb597b855 | ||
|
|
348d7fc534 | ||
|
|
76218091ad | ||
|
|
95c9e37797 | ||
|
|
aa277b373b | ||
|
|
79d9f47053 | ||
|
|
f984f08a14 | ||
|
|
6f94095bb0 | ||
|
|
2d4685ac5f | ||
|
|
7f9e4e14f0 | ||
|
|
c5777c01c1 | ||
|
|
459e9caf14 | ||
|
|
0940ba6762 | ||
|
|
d8ed23f227 | ||
|
|
948eba6566 | ||
|
|
1a14bdf256 | ||
|
|
bbc684b335 | ||
|
|
2b8f4e1d2c | ||
|
|
d634c2a292 | ||
|
|
8b97e7bc53 | ||
|
|
88f871002c | ||
|
|
874ad7c9b7 | ||
|
|
89d6f6f247 | ||
|
|
1324a1cb16 |
@@ -2,7 +2,7 @@
|
||||
<configuration default="false" name="ruoyi-monitor-admin" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
|
||||
<deployment type="dockerfile">
|
||||
<settings>
|
||||
<option name="imageTag" value="ruoyi/ruoyi-monitor-admin:5.5.2" />
|
||||
<option name="imageTag" value="ruoyi/ruoyi-monitor-admin:5.6.0" />
|
||||
<option name="buildOnly" value="true" />
|
||||
<option name="sourceFilePath" value="ruoyi-extend/ruoyi-monitor-admin/Dockerfile" />
|
||||
</settings>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<configuration default="false" name="ruoyi-server" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
|
||||
<deployment type="dockerfile">
|
||||
<settings>
|
||||
<option name="imageTag" value="ruoyi/ruoyi-server:5.5.2" />
|
||||
<option name="imageTag" value="ruoyi/ruoyi-server:5.6.0" />
|
||||
<option name="buildOnly" value="true" />
|
||||
<option name="sourceFilePath" value="ruoyi-admin/Dockerfile" />
|
||||
</settings>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<configuration default="false" name="ruoyi-snailjob-server" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
|
||||
<deployment type="dockerfile">
|
||||
<settings>
|
||||
<option name="imageTag" value="ruoyi/ruoyi-snailjob-server:5.5.2" />
|
||||
<option name="imageTag" value="ruoyi/ruoyi-snailjob-server:5.6.0" />
|
||||
<option name="buildOnly" value="true" />
|
||||
<option name="sourceFilePath" value="ruoyi-extend/ruoyi-snailjob-server/Dockerfile" />
|
||||
</settings>
|
||||
|
||||
14
README.md
14
README.md
@@ -5,13 +5,12 @@
|
||||
## 平台简介
|
||||
|
||||
[](https://gitee.com/dromara/RuoYi-Vue-Plus)
|
||||
[](https://github.com/dromara/RuoYi-Vue-Plus)
|
||||
[](https://github.com/dromara/RuoYi-Vue-Plus)
|
||||
[](https://gitcode.com/dromara/RuoYi-Vue-Plus)
|
||||
[](https://gitee.com/dromara/RuoYi-Vue-Plus/blob/5.X/LICENSE)
|
||||
[](https://www.jetbrains.com/?from=RuoYi-Vue-Plus)
|
||||
<br>
|
||||
[](https://gitee.com/dromara/RuoYi-Vue-Plus)
|
||||
[]()
|
||||
[](https://gitee.com/dromara/RuoYi-Vue-Plus)
|
||||
[]()
|
||||
[]()
|
||||
[]()
|
||||
|
||||
@@ -23,7 +22,7 @@
|
||||
> 系统演示: [传送门](https://plus-doc.dromara.org/#/common/demo_system)
|
||||
|
||||
> 官方前端项目地址: [gitee](https://gitee.com/JavaLionLi/plus-ui) - [github](https://github.com/JavaLionLi/plus-ui) - [gitcode](https://gitcode.com/dromara/plus-ui)<br>
|
||||
> 成员前端项目地址: 基于vben5 [ruoyi-plus-vben5](https://gitee.com/dapppp/ruoyi-plus-vben5)<br>
|
||||
> 成员前端项目地址: 基于vben5 [ruoyi-plus-vben5](https://github.com/imdap/ruoyi-plus-vben5)<br>
|
||||
> 成员前端项目地址: 基于soybean [ruoyi-plus-soybean](https://gitee.com/xlsea/ruoyi-plus-soybean)<br>
|
||||
> 成员项目地址: 删除多租户与工作流 [RuoYi-Vue-Plus-Single](https://gitee.com/ColorDreams/RuoYi-Vue-Plus-Single)<br>
|
||||
|
||||
@@ -33,9 +32,8 @@
|
||||
|
||||
MaxKey 业界领先单点登录产品 - https://gitee.com/dromara/MaxKey <br>
|
||||
CCFlow 驰聘低代码-流程-表单 - https://gitee.com/opencc/RuoYi-JFlow <br>
|
||||
数舵科技 软件定制开发APP小程序等 - http://www.shuduokeji.com/ <br>
|
||||
数舵科技 软件定制开发APP小程序等 - https://www.shuduokeji.com/ <br>
|
||||
引迈信息 软件开发平台 - https://www.jnpfsoft.com/index.html?from=plus-doc <br>
|
||||
<font color="red">**启山商城系统 多租户商城源码可免费商用可二次开发 - https://www.73app.cn/** </font><br>
|
||||
Mall4J 高质量Java商城系统 - https://www.mall4j.com/cn/?statId=11 <br>
|
||||
aizuda flowlong 工作流 - https://gitee.com/aizuda/flowlong <br>
|
||||
Ruoyi-Plus-Uniapp - https://ruoyi.plus <br>
|
||||
@@ -55,7 +53,7 @@ Topiam IAM/IDaaS身份管理平台 - https://www.topiam.cn/ <br>
|
||||
| 权限注解 | 采用 Sa-Token 支持注解 登录校验、角色校验、权限校验、二级认证校验、HttpBasic校验、忽略校验<br/>角色与权限校验支持多种条件 如 `AND` `OR` 或 `权限 OR 角色` 等复杂表达式 | 只支持是否存在匹配 |
|
||||
| 三方鉴权 | 采用 JustAuth 第三方登录组件 支持微信、钉钉等数十种三方认证 | 无 |
|
||||
| 关系数据库支持 | 原生支持 MySQL、Oracle、PostgreSQL、SQLServer<br/>可同时使用异构切换(支持其他 mybatis-plus 支持的所有数据库 只需要增加jdbc依赖即可使用 达梦金仓等均有成功案例) | 支持 Mysql、Oracle 不支持同时使用、不支持异构切换 |
|
||||
| 缓存数据库 | 支持 Redis 5-7 支持大部分新功能特性 如 分布式限流、分布式队列 | Redis 简单 get set 支持 |
|
||||
| 缓存数据库 | 支持 Redis >= 6 支持大部分新功能特性 如 分布式限流、分布式队列 | Redis 简单 get set 支持 |
|
||||
| Redis客户端 | 采用 Redisson Redis官方推荐 基于Netty的客户端工具<br/>支持Redis 90%以上的命令 底层优化规避很多不正确的用法 例如: keys被转换为scan<br/>支持单机、哨兵、单主集群、多主集群等模式 | Lettuce + RedisTemplate 支持模式少 工具使用繁琐<br/>连接池采用 common-pool Bug多经常性出问题 |
|
||||
| 缓存注解 | 采用 Spring-Cache 注解 对其扩展了实现支持了更多功能<br/>例如 过期时间 最大空闲时间 组最大长度等 只需一个注解即可完成数据自动缓存 | 需手动编写Redis代码逻辑 |
|
||||
| ORM框架 | 采用 Mybatis-Plus 基于对象几乎不用写SQL全java操作 功能强大插件众多<br/>例如多租户插件 分页插件 乐观锁插件等等 | 采用 Mybatis 基于XML需要手写SQL |
|
||||
|
||||
29
pom.xml
29
pom.xml
@@ -13,32 +13,32 @@
|
||||
<description>Dromara RuoYi-Vue-Plus多租户管理系统</description>
|
||||
|
||||
<properties>
|
||||
<revision>5.5.2</revision>
|
||||
<spring-boot.version>3.5.9</spring-boot.version>
|
||||
<revision>5.6.0</revision>
|
||||
<spring-boot.version>3.5.12</spring-boot.version>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||
<java.version>17</java.version>
|
||||
<mybatis.version>3.5.16</mybatis.version>
|
||||
<springdoc.version>2.8.14</springdoc.version>
|
||||
<mybatis.version>3.5.19</mybatis.version>
|
||||
<springdoc.version>2.8.15</springdoc.version>
|
||||
<therapi-javadoc.version>0.15.0</therapi-javadoc.version>
|
||||
<fastexcel.version>1.3.0</fastexcel.version>
|
||||
<velocity.version>2.3</velocity.version>
|
||||
<satoken.version>1.44.0</satoken.version>
|
||||
<mybatis-plus.version>3.5.14</mybatis-plus.version>
|
||||
<mybatis-plus.version>3.5.16</mybatis-plus.version>
|
||||
<p6spy.version>3.9.1</p6spy.version>
|
||||
<hutool.version>5.8.40</hutool.version>
|
||||
<spring-boot-admin.version>3.5.5</spring-boot-admin.version>
|
||||
<hutool.version>5.8.43</hutool.version>
|
||||
<spring-boot-admin.version>3.5.6</spring-boot-admin.version>
|
||||
<redisson.version>3.52.0</redisson.version>
|
||||
<lock4j.version>2.2.7</lock4j.version>
|
||||
<dynamic-ds.version>4.3.1</dynamic-ds.version>
|
||||
<snailjob.version>1.9.0</snailjob.version>
|
||||
<mapstruct-plus.version>1.5.0</mapstruct-plus.version>
|
||||
<mapstruct-plus.lombok.version>0.2.0</mapstruct-plus.lombok.version>
|
||||
<lombok.version>1.18.40</lombok.version>
|
||||
<bouncycastle.version>1.80</bouncycastle.version>
|
||||
<lombok.version>1.18.42</lombok.version>
|
||||
<bouncycastle.version>1.83</bouncycastle.version>
|
||||
<justauth.version>1.16.7</justauth.version>
|
||||
<!-- 离线IP地址定位库 -->
|
||||
<ip2region.version>3.3.1</ip2region.version>
|
||||
<ip2region.version>3.3.4</ip2region.version>
|
||||
<!-- OSS 配置 -->
|
||||
<aws.sdk.version>2.28.22</aws.sdk.version>
|
||||
<!-- SMS 配置 -->
|
||||
@@ -226,13 +226,13 @@
|
||||
<artifactId>s3</artifactId>
|
||||
<version>${aws.sdk.version}</version>
|
||||
</dependency>
|
||||
<!-- 基于 AWS CRT 的 S3 客户端的性能增强的 S3 传输管理器 -->
|
||||
<!-- 客户端的性能增强传输管理器 -->
|
||||
<dependency>
|
||||
<groupId>software.amazon.awssdk</groupId>
|
||||
<artifactId>s3-transfer-manager</artifactId>
|
||||
<version>${aws.sdk.version}</version>
|
||||
</dependency>
|
||||
<!-- 将基于 Netty 的 HTTP 客户端从类路径中移除 -->
|
||||
<!-- 适用于 Netty 的客户端 -->
|
||||
<dependency>
|
||||
<groupId>software.amazon.awssdk</groupId>
|
||||
<artifactId>netty-nio-client</artifactId>
|
||||
@@ -284,7 +284,7 @@
|
||||
<!-- 加密包引入 -->
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcprov-jdk15to18</artifactId>
|
||||
<artifactId>bcpkix-jdk18on</artifactId>
|
||||
<version>${bouncycastle.version}</version>
|
||||
</dependency>
|
||||
|
||||
@@ -375,8 +375,7 @@
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>${maven-compiler-plugin.version}</version>
|
||||
<configuration>
|
||||
<source>${java.version}</source>
|
||||
<target>${java.version}</target>
|
||||
<release>${java.version}</release>
|
||||
<encoding>${project.build.sourceEncoding}</encoding>
|
||||
<annotationProcessorPaths>
|
||||
<path>
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
package org.dromara.web.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaIgnore;
|
||||
import cn.hutool.captcha.AbstractCaptcha;
|
||||
import cn.hutool.captcha.generator.CodeGenerator;
|
||||
import cn.hutool.captcha.generator.MathGenerator;
|
||||
import cn.hutool.captcha.generator.RandomGenerator;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
@@ -14,14 +15,13 @@ import org.dromara.common.core.domain.R;
|
||||
import org.dromara.common.core.exception.ServiceException;
|
||||
import org.dromara.common.core.utils.SpringUtils;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.common.core.utils.reflect.ReflectUtils;
|
||||
import org.dromara.common.mail.config.properties.MailProperties;
|
||||
import org.dromara.common.mail.utils.MailUtils;
|
||||
import org.dromara.common.ratelimiter.annotation.RateLimiter;
|
||||
import org.dromara.common.ratelimiter.enums.LimitType;
|
||||
import org.dromara.common.redis.utils.RedisUtils;
|
||||
import org.dromara.common.web.core.WaveAndCircleCaptcha;
|
||||
import org.dromara.common.web.config.properties.CaptchaProperties;
|
||||
import org.dromara.common.web.enums.CaptchaType;
|
||||
import org.dromara.sms4j.api.SmsBlend;
|
||||
import org.dromara.sms4j.api.entity.SmsResponse;
|
||||
import org.dromara.sms4j.core.factory.SmsFactory;
|
||||
@@ -33,6 +33,7 @@ import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.awt.*;
|
||||
import java.time.Duration;
|
||||
import java.util.LinkedHashMap;
|
||||
|
||||
@@ -130,19 +131,21 @@ public class CaptchaController {
|
||||
String uuid = IdUtil.simpleUUID();
|
||||
String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + uuid;
|
||||
// 生成验证码
|
||||
CaptchaType captchaType = captchaProperties.getType();
|
||||
String captchaType = captchaProperties.getType();
|
||||
CodeGenerator codeGenerator;
|
||||
if (CaptchaType.MATH == captchaType) {
|
||||
codeGenerator = ReflectUtils.newInstance(captchaType.getClazz(), captchaProperties.getNumberLength(), false);
|
||||
if ("math".equals(captchaType)) {
|
||||
codeGenerator = new MathGenerator(captchaProperties.getNumberLength(), false);
|
||||
} else {
|
||||
codeGenerator = ReflectUtils.newInstance(captchaType.getClazz(), captchaProperties.getCharLength());
|
||||
codeGenerator = new RandomGenerator(captchaProperties.getCharLength());
|
||||
}
|
||||
AbstractCaptcha captcha = SpringUtils.getBean(captchaProperties.getCategory().getClazz());
|
||||
WaveAndCircleCaptcha captcha = new WaveAndCircleCaptcha(160, 60);
|
||||
// captcha.setBackground(Color.WHITE); // 不设置就是透明底
|
||||
captcha.setFont(new Font("Arial", Font.BOLD, 45));
|
||||
captcha.setGenerator(codeGenerator);
|
||||
captcha.createCode();
|
||||
// 如果是数学验证码,使用SpEL表达式处理验证码结果
|
||||
String code = captcha.getCode();
|
||||
if (CaptchaType.MATH == captchaType) {
|
||||
if ("math".equals(captchaType)) {
|
||||
ExpressionParser parser = new SpelExpressionParser();
|
||||
Expression exp = parser.parseExpression(StringUtils.remove(code, "="));
|
||||
code = exp.getValue(String.class);
|
||||
|
||||
@@ -8,7 +8,7 @@ server:
|
||||
# undertow 配置
|
||||
undertow:
|
||||
# HTTP post内容的最大大小。当值为-1时,默认值为大小是无限的
|
||||
max-http-post-size: -1
|
||||
max-http-post-size: 1GB
|
||||
# 以下的配置会影响buffer,这些buffer会用于服务器连接的IO操作,有点类似netty的池化内存管理
|
||||
# 每块buffer的空间大小,越小的空间被利用越充分
|
||||
buffer-size: 512
|
||||
@@ -24,9 +24,7 @@ captcha:
|
||||
# 是否启用验证码校验
|
||||
enable: true
|
||||
# 验证码类型 math 数组计算 char 字符验证
|
||||
type: MATH
|
||||
# line 线段干扰 circle 圆圈干扰 shear 扭曲干扰
|
||||
category: CIRCLE
|
||||
type: math
|
||||
# 数字验证码位数
|
||||
numberLength: 1
|
||||
# 字符验证码长度
|
||||
|
||||
@@ -5,7 +5,7 @@ user.jcaptcha.expire=验证码已失效
|
||||
user.not.exists=对不起, 您的账号:{0} 不存在.
|
||||
user.password.not.match=用户不存在/密码错误
|
||||
user.password.retry.limit.count=密码输入错误{0}次
|
||||
user.password.retry.limit.exceed=密码输入错误{0}次,帐户锁定{1}分钟
|
||||
user.password.retry.limit.exceed=密码输入错误{0}次,账户锁定{1}分钟
|
||||
user.password.delete=对不起,您的账号:{0} 已被删除
|
||||
user.blocked=对不起,您的账号:{0} 已禁用,请联系管理员
|
||||
role.blocked=角色已封禁,请联系管理员
|
||||
@@ -47,10 +47,10 @@ repeat.submit.message=不允许重复提交,请稍候再试
|
||||
rate.limiter.message=访问过于频繁,请稍候再试
|
||||
sms.code.not.blank=短信验证码不能为空
|
||||
sms.code.retry.limit.count=短信验证码输入错误{0}次
|
||||
sms.code.retry.limit.exceed=短信验证码输入错误{0}次,帐户锁定{1}分钟
|
||||
sms.code.retry.limit.exceed=短信验证码输入错误{0}次,账户锁定{1}分钟
|
||||
email.code.not.blank=邮箱验证码不能为空
|
||||
email.code.retry.limit.count=邮箱验证码输入错误{0}次
|
||||
email.code.retry.limit.exceed=邮箱验证码输入错误{0}次,帐户锁定{1}分钟
|
||||
email.code.retry.limit.exceed=邮箱验证码输入错误{0}次,账户锁定{1}分钟
|
||||
xcx.code.not.blank=小程序[code]不能为空
|
||||
social.source.not.blank=第三方登录平台[source]不能为空
|
||||
social.code.not.blank=第三方登录平台[code]不能为空
|
||||
|
||||
@@ -5,7 +5,7 @@ user.jcaptcha.expire=验证码已失效
|
||||
user.not.exists=对不起, 您的账号:{0} 不存在.
|
||||
user.password.not.match=用户不存在/密码错误
|
||||
user.password.retry.limit.count=密码输入错误{0}次
|
||||
user.password.retry.limit.exceed=密码输入错误{0}次,帐户锁定{1}分钟
|
||||
user.password.retry.limit.exceed=密码输入错误{0}次,账户锁定{1}分钟
|
||||
user.password.delete=对不起,您的账号:{0} 已被删除
|
||||
user.blocked=对不起,您的账号:{0} 已禁用,请联系管理员
|
||||
role.blocked=角色已封禁,请联系管理员
|
||||
@@ -47,10 +47,10 @@ repeat.submit.message=不允许重复提交,请稍候再试
|
||||
rate.limiter.message=访问过于频繁,请稍候再试
|
||||
sms.code.not.blank=短信验证码不能为空
|
||||
sms.code.retry.limit.count=短信验证码输入错误{0}次
|
||||
sms.code.retry.limit.exceed=短信验证码输入错误{0}次,帐户锁定{1}分钟
|
||||
sms.code.retry.limit.exceed=短信验证码输入错误{0}次,账户锁定{1}分钟
|
||||
email.code.not.blank=邮箱验证码不能为空
|
||||
email.code.retry.limit.count=邮箱验证码输入错误{0}次
|
||||
email.code.retry.limit.exceed=邮箱验证码输入错误{0}次,帐户锁定{1}分钟
|
||||
email.code.retry.limit.exceed=邮箱验证码输入错误{0}次,账户锁定{1}分钟
|
||||
xcx.code.not.blank=小程序[code]不能为空
|
||||
social.source.not.blank=第三方登录平台[source]不能为空
|
||||
social.code.not.blank=第三方登录平台[code]不能为空
|
||||
|
||||
Binary file not shown.
@@ -14,7 +14,7 @@
|
||||
</description>
|
||||
|
||||
<properties>
|
||||
<revision>5.5.2</revision>
|
||||
<revision>5.6.0</revision>
|
||||
</properties>
|
||||
|
||||
<dependencyManagement>
|
||||
|
||||
@@ -52,7 +52,7 @@ public interface CacheNames {
|
||||
String SYS_USER_NAME = "sys_user_name#30d";
|
||||
|
||||
/**
|
||||
* 用户名称
|
||||
* 用户昵称
|
||||
*/
|
||||
String SYS_NICKNAME = "sys_nickname#30d";
|
||||
|
||||
|
||||
@@ -23,8 +23,8 @@ public class FlowCopyDTO implements Serializable {
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 用户名称
|
||||
* 用户昵称
|
||||
*/
|
||||
private String userName;
|
||||
private String nickName;
|
||||
|
||||
}
|
||||
|
||||
@@ -48,7 +48,8 @@ public class StartProcessDTO implements Serializable {
|
||||
|
||||
public Map<String, Object> getVariables() {
|
||||
if (variables == null) {
|
||||
return new HashMap<>(16);
|
||||
variables = new HashMap<>(16);
|
||||
return variables;
|
||||
}
|
||||
variables.entrySet().removeIf(entry -> Objects.isNull(entry.getValue()));
|
||||
return variables;
|
||||
|
||||
@@ -61,7 +61,7 @@ public class UserDTO implements Serializable {
|
||||
private String sex;
|
||||
|
||||
/**
|
||||
* 帐号状态(0正常 1停用)
|
||||
* 账号状态(0正常 1停用)
|
||||
*/
|
||||
private String status;
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ public class UserOnlineDTO implements Serializable {
|
||||
private String deptName;
|
||||
|
||||
/**
|
||||
* 用户名称
|
||||
* 用户账号
|
||||
*/
|
||||
private String userName;
|
||||
|
||||
|
||||
@@ -21,18 +21,18 @@ public interface UserService {
|
||||
String selectUserNameById(Long userId);
|
||||
|
||||
/**
|
||||
* 通过用户ID查询用户账户
|
||||
* 通过用户ID查询用户昵称
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @return 用户名称
|
||||
* @return 用户昵称
|
||||
*/
|
||||
String selectNicknameById(Long userId);
|
||||
|
||||
/**
|
||||
* 通过用户ID查询用户账户
|
||||
* 通过用户ID查询用户昵称
|
||||
*
|
||||
* @param userIds 用户ID 多个用逗号隔开
|
||||
* @return 用户名称
|
||||
* @return 用户昵称
|
||||
*/
|
||||
String selectNicknameByIds(String userIds);
|
||||
|
||||
@@ -93,11 +93,11 @@ public interface UserService {
|
||||
List<UserDTO> selectUsersByPostIds(List<Long> postIds);
|
||||
|
||||
/**
|
||||
* 根据用户 ID 列表查询用户名称映射关系
|
||||
* 根据用户 ID 列表查询用户昵称映射关系
|
||||
*
|
||||
* @param userIds 用户 ID 列表
|
||||
* @return Map,其中 key 为用户 ID,value 为对应的用户名称
|
||||
* @return Map,其中 key 为用户 ID,value 为对应的用户昵称
|
||||
*/
|
||||
Map<Long, String> selectUserNamesByIds(List<Long> userIds);
|
||||
Map<Long, String> selectUserNicksByIds(List<Long> userIds);
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.dromara.common.core.utils.ip;
|
||||
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.io.resource.ResourceUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.common.core.exception.ServiceException;
|
||||
@@ -9,7 +8,6 @@ import org.lionsoul.ip2region.service.Config;
|
||||
import org.lionsoul.ip2region.service.Ip2Region;
|
||||
import org.lionsoul.ip2region.xdb.Util;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.time.Duration;
|
||||
|
||||
@@ -31,6 +29,11 @@ public class RegionUtils {
|
||||
// 下载地址:https://gitee.com/lionsoul/ip2region/blob/master/data/ip2region_v6.xdb
|
||||
public static final String DEFAULT_IPV6_XDB_PATH = "ip2region_v6.xdb";
|
||||
|
||||
// 默认缓存切片大小为15MB(仅针对BufferCache全量读取有效,如果你的xdb数据库很大,合理设置该值可以有效提升BufferCache模式下的查询效率,具体可以查看Ip2Region的README)
|
||||
// 注意:设置过大的值可能会申请内存时,因内存不足而导致OOM,请合理设置该值。
|
||||
// README:https://gitee.com/lionsoul/ip2region/tree/master/binding/java
|
||||
public static final int DEFAULT_CACHE_SLICE_BYTES = 1024 * 1024 * 15;
|
||||
|
||||
// 未知地址
|
||||
public static final String UNKNOWN_ADDRESS = "未知";
|
||||
|
||||
@@ -43,20 +46,18 @@ public class RegionUtils {
|
||||
// 注意:Ip2Region 的xdb文件加载策略 CachePolicy 有三种,分别是:BufferCache(全量读取xdb到内存中)、VIndexCache(默认策略,按需读取并缓存)、NoCache(实时读取)
|
||||
// 本项目工具使用的 CachePolicy 为 BufferCache,BufferCache会加载整个xdb文件到内存中,setXdbInputStream 仅支持 BufferCache 策略。
|
||||
// 因为加载整个xdb文件会耗费非常大的内存,如果你不希望加载整个xdb到内存中,更推荐使用 VIndexCache 或 NoCache(即实时读取文件)策略和 setXdbPath/setXdbFile 加载方法(需要注意的一点,setXdbPath 和 setXdbFile 不支持读取ClassPath(即源码和resource目录)中的文件)。
|
||||
// 一般而言,更建议把xdb数据库放到一个指定的文件目录中(即不打包进jar包中),然后使用 NoCache + 配合SearcherPool的并发池读取数据,更方便随时更新xdb数据库
|
||||
// 一般而言,更建议把xdb数据库放到一个指定的文件目录中(即不打包进jar包中),然后使用 VIndexCache + 配合SearcherPool的并发池读取数据,更方便随时更新xdb数据库
|
||||
|
||||
// TODO 2025年12月23日 Ip2Region封装的 InputStream 读取函数 Searcher.loadContentFromInputStream 在Linux环境下会申请过大的byte[]空间而导致OOM,这里先用临时文件的方案解决,等后续 Ip2Region 更新解决方案
|
||||
// 创建临时文件
|
||||
File v4TempXdb = FileUtil.writeFromStream(ResourceUtil.getStream(DEFAULT_IPV4_XDB_PATH), FileUtil.createTempFile());
|
||||
InputStream v4InputStream = ResourceUtil.getStream(DEFAULT_IPV4_XDB_PATH);
|
||||
|
||||
// IPv4配置
|
||||
Config v4Config = Config.custom()
|
||||
.setCachePolicy(Config.BufferCache)
|
||||
.setXdbFile(v4TempXdb)
|
||||
// .setXdbInputStream(ResourceUtil.getStream(DEFAULT_IPV4_XDB_PATH))
|
||||
//.setXdbFile(v4TempXdb)
|
||||
.setXdbInputStream(v4InputStream)
|
||||
//
|
||||
.setCacheSliceBytes(DEFAULT_CACHE_SLICE_BYTES)
|
||||
.asV4();
|
||||
// 删除临时文件
|
||||
v4TempXdb.delete();
|
||||
|
||||
// IPv6配置
|
||||
Config v6Config = null;
|
||||
@@ -64,17 +65,12 @@ public class RegionUtils {
|
||||
if (v6XdbInputStream == null) {
|
||||
log.warn("未加载 IPv6 地址库:未在类路径下找到文件 {}。当前仅启用 IPv4 查询。如需启用 IPv6,请将 ip2region_v6.xdb 放置到 resources 目录", DEFAULT_IPV6_XDB_PATH);
|
||||
} else {
|
||||
// 创建临时文件
|
||||
File v6TempXdb = FileUtil.writeFromStream(ResourceUtil.getStream(DEFAULT_IPV4_XDB_PATH), FileUtil.createTempFile());
|
||||
|
||||
v6Config = Config.custom()
|
||||
.setCachePolicy(Config.BufferCache)
|
||||
.setXdbFile(v6TempXdb)
|
||||
// .setXdbInputStream(v6XdbInputStream)
|
||||
//.setXdbFile(v6TempXdb)
|
||||
.setXdbInputStream(v6XdbInputStream)
|
||||
.setCacheSliceBytes(DEFAULT_CACHE_SLICE_BYTES)
|
||||
.asV6();
|
||||
|
||||
// 删除临时文件
|
||||
v6TempXdb.delete();
|
||||
}
|
||||
|
||||
// 初始化Ip2Region实例
|
||||
@@ -94,9 +90,9 @@ public class RegionUtils {
|
||||
try {
|
||||
String region = ip2Region.search(ipString);
|
||||
if (StringUtils.isBlank(region)) {
|
||||
region = UNKNOWN_ADDRESS;
|
||||
return UNKNOWN_ADDRESS;
|
||||
}
|
||||
return region;
|
||||
return StringUtils.replace(region, "0", UNKNOWN_ADDRESS);
|
||||
} catch (Exception e) {
|
||||
log.error("IP地址离线获取城市异常 {}", ipString);
|
||||
return UNKNOWN_ADDRESS;
|
||||
@@ -113,9 +109,9 @@ public class RegionUtils {
|
||||
try {
|
||||
String region = ip2Region.search(ipBytes);
|
||||
if (StringUtils.isBlank(region)) {
|
||||
region = UNKNOWN_ADDRESS;
|
||||
return UNKNOWN_ADDRESS;
|
||||
}
|
||||
return region;
|
||||
return StringUtils.replace(region, "0", UNKNOWN_ADDRESS);
|
||||
} catch (Exception e) {
|
||||
log.error("IP地址离线获取城市异常 {}", Util.ipToString(ipBytes));
|
||||
return UNKNOWN_ADDRESS;
|
||||
|
||||
@@ -7,6 +7,8 @@ import io.swagger.v3.oas.models.security.SecurityRequirement;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.common.doc.config.properties.SpringDocProperties;
|
||||
import org.dromara.common.doc.core.resolver.JavadocResolver;
|
||||
import org.dromara.common.doc.core.resolver.SaTokenAnnotationMetadataJavadocResolver;
|
||||
import org.dromara.common.doc.handler.OpenApiHandler;
|
||||
import org.springdoc.core.configuration.SpringDocConfiguration;
|
||||
import org.springdoc.core.customizers.OpenApiBuilderCustomizer;
|
||||
@@ -84,8 +86,9 @@ public class SpringDocConfig {
|
||||
SecurityService securityParser,
|
||||
SpringDocConfigProperties springDocConfigProperties, PropertyResolverUtils propertyResolverUtils,
|
||||
Optional<List<OpenApiBuilderCustomizer>> openApiBuilderCustomisers,
|
||||
Optional<List<ServerBaseUrlCustomizer>> serverBaseUrlCustomisers, Optional<JavadocProvider> javadocProvider) {
|
||||
return new OpenApiHandler(openAPI, securityParser, springDocConfigProperties, propertyResolverUtils, openApiBuilderCustomisers, serverBaseUrlCustomisers, javadocProvider);
|
||||
Optional<List<ServerBaseUrlCustomizer>> serverBaseUrlCustomisers, Optional<JavadocProvider> javadocProvider,
|
||||
List<JavadocResolver> javadocResolvers) {
|
||||
return new OpenApiHandler(openAPI, securityParser, springDocConfigProperties, propertyResolverUtils, openApiBuilderCustomisers, serverBaseUrlCustomisers, javadocProvider, javadocResolvers);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -112,6 +115,14 @@ public class SpringDocConfig {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册SaToken JavaDoc权限注解解析器
|
||||
*/
|
||||
@Bean
|
||||
public JavadocResolver saTokenAnnotationJavadocResolver() {
|
||||
return new SaTokenAnnotationMetadataJavadocResolver();
|
||||
}
|
||||
|
||||
/**
|
||||
* 单独使用一个类便于判断 解决springdoc路径拼接重复问题
|
||||
*
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
package org.dromara.common.doc.core.model;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude.Include;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 存储权限框架注解解析后的权限和角色信息
|
||||
*
|
||||
* @author AprilWind
|
||||
*/
|
||||
@Data
|
||||
@JsonInclude(Include.NON_EMPTY)
|
||||
public class SaTokenSecurityMetadata {
|
||||
|
||||
/**
|
||||
* 权限校验信息列表(对应 @SaCheckPermission 注解)
|
||||
*/
|
||||
private List<AuthInfo> permissions = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* 角色校验信息列表(对应 @SaCheckRole 注解)
|
||||
*/
|
||||
private List<AuthInfo> roles = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* 是否忽略校验(对应 @SaIgnore 注解)
|
||||
*/
|
||||
private boolean ignore = false;
|
||||
|
||||
/**
|
||||
* 添加权限信息
|
||||
*
|
||||
* @param values 权限值数组
|
||||
* @param mode 校验模式(AND/OR)
|
||||
* @param type 权限类型
|
||||
* @param orRoles 或角色数组
|
||||
*/
|
||||
public void addPermission(String[] values, String mode, String type, String[] orRoles) {
|
||||
if (values != null && values.length > 0) {
|
||||
AuthInfo authInfo = new AuthInfo();
|
||||
authInfo.setValues(values);
|
||||
authInfo.setMode(mode);
|
||||
authInfo.setType(type);
|
||||
if (orRoles != null && orRoles.length > 0) {
|
||||
authInfo.setOrValues(orRoles);
|
||||
authInfo.setOrType("role");
|
||||
}
|
||||
this.permissions.add(authInfo);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加角色信息
|
||||
*
|
||||
* @param values 角色值数组
|
||||
* @param mode 校验模式(AND/OR)
|
||||
* @param type 角色类型
|
||||
*/
|
||||
public void addRole(String[] values, String mode, String type) {
|
||||
if (values != null && values.length > 0) {
|
||||
AuthInfo authInfo = new AuthInfo();
|
||||
authInfo.setValues(values);
|
||||
authInfo.setMode(mode);
|
||||
authInfo.setType(type);
|
||||
this.roles.add(authInfo);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成 Markdown 结构的权限说明
|
||||
*
|
||||
* @return Markdown 文本
|
||||
*/
|
||||
public String toMarkdownString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("<br><h3>访问权限</h3><br>");
|
||||
|
||||
if (ignore) {
|
||||
sb.append("> **权限策略**:忽略权限检查<br>");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
if (!ignore && permissions.isEmpty() && roles.isEmpty()){
|
||||
sb.append("> **权限策略**:需要登录<br><br>");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
if (!permissions.isEmpty()) {
|
||||
sb.append("**权限校验:**<br><br>");
|
||||
|
||||
permissions.forEach(p -> {
|
||||
String permTags = Arrays.stream(p.getValues())
|
||||
.map(v -> "`" + v + "`")
|
||||
.collect(Collectors.joining(p.getModeSymbol()));
|
||||
|
||||
sb.append("- ").append(permTags).append("<br>");
|
||||
|
||||
if (p.getOrValues() != null && p.getOrValues().length > 0) {
|
||||
String orTags = Arrays.stream(p.getOrValues())
|
||||
.map(v -> "`" + v + "`")
|
||||
.collect(Collectors.joining(p.getModeSymbol()));
|
||||
sb.append(" - 或角色:").append(orTags).append("<br>");
|
||||
}
|
||||
});
|
||||
|
||||
sb.append("<br>");
|
||||
}
|
||||
|
||||
if (!roles.isEmpty()) {
|
||||
sb.append("**角色校验:**<br><br>");
|
||||
|
||||
roles.forEach(r -> {
|
||||
|
||||
String roleTags = Arrays.stream(r.getValues())
|
||||
.map(v -> "`" + v + "`")
|
||||
.collect(Collectors.joining(r.getModeSymbol()));
|
||||
|
||||
sb.append("- ").append(roleTags).append("<br>");
|
||||
});
|
||||
}
|
||||
|
||||
return sb.toString().trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* 认证信息
|
||||
*/
|
||||
@Data
|
||||
@JsonInclude(Include.NON_EMPTY)
|
||||
public static class AuthInfo {
|
||||
|
||||
/**
|
||||
* 权限或角色值数组
|
||||
*/
|
||||
private String[] values;
|
||||
|
||||
/**
|
||||
* 校验模式(AND/OR)
|
||||
*/
|
||||
private String mode;
|
||||
|
||||
/**
|
||||
* 类型说明
|
||||
*/
|
||||
private String type;
|
||||
|
||||
/**
|
||||
* 或权限/角色值数组(用于权限校验时的或角色校验)
|
||||
*/
|
||||
private String[] orValues;
|
||||
|
||||
/**
|
||||
* 或值的类型(role/permission)
|
||||
*/
|
||||
private String orType;
|
||||
|
||||
/**
|
||||
* 重写mode的获取方法,返回符号而非文字
|
||||
* @return AND→&,OR→|,默认→&
|
||||
*/
|
||||
public String getModeSymbol() {
|
||||
if (mode == null) {
|
||||
return " & "; // 默认AND,返回&
|
||||
}
|
||||
return "AND".equalsIgnoreCase(mode) ? " & " : " | ";
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
package org.dromara.common.doc.core.resolver;
|
||||
|
||||
import cn.hutool.core.annotation.AnnotationUtil;
|
||||
import cn.hutool.core.util.ClassLoaderUtil;
|
||||
import io.swagger.v3.oas.models.Operation;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* 抽象元数据 Javadoc 解析器
|
||||
*
|
||||
* @param <M> 元数据类型
|
||||
* @author 秋辞未寒
|
||||
*/
|
||||
public abstract class AbstractMetadataJavadocResolver<M> implements JavadocResolver {
|
||||
|
||||
public static final int HIGHEST_PRECEDENCE = Integer.MIN_VALUE;
|
||||
public static final int LOWEST_PRECEDENCE = Integer.MAX_VALUE;
|
||||
|
||||
private final Supplier<M> metadataProvider;
|
||||
|
||||
private final int order;
|
||||
|
||||
public AbstractMetadataJavadocResolver(Supplier<M> metadataProvider) {
|
||||
this(metadataProvider, LOWEST_PRECEDENCE);
|
||||
}
|
||||
|
||||
public AbstractMetadataJavadocResolver(Supplier<M> metadataProvider, int order) {
|
||||
this.metadataProvider = metadataProvider;
|
||||
this.order = order;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return order;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String resolve(HandlerMethod handlerMethod, Operation operation) {
|
||||
return resolve(handlerMethod, operation, metadataProvider.get());
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行解析并返回解析到的 Javadoc 内容
|
||||
* @param handlerMethod 处理器方法
|
||||
* @param operation Swagger Operation实例
|
||||
* @param metadata 元信息
|
||||
* @return 解析到的 Javadoc 内容
|
||||
*/
|
||||
public abstract String resolve(HandlerMethod handlerMethod, Operation operation, M metadata);
|
||||
|
||||
/**
|
||||
* 检查处理器方法所属的类上是否存在注解
|
||||
* @param handlerMethod 处理器方法
|
||||
* @param annotationClass 注解类
|
||||
* @return 是否存在注解
|
||||
*/
|
||||
public boolean hasClassAnnotation(HandlerMethod handlerMethod,Class<? extends Annotation> annotationClass){
|
||||
return AnnotationUtil.hasAnnotation(handlerMethod.getBeanType(), annotationClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查处理器方法所属的类上是否存在注解
|
||||
* @param handlerMethod 处理器方法
|
||||
* @param annotationTypeName 注解类名称
|
||||
* @return 是否存在注解
|
||||
*/
|
||||
public boolean hasClassAnnotation(HandlerMethod handlerMethod, String annotationTypeName){
|
||||
return AnnotationUtil.hasAnnotation(handlerMethod.getBeanType(), annotationTypeName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查处理器方法上是否存在注解
|
||||
* @param handlerMethod 处理器方法
|
||||
* @param annotationClass 注解类
|
||||
* @return 是否存在注解
|
||||
*/
|
||||
public boolean hasMethodAnnotation(HandlerMethod handlerMethod,Class<? extends Annotation> annotationClass){
|
||||
return AnnotationUtil.hasAnnotation(handlerMethod.getMethod(), annotationClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查处理器方法上是否存在注解
|
||||
* @param handlerMethod 处理器方法
|
||||
* @param annotationTypeName 注解类名称
|
||||
* @return 是否存在注解
|
||||
*/
|
||||
public boolean hasMethodAnnotation(HandlerMethod handlerMethod, String annotationTypeName){
|
||||
return AnnotationUtil.hasAnnotation(handlerMethod.getMethod(), annotationTypeName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查处理器方法上是否存在注解
|
||||
* @param handlerMethod 处理器方法
|
||||
* @param annotationClass 注解类
|
||||
* @return 是否存在注解
|
||||
*/
|
||||
public boolean hasAnnotation(HandlerMethod handlerMethod,Class<? extends Annotation> annotationClass){
|
||||
return this.hasClassAnnotation(handlerMethod, annotationClass) || this.hasMethodAnnotation(handlerMethod, annotationClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查处理器方法上是否存在注解
|
||||
* @param handlerMethod 处理器方法
|
||||
* @param annotationTypeName 注解类名称
|
||||
* @return 是否存在注解
|
||||
*/
|
||||
public boolean hasAnnotation(HandlerMethod handlerMethod, String annotationTypeName){
|
||||
return this.hasClassAnnotation(handlerMethod, annotationTypeName) || this.hasMethodAnnotation(handlerMethod, annotationTypeName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取处理器方法所属类上的注解的值
|
||||
* @param handlerMethod 处理器方法
|
||||
* @param annotationClass 注解类
|
||||
* @return 注解的值
|
||||
*/
|
||||
public Map<String, Object> getClassAnnotationValueMap(HandlerMethod handlerMethod, Class<? extends Annotation> annotationClass) {
|
||||
return AnnotationUtil.getAnnotationValueMap(handlerMethod.getBeanType(), annotationClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取处理器方法所属类上的注解的值
|
||||
* @param handlerMethod 处理器方法
|
||||
* @param annotationClassName 注解类名称
|
||||
* @return 注解的值
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public Map<String, Object> getClassAnnotationValueMap(HandlerMethod handlerMethod, String annotationClassName) {
|
||||
Class<? extends Annotation> annotationClass = (Class<? extends Annotation>) ClassLoaderUtil.loadClass(annotationClassName, false);
|
||||
return AnnotationUtil.getAnnotationValueMap(handlerMethod.getBeanType(), annotationClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取处理器方法上的注解的值
|
||||
* @param handlerMethod 处理器方法
|
||||
* @param annotationClass 注解类
|
||||
* @return 注解的值
|
||||
*/
|
||||
public Map<String, Object> getMethodAnnotationValueMap(HandlerMethod handlerMethod, Class<? extends Annotation> annotationClass) {
|
||||
return AnnotationUtil.getAnnotationValueMap(handlerMethod.getMethod(), annotationClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取处理器方法所属类上的注解的值
|
||||
* @param handlerMethod 处理器方法
|
||||
* @param annotationClassName 注解类名称
|
||||
* @return 注解的值
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public Map<String, Object> getMethodAnnotationValueMap(HandlerMethod handlerMethod, String annotationClassName) {
|
||||
Class<? extends Annotation> annotationClass = (Class<? extends Annotation>) ClassLoaderUtil.loadClass(annotationClassName, false);
|
||||
return AnnotationUtil.getAnnotationValueMap(handlerMethod.getMethod(), annotationClass);
|
||||
}
|
||||
|
||||
private Map<String, Object> getAnnotationValueMap(AnnotatedElement annotatedElement, Class<? extends Annotation> annotationClass) {
|
||||
return AnnotationUtil.getAnnotationValueMap(annotatedElement, annotationClass);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package org.dromara.common.doc.core.resolver;
|
||||
|
||||
import io.swagger.v3.oas.models.Operation;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
|
||||
/**
|
||||
* Javadoc解析器接口
|
||||
*
|
||||
* @author echo
|
||||
* @author 秋辞未寒
|
||||
*/
|
||||
public interface JavadocResolver extends Comparable<JavadocResolver>, Ordered {
|
||||
|
||||
/**
|
||||
* 检查解析器是否支持解析 HandlerMethod
|
||||
* @param handlerMethod 处理器方法
|
||||
* @return 是否支持解析
|
||||
*/
|
||||
boolean supports(HandlerMethod handlerMethod);
|
||||
|
||||
/**
|
||||
* 执行解析并返回解析到的 Javadoc 内容
|
||||
* @param handlerMethod 处理器方法
|
||||
* @param operation Swagger Operation实例
|
||||
* @return 解析到的 Javadoc 内容
|
||||
*/
|
||||
String resolve(HandlerMethod handlerMethod, Operation operation);
|
||||
|
||||
/**
|
||||
* 获取解析器优先级
|
||||
*/
|
||||
default int getOrder() {
|
||||
return Ordered.LOWEST_PRECEDENCE;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取解析器的名称
|
||||
*
|
||||
* @return 解析器名称
|
||||
*/
|
||||
default String getName() {
|
||||
return this.getClass().getSimpleName();
|
||||
}
|
||||
|
||||
@Override
|
||||
default int compareTo(@NotNull JavadocResolver o) {
|
||||
return Integer.compare(getOrder(), o.getOrder());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
package org.dromara.common.doc.core.resolver;
|
||||
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.util.ClassLoaderUtil;
|
||||
import io.swagger.v3.oas.models.Operation;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.common.doc.core.model.SaTokenSecurityMetadata;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* 基于JavaDoc的SaToken权限解析器
|
||||
*
|
||||
* @author echo
|
||||
* @author 秋辞未寒
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@Slf4j
|
||||
public class SaTokenAnnotationMetadataJavadocResolver extends AbstractMetadataJavadocResolver<SaTokenSecurityMetadata> {
|
||||
|
||||
/**
|
||||
* 默认元数据提供者,每次解析都会创建一个新的元数据对象
|
||||
*/
|
||||
public static final Supplier<SaTokenSecurityMetadata> DEFAULT_METADATA_PROVIDER = SaTokenSecurityMetadata::new;
|
||||
|
||||
private static final String BASE_CLASS_NAME = "cn.dev33.satoken.annotation";
|
||||
private static final String SA_CHECK_ROLE_CLASS_NAME = BASE_CLASS_NAME + ".SaCheckRole";
|
||||
private static final String SA_CHECK_PERMISSION_CLASS_NAME = BASE_CLASS_NAME + ".SaCheckPermission";
|
||||
private static final String SA_IGNORE_CLASS_NAME = BASE_CLASS_NAME + ".SaIgnore";
|
||||
private static final String SA_CHECK_LOGIN_NAME = BASE_CLASS_NAME + ".SaCheckLogin";
|
||||
|
||||
private static final Class<? extends Annotation> SA_CHECK_ROLE_CLASS;
|
||||
private static final Class<? extends Annotation> SA_CHECK_PERMISSION_CLASS;
|
||||
private static final Class<? extends Annotation> SA_IGNORE_CLASS;
|
||||
private static final Class<? extends Annotation> SA_CHECK_LOGIN_CLASS;
|
||||
|
||||
|
||||
static {
|
||||
// 通过类加载器去加载注解类Class实例
|
||||
SA_CHECK_ROLE_CLASS = (Class<? extends Annotation>) ClassLoaderUtil.loadClass(SA_CHECK_ROLE_CLASS_NAME, false);
|
||||
SA_CHECK_PERMISSION_CLASS = (Class<? extends Annotation>) ClassLoaderUtil.loadClass(SA_CHECK_PERMISSION_CLASS_NAME, false);
|
||||
SA_IGNORE_CLASS = (Class<? extends Annotation>) ClassLoaderUtil.loadClass(SA_IGNORE_CLASS_NAME, false);
|
||||
SA_CHECK_LOGIN_CLASS = (Class<? extends Annotation>) ClassLoaderUtil.loadClass(SA_CHECK_LOGIN_NAME, false);
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("SaTokenAnnotationJavadocResolver init success, load annotation class: {}", List.of(SA_CHECK_ROLE_CLASS, SA_CHECK_PERMISSION_CLASS, SA_IGNORE_CLASS, SA_CHECK_LOGIN_CLASS));
|
||||
}
|
||||
}
|
||||
|
||||
public SaTokenAnnotationMetadataJavadocResolver() {
|
||||
this(DEFAULT_METADATA_PROVIDER);
|
||||
}
|
||||
|
||||
public SaTokenAnnotationMetadataJavadocResolver(Supplier<SaTokenSecurityMetadata> metadataProvider) {
|
||||
super(metadataProvider);
|
||||
}
|
||||
|
||||
public SaTokenAnnotationMetadataJavadocResolver(int order) {
|
||||
this(DEFAULT_METADATA_PROVIDER,order);
|
||||
}
|
||||
|
||||
public SaTokenAnnotationMetadataJavadocResolver(Supplier<SaTokenSecurityMetadata> metadataProvider, int order) {
|
||||
super(metadataProvider,order);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(HandlerMethod handlerMethod) {
|
||||
return hasAnnotation(handlerMethod, SA_CHECK_ROLE_CLASS) || hasAnnotation(handlerMethod, SA_CHECK_PERMISSION_CLASS) || hasAnnotation(handlerMethod, SA_IGNORE_CLASS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String resolve(HandlerMethod handlerMethod, Operation operation, SaTokenSecurityMetadata metadata) {
|
||||
// 检查是否忽略校验
|
||||
if(hasAnnotation(handlerMethod, SA_IGNORE_CLASS_NAME)){
|
||||
metadata.setIgnore(true);
|
||||
return metadata.toMarkdownString();
|
||||
}
|
||||
|
||||
// 解析权限校验
|
||||
resolvePermissionCheck(handlerMethod, metadata);
|
||||
|
||||
// 解析角色校验
|
||||
resolveRoleCheck(handlerMethod, metadata);
|
||||
return metadata.toMarkdownString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析权限校验
|
||||
*/
|
||||
private void resolvePermissionCheck(HandlerMethod handlerMethod, SaTokenSecurityMetadata metadata) {
|
||||
// 解析获取方法上的注解角色信息
|
||||
if (hasMethodAnnotation(handlerMethod, SA_CHECK_PERMISSION_CLASS_NAME)) {
|
||||
Map<String, Object> annotationValueMap = getMethodAnnotationValueMap(handlerMethod, SA_CHECK_PERMISSION_CLASS);
|
||||
resolvePermissionAnnotation(metadata, annotationValueMap);
|
||||
}
|
||||
// 解析获取类上的注解角色信息
|
||||
if (hasClassAnnotation(handlerMethod, SA_CHECK_PERMISSION_CLASS_NAME)) {
|
||||
Map<String, Object> annotationValueMap = getClassAnnotationValueMap(handlerMethod, SA_CHECK_PERMISSION_CLASS);
|
||||
resolvePermissionAnnotation(metadata, annotationValueMap);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析权限注解
|
||||
*/
|
||||
private void resolvePermissionAnnotation(SaTokenSecurityMetadata metadata, Map<String, Object> annotationValueMap) {
|
||||
try {
|
||||
// 反射获取注解属性
|
||||
Object value = annotationValueMap.get( "value");
|
||||
Object mode = annotationValueMap.get( "mode");
|
||||
Object type = annotationValueMap.get( "type");
|
||||
Object orRole = annotationValueMap.get( "orRole");
|
||||
|
||||
String[] values = Convert.toStrArray(value);
|
||||
String modeStr = mode != null ? mode.toString() : "AND";
|
||||
String typeStr = type != null ? type.toString() : "";
|
||||
String[] orRoles = Convert.toStrArray(orRole);
|
||||
|
||||
metadata.addPermission(values, modeStr, typeStr, orRoles);
|
||||
} catch (Exception ignore) {
|
||||
// 忽略解析错误
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析角色校验
|
||||
*/
|
||||
private void resolveRoleCheck(HandlerMethod handlerMethod, SaTokenSecurityMetadata metadata) {
|
||||
// 解析获取方法上的注解角色信息
|
||||
if (hasMethodAnnotation(handlerMethod, SA_CHECK_ROLE_CLASS_NAME)) {
|
||||
Map<String, Object> annotationValueMap = getMethodAnnotationValueMap(handlerMethod, SA_CHECK_ROLE_CLASS);
|
||||
resolveRoleAnnotation(metadata, annotationValueMap);
|
||||
}
|
||||
// 解析获取类上的注解角色信息
|
||||
if (hasClassAnnotation(handlerMethod, SA_CHECK_ROLE_CLASS_NAME)) {
|
||||
Map<String, Object> annotationValueMap = getClassAnnotationValueMap(handlerMethod, SA_CHECK_ROLE_CLASS);
|
||||
resolveRoleAnnotation(metadata, annotationValueMap);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析角色注解
|
||||
*/
|
||||
private void resolveRoleAnnotation(SaTokenSecurityMetadata metadata, Map<String, Object> annotationValueMap) {
|
||||
try {
|
||||
// 反射获取注解属性
|
||||
Object value = annotationValueMap.get("value");
|
||||
Object mode = annotationValueMap.get("mode");
|
||||
Object type = annotationValueMap.get("type");
|
||||
|
||||
String[] values = Convert.toStrArray(value);
|
||||
String modeStr = mode != null ? mode.toString() : "AND";
|
||||
String typeStr = type != null ? type.toString() : "";
|
||||
|
||||
metadata.addRole(values, modeStr, typeStr);
|
||||
} catch (Exception ignore) {
|
||||
// 忽略解析错误
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import io.swagger.v3.oas.models.tags.Tag;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.dromara.common.core.utils.StreamUtils;
|
||||
import org.dromara.common.doc.core.resolver.JavadocResolver;
|
||||
import org.springdoc.core.customizers.OpenApiBuilderCustomizer;
|
||||
import org.springdoc.core.customizers.ServerBaseUrlCustomizer;
|
||||
import org.springdoc.core.properties.SpringDocConfigProperties;
|
||||
@@ -83,6 +84,11 @@ public class OpenApiHandler extends OpenAPIService {
|
||||
*/
|
||||
private final PropertyResolverUtils propertyResolverUtils;
|
||||
|
||||
/**
|
||||
* Javadoc解析器接口
|
||||
*/
|
||||
private final List<JavadocResolver> javadocResolvers;
|
||||
|
||||
/**
|
||||
* The javadoc provider.
|
||||
*/
|
||||
@@ -123,7 +129,8 @@ public class OpenApiHandler extends OpenAPIService {
|
||||
SpringDocConfigProperties springDocConfigProperties, PropertyResolverUtils propertyResolverUtils,
|
||||
Optional<List<OpenApiBuilderCustomizer>> openApiBuilderCustomizers,
|
||||
Optional<List<ServerBaseUrlCustomizer>> serverBaseUrlCustomizers,
|
||||
Optional<JavadocProvider> javadocProvider) {
|
||||
Optional<JavadocProvider> javadocProvider,
|
||||
List<JavadocResolver> javadocResolvers) {
|
||||
super(openAPI, securityParser, springDocConfigProperties, propertyResolverUtils, openApiBuilderCustomizers, serverBaseUrlCustomizers, javadocProvider);
|
||||
if (openAPI.isPresent()) {
|
||||
this.openAPI = openAPI.get();
|
||||
@@ -140,6 +147,7 @@ public class OpenApiHandler extends OpenAPIService {
|
||||
this.openApiBuilderCustomisers = openApiBuilderCustomizers;
|
||||
this.serverBaseUrlCustomizers = serverBaseUrlCustomizers;
|
||||
this.javadocProvider = javadocProvider;
|
||||
this.javadocResolvers = javadocResolvers == null ? new ArrayList<>() : javadocResolvers;
|
||||
if (springDocConfigProperties.isUseFqn())
|
||||
TypeNameResolver.std.setUseFqn(true);
|
||||
}
|
||||
@@ -220,6 +228,22 @@ public class OpenApiHandler extends OpenAPIService {
|
||||
securityParser.buildSecurityRequirement(securityRequirements, operation);
|
||||
}
|
||||
|
||||
if (javadocProvider.isPresent()) {
|
||||
String description = javadocProvider.get().getMethodJavadocDescription(handlerMethod.getMethod());
|
||||
String summary = javadocProvider.get().getFirstSentence(description);
|
||||
if (StringUtils.isNotBlank(description)){
|
||||
operation.setSummary(summary);
|
||||
}
|
||||
// 调用解析器提取JavaDoc中的权限信息
|
||||
if (javadocResolvers != null && !javadocResolvers.isEmpty()) {
|
||||
for (JavadocResolver resolver : javadocResolvers) {
|
||||
String desc = resolver.resolve(handlerMethod, operation);
|
||||
description = description + desc;
|
||||
}
|
||||
operation.setDescription(description);
|
||||
}
|
||||
}
|
||||
|
||||
return operation;
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcprov-jdk15to18</artifactId>
|
||||
<artifactId>bcpkix-jdk18on</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
|
||||
@@ -6,10 +6,7 @@ import cn.hutool.core.util.ObjectUtil;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.ibatis.executor.parameter.ParameterHandler;
|
||||
import org.apache.ibatis.plugin.Interceptor;
|
||||
import org.apache.ibatis.plugin.Intercepts;
|
||||
import org.apache.ibatis.plugin.Invocation;
|
||||
import org.apache.ibatis.plugin.Signature;
|
||||
import org.apache.ibatis.plugin.*;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.common.encrypt.annotation.EncryptField;
|
||||
import org.dromara.common.encrypt.core.EncryptContext;
|
||||
@@ -42,19 +39,19 @@ public class MybatisEncryptInterceptor implements Interceptor {
|
||||
|
||||
@Override
|
||||
public Object intercept(Invocation invocation) throws Throwable {
|
||||
return invocation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object plugin(Object target) {
|
||||
Object target = invocation.getTarget();
|
||||
if (target instanceof ParameterHandler parameterHandler) {
|
||||
// 进行加密操作
|
||||
Object parameterObject = parameterHandler.getParameterObject();
|
||||
if (ObjectUtil.isNotNull(parameterObject) && !(parameterObject instanceof String)) {
|
||||
this.encryptHandler(parameterObject);
|
||||
}
|
||||
}
|
||||
return target;
|
||||
return invocation.proceed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object plugin(Object target) {
|
||||
return Plugin.wrap(target, this);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -64,8 +64,10 @@ public class CellMergeHandler {
|
||||
// 当前行数据字段值
|
||||
Object currentRowObjFieldVal = ReflectUtils.invokeGetter(currentRowObj, field.getName());
|
||||
|
||||
// 空值跳过不处理
|
||||
if (currentRowObjFieldVal == null || "".equals(currentRowObjFieldVal)) {
|
||||
// 空值视为合并中断,需要先收口上一段合并区间
|
||||
if (isBlankCell(currentRowObjFieldVal)) {
|
||||
appendMergeResult(result, rowRepeatCellMap.get(field), i - 1, colNum);
|
||||
rowRepeatCellMap.remove(field);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -78,7 +80,6 @@ public class CellMergeHandler {
|
||||
// 获取 单元格合并Map 中字段值
|
||||
RepeatCell repeatCell = rowRepeatCellMap.get(field);
|
||||
Object cellValue = repeatCell.value();
|
||||
int current = repeatCell.current();
|
||||
|
||||
// 检查是否满足合并条件
|
||||
// currentRowObj 当前行数据
|
||||
@@ -86,33 +87,14 @@ public class CellMergeHandler {
|
||||
// cellMerge 当前行字段合并注解
|
||||
boolean merge = isMerge(currentRowObj, rows.get(i - 1), cellMerge);
|
||||
|
||||
// 是否添加到结果集
|
||||
boolean isAddResult = false;
|
||||
// 最新行
|
||||
int lastRow = i + rowIndex - 1;
|
||||
|
||||
// 如果当前行字段值和缓存中的字段值不相等,或不满足合并条件,则替换
|
||||
if (!currentRowObjFieldVal.equals(cellValue) || !merge) {
|
||||
appendMergeResult(result, repeatCell, i - 1, colNum);
|
||||
rowRepeatCellMap.put(field, RepeatCell.of(currentRowObjFieldVal, i));
|
||||
isAddResult = true;
|
||||
}
|
||||
|
||||
// 如果最后一行不能合并,检查之前的数据是否需要合并;如果最后一行可以合并,则直接合并到最后
|
||||
if (i == rows.size() - 1) {
|
||||
isAddResult = true;
|
||||
if (i > current) {
|
||||
lastRow = i + rowIndex;
|
||||
}
|
||||
}
|
||||
|
||||
if (isAddResult && i > current) {
|
||||
//如果是同一行,则跳过合并
|
||||
if (current + rowIndex == lastRow) {
|
||||
continue;
|
||||
}
|
||||
result.add(new CellRangeAddress(current + rowIndex, lastRow, colNum, colNum));
|
||||
}
|
||||
}
|
||||
appendMergeResult(result, rowRepeatCellMap.get(field), rows.size() - 1, colNum);
|
||||
rowRepeatCellMap.remove(field);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -167,6 +149,17 @@ public class CellMergeHandler {
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean isBlankCell(Object value) {
|
||||
return value == null || StrUtil.isBlankIfStr(value);
|
||||
}
|
||||
|
||||
private void appendMergeResult(List<CellRangeAddress> result, RepeatCell repeatCell, int endIndex, int colNum) {
|
||||
if (repeatCell == null || endIndex <= repeatCell.current()) {
|
||||
return;
|
||||
}
|
||||
result.add(new CellRangeAddress(repeatCell.current() + rowIndex, endIndex + rowIndex, colNum, colNum));
|
||||
}
|
||||
|
||||
/**
|
||||
* 单元格合并
|
||||
*/
|
||||
|
||||
@@ -192,7 +192,7 @@ public class MailUtils {
|
||||
/**
|
||||
* 发送邮件给多人
|
||||
*
|
||||
* @param mailAccount 邮件帐户信息
|
||||
* @param mailAccount 邮件账户信息
|
||||
* @param tos 收件人列表
|
||||
* @param subject 标题
|
||||
* @param content 正文
|
||||
@@ -207,7 +207,7 @@ public class MailUtils {
|
||||
/**
|
||||
* 发送邮件给多人
|
||||
*
|
||||
* @param mailAccount 邮件帐户信息
|
||||
* @param mailAccount 邮件账户信息
|
||||
* @param tos 收件人列表
|
||||
* @param ccs 抄送人列表,可以为null或空
|
||||
* @param bccs 密送人列表,可以为null或空
|
||||
@@ -343,7 +343,7 @@ public class MailUtils {
|
||||
/**
|
||||
* 发送邮件给多人
|
||||
*
|
||||
* @param mailAccount 邮件帐户信息
|
||||
* @param mailAccount 邮件账户信息
|
||||
* @param tos 收件人列表
|
||||
* @param subject 标题
|
||||
* @param content 正文
|
||||
@@ -360,7 +360,7 @@ public class MailUtils {
|
||||
/**
|
||||
* 发送邮件给多人
|
||||
*
|
||||
* @param mailAccount 邮件帐户信息
|
||||
* @param mailAccount 邮件账户信息
|
||||
* @param tos 收件人列表
|
||||
* @param ccs 抄送人列表,可以为null或空
|
||||
* @param bccs 密送人列表,可以为null或空
|
||||
@@ -400,7 +400,7 @@ public class MailUtils {
|
||||
/**
|
||||
* 发送邮件给多人
|
||||
*
|
||||
* @param mailAccount 邮件帐户信息
|
||||
* @param mailAccount 邮件账户信息
|
||||
* @param useGlobalSession 是否全局共享Session
|
||||
* @param tos 收件人列表
|
||||
* @param ccs 抄送人列表,可以为null或空
|
||||
|
||||
@@ -23,7 +23,7 @@ import java.util.function.Supplier;
|
||||
* @version 3.5.0
|
||||
*/
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
@SuppressWarnings("unchecked cast")
|
||||
@SuppressWarnings("unchecked")
|
||||
public class DataPermissionHelper {
|
||||
|
||||
private static final String DATA_PERMISSION_KEY = "data:permission";
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
<groupId>software.amazon.awssdk</groupId>
|
||||
<artifactId>s3</artifactId>
|
||||
<exclusions>
|
||||
<!-- 将基于 CRT 的 HTTP 客户端从类路径中移除 -->
|
||||
<!-- 东西 30M 特别大的 jar 包 性能跟 Netty 差不多 有需要可以自行替换使用 -->
|
||||
<exclusion>
|
||||
<groupId>software.amazon.awssdk</groupId>
|
||||
<artifactId>aws-crt-client</artifactId>
|
||||
@@ -49,13 +49,13 @@
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<!-- 将基于 Netty 的 HTTP 客户端从类路径中移除 -->
|
||||
<!-- 适用于 Netty 的客户端 -->
|
||||
<dependency>
|
||||
<groupId>software.amazon.awssdk</groupId>
|
||||
<artifactId>netty-nio-client</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 基于 AWS CRT 的 S3 客户端的性能增强的 S3 传输管理器 -->
|
||||
<!-- 客户端的性能增强传输管理器 -->
|
||||
<dependency>
|
||||
<groupId>software.amazon.awssdk</groupId>
|
||||
<artifactId>s3-transfer-manager</artifactId>
|
||||
|
||||
@@ -141,7 +141,8 @@ public class OssClient {
|
||||
try {
|
||||
// 构建上传请求对象
|
||||
FileUpload fileUpload = transferManager.uploadFile(
|
||||
x -> x.putObjectRequest(
|
||||
x -> {
|
||||
x.source(filePath).putObjectRequest(
|
||||
y -> y.bucket(properties.getBucketName())
|
||||
.key(key)
|
||||
.contentMD5(StringUtils.isNotEmpty(md5Digest) ? md5Digest : null)
|
||||
@@ -149,10 +150,13 @@ public class OssClient {
|
||||
// 用于设置对象的访问控制列表(ACL)。不同云厂商对ACL的支持和实现方式有所不同,
|
||||
// 因此根据具体的云服务提供商,你可能需要进行不同的配置(自行开启,阿里云有acl权限配置,腾讯云没有acl权限配置)
|
||||
//.acl(getAccessPolicy().getObjectCannedACL())
|
||||
.build())
|
||||
.addTransferListener(LoggingTransferListener.create())
|
||||
.source(filePath).build());
|
||||
|
||||
.build()
|
||||
);
|
||||
if (log.isDebugEnabled()) {
|
||||
x.addTransferListener(LoggingTransferListener.create());
|
||||
}
|
||||
}
|
||||
);
|
||||
// 等待上传完成并获取上传结果
|
||||
CompletedFileUpload uploadResult = fileUpload.completionFuture().join();
|
||||
String eTag = uploadResult.response().eTag();
|
||||
@@ -192,16 +196,21 @@ public class OssClient {
|
||||
|
||||
// 使用 transferManager 进行上传
|
||||
Upload upload = transferManager.upload(
|
||||
x -> x.requestBody(body).addTransferListener(LoggingTransferListener.create())
|
||||
.putObjectRequest(
|
||||
x -> {
|
||||
x.requestBody(body).putObjectRequest(
|
||||
y -> y.bucket(properties.getBucketName())
|
||||
.key(key)
|
||||
.contentType(contentType)
|
||||
// 用于设置对象的访问控制列表(ACL)。不同云厂商对ACL的支持和实现方式有所不同,
|
||||
// 因此根据具体的云服务提供商,你可能需要进行不同的配置(自行开启,阿里云有acl权限配置,腾讯云没有acl权限配置)
|
||||
//.acl(getAccessPolicy().getObjectCannedACL())
|
||||
.build())
|
||||
.build());
|
||||
.build()
|
||||
);
|
||||
if (log.isDebugEnabled()) {
|
||||
x.addTransferListener(LoggingTransferListener.create());
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// 将输入流写入请求体
|
||||
body.writeInputStream(inputStream);
|
||||
@@ -229,13 +238,17 @@ public class OssClient {
|
||||
Path tempFilePath = FileUtils.createTempFile().toPath();
|
||||
// 使用 S3TransferManager 下载文件
|
||||
FileDownload downloadFile = transferManager.downloadFile(
|
||||
x -> x.getObjectRequest(
|
||||
x -> {
|
||||
x.destination(tempFilePath).getObjectRequest(
|
||||
y -> y.bucket(properties.getBucketName())
|
||||
.key(removeBaseUrl(path))
|
||||
.build())
|
||||
.addTransferListener(LoggingTransferListener.create())
|
||||
.destination(tempFilePath)
|
||||
.build());
|
||||
.build()
|
||||
);
|
||||
if (log.isDebugEnabled()) {
|
||||
x.addTransferListener(LoggingTransferListener.create());
|
||||
}
|
||||
}
|
||||
);
|
||||
// 等待文件下载操作完成
|
||||
downloadFile.completionFuture().join();
|
||||
return tempFilePath;
|
||||
@@ -244,8 +257,8 @@ public class OssClient {
|
||||
/**
|
||||
* 下载文件从 Amazon S3 到 输出流
|
||||
*
|
||||
* @param key 文件在 Amazon S3 中的对象键
|
||||
* @param out 输出流
|
||||
* @param key 文件在 Amazon S3 中的对象键
|
||||
* @param out 输出流
|
||||
* @param consumer 自定义处理逻辑
|
||||
* @throws OssException 如果下载失败,抛出自定义异常
|
||||
*/
|
||||
@@ -260,26 +273,24 @@ public class OssClient {
|
||||
/**
|
||||
* 下载文件从 Amazon S3 到 输出流
|
||||
*
|
||||
* @param key 文件在 Amazon S3 中的对象键
|
||||
* @param key 文件在 Amazon S3 中的对象键
|
||||
* @param contentLengthConsumer 文件大小消费者函数
|
||||
* @return 写出订阅器
|
||||
* @throws OssException 如果下载失败,抛出自定义异常
|
||||
*/
|
||||
public WriteOutSubscriber<OutputStream> download(String key, Consumer<Long> contentLengthConsumer) {
|
||||
try {
|
||||
// 构建下载请求
|
||||
DownloadRequest<ResponsePublisher<GetObjectResponse>> publisherDownloadRequest = DownloadRequest.builder()
|
||||
// 文件对象
|
||||
.getObjectRequest(y -> y.bucket(properties.getBucketName())
|
||||
.key(key)
|
||||
.build())
|
||||
.addTransferListener(LoggingTransferListener.create())
|
||||
DownloadRequest.TypedBuilder<ResponsePublisher<GetObjectResponse>> typedBuilder = DownloadRequest.builder()
|
||||
// 使用发布订阅转换器
|
||||
.responseTransformer(AsyncResponseTransformer.toPublisher())
|
||||
.build();
|
||||
// 文件对象
|
||||
.getObjectRequest(y -> y.bucket(properties.getBucketName()).key(key).build());
|
||||
if (log.isDebugEnabled()) {
|
||||
typedBuilder.addTransferListener(LoggingTransferListener.create());
|
||||
}
|
||||
|
||||
// 使用 S3TransferManager 下载文件
|
||||
Download<ResponsePublisher<GetObjectResponse>> publisherDownload = transferManager.download(publisherDownloadRequest);
|
||||
Download<ResponsePublisher<GetObjectResponse>> publisherDownload = transferManager.download(typedBuilder.build());
|
||||
// 获取下载发布订阅转换器
|
||||
ResponsePublisher<GetObjectResponse> publisher = publisherDownload.completionFuture().join().result();
|
||||
// 执行文件大小消费者函数
|
||||
@@ -289,7 +300,7 @@ public class OssClient {
|
||||
// 构建写出订阅器对象
|
||||
return out -> {
|
||||
// 创建可写入的字节通道
|
||||
try(WritableByteChannel channel = Channels.newChannel(out)){
|
||||
try (WritableByteChannel channel = Channels.newChannel(out)) {
|
||||
// 订阅数据
|
||||
publisher.subscribe(byteBuffer -> {
|
||||
while (byteBuffer.hasRemaining()) {
|
||||
@@ -347,7 +358,7 @@ public class OssClient {
|
||||
*
|
||||
* @param objectKey 对象KEY
|
||||
* @param expiredTime 链接授权到期时间
|
||||
* @param metadata 元数据
|
||||
* @param metadata 元数据
|
||||
*/
|
||||
public String createPresignedPutUrl(String objectKey, Duration expiredTime, Map<String, String> metadata) {
|
||||
// 使用 AWS S3 预签名 URL 的生成器 获取上传文件对象的预签名 URL
|
||||
|
||||
@@ -113,7 +113,7 @@ public class PlusSaTokenDao implements SaTokenDaoBySessionFollowObject {
|
||||
* @param key 键名称
|
||||
* @return object
|
||||
*/
|
||||
@SuppressWarnings("unchecked cast")
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <T> T getObject(String key, Class<T> classType) {
|
||||
Object o = CAFFEINE.get(key, k -> RedisUtils.getCacheObject(key));
|
||||
|
||||
@@ -63,7 +63,7 @@ public class LoginHelper {
|
||||
/**
|
||||
* 获取用户(多级缓存)
|
||||
*/
|
||||
@SuppressWarnings("unchecked cast")
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T extends LoginUser> T getLoginUser() {
|
||||
SaSession session = StpUtil.getTokenSession();
|
||||
if (ObjectUtil.isNull(session)) {
|
||||
@@ -75,7 +75,7 @@ public class LoginHelper {
|
||||
/**
|
||||
* 获取用户基于token
|
||||
*/
|
||||
@SuppressWarnings("unchecked cast")
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T extends LoginUser> T getLoginUser(String token) {
|
||||
SaSession session = StpUtil.getTokenSessionByToken(token);
|
||||
if (ObjectUtil.isNull(session)) {
|
||||
|
||||
@@ -25,9 +25,24 @@ import java.util.Objects;
|
||||
@Slf4j
|
||||
public class SensitiveHandler extends JsonSerializer<String> implements ContextualSerializer {
|
||||
|
||||
private SensitiveStrategy strategy;
|
||||
private String[] roleKey;
|
||||
private String[] perms;
|
||||
private final SensitiveStrategy strategy;
|
||||
private final String[] roleKey;
|
||||
private final String[] perms;
|
||||
|
||||
/**
|
||||
* 提供给 jackson 创建上下文序列化器时使用 不然会报错
|
||||
*/
|
||||
public SensitiveHandler() {
|
||||
this.strategy = null;
|
||||
this.roleKey = null;
|
||||
this.perms = null;
|
||||
}
|
||||
|
||||
public SensitiveHandler(SensitiveStrategy strategy, String[] strings, String[] perms) {
|
||||
this.strategy = strategy;
|
||||
this.roleKey = strings;
|
||||
this.perms = perms;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
|
||||
@@ -48,10 +63,7 @@ public class SensitiveHandler extends JsonSerializer<String> implements Contextu
|
||||
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException {
|
||||
Sensitive annotation = property.getAnnotation(Sensitive.class);
|
||||
if (Objects.nonNull(annotation) && Objects.equals(String.class, property.getType().getRawClass())) {
|
||||
this.strategy = annotation.strategy();
|
||||
this.roleKey = annotation.roleKey();
|
||||
this.perms = annotation.perms();
|
||||
return this;
|
||||
return new SensitiveHandler(annotation.strategy(), annotation.roleKey(), annotation.perms());
|
||||
}
|
||||
return prov.findValueSerializer(property.getType(), property);
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ public interface TransConstant {
|
||||
String USER_ID_TO_NAME = "user_id_to_name";
|
||||
|
||||
/**
|
||||
* 用户id转用户名称
|
||||
* 用户id转用户昵称
|
||||
*/
|
||||
String USER_ID_TO_NICKNAME = "user_id_to_nickname";
|
||||
|
||||
|
||||
@@ -31,7 +31,18 @@ public class TranslationHandler extends JsonSerializer<Object> implements Contex
|
||||
*/
|
||||
public static final Map<String, TranslationInterface<?>> TRANSLATION_MAPPER = new ConcurrentHashMap<>();
|
||||
|
||||
private Translation translation;
|
||||
private final Translation translation;
|
||||
|
||||
/**
|
||||
* 提供给 jackson 创建上下文序列化器时使用 不然会报错
|
||||
*/
|
||||
public TranslationHandler() {
|
||||
this.translation = null;
|
||||
}
|
||||
|
||||
public TranslationHandler(Translation translation) {
|
||||
this.translation = translation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
|
||||
@@ -63,8 +74,7 @@ public class TranslationHandler extends JsonSerializer<Object> implements Contex
|
||||
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException {
|
||||
Translation translation = property.getAnnotation(Translation.class);
|
||||
if (Objects.nonNull(translation)) {
|
||||
this.translation = translation;
|
||||
return this;
|
||||
return new TranslationHandler(translation);
|
||||
}
|
||||
return prov.findValueSerializer(property.getType(), property);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import org.dromara.common.translation.constant.TransConstant;
|
||||
import org.dromara.common.translation.core.TranslationInterface;
|
||||
|
||||
/**
|
||||
* 用户名称翻译实现
|
||||
* 用户昵称翻译实现
|
||||
*
|
||||
* @author may
|
||||
*/
|
||||
|
||||
@@ -1,16 +1,8 @@
|
||||
package org.dromara.common.web.config;
|
||||
|
||||
import cn.hutool.captcha.CaptchaUtil;
|
||||
import cn.hutool.captcha.CircleCaptcha;
|
||||
import cn.hutool.captcha.LineCaptcha;
|
||||
import cn.hutool.captcha.ShearCaptcha;
|
||||
import org.dromara.common.web.config.properties.CaptchaProperties;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
|
||||
import java.awt.*;
|
||||
|
||||
/**
|
||||
* 验证码配置
|
||||
@@ -21,45 +13,4 @@ import java.awt.*;
|
||||
@EnableConfigurationProperties(CaptchaProperties.class)
|
||||
public class CaptchaConfig {
|
||||
|
||||
private static final int WIDTH = 160;
|
||||
private static final int HEIGHT = 60;
|
||||
private static final Color BACKGROUND = Color.LIGHT_GRAY;
|
||||
private static final Font FONT = new Font("Arial", Font.BOLD, 48);
|
||||
|
||||
/**
|
||||
* 圆圈干扰验证码
|
||||
*/
|
||||
@Lazy
|
||||
@Bean
|
||||
public CircleCaptcha circleCaptcha() {
|
||||
CircleCaptcha captcha = CaptchaUtil.createCircleCaptcha(WIDTH, HEIGHT);
|
||||
captcha.setBackground(BACKGROUND);
|
||||
captcha.setFont(FONT);
|
||||
return captcha;
|
||||
}
|
||||
|
||||
/**
|
||||
* 线段干扰的验证码
|
||||
*/
|
||||
@Lazy
|
||||
@Bean
|
||||
public LineCaptcha lineCaptcha() {
|
||||
LineCaptcha captcha = CaptchaUtil.createLineCaptcha(WIDTH, HEIGHT);
|
||||
captcha.setBackground(BACKGROUND);
|
||||
captcha.setFont(FONT);
|
||||
return captcha;
|
||||
}
|
||||
|
||||
/**
|
||||
* 扭曲干扰验证码
|
||||
*/
|
||||
@Lazy
|
||||
@Bean
|
||||
public ShearCaptcha shearCaptcha() {
|
||||
ShearCaptcha captcha = CaptchaUtil.createShearCaptcha(WIDTH, HEIGHT);
|
||||
captcha.setBackground(BACKGROUND);
|
||||
captcha.setFont(FONT);
|
||||
return captcha;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
package org.dromara.common.web.config;
|
||||
|
||||
import io.undertow.UndertowOptions;
|
||||
import io.undertow.server.DefaultByteBufferPool;
|
||||
import io.undertow.server.handlers.DisallowedMethodsHandler;
|
||||
import io.undertow.util.HttpString;
|
||||
import io.undertow.websockets.jsr.WebSocketDeploymentInfo;
|
||||
import org.dromara.common.core.utils.SpringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.web.ServerProperties;
|
||||
import org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory;
|
||||
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
|
||||
import org.springframework.core.task.VirtualThreadTaskExecutor;
|
||||
@@ -18,6 +21,9 @@ import org.springframework.core.task.VirtualThreadTaskExecutor;
|
||||
@AutoConfiguration
|
||||
public class UndertowConfig implements WebServerFactoryCustomizer<UndertowServletWebServerFactory> {
|
||||
|
||||
@Autowired
|
||||
private ServerProperties serverProperties;
|
||||
|
||||
/**
|
||||
* 自定义 Undertow 配置
|
||||
* <p>
|
||||
@@ -31,6 +37,11 @@ public class UndertowConfig implements WebServerFactoryCustomizer<UndertowServle
|
||||
*/
|
||||
@Override
|
||||
public void customize(UndertowServletWebServerFactory factory) {
|
||||
long bytes = serverProperties.getUndertow().getMaxHttpPostSize().toBytes();
|
||||
factory.addBuilderCustomizers(builder -> {
|
||||
builder.setServerOption(UndertowOptions.MULTIPART_MAX_ENTITY_SIZE, bytes);
|
||||
});
|
||||
|
||||
factory.addDeploymentInfoCustomizers(deploymentInfo -> {
|
||||
// 配置 WebSocket 部署信息,设置 WebSocket 使用的缓冲区池
|
||||
WebSocketDeploymentInfo webSocketDeploymentInfo = new WebSocketDeploymentInfo();
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package org.dromara.common.web.config.properties;
|
||||
|
||||
import org.dromara.common.web.enums.CaptchaCategory;
|
||||
import org.dromara.common.web.enums.CaptchaType;
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
@@ -19,12 +17,7 @@ public class CaptchaProperties {
|
||||
/**
|
||||
* 验证码类型
|
||||
*/
|
||||
private CaptchaType type;
|
||||
|
||||
/**
|
||||
* 验证码类别
|
||||
*/
|
||||
private CaptchaCategory category;
|
||||
private String type;
|
||||
|
||||
/**
|
||||
* 数字验证码位数
|
||||
|
||||
@@ -0,0 +1,197 @@
|
||||
package org.dromara.common.web.core;
|
||||
|
||||
import cn.hutool.captcha.AbstractCaptcha;
|
||||
import cn.hutool.captcha.generator.CodeGenerator;
|
||||
import cn.hutool.captcha.generator.RandomGenerator;
|
||||
import cn.hutool.core.img.GraphicsUtil;
|
||||
import cn.hutool.core.img.ImgUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.Serial;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
/**
|
||||
* 带干扰线、波浪、圆的验证码
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
public class WaveAndCircleCaptcha extends AbstractCaptcha {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
// 构造方法(略,与之前一致)
|
||||
public WaveAndCircleCaptcha(int width, int height) {
|
||||
this(width, height, 4);
|
||||
}
|
||||
|
||||
public WaveAndCircleCaptcha(int width, int height, int codeCount) {
|
||||
this(width, height, codeCount, 6);
|
||||
}
|
||||
|
||||
public WaveAndCircleCaptcha(int width, int height, int codeCount, int interfereCount) {
|
||||
this(width, height, new RandomGenerator(codeCount), interfereCount);
|
||||
}
|
||||
|
||||
public WaveAndCircleCaptcha(int width, int height, CodeGenerator generator, int interfereCount) {
|
||||
super(width, height, generator, interfereCount);
|
||||
}
|
||||
|
||||
public WaveAndCircleCaptcha(int width, int height, int codeCount, int interfereCount, float size) {
|
||||
super(width, height, new RandomGenerator(codeCount), interfereCount, size);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Image createImage(String code) {
|
||||
final BufferedImage image = new BufferedImage(
|
||||
width,
|
||||
height,
|
||||
(null == this.background) ? BufferedImage.TYPE_4BYTE_ABGR : BufferedImage.TYPE_INT_RGB
|
||||
);
|
||||
final Graphics2D g = ImgUtil.createGraphics(image, this.background);
|
||||
|
||||
try {
|
||||
drawString(g, code);
|
||||
// 扭曲
|
||||
shear(g, this.width, this.height, ObjectUtil.defaultIfNull(this.background, Color.WHITE));
|
||||
drawInterfere(g);
|
||||
} finally {
|
||||
g.dispose();
|
||||
}
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
private void drawString(Graphics2D g, String code) {
|
||||
// 设置抗锯齿(让字体渲染更清晰)
|
||||
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
|
||||
|
||||
if (this.textAlpha != null) {
|
||||
g.setComposite(this.textAlpha);
|
||||
}
|
||||
|
||||
GraphicsUtil.drawStringColourful(g, code, this.font, this.width, this.height);
|
||||
}
|
||||
|
||||
protected void drawInterfere(Graphics2D g) {
|
||||
ThreadLocalRandom random = RandomUtil.getRandom();
|
||||
int circleCount = Math.max(0, this.interfereCount - 1);
|
||||
|
||||
// 圈圈
|
||||
for (int i = 0; i < circleCount; i++) {
|
||||
g.setColor(ImgUtil.randomColor(random));
|
||||
int x = random.nextInt(width);
|
||||
int y = random.nextInt(height);
|
||||
int w = random.nextInt(height >> 1);
|
||||
int h = random.nextInt(height >> 1);
|
||||
g.drawOval(x, y, w, h);
|
||||
}
|
||||
|
||||
// 仅 1 条平滑波浪线
|
||||
if (this.interfereCount >= 1) {
|
||||
g.setColor(getRandomColor(120, 230, random));
|
||||
drawSmoothWave(g, random);
|
||||
}
|
||||
}
|
||||
|
||||
private void drawSmoothWave(Graphics2D g, ThreadLocalRandom random) {
|
||||
int amplitude = random.nextInt(8) + 5; // 波动幅度
|
||||
int wavelength = random.nextInt(40) + 30; // 波长
|
||||
double phase = random.nextDouble() * Math.PI * 2;
|
||||
|
||||
// ✅ 关键:限制 baseY 在中间区域
|
||||
int centerY = height / 2;
|
||||
int verticalJitter = Math.max(5, height / 6); // 至少偏移5像素
|
||||
int baseY = centerY - verticalJitter + random.nextInt(verticalJitter * 2);
|
||||
|
||||
g.setStroke(new BasicStroke(2.5f)); // 线宽
|
||||
|
||||
int[] xPoints = new int[width];
|
||||
int[] yPoints = new int[width];
|
||||
for (int x = 0; x < width; x++) {
|
||||
int y = baseY + (int) (amplitude * Math.sin((double) x / wavelength * 2 * Math.PI + phase));
|
||||
// 限制 y 不要超出图像边界(可选)
|
||||
y = Math.max(amplitude, Math.min(y, height - amplitude));
|
||||
xPoints[x] = x;
|
||||
yPoints[x] = y;
|
||||
}
|
||||
g.drawPolyline(xPoints, yPoints, width);
|
||||
}
|
||||
|
||||
private Color getRandomColor(int min, int max, ThreadLocalRandom random) {
|
||||
int range = max - min;
|
||||
return new Color(
|
||||
min + random.nextInt(range),
|
||||
min + random.nextInt(range),
|
||||
min + random.nextInt(range)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 扭曲
|
||||
*
|
||||
* @param g {@link Graphics}
|
||||
* @param w1 w1
|
||||
* @param h1 h1
|
||||
* @param color 颜色
|
||||
*/
|
||||
private void shear(Graphics g, int w1, int h1, Color color) {
|
||||
shearX(g, w1, h1, color);
|
||||
shearY(g, w1, h1, color);
|
||||
}
|
||||
|
||||
/**
|
||||
* X坐标扭曲
|
||||
*
|
||||
* @param g {@link Graphics}
|
||||
* @param w1 宽
|
||||
* @param h1 高
|
||||
* @param color 颜色
|
||||
*/
|
||||
private void shearX(Graphics g, int w1, int h1, Color color) {
|
||||
|
||||
int period = RandomUtil.randomInt(this.width);
|
||||
|
||||
int frames = 1;
|
||||
int phase = RandomUtil.randomInt(2);
|
||||
|
||||
for (int i = 0; i < h1; i++) {
|
||||
double d = (double) (period >> 1) * Math.sin((double) i / (double) period + (6.2831853071795862D * (double) phase) / (double) frames);
|
||||
g.copyArea(0, i, w1, 1, (int) d, 0);
|
||||
g.setColor(color);
|
||||
g.drawLine((int) d, i, 0, i);
|
||||
g.drawLine((int) d + w1, i, w1, i);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Y坐标扭曲
|
||||
*
|
||||
* @param g {@link Graphics}
|
||||
* @param w1 宽
|
||||
* @param h1 高
|
||||
* @param color 颜色
|
||||
*/
|
||||
private void shearY(Graphics g, int w1, int h1, Color color) {
|
||||
|
||||
int period = RandomUtil.randomInt(this.height >> 1);
|
||||
|
||||
int frames = 20;
|
||||
int phase = 7;
|
||||
for (int i = 0; i < w1; i++) {
|
||||
double d = (double) (period >> 1) * Math.sin((double) i / (double) period + (6.2831853071795862D * (double) phase) / (double) frames);
|
||||
g.copyArea(i, 0, 1, h1, 0, (int) d);
|
||||
g.setColor(color);
|
||||
// 擦除原位置的痕迹
|
||||
g.drawLine(i, (int) d, i, 0);
|
||||
g.drawLine(i, (int) d + h1, i, h1);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
package org.dromara.common.web.enums;
|
||||
|
||||
import cn.hutool.captcha.AbstractCaptcha;
|
||||
import cn.hutool.captcha.CircleCaptcha;
|
||||
import cn.hutool.captcha.LineCaptcha;
|
||||
import cn.hutool.captcha.ShearCaptcha;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 验证码类别
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum CaptchaCategory {
|
||||
|
||||
/**
|
||||
* 线段干扰
|
||||
*/
|
||||
LINE(LineCaptcha.class),
|
||||
|
||||
/**
|
||||
* 圆圈干扰
|
||||
*/
|
||||
CIRCLE(CircleCaptcha.class),
|
||||
|
||||
/**
|
||||
* 扭曲干扰
|
||||
*/
|
||||
SHEAR(ShearCaptcha.class);
|
||||
|
||||
private final Class<? extends AbstractCaptcha> clazz;
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package org.dromara.common.web.enums;
|
||||
|
||||
import cn.hutool.captcha.generator.CodeGenerator;
|
||||
import cn.hutool.captcha.generator.MathGenerator;
|
||||
import cn.hutool.captcha.generator.RandomGenerator;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 验证码类型
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum CaptchaType {
|
||||
|
||||
/**
|
||||
* 数字
|
||||
*/
|
||||
MATH(MathGenerator.class),
|
||||
|
||||
/**
|
||||
* 字符
|
||||
*/
|
||||
CHAR(RandomGenerator.class);
|
||||
|
||||
private final Class<? extends CodeGenerator> clazz;
|
||||
}
|
||||
@@ -14,6 +14,7 @@ import org.dromara.common.core.exception.SseException;
|
||||
import org.dromara.common.core.exception.base.BaseException;
|
||||
import org.dromara.common.core.utils.StreamUtils;
|
||||
import org.dromara.common.json.utils.JsonUtils;
|
||||
import org.springframework.context.MessageSourceResolvable;
|
||||
import org.springframework.context.support.DefaultMessageSourceResolvable;
|
||||
import org.springframework.expression.ExpressionException;
|
||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||
@@ -25,6 +26,7 @@ import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
import org.springframework.web.context.request.async.AsyncRequestTimeoutException;
|
||||
import org.springframework.web.method.annotation.HandlerMethodValidationException;
|
||||
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
|
||||
import org.springframework.web.servlet.NoHandlerFoundException;
|
||||
|
||||
@@ -191,6 +193,16 @@ public class GlobalExceptionHandler {
|
||||
return R.fail(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 方法参数校验异常 用于处理 @Validated 注解
|
||||
*/
|
||||
@ExceptionHandler(HandlerMethodValidationException.class)
|
||||
public R<Void> handlerMethodValidationException(HandlerMethodValidationException e) {
|
||||
log.error(e.getMessage());
|
||||
String message = StreamUtils.join(e.getAllErrors(), MessageSourceResolvable::getDefaultMessage, ", ");
|
||||
return R.fail(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* JSON 解析异常(Jackson 在处理 JSON 格式出错时抛出)
|
||||
* 可能是请求体格式非法,也可能是服务端反序列化失败
|
||||
|
||||
@@ -8,6 +8,7 @@ import org.dromara.common.websocket.holder.WebSocketSessionHolder;
|
||||
import org.dromara.common.websocket.utils.WebSocketUtils;
|
||||
import org.springframework.web.socket.*;
|
||||
import org.springframework.web.socket.handler.AbstractWebSocketHandler;
|
||||
import org.springframework.web.socket.handler.ConcurrentWebSocketSessionDecorator;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
@@ -33,7 +34,7 @@ public class PlusWebSocketHandler extends AbstractWebSocketHandler {
|
||||
log.info("[connect] invalid token received. sessionId: {}", session.getId());
|
||||
return;
|
||||
}
|
||||
WebSocketSessionHolder.addSession(loginUser.getUserId(), session);
|
||||
WebSocketSessionHolder.addSession(loginUser.getUserId(), new ConcurrentWebSocketSessionDecorator(session, 10 * 1000, 64000));
|
||||
log.info("[connect] sessionId: {},userId:{},userType:{}", session.getId(), loginUser.getUserId(), loginUser.getUserType());
|
||||
}
|
||||
|
||||
|
||||
@@ -113,7 +113,7 @@ public class WebSocketUtils {
|
||||
* @param session WebSocket会话
|
||||
* @param message 要发送的WebSocket消息对象
|
||||
*/
|
||||
private synchronized static void sendMessage(WebSocketSession session, WebSocketMessage<?> message) {
|
||||
private static void sendMessage(WebSocketSession session, WebSocketMessage<?> message) {
|
||||
if (session == null || !session.isOpen()) {
|
||||
log.warn("[send] session会话已经关闭");
|
||||
} else {
|
||||
|
||||
@@ -1,146 +0,0 @@
|
||||
package com.aizuda.snailjob.server.common.register;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.aizuda.snailjob.common.core.enums.NodeTypeEnum;
|
||||
import com.aizuda.snailjob.common.core.util.JsonUtil;
|
||||
import com.aizuda.snailjob.common.core.util.NetUtil;
|
||||
import com.aizuda.snailjob.common.core.util.SnailJobVersion;
|
||||
import com.aizuda.snailjob.common.core.util.StreamUtils;
|
||||
import com.aizuda.snailjob.common.log.SnailJobLog;
|
||||
import com.aizuda.snailjob.server.common.cache.CacheConsumerGroup;
|
||||
import com.aizuda.snailjob.server.common.config.SystemProperties;
|
||||
import com.aizuda.snailjob.server.common.convert.RegisterNodeInfoConverter;
|
||||
import com.aizuda.snailjob.server.common.dto.ServerNodeExtAttrs;
|
||||
import com.aizuda.snailjob.server.common.handler.InstanceManager;
|
||||
import com.aizuda.snailjob.template.datasource.persistence.po.ServerNode;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.google.common.collect.Lists;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.boot.autoconfigure.web.ServerProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 服务端注册
|
||||
*
|
||||
* @author opensnail
|
||||
* @date 2023-06-07
|
||||
* @since 1.6.0
|
||||
*/
|
||||
@Component(ServerRegister.BEAN_NAME)
|
||||
@RequiredArgsConstructor
|
||||
public class ServerRegister extends AbstractRegister {
|
||||
public static final String BEAN_NAME = "serverRegister";
|
||||
private final ScheduledExecutorService serverRegisterNode = Executors.newSingleThreadScheduledExecutor(r -> new Thread(r, "server-register-node"));
|
||||
public static final int DELAY_TIME = 30;
|
||||
public static final String CURRENT_CID;
|
||||
public static final String GROUP_NAME = "DEFAULT_SERVER";
|
||||
public static final String NAMESPACE_ID = "DEFAULT_SERVER_NAMESPACE_ID";
|
||||
private final InstanceManager instanceManager;
|
||||
private final SystemProperties systemProperties;
|
||||
private final ServerProperties serverProperties;
|
||||
|
||||
static {
|
||||
CURRENT_CID = IdUtil.getSnowflakeNextIdStr();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(int type) {
|
||||
return getNodeType().equals(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void beforeProcessor(RegisterContext context) {
|
||||
// 新增扩展参数
|
||||
ServerNodeExtAttrs serverNodeExtAttrs = new ServerNodeExtAttrs();
|
||||
serverNodeExtAttrs.setWebPort(serverProperties.getPort());
|
||||
serverNodeExtAttrs.setSystemVersion(SnailJobVersion.getVersion());
|
||||
|
||||
context.setGroupName(GROUP_NAME);
|
||||
context.setHostId(CURRENT_CID);
|
||||
String serverHost = systemProperties.getServerHost();
|
||||
if (StrUtil.isEmptyIfStr(serverHost)) {
|
||||
serverHost = NetUtil.getLocalIpStr();
|
||||
}
|
||||
context.setHostIp(serverHost);
|
||||
context.setHostPort(systemProperties.getServerPort());
|
||||
context.setContextPath(Optional.ofNullable(serverProperties.getServlet().getContextPath()).orElse(StrUtil.EMPTY));
|
||||
context.setNamespaceId(NAMESPACE_ID);
|
||||
context.setExtAttrs(JsonUtil.toJsonString(serverNodeExtAttrs));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LocalDateTime getExpireAt() {
|
||||
return LocalDateTime.now().plusSeconds(DELAY_TIME);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doRegister(RegisterContext context, ServerNode serverNode) {
|
||||
refreshExpireAt(Lists.newArrayList(serverNode));
|
||||
return Boolean.TRUE;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void afterProcessor(final ServerNode serverNode) {
|
||||
try {
|
||||
// 同步当前POD消费的组的节点信息
|
||||
// netty的client只会注册到一个服务端,若组分配的和client连接的不是一个POD则会导致当前POD没有其他客户端的注册信息
|
||||
ConcurrentMap<String /*groupName*/, Set<String>/*namespaceId*/> allConsumerGroupName = CacheConsumerGroup.getAllConsumerGroupName();
|
||||
if (CollUtil.isNotEmpty(allConsumerGroupName)) {
|
||||
Set<String> namespaceIdSets = StreamUtils.toSetByFlatMap(allConsumerGroupName.values(), Set::stream);
|
||||
if (CollUtil.isEmpty(namespaceIdSets)) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<ServerNode> serverNodes = serverNodeMapper.selectList(
|
||||
new LambdaQueryWrapper<ServerNode>()
|
||||
.eq(ServerNode::getNodeType, NodeTypeEnum.CLIENT.getType())
|
||||
.in(ServerNode::getNamespaceId, namespaceIdSets)
|
||||
.in(ServerNode::getGroupName, allConsumerGroupName.keySet()));
|
||||
for (final ServerNode node : serverNodes) {
|
||||
// 刷新全量本地缓存
|
||||
instanceManager.registerOrUpdate(RegisterNodeInfoConverter.INSTANCE.toRegisterNodeInfo(node));
|
||||
// 刷新过期时间
|
||||
CacheConsumerGroup.addOrUpdate(node.getGroupName(), node.getNamespaceId());
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
SnailJobLog.LOCAL.error("Client refresh failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Integer getNodeType() {
|
||||
return NodeTypeEnum.SERVER.getType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
SnailJobLog.LOCAL.info("ServerRegister start");
|
||||
|
||||
serverRegisterNode.scheduleAtFixedRate(() -> {
|
||||
try {
|
||||
this.register(new RegisterContext());
|
||||
} catch (Exception e) {
|
||||
SnailJobLog.LOCAL.error("Server-side registration failed", e);
|
||||
}
|
||||
}, 0, DELAY_TIME * 2 / 3, TimeUnit.SECONDS);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
SnailJobLog.LOCAL.info("ServerRegister close");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
package org.dromara.demo.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.*;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.common.core.domain.R;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* SaToken 权限测试 接口文档输出测试
|
||||
*
|
||||
* @author AprilWind
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/demo/saTokenDoc")
|
||||
public class SaTokenTestController {
|
||||
|
||||
// ====================== 基础场景:单一校验规则 ======================
|
||||
|
||||
/**
|
||||
* 场景1:仅登录校验(无角色/权限限制,只需登录态)
|
||||
*/
|
||||
@SaCheckLogin
|
||||
@GetMapping("/basic/loginOnly")
|
||||
public R<Void> loginOnly() {
|
||||
log.info("【场景1】仅登录校验通过");
|
||||
return R.ok("仅登录校验通过,无需角色/权限");
|
||||
}
|
||||
|
||||
/**
|
||||
* 场景2:单一角色校验(AND模式,默认)
|
||||
*/
|
||||
@SaCheckRole("admin")
|
||||
@GetMapping("/basic/singleRole")
|
||||
public R<Void> singleRole() {
|
||||
log.info("【场景2】单一角色(admin)校验通过");
|
||||
return R.ok("拥有admin角色,校验通过");
|
||||
}
|
||||
|
||||
/**
|
||||
* 场景3:单一权限校验(AND模式,默认)
|
||||
*/
|
||||
@SaCheckPermission("system:user:view")
|
||||
@GetMapping("/basic/singlePermission")
|
||||
public R<Void> singlePermission() {
|
||||
log.info("【场景3】单一权限(system:user:view)校验通过");
|
||||
return R.ok("拥有system:user:view权限,校验通过");
|
||||
}
|
||||
|
||||
/**
|
||||
* 场景4:忽略所有权限校验(SaIgnore优先级最高)
|
||||
*/
|
||||
@SaIgnore
|
||||
@SaCheckRole("none_exist") // 该注解会被忽略
|
||||
@GetMapping("/basic/ignoreAll")
|
||||
public R<Void> ignoreAll() {
|
||||
log.info("【场景4】SaIgnore忽略所有权限校验");
|
||||
return R.ok("SaIgnore生效,所有权限校验被忽略");
|
||||
}
|
||||
|
||||
// ====================== 进阶场景:多条件组合(AND/OR) ======================
|
||||
|
||||
/**
|
||||
* 场景5:多角色AND模式(必须同时拥有所有角色)
|
||||
*/
|
||||
@SaCheckRole(value = {"admin", "operator"}, mode = SaMode.AND)
|
||||
@GetMapping("/advance/multiRoleAnd")
|
||||
public R<Void> multiRoleAnd() {
|
||||
log.info("【场景5】多角色AND模式(admin+operator)校验通过");
|
||||
return R.ok("同时拥有admin和operator角色,校验通过");
|
||||
}
|
||||
|
||||
/**
|
||||
* 场景6:多角色OR模式(拥有任一角色即可)
|
||||
*/
|
||||
@SaCheckRole(value = {"admin", "test"}, mode = SaMode.OR)
|
||||
@GetMapping("/advance/multiRoleOr")
|
||||
public R<Void> multiRoleOr() {
|
||||
log.info("【场景6】多角色OR模式(admin|test)校验通过");
|
||||
return R.ok("拥有admin或test角色,校验通过");
|
||||
}
|
||||
|
||||
/**
|
||||
* 场景7:多权限AND模式(必须同时拥有所有权限)
|
||||
*/
|
||||
@SaCheckPermission(value = {"system:user:edit", "system:log:view"}, mode = SaMode.AND)
|
||||
@GetMapping("/advance/multiPermAnd")
|
||||
public R<Void> multiPermAnd() {
|
||||
log.info("【场景7】多权限AND模式(system:user:edit+system:log:view)校验通过");
|
||||
return R.ok("同时拥有system:user:edit和system:log:view权限,校验通过");
|
||||
}
|
||||
|
||||
/**
|
||||
* 场景8:多权限OR模式(拥有任一权限即可)
|
||||
*/
|
||||
@SaCheckPermission(value = {"system:user:add", "system:user:delete"}, mode = SaMode.OR)
|
||||
@GetMapping("/advance/multiPermOr")
|
||||
public R<Void> multiPermOr() {
|
||||
log.info("【场景8】多权限OR模式(system:user:add|system:user:delete)校验通过");
|
||||
return R.ok("拥有system:user:add或system:user:delete权限,校验通过");
|
||||
}
|
||||
|
||||
// ====================== 高级场景:通配符/混合组合 ======================
|
||||
|
||||
/**
|
||||
* 场景9:权限通配符匹配(前缀匹配)
|
||||
* 拥有system:user:* 即可匹配所有用户模块权限
|
||||
*/
|
||||
@SaCheckPermission("system:user:*")
|
||||
@GetMapping("/advanced/permWildcardPrefix")
|
||||
public R<Void> permWildcardPrefix() {
|
||||
log.info("【场景9】权限通配符(system:user:*)校验通过");
|
||||
return R.ok("拥有system:user:*前缀权限,校验通过");
|
||||
}
|
||||
|
||||
/**
|
||||
* 场景10:角色通配符匹配(前缀匹配)
|
||||
* 拥有admin_* 即可匹配所有admin开头的角色
|
||||
*/
|
||||
@SaCheckRole("admin_*")
|
||||
@GetMapping("/advanced/roleWildcardPrefix")
|
||||
public R<Void> roleWildcardPrefix() {
|
||||
log.info("【场景10】角色通配符(admin_*)校验通过");
|
||||
return R.ok("拥有admin_*前缀角色,校验通过");
|
||||
}
|
||||
|
||||
/**
|
||||
* 场景11:权限+角色混合AND模式(所有条件必须满足)
|
||||
* 需同时满足:拥有admin角色 + 拥有system:user:all权限
|
||||
*/
|
||||
@SaCheckRole("admin")
|
||||
@SaCheckPermission("system:user:all")
|
||||
@GetMapping("/advanced/mixRolePermAnd")
|
||||
public R<Void> mixRolePermAnd() {
|
||||
log.info("【场景11】角色+权限混合AND(admin+system:user:all)校验通过");
|
||||
return R.ok("拥有admin角色且拥有system:user:all权限,校验通过");
|
||||
}
|
||||
|
||||
/**
|
||||
* 场景12:权限+角色混合OR模式(任一条件满足即可)
|
||||
* 满足任一:拥有super_admin角色 | 拥有system:manage权限
|
||||
*/
|
||||
@SaCheckRole(value = {"super_admin"}, mode = SaMode.OR)
|
||||
@SaCheckPermission(value = {"system:manage"}, mode = SaMode.OR)
|
||||
@GetMapping("/advanced/mixRolePermOr")
|
||||
public R<Void> mixRolePermOr() {
|
||||
log.info("【场景12】角色+权限混合OR(super_admin|system:manage)校验通过");
|
||||
return R.ok("拥有super_admin角色或system:manage权限,校验通过");
|
||||
}
|
||||
|
||||
/**
|
||||
* 场景13:orRole参数(权限校验失败时,兜底角色校验)
|
||||
* 核心逻辑:无system:user:export权限时,检查是否有admin/operator角色
|
||||
*/
|
||||
@SaCheckPermission(value = "system:user:export", orRole = {"admin", "operator"})
|
||||
@GetMapping("/advanced/permWithOrRole")
|
||||
public R<Void> permWithOrRole() {
|
||||
log.info("【场景13】权限+orRole兜底校验通过");
|
||||
return R.ok("拥有system:user:export权限,或拥有admin/operator角色,校验通过");
|
||||
}
|
||||
|
||||
// ====================== 特殊场景:临时权限/注解覆盖 ======================
|
||||
|
||||
/**
|
||||
* 场景14:SaIgnore局部覆盖(方法注解覆盖类注解,若有)
|
||||
* 假设类上有@SaCheckLogin,方法上@SaIgnore会覆盖
|
||||
*/
|
||||
@SaIgnore
|
||||
@GetMapping("/special/ignoreOverride")
|
||||
public R<Void> ignoreOverride() {
|
||||
log.info("【场景14】SaIgnore覆盖类级别权限注解");
|
||||
return R.ok("方法级SaIgnore覆盖类级别权限校验");
|
||||
}
|
||||
|
||||
/**
|
||||
* 场景15:临时权限校验(SaCheckPermission逻辑:临时权限>永久权限)
|
||||
* 注:临时权限需通过SaToken API手动设置,如 SaHolder.getStpLogic().setTempPermission("system:temp:test")
|
||||
*/
|
||||
@SaCheckPermission("system:temp:test")
|
||||
@GetMapping("/special/tempPermission")
|
||||
public R<Void> tempPermission() {
|
||||
log.info("【场景15】临时权限(system:temp:test)校验通过");
|
||||
return R.ok("临时权限校验通过(需先通过API设置临时权限)");
|
||||
}
|
||||
|
||||
/**
|
||||
* 场景16:登录类型指定(多端登录场景,如PC/APP/小程序)
|
||||
* 注:需配合SaToken多账号体系配置
|
||||
*/
|
||||
@SaCheckLogin(type = "PC") // 仅校验PC端的登录态
|
||||
@GetMapping("/special/loginTypeSpecify")
|
||||
public R<Void> loginTypeSpecify() {
|
||||
log.info("【场景16】指定登录类型(PC)校验通过");
|
||||
return R.ok("仅PC端登录态校验通过");
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@ import org.dromara.common.log.annotation.Log;
|
||||
import org.dromara.common.log.enums.BusinessType;
|
||||
import org.dromara.demo.domain.TestDemo;
|
||||
import org.dromara.demo.domain.bo.TestDemoBo;
|
||||
import org.dromara.demo.domain.bo.TestDemoImportVo;
|
||||
import org.dromara.demo.domain.vo.TestDemoImportVo;
|
||||
import org.dromara.demo.domain.vo.TestDemoVo;
|
||||
import org.dromara.demo.service.ITestDemoService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@@ -35,8 +35,8 @@ public class ExportDemoVo implements Serializable {
|
||||
/**
|
||||
* 用户昵称
|
||||
*/
|
||||
@ExcelProperty(value = "用户名", index = 0)
|
||||
@NotEmpty(message = "用户名不能为空", groups = AddGroup.class)
|
||||
@ExcelProperty(value = "用户昵称", index = 0)
|
||||
@NotEmpty(message = "用户昵称不能为空", groups = AddGroup.class)
|
||||
private String nickName;
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
package org.dromara.demo.domain.bo;
|
||||
package org.dromara.demo.domain.vo;
|
||||
|
||||
import cn.idev.excel.annotation.ExcelProperty;
|
||||
import io.github.linpeilie.annotations.AutoMapper;
|
||||
import lombok.Data;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import org.dromara.demo.domain.TestDemo;
|
||||
|
||||
/**
|
||||
* 测试单表业务对象 test_demo
|
||||
@@ -13,6 +15,7 @@ import jakarta.validation.constraints.NotNull;
|
||||
* @date 2021-07-26
|
||||
*/
|
||||
@Data
|
||||
@AutoMapper(target = TestDemo.class)
|
||||
public class TestDemoImportVo {
|
||||
|
||||
/**
|
||||
@@ -523,6 +523,9 @@ public class GenTableServiceImpl implements IGenTableService {
|
||||
* @param table 业务表信息
|
||||
*/
|
||||
public void setPkColumn(GenTable table) {
|
||||
if (CollUtil.isEmpty(table.getColumns())) {
|
||||
throw new ServiceException("表【" + table.getTableName() + "】字段为空,请检查表结构");
|
||||
}
|
||||
for (GenTableColumn column : table.getColumns()) {
|
||||
if (column.isPk()) {
|
||||
table.setPkColumn(column);
|
||||
|
||||
@@ -21,6 +21,7 @@ import java.util.stream.IntStream;
|
||||
*
|
||||
* @author 老马
|
||||
*/
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
@Component
|
||||
@JobExecutor(name = "testMapJobAnnotation")
|
||||
public class TestMapJobAnnotation {
|
||||
|
||||
@@ -23,6 +23,7 @@ import java.util.stream.IntStream;
|
||||
*
|
||||
* @author 老马
|
||||
*/
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
@Component
|
||||
@JobExecutor(name = "testMapReduceAnnotation1")
|
||||
public class TestMapReduceAnnotation1 {
|
||||
|
||||
@@ -83,7 +83,7 @@ public class SysDictDataController extends BaseController {
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增字典类型
|
||||
* 新增字典数据
|
||||
*/
|
||||
@SaCheckPermission("system:dict:add")
|
||||
@Log(title = "字典数据", businessType = BusinessType.INSERT)
|
||||
@@ -98,7 +98,7 @@ public class SysDictDataController extends BaseController {
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改保存字典类型
|
||||
* 修改保存字典数据
|
||||
*/
|
||||
@SaCheckPermission("system:dict:edit")
|
||||
@Log(title = "字典数据", businessType = BusinessType.UPDATE)
|
||||
@@ -113,12 +113,12 @@ public class SysDictDataController extends BaseController {
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除字典类型
|
||||
* 删除字典数据
|
||||
*
|
||||
* @param dictCodes 字典code串
|
||||
*/
|
||||
@SaCheckPermission("system:dict:remove")
|
||||
@Log(title = "字典类型", businessType = BusinessType.DELETE)
|
||||
@Log(title = "字典数据", businessType = BusinessType.DELETE)
|
||||
@DeleteMapping("/{dictCodes}")
|
||||
public R<Void> remove(@PathVariable Long[] dictCodes) {
|
||||
dictDataService.deleteDictDataByIds(Arrays.asList(dictCodes));
|
||||
|
||||
@@ -137,6 +137,8 @@ public class SysMenuController extends BaseController {
|
||||
return R.fail("新增菜单'" + menu.getMenuName() + "'失败,菜单名称已存在");
|
||||
} else if (SystemConstants.YES_FRAME.equals(menu.getIsFrame()) && !StringUtils.ishttp(menu.getPath())) {
|
||||
return R.fail("新增菜单'" + menu.getMenuName() + "'失败,地址必须以http(s)://开头");
|
||||
} else if (!menuService.checkRouteConfigUnique(menu)) {
|
||||
return R.fail("新增菜单'" + menu.getMenuName() + "'失败,路由名称或地址已存在");
|
||||
}
|
||||
return toAjax(menuService.insertMenu(menu));
|
||||
}
|
||||
@@ -156,6 +158,8 @@ public class SysMenuController extends BaseController {
|
||||
return R.fail("修改菜单'" + menu.getMenuName() + "'失败,地址必须以http(s)://开头");
|
||||
} else if (menu.getMenuId().equals(menu.getParentId())) {
|
||||
return R.fail("修改菜单'" + menu.getMenuName() + "'失败,上级菜单不能选择自己");
|
||||
} else if (!menuService.checkRouteConfigUnique(menu)) {
|
||||
return R.fail("修改菜单'" + menu.getMenuName() + "'失败,路由名称或地址已存在");
|
||||
}
|
||||
return toAjax(menuService.updateMenu(menu));
|
||||
}
|
||||
|
||||
@@ -130,11 +130,11 @@ public class SysMenu extends BaseEntity {
|
||||
public String getRouterPath() {
|
||||
String routerPath = this.path;
|
||||
// 内链打开外网方式
|
||||
if (getParentId() != 0L && isInnerLink()) {
|
||||
if (!Constants.TOP_PARENT_ID.equals(getParentId()) && isInnerLink()) {
|
||||
routerPath = innerLinkReplaceEach(routerPath);
|
||||
}
|
||||
// 非外链并且是一级目录(类型为目录)
|
||||
if (0L == getParentId() && SystemConstants.TYPE_DIR.equals(getMenuType())
|
||||
if (Constants.TOP_PARENT_ID.equals(getParentId()) && SystemConstants.TYPE_DIR.equals(getMenuType())
|
||||
&& SystemConstants.NO_FRAME.equals(getIsFrame())) {
|
||||
routerPath = "/" + this.path;
|
||||
}
|
||||
@@ -152,7 +152,7 @@ public class SysMenu extends BaseEntity {
|
||||
String component = SystemConstants.LAYOUT;
|
||||
if (StringUtils.isNotEmpty(this.component) && !isMenuFrame()) {
|
||||
component = this.component;
|
||||
} else if (StringUtils.isEmpty(this.component) && getParentId() != 0L && isInnerLink()) {
|
||||
} else if (StringUtils.isEmpty(this.component) && !Constants.TOP_PARENT_ID.equals(getParentId()) && isInnerLink()) {
|
||||
component = SystemConstants.INNER_LINK;
|
||||
} else if (StringUtils.isEmpty(this.component) && isParentView()) {
|
||||
component = SystemConstants.PARENT_VIEW;
|
||||
@@ -164,7 +164,7 @@ public class SysMenu extends BaseEntity {
|
||||
* 是否为菜单内部跳转
|
||||
*/
|
||||
public boolean isMenuFrame() {
|
||||
return getParentId() == 0L && SystemConstants.TYPE_MENU.equals(menuType) && isFrame.equals(SystemConstants.NO_FRAME);
|
||||
return Constants.TOP_PARENT_ID.equals(getParentId()) && SystemConstants.TYPE_MENU.equals(menuType) && isFrame.equals(SystemConstants.NO_FRAME);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -178,7 +178,7 @@ public class SysMenu extends BaseEntity {
|
||||
* 是否为parent_view组件
|
||||
*/
|
||||
public boolean isParentView() {
|
||||
return getParentId() != 0L && SystemConstants.TYPE_DIR.equals(menuType);
|
||||
return !Constants.TOP_PARENT_ID.equals(getParentId()) && SystemConstants.TYPE_DIR.equals(menuType);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -78,7 +78,7 @@ public class SysUser extends TenantEntity {
|
||||
private String password;
|
||||
|
||||
/**
|
||||
* 帐号状态(0正常 1停用)
|
||||
* 账号状态(0正常 1停用)
|
||||
*/
|
||||
private String status;
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ public class SysUserOnline {
|
||||
private String deptName;
|
||||
|
||||
/**
|
||||
* 用户名称
|
||||
* 用户账号
|
||||
*/
|
||||
private String userName;
|
||||
|
||||
|
||||
@@ -78,7 +78,7 @@ public class SysUserBo extends BaseEntity {
|
||||
private String password;
|
||||
|
||||
/**
|
||||
* 帐号状态(0正常 1停用)
|
||||
* 账号状态(0正常 1停用)
|
||||
*/
|
||||
private String status;
|
||||
|
||||
|
||||
@@ -34,13 +34,13 @@ public class SysUserExportVo implements Serializable {
|
||||
/**
|
||||
* 用户账号
|
||||
*/
|
||||
@ExcelProperty(value = "登录名称")
|
||||
@ExcelProperty(value = "用户账号")
|
||||
private String userName;
|
||||
|
||||
/**
|
||||
* 用户昵称
|
||||
*/
|
||||
@ExcelProperty(value = "用户名称")
|
||||
@ExcelProperty(value = "用户昵称")
|
||||
private String nickName;
|
||||
|
||||
/**
|
||||
@@ -63,9 +63,9 @@ public class SysUserExportVo implements Serializable {
|
||||
private String sex;
|
||||
|
||||
/**
|
||||
* 帐号状态(0正常 1停用)
|
||||
* 账号状态(0正常 1停用)
|
||||
*/
|
||||
@ExcelProperty(value = "帐号状态", converter = ExcelDictConvert.class)
|
||||
@ExcelProperty(value = "账号状态", converter = ExcelDictConvert.class)
|
||||
@ExcelDictFormat(dictType = "sys_normal_disable")
|
||||
private String status;
|
||||
|
||||
|
||||
@@ -38,13 +38,13 @@ public class SysUserImportVo implements Serializable {
|
||||
/**
|
||||
* 用户账号
|
||||
*/
|
||||
@ExcelProperty(value = "登录名称")
|
||||
@ExcelProperty(value = "用户账号")
|
||||
private String userName;
|
||||
|
||||
/**
|
||||
* 用户昵称
|
||||
*/
|
||||
@ExcelProperty(value = "用户名称")
|
||||
@ExcelProperty(value = "用户昵称")
|
||||
private String nickName;
|
||||
|
||||
/**
|
||||
@@ -67,9 +67,9 @@ public class SysUserImportVo implements Serializable {
|
||||
private String sex;
|
||||
|
||||
/**
|
||||
* 帐号状态(0正常 1停用)
|
||||
* 账号状态(0正常 1停用)
|
||||
*/
|
||||
@ExcelProperty(value = "帐号状态", converter = ExcelDictConvert.class)
|
||||
@ExcelProperty(value = "账号状态", converter = ExcelDictConvert.class)
|
||||
@ExcelDictFormat(dictType = "sys_normal_disable")
|
||||
private String status;
|
||||
|
||||
|
||||
@@ -89,7 +89,7 @@ public class SysUserVo implements Serializable {
|
||||
private String password;
|
||||
|
||||
/**
|
||||
* 帐号状态(0正常 1停用)
|
||||
* 账号状态(0正常 1停用)
|
||||
*/
|
||||
private String status;
|
||||
|
||||
|
||||
@@ -160,4 +160,13 @@ public interface ISysMenuService {
|
||||
* @return 结果
|
||||
*/
|
||||
boolean checkMenuNameUnique(SysMenuBo menu);
|
||||
|
||||
/**
|
||||
* 校验路由组合是否唯一
|
||||
*
|
||||
* @param menu 菜单信息
|
||||
* @return 结果
|
||||
*/
|
||||
boolean checkRouteConfigUnique(SysMenuBo menu);
|
||||
|
||||
}
|
||||
|
||||
@@ -101,7 +101,7 @@ public interface ISysUserService {
|
||||
String selectUserPostGroup(Long userId);
|
||||
|
||||
/**
|
||||
* 校验用户名称是否唯一
|
||||
* 校验用户账号是否唯一
|
||||
*
|
||||
* @param user 用户信息
|
||||
* @return 结果
|
||||
@@ -174,7 +174,7 @@ public interface ISysUserService {
|
||||
* 修改用户状态
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param status 帐号状态
|
||||
* @param status 账号状态
|
||||
* @return 结果
|
||||
*/
|
||||
int updateUserStatus(Long userId, String status);
|
||||
|
||||
@@ -229,6 +229,9 @@ public class SysDictTypeServiceImpl implements ISysDictTypeService, DictService
|
||||
@Override
|
||||
public String getDictLabel(String dictType, String dictValue, String separator) {
|
||||
List<SysDictDataVo> datas = SpringUtils.getAopProxy(this).selectDictDataByType(dictType);
|
||||
if (CollUtil.isEmpty(datas)) {
|
||||
return StringUtils.EMPTY;
|
||||
}
|
||||
Map<String, String> map = StreamUtils.toMap(datas, SysDictDataVo::getDictValue, SysDictDataVo::getDictLabel);
|
||||
if (StringUtils.containsAny(dictValue, separator)) {
|
||||
return Arrays.stream(dictValue.split(separator))
|
||||
@@ -250,6 +253,9 @@ public class SysDictTypeServiceImpl implements ISysDictTypeService, DictService
|
||||
@Override
|
||||
public String getDictValue(String dictType, String dictLabel, String separator) {
|
||||
List<SysDictDataVo> datas = SpringUtils.getAopProxy(this).selectDictDataByType(dictType);
|
||||
if (CollUtil.isEmpty(datas)) {
|
||||
return StringUtils.EMPTY;
|
||||
}
|
||||
Map<String, String> map = StreamUtils.toMap(datas, SysDictDataVo::getDictLabel, SysDictDataVo::getDictValue);
|
||||
if (StringUtils.containsAny(dictLabel, separator)) {
|
||||
return Arrays.stream(dictLabel.split(separator))
|
||||
@@ -269,6 +275,9 @@ public class SysDictTypeServiceImpl implements ISysDictTypeService, DictService
|
||||
@Override
|
||||
public Map<String, String> getAllDictByDictType(String dictType) {
|
||||
List<SysDictDataVo> list = SpringUtils.getAopProxy(this).selectDictDataByType(dictType);
|
||||
if (CollUtil.isEmpty(list)) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
// 保证顺序
|
||||
LinkedHashMap<String, String> map = new LinkedHashMap<>();
|
||||
for (SysDictDataVo vo : list) {
|
||||
@@ -286,6 +295,9 @@ public class SysDictTypeServiceImpl implements ISysDictTypeService, DictService
|
||||
@Override
|
||||
public DictTypeDTO getDictType(String dictType) {
|
||||
SysDictTypeVo vo = SpringUtils.getAopProxy(this).selectDictTypeByType(dictType);
|
||||
if (ObjectUtil.isNull(vo)) {
|
||||
return null;
|
||||
}
|
||||
return BeanUtil.toBean(vo, DictTypeDTO.class);
|
||||
}
|
||||
|
||||
@@ -298,6 +310,9 @@ public class SysDictTypeServiceImpl implements ISysDictTypeService, DictService
|
||||
@Override
|
||||
public List<DictDataDTO> getDictData(String dictType) {
|
||||
List<SysDictDataVo> list = SpringUtils.getAopProxy(this).selectDictDataByType(dictType);
|
||||
if (CollUtil.isEmpty(list)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
return BeanUtil.copyToList(list, DictDataDTO.class);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import cn.hutool.core.lang.tree.Tree;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.common.core.constant.Constants;
|
||||
import org.dromara.common.core.constant.SystemConstants;
|
||||
import org.dromara.common.core.utils.MapstructUtils;
|
||||
@@ -29,13 +30,17 @@ import org.dromara.system.service.ISysMenuService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 菜单 业务层处理
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@Service
|
||||
public class SysMenuServiceImpl implements ISysMenuService {
|
||||
@@ -107,7 +112,7 @@ public class SysMenuServiceImpl implements ISysMenuService {
|
||||
/**
|
||||
* 根据用户ID查询菜单
|
||||
*
|
||||
* @param userId 用户名称
|
||||
* @param userId 用户ID
|
||||
* @return 菜单列表
|
||||
*/
|
||||
@Override
|
||||
@@ -353,6 +358,51 @@ public class SysMenuServiceImpl implements ISysMenuService {
|
||||
return !exist;
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验路由名称是否唯一
|
||||
*
|
||||
* @param menuBo 菜单信息
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public boolean checkRouteConfigUnique(SysMenuBo menuBo) {
|
||||
SysMenu menu = MapstructUtils.convert(menuBo, SysMenu.class);
|
||||
if (SystemConstants.TYPE_BUTTON.equals(menu.getMenuType())) {
|
||||
return true;
|
||||
}
|
||||
long menuId = ObjectUtil.isNull(menu.getMenuId()) ? -1L : menu.getMenuId();
|
||||
Long parentId = menu.getParentId();
|
||||
String path = menu.getPath();
|
||||
String routeName = StringUtils.isEmpty(menu.getRouteName()) ? path : menu.getRouteName();
|
||||
List<SysMenu> sysMenuList = baseMapper.selectList(
|
||||
new LambdaQueryWrapper<SysMenu>()
|
||||
.in(SysMenu::getMenuType, SystemConstants.TYPE_DIR, SystemConstants.TYPE_MENU)
|
||||
.and(w ->
|
||||
w.eq(SysMenu::getPath, path).or().eq(SysMenu::getPath, routeName)
|
||||
));
|
||||
for (SysMenu sysMenu : sysMenuList) {
|
||||
if (!sysMenu.getMenuId().equals(menuId)) {
|
||||
Long dbParentId = sysMenu.getParentId();
|
||||
String dbPath = sysMenu.getPath();
|
||||
String dbRouteName = StringUtils.isEmpty(sysMenu.getRouteName()) ? dbPath : sysMenu.getRouteName();
|
||||
if (StringUtils.equalsAnyIgnoreCase(path, dbPath) && parentId.equals(dbParentId)) {
|
||||
log.warn("[同级路由冲突] 同级下已存在相同路由路径 '{}',冲突菜单:{}", dbPath, sysMenu.getMenuName());
|
||||
return false;
|
||||
} else if (StringUtils.equalsAnyIgnoreCase(path, dbPath)
|
||||
&& Constants.TOP_PARENT_ID.equals(parentId)
|
||||
&& Constants.TOP_PARENT_ID.equals(dbParentId)) {
|
||||
log.warn("[根目录路由冲突] 根目录下路由 '{}' 必须唯一,已被菜单 '{}' 占用", path, sysMenu.getMenuName());
|
||||
return false;
|
||||
} else if (StringUtils.equalsAnyIgnoreCase(routeName, dbRouteName)
|
||||
&& sysMenu.getMenuType().equals(menu.getMenuType())) {
|
||||
log.warn("[路由名称冲突] 路由名称 '{}' 需全局唯一,已被菜单 '{}' 使用", routeName, sysMenu.getMenuName());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据父节点的ID获取所有子节点
|
||||
*
|
||||
|
||||
@@ -232,7 +232,7 @@ public class SysUserServiceImpl implements ISysUserService, UserService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验用户名称是否唯一
|
||||
* 校验用户账号是否唯一
|
||||
*
|
||||
* @param user 用户信息
|
||||
* @return 结果
|
||||
@@ -375,7 +375,7 @@ public class SysUserServiceImpl implements ISysUserService, UserService {
|
||||
* 修改用户状态
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param status 帐号状态
|
||||
* @param status 账号状态
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
@@ -497,6 +497,11 @@ public class SysUserServiceImpl implements ISysUserService, UserService {
|
||||
roleList.remove(SystemConstants.SUPER_ADMIN_ID);
|
||||
}
|
||||
|
||||
// 移除超管角色后若无剩余角色,说明仅选了超管角色且不允许分配,显式报错
|
||||
if (roleList.isEmpty()) {
|
||||
throw new ServiceException("不允许为普通用户分配超级管理员角色,请至少选择一个其他角色");
|
||||
}
|
||||
|
||||
// 校验是否有权限访问这些角色(含数据权限控制)
|
||||
if (roleMapper.selectRoleCount(roleList) != roleList.size()) {
|
||||
throw new ServiceException("没有权限访问角色的数据");
|
||||
@@ -594,10 +599,10 @@ public class SysUserServiceImpl implements ISysUserService, UserService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过用户ID查询用户账户
|
||||
* 通过用户ID查询用户昵称
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @return 用户账户
|
||||
* @return 用户昵称
|
||||
*/
|
||||
@Override
|
||||
@Cacheable(cacheNames = CacheNames.SYS_NICKNAME, key = "#userId")
|
||||
@@ -608,10 +613,10 @@ public class SysUserServiceImpl implements ISysUserService, UserService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过用户ID查询用户账户
|
||||
* 通过用户ID查询用户昵称
|
||||
*
|
||||
* @param userIds 用户ID 多个用逗号隔开
|
||||
* @return 用户账户
|
||||
* @return 用户昵称
|
||||
*/
|
||||
@Override
|
||||
public String selectNicknameByIds(String userIds) {
|
||||
@@ -751,13 +756,13 @@ public class SysUserServiceImpl implements ISysUserService, UserService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据用户 ID 列表查询用户名称映射关系
|
||||
* 根据用户 ID 列表查询用户昵称映射关系
|
||||
*
|
||||
* @param userIds 用户 ID 列表
|
||||
* @return Map,其中 key 为用户 ID,value 为对应的用户名称
|
||||
* @return Map,其中 key 为用户 ID,value 为对应的用户昵称
|
||||
*/
|
||||
@Override
|
||||
public Map<Long, String> selectUserNamesByIds(List<Long> userIds) {
|
||||
public Map<Long, String> selectUserNicksByIds(List<Long> userIds) {
|
||||
if (CollUtil.isEmpty(userIds)) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
@@ -23,26 +23,6 @@ public interface FlowConstant {
|
||||
*/
|
||||
String INITIATOR_DEPT_ID = "initiatorDeptId";
|
||||
|
||||
/**
|
||||
* 委托
|
||||
*/
|
||||
String DELEGATE_TASK = "delegateTask";
|
||||
|
||||
/**
|
||||
* 转办
|
||||
*/
|
||||
String TRANSFER_TASK = "transferTask";
|
||||
|
||||
/**
|
||||
* 加签
|
||||
*/
|
||||
String ADD_SIGNATURE = "addSignature";
|
||||
|
||||
/**
|
||||
* 减签
|
||||
*/
|
||||
String REDUCTION_SIGNATURE = "reductionSignature";
|
||||
|
||||
/**
|
||||
* 流程分类Id转名称
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
package org.dromara.workflow.common.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 任务操作类型枚举
|
||||
*
|
||||
* @author may
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum TaskOperationEnum {
|
||||
|
||||
/**
|
||||
* 委派
|
||||
*/
|
||||
DELEGATE_TASK("delegateTask", "委派"),
|
||||
|
||||
/**
|
||||
* 转办
|
||||
*/
|
||||
TRANSFER_TASK("transferTask", "转办"),
|
||||
|
||||
/**
|
||||
* 加签
|
||||
*/
|
||||
ADD_SIGNATURE("addSignature", "加签"),
|
||||
|
||||
/**
|
||||
* 减签
|
||||
*/
|
||||
REDUCTION_SIGNATURE("reductionSignature", "减签");
|
||||
|
||||
private final String code;
|
||||
private final String desc;
|
||||
|
||||
private static final Map<String, TaskOperationEnum> CODE_MAP = Arrays.stream(values())
|
||||
.collect(Collectors.toConcurrentMap(TaskOperationEnum::getCode, Function.identity()));
|
||||
|
||||
/**
|
||||
* 根据 code 获取枚举
|
||||
*/
|
||||
public static TaskOperationEnum getByCode(String code) {
|
||||
return CODE_MAP.get(code);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.dromara.workflow.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.dromara.common.core.domain.R;
|
||||
@@ -45,6 +46,7 @@ public class FlwDefinitionController extends BaseController {
|
||||
* @param pageQuery 分页
|
||||
*/
|
||||
@GetMapping("/list")
|
||||
@SaCheckPermission("workflow:definition:list")
|
||||
public TableDataInfo<FlowDefinitionVo> list(FlowDefinition flowDefinition, PageQuery pageQuery) {
|
||||
return flwDefinitionService.queryList(flowDefinition, pageQuery);
|
||||
}
|
||||
@@ -56,6 +58,7 @@ public class FlwDefinitionController extends BaseController {
|
||||
* @param pageQuery 分页
|
||||
*/
|
||||
@GetMapping("/unPublishList")
|
||||
@SaCheckPermission("workflow:definition:list")
|
||||
public TableDataInfo<FlowDefinitionVo> unPublishList(FlowDefinition flowDefinition, PageQuery pageQuery) {
|
||||
return flwDefinitionService.unPublishList(flowDefinition, pageQuery);
|
||||
}
|
||||
@@ -66,6 +69,7 @@ public class FlwDefinitionController extends BaseController {
|
||||
* @param id 流程定义id
|
||||
*/
|
||||
@GetMapping(value = "/{id}")
|
||||
@SaCheckPermission("workflow:definition:query")
|
||||
public R<Definition> getInfo(@PathVariable Long id) {
|
||||
return R.ok(defService.getById(id));
|
||||
}
|
||||
@@ -79,6 +83,7 @@ public class FlwDefinitionController extends BaseController {
|
||||
@PostMapping
|
||||
@RepeatSubmit()
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@SaCheckPermission("workflow:definition:add")
|
||||
public R<Boolean> add(@RequestBody FlowDefinition flowDefinition) {
|
||||
return R.ok(defService.checkAndSave(flowDefinition));
|
||||
}
|
||||
@@ -92,6 +97,7 @@ public class FlwDefinitionController extends BaseController {
|
||||
@PutMapping
|
||||
@RepeatSubmit()
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@SaCheckPermission("workflow:definition:edit")
|
||||
public R<Boolean> edit(@RequestBody FlowDefinition flowDefinition) {
|
||||
return R.ok(defService.updateById(flowDefinition));
|
||||
}
|
||||
@@ -104,6 +110,7 @@ public class FlwDefinitionController extends BaseController {
|
||||
@Log(title = "流程定义", businessType = BusinessType.INSERT)
|
||||
@PutMapping("/publish/{id}")
|
||||
@RepeatSubmit()
|
||||
@SaCheckPermission("workflow:definition:publish")
|
||||
public R<Boolean> publish(@PathVariable Long id) {
|
||||
return R.ok(flwDefinitionService.publish(id));
|
||||
}
|
||||
@@ -117,6 +124,7 @@ public class FlwDefinitionController extends BaseController {
|
||||
@PutMapping("/unPublish/{id}")
|
||||
@RepeatSubmit()
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@SaCheckPermission("workflow:definition:publish")
|
||||
public R<Boolean> unPublish(@PathVariable Long id) {
|
||||
return R.ok(defService.unPublish(id));
|
||||
}
|
||||
@@ -126,6 +134,7 @@ public class FlwDefinitionController extends BaseController {
|
||||
*/
|
||||
@Log(title = "流程定义", businessType = BusinessType.DELETE)
|
||||
@DeleteMapping("/{ids}")
|
||||
@SaCheckPermission("workflow:definition:remove")
|
||||
public R<Void> remove(@PathVariable List<Long> ids) {
|
||||
return toAjax(flwDefinitionService.removeDef(ids));
|
||||
}
|
||||
@@ -139,6 +148,7 @@ public class FlwDefinitionController extends BaseController {
|
||||
@PostMapping("/copy/{id}")
|
||||
@RepeatSubmit()
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@SaCheckPermission("workflow:definition:copy")
|
||||
public R<Boolean> copy(@PathVariable Long id) {
|
||||
return R.ok(defService.copyDef(id));
|
||||
}
|
||||
@@ -151,6 +161,7 @@ public class FlwDefinitionController extends BaseController {
|
||||
*/
|
||||
@Log(title = "流程定义", businessType = BusinessType.IMPORT)
|
||||
@PostMapping("/importDef")
|
||||
@SaCheckPermission("workflow:definition:import")
|
||||
public R<Boolean> importDef(MultipartFile file, String category) {
|
||||
return R.ok(flwDefinitionService.importJson(file, category));
|
||||
}
|
||||
@@ -164,6 +175,7 @@ public class FlwDefinitionController extends BaseController {
|
||||
*/
|
||||
@Log(title = "流程定义", businessType = BusinessType.EXPORT)
|
||||
@PostMapping("/exportDef/{id}")
|
||||
@SaCheckPermission("workflow:definition:export")
|
||||
public void exportDef(@PathVariable Long id, HttpServletResponse response) throws IOException {
|
||||
flwDefinitionService.exportDef(id, response);
|
||||
}
|
||||
@@ -174,6 +186,7 @@ public class FlwDefinitionController extends BaseController {
|
||||
* @param id 流程定义id
|
||||
*/
|
||||
@GetMapping("/xmlString/{id}")
|
||||
@SaCheckPermission("workflow:definition:query")
|
||||
public R<String> xmlString(@PathVariable Long id) {
|
||||
return R.ok("操作成功", defService.exportJson(id));
|
||||
}
|
||||
@@ -188,6 +201,7 @@ public class FlwDefinitionController extends BaseController {
|
||||
@PutMapping("/active/{id}")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@Log(title = "流程定义", businessType = BusinessType.UPDATE)
|
||||
@SaCheckPermission("workflow:definition:active")
|
||||
public R<Boolean> active(@PathVariable Long id, @RequestParam boolean active) {
|
||||
return R.ok(active ? defService.active(id) : defService.unActive(id));
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.dromara.workflow.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.dromara.common.core.domain.R;
|
||||
@@ -46,6 +47,7 @@ public class FlwInstanceController extends BaseController {
|
||||
* @param pageQuery 分页
|
||||
*/
|
||||
@GetMapping("/pageByRunning")
|
||||
@SaCheckPermission("workflow:instance:list")
|
||||
public TableDataInfo<FlowInstanceVo> selectRunningInstanceList(FlowInstanceBo flowInstanceBo, PageQuery pageQuery) {
|
||||
return flwInstanceService.selectRunningInstanceList(flowInstanceBo, pageQuery);
|
||||
}
|
||||
@@ -57,6 +59,7 @@ public class FlwInstanceController extends BaseController {
|
||||
* @param pageQuery 分页
|
||||
*/
|
||||
@GetMapping("/pageByFinish")
|
||||
@SaCheckPermission("workflow:instance:list")
|
||||
public TableDataInfo<FlowInstanceVo> selectFinishInstanceList(FlowInstanceBo flowInstanceBo, PageQuery pageQuery) {
|
||||
return flwInstanceService.selectFinishInstanceList(flowInstanceBo, pageQuery);
|
||||
}
|
||||
@@ -67,6 +70,7 @@ public class FlwInstanceController extends BaseController {
|
||||
* @param businessId 业务id
|
||||
*/
|
||||
@GetMapping("/getInfo/{businessId}")
|
||||
@SaCheckPermission("workflow:instance:query")
|
||||
public R<FlowInstanceVo> getInfo(@PathVariable Long businessId) {
|
||||
return R.ok(flwInstanceService.queryByBusinessId(businessId));
|
||||
}
|
||||
@@ -78,6 +82,7 @@ public class FlwInstanceController extends BaseController {
|
||||
*/
|
||||
@DeleteMapping("/deleteByBusinessIds/{businessIds}")
|
||||
@Log(title = "流程实例管理", businessType = BusinessType.DELETE)
|
||||
@SaCheckPermission("workflow:instance:remove")
|
||||
public R<Void> deleteByBusinessIds(@PathVariable List<Long> businessIds) {
|
||||
return toAjax(flwInstanceService.deleteByBusinessIds(StreamUtils.toList(businessIds, Convert::toStr)));
|
||||
}
|
||||
@@ -89,6 +94,7 @@ public class FlwInstanceController extends BaseController {
|
||||
*/
|
||||
@DeleteMapping("/deleteByInstanceIds/{instanceIds}")
|
||||
@Log(title = "流程实例管理", businessType = BusinessType.DELETE)
|
||||
@SaCheckPermission("workflow:instance:remove")
|
||||
public R<Void> deleteByInstanceIds(@PathVariable List<Long> instanceIds) {
|
||||
return toAjax(flwInstanceService.deleteByInstanceIds(instanceIds));
|
||||
}
|
||||
@@ -100,6 +106,7 @@ public class FlwInstanceController extends BaseController {
|
||||
*/
|
||||
@DeleteMapping("/deleteHisByInstanceIds/{instanceIds}")
|
||||
@Log(title = "流程实例管理", businessType = BusinessType.DELETE)
|
||||
@SaCheckPermission("workflow:instance:remove")
|
||||
public R<Void> deleteHisByInstanceIds(@PathVariable List<Long> instanceIds) {
|
||||
return toAjax(flwInstanceService.deleteHisByInstanceIds(instanceIds));
|
||||
}
|
||||
@@ -112,6 +119,7 @@ public class FlwInstanceController extends BaseController {
|
||||
@RepeatSubmit()
|
||||
@PutMapping("/cancelProcessApply")
|
||||
@Log(title = "流程实例管理", businessType = BusinessType.UPDATE)
|
||||
@SaCheckPermission("workflow:instance:cancel")
|
||||
public R<Void> cancelProcessApply(@RequestBody FlowCancelBo bo) {
|
||||
return toAjax(flwInstanceService.cancelProcessApply(bo));
|
||||
}
|
||||
@@ -125,6 +133,7 @@ public class FlwInstanceController extends BaseController {
|
||||
@RepeatSubmit()
|
||||
@PutMapping("/active/{id}")
|
||||
@Log(title = "流程实例管理", businessType = BusinessType.UPDATE)
|
||||
@SaCheckPermission("workflow:instance:active")
|
||||
public R<Boolean> active(@PathVariable Long id, @RequestParam boolean active) {
|
||||
return R.ok(active ? insService.active(id) : insService.unActive(id));
|
||||
}
|
||||
@@ -136,6 +145,7 @@ public class FlwInstanceController extends BaseController {
|
||||
* @param pageQuery 分页
|
||||
*/
|
||||
@GetMapping("/pageByCurrent")
|
||||
@SaCheckPermission("workflow:instance:currentList")
|
||||
public TableDataInfo<FlowInstanceVo> selectCurrentInstanceList(FlowInstanceBo flowInstanceBo, PageQuery pageQuery) {
|
||||
return flwInstanceService.selectCurrentInstanceList(flowInstanceBo, pageQuery);
|
||||
}
|
||||
@@ -146,6 +156,7 @@ public class FlwInstanceController extends BaseController {
|
||||
* @param businessId 业务id
|
||||
*/
|
||||
@GetMapping("/flowHisTaskList/{businessId}")
|
||||
@SaCheckPermission("workflow:instance:query")
|
||||
public R<Map<String, Object>> flowHisTaskList(@PathVariable String businessId) {
|
||||
return R.ok(flwInstanceService.flowHisTaskList(businessId));
|
||||
}
|
||||
@@ -156,6 +167,7 @@ public class FlwInstanceController extends BaseController {
|
||||
* @param instanceId 流程实例id
|
||||
*/
|
||||
@GetMapping("/instanceVariable/{instanceId}")
|
||||
@SaCheckPermission("workflow:instance:variableQuery")
|
||||
public R<Map<String, Object>> instanceVariable(@PathVariable Long instanceId) {
|
||||
return R.ok(flwInstanceService.instanceVariable(instanceId));
|
||||
}
|
||||
@@ -168,6 +180,7 @@ public class FlwInstanceController extends BaseController {
|
||||
@RepeatSubmit()
|
||||
@PutMapping("/updateVariable")
|
||||
@Log(title = "流程实例管理", businessType = BusinessType.UPDATE)
|
||||
@SaCheckPermission("workflow:instance:variable")
|
||||
public R<Void> updateVariable(@Validated @RequestBody FlowVariableBo bo) {
|
||||
return toAjax(flwInstanceService.updateVariable(bo));
|
||||
}
|
||||
@@ -180,6 +193,7 @@ public class FlwInstanceController extends BaseController {
|
||||
@Log(title = "流程实例管理", businessType = BusinessType.INSERT)
|
||||
@RepeatSubmit()
|
||||
@PostMapping("/invalid")
|
||||
@SaCheckPermission("workflow:instance:invalid")
|
||||
public R<Boolean> invalid(@Validated @RequestBody FlowInvalidBo bo) {
|
||||
return R.ok(flwInstanceService.processInvalid(bo));
|
||||
}
|
||||
|
||||
@@ -61,7 +61,8 @@ public class BackProcessBo implements Serializable {
|
||||
|
||||
public Map<String, Object> getVariables() {
|
||||
if (variables == null) {
|
||||
return new HashMap<>(16);
|
||||
variables = new HashMap<>(16);
|
||||
return variables;
|
||||
}
|
||||
variables.entrySet().removeIf(entry -> Objects.isNull(entry.getValue()));
|
||||
return variables;
|
||||
|
||||
@@ -23,8 +23,8 @@ public class FlowCopyBo implements Serializable {
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 用户名称
|
||||
* 用户昵称
|
||||
*/
|
||||
private String userName;
|
||||
private String nickName;
|
||||
|
||||
}
|
||||
|
||||
@@ -30,7 +30,8 @@ public class FlowNextNodeBo implements Serializable {
|
||||
|
||||
public Map<String, Object> getVariables() {
|
||||
if (variables == null) {
|
||||
return new HashMap<>(16);
|
||||
variables = new HashMap<>(16);
|
||||
return variables;
|
||||
}
|
||||
variables.entrySet().removeIf(entry -> Objects.isNull(entry.getValue()));
|
||||
return variables;
|
||||
|
||||
@@ -53,7 +53,8 @@ public class StartProcessBo implements Serializable {
|
||||
|
||||
public Map<String, Object> getVariables() {
|
||||
if (variables == null) {
|
||||
return new HashMap<>(16);
|
||||
variables = new HashMap<>(16);
|
||||
return variables;
|
||||
}
|
||||
variables.entrySet().removeIf(entry -> Objects.isNull(entry.getValue()));
|
||||
return variables;
|
||||
|
||||
@@ -40,6 +40,11 @@ public class TaskOperationBo implements Serializable {
|
||||
@NotNull(message = "任务id不能为空")
|
||||
private Long taskId;
|
||||
|
||||
/**
|
||||
* 消息类型
|
||||
*/
|
||||
private List<String> messageType;
|
||||
|
||||
/**
|
||||
* 意见或备注信息(可选)
|
||||
*/
|
||||
|
||||
@@ -24,10 +24,10 @@ public class FlowCopyVo implements Serializable {
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 用户名称
|
||||
* 用户昵称
|
||||
*/
|
||||
@Translation(type = TransConstant.USER_ID_TO_NICKNAME, mapper = "userId")
|
||||
private String userName;
|
||||
private String nickName;
|
||||
|
||||
public FlowCopyVo(Long userId) {
|
||||
this.userId = userId;
|
||||
|
||||
@@ -74,17 +74,19 @@ public class WorkflowGlobalListener implements GlobalListener {
|
||||
String ext = listenerVariable.getNode().getExt();
|
||||
if (StringUtils.isNotBlank(ext)) {
|
||||
Map<String, Object> variable = listenerVariable.getVariable();
|
||||
if (CollUtil.isNotEmpty(variable)) {
|
||||
if (CollUtil.isEmpty(variable)) {
|
||||
variable = new HashMap<>();
|
||||
}
|
||||
NodeExtVo nodeExt = nodeExtService.parseNodeExt(ext, variable);
|
||||
Set<String> copyList = nodeExt.getCopySettings();
|
||||
if (CollUtil.isNotEmpty(copyList)) {
|
||||
List<Long> userIds = StreamUtils.toList(copyList, Convert::toLong);
|
||||
Map<Long, String> nickNameMap = userService.selectUserNicksByIds(userIds);
|
||||
List<FlowCopyBo> list = StreamUtils.toList(copyList, x -> {
|
||||
FlowCopyBo bo = new FlowCopyBo();
|
||||
Long id = Convert.toLong(x);
|
||||
bo.setUserId(id);
|
||||
bo.setUserName(userService.selectUserNameById(id));
|
||||
bo.setNickName(nickNameMap.getOrDefault(id, StringUtils.EMPTY));
|
||||
return bo;
|
||||
});
|
||||
variable.put(FlowConstant.FLOW_COPY_LIST, list);
|
||||
@@ -159,7 +161,7 @@ public class WorkflowGlobalListener implements GlobalListener {
|
||||
flowTask.setPermissionList(List.of(userIdArray));
|
||||
// 移除已处理的状态变量
|
||||
variable.remove(nodeKey);
|
||||
FlowEngine.insService().removeVariables(flowTask.getInstanceId(),nodeKey);
|
||||
FlowEngine.insService().removeVariables(flowTask.getInstanceId(), nodeKey);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import cn.hutool.core.util.ObjectUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.common.core.domain.dto.UserDTO;
|
||||
import org.dromara.common.core.exception.ServiceException;
|
||||
import org.dromara.common.core.utils.SpringUtils;
|
||||
import org.dromara.common.core.utils.StreamUtils;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
@@ -98,7 +99,14 @@ public class FlwCommonServiceImpl implements IFlwCommonService {
|
||||
}
|
||||
case EMAIL_MESSAGE -> MailUtils.sendText(emails, subject, message);
|
||||
case SMS_MESSAGE -> {
|
||||
// TODO: 补充短信发送逻辑
|
||||
// LinkedHashMap<String, String> map = new LinkedHashMap<>(1);
|
||||
// // 根据具体短信服务商参数用法传参
|
||||
// map.put("code", "1234");
|
||||
// // 自动获取一个短信服务商
|
||||
// SmsBlend smsBlend = SmsFactory.getSmsBlend();
|
||||
// // 指定获取一个短信服务商 configKey
|
||||
// SmsBlend smsBlend = SmsFactory.getSmsBlend("config1");
|
||||
// SmsResponse smsResponse = smsBlend.sendMessage(phones, templateId, map);
|
||||
log.info("【短信发送 - TODO】用户数量={} 内容={}", userList.size(), message);
|
||||
}
|
||||
default -> log.warn("【消息发送】未处理的消息类型:{}", messageTypeEnum);
|
||||
@@ -119,6 +127,9 @@ public class FlwCommonServiceImpl implements IFlwCommonService {
|
||||
@Override
|
||||
public String applyNodeCode(Long definitionId) {
|
||||
List<Node> firstBetweenNode = FlowEngine.nodeService().getFirstBetweenNode(definitionId, new HashMap<>());
|
||||
if (CollUtil.isEmpty(firstBetweenNode)) {
|
||||
throw new ServiceException("流程定义缺少申请人节点,请检查流程定义配置");
|
||||
}
|
||||
return firstBetweenNode.get(0).getNodeCode();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,8 +111,14 @@ public class FlwInstanceServiceImpl implements IFlwInstanceService {
|
||||
@Override
|
||||
public FlowInstanceVo queryByBusinessId(Long businessId) {
|
||||
FlowInstance instance = this.selectInstByBusinessId(Convert.toStr(businessId));
|
||||
if (ObjectUtil.isNull(instance)) {
|
||||
throw new ServiceException(ExceptionCons.NOT_FOUNT_INSTANCE);
|
||||
}
|
||||
FlowInstanceVo instanceVo = BeanUtil.toBean(instance, FlowInstanceVo.class);
|
||||
Definition definition = defService.getById(instanceVo.getDefinitionId());
|
||||
if (ObjectUtil.isNull(definition)) {
|
||||
throw new ServiceException(ExceptionCons.NOT_FOUNT_DEF);
|
||||
}
|
||||
instanceVo.setFlowName(definition.getFlowName());
|
||||
instanceVo.setFlowCode(definition.getFlowCode());
|
||||
instanceVo.setVersion(definition.getVersion());
|
||||
@@ -187,6 +193,8 @@ public class FlwInstanceServiceImpl implements IFlwInstanceService {
|
||||
log.warn("未找到对应的流程实例信息,无法执行删除操作。");
|
||||
return false;
|
||||
}
|
||||
// 发送事件
|
||||
processDeleteHandler(flowInstances);
|
||||
return insService.remove(StreamUtils.toList(flowInstances, FlowInstance::getId));
|
||||
}
|
||||
|
||||
@@ -199,27 +207,13 @@ public class FlwInstanceServiceImpl implements IFlwInstanceService {
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean deleteByInstanceIds(List<Long> instanceIds) {
|
||||
// 获取实例信息
|
||||
List<Instance> instances = insService.getByIds(instanceIds);
|
||||
if (CollUtil.isEmpty(instances)) {
|
||||
List<FlowInstance> flowInstances = flowInstanceMapper.selectByIds(instanceIds);
|
||||
if (CollUtil.isEmpty(flowInstances)) {
|
||||
log.warn("未找到对应的流程实例信息,无法执行删除操作。");
|
||||
return false;
|
||||
}
|
||||
// 获取定义信息
|
||||
Map<Long, Definition> definitionMap = StreamUtils.toMap(
|
||||
defService.getByIds(StreamUtils.toList(instances, Instance::getDefinitionId)),
|
||||
Definition::getId,
|
||||
Function.identity()
|
||||
);
|
||||
|
||||
// 逐一触发删除事件
|
||||
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());
|
||||
});
|
||||
// 发送事件
|
||||
processDeleteHandler(flowInstances);
|
||||
// 删除实例
|
||||
return insService.remove(instanceIds);
|
||||
}
|
||||
@@ -233,26 +227,13 @@ public class FlwInstanceServiceImpl implements IFlwInstanceService {
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean deleteHisByInstanceIds(List<Long> instanceIds) {
|
||||
// 获取实例信息
|
||||
List<Instance> instances = insService.getByIds(instanceIds);
|
||||
if (CollUtil.isEmpty(instances)) {
|
||||
List<FlowInstance> flowInstances = flowInstanceMapper.selectByIds(instanceIds);
|
||||
if (CollUtil.isEmpty(flowInstances)) {
|
||||
log.warn("未找到对应的流程实例信息,无法执行删除操作。");
|
||||
return false;
|
||||
}
|
||||
// 获取定义信息
|
||||
Map<Long, Definition> definitionMap = StreamUtils.toMap(
|
||||
defService.getByIds(StreamUtils.toList(instances, Instance::getDefinitionId)),
|
||||
Definition::getId,
|
||||
Function.identity()
|
||||
);
|
||||
// 逐一触发删除事件
|
||||
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());
|
||||
});
|
||||
// 发送事件
|
||||
processDeleteHandler(flowInstances);
|
||||
List<FlowTask> flowTaskList = flwTaskService.selectByInstIds(instanceIds);
|
||||
if (CollUtil.isNotEmpty(flowTaskList)) {
|
||||
FlowEngine.userService().deleteByTaskIds(StreamUtils.toList(flowTaskList, FlowTask::getId));
|
||||
@@ -263,6 +244,35 @@ public class FlwInstanceServiceImpl implements IFlwInstanceService {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
private void processDeleteHandler(List<FlowInstance> flowInstances) {
|
||||
|
||||
String userId = LoginHelper.getUserIdStr();
|
||||
for (FlowInstance flowInstance : flowInstances) {
|
||||
//如果创建人与当前登陆人一致或者当前登陆人为管理员才能删除
|
||||
if (LoginHelper.isSuperAdmin() || flowInstance.getCreateBy().equals(userId)) {
|
||||
continue;
|
||||
}
|
||||
throw new ServiceException("权限不足,无法删除流程实例信息!");
|
||||
}
|
||||
// 获取定义信息
|
||||
Map<Long, Definition> definitionMap = StreamUtils.toMap(
|
||||
defService.getByIds(StreamUtils.toList(flowInstances, Instance::getDefinitionId)),
|
||||
Definition::getId,
|
||||
Function.identity()
|
||||
);
|
||||
|
||||
// 逐一触发删除事件
|
||||
flowInstances.forEach(instance -> {
|
||||
Definition definition = definitionMap.get(instance.getDefinitionId());
|
||||
if (ObjectUtil.isNull(definition)) {
|
||||
log.warn("实例 ID: {} 对应的流程定义信息未找到,跳过删除事件触发。", instance.getId());
|
||||
return;
|
||||
}
|
||||
flowProcessEventHandler.processDeleteHandler(definition.getFlowCode(), instance.getBusinessId());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 撤销流程
|
||||
*
|
||||
@@ -279,8 +289,11 @@ public class FlwInstanceServiceImpl implements IFlwInstanceService {
|
||||
if (definition == null) {
|
||||
throw new ServiceException(ExceptionCons.NOT_FOUNT_DEF);
|
||||
}
|
||||
String message = bo.getMessage();
|
||||
String userIdStr = LoginHelper.getUserIdStr();
|
||||
if (!LoginHelper.isSuperAdmin() && !instance.getCreateBy().equals(userIdStr)) {
|
||||
throw new ServiceException("权限不足,无法撤销流程!");
|
||||
}
|
||||
String message = bo.getMessage();
|
||||
BusinessStatusEnum.checkCancelStatus(instance.getFlowStatus());
|
||||
FlowParams flowParams = FlowParams.build()
|
||||
.message(message)
|
||||
@@ -383,6 +396,9 @@ public class FlwInstanceServiceImpl implements IFlwInstanceService {
|
||||
@Override
|
||||
public Map<String, Object> instanceVariable(Long instanceId) {
|
||||
FlowInstance flowInstance = flowInstanceMapper.selectById(instanceId);
|
||||
if (ObjectUtil.isNull(flowInstance)) {
|
||||
throw new ServiceException(ExceptionCons.NOT_FOUNT_INSTANCE);
|
||||
}
|
||||
Map<String, Object> variableMap = Optional.ofNullable(flowInstance.getVariableMap()).orElse(Collections.emptyMap());
|
||||
List<Map<String, Object>> variableList = variableMap.entrySet().stream()
|
||||
.map(entry -> Map.of("key", entry.getKey(), "value", entry.getValue()))
|
||||
|
||||
@@ -102,7 +102,7 @@ public class FlwNodeExtServiceImpl implements NodeExtService, IFlwNodeExtService
|
||||
* @param sources 数据来源(枚举类或字典类型)
|
||||
* @return 构建的 `NodeExt` 对象
|
||||
*/
|
||||
@SuppressWarnings("unchecked cast")
|
||||
@SuppressWarnings("unchecked")
|
||||
private NodeExt buildNodeExt(String code, String name, int type, List<Object> sources) {
|
||||
NodeExt nodeExt = new NodeExt();
|
||||
nodeExt.setCode(code);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.dromara.workflow.service.impl;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
@@ -9,6 +10,7 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.common.core.constant.SystemConstants;
|
||||
import org.dromara.common.core.domain.dto.TaskAssigneeDTO;
|
||||
import org.dromara.common.core.domain.model.TaskAssigneeBody;
|
||||
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;
|
||||
@@ -125,7 +127,14 @@ public class FlwSpelServiceImpl implements IFlwSpelService {
|
||||
* 保存前的数据校验
|
||||
*/
|
||||
private void validEntityBeforeSave(FlowSpel entity){
|
||||
//TODO 做一些数据校验,如唯一约束
|
||||
if (StringUtils.isNotBlank(entity.getViewSpel())) {
|
||||
boolean exists = baseMapper.exists(new LambdaQueryWrapper<FlowSpel>()
|
||||
.eq(FlowSpel::getViewSpel, entity.getViewSpel())
|
||||
.ne(ObjectUtil.isNotNull(entity.getId()), FlowSpel::getId, entity.getId()));
|
||||
if (exists) {
|
||||
throw new ServiceException("SpEL表达式已存在,请勿重复添加");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -137,7 +146,7 @@ public class FlwSpelServiceImpl implements IFlwSpelService {
|
||||
*/
|
||||
@Override
|
||||
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
|
||||
if(isValid){
|
||||
if (isValid){
|
||||
//TODO 做一些业务上的校验,判断是否需要校验
|
||||
}
|
||||
return baseMapper.deleteByIds(ids) > 0;
|
||||
|
||||
@@ -244,7 +244,7 @@ public class FlwTaskAssigneeServiceImpl implements IFlwTaskAssigneeService, Hand
|
||||
|
||||
List<Long> longIds = StreamUtils.toList(ids, Convert::toLong);
|
||||
Map<Long, String> rawMap = switch (type) {
|
||||
case USER -> userService.selectUserNamesByIds(longIds);
|
||||
case USER -> userService.selectUserNicksByIds(longIds);
|
||||
case ROLE -> roleService.selectRoleNamesByIds(longIds);
|
||||
case DEPT -> deptService.selectDeptNamesByIds(longIds);
|
||||
case POST -> postService.selectPostNamesByIds(longIds);
|
||||
|
||||
@@ -44,8 +44,8 @@ import org.dromara.warm.flow.orm.mapper.FlowInstanceMapper;
|
||||
import org.dromara.warm.flow.orm.mapper.FlowNodeMapper;
|
||||
import org.dromara.warm.flow.orm.mapper.FlowTaskMapper;
|
||||
import org.dromara.workflow.common.ConditionalOnEnable;
|
||||
import org.dromara.workflow.common.constant.FlowConstant;
|
||||
import org.dromara.workflow.common.enums.TaskAssigneeType;
|
||||
import org.dromara.workflow.common.enums.TaskOperationEnum;
|
||||
import org.dromara.workflow.common.enums.TaskStatusEnum;
|
||||
import org.dromara.workflow.domain.FlowInstanceBizExt;
|
||||
import org.dromara.workflow.domain.bo.*;
|
||||
@@ -127,6 +127,9 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
|
||||
// 已存在流程
|
||||
BusinessStatusEnum.checkStartStatus(flowInstance.getFlowStatus());
|
||||
List<Task> taskList = taskService.list(new FlowTask().setInstanceId(flowInstance.getId()));
|
||||
if (CollUtil.isEmpty(taskList)) {
|
||||
throw new ServiceException("流程实例缺少任务,请检查流程定义配置");
|
||||
}
|
||||
taskService.mergeVariable(flowInstance, variables);
|
||||
insService.updateById(flowInstance);
|
||||
StartProcessReturnDTO dto = new StartProcessReturnDTO();
|
||||
@@ -143,9 +146,9 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
|
||||
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);
|
||||
variables.put(FlowConstant.BUSINESS_CODE, this.generateBusinessCode(bizExt));
|
||||
boolean autoPass = !ObjectUtil.isNull(dict) && dict.getBool(AUTO_PASS);
|
||||
variables.put(AUTO_PASS, autoPass);
|
||||
variables.put(BUSINESS_CODE, this.generateBusinessCode(bizExt));
|
||||
FlowParams flowParams = FlowParams.build()
|
||||
.handler(startProcessBo.getHandler())
|
||||
.flowCode(startProcessBo.getFlowCode())
|
||||
@@ -156,6 +159,9 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
|
||||
this.buildFlowInstanceBizExt(instance, bizExt);
|
||||
// 申请人执行流程
|
||||
List<Task> taskList = taskService.list(new FlowTask().setInstanceId(instance.getId()));
|
||||
if (CollUtil.isEmpty(taskList)) {
|
||||
throw new ServiceException("流程启动失败,未生成任务");
|
||||
}
|
||||
if (taskList.size() > 1) {
|
||||
throw new ServiceException("请检查流程第一个环节是否为申请人!");
|
||||
}
|
||||
@@ -207,11 +213,11 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
|
||||
List<FlowCopyBo> flowCopyList = completeTaskBo.getFlowCopyList();
|
||||
// 设置抄送人
|
||||
Map<String, Object> variables = completeTaskBo.getVariables();
|
||||
variables.put(FlowConstant.FLOW_COPY_LIST, flowCopyList);
|
||||
variables.put(FLOW_COPY_LIST, flowCopyList);
|
||||
// 消息类型
|
||||
variables.put(FlowConstant.MESSAGE_TYPE, messageType);
|
||||
variables.put(MESSAGE_TYPE, messageType);
|
||||
// 消息通知
|
||||
variables.put(FlowConstant.MESSAGE_NOTICE, notice);
|
||||
variables.put(MESSAGE_NOTICE, notice);
|
||||
|
||||
|
||||
FlowTask flowTask = flowTaskMapper.selectById(taskId);
|
||||
@@ -219,9 +225,12 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
|
||||
throw new ServiceException("流程任务不存在或任务已审批!");
|
||||
}
|
||||
Instance ins = insService.getById(flowTask.getInstanceId());
|
||||
if (ObjectUtil.isNull(ins)) {
|
||||
throw new ServiceException("流程实例不存在");
|
||||
}
|
||||
// 检查流程状态是否为草稿、已撤销或已退回状态,若是则执行流程提交监听
|
||||
if (BusinessStatusEnum.isDraftOrCancelOrBack(ins.getFlowStatus())) {
|
||||
variables.put(FlowConstant.SUBMIT, true);
|
||||
variables.put(SUBMIT, true);
|
||||
}
|
||||
// 设置弹窗处理人
|
||||
Map<String, Object> assigneeMap = setPopAssigneeMap(completeTaskBo.getAssigneeMap(), ins.getVariableMap());
|
||||
@@ -274,9 +283,9 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
|
||||
flowParams.
|
||||
message("流程引擎自动审批!").
|
||||
variable(Map.of(
|
||||
FlowConstant.SUBMIT, false,
|
||||
FlowConstant.FLOW_COPY_LIST, Collections.emptyList(),
|
||||
FlowConstant.MESSAGE_NOTICE, StringUtils.EMPTY));
|
||||
SUBMIT, false,
|
||||
FLOW_COPY_LIST, Collections.emptyList(),
|
||||
MESSAGE_NOTICE, StringUtils.EMPTY));
|
||||
skipTask(task.getId(), flowParams, instanceId, true);
|
||||
}
|
||||
}
|
||||
@@ -341,7 +350,7 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
|
||||
FlowParams flowParams = FlowParams.build()
|
||||
.skipType(SkipType.NONE.getKey())
|
||||
.hisStatus(TaskStatusEnum.COPY.getStatus())
|
||||
.message("【抄送给】" + StreamUtils.join(flowCopyList, FlowCopyBo::getUserName));
|
||||
.message("【抄送给】" + StreamUtils.join(flowCopyList, FlowCopyBo::getNickName));
|
||||
HisTask hisTask = hisTaskService.setSkipHisTask(task, flowNode, flowParams);
|
||||
hisTask.setCreateTime(updateTime);
|
||||
hisTask.setUpdateTime(updateTime);
|
||||
@@ -482,15 +491,18 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
|
||||
throw new ServiceException("任务不存在!");
|
||||
}
|
||||
Instance inst = insService.getById(task.getInstanceId());
|
||||
if (ObjectUtil.isNull(inst)) {
|
||||
throw new ServiceException("流程实例不存在");
|
||||
}
|
||||
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(MESSAGE_TYPE, messageType);
|
||||
// 消息通知
|
||||
variable.put(FlowConstant.MESSAGE_NOTICE, notice);
|
||||
variable.put(MESSAGE_NOTICE, notice);
|
||||
|
||||
FlowParams flowParams = FlowParams.build()
|
||||
.nodeCode(bo.getNodeCode())
|
||||
@@ -513,6 +525,9 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
|
||||
@Override
|
||||
public List<Node> getBackTaskNode(Long taskId, String nowNodeCode) {
|
||||
FlowTask task = flowTaskMapper.selectById(taskId);
|
||||
if (ObjectUtil.isNull(task)) {
|
||||
throw new ServiceException("任务不存在!");
|
||||
}
|
||||
List<Node> nodeCodes = nodeService.getByNodeCodes(Collections.singletonList(nowNodeCode), task.getDefinitionId());
|
||||
if (!CollUtil.isNotEmpty(nodeCodes)) {
|
||||
return nodeCodes;
|
||||
@@ -597,7 +612,13 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
|
||||
}
|
||||
FlowTaskVo flowTaskVo = BeanUtil.toBean(task, FlowTaskVo.class);
|
||||
Instance instance = insService.getById(task.getInstanceId());
|
||||
if (ObjectUtil.isNull(instance)) {
|
||||
throw new ServiceException("流程实例不存在");
|
||||
}
|
||||
Definition definition = defService.getById(task.getDefinitionId());
|
||||
if (ObjectUtil.isNull(definition)) {
|
||||
throw new ServiceException("流程定义不存在");
|
||||
}
|
||||
flowTaskVo.setFlowStatus(instance.getFlowStatus());
|
||||
flowTaskVo.setVersion(definition.getVersion());
|
||||
flowTaskVo.setFlowCode(definition.getFlowCode());
|
||||
@@ -640,11 +661,23 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
|
||||
Long taskId = bo.getTaskId();
|
||||
Map<String, Object> variables = bo.getVariables();
|
||||
Task task = taskService.getById(taskId);
|
||||
if (ObjectUtil.isNull(task)) {
|
||||
throw new ServiceException("任务不存在!");
|
||||
}
|
||||
Instance instance = insService.getById(task.getInstanceId());
|
||||
if (ObjectUtil.isNull(instance)) {
|
||||
throw new ServiceException("流程实例不存在");
|
||||
}
|
||||
Definition definition = defService.getById(task.getDefinitionId());
|
||||
if (ObjectUtil.isNull(definition)) {
|
||||
throw new ServiceException("流程定义不存在");
|
||||
}
|
||||
Map<String, Object> mergeVariable = MapUtil.mergeAll(instance.getVariableMap(), variables);
|
||||
// 获取下一节点列表
|
||||
List<Node> nextNodeList = nodeService.getNextNodeList(task.getDefinitionId(), task.getNodeCode(), null, SkipType.PASS.getKey(), mergeVariable);
|
||||
if (CollUtil.isEmpty(nextNodeList)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
List<FlowNode> nextFlowNodes = BeanUtil.copyToList(nextNodeList, FlowNode.class);
|
||||
// 只获取中间节点
|
||||
nextFlowNodes = StreamUtils.filter(nextFlowNodes, node -> NodeType.BETWEEN.getKey().equals(node.getNodeType()));
|
||||
@@ -719,13 +752,19 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean taskOperation(TaskOperationBo bo, String taskOperation) {
|
||||
TaskOperationEnum op = TaskOperationEnum.getByCode(taskOperation);
|
||||
if (op == null) {
|
||||
log.error("Invalid operation type:{} ", taskOperation);
|
||||
throw new ServiceException("Invalid operation type " + taskOperation);
|
||||
}
|
||||
|
||||
FlowParams flowParams = FlowParams.build().message(bo.getMessage());
|
||||
if (LoginHelper.isSuperAdmin() || LoginHelper.isTenantAdmin()) {
|
||||
flowParams.ignore(true);
|
||||
}
|
||||
|
||||
// 根据操作类型构建 FlowParams
|
||||
switch (taskOperation) {
|
||||
switch (op) {
|
||||
case DELEGATE_TASK, TRANSFER_TASK -> {
|
||||
ValidatorUtils.validate(bo, AddGroup.class);
|
||||
flowParams.addHandlers(Collections.singletonList(bo.getUserId()));
|
||||
@@ -738,47 +777,63 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
|
||||
ValidatorUtils.validate(bo, EditGroup.class);
|
||||
flowParams.reductionHandlers(bo.getUserIds());
|
||||
}
|
||||
default -> {
|
||||
log.error("Invalid operation type:{} ", taskOperation);
|
||||
throw new ServiceException("Invalid operation type " + taskOperation);
|
||||
}
|
||||
}
|
||||
|
||||
Long taskId = bo.getTaskId();
|
||||
Task task = taskService.getById(taskId);
|
||||
if (ObjectUtil.isNull(task)) {
|
||||
throw new ServiceException("任务不存在!");
|
||||
}
|
||||
FlowNode flowNode = getByNodeCode(task.getNodeCode(), task.getDefinitionId());
|
||||
if (ADD_SIGNATURE.equals(taskOperation) || REDUCTION_SIGNATURE.equals(taskOperation)) {
|
||||
if (ObjectUtil.isNull(flowNode)) {
|
||||
throw new ServiceException("流程节点不存在");
|
||||
}
|
||||
if (op == TaskOperationEnum.ADD_SIGNATURE || op == TaskOperationEnum.REDUCTION_SIGNATURE) {
|
||||
if (CooperateType.isOrSign(flowNode.getNodeRatio())) {
|
||||
throw new ServiceException(task.getNodeName() + "不是会签或票签节点!");
|
||||
}
|
||||
}
|
||||
|
||||
// 设置任务状态并执行对应的任务操作
|
||||
switch (taskOperation) {
|
||||
//委派任务
|
||||
boolean result = false;
|
||||
switch (op) {
|
||||
case DELEGATE_TASK -> {
|
||||
flowParams.hisStatus(TaskStatusEnum.DEPUTE.getStatus());
|
||||
return taskService.depute(taskId, flowParams);
|
||||
result = taskService.depute(taskId, flowParams);
|
||||
}
|
||||
//转办任务
|
||||
case TRANSFER_TASK -> {
|
||||
flowParams.hisStatus(TaskStatusEnum.TRANSFER.getStatus());
|
||||
return taskService.transfer(taskId, flowParams);
|
||||
result = taskService.transfer(taskId, flowParams);
|
||||
}
|
||||
//加签,增加办理人
|
||||
case ADD_SIGNATURE -> {
|
||||
flowParams.hisStatus(TaskStatusEnum.SIGN.getStatus());
|
||||
return taskService.addSignature(taskId, flowParams);
|
||||
result = taskService.addSignature(taskId, flowParams);
|
||||
}
|
||||
//减签,减少办理人
|
||||
case REDUCTION_SIGNATURE -> {
|
||||
flowParams.hisStatus(TaskStatusEnum.SIGN_OFF.getStatus());
|
||||
return taskService.reductionSignature(taskId, flowParams);
|
||||
}
|
||||
default -> {
|
||||
log.error("Invalid operation type:{} ", taskOperation);
|
||||
throw new ServiceException("Invalid operation type " + taskOperation);
|
||||
result = taskService.reductionSignature(taskId, flowParams);
|
||||
}
|
||||
}
|
||||
|
||||
// 操作执行成功后再发送消息
|
||||
if (result && CollUtil.isNotEmpty(bo.getMessageType())) {
|
||||
List<Long> userIdList = new ArrayList<>();
|
||||
if (StrUtil.isNotBlank(bo.getUserId())) {
|
||||
userIdList.add(Convert.toLong(bo.getUserId()));
|
||||
}
|
||||
if (CollUtil.isNotEmpty(bo.getUserIds())) {
|
||||
userIdList.addAll(StreamUtils.toList(bo.getUserIds(), Convert::toLong));
|
||||
}
|
||||
if (CollUtil.isNotEmpty(userIdList)) {
|
||||
flwCommonService.sendMessage(
|
||||
bo.getMessageType(),
|
||||
StringUtils.isNotBlank(bo.getMessage()) ? bo.getMessage() : "单据「" + op.getDesc() + "」通知",
|
||||
"单据「" + op.getDesc() + "」提醒",
|
||||
userService.selectListByIds(userIdList)
|
||||
);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -65,8 +65,8 @@ services:
|
||||
network_mode: "host"
|
||||
|
||||
minio:
|
||||
# minio 最后一个未阉割版本 不能再进行升级 在往上的版本功能被阉割
|
||||
image: minio/minio:RELEASE.2025-04-22T22-12-26Z
|
||||
# pgsty 开源社区 fork 重新维护的最新版 minio
|
||||
image: pgsty/minio:RELEASE.2026-02-14T12-00-00Z
|
||||
container_name: minio
|
||||
ports:
|
||||
# api 端口
|
||||
@@ -99,7 +99,7 @@ services:
|
||||
network_mode: "host"
|
||||
|
||||
ruoyi-server1:
|
||||
image: ruoyi/ruoyi-server:5.5.2
|
||||
image: ruoyi/ruoyi-server:5.6.0
|
||||
container_name: ruoyi-server1
|
||||
environment:
|
||||
# 时区上海
|
||||
@@ -115,7 +115,7 @@ services:
|
||||
network_mode: "host"
|
||||
|
||||
ruoyi-server2:
|
||||
image: ruoyi/ruoyi-server:5.5.2
|
||||
image: ruoyi/ruoyi-server:5.6.0
|
||||
container_name: ruoyi-server2
|
||||
environment:
|
||||
# 时区上海
|
||||
@@ -131,7 +131,7 @@ services:
|
||||
network_mode: "host"
|
||||
|
||||
ruoyi-monitor-admin:
|
||||
image: ruoyi/ruoyi-monitor-admin:5.5.2
|
||||
image: ruoyi/ruoyi-monitor-admin:5.6.0
|
||||
container_name: ruoyi-monitor-admin
|
||||
environment:
|
||||
# 时区上海
|
||||
@@ -143,7 +143,7 @@ services:
|
||||
network_mode: "host"
|
||||
|
||||
ruoyi-snailjob-server:
|
||||
image: ruoyi/ruoyi-snailjob-server:5.5.2
|
||||
image: ruoyi/ruoyi-snailjob-server:5.6.0
|
||||
container_name: ruoyi-snailjob-server
|
||||
environment:
|
||||
# 时区上海
|
||||
|
||||
@@ -257,7 +257,7 @@ comment on column sys_user.phonenumber is '手机号码';
|
||||
comment on column sys_user.sex is '用户性别(0男 1女 2未知)';
|
||||
comment on column sys_user.avatar is '头像路径';
|
||||
comment on column sys_user.password is '密码';
|
||||
comment on column sys_user.status is '帐号状态(0正常 1停用)';
|
||||
comment on column sys_user.status is '账号状态(0正常 1停用)';
|
||||
comment on column sys_user.del_flag is '删除标志(0代表存在 1代表删除)';
|
||||
comment on column sys_user.login_ip is '最后登录IP';
|
||||
comment on column sys_user.login_date is '最后登录时间';
|
||||
|
||||
@@ -473,11 +473,11 @@ INSERT INTO sys_menu VALUES ('11618', '我的任务', '0', '7', 'task', '', '',
|
||||
INSERT INTO sys_menu VALUES ('11619', '我的待办', '11618', '2', 'taskWaiting', 'workflow/task/taskWaiting', '', '1', '1', 'C', '0', '0', '', 'waiting', 103, 1, SYSDATE, NULL, NULL, '');
|
||||
INSERT INTO sys_menu VALUES ('11632', '我的已办', '11618', '3', 'taskFinish', 'workflow/task/taskFinish', '', '1', '1', 'C', '0', '0', '', 'finish', 103, 1, SYSDATE, NULL, NULL, '');
|
||||
INSERT INTO sys_menu VALUES ('11633', '我的抄送', '11618', '4', 'taskCopyList', 'workflow/task/taskCopyList', '', '1', '1', 'C', '0', '0', '', 'my-copy', 103, 1, SYSDATE, NULL, NULL, '');
|
||||
INSERT INTO sys_menu VALUES ('11620', '流程定义', '11616', '3', 'processDefinition', 'workflow/processDefinition/index', '', '1', '1', 'C', '0', '0', '', 'process-definition', 103, 1, SYSDATE, NULL, NULL, '');
|
||||
INSERT INTO sys_menu VALUES ('11621', '流程实例', '11630', '1', 'processInstance', 'workflow/processInstance/index', '', '1', '1', 'C', '0', '0', '', 'tree-table', 103, 1, SYSDATE, NULL, NULL, '');
|
||||
INSERT INTO sys_menu VALUES ('11620', '流程定义', '11616', '3', 'processDefinition', 'workflow/processDefinition/index', '', '1', '1', 'C', '0', '0', 'workflow:definition:list', 'process-definition', 103, 1, SYSDATE, NULL, NULL, '');
|
||||
INSERT INTO sys_menu VALUES ('11621', '流程实例', '11630', '1', 'processInstance', 'workflow/processInstance/index', '', '1', '1', 'C', '0', '0', 'workflow:instance:list', 'tree-table', 103, 1, SYSDATE, NULL, NULL, '');
|
||||
INSERT INTO sys_menu VALUES ('11622', '流程分类', '11616', '1', 'category', 'workflow/category/index', '', '1', '0', 'C', '0', '0', 'workflow:category:list', 'category', 103, 1, SYSDATE, NULL, NULL, '');
|
||||
INSERT INTO sys_menu VALUES ('11629', '我发起的', '11618', '1', 'myDocument', 'workflow/task/myDocument', '', '1', '1', 'C', '0', '0', '', 'guide', 103, 1, SYSDATE, NULL, NULL, '');
|
||||
INSERT INTO sys_menu VALUES ('11630', '流程监控', '11616', '4', 'monitor', '', '', '1', '0', 'M', '0', '0', '', 'monitor', 103, 1, SYSDATE, NULL, NULL, '');
|
||||
INSERT INTO sys_menu VALUES ('11629', '我发起的', '11618', '1', 'myDocument', 'workflow/task/myDocument', '', '1', '1', 'C', '0', '0', 'workflow:instance:currentList', 'guide', 103, 1, SYSDATE, NULL, NULL, '');
|
||||
INSERT INTO sys_menu VALUES ('11630', '流程监控', '11616', '4', 'processMonitor', '', '', '1', '0', 'M', '0', '0', '', 'monitor', 103, 1, SYSDATE, NULL, NULL, '');
|
||||
INSERT INTO sys_menu VALUES ('11631', '待办任务', '11630', '2', 'allTaskWaiting', 'workflow/task/allTaskWaiting', '', '1', '1', 'C', '0', '0', '', 'waiting', 103, 1, SYSDATE, NULL, NULL, '');
|
||||
INSERT INTO sys_menu VALUES ('11700', '流程设计', '11616', '5', 'design/index', 'workflow/processDefinition/design', '', '1', '1', 'C', '1', '0', 'workflow:leave:edit', '#', 103, 1, SYSDATE, NULL, NULL, '/workflow/processDefinition');
|
||||
INSERT INTO sys_menu VALUES ('11701', '请假申请', '11616', '6', 'leaveEdit/index', 'workflow/leave/leaveEdit', '', '1', '1', 'C', '1', '0', 'workflow:leave:edit', '#', 103, 1, SYSDATE, NULL, NULL, '');
|
||||
@@ -488,6 +488,26 @@ INSERT INTO sys_menu VALUES ('11625', '流程分类修改', '11622', '3', '#', '
|
||||
INSERT INTO sys_menu VALUES ('11626', '流程分类删除', '11622', '4', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:category:remove', '#', 103, 1, SYSDATE, NULL, NULL, '');
|
||||
INSERT INTO sys_menu VALUES ('11627', '流程分类导出', '11622', '5', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:category:export', '#', 103, 1, SYSDATE, NULL, NULL, '');
|
||||
|
||||
-- 流程实例管理相关按钮
|
||||
INSERT INTO sys_menu VALUES ('11653', '流程实例查询', '11621', '1', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:instance:query', '#', 103, 1, SYSDATE, NULL, NULL, '');
|
||||
INSERT INTO sys_menu VALUES ('11654', '流程变量查询', '11621', '2', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:instance:variableQuery', '#', 103, 1, SYSDATE, NULL, NULL, '');
|
||||
INSERT INTO sys_menu VALUES ('11655', '流程变量修改', '11621', '3', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:instance:variable', '#', 103, 1, SYSDATE, NULL, NULL, '');
|
||||
INSERT INTO sys_menu VALUES ('11656', '流程实例激活/挂起', '11621', '4', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:instance:active', '#', 103, 1, SYSDATE, NULL, NULL, '');
|
||||
INSERT INTO sys_menu VALUES ('11657', '流程实例删除', '11621', '5', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:instance:remove', '#', 103, 1, SYSDATE, NULL, NULL, '');
|
||||
INSERT INTO sys_menu VALUES ('11658', '流程实例作废', '11621', '6', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:instance:invalid', '#', 103, 1, SYSDATE, NULL, NULL, '');
|
||||
INSERT INTO sys_menu VALUES ('11659', '流程实例撤销', '11621', '7', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:instance:cancel', '#', 103, 1, SYSDATE, NULL, NULL, '');
|
||||
|
||||
-- 流程定义管理相关按钮
|
||||
INSERT INTO sys_menu VALUES ('11644', '流程定义查询', '11620', '1', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:definition:query', '#', 103, 1, SYSDATE, NULL, NULL, '');
|
||||
INSERT INTO sys_menu VALUES ('11645', '流程定义新增', '11620', '2', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:definition:add', '#', 103, 1, SYSDATE, NULL, NULL, '');
|
||||
INSERT INTO sys_menu VALUES ('11646', '流程定义修改', '11620', '3', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:definition:edit', '#', 103, 1, SYSDATE, NULL, NULL, '');
|
||||
INSERT INTO sys_menu VALUES ('11647', '流程定义删除', '11620', '4', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:definition:remove', '#', 103, 1, SYSDATE, NULL, NULL, '');
|
||||
INSERT INTO sys_menu VALUES ('11648', '流程定义导出', '11620', '5', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:definition:export', '#', 103, 1, SYSDATE, NULL, NULL, '');
|
||||
INSERT INTO sys_menu VALUES ('11649', '流程定义导入', '11620', '6', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:definition:import', '#', 103, 1, SYSDATE, NULL, NULL, '');
|
||||
INSERT INTO sys_menu VALUES ('11650', '流程定义发布/取消发布', '11620', '7', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:definition:publish', '#', 103, 1, SYSDATE, NULL, NULL, '');
|
||||
INSERT INTO sys_menu VALUES ('11651', '流程定义复制', '11620', '8', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:definition:copy', '#', 103, 1, SYSDATE, NULL, NULL, '');
|
||||
INSERT INTO sys_menu VALUES ('11652', '流程定义激活/挂起', '11620', '9', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:definition:active', '#', 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, '');
|
||||
|
||||
@@ -258,7 +258,7 @@ comment on column sys_user.phonenumber is '手机号码';
|
||||
comment on column sys_user.sex is '用户性别(0男 1女 2未知)';
|
||||
comment on column sys_user.avatar is '头像地址';
|
||||
comment on column sys_user.password is '密码';
|
||||
comment on column sys_user.status is '帐号状态(0正常 1停用)';
|
||||
comment on column sys_user.status is '账号状态(0正常 1停用)';
|
||||
comment on column sys_user.del_flag is '删除标志(0代表存在 1代表删除)';
|
||||
comment on column sys_user.login_ip is '最后登陆IP';
|
||||
comment on column sys_user.login_date is '最后登陆时间';
|
||||
|
||||
@@ -451,11 +451,11 @@ INSERT INTO sys_menu VALUES ('11618', '我的任务', '0', '7', 'task', '', '',
|
||||
INSERT INTO sys_menu VALUES ('11619', '我的待办', '11618', '2', 'taskWaiting', 'workflow/task/taskWaiting', '', '1', '1', 'C', '0', '0', '', 'waiting', 103, 1, now(), NULL, NULL, '');
|
||||
INSERT INTO sys_menu VALUES ('11632', '我的已办', '11618', '3', 'taskFinish', 'workflow/task/taskFinish', '', '1', '1', 'C', '0', '0', '', 'finish', 103, 1, now(), NULL, NULL, '');
|
||||
INSERT INTO sys_menu VALUES ('11633', '我的抄送', '11618', '4', 'taskCopyList', 'workflow/task/taskCopyList', '', '1', '1', 'C', '0', '0', '', 'my-copy', 103, 1, now(), NULL, NULL, '');
|
||||
INSERT INTO sys_menu VALUES ('11620', '流程定义', '11616', '3', 'processDefinition', 'workflow/processDefinition/index', '', '1', '1', 'C', '0', '0', '', 'process-definition', 103, 1, now(), NULL, NULL, '');
|
||||
INSERT INTO sys_menu VALUES ('11621', '流程实例', '11630', '1', 'processInstance', 'workflow/processInstance/index', '', '1', '1', 'C', '0', '0', '', 'tree-table', 103, 1, now(), NULL, NULL, '');
|
||||
INSERT INTO sys_menu VALUES ('11620', '流程定义', '11616', '3', 'processDefinition', 'workflow/processDefinition/index', '', '1', '1', 'C', '0', '0', 'workflow:definition:list', 'process-definition', 103, 1, now(), NULL, NULL, '');
|
||||
INSERT INTO sys_menu VALUES ('11621', '流程实例', '11630', '1', 'processInstance', 'workflow/processInstance/index', '', '1', '1', 'C', '0', '0', 'workflow:instance:list', 'tree-table', 103, 1, now(), NULL, NULL, '');
|
||||
INSERT INTO sys_menu VALUES ('11622', '流程分类', '11616', '1', 'category', 'workflow/category/index', '', '1', '0', 'C', '0', '0', 'workflow:category:list', 'category', 103, 1, now(), NULL, NULL, '');
|
||||
INSERT INTO sys_menu VALUES ('11629', '我发起的', '11618', '1', 'myDocument', 'workflow/task/myDocument', '', '1', '1', 'C', '0', '0', '', 'guide', 103, 1, now(), NULL, NULL, '');
|
||||
INSERT INTO sys_menu VALUES ('11630', '流程监控', '11616', '4', 'monitor', '', '', '1', '0', 'M', '0', '0', '', 'monitor', 103, 1, now(), NULL, NULL, '');
|
||||
INSERT INTO sys_menu VALUES ('11629', '我发起的', '11618', '1', 'myDocument', 'workflow/task/myDocument', '', '1', '1', 'C', '0', '0', 'workflow:instance:currentList', 'guide', 103, 1, now(), NULL, NULL, '');
|
||||
INSERT INTO sys_menu VALUES ('11630', '流程监控', '11616', '4', 'processMonitor', '', '', '1', '0', 'M', '0', '0', '', 'monitor', 103, 1, now(), NULL, NULL, '');
|
||||
INSERT INTO sys_menu VALUES ('11631', '待办任务', '11630', '2', 'allTaskWaiting', 'workflow/task/allTaskWaiting', '', '1', '1', 'C', '0', '0', '', 'waiting', 103, 1, now(), NULL, NULL, '');
|
||||
INSERT INTO sys_menu VALUES ('11700', '流程设计', '11616', '5', 'design/index', 'workflow/processDefinition/design', '', '1', '1', 'C', '1', '0', 'workflow:leave:edit', '#', 103, 1, now(), NULL, NULL, '/workflow/processDefinition');
|
||||
INSERT INTO sys_menu VALUES ('11701', '请假申请', '11616', '6', 'leaveEdit/index', 'workflow/leave/leaveEdit', '', '1', '1', 'C', '1', '0', 'workflow:leave:edit', '#', 103, 1, now(), NULL, NULL, '');
|
||||
@@ -466,6 +466,26 @@ INSERT INTO sys_menu VALUES ('11625', '流程分类修改', '11622', '3', '#', '
|
||||
INSERT INTO sys_menu VALUES ('11626', '流程分类删除', '11622', '4', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:category:remove', '#', 103, 1, now(), NULL, NULL, '');
|
||||
INSERT INTO sys_menu VALUES ('11627', '流程分类导出', '11622', '5', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:category:export', '#', 103, 1, now(), NULL, NULL, '');
|
||||
|
||||
-- 流程实例管理相关按钮
|
||||
INSERT INTO sys_menu VALUES ('11653', '流程实例查询', '11621', '1', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:instance:query', '#', 103, 1, now(), NULL, NULL, '');
|
||||
INSERT INTO sys_menu VALUES ('11654', '流程变量查询', '11621', '2', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:instance:variableQuery', '#', 103, 1, now(), NULL, NULL, '');
|
||||
INSERT INTO sys_menu VALUES ('11655', '流程变量修改', '11621', '3', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:instance:variable', '#', 103, 1, now(), NULL, NULL, '');
|
||||
INSERT INTO sys_menu VALUES ('11656', '流程实例激活/挂起', '11621', '4', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:instance:active', '#', 103, 1, now(), NULL, NULL, '');
|
||||
INSERT INTO sys_menu VALUES ('11657', '流程实例删除', '11621', '5', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:instance:remove', '#', 103, 1, now(), NULL, NULL, '');
|
||||
INSERT INTO sys_menu VALUES ('11658', '流程实例作废', '11621', '6', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:instance:invalid', '#', 103, 1, now(), NULL, NULL, '');
|
||||
INSERT INTO sys_menu VALUES ('11659', '流程实例撤销', '11621', '7', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:instance:cancel', '#', 103, 1, now(), NULL, NULL, '');
|
||||
|
||||
-- 流程定义管理相关按钮
|
||||
INSERT INTO sys_menu VALUES ('11644', '流程定义查询', '11620', '1', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:definition:query', '#', 103, 1, now(), NULL, NULL, '');
|
||||
INSERT INTO sys_menu VALUES ('11645', '流程定义新增', '11620', '2', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:definition:add', '#', 103, 1, now(), NULL, NULL, '');
|
||||
INSERT INTO sys_menu VALUES ('11646', '流程定义修改', '11620', '3', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:definition:edit', '#', 103, 1, now(), NULL, NULL, '');
|
||||
INSERT INTO sys_menu VALUES ('11647', '流程定义删除', '11620', '4', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:definition:remove', '#', 103, 1, now(), NULL, NULL, '');
|
||||
INSERT INTO sys_menu VALUES ('11648', '流程定义导出', '11620', '5', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:definition:export', '#', 103, 1, now(), NULL, NULL, '');
|
||||
INSERT INTO sys_menu VALUES ('11649', '流程定义导入', '11620', '6', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:definition:import', '#', 103, 1, now(), NULL, NULL, '');
|
||||
INSERT INTO sys_menu VALUES ('11650', '流程定义发布/取消发布', '11620', '7', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:definition:publish', '#', 103, 1, now(), NULL, NULL, '');
|
||||
INSERT INTO sys_menu VALUES ('11651', '流程定义复制', '11620', '8', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:definition:copy', '#', 103, 1, now(), NULL, NULL, '');
|
||||
INSERT INTO sys_menu VALUES ('11652', '流程定义激活/挂起', '11620', '9', '#', '', '', '1', '0', 'F', '0', '0', 'workflow:definition:active', '#', 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, '');
|
||||
|
||||
@@ -148,7 +148,7 @@ create table sys_user (
|
||||
sex char(1) default '0' comment '用户性别(0男 1女 2未知)',
|
||||
avatar bigint(20) comment '头像地址',
|
||||
password varchar(100) default '' comment '密码',
|
||||
status char(1) default '0' comment '帐号状态(0正常 1停用)',
|
||||
status char(1) default '0' comment '账号状态(0正常 1停用)',
|
||||
del_flag char(1) default '0' comment '删除标志(0代表存在 1代表删除)',
|
||||
login_ip varchar(128) default '' comment '最后登录IP',
|
||||
login_date datetime comment '最后登录时间',
|
||||
|
||||
@@ -263,12 +263,12 @@ insert into sys_menu values ('11618', '我的任务', '0', '7', 'task', '', '',
|
||||
insert into sys_menu values ('11619', '我的待办', '11618', '2', 'taskWaiting', 'workflow/task/taskWaiting', '', '1', '1', 'C', '0', '0', '', 'waiting', 103, 1, sysdate(), NULL, NULL, '');
|
||||
insert into sys_menu values ('11632', '我的已办', '11618', '3', 'taskFinish', 'workflow/task/taskFinish', '', '1', '1', 'C', '0', '0', '', 'finish', 103, 1, sysdate(), NULL, NULL, '');
|
||||
insert into sys_menu values ('11633', '我的抄送', '11618', '4', 'taskCopyList', 'workflow/task/taskCopyList', '', '1', '1', 'C', '0', '0', '', 'my-copy', 103, 1, sysdate(), NULL, NULL, '');
|
||||
insert into sys_menu values ('11620', '流程定义', '11616', '3', 'processDefinition', 'workflow/processDefinition/index', '', '1', '1', 'C', '0', '0', '', 'process-definition', 103, 1, sysdate(), NULL, NULL, '');
|
||||
insert into sys_menu values ('11621', '流程实例', '11630', '1', 'processInstance', 'workflow/processInstance/index', '', '1', '1', 'C', '0', '0', '', 'tree-table', 103, 1, sysdate(), NULL, NULL, '');
|
||||
insert into sys_menu values ('11620', '流程定义', '11616', '3', 'processDefinition', 'workflow/processDefinition/index', '', '1', '1', 'C', '0', '0', 'workflow:definition:list', 'process-definition', 103, 1, sysdate(), NULL, NULL, '');
|
||||
insert into sys_menu values ('11621', '流程实例', '11630', '1', 'processInstance', 'workflow/processInstance/index', '', '1', '1', 'C', '0', '0', 'workflow:instance:list', 'tree-table', 103, 1, sysdate(), NULL, NULL, '');
|
||||
insert into sys_menu values ('11622', '流程分类', '11616', '1', 'category', 'workflow/category/index', '', '1', '0', 'C', '0', '0', 'workflow:category:list', 'category', 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 ('11629', '我发起的', '11618', '1', 'myDocument', 'workflow/task/myDocument', '', '1', '1', 'C', '0', '0', '', 'guide', 103, 1, sysdate(), NULL, NULL, '');
|
||||
insert into sys_menu values ('11630', '流程监控', '11616', '4', 'monitor', '', '', '1', '0', 'M', '0', '0', '', 'monitor', 103, 1, sysdate(), NULL, NULL, '');
|
||||
insert into sys_menu values ('11629', '我发起的', '11618', '1', 'myDocument', 'workflow/task/myDocument', '', '1', '1', 'C', '0', '0', 'workflow:instance:currentList', 'guide', 103, 1, sysdate(), NULL, NULL, '');
|
||||
insert into sys_menu values ('11630', '流程监控', '11616', '4', 'processMonitor', '', '', '1', '0', 'M', '0', '0', '', 'monitor', 103, 1, sysdate(), NULL, NULL, '');
|
||||
insert into sys_menu values ('11631', '待办任务', '11630', '2', 'allTaskWaiting', 'workflow/task/allTaskWaiting', '', '1', '1', 'C', '0', '0', '', 'waiting', 103, 1, sysdate(), NULL, NULL, '');
|
||||
insert into sys_menu values ('11700', '流程设计', '11616', '5', 'design/index', 'workflow/processDefinition/design', '', 1, 1, 'C', '1', '0', 'workflow:leave:edit', '#', 103, 1, sysdate(), null, null, '/workflow/processDefinition');
|
||||
insert into sys_menu values ('11701', '请假申请', '11616', '6', 'leaveEdit/index', 'workflow/leave/leaveEdit', '', 1, 1, 'C', '1', '0', 'workflow:leave:edit', '#', 103, 1, sysdate(), null, null, '');
|
||||
@@ -278,6 +278,26 @@ insert into sys_menu values ('11624', '流程分类新增', '11622', '2', '#', '
|
||||
insert into sys_menu values ('11625', '流程分类修改', '11622', '3', '#', '', '', 1, 0, 'F', '0', '0', 'workflow:category:edit', '#', 103, 1,sysdate(), null, null, '');
|
||||
insert into sys_menu values ('11626', '流程分类删除', '11622', '4', '#', '', '', 1, 0, 'F', '0', '0', 'workflow:category:remove', '#', 103,1, sysdate(), null, null, '');
|
||||
insert into sys_menu values ('11627', '流程分类导出', '11622', '5', '#', '', '', 1, 0, 'F', '0', '0', 'workflow:category:export', '#', 103,1, sysdate(), null, null, '');
|
||||
|
||||
-- 流程实例管理相关按钮
|
||||
insert into sys_menu values ('11653', '流程实例查询', '11621', '1', '#', '', '', 1, 0, 'F', '0', '0', 'workflow:instance:query', '#', 103, 1, sysdate(), null, null, '');
|
||||
insert into sys_menu values ('11654', '流程变量查询', '11621', '2', '#', '', '', 1, 0, 'F', '0', '0', 'workflow:instance:variableQuery', '#', 103, 1, sysdate(), null, null, '');
|
||||
insert into sys_menu values ('11655', '流程变量修改', '11621', '3', '#', '', '', 1, 0, 'F', '0', '0', 'workflow:instance:variable', '#', 103, 1, sysdate(), null, null, '');
|
||||
insert into sys_menu values ('11656', '流程实例激活/挂起', '11621', '4', '#', '', '', 1, 0, 'F', '0', '0', 'workflow:instance:active', '#', 103, 1, sysdate(), null, null, '');
|
||||
insert into sys_menu values ('11657', '流程实例删除', '11621', '5', '#', '', '', 1, 0, 'F', '0', '0', 'workflow:instance:remove', '#', 103, 1, sysdate(), null, null, '');
|
||||
insert into sys_menu values ('11658', '流程实例作废', '11621', '6', '#', '', '', 1, 0, 'F', '0', '0', 'workflow:instance:invalid', '#', 103, 1, sysdate(), null, null, '');
|
||||
insert into sys_menu values ('11659', '流程实例撤销', '11621', '7', '#', '', '', 1, 0, 'F', '0', '0', 'workflow:instance:cancel', '#', 103, 1, sysdate(), null, null, '');
|
||||
|
||||
-- 流程定义管理相关按钮
|
||||
insert into sys_menu values ('11644', '流程定义查询', '11620', '1', '#', '', '', 1, 0, 'F', '0', '0', 'workflow:definition:query', '#', 103, 1, sysdate(), null, null, '');
|
||||
insert into sys_menu values ('11645', '流程定义新增', '11620', '2', '#', '', '', 1, 0, 'F', '0', '0', 'workflow:definition:add', '#', 103, 1, sysdate(), null, null, '');
|
||||
insert into sys_menu values ('11646', '流程定义修改', '11620', '3', '#', '', '', 1, 0, 'F', '0', '0', 'workflow:definition:edit', '#', 103, 1, sysdate(), null, null, '');
|
||||
insert into sys_menu values ('11647', '流程定义删除', '11620', '4', '#', '', '', 1, 0, 'F', '0', '0', 'workflow:definition:remove', '#', 103, 1, sysdate(), null, null, '');
|
||||
insert into sys_menu values ('11648', '流程定义导出', '11620', '5', '#', '', '', 1, 0, 'F', '0', '0', 'workflow:definition:export', '#', 103, 1, sysdate(), null, null, '');
|
||||
insert into sys_menu values ('11649', '流程定义导入', '11620', '6', '#', '', '', 1, 0, 'F', '0', '0', 'workflow:definition:import', '#', 103, 1, sysdate(), null, null, '');
|
||||
insert into sys_menu values ('11650', '流程定义发布/取消发布', '11620', '7', '#', '', '', 1, 0, 'F', '0', '0', 'workflow:definition:publish', '#', 103, 1, sysdate(), null, null, '');
|
||||
insert into sys_menu values ('11651', '流程定义复制', '11620', '8', '#', '', '', 1, 0, 'F', '0', '0', 'workflow:definition:copy', '#', 103, 1, sysdate(), null, null, '');
|
||||
insert into sys_menu values ('11652', '流程定义激活/挂起', '11620', '9', '#', '', '', 1, 0, 'F', '0', '0', 'workflow:definition:active', '#', 103, 1, sysdate(), null, null, '');
|
||||
-- 流程表达式管理相关按钮
|
||||
INSERT INTO sys_menu VALUES ('11802', '流程达式定义查询', '11801', 1, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:query', '#', 103, 1, sysdate(), NULL, NULL, '');
|
||||
INSERT INTO sys_menu VALUES ('11803', '流程达式定义新增', '11801', 2, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:add', '#', 103, 1, sysdate(), NULL, NULL, '');
|
||||
|
||||
@@ -2802,7 +2802,7 @@ EXEC sys.sp_addextendedproperty
|
||||
'COLUMN', N'password'
|
||||
GO
|
||||
EXEC sys.sp_addextendedproperty
|
||||
'MS_Description', N'帐号状态(0正常 1停用)' ,
|
||||
'MS_Description', N'账号状态(0正常 1停用)' ,
|
||||
'SCHEMA', N'dbo',
|
||||
'TABLE', N'sys_user',
|
||||
'COLUMN', N'status'
|
||||
|
||||
@@ -1573,13 +1573,13 @@ INSERT sys_menu VALUES (11633, N'我的抄送', 11618, 4, N'taskCopyList', N'wor
|
||||
GO
|
||||
INSERT sys_menu VALUES (11620, N'流程定义', 11616, 3, N'processDefinition', N'workflow/processDefinition/index', N'', 1, 1, N'C', N'0', N'0', N'', N'process-definition', 103, 1, GETDATE(), NULL, NULL, N'');
|
||||
GO
|
||||
INSERT sys_menu VALUES (11621, N'流程实例', 11630, 1, N'processInstance', N'workflow/processInstance/index', N'', 1, 1, N'C', N'0', N'0', N'', N'tree-table', 103, 1, GETDATE(), NULL, NULL, N'');
|
||||
INSERT sys_menu VALUES (11621, N'流程实例', 11630, 1, N'processInstance', N'workflow/processInstance/index', N'', 1, 1, N'C', N'0', N'0', N'workflow:instance:list', N'tree-table', 103, 1, GETDATE(), NULL, NULL, N'');
|
||||
GO
|
||||
INSERT sys_menu VALUES (11622, N'流程分类', 11616, 1, N'category', N'workflow/category/index', N'', 1, 0, N'C', N'0', N'0', N'workflow:category:list', N'category', 103, 1, GETDATE(), NULL, NULL, N'');
|
||||
GO
|
||||
INSERT sys_menu VALUES (11629, N'我发起的', 11618, 1, N'myDocument', N'workflow/task/myDocument', N'', 1, 1, N'C', N'0', N'0', N'', N'guide', 103, 1, GETDATE(), NULL, NULL, N'');
|
||||
INSERT sys_menu VALUES (11629, N'我发起的', 11618, 1, N'myDocument', N'workflow/task/myDocument', N'', 1, 1, N'C', N'0', N'0', N'workflow:instance:currentList', N'guide', 103, 1, GETDATE(), NULL, NULL, N'');
|
||||
GO
|
||||
INSERT sys_menu VALUES (11630, N'流程监控', 11616, 4, N'monitor', NULL, N'', 1, 0, N'M', N'0', N'0', N'', N'monitor', 103, 1, GETDATE(), NULL, NULL, N'');
|
||||
INSERT sys_menu VALUES (11630, N'流程监控', 11616, 4, N'processMonitor', NULL, N'', 1, 0, N'M', N'0', N'0', N'', N'monitor', 103, 1, GETDATE(), NULL, NULL, N'');
|
||||
GO
|
||||
INSERT sys_menu VALUES (11631, N'待办任务', 11630, 2, N'allTaskWaiting', N'workflow/task/allTaskWaiting', N'', 1, 1, N'C', N'0', N'0', N'', N'waiting', 103, 1, GETDATE(), NULL, NULL, N'');
|
||||
GO
|
||||
@@ -1600,6 +1600,22 @@ GO
|
||||
INSERT sys_menu VALUES (11627, N'流程分类导出', 11622, 5, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'workflow:category:export', N'#', 103, 1, GETDATE(), NULL, NULL, N'');
|
||||
GO
|
||||
|
||||
-- 流程实例管理相关按钮
|
||||
INSERT sys_menu VALUES (11653, N'流程实例查询', 11621, 1, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'workflow:instance:query', N'#', 103, 1, GETDATE(), NULL, NULL, N'');
|
||||
GO
|
||||
INSERT sys_menu VALUES (11654, N'流程变量查询', 11621, 2, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'workflow:instance:variableQuery', N'#', 103, 1, GETDATE(), NULL, NULL, N'');
|
||||
GO
|
||||
INSERT sys_menu VALUES (11655, N'流程变量修改', 11621, 3, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'workflow:instance:variable', N'#', 103, 1, GETDATE(), NULL, NULL, N'');
|
||||
GO
|
||||
INSERT sys_menu VALUES (11656, N'流程实例激活/挂起', 11621, 4, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'workflow:instance:active', N'#', 103, 1, GETDATE(), NULL, NULL, N'');
|
||||
GO
|
||||
INSERT sys_menu VALUES (11657, N'流程实例删除', 11621, 5, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'workflow:instance:remove', N'#', 103, 1, GETDATE(), NULL, NULL, N'');
|
||||
GO
|
||||
INSERT sys_menu VALUES (11658, N'流程实例作废', 11621, 6, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'workflow:instance:invalid', N'#', 103, 1, GETDATE(), NULL, NULL, N'');
|
||||
GO
|
||||
INSERT sys_menu VALUES (11659, N'流程实例撤销', 11621, 7, N'#', N'', N'', 1, 0, N'F', N'0', N'0', N'workflow:instance:cancel', N'#', 103, 1, GETDATE(), NULL, NULL, N'');
|
||||
GO
|
||||
|
||||
INSERT sys_menu VALUES (11801, N'流程表达式', N'11616', 2, N'spel', N'workflow/spel/index', N'', 1, 0, N'C', N'0', N'0', N'workflow:spel:list', N'input', 103, 1, GETDATE(), 1, GETDATE(), N'流程达式定义菜单');
|
||||
GO
|
||||
INSERT sys_menu VALUES (11802, N'流程spel表达式定义查询', N'11801', 1, N'#', N'', NULL, 1, 0, N'F', N'0', N'0', N'workflow:spel:query', N'#', 103, 1, GETDATE(), NULL, NULL, N'');
|
||||
|
||||
24
script/sql/update/oracle/update_5.5.3_5.6.0.sql
Normal file
24
script/sql/update/oracle/update_5.5.3_5.6.0.sql
Normal file
@@ -0,0 +1,24 @@
|
||||
-- 修正已有菜单权限标识
|
||||
update sys_menu set perms = 'workflow:definition:list' where menu_id = '11620';
|
||||
update sys_menu set perms = 'workflow:instance:list' where menu_id = '11621';
|
||||
update sys_menu set perms = 'workflow:instance:currentList' where menu_id = '11629';
|
||||
|
||||
-- 流程定义管理相关按钮
|
||||
INSERT INTO sys_menu VALUES ('11644', '流程定义查询', '11620', '1', '#', '', '', 'N', 'Y', 'F', '0', '0', 'workflow:definition:query', '#', 103, 1, SYSDATE, NULL, NULL, '');
|
||||
INSERT INTO sys_menu VALUES ('11645', '流程定义新增', '11620', '2', '#', '', '', 'N', 'Y', 'F', '0', '0', 'workflow:definition:add', '#', 103, 1, SYSDATE, NULL, NULL, '');
|
||||
INSERT INTO sys_menu VALUES ('11646', '流程定义修改', '11620', '3', '#', '', '', 'N', 'Y', 'F', '0', '0', 'workflow:definition:edit', '#', 103, 1, SYSDATE, NULL, NULL, '');
|
||||
INSERT INTO sys_menu VALUES ('11647', '流程定义删除', '11620', '4', '#', '', '', 'N', 'Y', 'F', '0', '0', 'workflow:definition:remove', '#', 103, 1, SYSDATE, NULL, NULL, '');
|
||||
INSERT INTO sys_menu VALUES ('11648', '流程定义导出', '11620', '5', '#', '', '', 'N', 'Y', 'F', '0', '0', 'workflow:definition:export', '#', 103, 1, SYSDATE, NULL, NULL, '');
|
||||
INSERT INTO sys_menu VALUES ('11649', '流程定义导入', '11620', '6', '#', '', '', 'N', 'Y', 'F', '0', '0', 'workflow:definition:import', '#', 103, 1, SYSDATE, NULL, NULL, '');
|
||||
INSERT INTO sys_menu VALUES ('11650', '流程定义发布/取消发布', '11620', '7', '#', '', '', 'N', 'Y', 'F', '0', '0', 'workflow:definition:publish', '#', 103, 1, SYSDATE, NULL, NULL, '');
|
||||
INSERT INTO sys_menu VALUES ('11651', '流程定义复制', '11620', '8', '#', '', '', 'N', 'Y', 'F', '0', '0', 'workflow:definition:copy', '#', 103, 1, SYSDATE, NULL, NULL, '');
|
||||
INSERT INTO sys_menu VALUES ('11652', '流程定义激活/挂起', '11620', '9', '#', '', '', 'N', 'Y', 'F', '0', '0', 'workflow:definition:active', '#', 103, 1, SYSDATE, NULL, NULL, '');
|
||||
|
||||
-- 流程实例管理相关按钮
|
||||
INSERT INTO sys_menu VALUES ('11653', '流程实例查询', '11621', '1', '#', '', '', 'N', 'Y', 'F', '0', '0', 'workflow:instance:query', '#', 103, 1, SYSDATE, NULL, NULL, '');
|
||||
INSERT INTO sys_menu VALUES ('11654', '流程变量查询', '11621', '2', '#', '', '', 'N', 'Y', 'F', '0', '0', 'workflow:instance:variableQuery', '#', 103, 1, SYSDATE, NULL, NULL, '');
|
||||
INSERT INTO sys_menu VALUES ('11655', '流程变量修改', '11621', '3', '#', '', '', 'N', 'Y', 'F', '0', '0', 'workflow:instance:variable', '#', 103, 1, SYSDATE, NULL, NULL, '');
|
||||
INSERT INTO sys_menu VALUES ('11656', '流程实例激活/挂起', '11621', '4', '#', '', '', 'N', 'Y', 'F', '0', '0', 'workflow:instance:active', '#', 103, 1, SYSDATE, NULL, NULL, '');
|
||||
INSERT INTO sys_menu VALUES ('11657', '流程实例删除', '11621', '5', '#', '', '', 'N', 'Y', 'F', '0', '0', 'workflow:instance:remove', '#', 103, 1, SYSDATE, NULL, NULL, '');
|
||||
INSERT INTO sys_menu VALUES ('11658', '流程实例作废', '11621', '6', '#', '', '', 'N', 'Y', 'F', '0', '0', 'workflow:instance:invalid', '#', 103, 1, SYSDATE, NULL, NULL, '');
|
||||
INSERT INTO sys_menu VALUES ('11659', '流程实例撤销', '11621', '7', '#', '', '', 'N', 'Y', 'F', '0', '0', 'workflow:instance:cancel', '#', 103, 1, SYSDATE, NULL, NULL, '');
|
||||
24
script/sql/update/postgres/update_5.5.3_5.6.0.sql
Normal file
24
script/sql/update/postgres/update_5.5.3_5.6.0.sql
Normal file
@@ -0,0 +1,24 @@
|
||||
-- 修正已有菜单权限标识
|
||||
update sys_menu set perms = 'workflow:definition:list' where menu_id = '11620';
|
||||
update sys_menu set perms = 'workflow:instance:list' where menu_id = '11621';
|
||||
update sys_menu set perms = 'workflow:instance:currentList' where menu_id = '11629';
|
||||
|
||||
-- 流程定义管理相关按钮
|
||||
INSERT INTO sys_menu VALUES ('11644', '流程定义查询', '11620', '1', '#', '', '', 'N', 'Y', 'F', '0', '0', 'workflow:definition:query', '#', 103, 1, now(), NULL, NULL, '');
|
||||
INSERT INTO sys_menu VALUES ('11645', '流程定义新增', '11620', '2', '#', '', '', 'N', 'Y', 'F', '0', '0', 'workflow:definition:add', '#', 103, 1, now(), NULL, NULL, '');
|
||||
INSERT INTO sys_menu VALUES ('11646', '流程定义修改', '11620', '3', '#', '', '', 'N', 'Y', 'F', '0', '0', 'workflow:definition:edit', '#', 103, 1, now(), NULL, NULL, '');
|
||||
INSERT INTO sys_menu VALUES ('11647', '流程定义删除', '11620', '4', '#', '', '', 'N', 'Y', 'F', '0', '0', 'workflow:definition:remove', '#', 103, 1, now(), NULL, NULL, '');
|
||||
INSERT INTO sys_menu VALUES ('11648', '流程定义导出', '11620', '5', '#', '', '', 'N', 'Y', 'F', '0', '0', 'workflow:definition:export', '#', 103, 1, now(), NULL, NULL, '');
|
||||
INSERT INTO sys_menu VALUES ('11649', '流程定义导入', '11620', '6', '#', '', '', 'N', 'Y', 'F', '0', '0', 'workflow:definition:import', '#', 103, 1, now(), NULL, NULL, '');
|
||||
INSERT INTO sys_menu VALUES ('11650', '流程定义发布/取消发布', '11620', '7', '#', '', '', 'N', 'Y', 'F', '0', '0', 'workflow:definition:publish', '#', 103, 1, now(), NULL, NULL, '');
|
||||
INSERT INTO sys_menu VALUES ('11651', '流程定义复制', '11620', '8', '#', '', '', 'N', 'Y', 'F', '0', '0', 'workflow:definition:copy', '#', 103, 1, now(), NULL, NULL, '');
|
||||
INSERT INTO sys_menu VALUES ('11652', '流程定义激活/挂起', '11620', '9', '#', '', '', 'N', 'Y', 'F', '0', '0', 'workflow:definition:active', '#', 103, 1, now(), NULL, NULL, '');
|
||||
|
||||
-- 流程实例管理相关按钮
|
||||
INSERT INTO sys_menu VALUES ('11653', '流程实例查询', '11621', '1', '#', '', '', 'N', 'Y', 'F', '0', '0', 'workflow:instance:query', '#', 103, 1, now(), NULL, NULL, '');
|
||||
INSERT INTO sys_menu VALUES ('11654', '流程变量查询', '11621', '2', '#', '', '', 'N', 'Y', 'F', '0', '0', 'workflow:instance:variableQuery', '#', 103, 1, now(), NULL, NULL, '');
|
||||
INSERT INTO sys_menu VALUES ('11655', '流程变量修改', '11621', '3', '#', '', '', 'N', 'Y', 'F', '0', '0', 'workflow:instance:variable', '#', 103, 1, now(), NULL, NULL, '');
|
||||
INSERT INTO sys_menu VALUES ('11656', '流程实例激活/挂起', '11621', '4', '#', '', '', 'N', 'Y', 'F', '0', '0', 'workflow:instance:active', '#', 103, 1, now(), NULL, NULL, '');
|
||||
INSERT INTO sys_menu VALUES ('11657', '流程实例删除', '11621', '5', '#', '', '', 'N', 'Y', 'F', '0', '0', 'workflow:instance:remove', '#', 103, 1, now(), NULL, NULL, '');
|
||||
INSERT INTO sys_menu VALUES ('11658', '流程实例作废', '11621', '6', '#', '', '', 'N', 'Y', 'F', '0', '0', 'workflow:instance:invalid', '#', 103, 1, now(), NULL, NULL, '');
|
||||
INSERT INTO sys_menu VALUES ('11659', '流程实例撤销', '11621', '7', '#', '', '', 'N', 'Y', 'F', '0', '0', 'workflow:instance:cancel', '#', 103, 1, now(), NULL, NULL, '');
|
||||
43
script/sql/update/sqlserver/update_5.5.3_5.6.0.sql
Normal file
43
script/sql/update/sqlserver/update_5.5.3_5.6.0.sql
Normal file
@@ -0,0 +1,43 @@
|
||||
-- 修正已有菜单权限标识
|
||||
update sys_menu set perms = N'workflow:definition:list' where menu_id = 11620;
|
||||
GO
|
||||
update sys_menu set perms = N'workflow:instance:list' where menu_id = 11621;
|
||||
GO
|
||||
update sys_menu set perms = N'workflow:instance:currentList' where menu_id = 11629;
|
||||
GO
|
||||
|
||||
-- 流程定义管理相关按钮
|
||||
INSERT sys_menu VALUES (11644, N'流程定义查询', N'11620', 1, N'#', N'', N'', N'N', N'Y', N'F', N'0', N'0', N'workflow:definition:query', N'#', 103, 1, GETDATE(), NULL, NULL, N'');
|
||||
GO
|
||||
INSERT sys_menu VALUES (11645, N'流程定义新增', N'11620', 2, N'#', N'', N'', N'N', N'Y', N'F', N'0', N'0', N'workflow:definition:add', N'#', 103, 1, GETDATE(), NULL, NULL, N'');
|
||||
GO
|
||||
INSERT sys_menu VALUES (11646, N'流程定义修改', N'11620', 3, N'#', N'', N'', N'N', N'Y', N'F', N'0', N'0', N'workflow:definition:edit', N'#', 103, 1, GETDATE(), NULL, NULL, N'');
|
||||
GO
|
||||
INSERT sys_menu VALUES (11647, N'流程定义删除', N'11620', 4, N'#', N'', N'', N'N', N'Y', N'F', N'0', N'0', N'workflow:definition:remove', N'#', 103, 1, GETDATE(), NULL, NULL, N'');
|
||||
GO
|
||||
INSERT sys_menu VALUES (11648, N'流程定义导出', N'11620', 5, N'#', N'', N'', N'N', N'Y', N'F', N'0', N'0', N'workflow:definition:export', N'#', 103, 1, GETDATE(), NULL, NULL, N'');
|
||||
GO
|
||||
INSERT sys_menu VALUES (11649, N'流程定义导入', N'11620', 6, N'#', N'', N'', N'N', N'Y', N'F', N'0', N'0', N'workflow:definition:import', N'#', 103, 1, GETDATE(), NULL, NULL, N'');
|
||||
GO
|
||||
INSERT sys_menu VALUES (11650, N'流程定义发布/取消发布', N'11620', 7, N'#', N'', N'', N'N', N'Y', N'F', N'0', N'0', N'workflow:definition:publish', N'#', 103, 1, GETDATE(), NULL, NULL, N'');
|
||||
GO
|
||||
INSERT sys_menu VALUES (11651, N'流程定义复制', N'11620', 8, N'#', N'', N'', N'N', N'Y', N'F', N'0', N'0', N'workflow:definition:copy', N'#', 103, 1, GETDATE(), NULL, NULL, N'');
|
||||
GO
|
||||
INSERT sys_menu VALUES (11652, N'流程定义激活/挂起', N'11620', 9, N'#', N'', N'', N'N', N'Y', N'F', N'0', N'0', N'workflow:definition:active', N'#', 103, 1, GETDATE(), NULL, NULL, N'');
|
||||
GO
|
||||
|
||||
-- 流程实例管理相关按钮
|
||||
INSERT sys_menu VALUES (11653, N'流程实例查询', N'11621', 1, N'#', N'', N'', N'N', N'Y', N'F', N'0', N'0', N'workflow:instance:query', N'#', 103, 1, GETDATE(), NULL, NULL, N'');
|
||||
GO
|
||||
INSERT sys_menu VALUES (11654, N'流程变量查询', N'11621', 2, N'#', N'', N'', N'N', N'Y', N'F', N'0', N'0', N'workflow:instance:variableQuery', N'#', 103, 1, GETDATE(), NULL, NULL, N'');
|
||||
GO
|
||||
INSERT sys_menu VALUES (11655, N'流程变量修改', N'11621', 3, N'#', N'', N'', N'N', N'Y', N'F', N'0', N'0', N'workflow:instance:variable', N'#', 103, 1, GETDATE(), NULL, NULL, N'');
|
||||
GO
|
||||
INSERT sys_menu VALUES (11656, N'流程实例激活/挂起', N'11621', 4, N'#', N'', N'', N'N', N'Y', N'F', N'0', N'0', N'workflow:instance:active', N'#', 103, 1, GETDATE(), NULL, NULL, N'');
|
||||
GO
|
||||
INSERT sys_menu VALUES (11657, N'流程实例删除', N'11621', 5, N'#', N'', N'', N'N', N'Y', N'F', N'0', N'0', N'workflow:instance:remove', N'#', 103, 1, GETDATE(), NULL, NULL, N'');
|
||||
GO
|
||||
INSERT sys_menu VALUES (11658, N'流程实例作废', N'11621', 6, N'#', N'', N'', N'N', N'Y', N'F', N'0', N'0', N'workflow:instance:invalid', N'#', 103, 1, GETDATE(), NULL, NULL, N'');
|
||||
GO
|
||||
INSERT sys_menu VALUES (11659, N'流程实例撤销', N'11621', 7, N'#', N'', N'', N'N', N'Y', N'F', N'0', N'0', N'workflow:instance:cancel', N'#', 103, 1, GETDATE(), NULL, NULL, N'');
|
||||
GO
|
||||
24
script/sql/update/update_5.5.3_5.6.0.sql
Normal file
24
script/sql/update/update_5.5.3_5.6.0.sql
Normal file
@@ -0,0 +1,24 @@
|
||||
-- 修正已有菜单权限标识
|
||||
update sys_menu set perms = 'workflow:definition:list' where menu_id = '11620';
|
||||
update sys_menu set perms = 'workflow:instance:list' where menu_id = '11621';
|
||||
update sys_menu set perms = 'workflow:instance:currentList' where menu_id = '11629';
|
||||
|
||||
-- 流程定义管理相关按钮
|
||||
INSERT INTO sys_menu VALUES ('11644', '流程定义查询', '11620', '1', '#', '', '', 'N', 'Y', 'F', '0', '0', 'workflow:definition:query', '#', 103, 1, sysdate(), null, null, '');
|
||||
INSERT INTO sys_menu VALUES ('11645', '流程定义新增', '11620', '2', '#', '', '', 'N', 'Y', 'F', '0', '0', 'workflow:definition:add', '#', 103, 1, sysdate(), null, null, '');
|
||||
INSERT INTO sys_menu VALUES ('11646', '流程定义修改', '11620', '3', '#', '', '', 'N', 'Y', 'F', '0', '0', 'workflow:definition:edit', '#', 103, 1, sysdate(), null, null, '');
|
||||
INSERT INTO sys_menu VALUES ('11647', '流程定义删除', '11620', '4', '#', '', '', 'N', 'Y', 'F', '0', '0', 'workflow:definition:remove', '#', 103, 1, sysdate(), null, null, '');
|
||||
INSERT INTO sys_menu VALUES ('11648', '流程定义导出', '11620', '5', '#', '', '', 'N', 'Y', 'F', '0', '0', 'workflow:definition:export', '#', 103, 1, sysdate(), null, null, '');
|
||||
INSERT INTO sys_menu VALUES ('11649', '流程定义导入', '11620', '6', '#', '', '', 'N', 'Y', 'F', '0', '0', 'workflow:definition:import', '#', 103, 1, sysdate(), null, null, '');
|
||||
INSERT INTO sys_menu VALUES ('11650', '流程定义发布/取消发布', '11620', '7', '#', '', '', 'N', 'Y', 'F', '0', '0', 'workflow:definition:publish', '#', 103, 1, sysdate(), null, null, '');
|
||||
INSERT INTO sys_menu VALUES ('11651', '流程定义复制', '11620', '8', '#', '', '', 'N', 'Y', 'F', '0', '0', 'workflow:definition:copy', '#', 103, 1, sysdate(), null, null, '');
|
||||
INSERT INTO sys_menu VALUES ('11652', '流程定义激活/挂起', '11620', '9', '#', '', '', 'N', 'Y', 'F', '0', '0', 'workflow:definition:active', '#', 103, 1, sysdate(), null, null, '');
|
||||
|
||||
-- 流程实例管理相关按钮
|
||||
INSERT INTO sys_menu VALUES ('11653', '流程实例查询', '11621', '1', '#', '', '', 'N', 'Y', 'F', '0', '0', 'workflow:instance:query', '#', 103, 1, sysdate(), null, null, '');
|
||||
INSERT INTO sys_menu VALUES ('11654', '流程变量查询', '11621', '2', '#', '', '', 'N', 'Y', 'F', '0', '0', 'workflow:instance:variableQuery', '#', 103, 1, sysdate(), null, null, '');
|
||||
INSERT INTO sys_menu VALUES ('11655', '流程变量修改', '11621', '3', '#', '', '', 'N', 'Y', 'F', '0', '0', 'workflow:instance:variable', '#', 103, 1, sysdate(), null, null, '');
|
||||
INSERT INTO sys_menu VALUES ('11656', '流程实例激活/挂起', '11621', '4', '#', '', '', 'N', 'Y', 'F', '0', '0', 'workflow:instance:active', '#', 103, 1, sysdate(), null, null, '');
|
||||
INSERT INTO sys_menu VALUES ('11657', '流程实例删除', '11621', '5', '#', '', '', 'N', 'Y', 'F', '0', '0', 'workflow:instance:remove', '#', 103, 1, sysdate(), null, null, '');
|
||||
INSERT INTO sys_menu VALUES ('11658', '流程实例作废', '11621', '6', '#', '', '', 'N', 'Y', 'F', '0', '0', 'workflow:instance:invalid', '#', 103, 1, sysdate(), null, null, '');
|
||||
INSERT INTO sys_menu VALUES ('11659', '流程实例撤销', '11621', '7', '#', '', '', 'N', 'Y', 'F', '0', '0', 'workflow:instance:cancel', '#', 103, 1, sysdate(), null, null, '');
|
||||
Reference in New Issue
Block a user