Ver código fonte

Merge branch 'dev'

mxd 3 anos atrás
pai
commit
274da01494
37 arquivos alterados com 611 adições e 51 exclusões
  1. 1 1
      README.md
  2. 1 1
      magic-api-spring-boot-starter/pom.xml
  3. 1 1
      magic-api/pom.xml
  4. 1 1
      magic-api/src/main/java/org/ssssssss/magicapi/adapter/DialectAdapter.java
  5. 33 2
      magic-api/src/main/java/org/ssssssss/magicapi/controller/MagicAPIController.java
  6. 5 2
      magic-api/src/main/java/org/ssssssss/magicapi/controller/MagicDebugHandler.java
  7. 32 1
      magic-api/src/main/java/org/ssssssss/magicapi/controller/MagicFunctionController.java
  8. 18 0
      magic-api/src/main/java/org/ssssssss/magicapi/controller/MagicGroupController.java
  9. 1 1
      magic-api/src/main/java/org/ssssssss/magicapi/controller/RequestHandler.java
  10. 9 1
      magic-api/src/main/java/org/ssssssss/magicapi/interceptor/Authorization.java
  11. 3 0
      magic-api/src/main/java/org/ssssssss/magicapi/model/ApiInfo.java
  12. 4 0
      magic-api/src/main/java/org/ssssssss/magicapi/model/Constants.java
  13. 16 0
      magic-api/src/main/java/org/ssssssss/magicapi/model/FunctionInfo.java
  14. 2 0
      magic-api/src/main/java/org/ssssssss/magicapi/model/JsonCodeConstants.java
  15. 10 0
      magic-api/src/main/java/org/ssssssss/magicapi/model/MagicEntity.java
  16. 17 17
      magic-api/src/main/java/org/ssssssss/magicapi/modules/table/NamedTable.java
  17. 10 0
      magic-api/src/main/java/org/ssssssss/magicapi/provider/MagicAPIService.java
  18. 19 0
      magic-api/src/main/java/org/ssssssss/magicapi/provider/StoreServiceProvider.java
  19. 60 7
      magic-api/src/main/java/org/ssssssss/magicapi/provider/impl/DefaultMagicAPIService.java
  20. 2 2
      magic-api/src/main/java/org/ssssssss/magicapi/swagger/SwaggerEntity.java
  21. 1 1
      magic-api/src/main/java/org/ssssssss/magicapi/swagger/SwaggerProvider.java
  22. 8 0
      magic-api/src/main/java/org/ssssssss/magicapi/utils/JsonUtils.java
  23. 1 1
      magic-editor/pom.xml
  24. 1 1
      magic-editor/src/console/package.json
  25. 8 0
      magic-editor/src/console/src/assets/iconfont/iconfont.css
  26. BIN
      magic-editor/src/console/src/assets/iconfont/iconfont.ttf
  27. 13 5
      magic-editor/src/console/src/components/editor/magic-script-editor.vue
  28. 4 1
      magic-editor/src/console/src/components/layout/magic-group.vue
  29. 3 0
      magic-editor/src/console/src/components/layout/magic-option.vue
  30. 4 1
      magic-editor/src/console/src/components/magic-editor.vue
  31. 52 1
      magic-editor/src/console/src/components/resources/magic-api-list.vue
  32. 3 0
      magic-editor/src/console/src/components/resources/magic-datasource-list.vue
  33. 53 1
      magic-editor/src/console/src/components/resources/magic-function-list.vue
  34. 199 0
      magic-editor/src/console/src/components/resources/magic-group-choose.vue
  35. 11 0
      magic-editor/src/console/src/components/resources/magic-resource.css
  36. 3 0
      magic-editor/src/console/src/scripts/contants.js
  37. 2 2
      pom.xml

