diff --git a/README.md b/README.md
index 6557b21cd..96dc4ca5e 100644
--- a/README.md
+++ b/README.md
@@ -53,7 +53,7 @@ Topiam IAM/IDaaS身份管理平台 - https://www.topiam.cn/
| 权限注解 | 采用 Sa-Token 支持注解 登录校验、角色校验、权限校验、二级认证校验、HttpBasic校验、忽略校验
角色与权限校验支持多种条件 如 `AND` `OR` 或 `权限 OR 角色` 等复杂表达式 | 只支持是否存在匹配 |
| 三方鉴权 | 采用 JustAuth 第三方登录组件 支持微信、钉钉等数十种三方认证 | 无 |
| 关系数据库支持 | 原生支持 MySQL、Oracle、PostgreSQL、SQLServer
可同时使用异构切换(支持其他 mybatis-plus 支持的所有数据库 只需要增加jdbc依赖即可使用 达梦金仓等均有成功案例) | 支持 Mysql、Oracle 不支持同时使用、不支持异构切换 |
-| 缓存数据库 | 支持 Redis 5-7 支持大部分新功能特性 如 分布式限流、分布式队列 | Redis 简单 get set 支持 |
+| 缓存数据库 | 支持 Redis >= 6 支持大部分新功能特性 如 分布式限流、分布式队列 | Redis 简单 get set 支持 |
| Redis客户端 | 采用 Redisson Redis官方推荐 基于Netty的客户端工具
支持Redis 90%以上的命令 底层优化规避很多不正确的用法 例如: keys被转换为scan
支持单机、哨兵、单主集群、多主集群等模式 | Lettuce + RedisTemplate 支持模式少 工具使用繁琐
连接池采用 common-pool Bug多经常性出问题 |
| 缓存注解 | 采用 Spring-Cache 注解 对其扩展了实现支持了更多功能
例如 过期时间 最大空闲时间 组最大长度等 只需一个注解即可完成数据自动缓存 | 需手动编写Redis代码逻辑 |
| ORM框架 | 采用 Mybatis-Plus 基于对象几乎不用写SQL全java操作 功能强大插件众多
例如多租户插件 分页插件 乐观锁插件等等 | 采用 Mybatis 基于XML需要手写SQL |
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/CacheNames.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/CacheNames.java
index b1f546d36..4e07ac5f9 100644
--- a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/CacheNames.java
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/CacheNames.java
@@ -47,7 +47,7 @@ public interface CacheNames {
String SYS_USER_NAME = "sys_user_name#30d";
/**
- * 用户名称
+ * 用户昵称
*/
String SYS_NICKNAME = "sys_nickname#30d";
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/FlowCopyDTO.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/FlowCopyDTO.java
index 2f20b21f7..6001331d5 100644
--- a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/FlowCopyDTO.java
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/FlowCopyDTO.java
@@ -23,8 +23,8 @@ public class FlowCopyDTO implements Serializable {
private Long userId;
/**
- * 用户名称
+ * 用户昵称
*/
- private String userName;
+ private String nickName;
}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/StartProcessDTO.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/StartProcessDTO.java
index fa3565789..115b42f68 100644
--- a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/StartProcessDTO.java
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/StartProcessDTO.java
@@ -48,7 +48,8 @@ public class StartProcessDTO implements Serializable {
public Map getVariables() {
if (variables == null) {
- return new HashMap<>(16);
+ variables = new HashMap<>(16);
+ return variables;
}
variables.entrySet().removeIf(entry -> Objects.isNull(entry.getValue()));
return variables;
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/UserOnlineDTO.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/UserOnlineDTO.java
index 43d8c3c11..4f6534ffa 100644
--- a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/UserOnlineDTO.java
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/UserOnlineDTO.java
@@ -30,7 +30,7 @@ public class UserOnlineDTO implements Serializable {
private String deptName;
/**
- * 用户名称
+ * 用户账号
*/
private String userName;
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/UserService.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/UserService.java
index eefeef011..c61b1ed90 100644
--- a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/UserService.java
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/UserService.java
@@ -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 selectUsersByPostIds(List postIds);
/**
- * 根据用户 ID 列表查询用户名称映射关系
+ * 根据用户 ID 列表查询用户昵称映射关系
*
* @param userIds 用户 ID 列表
- * @return Map,其中 key 为用户 ID,value 为对应的用户名称
+ * @return Map,其中 key 为用户 ID,value 为对应的用户昵称
*/
- Map selectUserNamesByIds(List userIds);
+ Map selectUserNicksByIds(List userIds);
}
diff --git a/ruoyi-common/ruoyi-common-doc/src/main/java/org/dromara/common/doc/config/SpringDocConfig.java b/ruoyi-common/ruoyi-common-doc/src/main/java/org/dromara/common/doc/config/SpringDocConfig.java
index f53cb5c47..6af231ee7 100644
--- a/ruoyi-common/ruoyi-common-doc/src/main/java/org/dromara/common/doc/config/SpringDocConfig.java
+++ b/ruoyi-common/ruoyi-common-doc/src/main/java/org/dromara/common/doc/config/SpringDocConfig.java
@@ -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> openApiBuilderCustomisers,
- Optional> serverBaseUrlCustomisers, Optional javadocProvider) {
- return new OpenApiHandler(openAPI, securityParser, springDocConfigProperties, propertyResolverUtils, openApiBuilderCustomisers, serverBaseUrlCustomisers, javadocProvider);
+ Optional> serverBaseUrlCustomisers, Optional javadocProvider,
+ List 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路径拼接重复问题
*
diff --git a/ruoyi-common/ruoyi-common-doc/src/main/java/org/dromara/common/doc/core/model/SaTokenSecurityMetadata.java b/ruoyi-common/ruoyi-common-doc/src/main/java/org/dromara/common/doc/core/model/SaTokenSecurityMetadata.java
new file mode 100644
index 000000000..e0782a20c
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-doc/src/main/java/org/dromara/common/doc/core/model/SaTokenSecurityMetadata.java
@@ -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 permissions = new ArrayList<>();
+
+ /**
+ * 角色校验信息列表(对应 @SaCheckRole 注解)
+ */
+ private List 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("
访问权限
");
+
+ if (ignore) {
+ sb.append("> **权限策略**:忽略权限检查
");
+ return sb.toString();
+ }
+
+ if (!ignore && permissions.isEmpty() && roles.isEmpty()){
+ sb.append("> **权限策略**:需要登录
");
+ return sb.toString();
+ }
+
+ if (!permissions.isEmpty()) {
+ sb.append("**权限校验:**
");
+
+ permissions.forEach(p -> {
+ String permTags = Arrays.stream(p.getValues())
+ .map(v -> "`" + v + "`")
+ .collect(Collectors.joining(p.getModeSymbol()));
+
+ sb.append("- ").append(permTags).append("
");
+
+ 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("
");
+ }
+ });
+
+ sb.append("
");
+ }
+
+ if (!roles.isEmpty()) {
+ sb.append("**角色校验:**
");
+
+ roles.forEach(r -> {
+
+ String roleTags = Arrays.stream(r.getValues())
+ .map(v -> "`" + v + "`")
+ .collect(Collectors.joining(r.getModeSymbol()));
+
+ sb.append("- ").append(roleTags).append("
");
+ });
+ }
+
+ 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) ? " & " : " | ";
+ }
+
+ }
+}
diff --git a/ruoyi-common/ruoyi-common-doc/src/main/java/org/dromara/common/doc/core/resolver/AbstractMetadataJavadocResolver.java b/ruoyi-common/ruoyi-common-doc/src/main/java/org/dromara/common/doc/core/resolver/AbstractMetadataJavadocResolver.java
new file mode 100644
index 000000000..e9333c044
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-doc/src/main/java/org/dromara/common/doc/core/resolver/AbstractMetadataJavadocResolver.java
@@ -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 元数据类型
+ * @author 秋辞未寒
+ */
+public abstract class AbstractMetadataJavadocResolver implements JavadocResolver {
+
+ public static final int HIGHEST_PRECEDENCE = Integer.MIN_VALUE;
+ public static final int LOWEST_PRECEDENCE = Integer.MAX_VALUE;
+
+ private final Supplier metadataProvider;
+
+ private final int order;
+
+ public AbstractMetadataJavadocResolver(Supplier metadataProvider) {
+ this(metadataProvider, LOWEST_PRECEDENCE);
+ }
+
+ public AbstractMetadataJavadocResolver(Supplier 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 getClassAnnotationValueMap(HandlerMethod handlerMethod, Class extends Annotation> annotationClass) {
+ return AnnotationUtil.getAnnotationValueMap(handlerMethod.getBeanType(), annotationClass);
+ }
+
+ /**
+ * 获取处理器方法所属类上的注解的值
+ * @param handlerMethod 处理器方法
+ * @param annotationClassName 注解类名称
+ * @return 注解的值
+ */
+ @SuppressWarnings("unchecked")
+ public Map 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 getMethodAnnotationValueMap(HandlerMethod handlerMethod, Class extends Annotation> annotationClass) {
+ return AnnotationUtil.getAnnotationValueMap(handlerMethod.getMethod(), annotationClass);
+ }
+
+ /**
+ * 获取处理器方法所属类上的注解的值
+ * @param handlerMethod 处理器方法
+ * @param annotationClassName 注解类名称
+ * @return 注解的值
+ */
+ @SuppressWarnings("unchecked")
+ public Map getMethodAnnotationValueMap(HandlerMethod handlerMethod, String annotationClassName) {
+ Class extends Annotation> annotationClass = (Class extends Annotation>) ClassLoaderUtil.loadClass(annotationClassName, false);
+ return AnnotationUtil.getAnnotationValueMap(handlerMethod.getMethod(), annotationClass);
+ }
+
+ private Map getAnnotationValueMap(AnnotatedElement annotatedElement, Class extends Annotation> annotationClass) {
+ return AnnotationUtil.getAnnotationValueMap(annotatedElement, annotationClass);
+ }
+}
diff --git a/ruoyi-common/ruoyi-common-doc/src/main/java/org/dromara/common/doc/core/resolver/JavadocResolver.java b/ruoyi-common/ruoyi-common-doc/src/main/java/org/dromara/common/doc/core/resolver/JavadocResolver.java
new file mode 100644
index 000000000..1e295b8a1
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-doc/src/main/java/org/dromara/common/doc/core/resolver/JavadocResolver.java
@@ -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, 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());
+ }
+}
diff --git a/ruoyi-common/ruoyi-common-doc/src/main/java/org/dromara/common/doc/core/resolver/SaTokenAnnotationMetadataJavadocResolver.java b/ruoyi-common/ruoyi-common-doc/src/main/java/org/dromara/common/doc/core/resolver/SaTokenAnnotationMetadataJavadocResolver.java
new file mode 100644
index 000000000..9e09619de
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-doc/src/main/java/org/dromara/common/doc/core/resolver/SaTokenAnnotationMetadataJavadocResolver.java
@@ -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 {
+
+ /**
+ * 默认元数据提供者,每次解析都会创建一个新的元数据对象
+ */
+ public static final Supplier 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 metadataProvider) {
+ super(metadataProvider);
+ }
+
+ public SaTokenAnnotationMetadataJavadocResolver(int order) {
+ this(DEFAULT_METADATA_PROVIDER,order);
+ }
+
+ public SaTokenAnnotationMetadataJavadocResolver(Supplier 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 annotationValueMap = getMethodAnnotationValueMap(handlerMethod, SA_CHECK_PERMISSION_CLASS);
+ resolvePermissionAnnotation(metadata, annotationValueMap);
+ }
+ // 解析获取类上的注解角色信息
+ if (hasClassAnnotation(handlerMethod, SA_CHECK_PERMISSION_CLASS_NAME)) {
+ Map annotationValueMap = getClassAnnotationValueMap(handlerMethod, SA_CHECK_PERMISSION_CLASS);
+ resolvePermissionAnnotation(metadata, annotationValueMap);
+ }
+ }
+
+ /**
+ * 解析权限注解
+ */
+ private void resolvePermissionAnnotation(SaTokenSecurityMetadata metadata, Map 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 annotationValueMap = getMethodAnnotationValueMap(handlerMethod, SA_CHECK_ROLE_CLASS);
+ resolveRoleAnnotation(metadata, annotationValueMap);
+ }
+ // 解析获取类上的注解角色信息
+ if (hasClassAnnotation(handlerMethod, SA_CHECK_ROLE_CLASS_NAME)) {
+ Map annotationValueMap = getClassAnnotationValueMap(handlerMethod, SA_CHECK_ROLE_CLASS);
+ resolveRoleAnnotation(metadata, annotationValueMap);
+ }
+ }
+
+ /**
+ * 解析角色注解
+ */
+ private void resolveRoleAnnotation(SaTokenSecurityMetadata metadata, Map 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) {
+ // 忽略解析错误
+ }
+ }
+
+}
diff --git a/ruoyi-common/ruoyi-common-doc/src/main/java/org/dromara/common/doc/handler/OpenApiHandler.java b/ruoyi-common/ruoyi-common-doc/src/main/java/org/dromara/common/doc/handler/OpenApiHandler.java
index 56b73694d..32be73227 100644
--- a/ruoyi-common/ruoyi-common-doc/src/main/java/org/dromara/common/doc/handler/OpenApiHandler.java
+++ b/ruoyi-common/ruoyi-common-doc/src/main/java/org/dromara/common/doc/handler/OpenApiHandler.java
@@ -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 javadocResolvers;
+
/**
* The javadoc provider.
*/
@@ -123,7 +129,8 @@ public class OpenApiHandler extends OpenAPIService {
SpringDocConfigProperties springDocConfigProperties, PropertyResolverUtils propertyResolverUtils,
Optional> openApiBuilderCustomizers,
Optional> serverBaseUrlCustomizers,
- Optional javadocProvider) {
+ Optional javadocProvider,
+ List 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;
}
diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/interceptor/MybatisEncryptInterceptor.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/interceptor/MybatisEncryptInterceptor.java
index bcc2f4c9f..617fb7979 100644
--- a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/interceptor/MybatisEncryptInterceptor.java
+++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/interceptor/MybatisEncryptInterceptor.java
@@ -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);
}
/**
diff --git a/ruoyi-common/ruoyi-common-sensitive/src/main/java/org/dromara/common/sensitive/handler/SensitiveHandler.java b/ruoyi-common/ruoyi-common-sensitive/src/main/java/org/dromara/common/sensitive/handler/SensitiveHandler.java
index 76959259e..4c11af845 100644
--- a/ruoyi-common/ruoyi-common-sensitive/src/main/java/org/dromara/common/sensitive/handler/SensitiveHandler.java
+++ b/ruoyi-common/ruoyi-common-sensitive/src/main/java/org/dromara/common/sensitive/handler/SensitiveHandler.java
@@ -23,9 +23,24 @@ import java.util.Objects;
@Slf4j
public class SensitiveHandler extends ValueSerializer {
- 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, SerializationContext ctxt) throws JacksonException {
@@ -46,10 +61,7 @@ public class SensitiveHandler extends ValueSerializer {
public ValueSerializer> createContextual(SerializationContext ctxt, BeanProperty property) {
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 super.createContextual(ctxt, property);
}
diff --git a/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/constant/TransConstant.java b/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/constant/TransConstant.java
index c084ea1a0..50dce798a 100644
--- a/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/constant/TransConstant.java
+++ b/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/constant/TransConstant.java
@@ -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";
diff --git a/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/handler/TranslationHandler.java b/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/handler/TranslationHandler.java
index a8511055b..3393d03ee 100644
--- a/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/handler/TranslationHandler.java
+++ b/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/handler/TranslationHandler.java
@@ -29,7 +29,18 @@ public class TranslationHandler extends ValueSerializer