init
This commit is contained in:
51
ruoyi-common/ruoyi-common-doc/pom.xml
Normal file
51
ruoyi-common/ruoyi-common-doc/pom.xml
Normal file
@@ -0,0 +1,51 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-common</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>ruoyi-common-doc</artifactId>
|
||||
|
||||
<description>
|
||||
ruoyi-common-doc 系统接口
|
||||
</description>
|
||||
|
||||
<dependencies>
|
||||
<!-- 核心模块 -->
|
||||
<dependency>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-common-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Web 服务器抽象 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-web-server</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 接口文档 -->
|
||||
<dependency>
|
||||
<groupId>org.springdoc</groupId>
|
||||
<artifactId>springdoc-openapi-starter-webmvc-api</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- JavaDoc 运行时读取 -->
|
||||
<dependency>
|
||||
<groupId>com.github.therapi</groupId>
|
||||
<artifactId>therapi-runtime-javadoc</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Jackson Kotlin 模块 -->
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.module</groupId>
|
||||
<artifactId>jackson-module-kotlin</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,154 @@
|
||||
package org.dromara.common.doc.config;
|
||||
|
||||
import io.swagger.v3.oas.models.OpenAPI;
|
||||
import io.swagger.v3.oas.models.Paths;
|
||||
import io.swagger.v3.oas.models.info.Info;
|
||||
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.customizer.ClassTagOperationCustomizer;
|
||||
import org.dromara.common.doc.core.customizer.JavadocOperationCustomizer;
|
||||
import org.dromara.common.doc.core.resolver.JavadocResolver;
|
||||
import org.dromara.common.doc.core.resolver.SaTokenAnnotationMetadataJavadocResolver;
|
||||
import org.springdoc.core.configuration.SpringDocConfiguration;
|
||||
import org.springdoc.core.customizers.GlobalOpenApiCustomizer;
|
||||
import org.springdoc.core.providers.JavadocProvider;
|
||||
import org.springdoc.core.utils.PropertyResolverUtils;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.web.server.autoconfigure.ServerProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 接口文档配置
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@AutoConfiguration(before = SpringDocConfiguration.class)
|
||||
@EnableConfigurationProperties(SpringDocProperties.class)
|
||||
@ConditionalOnProperty(name = "springdoc.api-docs.enabled", havingValue = "true", matchIfMissing = true)
|
||||
public class SpringDocConfig {
|
||||
|
||||
private final ServerProperties serverProperties;
|
||||
|
||||
/**
|
||||
* 构建基础 OpenAPI 文档对象。
|
||||
*
|
||||
* @param properties SpringDoc 配置
|
||||
* @return OpenAPI 对象
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(OpenAPI.class)
|
||||
public OpenAPI openApi(SpringDocProperties properties) {
|
||||
OpenAPI openApi = new OpenAPI();
|
||||
// 文档基本信息
|
||||
SpringDocProperties.InfoProperties infoProperties = properties.getInfo();
|
||||
Info info = convertInfo(infoProperties);
|
||||
openApi.info(info);
|
||||
// 扩展文档信息
|
||||
openApi.externalDocs(properties.getExternalDocs());
|
||||
openApi.tags(properties.getTags());
|
||||
openApi.paths(properties.getPaths());
|
||||
if (properties.getComponents() != null) {
|
||||
openApi.components(properties.getComponents());
|
||||
Set<String> keySet = properties.getComponents().getSecuritySchemes().keySet();
|
||||
List<SecurityRequirement> list = new ArrayList<>();
|
||||
SecurityRequirement securityRequirement = new SecurityRequirement();
|
||||
keySet.forEach(securityRequirement::addList);
|
||||
list.add(securityRequirement);
|
||||
openApi.security(list);
|
||||
}
|
||||
return openApi;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将自定义文档信息配置转换为 OpenAPI Info。
|
||||
*
|
||||
* @param infoProperties 文档信息配置
|
||||
* @return Info 对象
|
||||
*/
|
||||
private Info convertInfo(SpringDocProperties.InfoProperties infoProperties) {
|
||||
Info info = new Info();
|
||||
info.setTitle(infoProperties.getTitle());
|
||||
info.setDescription(infoProperties.getDescription());
|
||||
info.setContact(infoProperties.getContact());
|
||||
info.setLicense(infoProperties.getLicense());
|
||||
info.setVersion(infoProperties.getVersion());
|
||||
return info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Controller 类级标签增强
|
||||
*/
|
||||
@Bean
|
||||
public ClassTagOperationCustomizer classTagOperationCustomizer(Optional<JavadocProvider> javadocProvider,
|
||||
PropertyResolverUtils propertyResolverUtils) {
|
||||
return new ClassTagOperationCustomizer(javadocProvider, propertyResolverUtils);
|
||||
}
|
||||
|
||||
/**
|
||||
* 方法 JavaDoc 与权限描述增强
|
||||
*/
|
||||
@Bean
|
||||
public JavadocOperationCustomizer javadocOperationCustomizer(Optional<JavadocProvider> javadocProvider,
|
||||
List<JavadocResolver> javadocResolvers) {
|
||||
return new JavadocOperationCustomizer(javadocProvider, javadocResolvers);
|
||||
}
|
||||
|
||||
/**
|
||||
* 对已经生成好的 OpenApi 进行自定义操作
|
||||
*/
|
||||
@Bean
|
||||
public GlobalOpenApiCustomizer openApiCustomizer() {
|
||||
String contextPath = serverProperties.getServlet().getContextPath();
|
||||
String finalContextPath;
|
||||
if (StringUtils.isBlank(contextPath) || "/".equals(contextPath)) {
|
||||
finalContextPath = "";
|
||||
} else {
|
||||
finalContextPath = contextPath;
|
||||
}
|
||||
// 对所有路径增加前置上下文路径
|
||||
return openApi -> {
|
||||
Paths oldPaths = openApi.getPaths();
|
||||
if (oldPaths instanceof PlusPaths) {
|
||||
return;
|
||||
}
|
||||
PlusPaths newPaths = new PlusPaths();
|
||||
oldPaths.forEach((k, v) -> newPaths.addPathItem(finalContextPath + k, v));
|
||||
openApi.setPaths(newPaths);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册SaToken JavaDoc权限注解解析器
|
||||
*/
|
||||
@Bean
|
||||
public JavadocResolver saTokenAnnotationJavadocResolver() {
|
||||
return new SaTokenAnnotationMetadataJavadocResolver();
|
||||
}
|
||||
|
||||
/**
|
||||
* 单独使用一个类便于判断 解决springdoc路径拼接重复问题
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
static class PlusPaths extends Paths {
|
||||
|
||||
/**
|
||||
* 构造路径缓存标记对象。
|
||||
*/
|
||||
public PlusPaths() {
|
||||
super();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package org.dromara.common.doc.config.properties;
|
||||
|
||||
import io.swagger.v3.oas.models.Components;
|
||||
import io.swagger.v3.oas.models.ExternalDocumentation;
|
||||
import io.swagger.v3.oas.models.Paths;
|
||||
import io.swagger.v3.oas.models.info.Contact;
|
||||
import io.swagger.v3.oas.models.info.License;
|
||||
import io.swagger.v3.oas.models.tags.Tag;
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.boot.context.properties.NestedConfigurationProperty;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* swagger 配置属性
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Data
|
||||
@ConfigurationProperties(prefix = "springdoc")
|
||||
public class SpringDocProperties {
|
||||
|
||||
/**
|
||||
* 文档基本信息
|
||||
*/
|
||||
@NestedConfigurationProperty
|
||||
private InfoProperties info = new InfoProperties();
|
||||
|
||||
/**
|
||||
* 扩展文档地址
|
||||
*/
|
||||
@NestedConfigurationProperty
|
||||
private ExternalDocumentation externalDocs;
|
||||
|
||||
/**
|
||||
* 标签
|
||||
*/
|
||||
private List<Tag> tags = null;
|
||||
|
||||
/**
|
||||
* 路径
|
||||
*/
|
||||
@NestedConfigurationProperty
|
||||
private Paths paths = null;
|
||||
|
||||
/**
|
||||
* 组件
|
||||
*/
|
||||
@NestedConfigurationProperty
|
||||
private Components components = null;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 文档的基础属性信息
|
||||
* </p>
|
||||
*
|
||||
* @see io.swagger.v3.oas.models.info.Info
|
||||
* <p>
|
||||
* 为了 springboot 自动生产配置提示信息,所以这里复制一个类出来
|
||||
*/
|
||||
@Data
|
||||
public static class InfoProperties {
|
||||
|
||||
/**
|
||||
* 标题
|
||||
*/
|
||||
private String title = null;
|
||||
|
||||
/**
|
||||
* 描述
|
||||
*/
|
||||
private String description = null;
|
||||
|
||||
/**
|
||||
* 联系人信息
|
||||
*/
|
||||
@NestedConfigurationProperty
|
||||
private Contact contact = null;
|
||||
|
||||
/**
|
||||
* 许可证
|
||||
*/
|
||||
@NestedConfigurationProperty
|
||||
private License license = null;
|
||||
|
||||
/**
|
||||
* 版本
|
||||
*/
|
||||
private String version = null;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
package org.dromara.common.doc.core.customizer;
|
||||
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import io.swagger.v3.core.util.AnnotationsUtils;
|
||||
import io.swagger.v3.oas.annotations.tags.Tags;
|
||||
import io.swagger.v3.oas.models.OpenAPI;
|
||||
import io.swagger.v3.oas.models.Operation;
|
||||
import io.swagger.v3.oas.models.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springdoc.core.customizers.GlobalOpenApiCustomizer;
|
||||
import org.springdoc.core.customizers.GlobalOperationCustomizer;
|
||||
import org.springdoc.core.providers.JavadocProvider;
|
||||
import org.springdoc.core.service.OpenAPIService;
|
||||
import org.springdoc.core.utils.PropertyResolverUtils;
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
|
||||
import java.io.StringReader;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Controller 类级标签增强。
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class ClassTagOperationCustomizer implements GlobalOperationCustomizer, GlobalOpenApiCustomizer {
|
||||
|
||||
/**
|
||||
* JavaDoc 提供器。
|
||||
*/
|
||||
private final Optional<JavadocProvider> javadocProvider;
|
||||
|
||||
/**
|
||||
* SpringDoc 属性解析工具。
|
||||
*/
|
||||
private final PropertyResolverUtils propertyResolverUtils;
|
||||
|
||||
/**
|
||||
* 已解析的 OpenAPI 顶层标签缓存。
|
||||
*/
|
||||
private final Map<String, Tag> tags = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* 已被类 JavaDoc 替换的自动标签名称集合。
|
||||
*/
|
||||
private final Set<String> replacedAutoTagNames = ConcurrentHashMap.newKeySet();
|
||||
|
||||
/**
|
||||
* 自定义接口操作的标签信息。
|
||||
*
|
||||
* @param operation OpenAPI 操作对象
|
||||
* @param handlerMethod 处理器方法
|
||||
* @return 自定义后的 OpenAPI 操作对象
|
||||
*/
|
||||
@Override
|
||||
public Operation customize(Operation operation, HandlerMethod handlerMethod) {
|
||||
Class<?> beanType = handlerMethod.getBeanType();
|
||||
List<io.swagger.v3.oas.annotations.tags.Tag> classTags = getClassTags(beanType);
|
||||
if (!CollectionUtils.isEmpty(classTags)) {
|
||||
// 优先使用 Controller 类上的 @Tag / @Tags,保持 Swagger 原生注解语义
|
||||
addAnnotationTags(operation, classTags);
|
||||
return operation;
|
||||
}
|
||||
|
||||
String tagName = getClassJavadocTagName(beanType);
|
||||
if (StringUtils.isBlank(tagName)) {
|
||||
return operation;
|
||||
}
|
||||
|
||||
String autoTagName = OpenAPIService.splitCamelCase(beanType.getSimpleName());
|
||||
if (!shouldUseClassJavadocTag(operation, autoTagName)) {
|
||||
return operation;
|
||||
}
|
||||
|
||||
// 无显式 @Tag 时,将 springdoc 自动生成的类名 tag 替换为类 JavaDoc 第一行
|
||||
removeOperationTag(operation, autoTagName);
|
||||
addOperationTag(operation, tagName);
|
||||
replacedAutoTagNames.add(autoTagName);
|
||||
tags.putIfAbsent(tagName, new Tag().name(tagName).description(javadocProvider.get().getClassJavadoc(beanType)));
|
||||
return operation;
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义 OpenAPI 顶层标签集合。
|
||||
*
|
||||
* @param openApi OpenAPI 文档对象
|
||||
*/
|
||||
@Override
|
||||
public void customise(OpenAPI openApi) {
|
||||
if (!CollectionUtils.isEmpty(openApi.getTags()) && !CollectionUtils.isEmpty(replacedAutoTagNames)) {
|
||||
// 移除已被 JavaDoc tag 替换的默认类名 tag,避免 Swagger UI 出现空分组
|
||||
openApi.getTags().removeIf(tag -> replacedAutoTagNames.contains(tag.getName()));
|
||||
}
|
||||
// 将类级 @Tag 描述或 JavaDoc 描述补充到 OpenAPI 顶层 tags
|
||||
tags.values().forEach(tag -> {
|
||||
if (openApi.getTags() == null || openApi.getTags().stream().noneMatch(item -> Objects.equals(item.getName(), tag.getName()))) {
|
||||
openApi.addTagsItem(tag);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 Controller 类上的 Swagger 标签注解。
|
||||
*
|
||||
* @param beanType Controller 类型
|
||||
* @return 标签注解列表
|
||||
*/
|
||||
private List<io.swagger.v3.oas.annotations.tags.Tag> getClassTags(Class<?> beanType) {
|
||||
Set<Tags> tagsSet = AnnotatedElementUtils.findAllMergedAnnotations(beanType, Tags.class);
|
||||
Set<io.swagger.v3.oas.annotations.tags.Tag> mergedTags = tagsSet.stream()
|
||||
.flatMap(item -> Stream.of(item.value()))
|
||||
.collect(Collectors.toSet());
|
||||
mergedTags.addAll(AnnotatedElementUtils.findAllMergedAnnotations(beanType, io.swagger.v3.oas.annotations.tags.Tag.class));
|
||||
return new ArrayList<>(mergedTags);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将类级 Swagger 标签追加到接口操作与顶层标签缓存。
|
||||
*
|
||||
* @param operation OpenAPI 操作对象
|
||||
* @param classTags 类级标签注解
|
||||
*/
|
||||
private void addAnnotationTags(Operation operation, List<io.swagger.v3.oas.annotations.tags.Tag> classTags) {
|
||||
classTags.stream()
|
||||
.map(io.swagger.v3.oas.annotations.tags.Tag::name)
|
||||
.map(name -> propertyResolverUtils.resolve(name, Locale.getDefault()))
|
||||
.filter(StringUtils::isNotBlank)
|
||||
.forEach(name -> addOperationTag(operation, name));
|
||||
|
||||
AnnotationsUtils.getTags(classTags.toArray(new io.swagger.v3.oas.annotations.tags.Tag[0]), true)
|
||||
.ifPresent(items -> items.forEach(tag -> {
|
||||
tag.name(propertyResolverUtils.resolve(tag.getName(), Locale.getDefault()));
|
||||
tag.description(propertyResolverUtils.resolve(tag.getDescription(), Locale.getDefault()));
|
||||
if (StringUtils.isNotBlank(tag.getName())) {
|
||||
tags.putIfAbsent(tag.getName(), tag);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取类 JavaDoc 第一行作为标签名称。
|
||||
*
|
||||
* @param beanType Controller 类型
|
||||
* @return 标签名称,未配置 JavaDoc 时返回 null
|
||||
*/
|
||||
private String getClassJavadocTagName(Class<?> beanType) {
|
||||
if (javadocProvider.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
String description = javadocProvider.get().getClassJavadoc(beanType);
|
||||
if (StringUtils.isBlank(description)) {
|
||||
return null;
|
||||
}
|
||||
// 与原 OpenApiHandler 保持一致:类 JavaDoc 第一行作为 tag 名,完整 JavaDoc 作为 tag 描述
|
||||
List<String> lines = IoUtil.readLines(new StringReader(description), new ArrayList<>());
|
||||
return lines.stream().filter(StringUtils::isNotBlank).findFirst().orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断当前操作是否可以使用类 JavaDoc 标签替换默认标签。
|
||||
*
|
||||
* @param operation OpenAPI 操作对象
|
||||
* @param autoTagName SpringDoc 自动生成的标签名
|
||||
* @return true 可以替换 false 不替换
|
||||
*/
|
||||
private boolean shouldUseClassJavadocTag(Operation operation, String autoTagName) {
|
||||
return CollectionUtils.isEmpty(operation.getTags()) || operation.getTags().contains(autoTagName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 为接口操作追加标签。
|
||||
*
|
||||
* @param operation OpenAPI 操作对象
|
||||
* @param tagName 标签名称
|
||||
*/
|
||||
private void addOperationTag(Operation operation, String tagName) {
|
||||
if (operation.getTags() == null) {
|
||||
operation.setTags(new ArrayList<>());
|
||||
}
|
||||
if (!operation.getTags().contains(tagName)) {
|
||||
operation.addTagsItem(tagName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从接口操作中移除指定标签。
|
||||
*
|
||||
* @param operation OpenAPI 操作对象
|
||||
* @param tagName 标签名称
|
||||
*/
|
||||
private void removeOperationTag(Operation operation, String tagName) {
|
||||
if (!CollectionUtils.isEmpty(operation.getTags())) {
|
||||
operation.getTags().removeIf(item -> Objects.equals(item, tagName));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package org.dromara.common.doc.core.customizer;
|
||||
|
||||
import io.swagger.v3.oas.models.Operation;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.dromara.common.doc.core.resolver.JavadocResolver;
|
||||
import org.springdoc.core.customizers.GlobalOperationCustomizer;
|
||||
import org.springdoc.core.providers.JavadocProvider;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 方法 JavaDoc 与扩展描述增强。
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class JavadocOperationCustomizer implements GlobalOperationCustomizer {
|
||||
|
||||
/**
|
||||
* JavaDoc 提供器。
|
||||
*/
|
||||
private final Optional<JavadocProvider> javadocProvider;
|
||||
|
||||
/**
|
||||
* JavaDoc 扩展解析器列表。
|
||||
*/
|
||||
private final List<JavadocResolver> javadocResolvers;
|
||||
|
||||
/**
|
||||
* 自定义接口操作的 JavaDoc 描述。
|
||||
*
|
||||
* @param operation OpenAPI 操作对象
|
||||
* @param handlerMethod 处理器方法
|
||||
* @return 自定义后的 OpenAPI 操作对象
|
||||
*/
|
||||
@Override
|
||||
public Operation customize(Operation operation, HandlerMethod handlerMethod) {
|
||||
javadocProvider.ifPresent(provider -> {
|
||||
String description = provider.getMethodJavadocDescription(handlerMethod.getMethod());
|
||||
if (StringUtils.isNotBlank(description)) {
|
||||
// 使用方法 JavaDoc 首句作为接口摘要,完整 JavaDoc 作为接口描述
|
||||
operation.setSummary(provider.getFirstSentence(description));
|
||||
operation.setDescription(description);
|
||||
}
|
||||
});
|
||||
|
||||
if (CollectionUtils.isEmpty(javadocResolvers)) {
|
||||
return operation;
|
||||
}
|
||||
|
||||
StringBuilder description = new StringBuilder(Optional.ofNullable(operation.getDescription()).orElse(""));
|
||||
List<String> resolvedDescriptions = javadocResolvers.stream()
|
||||
.sorted()
|
||||
// 只执行支持当前 HandlerMethod 的扩展解析器,避免无注解接口被追加权限说明
|
||||
.filter(resolver -> resolver.supports(handlerMethod))
|
||||
.map(resolver -> resolver.resolve(handlerMethod, operation))
|
||||
.filter(StringUtils::isNotBlank)
|
||||
.toList();
|
||||
if (!resolvedDescriptions.isEmpty()) {
|
||||
// 在原方法 JavaDoc 后追加权限等扩展描述,保持现有 resolver 扩展点
|
||||
resolvedDescriptions.forEach(description::append);
|
||||
operation.setDescription(description.toString());
|
||||
}
|
||||
return operation;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
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,217 @@
|
||||
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;
|
||||
|
||||
/**
|
||||
* 构造元数据 Javadoc 解析器。
|
||||
*
|
||||
* @param metadataProvider 元数据提供者
|
||||
*/
|
||||
public AbstractMetadataJavadocResolver(Supplier<M> metadataProvider) {
|
||||
this(metadataProvider, LOWEST_PRECEDENCE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造带排序值的元数据 Javadoc 解析器。
|
||||
*
|
||||
* @param metadataProvider 元数据提供者
|
||||
* @param order 排序值
|
||||
*/
|
||||
public AbstractMetadataJavadocResolver(Supplier<M> metadataProvider, int order) {
|
||||
this.metadataProvider = metadataProvider;
|
||||
this.order = order;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取解析器排序值。
|
||||
*
|
||||
* @return 排序值
|
||||
*/
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return order;
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用当前元数据解析接口文档描述。
|
||||
*
|
||||
* @param handlerMethod 处理器方法
|
||||
* @param operation Swagger Operation 实例
|
||||
* @return 解析后的 Javadoc 内容
|
||||
*/
|
||||
@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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定元素上的注解属性映射。
|
||||
*
|
||||
* @param annotatedElement 被注解元素
|
||||
* @param annotationClass 注解类型
|
||||
* @return 注解属性映射
|
||||
*/
|
||||
private Map<String, Object> getAnnotationValueMap(AnnotatedElement annotatedElement, Class<? extends Annotation> annotationClass) {
|
||||
return AnnotationUtil.getAnnotationValueMap(annotatedElement, annotationClass);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
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);
|
||||
|
||||
/**
|
||||
* 获取解析器优先级。
|
||||
*
|
||||
* @return 优先级
|
||||
*/
|
||||
default int getOrder() {
|
||||
return Ordered.LOWEST_PRECEDENCE;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取解析器的名称
|
||||
*
|
||||
* @return 解析器名称
|
||||
*/
|
||||
default String getName() {
|
||||
return this.getClass().getSimpleName();
|
||||
}
|
||||
|
||||
/**
|
||||
* 比较解析器执行顺序。
|
||||
*
|
||||
* @param o 其他解析器
|
||||
* @return 比较结果
|
||||
*/
|
||||
@Override
|
||||
default int compareTo(@NotNull JavadocResolver o) {
|
||||
return Integer.compare(getOrder(), o.getOrder());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
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;
|
||||
|
||||
/**
|
||||
* Sa-Token 注解包名。
|
||||
*/
|
||||
private static final String BASE_CLASS_NAME = "cn.dev33.satoken.annotation";
|
||||
|
||||
/**
|
||||
* Sa-Token 角色校验注解类名。
|
||||
*/
|
||||
private static final String SA_CHECK_ROLE_CLASS_NAME = BASE_CLASS_NAME + ".SaCheckRole";
|
||||
|
||||
/**
|
||||
* Sa-Token 权限校验注解类名。
|
||||
*/
|
||||
private static final String SA_CHECK_PERMISSION_CLASS_NAME = BASE_CLASS_NAME + ".SaCheckPermission";
|
||||
|
||||
/**
|
||||
* Sa-Token 忽略校验注解类名。
|
||||
*/
|
||||
private static final String SA_IGNORE_CLASS_NAME = BASE_CLASS_NAME + ".SaIgnore";
|
||||
|
||||
/**
|
||||
* Sa-Token 登录校验注解类名。
|
||||
*/
|
||||
private static final String SA_CHECK_LOGIN_NAME = BASE_CLASS_NAME + ".SaCheckLogin";
|
||||
|
||||
/**
|
||||
* Sa-Token 角色校验注解类型。
|
||||
*/
|
||||
private static final Class<? extends Annotation> SA_CHECK_ROLE_CLASS;
|
||||
|
||||
/**
|
||||
* Sa-Token 权限校验注解类型。
|
||||
*/
|
||||
private static final Class<? extends Annotation> SA_CHECK_PERMISSION_CLASS;
|
||||
|
||||
/**
|
||||
* Sa-Token 忽略校验注解类型。
|
||||
*/
|
||||
private static final Class<? extends Annotation> SA_IGNORE_CLASS;
|
||||
|
||||
/**
|
||||
* Sa-Token 登录校验注解类型。
|
||||
*/
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造 Sa-Token 权限解析器。
|
||||
*/
|
||||
public SaTokenAnnotationMetadataJavadocResolver() {
|
||||
this(DEFAULT_METADATA_PROVIDER);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用自定义元数据提供者创建解析器。
|
||||
*
|
||||
* @param metadataProvider 元数据提供者
|
||||
*/
|
||||
public SaTokenAnnotationMetadataJavadocResolver(Supplier<SaTokenSecurityMetadata> metadataProvider) {
|
||||
super(metadataProvider);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用指定顺序创建解析器。
|
||||
*
|
||||
* @param order 顺序值
|
||||
*/
|
||||
public SaTokenAnnotationMetadataJavadocResolver(int order) {
|
||||
this(DEFAULT_METADATA_PROVIDER, order);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用自定义元数据提供者和顺序创建解析器。
|
||||
*
|
||||
* @param metadataProvider 元数据提供者
|
||||
* @param order 顺序值
|
||||
*/
|
||||
public SaTokenAnnotationMetadataJavadocResolver(Supplier<SaTokenSecurityMetadata> metadataProvider, int order) {
|
||||
super(metadataProvider, order);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断当前方法是否需要由该解析器处理。
|
||||
*
|
||||
* @param handlerMethod Handler 方法
|
||||
* @return 是否支持
|
||||
*/
|
||||
@Override
|
||||
public boolean supports(HandlerMethod handlerMethod) {
|
||||
return hasAnnotation(handlerMethod, SA_CHECK_ROLE_CLASS) || hasAnnotation(handlerMethod, SA_CHECK_PERMISSION_CLASS) || hasAnnotation(handlerMethod, SA_IGNORE_CLASS);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析 Sa-Token 注解并转换为文档说明。
|
||||
*
|
||||
* @param handlerMethod Handler 方法
|
||||
* @param operation OpenAPI 操作对象
|
||||
* @param metadata 权限元数据
|
||||
* @return Markdown 描述
|
||||
*/
|
||||
@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) {
|
||||
// 忽略解析错误
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
org.dromara.common.doc.config.SpringDocConfig
|
||||
Reference in New Issue
Block a user