dbeaver导入实现

This commit is contained in:
lzy
2023-09-26 11:38:58 +08:00
parent 9bf8568dfc
commit 91380ae783
5 changed files with 209 additions and 37 deletions

View File

@ -0,0 +1,29 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2023 DBeaver Corp and others
*
* 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 ai.chat2db.server.web.api.controller.ncx.dbeaver;
/**
* Value encryptor
*/
public interface DBSValueEncryptor {
byte[] encryptValue(byte[] value);
byte[] decryptValue(byte[] value);
}

View File

@ -0,0 +1,105 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2023 DBeaver Corp and others
*
* 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 ai.chat2db.server.web.api.controller.ncx.dbeaver;
import org.apache.poi.util.IOUtils;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
/**
* Default value encryptor.
*
* Uses Eclipse secure preferences to read/write secrets.
*/
public class DefaultValueEncryptor implements DBSValueEncryptor {
public static final String CIPHER_NAME = "AES/CBC/PKCS5Padding";
public static final String KEY_ALGORITHM = "AES";
private static final byte[] LOCAL_KEY_CACHE = new byte[] { -70, -69, 74, -97, 119, 74, -72, 83, -55, 108, 45, 101, 61, -2, 84, 74 };
private final SecretKey secretKey;
private final Cipher cipher;
public DefaultValueEncryptor(SecretKey secretKey) {
this.secretKey = secretKey;
try {
this.cipher = Cipher.getInstance(CIPHER_NAME);
} catch (Exception e) {
throw new IllegalStateException("Internal error during encrypted init", e);
}
}
/**
* 通过 DBeaver 源码查看到默认的 SecretKey
**/
public static SecretKey getLocalSecretKey() {
return new SecretKeySpec(LOCAL_KEY_CACHE, DefaultValueEncryptor.KEY_ALGORITHM);
}
public static SecretKey makeSecretKeyFromPassword(String password) {
byte[] bytes = password.getBytes(StandardCharsets.UTF_8);
byte[] passBytes = Arrays.copyOf(bytes, 16);
return new SecretKeySpec(passBytes, KEY_ALGORITHM);
}
@Override
public byte[] encryptValue(byte[] value) {
try {
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
byte[] iv = cipher.getIV();
ByteArrayOutputStream resultBuffer = new ByteArrayOutputStream();
try (CipherOutputStream cipherOut = new CipherOutputStream(resultBuffer, cipher)) {
resultBuffer.write(iv);
cipherOut.write(value);
}
return resultBuffer.toByteArray();
} catch (Exception e) {
throw new RuntimeException("Error encrypting value", e);
}
}
@Override
public byte[] decryptValue(byte[] value) {
try (InputStream byteStream = new ByteArrayInputStream(value)) {
byte[] fileIv = new byte[16];
byteStream.read(fileIv);
cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(fileIv));
try (CipherInputStream cipherIn = new CipherInputStream(byteStream, cipher)) {
ByteArrayOutputStream resultBuffer = new ByteArrayOutputStream();
IOUtils.copy(cipherIn, resultBuffer);
return resultBuffer.toByteArray();
}
} catch (Exception e) {
throw new RuntimeException("Error decrypting value", e);
}
}
}

View File

