diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/ConverterController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/ConverterController.java new file mode 100644 index 000000000..b9d6905ac --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/ConverterController.java @@ -0,0 +1,82 @@ +package ai.chat2db.server.web.api.controller.ncx; + +import ai.chat2db.server.tools.base.wrapper.result.DataResult; +import ai.chat2db.server.tools.common.util.ConfigUtils; +import ai.chat2db.server.web.api.controller.ncx.service.ConverterService; +import ai.chat2db.server.web.api.controller.ncx.vo.UploadVO; +import ai.chat2db.server.web.api.util.FileUtils; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import java.io.File; +import java.util.Objects; +import java.util.UUID; + +/** + * ConverterController + * + * @author lzy + **/ +@RequestMapping("/api/converter") +@RestController +@Slf4j +public class ConverterController { + + @Autowired + private ConverterService converterService; + + /** + * 导出教程 + * + * @param file file + * @return DataResult + * @see + **/ + @SneakyThrows + @PostMapping("/ncx/upload") + public DataResult ncxUploadFile(@RequestParam("file") MultipartFile file) { + // 验证文件后缀 + String fileExtension = FileUtils.getFileExtension(Objects.requireNonNull(file.getOriginalFilename())); + if (!fileExtension.equalsIgnoreCase(FileUtils.ConfigFile.NCX.name())) { + return DataResult.error("1", "上传的文件必须是ncx文件!"); + } + File temp = new File(ConfigUtils.CONFIG_BASE_PATH + File.separator + UUID.randomUUID() + ".tmp"); + file.transferTo(temp); + return DataResult.of(converterService.uploadFile(temp)); + } + + @SneakyThrows + @PostMapping("/dbp/upload") + public DataResult dbpUploadFile(@RequestParam("file") MultipartFile file) { + // 验证文件后缀 + String fileExtension = FileUtils.getFileExtension(Objects.requireNonNull(file.getOriginalFilename())); + if (!fileExtension.equalsIgnoreCase(FileUtils.ConfigFile.DBP.name())) { + return DataResult.error("1", "上传的文件必须是ncx文件!"); + } + File temp = new File(ConfigUtils.CONFIG_BASE_PATH + File.separator + UUID.randomUUID() + ".tmp"); + file.transferTo(temp); + return DataResult.of(converterService.dbpUploadFile(temp)); + } + + + /** + * 导入datagrip的连接信息,通过 ctrl/cmd + c(shift多选)复制连接,再导入进来 + * 目前复制的连接信息里面是没有密码的、ssh连接信息也没有 + * + * @param text text + * @return DataResult + **/ + @SneakyThrows + @PostMapping("/datagrip/upload") + public DataResult datagripUploadFile(@RequestParam("text") String text) { + return DataResult.of(converterService.datagripUploadFile(text)); + } + + +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/cipher/CommonCipher.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/cipher/CommonCipher.java new file mode 100644 index 000000000..55fe1f86a --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/cipher/CommonCipher.java @@ -0,0 +1,44 @@ +package ai.chat2db.server.web.api.controller.ncx.cipher; + +import java.util.Formatter; + +/** + * CommonCipher 公共加/解密 + * + * @author lzy + */ +public abstract class CommonCipher { + + public String encryptString(String plaintext) { + return null; + } + + public String decryptString(String ciphertext) { + return null; + } + + public String printHexBinary(byte[] data) { + StringBuilder hexBuilder = new StringBuilder(); + Formatter formatter = new Formatter(hexBuilder); + for (byte b : data) { + formatter.format("%02x", b); + } + return hexBuilder.toString(); + } + + public static byte[] parseHexBinary(String data) { + return hexStringToByteArray(data); + } + + public static byte[] hexStringToByteArray(String hex) { + if (hex.length() % 2 != 0) { + throw new IllegalArgumentException("Hex string length must be even"); + } + byte[] bytes = new byte[hex.length() / 2]; + for (int i = 0; i < hex.length(); i += 2) { + String byteString = hex.substring(i, i + 2); + bytes[i / 2] = (byte) Integer.parseInt(byteString, 16); + } + return bytes; + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/cipher/Navicat11Cipher.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/cipher/Navicat11Cipher.java new file mode 100644 index 000000000..dc3c053c2 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/cipher/Navicat11Cipher.java @@ -0,0 +1,177 @@ +package ai.chat2db.server.web.api.controller.ncx.cipher; + +import org.apache.commons.lang3.StringUtils; + +import javax.crypto.Cipher; +import javax.crypto.spec.SecretKeySpec; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.util.Arrays; + +/** + * Navicat11及以下密码加密解密 + * + * @author lzy + */ +public class Navicat11Cipher extends CommonCipher { + public static final String DefaultUserKey = "3DC5CA39"; + private static byte[] IV; + + private static SecretKeySpec key; + private static Cipher encryptor; + private static Cipher decrypt; + + private static void initKey() { + try { + MessageDigest sha1 = MessageDigest.getInstance("SHA1"); + byte[] userKey_data = Navicat11Cipher.DefaultUserKey.getBytes(StandardCharsets.UTF_8); + sha1.update(userKey_data, 0, userKey_data.length); + key = new SecretKeySpec(sha1.digest(), "Blowfish"); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private static void initCipherEncrypt() { + try { + // Must use NoPadding + encryptor = Cipher.getInstance("Blowfish/ECB/NoPadding"); + encryptor.init(Cipher.ENCRYPT_MODE, key); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private static void initCipherDecrypt() { + try { + // Must use NoPadding + decrypt = Cipher.getInstance("Blowfish/ECB/NoPadding"); + decrypt.init(Cipher.DECRYPT_MODE, key); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private static void initIV() { + try { + byte[] initVec = parseHexBinary("FFFFFFFFFFFFFFFF"); + IV = encryptor.doFinal(initVec); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private void xorBytes(byte[] a, byte[] b) { + for (int i = 0; i < a.length; i++) { + int aVal = a[i] & 0xff; // convert byte to integer + int bVal = b[i] & 0xff; + a[i] = (byte) (aVal ^ bVal); // xor aVal and bVal and typecast to byte + } + } + + private void xorBytes(byte[] a, byte[] b, int l) { + for (int i = 0; i < l; i++) { + int aVal = a[i] & 0xff; // convert byte to integer + int bVal = b[i] & 0xff; + a[i] = (byte) (aVal ^ bVal); // xor aVal and bVal and typecast to byte + } + } + + static { + initKey(); + initCipherEncrypt(); + initCipherDecrypt(); + initIV(); + } + + private byte[] Encrypt(byte[] inData) { + try { + byte[] CV = Arrays.copyOf(IV, IV.length); + byte[] ret = new byte[inData.length]; + + int blocks_len = inData.length / 8; + int left_len = inData.length % 8; + + for (int i = 0; i < blocks_len; i++) { + byte[] temp = Arrays.copyOfRange(inData, i * 8, (i * 8) + 8); + + xorBytes(temp, CV); + temp = encryptor.doFinal(temp); + xorBytes(CV, temp); + + System.arraycopy(temp, 0, ret, i * 8, 8); + } + + if (left_len != 0) { + CV = encryptor.doFinal(CV); + byte[] temp = Arrays.copyOfRange(inData, blocks_len * 8, (blocks_len * 8) + left_len); + xorBytes(temp, CV, left_len); + System.arraycopy(temp, 0, ret, blocks_len * 8, temp.length); + } + + return ret; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public String encryptString(String inputString) { + try { + byte[] inData = inputString.getBytes(StandardCharsets.UTF_8); + byte[] outData = Encrypt(inData); + return printHexBinary(outData); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private byte[] Decrypt(byte[] inData) { + try { + byte[] cv = Arrays.copyOf(IV, IV.length); + byte[] ret = new byte[inData.length]; + + int blocks_len = inData.length / 8; + int left_len = inData.length % 8; + + for (int i = 0; i < blocks_len; i++) { + byte[] temp = Arrays.copyOfRange(inData, i * 8, (i * 8) + 8); + + temp = decrypt.doFinal(temp); + xorBytes(temp, cv); + System.arraycopy(temp, 0, ret, i * 8, 8); + for (int j = 0; j < cv.length; j++) { + cv[j] = (byte) (cv[j] ^ inData[i * 8 + j]); + } + } + + if (left_len != 0) { + cv = encryptor.doFinal(cv); + byte[] temp = Arrays.copyOfRange(inData, blocks_len * 8, (blocks_len * 8) + left_len); + + xorBytes(temp, cv, left_len); + for (int j = 0; j < temp.length; j++) { + ret[blocks_len * 8 + j] = temp[j]; + } + } + + return ret; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public String decryptString(String hexString) { + if (StringUtils.isEmpty(hexString)) { + return ""; + } + try { + byte[] inData = parseHexBinary(hexString); + byte[] outData = Decrypt(inData); + return new String(outData, StandardCharsets.UTF_8); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/cipher/Navicat12Cipher.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/cipher/Navicat12Cipher.java new file mode 100644 index 000000000..45fdd48b0 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/cipher/Navicat12Cipher.java @@ -0,0 +1,50 @@ +package ai.chat2db.server.web.api.controller.ncx.cipher; + +import org.apache.commons.lang3.StringUtils; + +import javax.crypto.Cipher; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import java.nio.charset.StandardCharsets; + +/** + * Navicat12及以上密码加密解密 + * + * @author lzy + */ +public class Navicat12Cipher extends CommonCipher { + private static final SecretKeySpec AES_KEY; + private static final IvParameterSpec AES_IV; + + static { + AES_KEY = new SecretKeySpec("libcckeylibcckey".getBytes(StandardCharsets.UTF_8), "AES"); + AES_IV = new IvParameterSpec("libcciv libcciv ".getBytes(StandardCharsets.UTF_8)); + } + + @Override + public String encryptString(String plaintext) { + try { + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + cipher.init(Cipher.ENCRYPT_MODE, AES_KEY, AES_IV); + byte[] ret = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8)); + return printHexBinary(ret); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public String decryptString(String ciphertext) { + if (StringUtils.isEmpty(ciphertext)) { + return ""; + } + try { + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + cipher.init(Cipher.DECRYPT_MODE, AES_KEY, AES_IV); + byte[] ret = cipher.doFinal(parseHexBinary(ciphertext)); + return new String(ret, StandardCharsets.UTF_8); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/enums/DataBaseType.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/enums/DataBaseType.java new file mode 100644 index 000000000..3dacfeade --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/enums/DataBaseType.java @@ -0,0 +1,87 @@ +package ai.chat2db.server.web.api.controller.ncx.enums; + +import lombok.Getter; +import org.apache.commons.lang3.StringUtils; + +/** + * DataBaseType + * + * @author lzy + **/ +@Getter +public enum DataBaseType { + /** + * MYSQL + */ + MYSQL("jdbc:mysql://%s:%s"), + /** + * ORACLE + */ + ORACLE("jdbc:oracle:thin:@%s:%s:XE"), + /** + * SQL_SERVER + */ + SQLSERVER("jdbc:sqlserver://%s:%s"), + /** + * SQL_SERVER + */ + SQLITE("jdbc:sqlite:%s"), + /** + * POSTGRESQL + **/ + POSTGRESQL("jdbc:postgresql://%s:%s"), + /** + * DB2 + **/ + DB2("jdbc:db2://%s:%s"), + /** + * Mariadb + **/ + Mariadb("jdbc:mariadb://%s:%s"), + /** + * DM + **/ + DM("jdbc:dm://%s:%s"), + /** + * KINGBASE8 + **/ + KINGBASE8("jdbc:kingbase8://%s:%s"), + /** + * Presto + **/ + Presto("jdbc:presto://%s:%s"), + /** + * OceanBase + **/ + OceanBase("jdbc:oceanbase://%s:%s"), + /** + * Hive + **/ + Hive("jdbc:hive2://%s:%s"), + /** + * ClickHouse + **/ + ClickHouse("jdbc:clickhouse://%s:%s"); + + private String urlString; + + DataBaseType(String urlString) { + this.urlString = urlString; + } + + public void setUrlString(String urlString) { + this.urlString = urlString; + } + + public static DataBaseType matchType(String value) { + if (StringUtils.isNotEmpty(value)) { + for (DataBaseType dataBase : DataBaseType.values()) { + if (dataBase.name().equals(value.toUpperCase())) { + return dataBase; + } + } + } + return null; + } + +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/enums/VersionEnum.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/enums/VersionEnum.java new file mode 100644 index 000000000..18b34e71e --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/enums/VersionEnum.java @@ -0,0 +1,17 @@ +package ai.chat2db.server.web.api.controller.ncx.enums; + +/** + * navicat版本枚举(版本区分navicat加密算法) + * + * @author lzy + */ +public enum VersionEnum { + /** + * navicat11 + */ + native11, + /** + * navicat12+ + */ + navicat12more +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/factory/CipherFactory.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/factory/CipherFactory.java new file mode 100644 index 000000000..53253d966 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/factory/CipherFactory.java @@ -0,0 +1,45 @@ +package ai.chat2db.server.web.api.controller.ncx.factory; + +import ai.chat2db.server.web.api.controller.ncx.cipher.CommonCipher; +import ai.chat2db.server.web.api.controller.ncx.cipher.Navicat11Cipher; +import ai.chat2db.server.web.api.controller.ncx.cipher.Navicat12Cipher; +import ai.chat2db.server.web.api.controller.ncx.enums.VersionEnum; +import lombok.SneakyThrows; +import org.springframework.stereotype.Service; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * CipherFactory + * + * @author lzy + **/ +@Service +public class CipherFactory { + /** + * NavicatCipher缓存池 + */ + private static final Map REPORT_POOL = new ConcurrentHashMap<>(0); + + static { + REPORT_POOL.put(VersionEnum.native11.name(), new Navicat11Cipher()); + REPORT_POOL.put(VersionEnum.navicat12more.name(), new Navicat12Cipher()); + } + + /** + * 获取对应加/解密方法 + * + * @param type 类型 + * @return ITokenGranter + */ + @SneakyThrows + public static CommonCipher get(String type) { + CommonCipher cipher = REPORT_POOL.get(type); + if (cipher == null) { + throw new ClassNotFoundException("no CommonCipher was found"); + } else { + return cipher; + } + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/service/ConverterService.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/service/ConverterService.java new file mode 100644 index 000000000..fc55f7a0e --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/service/ConverterService.java @@ -0,0 +1,20 @@ +package ai.chat2db.server.web.api.controller.ncx.service; + +import ai.chat2db.server.web.api.controller.ncx.vo.UploadVO; + +import java.io.File; +import java.io.InputStream; + +/** + * ConverterService + * + * @author lzy + **/ +public interface ConverterService { + + UploadVO uploadFile(File file); + + UploadVO dbpUploadFile(File file); + + UploadVO datagripUploadFile(String text); +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/service/impl/ConverterServiceImpl.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/service/impl/ConverterServiceImpl.java new file mode 100644 index 000000000..4546a79a9 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/service/impl/ConverterServiceImpl.java @@ -0,0 +1,259 @@ +package ai.chat2db.server.web.api.controller.ncx.service.impl; + +import ai.chat2db.server.domain.core.util.DesUtil; +import ai.chat2db.server.domain.repository.entity.DataSourceDO; +import ai.chat2db.server.domain.repository.mapper.DataSourceMapper; +import ai.chat2db.server.web.api.controller.ncx.cipher.CommonCipher; +import ai.chat2db.server.web.api.controller.ncx.enums.DataBaseType; +import ai.chat2db.server.web.api.controller.ncx.enums.VersionEnum; +import ai.chat2db.server.web.api.controller.ncx.factory.CipherFactory; +import ai.chat2db.server.web.api.controller.ncx.service.ConverterService; +import ai.chat2db.server.web.api.controller.ncx.vo.UploadVO; +import ai.chat2db.spi.model.SSHInfo; +import com.alibaba.excel.util.FileUtils; +import com.alibaba.fastjson2.JSON; +import lombok.SneakyThrows; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.w3c.dom.*; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * ConverterServiceImpl + * + * @author lzy + **/ +@Service +@Transactional(rollbackFor = Exception.class) +public class ConverterServiceImpl implements ConverterService { + + private static final double NAVICAT11 = 1.1D; + + private static CommonCipher cipher; + + /** + * 连接信息头部 + **/ + private static final String DATASOURCE_SETTINGS = "#DataSourceSettings#"; + private static final String XML_HEADER = ""; + /** + * xml连接信息开始标志位 + **/ + private static final String BEGIN = "#BEGIN#"; + + @Autowired + private DataSourceMapper dataSourceMapper; + /** + * jdbc通用匹配ip和端口 + */ + public static final Pattern IP_PORT = Pattern.compile("jdbc:(?[a-z]+)://(?[a-zA-Z0-9-//.]+):(?[0-9]+)"); + /** + * oracle匹配ip和端口 + */ + public static final Pattern ORACLE_IP_PORT = Pattern.compile("jdbc:(?[a-z]+):(?[a-z]+):@(?[a-zA-Z0-9-//.]+):(?[0-9]+)"); + + @Override + public UploadVO uploadFile(File file) { + + UploadVO vo = new UploadVO(); + try { + // List>> 要导入的连接 + List>> configMap = new ArrayList<>(); + //1、创建一个DocumentBuilderFactory的对象 + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + //2、创建一个DocumentBuilder的对象 + //创建DocumentBuilder对象 + DocumentBuilder db = dbf.newDocumentBuilder(); + //3、通过DocumentBuilder对象的parser方法加载xml文件到当前项目下 + Document document = db.parse(file); + //获取所有Connections节点的集合 + NodeList connectList = document.getElementsByTagName("Connection"); + + NodeList nodeList = document.getElementsByTagName("Connections"); + //选中第一个节点 + NamedNodeMap verMap = nodeList.item(0).getAttributes(); + double version = Double.parseDouble((verMap.getNamedItem("Ver").getNodeValue())); + if (version <= NAVICAT11) { + cipher = CipherFactory.get(VersionEnum.native11.name()); + } else { + cipher = CipherFactory.get(VersionEnum.navicat12more.name()); + } + //配置map + Map> connectionMap = new HashMap<>(); + //遍历每一个Connections节点 + for (int i = 0; i < connectList.getLength(); i++) { + //通过 item(i)方法 获取一个Connection节点,nodeList的索引值从0开始 + Node connect = connectList.item(i); + //获取Connection节点的所有属性集合 + NamedNodeMap attrs = connect.getAttributes(); + //遍历Connection的属性 + Map map = new HashMap<>(0); + for (int j = 0; j < attrs.getLength(); j++) { + //通过item(index)方法获取connect节点的某一个属性 + Node attr = attrs.item(j); + map.put(attr.getNodeName(), attr.getNodeValue()); + } + connectionMap.put(map.get("ConnectionName") + map.get("ConnType"), map); + } + configMap.add(connectionMap); + // 将获取到navicat导入的链接,写入chat2db的h2数据库 + insertDBConfig(configMap); + //删除临时文件 + FileUtils.delete(file); + } catch (Exception e) { + throw new RuntimeException(e); + } + return vo; + } + + + @Override + public UploadVO dbpUploadFile(File file) { + return null; + } + + @SneakyThrows + @Override + public UploadVO datagripUploadFile(String text) { + UploadVO vo = new UploadVO(); + if (!text.startsWith(DATASOURCE_SETTINGS)) { + throw new RuntimeException("连接信息的头部不正确!"); + } + String[] items = text.split("\n"); + List configs = new ArrayList<>(); + for (int i = 0; i < items.length; i++) { + if (items[i].equals(BEGIN)) { + configs.add(XML_HEADER + items[i + 1]); + } + } + for (String config : configs) { + //1、创建一个DocumentBuilderFactory的对象 + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + //2、创建一个DocumentBuilder的对象 + //创建DocumentBuilder对象 + DocumentBuilder db = dbf.newDocumentBuilder(); + //3、通过DocumentBuilder对象的parser方法加载xml文件到当前项目下 + try (InputStream inputStream = new ByteArrayInputStream(config.getBytes(StandardCharsets.UTF_8))) { + Document document = db.parse(inputStream); + // 获取根元素 + Element rootElement = document.getDocumentElement(); + //创建datasource + DataSourceDO dataSourceDO = new DataSourceDO(); + LocalDateTime dateTime = LocalDateTime.now(); + dataSourceDO.setGmtCreate(dateTime); + dataSourceDO.setGmtModified(dateTime); + dataSourceDO.setAlias(rootElement.getAttribute("name")); + // 获取子元素 database-info + Element databaseInfoElement = (Element) rootElement.getElementsByTagName("database-info").item(0); + + // 获取连接相关信息 + String type = databaseInfoElement.getAttribute("dbms"); + String jdbcUrl = rootElement.getElementsByTagName("jdbc-url").item(0).getTextContent(); + String username = rootElement.getElementsByTagName("user-name").item(0).getTextContent(); + String driverName = rootElement.getElementsByTagName("jdbc-driver").item(0).getTextContent(); + String host = ""; + String port = ""; + if (type.equals(DataBaseType.ORACLE.name())) { + // 创建 Matcher 对象 + Matcher matcher = ORACLE_IP_PORT.matcher(jdbcUrl); + // 查找匹配的 IP 地址和端口号 + if (matcher.find()) { + host = matcher.group("host"); + port = matcher.group("port"); + } + } else { + // 创建 Matcher 对象 + Matcher matcher = IP_PORT.matcher(jdbcUrl); + // 查找匹配的 IP 地址和端口号 + if (matcher.find()) { + host = matcher.group("host"); + port = matcher.group("port"); + + } + } + dataSourceDO.setHost(host); + dataSourceDO.setPort(port); + dataSourceDO.setUrl(jdbcUrl); + dataSourceDO.setUserName(username); + dataSourceDO.setDriver(driverName); + dataSourceDO.setType(type); + dataSourceMapper.insert(dataSourceDO); + } + } + return vo; + } + + /** + * 写入到数据库 + * + * @param list 读取ncx文件的数据 + */ + @SneakyThrows + public void insertDBConfig(List>> list) { + for (Map> map : list) { + for (Map.Entry> valueMap : map.entrySet()) { + Map resultMap = valueMap.getValue(); + // mysql的版本还无法区分 + DataBaseType dataBaseType = DataBaseType.matchType(resultMap.get("ConnType")); + DataSourceDO dataSourceDO; + if (null == dataBaseType) { + //未匹配到数据库类型,如:navicat支持MongoDB等,但chat2DB暂不支持 + continue; + } else { + dataSourceDO = new DataSourceDO(); + dataSourceDO.setHost(resultMap.get("Host")); + dataSourceDO.setPort(resultMap.get("Port")); + dataSourceDO.setUrl(String.format(dataBaseType.getUrlString(), dataSourceDO.getHost(), dataSourceDO.getPort())); + } + // 解密密码 + String password = cipher.decryptString(resultMap.getOrDefault("Password", "")); + LocalDateTime dateTime = LocalDateTime.now(); + dataSourceDO.setGmtCreate(dateTime); + dataSourceDO.setGmtModified(dateTime); + dataSourceDO.setAlias(resultMap.get("ConnectionName")); + dataSourceDO.setUserName(resultMap.get("UserName")); + dataSourceDO.setType(resultMap.get("ConnType")); + //password 为解密出来的密文,再使用chat2db的加密 + DesUtil desUtil = new DesUtil(DesUtil.DES_KEY); + String encryptStr = desUtil.encrypt(password, "CBC"); + dataSourceDO.setPassword(encryptStr); + SSHInfo sshInfo = new SSHInfo(); + if ("false".equals(resultMap.get("SSH"))) { + sshInfo.setUse(false); + } else { + sshInfo.setUse(true); + sshInfo.setHostName(resultMap.get("SSH_Host")); + sshInfo.setPort(resultMap.get("SSH_Port")); + sshInfo.setUserName(resultMap.get("SSH_UserName")); + // 目前chat2DB只支持 password 和 Private key + boolean passwordType = "password".equalsIgnoreCase(resultMap.get("SSH_AuthenMethod")); + sshInfo.setAuthenticationType(passwordType ? "password" : "Private key"); + if (passwordType) { + // 解密密码 + String ssh_password = cipher.decryptString(resultMap.getOrDefault("SSH_Password", "")); + sshInfo.setPassword(ssh_password); + } else { + sshInfo.setKeyFile(resultMap.get("SSH_PrivateKey")); + sshInfo.setPassphrase(resultMap.get("SSH_Passphrase")); + } + } + dataSourceDO.setSsh(JSON.toJSONString(sshInfo)); + dataSourceMapper.insert(dataSourceDO); + } + } + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/vo/UploadVO.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/vo/UploadVO.java new file mode 100644 index 000000000..452c228cc --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/vo/UploadVO.java @@ -0,0 +1,19 @@ +package ai.chat2db.server.web.api.controller.ncx.vo; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +/** + * UploadVO + * + * @author lzy + **/ +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class UploadVO { + private String result; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/DatabaseExportService.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/DatabaseExportService.java index 1f9345298..a33a4d650 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/DatabaseExportService.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/DatabaseExportService.java @@ -3,7 +3,10 @@ import ai.chat2db.server.domain.api.enums.ExportTypeEnum; import ai.chat2db.server.domain.api.model.IndexInfo; import ai.chat2db.server.domain.api.model.TableParameter; +import ai.chat2db.server.tools.common.util.I18nUtils; import ai.chat2db.server.web.api.controller.rdb.doc.conf.ExportOptions; +import ai.chat2db.server.web.api.controller.rdb.doc.constant.CommonConstant; +import ai.chat2db.server.web.api.controller.rdb.doc.constant.PatternConstant; import ai.chat2db.server.web.api.controller.rdb.vo.ColumnVO; import ai.chat2db.server.web.api.controller.rdb.vo.IndexVO; import ai.chat2db.server.web.api.controller.rdb.vo.TableVO; @@ -58,6 +61,41 @@ public class DatabaseExportService { public final static String JOINER = "---"; private void init() { + CommonConstant.INDEX_HEAD_NAMES = new String[]{I18nUtils.getMessage("main.indexName"), + I18nUtils.getMessage("main.indexFieldName"), + I18nUtils.getMessage("main.indexType"), + I18nUtils.getMessage("main.indexMethod"), + I18nUtils.getMessage("main.indexNote")}; + CommonConstant.COLUMN_HEAD_NAMES = new String[]{I18nUtils.getMessage("main.fieldNo"), + I18nUtils.getMessage("main.fieldName"), + I18nUtils.getMessage("main.fieldType"), + I18nUtils.getMessage("main.fieldLength"), + I18nUtils.getMessage("main.fieldIfEmpty"), + I18nUtils.getMessage("main.fieldDefault"), + I18nUtils.getMessage("main.fieldDecimalPlaces"), + I18nUtils.getMessage("main.fieldNote")}; + // index表头 + StringBuilder mdIndex = new StringBuilder(PatternConstant.MD_SPLIT); + StringBuilder htmlIndex = new StringBuilder(""); + for (int i = 0; i < CommonConstant.INDEX_HEAD_NAMES.length; i++) { + mdIndex.append(CommonConstant.INDEX_HEAD_NAMES[i]).append(i == CommonConstant.INDEX_HEAD_NAMES.length - 1 ? "" : PatternConstant.MD_SPLIT); + htmlIndex.append(CommonConstant.INDEX_HEAD_NAMES[i]).append(i == CommonConstant.INDEX_HEAD_NAMES.length - 1 ? "" : ""); + } + mdIndex.append(PatternConstant.MD_SPLIT); + htmlIndex.append(""); + // column 表头 + StringBuilder mdColumn = new StringBuilder(PatternConstant.MD_SPLIT); + StringBuilder htmlColumn = new StringBuilder(""); + for (int i = 0; i < CommonConstant.COLUMN_HEAD_NAMES.length; i++) { + mdColumn.append(CommonConstant.COLUMN_HEAD_NAMES[i]).append(i == CommonConstant.COLUMN_HEAD_NAMES.length - 1 ? "" : PatternConstant.MD_SPLIT); + htmlColumn.append(CommonConstant.COLUMN_HEAD_NAMES[i]).append(i == CommonConstant.COLUMN_HEAD_NAMES.length - 1 ? "" : ""); + } + mdColumn.append(PatternConstant.MD_SPLIT); + htmlColumn.append(""); + PatternConstant.ALL_INDEX_TABLE_HEADER = mdIndex.toString(); + PatternConstant.HTML_INDEX_TABLE_HEADER = htmlIndex.toString(); + PatternConstant.ALL_TABLE_HEADER = mdColumn.toString(); + PatternConstant.HTML_TABLE_HEADER = htmlColumn.toString(); listMap.clear(); indexMap.clear(); } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/constant/CommonConstant.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/constant/CommonConstant.java index b66f94752..cb3ef4b0a 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/constant/CommonConstant.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/constant/CommonConstant.java @@ -1,6 +1,5 @@ package ai.chat2db.server.web.api.controller.rdb.doc.constant; -import ai.chat2db.server.tools.common.util.I18nUtils; import lombok.extern.java.Log; /** @@ -13,19 +12,6 @@ public final class CommonConstant { /** * 表head **/ - public static String[] INDEX_HEAD_NAMES = - {I18nUtils.getMessage("main.indexName"), - I18nUtils.getMessage("main.indexFieldName"), - I18nUtils.getMessage("main.indexType"), - I18nUtils.getMessage("main.indexMethod"), - I18nUtils.getMessage("main.indexNote")}; - public static String[] COLUMN_HEAD_NAMES = - {I18nUtils.getMessage("main.fieldNo"), - I18nUtils.getMessage("main.fieldName"), - I18nUtils.getMessage("main.fieldType"), - I18nUtils.getMessage("main.fieldLength"), - I18nUtils.getMessage("main.fieldIfEmpty"), - I18nUtils.getMessage("main.fieldDefault"), - I18nUtils.getMessage("main.fieldDecimalPlaces"), - I18nUtils.getMessage("main.fieldNote")}; + public static String[] INDEX_HEAD_NAMES = {}; + public static String[] COLUMN_HEAD_NAMES = {}; } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/constant/PatternConstant.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/constant/PatternConstant.java index aa051ecfa..38521e54d 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/constant/PatternConstant.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/constant/PatternConstant.java @@ -1,8 +1,6 @@ package ai.chat2db.server.web.api.controller.rdb.doc.constant; -import ai.chat2db.server.tools.common.util.I18nUtils; - /** * PatternConstant * @@ -20,21 +18,10 @@ public final class PatternConstant { */ public static final String TITLE = "# %s"; public static final String CATALOG = "## %s"; - public static final String ALL_TABLE_HEADER = MD_SPLIT + I18nUtils.getMessage("main.fieldNo") + MD_SPLIT + - I18nUtils.getMessage("main.fieldName") + MD_SPLIT + - I18nUtils.getMessage("main.fieldType") + MD_SPLIT + - I18nUtils.getMessage("main.fieldLength") + MD_SPLIT + - I18nUtils.getMessage("main.fieldIfEmpty") + MD_SPLIT + - I18nUtils.getMessage("main.fieldDefault") + MD_SPLIT + - I18nUtils.getMessage("main.fieldDecimalPlaces") + MD_SPLIT + - I18nUtils.getMessage("main.fieldNote") + MD_SPLIT; + public static String ALL_TABLE_HEADER = ""; public static String TABLE_BODY = "|%s|%s|%s|%s|%s|%s|%s|%s|"; public static String TABLE_SEPARATOR = "|:----:|----|----|----|----|----|----|----|"; - public static final String ALL_INDEX_TABLE_HEADER = MD_SPLIT + I18nUtils.getMessage("main.indexName") + MD_SPLIT + - I18nUtils.getMessage("main.indexFieldName") + MD_SPLIT + - I18nUtils.getMessage("main.indexType") + MD_SPLIT + - I18nUtils.getMessage("main.indexMethod") + MD_SPLIT + - I18nUtils.getMessage("main.indexNote") + MD_SPLIT; + public static String ALL_INDEX_TABLE_HEADER = ""; public static String INDEX_TABLE_BODY = "|%s|%s|%s|%s|"; public static String INDEX_TABLE_SEPARATOR = "|:----:|----|----|----|"; @@ -44,20 +31,8 @@ public final class PatternConstant { public static final String HTML_TITLE = "

{0}

"; public static final String HTML_CATALOG = "

{1}

"; public static final String HTML_INDEX_ITEM = "
{1}"; - public static String HTML_TABLE_HEADER = "" + I18nUtils.getMessage("main.fieldNo") - + "" + I18nUtils.getMessage("main.fieldName") - + ""+ I18nUtils.getMessage("main.fieldType") - + "" + I18nUtils.getMessage("main.fieldLength") - + "" + I18nUtils.getMessage("main.fieldIfEmpty") - + "" + I18nUtils.getMessage("main.fieldDefault") - + "" + I18nUtils.getMessage("main.fieldDecimalPlaces") - + "" + I18nUtils.getMessage("main.fieldNote") - + ""; + public static String HTML_TABLE_HEADER = ""; public static String HTML_TABLE_BODY = "%s%s%s%s%s%s%s%s"; - public static String HTML_INDEX_TABLE_HEADER = "" + I18nUtils.getMessage("main.indexName") - + "" + I18nUtils.getMessage("main.indexFieldName") - + "" + I18nUtils.getMessage("main.indexType") - + "" + I18nUtils.getMessage("main.indexMethod") - + "" + I18nUtils.getMessage("main.indexNote") + ""; + public static String HTML_INDEX_TABLE_HEADER = ""; public static String HTML_INDEX_TABLE_BODY = "%s%s%s%s"; } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/util/FileUtils.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/util/FileUtils.java new file mode 100644 index 000000000..b1fdd2f54 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/util/FileUtils.java @@ -0,0 +1,26 @@ +package ai.chat2db.server.web.api.util; + +/** + * FileUtil + * + * @author lzy + **/ +public class FileUtils { + + public enum ConfigFile { + // navicat连接信息文件 + NCX, + // dbeaver连接信息文件 + DBP + } + + public static String getFileExtension(String fileName) { + int dotIndex = fileName.lastIndexOf("."); + if (dotIndex > 0) { + return fileName.substring(dotIndex + 1).toLowerCase(); + } else { + return ""; + } + } + +}