diff --git a/api/src/main/java/com/databasir/api/DatabaseTypeController.java b/api/src/main/java/com/databasir/api/DatabaseTypeController.java index a68b5b4..6ceb11e 100644 --- a/api/src/main/java/com/databasir/api/DatabaseTypeController.java +++ b/api/src/main/java/com/databasir/api/DatabaseTypeController.java @@ -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 uploadDriver(@RequestPart MultipartFile file) { + String driverPath = databaseTypeService.uploadDriver(file); + return JsonData.ok(driverPath); + } + } diff --git a/api/src/main/resources/application.properties b/api/src/main/resources/application.properties index 10e19ec..0b98ef2 100644 --- a/api/src/main/resources/application.properties +++ b/api/src/main/resources/application.properties @@ -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 diff --git a/core/src/main/java/com/databasir/core/domain/DomainErrors.java b/core/src/main/java/com/databasir/core/domain/DomainErrors.java index c488dab..9e7ae45 100644 --- a/core/src/main/java/com/databasir/core/domain/DomainErrors.java +++ b/core/src/main/java/com/databasir/core/domain/DomainErrors.java @@ -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; diff --git a/core/src/main/java/com/databasir/core/domain/database/converter/DatabaseTypePojoConverter.java b/core/src/main/java/com/databasir/core/domain/database/converter/DatabaseTypePojoConverter.java index 524a9c7..3621ee8 100644 --- a/core/src/main/java/com/databasir/core/domain/database/converter/DatabaseTypePojoConverter.java +++ b/core/src/main/java/com/databasir/core/domain/database/converter/DatabaseTypePojoConverter.java @@ -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); diff --git a/core/src/main/java/com/databasir/core/domain/database/data/DatabaseTypeCreateRequest.java b/core/src/main/java/com/databasir/core/domain/database/data/DatabaseTypeCreateRequest.java index e6c449f..e7e2ed3 100644 --- a/core/src/main/java/com/databasir/core/domain/database/data/DatabaseTypeCreateRequest.java +++ b/core/src/main/java/com/databasir/core/domain/database/data/DatabaseTypeCreateRequest.java @@ -15,9 +15,10 @@ public class DatabaseTypeCreateRequest { @NotBlank private String description; - @NotBlank private String jdbcDriverFileUrl; + private String jdbcDriverFilePath; + @NotBlank private String jdbcDriverClassName; diff --git a/core/src/main/java/com/databasir/core/domain/database/data/DatabaseTypeUpdateRequest.java b/core/src/main/java/com/databasir/core/domain/database/data/DatabaseTypeUpdateRequest.java index f6b0711..689d0c9 100644 --- a/core/src/main/java/com/databasir/core/domain/database/data/DatabaseTypeUpdateRequest.java +++ b/core/src/main/java/com/databasir/core/domain/database/data/DatabaseTypeUpdateRequest.java @@ -19,9 +19,10 @@ public class DatabaseTypeUpdateRequest { @NotBlank private String description; - @NotBlank private String jdbcDriverFileUrl; + private String jdbcDriverFilePath; + @NotBlank private String jdbcDriverClassName; diff --git a/core/src/main/java/com/databasir/core/domain/database/data/DriverClassNameResolveRequest.java b/core/src/main/java/com/databasir/core/domain/database/data/DriverClassNameResolveRequest.java index 08f24b4..5db2d4f 100644 --- a/core/src/main/java/com/databasir/core/domain/database/data/DriverClassNameResolveRequest.java +++ b/core/src/main/java/com/databasir/core/domain/database/data/DriverClassNameResolveRequest.java @@ -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; } diff --git a/core/src/main/java/com/databasir/core/domain/database/service/DatabaseTypeService.java b/core/src/main/java/com/databasir/core/domain/database/service/DatabaseTypeService.java index 9a72474..258452d 100644 --- a/core/src/main/java/com/databasir/core/domain/database/service/DatabaseTypeService.java +++ b/core/src/main/java/com/databasir/core/domain/database/service/DatabaseTypeService.java @@ -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(); + } + } } diff --git a/core/src/main/java/com/databasir/core/domain/database/validator/DatabaseTypeUpdateValidator.java b/core/src/main/java/com/databasir/core/domain/database/validator/DatabaseTypeUpdateValidator.java new file mode 100644 index 0000000..9ba5d39 --- /dev/null +++ b/core/src/main/java/com/databasir/core/domain/database/validator/DatabaseTypeUpdateValidator.java @@ -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; + } +} diff --git a/core/src/main/java/com/databasir/core/infrastructure/connection/CustomDatabaseConnectionFactory.java b/core/src/main/java/com/databasir/core/infrastructure/connection/CustomDatabaseConnectionFactory.java index 48f1859..6facee7 100644 --- a/core/src/main/java/com/databasir/core/infrastructure/connection/CustomDatabaseConnectionFactory.java +++ b/core/src/main/java/com/databasir/core/infrastructure/connection/CustomDatabaseConnectionFactory.java @@ -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); } } diff --git a/core/src/main/java/com/databasir/core/infrastructure/driver/DriverResources.java b/core/src/main/java/com/databasir/core/infrastructure/driver/DriverResources.java index c8ec447..149687c 100644 --- a/core/src/main/java/com/databasir/core/infrastructure/driver/DriverResources.java +++ b/core/src/main/java/com/databasir/core/infrastructure/driver/DriverResources.java @@ -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 diff --git a/core/src/test/java/com/databasir/core/domain/database/service/DatabaseTypeServiceTest.java b/core/src/test/java/com/databasir/core/domain/database/service/DatabaseTypeServiceTest.java index 3124482..cb69ab2 100644 --- a/core/src/test/java/com/databasir/core/domain/database/service/DatabaseTypeServiceTest.java +++ b/core/src/test/java/com/databasir/core/domain/database/service/DatabaseTypeServiceTest.java @@ -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