mirror of
https://github.com/dromara/RuoYi-Vue-Plus.git
synced 2026-04-27 13:34:26 +08:00
Merge remote-tracking branch 'origin/dev' into futuer/boot4
This commit is contained in:
@@ -53,7 +53,7 @@ Topiam IAM/IDaaS身份管理平台 - https://www.topiam.cn/ <br>
|
|||||||
| 权限注解 | 采用 Sa-Token 支持注解 登录校验、角色校验、权限校验、二级认证校验、HttpBasic校验、忽略校验<br/>角色与权限校验支持多种条件 如 `AND` `OR` 或 `权限 OR 角色` 等复杂表达式 | 只支持是否存在匹配 |
|
| 权限注解 | 采用 Sa-Token 支持注解 登录校验、角色校验、权限校验、二级认证校验、HttpBasic校验、忽略校验<br/>角色与权限校验支持多种条件 如 `AND` `OR` 或 `权限 OR 角色` 等复杂表达式 | 只支持是否存在匹配 |
|
||||||
| 三方鉴权 | 采用 JustAuth 第三方登录组件 支持微信、钉钉等数十种三方认证 | 无 |
|
| 三方鉴权 | 采用 JustAuth 第三方登录组件 支持微信、钉钉等数十种三方认证 | 无 |
|
||||||
| 关系数据库支持 | 原生支持 MySQL、Oracle、PostgreSQL、SQLServer<br/>可同时使用异构切换(支持其他 mybatis-plus 支持的所有数据库 只需要增加jdbc依赖即可使用 达梦金仓等均有成功案例) | 支持 Mysql、Oracle 不支持同时使用、不支持异构切换 |
|
| 关系数据库支持 | 原生支持 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多经常性出问题 |
|
| Redis客户端 | 采用 Redisson Redis官方推荐 基于Netty的客户端工具<br/>支持Redis 90%以上的命令 底层优化规避很多不正确的用法 例如: keys被转换为scan<br/>支持单机、哨兵、单主集群、多主集群等模式 | Lettuce + RedisTemplate 支持模式少 工具使用繁琐<br/>连接池采用 common-pool Bug多经常性出问题 |
|
||||||
| 缓存注解 | 采用 Spring-Cache 注解 对其扩展了实现支持了更多功能<br/>例如 过期时间 最大空闲时间 组最大长度等 只需一个注解即可完成数据自动缓存 | 需手动编写Redis代码逻辑 |
|
| 缓存注解 | 采用 Spring-Cache 注解 对其扩展了实现支持了更多功能<br/>例如 过期时间 最大空闲时间 组最大长度等 只需一个注解即可完成数据自动缓存 | 需手动编写Redis代码逻辑 |
|
||||||
| ORM框架 | 采用 Mybatis-Plus 基于对象几乎不用写SQL全java操作 功能强大插件众多<br/>例如多租户插件 分页插件 乐观锁插件等等 | 采用 Mybatis 基于XML需要手写SQL |
|
| ORM框架 | 采用 Mybatis-Plus 基于对象几乎不用写SQL全java操作 功能强大插件众多<br/>例如多租户插件 分页插件 乐观锁插件等等 | 采用 Mybatis 基于XML需要手写SQL |
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ public interface CacheNames {
|
|||||||
String SYS_USER_NAME = "sys_user_name#30d";
|
String SYS_USER_NAME = "sys_user_name#30d";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户名称
|
* 用户昵称
|
||||||
*/
|
*/
|
||||||
String SYS_NICKNAME = "sys_nickname#30d";
|
String SYS_NICKNAME = "sys_nickname#30d";
|
||||||
|
|
||||||
|
|||||||
@@ -23,8 +23,8 @@ public class FlowCopyDTO implements Serializable {
|
|||||||
private Long userId;
|
private Long userId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户名称
|
* 用户昵称
|
||||||
*/
|
*/
|
||||||
private String userName;
|
private String nickName;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,8 @@ public class StartProcessDTO implements Serializable {
|
|||||||
|
|
||||||
public Map<String, Object> getVariables() {
|
public Map<String, Object> getVariables() {
|
||||||
if (variables == null) {
|
if (variables == null) {
|
||||||
return new HashMap<>(16);
|
variables = new HashMap<>(16);
|
||||||
|
return variables;
|
||||||
}
|
}
|
||||||
variables.entrySet().removeIf(entry -> Objects.isNull(entry.getValue()));
|
variables.entrySet().removeIf(entry -> Objects.isNull(entry.getValue()));
|
||||||
return variables;
|
return variables;
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ public class UserOnlineDTO implements Serializable {
|
|||||||
private String deptName;
|
private String deptName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户名称
|
* 用户账号
|
||||||
*/
|
*/
|
||||||
private String userName;
|
private String userName;
|
||||||
|
|
||||||
|
|||||||
@@ -21,18 +21,18 @@ public interface UserService {
|
|||||||
String selectUserNameById(Long userId);
|
String selectUserNameById(Long userId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 通过用户ID查询用户账户
|
* 通过用户ID查询用户昵称
|
||||||
*
|
*
|
||||||
* @param userId 用户ID
|
* @param userId 用户ID
|
||||||
* @return 用户名称
|
* @return 用户昵称
|
||||||
*/
|
*/
|
||||||
String selectNicknameById(Long userId);
|
String selectNicknameById(Long userId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 通过用户ID查询用户账户
|
* 通过用户ID查询用户昵称
|
||||||
*
|
*
|
||||||
* @param userIds 用户ID 多个用逗号隔开
|
* @param userIds 用户ID 多个用逗号隔开
|
||||||
* @return 用户名称
|
* @return 用户昵称
|
||||||
*/
|
*/
|
||||||
String selectNicknameByIds(String userIds);
|
String selectNicknameByIds(String userIds);
|
||||||
|
|
||||||
@@ -93,11 +93,11 @@ public interface UserService {
|
|||||||
List<UserDTO> selectUsersByPostIds(List<Long> postIds);
|
List<UserDTO> selectUsersByPostIds(List<Long> postIds);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据用户 ID 列表查询用户名称映射关系
|
* 根据用户 ID 列表查询用户昵称映射关系
|
||||||
*
|
*
|
||||||
* @param userIds 用户 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);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import io.swagger.v3.oas.models.security.SecurityRequirement;
|
|||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.dromara.common.core.utils.StringUtils;
|
import org.dromara.common.core.utils.StringUtils;
|
||||||
import org.dromara.common.doc.config.properties.SpringDocProperties;
|
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.dromara.common.doc.handler.OpenApiHandler;
|
||||||
import org.springdoc.core.configuration.SpringDocConfiguration;
|
import org.springdoc.core.configuration.SpringDocConfiguration;
|
||||||
import org.springdoc.core.customizers.OpenApiBuilderCustomizer;
|
import org.springdoc.core.customizers.OpenApiBuilderCustomizer;
|
||||||
@@ -84,8 +86,9 @@ public class SpringDocConfig {
|
|||||||
SecurityService securityParser,
|
SecurityService securityParser,
|
||||||
SpringDocConfigProperties springDocConfigProperties, PropertyResolverUtils propertyResolverUtils,
|
SpringDocConfigProperties springDocConfigProperties, PropertyResolverUtils propertyResolverUtils,
|
||||||
Optional<List<OpenApiBuilderCustomizer>> openApiBuilderCustomisers,
|
Optional<List<OpenApiBuilderCustomizer>> openApiBuilderCustomisers,
|
||||||
Optional<List<ServerBaseUrlCustomizer>> serverBaseUrlCustomisers, Optional<JavadocProvider> javadocProvider) {
|
Optional<List<ServerBaseUrlCustomizer>> serverBaseUrlCustomisers, Optional<JavadocProvider> javadocProvider,
|
||||||
return new OpenApiHandler(openAPI, securityParser, springDocConfigProperties, propertyResolverUtils, openApiBuilderCustomisers, serverBaseUrlCustomisers, 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路径拼接重复问题
|
* 单独使用一个类便于判断 解决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 lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.dromara.common.core.utils.StreamUtils;
|
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.OpenApiBuilderCustomizer;
|
||||||
import org.springdoc.core.customizers.ServerBaseUrlCustomizer;
|
import org.springdoc.core.customizers.ServerBaseUrlCustomizer;
|
||||||
import org.springdoc.core.properties.SpringDocConfigProperties;
|
import org.springdoc.core.properties.SpringDocConfigProperties;
|
||||||
@@ -83,6 +84,11 @@ public class OpenApiHandler extends OpenAPIService {
|
|||||||
*/
|
*/
|
||||||
private final PropertyResolverUtils propertyResolverUtils;
|
private final PropertyResolverUtils propertyResolverUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Javadoc解析器接口
|
||||||
|
*/
|
||||||
|
private final List<JavadocResolver> javadocResolvers;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The javadoc provider.
|
* The javadoc provider.
|
||||||
*/
|
*/
|
||||||
@@ -123,7 +129,8 @@ public class OpenApiHandler extends OpenAPIService {
|
|||||||
SpringDocConfigProperties springDocConfigProperties, PropertyResolverUtils propertyResolverUtils,
|
SpringDocConfigProperties springDocConfigProperties, PropertyResolverUtils propertyResolverUtils,
|
||||||
Optional<List<OpenApiBuilderCustomizer>> openApiBuilderCustomizers,
|
Optional<List<OpenApiBuilderCustomizer>> openApiBuilderCustomizers,
|
||||||
Optional<List<ServerBaseUrlCustomizer>> serverBaseUrlCustomizers,
|
Optional<List<ServerBaseUrlCustomizer>> serverBaseUrlCustomizers,
|
||||||
Optional<JavadocProvider> javadocProvider) {
|
Optional<JavadocProvider> javadocProvider,
|
||||||
|
List<JavadocResolver> javadocResolvers) {
|
||||||
super(openAPI, securityParser, springDocConfigProperties, propertyResolverUtils, openApiBuilderCustomizers, serverBaseUrlCustomizers, javadocProvider);
|
super(openAPI, securityParser, springDocConfigProperties, propertyResolverUtils, openApiBuilderCustomizers, serverBaseUrlCustomizers, javadocProvider);
|
||||||
if (openAPI.isPresent()) {
|
if (openAPI.isPresent()) {
|
||||||
this.openAPI = openAPI.get();
|
this.openAPI = openAPI.get();
|
||||||
@@ -140,6 +147,7 @@ public class OpenApiHandler extends OpenAPIService {
|
|||||||
this.openApiBuilderCustomisers = openApiBuilderCustomizers;
|
this.openApiBuilderCustomisers = openApiBuilderCustomizers;
|
||||||
this.serverBaseUrlCustomizers = serverBaseUrlCustomizers;
|
this.serverBaseUrlCustomizers = serverBaseUrlCustomizers;
|
||||||
this.javadocProvider = javadocProvider;
|
this.javadocProvider = javadocProvider;
|
||||||
|
this.javadocResolvers = javadocResolvers == null ? new ArrayList<>() : javadocResolvers;
|
||||||
if (springDocConfigProperties.isUseFqn())
|
if (springDocConfigProperties.isUseFqn())
|
||||||
TypeNameResolver.std.setUseFqn(true);
|
TypeNameResolver.std.setUseFqn(true);
|
||||||
}
|
}
|
||||||
@@ -220,6 +228,22 @@ public class OpenApiHandler extends OpenAPIService {
|
|||||||
securityParser.buildSecurityRequirement(securityRequirements, operation);
|
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;
|
return operation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,10 +6,7 @@ import cn.hutool.core.util.ObjectUtil;
|
|||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.ibatis.executor.parameter.ParameterHandler;
|
import org.apache.ibatis.executor.parameter.ParameterHandler;
|
||||||
import org.apache.ibatis.plugin.Interceptor;
|
import org.apache.ibatis.plugin.*;
|
||||||
import org.apache.ibatis.plugin.Intercepts;
|
|
||||||
import org.apache.ibatis.plugin.Invocation;
|
|
||||||
import org.apache.ibatis.plugin.Signature;
|
|
||||||
import org.dromara.common.core.utils.StringUtils;
|
import org.dromara.common.core.utils.StringUtils;
|
||||||
import org.dromara.common.encrypt.annotation.EncryptField;
|
import org.dromara.common.encrypt.annotation.EncryptField;
|
||||||
import org.dromara.common.encrypt.core.EncryptContext;
|
import org.dromara.common.encrypt.core.EncryptContext;
|
||||||
@@ -42,19 +39,19 @@ public class MybatisEncryptInterceptor implements Interceptor {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object intercept(Invocation invocation) throws Throwable {
|
public Object intercept(Invocation invocation) throws Throwable {
|
||||||
return invocation;
|
Object target = invocation.getTarget();
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object plugin(Object target) {
|
|
||||||
if (target instanceof ParameterHandler parameterHandler) {
|
if (target instanceof ParameterHandler parameterHandler) {
|
||||||
// 进行加密操作
|
|
||||||
Object parameterObject = parameterHandler.getParameterObject();
|
Object parameterObject = parameterHandler.getParameterObject();
|
||||||
if (ObjectUtil.isNotNull(parameterObject) && !(parameterObject instanceof String)) {
|
if (ObjectUtil.isNotNull(parameterObject) && !(parameterObject instanceof String)) {
|
||||||
this.encryptHandler(parameterObject);
|
this.encryptHandler(parameterObject);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return target;
|
return invocation.proceed();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object plugin(Object target) {
|
||||||
|
return Plugin.wrap(target, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -23,9 +23,24 @@ import java.util.Objects;
|
|||||||
@Slf4j
|
@Slf4j
|
||||||
public class SensitiveHandler extends ValueSerializer<String> {
|
public class SensitiveHandler extends ValueSerializer<String> {
|
||||||
|
|
||||||
private SensitiveStrategy strategy;
|
private final SensitiveStrategy strategy;
|
||||||
private String[] roleKey;
|
private final String[] roleKey;
|
||||||
private String[] perms;
|
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
|
@Override
|
||||||
public void serialize(String value, JsonGenerator gen, SerializationContext ctxt) throws JacksonException {
|
public void serialize(String value, JsonGenerator gen, SerializationContext ctxt) throws JacksonException {
|
||||||
@@ -46,10 +61,7 @@ public class SensitiveHandler extends ValueSerializer<String> {
|
|||||||
public ValueSerializer<?> createContextual(SerializationContext ctxt, BeanProperty property) {
|
public ValueSerializer<?> createContextual(SerializationContext ctxt, BeanProperty property) {
|
||||||
Sensitive annotation = property.getAnnotation(Sensitive.class);
|
Sensitive annotation = property.getAnnotation(Sensitive.class);
|
||||||
if (Objects.nonNull(annotation) && Objects.equals(String.class, property.getType().getRawClass())) {
|
if (Objects.nonNull(annotation) && Objects.equals(String.class, property.getType().getRawClass())) {
|
||||||
this.strategy = annotation.strategy();
|
return new SensitiveHandler(annotation.strategy(), annotation.roleKey(), annotation.perms());
|
||||||
this.roleKey = annotation.roleKey();
|
|
||||||
this.perms = annotation.perms();
|
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
return super.createContextual(ctxt, property);
|
return super.createContextual(ctxt, property);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ public interface TransConstant {
|
|||||||
String USER_ID_TO_NAME = "user_id_to_name";
|
String USER_ID_TO_NAME = "user_id_to_name";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户id转用户名称
|
* 用户id转用户昵称
|
||||||
*/
|
*/
|
||||||
String USER_ID_TO_NICKNAME = "user_id_to_nickname";
|
String USER_ID_TO_NICKNAME = "user_id_to_nickname";
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,18 @@ public class TranslationHandler extends ValueSerializer<Object> {
|
|||||||
*/
|
*/
|
||||||
public static final Map<String, TranslationInterface<?>> TRANSLATION_MAPPER = new ConcurrentHashMap<>();
|
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
|
@Override
|
||||||
public void serialize(Object value, JsonGenerator gen, SerializationContext ctxt) throws JacksonException {
|
public void serialize(Object value, JsonGenerator gen, SerializationContext ctxt) throws JacksonException {
|
||||||
@@ -61,8 +72,7 @@ public class TranslationHandler extends ValueSerializer<Object> {
|
|||||||
public ValueSerializer<?> createContextual(SerializationContext ctxt, BeanProperty property) {
|
public ValueSerializer<?> createContextual(SerializationContext ctxt, BeanProperty property) {
|
||||||
Translation translation = property.getAnnotation(Translation.class);
|
Translation translation = property.getAnnotation(Translation.class);
|
||||||
if (Objects.nonNull(translation)) {
|
if (Objects.nonNull(translation)) {
|
||||||
this.translation = translation;
|
return new TranslationHandler(translation);
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
return super.createContextual(ctxt, property);
|
return super.createContextual(ctxt, property);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import org.dromara.common.translation.constant.TransConstant;
|
|||||||
import org.dromara.common.translation.core.TranslationInterface;
|
import org.dromara.common.translation.core.TranslationInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户名称翻译实现
|
* 用户昵称翻译实现
|
||||||
*
|
*
|
||||||
* @author may
|
* @author may
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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端登录态校验通过");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -35,8 +35,8 @@ public class ExportDemoVo implements Serializable {
|
|||||||
/**
|
/**
|
||||||
* 用户昵称
|
* 用户昵称
|
||||||
*/
|
*/
|
||||||
@ExcelProperty(value = "用户名", index = 0)
|
@ExcelProperty(value = "用户昵称", index = 0)
|
||||||
@NotEmpty(message = "用户名不能为空", groups = AddGroup.class)
|
@NotEmpty(message = "用户昵称不能为空", groups = AddGroup.class)
|
||||||
private String nickName;
|
private String nickName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -523,6 +523,9 @@ public class GenTableServiceImpl implements IGenTableService {
|
|||||||
* @param table 业务表信息
|
* @param table 业务表信息
|
||||||
*/
|
*/
|
||||||
public void setPkColumn(GenTable table) {
|
public void setPkColumn(GenTable table) {
|
||||||
|
if (CollUtil.isEmpty(table.getColumns())) {
|
||||||
|
throw new ServiceException("表【" + table.getTableName() + "】字段为空,请检查表结构");
|
||||||
|
}
|
||||||
for (GenTableColumn column : table.getColumns()) {
|
for (GenTableColumn column : table.getColumns()) {
|
||||||
if (column.isPk()) {
|
if (column.isPk()) {
|
||||||
table.setPkColumn(column);
|
table.setPkColumn(column);
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ public class SysDictDataController extends BaseController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 新增字典类型
|
* 新增字典数据
|
||||||
*/
|
*/
|
||||||
@SaCheckPermission("system:dict:add")
|
@SaCheckPermission("system:dict:add")
|
||||||
@Log(title = "字典数据", businessType = BusinessType.INSERT)
|
@Log(title = "字典数据", businessType = BusinessType.INSERT)
|
||||||
@@ -98,7 +98,7 @@ public class SysDictDataController extends BaseController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 修改保存字典类型
|
* 修改保存字典数据
|
||||||
*/
|
*/
|
||||||
@SaCheckPermission("system:dict:edit")
|
@SaCheckPermission("system:dict:edit")
|
||||||
@Log(title = "字典数据", businessType = BusinessType.UPDATE)
|
@Log(title = "字典数据", businessType = BusinessType.UPDATE)
|
||||||
@@ -113,12 +113,12 @@ public class SysDictDataController extends BaseController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除字典类型
|
* 删除字典数据
|
||||||
*
|
*
|
||||||
* @param dictCodes 字典code串
|
* @param dictCodes 字典code串
|
||||||
*/
|
*/
|
||||||
@SaCheckPermission("system:dict:remove")
|
@SaCheckPermission("system:dict:remove")
|
||||||
@Log(title = "字典类型", businessType = BusinessType.DELETE)
|
@Log(title = "字典数据", businessType = BusinessType.DELETE)
|
||||||
@DeleteMapping("/{dictCodes}")
|
@DeleteMapping("/{dictCodes}")
|
||||||
public R<Void> remove(@PathVariable Long[] dictCodes) {
|
public R<Void> remove(@PathVariable Long[] dictCodes) {
|
||||||
dictDataService.deleteDictDataByIds(Arrays.asList(dictCodes));
|
dictDataService.deleteDictDataByIds(Arrays.asList(dictCodes));
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ public class SysUserOnline {
|
|||||||
private String deptName;
|
private String deptName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户名称
|
* 用户账号
|
||||||
*/
|
*/
|
||||||
private String userName;
|
private String userName;
|
||||||
|
|
||||||
|
|||||||
@@ -32,13 +32,13 @@ public class SysUserExportVo implements Serializable {
|
|||||||
/**
|
/**
|
||||||
* 用户账号
|
* 用户账号
|
||||||
*/
|
*/
|
||||||
@ExcelProperty(value = "登录名称")
|
@ExcelProperty(value = "用户账号")
|
||||||
private String userName;
|
private String userName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户昵称
|
* 用户昵称
|
||||||
*/
|
*/
|
||||||
@ExcelProperty(value = "用户名称")
|
@ExcelProperty(value = "用户昵称")
|
||||||
private String nickName;
|
private String nickName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -38,13 +38,13 @@ public class SysUserImportVo implements Serializable {
|
|||||||
/**
|
/**
|
||||||
* 用户账号
|
* 用户账号
|
||||||
*/
|
*/
|
||||||
@ExcelProperty(value = "登录名称")
|
@ExcelProperty(value = "用户账号")
|
||||||
private String userName;
|
private String userName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户昵称
|
* 用户昵称
|
||||||
*/
|
*/
|
||||||
@ExcelProperty(value = "用户名称")
|
@ExcelProperty(value = "用户昵称")
|
||||||
private String nickName;
|
private String nickName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ public interface ISysUserService {
|
|||||||
String selectUserPostGroup(Long userId);
|
String selectUserPostGroup(Long userId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 校验用户名称是否唯一
|
* 校验用户账号是否唯一
|
||||||
*
|
*
|
||||||
* @param user 用户信息
|
* @param user 用户信息
|
||||||
* @return 结果
|
* @return 结果
|
||||||
|
|||||||
@@ -229,6 +229,9 @@ public class SysDictTypeServiceImpl implements ISysDictTypeService, DictService
|
|||||||
@Override
|
@Override
|
||||||
public String getDictLabel(String dictType, String dictValue, String separator) {
|
public String getDictLabel(String dictType, String dictValue, String separator) {
|
||||||
List<SysDictDataVo> datas = SpringUtils.getAopProxy(this).selectDictDataByType(dictType);
|
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);
|
Map<String, String> map = StreamUtils.toMap(datas, SysDictDataVo::getDictValue, SysDictDataVo::getDictLabel);
|
||||||
if (StringUtils.containsAny(dictValue, separator)) {
|
if (StringUtils.containsAny(dictValue, separator)) {
|
||||||
return Arrays.stream(dictValue.split(separator))
|
return Arrays.stream(dictValue.split(separator))
|
||||||
@@ -250,6 +253,9 @@ public class SysDictTypeServiceImpl implements ISysDictTypeService, DictService
|
|||||||
@Override
|
@Override
|
||||||
public String getDictValue(String dictType, String dictLabel, String separator) {
|
public String getDictValue(String dictType, String dictLabel, String separator) {
|
||||||
List<SysDictDataVo> datas = SpringUtils.getAopProxy(this).selectDictDataByType(dictType);
|
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);
|
Map<String, String> map = StreamUtils.toMap(datas, SysDictDataVo::getDictLabel, SysDictDataVo::getDictValue);
|
||||||
if (StringUtils.containsAny(dictLabel, separator)) {
|
if (StringUtils.containsAny(dictLabel, separator)) {
|
||||||
return Arrays.stream(dictLabel.split(separator))
|
return Arrays.stream(dictLabel.split(separator))
|
||||||
@@ -269,6 +275,9 @@ public class SysDictTypeServiceImpl implements ISysDictTypeService, DictService
|
|||||||
@Override
|
@Override
|
||||||
public Map<String, String> getAllDictByDictType(String dictType) {
|
public Map<String, String> getAllDictByDictType(String dictType) {
|
||||||
List<SysDictDataVo> list = SpringUtils.getAopProxy(this).selectDictDataByType(dictType);
|
List<SysDictDataVo> list = SpringUtils.getAopProxy(this).selectDictDataByType(dictType);
|
||||||
|
if (CollUtil.isEmpty(list)) {
|
||||||
|
return new HashMap<>();
|
||||||
|
}
|
||||||
// 保证顺序
|
// 保证顺序
|
||||||
LinkedHashMap<String, String> map = new LinkedHashMap<>();
|
LinkedHashMap<String, String> map = new LinkedHashMap<>();
|
||||||
for (SysDictDataVo vo : list) {
|
for (SysDictDataVo vo : list) {
|
||||||
@@ -286,6 +295,9 @@ public class SysDictTypeServiceImpl implements ISysDictTypeService, DictService
|
|||||||
@Override
|
@Override
|
||||||
public DictTypeDTO getDictType(String dictType) {
|
public DictTypeDTO getDictType(String dictType) {
|
||||||
SysDictTypeVo vo = SpringUtils.getAopProxy(this).selectDictTypeByType(dictType);
|
SysDictTypeVo vo = SpringUtils.getAopProxy(this).selectDictTypeByType(dictType);
|
||||||
|
if (ObjectUtil.isNull(vo)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return BeanUtil.toBean(vo, DictTypeDTO.class);
|
return BeanUtil.toBean(vo, DictTypeDTO.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -298,6 +310,9 @@ public class SysDictTypeServiceImpl implements ISysDictTypeService, DictService
|
|||||||
@Override
|
@Override
|
||||||
public List<DictDataDTO> getDictData(String dictType) {
|
public List<DictDataDTO> getDictData(String dictType) {
|
||||||
List<SysDictDataVo> list = SpringUtils.getAopProxy(this).selectDictDataByType(dictType);
|
List<SysDictDataVo> list = SpringUtils.getAopProxy(this).selectDictDataByType(dictType);
|
||||||
|
if (CollUtil.isEmpty(list)) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
return BeanUtil.copyToList(list, DictDataDTO.class);
|
return BeanUtil.copyToList(list, DictDataDTO.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ public class SysMenuServiceImpl implements ISysMenuService {
|
|||||||
/**
|
/**
|
||||||
* 根据用户ID查询菜单
|
* 根据用户ID查询菜单
|
||||||
*
|
*
|
||||||
* @param userId 用户名称
|
* @param userId 用户ID
|
||||||
* @return 菜单列表
|
* @return 菜单列表
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -232,7 +232,7 @@ public class SysUserServiceImpl implements ISysUserService, UserService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 校验用户名称是否唯一
|
* 校验用户账号是否唯一
|
||||||
*
|
*
|
||||||
* @param user 用户信息
|
* @param user 用户信息
|
||||||
* @return 结果
|
* @return 结果
|
||||||
@@ -496,6 +496,11 @@ public class SysUserServiceImpl implements ISysUserService, UserService {
|
|||||||
roleList.remove(SystemConstants.SUPER_ADMIN_ID);
|
roleList.remove(SystemConstants.SUPER_ADMIN_ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 移除超管角色后若无剩余角色,说明仅选了超管角色且不允许分配,显式报错
|
||||||
|
if (roleList.isEmpty()) {
|
||||||
|
throw new ServiceException("不允许为普通用户分配超级管理员角色,请至少选择一个其他角色");
|
||||||
|
}
|
||||||
|
|
||||||
// 校验是否有权限访问这些角色(含数据权限控制)
|
// 校验是否有权限访问这些角色(含数据权限控制)
|
||||||
if (roleMapper.selectRoleCount(roleList) != roleList.size()) {
|
if (roleMapper.selectRoleCount(roleList) != roleList.size()) {
|
||||||
throw new ServiceException("没有权限访问角色的数据");
|
throw new ServiceException("没有权限访问角色的数据");
|
||||||
@@ -593,10 +598,10 @@ public class SysUserServiceImpl implements ISysUserService, UserService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 通过用户ID查询用户账户
|
* 通过用户ID查询用户昵称
|
||||||
*
|
*
|
||||||
* @param userId 用户ID
|
* @param userId 用户ID
|
||||||
* @return 用户账户
|
* @return 用户昵称
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
@Cacheable(cacheNames = CacheNames.SYS_NICKNAME, key = "#userId")
|
@Cacheable(cacheNames = CacheNames.SYS_NICKNAME, key = "#userId")
|
||||||
@@ -607,10 +612,10 @@ public class SysUserServiceImpl implements ISysUserService, UserService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 通过用户ID查询用户账户
|
* 通过用户ID查询用户昵称
|
||||||
*
|
*
|
||||||
* @param userIds 用户ID 多个用逗号隔开
|
* @param userIds 用户ID 多个用逗号隔开
|
||||||
* @return 用户账户
|
* @return 用户昵称
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public String selectNicknameByIds(String userIds) {
|
public String selectNicknameByIds(String userIds) {
|
||||||
@@ -750,13 +755,13 @@ public class SysUserServiceImpl implements ISysUserService, UserService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据用户 ID 列表查询用户名称映射关系
|
* 根据用户 ID 列表查询用户昵称映射关系
|
||||||
*
|
*
|
||||||
* @param userIds 用户 ID 列表
|
* @param userIds 用户 ID 列表
|
||||||
* @return Map,其中 key 为用户 ID,value 为对应的用户名称
|
* @return Map,其中 key 为用户 ID,value 为对应的用户昵称
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Map<Long, String> selectUserNamesByIds(List<Long> userIds) {
|
public Map<Long, String> selectUserNicksByIds(List<Long> userIds) {
|
||||||
if (CollUtil.isEmpty(userIds)) {
|
if (CollUtil.isEmpty(userIds)) {
|
||||||
return Collections.emptyMap();
|
return Collections.emptyMap();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,26 +23,6 @@ public interface FlowConstant {
|
|||||||
*/
|
*/
|
||||||
String INITIATOR_DEPT_ID = "initiatorDeptId";
|
String INITIATOR_DEPT_ID = "initiatorDeptId";
|
||||||
|
|
||||||
/**
|
|
||||||
* 委托
|
|
||||||
*/
|
|
||||||
String DELEGATE_TASK = "delegateTask";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 转办
|
|
||||||
*/
|
|
||||||
String TRANSFER_TASK = "transferTask";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 加签
|
|
||||||
*/
|
|
||||||
String ADD_SIGNATURE = "addSignature";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 减签
|
|
||||||
*/
|
|
||||||
String REDUCTION_SIGNATURE = "reductionSignature";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 流程分类Id转名称
|
* 流程分类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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -61,7 +61,8 @@ public class BackProcessBo implements Serializable {
|
|||||||
|
|
||||||
public Map<String, Object> getVariables() {
|
public Map<String, Object> getVariables() {
|
||||||
if (variables == null) {
|
if (variables == null) {
|
||||||
return new HashMap<>(16);
|
variables = new HashMap<>(16);
|
||||||
|
return variables;
|
||||||
}
|
}
|
||||||
variables.entrySet().removeIf(entry -> Objects.isNull(entry.getValue()));
|
variables.entrySet().removeIf(entry -> Objects.isNull(entry.getValue()));
|
||||||
return variables;
|
return variables;
|
||||||
|
|||||||
@@ -23,8 +23,8 @@ public class FlowCopyBo implements Serializable {
|
|||||||
private Long userId;
|
private Long userId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户名称
|
* 用户昵称
|
||||||
*/
|
*/
|
||||||
private String userName;
|
private String nickName;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,8 @@ public class FlowNextNodeBo implements Serializable {
|
|||||||
|
|
||||||
public Map<String, Object> getVariables() {
|
public Map<String, Object> getVariables() {
|
||||||
if (variables == null) {
|
if (variables == null) {
|
||||||
return new HashMap<>(16);
|
variables = new HashMap<>(16);
|
||||||
|
return variables;
|
||||||
}
|
}
|
||||||
variables.entrySet().removeIf(entry -> Objects.isNull(entry.getValue()));
|
variables.entrySet().removeIf(entry -> Objects.isNull(entry.getValue()));
|
||||||
return variables;
|
return variables;
|
||||||
|
|||||||
@@ -53,7 +53,8 @@ public class StartProcessBo implements Serializable {
|
|||||||
|
|
||||||
public Map<String, Object> getVariables() {
|
public Map<String, Object> getVariables() {
|
||||||
if (variables == null) {
|
if (variables == null) {
|
||||||
return new HashMap<>(16);
|
variables = new HashMap<>(16);
|
||||||
|
return variables;
|
||||||
}
|
}
|
||||||
variables.entrySet().removeIf(entry -> Objects.isNull(entry.getValue()));
|
variables.entrySet().removeIf(entry -> Objects.isNull(entry.getValue()));
|
||||||
return variables;
|
return variables;
|
||||||
|
|||||||
@@ -40,6 +40,11 @@ public class TaskOperationBo implements Serializable {
|
|||||||
@NotNull(message = "任务id不能为空")
|
@NotNull(message = "任务id不能为空")
|
||||||
private Long taskId;
|
private Long taskId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 消息类型
|
||||||
|
*/
|
||||||
|
private List<String> messageType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 意见或备注信息(可选)
|
* 意见或备注信息(可选)
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -24,10 +24,10 @@ public class FlowCopyVo implements Serializable {
|
|||||||
private Long userId;
|
private Long userId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户名称
|
* 用户昵称
|
||||||
*/
|
*/
|
||||||
@Translation(type = TransConstant.USER_ID_TO_NICKNAME, mapper = "userId")
|
@Translation(type = TransConstant.USER_ID_TO_NICKNAME, mapper = "userId")
|
||||||
private String userName;
|
private String nickName;
|
||||||
|
|
||||||
public FlowCopyVo(Long userId) {
|
public FlowCopyVo(Long userId) {
|
||||||
this.userId = userId;
|
this.userId = userId;
|
||||||
|
|||||||
@@ -80,11 +80,13 @@ public class WorkflowGlobalListener implements GlobalListener {
|
|||||||
NodeExtVo nodeExt = nodeExtService.parseNodeExt(ext, variable);
|
NodeExtVo nodeExt = nodeExtService.parseNodeExt(ext, variable);
|
||||||
Set<String> copyList = nodeExt.getCopySettings();
|
Set<String> copyList = nodeExt.getCopySettings();
|
||||||
if (CollUtil.isNotEmpty(copyList)) {
|
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 -> {
|
List<FlowCopyBo> list = StreamUtils.toList(copyList, x -> {
|
||||||
FlowCopyBo bo = new FlowCopyBo();
|
FlowCopyBo bo = new FlowCopyBo();
|
||||||
Long id = Convert.toLong(x);
|
Long id = Convert.toLong(x);
|
||||||
bo.setUserId(id);
|
bo.setUserId(id);
|
||||||
bo.setUserName(userService.selectUserNameById(id));
|
bo.setNickName(nickNameMap.getOrDefault(id, StringUtils.EMPTY));
|
||||||
return bo;
|
return bo;
|
||||||
});
|
});
|
||||||
variable.put(FlowConstant.FLOW_COPY_LIST, list);
|
variable.put(FlowConstant.FLOW_COPY_LIST, list);
|
||||||
@@ -159,7 +161,7 @@ public class WorkflowGlobalListener implements GlobalListener {
|
|||||||
flowTask.setPermissionList(List.of(userIdArray));
|
flowTask.setPermissionList(List.of(userIdArray));
|
||||||
// 移除已处理的状态变量
|
// 移除已处理的状态变量
|
||||||
variable.remove(nodeKey);
|
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.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.dromara.common.core.domain.dto.UserDTO;
|
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.SpringUtils;
|
||||||
import org.dromara.common.core.utils.StreamUtils;
|
import org.dromara.common.core.utils.StreamUtils;
|
||||||
import org.dromara.common.core.utils.StringUtils;
|
import org.dromara.common.core.utils.StringUtils;
|
||||||
@@ -126,6 +127,9 @@ public class FlwCommonServiceImpl implements IFlwCommonService {
|
|||||||
@Override
|
@Override
|
||||||
public String applyNodeCode(Long definitionId) {
|
public String applyNodeCode(Long definitionId) {
|
||||||
List<Node> firstBetweenNode = FlowEngine.nodeService().getFirstBetweenNode(definitionId, new HashMap<>());
|
List<Node> firstBetweenNode = FlowEngine.nodeService().getFirstBetweenNode(definitionId, new HashMap<>());
|
||||||
|
if (CollUtil.isEmpty(firstBetweenNode)) {
|
||||||
|
throw new ServiceException("流程定义缺少申请人节点,请检查流程定义配置");
|
||||||
|
}
|
||||||
return firstBetweenNode.get(0).getNodeCode();
|
return firstBetweenNode.get(0).getNodeCode();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -111,8 +111,14 @@ public class FlwInstanceServiceImpl implements IFlwInstanceService {
|
|||||||
@Override
|
@Override
|
||||||
public FlowInstanceVo queryByBusinessId(Long businessId) {
|
public FlowInstanceVo queryByBusinessId(Long businessId) {
|
||||||
FlowInstance instance = this.selectInstByBusinessId(Convert.toStr(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);
|
FlowInstanceVo instanceVo = BeanUtil.toBean(instance, FlowInstanceVo.class);
|
||||||
Definition definition = defService.getById(instanceVo.getDefinitionId());
|
Definition definition = defService.getById(instanceVo.getDefinitionId());
|
||||||
|
if (ObjectUtil.isNull(definition)) {
|
||||||
|
throw new ServiceException(ExceptionCons.NOT_FOUNT_DEF);
|
||||||
|
}
|
||||||
instanceVo.setFlowName(definition.getFlowName());
|
instanceVo.setFlowName(definition.getFlowName());
|
||||||
instanceVo.setFlowCode(definition.getFlowCode());
|
instanceVo.setFlowCode(definition.getFlowCode());
|
||||||
instanceVo.setVersion(definition.getVersion());
|
instanceVo.setVersion(definition.getVersion());
|
||||||
@@ -383,6 +389,9 @@ public class FlwInstanceServiceImpl implements IFlwInstanceService {
|
|||||||
@Override
|
@Override
|
||||||
public Map<String, Object> instanceVariable(Long instanceId) {
|
public Map<String, Object> instanceVariable(Long instanceId) {
|
||||||
FlowInstance flowInstance = flowInstanceMapper.selectById(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());
|
Map<String, Object> variableMap = Optional.ofNullable(flowInstance.getVariableMap()).orElse(Collections.emptyMap());
|
||||||
List<Map<String, Object>> variableList = variableMap.entrySet().stream()
|
List<Map<String, Object>> variableList = variableMap.entrySet().stream()
|
||||||
.map(entry -> Map.of("key", entry.getKey(), "value", entry.getValue()))
|
.map(entry -> Map.of("key", entry.getKey(), "value", entry.getValue()))
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package org.dromara.workflow.service.impl;
|
package org.dromara.workflow.service.impl;
|
||||||
|
|
||||||
import cn.hutool.core.collection.CollUtil;
|
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.conditions.query.LambdaQueryWrapper;
|
||||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
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.constant.SystemConstants;
|
||||||
import org.dromara.common.core.domain.dto.TaskAssigneeDTO;
|
import org.dromara.common.core.domain.dto.TaskAssigneeDTO;
|
||||||
import org.dromara.common.core.domain.model.TaskAssigneeBody;
|
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.MapstructUtils;
|
||||||
import org.dromara.common.core.utils.StreamUtils;
|
import org.dromara.common.core.utils.StreamUtils;
|
||||||
import org.dromara.common.core.utils.StringUtils;
|
import org.dromara.common.core.utils.StringUtils;
|
||||||
@@ -125,7 +127,14 @@ public class FlwSpelServiceImpl implements IFlwSpelService {
|
|||||||
* 保存前的数据校验
|
* 保存前的数据校验
|
||||||
*/
|
*/
|
||||||
private void validEntityBeforeSave(FlowSpel entity){
|
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
|
@Override
|
||||||
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
|
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
|
||||||
if(isValid){
|
if (isValid){
|
||||||
//TODO 做一些业务上的校验,判断是否需要校验
|
//TODO 做一些业务上的校验,判断是否需要校验
|
||||||
}
|
}
|
||||||
return baseMapper.deleteByIds(ids) > 0;
|
return baseMapper.deleteByIds(ids) > 0;
|
||||||
|
|||||||
@@ -244,7 +244,7 @@ public class FlwTaskAssigneeServiceImpl implements IFlwTaskAssigneeService, Hand
|
|||||||
|
|
||||||
List<Long> longIds = StreamUtils.toList(ids, Convert::toLong);
|
List<Long> longIds = StreamUtils.toList(ids, Convert::toLong);
|
||||||
Map<Long, String> rawMap = switch (type) {
|
Map<Long, String> rawMap = switch (type) {
|
||||||
case USER -> userService.selectUserNamesByIds(longIds);
|
case USER -> userService.selectUserNicksByIds(longIds);
|
||||||
case ROLE -> roleService.selectRoleNamesByIds(longIds);
|
case ROLE -> roleService.selectRoleNamesByIds(longIds);
|
||||||
case DEPT -> deptService.selectDeptNamesByIds(longIds);
|
case DEPT -> deptService.selectDeptNamesByIds(longIds);
|
||||||
case POST -> postService.selectPostNamesByIds(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.FlowNodeMapper;
|
||||||
import org.dromara.warm.flow.orm.mapper.FlowTaskMapper;
|
import org.dromara.warm.flow.orm.mapper.FlowTaskMapper;
|
||||||
import org.dromara.workflow.common.ConditionalOnEnable;
|
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.TaskAssigneeType;
|
||||||
|
import org.dromara.workflow.common.enums.TaskOperationEnum;
|
||||||
import org.dromara.workflow.common.enums.TaskStatusEnum;
|
import org.dromara.workflow.common.enums.TaskStatusEnum;
|
||||||
import org.dromara.workflow.domain.FlowInstanceBizExt;
|
import org.dromara.workflow.domain.FlowInstanceBizExt;
|
||||||
import org.dromara.workflow.domain.bo.*;
|
import org.dromara.workflow.domain.bo.*;
|
||||||
@@ -127,6 +127,9 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
|
|||||||
// 已存在流程
|
// 已存在流程
|
||||||
BusinessStatusEnum.checkStartStatus(flowInstance.getFlowStatus());
|
BusinessStatusEnum.checkStartStatus(flowInstance.getFlowStatus());
|
||||||
List<Task> taskList = taskService.list(new FlowTask().setInstanceId(flowInstance.getId()));
|
List<Task> taskList = taskService.list(new FlowTask().setInstanceId(flowInstance.getId()));
|
||||||
|
if (CollUtil.isEmpty(taskList)) {
|
||||||
|
throw new ServiceException("流程实例缺少任务,请检查流程定义配置");
|
||||||
|
}
|
||||||
taskService.mergeVariable(flowInstance, variables);
|
taskService.mergeVariable(flowInstance, variables);
|
||||||
insService.updateById(flowInstance);
|
insService.updateById(flowInstance);
|
||||||
StartProcessReturnDTO dto = new StartProcessReturnDTO();
|
StartProcessReturnDTO dto = new StartProcessReturnDTO();
|
||||||
@@ -143,9 +146,9 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
|
|||||||
throw new ServiceException("流程【" + startProcessBo.getFlowCode() + "】未发布,请先在流程设计器中发布流程定义");
|
throw new ServiceException("流程【" + startProcessBo.getFlowCode() + "】未发布,请先在流程设计器中发布流程定义");
|
||||||
}
|
}
|
||||||
Dict dict = JsonUtils.parseMap(definition.getExt());
|
Dict dict = JsonUtils.parseMap(definition.getExt());
|
||||||
boolean autoPass = !ObjectUtil.isNull(dict) && dict.getBool(FlowConstant.AUTO_PASS);
|
boolean autoPass = !ObjectUtil.isNull(dict) && dict.getBool(AUTO_PASS);
|
||||||
variables.put(FlowConstant.AUTO_PASS, autoPass);
|
variables.put(AUTO_PASS, autoPass);
|
||||||
variables.put(FlowConstant.BUSINESS_CODE, this.generateBusinessCode(bizExt));
|
variables.put(BUSINESS_CODE, this.generateBusinessCode(bizExt));
|
||||||
FlowParams flowParams = FlowParams.build()
|
FlowParams flowParams = FlowParams.build()
|
||||||
.handler(startProcessBo.getHandler())
|
.handler(startProcessBo.getHandler())
|
||||||
.flowCode(startProcessBo.getFlowCode())
|
.flowCode(startProcessBo.getFlowCode())
|
||||||
@@ -156,6 +159,9 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
|
|||||||
this.buildFlowInstanceBizExt(instance, bizExt);
|
this.buildFlowInstanceBizExt(instance, bizExt);
|
||||||
// 申请人执行流程
|
// 申请人执行流程
|
||||||
List<Task> taskList = taskService.list(new FlowTask().setInstanceId(instance.getId()));
|
List<Task> taskList = taskService.list(new FlowTask().setInstanceId(instance.getId()));
|
||||||
|
if (CollUtil.isEmpty(taskList)) {
|
||||||
|
throw new ServiceException("流程启动失败,未生成任务");
|
||||||
|
}
|
||||||
if (taskList.size() > 1) {
|
if (taskList.size() > 1) {
|
||||||
throw new ServiceException("请检查流程第一个环节是否为申请人!");
|
throw new ServiceException("请检查流程第一个环节是否为申请人!");
|
||||||
}
|
}
|
||||||
@@ -207,11 +213,11 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
|
|||||||
List<FlowCopyBo> flowCopyList = completeTaskBo.getFlowCopyList();
|
List<FlowCopyBo> flowCopyList = completeTaskBo.getFlowCopyList();
|
||||||
// 设置抄送人
|
// 设置抄送人
|
||||||
Map<String, Object> variables = completeTaskBo.getVariables();
|
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);
|
FlowTask flowTask = flowTaskMapper.selectById(taskId);
|
||||||
@@ -219,9 +225,12 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
|
|||||||
throw new ServiceException("流程任务不存在或任务已审批!");
|
throw new ServiceException("流程任务不存在或任务已审批!");
|
||||||
}
|
}
|
||||||
Instance ins = insService.getById(flowTask.getInstanceId());
|
Instance ins = insService.getById(flowTask.getInstanceId());
|
||||||
|
if (ObjectUtil.isNull(ins)) {
|
||||||
|
throw new ServiceException("流程实例不存在");
|
||||||
|
}
|
||||||
// 检查流程状态是否为草稿、已撤销或已退回状态,若是则执行流程提交监听
|
// 检查流程状态是否为草稿、已撤销或已退回状态,若是则执行流程提交监听
|
||||||
if (BusinessStatusEnum.isDraftOrCancelOrBack(ins.getFlowStatus())) {
|
if (BusinessStatusEnum.isDraftOrCancelOrBack(ins.getFlowStatus())) {
|
||||||
variables.put(FlowConstant.SUBMIT, true);
|
variables.put(SUBMIT, true);
|
||||||
}
|
}
|
||||||
// 设置弹窗处理人
|
// 设置弹窗处理人
|
||||||
Map<String, Object> assigneeMap = setPopAssigneeMap(completeTaskBo.getAssigneeMap(), ins.getVariableMap());
|
Map<String, Object> assigneeMap = setPopAssigneeMap(completeTaskBo.getAssigneeMap(), ins.getVariableMap());
|
||||||
@@ -274,9 +283,9 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
|
|||||||
flowParams.
|
flowParams.
|
||||||
message("流程引擎自动审批!").
|
message("流程引擎自动审批!").
|
||||||
variable(Map.of(
|
variable(Map.of(
|
||||||
FlowConstant.SUBMIT, false,
|
SUBMIT, false,
|
||||||
FlowConstant.FLOW_COPY_LIST, Collections.emptyList(),
|
FLOW_COPY_LIST, Collections.emptyList(),
|
||||||
FlowConstant.MESSAGE_NOTICE, StringUtils.EMPTY));
|
MESSAGE_NOTICE, StringUtils.EMPTY));
|
||||||
skipTask(task.getId(), flowParams, instanceId, true);
|
skipTask(task.getId(), flowParams, instanceId, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -341,7 +350,7 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
|
|||||||
FlowParams flowParams = FlowParams.build()
|
FlowParams flowParams = FlowParams.build()
|
||||||
.skipType(SkipType.NONE.getKey())
|
.skipType(SkipType.NONE.getKey())
|
||||||
.hisStatus(TaskStatusEnum.COPY.getStatus())
|
.hisStatus(TaskStatusEnum.COPY.getStatus())
|
||||||
.message("【抄送给】" + StreamUtils.join(flowCopyList, FlowCopyBo::getUserName));
|
.message("【抄送给】" + StreamUtils.join(flowCopyList, FlowCopyBo::getNickName));
|
||||||
HisTask hisTask = hisTaskService.setSkipHisTask(task, flowNode, flowParams);
|
HisTask hisTask = hisTaskService.setSkipHisTask(task, flowNode, flowParams);
|
||||||
hisTask.setCreateTime(updateTime);
|
hisTask.setCreateTime(updateTime);
|
||||||
hisTask.setUpdateTime(updateTime);
|
hisTask.setUpdateTime(updateTime);
|
||||||
@@ -482,15 +491,18 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
|
|||||||
throw new ServiceException("任务不存在!");
|
throw new ServiceException("任务不存在!");
|
||||||
}
|
}
|
||||||
Instance inst = insService.getById(task.getInstanceId());
|
Instance inst = insService.getById(task.getInstanceId());
|
||||||
|
if (ObjectUtil.isNull(inst)) {
|
||||||
|
throw new ServiceException("流程实例不存在");
|
||||||
|
}
|
||||||
BusinessStatusEnum.checkBackStatus(inst.getFlowStatus());
|
BusinessStatusEnum.checkBackStatus(inst.getFlowStatus());
|
||||||
Long definitionId = task.getDefinitionId();
|
Long definitionId = task.getDefinitionId();
|
||||||
String applyNodeCode = flwCommonService.applyNodeCode(definitionId);
|
String applyNodeCode = flwCommonService.applyNodeCode(definitionId);
|
||||||
|
|
||||||
Map<String, Object> variable = new HashMap<>();
|
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()
|
FlowParams flowParams = FlowParams.build()
|
||||||
.nodeCode(bo.getNodeCode())
|
.nodeCode(bo.getNodeCode())
|
||||||
@@ -513,6 +525,9 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
|
|||||||
@Override
|
@Override
|
||||||
public List<Node> getBackTaskNode(Long taskId, String nowNodeCode) {
|
public List<Node> getBackTaskNode(Long taskId, String nowNodeCode) {
|
||||||
FlowTask task = flowTaskMapper.selectById(taskId);
|
FlowTask task = flowTaskMapper.selectById(taskId);
|
||||||
|
if (ObjectUtil.isNull(task)) {
|
||||||
|
throw new ServiceException("任务不存在!");
|
||||||
|
}
|
||||||
List<Node> nodeCodes = nodeService.getByNodeCodes(Collections.singletonList(nowNodeCode), task.getDefinitionId());
|
List<Node> nodeCodes = nodeService.getByNodeCodes(Collections.singletonList(nowNodeCode), task.getDefinitionId());
|
||||||
if (!CollUtil.isNotEmpty(nodeCodes)) {
|
if (!CollUtil.isNotEmpty(nodeCodes)) {
|
||||||
return nodeCodes;
|
return nodeCodes;
|
||||||
@@ -597,7 +612,13 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
|
|||||||
}
|
}
|
||||||
FlowTaskVo flowTaskVo = BeanUtil.toBean(task, FlowTaskVo.class);
|
FlowTaskVo flowTaskVo = BeanUtil.toBean(task, FlowTaskVo.class);
|
||||||
Instance instance = insService.getById(task.getInstanceId());
|
Instance instance = insService.getById(task.getInstanceId());
|
||||||
|
if (ObjectUtil.isNull(instance)) {
|
||||||
|
throw new ServiceException("流程实例不存在");
|
||||||
|
}
|
||||||
Definition definition = defService.getById(task.getDefinitionId());
|
Definition definition = defService.getById(task.getDefinitionId());
|
||||||
|
if (ObjectUtil.isNull(definition)) {
|
||||||
|
throw new ServiceException("流程定义不存在");
|
||||||
|
}
|
||||||
flowTaskVo.setFlowStatus(instance.getFlowStatus());
|
flowTaskVo.setFlowStatus(instance.getFlowStatus());
|
||||||
flowTaskVo.setVersion(definition.getVersion());
|
flowTaskVo.setVersion(definition.getVersion());
|
||||||
flowTaskVo.setFlowCode(definition.getFlowCode());
|
flowTaskVo.setFlowCode(definition.getFlowCode());
|
||||||
@@ -640,11 +661,23 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
|
|||||||
Long taskId = bo.getTaskId();
|
Long taskId = bo.getTaskId();
|
||||||
Map<String, Object> variables = bo.getVariables();
|
Map<String, Object> variables = bo.getVariables();
|
||||||
Task task = taskService.getById(taskId);
|
Task task = taskService.getById(taskId);
|
||||||
|
if (ObjectUtil.isNull(task)) {
|
||||||
|
throw new ServiceException("任务不存在!");
|
||||||
|
}
|
||||||
Instance instance = insService.getById(task.getInstanceId());
|
Instance instance = insService.getById(task.getInstanceId());
|
||||||
|
if (ObjectUtil.isNull(instance)) {
|
||||||
|
throw new ServiceException("流程实例不存在");
|
||||||
|
}
|
||||||
Definition definition = defService.getById(task.getDefinitionId());
|
Definition definition = defService.getById(task.getDefinitionId());
|
||||||
|
if (ObjectUtil.isNull(definition)) {
|
||||||
|
throw new ServiceException("流程定义不存在");
|
||||||
|
}
|
||||||
Map<String, Object> mergeVariable = MapUtil.mergeAll(instance.getVariableMap(), variables);
|
Map<String, Object> mergeVariable = MapUtil.mergeAll(instance.getVariableMap(), variables);
|
||||||
// 获取下一节点列表
|
// 获取下一节点列表
|
||||||
List<Node> nextNodeList = nodeService.getNextNodeList(task.getDefinitionId(), task.getNodeCode(), null, SkipType.PASS.getKey(), mergeVariable);
|
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);
|
List<FlowNode> nextFlowNodes = BeanUtil.copyToList(nextNodeList, FlowNode.class);
|
||||||
// 只获取中间节点
|
// 只获取中间节点
|
||||||
nextFlowNodes = StreamUtils.filter(nextFlowNodes, node -> NodeType.BETWEEN.getKey().equals(node.getNodeType()));
|
nextFlowNodes = StreamUtils.filter(nextFlowNodes, node -> NodeType.BETWEEN.getKey().equals(node.getNodeType()));
|
||||||
@@ -719,13 +752,19 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
|
|||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public boolean taskOperation(TaskOperationBo bo, String taskOperation) {
|
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());
|
FlowParams flowParams = FlowParams.build().message(bo.getMessage());
|
||||||
if (LoginHelper.isSuperAdmin()) {
|
if (LoginHelper.isSuperAdmin()) {
|
||||||
flowParams.ignore(true);
|
flowParams.ignore(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 根据操作类型构建 FlowParams
|
// 根据操作类型构建 FlowParams
|
||||||
switch (taskOperation) {
|
switch (op) {
|
||||||
case DELEGATE_TASK, TRANSFER_TASK -> {
|
case DELEGATE_TASK, TRANSFER_TASK -> {
|
||||||
ValidatorUtils.validate(bo, AddGroup.class);
|
ValidatorUtils.validate(bo, AddGroup.class);
|
||||||
flowParams.addHandlers(Collections.singletonList(bo.getUserId()));
|
flowParams.addHandlers(Collections.singletonList(bo.getUserId()));
|
||||||
@@ -738,47 +777,63 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
|
|||||||
ValidatorUtils.validate(bo, EditGroup.class);
|
ValidatorUtils.validate(bo, EditGroup.class);
|
||||||
flowParams.reductionHandlers(bo.getUserIds());
|
flowParams.reductionHandlers(bo.getUserIds());
|
||||||
}
|
}
|
||||||
default -> {
|
|
||||||
log.error("Invalid operation type:{} ", taskOperation);
|
|
||||||
throw new ServiceException("Invalid operation type " + taskOperation);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Long taskId = bo.getTaskId();
|
Long taskId = bo.getTaskId();
|
||||||
Task task = taskService.getById(taskId);
|
Task task = taskService.getById(taskId);
|
||||||
|
if (ObjectUtil.isNull(task)) {
|
||||||
|
throw new ServiceException("任务不存在!");
|
||||||
|
}
|
||||||
FlowNode flowNode = getByNodeCode(task.getNodeCode(), task.getDefinitionId());
|
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())) {
|
if (CooperateType.isOrSign(flowNode.getNodeRatio())) {
|
||||||
throw new ServiceException(task.getNodeName() + "不是会签或票签节点!");
|
throw new ServiceException(task.getNodeName() + "不是会签或票签节点!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置任务状态并执行对应的任务操作
|
// 设置任务状态并执行对应的任务操作
|
||||||
switch (taskOperation) {
|
boolean result = false;
|
||||||
//委派任务
|
switch (op) {
|
||||||
case DELEGATE_TASK -> {
|
case DELEGATE_TASK -> {
|
||||||
flowParams.hisStatus(TaskStatusEnum.DEPUTE.getStatus());
|
flowParams.hisStatus(TaskStatusEnum.DEPUTE.getStatus());
|
||||||
return taskService.depute(taskId, flowParams);
|
result = taskService.depute(taskId, flowParams);
|
||||||
}
|
}
|
||||||
//转办任务
|
|
||||||
case TRANSFER_TASK -> {
|
case TRANSFER_TASK -> {
|
||||||
flowParams.hisStatus(TaskStatusEnum.TRANSFER.getStatus());
|
flowParams.hisStatus(TaskStatusEnum.TRANSFER.getStatus());
|
||||||
return taskService.transfer(taskId, flowParams);
|
result = taskService.transfer(taskId, flowParams);
|
||||||
}
|
}
|
||||||
//加签,增加办理人
|
|
||||||
case ADD_SIGNATURE -> {
|
case ADD_SIGNATURE -> {
|
||||||
flowParams.hisStatus(TaskStatusEnum.SIGN.getStatus());
|
flowParams.hisStatus(TaskStatusEnum.SIGN.getStatus());
|
||||||
return taskService.addSignature(taskId, flowParams);
|
result = taskService.addSignature(taskId, flowParams);
|
||||||
}
|
}
|
||||||
//减签,减少办理人
|
|
||||||
case REDUCTION_SIGNATURE -> {
|
case REDUCTION_SIGNATURE -> {
|
||||||
flowParams.hisStatus(TaskStatusEnum.SIGN_OFF.getStatus());
|
flowParams.hisStatus(TaskStatusEnum.SIGN_OFF.getStatus());
|
||||||
return taskService.reductionSignature(taskId, flowParams);
|
result = taskService.reductionSignature(taskId, flowParams);
|
||||||
}
|
|
||||||
default -> {
|
|
||||||
log.error("Invalid operation type:{} ", taskOperation);
|
|
||||||
throw new ServiceException("Invalid operation type " + taskOperation);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 操作执行成功后再发送消息
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user