support manual upload driver file (#131)

* feat: support upload driver from local

* feat: support get origin driver file name

* fix: checkstyle

* fix: compile error

* feat: update docs
This commit is contained in:
vran
2022-04-27 23:39:49 +08:00
committed by GitHub
parent 38ad685fb7
commit dc128fc84a
61 changed files with 342 additions and 170 deletions

View File

@@ -45,7 +45,11 @@ public enum DomainErrors implements DatabasirErrors {
INVALID_MOCK_DATA_SCRIPT("A_10029", "不合法的表达式"),
CANNOT_DELETE_SELF("A_10030", "无法对自己执行删除账号操作"),
DRIVER_CLASS_NOT_FOUND("A_10031", "获取驱动类名失败"),
DATABASE_DOCUMENT_DUPLICATE_KEY("A_10032", "文档版本重复");
DATABASE_DOCUMENT_DUPLICATE_KEY("A_10032", "文档版本重复"),
UPLOAD_DRIVER_FILE_ERROR("A_10033", "上传失败,请检查后重新上传"),
DRIVER_URL_AND_PATH_MUST_NOT_BE_ALL_BLANK("A_10034", "请填写下载驱动的地址或手动上传驱动文件"),
;
private final String errCode;

View File

@@ -14,10 +14,14 @@ public interface DatabaseTypePojoConverter {
@Mapping(target = "id", ignore = true)
@Mapping(target = "updateAt", ignore = true)
@Mapping(target = "createAt", ignore = true)
@Mapping(target = "jdbcDriverFilePath", source = "jdbcDriverFilePath")
DatabaseTypePojo of(DatabaseTypeCreateRequest request, String jdbcDriverFilePath);
@Mapping(target = "jdbcDriverFilePath", source = "jdbcDriverFilePath")
DatabaseTypePojo of(DatabaseTypeUpdateRequest request, String jdbcDriverFilePath);
DatabaseTypePojo of(DatabaseTypeUpdateRequest request);
DatabaseTypeDetailResponse toDetailResponse(DatabaseTypePojo data);
DatabaseTypePageResponse toPageResponse(DatabaseTypePojo pojo, Integer projectCount);

View File

@@ -15,9 +15,10 @@ public class DatabaseTypeCreateRequest {
@NotBlank
private String description;
@NotBlank
private String jdbcDriverFileUrl;
private String jdbcDriverFilePath;
@NotBlank
private String jdbcDriverClassName;

View File

@@ -19,9 +19,10 @@ public class DatabaseTypeUpdateRequest {
@NotBlank
private String description;
@NotBlank
private String jdbcDriverFileUrl;
private String jdbcDriverFilePath;
@NotBlank
private String jdbcDriverClassName;

View File

@@ -2,14 +2,12 @@ package com.databasir.core.domain.database.data;
import lombok.Data;
import javax.validation.constraints.NotBlank;
@Data
public class DriverClassNameResolveRequest {
private String databaseType;
@NotBlank
private String jdbcDriverFileUrl;
private String jdbcDriverFilePath;
}

View File

@@ -3,6 +3,7 @@ package com.databasir.core.domain.database.service;
import com.databasir.core.domain.DomainErrors;
import com.databasir.core.domain.database.converter.DatabaseTypePojoConverter;
import com.databasir.core.domain.database.data.*;
import com.databasir.core.domain.database.validator.DatabaseTypeUpdateValidator;
import com.databasir.core.infrastructure.connection.DatabaseTypes;
import com.databasir.core.infrastructure.driver.DriverResources;
import com.databasir.core.infrastructure.driver.DriverResult;
@@ -11,15 +12,20 @@ import com.databasir.dao.impl.ProjectDao;
import com.databasir.dao.tables.pojos.DatabaseTypePojo;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
@@ -36,14 +42,28 @@ public class DatabaseTypeService {
private final DatabaseTypePojoConverter databaseTypePojoConverter;
private final DatabaseTypeUpdateValidator databaseTypeUpdateValidator;
/**
* 1. validate: filePath, fileUrl
* 2. validate: databaseType
* 3. load from remote or local
* 4. validate driver class name
* 5. copy to standard directory
* 6. save to database
*/
public Integer create(DatabaseTypeCreateRequest request) {
if (databaseTypeDao.existsByDatabaseType(request.getDatabaseType())) {
databaseTypeUpdateValidator.validRequestRequiredParams(request);
String databaseType = request.getDatabaseType();
if (databaseTypeDao.existsByDatabaseType(databaseType)) {
throw DomainErrors.DATABASE_TYPE_NAME_DUPLICATE.exception();
}
DriverResult result =
driverResources.load(null, request.getJdbcDriverFileUrl(), request.getDatabaseType());
driverResources.validateDriverJar(result.getDriverFile(), request.getJdbcDriverClassName());
DatabaseTypePojo pojo = databaseTypePojoConverter.of(request, result.getDriverFilePath());
DriverResult result = loadAndValidate(request.getJdbcDriverFileUrl(),
request.getJdbcDriverFilePath(), request.getJdbcDriverClassName());
String targetPath = driverResources.copyToStandardDirectory(result.getDriverFile(), databaseType);
DatabaseTypePojo pojo = databaseTypePojoConverter.of(request, targetPath);
// TODO workaround
pojo.setJdbcDriverFileUrl(StringUtils.defaultIfBlank(request.getJdbcDriverFileUrl(), ""));
try {
return databaseTypeDao.insertAndReturnId(pojo);
} catch (DuplicateKeyException e) {
@@ -53,20 +73,23 @@ public class DatabaseTypeService {
@Transactional
public void update(DatabaseTypeUpdateRequest request) {
databaseTypeUpdateValidator.validRequestRequiredParams(request);
databaseTypeDao.selectOptionalById(request.getId()).ifPresent(data -> {
databaseTypeUpdateValidator.validDatabaseTypeIfNecessary(request, data);
String databaseType = request.getDatabaseType();
DatabaseTypePojo pojo;
if (!Objects.equals(request.getDatabaseType(), data.getDatabaseType())
|| !Objects.equals(request.getJdbcDriverFileUrl(), data.getJdbcDriverFileUrl())) {
if (databaseTypeUpdateValidator.shouldReloadDriver(request, data)) {
// 名称修改,下载地址修改需要删除原有的 driver 并重新下载验证
driverResources.deleteByDatabaseType(data.getDatabaseType());
// download
DriverResult result =
driverResources.load(null, request.getJdbcDriverFileUrl(), request.getDatabaseType());
driverResources.validateDriverJar(result.getDriverFile(), request.getJdbcDriverClassName());
// validate
pojo = databaseTypePojoConverter.of(request, result.getDriverFilePath());
DriverResult result = loadAndValidate(request.getJdbcDriverFileUrl(),
request.getJdbcDriverFilePath(), request.getJdbcDriverClassName());
String targetPath = driverResources.copyToStandardDirectory(result.getDriverFile(), databaseType);
pojo = databaseTypePojoConverter.of(request, targetPath);
pojo.setJdbcDriverFileUrl(StringUtils.defaultIfBlank(request.getJdbcDriverFileUrl(), ""));
} else {
pojo = databaseTypePojoConverter.of(request, data.getJdbcDriverFilePath());
pojo = databaseTypePojoConverter.of(request);
pojo.setJdbcDriverFileUrl(StringUtils.defaultIfBlank(request.getJdbcDriverFileUrl(), ""));
}
try {
@@ -77,6 +100,17 @@ public class DatabaseTypeService {
});
}
private DriverResult loadAndValidate(String remoteUrl, String localPath, String className) {
DriverResult result;
if (StringUtils.isNoneBlank(localPath)) {
result = driverResources.loadFromLocal(localPath);
} else {
result = driverResources.tempLoadFromRemote(remoteUrl);
}
driverResources.validateDriverJar(result.getDriverFile(), className);
return result;
}
public void deleteById(Integer id) {
databaseTypeDao.selectOptionalById(id).ifPresent(data -> {
if (DatabaseTypes.has(data.getDatabaseType())) {
@@ -120,7 +154,25 @@ public class DatabaseTypeService {
}
public String resolveDriverClassName(DriverClassNameResolveRequest request) {
return driverResources.resolveDriverClassName(request.getJdbcDriverFileUrl());
databaseTypeUpdateValidator.validRequestRequiredParams(request);
if (StringUtils.isNotBlank(request.getJdbcDriverFileUrl())) {
return driverResources.resolveDriverClassNameFromRemote(request.getJdbcDriverFileUrl());
} else {
return driverResources.resolveDriverClassNameFromLocal(request.getJdbcDriverFilePath());
}
}
public String uploadDriver(MultipartFile file) {
String parent = "temp";
String path = parent + "/" + System.currentTimeMillis() + "-" + file.getOriginalFilename();
try {
Files.createDirectories(Paths.get(parent));
Path targetPath = Paths.get(path);
Files.copy(file.getInputStream(), targetPath);
return path;
} catch (IOException e) {
log.error("upload driver file error", e);
throw DomainErrors.UPLOAD_DRIVER_FILE_ERROR.exception();
}
}
}

View File

@@ -0,0 +1,59 @@
package com.databasir.core.domain.database.validator;
import com.databasir.core.domain.DomainErrors;
import com.databasir.core.domain.database.data.DatabaseTypeCreateRequest;
import com.databasir.core.domain.database.data.DatabaseTypeUpdateRequest;
import com.databasir.core.domain.database.data.DriverClassNameResolveRequest;
import com.databasir.dao.impl.DatabaseTypeDao;
import com.databasir.dao.tables.pojos.DatabaseTypePojo;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import java.util.Objects;
@Component
@RequiredArgsConstructor
public class DatabaseTypeUpdateValidator {
private final DatabaseTypeDao databaseTypeDao;
public void validRequestRequiredParams(DatabaseTypeCreateRequest request) {
if (StringUtils.isAllBlank(request.getJdbcDriverFilePath(), request.getJdbcDriverFileUrl())) {
throw DomainErrors.DRIVER_URL_AND_PATH_MUST_NOT_BE_ALL_BLANK.exception();
}
}
public void validRequestRequiredParams(DatabaseTypeUpdateRequest request) {
if (StringUtils.isAllBlank(request.getJdbcDriverFilePath(), request.getJdbcDriverFileUrl())) {
throw DomainErrors.DRIVER_URL_AND_PATH_MUST_NOT_BE_ALL_BLANK.exception();
}
}
public void validRequestRequiredParams(DriverClassNameResolveRequest request) {
if (StringUtils.isAllBlank(request.getJdbcDriverFilePath(), request.getJdbcDriverFileUrl())) {
throw DomainErrors.DRIVER_URL_AND_PATH_MUST_NOT_BE_ALL_BLANK.exception();
}
}
public void validDatabaseTypeIfNecessary(DatabaseTypeUpdateRequest request, DatabaseTypePojo origin) {
if (!Objects.equals(request.getDatabaseType(), origin.getDatabaseType())) {
if (databaseTypeDao.existsByDatabaseType(request.getDatabaseType())) {
throw DomainErrors.DATABASE_TYPE_NAME_DUPLICATE.exception();
}
}
}
public boolean shouldReloadDriver(DatabaseTypeUpdateRequest request, DatabaseTypePojo origin) {
if (!Objects.equals(request.getDatabaseType(), origin.getDatabaseType())) {
return true;
}
if (!Objects.equals(request.getJdbcDriverFileUrl(), origin.getJdbcDriverFileUrl())) {
return true;
}
if (!Objects.equals(request.getJdbcDriverFilePath(), origin.getJdbcDriverFilePath())) {
return true;
}
return false;
}
}

View File

@@ -1,8 +1,8 @@
package com.databasir.core.infrastructure.connection;
import com.alibaba.excel.util.StringUtils;
import com.databasir.core.domain.DomainErrors;
import com.databasir.core.infrastructure.driver.DriverResources;
import com.databasir.core.infrastructure.driver.DriverResult;
import com.databasir.dao.impl.DatabaseTypeDao;
import com.databasir.dao.tables.pojos.DatabaseTypePojo;
import lombok.RequiredArgsConstructor;
@@ -15,10 +15,10 @@ import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Paths;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.SQLException;
import java.util.Objects;
import java.util.Properties;
@Component
@@ -83,14 +83,16 @@ public class CustomDatabaseConnectionFactory implements DatabaseConnectionFactor
}
private File loadDriver(DatabaseTypePojo type) {
String databaseType = type.getDatabaseType();
String file = type.getJdbcDriverFilePath();
String url = type.getJdbcDriverFileUrl();
DriverResult result = driverResources.load(file, url, databaseType);
File driverFile = result.getDriverFile();
if (!Objects.equals(result.getDriverFilePath(), type.getJdbcDriverFilePath())) {
databaseTypeDao.updateDriverFile(type.getId(), result.getDriverFilePath());
if (StringUtils.isNotBlank(type.getJdbcDriverFilePath())) {
return driverResources.loadFromLocal(type.getJdbcDriverFilePath()).getDriverFile();
}
return driverFile;
if (StringUtils.isNotBlank(type.getJdbcDriverFileUrl())) {
File remoteFile = driverResources.tempLoadFromRemote(type.getJdbcDriverFileUrl()).getDriverFile();
driverResources.validateDriverJar(remoteFile, type.getJdbcDriverClassName());
String targetFile = driverResources.copyToStandardDirectory(remoteFile, type.getDatabaseType());
return Paths.get(targetFile).toFile();
}
String databaseType = type.getDatabaseType();
throw DomainErrors.DOWNLOAD_DRIVER_ERROR.exception("驱动加载失败, database=" + databaseType);
}
}

View File

@@ -3,8 +3,10 @@ package com.databasir.core.infrastructure.driver;
import com.databasir.core.domain.DomainErrors;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.ClassUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
@@ -19,7 +21,7 @@ import java.net.URLClassLoader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.UUID;
import java.nio.file.StandardCopyOption;
import java.util.jar.JarFile;
@Component
@@ -32,48 +34,62 @@ public class DriverResources {
private final RestTemplate restTemplate;
public DriverResult load(String driverFilePath, String driverFileUrl, String databaseType) {
if (driverFilePath == null) {
String targetFile = targetDriverFile(databaseType);
File file = download(driverFileUrl, targetFile);
return new DriverResult(targetFile, file);
}
File driverFile = Paths.get(driverFilePath).toFile();
public DriverResult loadFromLocal(String localPath) {
File driverFile = Paths.get(localPath).toFile();
if (driverFile.exists()) {
return new DriverResult(driverFilePath, driverFile);
return new DriverResult(localPath, driverFile);
} else {
String targetFile = targetDriverFile(databaseType);
File file = download(driverFileUrl, targetFile);
return new DriverResult(targetFile, file);
throw DomainErrors.UPLOAD_DRIVER_FILE_ERROR.exception();
}
}
private File download(String driverFileUrl, String targetFile) {
Path path = Path.of(targetFile);
public DriverResult tempLoadFromRemote(String remoteUrl) {
Path dirPath;
try {
dirPath = Files.createTempDirectory("databasir-drivers");
} catch (IOException e) {
log.error("load driver error cause create temp dir failed", e);
throw DomainErrors.DOWNLOAD_DRIVER_ERROR.exception();
}
File file = download(remoteUrl, dirPath.toString());
return new DriverResult(file.getAbsolutePath(), file);
}
// create parent directory
if (Files.notExists(path)) {
path.getParent().toFile().mkdirs();
try {
Files.createFile(path);
} catch (IOException e) {
log.error("create file error " + targetFile, e);
throw DomainErrors.DOWNLOAD_DRIVER_ERROR.exception(e.getMessage());
}
private File download(String driverFileUrl, String parentDir) {
Path parentDirPath = Paths.get(parentDir);
try {
Files.createDirectories(parentDirPath);
} catch (IOException e) {
log.error("下载驱动时创建目录失败", e);
throw DomainErrors.DOWNLOAD_DRIVER_ERROR.exception(e);
}
// download
try {
return restTemplate.execute(driverFileUrl, HttpMethod.GET, null, response -> {
if (response.getStatusCode().is2xxSuccessful()) {
File file = path.toFile();
FileOutputStream out = new FileOutputStream(file);
String prefix = System.currentTimeMillis() + "";
String originFileName = response.getHeaders().getContentDisposition().getFilename();
String filename;
if (originFileName == null) {
URL url = new URL(driverFileUrl);
String nameFromUrl = FilenameUtils.getName(url.getPath());
if (StringUtils.endsWith(nameFromUrl, ".jar")) {
filename = prefix + "-" + nameFromUrl;
} else {
filename = prefix + ".jar";
}
} else {
filename = prefix + "-" + originFileName;
}
File targetFile = Paths.get(parentDir, filename).toFile();
FileOutputStream out = new FileOutputStream(targetFile);
StreamUtils.copy(response.getBody(), out);
IOUtils.closeQuietly(out, ex -> log.error("close file error", ex));
log.info("{} download success ", targetFile);
return file;
return targetFile;
} else {
log.error("{} download error from {}: {} ", targetFile, driverFileUrl, response);
log.error("{} download error from {}: {} ", parentDir, driverFileUrl, response);
throw DomainErrors.DOWNLOAD_DRIVER_ERROR.exception("驱动下载失败:"
+ response.getStatusCode()
+ ", "
@@ -81,7 +97,7 @@ public class DriverResources {
}
});
} catch (RestClientException e) {
log.error(targetFile + " download driver error", e);
log.error(parentDir + " download driver error", e);
throw DomainErrors.DOWNLOAD_DRIVER_ERROR.exception(e.getMessage());
}
}
@@ -105,18 +121,26 @@ public class DriverResources {
}
}
public String resolveDriverClassName(String driverFileUrl) {
String tempFilePath = "temp/" + UUID.randomUUID() + ".jar";
File driverFile = download(driverFileUrl, tempFilePath);
public String resolveDriverClassNameFromRemote(String driverFileUrl) {
DriverResult driverResult = tempLoadFromRemote(driverFileUrl);
File driverFile = driverResult.getDriverFile();
String className = resolveDriverClassName(driverFile);
try {
Files.deleteIfExists(driverFile.toPath());
} catch (IOException e) {
log.error("delete driver error " + tempFilePath, e);
log.error("delete driver error from " + driverResult.getDriverFilePath(), e);
}
return className;
}
public String resolveDriverClassNameFromLocal(String driverFilePath) {
File driverFile = Paths.get(driverFilePath).toFile();
if (!driverFile.exists()) {
throw DomainErrors.DRIVER_CLASS_NOT_FOUND.exception("驱动文件不存在,请重新上传");
}
return resolveDriverClassName(driverFile);
}
public String resolveDriverClassName(File driverFile) {
JarFile jarFile = null;
try {
@@ -177,9 +201,22 @@ public class DriverResources {
}
}
private String targetDriverFile(String databaseType) {
public String copyToStandardDirectory(File sourceFile, String databaseType) {
String targetFile = targetDriverFile(databaseType, sourceFile.getName());
try {
Path target = Paths.get(targetFile);
Files.createDirectories(target.getParent());
Files.copy(sourceFile.toPath(), target, StandardCopyOption.REPLACE_EXISTING);
return targetFile;
} catch (IOException e) {
log.error("copy driver file error", e);
throw DomainErrors.DOWNLOAD_DRIVER_ERROR.exception(e.getMessage());
}
}
private String targetDriverFile(String databaseType, String fileName) {
return driverBaseDirectory
+ "/" + databaseType
+ "/" + System.currentTimeMillis() + ".jar";
+ "/" + fileName;
}
}

View File

@@ -36,7 +36,9 @@ class DatabaseTypeServiceTest extends BaseTest {
@BeforeEach
public void setUp() {
Mockito.doNothing().when(driverResources).validateDriverJar(any(), anyString());
Mockito.when(driverResources.load(any(), anyString(), anyString()))
Mockito.when(driverResources.tempLoadFromRemote(any()))
.thenReturn(new DriverResult("", null));
Mockito.when(driverResources.loadFromLocal(any()))
.thenReturn(new DriverResult("", null));
}