Procházet zdrojové kódy

添加springdoc插件,不支持spring boot 2.x 版本

jmxd před 2 roky
rodič
revize
21c15a39dc

+ 50 - 0
magic-api-plugins/magic-api-plugin-springdoc/pom.xml

@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xmlns="http://maven.apache.org/POM/4.0.0"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.ssssssss</groupId>
+        <artifactId>magic-api-plugins</artifactId>
+        <version>2.0.2</version>
+    </parent>
+    <artifactId>magic-api-plugin-springdoc</artifactId>
+    <packaging>jar</packaging>
+    <name>magic-api-plugin-springdoc</name>
+    <description>magic-api-plugin-springdoc</description>
+    <properties>
+        <springdoc.version>2.0.4</springdoc.version>
+    </properties>
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-configuration-processor</artifactId>
+            <optional>true</optional>
+        </dependency>
+        <dependency>
+            <groupId>org.springdoc</groupId>
+            <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
+            <version>${springdoc.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>jakarta.servlet</groupId>
+            <artifactId>jakarta.servlet-api</artifactId>
+            <scope>provided</scope>
+        </dependency>
+    </dependencies>
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.10.1</version>
+                <configuration>
+                    <source>17</source>
+                    <target>17</target>
+                    <encoding>UTF-8</encoding>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+</project>

+ 137 - 0
magic-api-plugins/magic-api-plugin-springdoc/src/main/java/org/ssssssss/magicapi/springdoc/MagicSpringDocConfiguration.java

@@ -0,0 +1,137 @@
+package org.ssssssss.magicapi.springdoc;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springdoc.core.properties.SwaggerUiConfigParameters;
+import org.springdoc.core.properties.SwaggerUiConfigProperties;
+import org.springframework.beans.factory.ObjectProvider;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.context.annotation.Primary;
+import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
+import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
+import org.ssssssss.magicapi.core.config.MagicAPIProperties;
+import org.ssssssss.magicapi.core.config.MagicPluginConfiguration;
+import org.ssssssss.magicapi.core.model.Plugin;
+import org.ssssssss.magicapi.core.service.MagicResourceService;
+import org.ssssssss.magicapi.core.service.impl.RequestMagicDynamicRegistry;
+import org.ssssssss.magicapi.springdoc.entity.SwaggerEntity;
+import org.ssssssss.magicapi.springdoc.entity.SwaggerProvider;
+import org.ssssssss.magicapi.utils.Mapping;
+
+import jakarta.servlet.ServletContext;
+
+import java.util.*;
+
+@Configuration
+@EnableConfigurationProperties(SpringDocConfig.class)
+@ConditionalOnProperty(
+		name = {"springdoc.api-docs.enabled"},
+		matchIfMissing = true
+)
+public class MagicSpringDocConfiguration implements MagicPluginConfiguration {
+
+	private final MagicAPIProperties properties;
+	private final SpringDocConfig springDocConfig;
+	@Autowired
+	@Lazy
+	private RequestMappingHandlerMapping requestMappingHandlerMapping;
+
+	private final ObjectProvider<RequestMagicDynamicRegistry> requestMagicDynamicRegistryObjectProvider;
+	private final MagicResourceService magicResourceService;
+	private final ServletContext servletContext;
+
+	private boolean createdMapping = false;
+
+	private static Logger logger = LoggerFactory.getLogger(MagicSpringDocConfiguration.class);
+
+	public MagicSpringDocConfiguration(MagicAPIProperties properties, SpringDocConfig springDocConfig, ObjectProvider<RequestMagicDynamicRegistry> requestMagicDynamicRegistryObjectProvider, MagicResourceService magicResourceService, ServletContext servletContext) {
+		this.properties = properties;
+		this.springDocConfig = springDocConfig;
+		this.requestMagicDynamicRegistryObjectProvider = requestMagicDynamicRegistryObjectProvider;
+		this.magicResourceService = magicResourceService;
+		this.servletContext = servletContext;
+	}
+
+	@Override
+	public Plugin plugin() {
+		return new Plugin("SpringDoc");
+	}
+
+	@Bean
+	@Primary
+	@Lazy
+	public SwaggerUiConfigParameters magicSwaggerUiConfigParameters(SwaggerUiConfigProperties swaggerUiConfigProperties) {
+		return new SwaggerUiConfigParameters(swaggerUiConfigProperties) {
+			@Override
+			public Map<String, Object> getConfigParameters() {
+				Map<String, Object> params = super.getConfigParameters();
+				if (!createdMapping) {
+					createdMapping = true;
+					try {
+						createSwaggerProvider(requestMagicDynamicRegistryObjectProvider, magicResourceService, servletContext);
+					} catch (NoSuchMethodException e) {
+						logger.error("注册springdoc接口失败", e);
+						return params;
+					}
+				}
+				Set<SwaggerUrl> urls = (Set<SwaggerUrl>) params.get("urls");
+				if (urls == null) {
+					urls = new HashSet<>();
+					SwaggerUrl url = new SwaggerUrl("default", (String) params.remove("url"), null);
+					urls.add(url);
+				} else {
+					urls = new HashSet<>(urls);
+				}
+				urls.add(new SwaggerUrl(springDocConfig.getGroupName(), springDocConfig.getLocation(), null));
+				params.put("urls", urls);
+				return params;
+			}
+		};
+	}
+
+
+	private void createSwaggerProvider(ObjectProvider<RequestMagicDynamicRegistry> requestMagicDynamicRegistryObjectProvider, MagicResourceService magicResourceService, ServletContext servletContext) throws NoSuchMethodException {
+
+		Mapping mapping = Mapping.create(requestMappingHandlerMapping);
+		RequestMappingInfo requestMappingInfo = mapping.paths(springDocConfig.getLocation()).build();
+		SwaggerEntity.License license = new SwaggerEntity.License("MIT", "https://gitee.com/ssssssss-team/magic-api/blob/master/LICENSE");
+		SwaggerEntity.Info info = new SwaggerEntity.Info(springDocConfig.getDescription(), springDocConfig.getVersion(), springDocConfig.getTitle(), license, springDocConfig.getConcat());
+
+		//具体参考:https://swagger.io/docs/specification/2-0/authentication/
+		Map<String, Object> securityDefinitionMap = new HashMap<>();
+		Map<String, Object> securityMap = new HashMap<>();
+
+		if (springDocConfig.getBasicAuth() != null) {
+			securityDefinitionMap.put(SwaggerEntity.BasicAuth.KEY_NAME, springDocConfig.getBasicAuth());
+
+			//the Basic and API key security items use an empty array instead.
+			securityMap.put(SwaggerEntity.BasicAuth.KEY_NAME, new String[]{});
+		}
+		if (springDocConfig.getApiKeyAuth() != null) {
+			securityDefinitionMap.put(SwaggerEntity.ApiKeyAuth.KEY_NAME, springDocConfig.getApiKeyAuth());
+
+			//the Basic and API key security items use an empty array instead.
+			securityMap.put(SwaggerEntity.ApiKeyAuth.KEY_NAME, new String[]{});
+		}
+		if (springDocConfig.getOauth2() != null) {
+			SwaggerEntity.OAuth2 oAuth2 = springDocConfig.getOauth2();
+			securityDefinitionMap.put(SwaggerEntity.OAuth2.KEY_NAME, oAuth2);
+
+			Map<String, String> scopes = oAuth2.getScopes();
+			if (scopes != null) {
+				Set<String> strings = scopes.keySet();
+				securityMap.put(SwaggerEntity.OAuth2.KEY_NAME, strings);
+			}
+		}
+		// 构建文档信息
+		SwaggerProvider swaggerProvider = new SwaggerProvider(requestMagicDynamicRegistryObjectProvider.getObject(), magicResourceService, servletContext.getContextPath(),
+				info, properties.isPersistenceResponseBody(), properties.getPrefix(), securityDefinitionMap, securityMap);
+		// 注册swagger.json
+		mapping.register(requestMappingInfo, swaggerProvider, SwaggerProvider.class.getDeclaredMethod("swaggerJson"));
+	}
+}

+ 145 - 0
magic-api-plugins/magic-api-plugin-springdoc/src/main/java/org/ssssssss/magicapi/springdoc/SpringDocConfig.java

@@ -0,0 +1,145 @@
+package org.ssssssss.magicapi.springdoc;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.boot.context.properties.NestedConfigurationProperty;
+import org.ssssssss.magicapi.springdoc.entity.SwaggerEntity;
+
+/**
+ * Swagger 配置
+ *
+ * @author mxd
+ */
+@ConfigurationProperties(prefix = "magic-api.swagger")
+public class SpringDocConfig {
+
+	/**
+	 * 资源名称
+	 */
+	private String name = "MagicAPI接口";
+
+	/**
+	 * 资源位置
+	 */
+	private String location = "/v2/api-docs/magic-api/swagger2.json";
+
+	/**
+	 * 分组名称
+	 */
+	private String groupName = "magic-api";
+
+	/**
+	 * 文档标题
+	 */
+	private String title = "MagicAPI Swagger Docs";
+
+	/**
+	 * 文档描述
+	 */
+	private String description = "MagicAPI 接口信息";
+
+	@NestedConfigurationProperty
+	private SwaggerEntity.Concat concat = new SwaggerEntity.Concat();
+
+	/**
+	 * 基本认证
+	 */
+	@NestedConfigurationProperty
+	private SwaggerEntity.BasicAuth basicAuth;
+
+	/**
+	 * api密钥认证
+	 */
+	@NestedConfigurationProperty
+	private SwaggerEntity.ApiKeyAuth apiKeyAuth;
+
+	/**
+	 * oauth2认证
+	 */
+	@NestedConfigurationProperty
+	private SwaggerEntity.OAuth2 oauth2;
+
+	/**
+	 * 文档版本
+	 */
+	private String version = "1.0";
+
+	public String getGroupName() {
+		return groupName;
+	}
+
+	public void setGroupName(String groupName) {
+		this.groupName = groupName;
+	}
+
+	public String getName() {
+		return name;
+	}
+
+	public void setName(String name) {
+		this.name = name;
+	}
+
+	public String getLocation() {
+		return location;
+	}
+
+	public void setLocation(String location) {
+		this.location = location;
+	}
+
+	public String getTitle() {
+		return title;
+	}
+
+	public void setTitle(String title) {
+		this.title = title;
+	}
+
+	public String getDescription() {
+		return description;
+	}
+
+	public void setDescription(String description) {
+		this.description = description;
+	}
+
+	public String getVersion() {
+		return version;
+	}
+
+	public void setVersion(String version) {
+		this.version = version;
+	}
+
+	public SwaggerEntity.Concat getConcat() {
+		return concat;
+	}
+
+	public void setConcat(SwaggerEntity.Concat concat) {
+		this.concat = concat;
+	}
+
+	public SwaggerEntity.ApiKeyAuth getApiKeyAuth() {
+		return apiKeyAuth;
+	}
+
+	public void setApiKeyAuth(SwaggerEntity.ApiKeyAuth apiKeyAuth) {
+		this.apiKeyAuth = apiKeyAuth;
+	}
+
+	public SwaggerEntity.BasicAuth getBasicAuth() {
+		return basicAuth;
+	}
+
+	public void setBasicAuth(SwaggerEntity.BasicAuth basicAuth) {
+		this.basicAuth = basicAuth;
+	}
+
+	public SwaggerEntity.OAuth2 getOauth2() {
+		return oauth2;
+	}
+
+	public void setOauth2(SwaggerEntity.OAuth2 oauth2) {
+		this.oauth2 = oauth2;
+	}
+}

+ 637 - 0
magic-api-plugins/magic-api-plugin-springdoc/src/main/java/org/ssssssss/magicapi/springdoc/entity/SwaggerEntity.java

@@ -0,0 +1,637 @@
+package org.ssssssss.magicapi.springdoc.entity;
+
+import java.util.*;
+
+/**
+ * Swagger接口信息
+ *
+ * @author mxd
+ */
+public class SwaggerEntity {
+
+	private String swagger = "2.0";
+
+	private String host;
+
+	private String basePath;
+
+	private Info info;
+
+	private final Map<String, Object> securityDefinitions = new HashMap<>();
+
+	private final List<Map<String, Object>> security = new ArrayList<>();
+
+	private final Set<Tag> tags = new TreeSet<>(Comparator.comparing(Tag::getName));
+
+	private final Map<String, Object> definitions = new HashMap<>();
+
+	private final Map<String, Map<String, Path>> paths = new HashMap<>();
+
+	private static Map<String, Object> doProcessSchema(Object target) {
+		Map<String, Object> result = new HashMap<>(3);
+		result.put("type", getType(target));
+		if (target instanceof List) {
+			List<?> targetList = (List<?>) target;
+			if (targetList.size() > 0) {
+				result.put("items", doProcessSchema(targetList.get(0)));
+			} else {
+				result.put("items", Collections.emptyList());
+			}
+		} else if (target instanceof Map) {
+			Set<Map.Entry> entries = ((Map) target).entrySet();
+			Map<String, Map<String, Object>> properties = new HashMap<>(entries.size());
+			for (Map.Entry entry : entries) {
+				properties.put(Objects.toString(entry.getKey()), doProcessSchema(entry.getValue()));
+			}
+			result.put("properties", properties);
+		} else {
+			result.put("example", target == null ? "" : target);
+			result.put("description", target == null ? "" : target);
+		}
+		return result;
+	}
+
+	private static String getType(Object object) {
+		if (object instanceof Number) {
+			return "number";
+		}
+		if (object instanceof String) {
+			return "string";
+		}
+		if (object instanceof Boolean) {
+			return "boolean";
+		}
+		if (object instanceof List) {
+			return "array";
+		}
+		if (object instanceof Map) {
+			return "object";
+		}
+		return "string";
+	}
+
+	public static Map<String, Object> createParameter(boolean required, String name, String in, String type, String description, Object example) {
+		Map<String, Object> parameter = new HashMap<>();
+		parameter.put("required", required);
+		parameter.put("name", name);
+		parameter.put("in", in);
+		parameter.put("description", description);
+
+		if ("body".equalsIgnoreCase(in)) {
+			Map<String, Object> schema = new HashMap<>();
+			schema.put("type", type);
+			schema.put("example", example);
+			parameter.put("schema", schema);
+		} else {
+			parameter.put("x-example", example);
+			parameter.put("type", type);
+		}
+		return parameter;
+	}
+
+	public Info getInfo() {
+		return info;
+	}
+
+	public void setInfo(Info info) {
+		this.info = info;
+	}
+
+	public void addPath(String path, String method, Path pathInfo) {
+		Map<String, Path> map = paths.computeIfAbsent(path, k -> new HashMap<>());
+		map.put(method.toLowerCase(), pathInfo);
+	}
+
+	public void addTag(String name, String description) {
+		this.tags.add(new Tag(name, description));
+	}
+
+	public String getHost() {
+		return host;
+	}
+
+	public void setHost(String host) {
+		this.host = host;
+	}
+
+	public String getSwagger() {
+		return swagger;
+	}
+
+	public void setSwagger(String swagger) {
+		this.swagger = swagger;
+	}
+
+	public String getBasePath() {
+		return basePath;
+	}
+
+	public void setBasePath(String basePath) {
+		this.basePath = basePath;
+	}
+
+	public Map<String, Object> getDefinitions() {
+		return definitions;
+	}
+
+	public void addDefinitions(String path, Object definition) {
+		definitions.put(path, definition);
+	}
+
+	public Set<Tag> getTags() {
+		return tags;
+	}
+
+	public Map<String, Map<String, Path>> getPaths() {
+		return paths;
+	}
+
+	public Map<String, Object> getSecurityDefinitions() {
+		return securityDefinitions;
+	}
+
+	public List<Map<String, Object>> getSecurity() {
+		return security;
+	}
+
+	public void addSecurityDefinitions(Map<String, Object> map) {
+		securityDefinitions.putAll(map);
+	}
+
+	public void addSecurity(Map<String, Object> map) {
+		security.add(map);
+	}
+
+	public static class Concat {
+
+		private String name;
+
+		private String url;
+
+		private String email;
+
+		public String getName() {
+			return name;
+		}
+
+		public void setName(String name) {
+			this.name = name;
+		}
+
+		public String getUrl() {
+			return url;
+		}
+
+		public void setUrl(String url) {
+			this.url = url;
+		}
+
+		public String getEmail() {
+			return email;
+		}
+
+		public void setEmail(String email) {
+			this.email = email;
+		}
+	}
+
+	public static class Info {
+
+		private String description;
+
+		private String version;
+
+		private String title;
+
+		private License license;
+
+		private Concat concat;
+
+		public Info(String description, String version, String title, License license, Concat concat) {
+			this.description = description;
+			this.version = version;
+			this.title = title;
+			this.license = license;
+			this.concat = concat;
+		}
+
+		public String getDescription() {
+			return description;
+		}
+
+		public void setDescription(String description) {
+			this.description = description;
+		}
+
+		public String getVersion() {
+			return version;
+		}
+
+		public void setVersion(String version) {
+			this.version = version;
+		}
+
+		public String getTitle() {
+			return title;
+		}
+
+		public void setTitle(String title) {
+			this.title = title;
+		}
+
+		public License getLicense() {
+			return license;
+		}
+
+		public void setLicense(License license) {
+			this.license = license;
+		}
+
+		public Concat getConcat() {
+			return concat;
+		}
+
+		public void setConcat(Concat concat) {
+			this.concat = concat;
+		}
+	}
+
+	public static class Path {
+
+		private List<String> tags = new ArrayList<>();
+
+		private String summary;
+
+		private String description;
+
+		private final String operationId;
+
+		private List<String> produces = new ArrayList<>();
+
+		private List<String> consumes = new ArrayList<>();
+
+		private List<Map<String, Object>> parameters = new ArrayList<>();
+
+		private Map<String, Object> responses = new HashMap<>();
+
+		public Path(String operationId) {
+			this.operationId = operationId;
+		}
+
+		public void addProduce(String produce) {
+			this.produces.add(produce);
+		}
+
+		public void addConsume(String consume) {
+			this.consumes.add(consume);
+		}
+
+		public void addParameter(Map<String, Object> parameter) {
+			this.parameters.add(parameter);
+		}
+
+		public String getOperationId() {
+			return operationId;
+		}
+
+		public void addResponse(String status, Object object) {
+			Map<String, Object> response = new HashMap<>();
+			response.put("description", "OK");
+			response.put("schema", doProcessSchema(object));
+			response.put("example", object);
+			this.responses.put(status, response);
+		}
+
+		public List<String> getTags() {
+			return tags;
+		}
+
+		public void setTags(List<String> tags) {
+			this.tags = tags;
+		}
+
+		public void addTag(String tag) {
+			this.tags.add(tag);
+		}
+
+		public String getSummary() {
+			return summary;
+		}
+
+		public void setSummary(String summary) {
+			this.summary = summary;
+		}
+
+		public List<String> getProduces() {
+			return produces;
+		}
+
+		public void setProduces(List<String> produces) {
+			this.produces = produces;
+		}
+
+		public List<String> getConsumes() {
+			return consumes;
+		}
+
+		public void setConsumes(List<String> consumes) {
+			this.consumes = consumes;
+		}
+
+		public List<Map<String, Object>> getParameters() {
+			return parameters;
+		}
+
+		public void setParameters(List<Map<String, Object>> parameters) {
+			this.parameters = parameters;
+		}
+
+		public Map<String, Object> getResponses() {
+			return responses;
+		}
+
+		public void setResponses(Map<String, Object> responses) {
+			this.responses = responses;
+		}
+
+		public String getDescription() {
+			return description;
+		}
+
+		public void setDescription(String description) {
+			this.description = description;
+		}
+	}
+
+	public static class Parameter {
+
+		private String name;
+
+		private String in;
+
+		private boolean required = false;
+
+		private String type;
+
+		private Object schema;
+
+		private String description;
+
+		private Object example;
+
+		public Parameter(boolean required, String name, String in, String type, String description, Object example) {
+			this.name = name;
+			this.in = in;
+			this.type = type;
+			this.description = description;
+			this.required = required;
+			if ("body".equalsIgnoreCase(in)) {
+				this.schema = "";
+			} else {
+				this.example = example;
+				/*
+				 * fix swagger文档使用knife4j时无法显示接口详情的问题(query类型参数)
+				 * schema 需设置为空字符串,否则请求参数中数据类型字段显示不正确
+				 */
+				this.schema = "";
+			}
+		}
+
+		public String getName() {
+			return name;
+		}
+
+		public void setName(String name) {
+			this.name = name;
+		}
+
+		public String getIn() {
+			return in;
+		}
+
+		public void setIn(String in) {
+			this.in = in;
+		}
+
+		public boolean isRequired() {
+			return required;
+		}
+
+		public void setRequired(boolean required) {
+			this.required = required;
+		}
+
+		public String getType() {
+			return type;
+		}
+
+		public void setType(String type) {
+			this.type = type;
+		}
+
+		public Object getSchema() {
+			return schema;
+		}
+
+		public void setSchema(Object schema) {
+			this.schema = schema;
+		}
+
+		public String getDescription() {
+			return description;
+		}
+
+		public void setDescription(String description) {
+			this.description = description;
+		}
+
+		public Object getExample() {
+			return example;
+		}
+
+		public void setExample(Object example) {
+			this.example = example;
+		}
+	}
+
+	public static class Tag {
+
+		private String name;
+
+		private String description;
+
+		public Tag(String name, String description) {
+			this.name = name;
+			this.description = description;
+		}
+
+		public String getName() {
+			return name;
+		}
+
+		public void setName(String name) {
+			this.name = name;
+		}
+
+		public String getDescription() {
+			return description;
+		}
+
+		public void setDescription(String description) {
+			this.description = description;
+		}
+
+		@Override
+		public boolean equals(Object o) {
+			if (this == o) {
+				return true;
+			}
+			if (o == null || getClass() != o.getClass()) {
+				return false;
+			}
+			Tag tag = (Tag) o;
+			return Objects.equals(name, tag.name);
+		}
+
+		@Override
+		public int hashCode() {
+			return Objects.hash(name);
+		}
+	}
+
+	public static class License {
+
+		private String name;
+
+		private String url;
+
+		public License(String name, String url) {
+			this.name = name;
+			this.url = url;
+		}
+
+		public String getName() {
+			return name;
+		}
+
+		public void setName(String name) {
+			this.name = name;
+		}
+
+		public String getUrl() {
+			return url;
+		}
+
+		public void setUrl(String url) {
+			this.url = url;
+		}
+	}
+
+	public static class BasicAuth {
+
+		public final static String KEY_NAME = "BasicAuth";
+
+		/**
+		 * 类型,默认值
+		 */
+		private String type = "basic";
+
+		public String getType() {
+			return type;
+		}
+
+		public void setType(String type) {
+			this.type = type;
+		}
+	}
+
+	public static class ApiKeyAuth {
+
+		public final static String KEY_NAME = "ApiKeyAuth";
+
+		private String type = "apiKey";
+
+		private String name = "header";
+
+		private String in = "X-API-Key";
+
+		public String getType() {
+			return type;
+		}
+
+		public void setType(String type) {
+			this.type = type;
+		}
+
+		public String getName() {
+			return name;
+		}
+
+		public void setName(String name) {
+			this.name = name;
+		}
+
+		public String getIn() {
+			return in;
+		}
+
+		public void setIn(String in) {
+			this.in = in;
+		}
+	}
+
+	public static class OAuth2 {
+
+		public final static String KEY_NAME = "OAuth2";
+
+		private String type = "oauth2";
+
+		private String flow;
+
+		private String authorizationUrl;
+
+		private String tokenUrl;
+
+		private Map<String, String> scopes;
+
+		public String getType() {
+			return type;
+		}
+
+		public void setType(String type) {
+			this.type = type;
+		}
+
+		public String getFlow() {
+			return flow;
+		}
+
+		public void setFlow(String flow) {
+			this.flow = flow;
+		}
+
+		public String getAuthorizationUrl() {
+			return authorizationUrl;
+		}
+
+		public void setAuthorizationUrl(String authorizationUrl) {
+			this.authorizationUrl = authorizationUrl;
+		}
+
+		public String getTokenUrl() {
+			return tokenUrl;
+		}
+
+		public void setTokenUrl(String tokenUrl) {
+			this.tokenUrl = tokenUrl;
+		}
+
+		public Map<String, String> getScopes() {
+			return scopes;
+		}
+
+		public void setScopes(Map<String, String> scopes) {
+			this.scopes = scopes;
+		}
+	}
+}

+ 235 - 0
magic-api-plugins/magic-api-plugin-springdoc/src/main/java/org/ssssssss/magicapi/springdoc/entity/SwaggerProvider.java

@@ -0,0 +1,235 @@
+package org.ssssssss.magicapi.springdoc.entity;
+
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.util.CollectionUtils;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.ssssssss.magicapi.core.config.MagicConfiguration;
+import org.ssssssss.magicapi.core.model.ApiInfo;
+import org.ssssssss.magicapi.core.model.BaseDefinition;
+import org.ssssssss.magicapi.core.model.DataType;
+import org.ssssssss.magicapi.core.model.Path;
+import org.ssssssss.magicapi.core.service.MagicResourceService;
+import org.ssssssss.magicapi.core.service.impl.RequestMagicDynamicRegistry;
+import org.ssssssss.magicapi.utils.JsonUtils;
+import org.ssssssss.magicapi.utils.PathUtils;
+import org.ssssssss.script.parsing.ast.literal.BooleanLiteral;
+
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+
+import static org.ssssssss.magicapi.core.config.Constants.*;
+
+/**
+ * 生成swagger用的json
+ *
+ * @author mxd
+ */
+public class SwaggerProvider {
+
+	/**
+	 * swagger Model定义路径前缀
+	 */
+	private static final String DEFINITION = "#/definitions/";
+	/**
+	 * body空对象
+	 */
+	private static final String BODY_EMPTY = "{}";
+
+	private final Map<String, Object> DEFINITION_MAP = new ConcurrentHashMap<>();
+
+	private final RequestMagicDynamicRegistry requestMagicDynamicRegistry;
+
+	private final MagicResourceService magicResourceService;
+	/**
+	 * 基础路径
+	 */
+	private final String basePath;
+	private final SwaggerEntity.Info info;
+	private final boolean persistenceResponseBody;
+	private final String prefix;
+	private final Map<String, Object> securityDefinitionMap;
+	private final Map<String, Object> securityMap;
+
+	public SwaggerProvider(RequestMagicDynamicRegistry requestMagicDynamicRegistry, MagicResourceService magicResourceService,
+						   String basePath, SwaggerEntity.Info info, boolean persistenceResponseBody, String prefix, Map<String, Object> securityDefinitionMap, Map<String, Object> securityMap) {
+		this.requestMagicDynamicRegistry = requestMagicDynamicRegistry;
+		this.magicResourceService = magicResourceService;
+		this.basePath = basePath;
+		this.info = info;
+		this.persistenceResponseBody = persistenceResponseBody;
+		this.prefix = StringUtils.defaultIfBlank(prefix, "") + "/";
+		this.securityDefinitionMap = securityDefinitionMap;
+		this.securityMap = securityMap;
+	}
+
+	@ResponseBody
+	public SwaggerEntity swaggerJson() {
+		this.DEFINITION_MAP.clear();
+		List<ApiInfo> infos = requestMagicDynamicRegistry.mappings();
+		SwaggerEntity swaggerEntity = new SwaggerEntity();
+		swaggerEntity.setInfo(info);
+		swaggerEntity.setBasePath(this.basePath);
+		swaggerEntity.addSecurityDefinitions(securityDefinitionMap);
+		swaggerEntity.addSecurity(securityMap);
+
+		for (ApiInfo info : infos) {
+			String groupName = magicResourceService.getGroupName(info.getGroupId()).replace("/", "-");
+			String requestPath = PathUtils.replaceSlash(this.prefix + magicResourceService.getGroupPath(info.getGroupId()) + "/" + info.getPath());
+			SwaggerEntity.Path path = new SwaggerEntity.Path(info.getId());
+			path.addTag(groupName);
+			boolean hasBody = false;
+			try {
+				List<Map<String, Object>> parameters = parseParameters(info);
+				hasBody = parameters.stream().anyMatch(it -> VAR_NAME_REQUEST_BODY.equals(it.get("in")));
+				BaseDefinition baseDefinition = info.getRequestBodyDefinition();
+				if (hasBody && baseDefinition != null) {
+					doProcessDefinition(baseDefinition, info, groupName, "root_", "request", 0);
+				}
+				parameters.forEach(path::addParameter);
+				if (this.persistenceResponseBody) {
+					baseDefinition = info.getResponseBodyDefinition();
+					if (baseDefinition != null) {
+						Map<String, Object> responseMap = parseResponse(info);
+						if (!responseMap.isEmpty()) {
+							path.setResponses(responseMap);
+							doProcessDefinition(baseDefinition, info, groupName, "root_" + baseDefinition.getName(), "response", 0);
+						}
+					} else {
+						path.addResponse("200", JsonUtils.readValue(Objects.toString(info.getResponseBody(), BODY_EMPTY), Object.class));
+					}
+				}
+
+			} catch (Exception ignored) {
+			}
+			if (hasBody) {
+				path.addConsume("application/json");
+			} else {
+				path.addConsume("*/*");
+			}
+			path.addProduce("application/json");
+			path.setSummary(info.getName());
+			path.setDescription(StringUtils.defaultIfBlank(info.getDescription(), info.getName()));
+
+			swaggerEntity.addPath(requestPath, info.getMethod(), path);
+		}
+
+		if (this.DEFINITION_MAP.size() > 0) {
+			Set<Map.Entry<String, Object>> entries =this.DEFINITION_MAP.entrySet();
+			for (Map.Entry<String, Object> entry : entries) {
+				swaggerEntity.addDefinitions(entry.getKey(), entry.getValue());
+			}
+		}
+
+		return swaggerEntity;
+	}
+
+	private List<Map<String, Object>> parseParameters(ApiInfo info) {
+		List<Map<String, Object>> parameters = new ArrayList<>();
+		info.getParameters().forEach(it -> parameters.add(SwaggerEntity.createParameter(it.isRequired(), it.getName(), VAR_NAME_QUERY, it.getDataType().getJavascriptType(), it.getDescription(), it.getValue())));
+		info.getHeaders().forEach(it -> parameters.add(SwaggerEntity.createParameter(it.isRequired(), it.getName(), VAR_NAME_HEADER, it.getDataType().getJavascriptType(), it.getDescription(), it.getValue())));
+		List<Path> paths = new ArrayList<>(info.getPaths());
+		MagicConfiguration.getMagicResourceService().getGroupsByFileId(info.getId())
+				.stream()
+				.flatMap(it -> it.getPaths().stream())
+				.filter(it -> !paths.contains(it))
+				.forEach(paths::add);
+		paths.forEach(it -> parameters.add(SwaggerEntity.createParameter(it.isRequired(), it.getName(), VAR_NAME_PATH_VARIABLE, it.getDataType().getJavascriptType(), it.getDescription(), it.getValue())));
+		try {
+			BaseDefinition baseDefinition = info.getRequestBodyDefinition();
+			if (baseDefinition != null && !CollectionUtils.isEmpty(baseDefinition.getChildren())) {
+				Map<String, Object> parameter = SwaggerEntity.createParameter(baseDefinition.isRequired(), StringUtils.isNotBlank(baseDefinition.getName()) ? baseDefinition.getName() : VAR_NAME_REQUEST_BODY, VAR_NAME_REQUEST_BODY, baseDefinition.getDataType().getJavascriptType(), baseDefinition.getDescription(), baseDefinition);
+				Map<String, Object> schema = new HashMap<>(2);
+				String groupName = magicResourceService.getGroupName(info.getGroupId()).replace("/", "-");
+				String voName = groupName + "«" + info.getPath().replaceFirst("/", "").replaceAll("/", "_") + "«request«";
+				if (DataType.Array == baseDefinition.getDataType()) {
+					voName += "root_" + (StringUtils.isNotBlank(baseDefinition.getName()) ? baseDefinition.getName() + "_" : "_") + "»»»";
+
+					Map<String, Object> items = new HashMap<>(2);
+					items.put("originalRef", voName);
+					items.put("$ref", DEFINITION + voName);
+					schema.put("items", items);
+					schema.put("type", VAR_NAME_REQUEST_BODY_VALUE_TYPE_ARRAY);
+				} else {
+					voName += "root_" + baseDefinition.getName() + "»»»";
+					schema.put("originalRef", voName);
+					schema.put("$ref", DEFINITION + voName);
+				}
+				parameter.put("schema", schema);
+				parameters.add(parameter);
+			} else if (StringUtils.isNotBlank(info.getRequestBody())) {
+				Object object = JsonUtils.readValue(info.getResponseBody(), Object.class);
+				boolean isListOrMap = (object instanceof List || object instanceof Map);
+				if (isListOrMap && BooleanLiteral.isTrue(object)) {
+					parameters.add(SwaggerEntity.createParameter(false, VAR_NAME_REQUEST_BODY, VAR_NAME_REQUEST_BODY, object instanceof List ? VAR_NAME_REQUEST_BODY_VALUE_TYPE_ARRAY : VAR_NAME_REQUEST_BODY_VALUE_TYPE_OBJECT, null, object));
+				}
+			}
+
+		} catch (Exception ignored) {
+		}
+		return parameters;
+	}
+
+	private Map<String, Object> parseResponse(ApiInfo info) {
+		Map<String, Object> result = new HashMap<>();
+
+		BaseDefinition baseDefinition = info.getResponseBodyDefinition();
+		if (!CollectionUtils.isEmpty(baseDefinition.getChildren())) {
+			String groupName = magicResourceService.getGroupName(info.getGroupId()).replace("/", "-");
+			String voName = groupName + "«" + info.getPath().replaceFirst("/", "").replaceAll("/", "_") + "«response«";
+			voName += "root_" + baseDefinition.getName() + "»»»";
+
+			Map<String, Object> schema = new HashMap<>(2);
+			schema.put("originalRef", voName);
+			schema.put("$ref", DEFINITION + voName);
+
+			Map<String, Object> response = new HashMap<>(2);
+			response.put("description", "OK");
+			response.put("schema", schema);
+			result.put("200", response);
+		}
+
+		return result;
+	}
+
+	private Map<String, Object> doProcessDefinition(BaseDefinition target, ApiInfo info, String groupName, String parentName, String definitionType, int level) {
+		Map<String, Object> result = new HashMap<>(4);
+		result.put("description", target.getDescription());
+		if (DataType.Array == target.getDataType()) {
+			if (!CollectionUtils.isEmpty(target.getChildren())) {
+				result.put("items", doProcessDefinition(target.getChildren().get(0), info, groupName, parentName + target.getName() + "_", definitionType, level + 1));
+			} else {
+				result.put("items", Collections.emptyList());
+			}
+			result.put("type", target.getDataType().getJavascriptType());
+		} else if (DataType.Object == target.getDataType() || DataType.Any == target.getDataType()) {
+			String voName = groupName + "«" + info.getPath().replaceFirst("/", "").replaceAll("/", "_") + (StringUtils.equals("response", definitionType) ? "«response«" : "«request«") + parentName + target.getName() + "»»»";
+
+			Map<String, Object> definition = new HashMap<>(4);
+			Map<String, Map<String, Object>> properties = new HashMap<>(target.getChildren().size());
+			Set<String> requiredSet = new HashSet<>(target.getChildren().size());
+			for (BaseDefinition obj : target.getChildren()) {
+				properties.put(obj.getName(), doProcessDefinition(obj, info, groupName, parentName + target.getName() + "_", definitionType, level + 1));
+				if (obj.isRequired()) {
+					requiredSet.add(obj.getName());
+				}
+			}
+			definition.put("properties", properties);
+			definition.put("description", target.getDescription());
+			definition.put("type", target.getDataType().getJavascriptType());
+			definition.put("required", requiredSet);
+			if (this.DEFINITION_MAP.containsKey(voName)) {
+				// TODO 应该不会出现名字都一样的
+				voName = voName.replace("»»»", "_" + level + "»»»");
+			}
+
+			this.DEFINITION_MAP.put(voName, definition);
+			result.put("originalRef", voName);
+			result.put("$ref", DEFINITION + voName);
+
+		} else {
+			result.put("example", target.getValue());
+			result.put("type", target.getDataType().getJavascriptType());
+		}
+		return result;
+	}
+}

+ 1 - 0
magic-api-plugins/magic-api-plugin-springdoc/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

@@ -0,0 +1 @@
+org.ssssssss.magicapi.springdoc.MagicSpringDocConfiguration

+ 1 - 0
magic-api-plugins/pom.xml

@@ -17,6 +17,7 @@
         <module>magic-api-plugin-task</module>
         <module>magic-api-plugin-component</module>
         <module>magic-api-plugin-swagger</module>
+        <module>magic-api-plugin-springdoc</module>
         <module>magic-api-plugin-redis</module>
         <module>magic-api-plugin-mongo</module>
         <module>magic-api-plugin-elasticsearch</module>

+ 0 - 1
magic-api-servlet/magic-api-servlet-jakarta/pom.xml

@@ -19,7 +19,6 @@
         <dependency>
             <groupId>jakarta.servlet</groupId>
             <artifactId>jakarta.servlet-api</artifactId>
-            <version>6.0.0</version>
             <optional>true</optional>
         </dependency>
     </dependencies>

+ 6 - 0
pom.xml

@@ -35,6 +35,7 @@
         <commons-io.version>2.7</commons-io.version>
         <commons-text.version>1.6</commons-text.version>
         <commons-beanutils.version>1.9.4</commons-beanutils.version>
+        <jakarta.version>6.0.0</jakarta.version>
         <fastjson.version>1.2.83</fastjson.version>
         <spring-boot-starter-log4j.version>1.3.8.RELEASE</spring-boot-starter-log4j.version>
         <java.version>1.8</java.version>
@@ -113,6 +114,11 @@
                 <artifactId>fastjson</artifactId>
                 <version>${fastjson.version}</version>
             </dependency>
+            <dependency>
+                <groupId>jakarta.servlet</groupId>
+                <artifactId>jakarta.servlet-api</artifactId>
+                <version>${jakarta.version}</version>
+            </dependency>
         </dependencies>
     </dependencyManagement>
     <build>