Commit 02897568 authored by liang.tang's avatar liang.tang
Browse files

magic-api

parents
Pipeline #222 failed with stages
in 0 seconds
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"));
}
}
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.springdoc")
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;
}
}
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;
}
}
}
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;
}
}
<?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.1.1</version>
</parent>
<artifactId>magic-api-plugin-swagger</artifactId>
<packaging>jar</packaging>
<name>magic-api-plugin-swagger</name>
<description>magic-api-plugin-swagger</description>
<properties>
<swagger.version>2.9.2</swagger.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>${swagger.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
package org.ssssssss.magicapi.swagger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext;
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.swagger.entity.SwaggerEntity;
import org.ssssssss.magicapi.swagger.entity.SwaggerProvider;
import org.ssssssss.magicapi.utils.Mapping;
import springfox.documentation.swagger.web.SwaggerResource;
import springfox.documentation.swagger.web.SwaggerResourcesProvider;
import javax.servlet.ServletContext;
import java.util.*;
@Configuration
@EnableConfigurationProperties(SwaggerConfig.class)
@ConditionalOnClass(name = "springfox.documentation.swagger.web.SwaggerResourcesProvider")
public class MagicSwaggerConfiguration implements MagicPluginConfiguration {
private final MagicAPIProperties properties;
private final SwaggerConfig swaggerConfig;
private final ApplicationContext applicationContext;
@Autowired
@Lazy
private RequestMappingHandlerMapping requestMappingHandlerMapping;
public MagicSwaggerConfiguration(MagicAPIProperties properties, SwaggerConfig swaggerConfig, ApplicationContext applicationContext) {
this.properties = properties;
this.swaggerConfig = swaggerConfig;
this.applicationContext = applicationContext;
}
@Override
public Plugin plugin() {
return new Plugin("Swagger");
}
@Bean
@Primary
public SwaggerResourcesProvider magicSwaggerResourcesProvider(RequestMagicDynamicRegistry requestMagicDynamicRegistry, MagicResourceService magicResourceService, ServletContext servletContext) throws NoSuchMethodException {
Mapping mapping = Mapping.create(requestMappingHandlerMapping);
RequestMappingInfo requestMappingInfo = mapping.paths(swaggerConfig.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(swaggerConfig.getDescription(), swaggerConfig.getVersion(), swaggerConfig.getTitle(), license, swaggerConfig.getConcat());
//具体参考:https://swagger.io/docs/specification/2-0/authentication/
Map<String, Object> securityDefinitionMap = new HashMap<>();
Map<String, Object> securityMap = new HashMap<>();
if (swaggerConfig.getBasicAuth() != null) {
securityDefinitionMap.put(SwaggerEntity.BasicAuth.KEY_NAME, swaggerConfig.getBasicAuth());
//the Basic and API key security items use an empty array instead.
securityMap.put(SwaggerEntity.BasicAuth.KEY_NAME, new String[]{});
}
if (swaggerConfig.getApiKeyAuth() != null) {
securityDefinitionMap.put(SwaggerEntity.ApiKeyAuth.KEY_NAME, swaggerConfig.getApiKeyAuth());
//the Basic and API key security items use an empty array instead.
securityMap.put(SwaggerEntity.ApiKeyAuth.KEY_NAME, new String[]{});
}
if (swaggerConfig.getOauth2() != null) {
SwaggerEntity.OAuth2 oAuth2 = swaggerConfig.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(requestMagicDynamicRegistry, magicResourceService, servletContext.getContextPath(),
info, properties.isPersistenceResponseBody(), properties.getPrefix(), securityDefinitionMap, securityMap);
// 注册swagger.json
mapping.register(requestMappingInfo, swaggerProvider, SwaggerProvider.class.getDeclaredMethod("swaggerJson"));
return () -> {
List<SwaggerResource> resources = new ArrayList<>();
// 追加Magic Swagger信息
resources.add(swaggerResource(swaggerConfig.getName(), swaggerConfig.getLocation()));
Map<String, SwaggerResourcesProvider> beans = applicationContext.getBeansOfType(SwaggerResourcesProvider.class);
// 获取已定义的文档信息
for (Map.Entry<String, SwaggerResourcesProvider> entry : beans.entrySet()) {
if (!"magicSwaggerResourcesProvider".equalsIgnoreCase(entry.getKey())) {
resources.addAll(entry.getValue().get());
}
}
return resources;
};
}
/**
* 构建 SwaggerResource
*
* @param name 名字
* @param location 位置
*/
private SwaggerResource swaggerResource(String name, String location) {
SwaggerResource resource = new SwaggerResource();
resource.setName(name);
resource.setLocation(location);
resource.setSwaggerVersion("2.0");
return resource;
}
}
package org.ssssssss.magicapi.swagger;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.NestedConfigurationProperty;
import org.ssssssss.magicapi.swagger.entity.SwaggerEntity;
/**
* Swagger 配置
*
* @author mxd
*/
@ConfigurationProperties(prefix = "magic-api.swagger")
public class SwaggerConfig {
/**
* 资源名称
*/
private String name = "MagicAPI接口";
/**
* 资源位置
*/
private String location = "/v2/api-docs/magic-api/swagger2.json";
/**
* 文档标题
*/
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 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;
}
}
package org.ssssssss.magicapi.swagger.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;
}
}
}
package org.ssssssss.magicapi.swagger.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;
}
}
org.springframework.boot.autoconfigure.EnableAutoConfiguration=org.ssssssss.magicapi.swagger.MagicSwaggerConfiguration
<?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.1.1</version>
</parent>
<artifactId>magic-api-plugin-task</artifactId>
<packaging>jar</packaging>
<name>magic-api-plugin-task</name>
<description>magic-api-plugin-task</description>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<plugins>
<!-- npm install && npm run build -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.6.0</version>
<executions>
<execution>
<id>exec-npm-install</id>
<phase>generate-resources</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<executable>npm</executable>
<arguments>
<argument>install</argument>
</arguments>
<workingDirectory>${basedir}/src/console</workingDirectory>
</configuration>
</execution>
<execution>
<id>exec-npm-run-build</id>
<phase>generate-resources</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<executable>npm</executable>
<arguments>
<argument>run</argument>
<argument>build</argument>
</arguments>
<workingDirectory>${basedir}/src/console</workingDirectory>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.2.0</version>
<executions>
<execution>
<id>copy-resource</id>
<phase>generate-resources</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${basedir}/target/classes/magic-editor/plugins</outputDirectory>
<resources>
<resource>
<directory>${basedir}/src/console/dist</directory>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
{
"name": "magic-task",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "vite build"
},
"author": "",
"license": "ISC",
"devDependencies": {
"vue": "^3.2.31",
"@vitejs/plugin-vue": "^2.2.4",
"vite-plugin-svg-icons": "^1.1.0",
"vite": "^2.8.6"
}
}
<template>
<div class="magic-task-info">
<form>
<label>{{ $i('message.enable') }}</label>
<magic-checkbox v-model:value="info.enabled" />
<label>cron</label>
<magic-input v-model:value="info.cron" :placeholder="$i('task.form.placeholder.cron')" width="250px"/>
<label>{{ $i('task.form.name') }}</label>
<magic-input v-model:value="info.name" :placeholder="$i('task.form.placeholder.name')" width="250px"/>
<label>{{ $i('task.form.path') }}</label>
<magic-input v-model:value="info.path" :placeholder="$i('task.form.placeholder.path')" width="auto" style="flex:1"/>
</form>
<div style="flex:1;padding-top:5px;">
<magic-textarea v-model:value="info.description" :placeholder="$i('task.form.placeholder.description')"/>
</div>
</div>
</template>
<script setup>
import { inject } from 'vue'
const $i = inject('i18n.format')
const info = inject('info')
</script>
<style scoped>
.magic-task-info{
display: flex;
flex-direction: column;
flex: 1;
padding: 5px;
}
.magic-task-info form{
display: flex;
}
.magic-task-info form label{
display: inline-block;
width: 75px;
height: var(--magic-input-height);
line-height: var(--magic-input-height);
font-weight: 400;
text-align: right;
padding: 0 5px;
}
.magic-task-info form :deep(.magic-checkbox){
width: 22px;
height: 22px;
}
.magic-task-info form :deep(.magic-textarea){
margin: 5px;
}
</style>
\ No newline at end of file
export default {
task: {
title: 'Task Info',
name: 'Task',
form: {
name: 'Task Name',
path: 'Task Path',
placeholder: {
cron: 'Please Enter Cron Expression',
name: 'Please Enter Task Name',
path: 'Please Enter Task Path',
description: 'Please Enter Task Description'
}
}
},
}
\ No newline at end of file
export default {
task: {
title: '定时任务信息',
name: '定时任务',
form: {
name: '任务名称',
path: '任务路径',
placeholder: {
cron: '请输入Cron表达式',
name: '请输入任务名称',
path: '请输入任务路径',
description: '请输入任务描述'
}
}
}
}
\ No newline at end of file
<svg class="icon" style="width: 1em;height: 1em;vertical-align: middle;fill: currentColor;overflow: hidden;" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M512.78747336 189.40294037A372.25009177 372.25009177 0 1 1 512.73122556 933.8468761a372.25009177 372.25009177 0 0 1 0-744.50018353z m20.02433313 179.43151904h-39.93616901a6.69352725 6.69352725 0 0 0-6.69352725 6.69352725V604.2328627c0 2.19367667 1.01246621 4.1623613 2.75615881 5.39982038l137.41416897 100.23415877a6.63727863 6.63727863 0 0 0 9.28094102-1.4624511l23.79295651-32.39891977a6.5247822 6.5247822 0 0 0-1.51869891-9.22469321L539.44908511 581.17113175V375.52798666a6.69352725 6.69352725 0 0 0-6.63727862-6.69352726zM711.28710712 90.125a24.80542356 24.80542356 0 0 1-1e-8 49.61084629H314.23159262a24.80542356 24.80542356 0 0 1 0-49.61084629h397.1117623z" /></svg>
\ No newline at end of file
import MagicTask from './service/magic-task.js'
import localZhCN from './i18n/zh-cn.js'
import localEn from './i18n/en.js'
import MagicTaskInfo from './components/magic-task-info.vue'
import 'vite-plugin-svg-icons/register'
export default (opt) => {
const i18n = opt.i18n
// 添加i18n 国际化信息
i18n.add('zh-cn', localZhCN)
i18n.add('en', localEn)
return {
// 左侧资源
resource: [{
// 资源类型,和后端存储结构一致
type: 'task',
// 展示图标
icon: '#magic-task-task', // #开头表示图标在插件中
// 展示名称
title: 'task.name',
// 运行服务
service: MagicTask(opt.bus, opt.constants, i18n.format, opt.Message, opt.request),
}],
// 底部工具条
toolbars: [{
// 当打开的资源类型为 task 时显示
type: 'task',
// 工具条展示的标题
title: 'task.title',
// 展示图标
icon: 'parameter',
// 对应的组件
component: MagicTaskInfo,
}]
}
}
export default function (bus, constants, $i, Message, request) {
return {
// svg text
getIcon: item => ['TASK', '#9012FE'],
// 任务名称
name: $i('task.name'),
// 脚本语言
language: 'magicscript',
// 默认脚本
defaultScript: `return 'Hello magic-api-task'`,
// 执行测试的逻辑
doTest: (opened) => {
opened.running = true
const info = opened.item
const requestConfig = {
baseURL: constants.SERVER_URL,
url: '/task/execute',
method: 'POST',
responseType: 'json',
headers: {},
withCredentials: true
}
bus.$emit(Message.SWITCH_TOOLBAR, 'log')
requestConfig.headers[constants.HEADER_REQUEST_CLIENT_ID] = constants.CLIENT_ID
requestConfig.headers[constants.HEADER_REQUEST_SCRIPT_ID] = opened.item.id
requestConfig.headers[constants.HEADER_MAGIC_TOKEN] = constants.HEADER_MAGIC_TOKEN_VALUE
// 设置断点
requestConfig.headers[constants.HEADER_REQUEST_BREAKPOINTS] = (opened.decorations || []).filter(it => it.options.linesDecorationsClassName === 'breakpoints').map(it => it.range.startLineNumber).join(',')
const fullName = opened.path()
bus.status(`开始测试定时任务「${fullName}」`)
request.sendPost('/task/execute', { id: info.id }, requestConfig).success(res => {
opened.running = false
}).end(() => {
bus.status(`定时任务「${fullName}」测试完毕`)
opened.running = false
})
},
// 是否允许执行测试
runnable: true,
// 是否需要填写路径
requirePath: true,
// 合并
merge: item => item
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment