Просмотр исходного кода

Merge branch '2.x' into dev

# Conflicts:
#	README.md
#	magic-api-spring-boot-starter/pom.xml
#	magic-api/pom.xml
#	magic-api/src/main/java/org/ssssssss/magicapi/controller/MagicGroupController.java
#	magic-api/src/main/java/org/ssssssss/magicapi/model/JsonCodeConstants.java
#	magic-api/src/main/java/org/ssssssss/magicapi/provider/impl/MagicDatabaseBackupService.java
#	magic-editor/pom.xml
#	magic-editor/src/console/package.json
#	magic-editor/src/console/src/scripts/parsing/ast.js
#	magic-editor/src/console/src/scripts/parsing/parser.js
#	pom.xml
mxd 3 лет назад
Родитель
Сommit
92864e9fd8
100 измененных файлов с 2931 добавлено и 2585 удалено
  1. 1 1
      README.md
  2. 4 4
      db/README.md
  3. 0 22
      db/v0.2.x-v0.3.sql
  4. 0 29
      db/v0.4.x-v0.5-mysql.sql
  5. 0 34
      db/v0.5.x-v0.6-mysql.sql
  6. 0 49
      db/v0.6.x-v0.7.x升级脚本.ms
  7. 27 0
      magic-api-plugins/magic-api-plugin-cluster/pom.xml
  8. 28 0
      magic-api-plugins/magic-api-plugin-cluster/src/main/java/org/ssssssss/magicapi/cluster/ClusterConfig.java
  9. 25 35
      magic-api-plugins/magic-api-plugin-cluster/src/main/java/org/ssssssss/magicapi/cluster/MagicClusterConfiguration.java
  10. 54 0
      magic-api-plugins/magic-api-plugin-cluster/src/main/java/org/ssssssss/magicapi/cluster/MagicSynchronizationService.java
  11. 1 0
      magic-api-plugins/magic-api-plugin-cluster/src/main/resources/META-INF/spring.factories
  12. 21 0
      magic-api-plugins/magic-api-plugin-elasticsearch/pom.xml
  13. 49 0
      magic-api-plugins/magic-api-plugin-elasticsearch/src/main/java/org/ssssssss/magicapi/elasticsearch/ElasticSearchConnection.java
  14. 76 0
      magic-api-plugins/magic-api-plugin-elasticsearch/src/main/java/org/ssssssss/magicapi/elasticsearch/ElasticSearchIndex.java
  15. 26 0
      magic-api-plugins/magic-api-plugin-elasticsearch/src/main/java/org/ssssssss/magicapi/elasticsearch/ElasticSearchModule.java
  16. 94 0
      magic-api-plugins/magic-api-plugin-elasticsearch/src/main/java/org/ssssssss/magicapi/elasticsearch/ElasticSearchRest.java
  17. 23 0
      magic-api-plugins/magic-api-plugin-elasticsearch/src/main/java/org/ssssssss/magicapi/elasticsearch/MagicElasticSearchConfiguration.java
  18. 1 0
      magic-api-plugins/magic-api-plugin-elasticsearch/src/main/resources/META-INF/spring.factories
  19. 21 0
      magic-api-plugins/magic-api-plugin-mongo/pom.xml
  20. 11 14
      magic-api-plugins/magic-api-plugin-mongo/src/main/java/org/ssssssss/magicapi/mongo/MagicMongoConfiguration.java
  21. 1 1
      magic-api-plugins/magic-api-plugin-mongo/src/main/java/org/ssssssss/magicapi/mongo/MongoCollectionExtension.java
  22. 1 1
      magic-api-plugins/magic-api-plugin-mongo/src/main/java/org/ssssssss/magicapi/mongo/MongoFindIterableExtension.java
  23. 14 15
      magic-api-plugins/magic-api-plugin-mongo/src/main/java/org/ssssssss/magicapi/mongo/MongoModule.java
  24. 1 0
      magic-api-plugins/magic-api-plugin-mongo/src/main/resources/META-INF/spring.factories
  25. 21 0
      magic-api-plugins/magic-api-plugin-redis/pom.xml
  26. 46 0
      magic-api-plugins/magic-api-plugin-redis/src/main/java/org/ssssssss/magicapi/redis/MagicRedisConfiguration.java
  27. 12 11
      magic-api-plugins/magic-api-plugin-redis/src/main/java/org/ssssssss/magicapi/redis/RedisModule.java
  28. 3 2
      magic-api-plugins/magic-api-plugin-redis/src/main/java/org/ssssssss/magicapi/redis/RedisResource.java
  29. 1 0
      magic-api-plugins/magic-api-plugin-redis/src/main/resources/META-INF/spring.factories
  30. 31 0
      magic-api-plugins/magic-api-plugin-swagger/pom.xml
  31. 24 26
      magic-api-plugins/magic-api-plugin-swagger/src/main/java/org/ssssssss/magicapi/swagger/MagicSwaggerConfiguration.java
  32. 5 2
      magic-api-plugins/magic-api-plugin-swagger/src/main/java/org/ssssssss/magicapi/swagger/SwaggerConfig.java
  33. 6 10
      magic-api-plugins/magic-api-plugin-swagger/src/main/java/org/ssssssss/magicapi/swagger/entity/SwaggerEntity.java
  34. 40 52
      magic-api-plugins/magic-api-plugin-swagger/src/main/java/org/ssssssss/magicapi/swagger/entity/SwaggerProvider.java
  35. 1 0
      magic-api-plugins/magic-api-plugin-swagger/src/main/resources/META-INF/spring.factories
  36. 91 0
      magic-api-plugins/magic-api-plugin-task/pom.xml
  37. 18 0
      magic-api-plugins/magic-api-plugin-task/src/console/package.json
  38. 49 0
      magic-api-plugins/magic-api-plugin-task/src/console/src/components/magic-task-info.vue
  39. 16 0
      magic-api-plugins/magic-api-plugin-task/src/console/src/i18n/en.js
  40. 16 0
      magic-api-plugins/magic-api-plugin-task/src/console/src/i18n/zh-cn.js
  41. 1 0
      magic-api-plugins/magic-api-plugin-task/src/console/src/icons/task.svg
  42. 35 0
      magic-api-plugins/magic-api-plugin-task/src/console/src/index.js
  43. 42 0
      magic-api-plugins/magic-api-plugin-task/src/console/src/service/magic-task.js
  44. 37 0
      magic-api-plugins/magic-api-plugin-task/src/console/vite.config.js
  45. 70 0
      magic-api-plugins/magic-api-plugin-task/src/main/java/org/ssssssss/magicapi/task/model/TaskInfo.java
  46. 27 0
      magic-api-plugins/magic-api-plugin-task/src/main/java/org/ssssssss/magicapi/task/service/TaskInfoMagicResourceStorage.java
  47. 90 0
      magic-api-plugins/magic-api-plugin-task/src/main/java/org/ssssssss/magicapi/task/service/TaskMagicDynamicRegistry.java
  48. 58 0
      magic-api-plugins/magic-api-plugin-task/src/main/java/org/ssssssss/magicapi/task/starter/MagicAPITaskConfiguration.java
  49. 101 0
      magic-api-plugins/magic-api-plugin-task/src/main/java/org/ssssssss/magicapi/task/starter/MagicTaskConfig.java
  50. 43 0
      magic-api-plugins/magic-api-plugin-task/src/main/java/org/ssssssss/magicapi/task/web/MagicTaskController.java
  51. 1 0
      magic-api-plugins/magic-api-plugin-task/src/main/resources/META-INF/spring.factories
  52. 41 0
      magic-api-plugins/pom.xml
  53. 1 16
      magic-api-spring-boot-starter/pom.xml
  54. 2 1
      magic-api-spring-boot-starter/src/main/java/org/ssssssss/magicapi/spring/boot/starter/ApplicationUriPrinter.java
  55. 0 51
      magic-api-spring-boot-starter/src/main/java/org/ssssssss/magicapi/spring/boot/starter/ClusterConfig.java
  56. 101 347
      magic-api-spring-boot-starter/src/main/java/org/ssssssss/magicapi/spring/boot/starter/MagicAPIAutoConfiguration.java
  57. 74 0
      magic-api-spring-boot-starter/src/main/java/org/ssssssss/magicapi/spring/boot/starter/MagicDynamicRegistryConfiguration.java
  58. 226 0
      magic-api-spring-boot-starter/src/main/java/org/ssssssss/magicapi/spring/boot/starter/MagicModuleConfiguration.java
  59. 0 33
      magic-api-spring-boot-starter/src/main/resources/META-INF/additional-spring-configuration-metadata.json
  60. 6 12
      magic-api/pom.xml
  61. 5 5
      magic-api/src/main/java/org/ssssssss/magicapi/backup/model/Backup.java
  62. 5 38
      magic-api/src/main/java/org/ssssssss/magicapi/backup/service/MagicBackupService.java
  63. 161 0
      magic-api/src/main/java/org/ssssssss/magicapi/backup/service/MagicDatabaseBackupService.java
  64. 84 0
      magic-api/src/main/java/org/ssssssss/magicapi/backup/web/MagicBackupController.java
  65. 0 222
      magic-api/src/main/java/org/ssssssss/magicapi/config/MagicFunctionManager.java
  66. 0 19
      magic-api/src/main/java/org/ssssssss/magicapi/config/MagicModule.java
  67. 0 528
      magic-api/src/main/java/org/ssssssss/magicapi/config/MappingHandlerMapping.java
  68. 0 26
      magic-api/src/main/java/org/ssssssss/magicapi/config/MessageType.java
  69. 0 133
      magic-api/src/main/java/org/ssssssss/magicapi/config/WebSocketSessionManager.java
  70. 0 154
      magic-api/src/main/java/org/ssssssss/magicapi/controller/MagicAPIController.java
  71. 0 86
      magic-api/src/main/java/org/ssssssss/magicapi/controller/MagicDataSourceController.java
  72. 0 127
      magic-api/src/main/java/org/ssssssss/magicapi/controller/MagicFunctionController.java
  73. 0 102
      magic-api/src/main/java/org/ssssssss/magicapi/controller/MagicGroupController.java
  74. 0 33
      magic-api/src/main/java/org/ssssssss/magicapi/controller/MagicWorkbenchHandler.java
  75. 19 0
      magic-api/src/main/java/org/ssssssss/magicapi/core/annotation/MagicModule.java
  76. 3 1
      magic-api/src/main/java/org/ssssssss/magicapi/core/annotation/Message.java
  77. 2 2
      magic-api/src/main/java/org/ssssssss/magicapi/core/annotation/Valid.java
  78. 13 26
      magic-api/src/main/java/org/ssssssss/magicapi/core/config/Backup.java
  79. 2 2
      magic-api/src/main/java/org/ssssssss/magicapi/core/config/Cache.java
  80. 41 92
      magic-api/src/main/java/org/ssssssss/magicapi/core/config/Constants.java
  81. 2 2
      magic-api/src/main/java/org/ssssssss/magicapi/core/config/Crud.java
  82. 2 2
      magic-api/src/main/java/org/ssssssss/magicapi/core/config/Debug.java
  83. 100 0
      magic-api/src/main/java/org/ssssssss/magicapi/core/config/JsonCodeConstants.java
  84. 112 120
      magic-api/src/main/java/org/ssssssss/magicapi/core/config/MagicAPIProperties.java
  85. 49 74
      magic-api/src/main/java/org/ssssssss/magicapi/core/config/MagicConfiguration.java
  86. 2 8
      magic-api/src/main/java/org/ssssssss/magicapi/core/config/MagicCorsFilter.java
  87. 1 1
      magic-api/src/main/java/org/ssssssss/magicapi/core/config/MagicFunction.java
  88. 17 0
      magic-api/src/main/java/org/ssssssss/magicapi/core/config/MagicPluginConfiguration.java
  89. 45 0
      magic-api/src/main/java/org/ssssssss/magicapi/core/config/MessageType.java
  90. 2 2
      magic-api/src/main/java/org/ssssssss/magicapi/core/config/Page.java
  91. 2 2
      magic-api/src/main/java/org/ssssssss/magicapi/core/config/Resource.java
  92. 2 2
      magic-api/src/main/java/org/ssssssss/magicapi/core/config/ResponseCode.java
  93. 2 2
      magic-api/src/main/java/org/ssssssss/magicapi/core/config/Security.java
  94. 210 0
      magic-api/src/main/java/org/ssssssss/magicapi/core/config/WebSocketSessionManager.java
  95. 1 1
      magic-api/src/main/java/org/ssssssss/magicapi/core/context/CookieContext.java
  96. 88 0
      magic-api/src/main/java/org/ssssssss/magicapi/core/context/MagicConsoleSession.java
  97. 1 1
      magic-api/src/main/java/org/ssssssss/magicapi/core/context/MagicUser.java
  98. 1 3
      magic-api/src/main/java/org/ssssssss/magicapi/core/context/RequestContext.java
  99. 141 0
      magic-api/src/main/java/org/ssssssss/magicapi/core/context/RequestEntity.java
  100. 1 1
      magic-api/src/main/java/org/ssssssss/magicapi/core/context/SessionContext.java

+ 1 - 1
README.md

@@ -52,7 +52,7 @@ magic-api 是一个基于Java的接口快速开发框架,编写接口将通过
 <dependency>
 	<groupId>org.ssssssss</groupId>
     <artifactId>magic-api-spring-boot-starter</artifactId>
-    <version>1.7.5</version>
+    <version>2.0.0-beta.1</version>
 </dependency>
 ```
 ## 修改application.properties

+ 4 - 4
db/README.md

@@ -1,6 +1,6 @@
 > 0.7.x版本之后仅需要一张表两个字段,建表语句如下:
 ```sql
-CREATE TABLE `magic_api_file` (
+CREATE TABLE `magic_api_file_v2` (
   `file_path` varchar(512) NOT NULL,
   `file_content` mediumtext,
   PRIMARY KEY (`file_path`)
@@ -8,14 +8,14 @@ CREATE TABLE `magic_api_file` (
 ```
 ### 备份表建表语句
 ```sql
-CREATE TABLE `magic_api_backup` (
+CREATE TABLE `magic_backup_record_v2` (
   `id` varchar(32) NOT NULL COMMENT '原对象ID',
   `create_date` bigint(13) NOT NULL COMMENT '备份时间',
   `tag` varchar(32) DEFAULT NULL COMMENT '标签',
   `type` varchar(32) DEFAULT NULL COMMENT '类型',
   `name` varchar(64) DEFAULT NULL COMMENT '原名称',
-  `content` mediumtext COMMENT '备份内容',
+  `content` blob COMMENT '备份内容',
   `create_by` varchar(64) DEFAULT NULL COMMENT '操作人',
   PRIMARY KEY (`id`,`create_date`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
-```
+```

+ 0 - 22
db/v0.2.x-v0.3.sql

@@ -1,22 +0,0 @@
-SET NAMES utf8mb4;
-SET FOREIGN_KEY_CHECKS = 0;
-
-ALTER TABLE `magic_api_info` ADD COLUMN `api_group_prefix` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '分组前缀' AFTER `api_group_name`;
-ALTER TABLE `magic_api_info` ADD COLUMN `api_output` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '输出结果' AFTER `api_group_prefix`;
--- ----------------------------
--- Table structure for magic_api_info_his
--- ----------------------------
-CREATE TABLE `magic_api_info_his`  (
-    `id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'api_id',
-    `api_method` varchar(12) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '请求方法',
-    `api_path` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '请求路径',
-    `api_script` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '接口脚本',
-    `api_parameter` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '接口参数',
-    `api_option` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '接口选项',
-    `api_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '接口名称',
-    `api_group_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '接口分组',
-    `api_group_prefix` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '分组前缀',
-    `api_output` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '输出结果',
-    `api_create_time` bigint(20) NULL DEFAULT NULL COMMENT '创建时间',
-    `api_update_time` bigint(20) NULL DEFAULT NULL COMMENT '修改时间'
-) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'MagicAPI接口历史记录' ROW_FORMAT = Dynamic;

+ 0 - 29
db/v0.4.x-v0.5-mysql.sql

@@ -1,29 +0,0 @@
--- 创建分组表
-CREATE TABLE `magic_group`  (
-    `id` varchar(32) NOT NULL,
-    `group_name` varchar(64) NULL COMMENT '组名',
-    `group_type` varchar(1) NULL COMMENT '组类型,1:接口分组,2:函数分组',
-    `group_path` varchar(64) NULL COMMENT '分组路径',
-    `parent_id` varchar(32) NULL COMMENT '父级ID',
-    `deleted` char(1) NULL DEFAULT 0 COMMENT '是否被删除,1:是,0:否',
-    PRIMARY KEY (`id`)
-) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'MagicAPI分组信息表' ROW_FORMAT = Dynamic;
--- 插入分组数据
-insert into magic_group select md5(uuid()),api_group_name,'1',api_group_prefix,'0','0' from magic_api_info group by api_group_name,api_group_prefix;
--- 修改字段
-ALTER TABLE `magic_api_info` ADD COLUMN `api_group_id` varchar(32) NULL COMMENT '分组ID' AFTER `api_name`;
-ALTER TABLE `magic_api_info` CHANGE COLUMN `api_output` `api_response_body` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '输出结果' AFTER `api_group_id`;
-ALTER TABLE `magic_api_info` ADD COLUMN `api_description` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '接口描述' AFTER `api_response_body`;
-ALTER TABLE `magic_api_info`  ADD COLUMN `api_request_body` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '请求体' AFTER `api_group_id`, ADD COLUMN `api_request_header` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '请求Header' AFTER `api_request_body`;
-ALTER TABLE `magic_api_info_his` ADD COLUMN `api_group_id` varchar(32) NULL COMMENT '分组ID' AFTER `api_name`;
-ALTER TABLE `magic_api_info_his` CHANGE COLUMN `api_output` `api_response_body` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '输出结果' AFTER `api_group_id`;
-ALTER TABLE `magic_api_info_his` ADD COLUMN `api_description` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '接口描述' AFTER `api_response_body`;
-ALTER TABLE `magic_api_info_his`  ADD COLUMN `api_request_body` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '请求体' AFTER `api_group_id`, ADD COLUMN `api_request_header` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '请求Header' AFTER `api_request_body`;
-
--- 赋值api_group_id字段
-UPDATE magic_api_info mai JOIN magic_group mg ON mg.group_name = mai.api_group_name AND mg.group_path = mai.api_group_prefix SET mai.api_group_id = mg.id;
--- 对关联不上的,归根节点
-UPDATE magic_api_info SET api_group_id = '0' where api_group_id IS NULL;
--- 删除字段
-ALTER TABLE `magic_api_info` DROP COLUMN `api_group_name`,DROP COLUMN `api_group_prefix`;
-ALTER TABLE `magic_api_info_his` DROP COLUMN `api_group_name`,DROP COLUMN `api_group_prefix`;

+ 0 - 34
db/v0.5.x-v0.6-mysql.sql

@@ -1,34 +0,0 @@
-CREATE TABLE `magic_function`
-(
-    `id`                   varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci  NOT NULL COMMENT '主键',
-    `function_name`        varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci  NULL DEFAULT NULL COMMENT '函数名称',
-    `function_path`        varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci  NULL DEFAULT NULL COMMENT '函数路径',
-    `function_parameter`   mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci   NULL COMMENT '参数列表',
-    `function_return_type` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci  NULL DEFAULT NULL COMMENT '返回值类型',
-    `function_script`      mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci   NULL COMMENT '脚本',
-    `function_group_id`    varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci  NOT NULL COMMENT '所属分组',
-    `function_description` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '函数描述',
-    `function_create_time` bigint(20)                                                    NULL DEFAULT NULL COMMENT '创建时间',
-    `function_update_time` bigint(20)                                                    NULL DEFAULT NULL COMMENT '修改时间',
-    PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB
-  CHARACTER SET = utf8mb4
-  COLLATE = utf8mb4_general_ci COMMENT = 'MagicAPI 函数表'
-  ROW_FORMAT = Dynamic;
-
-CREATE TABLE `magic_function_his`
-(
-    `id`                   varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci  NOT NULL COMMENT 'function_id',
-    `function_name`        varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci  NULL DEFAULT NULL COMMENT '函数名称',
-    `function_path`        varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci  NULL DEFAULT NULL COMMENT '函数路径',
-    `function_parameter`   mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci   NULL COMMENT '参数列表',
-    `function_return_type` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci  NULL DEFAULT NULL COMMENT '返回值类型',
-    `function_script`      mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci   NULL COMMENT '脚本',
-    `function_group_id`    varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci  NOT NULL COMMENT '所属分组',
-    `function_description` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '函数描述',
-    `function_create_time` bigint(20)                                                    NULL DEFAULT NULL COMMENT '创建时间',
-    `function_update_time` bigint(20)                                                    NULL DEFAULT NULL COMMENT '修改时间'
-) ENGINE = InnoDB
-  CHARACTER SET = utf8mb4
-  COLLATE = utf8mb4_general_ci COMMENT = 'MagicAPI 函数历史记录'
-  ROW_FORMAT = Dynamic;

+ 0 - 49
db/v0.6.x-v0.7.x升级脚本.ms

@@ -1,49 +0,0 @@
-import 'org.ssssssss.magicapi.adapter.Resource' as root;
-import 'org.ssssssss.magicapi.provider.GroupServiceProvider';
-import 'org.ssssssss.magicapi.provider.ApiServiceProvider';
-import 'org.ssssssss.magicapi.provider.FunctionServiceProvider';
-import 'org.ssssssss.magicapi.utils.IoUtils' as IoUtils
-import 'org.ssssssss.magicapi.utils.JsonUtils' as JsonUtils
-import 'java.io.File' as File;
-var ds = db.camel();    //如果之前保存在其他库,这里可以修改为db.xxx.camel();
-var apiSql = """ select * from magic_api_info """;
-var groupSql = """ select * from magic_group where deleted = '0' """;
-var functionSql = """ select * from magic_function """;
-// 替换key,去除前缀,将首字母小写。
-var replaceKey = (it,src) => it.replaceKey(src,'').replaceKey(it => it.substring(0,1).toLowerCase() + it.substring(1));
-// list转tree
-var toTree = (list,parentId)=>list.filter(it => it.parentId == parentId).each(it => it.children = toTree(list,it.id))
-// 查询分组列表
-var groupList = ds.select(groupSql).map(it => replaceKey(it,"group"));
-// 将接口分组转为tree
-var apiTree = toTree(groupList.filter(it => it.type == '1'),'0');
-// 将函数分组转为tree
-var functionTree = toTree(groupList.filter(it => it.type == '2'),'0');
-// 记录分组所在路径
-var groups = {};
-// 处理分组
-var processGroup = (parent,list)=>{
-    if(!parent.exists()){
-        parent.mkdir();
-    }
-    list.each(it => {
-        var resource = parent.getResource(it.name);
-        resource.mkdir();
-        groups[it.id] = resource;
-        // 防止序列化children
-        var children = it.remove('children');
-        resource.getResource('group.json').write(JsonUtils.toJsonString(it))
-        if(children){
-            processGroup(resource,children);
-        }
-    });
-}
-// 处理接口分组
-processGroup(root.getResource("api"),apiTree);
-// 处理函数分组
-processGroup(root.getResource("function"),functionTree);
-// 处理接口
-ds.select(apiSql).map(it => replaceKey(it,'api')).each(it => groups[it.groupId].getResource(it.name + '.ms').write(ApiServiceProvider.serialize(it)));
-// 处理函数
-ds.select(functionSql).map(it => replaceKey(it,'function')).each(it => groups[it.groupId].getResource(it.name + '.ms').write(FunctionServiceProvider.serialize(it)));
-return 'ok';

+ 27 - 0
magic-api-plugins/magic-api-plugin-cluster/pom.xml

@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xmlns="http://maven.apache.org/POM/4.0.0"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.ssssssss</groupId>
+        <artifactId>magic-api-plugins</artifactId>
+        <version>2.0.0-beta.1</version>
+    </parent>
+    <artifactId>magic-api-plugin-cluster</artifactId>
+    <packaging>jar</packaging>
+    <name>magic-api-plugin-cluster</name>
+    <description>magic-api-plugin-cluster</description>
+    <dependencies>
+        <dependency>
+            <groupId>org.ssssssss</groupId>
+            <artifactId>magic-api-plugin-redis</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-configuration-processor</artifactId>
+            <optional>true</optional>
+        </dependency>
+    </dependencies>
+</project>

+ 28 - 0
magic-api-plugins/magic-api-plugin-cluster/src/main/java/org/ssssssss/magicapi/cluster/ClusterConfig.java

@@ -0,0 +1,28 @@
+package org.ssssssss.magicapi.cluster;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+import java.util.UUID;
+
+/**
+ * 集群配置
+ *
+ * @author mxd
+ * @since 1.2.0
+ */
+@ConfigurationProperties(prefix = "magic-api.cluster")
+public class ClusterConfig {
+
+	/**
+	 * redis 通道
+	 */
+	private String channel = "magic-api:notify:channel";
+
+	public String getChannel() {
+		return channel;
+	}
+
+	public void setChannel(String channel) {
+		this.channel = channel;
+	}
+}

+ 25 - 35
magic-api-spring-boot-starter/src/main/java/org/ssssssss/magicapi/spring/boot/starter/MagicRedisAutoConfiguration.java → magic-api-plugins/magic-api-plugin-cluster/src/main/java/org/ssssssss/magicapi/cluster/MagicClusterConfiguration.java

@@ -1,85 +1,75 @@
-package org.ssssssss.magicapi.spring.boot.starter;
+package org.ssssssss.magicapi.cluster;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.ObjectProvider;
-import org.springframework.boot.autoconfigure.AutoConfigureBefore;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
 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.data.redis.connection.RedisConnectionFactory;
 import org.springframework.data.redis.core.StringRedisTemplate;
 import org.springframework.data.redis.listener.ChannelTopic;
 import org.springframework.data.redis.listener.RedisMessageListenerContainer;
-import org.ssssssss.magicapi.adapter.Resource;
-import org.ssssssss.magicapi.adapter.resource.RedisResource;
-import org.ssssssss.magicapi.model.MagicNotify;
-import org.ssssssss.magicapi.modules.RedisModule;
-import org.ssssssss.magicapi.provider.MagicAPIService;
-import org.ssssssss.magicapi.provider.MagicNotifyService;
+import org.ssssssss.magicapi.core.config.MagicAPIProperties;
+import org.ssssssss.magicapi.core.config.MagicPluginConfiguration;
+import org.ssssssss.magicapi.core.model.MagicNotify;
+import org.ssssssss.magicapi.core.model.Plugin;
+import org.ssssssss.magicapi.core.service.MagicAPIService;
+import org.ssssssss.magicapi.core.service.MagicNotifyService;
 import org.ssssssss.magicapi.utils.JsonUtils;
 
 import java.util.Objects;
 
-/**
- * redis配置
- *
- * @author mxd
- */
-@ConditionalOnClass(RedisConnectionFactory.class)
+
+@EnableConfigurationProperties(ClusterConfig.class)
 @Configuration
-@AutoConfigureBefore(MagicAPIAutoConfiguration.class)
-public class MagicRedisAutoConfiguration {
+public class MagicClusterConfiguration implements MagicPluginConfiguration {
 
-	private final static Logger logger = LoggerFactory.getLogger(MagicRedisAutoConfiguration.class);
+	private final ClusterConfig config;
 
 	private final MagicAPIProperties properties;
 
 	private final StringRedisTemplate stringRedisTemplate;
 
-	public MagicRedisAutoConfiguration(MagicAPIProperties properties, ObjectProvider<StringRedisTemplate> stringRedisTemplateProvider) {
+	private final Logger logger = LoggerFactory.getLogger(MagicClusterConfiguration.class);
+
+	public MagicClusterConfiguration(MagicAPIProperties properties, ClusterConfig config, ObjectProvider<StringRedisTemplate> stringRedisTemplateProvider) {
 		this.properties = properties;
+		this.config = config;
 		this.stringRedisTemplate = stringRedisTemplateProvider.getIfAvailable();
 	}
 
-	/**
-	 * 注入redis模块
-	 */
-	@Bean
-	public RedisModule redisFunctions(RedisConnectionFactory connectionFactory) {
-		return new RedisModule(connectionFactory);
+	@Override
+	public Plugin plugin() {
+		return new Plugin("Cluster");
 	}
 
 	/**
-	 * 使用Redis存储
+	 * 使用Redis推送通知
 	 */
 	@Bean
 	@ConditionalOnMissingBean
-	@ConditionalOnProperty(prefix = "magic-api", name = "resource.type", havingValue = "redis")
-	public Resource magicRedisResource(RedisConnectionFactory connectionFactory) {
-		ResourceConfig resource = properties.getResource();
-		return new RedisResource(new StringRedisTemplate(connectionFactory), resource.getPrefix(), resource.isReadonly());
+	public MagicNotifyService magicNotifyService() {
+		return magicNotify -> stringRedisTemplate.convertAndSend(config.getChannel(), Objects.requireNonNull(JsonUtils.toJsonString(magicNotify)));
 	}
 
 	/**
-	 * 使用Redis推送通知
+	 * 消息处理服务
 	 */
 	@Bean
 	@ConditionalOnMissingBean
-	@ConditionalOnProperty(prefix = "magic-api", name = "cluster-config.enable", havingValue = "true")
-	public MagicNotifyService magicNotifyService() {
-		return magicNotify -> stringRedisTemplate.convertAndSend(properties.getClusterConfig().getChannel(), Objects.requireNonNull(JsonUtils.toJsonString(magicNotify)));
+	public MagicSynchronizationService magicSynchronizationService(MagicNotifyService magicNotifyService) {
+		return new MagicSynchronizationService(magicNotifyService, properties.getInstanceId());
 	}
 
 	/**
 	 * 集群通知监听
 	 */
 	@Bean
-	@ConditionalOnProperty(prefix = "magic-api", name = "cluster-config.enable", havingValue = "true")
 	public RedisMessageListenerContainer magicRedisMessageListenerContainer(RedisConnectionFactory redisConnectionFactory, MagicAPIService magicAPIService) {
-		ClusterConfig config = properties.getClusterConfig();
 		logger.info("开启集群通知监听, Redis channel: {}", config.getChannel());
 		RedisMessageListenerContainer redisMessageListenerContainer = new RedisMessageListenerContainer();
 		redisMessageListenerContainer.setConnectionFactory(redisConnectionFactory);

+ 54 - 0
magic-api-plugins/magic-api-plugin-cluster/src/main/java/org/ssssssss/magicapi/cluster/MagicSynchronizationService.java

@@ -0,0 +1,54 @@
+package org.ssssssss.magicapi.cluster;
+
+import org.springframework.context.event.EventListener;
+import org.ssssssss.magicapi.core.event.*;
+import org.ssssssss.magicapi.core.config.Constants;
+import org.ssssssss.magicapi.core.model.MagicNotify;
+import org.ssssssss.magicapi.core.service.MagicNotifyService;
+
+public class MagicSynchronizationService {
+
+	private final MagicNotifyService magicNotifyService;
+
+	/**
+	 * 当前实例ID
+	 */
+	private final String instanceId;
+
+	public MagicSynchronizationService(MagicNotifyService magicNotifyService, String instanceId) {
+		this.magicNotifyService = magicNotifyService;
+		this.instanceId = instanceId;
+	}
+
+	@EventListener(condition = "#event.source != T(org.ssssssss.magicapi.core.config.Constants).EVENT_SOURCE_NOTIFY")
+	public void onFolderEvent(GroupEvent event) {
+		switch (event.getAction()) {
+			case CREATE:
+			case SAVE:
+			case MOVE:
+			case DELETE:
+				magicNotifyService.sendNotify(new MagicNotify(instanceId, event.getGroup().getId(), event.getAction(), event.getType()));
+				break;
+		}
+	}
+
+	@EventListener(condition = "#event.source != T(org.ssssssss.magicapi.core.config.Constants).EVENT_SOURCE_NOTIFY")
+	public void onFileEvent(FileEvent event) {
+		if (Constants.EVENT_SOURCE_NOTIFY.equals(event.getSource())) {
+			return;
+		}
+		switch (event.getAction()) {
+			case CREATE:
+			case SAVE:
+			case MOVE:
+			case DELETE:
+				magicNotifyService.sendNotify(new MagicNotify(instanceId, event.getEntity().getId(), event.getAction(), Constants.EVENT_TYPE_FILE));
+				break;
+		}
+	}
+
+	@EventListener(condition = "#event.action == T(org.ssssssss.magicapi.core.event.EventAction).CLEAR && #event.source != T(org.ssssssss.magicapi.core.config.Constants).EVENT_SOURCE_NOTIFY")
+	public void onClearEvent(MagicEvent event){
+		magicNotifyService.sendNotify(new MagicNotify(instanceId, null, event.getAction(), null));
+	}
+}

+ 1 - 0
magic-api-plugins/magic-api-plugin-cluster/src/main/resources/META-INF/spring.factories

@@ -0,0 +1 @@
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=org.ssssssss.magicapi.cluster.MagicClusterConfiguration

+ 21 - 0
magic-api-plugins/magic-api-plugin-elasticsearch/pom.xml

@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xmlns="http://maven.apache.org/POM/4.0.0"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.ssssssss</groupId>
+        <artifactId>magic-api-plugins</artifactId>
+        <version>2.0.0-beta.1</version>
+    </parent>
+    <artifactId>magic-api-plugin-elasticsearch</artifactId>
+    <packaging>jar</packaging>
+    <name>magic-api-plugin-elasticsearch</name>
+    <description>magic-api-plugin-elasticsearch</description>
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
+        </dependency>
+    </dependencies>
+</project>

+ 49 - 0
magic-api-plugins/magic-api-plugin-elasticsearch/src/main/java/org/ssssssss/magicapi/elasticsearch/ElasticSearchConnection.java

@@ -0,0 +1,49 @@
+package org.ssssssss.magicapi.elasticsearch;
+
+import org.elasticsearch.client.RestClient;
+
+import java.io.IOException;
+import java.util.Map;
+
+public class ElasticSearchConnection extends ElasticSearchRest {
+
+	public ElasticSearchConnection(RestClient restClient, String endpoint) {
+		super(restClient);
+		super.endpoint(endpoint);
+	}
+
+	public ElasticSearchConnection parameter(String key, String value) {
+		if (value != null) {
+			parameters.put(key, value);
+		}
+		return this;
+	}
+
+	public ElasticSearchConnection parameters(Map<String, String> params) {
+		if (params != null) {
+			parameters.putAll(params);
+		}
+		return this;
+	}
+
+	public Object put(Object data) throws IOException {
+		return processResponse(json(data).doPut());
+	}
+
+	public Object delete() throws IOException {
+		return processResponse(doDelete());
+	}
+
+	public Object delete(Object data) throws IOException {
+		return processResponse(json(data).doDelete());
+	}
+
+	public Object post(Object data) throws IOException {
+		return processResponse(json(data).doPost());
+	}
+
+	public Object get() throws IOException {
+		return processResponse(doGet());
+	}
+
+}

+ 76 - 0
magic-api-plugins/magic-api-plugin-elasticsearch/src/main/java/org/ssssssss/magicapi/elasticsearch/ElasticSearchIndex.java

@@ -0,0 +1,76 @@
+package org.ssssssss.magicapi.elasticsearch;
+
+import org.elasticsearch.client.RestClient;
+import org.ssssssss.magicapi.utils.JsonUtils;
+import org.ssssssss.script.annotation.Comment;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+public class ElasticSearchIndex {
+
+	private final RestClient restClient;
+
+	private final String name;
+
+	private final String type;
+
+
+	public ElasticSearchIndex(RestClient restClient, String name, String type) {
+		this.restClient = restClient;
+		this.name = name;
+		this.type = type;
+	}
+
+	@Comment("根据`_id`保存,当存在时更新,不存在时插入")
+	public Object save(@Comment(value = "_id", name = "_id")String _id, @Comment(value = "保存对象", name = "data")Object data) throws IOException {
+		return connect("/%s/%s/%s", this.name, this.type, _id).post(data);
+	}
+
+	@Comment("不指定`_id`插入")
+	public Object insert(@Comment(value = "插入对象", name = "data")Object data) throws IOException {
+		return connect("/%s/%s", this.name, this.type).post(data);
+	}
+
+	@Comment("指定`_id`插入,当`_id`存在时不会更新")
+	public Object insert(@Comment(value = "_id", name = "_id")String _id, @Comment(value = "插入对象", name = "data")Object data) throws IOException {
+		return connect("/%s/%s/%s/_create", this.name, this.type, _id).post(data);
+	}
+
+	@Comment("根据`id`删除")
+	public Object delete(@Comment(value = "id", name = "id")String id) throws IOException {
+		return connect("/%s/%s/%s", this.name, this.type, id).delete();
+	}
+
+	@Comment("批量保存,当包含`id`时,则使用该列值匹配保存")
+	public Object bulkSave(@Comment(value = "保存内容", name = "list") List<Map<String, Object>> list) throws IOException {
+		StringBuilder builder = new StringBuilder();
+		list.forEach(item -> {
+			Object id = item.get("id");
+			if(id != null){
+				builder.append(String.format("{ \"index\":{ \"_id\": \"%s\" } }\r\n", id));
+			} else {
+				builder.append("{ \"index\":{} }\r\n");
+			}
+			builder.append(JsonUtils.toJsonStringWithoutPretty(item));
+			builder.append("\r\n");
+		});
+		return connect("/%s/%s/_bulk", this.name, this.type).post(builder.toString());
+	}
+
+	@Comment("根据`_id`修改")
+	public Object update(@Comment(value = "_id", name = "_id")String _id, @Comment(value = "修改项", name = "data")Object data) throws IOException {
+		return connect("/%s/%s/%s", this.name, this.type, _id).post(Collections.singletonMap("doc", data));
+	}
+
+	@Comment("搜索")
+	public Object search(@Comment(value = "搜索`DSL`语句", name = "dsl")Map<String, Object> dsl) throws IOException {
+		return connect("/%s/_search", this.name).post(dsl);
+	}
+
+	private ElasticSearchConnection connect(String format, Object... args) {
+		return new ElasticSearchConnection(this.restClient, String.format(format, args));
+	}
+}

+ 26 - 0
magic-api-plugins/magic-api-plugin-elasticsearch/src/main/java/org/ssssssss/magicapi/elasticsearch/ElasticSearchModule.java

@@ -0,0 +1,26 @@
+package org.ssssssss.magicapi.elasticsearch;
+
+import org.elasticsearch.client.RestClient;
+import org.ssssssss.magicapi.core.annotation.MagicModule;
+import org.ssssssss.script.annotation.Comment;
+
+@MagicModule("elasticsearch")
+public class ElasticSearchModule {
+
+	private static final String DOC = "_doc";
+
+	private final RestClient restClient;
+
+	public ElasticSearchModule(RestClient restClient) {
+		this.restClient = restClient;
+	}
+
+	@Comment(value = "ElasticSearch REST API")
+	public ElasticSearchConnection rest(String url){
+		return new ElasticSearchConnection(this.restClient, url);
+	}
+
+	public ElasticSearchIndex index(String indexName){
+		return new ElasticSearchIndex(this.restClient, indexName, DOC);
+	}
+}

+ 94 - 0
magic-api-plugins/magic-api-plugin-elasticsearch/src/main/java/org/ssssssss/magicapi/elasticsearch/ElasticSearchRest.java

@@ -0,0 +1,94 @@
+package org.ssssssss.magicapi.elasticsearch;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.entity.ContentType;
+import org.apache.http.nio.entity.NStringEntity;
+import org.apache.http.util.EntityUtils;
+import org.elasticsearch.client.Request;
+import org.elasticsearch.client.Response;
+import org.elasticsearch.client.RestClient;
+import org.ssssssss.magicapi.utils.JsonUtils;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+public class ElasticSearchRest {
+
+	private final RestClient restClient;
+
+	private String method;
+
+	private String endpoint = "/";
+
+	private HttpEntity entity;
+
+	protected final Map<String, String> parameters = new HashMap<>();
+
+	public ElasticSearchRest(RestClient restClient) {
+		this.restClient = restClient;
+	}
+
+	ElasticSearchRest endpoint(String endpoint){
+		this.endpoint = endpoint;
+		return this;
+	}
+
+	Response doGet() throws IOException {
+		this.method = "GET";
+		return execute();
+	}
+
+	Response doPost() throws IOException {
+		this.method = "POST";
+		return execute();
+	}
+
+	Response doDelete() throws IOException {
+		this.method = "DELETE";
+		return execute();
+	}
+
+	Response doPut() throws IOException {
+		this.method = "PUT";
+		return execute();
+	}
+
+	ElasticSearchRest json(Object data){
+		if(data == null){
+			return this;
+		}
+		String json = null;
+		if(data instanceof CharSequence){
+			json = data.toString();
+		} else {
+			json = JsonUtils.toJsonString(data);
+		}
+		if(json != null){
+			this.entity = new NStringEntity(json, ContentType.APPLICATION_JSON);
+		}
+		return this;
+	}
+
+	private Response execute() throws IOException {
+		Request request = new Request(method, this.endpoint);
+		request.addParameters(parameters);
+		request.setEntity(entity);
+		return this.restClient.performRequest(request);
+	}
+
+	Object processResponse(Response response) throws IOException {
+		int code = response.getStatusLine().getStatusCode();
+		if (code >= 200 && code < 300) {    // 2xx
+			HttpEntity entity = response.getEntity();
+			String resp = EntityUtils.toString(entity, StandardCharsets.UTF_8);
+			ContentType contentType = ContentType.get(entity);
+			if (Objects.equals(ContentType.APPLICATION_JSON.getMimeType(), contentType.getMimeType())) {
+				return JsonUtils.readValue(resp, Object.class);
+			}
+		}
+		return response;
+	}
+}

+ 23 - 0
magic-api-plugins/magic-api-plugin-elasticsearch/src/main/java/org/ssssssss/magicapi/elasticsearch/MagicElasticSearchConfiguration.java

@@ -0,0 +1,23 @@
+package org.ssssssss.magicapi.elasticsearch;
+
+import org.elasticsearch.client.RestHighLevelClient;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.ssssssss.magicapi.core.config.MagicPluginConfiguration;
+import org.ssssssss.magicapi.core.model.Plugin;
+
+@Configuration
+public class MagicElasticSearchConfiguration implements MagicPluginConfiguration {
+
+	@Override
+	public Plugin plugin() {
+		return new Plugin("ElasticSearch");
+	}
+
+	@Bean
+	@ConditionalOnBean(RestHighLevelClient.class)
+	public ElasticSearchModule elasticSearchModule(RestHighLevelClient restHighLevelClient){
+		return new ElasticSearchModule(restHighLevelClient.getLowLevelClient());
+	}
+}

+ 1 - 0
magic-api-plugins/magic-api-plugin-elasticsearch/src/main/resources/META-INF/spring.factories

@@ -0,0 +1 @@
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=org.ssssssss.magicapi.elasticsearch.MagicElasticSearchConfiguration

+ 21 - 0
magic-api-plugins/magic-api-plugin-mongo/pom.xml

@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xmlns="http://maven.apache.org/POM/4.0.0"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.ssssssss</groupId>
+        <artifactId>magic-api-plugins</artifactId>
+        <version>2.0.0-beta.1</version>
+    </parent>
+    <artifactId>magic-api-plugin-mongo</artifactId>
+    <packaging>jar</packaging>
+    <name>magic-api-plugin-mongo</name>
+    <description>magic-api-plugin-mongo</description>
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-mongodb</artifactId>
+        </dependency>
+    </dependencies>
+</project>

+ 11 - 14
magic-api-spring-boot-starter/src/main/java/org/ssssssss/magicapi/spring/boot/starter/MagicMongoAutoConfiguration.java → magic-api-plugins/magic-api-plugin-mongo/src/main/java/org/ssssssss/magicapi/mongo/MagicMongoConfiguration.java

@@ -1,31 +1,28 @@
-package org.ssssssss.magicapi.spring.boot.starter;
+package org.ssssssss.magicapi.mongo;
 
 import com.mongodb.client.FindIterable;
 import com.mongodb.client.MongoCollection;
-import org.springframework.boot.autoconfigure.AutoConfigureBefore;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.data.mongodb.core.MongoTemplate;
-import org.ssssssss.magicapi.modules.MongoCollectionExtension;
-import org.ssssssss.magicapi.modules.MongoFindIterableExtension;
-import org.ssssssss.magicapi.modules.MongoModule;
+import org.ssssssss.magicapi.core.config.MagicPluginConfiguration;
+import org.ssssssss.magicapi.core.model.Plugin;
 import org.ssssssss.script.reflection.JavaReflection;
 
-/**
- * mongo配置
- *
- * @author mxd
- */
 @Configuration
-@ConditionalOnBean(MongoTemplate.class)
-@AutoConfigureBefore(MagicAPIAutoConfiguration.class)
-public class MagicMongoAutoConfiguration {
+public class MagicMongoConfiguration implements MagicPluginConfiguration {
+
+	@Override
+	public Plugin plugin() {
+		return new Plugin("Mongo");
+	}
 
 	/**
 	 * 注入mongo模块
 	 */
 	@Bean
+	@ConditionalOnMissingBean
 	public MongoModule mongoFunctions(MongoTemplate mongoTemplate) {
 		JavaReflection.registerMethodExtension(MongoCollection.class, new MongoCollectionExtension());
 		JavaReflection.registerMethodExtension(FindIterable.class, new MongoFindIterableExtension());

+ 1 - 1
magic-api/src/main/java/org/ssssssss/magicapi/modules/MongoCollectionExtension.java → magic-api-plugins/magic-api-plugin-mongo/src/main/java/org/ssssssss/magicapi/mongo/MongoCollectionExtension.java

@@ -1,4 +1,4 @@
-package org.ssssssss.magicapi.modules;
+package org.ssssssss.magicapi.mongo;
 
 import com.mongodb.client.FindIterable;
 import com.mongodb.client.MongoCollection;

+ 1 - 1
magic-api/src/main/java/org/ssssssss/magicapi/modules/MongoFindIterableExtension.java → magic-api-plugins/magic-api-plugin-mongo/src/main/java/org/ssssssss/magicapi/mongo/MongoFindIterableExtension.java

@@ -1,4 +1,4 @@
-package org.ssssssss.magicapi.modules;
+package org.ssssssss.magicapi.mongo;
 
 import com.mongodb.client.FindIterable;
 import com.mongodb.client.MongoCursor;

+ 14 - 15
magic-api/src/main/java/org/ssssssss/magicapi/modules/MongoModule.java → magic-api-plugins/magic-api-plugin-mongo/src/main/java/org/ssssssss/magicapi/mongo/MongoModule.java

@@ -1,4 +1,4 @@
-package org.ssssssss.magicapi.modules;
+package org.ssssssss.magicapi.mongo;
 
 import com.mongodb.client.MongoCollection;
 import com.mongodb.client.MongoDatabase;
@@ -8,15 +8,16 @@ import org.bson.conversions.Bson;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.data.mongodb.core.MongoTemplate;
-import org.ssssssss.magicapi.config.MagicModule;
-import org.ssssssss.magicapi.model.Constants;
+import org.ssssssss.magicapi.core.config.Constants;
+import org.ssssssss.magicapi.core.annotation.MagicModule;
 import org.ssssssss.script.convert.ClassImplicitConvert;
+import org.ssssssss.script.functions.DynamicAttribute;
 import org.ssssssss.script.reflection.JavaInvoker;
 import org.ssssssss.script.reflection.JavaReflection;
 import org.ssssssss.script.runtime.Variables;
 
+import java.beans.Transient;
 import java.lang.reflect.Method;
-import java.util.HashMap;
 import java.util.Map;
 
 /**
@@ -24,7 +25,8 @@ import java.util.Map;
  *
  * @author mxd
  */
-public class MongoModule extends HashMap<String, MongoModule.MongoDataBaseGetter> implements MagicModule, ClassImplicitConvert {
+@MagicModule("mongo")
+public class MongoModule implements ClassImplicitConvert, DynamicAttribute<MongoModule.MongoDataBaseGetter, MongoModule.MongoDataBaseGetter> {
 
 	private static final Logger logger = LoggerFactory.getLogger(MongoModule.class);
 
@@ -54,23 +56,19 @@ public class MongoModule extends HashMap<String, MongoModule.MongoDataBaseGetter
 	}
 
 	@Override
-	public MongoDataBaseGetter get(Object databaseName) {
+	@Transient
+	public MongoDataBaseGetter getDynamicAttribute(String databaseName) {
 		try {
 			if (databaseName == null) {
 				return null;
 			}
-			MongoDatabase database = (MongoDatabase) invoker.invoke0(factory, null, new Object[]{databaseName.toString()});
+			MongoDatabase database = (MongoDatabase) invoker.invoke0(factory, null, new Object[]{databaseName});
 			return new MongoDataBaseGetter(database);
 		} catch (Throwable e) {
 			throw new RuntimeException(e);
 		}
 	}
 
-	@Override
-	public String getModuleName() {
-		return "mongo";
-	}
-
 	@Override
 	public boolean support(Class<?> from, Class<?> to) {
 		return Map.class.isAssignableFrom(from) && (Bson.class.isAssignableFrom(to));
@@ -81,7 +79,7 @@ public class MongoModule extends HashMap<String, MongoModule.MongoDataBaseGetter
 		return new Document((Map<String, Object>) source);
 	}
 
-	public static class MongoDataBaseGetter extends HashMap<String, MongoCollection<Document>> {
+	public static class MongoDataBaseGetter implements DynamicAttribute<MongoCollection<Document>, MongoCollection<Document>> {
 
 		MongoDatabase database;
 
@@ -90,8 +88,9 @@ public class MongoModule extends HashMap<String, MongoModule.MongoDataBaseGetter
 		}
 
 		@Override
-		public MongoCollection<Document> get(Object key) {
-			return database.getCollection(key.toString());
+		@Transient
+		public MongoCollection<Document> getDynamicAttribute(String key) {
+			return database.getCollection(key);
 		}
 	}
 }

+ 1 - 0
magic-api-plugins/magic-api-plugin-mongo/src/main/resources/META-INF/spring.factories

@@ -0,0 +1 @@
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=org.ssssssss.magicapi.mongo.MagicMongoConfiguration

+ 21 - 0
magic-api-plugins/magic-api-plugin-redis/pom.xml

@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xmlns="http://maven.apache.org/POM/4.0.0"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.ssssssss</groupId>
+        <artifactId>magic-api-plugins</artifactId>
+        <version>2.0.0-beta.1</version>
+    </parent>
+    <artifactId>magic-api-plugin-redis</artifactId>
+    <packaging>jar</packaging>
+    <name>magic-api-plugin-redis</name>
+    <description>magic-api-plugin-redis</description>
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-redis</artifactId>
+        </dependency>
+    </dependencies>
+</project>

+ 46 - 0
magic-api-plugins/magic-api-plugin-redis/src/main/java/org/ssssssss/magicapi/redis/MagicRedisConfiguration.java

@@ -0,0 +1,46 @@
+package org.ssssssss.magicapi.redis;
+
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.ssssssss.magicapi.core.config.MagicAPIProperties;
+import org.ssssssss.magicapi.core.config.MagicPluginConfiguration;
+import org.ssssssss.magicapi.core.config.Resource;
+import org.ssssssss.magicapi.core.model.Plugin;
+
+@Configuration
+public class MagicRedisConfiguration implements MagicPluginConfiguration {
+
+	private final MagicAPIProperties properties;
+
+	public MagicRedisConfiguration(MagicAPIProperties properties) {
+		this.properties = properties;
+	}
+
+	/**
+	 * 使用Redis存储
+	 */
+	@Bean
+	@ConditionalOnMissingBean
+	@ConditionalOnProperty(prefix = "magic-api", name = "resource.type", havingValue = "redis")
+	public org.ssssssss.magicapi.core.resource.Resource magicRedisResource(RedisConnectionFactory connectionFactory) {
+		Resource resource = properties.getResource();
+		return new RedisResource(new StringRedisTemplate(connectionFactory), resource.getPrefix(), resource.isReadonly());
+	}
+
+	/**
+	 * 注入redis模块
+	 */
+	@Bean
+	public RedisModule redisFunctions(RedisConnectionFactory connectionFactory) {
+		return new RedisModule(connectionFactory);
+	}
+
+	@Override
+	public Plugin plugin() {
+		return new Plugin("Redis");
+	}
+}

+ 12 - 11
magic-api/src/main/java/org/ssssssss/magicapi/modules/RedisModule.java → magic-api-plugins/magic-api-plugin-redis/src/main/java/org/ssssssss/magicapi/redis/RedisModule.java

@@ -1,20 +1,20 @@
-package org.ssssssss.magicapi.modules;
+package org.ssssssss.magicapi.redis;
 
 import org.springframework.data.redis.connection.RedisConnectionFactory;
 import org.springframework.data.redis.core.RedisCallback;
 import org.springframework.data.redis.core.StringRedisTemplate;
-import org.ssssssss.magicapi.config.MagicModule;
+import org.ssssssss.magicapi.core.annotation.MagicModule;
 import org.ssssssss.script.functions.DynamicMethod;
 
-import java.util.ArrayList;
-import java.util.List;
+import java.util.*;
 
 /**
  * redis模块
  *
  * @author mxd
  */
-public class RedisModule implements MagicModule, DynamicMethod {
+@MagicModule("redis")
+public class RedisModule implements DynamicMethod {
 
 	private final StringRedisTemplate redisTemplate;
 
@@ -22,11 +22,6 @@ public class RedisModule implements MagicModule, DynamicMethod {
 		this.redisTemplate = new StringRedisTemplate(connectionFactory);
 	}
 
-	@Override
-	public String getModuleName() {
-		return "redis";
-	}
-
 	/**
 	 * 序列化
 	 */
@@ -40,13 +35,13 @@ public class RedisModule implements MagicModule, DynamicMethod {
 	/**
 	 * 反序列化
 	 */
+	@SuppressWarnings("unchecked")
 	private Object deserialize(Object value) {
 		if (value != null) {
 			if (value instanceof byte[]) {
 				return this.redisTemplate.getStringSerializer().deserialize((byte[]) value);
 			}
 			if (value instanceof List) {
-				@SuppressWarnings("unchecked")
 				List<Object> valueList = (List<Object>) value;
 				List<Object> resultList = new ArrayList<>(valueList.size());
 				for (Object val : valueList) {
@@ -54,6 +49,12 @@ public class RedisModule implements MagicModule, DynamicMethod {
 				}
 				return resultList;
 			}
+			if(value instanceof Map){
+				Map<Object, Object> map = (Map<Object, Object>) value;
+				LinkedHashMap<Object, Object> newMap = new LinkedHashMap<>(map.size());
+				map.forEach((key, val) -> newMap.put(deserialize(key), deserialize(val)));
+				return newMap;
+			}
 		}
 		return value;
 	}

+ 3 - 2
magic-api/src/main/java/org/ssssssss/magicapi/adapter/resource/RedisResource.java → magic-api-plugins/magic-api-plugin-redis/src/main/java/org/ssssssss/magicapi/redis/RedisResource.java

@@ -1,4 +1,4 @@
-package org.ssssssss.magicapi.adapter.resource;
+package org.ssssssss.magicapi.redis;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -6,7 +6,8 @@ import org.springframework.data.redis.core.Cursor;
 import org.springframework.data.redis.core.RedisCallback;
 import org.springframework.data.redis.core.ScanOptions;
 import org.springframework.data.redis.core.StringRedisTemplate;
-import org.ssssssss.magicapi.adapter.Resource;
+import org.ssssssss.magicapi.core.resource.KeyValueResource;
+import org.ssssssss.magicapi.core.resource.Resource;
 
 import java.nio.charset.StandardCharsets;
 import java.util.*;

+ 1 - 0
magic-api-plugins/magic-api-plugin-redis/src/main/resources/META-INF/spring.factories

@@ -0,0 +1 @@
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=org.ssssssss.magicapi.redis.MagicRedisConfiguration

+ 31 - 0
magic-api-plugins/magic-api-plugin-swagger/pom.xml

@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xmlns="http://maven.apache.org/POM/4.0.0"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.ssssssss</groupId>
+        <artifactId>magic-api-plugins</artifactId>
+        <version>2.0.0-beta.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>

+ 24 - 26
magic-api-spring-boot-starter/src/main/java/org/ssssssss/magicapi/spring/boot/starter/MagicSwaggerConfiguration.java → magic-api-plugins/magic-api-plugin-swagger/src/main/java/org/ssssssss/magicapi/swagger/MagicSwaggerConfiguration.java

@@ -1,4 +1,4 @@
-package org.ssssssss.magicapi.spring.boot.starter;
+package org.ssssssss.magicapi.swagger;
 
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.autoconfigure.AutoConfigureAfter;
@@ -11,10 +11,13 @@ 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.config.MappingHandlerMapping;
-import org.ssssssss.magicapi.provider.GroupServiceProvider;
-import org.ssssssss.magicapi.swagger.SwaggerEntity;
-import org.ssssssss.magicapi.swagger.SwaggerProvider;
+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;
@@ -24,44 +27,39 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 
-/**
- * Swagger配置类
- *
- * @author mxd
- */
 @Configuration
-@AutoConfigureAfter({MagicAPIAutoConfiguration.class})
-@EnableConfigurationProperties(MagicAPIProperties.class)
+@EnableConfigurationProperties(SwaggerConfig.class)
 @ConditionalOnClass(name = "springfox.documentation.swagger.web.SwaggerResourcesProvider")
-public class MagicSwaggerConfiguration {
+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, ApplicationContext applicationContext) {
+	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(MappingHandlerMapping handlerMapping, GroupServiceProvider groupServiceProvider, ServletContext servletContext) throws NoSuchMethodException {
-		SwaggerConfig config = properties.getSwaggerConfig();
+	public SwaggerResourcesProvider magicSwaggerResourcesProvider(RequestMagicDynamicRegistry requestMagicDynamicRegistry, MagicResourceService magicResourceService, ServletContext servletContext) throws NoSuchMethodException {
 		Mapping mapping = Mapping.create(requestMappingHandlerMapping);
-		RequestMappingInfo requestMappingInfo = mapping.paths(config.getLocation()).build();
-
-		// 构建文档信息
-		SwaggerProvider swaggerProvider = new SwaggerProvider();
-		swaggerProvider.setGroupServiceProvider(groupServiceProvider);
-		swaggerProvider.setMappingHandlerMapping(handlerMapping);
-		swaggerProvider.setPersistenceResponseBody(properties.isPersistenceResponseBody());
+		RequestMappingInfo requestMappingInfo = mapping.paths(swaggerConfig.getLocation()).build();
 		SwaggerEntity.License license = new SwaggerEntity.License("MIT", "https://gitee.com/ssssssss-team/magic-api/blob/master/LICENSE");
-		swaggerProvider.setInfo(new SwaggerEntity.Info(config.getDescription(), config.getVersion(), config.getTitle(), license, config.getConcat()));
-		swaggerProvider.setBasePath(servletContext.getContextPath());
+		SwaggerEntity.Info info = new SwaggerEntity.Info(swaggerConfig.getDescription(), swaggerConfig.getVersion(), swaggerConfig.getTitle(), license, swaggerConfig.getConcat());
+		// 构建文档信息
+		SwaggerProvider swaggerProvider = new SwaggerProvider(requestMagicDynamicRegistry, magicResourceService, servletContext.getContextPath(), info, properties.isPersistenceResponseBody());
 
 
 		// 注册swagger.json
@@ -70,7 +68,7 @@ public class MagicSwaggerConfiguration {
 		return () -> {
 			List<SwaggerResource> resources = new ArrayList<>();
 			// 追加Magic Swagger信息
-			resources.add(swaggerResource(config.getName(), config.getLocation()));
+			resources.add(swaggerResource(swaggerConfig.getName(), swaggerConfig.getLocation()));
 			Map<String, SwaggerResourcesProvider> beans = applicationContext.getBeansOfType(SwaggerResourcesProvider.class);
 			// 获取已定义的文档信息
 			for (Map.Entry<String, SwaggerResourcesProvider> entry : beans.entrySet()) {

+ 5 - 2
magic-api-spring-boot-starter/src/main/java/org/ssssssss/magicapi/spring/boot/starter/SwaggerConfig.java → magic-api-plugins/magic-api-plugin-swagger/src/main/java/org/ssssssss/magicapi/swagger/SwaggerConfig.java

@@ -1,13 +1,16 @@
-package org.ssssssss.magicapi.spring.boot.starter;
+package org.ssssssss.magicapi.swagger;
 
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
 import org.springframework.boot.context.properties.NestedConfigurationProperty;
-import org.ssssssss.magicapi.swagger.SwaggerEntity;
+import org.ssssssss.magicapi.swagger.entity.SwaggerEntity;
 
 /**
  * Swagger 配置
  *
  * @author mxd
  */
+@ConfigurationProperties(prefix = "magic-api.swagger")
 public class SwaggerConfig {
 
 	/**

+ 6 - 10
magic-api/src/main/java/org/ssssssss/magicapi/swagger/SwaggerEntity.java → magic-api-plugins/magic-api-plugin-swagger/src/main/java/org/ssssssss/magicapi/swagger/entity/SwaggerEntity.java

@@ -1,4 +1,4 @@
-package org.ssssssss.magicapi.swagger;
+package org.ssssssss.magicapi.swagger.entity;
 
 import java.util.*;
 
@@ -17,17 +17,17 @@ public class SwaggerEntity {
 
 	private Info info;
 
-	private Set<Tag> tags = new TreeSet<>(Comparator.comparing(Tag::getName));
+	private final Set<Tag> tags = new TreeSet<>(Comparator.comparing(Tag::getName));
 
-	private Map<String, Object> definitions = new HashMap<>();
+	private final Map<String, Object> definitions = new HashMap<>();
 
-	private Map<String, Map<String, Path>> paths = 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;
+			List<?> targetList = (List<?>) target;
 			if (targetList.size() > 0) {
 				result.put("items", doProcessSchema(targetList.get(0)));
 			} else {
@@ -244,7 +244,7 @@ public class SwaggerEntity {
 
 		private String description;
 
-		private String operationId;
+		private final String operationId;
 
 		private List<String> produces = new ArrayList<>();
 
@@ -343,7 +343,6 @@ public class SwaggerEntity {
 		}
 	}
 
-	@Deprecated
 	public static class Parameter {
 
 		private String name;
@@ -367,9 +366,6 @@ public class SwaggerEntity {
 			this.description = description;
 			this.required = required;
 			if ("body".equalsIgnoreCase(in)) {
-				Map<String, Object> schema = new HashMap<>();
-				schema.put("type", type);
-				schema.put("example", example);
 				this.schema = "";
 			} else {
 				this.example = example;

+ 40 - 52
magic-api/src/main/java/org/ssssssss/magicapi/swagger/SwaggerProvider.java → magic-api-plugins/magic-api-plugin-swagger/src/main/java/org/ssssssss/magicapi/swagger/entity/SwaggerProvider.java

@@ -1,21 +1,23 @@
-package org.ssssssss.magicapi.swagger;
+package org.ssssssss.magicapi.swagger.entity;
 
-import com.fasterxml.jackson.databind.ObjectMapper;
 import org.apache.commons.lang3.StringUtils;
 import org.springframework.util.CollectionUtils;
 import org.springframework.web.bind.annotation.ResponseBody;
-import org.ssssssss.magicapi.config.MappingHandlerMapping;
-import org.ssssssss.magicapi.model.ApiInfo;
-import org.ssssssss.magicapi.model.BaseDefinition;
-import org.ssssssss.magicapi.model.DataType;
-import org.ssssssss.magicapi.model.Path;
-import org.ssssssss.magicapi.provider.GroupServiceProvider;
+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.model.Constants.*;
+import static org.ssssssss.magicapi.core.config.Constants.*;
 
 /**
  * 生成swagger用的json
@@ -32,56 +34,46 @@ public class SwaggerProvider {
 	 * body空对象
 	 */
 	private static final String BODY_EMPTY = "{}";
+
 	private final Map<String, Object> DEFINITION_MAP = new ConcurrentHashMap<>();
-	private MappingHandlerMapping mappingHandlerMapping;
+
+	private final RequestMagicDynamicRegistry requestMagicDynamicRegistry;
+
+	private final MagicResourceService magicResourceService;
 	/**
 	 * 基础路径
 	 */
-	private String basePath;
-	private GroupServiceProvider groupServiceProvider;
-	private SwaggerEntity.Info info;
-	private boolean persistenceResponseBody;
-
-	public void setMappingHandlerMapping(MappingHandlerMapping mappingHandlerMapping) {
-		this.mappingHandlerMapping = mappingHandlerMapping;
-	}
-
-	public void setGroupServiceProvider(GroupServiceProvider groupServiceProvider) {
-		this.groupServiceProvider = groupServiceProvider;
-	}
+	private final String basePath;
+	private final SwaggerEntity.Info info;
+	private final boolean persistenceResponseBody;
 
-	public void setInfo(SwaggerEntity.Info info) {
-		this.info = info;
-	}
-
-	public void setBasePath(String basePath) {
+	public SwaggerProvider(RequestMagicDynamicRegistry requestMagicDynamicRegistry, MagicResourceService magicResourceService, String basePath, SwaggerEntity.Info info, boolean persistenceResponseBody) {
+		this.requestMagicDynamicRegistry = requestMagicDynamicRegistry;
+		this.magicResourceService = magicResourceService;
 		this.basePath = basePath;
-	}
-
-	public void setPersistenceResponseBody(boolean persistenceResponseBody) {
+		this.info = info;
 		this.persistenceResponseBody = persistenceResponseBody;
 	}
 
 	@ResponseBody
 	public SwaggerEntity swaggerJson() {
 		this.DEFINITION_MAP.clear();
-		List<ApiInfo> infos = mappingHandlerMapping.getApiInfos();
+		List<ApiInfo> infos = requestMagicDynamicRegistry.mappings();
 		SwaggerEntity swaggerEntity = new SwaggerEntity();
 		swaggerEntity.setInfo(info);
 		swaggerEntity.setBasePath(this.basePath);
-		ObjectMapper mapper = new ObjectMapper();
 		for (ApiInfo info : infos) {
-			String groupName = groupServiceProvider.getFullName(info.getGroupId()).replace("/", "-");
-			String requestPath = "/" + mappingHandlerMapping.getRequestPath(info.getGroupId(), info.getPath());
+			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(mapper, info);
+				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, "root_", "request", 0);
+					doProcessDefinition(baseDefinition, info, groupName, "root_", "request", 0);
 				}
 				parameters.forEach(path::addParameter);
 				if (this.persistenceResponseBody) {
@@ -90,10 +82,10 @@ public class SwaggerProvider {
 						Map responseMap = parseResponse(info);
 						if (!responseMap.isEmpty()) {
 							path.setResponses(responseMap);
-							doProcessDefinition(baseDefinition, info, "root_" + baseDefinition.getName(), "response", 0);
+							doProcessDefinition(baseDefinition, info, groupName, "root_" + baseDefinition.getName(), "response", 0);
 						}
 					} else {
-						path.addResponse("200", mapper.readValue(Objects.toString(info.getResponseBody(), BODY_EMPTY), Object.class));
+						path.addResponse("200", JsonUtils.readValue(Objects.toString(info.getResponseBody(), BODY_EMPTY), Object.class));
 					}
 				}
 
@@ -121,26 +113,23 @@ public class SwaggerProvider {
 		return swaggerEntity;
 	}
 
-	private List<Map<String, Object>> parseParameters(ObjectMapper mapper, ApiInfo info) {
+	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());
-		MappingHandlerMapping.findGroups(info.getGroupId())
+		MagicConfiguration.getMagicResourceService().getGroupsByFileId(info.getId())
 				.stream()
 				.flatMap(it -> it.getPaths().stream())
-				.forEach(it -> {
-					if (!paths.contains(it)) {
-						paths.add(it);
-					}
-				});
+				.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 = groupServiceProvider.getFullName(info.getGroupId()).replace("/", "-");
+				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() + "_" : "_") + "»»»";
@@ -158,7 +147,7 @@ public class SwaggerProvider {
 				parameter.put("schema", schema);
 				parameters.add(parameter);
 			} else {
-				Object object = mapper.readValue(info.getRequestBody(), Object.class);
+				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));
@@ -175,7 +164,7 @@ public class SwaggerProvider {
 
 		BaseDefinition baseDefinition = info.getResponseBodyDefinition();
 		if (!CollectionUtils.isEmpty(baseDefinition.getChildren())) {
-			String groupName = groupServiceProvider.getFullName(info.getGroupId()).replace("/", "-");
+			String groupName = magicResourceService.getGroupName(info.getGroupId()).replace("/", "-");
 			String voName = groupName + "«" + info.getPath().replaceFirst("/", "").replaceAll("/", "_") + "«response«";
 			voName += "root_" + baseDefinition.getName() + "»»»";
 
@@ -192,25 +181,24 @@ public class SwaggerProvider {
 		return result;
 	}
 
-	private Map<String, Object> doProcessDefinition(BaseDefinition target, ApiInfo info, String parentName, String definitionType, int level) {
+	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, parentName + target.getName() + "_", definitionType, level + 1));
+				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 groupName = groupServiceProvider.getFullName(info.getGroupId()).replace("/", "-");
 			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, parentName + target.getName() + "_", definitionType, level + 1));
+				properties.put(obj.getName(), doProcessDefinition(obj, info, groupName, parentName + target.getName() + "_", definitionType, level + 1));
 				if (obj.isRequired()) {
 					requiredSet.add(obj.getName());
 				}

+ 1 - 0
magic-api-plugins/magic-api-plugin-swagger/src/main/resources/META-INF/spring.factories

@@ -0,0 +1 @@
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=org.ssssssss.magicapi.swagger.MagicSwaggerConfiguration

+ 91 - 0
magic-api-plugins/magic-api-plugin-task/pom.xml

@@ -0,0 +1,91 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xmlns="http://maven.apache.org/POM/4.0.0"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.ssssssss</groupId>
+        <artifactId>magic-api-plugins</artifactId>
+        <version>2.0.0-beta.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>

+ 18 - 0
magic-api-plugins/magic-api-plugin-task/src/console/package.json

@@ -0,0 +1,18 @@
+{
+  "name": "magic-test",
+  "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.26",
+    "@vitejs/plugin-vue": "^2.0.1",
+    "vite-plugin-svg-icons": "^1.1.0",
+    "vite": "^2.7.10"
+  }
+}

+ 49 - 0
magic-api-plugins/magic-api-plugin-task/src/console/src/components/magic-task-info.vue

@@ -0,0 +1,49 @@
+<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: 22px;
+	line-height: 22px;
+	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>

+ 16 - 0
magic-api-plugins/magic-api-plugin-task/src/console/src/i18n/en.js

@@ -0,0 +1,16 @@
+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'
+            }
+        }
+    },
+}

+ 16 - 0
magic-api-plugins/magic-api-plugin-task/src/console/src/i18n/zh-cn.js

@@ -0,0 +1,16 @@
+export default {
+    task: {
+        title: '定时任务信息',
+        name: '定时任务',
+        form: {
+            name: '任务名称',
+            path: '任务路径',
+            placeholder: {
+                cron: '请输入Cron表达式',
+                name: '请输入任务名称',
+                path: '请输入任务路径',
+                description: '请输入任务描述'
+            }
+        }
+    }
+}

+ 1 - 0
magic-api-plugins/magic-api-plugin-task/src/console/src/icons/task.svg

@@ -0,0 +1 @@
+<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>

+ 35 - 0
magic-api-plugins/magic-api-plugin-task/src/console/src/index.js

@@ -0,0 +1,35 @@
+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,
+        }]
+    }
+}

+ 42 - 0
magic-api-plugins/magic-api-plugin-task/src/console/src/service/magic-task.js

@@ -0,0 +1,42 @@
+export default function (bus, constants, $i, Message, request) {
+    return {
+        // svg text
+        getIcon: item => ['TASK', '#9012FE'],
+        // 任务名称
+        name: $i('task.name'),
+        language: 'magicscript',
+        // 执行测试的逻辑
+        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
+    }
+}

+ 37 - 0
magic-api-plugins/magic-api-plugin-task/src/console/vite.config.js

@@ -0,0 +1,37 @@
+import vue from '@vitejs/plugin-vue'
+import viteSvgIcons from 'vite-plugin-svg-icons'
+import path from 'path'
+import pkg from './package.json'
+
+export default {
+    base: './',
+    build: {
+        minify: false,
+        cssCodeSplit: true, // 将组件的 style 打包到 js 文件中
+        outDir: 'dist',
+        lib: {
+            target: 'esnext',
+            formats: ['iife'],
+            entry: path.resolve(__dirname, 'src/index.js'),
+            name: 'MagicTask',
+            fileName: (format) => `magic-task.${pkg.version}.${format}.js`
+        },
+        rollupOptions: {
+            // 确保外部化处理那些你不想打包进库的依赖
+            external: ['vue'],
+            output: {
+                // 在 UMD 构建模式下为这些外部化的依赖提供一个全局变量
+                globals: {
+                    vue: 'Vue'
+                }
+            }
+        }
+    },
+    plugins: [
+        vue(),
+        viteSvgIcons({
+			iconDirs: [path.resolve(process.cwd(), 'src/icons')],
+			symbolId: 'magic-task-[name]'
+		}),
+    ]
+}

+ 70 - 0
magic-api-plugins/magic-api-plugin-task/src/main/java/org/ssssssss/magicapi/task/model/TaskInfo.java

@@ -0,0 +1,70 @@
+package org.ssssssss.magicapi.task.model;
+
+import org.ssssssss.magicapi.core.model.MagicEntity;
+import org.ssssssss.magicapi.core.model.PathMagicEntity;
+
+import java.util.Objects;
+
+public class TaskInfo extends PathMagicEntity {
+
+	/**
+	 *  cron 表达式
+	 */
+	private String cron;
+
+	/**
+	 * 是否启用
+	 */
+	private boolean enabled;
+
+
+	public String getCron() {
+		return cron;
+	}
+
+	public void setCron(String cron) {
+		this.cron = cron;
+	}
+
+	public boolean isEnabled() {
+		return enabled;
+	}
+
+	public void setEnabled(boolean enabled) {
+		this.enabled = enabled;
+	}
+
+	public TaskInfo copy() {
+		TaskInfo info = new TaskInfo();
+		super.copyTo(info);
+		info.setCron(this.cron);
+		info.setEnabled(this.enabled);
+		return info;
+	}
+
+	@Override
+	public MagicEntity simple() {
+		TaskInfo info = new TaskInfo();
+		super.simple(info);
+		return info;
+	}
+
+	@Override
+	public boolean equals(Object o) {
+		if (this == o) return true;
+		if (o == null || getClass() != o.getClass()) return false;
+		if (!super.equals(o)) return false;
+		TaskInfo taskInfo = (TaskInfo) o;
+		return Objects.equals(id, taskInfo.id) &&
+				Objects.equals(path, taskInfo.path) &&
+				Objects.equals(script, taskInfo.script) &&
+				Objects.equals(name, taskInfo.name) &&
+				Objects.equals(cron, taskInfo.cron) &&
+				Objects.equals(enabled, taskInfo.enabled);
+	}
+
+	@Override
+	public int hashCode() {
+		return Objects.hash(id, path, script, name, groupId, cron, enabled);
+	}
+}

+ 27 - 0
magic-api-plugins/magic-api-plugin-task/src/main/java/org/ssssssss/magicapi/task/service/TaskInfoMagicResourceStorage.java

@@ -0,0 +1,27 @@
+package org.ssssssss.magicapi.task.service;
+
+import org.ssssssss.magicapi.core.service.AbstractPathMagicResourceStorage;
+import org.ssssssss.magicapi.task.model.TaskInfo;
+
+public class TaskInfoMagicResourceStorage extends AbstractPathMagicResourceStorage<TaskInfo> {
+
+	@Override
+	public String folder() {
+		return "task";
+	}
+
+	@Override
+	public Class<TaskInfo> magicClass() {
+		return TaskInfo.class;
+	}
+
+	@Override
+	public void validate(TaskInfo entity) {
+		notBlank(entity.getCron(), CRON_ID_REQUIRED);
+	}
+
+	@Override
+	public String buildMappingKey(TaskInfo info) {
+		return buildMappingKey(info, magicResourceService.getGroupPath(info.getGroupId()));
+	}
+}

+ 90 - 0
magic-api-plugins/magic-api-plugin-task/src/main/java/org/ssssssss/magicapi/task/service/TaskMagicDynamicRegistry.java

@@ -0,0 +1,90 @@
+package org.ssssssss.magicapi.task.service;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.event.EventListener;
+import org.springframework.scheduling.TaskScheduler;
+import org.springframework.scheduling.config.CronTask;
+import org.ssssssss.magicapi.core.config.MagicConfiguration;
+import org.ssssssss.magicapi.core.event.FileEvent;
+import org.ssssssss.magicapi.core.event.GroupEvent;
+import org.ssssssss.magicapi.core.service.AbstractMagicDynamicRegistry;
+import org.ssssssss.magicapi.core.service.MagicResourceStorage;
+import org.ssssssss.magicapi.task.model.TaskInfo;
+import org.ssssssss.magicapi.utils.ScriptManager;
+import org.ssssssss.script.MagicScriptContext;
+
+import java.util.concurrent.ScheduledFuture;
+
+public class TaskMagicDynamicRegistry extends AbstractMagicDynamicRegistry<TaskInfo> {
+
+	private final TaskScheduler taskScheduler;
+
+	private static final Logger logger = LoggerFactory.getLogger(TaskMagicDynamicRegistry.class);
+
+	public TaskMagicDynamicRegistry(MagicResourceStorage<TaskInfo> magicResourceStorage, TaskScheduler taskScheduler) {
+		super(magicResourceStorage);
+		this.taskScheduler = taskScheduler;
+	}
+
+	@EventListener(condition = "#event.type == 'task'")
+	public void onFileEvent(FileEvent event) {
+		processEvent(event);
+	}
+
+	@EventListener(condition = "#event.type == 'task'")
+	public void onGroupEvent(GroupEvent event) {
+		processEvent(event);
+	}
+
+	@Override
+	public boolean register(TaskInfo entity) {
+		unregister(entity);
+		return super.register(entity);
+	}
+
+	@Override
+	protected boolean register(MappingNode<TaskInfo> mappingNode) {
+		TaskInfo info = mappingNode.getEntity();
+		if (taskScheduler != null) {
+			CronTask cronTask = new CronTask(() -> {
+				TaskInfo entity = mappingNode.getEntity();
+				String scriptName = MagicConfiguration.getMagicResourceService().getScriptName(entity);
+				if (entity.isEnabled()) {
+					try {
+						logger.info("定时任务:[{}]开始执行", scriptName);
+						MagicScriptContext magicScriptContext = new MagicScriptContext();
+						magicScriptContext.setScriptName(scriptName);
+						ScriptManager.executeScript(entity.getScript(), magicScriptContext);
+					} catch (Exception e) {
+						logger.error("定时任务执行出错", e);
+					} finally {
+						logger.info("定时任务:[{}]执行完毕", scriptName);
+					}
+				}
+			}, info.getCron());
+			mappingNode.setMappingData(taskScheduler.schedule(cronTask.getRunnable(), cronTask.getTrigger()));
+			logger.debug("注册定时任务:[{},{}]", MagicConfiguration.getMagicResourceService().getScriptName(info), info.getCron());
+		}
+
+		return true;
+	}
+
+	@Override
+	protected void unregister(MappingNode<TaskInfo> mappingNode) {
+		if (taskScheduler == null) {
+			return;
+		}
+		TaskInfo info = mappingNode.getEntity();
+		logger.debug("取消注册定时任务:[{}, {}, {}]", info.getName(), info.getPath(), info.getCron());
+		ScheduledFuture<?> scheduledFuture = (ScheduledFuture<?>) mappingNode.getMappingData();
+		if (scheduledFuture != null) {
+			try {
+				scheduledFuture.cancel(true);
+			} catch (Exception e) {
+				String scriptName = MagicConfiguration.getMagicResourceService().getScriptName(info);
+				logger.warn("定时任务:[{}]取消失败", scriptName, e);
+			}
+		}
+	}
+}

+ 58 - 0
magic-api-plugins/magic-api-plugin-task/src/main/java/org/ssssssss/magicapi/task/starter/MagicAPITaskConfiguration.java

@@ -0,0 +1,58 @@
+package org.ssssssss.magicapi.task.starter;
+
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
+import org.ssssssss.magicapi.core.config.MagicPluginConfiguration;
+import org.ssssssss.magicapi.core.model.Plugin;
+import org.ssssssss.magicapi.core.web.MagicControllerRegister;
+import org.ssssssss.magicapi.task.service.TaskInfoMagicResourceStorage;
+import org.ssssssss.magicapi.task.service.TaskMagicDynamicRegistry;
+import org.ssssssss.magicapi.task.web.MagicTaskController;
+
+@Configuration
+@EnableConfigurationProperties(MagicTaskConfig.class)
+public class MagicAPITaskConfiguration implements MagicPluginConfiguration {
+
+	private final MagicTaskConfig config;
+
+	public MagicAPITaskConfiguration(MagicTaskConfig config) {
+		this.config = config;
+	}
+
+	@Bean
+	@ConditionalOnMissingBean
+	public TaskInfoMagicResourceStorage taskInfoMagicResourceStorage() {
+		return new TaskInfoMagicResourceStorage();
+	}
+
+	@Bean
+	@ConditionalOnMissingBean
+	public TaskMagicDynamicRegistry taskMagicDynamicRegistry(TaskInfoMagicResourceStorage taskInfoMagicResourceStorage) {
+		MagicTaskConfig.Shutdown shutdown = config.getShutdown();
+		ThreadPoolTaskScheduler poolTaskScheduler = null;
+		if(config.isEnable()){
+			poolTaskScheduler = new ThreadPoolTaskScheduler();
+			poolTaskScheduler.setPoolSize(config.getPool().getSize());
+			poolTaskScheduler.setWaitForTasksToCompleteOnShutdown(shutdown.isAwaitTermination());
+			if(shutdown.getAwaitTerminationPeriod() != null){
+				poolTaskScheduler.setAwaitTerminationSeconds((int) shutdown.getAwaitTerminationPeriod().getSeconds());
+			}
+			poolTaskScheduler.setThreadNamePrefix(config.getThreadNamePrefix());
+			poolTaskScheduler.initialize();
+		}
+		return new TaskMagicDynamicRegistry(taskInfoMagicResourceStorage, poolTaskScheduler);
+	}
+
+	@Override
+	public Plugin plugin() {
+		return new Plugin("定时任务", "MagicTask", "magic-task.1.0.0.iife.js");
+	}
+
+	@Override
+	public MagicControllerRegister controllerRegister() {
+		return (mapping, configuration) -> mapping.registerController(new MagicTaskController(configuration));
+	}
+}

+ 101 - 0
magic-api-plugins/magic-api-plugin-task/src/main/java/org/ssssssss/magicapi/task/starter/MagicTaskConfig.java

@@ -0,0 +1,101 @@
+package org.ssssssss.magicapi.task.starter;
+
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+import java.time.Duration;
+
+@ConfigurationProperties("magic-api.task")
+public class MagicTaskConfig {
+
+	/**
+	 * 是否启用定时任务
+	 */
+	private boolean enable = true;
+
+	/**
+	 * 线程池相关配置
+	 */
+	private final Pool pool = new Pool();
+
+	/**
+	 * 关闭时相关配置
+	 */
+	private final Shutdown shutdown = new Shutdown();
+
+	/**
+	 * 线程池前缀
+	 */
+	private String threadNamePrefix = "magic-task-";
+
+	public Pool getPool() {
+		return this.pool;
+	}
+
+	public Shutdown getShutdown() {
+		return this.shutdown;
+	}
+
+	public String getThreadNamePrefix() {
+		return this.threadNamePrefix;
+	}
+
+	public void setThreadNamePrefix(String threadNamePrefix) {
+		this.threadNamePrefix = threadNamePrefix;
+	}
+
+	public boolean isEnable() {
+		return enable;
+	}
+
+	public void setEnable(boolean enable) {
+		this.enable = enable;
+	}
+
+	public static class Pool {
+
+		/**
+		 * 线程池大小
+		 */
+		private int size = Runtime.getRuntime().availableProcessors();
+
+		public int getSize() {
+			return this.size;
+		}
+
+		public void setSize(int size) {
+			this.size = size;
+		}
+
+	}
+
+	public static class Shutdown {
+
+		/**
+		 * 关闭时是否等待任务执行完毕,默认为false
+		 */
+		private boolean awaitTermination;
+
+		/**
+		 * 关闭时最多等待任务执行完毕的时间
+		 */
+		private Duration awaitTerminationPeriod;
+
+		public boolean isAwaitTermination() {
+			return this.awaitTermination;
+		}
+
+		public void setAwaitTermination(boolean awaitTermination) {
+			this.awaitTermination = awaitTermination;
+		}
+
+		public Duration getAwaitTerminationPeriod() {
+			return this.awaitTerminationPeriod;
+		}
+
+		public void setAwaitTerminationPeriod(Duration awaitTerminationPeriod) {
+			this.awaitTerminationPeriod = awaitTerminationPeriod;
+		}
+
+	}
+}

+ 43 - 0
magic-api-plugins/magic-api-plugin-task/src/main/java/org/ssssssss/magicapi/task/web/MagicTaskController.java

@@ -0,0 +1,43 @@
+package org.ssssssss.magicapi.task.web;
+
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.ssssssss.magicapi.core.config.MagicConfiguration;
+import org.ssssssss.magicapi.core.config.WebSocketSessionManager;
+import org.ssssssss.magicapi.core.logging.MagicLoggerContext;
+import org.ssssssss.magicapi.core.model.DebugRequest;
+import org.ssssssss.magicapi.core.model.JsonBean;
+import org.ssssssss.magicapi.core.model.MagicEntity;
+import org.ssssssss.magicapi.core.web.MagicController;
+import org.ssssssss.magicapi.core.web.MagicExceptionHandler;
+import org.ssssssss.magicapi.utils.ScriptManager;
+import org.ssssssss.script.MagicScriptDebugContext;
+
+import javax.servlet.http.HttpServletRequest;
+
+public class MagicTaskController extends MagicController implements MagicExceptionHandler {
+
+	public MagicTaskController(MagicConfiguration configuration) {
+		super(configuration);
+	}
+
+	@PostMapping("/task/execute")
+	@ResponseBody
+	public JsonBean<Object> execute(String id, HttpServletRequest request){
+		MagicEntity entity = MagicConfiguration.getMagicResourceService().file(id);
+		notNull(entity, FILE_NOT_FOUND);
+		String script = entity.getScript();
+		DebugRequest debugRequest = DebugRequest.create(request);
+		MagicLoggerContext.SESSION.set(debugRequest.getRequestedClientId());
+		String sessionAndScriptId = debugRequest.getRequestedClientId() + debugRequest.getRequestedScriptId();
+		try {
+			MagicScriptDebugContext magicScriptContext = debugRequest.createMagicScriptContext(configuration.getDebugTimeout());
+			WebSocketSessionManager.addMagicScriptContext(sessionAndScriptId, magicScriptContext);
+			magicScriptContext.setScriptName(MagicConfiguration.getMagicResourceService().getScriptName(entity));
+			return new JsonBean<>(ScriptManager.executeScript(script, magicScriptContext));
+		} finally {
+			WebSocketSessionManager.removeMagicScriptContext(sessionAndScriptId);
+			MagicLoggerContext.SESSION.remove();
+		}
+	}
+}

+ 1 - 0
magic-api-plugins/magic-api-plugin-task/src/main/resources/META-INF/spring.factories

@@ -0,0 +1 @@
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=org.ssssssss.magicapi.task.starter.MagicAPITaskConfiguration

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

@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.ssssssss</groupId>
+        <artifactId>magic-api-parent</artifactId>
+        <version>2.0.0-beta.1</version>
+    </parent>
+    <artifactId>magic-api-plugins</artifactId>
+    <version>2.0.0-beta.1</version>
+    <packaging>pom</packaging>
+    <name>magic-api-plugins</name>
+    <description>auto generate http api</description>
+    <modules>
+        <module>magic-api-plugin-task</module>
+        <module>magic-api-plugin-swagger</module>
+        <module>magic-api-plugin-redis</module>
+        <module>magic-api-plugin-mongo</module>
+        <module>magic-api-plugin-elasticsearch</module>
+        <module>magic-api-plugin-cluster</module>
+    </modules>
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.ssssssss</groupId>
+            <artifactId>magic-api</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.ssssssss</groupId>
+            <artifactId>magic-script</artifactId>
+            <scope>provided</scope>
+        </dependency>
+    </dependencies>
+</project>

+ 1 - 16
magic-api-spring-boot-starter/pom.xml

@@ -6,7 +6,7 @@
     <parent>
         <groupId>org.ssssssss</groupId>
         <artifactId>magic-api-parent</artifactId>
-        <version>1.7.5</version>
+        <version>2.0.0-beta.1</version>
     </parent>
     <artifactId>magic-api-spring-boot-starter</artifactId>
     <packaging>jar</packaging>
@@ -18,16 +18,6 @@
             <artifactId>spring-boot-starter</artifactId>
             <scope>provided</scope>
         </dependency>
-        <dependency>
-            <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-starter-data-redis</artifactId>
-            <scope>provided</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-starter-data-mongodb</artifactId>
-            <scope>provided</scope>
-        </dependency>
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-web</artifactId>
@@ -55,11 +45,6 @@
             <artifactId>fastjson</artifactId>
             <scope>provided</scope>
         </dependency>
-        <dependency>
-            <groupId>io.springfox</groupId>
-            <artifactId>springfox-swagger2</artifactId>
-            <scope>provided</scope>
-        </dependency>
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-jdbc</artifactId>

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

@@ -7,6 +7,7 @@ import org.springframework.boot.CommandLineRunner;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.core.annotation.Order;
 import org.springframework.stereotype.Component;
+import org.ssssssss.magicapi.core.config.MagicAPIProperties;
 import org.ssssssss.magicapi.utils.PathUtils;
 
 import java.net.InetAddress;
@@ -25,7 +26,7 @@ import java.util.Objects;
 @Order
 public class ApplicationUriPrinter implements CommandLineRunner {
 
-	@Value("${server.port:9999}")
+	@Value("${server.port:8080}")
 	private int port;
 
 	@Value("${server.servlet.context-path:}")

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

@@ -1,51 +0,0 @@
-package org.ssssssss.magicapi.spring.boot.starter;
-
-import java.util.UUID;
-
-/**
- * 集群配置
- *
- * @author mxd
- * @since 1.2.0
- */
-public class ClusterConfig {
-
-	/**
-	 * 是否启用,默认不启用
-	 */
-	private boolean enable = false;
-
-	/**
-	 * 实例ID,集群环境下,要保证每台机器不同。默认启动后随机生成uuid
-	 */
-	private String instanceId = UUID.randomUUID().toString();
-
-	/**
-	 * redis 通道
-	 */
-	private String channel = "magic-api:notify:channel";
-
-	public String getInstanceId() {
-		return instanceId;
-	}
-
-	public void setInstanceId(String instanceId) {
-		this.instanceId = instanceId;
-	}
-
-	public boolean isEnable() {
-		return enable;
-	}
-
-	public void setEnable(boolean enable) {
-		this.enable = enable;
-	}
-
-	public String getChannel() {
-		return channel;
-	}
-
-	public void setChannel(String channel) {
-		this.channel = channel;
-	}
-}

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

@@ -5,7 +5,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.ObjectProvider;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
+import org.springframework.boot.autoconfigure.AutoConfigureAfter;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
@@ -17,46 +17,52 @@ import org.springframework.context.annotation.Configuration;
 import org.springframework.context.annotation.Import;
 import org.springframework.context.annotation.Lazy;
 import org.springframework.core.Ordered;
-import org.springframework.core.env.Environment;
-import org.springframework.http.MediaType;
 import org.springframework.http.converter.HttpMessageConverter;
-import org.springframework.http.converter.StringHttpMessageConverter;
 import org.springframework.jdbc.core.JdbcTemplate;
-import org.springframework.web.bind.annotation.ResponseBody;
-import org.springframework.web.client.RestTemplate;
+import org.springframework.web.bind.annotation.RequestMethod;
 import org.springframework.web.multipart.MultipartFile;
 import org.springframework.web.multipart.MultipartResolver;
 import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
 import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
 import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
-import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
 import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
 import org.springframework.web.socket.config.annotation.EnableWebSocket;
 import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
 import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistration;
 import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
-import org.ssssssss.magicapi.adapter.ColumnMapperAdapter;
-import org.ssssssss.magicapi.adapter.DialectAdapter;
-import org.ssssssss.magicapi.adapter.Resource;
-import org.ssssssss.magicapi.adapter.ResourceAdapter;
-import org.ssssssss.magicapi.adapter.resource.DatabaseResource;
-import org.ssssssss.magicapi.cache.DefaultSqlCache;
-import org.ssssssss.magicapi.cache.SqlCache;
-import org.ssssssss.magicapi.config.*;
-import org.ssssssss.magicapi.controller.*;
-import org.ssssssss.magicapi.dialect.Dialect;
-import org.ssssssss.magicapi.exception.MagicAPIException;
-import org.ssssssss.magicapi.interceptor.*;
-import org.ssssssss.magicapi.logging.LoggerManager;
-import org.ssssssss.magicapi.model.Constants;
-import org.ssssssss.magicapi.model.DataType;
-import org.ssssssss.magicapi.model.Options;
-import org.ssssssss.magicapi.modules.*;
-import org.ssssssss.magicapi.provider.*;
-import org.ssssssss.magicapi.provider.impl.*;
-import org.ssssssss.magicapi.utils.ClassScanner;
+import org.ssssssss.magicapi.backup.service.MagicBackupService;
+import org.ssssssss.magicapi.backup.service.MagicDatabaseBackupService;
+import org.ssssssss.magicapi.backup.web.MagicBackupController;
+import org.ssssssss.magicapi.core.annotation.MagicModule;
+import org.ssssssss.magicapi.core.config.*;
+import org.ssssssss.magicapi.core.exception.MagicAPIException;
+import org.ssssssss.magicapi.core.handler.MagicCoordinationHandler;
+import org.ssssssss.magicapi.core.handler.MagicDebugHandler;
+import org.ssssssss.magicapi.core.handler.MagicWebSocketDispatcher;
+import org.ssssssss.magicapi.core.handler.MagicWorkbenchHandler;
+import org.ssssssss.magicapi.core.interceptor.*;
+import org.ssssssss.magicapi.core.logging.LoggerManager;
+import org.ssssssss.magicapi.core.model.DataType;
+import org.ssssssss.magicapi.core.model.MagicEntity;
+import org.ssssssss.magicapi.core.model.Plugin;
+import org.ssssssss.magicapi.core.resource.DatabaseResource;
+import org.ssssssss.magicapi.core.resource.ResourceAdapter;
+import org.ssssssss.magicapi.core.service.*;
+import org.ssssssss.magicapi.core.service.impl.DefaultMagicAPIService;
+import org.ssssssss.magicapi.core.service.impl.DefaultMagicResourceService;
+import org.ssssssss.magicapi.core.service.impl.RequestMagicDynamicRegistry;
+import org.ssssssss.magicapi.core.web.MagicResourceController;
+import org.ssssssss.magicapi.core.web.MagicWorkbenchController;
+import org.ssssssss.magicapi.core.web.RequestHandler;
+import org.ssssssss.magicapi.datasource.model.MagicDynamicDataSource;
+import org.ssssssss.magicapi.datasource.service.DataSourceEncryptProvider;
+import org.ssssssss.magicapi.datasource.web.MagicDataSourceController;
+import org.ssssssss.magicapi.function.service.FunctionMagicDynamicRegistry;
+import org.ssssssss.magicapi.jsr223.LanguageProvider;
+import org.ssssssss.magicapi.modules.servlet.RequestModule;
+import org.ssssssss.magicapi.modules.servlet.ResponseModule;
+import org.ssssssss.magicapi.modules.spring.EnvModule;
 import org.ssssssss.magicapi.utils.Mapping;
-import org.ssssssss.magicapi.utils.PathUtils;
 import org.ssssssss.script.MagicResourceLoader;
 import org.ssssssss.script.MagicScript;
 import org.ssssssss.script.MagicScriptEngine;
@@ -66,16 +72,12 @@ import org.ssssssss.script.functions.ExtensionMethod;
 import org.ssssssss.script.parsing.ast.statement.AsyncCall;
 import org.ssssssss.script.reflection.JavaReflection;
 
-import javax.servlet.http.HttpServletRequest;
-import javax.sql.DataSource;
-import java.io.File;
 import java.io.IOException;
-import java.lang.reflect.Method;
-import java.nio.charset.StandardCharsets;
 import java.util.*;
 import java.util.concurrent.ScheduledThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
 import java.util.function.BiFunction;
+import java.util.stream.Collectors;
 
 /**
  * magic-api自动配置类
@@ -85,8 +87,9 @@ import java.util.function.BiFunction;
 @Configuration
 @ConditionalOnClass({RequestMappingHandlerMapping.class})
 @EnableConfigurationProperties(MagicAPIProperties.class)
-@Import({MagicRedisAutoConfiguration.class, MagicMongoAutoConfiguration.class, MagicSwaggerConfiguration.class, MagicJsonAutoConfiguration.class, ApplicationUriPrinter.class})
+@Import({MagicJsonAutoConfiguration.class, ApplicationUriPrinter.class, MagicModuleConfiguration.class, MagicDynamicRegistryConfiguration.class})
 @EnableWebSocket
+@AutoConfigureAfter(MagicPluginConfiguration.class)
 public class MagicAPIAutoConfiguration implements WebMvcConfigurer, WebSocketConfigurer {
 
 	private static final Logger logger = LoggerFactory.getLogger(MagicAPIAutoConfiguration.class);
@@ -96,15 +99,6 @@ public class MagicAPIAutoConfiguration implements WebMvcConfigurer, WebSocketCon
 	 */
 	private final ObjectProvider<List<RequestInterceptor>> requestInterceptorsProvider;
 
-	/**
-	 * SQL拦截器
-	 */
-	private final ObjectProvider<List<SQLInterceptor>> sqlInterceptorsProvider;
-
-	/**
-	 * 单表API拦截器
-	 */
-	private final ObjectProvider<List<NamedTableInterceptor>> namedTableInterceptorsProvider;
 
 	/**
 	 * 自定义的类型扩展
@@ -116,16 +110,6 @@ public class MagicAPIAutoConfiguration implements WebMvcConfigurer, WebSocketCon
 	 */
 	private final ObjectProvider<List<HttpMessageConverter<?>>> httpMessageConvertersProvider;
 
-	/**
-	 * 自定义的方言
-	 */
-	private final ObjectProvider<List<Dialect>> dialectsProvider;
-
-	/**
-	 * 自定义的列名转换
-	 */
-	private final ObjectProvider<List<ColumnMapperProvider>> columnMapperProvidersProvider;
-
 
 	private final ObjectProvider<AuthorizationInterceptor> authorizationInterceptorProvider;
 
@@ -134,11 +118,15 @@ public class MagicAPIAutoConfiguration implements WebMvcConfigurer, WebSocketCon
 	 */
 	private final ObjectProvider<List<MagicFunction>> magicFunctionsProvider;
 
+	private final ObjectProvider<List<MagicPluginConfiguration>> magicPluginsProvider;
+
 	private final ObjectProvider<MagicNotifyService> magicNotifyServiceProvider;
 
-	private final ObjectProvider<DataSourceEncryptProvider> dataSourceEncryptProvider;
+	private final ObjectProvider<List<MagicDynamicRegistry<? extends MagicEntity>>> magicDynamicRegistriesProvider;
+
+	private final ObjectProvider<List<MagicResourceStorage<? extends MagicEntity>>> magicResourceStoragesProvider;
 
-	private final Environment environment;
+	private final ObjectProvider<DataSourceEncryptProvider> dataSourceEncryptProvider;
 
 	private final MagicCorsFilter magicCorsFilter = new MagicCorsFilter();
 
@@ -156,94 +144,40 @@ public class MagicAPIAutoConfiguration implements WebMvcConfigurer, WebSocketCon
 	@Lazy
 	private RequestMappingHandlerMapping requestMappingHandlerMapping;
 
-	@Autowired(required = false)
-	private MultipartResolver multipartResolver;
-
-	private String allClassTxt;
 	private DefaultAuthorizationInterceptor defaultAuthorizationInterceptor;
 
 	public MagicAPIAutoConfiguration(MagicAPIProperties properties,
-									 ObjectProvider<List<Dialect>> dialectsProvider,
 									 ObjectProvider<List<RequestInterceptor>> requestInterceptorsProvider,
-									 ObjectProvider<List<SQLInterceptor>> sqlInterceptorsProvider,
 									 ObjectProvider<List<ExtensionMethod>> extensionMethodsProvider,
 									 ObjectProvider<List<HttpMessageConverter<?>>> httpMessageConvertersProvider,
-									 ObjectProvider<List<ColumnMapperProvider>> columnMapperProvidersProvider,
 									 ObjectProvider<List<MagicFunction>> magicFunctionsProvider,
+									 ObjectProvider<List<MagicPluginConfiguration>> magicPluginsProvider,
 									 ObjectProvider<MagicNotifyService> magicNotifyServiceProvider,
 									 ObjectProvider<AuthorizationInterceptor> authorizationInterceptorProvider,
-									 ObjectProvider<List<NamedTableInterceptor>> namedTableInterceptorsProvider,
 									 ObjectProvider<DataSourceEncryptProvider> dataSourceEncryptProvider,
-									 Environment environment,
+									 ObjectProvider<List<MagicDynamicRegistry<? extends MagicEntity>>> magicDynamicRegistriesProvider,
+									 ObjectProvider<List<MagicResourceStorage<? extends MagicEntity>>> magicResourceStoragesProvider,
 									 ApplicationContext applicationContext
 	) {
 		this.properties = properties;
-		this.dialectsProvider = dialectsProvider;
 		this.requestInterceptorsProvider = requestInterceptorsProvider;
-		this.sqlInterceptorsProvider = sqlInterceptorsProvider;
 		this.extensionMethodsProvider = extensionMethodsProvider;
 		this.httpMessageConvertersProvider = httpMessageConvertersProvider;
-		this.columnMapperProvidersProvider = columnMapperProvidersProvider;
 		this.magicFunctionsProvider = magicFunctionsProvider;
+		this.magicPluginsProvider = magicPluginsProvider;
 		this.magicNotifyServiceProvider = magicNotifyServiceProvider;
 		this.authorizationInterceptorProvider = authorizationInterceptorProvider;
-		this.namedTableInterceptorsProvider = namedTableInterceptorsProvider;
 		this.dataSourceEncryptProvider = dataSourceEncryptProvider;
-		this.environment = environment;
+		this.magicDynamicRegistriesProvider = magicDynamicRegistriesProvider;
+		this.magicResourceStoragesProvider = magicResourceStoragesProvider;
 		this.applicationContext = applicationContext;
 	}
 
-	private String redirectIndex(HttpServletRequest request) {
-		if (request.getRequestURI().endsWith("/")) {
-			return "redirect:./index.html";
-		}
-		return "redirect:" + properties.getWeb() + "/index.html";
-	}
-
-	@ResponseBody
-	private MagicAPIProperties readConfig() {
-		return properties;
-	}
-
-	@ResponseBody
-	private String readClass() {
-		if (allClassTxt == null) {
-			try {
-				allClassTxt = ClassScanner.compress(ClassScanner.scan());
-			} catch (Throwable t) {
-				logger.warn("扫描Class失败", t);
-				allClassTxt = "";
-			}
-		}
-		return allClassTxt;
-	}
-
-	@Bean
-	@ConditionalOnMissingBean(HttpModule.class)
-	public HttpModule magicHttpModule() {
-		return new HttpModule(createRestTemplate());
-	}
-
-	/**
-	 * 注入动态数据源
-	 */
-	@Bean
-	@ConditionalOnMissingBean(MagicDynamicDataSource.class)
-	public MagicDynamicDataSource magicDynamicDataSource(@Autowired(required = false) DataSource dataSource) {
-		MagicDynamicDataSource dynamicDataSource = new MagicDynamicDataSource();
-		if (dataSource != null) {
-			dynamicDataSource.put(dataSource);
-		} else {
-			logger.warn("当前数据源未配置");
-		}
-		return dynamicDataSource;
-	}
-
 	@Bean
-	@ConditionalOnMissingBean(Resource.class)
+	@ConditionalOnMissingBean(org.ssssssss.magicapi.core.resource.Resource.class)
 	@ConditionalOnProperty(prefix = "magic-api", name = "resource.type", havingValue = "database")
-	public Resource magicDatabaseResource(MagicDynamicDataSource magicDynamicDataSource) {
-		ResourceConfig resourceConfig = properties.getResource();
+	public org.ssssssss.magicapi.core.resource.Resource magicDatabaseResource(MagicDynamicDataSource magicDynamicDataSource) {
+		Resource resourceConfig = properties.getResource();
 		if (magicDynamicDataSource.isEmpty()) {
 			throw new MagicAPIException("当前未配置数据源,如已配置,请引入 spring-boot-starter-jdbc 后在试!");
 		}
@@ -252,18 +186,18 @@ public class MagicAPIAutoConfiguration implements WebMvcConfigurer, WebSocketCon
 	}
 
 	@Bean
-	@ConditionalOnMissingBean(Resource.class)
+	@ConditionalOnMissingBean(org.ssssssss.magicapi.core.resource.Resource.class)
 	@ConditionalOnProperty(prefix = "magic-api", name = "resource.type", havingValue = "file", matchIfMissing = true)
-	public Resource magicResource() throws IOException {
-		ResourceConfig resourceConfig = properties.getResource();
+	public org.ssssssss.magicapi.core.resource.Resource magicResource() throws IOException {
+		Resource resourceConfig = properties.getResource();
 		return ResourceAdapter.getResource(resourceConfig.getLocation(), resourceConfig.isReadonly());
 	}
 
 	@Bean
 	@ConditionalOnMissingBean(MagicBackupService.class)
-	@ConditionalOnProperty(prefix = "magic-api", name = "backup-config.resource-type", havingValue = "database")
+	@ConditionalOnProperty(prefix = "magic-api", name = "backup.enable", havingValue = "true")
 	public MagicBackupService magicDatabaseBackupService(MagicDynamicDataSource magicDynamicDataSource) {
-		BackupConfig backupConfig = properties.getBackupConfig();
+		Backup backupConfig = properties.getBackup();
 		MagicDynamicDataSource.DataSourceNode dataSourceNode = magicDynamicDataSource.getDataSource(backupConfig.getDatasource());
 		return new MagicDatabaseBackupService(new JdbcTemplate(dataSourceNode.getDataSource()), backupConfig.getTableName());
 	}
@@ -277,16 +211,6 @@ public class MagicAPIAutoConfiguration implements WebMvcConfigurer, WebSocketCon
 			LoggerManager.createMagicAppender();
 			// 配置静态资源路径
 			registry.addResourceHandler(web + "/**").addResourceLocations("classpath:/magic-editor/");
-			try {
-				Mapping mapping = Mapping.create(requestMappingHandlerMapping);
-						// 默认首页设置
-				mapping.register(mapping.paths(web).build(), this, MagicAPIAutoConfiguration.class.getDeclaredMethod("redirectIndex", HttpServletRequest.class))
-						// 读取配置
-						.register(mapping.paths(web + "/config.json").build(), this, MagicAPIAutoConfiguration.class.getDeclaredMethod("readConfig"))
-						// 读取配置
-						.register(mapping.paths(web + "/classes.txt").produces("text/plain").build(), this, MagicAPIAutoConfiguration.class.getDeclaredMethod("readClass"));
-			} catch (NoSuchMethodException ignored) {
-			}
 		}
 	}
 
@@ -310,148 +234,33 @@ public class MagicAPIAutoConfiguration implements WebMvcConfigurer, WebSocketCon
 	}
 
 	@Bean
-	@ConditionalOnMissingBean(PageProvider.class)
-	public PageProvider pageProvider() {
-		PageConfig pageConfig = properties.getPageConfig();
-		logger.info("未找到分页实现,采用默认分页实现,分页配置:(页码={},页大小={},默认首页={},默认页大小={})", pageConfig.getPage(), pageConfig.getSize(), pageConfig.getDefaultPage(), pageConfig.getDefaultSize());
-		return new DefaultPageProvider(pageConfig.getPage(), pageConfig.getSize(), pageConfig.getDefaultPage(), pageConfig.getDefaultSize());
-	}
-
-	/**
-	 * 注入结果构建方法
-	 */
-	@Bean
-	@ConditionalOnMissingBean(ResultProvider.class)
-	public ResultProvider resultProvider() {
-		return new DefaultResultProvider(properties.getResponse());
-	}
-
-	/**
-	 * 注入SQL缓存实现
-	 */
-	@Bean
-	@ConditionalOnMissingBean(SqlCache.class)
-	public SqlCache sqlCache() {
-		CacheConfig cacheConfig = properties.getCacheConfig();
-		logger.info("未找到SQL缓存实现,采用默认缓存实现(LRU+TTL),缓存配置:(容量={},TTL={})", cacheConfig.getCapacity(), cacheConfig.getTtl());
-		return new DefaultSqlCache(cacheConfig.getCapacity(), cacheConfig.getTtl());
-	}
-
-	/**
-	 * 注入接口映射
-	 */
-	@Bean
-	public MappingHandlerMapping mappingHandlerMapping() throws NoSuchMethodException {
-		String prefix = StringUtils.isNotBlank(properties.getPrefix()) ? PathUtils.replaceSlash("/" + properties.getPrefix() + "/") : null;
-		return new MappingHandlerMapping(prefix, properties.isAllowOverride());
-	}
-
-	@Bean
-	@ConditionalOnMissingBean(FunctionServiceProvider.class)
-	public FunctionServiceProvider functionServiceProvider(GroupServiceProvider groupServiceProvider, Resource magicResource) {
-		return new DefaultFunctionServiceProvider(groupServiceProvider, magicResource);
-	}
-
-	/**
-	 * 注入分组存储service
-	 */
-	@Bean
-	@ConditionalOnMissingBean(GroupServiceProvider.class)
-	public GroupServiceProvider groupServiceProvider(Resource magicResource) {
-		return new DefaultGroupServiceProvider(magicResource);
+	@ConditionalOnMissingBean
+	public MagicResourceService magicResourceService(org.ssssssss.magicapi.core.resource.Resource workspace) {
+		return new DefaultMagicResourceService(workspace, magicResourceStoragesProvider.getObject(), applicationContext);
 	}
 
-	/**
-	 * 注入接口存储service
-	 */
-	@Bean
-	@ConditionalOnMissingBean(ApiServiceProvider.class)
-	public ApiServiceProvider apiServiceProvider(GroupServiceProvider groupServiceProvider, Resource magicResource) {
-		return new DefaultApiServiceProvider(groupServiceProvider, magicResource);
-	}
 
 	@Bean
 	@ConditionalOnMissingBean(MagicNotifyService.class)
 	public MagicNotifyService magicNotifyService() {
-		logger.info("未配置集群通知服务,本实例不会推送通知,集群环境下可能会有问题,如需开启,请配置magic-api.cluster-config.enable=true,若开启后本提示还在,请检查 spring-boot-starter-data-redis 是否引入");
+		logger.info("未配置集群通知服务,本实例不会推送通知,集群环境下可能会有问题,如需开启,请引用magic-api-plugin-cluster插件");
 		return magicNotify -> {
 		};
 	}
 
-	@Bean
-	@ConditionalOnMissingBean(MagicBackupService.class)
-	@ConditionalOnProperty(prefix = "magic-api", name = "backup-config.resource-type", havingValue = "file", matchIfMissing = true)
-	public MagicBackupService magicFileBackupService() {
-		return new MagicFileBackupService(new File(properties.getBackupConfig().getLocation()));
-	}
-
-	@Bean
-	public MagicFunctionManager magicFunctionManager(GroupServiceProvider groupServiceProvider, FunctionServiceProvider functionServiceProvider) {
-		return new MagicFunctionManager(groupServiceProvider, functionServiceProvider);
-	}
-
 	/**
 	 * 注入API调用Service
 	 */
 	@Bean
 	@ConditionalOnMissingBean
-	public MagicAPIService magicAPIService(MappingHandlerMapping mappingHandlerMapping,
-										   ApiServiceProvider apiServiceProvider,
-										   FunctionServiceProvider functionServiceProvider,
-										   GroupServiceProvider groupServiceProvider,
-										   ResultProvider resultProvider,
-										   MagicDynamicDataSource magicDynamicDataSource,
-										   MagicFunctionManager magicFunctionManager,
-										   Resource workspace,
-										   MagicBackupService magicBackupService) {
-		return new DefaultMagicAPIService(mappingHandlerMapping, apiServiceProvider, functionServiceProvider, groupServiceProvider, resultProvider, magicDynamicDataSource, magicFunctionManager, magicNotifyServiceProvider.getObject(), properties.getClusterConfig().getInstanceId(), workspace, magicBackupService, dataSourceEncryptProvider.getIfAvailable() , properties.isThrowException());
-	}
-
-	/**
-	 * 注入数据库查询模块
-	 */
-	@Bean
-	@ConditionalOnBean({MagicDynamicDataSource.class})
-	public SQLModule magicSqlModule(MagicDynamicDataSource dynamicDataSource,
-									ResultProvider resultProvider,
-									PageProvider pageProvider,
-									SqlCache sqlCache) {
-		SQLModule sqlModule = new SQLModule(dynamicDataSource);
-		if (!dynamicDataSource.isEmpty()) {
-			sqlModule.setDataSourceNode(dynamicDataSource.getDataSource());
-		}
-		sqlModule.setResultProvider(resultProvider);
-		sqlModule.setPageProvider(pageProvider);
-		List<SQLInterceptor> sqlInterceptors = sqlInterceptorsProvider.getIfAvailable(ArrayList::new);
-		if (properties.isShowSql()) {
-			sqlInterceptors.add(new DefaultSqlInterceptor());
-		}
-		sqlModule.setSqlInterceptors(sqlInterceptors);
-		sqlModule.setNamedTableInterceptors(namedTableInterceptorsProvider.getIfAvailable(Collections::emptyList));
-		ColumnMapperAdapter columnMapperAdapter = new ColumnMapperAdapter();
-		this.columnMapperProvidersProvider.getIfAvailable(Collections::emptyList).stream().filter(mapperProvider -> !"default".equals(mapperProvider.name())).forEach(columnMapperAdapter::add);
-		columnMapperAdapter.setDefault(properties.getSqlColumnCase());
-		sqlModule.setColumnMapperProvider(columnMapperAdapter);
-		sqlModule.setColumnMapRowMapper(columnMapperAdapter.getDefaultColumnMapRowMapper());
-		sqlModule.setRowMapColumnMapper(columnMapperAdapter.getDefaultRowMapColumnMapper());
-		sqlModule.setSqlCache(sqlCache);
-		DialectAdapter dialectAdapter = new DialectAdapter();
-		dialectsProvider.getIfAvailable(Collections::emptyList).forEach(dialectAdapter::add);
-		sqlModule.setDialectAdapter(dialectAdapter);
-		sqlModule.setLogicDeleteColumn(properties.getCrudConfig().getLogicDeleteColumn());
-		sqlModule.setLogicDeleteValue(properties.getCrudConfig().getLogicDeleteValue());
-		return sqlModule;
+	public MagicAPIService magicAPIService(ResultProvider resultProvider, MagicResourceService magicResourceService, RequestMagicDynamicRegistry requestMagicDynamicRegistry, FunctionMagicDynamicRegistry functionMagicDynamicRegistry) {
+		return new DefaultMagicAPIService(resultProvider, properties.getInstanceId(), magicResourceService, requestMagicDynamicRegistry, functionMagicDynamicRegistry, properties.isThrowException(), applicationContext);
 	}
 
 	/**
 	 * 注册模块、类型扩展
 	 */
-	private void setupMagicModules(MagicDynamicDataSource dynamicDataSource,
-                                   SQLModule sqlModule,
-                                   ResultProvider resultProvider,
-								   List<MagicModule> magicModules,
-								   List<ExtensionMethod> extensionMethods,
-								   List<LanguageProvider> languageProviders) {
+	private void setupMagicModules(List<ExtensionMethod> extensionMethods, List<LanguageProvider> languageProviders) {
 		// 设置脚本import时 class加载策略
 		MagicResourceLoader.setClassLoader((className) -> {
 			try {
@@ -480,27 +289,13 @@ public class MagicAPIAutoConfiguration implements WebMvcConfigurer, WebSocketCon
 				}).orElse(null)
 		);
 		logger.info("注册模块:{} -> {}", "log", Logger.class);
-		MagicResourceLoader.addModule("log", new DynamicModuleImport(Logger.class, context -> LoggerFactory.getLogger(Objects.toString(context.getScriptName(),"Unknown"))));
+		MagicResourceLoader.addModule("log", new DynamicModuleImport(Logger.class, context -> LoggerFactory.getLogger(Objects.toString(context.getScriptName(), "Unknown"))));
 		List<String> importModules = properties.getAutoImportModuleList();
-		logger.info("注册模块:{} -> {}", "env", EnvModule.class);
-		MagicResourceLoader.addModule("env", new EnvModule(environment));
-		logger.info("注册模块:{} -> {}", "request", RequestModule.class);
-		MagicResourceLoader.addModule("request", new RequestModule(multipartResolver));
-		logger.info("注册模块:{} -> {}", "response", ResponseModule.class);
-		MagicResourceLoader.addModule("response", new ResponseModule(resultProvider));
-		logger.info("注册模块:{} -> {}", "assert", AssertModule.class);
-		MagicResourceLoader.addModule("assert", new AssertModule());
-		magicModules.forEach(module -> {
-			logger.info("注册模块:{} -> {}", module.getModuleName(), module.getClass());
-			MagicResourceLoader.addModule(module.getModuleName(), module);
+		applicationContext.getBeansWithAnnotation(MagicModule.class).values().forEach(module -> {
+			String moduleName = module.getClass().getAnnotation(MagicModule.class).value();
+			logger.info("注册模块:{} -> {}", moduleName, module.getClass());
+			MagicResourceLoader.addModule(moduleName, module);
 		});
-        MagicResourceLoader.addModule(sqlModule.getModuleName(), new DynamicModuleImport(SQLModule.class, context -> {
-			String dataSourceKey = context.getString(Options.DEFAULT_DATA_SOURCE.getValue());
-			if(StringUtils.isEmpty(dataSourceKey)) return sqlModule;
-			SQLModule newSqlModule = sqlModule.cloneSQLModule();
-			newSqlModule.setDataSourceNode(dynamicDataSource.getDataSource(dataSourceKey));
-            return newSqlModule;
-        }));
 		MagicResourceLoader.getModuleNames().stream().filter(importModules::contains).forEach(moduleName -> {
 			logger.info("自动导入模块:{}", moduleName);
 			MagicScriptEngine.addDefaultImport(moduleName, MagicResourceLoader.loadModule(moduleName));
@@ -516,47 +311,34 @@ public class MagicAPIAutoConfiguration implements WebMvcConfigurer, WebSocketCon
 	}
 
 	@Bean
-	public JSR223LanguageProvider jsr223LanguageProvider() {
-		return new JSR223LanguageProvider();
-	}
-
-	@Bean
-	public MagicConfiguration magicConfiguration(MagicDynamicDataSource dynamicDataSource,
-                                                 SQLModule sqlModule,
-                                                 List<MagicModule> magicModules,
-												 List<LanguageProvider> languageProviders,
-												 Resource magicResource,
+	public MagicConfiguration magicConfiguration(List<LanguageProvider> languageProviders,
+												 org.ssssssss.magicapi.core.resource.Resource magicResource,
 												 ResultProvider resultProvider,
+												 MagicResourceService magicResourceService,
 												 MagicAPIService magicAPIService,
-												 ApiServiceProvider apiServiceProvider,
-												 GroupServiceProvider groupServiceProvider,
-												 MappingHandlerMapping mappingHandlerMapping,
-												 FunctionServiceProvider functionServiceProvider,
 												 MagicNotifyService magicNotifyService,
-												 MagicFunctionManager magicFunctionManager,
-												 MagicBackupService magicBackupService) throws NoSuchMethodException {
+												 RequestMagicDynamicRegistry requestMagicDynamicRegistry,
+												 @Autowired(required = false) MagicBackupService magicBackupService) throws NoSuchMethodException {
 		logger.info("magic-api工作目录:{}", magicResource);
 		AsyncCall.setThreadPoolExecutorSize(properties.getThreadPoolExecutorSize());
 		DataType.DATE_PATTERNS = properties.getDatePattern();
 		MagicScript.setCompileCache(properties.getCompileCacheSize());
 		// 设置响应结果的code值
-		ResponseCodeConfig responseCodeConfig = properties.getResponseCodeConfig();
+		ResponseCode responseCodeConfig = properties.getResponseCode();
 		Constants.RESPONSE_CODE_SUCCESS = responseCodeConfig.getSuccess();
 		Constants.RESPONSE_CODE_INVALID = responseCodeConfig.getInvalid();
 		Constants.RESPONSE_CODE_EXCEPTION = responseCodeConfig.getException();
 		// 设置模块和扩展方法
-		setupMagicModules(dynamicDataSource, sqlModule, resultProvider, magicModules, extensionMethodsProvider.getIfAvailable(Collections::emptyList), languageProviders);
+		setupMagicModules(extensionMethodsProvider.getIfAvailable(Collections::emptyList), languageProviders);
 		MagicConfiguration configuration = new MagicConfiguration();
 		configuration.setMagicAPIService(magicAPIService);
 		configuration.setMagicNotifyService(magicNotifyService);
-		configuration.setInstanceId(properties.getClusterConfig().getInstanceId());
-		configuration.setApiServiceProvider(apiServiceProvider);
-		configuration.setGroupServiceProvider(groupServiceProvider);
-		configuration.setMappingHandlerMapping(mappingHandlerMapping);
-		configuration.setFunctionServiceProvider(functionServiceProvider);
+		configuration.setInstanceId(properties.getInstanceId());
+		configuration.setMagicResourceService(magicResourceService);
+		configuration.setMagicDynamicRegistries(magicDynamicRegistriesProvider.getObject());
 		configuration.setMagicBackupService(magicBackupService);
-		SecurityConfig securityConfig = properties.getSecurityConfig();
-		configuration.setDebugTimeout(properties.getDebugConfig().getTimeout());
+		Security securityConfig = properties.getSecurityConfig();
+		configuration.setDebugTimeout(properties.getDebug().getTimeout());
 		configuration.setHttpMessageConverters(httpMessageConvertersProvider.getIfAvailable(Collections::emptyList));
 		configuration.setResultProvider(resultProvider);
 		configuration.setThrowException(properties.isThrowException());
@@ -568,32 +350,25 @@ public class MagicAPIAutoConfiguration implements WebMvcConfigurer, WebSocketCon
 		// 向页面传递配置信息时不传递用户名密码,增强安全性
 		securityConfig.setUsername(null);
 		securityConfig.setPassword(null);
-
+		requestMagicDynamicRegistry.setHandler(new RequestHandler(configuration, requestMagicDynamicRegistry));
+		List<MagicPluginConfiguration> pluginConfigurations = magicPluginsProvider.getIfAvailable(Collections::emptyList);
+		List<Plugin> plugins = pluginConfigurations.stream().map(MagicPluginConfiguration::plugin).collect(Collectors.toList());
 		// 构建UI请求处理器
 		String base = properties.getWeb();
-		mappingHandlerMapping.setRequestMappingHandlerMapping(requestMappingHandlerMapping);
-		MagicDataSourceController dataSourceController = new MagicDataSourceController(configuration);
-		MagicWorkbenchController magicWorkbenchController = new MagicWorkbenchController(configuration, properties.getSecretKey());
+		Mapping mapping = Mapping.create(requestMappingHandlerMapping, base, properties.getPrefix());
+		MagicWorkbenchController magicWorkbenchController = new MagicWorkbenchController(configuration, properties, plugins);
 		if (base != null) {
 			configuration.setEnableWeb(true);
-			List<MagicController> controllers = new ArrayList<>(Arrays.asList(
-					new MagicAPIController(configuration),
-					dataSourceController,
-					magicWorkbenchController,
-					new MagicGroupController(configuration),
-					new MagicFunctionController(configuration)
-			));
-			controllers.forEach(item -> mappingHandlerMapping.registerController(item, base));
+			mapping.registerController(magicWorkbenchController)
+					.registerController(new MagicResourceController(configuration))
+					.registerController(new MagicDataSourceController(configuration))
+					.registerController(new MagicBackupController(configuration));
+			pluginConfigurations.forEach(it -> it.controllerRegister().register(mapping, configuration));
 		}
 		// 注册接收推送的接口
 		if (StringUtils.isNotBlank(properties.getSecretKey())) {
-			Mapping mapping = Mapping.create(requestMappingHandlerMapping);
-			RequestMappingInfo requestMappingInfo = mapping.paths(properties.getPushPath()).build();
-			Method method = MagicWorkbenchController.class.getDeclaredMethod("receivePush", MultipartFile.class, String.class, Long.class, String.class);
-			mapping.register(requestMappingInfo, magicWorkbenchController, method);
+			mapping.register(mapping.paths(properties.getPushPath()).methods(RequestMethod.POST).build(), magicWorkbenchController, MagicWorkbenchController.class.getDeclaredMethod("receivePush", MultipartFile.class, String.class, Long.class, String.class));
 		}
-		// 注册数据源
-		magicAPIService.registerAllDataSource();
 		// 设置拦截器信息
 		this.requestInterceptorsProvider.getIfAvailable(Collections::emptyList).forEach(interceptor -> {
 			logger.info("注册请求拦截器:{}", interceptor.getClass());
@@ -601,21 +376,14 @@ public class MagicAPIAutoConfiguration implements WebMvcConfigurer, WebSocketCon
 		});
 		// 打印banner
 		if (this.properties.isBanner()) {
-			configuration.printBanner();
+			configuration.printBanner(plugins.stream().map(Plugin::getName).collect(Collectors.toList()));
+		}
+		if (magicBackupService == null) {
+			logger.error("当前备份设置未配置,强烈建议配置备份设置,以免代码丢失。");
 		}
-		configuration.setMagicFunctionManager(magicFunctionManager);
-		// 注册函数加载器
-		magicFunctionManager.registerFunctionLoader();
-		// 注册所有函数
-		magicFunctionManager.registerAllFunction();
-		mappingHandlerMapping.setHandler(new RequestHandler(configuration));
-		mappingHandlerMapping.setMagicApiService(apiServiceProvider);
-		mappingHandlerMapping.setGroupServiceProvider(groupServiceProvider);
-		// 注册所有映射
-		mappingHandlerMapping.registerAllMapping();
 		// 备份清理
-		if (properties.getBackupConfig().getMaxHistory() > 0) {
-			long interval = properties.getBackupConfig().getMaxHistory() * 86400000L;
+		if (properties.getBackup().isEnable() && properties.getBackup().getMaxHistory() > 0 && magicBackupService != null) {
+			long interval = properties.getBackup().getMaxHistory() * 86400000L;
 			// 1小时执行1次
 			new ScheduledThreadPoolExecutor(1, r -> new Thread(r, "magic-api-clean-task")).scheduleAtFixedRate(() -> {
 				try {
@@ -635,26 +403,11 @@ public class MagicAPIAutoConfiguration implements WebMvcConfigurer, WebSocketCon
 		if (defaultAuthorizationInterceptor != null) {
 			return defaultAuthorizationInterceptor;
 		}
-		SecurityConfig securityConfig = properties.getSecurityConfig();
+		Security securityConfig = properties.getSecurityConfig();
 		defaultAuthorizationInterceptor = new DefaultAuthorizationInterceptor(securityConfig.getUsername(), securityConfig.getPassword());
 		return defaultAuthorizationInterceptor;
 	}
 
-	private RestTemplate createRestTemplate() {
-		RestTemplate restTemplate = new RestTemplate();
-		restTemplate.getMessageConverters().add(new StringHttpMessageConverter(StandardCharsets.UTF_8) {
-			{
-				setSupportedMediaTypes(Collections.singletonList(MediaType.ALL));
-			}
-
-			@Override
-			public boolean supports(Class<?> clazz) {
-				return true;
-			}
-		});
-		return restTemplate;
-	}
-
 	@Override
 	public void registerWebSocketHandlers(WebSocketHandlerRegistry webSocketHandlerRegistry) {
 		String web = properties.getWeb();
@@ -662,8 +415,9 @@ public class MagicAPIAutoConfiguration implements WebMvcConfigurer, WebSocketCon
 		WebSocketSessionManager.setMagicNotifyService(magicNotifyService);
 		if (web != null && !registerWebsocket) {
 			registerWebsocket = true;
-			MagicWebSocketDispatcher dispatcher = new MagicWebSocketDispatcher(properties.getClusterConfig().getInstanceId(), magicNotifyService, Arrays.asList(
+			MagicWebSocketDispatcher dispatcher = new MagicWebSocketDispatcher(properties.getInstanceId(), magicNotifyService, Arrays.asList(
 					new MagicDebugHandler(),
+					new MagicCoordinationHandler(),
 					new MagicWorkbenchHandler(authorizationInterceptorProvider.getIfAvailable(this::createAuthorizationInterceptor))
 			));
 			WebSocketHandlerRegistration registration = webSocketHandlerRegistry.addHandler(dispatcher, web + "/console");

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

@@ -0,0 +1,74 @@
+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.ConditionalOnMissingBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
+import org.ssssssss.magicapi.core.config.MagicAPIProperties;
+import org.ssssssss.magicapi.core.interceptor.DefaultResultProvider;
+import org.ssssssss.magicapi.core.interceptor.ResultProvider;
+import org.ssssssss.magicapi.core.service.impl.ApiInfoMagicResourceStorage;
+import org.ssssssss.magicapi.core.service.impl.RequestMagicDynamicRegistry;
+import org.ssssssss.magicapi.datasource.model.MagicDynamicDataSource;
+import org.ssssssss.magicapi.datasource.service.DataSourceInfoMagicResourceStorage;
+import org.ssssssss.magicapi.datasource.service.DataSourceMagicDynamicRegistry;
+import org.ssssssss.magicapi.function.service.FunctionInfoMagicResourceStorage;
+import org.ssssssss.magicapi.function.service.FunctionMagicDynamicRegistry;
+import org.ssssssss.magicapi.utils.Mapping;
+
+@Configuration
+@AutoConfigureAfter(MagicModuleConfiguration.class)
+public class MagicDynamicRegistryConfiguration {
+
+
+	private final MagicAPIProperties properties;
+
+	@Autowired
+	@Lazy
+	private RequestMappingHandlerMapping requestMappingHandlerMapping;
+
+
+	public MagicDynamicRegistryConfiguration(MagicAPIProperties properties) {
+		this.properties = properties;
+	}
+
+	@Bean
+	@ConditionalOnMissingBean
+	public ApiInfoMagicResourceStorage apiInfoMagicResourceStorage() {
+		return new ApiInfoMagicResourceStorage();
+	}
+
+	@Bean
+	@ConditionalOnMissingBean
+	public RequestMagicDynamicRegistry magicRequestMagicDynamicRegistry(ApiInfoMagicResourceStorage apiInfoMagicResourceStorage) throws NoSuchMethodException {
+		return new RequestMagicDynamicRegistry(apiInfoMagicResourceStorage, Mapping.create(requestMappingHandlerMapping, properties.getWeb(), properties.getPrefix()), properties.isAllowOverride());
+	}
+
+	@Bean
+	@ConditionalOnMissingBean
+	public FunctionInfoMagicResourceStorage functionInfoMagicResourceStorage() {
+		return new FunctionInfoMagicResourceStorage();
+	}
+
+	@Bean
+	@ConditionalOnMissingBean
+	public FunctionMagicDynamicRegistry functionMagicDynamicRegistry(FunctionInfoMagicResourceStorage functionInfoMagicResourceStorage) {
+		return new FunctionMagicDynamicRegistry(functionInfoMagicResourceStorage);
+	}
+
+	@Bean
+	@ConditionalOnMissingBean
+	public DataSourceInfoMagicResourceStorage dataSourceInfoMagicResourceStorage() {
+		return new DataSourceInfoMagicResourceStorage();
+	}
+
+	@Bean
+	@ConditionalOnMissingBean
+	public DataSourceMagicDynamicRegistry dataSourceMagicDynamicRegistry(DataSourceInfoMagicResourceStorage dataSourceInfoMagicResourceStorage, MagicDynamicDataSource magicDynamicDataSource) {
+		return new DataSourceMagicDynamicRegistry(dataSourceInfoMagicResourceStorage, magicDynamicDataSource);
+	}
+
+}

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

@@ -0,0 +1,226 @@
+package org.ssssssss.magicapi.spring.boot.starter;
+
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.ObjectProvider;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.core.env.Environment;
+import org.springframework.http.MediaType;
+import org.springframework.http.converter.StringHttpMessageConverter;
+import org.springframework.web.client.RestTemplate;
+import org.springframework.web.multipart.MultipartResolver;
+import org.ssssssss.magicapi.core.config.Cache;
+import org.ssssssss.magicapi.core.config.MagicAPIProperties;
+import org.ssssssss.magicapi.core.config.Page;
+import org.ssssssss.magicapi.core.interceptor.DefaultResultProvider;
+import org.ssssssss.magicapi.core.interceptor.ResultProvider;
+import org.ssssssss.magicapi.core.model.Options;
+import org.ssssssss.magicapi.datasource.model.MagicDynamicDataSource;
+import org.ssssssss.magicapi.jsr223.JSR223LanguageProvider;
+import org.ssssssss.magicapi.modules.db.ColumnMapperAdapter;
+import org.ssssssss.magicapi.modules.db.SQLModule;
+import org.ssssssss.magicapi.modules.db.cache.DefaultSqlCache;
+import org.ssssssss.magicapi.modules.db.cache.SqlCache;
+import org.ssssssss.magicapi.modules.db.dialect.Dialect;
+import org.ssssssss.magicapi.modules.db.dialect.DialectAdapter;
+import org.ssssssss.magicapi.modules.db.inteceptor.DefaultSqlInterceptor;
+import org.ssssssss.magicapi.modules.db.inteceptor.NamedTableInterceptor;
+import org.ssssssss.magicapi.modules.db.inteceptor.SQLInterceptor;
+import org.ssssssss.magicapi.modules.db.provider.ColumnMapperProvider;
+import org.ssssssss.magicapi.modules.db.provider.DefaultPageProvider;
+import org.ssssssss.magicapi.modules.db.provider.PageProvider;
+import org.ssssssss.magicapi.modules.http.HttpModule;
+import org.ssssssss.magicapi.modules.servlet.RequestModule;
+import org.ssssssss.magicapi.modules.servlet.ResponseModule;
+import org.ssssssss.magicapi.modules.spring.EnvModule;
+import org.ssssssss.script.MagicResourceLoader;
+import org.ssssssss.script.functions.DynamicModuleImport;
+
+import javax.sql.DataSource;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class MagicModuleConfiguration {
+
+	private static final Logger logger = LoggerFactory.getLogger(MagicModuleConfiguration.class);
+
+	private final MagicAPIProperties properties;
+
+
+	/**
+	 * SQL拦截器
+	 */
+	private final ObjectProvider<List<SQLInterceptor>> sqlInterceptorsProvider;
+
+	/**
+	 * 单表API拦截器
+	 */
+	private final ObjectProvider<List<NamedTableInterceptor>> namedTableInterceptorsProvider;
+
+	/**
+	 * 自定义的方言
+	 */
+	private final ObjectProvider<List<Dialect>> dialectsProvider;
+
+	/**
+	 * 自定义的列名转换
+	 */
+	private final ObjectProvider<List<ColumnMapperProvider>> columnMapperProvidersProvider;
+
+	private final Environment environment;
+
+	@Autowired(required = false)
+	private MultipartResolver multipartResolver;
+
+	public MagicModuleConfiguration(MagicAPIProperties properties,
+									ObjectProvider<List<SQLInterceptor>> sqlInterceptorsProvider,
+									ObjectProvider<List<NamedTableInterceptor>> namedTableInterceptorsProvider,
+									ObjectProvider<List<Dialect>> dialectsProvider,
+									ObjectProvider<List<ColumnMapperProvider>> columnMapperProvidersProvider,
+									Environment environment) {
+		this.properties = properties;
+		this.sqlInterceptorsProvider = sqlInterceptorsProvider;
+		this.namedTableInterceptorsProvider = namedTableInterceptorsProvider;
+		this.dialectsProvider = dialectsProvider;
+		this.columnMapperProvidersProvider = columnMapperProvidersProvider;
+		this.environment = environment;
+	}
+
+	/**
+	 * 注入动态数据源
+	 */
+	@Bean
+	@ConditionalOnMissingBean(MagicDynamicDataSource.class)
+	public MagicDynamicDataSource magicDynamicDataSource(@Autowired(required = false) DataSource dataSource) {
+		MagicDynamicDataSource dynamicDataSource = new MagicDynamicDataSource();
+		if (dataSource != null) {
+			dynamicDataSource.put(dataSource);
+		} else {
+			logger.warn("当前数据源未配置");
+		}
+		return dynamicDataSource;
+	}
+
+	@Bean
+	@ConditionalOnMissingBean(PageProvider.class)
+	public PageProvider pageProvider() {
+		Page pageConfig = properties.getPage();
+		logger.info("未找到分页实现,采用默认分页实现,分页配置:(页码={},页大小={},默认首页={},默认页大小={})", pageConfig.getPage(), pageConfig.getSize(), pageConfig.getDefaultPage(), pageConfig.getDefaultSize());
+		return new DefaultPageProvider(pageConfig.getPage(), pageConfig.getSize(), pageConfig.getDefaultPage(), pageConfig.getDefaultSize());
+	}
+
+	/**
+	 * 注入SQL缓存实现
+	 */
+	@Bean
+	@ConditionalOnMissingBean(SqlCache.class)
+	public SqlCache sqlCache() {
+		Cache cacheConfig = properties.getCache();
+		logger.info("未找到SQL缓存实现,采用默认缓存实现(LRU+TTL),缓存配置:(容量={},TTL={})", cacheConfig.getCapacity(), cacheConfig.getTtl());
+		return new DefaultSqlCache(cacheConfig.getCapacity(), cacheConfig.getTtl());
+	}
+
+	/**
+	 * 注入数据库查询模块
+	 */
+	@Bean
+	@ConditionalOnBean({MagicDynamicDataSource.class})
+	public SQLModule magicSqlModule(MagicDynamicDataSource dynamicDataSource,
+									ResultProvider resultProvider,
+									PageProvider pageProvider,
+									SqlCache sqlCache) {
+		SQLModule sqlModule = new SQLModule(dynamicDataSource);
+		if (!dynamicDataSource.isEmpty()) {
+			sqlModule.setDataSourceNode(dynamicDataSource.getDataSource());
+		}
+		sqlModule.setResultProvider(resultProvider);
+		sqlModule.setPageProvider(pageProvider);
+		List<SQLInterceptor> sqlInterceptors = sqlInterceptorsProvider.getIfAvailable(ArrayList::new);
+		if (properties.isShowSql()) {
+			sqlInterceptors.add(new DefaultSqlInterceptor());
+		}
+		sqlModule.setSqlInterceptors(sqlInterceptors);
+		sqlModule.setNamedTableInterceptors(namedTableInterceptorsProvider.getIfAvailable(Collections::emptyList));
+		ColumnMapperAdapter columnMapperAdapter = new ColumnMapperAdapter();
+		this.columnMapperProvidersProvider.getIfAvailable(Collections::emptyList).stream().filter(mapperProvider -> !"default".equals(mapperProvider.name())).forEach(columnMapperAdapter::add);
+		columnMapperAdapter.setDefault(properties.getSqlColumnCase());
+		sqlModule.setColumnMapperProvider(columnMapperAdapter);
+		sqlModule.setColumnMapRowMapper(columnMapperAdapter.getDefaultColumnMapRowMapper());
+		sqlModule.setRowMapColumnMapper(columnMapperAdapter.getDefaultRowMapColumnMapper());
+		sqlModule.setSqlCache(sqlCache);
+		DialectAdapter dialectAdapter = new DialectAdapter();
+		dialectsProvider.getIfAvailable(Collections::emptyList).forEach(dialectAdapter::add);
+		sqlModule.setDialectAdapter(dialectAdapter);
+		sqlModule.setLogicDeleteColumn(properties.getCrud().getLogicDeleteColumn());
+		sqlModule.setLogicDeleteValue(properties.getCrud().getLogicDeleteValue());
+		MagicResourceLoader.addModule("db", new DynamicModuleImport(SQLModule.class, context -> {
+			String dataSourceKey = context.getString(Options.DEFAULT_DATA_SOURCE.getValue());
+			if (StringUtils.isEmpty(dataSourceKey)) return sqlModule;
+			SQLModule newSqlModule = sqlModule.cloneSQLModule();
+			newSqlModule.setDataSourceNode(dynamicDataSource.getDataSource(dataSourceKey));
+			return newSqlModule;
+		}));
+		return sqlModule;
+	}
+
+	@Bean
+	public JSR223LanguageProvider jsr223LanguageProvider() {
+		return new JSR223LanguageProvider();
+	}
+
+	@Bean
+	@ConditionalOnMissingBean(HttpModule.class)
+	public HttpModule magicHttpModule() {
+		return new HttpModule(createRestTemplate());
+	}
+
+	@Bean
+	@ConditionalOnMissingBean
+	public EnvModule magicEnvModule(){
+		return new EnvModule(environment);
+	}
+
+	@Bean
+	@ConditionalOnMissingBean
+	public RequestModule magicRequestModule(){
+		return new RequestModule(multipartResolver);
+	}
+
+	/**
+	 * 注入结果构建方法
+	 */
+	@Bean
+	@ConditionalOnMissingBean(ResultProvider.class)
+	public ResultProvider resultProvider() {
+		return new DefaultResultProvider(properties.getResponse());
+	}
+
+
+	@Bean
+	@ConditionalOnMissingBean
+	public ResponseModule magicResponseModule(ResultProvider resultProvider){
+		return new ResponseModule(resultProvider);
+	}
+
+	private RestTemplate createRestTemplate() {
+		RestTemplate restTemplate = new RestTemplate();
+		restTemplate.getMessageConverters().add(new StringHttpMessageConverter(StandardCharsets.UTF_8) {
+			{
+				setSupportedMediaTypes(Collections.singletonList(MediaType.ALL));
+			}
+
+			@Override
+			public boolean supports(Class<?> clazz) {
+				return true;
+			}
+		});
+		return restTemplate;
+	}
+
+}

+ 0 - 33
magic-api-spring-boot-starter/src/main/resources/META-INF/additional-spring-configuration-metadata.json

@@ -1,33 +0,0 @@
-{
-  "groups": [
-    {
-      "sourceType": "org.ssssssss.magicapi.spring.boot.starter.MagicAPIProperties",
-      "name": "magic-api",
-      "type": "org.ssssssss.magicapi.spring.boot.starter.MagicAPIProperties"
-    },
-    {
-      "sourceType": "org.ssssssss.magicapi.spring.boot.starter.MagicAPIProperties",
-      "name": "page-config",
-      "sourceMethod": "getPageConfig()",
-      "type": "org.ssssssss.magicapi.spring.boot.starter.PageConfig"
-    },
-    {
-      "sourceType": "org.ssssssss.magicapi.spring.boot.starter.MagicAPIProperties",
-      "name": "cache-config",
-      "sourceMethod": "getCacheConfig()",
-      "type": "org.ssssssss.magicapi.spring.boot.starter.CacheConfig"
-    },
-    {
-      "sourceType": "org.ssssssss.magicapi.spring.boot.starter.MagicAPIProperties",
-      "name": "debug-config",
-      "sourceMethod": "getDebugConfig()",
-      "type": "org.ssssssss.magicapi.spring.boot.starter.DebugConfig"
-    },
-    {
-      "sourceType": "org.ssssssss.magicapi.spring.boot.starter.MagicAPIProperties",
-      "name": "crud-config",
-      "sourceMethod": "getCrudConfig()",
-      "type": "org.ssssssss.magicapi.spring.boot.starter.CrudConfig"
-    }
-  ]
-}

+ 6 - 12
magic-api/pom.xml

@@ -6,7 +6,7 @@
     <parent>
         <groupId>org.ssssssss</groupId>
         <artifactId>magic-api-parent</artifactId>
-        <version>1.7.5</version>
+        <version>2.0.0-beta.1</version>
     </parent>
     <artifactId>magic-api</artifactId>
     <packaging>jar</packaging>
@@ -26,20 +26,9 @@
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-websocket</artifactId>
         </dependency>
-        <dependency>
-            <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-starter-data-redis</artifactId>
-            <scope>provided</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-starter-data-mongodb</artifactId>
-            <scope>provided</scope>
-        </dependency>
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-web</artifactId>
-            <scope>provided</scope>
         </dependency>
         <dependency>
             <groupId>org.springframework</groupId>
@@ -71,5 +60,10 @@
             <groupId>org.apache.commons</groupId>
             <artifactId>commons-compress</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-configuration-processor</artifactId>
+            <optional>true</optional>
+        </dependency>
     </dependencies>
 </project>

+ 5 - 5
magic-api/src/main/java/org/ssssssss/magicapi/model/Backup.java → magic-api/src/main/java/org/ssssssss/magicapi/backup/model/Backup.java

@@ -1,4 +1,4 @@
-package org.ssssssss.magicapi.model;
+package org.ssssssss.magicapi.backup.model;
 
 /**
  * 备份记录
@@ -34,7 +34,7 @@ public class Backup {
 	/**
 	 * 备份内容
 	 */
-	private String content;
+	private byte[] content;
 
 	/**
 	 * 操作人,取用户名,空为系统记录
@@ -45,7 +45,7 @@ public class Backup {
 	public Backup() {
 	}
 
-	public Backup(String id, String type, String name, String content) {
+	public Backup(String id, String type, String name, byte[] content) {
 		this.id = id;
 		this.type = type;
 		this.name = name;
@@ -84,11 +84,11 @@ public class Backup {
 		this.id = id;
 	}
 
-	public String getContent() {
+	public byte[] getContent() {
 		return content;
 	}
 
-	public void setContent(String content) {
+	public void setContent(byte[] content) {
 		this.content = content;
 	}
 

+ 5 - 38
magic-api/src/main/java/org/ssssssss/magicapi/provider/MagicBackupService.java → magic-api/src/main/java/org/ssssssss/magicapi/backup/service/MagicBackupService.java

@@ -1,8 +1,8 @@
-package org.ssssssss.magicapi.provider;
+package org.ssssssss.magicapi.backup.service;
 
-import org.ssssssss.magicapi.model.*;
-import org.ssssssss.magicapi.utils.JsonUtils;
+import org.ssssssss.magicapi.backup.model.Backup;
 
+import java.io.IOException;
 import java.util.List;
 
 /**
@@ -14,33 +14,6 @@ public interface MagicBackupService {
 
 	int FETCH_SIZE = 100;
 
-	/**
-	 * 备份接口
-	 *
-	 * @param apiInfo 接口信息
-	 */
-	default void backup(ApiInfo apiInfo) {
-		doBackup(new Backup(apiInfo.getId(), Constants.PATH_API, apiInfo.getName(), JsonUtils.toJsonString(apiInfo)));
-	}
-
-	/**
-	 * 备份函数
-	 *
-	 * @param functionInfo 函数信息
-	 */
-	default void backup(FunctionInfo functionInfo) {
-		doBackup(new Backup(functionInfo.getId(), Constants.PATH_FUNCTION, functionInfo.getName(), JsonUtils.toJsonString(functionInfo)));
-	}
-
-	/**
-	 * 备份数据源
-	 *
-	 * @param dataSourceInfo 数据源信息
-	 */
-	default void backup(DataSourceInfo dataSourceInfo) {
-		doBackup(new Backup(dataSourceInfo.getId(), Constants.PATH_DATASOURCE, dataSourceInfo.get("name"), JsonUtils.toJsonString(dataSourceInfo)));
-	}
-
 	/**
 	 * 执行备份动作
 	 *
@@ -48,6 +21,8 @@ public interface MagicBackupService {
 	 */
 	void doBackup(Backup backup);
 
+	void doBackupAll(String name, String createBy) throws IOException;
+
 	/**
 	 * 根据时间戳查询最近的 FETCH_SIZE 条记录
 	 *
@@ -85,14 +60,6 @@ public interface MagicBackupService {
 	 */
 	long removeBackup(String id);
 
-	/**
-	 * 删除一组备份信息
-	 *
-	 * @param idList 对象ID集合
-	 * @return 返回删除的记录数
-	 */
-	long removeBackup(List<String> idList);
-
 	/**
 	 * 根据13位时间戳删除备份记录(清除小于该值的记录)
 	 *

+ 161 - 0
magic-api/src/main/java/org/ssssssss/magicapi/backup/service/MagicDatabaseBackupService.java

@@ -0,0 +1,161 @@
+package org.ssssssss.magicapi.backup.service;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.event.EventListener;
+import org.springframework.jdbc.core.BeanPropertyRowMapper;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.ssssssss.magicapi.core.config.MagicConfiguration;
+import org.ssssssss.magicapi.core.event.FileEvent;
+import org.ssssssss.magicapi.core.event.GroupEvent;
+import org.ssssssss.magicapi.backup.model.Backup;
+import org.ssssssss.magicapi.core.model.Group;
+import org.ssssssss.magicapi.core.model.MagicEntity;
+import org.ssssssss.magicapi.utils.JsonUtils;
+import org.ssssssss.magicapi.utils.WebUtils;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * 数据库备份实现
+ *
+ * @author mxd
+ */
+public class MagicDatabaseBackupService implements MagicBackupService {
+
+	private final static String DEFAULT_COLUMNS = "id,create_date,tag,type,name,create_by";
+
+	private final JdbcTemplate template;
+
+	private final String INSERT_SQL;
+
+	private final String FIND_BY_ID;
+
+	private final String FIND_BY_TAG;
+
+	private final String FIND_BY_TIMESTAMP;
+
+	private final String FIND_BY_ID_AND_TIMESTAMP;
+
+	private final String DELETE_BY_ID;
+
+	private final String DELETE_BY_TIMESTAMP;
+
+	private final BeanPropertyRowMapper<Backup> rowMapper = new BeanPropertyRowMapper<>(Backup.class);
+
+	private static final Logger logger = LoggerFactory.getLogger(MagicDatabaseBackupService.class);
+
+	public MagicDatabaseBackupService(JdbcTemplate template, String tableName) {
+		this.template = template;
+		this.template.setMaxRows(FETCH_SIZE);
+		this.INSERT_SQL = String.format("insert into %s(%s,content) values(?,?,?,?,?,?,?)", tableName, DEFAULT_COLUMNS);
+		this.FIND_BY_ID = String.format("select %s from %s where id = ? order by create_date desc", DEFAULT_COLUMNS, tableName);
+		this.DELETE_BY_ID = String.format("delete from %s where id = ?", tableName);
+		this.FIND_BY_TAG = String.format("select %s from %s where tag = ? order by create_date desc", DEFAULT_COLUMNS, tableName);
+		this.FIND_BY_TIMESTAMP = String.format("select %s from %s where create_date < ? order by create_date desc", DEFAULT_COLUMNS, tableName);
+		this.DELETE_BY_TIMESTAMP = String.format("delete from %s where create_date < ?", tableName);
+		this.FIND_BY_ID_AND_TIMESTAMP = String.format("select * from %s where id = ? and create_date = ?", tableName);
+	}
+
+	@Override
+	public void doBackupAll(String name, String createBy) throws IOException {
+		ByteArrayOutputStream baos = new ByteArrayOutputStream();
+		MagicConfiguration.getMagicResourceService().export(null, null, baos);
+		Backup backup = new Backup();
+		backup.setId("full");
+		backup.setType("full");
+		backup.setName(name);
+		backup.setCreateBy(createBy);
+		backup.setContent(baos.toByteArray());
+		doBackup(backup);
+	}
+
+	@Override
+	public void doBackup(Backup backup) {
+		try {
+			if (backup.getCreateDate() == 0) {
+				backup.setCreateDate(System.currentTimeMillis());
+			}
+			if (backup.getCreateBy() == null) {
+				backup.setCreateBy(WebUtils.currentUserName());
+			}
+			template.update(INSERT_SQL, backup.getId(), backup.getCreateDate(), backup.getTag(), backup.getType(), backup.getName(), backup.getCreateBy(), backup.getContent());
+		} catch (Exception e) {
+			logger.warn("备份失败", e);
+		}
+	}
+
+	@Override
+	public List<Backup> backupList(long timestamp) {
+		return template.query(FIND_BY_TIMESTAMP, rowMapper, timestamp);
+	}
+
+	@Override
+	public List<Backup> backupById(String id) {
+		return template.query(FIND_BY_ID, rowMapper, id);
+	}
+
+	@Override
+	public Backup backupInfo(String id, long timestamp) {
+		return template.queryForObject(FIND_BY_ID_AND_TIMESTAMP, rowMapper, id, timestamp);
+	}
+
+	@Override
+	public List<Backup> backupByTag(String tag) {
+		return template.query(FIND_BY_TAG, rowMapper, tag);
+	}
+
+	@Override
+	public long removeBackup(String id) {
+		return template.update(DELETE_BY_ID, id);
+	}
+
+	@Override
+	public long removeBackupByTimestamp(long timestamp) {
+		try {
+			return template.update(DELETE_BY_TIMESTAMP, timestamp);
+		} catch (Exception e) {
+			logger.warn("删除备份失败", e);
+			return -1;
+		}
+	}
+
+	@EventListener(condition = "#event.source != T(org.ssssssss.magicapi.core.config.Constants).EVENT_SOURCE_NOTIFY")
+	public void onFileEvent(FileEvent event) {
+		switch (event.getAction()) {
+			case SAVE:
+			case CREATE:
+			case MOVE:
+				break;
+			default:
+				return;
+		}
+		MagicEntity entity = event.getEntity();
+		doBackup(entity.getId(), JsonUtils.toJsonBytes(entity), entity.getName(), event.getType());
+	}
+
+	@EventListener(condition = "#event.source != T(org.ssssssss.magicapi.core.config.Constants).EVENT_SOURCE_NOTIFY")
+	public void onFolderEvent(GroupEvent event) {
+		switch (event.getAction()) {
+			case SAVE:
+			case CREATE:
+			case MOVE:
+				break;
+			default:
+				return;
+		}
+		Group group = event.getGroup();
+		doBackup(group.getId(), JsonUtils.toJsonBytes(group), group.getName(), group.getType() + "-group");
+	}
+
+	private void doBackup(String id, byte[] content, String name, String type) {
+		Backup backup = new Backup();
+		backup.setName(name);
+		backup.setId(id);
+		backup.setContent(content);
+		backup.setType(type);
+		doBackup(backup);
+	}
+}

+ 84 - 0
magic-api/src/main/java/org/ssssssss/magicapi/backup/web/MagicBackupController.java

@@ -0,0 +1,84 @@
+package org.ssssssss.magicapi.backup.web;
+
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.ssssssss.magicapi.backup.model.Backup;
+import org.ssssssss.magicapi.core.config.Constants;
+import org.ssssssss.magicapi.core.web.MagicController;
+import org.ssssssss.magicapi.core.web.MagicExceptionHandler;
+import org.ssssssss.magicapi.core.config.MagicConfiguration;
+import org.ssssssss.magicapi.core.model.*;
+import org.ssssssss.magicapi.backup.service.MagicBackupService;
+import org.ssssssss.magicapi.core.service.MagicDynamicRegistry;
+import org.ssssssss.magicapi.utils.JsonUtils;
+import org.ssssssss.magicapi.utils.WebUtils;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+
+public class MagicBackupController extends MagicController implements MagicExceptionHandler {
+
+	private final MagicBackupService service;
+
+	public MagicBackupController(MagicConfiguration configuration) {
+		super(configuration);
+		this.service = configuration.getMagicBackupService();
+	}
+
+	@GetMapping("/backups")
+	@ResponseBody
+	public JsonBean<List<Backup>> backups(Long timestamp) {
+		if(service == null){
+			return new JsonBean<>(Collections.emptyList());
+		}
+		return new JsonBean<>(service.backupList(timestamp == null ? System.currentTimeMillis() : timestamp));
+	}
+
+	@GetMapping("/backup/rollback")
+	@ResponseBody
+	public JsonBean<Boolean> rollback(String id, Long timestamp) throws IOException {
+		notNull(service, BACKUP_NOT_ENABLED);
+		Backup backup = service.backupInfo(id, timestamp);
+		if("full".equals(id)){
+			service.doBackupAll("还原全量备份前,系统自动全量备份", WebUtils.currentUserName());
+			configuration.getMagicAPIService().upload(new ByteArrayInputStream(backup.getContent()), Constants.UPLOAD_MODE_FULL);
+			return new JsonBean<>(true);
+		}
+		if(backup.getType().endsWith("-group")){
+			Group group = JsonUtils.readValue(backup.getContent(), Group.class);
+			return new JsonBean<>(MagicConfiguration.getMagicResourceService().saveGroup(group));
+		}
+		MagicEntity entity = configuration.getMagicDynamicRegistries().stream()
+				.map(MagicDynamicRegistry::getMagicResourceStorage)
+				.filter(it -> it.folder().equals(backup.getType()))
+				.map(it -> it.read(backup.getContent()))
+				.findFirst()
+				.orElse(null);
+		if(entity != null){
+			return new JsonBean<>(MagicConfiguration.getMagicResourceService().saveFile(entity));
+		}
+		return new JsonBean<>(false);
+	}
+
+	@GetMapping("/backup")
+	@ResponseBody
+	public JsonBean<String> backup(Long timestamp, String id) {
+		notNull(service, BACKUP_NOT_ENABLED);
+		notBlank(id, PARAMETER_INVALID);
+		notNull(timestamp, PARAMETER_INVALID);
+		Backup backup = service.backupInfo(id, timestamp);
+		MagicEntity entity = JsonUtils.readValue(backup.getContent(), MagicEntity.class);
+		return new JsonBean<>(entity == null ? null : entity.getScript());
+	}
+
+	@PostMapping("/backup/full")
+	@ResponseBody
+	public JsonBean<Boolean> doBackup() throws IOException {
+		notNull(service, BACKUP_NOT_ENABLED);
+		service.doBackupAll("主动全量备份", WebUtils.currentUserName());
+		return new JsonBean<>(true);
+	}
+}

+ 0 - 222
magic-api/src/main/java/org/ssssssss/magicapi/config/MagicFunctionManager.java

@@ -1,222 +0,0 @@
-package org.ssssssss.magicapi.config;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.ssssssss.magicapi.model.FunctionInfo;
-import org.ssssssss.magicapi.model.Group;
-import org.ssssssss.magicapi.model.Parameter;
-import org.ssssssss.magicapi.model.TreeNode;
-import org.ssssssss.magicapi.provider.FunctionServiceProvider;
-import org.ssssssss.magicapi.provider.GroupServiceProvider;
-import org.ssssssss.magicapi.script.ScriptManager;
-import org.ssssssss.magicapi.utils.PathUtils;
-import org.ssssssss.script.MagicResourceLoader;
-import org.ssssssss.script.MagicScriptContext;
-import org.ssssssss.script.exception.MagicExitException;
-import org.ssssssss.script.runtime.ExitValue;
-
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.function.Function;
-import java.util.stream.Collectors;
-
-/**
- * 函数映射管理
- *
- * @author mxd
- */
-public class MagicFunctionManager {
-
-	private static final Logger logger = LoggerFactory.getLogger(MagicFunctionManager.class);
-	private static final Map<String, FunctionInfo> MAPPINGS = new ConcurrentHashMap<>();
-	private final GroupServiceProvider groupServiceProvider;
-	private final FunctionServiceProvider functionServiceProvider;
-	private TreeNode<Group> groups;
-
-	public MagicFunctionManager(GroupServiceProvider groupServiceProvider, FunctionServiceProvider functionServiceProvider) {
-		this.groupServiceProvider = groupServiceProvider;
-		this.functionServiceProvider = functionServiceProvider;
-	}
-
-	public void registerFunctionLoader() {
-		MagicResourceLoader.addFunctionLoader((context, path) -> {
-			FunctionInfo info = MAPPINGS.get(path);
-			if (info != null) {
-				String scriptName = groupServiceProvider.getScriptName(info.getGroupId(), info.getName(), info.getPath());
-				List<Parameter> parameters = info.getParameters();
-				return (Function<Object[], Object>) objects -> {
-					MagicScriptContext functionContext = new MagicScriptContext(context.getRootVariables());
-					functionContext.setScriptName(scriptName);
-					if (objects != null) {
-						for (int i = 0, len = objects.length, size = parameters.size(); i < len && i < size; i++) {
-							functionContext.set(parameters.get(i).getName(), objects[i]);
-						}
-					}
-					Object value = ScriptManager.executeScript(info.getScript(), functionContext);
-					if (value instanceof ExitValue) {
-						throw new MagicExitException((ExitValue) value);
-					}
-					return value;
-				};
-			}
-			return null;
-		});
-	}
-
-	/**
-	 * 加载所有分组
-	 */
-	public synchronized void loadGroup() {
-		groups = groupServiceProvider.functionGroupTree();
-	}
-
-	public void registerAllFunction() {
-		loadGroup();
-		functionServiceProvider.listWithScript().stream()
-				.filter(it -> groupServiceProvider.getFullPath(it.getGroupId()) != null)
-				.forEach(this::register);
-	}
-
-	public boolean hasRegister(FunctionInfo info) {
-		String path = PathUtils.replaceSlash(Objects.toString(groupServiceProvider.getFullPath(info.getGroupId()), "") + "/" + info.getPath());
-		FunctionInfo functionInfo = MAPPINGS.get(path);
-		return functionInfo != null && !Objects.equals(info.getId(), functionInfo.getId());
-	}
-
-	public boolean hasRegister(Set<String> paths) {
-		return paths.stream().anyMatch(MAPPINGS::containsKey);
-	}
-
-	/**
-	 * 函数移动
-	 */
-	public boolean move(String id, String groupId) {
-		FunctionInfo info = MAPPINGS.get(id);
-		if (info == null) {
-			return false;
-		}
-		String path = Objects.toString(groupServiceProvider.getFullPath(groupId), "");
-		FunctionInfo functionInfo = MAPPINGS.get(PathUtils.replaceSlash(path + "/" + info.getPath()));
-		if (functionInfo != null && !Objects.equals(functionInfo.getId(), id)) {
-			return false;
-		}
-		unregister(id);
-		info.setGroupId(groupId);
-		register(info);
-		return true;
-	}
-
-
-	public void register(FunctionInfo functionInfo) {
-		if (functionInfo == null) {
-			return;
-		}
-		FunctionInfo oldFunctionInfo = MAPPINGS.get(functionInfo.getId());
-		if (oldFunctionInfo != null) {
-			// 完全一致时不用注册
-			if (functionInfo.equals(oldFunctionInfo)) {
-				return;
-			}
-			// 如果路径不一致,则需要取消注册
-			if (!Objects.equals(functionInfo.getPath(), oldFunctionInfo.getPath())) {
-				unregister(functionInfo.getId());
-			}
-		}
-		String path = Objects.toString(groupServiceProvider.getFullPath(functionInfo.getGroupId()), "");
-		MAPPINGS.put(functionInfo.getId(), functionInfo);
-		path = PathUtils.replaceSlash(path + "/" + functionInfo.getPath());
-		functionInfo.setMappingPath(path);
-		MAPPINGS.put(path, functionInfo);
-		logger.info("注册函数:[{}:{}]", functionInfo.getName(), path);
-	}
-
-	public List<FunctionInfo> getFunctionInfos() {
-		return MAPPINGS.values().stream().distinct().collect(Collectors.toList());
-	}
-
-	public FunctionInfo getFunctionInfo(String path) {
-		return MAPPINGS.get(path);
-	}
-
-	private boolean hasConflict(TreeNode<Group> group, String newPath) {
-		// 获取要移动的接口
-		List<FunctionInfo> infos = MAPPINGS.values().stream()
-				.filter(info -> Objects.equals(info.getGroupId(), group.getNode().getId()))
-				.distinct()
-				.collect(Collectors.toList());
-		// 判断是否有冲突
-		for (FunctionInfo info : infos) {
-			if (MAPPINGS.containsKey(PathUtils.replaceSlash(newPath + "/" + info.getPath()))) {
-				return true;
-			}
-		}
-		for (TreeNode<Group> child : group.getChildren()) {
-			if (hasConflict(child, newPath + "/" + Objects.toString(child.getNode().getPath(), ""))) {
-				return true;
-			}
-		}
-		return false;
-	}
-
-	public TreeNode<Group> findGroupTree(String groupId) {
-		return groups.findTreeNode(it -> it.getId().equals(groupId));
-	}
-
-	public boolean checkGroup(Group group) {
-		TreeNode<Group> oldTree = groups.findTreeNode((item) -> item.getId().equals(group.getId()));
-		// 如果只改了名字,则不做任何操作
-		if (Objects.equals(oldTree.getNode().getParentId(), group.getParentId()) &&
-				Objects.equals(oldTree.getNode().getPath(), group.getPath())) {
-			return true;
-		}
-		// 新的接口分组路径
-		String newPath = Objects.toString(groupServiceProvider.getFullPath(group.getParentId()), "");
-		// 检测冲突
-		return !hasConflict(oldTree, newPath + "/" + Objects.toString(group.getPath(), ""));
-	}
-
-	private void recurseUpdateGroup(TreeNode<Group> node, boolean updateGroupId) {
-		MAPPINGS.values().stream()
-				.filter(info -> Objects.equals(info.getGroupId(), node.getNode().getId()))
-				.distinct()
-				.collect(Collectors.toList())
-				.forEach(info -> {
-					unregister(info.getId());
-					if (updateGroupId) {
-						info.setGroupId(node.getNode().getId());
-					}
-					register(info);
-				});
-		for (TreeNode<Group> child : node.getChildren()) {
-			recurseUpdateGroup(child, false);
-		}
-	}
-
-	public boolean updateGroup(String groupId) {
-		loadGroup();    // 重新加载分组
-		TreeNode<Group> groupTreeNode = groups.findTreeNode((item) -> item.getId().equals(groupId));
-		recurseUpdateGroup(groupTreeNode, true);
-		return functionServiceProvider.reload(groupId);
-	}
-
-	public void deleteGroup(List<String> groupIds) {
-		MAPPINGS.values().stream()
-				.filter(info -> groupIds.contains(info.getGroupId()))
-				.distinct()
-				.collect(Collectors.toList())
-				.forEach(info -> unregister(info.getId()));
-		// 刷新分组缓存
-		loadGroup();
-	}
-
-	public void unregister(String id) {
-		FunctionInfo functionInfo = MAPPINGS.remove(id);
-		if (functionInfo != null) {
-			MAPPINGS.remove(functionInfo.getMappingPath());
-			logger.info("取消注册函数:[{},{}]", functionInfo.getName(), functionInfo.getMappingPath());
-		}
-	}
-}

+ 0 - 19
magic-api/src/main/java/org/ssssssss/magicapi/config/MagicModule.java

@@ -1,19 +0,0 @@
-package org.ssssssss.magicapi.config;
-
-import org.ssssssss.script.annotation.UnableCall;
-
-/**
- * 模块,主要用于import指令,import时根据模块名获取当前类如:<code>import assert</code>;
- *
- * @author mxd
- */
-public interface MagicModule {
-
-	/**
-	 * 获取模块名
-	 *
-	 * @return 返回模块名称
-	 */
-	@UnableCall
-	String getModuleName();
-}

+ 0 - 528
magic-api/src/main/java/org/ssssssss/magicapi/config/MappingHandlerMapping.java

@@ -1,528 +0,0 @@
-package org.ssssssss.magicapi.config;
-
-import org.apache.commons.lang3.StringUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestMethod;
-import org.springframework.web.context.request.NativeWebRequest;
-import org.springframework.web.context.request.RequestAttributes;
-import org.springframework.web.context.request.ServletWebRequest;
-import org.springframework.web.method.HandlerMethod;
-import org.springframework.web.servlet.HandlerMapping;
-import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
-import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
-import org.ssssssss.magicapi.controller.RequestHandler;
-import org.ssssssss.magicapi.model.ApiInfo;
-import org.ssssssss.magicapi.model.Constants;
-import org.ssssssss.magicapi.model.Group;
-import org.ssssssss.magicapi.model.TreeNode;
-import org.ssssssss.magicapi.provider.ApiServiceProvider;
-import org.ssssssss.magicapi.provider.GroupServiceProvider;
-import org.ssssssss.magicapi.utils.Mapping;
-import org.ssssssss.magicapi.utils.PathUtils;
-
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import java.lang.reflect.Method;
-import java.util.*;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
-/**
- * 请求映射
- *
- * @author mxd
- */
-public class MappingHandlerMapping {
-
-	/**
-	 * 已缓存的映射信息
-	 */
-	private static final Map<String, MappingNode> MAPPINGS = new ConcurrentHashMap<>();
-
-	private static final Logger logger = LoggerFactory.getLogger(MappingHandlerMapping.class);
-	/**
-	 * 接口分组
-	 */
-	private static TreeNode<Group> groups;
-	/**
-	 * 请求到达时处理的方法
-	 */
-	private final Method method = RequestHandler.class.getDeclaredMethod("invoke", HttpServletRequest.class, HttpServletResponse.class, Map.class, Map.class, Map.class);
-	/**
-	 * 统一接口前缀
-	 */
-	private final String prefix;
-	/**
-	 * 是否覆盖应用接口
-	 */
-	private final boolean allowOverride;
-	/**
-	 * 缓存已映射的接口信息
-	 */
-	private final List<ApiInfo> apiInfos = Collections.synchronizedList(new ArrayList<>());
-
-	private Mapping mappingHelper;
-	/**
-	 * 请求处理器
-	 */
-	private Object handler;
-	/**
-	 * 接口信息读取
-	 */
-	private ApiServiceProvider magicApiService;
-	/**
-	 * 分组信息读取
-	 */
-	private GroupServiceProvider groupServiceProvider;
-
-	public MappingHandlerMapping(String prefix, boolean allowOverride) throws NoSuchMethodException {
-		this.prefix = prefix;
-		this.allowOverride = allowOverride;
-	}
-
-	/**
-	 * 根据request获取对应的接口信息
-	 */
-	public static ApiInfo getMappingApiInfo(HttpServletRequest request) {
-		NativeWebRequest webRequest = new ServletWebRequest(request);
-		// 找到注册的路径
-		String requestMapping = (String) webRequest.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
-		// 根据请求方法和路径获取接口信息
-		return getMappingApiInfo(buildMappingKey(request.getMethod(), requestMapping));
-	}
-
-	/**
-	 * 根据绑定的key获取接口信息
-	 */
-	private static ApiInfo getMappingApiInfo(String key) {
-		return MAPPINGS.get(key).getInfo();
-	}
-
-	/**
-	 * 构建缓存map的key
-	 *
-	 * @param requestMethod  请求方法
-	 * @param requestMapping 请求路径
-	 */
-	private static String buildMappingKey(String requestMethod, String requestMapping) {
-		if (StringUtils.isNotBlank(requestMapping) && !requestMapping.startsWith("/")) {
-			requestMapping = "/" + requestMapping;
-		}
-		return Objects.toString(requestMethod, "GET").toUpperCase() + ":" + requestMapping;
-	}
-
-	public static Group findGroup(String groupId) {
-		TreeNode<Group> node = groups.findTreeNode(it -> it.getId().equals(groupId));
-		return node != null ? node.getNode() : null;
-	}
-
-	public static List<Group> findGroups(String groupId) {
-		List<Group> groups = new ArrayList<>();
-		Group group;
-		while (!Constants.ROOT_ID.equals(groupId) && (group = MappingHandlerMapping.findGroup(groupId)) != null) {
-			groups.add(group);
-			groupId = group.getParentId();
-		}
-		return groups;
-	}
-
-	public TreeNode<Group> findGroupTree(String groupId) {
-		return groups.findTreeNode(it -> it.getId().equals(groupId));
-	}
-
-	public void setRequestMappingHandlerMapping(RequestMappingHandlerMapping requestMappingHandlerMapping) {
-		this.mappingHelper = Mapping.create(requestMappingHandlerMapping);
-	}
-
-	public void setHandler(Object handler) {
-		this.handler = handler;
-	}
-
-	public void setMagicApiService(ApiServiceProvider magicApiService) {
-		this.magicApiService = magicApiService;
-	}
-
-	public void setGroupServiceProvider(GroupServiceProvider groupServiceProvider) {
-		this.groupServiceProvider = groupServiceProvider;
-	}
-
-	public List<ApiInfo> getApiInfos() {
-		return apiInfos;
-	}
-
-	/**
-	 * 加载所有分组
-	 */
-	public synchronized void loadGroup() {
-		groups = groupServiceProvider.apiGroupTree();
-	}
-
-	/**
-	 * 注册请求
-	 */
-	public void registerAllMapping() {
-		try {
-			loadGroup();
-			List<ApiInfo> list = magicApiService.listWithScript();
-			if (list != null) {
-				list = list.stream().filter(it -> groupServiceProvider.getFullPath(it.getGroupId()) != null).collect(Collectors.toList());
-				for (ApiInfo info : list) {
-					try {
-						// 当接口存在时,刷新缓存
-						registerMapping(info, true);
-					} catch (Exception e) {
-						logger.error("接口:{}注册失败", info.getName(), e);
-					}
-				}
-				List<String> resistedList = list.stream().map(ApiInfo::getId).collect(Collectors.toList());
-				Iterator<ApiInfo> iterator = apiInfos.iterator();
-				while (iterator.hasNext()) {
-					String oldId = iterator.next().getId();
-					// 当接口不存在时,取消注册接口
-					if (!resistedList.contains(oldId)) {
-						unregisterMapping(oldId, false);
-						iterator.remove();
-					}
-				}
-			}
-		} catch (Exception e) {
-			logger.info("注册接口映射失败", e);
-		}
-	}
-
-	/**
-	 * 根据请求方法和路径获取接口信息
-	 *
-	 * @param method         请求方法
-	 * @param requestMapping 请求路径
-	 */
-	public ApiInfo getApiInfo(String method, String requestMapping) {
-		MappingNode mappingNode = MAPPINGS.get(buildMappingKey(method, concatPath("", requestMapping)));
-		return mappingNode == null ? null : mappingNode.getInfo();
-	}
-
-	private boolean hasConflict(TreeNode<Group> group, String newPath) {
-		// 获取要移动的接口
-		List<ApiInfo> infos = apiInfos.stream().filter(info -> Objects.equals(info.getGroupId(), group.getNode().getId())).collect(Collectors.toList());
-		// 判断是否有冲突
-		for (ApiInfo info : infos) {
-			String path = concatPath(newPath, "/" + info.getPath());
-			String mappingKey = buildMappingKey(info.getMethod(), path);
-			MappingNode mappingNode = MAPPINGS.get(mappingKey);
-			if (mappingNode != null) {
-				if (mappingNode.getInfo().equals(info)) {
-					continue;
-				}
-				return true;
-			}
-			if (!allowOverride) {
-				Map<RequestMappingInfo, HandlerMethod> handlerMethods = this.mappingHelper.getHandlerMethods();
-				if (handlerMethods.get(getRequestMapping(info.getMethod(), path)) != null) {
-					return true;
-				}
-			}
-		}
-		for (TreeNode<Group> child : group.getChildren()) {
-			if (hasConflict(child, newPath + "/" + Objects.toString(child.getNode().getPath(), ""))) {
-				return true;
-			}
-		}
-		return false;
-	}
-
-	/**
-	 * 检测是否允许修改
-	 */
-	public boolean checkGroup(Group group) {
-		TreeNode<Group> oldTree = groups.findTreeNode((item) -> item.getId().equals(group.getId()));
-		// 如果没移动目录且没改路径,则只需要判断名字是否冲突
-		boolean parentIdEquals = Objects.equals(oldTree.getNode().getParentId(), group.getParentId());
-		boolean nameEquals = Objects.equals(oldTree.getNode().getName(), group.getName());
-		if (parentIdEquals && Objects.equals(oldTree.getNode().getPath(), group.getPath())) {
-			return nameEquals || !groupServiceProvider.exists(group);
-		}
-		// 检测名字是否冲突
-		boolean requiredChecked = (!parentIdEquals || !nameEquals);
-		if (requiredChecked && groupServiceProvider.exists(group)) {
-			return false;
-		}
-		// 新的接口分组路径
-		String newPath = groupServiceProvider.getFullPath(group.getParentId());
-		// 检测冲突
-		return !hasConflict(oldTree, newPath + "/" + Objects.toString(group.getPath(), ""));
-	}
-
-	public boolean hasRegister(Set<String> paths) {
-		return paths.stream().anyMatch(MAPPINGS::containsKey);
-	}
-
-	/**
-	 * 删除分组
-	 */
-	public void deleteGroup(List<String> groupIds) {
-		// 找到对应的所有接口
-		List<ApiInfo> deleteInfos = apiInfos.stream().filter(info -> groupIds.contains(info.getGroupId())).collect(Collectors.toList());
-		for (ApiInfo info : deleteInfos) {
-			unregisterMapping(info.getId(), true);
-		}
-		// 全部删除
-		apiInfos.removeAll(deleteInfos);
-		// 刷新分组缓存
-		loadGroup();
-	}
-
-	/**
-	 * 修改分组
-	 */
-	public boolean updateGroup(String groupId) {
-		loadGroup();    // 重新加载分组
-		TreeNode<Group> groupTreeNode = groups.findTreeNode((item) -> item.getId().equals(groupId));
-		recurseUpdateGroup(groupTreeNode, true);
-		return magicApiService.reload(groupId);
-	}
-
-	private void recurseUpdateGroup(TreeNode<Group> node, boolean updateGroupId) {
-		apiInfos.stream().filter(info -> Objects.equals(info.getGroupId(), node.getNode().getId())).forEach(info -> {
-			unregisterMapping(info.getId(), false);
-			if (updateGroupId) {
-				info.setGroupId(node.getNode().getId());
-			}
-			registerMapping(info, false);
-		});
-		for (TreeNode<Group> child : node.getChildren()) {
-			recurseUpdateGroup(child, false);
-		}
-	}
-
-	/**
-	 * 判断是否已注册
-	 */
-	public boolean hasRegisterMapping(ApiInfo info) {
-		if (info.getId() != null) {
-			MappingNode mappingNode = MAPPINGS.get(info.getId());
-			ApiInfo oldInfo = mappingNode == null ? null : mappingNode.getInfo();
-			if (oldInfo != null
-					&& Objects.equals(oldInfo.getGroupId(), info.getGroupId())
-					&& Objects.equals(oldInfo.getMethod(), info.getMethod())
-					&& Objects.equals(oldInfo.getPath(), info.getPath())) {
-				return false;
-			}
-		}
-		String mappingKey = getMappingKey(info);
-		if (MAPPINGS.containsKey(mappingKey)) {
-			return !MAPPINGS.get(mappingKey).getInfo().getId().equals(info.getId());
-		}
-		if (!allowOverride) {
-			Map<RequestMappingInfo, HandlerMethod> handlerMethods = this.mappingHelper.getHandlerMethods();
-			return handlerMethods.get(getRequestMapping(info)) != null;
-		}
-		return false;
-	}
-
-	/**
-	 * 接口移动
-	 */
-	public boolean move(String id, String groupId) {
-		MappingNode mappingNode = MAPPINGS.get(id);
-		if (mappingNode == null) {
-			return false;
-		}
-		ApiInfo copy = mappingNode.getInfo().copy();
-		copy.setGroupId(groupId);
-		if (hasRegisterMapping(copy)) {
-			return false;
-		}
-		unregisterMapping(id, true);
-		registerMapping(copy, true);
-		return true;
-	}
-
-	/**
-	 * 注册请求映射
-	 */
-	public void registerMapping(ApiInfo info, boolean delete) {
-		if (info == null) {
-			return;
-		}
-		// 先判断是否已注册,如果已注册,则先取消注册在进行注册。
-		MappingNode mappingNode = MAPPINGS.get(info.getId());
-		String newMappingKey = getMappingKey(info);
-		if (mappingNode != null) {
-			ApiInfo oldInfo = mappingNode.getInfo();
-			String oldMappingKey = mappingNode.getMappingKey();
-			// URL 路径一致时,刷新脚本内容即可
-			if (Objects.equals(oldMappingKey, newMappingKey)) {
-				if (!info.equals(oldInfo)) {
-					mappingNode.setInfo(info);
-					MAPPINGS.get(newMappingKey).setInfo(info);
-					if (delete) {
-						refreshCache(info);
-					}
-					logger.info("刷新接口:{},{}", info.getName(), newMappingKey);
-				}
-				return;
-			}
-			// URL不一致时,需要取消注册旧接口,重新注册新接口
-			logger.info("取消注册接口:{},{}", oldInfo.getName(), oldMappingKey);
-			// 取消注册
-			MAPPINGS.remove(oldMappingKey);
-			mappingHelper.unregister(getRequestMapping(oldInfo));
-		}
-		mappingNode = new MappingNode(info);
-		mappingNode.setMappingKey(newMappingKey);
-		// 注册
-		RequestMappingInfo requestMapping = getRequestMapping(info);
-		mappingNode.setRequestMappingInfo(requestMapping);
-		mappingNode.setInfo(info);
-		// 如果与应用冲突
-		if (!overrideApplicationMapping(requestMapping)) {
-			logger.error("接口{},{}与应用冲突,无法注册", info.getName(), newMappingKey);
-			return;
-		}
-		logger.info("注册接口:{},{}", info.getName(), newMappingKey);
-		MAPPINGS.put(info.getId(), mappingNode);
-		MAPPINGS.put(newMappingKey, mappingNode);
-		registerMapping(requestMapping, handler, method);
-		if (delete) {
-			// 刷新缓存
-			refreshCache(info);
-		}
-	}
-
-	private void refreshCache(ApiInfo info) {
-		apiInfos.removeIf(i -> i.getId().equalsIgnoreCase(info.getId()));
-		apiInfos.add(info);
-	}
-
-	private void registerMapping(RequestMappingInfo requestMapping, Object handler, Method method) {
-		mappingHelper.register(requestMapping, handler, method);
-	}
-
-	/**
-	 * 取消注册请求映射
-	 */
-	public void unregisterMapping(String id, boolean delete) {
-		MappingNode mappingNode = MAPPINGS.remove(id);
-		if (mappingNode != null) {
-			ApiInfo info = mappingNode.getInfo();
-			logger.info("取消注册接口:{}", info.getName());
-			MAPPINGS.remove(mappingNode.getMappingKey());
-			mappingHelper.unregister(mappingNode.getRequestMappingInfo());
-			if (delete) {
-				// 刷新缓存
-				apiInfos.removeIf(i -> i.getId().equalsIgnoreCase(info.getId()));
-			}
-		}
-	}
-
-	/**
-	 * 根据接口信息获取绑定map的key
-	 */
-	private String getMappingKey(ApiInfo info) {
-		return buildMappingKey(info.getMethod(), getRequestPath(info.getGroupId(), info.getPath()));
-	}
-
-	/**
-	 * 处理前缀
-	 *
-	 * @param groupId 分组ID
-	 * @param path    请求路径
-	 */
-	public String getRequestPath(String groupId, String path) {
-		return concatPath(groupServiceProvider.getFullPath(groupId), path);
-
-	}
-
-	public void registerController(Object target, String base) {
-		Method[] methods = target.getClass().getDeclaredMethods();
-		for (Method method : methods) {
-			RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
-			if (requestMapping != null) {
-				String[] paths = Stream.of(requestMapping.value()).map(value -> base + value).toArray(String[]::new);
-				mappingHelper.register(mappingHelper.paths(paths).build(), target, method);
-			}
-		}
-	}
-
-	private String concatPath(String groupPath, String path) {
-		path = groupPath + "/" + path;
-		if (prefix != null) {
-			path = prefix + "/" + path;
-		}
-		path = PathUtils.replaceSlash(path);
-		if (path.startsWith("/")) {
-			return path.substring(1);
-		}
-		return path;
-	}
-
-	/**
-	 * 覆盖应用接口
-	 */
-	private boolean overrideApplicationMapping(RequestMappingInfo requestMapping) {
-		if (mappingHelper.getHandlerMethods().containsKey(requestMapping)) {
-			if (!allowOverride) {
-				// 不允许覆盖
-				return false;
-			}
-			logger.warn("取消注册应用接口:{}", requestMapping);
-			// 取消注册原接口
-			mappingHelper.unregister(requestMapping);
-		}
-		return true;
-	}
-
-	/**
-	 * 根据接口信息构建 RequestMappingInfo
-	 */
-	private RequestMappingInfo getRequestMapping(ApiInfo info) {
-		return mappingHelper.paths(getRequestPath(info.getGroupId(), info.getPath())).methods(RequestMethod.valueOf(info.getMethod().toUpperCase())).build();
-	}
-
-	/**
-	 * 根据接口信息构建 RequestMappingInfo
-	 */
-	private RequestMappingInfo getRequestMapping(String method, String path) {
-		return mappingHelper.paths(path).methods(RequestMethod.valueOf(method.toUpperCase())).build();
-	}
-
-	static class MappingNode {
-
-		private ApiInfo info;
-
-		private String mappingKey;
-
-		private RequestMappingInfo requestMappingInfo;
-
-		public MappingNode(ApiInfo info) {
-			this.info = info;
-		}
-
-		public ApiInfo getInfo() {
-			return info;
-		}
-
-		public void setInfo(ApiInfo info) {
-			this.info = info;
-		}
-
-		public String getMappingKey() {
-			return mappingKey;
-		}
-
-		public void setMappingKey(String mappingKey) {
-			this.mappingKey = mappingKey;
-		}
-
-		public RequestMappingInfo getRequestMappingInfo() {
-			return requestMappingInfo;
-		}
-
-		public void setRequestMappingInfo(RequestMappingInfo requestMappingInfo) {
-			this.requestMappingInfo = requestMappingInfo;
-		}
-	}
-}

+ 0 - 26
magic-api/src/main/java/org/ssssssss/magicapi/config/MessageType.java

@@ -1,26 +0,0 @@
-package org.ssssssss.magicapi.config;
-
-/**
- * WebSocket 消息类型
- *
- * @author mxd
- */
-public enum MessageType {
-	/* S -> C message */
-	/* 日志消息 */
-	LOG,
-	/* 进入断点 */
-	BREAKPOINT,
-	/* 请求接口发生异常 */
-	EXCEPTION,
-
-	/* C -> S message */
-	/* 设置断点 */
-	SET_BREAKPOINT,
-	/* 恢复断点 */
-	RESUME_BREAKPOINT,
-	/* 设置 Session ID */
-	SET_SESSION_ID,
-	/* 登录 */
-	LOGIN
-}

+ 0 - 133
magic-api/src/main/java/org/ssssssss/magicapi/config/WebSocketSessionManager.java

@@ -1,133 +0,0 @@
-package org.ssssssss.magicapi.config;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.web.socket.TextMessage;
-import org.ssssssss.magicapi.model.Constants;
-import org.ssssssss.magicapi.model.MagicConsoleSession;
-import org.ssssssss.magicapi.model.MagicNotify;
-import org.ssssssss.magicapi.provider.MagicNotifyService;
-import org.ssssssss.magicapi.utils.JsonUtils;
-import org.ssssssss.script.MagicScriptDebugContext;
-
-import java.io.IOException;
-import java.util.Map;
-import java.util.Objects;
-import java.util.concurrent.ConcurrentHashMap;
-
-/**
- * WebSocket Session 管理
- *
- * @author mxd
- */
-public class WebSocketSessionManager {
-
-	private static final Logger logger = LoggerFactory.getLogger(WebSocketSessionManager.class);
-
-	private static final Map<String, MagicConsoleSession> SESSION = new ConcurrentHashMap<>();
-
-	private static MagicNotifyService magicNotifyService;
-
-	private static String instanceId;
-
-	public static void add(MagicConsoleSession session) {
-		SESSION.put(session.getId(), session);
-	}
-
-	public static void remove(MagicConsoleSession session) {
-		if (session.getId() != null) {
-			remove(session.getId());
-		}
-	}
-
-	public static void remove(String sessionId) {
-		SESSION.remove(sessionId);
-	}
-
-	public static void sendToAll(MessageType messageType, Object... values) {
-		String content = buildMessage(messageType, values);
-		sendToAll(content);
-	}
-
-	private static void sendToAll(String content) {
-		SESSION.values().stream().filter(MagicConsoleSession::writeable).forEach(session -> sendBySession(session, content));
-		sendToOther(null, content);
-	}
-
-	public static void sendBySessionId(String sessionId, MessageType messageType, Object... values) {
-		MagicConsoleSession session = findSession(sessionId);
-		String content = buildMessage(messageType, values);
-		if (session != null && session.writeable()) {
-			sendBySession(session, content);
-		} else {
-			sendToOther(sessionId, content);
-		}
-	}
-
-	private static void sendToOther(String sessionId, String content) {
-		if (magicNotifyService != null) {
-			// 通知其他机器去发送消息
-			magicNotifyService.sendNotify(new MagicNotify(instanceId, Constants.NOTIFY_WS_S_C, sessionId, content));
-		}
-	}
-
-	private static String buildMessage(MessageType messageType, Object... values) {
-		StringBuilder builder = new StringBuilder(messageType.name().toLowerCase());
-		if (values != null) {
-			for (int i = 0, len = values.length; i < len; i++) {
-				builder.append(",");
-				Object value = values[i];
-				if (i + 1 < len || value instanceof CharSequence || value instanceof Number) {
-					builder.append(value);
-				} else {
-					builder.append(JsonUtils.toJsonString(value));
-				}
-			}
-		}
-		return builder.toString();
-	}
-
-	public static void sendBySessionId(String sessionId, String content) {
-		if (sessionId == null) {
-			sendToAll(content);
-		} else {
-			MagicConsoleSession session = findSession(sessionId);
-			if (session != null) {
-				sendBySession(session, content);
-			}
-		}
-	}
-
-	public static void sendBySession(MagicConsoleSession session, String content) {
-		try {
-			session.getWebSocketSession().sendMessage(new TextMessage(content));
-		} catch (IOException e) {
-			logger.error("发送WebSocket消息失败", e);
-		}
-	}
-
-	public static MagicConsoleSession findSession(String sessionId) {
-		return SESSION.values().stream()
-				.filter(it -> Objects.equals(sessionId, it.getSessionId()))
-				.findFirst()
-				.orElse(null);
-	}
-
-	public static void setMagicNotifyService(MagicNotifyService magicNotifyService) {
-		WebSocketSessionManager.magicNotifyService = magicNotifyService;
-	}
-
-	public static void setInstanceId(String instanceId) {
-		WebSocketSessionManager.instanceId = instanceId;
-	}
-
-	public static void createSession(String sessionId, MagicScriptDebugContext debugContext) {
-		MagicConsoleSession consoleSession = findSession(sessionId);
-		if (consoleSession == null) {
-			consoleSession = new MagicConsoleSession(sessionId, debugContext);
-			SESSION.put(sessionId, consoleSession);
-		} else {
-			consoleSession.setMagicScriptDebugContext(debugContext);
-		}
-	}
-}

+ 0 - 154
magic-api/src/main/java/org/ssssssss/magicapi/controller/MagicAPIController.java

@@ -1,154 +0,0 @@
-package org.ssssssss.magicapi.controller;
-
-import org.apache.commons.lang3.StringUtils;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.ResponseBody;
-import org.ssssssss.magicapi.config.MagicConfiguration;
-import org.ssssssss.magicapi.config.Valid;
-import org.ssssssss.magicapi.interceptor.Authorization;
-import org.ssssssss.magicapi.model.ApiInfo;
-import org.ssssssss.magicapi.model.Backup;
-import org.ssssssss.magicapi.model.Constants;
-import org.ssssssss.magicapi.model.JsonBean;
-
-import javax.servlet.http.HttpServletRequest;
-import java.util.Comparator;
-import java.util.List;
-import java.util.stream.Collectors;
-
-/**
- * 接口相关操作
- *
- * @author mxd
- */
-public class MagicAPIController extends MagicController implements MagicExceptionHandler {
-
-	public MagicAPIController(MagicConfiguration configuration) {
-		super(configuration);
-	}
-
-	/**
-	 * 删除接口
-	 *
-	 * @param id 接口ID
-	 */
-	@RequestMapping("/delete")
-	@ResponseBody
-	@Valid(readonly = false)
-	public JsonBean<Boolean> delete(HttpServletRequest request, String id) {
-		ApiInfo apiInfo = getApiInfo(id);
-		isTrue(allowVisit(request, Authorization.DELETE, apiInfo), PERMISSION_INVALID);
-		isTrue(!Constants.LOCK.equals(apiInfo.getLock()), RESOURCE_LOCKED);
-		return new JsonBean<>(magicAPIService.deleteApi(id));
-	}
-
-	/**
-	 * 查询所有接口
-	 */
-	@RequestMapping("/list")
-	@ResponseBody
-	public JsonBean<List<ApiInfo>> list(HttpServletRequest request) {
-		return new JsonBean<>(magicAPIService.apiList()
-				.stream()
-				.filter(it -> allowVisit(request, Authorization.VIEW, it))
-				.map(ApiInfo::simple)
-				.collect(Collectors.toList())
-		);
-	}
-
-	/**
-	 * 查询接口详情
-	 *
-	 * @param id 接口ID
-	 */
-	@RequestMapping("/get")
-	@ResponseBody
-	public JsonBean<ApiInfo> get(HttpServletRequest request, String id) {
-		isTrue(allowVisit(request, Authorization.VIEW, getApiInfo(id)), PERMISSION_INVALID);
-		return new JsonBean<>(magicAPIService.getApiInfo(id));
-	}
-
-	/**
-	 * 查询历史记录
-	 *
-	 * @param id 接口ID
-	 */
-	@RequestMapping("/backups")
-	@ResponseBody
-	public JsonBean<List<Backup>> backupList(HttpServletRequest request, String id) {
-		isTrue(allowVisit(request, Authorization.VIEW, getApiInfo(id)), PERMISSION_INVALID);
-		return new JsonBean<>(magicBackupService.backupById(id));
-	}
-
-	/**
-	 * 获取历史记录
-	 *
-	 * @param id        接口ID
-	 * @param timestamp 时间点
-	 */
-	@RequestMapping("/backup/get")
-	@ResponseBody
-	public JsonBean<Backup> backups(HttpServletRequest request, String id, Long timestamp) {
-		isTrue(allowVisit(request, Authorization.VIEW, getApiInfo(id)), PERMISSION_INVALID);
-		return new JsonBean<>(magicBackupService.backupInfo(id, timestamp));
-	}
-
-	/**
-	 * 移动接口
-	 */
-	@RequestMapping("/api/move")
-	@ResponseBody
-	@Valid(readonly = false)
-	public JsonBean<Boolean> apiMove(HttpServletRequest request, String id, String groupId) {
-		ApiInfo apiInfo = getApiInfo(id).copy();
-		// 新的分组ID
-		apiInfo.setGroupId(groupId);
-		isTrue(allowVisit(request, Authorization.SAVE, apiInfo), PERMISSION_INVALID);
-		isTrue(!Constants.LOCK.equals(apiInfo.getLock()), RESOURCE_LOCKED);
-		return new JsonBean<>(magicAPIService.moveApi(id, groupId));
-	}
-
-	/**
-	 * 保存接口
-	 */
-	@RequestMapping("/save")
-	@ResponseBody
-	@Valid(readonly = false)
-	public JsonBean<String> save(HttpServletRequest request, @RequestBody ApiInfo info) {
-		isTrue(allowVisit(request, Authorization.SAVE, info), PERMISSION_INVALID);
-		if (StringUtils.isNotBlank(info.getId())) {
-			ApiInfo oldInfo = getApiInfo(info.getId());
-			isTrue(!Constants.LOCK.equals(oldInfo.getLock()), RESOURCE_LOCKED);
-		}
-		return new JsonBean<>(magicAPIService.saveApi(info));
-	}
-
-	/**
-	 * 锁定接口
-	 */
-	@RequestMapping("/lock")
-	@ResponseBody
-	@Valid(readonly = false)
-	public JsonBean<Boolean> lock(HttpServletRequest request, String id) {
-		isTrue(allowVisit(request, Authorization.LOCK, getApiInfo(id)), PERMISSION_INVALID);
-		return new JsonBean<>(magicAPIService.lockApi(id));
-	}
-
-	/**
-	 * 解锁接口
-	 */
-	@RequestMapping("/unlock")
-	@ResponseBody
-	@Valid(readonly = false)
-	public JsonBean<Boolean> unlock(HttpServletRequest request, String id) {
-		isTrue(allowVisit(request, Authorization.UNLOCK, getApiInfo(id)), PERMISSION_INVALID);
-		return new JsonBean<>(magicAPIService.unlockApi(id));
-	}
-
-	private ApiInfo getApiInfo(String id) {
-		ApiInfo apiInfo = magicAPIService.getApiInfo(id);
-		notNull(apiInfo, API_NOT_FOUND);
-		return apiInfo;
-	}
-}

+ 0 - 86
magic-api/src/main/java/org/ssssssss/magicapi/controller/MagicDataSourceController.java

@@ -1,86 +0,0 @@
-package org.ssssssss.magicapi.controller;
-
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.ResponseBody;
-import org.ssssssss.magicapi.config.MagicConfiguration;
-import org.ssssssss.magicapi.config.Valid;
-import org.ssssssss.magicapi.interceptor.Authorization;
-import org.ssssssss.magicapi.model.DataSourceInfo;
-import org.ssssssss.magicapi.model.JsonBean;
-
-import javax.servlet.http.HttpServletRequest;
-import java.util.List;
-import java.util.stream.Collectors;
-
-/**
- * 数据源相关操作
- *
- * @author mxd
- */
-public class MagicDataSourceController extends MagicController implements MagicExceptionHandler {
-
-	public MagicDataSourceController(MagicConfiguration configuration) {
-		super(configuration);
-	}
-
-	/**
-	 * 查询数据源列表
-	 */
-	@RequestMapping("/datasource/list")
-	@ResponseBody
-	public JsonBean<List<DataSourceInfo>> list(HttpServletRequest request) {
-		return new JsonBean<>(magicAPIService.datasourceList()
-				.stream()
-				.filter(it -> allowVisit(request, Authorization.VIEW, it))
-				.collect(Collectors.toList())
-		);
-	}
-
-	@RequestMapping("/datasource/test")
-	@ResponseBody
-	public JsonBean<String> test(@RequestBody DataSourceInfo properties) {
-		return new JsonBean<>(magicAPIService.testDataSource(properties));
-	}
-
-	/**
-	 * 保存数据源
-	 *
-	 * @param properties 数据源配置信息
-	 */
-	@RequestMapping("/datasource/save")
-	@Valid(readonly = false)
-	@ResponseBody
-	public JsonBean<String> save(HttpServletRequest request, @RequestBody DataSourceInfo properties) {
-		isTrue(allowVisit(request, Authorization.SAVE, properties), PERMISSION_INVALID);
-		return new JsonBean<>(magicAPIService.saveDataSource(properties));
-	}
-
-	/**
-	 * 删除数据源
-	 *
-	 * @param id 数据源ID
-	 */
-	@RequestMapping("/datasource/delete")
-	@Valid(readonly = false)
-	@ResponseBody
-	public JsonBean<Boolean> delete(HttpServletRequest request, String id) {
-		DataSourceInfo dataSource = getDataSourceInfo(id);
-		isTrue(allowVisit(request, Authorization.DELETE, dataSource), PERMISSION_INVALID);
-		return new JsonBean<>(magicAPIService.deleteDataSource(id));
-	}
-
-	@RequestMapping("/datasource/detail")
-	@ResponseBody
-	public JsonBean<DataSourceInfo> detail(HttpServletRequest request, String id) {
-		DataSourceInfo dataSource = getDataSourceInfo(id);
-		isTrue(allowVisit(request, Authorization.VIEW, dataSource), PERMISSION_INVALID);
-		return new JsonBean<>(dataSource);
-	}
-
-	private DataSourceInfo getDataSourceInfo(String id) {
-		DataSourceInfo dataSource = magicAPIService.getDataSource(id);
-		notNull(dataSource, DATASOURCE_NOT_FOUND);
-		return dataSource;
-	}
-}

+ 0 - 127
magic-api/src/main/java/org/ssssssss/magicapi/controller/MagicFunctionController.java

@@ -1,127 +0,0 @@
-package org.ssssssss.magicapi.controller;
-
-import org.apache.commons.lang3.StringUtils;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.ResponseBody;
-import org.ssssssss.magicapi.config.MagicConfiguration;
-import org.ssssssss.magicapi.config.Valid;
-import org.ssssssss.magicapi.interceptor.Authorization;
-import org.ssssssss.magicapi.model.Backup;
-import org.ssssssss.magicapi.model.Constants;
-import org.ssssssss.magicapi.model.FunctionInfo;
-import org.ssssssss.magicapi.model.JsonBean;
-
-import javax.servlet.http.HttpServletRequest;
-import java.util.Comparator;
-import java.util.List;
-import java.util.stream.Collectors;
-
-/**
- * 函数相关操作
- *
- * @author mxd
- */
-public class MagicFunctionController extends MagicController implements MagicExceptionHandler {
-
-
-	public MagicFunctionController(MagicConfiguration configuration) {
-		super(configuration);
-	}
-
-	@RequestMapping("/function/list")
-	@ResponseBody
-	public JsonBean<List<FunctionInfo>> list(HttpServletRequest request) {
-		return new JsonBean<>(magicAPIService.functionList()
-				.stream()
-				.filter(it -> allowVisit(request, Authorization.VIEW, it))
-				.collect(Collectors.toList())
-		);
-	}
-
-	@RequestMapping("/function/get")
-	@ResponseBody
-	public JsonBean<FunctionInfo> get(HttpServletRequest request, String id) {
-		isTrue(allowVisit(request, Authorization.VIEW, getFunctionInfo(id)), PERMISSION_INVALID);
-		return new JsonBean<>(magicAPIService.getFunctionInfo(id));
-	}
-
-	@RequestMapping("/function/backup/get")
-	@ResponseBody
-	public JsonBean<Backup> backups(HttpServletRequest request, String id, Long timestamp) {
-		isTrue(allowVisit(request, Authorization.VIEW, getFunctionInfo(id)), PERMISSION_INVALID);
-		return new JsonBean<>(magicBackupService.backupInfo(id, timestamp));
-	}
-
-	@RequestMapping("/function/backups")
-	@ResponseBody
-	public JsonBean<List<Backup>> backupList(HttpServletRequest request, String id) {
-		isTrue(allowVisit(request, Authorization.VIEW, getFunctionInfo(id)), PERMISSION_INVALID);
-		return new JsonBean<>(magicBackupService.backupById(id)
-				.stream()
-				.sorted(Comparator.comparing(Backup::getCreateDate).reversed())
-				.collect(Collectors.toList()));
-	}
-
-	@RequestMapping("/function/move")
-	@ResponseBody
-	@Valid(readonly = false)
-	public JsonBean<Boolean> move(HttpServletRequest request, String id, String groupId) {
-		FunctionInfo functionInfo = getFunctionInfo(id);
-		functionInfo.setGroupId(groupId);
-		isTrue(allowVisit(request, Authorization.SAVE, functionInfo), PERMISSION_INVALID);
-		isTrue(!Constants.LOCK.equals(functionInfo.getLock()), RESOURCE_LOCKED);
-		return new JsonBean<>(magicAPIService.moveFunction(id, groupId));
-	}
-
-
-	@RequestMapping("/function/save")
-	@ResponseBody
-	@Valid(readonly = false)
-	public JsonBean<String> save(HttpServletRequest request, @RequestBody FunctionInfo functionInfo) {
-		isTrue(allowVisit(request, Authorization.SAVE, functionInfo), PERMISSION_INVALID);
-		if (StringUtils.isNotBlank(functionInfo.getId())) {
-			FunctionInfo oldInfo = getFunctionInfo(functionInfo.getId());
-			isTrue(!Constants.LOCK.equals(oldInfo.getLock()), RESOURCE_LOCKED);
-		}
-		return new JsonBean<>(magicAPIService.saveFunction(functionInfo));
-	}
-
-	@RequestMapping("/function/delete")
-	@ResponseBody
-	@Valid(readonly = false)
-	public JsonBean<Boolean> delete(HttpServletRequest request, String id) {
-		FunctionInfo info = getFunctionInfo(id);
-		isTrue(allowVisit(request, Authorization.DELETE, info), PERMISSION_INVALID);
-		isTrue(!Constants.LOCK.equals(info.getLock()), RESOURCE_LOCKED);
-		return new JsonBean<>(magicAPIService.deleteFunction(id));
-	}
-
-	/**
-	 * 锁定函数
-	 */
-	@RequestMapping("/function/lock")
-	@ResponseBody
-	@Valid(readonly = false)
-	public JsonBean<Boolean> lock(HttpServletRequest request, String id) {
-		isTrue(allowVisit(request, Authorization.LOCK, getFunctionInfo(id)), PERMISSION_INVALID);
-		return new JsonBean<>(magicAPIService.lockFunction(id));
-	}
-
-	/**
-	 * 解锁函数
-	 */
-	@RequestMapping("/function/unlock")
-	@ResponseBody
-	@Valid(readonly = false)
-	public JsonBean<Boolean> unlock(HttpServletRequest request, String id) {
-		isTrue(allowVisit(request, Authorization.UNLOCK, getFunctionInfo(id)), PERMISSION_INVALID);
-		return new JsonBean<>(magicAPIService.unlockFunction(id));
-	}
-
-	public FunctionInfo getFunctionInfo(String id) {
-		FunctionInfo functionInfo = magicAPIService.getFunctionInfo(id);
-		notNull(functionInfo, FUNCTION_NOT_FOUND);
-		return functionInfo;
-	}
-}

+ 0 - 102
magic-api/src/main/java/org/ssssssss/magicapi/controller/MagicGroupController.java

@@ -1,102 +0,0 @@
-package org.ssssssss.magicapi.controller;
-
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.ResponseBody;
-import org.ssssssss.magicapi.adapter.Resource;
-import org.ssssssss.magicapi.adapter.resource.FileResource;
-import org.ssssssss.magicapi.config.MagicConfiguration;
-import org.ssssssss.magicapi.config.Valid;
-import org.ssssssss.magicapi.interceptor.Authorization;
-import org.ssssssss.magicapi.model.Constants;
-import org.ssssssss.magicapi.model.Group;
-import org.ssssssss.magicapi.model.JsonBean;
-
-import javax.servlet.http.HttpServletRequest;
-import java.util.List;
-import java.util.stream.Collectors;
-
-/**
- * 分组相关操作
- *
- * @author mxd
- */
-public class MagicGroupController extends MagicController implements MagicExceptionHandler {
-
-	public MagicGroupController(MagicConfiguration configuration) {
-		super(configuration);
-	}
-
-	/**
-	 * 删除分组
-	 */
-	@RequestMapping("/group/delete")
-	@ResponseBody
-	@Valid(readonly = false)
-	public JsonBean<Boolean> deleteGroup(HttpServletRequest request, String groupId) {
-		Group group = magicAPIService.getGroup(groupId);
-		notNull(group, GROUP_NOT_FOUND);
-		isTrue(allowVisit(request, Authorization.DELETE, group), PERMISSION_INVALID);
-		return new JsonBean<>(magicAPIService.deleteGroup(groupId));
-	}
-
-	/**
-	 * 修改分组
-	 */
-	@RequestMapping("/group/update")
-	@ResponseBody
-	@Valid(readonly = false)
-	public synchronized JsonBean<Boolean> groupUpdate(HttpServletRequest request, @RequestBody Group group) {
-		isTrue(allowVisit(request, Authorization.SAVE, group), PERMISSION_INVALID);
-		if (magicAPIService.updateGroup(group)) {
-			return new JsonBean<>(true);
-		}
-		return new JsonBean<>(GROUP_CONFLICT);
-	}
-
-	/**
-	 * 查询所有分组
-	 */
-	@RequestMapping("/group/list")
-	@ResponseBody
-	public JsonBean<List<Group>> groupList(HttpServletRequest request, String type) {
-		return new JsonBean<>(magicAPIService.groupList(type)
-				.stream()
-				.filter(it -> allowVisit(request, Authorization.VIEW, it))
-				.collect(Collectors.toList())
-		);
-	}
-
-	/**
-	 * 创建分组
-	 */
-	@RequestMapping("/group/create")
-	@ResponseBody
-	@Valid(readonly = false)
-	public JsonBean<String> createGroup(HttpServletRequest request, @RequestBody Group group) {
-		isTrue(allowVisit(request, Authorization.SAVE, group), PERMISSION_INVALID);
-		Resource resource = configuration.getWorkspace();
-		if(resource instanceof FileResource){
-			isTrue(resource.exists(), FILE_PATH_NOT_EXISTS);
-		}
-		return new JsonBean<>(magicAPIService.createGroup(group));
-	}
-
-	/**
-	 * 复制分组
-	 */
-	@RequestMapping("/group/copy")
-	@ResponseBody
-	@Valid(readonly = false)
-	public JsonBean<String> copyGroup(HttpServletRequest request, String src, String target) {
-		Group group = magicAPIService.getGroup(src);
-		notNull(group, GROUP_NOT_FOUND);
-		if (!Constants.ROOT_ID.equals(target)) {
-			Group targetGroup = magicAPIService.getGroup(target);
-			notNull(targetGroup, GROUP_NOT_FOUND);
-			isTrue(allowVisit(request, Authorization.SAVE, targetGroup), PERMISSION_INVALID);
-		}
-		isTrue(allowVisit(request, Authorization.VIEW, group), PERMISSION_INVALID);
-		return new JsonBean<>(magicAPIService.copyGroup(src, target));
-	}
-}

+ 0 - 33
magic-api/src/main/java/org/ssssssss/magicapi/controller/MagicWorkbenchHandler.java

@@ -1,33 +0,0 @@
-package org.ssssssss.magicapi.controller;
-
-import org.ssssssss.magicapi.config.Message;
-import org.ssssssss.magicapi.config.MessageType;
-import org.ssssssss.magicapi.config.WebSocketSessionManager;
-import org.ssssssss.magicapi.exception.MagicLoginException;
-import org.ssssssss.magicapi.interceptor.AuthorizationInterceptor;
-import org.ssssssss.magicapi.model.MagicConsoleSession;
-
-/**
- * UI上其它操作处理
- *
- * @author mxd
- */
-public class MagicWorkbenchHandler {
-
-	private final AuthorizationInterceptor authorizationInterceptor;
-
-	public MagicWorkbenchHandler(AuthorizationInterceptor authorizationInterceptor) {
-		this.authorizationInterceptor = authorizationInterceptor;
-	}
-
-	@Message(MessageType.LOGIN)
-	public void onLogin(MagicConsoleSession session, String token) {
-		try {
-			if (!authorizationInterceptor.requireLogin() || authorizationInterceptor.getUserByToken(token) != null) {
-				WebSocketSessionManager.add(session);
-			}
-		} catch (MagicLoginException ignored) {
-
-		}
-	}
-}

+ 19 - 0
magic-api/src/main/java/org/ssssssss/magicapi/core/annotation/MagicModule.java

@@ -0,0 +1,19 @@
+package org.ssssssss.magicapi.core.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * 模块,主要用于import指令,import时根据模块名获取当前类如:<code>import assert</code>;
+ *
+ * @author mxd
+ */
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface MagicModule {
+
+	/**
+	 * 模块名
+	 */
+	String value();
+}

+ 3 - 1
magic-api/src/main/java/org/ssssssss/magicapi/config/Message.java → magic-api/src/main/java/org/ssssssss/magicapi/core/annotation/Message.java

@@ -1,4 +1,6 @@
-package org.ssssssss.magicapi.config;
+package org.ssssssss.magicapi.core.annotation;
+
+import org.ssssssss.magicapi.core.config.MessageType;
 
 import java.lang.annotation.*;
 

+ 2 - 2
magic-api/src/main/java/org/ssssssss/magicapi/config/Valid.java → magic-api/src/main/java/org/ssssssss/magicapi/core/annotation/Valid.java

@@ -1,6 +1,6 @@
-package org.ssssssss.magicapi.config;
+package org.ssssssss.magicapi.core.annotation;
 
-import org.ssssssss.magicapi.interceptor.Authorization;
+import org.ssssssss.magicapi.core.interceptor.Authorization;
 
 import java.lang.annotation.*;
 

+ 13 - 26
magic-api-spring-boot-starter/src/main/java/org/ssssssss/magicapi/spring/boot/starter/BackupConfig.java → magic-api/src/main/java/org/ssssssss/magicapi/core/config/Backup.java

@@ -1,22 +1,17 @@
-package org.ssssssss.magicapi.spring.boot.starter;
+package org.ssssssss.magicapi.core.config;
 
 /**
  * 备份配置
  *
  * @author mxd
- * @since 1.3.5
+ * @since 2.0.0
  */
-public class BackupConfig {
+public class Backup {
 
 	/**
-	 * 存储类型,可选 file, database
+	 * 是否启用备份配置,默认不启用
 	 */
-	private String resourceType = "file";
-
-	/**
-	 * 存储位置,选择存储为文件时专用
-	 */
-	private String location = "/data/magic-api/backup";
+	private boolean enable = false;
 
 	/**
 	 * 保留天数,<=0 为不限制
@@ -33,22 +28,6 @@ public class BackupConfig {
 	 */
 	private String datasource;
 
-	public String getResourceType() {
-		return resourceType;
-	}
-
-	public void setResourceType(String resourceType) {
-		this.resourceType = resourceType;
-	}
-
-	public String getLocation() {
-		return location;
-	}
-
-	public void setLocation(String location) {
-		this.location = location;
-	}
-
 	public int getMaxHistory() {
 		return maxHistory;
 	}
@@ -72,4 +51,12 @@ public class BackupConfig {
 	public void setDatasource(String datasource) {
 		this.datasource = datasource;
 	}
+
+	public boolean isEnable() {
+		return enable;
+	}
+
+	public void setEnable(boolean enable) {
+		this.enable = enable;
+	}
 }

+ 2 - 2
magic-api-spring-boot-starter/src/main/java/org/ssssssss/magicapi/spring/boot/starter/CacheConfig.java → magic-api/src/main/java/org/ssssssss/magicapi/core/config/Cache.java

@@ -1,11 +1,11 @@
-package org.ssssssss.magicapi.spring.boot.starter;
+package org.ssssssss.magicapi.core.config;
 
 /**
  * 缓存配置
  *
  * @author mxd
  */
-public class CacheConfig {
+public class Cache {
 
 	/**
 	 * 是否启用缓存

+ 41 - 92
magic-api/src/main/java/org/ssssssss/magicapi/model/Constants.java → magic-api/src/main/java/org/ssssssss/magicapi/core/config/Constants.java

@@ -1,10 +1,8 @@
-package org.ssssssss.magicapi.model;
+package org.ssssssss.magicapi.core.config;
+
+import java.util.Arrays;
+import java.util.List;
 
-/**
- * magic-api中使用的常量信息
- *
- * @author mxd
- */
 public class Constants {
 
 	/**
@@ -13,46 +11,11 @@ public class Constants {
 	public static final String CONST_STRING_TRUE = "true";
 
 
-	/**
-	 * 分组类型: 接口
-	 */
-	public static final String GROUP_TYPE_API = "1";
-
-	/**
-	 * 分组类型: 函数
-	 */
-	public static final String GROUP_TYPE_FUNCTION = "2";
-
-	/**
-	 * 接口文件夹名
-	 */
-	public static final String PATH_API = "api";
-
-	/**
-	 * 函数文件夹名
-	 */
-	public static final String PATH_FUNCTION = "function";
-
-	/**
-	 * 数据源文件夹名
-	 */
-	public static final String PATH_DATASOURCE = "datasource";
-
-	/**
-	 * 备份文件夹名
-	 */
-	public static final String PATH_BACKUPS = "backups";
-
 	/**
 	 * 空值
 	 */
 	public static final String EMPTY = "";
 
-	/**
-	 * 根节点ID
-	 */
-	public static final String ROOT_ID = "0";
-
 	/**
 	 * 表达式验证
 	 */
@@ -87,25 +50,31 @@ public class Constants {
 	 * 脚本中header的变量名
 	 */
 	public static final String VAR_NAME_HEADER = "header";
+
 	/**
 	 * 脚本中query的变量名
 	 */
 	public static final String VAR_NAME_QUERY = "query";
+	/**
 
 	/**
 	 * 脚本中RequestBody的变量名
 	 */
 	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_SESSION = "Magic-Request-Session";
+	public static final String HEADER_REQUEST_SCRIPT_ID = "Magic-Request-Script-Id";
+
+	public static final String HEADER_REQUEST_CLIENT_ID = "Magic-Request-Client-Id";
 
 	public static final String HEADER_REQUEST_BREAKPOINTS = "Magic-Request-Breakpoints";
 
@@ -115,72 +84,52 @@ public class Constants {
 
 	public static final String GROUP_METABASE = "group.json";
 
-	public static final String JSON_SUFFIX = ".json";
-
 	public static final String UPLOAD_MODE_FULL = "full";
 
 	public static final String LOCK = "1";
 
 	public static final String UNLOCK = "0";
-	/**
-	 * 执行成功的message值
-	 */
-	public static final String RESPONSE_MESSAGE_SUCCESS = "success";
-	/**
-	 * 通知新增
-	 */
-	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;
-	/**
-	 * 通知 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 String ROOT_ID = "0";
+
+	public static final String EVENT_TYPE_FILE = "file";
+
+	public static final String EVENT_SOURCE_NOTIFY = "notify";
+
+	public static final String WEBSOCKET_ATTRIBUTE_FILE_ID = "fileId";
+
+	public static final String WEBSOCKET_ATTRIBUTE_USER_ID = "id";
+
+	public static final String WEBSOCKET_ATTRIBUTE_USER_NAME = "username";
+
+	public static final String WEBSOCKET_ATTRIBUTE_USER_IP = "ip";
+
+	public static final String WEBSOCKET_ATTRIBUTE_CLIENT_ID = "cid";
+
 	/**
 	 * 执行成功的code值
 	 */
 	public static int RESPONSE_CODE_SUCCESS = 1;
+
+	/**
+	 * 执行成功的message值
+	 */
+	public static final String RESPONSE_MESSAGE_SUCCESS = "success";
+
 	/**
 	 * 执行出现异常的code值
 	 */
 	public static int RESPONSE_CODE_EXCEPTION = -1;
+
 	/**
 	 * 参数验证未通过的code值
 	 */
 	public static int RESPONSE_CODE_INVALID = 0;
 
+	/**
+	 * 空数组
+	 */
+	public static final Object[] EMPTY_OBJECT_ARRAY = new Object[0];
+
+
 }

+ 2 - 2
magic-api-spring-boot-starter/src/main/java/org/ssssssss/magicapi/spring/boot/starter/CrudConfig.java → magic-api/src/main/java/org/ssssssss/magicapi/core/config/Crud.java

@@ -1,4 +1,4 @@
-package org.ssssssss.magicapi.spring.boot.starter;
+package org.ssssssss.magicapi.core.config;
 
 /**
  * CRUD 配置
@@ -7,7 +7,7 @@ package org.ssssssss.magicapi.spring.boot.starter;
  * @date 2021-7-15 09:26:17
  * @since 1.3.4
  */
-public class CrudConfig {
+public class Crud {
 	/**
 	 * 逻辑删除列
 	 */

+ 2 - 2
magic-api-spring-boot-starter/src/main/java/org/ssssssss/magicapi/spring/boot/starter/DebugConfig.java → magic-api/src/main/java/org/ssssssss/magicapi/core/config/Debug.java

@@ -1,11 +1,11 @@
-package org.ssssssss.magicapi.spring.boot.starter;
+package org.ssssssss.magicapi.core.config;
 
 /**
  * Debug配置
  *
  * @author mxd
  */
-public class DebugConfig {
+public class Debug {
 
 	/**
 	 * 断点超时时间

+ 100 - 0
magic-api/src/main/java/org/ssssssss/magicapi/core/config/JsonCodeConstants.java

@@ -0,0 +1,100 @@
+package org.ssssssss.magicapi.core.config;
+
+import org.apache.commons.lang3.StringUtils;
+import org.ssssssss.magicapi.core.exception.InvalidArgumentException;
+import org.ssssssss.magicapi.core.model.JsonCode;
+
+public interface JsonCodeConstants {
+
+	JsonCode SUCCESS = new JsonCode(1, Constants.RESPONSE_MESSAGE_SUCCESS);
+
+	JsonCode IS_READ_ONLY = new JsonCode(-2, "当前为只读模式,无法操作");
+
+	JsonCode PERMISSION_INVALID = new JsonCode(-10, "无权限操作.");
+
+	JsonCode GROUP_NOT_FOUND = new JsonCode(1001, "找不到分组信息");
+
+	JsonCode NOT_SUPPORTED_GROUP_TYPE = new JsonCode(1002, "不支持该分组类型");
+
+	JsonCode TARGET_IS_REQUIRED = new JsonCode(1003, "目标网址不能为空");
+
+	JsonCode SECRET_KEY_IS_REQUIRED = new JsonCode(1004, "secretKey不能为空");
+
+	JsonCode MOVE_NAME_CONFLICT = new JsonCode(1005, "移动后名称会重复,请修改名称后在试。");
+
+	JsonCode SRC_GROUP_CONFLICT = new JsonCode(1006, "源对象和分组不能一致");
+
+	JsonCode FILE_NOT_FOUND = new JsonCode(1007, "找不到对应文件或分组");
+
+	JsonCode RESOURCE_LOCKED = new JsonCode(1008, "当前资源已被锁定,请解锁后在操作。");
+
+	JsonCode PATH_CONFLICT = new JsonCode(1009, "该路径已被使用,请换一个路径在试");
+
+	JsonCode RESOURCE_PATH_CONFLICT = new JsonCode(1010, "资源中[%s]有冲突,请检查");
+
+	JsonCode MOVE_PATH_CONFLICT = new JsonCode(1011, "移动后路径会冲突,请换一个路径在试");
+
+	JsonCode REQUEST_METHOD_REQUIRED = new JsonCode(1012, "请求方法不能为空");
+
+	JsonCode REQUEST_PATH_REQUIRED = new JsonCode(1013, "请求路径不能为空");
+
+	JsonCode FUNCTION_PATH_REQUIRED = new JsonCode(1014, "函数路径不能为空");
+
+	JsonCode FILE_PATH_NOT_EXISTS = new JsonCode(1015, "配置的文件路径不存在,请检查");
+
+	JsonCode REQUEST_PATH_CONFLICT = new JsonCode(1016, "接口[%s(%s)]与应用冲突,无法注册");
+
+	JsonCode SCRIPT_REQUIRED = new JsonCode(1017, "脚本内容不能为空");
+
+	JsonCode NAME_REQUIRED = new JsonCode(1018, "名称不能为空");
+
+	JsonCode PATH_REQUIRED = new JsonCode(1019, "路径不能为空");
+
+	JsonCode DS_URL_REQUIRED = new JsonCode(1020, "jdbcURL不能为空");
+
+	JsonCode DS_KEY_REQUIRED = new JsonCode(1021, "key不能为空");
+
+	JsonCode DS_KEY_CONFLICT = new JsonCode(1022, "数据源key已被使用,请更换后在试");
+
+	JsonCode GROUP_ID_REQUIRED = new JsonCode(1023, "请选择分组");
+
+	JsonCode CRON_ID_REQUIRED = new JsonCode(1024, "cron表达式不能为空");
+
+	JsonCode NAME_INVALID = new JsonCode(1025, "名称不能包含特殊字符,只允许中文、数字、字母以及+_-.()的组合且不能.开头");
+
+	JsonCode DATASOURCE_KEY_INVALID = new JsonCode(1026, "数据源Key不能包含特殊字符,只允许中文、数字、字母以及_组合");
+
+	JsonCode FILE_SAVE_FAILURE = new JsonCode(1027, "保存失败,同一组下分组名称不能重复且不能包含特殊字符。");
+
+	JsonCode PARAMETER_INVALID = new JsonCode(1028, "参数验证失败");
+
+	JsonCode HEADER_INVALID = new JsonCode(1029, "header验证失败");
+
+	JsonCode PATH_VARIABLE_INVALID = new JsonCode(1030, "路径变量验证失败");
+
+	JsonCode BODY_INVALID = new JsonCode(1031, "body验证失败");
+
+	JsonCode FILE_IS_REQUIRED = new JsonCode(1032, "请上传文件");
+
+	JsonCode SIGN_IS_INVALID = new JsonCode(1033, "签名验证失败,请检查秘钥是否正确");
+
+	JsonCode BACKUP_NOT_ENABLED = new JsonCode(1034, "未启用备份,无法操作");
+
+	JsonCode API_NOT_FOUND = new JsonCode(1035, "找不到接口");
+
+	default void notNull(Object value, JsonCode jsonCode) {
+		if (value == null) {
+			throw new InvalidArgumentException(jsonCode);
+		}
+	}
+
+	default void isTrue(boolean value, JsonCode jsonCode) {
+		if (!value) {
+			throw new InvalidArgumentException(jsonCode);
+		}
+	}
+
+	default void notBlank(String value, JsonCode jsonCode) {
+		isTrue(StringUtils.isNotBlank(value), jsonCode);
+	}
+}

+ 112 - 120
magic-api-spring-boot-starter/src/main/java/org/ssssssss/magicapi/spring/boot/starter/MagicAPIProperties.java → magic-api/src/main/java/org/ssssssss/magicapi/core/config/MagicAPIProperties.java

@@ -1,13 +1,14 @@
-package org.ssssssss.magicapi.spring.boot.starter;
+package org.ssssssss.magicapi.core.config;
 
 import org.apache.commons.lang3.StringUtils;
 import org.springframework.boot.context.properties.ConfigurationProperties;
 import org.springframework.boot.context.properties.NestedConfigurationProperty;
-import org.ssssssss.magicapi.controller.RequestHandler;
+import org.ssssssss.magicapi.core.web.RequestHandler;
 
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.UUID;
 
 /**
  * magic-api配置信息
@@ -135,51 +136,34 @@ public class MagicAPIProperties {
 	 */
 	private boolean persistenceResponseBody = true;
 
-	@NestedConfigurationProperty
-	private SecurityConfig securityConfig = new SecurityConfig();
-
-	@NestedConfigurationProperty
-	private PageConfig pageConfig = new PageConfig();
+	/**
+	 * 实例ID,集群环境下,要保证每台机器不同。默认启动后随机生成uuid
+	 */
+	private String instanceId = UUID.randomUUID().toString();
 
 	@NestedConfigurationProperty
-	private CacheConfig cacheConfig = new CacheConfig();
+	private Security securityConfig = new Security();
 
 	@NestedConfigurationProperty
-	private DebugConfig debugConfig = new DebugConfig();
+	private Page page = new Page();
 
 	@NestedConfigurationProperty
-	private SwaggerConfig swaggerConfig = new SwaggerConfig();
+	private Cache cache = new Cache();
 
 	@NestedConfigurationProperty
-	private ResourceConfig resource = new ResourceConfig();
+	private Debug debug = new Debug();
 
 	@NestedConfigurationProperty
-	private ResponseCodeConfig responseCodeConfig = new ResponseCodeConfig();
+	private Resource resource = new Resource();
 
 	@NestedConfigurationProperty
-	private ClusterConfig clusterConfig = new ClusterConfig();
+	private ResponseCode responseCode = new ResponseCode();
 
 	@NestedConfigurationProperty
-	private CrudConfig crudConfig = new CrudConfig();
+	private Crud crud = new Crud();
 
 	@NestedConfigurationProperty
-	private BackupConfig backupConfig = new BackupConfig();
-
-	public CrudConfig getCrudConfig() {
-		return crudConfig;
-	}
-
-	public void setCrudConfig(CrudConfig crudConfig) {
-		this.crudConfig = crudConfig;
-	}
-
-	public String getEditorConfig() {
-		return editorConfig;
-	}
-
-	public void setEditorConfig(String editorConfig) {
-		this.editorConfig = editorConfig;
-	}
+	private Backup backup = new Backup();
 
 	public String getWeb() {
 		if (StringUtils.isBlank(web)) {
@@ -217,44 +201,20 @@ public class MagicAPIProperties {
 		this.banner = banner;
 	}
 
-	public PageConfig getPageConfig() {
-		return pageConfig;
-	}
-
-	public void setPageConfig(PageConfig pageConfig) {
-		this.pageConfig = pageConfig;
-	}
-
-	public boolean isThrowException() {
-		return throwException;
-	}
-
-	public void setThrowException(boolean throwException) {
-		this.throwException = throwException;
-	}
-
-	public CacheConfig getCacheConfig() {
-		return cacheConfig;
-	}
-
-	public void setCacheConfig(CacheConfig cacheConfig) {
-		this.cacheConfig = cacheConfig;
-	}
-
-	public DebugConfig getDebugConfig() {
-		return debugConfig;
-	}
 
-	public void setDebugConfig(DebugConfig debugConfig) {
-		this.debugConfig = debugConfig;
+	public List<String> getAutoImportModuleList() {
+		return Arrays.asList(autoImportModule.replaceAll("\\s", "").split(","));
 	}
 
-	public SecurityConfig getSecurityConfig() {
-		return securityConfig;
+	public List<String> getAutoImportPackageList() {
+		if (autoImportPackage == null) {
+			return Collections.emptyList();
+		}
+		return Arrays.asList(autoImportPackage.replaceAll("\\s", "").split(","));
 	}
 
-	public void setSecurityConfig(SecurityConfig securityConfig) {
-		this.securityConfig = securityConfig;
+	public String getVersion() {
+		return version;
 	}
 
 	public String getPrefix() {
@@ -265,32 +225,20 @@ public class MagicAPIProperties {
 		this.prefix = prefix;
 	}
 
-	public SwaggerConfig getSwaggerConfig() {
-		return swaggerConfig;
+	public boolean isThrowException() {
+		return throwException;
 	}
 
-	public void setSwaggerConfig(SwaggerConfig swaggerConfig) {
-		this.swaggerConfig = swaggerConfig;
+	public void setThrowException(boolean throwException) {
+		this.throwException = throwException;
 	}
 
 	public String getAutoImportModule() {
 		return autoImportModule;
 	}
 
-	public void setAutoImportModule(String autoImport) {
-		this.autoImportModule = autoImport;
-	}
-
-	public List<String> getAutoImportModuleList() {
-		return Arrays.asList(autoImportModule.replaceAll("\\s", "").split(","));
-	}
-
-	public boolean isAllowOverride() {
-		return allowOverride;
-	}
-
-	public void setAllowOverride(boolean allowOverride) {
-		this.allowOverride = allowOverride;
+	public void setAutoImportModule(String autoImportModule) {
+		this.autoImportModule = autoImportModule;
 	}
 
 	public String getAutoImportPackage() {
@@ -301,11 +249,12 @@ public class MagicAPIProperties {
 		this.autoImportPackage = autoImportPackage;
 	}
 
-	public List<String> getAutoImportPackageList() {
-		if (autoImportPackage == null) {
-			return Collections.emptyList();
-		}
-		return Arrays.asList(autoImportPackage.replaceAll("\\s", "").split(","));
+	public boolean isAllowOverride() {
+		return allowOverride;
+	}
+
+	public void setAllowOverride(boolean allowOverride) {
+		this.allowOverride = allowOverride;
 	}
 
 	public int getThreadPoolExecutorSize() {
@@ -316,17 +265,12 @@ public class MagicAPIProperties {
 		this.threadPoolExecutorSize = threadPoolExecutorSize;
 	}
 
-	public String getVersion() {
-		return version;
-	}
-
-
-	public ResourceConfig getResource() {
-		return resource;
+	public String getEditorConfig() {
+		return editorConfig;
 	}
 
-	public void setResource(ResourceConfig resource) {
-		this.resource = resource;
+	public void setEditorConfig(String editorConfig) {
+		this.editorConfig = editorConfig;
 	}
 
 	public boolean isSupportCrossDomain() {
@@ -345,22 +289,6 @@ public class MagicAPIProperties {
 		this.response = response;
 	}
 
-	public ResponseCodeConfig getResponseCodeConfig() {
-		return responseCodeConfig;
-	}
-
-	public void setResponseCodeConfig(ResponseCodeConfig responseCodeConfig) {
-		this.responseCodeConfig = responseCodeConfig;
-	}
-
-	public ClusterConfig getClusterConfig() {
-		return clusterConfig;
-	}
-
-	public void setClusterConfig(ClusterConfig clusterConfig) {
-		this.clusterConfig = clusterConfig;
-	}
-
 	public String getSecretKey() {
 		return secretKey;
 	}
@@ -385,14 +313,6 @@ public class MagicAPIProperties {
 		this.showUrl = showUrl;
 	}
 
-	public BackupConfig getBackupConfig() {
-		return backupConfig;
-	}
-
-	public void setBackupConfig(BackupConfig backupConfig) {
-		this.backupConfig = backupConfig;
-	}
-
 	public boolean isShowSql() {
 		return showSql;
 	}
@@ -424,4 +344,76 @@ public class MagicAPIProperties {
 	public void setPersistenceResponseBody(boolean persistenceResponseBody) {
 		this.persistenceResponseBody = persistenceResponseBody;
 	}
+
+	public String getInstanceId() {
+		return instanceId;
+	}
+
+	public void setInstanceId(String instanceId) {
+		this.instanceId = instanceId;
+	}
+
+	public Security getSecurityConfig() {
+		return securityConfig;
+	}
+
+	public void setSecurityConfig(Security securityConfig) {
+		this.securityConfig = securityConfig;
+	}
+
+	public Page getPage() {
+		return page;
+	}
+
+	public void setPage(Page page) {
+		this.page = page;
+	}
+
+	public Cache getCache() {
+		return cache;
+	}
+
+	public void setCache(Cache cache) {
+		this.cache = cache;
+	}
+
+	public Debug getDebug() {
+		return debug;
+	}
+
+	public void setDebug(Debug debug) {
+		this.debug = debug;
+	}
+
+	public Resource getResource() {
+		return resource;
+	}
+
+	public void setResource(Resource resource) {
+		this.resource = resource;
+	}
+
+	public ResponseCode getResponseCode() {
+		return responseCode;
+	}
+
+	public void setResponseCode(ResponseCode responseCode) {
+		this.responseCode = responseCode;
+	}
+
+	public Crud getCrud() {
+		return crud;
+	}
+
+	public void setCrud(Crud crud) {
+		this.crud = crud;
+	}
+
+	public Backup getBackup() {
+		return backup;
+	}
+
+	public void setBackup(Backup backup) {
+		this.backup = backup;
+	}
 }

+ 49 - 74
magic-api/src/main/java/org/ssssssss/magicapi/config/MagicConfiguration.java → magic-api/src/main/java/org/ssssssss/magicapi/core/config/MagicConfiguration.java

@@ -1,55 +1,38 @@
-package org.ssssssss.magicapi.config;
+package org.ssssssss.magicapi.core.config;
 
 import org.springframework.http.converter.HttpMessageConverter;
-import org.ssssssss.magicapi.adapter.Resource;
-import org.ssssssss.magicapi.controller.RequestHandler;
-import org.ssssssss.magicapi.interceptor.AuthorizationInterceptor;
-import org.ssssssss.magicapi.interceptor.RequestInterceptor;
-import org.ssssssss.magicapi.provider.*;
+import org.ssssssss.magicapi.core.resource.Resource;
+import org.ssssssss.magicapi.core.web.RequestHandler;
+import org.ssssssss.magicapi.core.model.MagicEntity;
+import org.ssssssss.magicapi.core.service.MagicDynamicRegistry;
+import org.ssssssss.magicapi.core.service.MagicResourceService;
+import org.ssssssss.magicapi.core.interceptor.AuthorizationInterceptor;
+import org.ssssssss.magicapi.core.interceptor.RequestInterceptor;
+import org.ssssssss.magicapi.core.service.MagicAPIService;
+import org.ssssssss.magicapi.backup.service.MagicBackupService;
+import org.ssssssss.magicapi.core.service.MagicNotifyService;
+import org.ssssssss.magicapi.core.interceptor.ResultProvider;
+import org.ssssssss.magicapi.datasource.model.MagicDynamicDataSource;
 
 import java.util.ArrayList;
 import java.util.List;
 
-/**
- * 配置信息
- *
- * @author mxd
- */
 public class MagicConfiguration {
 
 	/**
 	 * 拦截器
 	 */
 	private final List<RequestInterceptor> requestInterceptors = new ArrayList<>();
-	/**
-	 * 接口映射
-	 */
-	private MappingHandlerMapping mappingHandlerMapping;
-	/**
-	 * 函数管理
-	 */
-	private MagicFunctionManager magicFunctionManager;
+
 	/**
 	 * 编辑器配置文件
 	 */
 	private String editorConfig;
-	/**
-	 * 接口查询Service
-	 */
-	private ApiServiceProvider apiServiceProvider;
-
-	/**
-	 * 分组查询Service
-	 */
-	private GroupServiceProvider groupServiceProvider;
-
-	/**
-	 * 函数查询Service
-	 */
-	private FunctionServiceProvider functionServiceProvider;
 
 	private MagicAPIService magicAPIService;
 
+	private MagicDynamicDataSource magicDynamicDataSource;
+
 	/**
 	 * 请求出错时,是否抛出异常
 	 */
@@ -72,6 +55,10 @@ public class MagicConfiguration {
 
 	private MagicBackupService magicBackupService;
 
+	private static MagicResourceService magicResourceService;
+
+	private List<MagicDynamicRegistry<? extends MagicEntity>> magicDynamicRegistries;
+
 	/**
 	 * debug 超时时间
 	 */
@@ -83,14 +70,6 @@ public class MagicConfiguration {
 		this.requestInterceptors.add(requestInterceptor);
 	}
 
-	public MappingHandlerMapping getMappingHandlerMapping() {
-		return mappingHandlerMapping;
-	}
-
-	public void setMappingHandlerMapping(MappingHandlerMapping mappingHandlerMapping) {
-		this.mappingHandlerMapping = mappingHandlerMapping;
-	}
-
 	public AuthorizationInterceptor getAuthorizationInterceptor() {
 		return authorizationInterceptor;
 	}
@@ -103,22 +82,6 @@ public class MagicConfiguration {
 		return requestInterceptors;
 	}
 
-	public ApiServiceProvider getApiServiceProvider() {
-		return apiServiceProvider;
-	}
-
-	public void setApiServiceProvider(ApiServiceProvider apiServiceProvider) {
-		this.apiServiceProvider = apiServiceProvider;
-	}
-
-	public GroupServiceProvider getGroupServiceProvider() {
-		return groupServiceProvider;
-	}
-
-	public void setGroupServiceProvider(GroupServiceProvider groupServiceProvider) {
-		this.groupServiceProvider = groupServiceProvider;
-	}
-
 	public boolean isThrowException() {
 		return throwException;
 	}
@@ -159,22 +122,6 @@ public class MagicConfiguration {
 		this.enableWeb = enableWeb;
 	}
 
-	public FunctionServiceProvider getFunctionServiceProvider() {
-		return functionServiceProvider;
-	}
-
-	public void setFunctionServiceProvider(FunctionServiceProvider functionServiceProvider) {
-		this.functionServiceProvider = functionServiceProvider;
-	}
-
-	public MagicFunctionManager getMagicFunctionManager() {
-		return magicFunctionManager;
-	}
-
-	public void setMagicFunctionManager(MagicFunctionManager magicFunctionManager) {
-		this.magicFunctionManager = magicFunctionManager;
-	}
-
 	public String getEditorConfig() {
 		return editorConfig;
 	}
@@ -223,15 +170,43 @@ public class MagicConfiguration {
 		this.magicBackupService = magicBackupService;
 	}
 
+	public MagicDynamicDataSource getMagicDynamicDataSource() {
+		return magicDynamicDataSource;
+	}
+
+	public void setMagicDynamicDataSource(MagicDynamicDataSource magicDynamicDataSource) {
+		this.magicDynamicDataSource = magicDynamicDataSource;
+	}
+
+	public static MagicResourceService getMagicResourceService() {
+		return MagicConfiguration.magicResourceService;
+	}
+
+	public void setMagicResourceService(MagicResourceService magicResourceService) {
+		MagicConfiguration.magicResourceService = magicResourceService;
+	}
+
+	public List<MagicDynamicRegistry<? extends MagicEntity>> getMagicDynamicRegistries() {
+		return magicDynamicRegistries;
+	}
+
+	public void setMagicDynamicRegistries(List<MagicDynamicRegistry<? extends MagicEntity>> magicDynamicRegistries) {
+		this.magicDynamicRegistries = magicDynamicRegistries;
+	}
+
 	/**
 	 * 打印banner
 	 */
-	public void printBanner() {
+	public void printBanner(List<String> plugins) {
 		System.out.println("  __  __                _           _     ____  ___ ");
 		System.out.println(" |  \\/  |  __ _   __ _ (_)  ___    / \\   |  _ \\|_ _|");
 		System.out.println(" | |\\/| | / _` | / _` || | / __|  / _ \\  | |_) || | ");
 		System.out.println(" | |  | || (_| || (_| || || (__  / ___ \\ |  __/ | | ");
 		System.out.println(" |_|  |_| \\__,_| \\__, ||_| \\___|/_/   \\_\\|_|   |___|");
 		System.out.println("                  |___/                        " + RequestHandler.class.getPackage().getImplementationVersion());
+		if(!plugins.isEmpty()){
+			System.out.println("集成插件:");
+			plugins.stream().peek(it -> System.out.print("- ")).forEach(System.out::println);
+		}
 	}
 }

+ 2 - 8
magic-api/src/main/java/org/ssssssss/magicapi/config/MagicCorsFilter.java → magic-api/src/main/java/org/ssssssss/magicapi/core/config/MagicCorsFilter.java

@@ -1,19 +1,13 @@
-package org.ssssssss.magicapi.config;
+package org.ssssssss.magicapi.core.config;
 
 import org.apache.commons.lang3.StringUtils;
 import org.springframework.http.HttpHeaders;
-import org.ssssssss.magicapi.model.Constants;
 
 import javax.servlet.*;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
 
-/**
- * 接口跨域处理
- *
- * @author mxd
- */
 public class MagicCorsFilter implements Filter {
 
 	@Override
@@ -41,7 +35,7 @@ public class MagicCorsFilter implements Filter {
 	@Override
 	public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
 		HttpServletRequest request = (HttpServletRequest) req;
-		if (StringUtils.isNotBlank(Constants.HEADER_REQUEST_SESSION)) {
+		if (StringUtils.isNotBlank(Constants.HEADER_REQUEST_CLIENT_ID)) {
 			process(request, (HttpServletResponse) resp);
 		}
 		chain.doFilter(req, resp);

+ 1 - 1
magic-api/src/main/java/org/ssssssss/magicapi/config/MagicFunction.java → magic-api/src/main/java/org/ssssssss/magicapi/core/config/MagicFunction.java

@@ -1,4 +1,4 @@
-package org.ssssssss.magicapi.config;
+package org.ssssssss.magicapi.core.config;
 
 /**
  * 函数,主要用于脚本中直接可使用的函数,如 now();

+ 17 - 0
magic-api/src/main/java/org/ssssssss/magicapi/core/config/MagicPluginConfiguration.java

@@ -0,0 +1,17 @@
+package org.ssssssss.magicapi.core.config;
+
+import org.ssssssss.magicapi.core.model.Plugin;
+import org.ssssssss.magicapi.core.web.MagicControllerRegister;
+
+public interface MagicPluginConfiguration {
+
+	Plugin plugin();
+
+
+	/**
+	 * 注册Controller
+	 */
+	default MagicControllerRegister controllerRegister(){
+		return (mapping, configuration) -> { };
+	}
+}

+ 45 - 0
magic-api/src/main/java/org/ssssssss/magicapi/core/config/MessageType.java

@@ -0,0 +1,45 @@
+package org.ssssssss.magicapi.core.config;
+
+/**
+ * 消息类型
+ */
+public enum MessageType {
+	/* S -> C message */
+	/* 日志消息 */
+	LOG,
+	/* 多个日志消息 */
+	LOGS,
+	/* 进入断点 */
+	BREAKPOINT,
+	/* 请求接口发生异常 */
+	EXCEPTION,
+	/* 登录结果 */
+	LOGIN_RESPONSE,
+	/* 通知客户端,有用户上线 */
+	USER_LOGIN,
+	/* 通知客户端,有用户下线 */
+	USER_LOGOUT,
+	/* 通知客户端,当前机器在线人数 */
+	ONLINE_USERS,
+	/* 通知客户端,他人进入文件*/
+	INTO_FILE_ID,
+	/* PONG */
+	PONG,
+
+	/* C -> S message */
+	/* 设置断点 */
+	SET_BREAKPOINT,
+	/* 恢复断点 */
+	RESUME_BREAKPOINT,
+	/* 登录 */
+	LOGIN,
+	/* 设置当前所在文件 */
+	SET_FILE_ID,
+	/* ping */
+	PING,
+
+	/* S <-> S -> C message*/
+	/* 获取当前在线用户 */
+	SEND_ONLINE
+
+}

+ 2 - 2
magic-api-spring-boot-starter/src/main/java/org/ssssssss/magicapi/spring/boot/starter/PageConfig.java → magic-api/src/main/java/org/ssssssss/magicapi/core/config/Page.java

@@ -1,11 +1,11 @@
-package org.ssssssss.magicapi.spring.boot.starter;
+package org.ssssssss.magicapi.core.config;
 
 /**
  * 分页配置
  *
  * @author mxd
  */
-public class PageConfig {
+public class Page {
 
 	/**
 	 * 默认page表达式

+ 2 - 2
magic-api-spring-boot-starter/src/main/java/org/ssssssss/magicapi/spring/boot/starter/ResourceConfig.java → magic-api/src/main/java/org/ssssssss/magicapi/core/config/Resource.java

@@ -1,11 +1,11 @@
-package org.ssssssss.magicapi.spring.boot.starter;
+package org.ssssssss.magicapi.core.config;
 
 /**
  * 接口存储配置
  *
  * @author mxd
  */
-public class ResourceConfig {
+public class Resource {
 
 	/**
 	 * 存储类型,默认是文件

+ 2 - 2
magic-api-spring-boot-starter/src/main/java/org/ssssssss/magicapi/spring/boot/starter/ResponseCodeConfig.java → magic-api/src/main/java/org/ssssssss/magicapi/core/config/ResponseCode.java

@@ -1,4 +1,4 @@
-package org.ssssssss.magicapi.spring.boot.starter;
+package org.ssssssss.magicapi.core.config;
 
 /**
  * json结果code配置
@@ -6,7 +6,7 @@ package org.ssssssss.magicapi.spring.boot.starter;
  * @author mxd
  * @since 1.1.2
  */
-public class ResponseCodeConfig {
+public class ResponseCode {
 
 	/**
 	 * 执行成功的code值

+ 2 - 2
magic-api-spring-boot-starter/src/main/java/org/ssssssss/magicapi/spring/boot/starter/SecurityConfig.java → magic-api/src/main/java/org/ssssssss/magicapi/core/config/Security.java

@@ -1,4 +1,4 @@
-package org.ssssssss.magicapi.spring.boot.starter;
+package org.ssssssss.magicapi.core.config;
 
 /**
  * 安全配置
@@ -6,7 +6,7 @@ package org.ssssssss.magicapi.spring.boot.starter;
  * @author mxd
  * @since 0.4.0
  */
-public class SecurityConfig {
+public class Security {
 
 	/**
 	 * 登录用的用户名

+ 210 - 0
magic-api/src/main/java/org/ssssssss/magicapi/core/config/WebSocketSessionManager.java

@@ -0,0 +1,210 @@
+package org.ssssssss.magicapi.core.config;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.web.socket.TextMessage;
+import org.ssssssss.magicapi.core.event.EventAction;
+import org.ssssssss.magicapi.core.context.MagicConsoleSession;
+import org.ssssssss.magicapi.core.model.MagicNotify;
+import org.ssssssss.magicapi.core.model.Pair;
+import org.ssssssss.magicapi.core.service.MagicNotifyService;
+import org.ssssssss.magicapi.utils.JsonUtils;
+import org.ssssssss.script.MagicScriptDebugContext;
+
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+public class WebSocketSessionManager {
+
+	private static final Logger logger = LoggerFactory.getLogger(WebSocketSessionManager.class);
+
+	private static final Map<String, MagicConsoleSession> SESSIONS = new ConcurrentHashMap<>();
+
+	private static MagicNotifyService magicNotifyService;
+
+	private static final Map<String, MagicScriptDebugContext> CONTEXTS = new ConcurrentHashMap<>();
+
+	private static String instanceId;
+
+	private static final List<Pair<String, String>> MESSAGE_CACHE = new ArrayList<>(200);
+
+	public static void add(MagicConsoleSession session) {
+		SESSIONS.put(session.getClientId(), session);
+	}
+
+	public static MagicConsoleSession getConsoleSession(String clientId) {
+		return SESSIONS.get(clientId);
+	}
+
+	static {
+		// 1秒1次发送日志
+		new ScheduledThreadPoolExecutor(1, r -> new Thread(r, "magic-api-send-log-task")).scheduleAtFixedRate(WebSocketSessionManager::flushLog, 1, 1, TimeUnit.SECONDS);
+		// 60秒检测一次是否在线
+		new ScheduledThreadPoolExecutor(1, r -> new Thread(r, "magic-api-websocket-clean-task")).scheduleAtFixedRate(WebSocketSessionManager::checkSession, 60, 60, TimeUnit.SECONDS);
+	}
+
+	public static Collection<MagicConsoleSession> getSessions() {
+		return SESSIONS.values();
+	}
+
+	public static void remove(MagicConsoleSession session) {
+		if (session.getClientId() != null) {
+			remove(session.getClientId());
+		}
+	}
+
+	public static void remove(String sessionId) {
+		SESSIONS.remove(sessionId);
+	}
+
+	public static void sendToAll(MessageType messageType, Object... values) {
+		String content = buildMessage(messageType, values);
+		sendToAll(content);
+	}
+
+	private static void sendToAll(String content) {
+		getSessions().stream().filter(MagicConsoleSession::writeable).forEach(session -> sendBySession(session, content));
+		sendToMachineByClientId(null, content);
+	}
+
+	public static void sendLogs(String sessionId, String message) {
+		synchronized (MESSAGE_CACHE) {
+			MESSAGE_CACHE.add(Pair.of(sessionId, message));
+			if (MESSAGE_CACHE.size() >= 100) {
+				flushLog();
+			}
+		}
+	}
+
+	public static void flushLog() {
+		try {
+			Map<String, List<String>> messages;
+			synchronized (MESSAGE_CACHE) {
+				messages = MESSAGE_CACHE.stream().collect(Collectors.groupingBy(Pair::getFirst, Collectors.mapping(Pair::getSecond, Collectors.toList())));
+				MESSAGE_CACHE.clear();
+			}
+			messages.forEach((clientId, logs) -> sendByClientId(clientId, logs.size() > 1 ? MessageType.LOGS : MessageType.LOG, logs));
+		} catch (Exception e) {
+			logger.warn("发送日志失败", e);
+		}
+	}
+
+	public static void sendByClientId(String clientId, MessageType messageType, Object... values) {
+		MagicConsoleSession session = findSession(clientId);
+		String content = buildMessage(messageType, values);
+		if (session != null && session.writeable()) {
+			sendBySession(session, content);
+		} else {
+			sendToMachineByClientId(clientId, content);
+		}
+	}
+
+	public static void sendToOther(String excludeClientId, MessageType messageType, Object... values) {
+		String content = buildMessage(messageType, values);
+		getSessions().stream()
+				.filter(MagicConsoleSession::writeable)
+				.filter(it -> !it.getClientId().equals(excludeClientId))
+				.forEach(session -> sendBySession(session, content));
+		sendToMachineByClientId(null, content);
+	}
+
+	public static void sendToMachineByClientId(String clientId, String content) {
+		if (magicNotifyService != null) {
+			// 通知其他机器去发送消息
+			magicNotifyService.sendNotify(new MagicNotify(instanceId, EventAction.WS_S_C, clientId, content));
+		}
+	}
+
+	public static void sendToMachine(MessageType messageType, Object... args) {
+		if (magicNotifyService != null) {
+			// 通知其他机器去发送消息
+			magicNotifyService.sendNotify(new MagicNotify(instanceId, EventAction.WS_S_S, null, buildMessage(messageType, args)));
+		}
+	}
+
+	public static String buildMessage(MessageType messageType, Object... values) {
+		StringBuilder builder = new StringBuilder(messageType.name().toLowerCase());
+		if (values != null) {
+			for (int i = 0, len = values.length; i < len; i++) {
+				builder.append(",");
+				Object value = values[i];
+				if (i + 1 < len || value instanceof CharSequence || value instanceof Number) {
+					builder.append(value);
+				} else {
+					builder.append(JsonUtils.toJsonString(value));
+				}
+			}
+		}
+		return builder.toString();
+	}
+
+	public static void sendByClientId(String clientId, String content) {
+		if (clientId == null) {
+			getSessions().stream().filter(MagicConsoleSession::writeable).forEach(session -> sendBySession(session, content));
+		} else {
+			MagicConsoleSession session = findSession(clientId);
+			if (session != null) {
+				sendBySession(session, content);
+			}
+		}
+	}
+
+	public static void sendBySession(MagicConsoleSession session, String content) {
+		try {
+			if (session != null) {
+				synchronized (session.getClientId()) {
+					session.getWebSocketSession().sendMessage(new TextMessage(content));
+				}
+			}
+		} catch (Exception e) {
+			logger.warn("发送WebSocket消息失败: {}", e.getMessage());
+		}
+	}
+
+	public static MagicConsoleSession findSession(String clientId) {
+		return getSessions().stream()
+				.filter(it -> Objects.equals(clientId, it.getClientId()))
+				.findFirst()
+				.orElse(null);
+	}
+
+	public static void setMagicNotifyService(MagicNotifyService magicNotifyService) {
+		WebSocketSessionManager.magicNotifyService = magicNotifyService;
+	}
+
+	public static void setInstanceId(String instanceId) {
+		WebSocketSessionManager.instanceId = instanceId;
+	}
+
+	public static void addMagicScriptContext(String sessionAndScriptId, MagicScriptDebugContext context) {
+		CONTEXTS.put(sessionAndScriptId, context);
+	}
+
+	public static MagicScriptDebugContext findMagicScriptContext(String sessionAndScriptId) {
+		return CONTEXTS.get(sessionAndScriptId);
+	}
+
+	public static void removeMagicScriptContext(String sessionAndScriptId) {
+		CONTEXTS.remove(sessionAndScriptId);
+	}
+
+	private static void checkSession() {
+		try {
+			long activateTime = System.currentTimeMillis() - 20 * 1000;
+			SESSIONS.entrySet().stream()
+					.filter(it -> it.getValue().getActivateTime() < activateTime)
+					.collect(Collectors.toList())
+					.forEach(entry -> {
+						MagicConsoleSession session = entry.getValue();
+						SESSIONS.remove(entry.getKey());
+						session.close();
+						sendToAll(MessageType.USER_LOGOUT, session.getAttributes());
+					});
+		} catch (Exception ignored) {
+		}
+	}
+
+}

+ 1 - 1
magic-api/src/main/java/org/ssssssss/magicapi/context/CookieContext.java → magic-api/src/main/java/org/ssssssss/magicapi/core/context/CookieContext.java

@@ -1,4 +1,4 @@
-package org.ssssssss.magicapi.context;
+package org.ssssssss.magicapi.core.context;
 
 import javax.servlet.http.Cookie;
 import javax.servlet.http.HttpServletRequest;

+ 88 - 0
magic-api/src/main/java/org/ssssssss/magicapi/core/context/MagicConsoleSession.java

@@ -0,0 +1,88 @@
+package org.ssssssss.magicapi.core.context;
+
+import org.springframework.web.socket.CloseStatus;
+import org.springframework.web.socket.WebSocketSession;
+import org.ssssssss.magicapi.core.config.Constants;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class MagicConsoleSession {
+
+	private static final Map<String, MagicConsoleSession> cached = new ConcurrentHashMap<>();
+
+	private String clientId;
+
+	private WebSocketSession webSocketSession;
+
+	private final Map<String, Object> attributes = new HashMap<>();
+
+	private long activateTime = System.currentTimeMillis();
+
+	public MagicConsoleSession(WebSocketSession webSocketSession) {
+		this.webSocketSession = webSocketSession;
+	}
+
+	public String getClientId() {
+		return clientId;
+	}
+
+	public WebSocketSession getWebSocketSession() {
+		return webSocketSession;
+	}
+
+	public boolean writeable() {
+		return webSocketSession != null && webSocketSession.isOpen();
+	}
+
+	public static MagicConsoleSession from(WebSocketSession session) {
+		MagicConsoleSession magicConsoleSession = cached.get(session.getId());
+		if (magicConsoleSession == null) {
+			magicConsoleSession = new MagicConsoleSession(session);
+			cached.put(session.getId(), magicConsoleSession);
+		}
+		return magicConsoleSession;
+	}
+
+	public static void remove(WebSocketSession session) {
+		cached.remove(session.getId());
+	}
+
+	public Object getAttribute(String key){
+		return attributes.get(key);
+	}
+
+	public void setAttribute(String key, Object value){
+		attributes.put(key, value);
+	}
+
+	public Map<String, Object> getAttributes(){
+		return attributes;
+	}
+
+	public void setClientId(String clientId) {
+		this.clientId = clientId;
+		setAttribute(Constants.WEBSOCKET_ATTRIBUTE_CLIENT_ID, clientId);
+	}
+
+	public long getActivateTime() {
+		return activateTime;
+	}
+
+	public void setActivateTime(long activateTime) {
+		this.activateTime = activateTime;
+	}
+
+	public void close(){
+		if(this.webSocketSession != null){
+			remove(this.webSocketSession);
+			try {
+				this.webSocketSession.close(CloseStatus.SESSION_NOT_RELIABLE);
+			} catch (Exception ignored) {
+
+			}
+			this.webSocketSession = null;
+		}
+	}
+}

+ 1 - 1
magic-api/src/main/java/org/ssssssss/magicapi/interceptor/MagicUser.java → magic-api/src/main/java/org/ssssssss/magicapi/core/context/MagicUser.java

@@ -1,4 +1,4 @@
-package org.ssssssss.magicapi.interceptor;
+package org.ssssssss.magicapi.core.context;
 
 /**
  * magic 用户对象

+ 1 - 3
magic-api/src/main/java/org/ssssssss/magicapi/context/RequestContext.java → magic-api/src/main/java/org/ssssssss/magicapi/core/context/RequestContext.java

@@ -1,6 +1,4 @@
-package org.ssssssss.magicapi.context;
-
-import org.ssssssss.magicapi.model.RequestEntity;
+package org.ssssssss.magicapi.core.context;
 
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;

+ 141 - 0
magic-api/src/main/java/org/ssssssss/magicapi/core/context/RequestEntity.java

@@ -0,0 +1,141 @@
+package org.ssssssss.magicapi.core.context;
+
+import org.ssssssss.magicapi.core.model.ApiInfo;
+import org.ssssssss.magicapi.core.model.DebugRequest;
+import org.ssssssss.script.MagicScriptContext;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.util.Map;
+import java.util.UUID;
+
+/**
+ * 请求信息
+ *
+ * @author mxd
+ */
+public class RequestEntity {
+
+	private final Long requestTime = System.currentTimeMillis();
+	private final String requestId = UUID.randomUUID().toString().replace("-", "");
+	private ApiInfo apiInfo;
+	private HttpServletRequest request;
+	private HttpServletResponse response;
+	private boolean requestedFromTest;
+	private Map<String, Object> parameters;
+	private Map<String, Object> pathVariables;
+	private MagicScriptContext magicScriptContext;
+	private Object requestBody;
+	private DebugRequest debugRequest;
+
+	private Map<String, Object> headers;
+
+	private RequestEntity() {
+
+	}
+
+	public static RequestEntity create() {
+		return new RequestEntity();
+	}
+
+	public ApiInfo getApiInfo() {
+		return apiInfo;
+	}
+
+	public RequestEntity info(ApiInfo apiInfo) {
+		this.apiInfo = apiInfo;
+		return this;
+	}
+
+	public HttpServletRequest getRequest() {
+		return request;
+	}
+
+	public RequestEntity request(HttpServletRequest request) {
+		this.request = request;
+		this.debugRequest = DebugRequest.create(request);
+		return this;
+	}
+
+	public HttpServletResponse getResponse() {
+		return response;
+	}
+
+	public RequestEntity response(HttpServletResponse response) {
+		this.response = response;
+		return this;
+	}
+
+	public boolean isRequestedFromTest() {
+		return requestedFromTest;
+	}
+
+	public RequestEntity requestedFromTest(boolean requestedFromTest) {
+		this.requestedFromTest = requestedFromTest;
+		return this;
+	}
+
+	public boolean isRequestedFromDebug() {
+		return requestedFromTest && !this.debugRequest.getRequestedBreakpoints().isEmpty();
+	}
+
+	public Map<String, Object> getParameters() {
+		return parameters;
+	}
+
+	public RequestEntity parameters(Map<String, Object> parameters) {
+		this.parameters = parameters;
+		return this;
+	}
+
+	public Map<String, Object> getPathVariables() {
+		return pathVariables;
+	}
+
+	public RequestEntity pathVariables(Map<String, Object> pathVariables) {
+		this.pathVariables = pathVariables;
+		return this;
+	}
+
+	public Long getRequestTime() {
+		return requestTime;
+	}
+
+	public MagicScriptContext getMagicScriptContext() {
+		return magicScriptContext;
+	}
+
+	public RequestEntity setMagicScriptContext(MagicScriptContext magicScriptContext) {
+		this.magicScriptContext = magicScriptContext;
+		return this;
+	}
+
+	public Map<String, Object> getHeaders() {
+		return headers;
+	}
+
+	public RequestEntity setHeaders(Map<String, Object> headers) {
+		this.headers = headers;
+		return this;
+	}
+
+	public String getRequestId() {
+		return requestId;
+	}
+
+	/**
+	 * 获取 RequestBody
+	 */
+	public Object getRequestBody() {
+		return this.requestBody;
+	}
+
+	public RequestEntity setRequestBody(Object requestBody) {
+		this.requestBody = requestBody;
+		return this;
+	}
+
+	public DebugRequest getDebugRequest() {
+		return debugRequest;
+	}
+}

+ 1 - 1
magic-api/src/main/java/org/ssssssss/magicapi/context/SessionContext.java → magic-api/src/main/java/org/ssssssss/magicapi/core/context/SessionContext.java

@@ -1,4 +1,4 @@
-package org.ssssssss.magicapi.context;
+package org.ssssssss.magicapi.core.context;
 
 import javax.servlet.http.HttpSession;
 import java.util.HashMap;

Некоторые файлы не были показаны из-за большого количества измененных файлов