Explorar o código

Update 优化JavaDoc 的Sa-Token 权限码展示,通过类加载的方式优化 Javadoc 解析器,支持执行多个 Javadoc 解析器

Yue hai 1 semana
pai
achega
41b355e35e

+ 22 - 9
SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/sensitive/handler/SensitiveHandler.java

@@ -20,14 +20,29 @@ import java.util.Objects;
 /**
  * 数据脱敏json序列化工具
  *
- * @author Yjoioooo
+ * @author Iwb
  */
 @Slf4j
 public class SensitiveHandler extends JsonSerializer<String> implements ContextualSerializer {
 
-    private SensitiveStrategy strategy;
-    private String[] roleKey;
-    private String[] perms;
+    private final SensitiveStrategy strategy;
+    private final String[] roleKey;
+    private final String[] perms;
+
+    /**
+     * 提供给 jackson 创建上下文序列化器时使用 不然会报错
+     */
+    public SensitiveHandler() {
+        this.strategy = null;
+        this.roleKey = null;
+        this.perms = null;
+    }
+
+    public SensitiveHandler(SensitiveStrategy strategy, String[] strings, String[] perms) {
+        this.strategy = strategy;
+        this.roleKey = strings;
+        this.perms = perms;
+    }
 
     @Override
     public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
@@ -45,13 +60,11 @@ public class SensitiveHandler extends JsonSerializer<String> implements Contextu
     }
 
     @Override
-    public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException {
+    public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property)
+            throws JsonMappingException {
         Sensitive annotation = property.getAnnotation(Sensitive.class);
         if (Objects.nonNull(annotation) && Objects.equals(String.class, property.getType().getRawClass())) {
-            this.strategy = annotation.strategy();
-            this.roleKey = annotation.roleKey();
-            this.perms = annotation.perms();
-            return this;
+            return new SensitiveHandler(annotation.strategy(), annotation.roleKey(), annotation.perms());
         }
         return prov.findValueSerializer(property.getType(), property);
     }

+ 18 - 6
SERVER/VberAdminPlusV3/vber-common/vber-common-doc/src/main/java/com/vber/common/doc/config/SpringDocConfig.java

@@ -2,6 +2,8 @@ package com.vber.common.doc.config;
 
 import com.vber.common.core.utils.StringUtils;
 import com.vber.common.doc.config.properties.SpringDocProperties;
+import com.vber.common.doc.core.resolver.JavadocResolver;
+import com.vber.common.doc.core.resolver.SaTokenAnnotationMetadataJavadocResolver;
 import com.vber.common.doc.handler.OpenApiHandler;
 import io.swagger.v3.oas.models.OpenAPI;
 import io.swagger.v3.oas.models.Paths;
@@ -81,11 +83,13 @@ public class SpringDocConfig {
      */
     @Bean
     public OpenAPIService openApiBuilder(Optional<OpenAPI> openAPI,
-                                         SecurityService securityParser,
-                                         SpringDocConfigProperties springDocConfigProperties, PropertyResolverUtils propertyResolverUtils,
-                                         Optional<List<OpenApiBuilderCustomizer>> openApiBuilderCustomisers,
-                                         Optional<List<ServerBaseUrlCustomizer>> serverBaseUrlCustomisers, Optional<JavadocProvider> javadocProvider) {
-        return new OpenApiHandler(openAPI, securityParser, springDocConfigProperties, propertyResolverUtils, openApiBuilderCustomisers, serverBaseUrlCustomisers, javadocProvider);
+            SecurityService securityParser,
+            SpringDocConfigProperties springDocConfigProperties, PropertyResolverUtils propertyResolverUtils,
+            Optional<List<OpenApiBuilderCustomizer>> openApiBuilderCustomisers,
+            Optional<List<ServerBaseUrlCustomizer>> serverBaseUrlCustomisers, Optional<JavadocProvider> javadocProvider,
+            List<JavadocResolver> javadocResolvers) {
+        return new OpenApiHandler(openAPI, securityParser, springDocConfigProperties, propertyResolverUtils,
+                openApiBuilderCustomisers, serverBaseUrlCustomisers, javadocProvider, javadocResolvers);
     }
 
     /**
@@ -112,10 +116,18 @@ public class SpringDocConfig {
         };
     }
 
+    /**
+     * 注册SaToken JavaDoc权限注解解析器
+     */
+    @Bean
+    public JavadocResolver saTokenAnnotationJavadocResolver() {
+        return new SaTokenAnnotationMetadataJavadocResolver();
+    }
+
     /**
      * 单独使用一个类便于判断 解决springdoc路径拼接重复问题
      *
-     * @author Iwb
+     * @author Lion Li
      */
     static class PlusPaths extends Paths {
 

+ 2 - 2
SERVER/VberAdminPlusV3/vber-common/vber-common-doc/src/main/java/com/vber/common/doc/config/properties/SpringDocProperties.java

@@ -56,8 +56,8 @@ public class SpringDocProperties {
      * </p>
      *
      * @see io.swagger.v3.oas.models.info.Info
-     * <p>
-     * 为了 springboot 自动生产配置提示信息,所以这里复制一个类出来
+     *
+     *      为了 springboot 自动生产配置提示信息,所以这里复制一个类出来
      */
     @Data
     public static class InfoProperties {

+ 176 - 0
SERVER/VberAdminPlusV3/vber-common/vber-common-doc/src/main/java/com/vber/common/doc/core/model/SaTokenSecurityMetadata.java

@@ -0,0 +1,176 @@
+package com.vber.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 Iwb
+ */
+@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) ? " & " : " | ";
+        }
+
+    }
+}

