mxd 4 年之前
父节点
当前提交
51df218525

+ 10 - 0
src/main/java/org/ssssssss/magicapi/config/MagicConfiguration.java

@@ -79,6 +79,8 @@ public class MagicConfiguration {
 
 	private boolean enableWeb = false;
 
+	private SyncConfig syncConfig;
+
 	public void setEnableWeb(boolean enableWeb) {
 		this.enableWeb = enableWeb;
 	}
@@ -195,6 +197,14 @@ public class MagicConfiguration {
 		this.magicFunctionManager = magicFunctionManager;
 	}
 
+	public SyncConfig getSyncConfig() {
+		return syncConfig;
+	}
+
+	public void setSyncConfig(SyncConfig syncConfig) {
+		this.syncConfig = syncConfig;
+	}
+
 	/**
 	 * 打印banner
 	 */

+ 14 - 1
src/main/java/org/ssssssss/magicapi/config/MappingHandlerMapping.java

@@ -3,6 +3,7 @@ package org.ssssssss.magicapi.config;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.util.StringUtils;
+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;
@@ -27,6 +28,7 @@ import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.Executors;
 import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 /**
  * 请求映射
@@ -204,7 +206,7 @@ public class MappingHandlerMapping {
 		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 path = concatPath(newPath, "/" + info.getPath());
 			String mappingKey = buildMappingKey(info.getMethod(), path);
 			if (mappings.containsKey(mappingKey)) {
 				return true;
@@ -413,6 +415,17 @@ public class MappingHandlerMapping {
 
 	}
 
+	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);
+				requestMappingHandlerMapping.registerMapping(RequestMappingInfo.paths(paths).build(), target, method);
+			}
+		}
+	}
+
 	private String concatPath(String groupPath, String path) {
 		path = groupPath + "/" + path;
 		if (prefix != null) {

+ 48 - 0
src/main/java/org/ssssssss/magicapi/config/SyncConfig.java

@@ -0,0 +1,48 @@
+package org.ssssssss.magicapi.config;
+
+/**
+ * 同步配置
+ *
+ * @since 0.7.0
+ */
+public class SyncConfig {
+
+	/**
+	 * 秘钥
+	 */
+	private String secret;
+
+	/**
+	 * 是否允许pull
+	 */
+	private boolean allowPull = true;
+
+	/**
+	 * 是否允许push
+	 */
+	private boolean allowPush = true;
+
+	public String getSecret() {
+		return secret;
+	}
+
+	public void setSecret(String secret) {
+		this.secret = secret;
+	}
+
+	public boolean isAllowPull() {
+		return allowPull;
+	}
+
+	public void setAllowPull(boolean allowPull) {
+		this.allowPull = allowPull;
+	}
+
+	public boolean isAllowPush() {
+		return allowPush;
+	}
+
+	public void setAllowPush(boolean allowPush) {
+		this.allowPush = allowPush;
+	}
+}

+ 10 - 2
src/main/java/org/ssssssss/magicapi/controller/MagicAPIController.java

@@ -169,9 +169,17 @@ public class MagicAPIController extends MagicController {
 				if (magicApiService.existsWithoutId(info.getGroupId(), info.getMethod(), info.getPath(), info.getId())) {
 					return new JsonBean<>(0, String.format("接口%s:%s已存在", info.getMethod(), info.getPath()));
 				}
-				magicApiService.update(info);
+				configuration.getMappingHandlerMapping().getApiInfos().stream()
+						.filter(it -> it.getId().equals(info.getId()))
+						.findFirst()
+						.ifPresent(it -> {
+							// 有变化时才修改
+							if (!it.equals(info)) {
+								magicApiService.update(info);
+								magicApiService.backup(info.getId());
+							}
+						});
 			}
-			magicApiService.backup(info.getId());
 			// 解除包装
 			magicApiService.unwrap(info);
 			// 注册接口

+ 77 - 0
src/main/java/org/ssssssss/magicapi/controller/MagicWorkbenchController.java

@@ -1,13 +1,24 @@
 package org.ssssssss.magicapi.controller;
 
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.http.HttpEntity;
 import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.MediaType;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.client.RestTemplate;
 import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
 import org.ssssssss.magicapi.config.MagicConfiguration;
+import org.ssssssss.magicapi.interceptor.RequestInterceptor;
 import org.ssssssss.magicapi.logging.MagicLoggerContext;
 import org.ssssssss.magicapi.model.JsonBean;
 import org.ssssssss.magicapi.model.Options;
+import org.ssssssss.magicapi.model.SynchronizeRequest;
+import org.ssssssss.magicapi.model.SynchronizeResponse;
+import org.ssssssss.magicapi.provider.ApiServiceProvider;
+import org.ssssssss.magicapi.provider.GroupServiceProvider;
 import org.ssssssss.magicapi.utils.MD5Utils;
 
 import javax.servlet.http.HttpServletRequest;
@@ -19,8 +30,16 @@ import java.util.stream.Stream;
 
 public class MagicWorkbenchController extends MagicController {
 
+	private RestTemplate restTemplate = new RestTemplate();
+
+	private ApiServiceProvider agiApiService;
+
+	private GroupServiceProvider groupService;
+
 	public MagicWorkbenchController(MagicConfiguration configuration) {
 		super(configuration);
+		this.agiApiService = configuration.getMagicApiService();
+		this.groupService = configuration.getGroupServiceProvider();
 	}
 
 	/**
@@ -56,4 +75,62 @@ public class MagicWorkbenchController extends MagicController {
 	public JsonBean<List<Map<String, String>>> options() {
 		return new JsonBean<>(Stream.of(Options.values()).map(item -> Collections.singletonMap(item.getValue(), item.getName())).collect(Collectors.toList()));
 	}
+
+
+	private String validateRequest(SynchronizeRequest request) {
+		if (StringUtils.isBlank(request.getMode())) {
+			return "请求参数有误";
+		}
+		if (request.getMode().equals("1") && StringUtils.isBlank(request.getGroupId())) {
+			return "分组id不能为空";
+		}
+		if (request.getMode().equals("2") && (StringUtils.isBlank(request.getApiId()) || StringUtils.isBlank(request.getFunctionId()))) {
+			return "函数id或接口id不能同时为空";
+		}
+		return null;
+	}
+
+	@RequestMapping("/synchronize")
+	@ResponseBody
+	public JsonBean<SynchronizeResponse> synchronize(SynchronizeRequest synchronizeRequest, HttpServletRequest request) {
+		if (!allowVisit(request, RequestInterceptor.Authorization.SYNC)) {
+			return new JsonBean<>(-10, "无权限执行同步方法");
+		}
+		String message = validateRequest(synchronizeRequest);
+		if (message == null) {
+			List<SynchronizeRequest.Info> infos = agiApiService.listForSync(null, synchronizeRequest.getApiId());
+			infos.forEach(it -> it.setGroupPath(groupService.getFullPath(it.getGroupId())));
+			synchronizeRequest.setInfos(infos);
+			return request(synchronizeRequest);
+		}
+		return new JsonBean<>(0, message);
+	}
+
+	@RequestMapping("/synchronize/pull")
+	@ResponseBody
+	public JsonBean<Void> pull(SynchronizeRequest synchronizeRequest, HttpServletRequest request) {
+		if (!allowVisit(request, RequestInterceptor.Authorization.PULL)) {
+			return new JsonBean<>(-10, "无权限执行拉取方法");
+		}
+		return null;
+	}
+
+	@RequestMapping("/synchronize/push")
+	@ResponseBody
+	public JsonBean<Void> push(SynchronizeRequest synchronizeRequest, HttpServletRequest request) {
+		if (!allowVisit(request, RequestInterceptor.Authorization.PUSH)) {
+			return new JsonBean<>(-10, "无权限执行推送方法");
+		}
+		return null;
+	}
+
+	private JsonBean<SynchronizeResponse> request(SynchronizeRequest request) {
+		HttpHeaders headers = new HttpHeaders();
+		headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
+		String requestURL = String.format("%s/_synchronize?secret=%s", request.getRemote(), request.getSecret());
+		HttpEntity<Object> entity = new HttpEntity<>(request, headers);
+		return restTemplate.exchange(requestURL, HttpMethod.POST, entity, new ParameterizedTypeReference<JsonBean<SynchronizeResponse>>() {
+		}).getBody();
+	}
+
 }

+ 89 - 0
src/main/java/org/ssssssss/magicapi/controller/SynchronizeController.java

@@ -0,0 +1,89 @@
+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.SyncConfig;
+import org.ssssssss.magicapi.model.JsonBean;
+import org.ssssssss.magicapi.model.SynchronizeRequest;
+import org.ssssssss.magicapi.model.SynchronizeResponse;
+import org.ssssssss.magicapi.provider.ApiServiceProvider;
+import org.ssssssss.magicapi.provider.GroupServiceProvider;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+public class SynchronizeController extends MagicController {
+
+	private SyncConfig syncConfig;
+
+	private ApiServiceProvider agiApiService;
+
+	private GroupServiceProvider groupService;
+
+
+	public SynchronizeController(MagicConfiguration configuration) {
+		super(configuration);
+		this.syncConfig = configuration.getSyncConfig();
+		this.agiApiService = configuration.getMagicApiService();
+		this.groupService = configuration.getGroupServiceProvider();
+	}
+
+	private boolean validateSecret(String secret) {
+		return StringUtils.isNotBlank(syncConfig.getSecret()) && Objects.equals(secret, syncConfig.getSecret());
+	}
+
+	@RequestMapping("/_synchronize")
+	@ResponseBody
+	public JsonBean<SynchronizeResponse> doSynchronize(@RequestBody SynchronizeRequest synchronizeRequest) {
+		if (!validateSecret(synchronizeRequest.getSecret())) {
+			return new JsonBean<>(-100, "秘钥不正确");
+		}
+		// 查询该环境下的信息
+		List<SynchronizeRequest.Info> oldInfos = agiApiService.listForSync(synchronizeRequest.getGroupId(), synchronizeRequest.getApiId());
+		List<SynchronizeRequest.Info> newInfos = synchronizeRequest.getInfos();
+		oldInfos.forEach(it -> it.setGroupPath(groupService.getFullPath(it.getGroupId())));
+		// 对比差异
+		Map<String, SynchronizeRequest.Info> oldInfoMap = oldInfos.stream().collect(Collectors.toMap(SynchronizeRequest.Info::getId, it -> it));
+		Map<String, SynchronizeRequest.Info> newInfoMap = newInfos.stream().collect(Collectors.toMap(SynchronizeRequest.Info::getId, it -> it));
+		SynchronizeResponse response = new SynchronizeResponse();
+		newInfos.forEach(info -> {
+			SynchronizeRequest.Info oldInfo = oldInfoMap.get(info.getId());
+			if (oldInfo == null) {
+				// 如果找不到,则是新增
+				response.addAdded(info);
+			} else if (!Objects.equals(info.getUpdateTime(), oldInfo.getUpdateTime())) {
+				// 修改时间不同,则是修改
+				response.addUpdated(oldInfo);
+			}
+		});
+		// 找出删除项
+		oldInfos.stream().filter(it -> !newInfoMap.containsKey(it.getId())).forEach(response::addRemoved);
+		return new JsonBean<>(response);
+	}
+
+
+//	@RequestMapping("/_synchronize/pull")
+//	@ResponseBody
+//	public JsonBean<Void> doPull(SynchronizeRequest synchronizeRequest) {
+//		if (!validateSecret(synchronizeRequest)) {
+//			return new JsonBean<>(-100, "秘钥不正确");
+//		}
+//		return null;
+//	}
+//
+//	@RequestMapping("/_synchronize/push")
+//	@ResponseBody
+//	public JsonBean<Void> doPush(SynchronizeRequest synchronizeRequest) {
+//		if (!validateSecret(synchronizeRequest)) {
+//			return new JsonBean<>(-100, "秘钥不正确");
+//		}
+//		return null;
+//	}
+
+
+}

+ 2 - 2
src/main/java/org/ssssssss/magicapi/interceptor/RequestInterceptor.java

@@ -12,7 +12,7 @@ import javax.servlet.http.HttpServletResponse;
 public interface RequestInterceptor {
 
 	enum Authorization {
-		SAVE, DETAIL, RUN, DELETE,
+		SAVE, DETAIL, RUN, DELETE, SYNC, PULL, PUSH
 	}
 
 	/**
@@ -38,7 +38,7 @@ public interface RequestInterceptor {
 	 * @param value 即将要返回到页面的值
 	 * @return 返回到页面的对象, 当返回null时执行后续拦截器,否则直接返回该值,不执行后续拦截器
 	 */
-	default Object postHandle(ApiInfo info, MagicScriptContext context, Object value,HttpServletRequest request,HttpServletResponse response) throws Exception {
+	default Object postHandle(ApiInfo info, MagicScriptContext context, Object value, HttpServletRequest request, HttpServletResponse response) throws Exception {
 		return null;
 	}
 

+ 13 - 1
src/main/java/org/ssssssss/magicapi/model/ApiInfo.java

@@ -1,7 +1,6 @@
 package org.ssssssss.magicapi.model;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
-import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
 
 import java.util.Map;
 import java.util.Objects;
@@ -71,6 +70,11 @@ public class ApiInfo {
 	 */
 	private String description;
 
+	/**
+	 * 最后更新时间
+	 */
+	private Long updateTime;
+
 	/**
 	 * 接口选项json->map
 	 */
@@ -195,6 +199,14 @@ public class ApiInfo {
 		return this.optionMap != null ? this.optionMap.get(key) : null;
 	}
 
+	public Long getUpdateTime() {
+		return updateTime;
+	}
+
+	public void setUpdateTime(Long updateTime) {
+		this.updateTime = updateTime;
+	}
+
 	@Override
 	public boolean equals(Object o) {
 		if (this == o) return true;

+ 176 - 0
src/main/java/org/ssssssss/magicapi/model/SynchronizeRequest.java

@@ -0,0 +1,176 @@
+package org.ssssssss.magicapi.model;
+
+import java.util.List;
+
+public class SynchronizeRequest {
+
+	private String secret;
+
+	private String remote;
+
+
+	/**
+	 * 0 全部
+	 * 1 一个组
+	 * 2 一个接口或函数
+	 */
+	private String mode;
+
+	/**
+	 * 分组ID
+	 */
+	private String groupId;
+
+	/**
+	 * 接口ID
+	 */
+	private String apiId;
+
+	/**
+	 * 函数ID
+	 */
+	private String functionId;
+
+	private List<Info> infos;
+
+	public String getMode() {
+		return mode;
+	}
+
+	public void setMode(String mode) {
+		this.mode = mode;
+	}
+
+	public String getGroupId() {
+		return groupId;
+	}
+
+	public void setGroupId(String groupId) {
+		this.groupId = groupId;
+	}
+
+	public String getApiId() {
+		return apiId;
+	}
+
+	public void setApiId(String apiId) {
+		this.apiId = apiId;
+	}
+
+	public String getFunctionId() {
+		return functionId;
+	}
+
+	public void setFunctionId(String functionId) {
+		this.functionId = functionId;
+	}
+
+	public String getSecret() {
+		return secret;
+	}
+
+	public void setSecret(String secret) {
+		this.secret = secret;
+	}
+
+	public String getRemote() {
+		return remote;
+	}
+
+	public void setRemote(String remote) {
+		this.remote = remote;
+	}
+
+	public List<Info> getInfos() {
+		return infos;
+	}
+
+	public void setInfos(List<Info> infos) {
+		this.infos = infos;
+	}
+
+	public static class Info {
+
+		private String id;
+
+		private String name;
+
+		private String method;
+
+		private String groupId;
+
+		private String groupPath;
+
+		private String path;
+
+		private Long updateTime;
+
+		public static Info from(ApiInfo apiInfo) {
+			Info info = new Info();
+			info.setId(apiInfo.getId());
+			info.setName(apiInfo.getName());
+			info.setMethod(apiInfo.getMethod());
+			info.setPath(apiInfo.getPath());
+			info.setUpdateTime(apiInfo.getUpdateTime());
+			info.setGroupId(apiInfo.getGroupId());
+			return info;
+		}
+
+		public String getGroupPath() {
+			return groupPath;
+		}
+
+		public void setGroupPath(String groupPath) {
+			this.groupPath = groupPath;
+		}
+
+		public String getId() {
+			return id;
+		}
+
+		public void setId(String id) {
+			this.id = id;
+		}
+
+		public String getName() {
+			return name;
+		}
+
+		public void setName(String name) {
+			this.name = name;
+		}
+
+		public String getMethod() {
+			return method;
+		}
+
+		public void setMethod(String method) {
+			this.method = method;
+		}
+
+		public String getPath() {
+			return path;
+		}
+
+		public void setPath(String path) {
+			this.path = path;
+		}
+
+		public Long getUpdateTime() {
+			return updateTime;
+		}
+
+		public void setUpdateTime(Long updateTime) {
+			this.updateTime = updateTime;
+		}
+
+		public String getGroupId() {
+			return groupId;
+		}
+
+		public void setGroupId(String groupId) {
+			this.groupId = groupId;
+		}
+	}
+
+}

+ 49 - 0
src/main/java/org/ssssssss/magicapi/model/SynchronizeResponse.java

@@ -0,0 +1,49 @@
+package org.ssssssss.magicapi.model;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class SynchronizeResponse {
+
+	private List<SynchronizeRequest.Info> added = new ArrayList<>();
+
+	private List<SynchronizeRequest.Info> removed = new ArrayList<>();
+
+	private List<SynchronizeRequest.Info> updated = new ArrayList<>();
+
+	public void addRemoved(SynchronizeRequest.Info info) {
+		this.removed.add(info);
+	}
+
+	public void addUpdated(SynchronizeRequest.Info info) {
+		this.updated.add(info);
+	}
+
+	public void addAdded(SynchronizeRequest.Info info) {
+		this.added.add(info);
+	}
+
+	public List<SynchronizeRequest.Info> getAdded() {
+		return added;
+	}
+
+	public void setAdded(List<SynchronizeRequest.Info> added) {
+		this.added = added;
+	}
+
+	public List<SynchronizeRequest.Info> getRemoved() {
+		return removed;
+	}
+
+	public void setRemoved(List<SynchronizeRequest.Info> removed) {
+		this.removed = removed;
+	}
+
+	public List<SynchronizeRequest.Info> getUpdated() {
+		return updated;
+	}
+
+	public void setUpdated(List<SynchronizeRequest.Info> updated) {
+		this.updated = updated;
+	}
+}

+ 7 - 0
src/main/java/org/ssssssss/magicapi/provider/StoreServiceProvider.java

@@ -1,5 +1,7 @@
 package org.ssssssss.magicapi.provider;
 
+import org.ssssssss.magicapi.model.SynchronizeRequest;
+
 import java.util.List;
 
 public interface StoreServiceProvider<T> {
@@ -50,6 +52,11 @@ public interface StoreServiceProvider<T> {
 	 */
 	List<T> listWithScript();
 
+	/**
+	 * 查询带有id,名字,请求访问,路径,修改时间的集合
+	 */
+	List<SynchronizeRequest.Info> listForSync(String groupId, String id);
+
 	/**
 	 * 查询接口详情(主要给页面使用)
 	 *

+ 23 - 3
src/main/java/org/ssssssss/magicapi/provider/impl/DefaultApiServiceProvider.java

@@ -1,15 +1,17 @@
 package org.ssssssss.magicapi.provider.impl;
 
+import org.apache.commons.lang3.StringUtils;
 import org.springframework.jdbc.core.BeanPropertyRowMapper;
 import org.springframework.jdbc.core.JdbcTemplate;
 import org.ssssssss.magicapi.model.ApiInfo;
+import org.ssssssss.magicapi.model.SynchronizeRequest;
 import org.ssssssss.magicapi.provider.ApiServiceProvider;
 
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 import java.util.UUID;
 import java.util.stream.Collectors;
-import java.util.stream.Stream;
 
 public class DefaultApiServiceProvider extends BeanPropertyRowMapper<ApiInfo> implements ApiServiceProvider {
 
@@ -55,6 +57,24 @@ public class DefaultApiServiceProvider extends BeanPropertyRowMapper<ApiInfo> im
 		return infos;
 	}
 
+	@Override
+	public List<SynchronizeRequest.Info> listForSync(String groupId, String id) {
+		String sql = "select id,api_group_id,api_name,api_method,api_path,api_update_time from magic_api_info";
+		List<String> parameters = new ArrayList<>();
+		if (StringUtils.isNotBlank(groupId) || StringUtils.isNotBlank(id)) {
+			sql += " where 1=1 ";
+			if (StringUtils.isNotBlank(groupId)) {
+				sql += " and api_group_id = ?";
+				parameters.add(groupId);
+			}
+			if (StringUtils.isNotBlank(id)) {
+				sql += " and id = ?";
+				parameters.add(id);
+			}
+		}
+		return template.query(sql, this, parameters.toArray()).stream().map(SynchronizeRequest.Info::from).collect(Collectors.toList());
+	}
+
 	public ApiInfo get(String id) {
 		String selectOne = "select " + COMMON_COLUMNS + "," + SCRIPT_COLUMNS + " from magic_api_info where id = ?";
 		ApiInfo info = template.queryForObject(selectOne, this, id);
@@ -70,7 +90,7 @@ public class DefaultApiServiceProvider extends BeanPropertyRowMapper<ApiInfo> im
 	@Override
 	public boolean deleteGroup(List<String> groupIds) {
 		List<Object[]> params = groupIds.stream().map(groupId -> new Object[]{groupId}).collect(Collectors.toList());
-		return Arrays.stream(template.batchUpdate("delete from magic_api_info where api_group_id = ?",params)).sum() >= 0;
+		return Arrays.stream(template.batchUpdate("delete from magic_api_info where api_group_id = ?", params)).sum() >= 0;
 	}
 
 	public boolean exists(String groupId, String method, String path) {
@@ -88,7 +108,7 @@ public class DefaultApiServiceProvider extends BeanPropertyRowMapper<ApiInfo> im
 		wrap(info);
 		long time = System.currentTimeMillis();
 		String insert = "insert into magic_api_info(id,api_method,api_path,api_script,api_name,api_group_id,api_parameter,api_description,api_option,api_request_header,api_request_body,api_response_body,api_create_time,api_update_time) values(?,?,?,?,?,?,?,?,?,?,?,?,?,?)";
-		return template.update(insert, info.getId(), info.getMethod(), info.getPath(), info.getScript(), info.getName(), info.getGroupId(), info.getParameter(), info.getDescription(), info.getOption(), info.getRequestHeader(), info.getRequestBody(),info.getResponseBody(), time, time) > 0;
+		return template.update(insert, info.getId(), info.getMethod(), info.getPath(), info.getScript(), info.getName(), info.getGroupId(), info.getParameter(), info.getDescription(), info.getOption(), info.getRequestHeader(), info.getRequestBody(), info.getResponseBody(), time, time) > 0;
 	}
 
 	public boolean update(ApiInfo info) {

+ 8 - 4
src/main/java/org/ssssssss/magicapi/provider/impl/DefaultFunctionServiceProvider.java

@@ -3,6 +3,7 @@ package org.ssssssss.magicapi.provider.impl;
 import org.springframework.jdbc.core.BeanPropertyRowMapper;
 import org.springframework.jdbc.core.JdbcTemplate;
 import org.ssssssss.magicapi.model.FunctionInfo;
+import org.ssssssss.magicapi.model.SynchronizeRequest;
 import org.ssssssss.magicapi.provider.FunctionServiceProvider;
 
 import java.util.Arrays;
@@ -85,14 +86,17 @@ public class DefaultFunctionServiceProvider extends BeanPropertyRowMapper<Functi
 	public List<FunctionInfo> listWithScript() {
 		String selectListWithScript = "select " + COMMON_COLUMNS + "," + SCRIPT_COLUMNS + " from magic_function";
 		List<FunctionInfo> infos = template.query(selectListWithScript, this);
-		if (infos != null) {
-			for (FunctionInfo info : infos) {
-				unwrap(info);
-			}
+		for (FunctionInfo info : infos) {
+			unwrap(info);
 		}
 		return infos;
 	}
 
+	@Override
+	public List<SynchronizeRequest.Info> listForSync(String groupId, String id) {
+		return null;
+	}
+
 	@Override
 	public FunctionInfo get(String id) {
 		String selectOne = "select " + COMMON_COLUMNS + "," + SCRIPT_COLUMNS + " from magic_function where id = ?";