@ -37,31 +37,31 @@ public enum DataBaseType {
/**
* Mariadb
**/
Mariadb("jdbc:mariadb://%s:%s"),
MARIADB("jdbc:mariadb://%s:%s"),
/**
* DM
**/
DM("jdbc:dm://%s:%s"),
/**
* KINGBASE8
* KINGBASE
**/
KINGBASE8("jdbc:kingbase8://%s:%s"),
KINGBASE("jdbc:kingbase8://%s:%s"),
/**
* Presto
**/
Presto("jdbc:presto://%s:%s"),
PRESTO("jdbc:presto://%s:%s"),
/**
* OceanBase
**/
OceanBase("jdbc:oceanbase://%s:%s"),
OCEANBASE("jdbc:oceanbase://%s:%s"),
/**
* Hive
**/
Hive("jdbc:hive2://%s:%s"),
HIVE("jdbc:hive2://%s:%s"),
/**
* ClickHouse
**/
ClickHouse("jdbc:clickhouse://%s:%s");
CLICKHOUSE("jdbc:clickhouse://%s:%s");
private String urlString;
@ -76,7 +76,8 @@ public enum DataBaseType {
public static DataBaseType matchType(String value) {
if (StringUtils.isNotEmpty(value)) {
for (DataBaseType dataBase : DataBaseType.values()) {
if (dataBase.name().equals(value.toUpperCase())) {
//kingbase -> kingbase8
if (value.toUpperCase().contains(dataBase.name())) {
return dataBase;
}
}

View File

@ -17,10 +17,6 @@
package ai.chat2db.server.web.api.controller.ncx.enums;
import ai.chat2db.server.tools.common.util.ConfigUtils;
import java.io.File;
/**
* Import/Export constants
*/
@ -29,8 +25,12 @@ public class ExportConstants {
public static final String ARCHIVE_FILE_EXT = ".dbp"; //NON-NLS-1
public static final String CONFIG_FILE = ".dbeaver"; //NON-NLS-1
public static final String CONFIG_DATASOURCE_FILE = "data-sources.json"; //NON-NLS-1
public static final String CONFIG_CREDENTIALS_FILE = "credentials-config.json"; //NON-NLS-1
public static final String DIR_PROJECTS = "projects"; //NON-NLS-1
public static final String DIR_DRIVERS = "drivers"; //NON-NLS-1
public static final String DIR_CONNECTIONS = "connections"; //NON-NLS-1
public static final String DIR_CONFIGURATION = "configuration"; //NON-NLS-1
public static final String GENERIC = "generic"; //NON-NLS-1
public static final String META_FILENAME = "meta.xml"; //NON-NLS-1

View File

@ -5,6 +5,7 @@ import ai.chat2db.server.domain.repository.entity.DataSourceDO;
import ai.chat2db.server.domain.repository.mapper.DataSourceMapper;
import ai.chat2db.server.tools.common.util.ConfigUtils;
import ai.chat2db.server.web.api.controller.ncx.cipher.CommonCipher;
import ai.chat2db.server.web.api.controller.ncx.dbeaver.DefaultValueEncryptor;
import ai.chat2db.server.web.api.controller.ncx.enums.DataBaseType;
import ai.chat2db.server.web.api.controller.ncx.enums.ExportConstants;
import ai.chat2db.server.web.api.controller.ncx.enums.VersionEnum;
@ -16,14 +17,16 @@ import ai.chat2db.spi.model.SSHInfo;
import cn.hutool.core.io.FileUtil;
import com.alibaba.excel.util.FileUtils;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.google.common.io.Files;
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 org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
@ -31,15 +34,15 @@ import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -68,6 +71,10 @@ public class ConverterServiceImpl implements ConverterService {
* xml连接信息开始标志位
**/
private static final String BEGIN = "#BEGIN#";
/**
* 密码json的key
**/
private static final String connection = "#connection";
@Autowired
private DataSourceMapper dataSourceMapper;
@ -82,7 +89,6 @@ public class ConverterServiceImpl implements ConverterService {
@Override
public UploadVO uploadFile(File file) {
UploadVO vo = new UploadVO();
try {
// List<Map <连接名Map<属性名,值>>> 要导入的连接
@ -169,30 +175,64 @@ public class ConverterServiceImpl implements ConverterService {
//配置的json文件
File json = new File(config + File.separator + ExportConstants.CONFIG_DATASOURCE_FILE);
JSONObject jsonObject = JSON.parseObject(new FileInputStream(json));
JSONObject connections = jsonObject.getJSONObject("connections");
JSONObject connections = jsonObject.getJSONObject(ExportConstants.DIR_CONNECTIONS);
Set<String> keys = connections.keySet();
for (String key : keys) {
JSONObject configurations = connections.getJSONObject(key);
JSONObject configuration = configurations.getJSONObject("configuration");
DataSourceDO dataSourceDO = new DataSourceDO();
LocalDateTime dateTime = LocalDateTime.now();
dataSourceDO.setGmtCreate(dateTime);
dataSourceDO.setGmtModified(dateTime);
dataSourceDO.setAlias(configurations.getString("name"));
dataSourceDO.setHost(configuration.getString("host"));
dataSourceDO.setPort(configuration.getString("port"));
dataSourceDO.setUrl(configuration.getString("url"));
//dataSourceDO.setUserName(configuration.getString("host"));
//dataSourceDO.setDriver(configuration.getString("host"));
dataSourceDO.setType(configurations.getString("provider").toUpperCase());
dataSourceMapper.insert(dataSourceDO);
JSONObject configuration = configurations.getJSONObject(ExportConstants.DIR_CONFIGURATION);
//匹配数据库类型
String provider = configurations.getString("provider");
if (provider.equals(ExportConstants.GENERIC)) {
//自定义驱动
JSONObject drivers = jsonObject.getJSONObject(ExportConstants.DIR_DRIVERS);
//获得驱动id
String driverId = configurations.getString("driver");
//获得所有generic
JSONObject generics = drivers.getJSONObject(provider);
//获得自己的驱动
JSONObject generic = generics.getJSONObject(driverId);
//如果不存在,则不导入
if (null == generic) {
continue;
}
//赋值驱动名称,用来确定数据库的类型
provider = generic.getString("name");
}
DataBaseType dataBaseType = DataBaseType.matchType(provider.toUpperCase());
DataSourceDO dataSourceDO;
//未匹配到数据库类型dbeaver支持自定义驱动等但chat2DB暂不支持
if (null != dataBaseType) {
//密码信息
File credentials = new File(config + File.separator + ExportConstants.CONFIG_CREDENTIALS_FILE);
DefaultValueEncryptor defaultValueEncryptor = new DefaultValueEncryptor(DefaultValueEncryptor.getLocalSecretKey());
JSONObject credentialsJson = JSON.parseObject(defaultValueEncryptor.decryptValue(Files.readAllBytes(credentials.toPath())));
dataSourceDO = new DataSourceDO();
LocalDateTime dateTime = LocalDateTime.now();
dataSourceDO.setGmtCreate(dateTime);
dataSourceDO.setGmtModified(dateTime);
dataSourceDO.setAlias(configurations.getString("name"));
dataSourceDO.setHost(configuration.getString("host"));
dataSourceDO.setPort(configuration.getString("port"));
dataSourceDO.setUrl(configuration.getString("url"));
if (null != credentialsJson) {
JSONObject userInfo = credentialsJson.getJSONObject(key);
JSONObject userPassword = userInfo.getJSONObject(connection);
dataSourceDO.setUserName(userPassword.getString("user"));
DesUtil desUtil = new DesUtil(DesUtil.DES_KEY);
String password = userPassword.getString("password");
String encryptStr = desUtil.encrypt(Optional.ofNullable(password).orElse(""), "CBC");
dataSourceDO.setPassword(encryptStr);
}
dataSourceDO.setType(dataBaseType.name());
dataSourceMapper.insert(dataSourceDO);
}
}
}
}
}
//删除临时文件
FileUtils.delete(file);
//删除dbeaver存留的配置临时文件
//删除导入dbeaverdbp产生的临时配置文件
//projects.forEach(v -> FileUtils.delete(new File(ConfigUtils.CONFIG_BASE_PATH + File.separator + v)));
return vo;
}
@ -212,13 +252,10 @@ public class ConverterServiceImpl implements ConverterService {
if (!folder.exists()) {
FileUtil.mkdir(folder);
}
resource = folder;
importDbeaverConfig(folder, childElement, entryPath + "/", zipFile);
} else {
File file = new File(resource.getPath() + File.separator + childName);
if (!file.exists()) {
FileUtil.writeFromStream(zipFile.getInputStream(resourceEntry), file);
}
FileUtil.writeFromStream(zipFile.getInputStream(resourceEntry), file, true);
}
}
}