mxd vor 3 Jahren
Ursprung
Commit
e6578fc3b3

+ 0 - 52
magic-api-spring-boot-starter/pom.xml

@@ -75,57 +75,5 @@
             <artifactId>spring-boot-configuration-processor</artifactId>
             <optional>true</optional>
         </dependency>
-        <dependency>
-            <groupId>org.ssssssss</groupId>
-            <artifactId>magic-api</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.ssssssss</groupId>
-            <artifactId>magic-api</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.ssssssss</groupId>
-            <artifactId>magic-api</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.ssssssss</groupId>
-            <artifactId>magic-api</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.ssssssss</groupId>
-            <artifactId>magic-api</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.ssssssss</groupId>
-            <artifactId>magic-api</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.ssssssss</groupId>
-            <artifactId>magic-api</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.ssssssss</groupId>
-            <artifactId>magic-api</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.ssssssss</groupId>
-            <artifactId>magic-api</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.ssssssss</groupId>
-            <artifactId>magic-api</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.ssssssss</groupId>
-            <artifactId>magic-api</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.ssssssss</groupId>
-            <artifactId>magic-api</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.ssssssss</groupId>
-            <artifactId>magic-api</artifactId>
-        </dependency>
     </dependencies>
 </project>

+ 1 - 1
magic-api-spring-boot-starter/src/main/java/org/ssssssss/magicapi/spring/boot/starter/MagicAPIAutoConfiguration.java

@@ -115,7 +115,7 @@ import java.util.function.BiFunction;
 @Configuration
 @ConditionalOnClass({RequestMappingHandlerMapping.class})
 @EnableConfigurationProperties(MagicAPIProperties.class)