+ 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.4.2</version>
+    <version>1.4.3</version>
 </dependency>
 ```
 ## 修改application.properties

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

@@ -6,7 +6,7 @@
     <parent>
         <groupId>org.ssssssss</groupId>
         <artifactId>magic-api-parent</artifactId>
-        <version>1.4.2</version>
+        <version>1.4.3</version>
     </parent>
     <artifactId>magic-api-spring-boot-starter</artifactId>
     <packaging>jar</packaging>

+ 1 - 1
magic-api/pom.xml

@@ -6,7 +6,7 @@
     <parent>
         <groupId>org.ssssssss</groupId>
         <artifactId>magic-api-parent</artifactId>
-        <version>1.4.2</version>
+        <version>1.4.3</version>
     </parent>
     <artifactId>magic-api</artifactId>
     <packaging>jar</packaging>

+ 1 - 1
magic-api/src/main/java/org/ssssssss/magicapi/adapter/DialectAdapter.java

@@ -28,7 +28,7 @@ public class DialectAdapter {
 	}
 
 	public void add(Dialect dialect) {
-		this.dialectList.add(dialect);
+		this.dialectList.add(0, dialect);
 	}
 
 	public Dialect getDialectFromConnection(Connection connection) {

+ 33 - 2
magic-api/src/main/java/org/ssssssss/magicapi/controller/MagicAPIController.java

@@ -1,5 +1,6 @@
 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;
@@ -8,6 +9,7 @@ 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;
@@ -33,7 +35,9 @@ public class MagicAPIController extends MagicController implements MagicExceptio
 	@ResponseBody
 	@Valid(readonly = false)
 	public JsonBean<Boolean> delete(HttpServletRequest request, String id) {
-		isTrue(allowVisit(request, Authorization.DELETE, getApiInfo(id)), PERMISSION_INVALID);
+		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));
 	}
 
@@ -102,6 +106,7 @@ public class MagicAPIController extends MagicController implements MagicExceptio
 		// 新的分组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));
 	}
 
@@ -113,10 +118,36 @@ public class MagicAPIController extends MagicController implements MagicExceptio
 	@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));
 	}
 
-	private ApiInfo getApiInfo(String id){
+	/**
+	 * 锁定接口
+	 */
+	@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;

+ 5 - 2
magic-api/src/main/java/org/ssssssss/magicapi/controller/MagicDebugHandler.java

@@ -1,8 +1,8 @@
 package org.ssssssss.magicapi.controller;
 
+import org.apache.commons.lang3.StringUtils;
 import org.ssssssss.magicapi.config.Message;
 import org.ssssssss.magicapi.config.MessageType;
-import org.ssssssss.magicapi.config.WebSocketSessionManager;
 import org.ssssssss.magicapi.model.MagicConsoleSession;
 import org.ssssssss.script.MagicScriptDebugContext;
 
@@ -40,10 +40,13 @@ public class MagicDebugHandler {
 	 * 当本机没有该Session时,通知其他机器处理
 	 */
 	@Message(MessageType.RESUME_BREAKPOINT)
-	public boolean resumeBreakpoint(MagicConsoleSession session, String stepInto) {
+	public boolean resumeBreakpoint(MagicConsoleSession session, String stepInto, String breakpoints) {
 		MagicScriptDebugContext context = session.getMagicScriptDebugContext();
 		if (context != null) {
 			context.setStepInto("1".equals(stepInto));
+			if(StringUtils.isNotBlank(breakpoints)){
+				context.setBreakpoints(Stream.of(breakpoints.split("\\|")).map(Integer::valueOf).collect(Collectors.toList()));
+			}
 			try {
 				context.singal();
 			} catch (InterruptedException ignored) {

+ 32 - 1
magic-api/src/main/java/org/ssssssss/magicapi/controller/MagicFunctionController.java

@@ -1,5 +1,6 @@
 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;
@@ -7,6 +8,7 @@ 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;
 
@@ -63,6 +65,7 @@ public class MagicFunctionController extends MagicController implements MagicExc
 		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));
 	}
 
@@ -72,6 +75,10 @@ public class MagicFunctionController extends MagicController implements MagicExc
 	@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));
 	}
 
@@ -79,10 +86,34 @@ public class MagicFunctionController extends MagicController implements MagicExc
 	@ResponseBody
 	@Valid(readonly = false)
 	public JsonBean<Boolean> delete(HttpServletRequest request, String id) {
-		isTrue(allowVisit(request, Authorization.DELETE, getFunctionInfo(id)), PERMISSION_INVALID);
+		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);

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

@@ -70,4 +70,22 @@ public class MagicGroupController extends MagicController implements MagicExcept
 		isTrue(allowVisit(request, Authorization.SAVE, group), PERMISSION_INVALID);
 		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(!"0".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));
+	}
 }

+ 1 - 1
magic-api/src/main/java/org/ssssssss/magicapi/controller/RequestHandler.java

@@ -340,7 +340,7 @@ public class RequestHandler extends MagicController {
 			debugContext.setCallback(variables -> {
 				List<Map<String, Object>> varList = (List<Map<String, Object>>) variables.get("variables");
 				varList.stream().filter(it -> it.containsKey("value")).forEach(variable -> {
-					variable.put("value", JsonUtils.toJsonString(variable.get("value")));
+					variable.put("value", JsonUtils.toJsonStringWithoutLog(variable.get("value")));
 				});
 				WebSocketSessionManager.sendBySessionId(sessionId, BREAKPOINT, variables);
 			});

+ 9 - 1
magic-api/src/main/java/org/ssssssss/magicapi/interceptor/Authorization.java

@@ -28,5 +28,13 @@ public enum Authorization {
 	/**
 	 * 执行推送动作
 	 */
-	PUSH
+	PUSH,
+	/**
+	 * 锁定动作
+	 */
+	LOCK,
+	/**
+	 * 解锁动作
+	 */
+	UNLOCK
 }

+ 3 - 0
magic-api/src/main/java/org/ssssssss/magicapi/model/ApiInfo.java

@@ -264,6 +264,7 @@ public class ApiInfo extends MagicEntity {
 		target.setGroupId(this.getGroupId());
 		target.setPath(this.getPath());
 		target.setMethod(this.getMethod());
+		target.setLock(this.getLock());
 		return target;
 	}
 
@@ -311,6 +312,8 @@ public class ApiInfo extends MagicEntity {
 		info.setPaths(this.paths);
 		info.setRequestBodyDefinition(this.requestBodyDefinition);
 		info.setResponseBodyDefinition(this.responseBodyDefinition);
+		info.setLock(this.lock);
+		info.setProperties(this.properties);
 		return info;
 	}
 }

+ 4 - 0
magic-api/src/main/java/org/ssssssss/magicapi/model/Constants.java

@@ -107,6 +107,10 @@ public class Constants {
 
 	public static final String UPLOAD_MODE_FULL = "full";
 
+	public static final String LOCK = "1";
+
+	public static final String UNLOCK = "0";
+
 	/**
 	 * 执行成功的code值
 	 */

+ 16 - 0
magic-api/src/main/java/org/ssssssss/magicapi/model/FunctionInfo.java

@@ -87,4 +87,20 @@ public class FunctionInfo extends MagicEntity {
 	public int hashCode() {
 		return Objects.hash(id, path, script, name, groupId, parameters, description, returnType);
 	}
+
+	public FunctionInfo copy(){
+		FunctionInfo info = new FunctionInfo();
+		info.setId(this.id);
+		info.setName(this.name);
+		info.setGroupId(this.groupId);
+		info.setScript(this.script);
+		info.setDescription(this.description);
+		info.setParameters(this.parameters);
+		info.setPath(this.path);
+		info.setMappingPath(this.mappingPath);
+		info.setReturnType(this.returnType);
+		info.setProperties(this.properties);
+		info.setLock(this.lock);
+		return info;
+	}
 }

+ 2 - 0
magic-api/src/main/java/org/ssssssss/magicapi/model/JsonCodeConstants.java

@@ -19,6 +19,8 @@ public interface JsonCodeConstants {
 
 	JsonCode NAME_CONFLICT = new JsonCode(0, "移动后名称会重复,请修改名称后在试。");
 
+	JsonCode RESOURCE_LOCKED = new JsonCode(0, "当前资源已被锁定,请解锁后在操作。");
+
 	JsonCode REQUEST_PATH_CONFLICT = new JsonCode(0, "该路径已被映射,请换一个请求方法或路径");
 
 	JsonCode FUNCTION_PATH_CONFLICT = new JsonCode(0, "该路径已被映射,请换一个请求方法或路径");

+ 10 - 0
magic-api/src/main/java/org/ssssssss/magicapi/model/MagicEntity.java

@@ -14,6 +14,8 @@ public class MagicEntity extends Attributes<Object> implements Cloneable {
 
 	protected Long updateTime;
 
+	protected String lock;
+
 	public String getId() {
 		return id;
 	}
@@ -62,6 +64,14 @@ public class MagicEntity extends Attributes<Object> implements Cloneable {
 		this.name = name;
 	}
 
+	public String getLock() {
+		return lock;
+	}
+
+	public void setLock(String lock) {
+		this.lock = lock;
+	}
+
 	public MagicEntity clone() {
 		try {
 			return (MagicEntity) super.clone();

+ 17 - 17
magic-api/src/main/java/org/ssssssss/magicapi/modules/table/NamedTable.java

@@ -48,10 +48,10 @@ public class NamedTable {
 		this.logicDeleteColumn = sqlModule.getLogicDeleteColumn();
 		String deleteValue = sqlModule.getLogicDeleteValue();
 		this.logicDeleteValue = deleteValue;
-		if(deleteValue != null){
-			if((deleteValue.startsWith("'") || deleteValue.startsWith("\"")) && deleteValue.length() > 2){
-				this.logicDeleteValue = deleteValue.substring(1,deleteValue.length() - 1);
-			}else{
+		if (deleteValue != null) {
+			if ((deleteValue.startsWith("'") || deleteValue.startsWith("\"")) && deleteValue.length() > 2) {
+				this.logicDeleteValue = deleteValue.substring(1, deleteValue.length() - 1);
+			} else {
 				try {
 					this.logicDeleteValue = Integer.parseInt(deleteValue);
 				} catch (NumberFormatException e) {
@@ -62,13 +62,13 @@ public class NamedTable {
 	}
 
 	@Comment("使用逻辑删除")
-	public NamedTable logic(){
+	public NamedTable logic() {
 		this.useLogic = true;
 		return this;
 	}
 
 	@Comment("更新空值")
-	public NamedTable withBlank(){
+	public NamedTable withBlank() {
 		this.withBlank = true;
 		return this;
 	}
@@ -107,24 +107,24 @@ public class NamedTable {
 	}
 
 	@Comment("设置要排除的列")
-	public NamedTable exclude(String column){
-		if(column != null){
+	public NamedTable exclude(String column) {
+		if (column != null) {
 			excludeColumns.add(column);
 		}
 		return this;
 	}
 
 	@Comment("设置要排除的列")
-	public NamedTable excludes(String ... columns){
-		if(columns != null){
+	public NamedTable excludes(String... columns) {
+		if (columns != null) {
 			excludeColumns.addAll(Arrays.asList(columns));
 		}
 		return this;
 	}
 
 	@Comment("设置要排除的列")
-	public NamedTable excludes(List<String> columns){
-		if(columns != null){
+	public NamedTable excludes(List<String> columns) {
+		if (columns != null) {
 			excludeColumns.addAll(columns);
 		}
 		return this;
@@ -169,7 +169,7 @@ public class NamedTable {
 	}
 
 	private Collection<Map.Entry<String, Object>> filterNotBlanks() {
-		if(this.withBlank){
+		if (this.withBlank) {
 			return this.columns.entrySet()
 					.stream()
 					.filter(it -> !excludeColumns.contains(it.getKey()))
@@ -212,7 +212,7 @@ public class NamedTable {
 
 	@Comment("执行delete语句")
 	public int delete() {
-		if(useLogic){
+		if (useLogic) {
 			Map<String, Object> dataMap = new HashMap<>();
 			dataMap.put(logicDeleteColumn, logicDeleteValue);
 			return update(dataMap);
@@ -315,7 +315,7 @@ public class NamedTable {
 			where.ne(useLogic, logicDeleteColumn, logicDeleteValue);
 			builder.append(where.getSql());
 			params.addAll(where.getParams());
-		}else if(useLogic){
+		} else if (useLogic) {
 			where.ne(logicDeleteColumn, logicDeleteValue);
 			builder.append(where.getSql());
 			params.addAll(where.getParams());
@@ -378,7 +378,7 @@ public class NamedTable {
 	}
 
 	@Comment("查询条数")
-	public int count(){
+	public int count() {
 		StringBuilder builder = new StringBuilder();
 		builder.append("select count(1) from ").append(tableName);
 		List<Object> params = buildWhere(builder);
@@ -386,7 +386,7 @@ public class NamedTable {
 	}
 
 	@Comment("判断是否存在")
-	public boolean exists(){
+	public boolean exists() {
 		return count() > 0;
 	}
 

+ 10 - 0
magic-api/src/main/java/org/ssssssss/magicapi/provider/MagicAPIService.java

@@ -47,6 +47,14 @@ public interface MagicAPIService extends MagicModule {
 	 */
 	String saveApi(ApiInfo apiInfo);
 
+	boolean lockApi(String id);
+
+	boolean unlockApi(String id);
+
+	boolean lockFunction(String id);
+
+	boolean unlockFunction(String id);
+
 	/**
 	 * 获取接口详情
 	 *
@@ -201,4 +209,6 @@ public interface MagicAPIService extends MagicModule {
 	 * 处理刷新通知
 	 */
 	boolean processNotify(MagicNotify magicNotify);
+
+	String copyGroup(String src, String target);
 }

+ 19 - 0
magic-api/src/main/java/org/ssssssss/magicapi/provider/StoreServiceProvider.java

@@ -4,6 +4,7 @@ import org.apache.commons.lang3.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.ssssssss.magicapi.adapter.Resource;
+import org.ssssssss.magicapi.model.Constants;
 import org.ssssssss.magicapi.model.MagicEntity;
 import org.ssssssss.magicapi.utils.JsonUtils;
 
@@ -84,6 +85,24 @@ public abstract class StoreServiceProvider<T extends MagicEntity> {
 		return false;
 	}
 
+	public boolean lock(String id){
+		T info = get(id);
+		if(info != null){
+			info.setLock(Constants.LOCK);
+			return update(info);
+		}
+		return false;
+	}
+
+	public boolean unlock(String id){
+		T info = get(id);
+		if(info != null){
+			info.setLock(Constants.UNLOCK);
+			return update(info);
+		}
+		return false;
+	}
+
 	/**
 	 * 查询所有(提供给页面,无需带script)
 	 */

+ 60 - 7
magic-api/src/main/java/org/ssssssss/magicapi/provider/impl/DefaultMagicAPIService.java

@@ -123,7 +123,7 @@ public class DefaultMagicAPIService implements MagicAPIService, JsonCodeConstant
 						MagicScriptContext.set(newContext);
 						try {
 							Object value = ScriptManager.executeScript(info.getScript(), newContext);
-							if(value instanceof ExitValue){
+							if (value instanceof ExitValue) {
 								throw new MagicExitException((ExitValue) value);
 							}
 							return value;
@@ -228,6 +228,33 @@ public class DefaultMagicAPIService implements MagicAPIService, JsonCodeConstant
 		return info.getId();
 	}
 
+	@Override
+	public boolean lockApi(String id) {
+		return lockWithNotify(apiServiceProvider.lock(id), id, NOTIFY_ACTION_API);
+	}
+
+	@Override
+	public boolean unlockApi(String id) {
+		return lockWithNotify(apiServiceProvider.unlock(id), id, NOTIFY_ACTION_API);
+	}
+
+	@Override
+	public boolean lockFunction(String id) {
+		return lockWithNotify(functionServiceProvider.lock(id), id, NOTIFY_ACTION_FUNCTION);
+	}
+
+	@Override
+	public boolean unlockFunction(String id) {
+		return lockWithNotify(functionServiceProvider.unlock(id), id, NOTIFY_ACTION_FUNCTION);
+	}
+
+	private boolean lockWithNotify(boolean success, String id, int type) {
+		if (success) {
+			magicNotifyService.sendNotify(new MagicNotify(instanceId, id, NOTIFY_ACTION_UPDATE, type));
+		}
+		return success;
+	}
+
 	@Override
 	public ApiInfo getApiInfo(String id) {
 		return apiServiceProvider.get(id);
@@ -288,7 +315,7 @@ public class DefaultMagicAPIService implements MagicAPIService, JsonCodeConstant
 			isTrue(!functionServiceProvider.existsWithoutId(functionInfo), FUNCTION_ALREADY_EXISTS.format(functionInfo.getPath()));
 			FunctionInfo oldInfo = functionServiceProvider.get(functionInfo.getId());
 			isTrue(functionServiceProvider.update(functionInfo), FUNCTION_SAVE_FAILURE);
-			if(!oldInfo.getScript().equals(functionInfo.getScript())){
+			if (!oldInfo.getScript().equals(functionInfo.getScript())) {
 				backupService.backup(functionInfo);
 			}
 		}
@@ -555,7 +582,7 @@ public class DefaultMagicAPIService implements MagicAPIService, JsonCodeConstant
 			// 检查上级分组是否存在
 			isTrue("0".equals(group.getParentId()) || groupServiceProvider.getGroupResource(group.getParentId()).exists(), GROUP_NOT_FOUND);
 		}
-		if(checked) {
+		if (checked) {
 			// 检测分组是否有冲突
 			groups.forEach(group -> {
 				Resource resource;
@@ -569,7 +596,7 @@ public class DefaultMagicAPIService implements MagicAPIService, JsonCodeConstant
 					isTrue(src == null || src.getId().equals(group.getId()), GROUP_CONFLICT);
 				}
 			});
-		}else{
+		} else {
 			Resource resource = workspace.getDirectory(PATH_API);
 			resource.delete();
 			resource.mkdir();
@@ -710,7 +737,7 @@ public class DefaultMagicAPIService implements MagicAPIService, JsonCodeConstant
 			case NOTIFY_ACTION_ALL:
 				return processAllNotify();
 		}
-		switch (action){
+		switch (action) {
 			case NOTIFY_WS_C_S:
 				return processWebSocketMessageReceived(magicNotify.getSessionId(), magicNotify.getContent());
 			case NOTIFY_WS_S_C:
@@ -719,6 +746,32 @@ public class DefaultMagicAPIService implements MagicAPIService, JsonCodeConstant
 		return false;
 	}
 
+	@Override
+	public String copyGroup(String srcId, String target) {
+		Group src = getGroup(srcId);
+		src.setId(null);
+		src.setParentId(target);
+		src.setName(src.getName() + "(复制)");
+		src.setPath(src.getPath() + "_copy");
+		String newId = createGroup(src);
+		if (GROUP_TYPE_API.equals(src.getType())) {
+			apiServiceProvider.listWithScript()
+					.stream().filter(it -> srcId.equals(it.getGroupId()))
+					.map(ApiInfo::copy)
+					.peek(it -> it.setGroupId(newId))
+					.peek(it -> it.setId(null))
+					.forEach(this::saveApi);
+		} else {
+			functionServiceProvider.listWithScript()
+					.stream().filter(it -> srcId.equals(it.getGroupId()))
+					.map(FunctionInfo::copy)
+					.peek(it -> it.setGroupId(newId))
+					.peek(it -> it.setId(null))
+					.forEach(this::saveFunction);
+		}
+		return newId;
+	}
+
 	@Override
 	public String getModuleName() {
 		return "magic";
@@ -867,7 +920,7 @@ public class DefaultMagicAPIService implements MagicAPIService, JsonCodeConstant
 			for (Resource file : root.files(".ms")) {
 				if (isApi) {
 					ApiInfo info = apiServiceProvider.deserialize(file.read());
-					if (checked){
+					if (checked) {
 						checkApiConflict(info);
 					}
 					apiInfos.add(info);
@@ -875,7 +928,7 @@ public class DefaultMagicAPIService implements MagicAPIService, JsonCodeConstant
 					isTrue(apiPaths.add(apiPath), UPLOAD_PATH_CONFLICT.format(apiPath));
 				} else {
 					FunctionInfo info = functionServiceProvider.deserialize(file.read());
-					if (checked){
+					if (checked) {
 						checkFunctionConflict(info);
 					}
 					functionInfos.add(info);

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

@@ -221,7 +221,7 @@ public class SwaggerEntity {
 
 		private String description;
 
-		private String operationId = UUID.randomUUID().toString().replace("-", "");
+		private String operationId;
 
 		private List<String> produces = new ArrayList<>();
 
@@ -247,7 +247,7 @@ public class SwaggerEntity {
 			return operationId;
 		}
 
-		public void setOperationId(String operationId) {
+		public Path(String operationId) {
 			this.operationId = operationId;
 		}
 

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

@@ -71,7 +71,7 @@ public class SwaggerProvider {
 		for (ApiInfo info : infos) {
 			String groupName = groupServiceProvider.getFullName(info.getGroupId()).replace("/", "-");
 			String requestPath = "/" + mappingHandlerMapping.getRequestPath(info.getGroupId(), info.getPath());
-			SwaggerEntity.Path path = new SwaggerEntity.Path();
+			SwaggerEntity.Path path = new SwaggerEntity.Path(info.getId());
 			path.addTag(groupName);
 			boolean hasBody = false;
 			try {

+ 8 - 0
magic-api/src/main/java/org/ssssssss/magicapi/utils/JsonUtils.java

@@ -31,6 +31,14 @@ public class JsonUtils {
 		}
 	}
 
+	public static String toJsonStringWithoutLog(Object target) {
+		try {
+			return mapper.writeValueAsString(target);
+		} catch (Exception e) {
+			return target == null ? null : target.toString();
+		}
+	}
+
 	public static <T> T readValue(String json, TypeReference<T> typeReference) {
 		try {
 			return mapper.readValue(json, typeReference);

+ 1 - 1
magic-editor/pom.xml

@@ -6,7 +6,7 @@
     <parent>
         <groupId>org.ssssssss</groupId>
         <artifactId>magic-api-parent</artifactId>
-        <version>1.4.2</version>
+        <version>1.4.3</version>
     </parent>
     <artifactId>magic-editor</artifactId>
     <packaging>jar</packaging>

+ 1 - 1
magic-editor/src/console/package.json

@@ -1,6 +1,6 @@
 {
   "name": "magic-editor",
-  "version": "1.4.2",
+  "version": "1.4.3",
   "private": false,
   "description": "magic-editor for magic-api",
   "main": "dist/magic-editor.umd.min.js",

+ 8 - 0
magic-editor/src/console/src/assets/iconfont/iconfont.css

@@ -11,6 +11,14 @@
   -moz-osx-font-smoothing: grayscale;
 }
 
+.ma-icon-lock:before {
+  content: "\e64f";
+}
+
+.ma-icon-unlock:before {
+  content: "\e783";
+}
+
 .ma-icon-collapse:before {
   content: "\e609";
 }

BIN
magic-editor/src/console/src/assets/iconfont/iconfont.ttf


+ 13 - 5
magic-editor/src/console/src/components/editor/magic-script-editor.vue

@@ -18,7 +18,8 @@
         >
           <i class="ma-svg-icon" v-if="item._type === 'api'" :class="['request-method-' + item.method]" />
           <i class="ma-svg-icon" v-if="item._type !== 'api'" :class="['icon-function']" />
-          {{item.name}}<span v-show="!item.id || item.script !== item.ext.tmpScript">*</span>
+          {{item.name}}<i class="ma-icon ma-icon-lock" v-if="item.lock === '1'" />
+          <span v-show="!item.id || item.script !== item.ext.tmpScript">*</span>
           <i class="ma-icon ma-icon-close" @click.stop="close(item.id || item.tmp_id)"/>
         </li>
       </ul>
@@ -548,7 +549,7 @@ export default {
         requestConfig.headers['Content-Type'] = 'multipart/form-data';
         let formData = new FormData()
         Object.keys(params).forEach(key => {
-          let value = requestConfig.data[key];
+          let value = params[key];
           if(value instanceof FileList){
             value.forEach(file => formData.append(key, file, file.name))
           }else{
@@ -620,7 +621,12 @@ export default {
         target.ext.debugDecorations && this.editor.deltaDecorations(target.ext.debugDecorations, [])
         target.ext.debuging = false
         target.ext.variables = []
-        bus.$emit('message', 'resume_breakpoint', step === true ? '1' : '0')
+        bus.$emit('message', 'resume_breakpoint', (step === true ? '1' : '0')+ ',' + this.editor
+          .getModel()
+          .getAllDecorations()
+          .filter(it => it.options.linesDecorationsClassName === 'breakpoints')
+          .map(it => it.range.startLineNumber)
+          .join('|'))
       }
     },
     doStepInto() {
@@ -958,12 +964,14 @@ ul li.draggableTargetItem {
   background: var(--hover-background);
 }
 
-ul li i {
+ul li i:not(.ma-icon-lock) {
   color: var(--icon-color);
   margin-left: 5px;
   font-size: 0.5em;
 }
-
+.ma-icon-lock{
+  margin-left: 5px;
+}
 .ma-editor-container > div {
   flex: 1;
 }

+ 4 - 1
magic-editor/src/console/src/components/layout/magic-group.vue

@@ -90,7 +90,8 @@ import MagicInput from '@/components/common/magic-input.vue'
 import MagicSelect from '@/components/common/magic-select.vue'
 import request from "@/api/request"
 import { requestGroup } from '@/scripts/utils.js'
-import bus from "@/scripts/bus";
+import bus from "@/scripts/bus"
+import contants from "@/scripts/contants.js"
 
 export default {
   name: 'MagicGroup',
@@ -128,6 +129,8 @@ export default {
   mounted() {
     let map = {}
     request.send('/options').success(data => {
+      data = data || []
+      data = data.concat(contants.OPTIONS)
       this.defaultOptions = data&&data.map(e => {
         let item = {text: e[0], value: e[0], description: e[1], defaultValue: e[2]}
         this.optionsMap[item.value] = item;

+ 3 - 0
magic-editor/src/console/src/components/layout/magic-option.vue

@@ -35,6 +35,7 @@
 import request from '@/api/request.js'
 import MagicInput from '@/components/common/magic-input.vue'
 import MagicSelect from '@/components/common/magic-select.vue'
+import contants from "@/scripts/contants.js"
 
 export default {
   name: 'MagicOption',
@@ -55,6 +56,8 @@ export default {
   mounted() {
     let map = {}
       request.send('/options').success(data => {
+        data = data || []
+        data = data.concat(contants.OPTIONS)
         this.defaultOptions = data&&data.map(e => {
           let item = {text: e[0], value: e[0], description: e[1], defaultValue: e[2]}
           this.optionsMap[item.value] = item;

+ 4 - 1
magic-editor/src/console/src/components/magic-editor.vue

@@ -105,7 +105,7 @@ export default {
     if (contants.BASE_URL.startsWith('http')) { // http开头
       link = contants.BASE_URL
     } else if (contants.BASE_URL.startsWith('/')) { // / 开头的
-      link = link + contants.BASE_URL
+      link = `${location.protocol}/${location.host}${contants.BASE_URL}`
     } else {
       link = link + '/' + contants.BASE_URL
     }
@@ -114,6 +114,9 @@ export default {
     })
     bus.$on('ws_open', () => bus.$emit('message', 'login', contants.HEADER_MAGIC_TOKEN_VALUE))
     contants.DEFAULT_EXPAND = this.config.defaultExpand !== false
+    contants.JDBC_DRIVERS = this.config.jdbcDrivers || []
+    contants.DATASOURCE_TYPES = this.config.datasourceTypes || []
+    contants.OPTIONS = this.config.options || []
     this.config.version = contants.MAGIC_API_VERSION_TEXT
     this.config.title = this.config.title || 'magic-api'
     this.config.themes = this.config.themes || {}

+ 52 - 1
magic-editor/src/console/src/components/resources/magic-api-list.vue

@@ -64,6 +64,7 @@
           <i class="ma-svg-icon" :class="['request-method-' + item.method]" />
           <label>{{ item.name }}</label>
           <span>({{ item.path }})</span>
+          <i class="ma-icon ma-icon-lock" v-if="item.lock === '1'"></i>
         </div>
       </template>
     </magic-tree>
@@ -82,6 +83,15 @@
         <button class="ma-button" @click="createGroupAction(false)">取消</button>
       </template>
     </magic-dialog>
+    <magic-dialog v-model="groupChooseVisible" title="复制分组" align="right" :moveable="false" width="340px" height="390px"
+                  className="ma-tree-wrapper">
+      <template #content>
+        <magic-group-choose ref="groupChoose" rootName="接口分组" type="1" height="300px" max-height="300px"/>
+      </template>
+      <template #buttons>
+        <button class="ma-button active" @click="copyGroup">复制</button>
+      </template>
+    </magic-dialog>
   </div>
 </template>
 
@@ -91,6 +101,7 @@ import MagicTree from '../common/magic-tree.vue'
 import request from '@/api/request.js'
 import MagicDialog from '@/components/common/modal/magic-dialog.vue'
 import MagicInput from '@/components/common/magic-input.vue'
+import MagicGroupChoose from '@/components/resources/magic-group-choose.vue'
 import { replaceURL, download as downloadFile, requestGroup, goToAnchor, deepClone } from '@/scripts/utils.js'
 import contants from '@/scripts/contants.js'
 import Key from '@/scripts/hotkey.js'
@@ -103,7 +114,8 @@ export default {
   components: {
     MagicTree,
     MagicDialog,
-    MagicInput
+    MagicInput,
+    MagicGroupChoose
   },
   data() {
     return {
@@ -116,6 +128,8 @@ export default {
       tree: [],
       // 数据排序规则,true:升序,false:降序
       treeSort: true,
+      groupChooseVisible: false,
+      srcId: '',
       // 新建分组对象
       createGroupObj: {
         visible: false,
@@ -320,6 +334,7 @@ export default {
                 parameters: null,
                 paths: null,
                 option: null,
+                lock: '0',
                 requestBody: null,
                 headers: null,
                 responseBody: null,
@@ -356,6 +371,15 @@ export default {
               this.openCreateGroupModal(item)
             }
           },
+          {
+            label: '复制分组',
+            icon: 'ma-icon-copy',
+            onClick: () => {
+              this.srcId = item.id
+              this.groupChooseVisible = true
+              this.$refs.groupChoose.initData()
+            }
+          },
           {
             label: '删除分组',
             icon: 'ma-icon-delete',
@@ -449,9 +473,27 @@ export default {
               this.copyPathToClipboard(item, true)
             }
           },
+          {
+            label: `${item.lock === '1' ? '解锁' : '锁定'}`,
+            icon: `ma-icon-${item.lock === '1' ? 'unlock' : 'lock'}`,
+            onClick: () => {
+              let action = item.lock === '1' ? '解锁接口' : '锁定接口';
+              request.send(item.lock === '1' ? 'unlock' : 'lock', {id: item.id}).success(data => {
+                if (data) {
+                  bus.$emit('status', `${action}「${item.name}(${item.path})」`)
+                  bus.$emit('report', `api_${item.lock === '1' ? 'unlock' : 'lock'}`)
+                  item['lock'] = item.lock === '1' ? '0' : '1';
+                  this.changeForceUpdate()
+                } else {
+                  this.$magicAlert({content: `${action}失败`})
+                }
+              })
+            }
+          },
           {
             label: '刷新接口',
             icon: 'ma-icon-refresh',
+            divided: true,
             onClick: () => {
               this.initData()
             }
@@ -473,6 +515,15 @@ export default {
       })
       return false
     },
+    copyGroup(){
+      let target = this.$refs.groupChoose.getSelected()
+      if(target && this.srcId){
+        this.groupChooseVisible = false
+        request.send('group/copy', { src: this.srcId, target }).success(() => {
+          this.initData();
+        })
+      }
+    },
     // 删除接口
     deleteApiInfo(item) {
       bus.$emit('status', `准备删除接口「${item.name}(${item.path})」`)

+ 3 - 0
magic-editor/src/console/src/components/resources/magic-datasource-list.vue

@@ -83,6 +83,7 @@
 <script>
 import bus from '@/scripts/bus.js'
 import request from '@/api/request.js'
+import contants from "@/scripts/contants.js"
 import MagicDialog from '@/components/common/modal/magic-dialog.vue'
 import MagicInput from '@/components/common/magic-input.vue'
 import {formatJson, isVisible, replaceURL} from '@/scripts/utils.js'
@@ -121,12 +122,14 @@ export default {
           'org.postgresql.Driver',
           'com.microsoft.sqlserver.jdbc.SQLServerDriver',
           'com.ibm.db2.jcc.DB2Driver',
+          ...contants.JDBC_DRIVERS
       ].map(it => { return {text: it, value: it} }),
       datasourceTypes: [
           'com.zaxxer.hikari.HikariDataSource',
           'com.alibaba.druid.pool.DruidDataSource',
           'org.apache.tomcat.jdbc.pool.DataSource',
           'org.apache.commons.dbcp2.BasicDataSource',
+          ...contants.DATASOURCE_TYPES
       ].map(it => { return {text: it, value: it} }),
       editor: null,
       // 是否展示loading

+ 53 - 1
magic-editor/src/console/src/components/resources/magic-function-list.vue

@@ -62,6 +62,7 @@
           <i class="ma-svg-icon icon-function" />
           <label>{{ item.name }}</label>
           <span>({{ item.path }})</span>
+          <i class="ma-icon ma-icon-lock" v-if="item.lock === '1'"></i>
         </div>
       </template>
     </magic-tree>
@@ -80,6 +81,16 @@
         <button class="ma-button" @click="createGroupAction(false)">取消</button>
       </template>
     </magic-dialog>
+
+    <magic-dialog v-model="groupChooseVisible" title="复制分组" align="right" :moveable="false" width="340px" height="390px"
+                  className="ma-tree-wrapper">
+      <template #content>
+        <magic-group-choose ref="groupChoose" rootName="函数分组" type="2" height="300px" max-height="300px"/>
+      </template>
+      <template #buttons>
+        <button class="ma-button active" @click="copyGroup">复制</button>
+      </template>
+    </magic-dialog>
   </div>
 </template>
 
@@ -89,6 +100,7 @@ import MagicTree from '@/components/common/magic-tree.vue'
 import request from '@/api/request.js'
 import MagicDialog from '@/components/common/modal/magic-dialog.vue'
 import MagicInput from '@/components/common/magic-input.vue'
+import MagicGroupChoose from '@/components/resources/magic-group-choose.vue'
 import { replaceURL, requestGroup, goToAnchor, deepClone } from '@/scripts/utils.js'
 import JavaClass from '@/scripts/editor/java-class.js'
 import Key from '@/scripts/hotkey.js'
@@ -102,7 +114,8 @@ export default {
   components: {
     MagicTree,
     MagicDialog,
-    MagicInput
+    MagicInput,
+    MagicGroupChoose
   },
   data() {
     return {
@@ -115,6 +128,8 @@ export default {
       tree: [],
       // 数据排序规则,true:升序,false:降序
       treeSort: true,
+      groupChooseVisible: false,
+      srcId: '',
       // 新建分组对象
       createGroupObj: {
         visible: false,
@@ -319,6 +334,7 @@ export default {
                 path: '',
                 script: null,
                 name: '未定义名称',
+                lock: '0',
                 parameters: null,
                 description: null,
                 level: item.level + 1,
@@ -352,6 +368,15 @@ export default {
               this.openCreateGroupModal(item)
             }
           },
+          {
+            label: '复制分组',
+            icon: 'ma-icon-copy',
+            onClick: () => {
+              this.srcId = item.id
+              this.groupChooseVisible = true
+              this.$refs.groupChoose.initData()
+            }
+          },
           {
             label: '删除分组',
             icon: 'ma-icon-delete',
@@ -413,9 +438,27 @@ export default {
               this.open(newItem)
             }
           },
+          {
+            label: `${item.lock === '1' ? '解锁' : '锁定'}`,
+            icon: `ma-icon-${item.lock === '1' ? 'unlock' : 'lock'}`,
+            onClick: () => {
+              let action = item.lock === '1' ? '解锁函数' : '锁定函数';
+              request.send(item.lock === '1' ? 'function/unlock' : 'function/lock', {id: item.id}).success(data => {
+                if (data) {
+                  bus.$emit('status', `${action}「${item.name}(${item.path})」`)
+                  bus.$emit('report', `function_${item.lock === '1' ? 'unlock' : 'lock'}`)
+                  item['lock'] = item.lock === '1' ? '0' : '1';
+                  this.changeForceUpdate()
+                } else {
+                  this.$magicAlert({content: `${action}失败`})
+                }
+              })
+            }
+          },
           {
             label: '刷新函数',
             icon: 'ma-icon-refresh',
+            divided: true,
             onClick: () => {
               this.initData()
             }
@@ -437,6 +480,15 @@ export default {
       })
       return false
     },
+    copyGroup(){
+      let target = this.$refs.groupChoose.getSelected()
+      if(target && this.srcId){
+        this.groupChooseVisible = false
+        request.send('group/copy', { src: this.srcId, target }).success(() => {
+          this.initData();
+        })
+      }
+    },
     // 删除接口
     deleteApiInfo(item) {
       bus.$emit('status', `准备删除函数「${item.name}(${item.path})」`)

+ 199 - 0
magic-editor/src/console/src/components/resources/magic-group-choose.vue

@@ -0,0 +1,199 @@
+<template>
+    <magic-tree :data="tree" :forceUpdate="forceUpdate" :style="{ height, maxHeight}" style="overflow: auto" :loading="showLoading > 0">
+      <template #folder="{ item }">
+        <div
+            :style="{ 'padding-left': 17 * item.level + 'px' }"
+            :title="`${item.name||''}${item.parentId !== 'root' ? '(' + (item.path || '') + ')' : ''}`"
+            class="ma-tree-item-header ma-tree-hover"
+            @click.stop="$set(item,'opened',!item.opened)"
+        >
+          <magic-checkbox :value="item.id === selectedItem" :checked-half="item.checkedHalf" @change="doSelected(item)"/>
+          <i :class="item.opened ? 'ma-icon-arrow-bottom' : 'ma-icon-arrow-right'" class="ma-icon" />
+          <i class="ma-icon ma-icon-list"></i>
+          <label>{{ item.name }}</label>
+          <span v-if="item.parentId !== 'root'">({{ item.path }})</span>
+        </div>
+      </template>
+    </magic-tree>
+</template>
+
+<script>
+import bus from '../../scripts/bus.js'
+import MagicTree from '../common/magic-tree.vue'
+import request from '@/api/request.js'
+import contants from '@/scripts/contants.js'
+import MagicCheckbox from "@/components/common/magic-checkbox";
+
+export default {
+  name: 'MagicGroupChoose',
+  props: {
+    height: {
+      type: String,
+      required: true
+    },
+    maxHeight: {
+      type: String,
+      required: true
+    },
+    rootName: {
+      type: String,
+      required: true
+    },
+    type: {
+      type: String,
+      required: true
+    }
+  },
+  components: {
+    MagicCheckbox,
+    MagicTree
+  },
+  data() {
+    return {
+      selectedItem: '',
+      bus: bus,
+      // 分组list数据
+      listGroupData: [],
+      // 分组+接口tree数据
+      tree: [],
+      // 数据排序规则,true:升序,false:降序
+      treeSort: true,
+      // 绑定给magic-tree组件,用来触发子组件强制更新
+      forceUpdate: true,
+      // 是否展示tree-loading,0表示不展示,大于0表示展示
+      showLoading: 0
+    }
+  },
+  methods: {
+    // 初始化数据
+    initData() {
+      this.showLoading = 1
+      this.tree = []
+      this.listGroupData = [
+          { id: '0',_type: 'root', name: this.rootName, parentId: 'root', path:'', selected: false, checkedHalf: false}
+      ]
+      request.send(`group/list?type=${this.type}`).success(data => {
+        data = data || []
+        this.listGroupData.push(...data.map(it => {
+          it.selected = false;
+          it.checkedHalf = false;
+          it._type = 'group';
+          return it;
+        }))
+        this.initTreeData()
+        this.showLoading--
+      })
+    },
+    // 初始化tree结构数据
+    initTreeData() {
+      // 1.把所有的分组id存map,方便接口列表放入,root为没有分组的接口
+      let groupItem = {root: []}
+      this.listGroupData.forEach(element => {
+        groupItem[element.id] = []
+        element.folder = true
+        this.$set(element, 'opened', true)
+        // 缓存一个name和path给后面使用
+        element.tmpName = element.name.indexOf('/') === 0 ? element.name : '/' + element.name
+        element.tmpPath = element.path.indexOf('/') === 0 ? element.path : '/' + element.path
+      })
+      // 3.将分组列表变成tree,并放入接口列表,分组在前,接口在后
+      let arrayToTree = (arr, parentItem, groupName, groupPath, level) => {
+        //  arr 是返回的数据  parendId 父id
+        let temp = []
+        let treeArr = arr
+        treeArr.forEach((item, index) => {
+          if (item.parentId === parentItem.id) {
+            item.level = level
+            item.tmpName = groupName + item.tmpName
+            item.tmpPath = groupPath + item.tmpPath
+            // 递归调用此函数
+            item.children = arrayToTree(treeArr, item, item.tmpName, item.tmpPath, level + 1)
+            if (groupItem[item.id]) {
+              groupItem[item.id].forEach(element => {
+                element.level = item.level + 1
+                element.groupName = item.tmpName
+                element.groupPath = item.tmpPath
+                element.groupId = item.id
+                this.$set(item.children, item.children.length, element)
+              })
+            }
+            this.$set(temp, temp.length, treeArr[index])
+          }
+        })
+        return temp
+      }
+      this.tree = [...arrayToTree(this.listGroupData, {id: 'root'}, '', '', 0), ...groupItem['root']]
+      this.sortTree()
+    },
+    getSelected() {
+      return this.selectedItem
+    },
+    doSelected(item) {
+      this.selectedItem = item.id
+    },
+    // 排序tree,分组在前,接口在后
+    sortTree() {
+      if (this.treeSort === null) {
+        return
+      }
+      let sortItem = function (item1, item2) {
+        return item1.name.localeCompare(item2.name, 'zh-CN')
+      }
+      let sortHandle = arr => {
+        // 分组
+        let folderArr = []
+        // 接口
+        let fileArr = []
+        arr.forEach(element => {
+          if (element.folder === true) {
+            if (element.children && element.children.length > 0) {
+              element.children = sortHandle(element.children)
+            }
+            folderArr.push(element)
+          } else {
+            fileArr.push(element)
+          }
+        })
+        folderArr.sort(sortItem)
+        fileArr.sort(sortItem)
+        if (this.treeSort === false) {
+          folderArr.reverse()
+          fileArr.reverse()
+        }
+        return folderArr.concat(fileArr)
+      }
+      this.tree = sortHandle(this.tree)
+      this.changeForceUpdate()
+    },
+    // 强制触发子组件更新
+    changeForceUpdate() {
+      this.forceUpdate = !this.forceUpdate
+    }
+  }
+}
+</script>
+
+<style>
+@import './magic-resource.css';
+.ma-tree-wrapper .ma-checkbox input + label{
+  width: 12px !important;
+  height: 12px !important;
+}
+.ma-tree-wrapper .ma-checkbox input + label::after{
+  width: 12px !important;
+  height: 12px !important;
+  line-height: 12px !important;
+  top: 0 !important;
+  left: 0 !important;
+}
+</style>
+<style scoped>
+.ma-checkbox{
+  display: inline-block;
+  width: 20px;
+  height: 12px;
+}
+.ma-tree-wrapper .ma-tree-container{
+  height: 100%;
+}
+</style>

+ 11 - 0
magic-editor/src/console/src/components/resources/magic-resource.css

@@ -41,10 +41,21 @@
 
 .ma-tree-wrapper span {
     color: var(--toolbox-list-span-color);
+    display: inline-block;
+    height: 22px;
+    line-height: 22px;
+}
+
+.ma-tree-wrapper .ma-icon-lock {
+    color: var(--toolbox-list-label-color);
+    margin-left: 5px;
 }
 
 .ma-tree-wrapper label {
     color: var(--toolbox-list-label-color);
+    display: inline-block;
+    height: 22px;
+    line-height: 22px;
 }
 
 .ma-tree-wrapper .ma-tree-toolbar-search {

+ 3 - 0
magic-editor/src/console/src/scripts/contants.js

@@ -22,6 +22,9 @@ const contants = {
   RESPONSE_CODE_SCRIPT_ERROR: -1000,
   RESPONSE_NO_PERMISSION: -10,
   DEFAULT_EXPAND: true,
+  JDBC_DRIVERS: [],
+  DATASOURCE_TYPES: [],
+  OPTIONS: [],
   config: {}
 }
 

+ 2 - 2
pom.xml

@@ -5,7 +5,7 @@
     <modelVersion>4.0.0</modelVersion>
     <groupId>org.ssssssss</groupId>
     <artifactId>magic-api-parent</artifactId>
-    <version>1.4.2</version>
+    <version>1.4.3</version>
     <packaging>pom</packaging>
     <name>magic-api-parent</name>
     <description>auto generate http api</description>
@@ -30,7 +30,7 @@
     </scm>
     <properties>
         <spring-boot.version>2.4.5</spring-boot.version>
-        <magic-script.version>1.5.2</magic-script.version>
+        <magic-script.version>1.5.3</magic-script.version>
         <commons-compress.version>1.21</commons-compress.version>
         <commons-io.version>2.7</commons-io.version>
         <commons-text.version>1.6</commons-text.version>