diff --git a/magic-api-plugins/magic-api-plugin-springdoc/pom.xml b/magic-api-plugins/magic-api-plugin-springdoc/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..a83036ed5366f69f5b9fc8ce3e2f400220af45ad --- /dev/null +++ b/magic-api-plugins/magic-api-plugin-springdoc/pom.xml @@ -0,0 +1,50 @@ + + + 4.0.0 + + org.ssssssss + magic-api-plugins + 2.0.2 + + magic-api-plugin-springdoc + jar + magic-api-plugin-springdoc + magic-api-plugin-springdoc + + 2.0.4 + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + ${springdoc.version} + provided + + + jakarta.servlet + jakarta.servlet-api + provided + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.10.1 + + 17 + 17 + UTF-8 + + + + + diff --git a/magic-api-plugins/magic-api-plugin-springdoc/src/main/java/org/ssssssss/magicapi/springdoc/MagicSpringDocConfiguration.java b/magic-api-plugins/magic-api-plugin-springdoc/src/main/java/org/ssssssss/magicapi/springdoc/MagicSpringDocConfiguration.java new file mode 100644 index 0000000000000000000000000000000000000000..b071e758005161fba0ab841740216583760266aa --- /dev/null +++ b/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 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 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 getConfigParameters() { + Map params = super.getConfigParameters(); + if (!createdMapping) { + createdMapping = true; + try { + createSwaggerProvider(requestMagicDynamicRegistryObjectProvider, magicResourceService, servletContext); + } catch (NoSuchMethodException e) { + logger.error("注册springdoc接口失败", e); + return params; + } + } + Set urls = (Set) 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 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 securityDefinitionMap = new HashMap<>(); + Map 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 scopes = oAuth2.getScopes(); + if (scopes != null) { + Set 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")); + } +} diff --git a/magic-api-plugins/magic-api-plugin-springdoc/src/main/java/org/ssssssss/magicapi/springdoc/SpringDocConfig.java b/magic-api-plugins/magic-api-plugin-springdoc/src/main/java/org/ssssssss/magicapi/springdoc/SpringDocConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..cfa67f651ca2e97a810eec77c5e7780318e47310 --- /dev/null +++ b/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; + } +} diff --git a/magic-api-plugins/magic-api-plugin-springdoc/src/main/java/org/ssssssss/magicapi/springdoc/entity/SwaggerEntity.java b/magic-api-plugins/magic-api-plugin-springdoc/src/main/java/org/ssssssss/magicapi/springdoc/entity/SwaggerEntity.java new file mode 100644 index 0000000000000000000000000000000000000000..ec00eecc9a7600fda57fbe1842851ec95bfc8cdd --- /dev/null +++ b/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 securityDefinitions = new HashMap<>(); + + private final List> security = new ArrayList<>(); + + private final Set tags = new TreeSet<>(Comparator.comparing(Tag::getName)); + + private final Map definitions = new HashMap<>(); + + private final Map> paths = new HashMap<>(); + + private static Map doProcessSchema(Object target) { + Map 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 entries = ((Map) target).entrySet(); + Map> 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 createParameter(boolean required, String name, String in, String type, String description, Object example) { + Map parameter = new HashMap<>(); + parameter.put("required", required); + parameter.put("name", name); + parameter.put("in", in); + parameter.put("description", description); + + if ("body".equalsIgnoreCase(in)) { + Map 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 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 getDefinitions() { + return definitions; + } + + public void addDefinitions(String path, Object definition) { + definitions.put(path, definition); + } + + public Set getTags() { + return tags; + } + + public Map> getPaths() { + return paths; + } + + public Map getSecurityDefinitions() { + return securityDefinitions; + } + + public List> getSecurity() { + return security; + } + + public void addSecurityDefinitions(Map map) { + securityDefinitions.putAll(map); + } + + public void addSecurity(Map 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 tags = new ArrayList<>(); + + private String summary; + + private String description; + + private final String operationId; + + private List produces = new ArrayList<>(); + + private List consumes = new ArrayList<>(); + + private List> parameters = new ArrayList<>(); + + private Map 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 parameter) { + this.parameters.add(parameter); + } + + public String getOperationId() { + return operationId; + } + + public void addResponse(String status, Object object) { + Map response = new HashMap<>(); + response.put("description", "OK"); + response.put("schema", doProcessSchema(object)); + response.put("example", object); + this.responses.put(status, response); + } + + public List getTags() { + return tags; + } + + public void setTags(List 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 getProduces() { + return produces; + } + + public void setProduces(List produces) { + this.produces = produces; + } + + public List getConsumes() { + return consumes; + } + + public void setConsumes(List consumes) { + this.consumes = consumes; + } + + public List> getParameters() { + return parameters; + } + + public void setParameters(List> parameters) { + this.parameters = parameters; + } + + public Map getResponses() { + return responses; + } + + public void setResponses(Map 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 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 getScopes() { + return scopes; + } + + public void setScopes(Map scopes) { + this.scopes = scopes; + } + } +} diff --git a/magic-api-plugins/magic-api-plugin-springdoc/src/main/java/org/ssssssss/magicapi/springdoc/entity/SwaggerProvider.java b/magic-api-plugins/magic-api-plugin-springdoc/src/main/java/org/ssssssss/magicapi/springdoc/entity/SwaggerProvider.java new file mode 100644 index 0000000000000000000000000000000000000000..7a69584eddcde712afff71bebcee8af7afff829e --- /dev/null +++ b/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 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 securityDefinitionMap; + private final Map securityMap; + + public SwaggerProvider(RequestMagicDynamicRegistry requestMagicDynamicRegistry, MagicResourceService magicResourceService, + String basePath, SwaggerEntity.Info info, boolean persistenceResponseBody, String prefix, Map securityDefinitionMap, Map 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 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> 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 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> entries =this.DEFINITION_MAP.entrySet(); + for (Map.Entry entry : entries) { + swaggerEntity.addDefinitions(entry.getKey(), entry.getValue()); + } + } + + return swaggerEntity; + } + + private List> parseParameters(ApiInfo info) { + List> 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 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 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 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 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 parseResponse(ApiInfo info) { + Map 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 schema = new HashMap<>(2); + schema.put("originalRef", voName); + schema.put("$ref", DEFINITION + voName); + + Map response = new HashMap<>(2); + response.put("description", "OK"); + response.put("schema", schema); + result.put("200", response); + } + + return result; + } + + private Map doProcessDefinition(BaseDefinition target, ApiInfo info, String groupName, String parentName, String definitionType, int level) { + Map 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 definition = new HashMap<>(4); + Map> properties = new HashMap<>(target.getChildren().size()); + Set 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; + } +} diff --git a/magic-api-plugins/magic-api-plugin-springdoc/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/magic-api-plugins/magic-api-plugin-springdoc/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000000000000000000000000000000000000..2138afce5c4e4f3b0a3182b8d388412710180b98 --- /dev/null +++ b/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 \ No newline at end of file diff --git a/magic-api-plugins/pom.xml b/magic-api-plugins/pom.xml index 054c51f68b29ae8ad2880b5b0fe64a4d6ba654b0..216612e0d20c000027a6450411183b1d7a1d03f5 100644 --- a/magic-api-plugins/pom.xml +++ b/magic-api-plugins/pom.xml @@ -17,6 +17,7 @@ magic-api-plugin-task magic-api-plugin-component magic-api-plugin-swagger + magic-api-plugin-springdoc magic-api-plugin-redis magic-api-plugin-mongo magic-api-plugin-elasticsearch diff --git a/magic-api-servlet/magic-api-servlet-jakarta/pom.xml b/magic-api-servlet/magic-api-servlet-jakarta/pom.xml index be5630f718beac4451094f5ba529b3ca4e24d4f6..14824eb0e23a5b7969fe45e7b16fc7e872525949 100644 --- a/magic-api-servlet/magic-api-servlet-jakarta/pom.xml +++ b/magic-api-servlet/magic-api-servlet-jakarta/pom.xml @@ -19,7 +19,6 @@ jakarta.servlet jakarta.servlet-api - 6.0.0 true diff --git a/pom.xml b/pom.xml index f52158e2335afa669c504382eb410468d5c4991e..871038bbf34ae7ab4f08c50b24cd94fea6b85b51 100644 --- a/pom.xml +++ b/pom.xml @@ -35,6 +35,7 @@ 2.7 1.6 1.9.4 + 6.0.0 1.2.83 1.3.8.RELEASE 1.8 @@ -113,6 +114,11 @@ fastjson ${fastjson.version} + + jakarta.servlet + jakarta.servlet-api + ${jakarta.version} +