feat: support upload driver from local

This commit is contained in:
vran 2022-04-27 17:29:31 +08:00
parent 38ad685fb7
commit 15a9f4ae18
12 changed files with 206 additions and 32 deletions

View File

@ -12,6 +12,7 @@ import org.springframework.data.web.PageableDefault;
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.validation.Valid;
import java.util.List;
@ -80,4 +81,11 @@ public class DatabaseTypeController {
return JsonData.ok(driverClassName);
}
@PostMapping(Routes.DatabaseType.UPLOAD_DRIVER)
@PreAuthorize("hasAnyAuthority('SYS_OWNER')")
public JsonData<String> uploadDriver(@RequestPart MultipartFile file) {
String driverPath = databaseTypeService.uploadDriver(file);
return JsonData.ok(driverPath);
}
}

View File

@ -4,6 +4,9 @@ spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=${databasir.db.username}
spring.datasource.password=${databasir.db.password}
spring.datasource.url=jdbc:mysql://${databasir.db.url}/${databasir.db.name:databasir}
spring.servlet.multipart.max-file-size=100MB
spring.servlet.multipart.max-request-size=100MB
spring.web.resources.static-locations=
# jooq
spring.jooq.sql-dialect=mysql
# flyway

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,29 @@ 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 +74,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 +101,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.loadFromRemote(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 +155,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.loadFromRemote(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

@ -19,6 +19,7 @@ import java.net.URLClassLoader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.UUID;
import java.util.jar.JarFile;
@ -32,6 +33,21 @@ public class DriverResources {
private final RestTemplate restTemplate;
public DriverResult loadFromLocal(String localPath) {
File driverFile = Paths.get(localPath).toFile();
if (driverFile.exists()) {
return new DriverResult(localPath, driverFile);
} else {
throw DomainErrors.UPLOAD_DRIVER_FILE_ERROR.exception();
}
}
public DriverResult loadFromRemote(String remoteUrl) {
String targetFile = "temp/" + System.currentTimeMillis() + ".jar";
File file = download(remoteUrl, targetFile);
return new DriverResult(targetFile, file);
}
public DriverResult load(String driverFilePath, String driverFileUrl, String databaseType) {
if (driverFilePath == null) {
String targetFile = targetDriverFile(databaseType);
@ -105,7 +121,7 @@ public class DriverResources {
}
}
public String resolveDriverClassName(String driverFileUrl) {
public String resolveDriverClassNameFromRemote(String driverFileUrl) {
String tempFilePath = "temp/" + UUID.randomUUID() + ".jar";
File driverFile = download(driverFileUrl, tempFilePath);
String className = resolveDriverClassName(driverFile);
@ -117,6 +133,14 @@ public class DriverResources {
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,6 +201,19 @@ public class DriverResources {
}
}
public String copyToStandardDirectory(File sourceFile, String databaseType) {
String targetFile = targetDriverFile(databaseType);
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) {
return driverBaseDirectory
+ "/" + databaseType

View File

@ -38,6 +38,10 @@ class DatabaseTypeServiceTest extends BaseTest {
Mockito.doNothing().when(driverResources).validateDriverJar(any(), anyString());
Mockito.when(driverResources.load(any(), anyString(), anyString()))
.thenReturn(new DriverResult("", null));
Mockito.when(driverResources.loadFromRemote(any()))
.thenReturn(new DriverResult("", null));
Mockito.when(driverResources.loadFromLocal(any()))
.thenReturn(new DriverResult("", null));
}
@Test