diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/mnt/rest/DatabaseController.java b/eladmin-system/src/main/java/me/zhengjie/modules/mnt/rest/DatabaseController.java index eb22d0c7..d99bade0 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/mnt/rest/DatabaseController.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/mnt/rest/DatabaseController.java @@ -3,15 +3,23 @@ package me.zhengjie.modules.mnt.rest; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import me.zhengjie.aop.log.Log; +import me.zhengjie.exception.BadRequestException; import me.zhengjie.modules.mnt.domain.Database; import me.zhengjie.modules.mnt.service.DatabaseService; +import me.zhengjie.modules.mnt.service.dto.DatabaseDto; import me.zhengjie.modules.mnt.service.dto.DatabaseQueryCriteria; +import me.zhengjie.modules.mnt.util.SqlUtils; +import me.zhengjie.utils.FileUtil; import org.springframework.data.domain.Pageable; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.http.HttpServletRequest; +import java.io.File; /** * @author zhanghouying @@ -22,6 +30,8 @@ import org.springframework.web.bind.annotation.*; @RequestMapping("/api/database") public class DatabaseController { + private String fileSavePath = System.getProperty("java.io.tmpdir"); + private final DatabaseService databaseService; public DatabaseController(DatabaseService databaseService) { @@ -61,4 +71,32 @@ public class DatabaseController { databaseService.delete(id); return new ResponseEntity(HttpStatus.OK); } + + @Log("测试数据库链接") + @ApiOperation(value = "测试数据库链接") + @PostMapping("/testConnect") + @PreAuthorize("@el.check('database:testConnect')") + public ResponseEntity testConnect(@Validated @RequestBody Database resources){ + return new ResponseEntity<>(databaseService.testConnection(resources),HttpStatus.CREATED); + } + + @Log("执行SQL脚本") + @ApiOperation(value = "执行SQL脚本") + @PostMapping(value = "/upload") + @PreAuthorize("@el.check('database:add')") + public ResponseEntity upload(@RequestBody MultipartFile file, HttpServletRequest request)throws Exception{ + String id = request.getParameter("id"); + DatabaseDto database = databaseService.findById(id); + String fileName = ""; + if(database != null){ + fileName = file.getOriginalFilename(); + File executeFile = new File(fileSavePath+fileName); + FileUtil.del(executeFile); + file.transferTo(executeFile); + String result = SqlUtils.executeFile(database.getJdbcUrl(), database.getUserName(), database.getPwd(), executeFile); + return new ResponseEntity<>(result,HttpStatus.OK); + }else{ + throw new BadRequestException("Database not exist"); + } + } } diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/mnt/service/DatabaseService.java b/eladmin-system/src/main/java/me/zhengjie/modules/mnt/service/DatabaseService.java index 9e2e7b18..aaa77a69 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/mnt/service/DatabaseService.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/mnt/service/DatabaseService.java @@ -51,4 +51,11 @@ public interface DatabaseService { * @param id / */ void delete(String id); + + /** + * 测试连接数据库 + * @param resources + * @return + */ + boolean testConnection(Database resources); } diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/mnt/service/impl/DatabaseServiceImpl.java b/eladmin-system/src/main/java/me/zhengjie/modules/mnt/service/impl/DatabaseServiceImpl.java index aa9e3cf7..ab1819d9 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/mnt/service/impl/DatabaseServiceImpl.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/mnt/service/impl/DatabaseServiceImpl.java @@ -1,12 +1,14 @@ package me.zhengjie.modules.mnt.service.impl; import cn.hutool.core.util.IdUtil; +import lombok.extern.slf4j.Slf4j; import me.zhengjie.modules.mnt.domain.Database; import me.zhengjie.modules.mnt.repository.DatabaseRepository; import me.zhengjie.modules.mnt.service.DatabaseService; import me.zhengjie.modules.mnt.service.dto.DatabaseDto; import me.zhengjie.modules.mnt.service.dto.DatabaseQueryCriteria; import me.zhengjie.modules.mnt.service.mapper.DatabaseMapper; +import me.zhengjie.modules.mnt.util.SqlUtils; import me.zhengjie.utils.PageUtil; import me.zhengjie.utils.QueryHelp; import me.zhengjie.utils.ValidationUtil; @@ -21,6 +23,7 @@ import org.springframework.transaction.annotation.Transactional; * @date 2019-08-24 */ @Service +@Slf4j @Transactional(propagation = Propagation.SUPPORTS, readOnly = true, rollbackFor = Exception.class) public class DatabaseServiceImpl implements DatabaseService { @@ -72,4 +75,15 @@ public class DatabaseServiceImpl implements DatabaseService { public void delete(String id) { databaseRepository.deleteById(id); } + + @Override + public boolean testConnection(Database resources) { + try { + return SqlUtils.testConnection(resources.getJdbcUrl(), resources.getUserName(), resources.getPwd()); + } catch (Exception e) { + log.error(e.getMessage()); + return false; + } + + } } diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/mnt/util/DataTypeEnum.java b/eladmin-system/src/main/java/me/zhengjie/modules/mnt/util/DataTypeEnum.java new file mode 100644 index 00000000..e6d4ef46 --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/mnt/util/DataTypeEnum.java @@ -0,0 +1,123 @@ +/* + * << + * Davinci + * == + * Copyright (C) 2016 - 2019 EDP + * == + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * >> + * + */ + +package me.zhengjie.modules.mnt.util; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public enum DataTypeEnum { + + MYSQL("mysql", "mysql", "com.mysql.jdbc.Driver", "`", "`", "'", "'"), + + ORACLE("oracle", "oracle", "oracle.jdbc.driver.OracleDriver", "\"", "\"", "\"", "\""), + + SQLSERVER("sqlserver", "sqlserver", "com.microsoft.sqlserver.jdbc.SQLServerDriver", "\"", "\"", "\"", "\""), + + H2("h2", "h2", "org.h2.Driver", "`", "`", "\"", "\""), + + PHOENIX("phoenix", "hbase phoenix", "org.apache.phoenix.jdbc.PhoenixDriver", "", "", "\"", "\""), + + MONGODB("mongo", "mongodb", "mongodb.jdbc.MongoDriver", "`", "`", "\"", "\""), + + ELASTICSEARCH("sql4es", "elasticsearch", "nl.anchormen.sql4es.jdbc.ESDriver", "", "", "'", "'"), + + PRESTO("presto", "presto", "com.facebook.presto.jdbc.PrestoDriver", "", "", "\"", "\""), + + MOONBOX("moonbox", "moonbox", "moonbox.jdbc.MbDriver", "`", "`", "`", "`"), + + CASSANDRA("cassandra", "cassandra", "com.github.adejanovski.cassandra.jdbc.CassandraDriver", "", "", "'", "'"), + + CLICKHOUSE("clickhouse", "clickhouse", "ru.yandex.clickhouse.ClickHouseDriver", "", "", "\"", "\""), + + KYLIN("kylin", "kylin", "org.apache.kylin.jdbc.Driver", "\"", "\"", "\"", "\""), + + VERTICA("vertica", "vertica", "com.vertica.jdbc.Driver", "", "", "'", "'"), + + HANA("sap", "sap hana", "com.sap.db.jdbc.Driver", "", "", "'", "'"), + + IMPALA("impala", "impala", "com.cloudera.impala.jdbc41.Driver", "", "", "'", "'"); + + + private String feature; + private String desc; + private String driver; + private String keywordPrefix; + private String keywordSuffix; + private String aliasPrefix; + private String aliasSuffix; + + private static final String jdbcUrlPrefix = "jdbc:"; + + DataTypeEnum(String feature, String desc, String driver, String keywordPrefix, String keywordSuffix, String aliasPrefix, String aliasSuffix) { + this.feature = feature; + this.desc = desc; + this.driver = driver; + this.keywordPrefix = keywordPrefix; + this.keywordSuffix = keywordSuffix; + this.aliasPrefix = aliasPrefix; + this.aliasSuffix = aliasSuffix; + } + + public static DataTypeEnum urlOf(String jdbcUrl) { + String url = jdbcUrl.toLowerCase().trim(); + for (DataTypeEnum dataTypeEnum : values()) { + if (url.startsWith(jdbcUrlPrefix + dataTypeEnum.feature)) { + try { + Class aClass = Class.forName(dataTypeEnum.getDriver()); + if (null == aClass) { + throw new RuntimeException("Unable to get driver instance for jdbcUrl: " + jdbcUrl); + } + } catch (ClassNotFoundException e) { + throw new RuntimeException("Unable to get driver instance: " + jdbcUrl); + } + return dataTypeEnum; + } + } + return null; + } + + public String getFeature() { + return feature; + } + + public String getDesc() { + return desc; + } + + public String getDriver() { + return driver; + } + + public String getKeywordPrefix() { + return keywordPrefix; + } + + public String getKeywordSuffix() { + return keywordSuffix; + } + + public String getAliasPrefix() { + return aliasPrefix; + } + + public String getAliasSuffix() { + return aliasSuffix; + } +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/mnt/util/SqlUtils.java b/eladmin-system/src/main/java/me/zhengjie/modules/mnt/util/SqlUtils.java new file mode 100644 index 00000000..9920dc87 --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/mnt/util/SqlUtils.java @@ -0,0 +1,223 @@ +package me.zhengjie.modules.mnt.util; + +import cn.hutool.crypto.SecureUtil; +import com.alibaba.druid.pool.DruidDataSource; +import com.alibaba.druid.util.StringUtils; +import com.google.common.collect.Lists; +import lombok.extern.slf4j.Slf4j; + +import javax.sql.DataSource; +import java.io.*; +import java.sql.*; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Slf4j +public class SqlUtils { + + public static final String COLON = ":"; + + private static volatile Map map = new HashMap<>(); + + private static String getKey(String jdbcUrl, String username, String password) { + StringBuilder sb = new StringBuilder(); + if (!StringUtils.isEmpty(username)) { + sb.append(username); + } + if (!StringUtils.isEmpty(password)) { + sb.append(COLON).append(password); + } + sb.append(COLON).append(jdbcUrl.trim()); + + return SecureUtil.md5(sb.toString()); + } + + /** + * 获取数据源 + * + * @param jdbcUrl + * @param userName + * @param password + * @return + */ + private static DataSource getDataSource(String jdbcUrl, String userName, String password) { + String key = getKey(jdbcUrl, userName, password); + if (!map.containsKey(key) || null == map.get(key)) { + DruidDataSource druidDataSource = new DruidDataSource(); + + String className = null; + try { + className = DriverManager.getDriver(jdbcUrl.trim()).getClass().getName(); + } catch (SQLException e) { + throw new RuntimeException("Get class name error: =" + jdbcUrl); + } + if (StringUtils.isEmpty(className)) { + DataTypeEnum dataTypeEnum = DataTypeEnum.urlOf(jdbcUrl); + if (null == dataTypeEnum) { + throw new RuntimeException("Not supported data type: jdbcUrl=" + jdbcUrl); + } + druidDataSource.setDriverClassName(dataTypeEnum.getDriver()); + } else { + druidDataSource.setDriverClassName(className); + } + + + druidDataSource.setUrl(jdbcUrl); + druidDataSource.setUsername(userName); + druidDataSource.setPassword(password); + // 配置获取连接等待超时的时间 + druidDataSource.setMaxWait(3000); + // 配置初始化大小、最小、最大 + druidDataSource.setInitialSize(1); + druidDataSource.setMinIdle(1); + druidDataSource.setMaxActive(1); + + // 配置间隔多久才进行一次检测需要关闭的空闲连接,单位是毫秒 + druidDataSource.setTimeBetweenEvictionRunsMillis(50000); + // 配置一旦重试多次失败后等待多久再继续重试连接,单位是毫秒 + druidDataSource.setTimeBetweenConnectErrorMillis(18000); + // 配置一个连接在池中最小生存的时间,单位是毫秒 + druidDataSource.setMinEvictableIdleTimeMillis(300000); + // 这个特性能解决 MySQL 服务器8小时关闭连接的问题 + druidDataSource.setMaxEvictableIdleTimeMillis(25200000); + + try { + druidDataSource.init(); + } catch (SQLException e) { + log.error("Exception during pool initialization", e); + throw new RuntimeException(e.getMessage()); + } + map.put(key, druidDataSource); + } + return map.get(key); + } + + private static Connection getConnection(String jdbcUrl, String userName, String password) { + DataSource dataSource = getDataSource(jdbcUrl, userName, password); + Connection connection = null; + try { + connection = dataSource.getConnection(); + } catch (Exception e) { + connection = null; + } + try { + if (null == connection || connection.isClosed() || !connection.isValid(5)) { + log.info("connection is closed or invalid, retry get connection!"); + connection = dataSource.getConnection(); + } + } catch (Exception e) { + log.error("create connection error, jdbcUrl: {}", jdbcUrl); + throw new RuntimeException("create connection error, jdbcUrl: " + jdbcUrl); + } + return connection; + } + + private static void releaseConnection(Connection connection) { + if (null != connection) { + try { + connection.close(); + connection = null; + } catch (Exception e) { + e.printStackTrace(); + log.error("connection close error", e.getMessage()); + } + } + } + + + public static void closeResult(ResultSet rs) { + if (rs != null) { + try { + rs.close(); + rs = null; + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + public static boolean testConnection(String jdbcUrl, String userName, String password) { + Connection connection = null; + try { + connection = getConnection(jdbcUrl, userName, password); + if (null != connection) { + return true; + } + } catch (Exception e) { + log.info("Get connection failed:", e.getMessage()); + } finally { + releaseConnection(connection); + } + return false; + } + + public static String executeFile(String jdbcUrl, String userName, String password, File sqlFile) { + Connection connection = getConnection(jdbcUrl, userName, password); + try { + batchExecute(connection, readSqlList( sqlFile)); + } catch (Exception e) { + log.error("sql脚本执行发生异常:{}",e.getMessage()); + return e.getMessage(); + }finally { + releaseConnection(connection); + } + return "success"; + } + + + /** + * 批量执行sql + * @param connection + * @param sqlList + * @return + */ + public static void batchExecute(Connection connection, List sqlList) throws SQLException { + Statement st = connection.createStatement(); + for (String sql : sqlList) { + if (sql.endsWith(";")) { + sql = sql.substring(0, sql.length() - 1); + } + st.addBatch(sql); + } + st.executeBatch(); + } + + /** + * 将文件中的sql语句以;为单位读取到列表中 + * @param sqlFile + * @return + * @throws Exception + */ + private static List readSqlList(File sqlFile) throws Exception { + List sqlList = Lists.newArrayList(); + StringBuilder sb = new StringBuilder(); + BufferedReader reader = null; + try { + reader = new BufferedReader(new InputStreamReader( + new FileInputStream(sqlFile), "UTF-8")); + String tmp = null; + while ((tmp = reader.readLine()) != null) { + log.info("line:{}", tmp); + if (tmp.endsWith(";")) { + sb.append(tmp); + sqlList.add(sb.toString()); + sb.delete(0, sb.length()); + } else { + sb.append(tmp); + } + } + if (!"".endsWith(sb.toString().trim())) { + sqlList.add(sb.toString()); + } + } finally { + try { + reader.close(); + } catch (IOException e1) { + } + } + + return sqlList; + } + +} diff --git a/sql/eladmin.sql b/sql/eladmin.sql index 1da6dd48..d2d41776 100644 --- a/sql/eladmin.sql +++ b/sql/eladmin.sql @@ -367,6 +367,8 @@ CREATE TABLE `mnt_database` ( PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact; +INSERT INTO `mnt_database` VALUES ('604dd98ae8b44b128544c2135628f87d', '本机', 'jdbc:log4jdbc:mysql://localhost:3306/eladmin?serverTimezone=UTC', 'root', '123456'); + -- ---------------------------- -- Table structure for mnt_deploy -- ----------------------------