mxd 4 роки тому
батько
коміт
b121db98a3

+ 82 - 29
src/main/java/org/ssssssss/magicapi/config/MagicDynamicDataSource.java

@@ -13,10 +13,7 @@ import org.ssssssss.magicapi.utils.Assert;
 
 import javax.sql.DataSource;
 import java.sql.Connection;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
 
 public class MagicDynamicDataSource {
 
@@ -34,34 +31,63 @@ public class MagicDynamicDataSource {
 	/**
 	 * 注册数据源(可以运行时注册)
 	 *
-	 * @param dataSourceName 数据源名称
+	 * @param dataSourceKey 数据源Key
 	 */
-	public void put(String dataSourceName, DataSource dataSource) {
-		if (dataSourceName == null) {
-			dataSourceName = "";
+	public void put(String dataSourceKey, DataSource dataSource) {
+		put(null, dataSourceKey, dataSourceKey, dataSource);
+	}
+
+	/**
+	 * 注册数据源(可以运行时注册)
+	 *
+	 * @param id             数据源ID
+	 * @param dataSourceKey  数据源Key
+	 * @param datasourceName 数据源名称
+	 */
+	public void put(String id, String dataSourceKey, String datasourceName, DataSource dataSource) {
+		if (dataSourceKey == null) {
+			dataSourceKey = "";
+		}
+		logger.info("注册数据源:{}", StringUtils.isNotBlank(dataSourceKey) ? dataSourceKey : "default");
+		this.dataSourceMap.put(dataSourceKey, new MagicDynamicDataSource.DataSourceNode(dataSource, dataSourceKey, datasourceName, id));
+		if (id != null) {
+			String finalDataSourceKey = dataSourceKey;
+			this.dataSourceMap.entrySet().stream()
+					.filter(it -> id.equals(it.getValue().getId()) && !finalDataSourceKey.equals(it.getValue().getKey()))
+					.findFirst()
+					.ifPresent(it -> {
+						logger.info("移除旧数据源:{}", it.getValue().getKey());
+						this.dataSourceMap.remove(it.getValue().getKey());
+					});
 		}
-		logger.info("注册数据源:{}", StringUtils.isNotBlank(dataSourceName) ? dataSourceName : "default");
-		this.dataSourceMap.put(dataSourceName, new MagicDynamicDataSource.DataSourceNode(dataSource));
 	}
 
 	/**
 	 * 获取全部数据源
 	 */
-	public List<String> datasources(){
+	public List<String> datasources() {
 		return new ArrayList<>(this.dataSourceMap.keySet());
 	}
 
+	/**
+	 * 获取全部数据源
+	 */
+	public Collection<DataSourceNode> datasourceNodes() {
+		return this.dataSourceMap.values();
+	}
+
 	/**
 	 * 删除数据源
-	 * @param datasourceName    数据源名称
+	 *
+	 * @param datasourceKey 数据源Key
 	 */
-	public boolean delete(String datasourceName){
+	public boolean delete(String datasourceKey) {
 		boolean result = false;
 		// 检查参数是否合法
-		if(datasourceName != null && !datasourceName.isEmpty()){
-			result = this.dataSourceMap.remove(datasourceName) != null;
+		if (datasourceKey != null && !datasourceKey.isEmpty()) {
+			result = this.dataSourceMap.remove(datasourceKey) != null;
 		}
-		logger.info("删除数据源:{}:{}", datasourceName, result ? "成功" : "失败");
+		logger.info("删除数据源:{}:{}", datasourceKey, result ? "成功" : "失败");
 		return result;
 	}
 
@@ -74,19 +100,26 @@ public class MagicDynamicDataSource {
 
 	/**
 	 * 获取数据源
-	 * @param dataSourceName    数据源名称
+	 *
+	 * @param datasourceKey 数据源Key
 	 */
-	public MagicDynamicDataSource.DataSourceNode getDataSource(String dataSourceName) {
-		if (dataSourceName == null) {
-			dataSourceName = "";
+	public MagicDynamicDataSource.DataSourceNode getDataSource(String datasourceKey) {
+		if (datasourceKey == null) {
+			datasourceKey = "";
 		}
-		MagicDynamicDataSource.DataSourceNode dataSourceNode = dataSourceMap.get(dataSourceName);
-		Assert.isNotNull(dataSourceNode, String.format("找不到数据源%s", dataSourceName));
+		MagicDynamicDataSource.DataSourceNode dataSourceNode = dataSourceMap.get(datasourceKey);
+		Assert.isNotNull(dataSourceNode, String.format("找不到数据源%s", datasourceKey));
 		return dataSourceNode;
 	}
 
 	public static class DataSourceNode {
 
+		private final String id;
+
+		private final String key;
+
+		private final String name;
+
 		/**
 		 * 事务管理器
 		 */
@@ -98,13 +131,33 @@ public class MagicDynamicDataSource {
 
 		private Dialect dialect;
 
-		DataSourceNode(DataSource dataSource) {
+
+		DataSourceNode(DataSource dataSource, String key) {
+			this(dataSource, key, key, null);
+		}
+
+		DataSourceNode(DataSource dataSource, String key, String name, String id) {
 			this.dataSource = dataSource;
+			this.key = key;
+			this.name = name;
+			this.id = id;
 			this.dataSourceTransactionManager = new DataSourceTransactionManager(this.dataSource);
 			this.jdbcTemplate = new JdbcTemplate(dataSource);
 		}
 
-		public JdbcTemplate getJdbcTemplate(){
+		public String getId() {
+			return id;
+		}
+
+		public String getName() {
+			return name;
+		}
+
+		public String getKey() {
+			return key;
+		}
+
+		public JdbcTemplate getJdbcTemplate() {
 			return this.jdbcTemplate;
 		}
 
@@ -112,13 +165,13 @@ public class MagicDynamicDataSource {
 			return dataSourceTransactionManager;
 		}
 
-		public Dialect getDialect(DialectAdapter dialectAdapter){
-			if(this.dialect == null){
+		public Dialect getDialect(DialectAdapter dialectAdapter) {
+			if (this.dialect == null) {
 				Connection connection = null;
 				try {
 					connection = this.dataSource.getConnection();
 					this.dialect = dialectAdapter.getDialectFromUrl(connection.getMetaData().getURL());
-					if(this.dialect == null){
+					if (this.dialect == null) {
 						throw new MagicAPIException("自动获取数据库方言失败");
 					}
 				} catch (Exception e) {
@@ -135,7 +188,7 @@ public class MagicDynamicDataSource {
 		put(dataSource);
 	}
 
-	public void add(String dataSourceName, DataSource dataSource) {
-		put(dataSourceName, dataSource);
+	public void add(String dataSourceKey, DataSource dataSource) {
+		put(dataSourceKey, dataSource);
 	}
 }

+ 0 - 9
src/main/java/org/ssssssss/magicapi/controller/MagicConfigController.java

@@ -34,15 +34,6 @@ public class MagicConfigController extends MagicController {
 	public JsonBean<Map<String, Object>> classes() {
 		Map<String, ScriptClass> classMap = MagicScriptEngine.getScriptClassMap();
 		classMap.putAll(MagicResourceLoader.getModules());
-		ScriptClass db = classMap.get(SQLModule.class.getName());
-		MagicDynamicDataSource datasource = configuration.getMagicDynamicDataSource();
-		if (db != null && datasource != null) {
-			List<ScriptClass.ScriptAttribute> attributes = new ArrayList<>();
-			// 给与前台动态数据源提示
-			datasource.datasources().stream().filter(StringUtils::isNotBlank)
-					.forEach(item -> attributes.add(new ScriptClass.ScriptAttribute("db", item)));
-			db.setAttributes(attributes);
-		}
 		Map<String, Object> values = new HashMap<>();
 		values.put("classes", classMap);
 		values.put("extensions", MagicScriptEngine.getExtensionScriptClass());

+ 200 - 0
src/main/java/org/ssssssss/magicapi/controller/MagicDataSourceController.java

@@ -0,0 +1,200 @@
+package org.ssssssss.magicapi.controller;
+
+import com.fasterxml.jackson.databind.type.TypeFactory;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.BeanUtils;
+import org.springframework.boot.context.properties.bind.Bindable;
+import org.springframework.boot.context.properties.bind.Binder;
+import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
+import org.springframework.boot.context.properties.source.ConfigurationPropertyNameAliases;
+import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
+import org.springframework.boot.context.properties.source.MapConfigurationPropertySource;
+import org.springframework.boot.jdbc.DatabaseDriver;
+import org.springframework.util.ClassUtils;
+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.config.MagicConfiguration;
+import org.ssssssss.magicapi.config.MagicDynamicDataSource;
+import org.ssssssss.magicapi.config.Valid;
+import org.ssssssss.magicapi.exception.InvalidArgumentException;
+import org.ssssssss.magicapi.interceptor.RequestInterceptor;
+import org.ssssssss.magicapi.model.JsonBean;
+import org.ssssssss.magicapi.utils.IoUtils;
+import org.ssssssss.magicapi.utils.JsonUtils;
+
+import javax.sql.DataSource;
+import java.util.*;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+public class MagicDataSourceController extends MagicController {
+
+	private static final ClassLoader classLoader = MagicDataSourceController.class.getClassLoader();
+
+	// copy from DataSourceBuilder
+	private static final String[] DATA_SOURCE_TYPE_NAMES = new String[]{
+			"com.zaxxer.hikari.HikariDataSource",
+			"org.apache.tomcat.jdbc.pool.DataSource",
+			"org.apache.commons.dbcp2.BasicDataSource"};
+
+	private final Resource resource;
+
+	public MagicDataSourceController(MagicConfiguration configuration) {
+		super(configuration);
+		resource = configuration.getWorkspace().getDirectory("datasource");
+	}
+
+	/**
+	 * 查询数据源列表
+	 */
+	@RequestMapping("/datasource/list")
+	@ResponseBody
+	public JsonBean<List<Map<String, Object>>> list() {
+		List<Map<String, Object>> list = configuration.getMagicDynamicDataSource().datasourceNodes().stream().map(it -> {
+			Map<String, Object> row = new HashMap<>();
+			row.put("id", it.getId());    // id为空的则认为是不可修改的
+			row.put("key", it.getKey());    // 如果为null 说明是主数据源
+			row.put("name", it.getName());
+			return row;
+		}).collect(Collectors.toList());
+		return new JsonBean<>(list);
+	}
+
+	@RequestMapping("/datasource/test")
+	@ResponseBody
+	public JsonBean<String> test(@RequestBody Map<String, String> properties) {
+		try {
+			DataSource dataSource = createDataSource(properties);
+			dataSource.getConnection();
+		} catch (Exception e) {
+			return new JsonBean<>(e.getMessage());
+		}
+		return new JsonBean<>();
+	}
+
+	/**
+	 * 保存数据源
+	 *
+	 * @param properties 数据源配置信息
+	 */
+	@RequestMapping("/datasource/save")
+	@Valid(readonly = false, authorization = RequestInterceptor.Authorization.SAVE)
+	@ResponseBody
+	public JsonBean<String> save(@RequestBody Map<String, String> properties) {
+		String key = properties.get("key");
+		// 校验key是否符合规则
+		notBlank(key, DATASOURCE_KEY_REQUIRED);
+		isTrue(IoUtils.validateFileName(key), DATASOURCE_KEY_INVALID);
+		String name = properties.getOrDefault("name", key);
+		String id = properties.get("id");
+		Stream<String> keyStream;
+		if (StringUtils.isBlank(id)) {
+			keyStream = configuration.getMagicDynamicDataSource().datasources().stream();
+		} else {
+			keyStream = configuration.getMagicDynamicDataSource().datasourceNodes().stream()
+					.filter(it -> !id.equals(it.getId()))
+					.map(MagicDynamicDataSource.DataSourceNode::getKey);
+		}
+		String dsId = StringUtils.isBlank(id) ? UUID.randomUUID().toString().replace("-", "") : id;
+		// 验证是否有冲突
+		isTrue(keyStream.noneMatch(key::equals), DATASOURCE_KEY_EXISTS);
+		// 注册数据源
+		configuration.getMagicDynamicDataSource().put(dsId, key, name, createDataSource(properties));
+		properties.put("id", dsId);
+		resource.getResource(dsId + ".json").write(JsonUtils.toJsonString(properties));
+		return new JsonBean<>(dsId);
+	}
+
+	/**
+	 * 删除数据源
+	 *
+	 * @param id 数据源ID
+	 */
+	@RequestMapping("/datasource/delete")
+	@Valid(readonly = false, authorization = RequestInterceptor.Authorization.DELETE)
+	@ResponseBody
+	public JsonBean<Boolean> delete(String id) {
+		// 查询数据源是否存在
+		Optional<MagicDynamicDataSource.DataSourceNode> dataSourceNode = configuration.getMagicDynamicDataSource().datasourceNodes().stream()
+				.filter(it -> id.equals(it.getId()))
+				.findFirst();
+		isTrue(dataSourceNode.isPresent(), DATASOURCE_NOT_FOUND);
+		Resource resource = this.resource.getResource(id + ".json");
+		// 删除数据源
+		isTrue(resource.delete(), DATASOURCE_NOT_FOUND);
+		// 取消注册数据源
+		dataSourceNode.ifPresent(it -> configuration.getMagicDynamicDataSource().delete(it.getKey()));
+		return new JsonBean<>(true);
+
+	}
+
+	@RequestMapping("/datasource/detail")
+	@Valid(authorization = RequestInterceptor.Authorization.DETAIL)
+	@ResponseBody
+	public JsonBean<Object> detail(String id) {
+		Resource resource = this.resource.getResource(id + ".json");
+		byte[] bytes = resource.read();
+		isTrue(bytes != null && bytes.length > 0, DATASOURCE_NOT_FOUND);
+		return new JsonBean<>(JsonUtils.readValue(bytes, LinkedHashMap.class));
+	}
+
+	// 启动之后注册数据源
+	public void registerDataSource() {
+		resource.readAll();
+		List<Resource> resources = resource.files(".json");
+		// 删除旧的数据源
+		configuration.getMagicDynamicDataSource().datasourceNodes().stream()
+				.filter(it -> it.getId() != null)
+				.map(MagicDynamicDataSource.DataSourceNode::getKey)
+				.collect(Collectors.toList())
+				.forEach(it -> configuration.getMagicDynamicDataSource().delete(it));
+		TypeFactory factory = TypeFactory.defaultInstance();
+		for (Resource item : resources) {
+			Map<String, String> properties = JsonUtils.readValue(item.read(), factory.constructMapType(HashMap.class, String.class, String.class));
+			if (properties != null) {
+				String key = properties.get("key");
+				String name = properties.getOrDefault("name", key);
+				configuration.getMagicDynamicDataSource().put(properties.get("id"), key, name, createDataSource(properties));
+			}
+		}
+	}
+
+	// copy from DataSourceBuilder
+	private DataSource createDataSource(Map<String, String> properties) {
+		Class<? extends DataSource> dataSourceType = getDataSourceType(properties.get("type"));
+		if (!properties.containsKey("driverClassName")
+				&& properties.containsKey("url")) {
+			String url = properties.get("url");
+			String driverClass = DatabaseDriver.fromJdbcUrl(url).getDriverClassName();
+			properties.put("driverClassName", driverClass);
+		}
+		DataSource dataSource = BeanUtils.instantiateClass(dataSourceType);
+		ConfigurationPropertySource source = new MapConfigurationPropertySource(properties);
+		ConfigurationPropertyNameAliases aliases = new ConfigurationPropertyNameAliases();
+		aliases.addAliases("url", "jdbc-url");
+		aliases.addAliases("username", "user");
+		Binder binder = new Binder(source.withAliases(aliases));
+		binder.bind(ConfigurationPropertyName.EMPTY, Bindable.ofInstance(dataSource));
+		return dataSource;
+	}
+
+	@SuppressWarnings("unchecked")
+	private Class<? extends DataSource> getDataSourceType(String datasourceType) {
+		if (StringUtils.isNotBlank(datasourceType)) {
+			try {
+				return (Class<? extends DataSource>) ClassUtils.forName(datasourceType, classLoader);
+			} catch (Exception e) {
+				throw new InvalidArgumentException(DATASOURCE_TYPE_NOT_FOUND.format(datasourceType));
+			}
+		}
+		for (String name : DATA_SOURCE_TYPE_NAMES) {
+			try {
+				return (Class<? extends DataSource>) ClassUtils.forName(name, classLoader);
+			} catch (Exception ignored) {
+			}
+		}
+		throw new InvalidArgumentException(DATASOURCE_TYPE_NOT_SET);
+	}
+}

+ 11 - 0
src/main/java/org/ssssssss/magicapi/model/JsonCodeConstants.java

@@ -34,6 +34,8 @@ public interface JsonCodeConstants {
 
 	JsonCode NAME_INVALID = new JsonCode(0, "名称不能包含特殊字符,只允许中文、数字、字母以及_组合");
 
+	JsonCode DATASOURCE_KEY_INVALID = new JsonCode(0, "数据源Key不能包含特殊字符,只允许中文、数字、字母以及_组合");
+
 	JsonCode API_ALREADY_EXISTS = new JsonCode(0, "接口%s:%s已存在或接口名称重复");
 
 	JsonCode FUNCTION_ALREADY_EXISTS = new JsonCode(0, "函数%s已存在或名称重复");
@@ -57,4 +59,13 @@ public interface JsonCodeConstants {
 	JsonCode DEBUG_SESSION_NOT_FOUND = new JsonCode(0, "debug session not found!");
 
 	JsonCode API_NOT_FOUND = new JsonCode(1001, "api not found");
+
+	JsonCode DATASOURCE_KEY_REQUIRED = new JsonCode(0, "数据源Key不能为空");
+
+	JsonCode DATASOURCE_KEY_EXISTS = new JsonCode(0, "数据源%s已存在或名称重复");
+
+	JsonCode DATASOURCE_TYPE_NOT_FOUND = new JsonCode(0, "%s not found");
+	JsonCode DATASOURCE_NOT_FOUND = new JsonCode(0, "找不到对应的数据源");
+
+	JsonCode DATASOURCE_TYPE_NOT_SET = new JsonCode(0, "请设置数据源类型");
 }

+ 10 - 0
src/main/java/org/ssssssss/magicapi/utils/JsonUtils.java

@@ -3,6 +3,7 @@ package org.ssssssss.magicapi.utils;
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.core.type.TypeReference;
 import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.JavaType;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -54,4 +55,13 @@ public class JsonUtils {
 			return null;
 		}
 	}
+	public static <T> T readValue(byte[] bytes, JavaType javaType) {
+		try {
+			return mapper.readValue(bytes, javaType);
+		} catch (IOException e) {
+			logger.error("读取json失败,json:{}", new String(bytes), e);
+			return null;
+		}
+	}
+
 }