-@Import({MagicRedisAutoConfiguration.class, MagicElasticSearchAutoConfiguration.class, MagicMongoAutoConfiguration.class, MagicJsonAutoConfiguration.class, ApplicationUriPrinter.class})
+@Import({MagicRedisAutoConfiguration.class, MagicElasticSearchAutoConfiguration.class, MagicMongoAutoConfiguration.class, MagicSwaggerConfiguration.class, MagicJsonAutoConfiguration.class, ApplicationUriPrinter.class})
 @EnableWebSocket
 public class MagicAPIAutoConfiguration implements WebMvcConfigurer, WebSocketConfigurer {
 

+ 11 - 0
magic-api-spring-boot-starter/src/main/java/org/ssssssss/magicapi/spring/boot/starter/MagicAPIProperties.java

@@ -147,6 +147,9 @@ public class MagicAPIProperties {
 	@NestedConfigurationProperty
 	private DebugConfig debugConfig = new DebugConfig();
 
+	@NestedConfigurationProperty
+	private SwaggerConfig swaggerConfig = new SwaggerConfig();
+
 	@NestedConfigurationProperty
 	private ResourceConfig resource = new ResourceConfig();
 
@@ -413,4 +416,12 @@ public class MagicAPIProperties {
 	public void setPersistenceResponseBody(boolean persistenceResponseBody) {
 		this.persistenceResponseBody = persistenceResponseBody;
 	}
+
+	public SwaggerConfig getSwaggerConfig() {
+		return swaggerConfig;
+	}
+
+	public void setSwaggerConfig(SwaggerConfig swaggerConfig) {
+		this.swaggerConfig = swaggerConfig;
+	}
 }

+ 89 - 0
magic-api-spring-boot-starter/src/main/java/org/ssssssss/magicapi/spring/boot/starter/MagicSwaggerConfiguration.java

@@ -0,0 +1,89 @@
+package org.ssssssss.magicapi.spring.boot.starter;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.AutoConfigureAfter;
+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.service.MagicResourceService;
+import org.ssssssss.magicapi.core.service.impl.RequestMagicDynamicRegistry;
+import org.ssssssss.magicapi.swagger.SwaggerEntity;
+import org.ssssssss.magicapi.swagger.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.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+@Configuration
+@AutoConfigureAfter({MagicAPIAutoConfiguration.class})
+@EnableConfigurationProperties(MagicAPIProperties.class)
+@ConditionalOnClass(name = "springfox.documentation.swagger.web.SwaggerResourcesProvider")
+public class MagicSwaggerConfiguration {
+
+	private final MagicAPIProperties properties;
+	private final ApplicationContext applicationContext;
+
+	@Autowired
+	@Lazy
+	private RequestMappingHandlerMapping requestMappingHandlerMapping;
+
+	public MagicSwaggerConfiguration(MagicAPIProperties properties, ApplicationContext applicationContext) {
+		this.properties = properties;
+		this.applicationContext = applicationContext;
+	}
+
+
+	@Bean
+	@Primary
+	public SwaggerResourcesProvider magicSwaggerResourcesProvider(RequestMagicDynamicRegistry requestMagicDynamicRegistry, MagicResourceService magicResourceService, ServletContext servletContext) throws NoSuchMethodException {
+		SwaggerConfig config = properties.getSwaggerConfig();
+		Mapping mapping = Mapping.create(requestMappingHandlerMapping);
+		RequestMappingInfo requestMappingInfo = mapping.paths(config.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(config.getDescription(), config.getVersion(), config.getTitle(), license, config.getConcat());
+		// 构建文档信息
+		SwaggerProvider swaggerProvider = new SwaggerProvider(requestMagicDynamicRegistry, magicResourceService, servletContext.getContextPath(), info, properties.isPersistenceResponseBody());
+
+
+		// 注册swagger.json
+		mapping.register(requestMappingInfo, swaggerProvider, SwaggerProvider.class.getDeclaredMethod("swaggerJson"));
+
+		return () -> {
+			List<SwaggerResource> resources = new ArrayList<>();
+			// 追加Magic Swagger信息
+			resources.add(swaggerResource(config.getName(), config.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;
+	}
+}

+ 88 - 0
magic-api-spring-boot-starter/src/main/java/org/ssssssss/magicapi/spring/boot/starter/SwaggerConfig.java

@@ -0,0 +1,88 @@
+package org.ssssssss.magicapi.spring.boot.starter;
+
+import org.springframework.boot.context.properties.NestedConfigurationProperty;
+import org.ssssssss.magicapi.swagger.SwaggerEntity;
+
+/**
+ * Swagger 配置
+ *
+ * @author mxd
+ */
+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();
+
+	/**
+	 * 文档版本
+	 */
+	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;
+	}
+}

+ 15 - 111
magic-api/src/main/java/org/ssssssss/magicapi/core/config/Constants.java

@@ -11,21 +11,6 @@ public class Constants {
 	public static final String CONST_STRING_TRUE = "true";
 
 
-	/**
-	 * 接口文件夹名
-	 */
-	public static final String PATH_API = "api";
-
-	/**
-	 * 函数文件夹名
-	 */
-	public static final String PATH_FUNCTION = "function";
-
-	/**
-	 * websocket文件夹名
-	 */
-	public static final String PATH_WEBSOCKET = "websocket";
-
 	/**
 	 * 空值
 	 */
@@ -65,6 +50,11 @@ public class Constants {
 	 * 脚本中header的变量名
 	 */
 	public static final String VAR_NAME_HEADER = "header";
+
+	/**
+	 * 脚本中query的变量名
+	 */
+	public static final String VAR_NAME_QUERY = "query";
 	/**
 
 	/**
@@ -72,6 +62,16 @@ public class Constants {
 	 */
 	public static final String VAR_NAME_REQUEST_BODY = "body";
 
+	/**
+	 * 脚本中RequestBody的变量值字段类型
+	 */
+	public static final String VAR_NAME_REQUEST_BODY_VALUE_TYPE_OBJECT = "object";
+
+	/**
+	 * 脚本中RequestBody的变量名字段类型
+	 */
+	public static final String VAR_NAME_REQUEST_BODY_VALUE_TYPE_ARRAY = "array";
+
 	public static final String HEADER_REQUEST_SCRIPT_ID = "Magic-Request-Script-Id";
 
 	public static final String HEADER_REQUEST_CLIENT_ID = "Magic-Request-Client-Id";
@@ -126,106 +126,10 @@ public class Constants {
 	 */
 	public static int RESPONSE_CODE_INVALID = 0;
 
-	/**
-	 * 通知新增
-	 */
-	public static final int NOTIFY_ACTION_ADD = 1;
-
-	/**
-	 * 通知修改
-	 */
-	public static final int NOTIFY_ACTION_UPDATE = 2;
-
-	/**
-	 * 通知删除
-	 */
-	public static final int NOTIFY_ACTION_DELETE = 3;
-
-	/**
-	 * 通知更新全部
-	 */
-	public static final int NOTIFY_ACTION_ALL = 0;
-
-	/**
-	 * 通知接口刷新
-	 */
-	public static final int NOTIFY_ACTION_API = 1;
-
-	/**
-	 * 通知分组刷新
-	 */
-	public static final int NOTIFY_ACTION_GROUP = 2;
-
-	/**
-	 * 通知函数刷新
-	 */
-	public static final int NOTIFY_ACTION_FUNCTION = 3;
-
-	/**
-	 * 通知数据源刷新
-	 */
-	public static final int NOTIFY_ACTION_DATASOURCE = 4;
-	/**
-	 * 通知WebSocket刷新
-	 */
-	public static final int NOTIFY_ACTION_WEBSOCKET = 5;
-
-
-	/**
-	 * 通知 C -> S 的WebSocket消息
-	 */
-	public static final int NOTIFY_WS_C_S = 100;
-
-	/**
-	 * 通知 S -> C 的WebSocket消息
-	 */
-	public static final int NOTIFY_WS_S_C = 200;
-
 	/**
 	 * 空数组
 	 */
 	public static final Object[] EMPTY_OBJECT_ARRAY = new Object[0];
 
 
-	/**
-	 * 数据库日期类型
-	 */
-	public static final List<String> DBTYPE_DATE = Arrays.asList("datetime", "time", "date", "timestamp");
-
-	/**
-	 * 数据库数值类型
-	 */
-	public static final List<String> DBTYPE_NUMBER = Arrays.asList("tinyint", "smallint", "mediumint", "int", "number", "integer", "bit", "bigint");
-
-	/**
-	 * 数据库数值类型
-	 */
-	public static final List<String> DBTYPE_DECIMAL = Arrays.asList("float", "double", "decimal");
-
-	/**
-	 * String类型
-	 */
-	public static final String JAVA_TYPE_STRING = "String";
-
-	/**
-	 * Date类型
-	 */
-	public static final String JAVA_TYPE_DATE = "Date";
-
-	/**
-	 * int类型
-	 */
-	public static final String JAVA_TYPE_INTEGER = "Integer";
-
-	/**
-	 * long类型
-	 */
-	public static final String JAVA_TYPE_LONG = "Long";
-
-	/**
-	 * BigDecimal类型
-	 */
-	public static final String JAVA_TYPE_BIGDECIMAL = "BigDecimal";
-
-
 }

+ 6 - 0
magic-api/src/main/java/org/ssssssss/magicapi/core/service/AbstractMagicDynamicRegistry.java

@@ -9,9 +9,11 @@ import org.ssssssss.magicapi.core.exception.MagicAPIException;
 import org.ssssssss.magicapi.core.model.MagicEntity;
 
 import java.util.Iterator;
+import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
 
 public abstract class AbstractMagicDynamicRegistry<T extends MagicEntity> implements MagicDynamicRegistry<T> {
 
@@ -115,6 +117,10 @@ public abstract class AbstractMagicDynamicRegistry<T extends MagicEntity> implem
 
 	}
 
+	public List<T> mappings(){
+		return this.mappings.values().stream().map(MappingNode::getEntity).collect(Collectors.toList());
+	}
+
 	protected MappingNode<T> buildMappingNode(T entity) {
 		MappingNode<T> mappingNode = new MappingNode<>(entity);
 		mappingNode.setMappingKey(this.magicResourceStorage.buildKey(entity));

+ 510 - 0
magic-api/src/main/java/org/ssssssss/magicapi/swagger/SwaggerEntity.java

@@ -0,0 +1,510 @@
+package org.ssssssss.magicapi.swagger;
+
+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 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 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;
+		}
+	}
+}

+ 225 - 0
magic-api/src/main/java/org/ssssssss/magicapi/swagger/SwaggerProvider.java

@@ -0,0 +1,225 @@
+package org.ssssssss.magicapi.swagger;
+
+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;
+
+	public SwaggerProvider(RequestMagicDynamicRegistry requestMagicDynamicRegistry, MagicResourceService magicResourceService, String basePath, SwaggerEntity.Info info, boolean persistenceResponseBody) {
+		this.requestMagicDynamicRegistry = requestMagicDynamicRegistry;
+		this.magicResourceService = magicResourceService;
+		this.basePath = basePath;
+		this.info = info;
+		this.persistenceResponseBody = persistenceResponseBody;
+	}
+
+	@ResponseBody
+	public SwaggerEntity swaggerJson() {
+		this.DEFINITION_MAP.clear();
+		List<ApiInfo> infos = requestMagicDynamicRegistry.mappings();
+		SwaggerEntity swaggerEntity = new SwaggerEntity();
+		swaggerEntity.setInfo(info);
+		swaggerEntity.setBasePath(this.basePath);
+		for (ApiInfo info : infos) {
+			String groupName = magicResourceService.getGroupName(info.getGroupId()).replace("/", "-");
+			String requestPath = PathUtils.replaceSlash("/" + 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 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> entries = ((Map) this.DEFINITION_MAP).entrySet();
+			for (Map.Entry entry : entries) {
+				swaggerEntity.addDefinitions(Objects.toString(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 {
+				Object object = JsonUtils.readValue(info.getRequestBody(), 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()) {
+			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;
+	}
+}

+ 3 - 1
magic-api/src/main/java/org/ssssssss/magicapi/task/service/TaskMagicDynamicRegistry.java

@@ -80,7 +80,9 @@ public class TaskMagicDynamicRegistry extends AbstractMagicDynamicRegistry<TaskI
 		if(scheduledFuture != null){
 			try {
 				scheduledFuture.cancel(true);
-			} catch (Exception ignored) {
+			} catch (Exception e) {
+				String scriptName = MagicConfiguration.getMagicResourceService().getScriptName(info);
+				logger.warn("定时任务:[{}]取消失败", scriptName, e);
 			}
 		}
 	}

+ 4 - 0
magic-api/src/main/java/org/ssssssss/magicapi/utils/Mapping.java

@@ -36,6 +36,10 @@ public class Mapping {
 		this.prefix = StringUtils.defaultIfBlank(prefix, "");
 	}
 
+	public static Mapping create(RequestMappingHandlerMapping mapping) {
+		return create(mapping, null, null);
+	}
+
 	public static Mapping create(RequestMappingHandlerMapping mapping, String base, String prefix) {
 		if (HAS_GET_PATTERN_PARSER) {
 			RequestMappingInfo.BuilderConfiguration config = new RequestMappingInfo.BuilderConfiguration();