+ 182 - 0
SERVER/VberAdminPlusV3/vber-common/vber-common-doc/src/main/java/com/vber/common/doc/core/resolver/AbstractMetadataJavadocResolver.java

@@ -0,0 +1,182 @@
+package com.vber.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 Iwb
+ */
+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);
+    }
+
+    @SuppressWarnings("unused")
+    private Map<String, Object> getAnnotationValueMap(AnnotatedElement annotatedElement,
+            Class<? extends Annotation> annotationClass) {
+        return AnnotationUtil.getAnnotationValueMap(annotatedElement, annotationClass);
+    }
+}

+ 52 - 0
SERVER/VberAdminPlusV3/vber-common/vber-common-doc/src/main/java/com/vber/common/doc/core/resolver/JavadocResolver.java

@@ -0,0 +1,52 @@
+package com.vber.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 Iwb
+ */
+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());
+    }
+}

+ 168 - 0
SERVER/VberAdminPlusV3/vber-common/vber-common-doc/src/main/java/com/vber/common/doc/core/resolver/SaTokenAnnotationMetadataJavadocResolver.java

@@ -0,0 +1,168 @@
+package com.vber.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 com.vber.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 Iwb
+ */
+@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) {
+            // 忽略解析错误
+        }
+    }
+
+}

+ 43 - 13
SERVER/VberAdminPlusV3/vber-common/vber-common-doc/src/main/java/com/vber/common/doc/handler/OpenApiHandler.java

@@ -2,6 +2,8 @@ package com.vber.common.doc.handler;
 
 import cn.hutool.core.io.IoUtil;
 import com.vber.common.core.utils.StreamUtils;
+import com.vber.common.doc.core.resolver.JavadocResolver;
+
 import io.swagger.v3.core.jackson.TypeNameResolver;
 import io.swagger.v3.core.util.AnnotationsUtils;
 import io.swagger.v3.oas.annotations.tags.Tags;
@@ -33,6 +35,8 @@ import java.util.stream.Stream;
 /**
  * 自定义 openapi 处理器
  * 对源码功能进行修改 增强使用
+ * 
+ * @author Iwb
  */
 @Slf4j
 @SuppressWarnings("all")
@@ -83,6 +87,11 @@ public class OpenApiHandler extends OpenAPIService {
      */
     private final PropertyResolverUtils propertyResolverUtils;
 
+    /**
+     * Javadoc解析器接口
+     */
+    private final List<JavadocResolver> javadocResolvers;
+
     /**
      * The javadoc provider.
      */
@@ -120,11 +129,13 @@ public class OpenApiHandler extends OpenAPIService {
      * @param javadocProvider           the javadoc provider
      */
     public OpenApiHandler(Optional<OpenAPI> openAPI, SecurityService securityParser,
-                          SpringDocConfigProperties springDocConfigProperties, PropertyResolverUtils propertyResolverUtils,
-                          Optional<List<OpenApiBuilderCustomizer>> openApiBuilderCustomizers,
-                          Optional<List<ServerBaseUrlCustomizer>> serverBaseUrlCustomizers,
-                          Optional<JavadocProvider> javadocProvider) {
-        super(openAPI, securityParser, springDocConfigProperties, propertyResolverUtils, openApiBuilderCustomizers, serverBaseUrlCustomizers, javadocProvider);
+            SpringDocConfigProperties springDocConfigProperties, PropertyResolverUtils propertyResolverUtils,
+            Optional<List<OpenApiBuilderCustomizer>> openApiBuilderCustomizers,
+            Optional<List<ServerBaseUrlCustomizer>> serverBaseUrlCustomizers,
+            Optional<JavadocProvider> javadocProvider,
+            List<JavadocResolver> javadocResolvers) {
+        super(openAPI, securityParser, springDocConfigProperties, propertyResolverUtils, openApiBuilderCustomizers,
+                serverBaseUrlCustomizers, javadocProvider);
         if (openAPI.isPresent()) {
             this.openAPI = openAPI.get();
             if (this.openAPI.getComponents() == null)
@@ -140,6 +151,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);
     }
@@ -159,7 +171,7 @@ public class OpenApiHandler extends OpenAPIService {
                     .collect(Collectors.toSet());
 
         if (springdocTags.containsKey(handlerMethod)) {
-            Tag tag = springdocTags.get(handlerMethod);
+            io.swagger.v3.oas.models.tags.Tag tag = springdocTags.get(handlerMethod);
             tagsStr.add(tag.getName());
             if (openAPI.getTags() == null || !openAPI.getTags().contains(tag)) {
                 openAPI.addTagsItem(tag);
@@ -179,11 +191,10 @@ public class OpenApiHandler extends OpenAPIService {
 
         if (isAutoTagClasses(operation)) {
 
-
             if (javadocProvider.isPresent()) {
                 String description = javadocProvider.get().getClassJavadoc(handlerMethod.getBeanType());
                 if (StringUtils.isNotBlank(description)) {
-                    Tag tag = new Tag();
+                    io.swagger.v3.oas.models.tags.Tag tag = new io.swagger.v3.oas.models.tags.Tag();
 
                     // 自定义部分 修改使用java注释当tag名
                     List<String> list = IoUtil.readLines(new StringReader(description), new ArrayList<>());
@@ -204,7 +215,7 @@ public class OpenApiHandler extends OpenAPIService {
 
         if (!CollectionUtils.isEmpty(tags)) {
             // Existing tags
-            List<Tag> openApiTags = openAPI.getTags();
+            List<io.swagger.v3.oas.models.tags.Tag> openApiTags = openAPI.getTags();
             if (!CollectionUtils.isEmpty(openApiTags))
                 tags.addAll(openApiTags);
             openAPI.setTags(new ArrayList<>(tags));
@@ -220,16 +231,34 @@ 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;
     }
 
-    private void buildTagsFromMethod(Method method, Set<Tag> tags, Set<String> tagsStr, Locale locale) {
+    private void buildTagsFromMethod(Method method, Set<io.swagger.v3.oas.models.tags.Tag> tags, Set<String> tagsStr,
+            Locale locale) {
         // method tags
         Set<Tags> tagsSet = AnnotatedElementUtils
                 .findAllMergedAnnotations(method, Tags.class);
         Set<io.swagger.v3.oas.annotations.tags.Tag> methodTags = tagsSet.stream()
                 .flatMap(x -> Stream.of(x.value())).collect(Collectors.toSet());
-        methodTags.addAll(AnnotatedElementUtils.findAllMergedAnnotations(method, io.swagger.v3.oas.annotations.tags.Tag.class));
+        methodTags.addAll(
+                AnnotatedElementUtils.findAllMergedAnnotations(method, io.swagger.v3.oas.annotations.tags.Tag.class));
         if (!CollectionUtils.isEmpty(methodTags)) {
             tagsStr.addAll(StreamUtils.toSet(methodTags, tag -> propertyResolverUtils.resolve(tag.name(), locale)));
             List<io.swagger.v3.oas.annotations.tags.Tag> allTags = new ArrayList<>(methodTags);
@@ -237,8 +266,9 @@ public class OpenApiHandler extends OpenAPIService {
         }
     }
 
-    private void addTags(List<io.swagger.v3.oas.annotations.tags.Tag> sourceTags, Set<Tag> tags, Locale locale) {
-        Optional<Set<Tag>> optionalTagSet = AnnotationsUtils
+    private void addTags(List<io.swagger.v3.oas.annotations.tags.Tag> sourceTags,
+            Set<io.swagger.v3.oas.models.tags.Tag> tags, Locale locale) {
+        Optional<Set<io.swagger.v3.oas.models.tags.Tag>> optionalTagSet = AnnotationsUtils
                 .getTags(sourceTags.toArray(new io.swagger.v3.oas.annotations.tags.Tag[0]), true);
         optionalTagSet.ifPresent(tagsSet -> {
             tagsSet.forEach(tag -> {

+ 13 - 3
SERVER/VberAdminPlusV3/vber-common/vber-common-translation/src/main/java/com/vber/common/translation/core/handler/TranslationHandler.java

@@ -31,7 +31,18 @@ public class TranslationHandler extends JsonSerializer<Object> implements Contex
      */
     public static final Map<String, TranslationInterface<?>> TRANSLATION_MAPPER = new ConcurrentHashMap<>();
 
-    private Translation translation;
+    private final Translation translation;
+
+    /**
+     * 提供给 jackson 创建上下文序列化器时使用 不然会报错
+     */
+    public TranslationHandler() {
+        this.translation = null;
+    }
+
+    public TranslationHandler(Translation translation) {
+        this.translation = translation;
+    }
 
     @Override
     public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
@@ -64,8 +75,7 @@ public class TranslationHandler extends JsonSerializer<Object> implements Contex
             throws JsonMappingException {
         Translation translation = property.getAnnotation(Translation.class);
         if (Objects.nonNull(translation)) {
-            this.translation = translation;
-            return this;
+            return new TranslationHandler(translation);
         }
         return prov.findValueSerializer(property.getType(), property);
     }