diff --git a/.gitignore b/.gitignore index 61a9a78..fa06651 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .gradle/** **/build/** .idea/** -**/.DS_Store \ No newline at end of file +**/.DS_Store +drivers/** \ No newline at end of file diff --git a/api/src/main/java/com/databasir/api/DatabaseTypeController.java b/api/src/main/java/com/databasir/api/DatabaseTypeController.java new file mode 100644 index 0000000..0cb6ce0 --- /dev/null +++ b/api/src/main/java/com/databasir/api/DatabaseTypeController.java @@ -0,0 +1,67 @@ +package com.databasir.api; + +import com.databasir.common.JsonData; +import com.databasir.core.domain.database.data.*; +import com.databasir.core.domain.database.service.DatabaseTypeService; +import com.databasir.core.domain.log.annotation.Operation; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.web.PageableDefault; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.validation.Valid; +import java.util.List; +import java.util.Optional; + +import static org.springframework.data.domain.Sort.Direction.DESC; + +@RequiredArgsConstructor +@Validated +@RestController +public class DatabaseTypeController { + + private final DatabaseTypeService databaseTypeService; + + @GetMapping(Routes.DatabaseType.LIST_SIMPLE) + public JsonData> listSimpleDatabaseTypes() { + List types = databaseTypeService.listSimpleDatabaseTypes(); + return JsonData.ok(types); + } + + @GetMapping(Routes.DatabaseType.LIST_PAGE) + public JsonData> listPage(@PageableDefault(sort = "id", direction = DESC) + Pageable page, + DatabaseTypePageCondition condition) { + Page data = databaseTypeService.findByPage(page, condition); + return JsonData.ok(data); + } + + @PostMapping(Routes.DatabaseType.CREATE) + @Operation(module = Operation.Modules.DATABASE_TYPE, name = "创建数据库类型") + public JsonData create(@RequestBody @Valid DatabaseTypeCreateRequest request) { + Integer id = databaseTypeService.create(request); + return JsonData.ok(id); + } + + @PatchMapping(Routes.DatabaseType.UPDATE) + @Operation(module = Operation.Modules.DATABASE_TYPE, name = "更新数据库类型") + public JsonData update(@RequestBody @Valid DatabaseTypeUpdateRequest request) { + databaseTypeService.update(request); + return JsonData.ok(); + } + + @DeleteMapping(Routes.DatabaseType.DELETE_ONE) + @Operation(module = Operation.Modules.DATABASE_TYPE, name = "删除数据库类型") + public JsonData delete(@PathVariable Integer id) { + databaseTypeService.deleteById(id); + return JsonData.ok(); + } + + @GetMapping(Routes.DatabaseType.GET_ONE) + public JsonData getOne(@PathVariable Integer id) { + Optional data = databaseTypeService.selectOne(id); + return JsonData.ok(data); + } +} diff --git a/api/src/main/java/com/databasir/api/Routes.java b/api/src/main/java/com/databasir/api/Routes.java index 136699d..9ecc184 100644 --- a/api/src/main/java/com/databasir/api/Routes.java +++ b/api/src/main/java/com/databasir/api/Routes.java @@ -130,4 +130,19 @@ public interface Routes { String GET_ONE = BASE + "/oauth2_apps/{id}"; } -} + + interface DatabaseType { + + String LIST_SIMPLE = BASE + "/simple_database_types"; + + String LIST_PAGE = BASE + "/database_types"; + + String GET_ONE = BASE + "/database_types/{id}"; + + String DELETE_ONE = BASE + "/database_types/{id}"; + + String UPDATE = BASE + "/database_types"; + + String CREATE = BASE + "/database_types"; + } +} \ No newline at end of file diff --git a/api/src/main/resources/application-local.properties b/api/src/main/resources/application-local.properties index a2ddb89..f9e5367 100644 --- a/api/src/main/resources/application-local.properties +++ b/api/src/main/resources/application-local.properties @@ -1,10 +1,11 @@ -server.port=8080 logging.level.org.jooq=INFO -spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver -spring.datasource.username=root -spring.datasource.password=123456 -spring.datasource.url=jdbc:mysql://localhost:3306/databasir spring.jooq.sql-dialect=mysql +# flyway spring.flyway.enabled=true spring.flyway.baseline-on-migrate=true spring.flyway.locations=classpath:db/migration +# db +databasir.db.url=localhost:3306 +databasir.db.username=root +databasir.db.password=123456 +databasir.db.driver-directory=drivers \ No newline at end of file diff --git a/core/src/main/java/com/databasir/core/config/RestTemplateConfig.java b/core/src/main/java/com/databasir/core/config/RestTemplateConfig.java new file mode 100644 index 0000000..2f83f2c --- /dev/null +++ b/core/src/main/java/com/databasir/core/config/RestTemplateConfig.java @@ -0,0 +1,32 @@ +package com.databasir.core.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.client.ClientHttpRequestFactory; +import org.springframework.http.client.SimpleClientHttpRequestFactory; +import org.springframework.http.converter.StringHttpMessageConverter; +import org.springframework.web.client.RestTemplate; + +import java.nio.charset.StandardCharsets; + +@Configuration +public class RestTemplateConfig { + + @Bean + public RestTemplate restTemplate(ClientHttpRequestFactory factory) { + RestTemplate restTemplate = new RestTemplate(factory); + // 支持中文编码 + restTemplate.getMessageConverters().set(1, + new StringHttpMessageConverter(StandardCharsets.UTF_8)); + return restTemplate; + + } + + @Bean + public ClientHttpRequestFactory simpleClientHttpRequestFactory() { + SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory(); + factory.setReadTimeout(1000 * 30);//单位为ms + factory.setConnectTimeout(1000 * 5);//单位为ms + return factory; + } +} 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 4141a3a..5aee792 100644 --- a/core/src/main/java/com/databasir/core/domain/DomainErrors.java +++ b/core/src/main/java/com/databasir/core/domain/DomainErrors.java @@ -27,7 +27,11 @@ public enum DomainErrors implements DatabasirErrors { INVALID_CRON_EXPRESSION("A_10012", "不合法的 cron 表达式"), REGISTRATION_ID_DUPLICATE("A_10013", "应用注册 ID 不能重复"), REGISTRATION_ID_NOT_FOUND("A_10014", "应用 ID 不存在"), - MISS_REQUIRED_PARAMETERS("A_10015", "缺少必填参数"); + MISS_REQUIRED_PARAMETERS("A_10015", "缺少必填参数"), + DATABASE_TYPE_NAME_DUPLICATE("A_10016", "数据库类型名已存在"), + MUST_NOT_MODIFY_SYSTEM_DEFAULT_DATABASE_TYPE("A_10017", "禁止修改系统默认数据库类型"), + DOWNLOAD_DRIVER_ERROR("A_10018", "驱动下载失败"), + ; private final String errCode; @@ -46,6 +50,6 @@ public enum DomainErrors implements DatabasirErrors { } public DatabasirException exception(String s) { - return exception(s, (Throwable) null); + return exception(s, null); } } 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 new file mode 100644 index 0000000..6e94b64 --- /dev/null +++ b/core/src/main/java/com/databasir/core/domain/database/converter/DatabaseTypePojoConverter.java @@ -0,0 +1,20 @@ +package com.databasir.core.domain.database.converter; + +import com.databasir.core.domain.database.data.DatabaseTypeCreateRequest; +import com.databasir.core.domain.database.data.DatabaseTypeDetailResponse; +import com.databasir.core.domain.database.data.DatabaseTypePageResponse; +import com.databasir.core.domain.database.data.DatabaseTypeUpdateRequest; +import com.databasir.dao.tables.pojos.DatabaseTypePojo; +import org.mapstruct.Mapper; + +@Mapper(componentModel = "spring") +public interface DatabaseTypePojoConverter { + + DatabaseTypePojo of(DatabaseTypeCreateRequest request); + + 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 new file mode 100644 index 0000000..0d4cd64 --- /dev/null +++ b/core/src/main/java/com/databasir/core/domain/database/data/DatabaseTypeCreateRequest.java @@ -0,0 +1,26 @@ +package com.databasir.core.domain.database.data; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; + +@Data +public class DatabaseTypeCreateRequest { + + @NotBlank + private String databaseType; + + private String icon; + + @NotBlank + private String description; + + @NotBlank + private String jdbcDriverFileUrl; + + @NotBlank + private String jdbcDriverClassName; + + @NotBlank + private String jdbcProtocol; +} diff --git a/core/src/main/java/com/databasir/core/domain/database/data/DatabaseTypeDetailResponse.java b/core/src/main/java/com/databasir/core/domain/database/data/DatabaseTypeDetailResponse.java new file mode 100644 index 0000000..5361615 --- /dev/null +++ b/core/src/main/java/com/databasir/core/domain/database/data/DatabaseTypeDetailResponse.java @@ -0,0 +1,31 @@ +package com.databasir.core.domain.database.data; + +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +public class DatabaseTypeDetailResponse { + + private Integer id; + + private String databaseType; + + private String icon; + + private String description; + + private String jdbcDriverFileUrl; + + private String jdbcDriverClassName; + + private String jdbcProtocol; + + private Boolean deleted; + + private Integer deletedToken; + + private LocalDateTime updateAt; + + private LocalDateTime createAt; +} diff --git a/core/src/main/java/com/databasir/core/domain/database/data/DatabaseTypePageCondition.java b/core/src/main/java/com/databasir/core/domain/database/data/DatabaseTypePageCondition.java new file mode 100644 index 0000000..cc1f2d9 --- /dev/null +++ b/core/src/main/java/com/databasir/core/domain/database/data/DatabaseTypePageCondition.java @@ -0,0 +1,26 @@ +package com.databasir.core.domain.database.data; + +import com.databasir.dao.Tables; +import lombok.Data; +import org.jooq.Condition; +import org.jooq.impl.DSL; + +import java.util.ArrayList; +import java.util.List; + +@Data +public class DatabaseTypePageCondition { + + private String databaseTypeContains; + + public Condition toCondition() { + List conditions = new ArrayList<>(); + conditions.add(Tables.DATABASE_TYPE.DELETED.eq(false)); + if (databaseTypeContains != null && !databaseTypeContains.trim().equals("")) { + conditions.add(Tables.DATABASE_TYPE.DATABASE_TYPE_.containsIgnoreCase(databaseTypeContains)); + } + return conditions.stream() + .reduce(Condition::and) + .orElse(DSL.trueCondition()); + } +} diff --git a/core/src/main/java/com/databasir/core/domain/database/data/DatabaseTypePageResponse.java b/core/src/main/java/com/databasir/core/domain/database/data/DatabaseTypePageResponse.java new file mode 100644 index 0000000..b040cb1 --- /dev/null +++ b/core/src/main/java/com/databasir/core/domain/database/data/DatabaseTypePageResponse.java @@ -0,0 +1,33 @@ +package com.databasir.core.domain.database.data; + +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +public class DatabaseTypePageResponse { + + private Integer id; + + private String databaseType; + + private String icon; + + private String description; + + private String jdbcDriverFileUrl; + + private String jdbcDriverClassName; + + private String jdbcProtocol; + + private Boolean deleted; + + private Integer deletedToken; + + private Integer projectCount; + + private LocalDateTime updateAt; + + private LocalDateTime createAt; +} 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 new file mode 100644 index 0000000..4c056e3 --- /dev/null +++ b/core/src/main/java/com/databasir/core/domain/database/data/DatabaseTypeUpdateRequest.java @@ -0,0 +1,30 @@ +package com.databasir.core.domain.database.data; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +@Data +public class DatabaseTypeUpdateRequest { + + @NotNull + private Integer id; + + @NotBlank + private String databaseType; + + private String icon; + + @NotBlank + private String description; + + @NotBlank + private String jdbcDriverFileUrl; + + @NotBlank + private String jdbcDriverClassName; + + @NotBlank + private String jdbcProtocol; +} 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 new file mode 100644 index 0000000..e17ce71 --- /dev/null +++ b/core/src/main/java/com/databasir/core/domain/database/service/DatabaseTypeService.java @@ -0,0 +1,103 @@ +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.infrastructure.connection.DatabaseTypes; +import com.databasir.core.infrastructure.driver.DriverResources; +import com.databasir.dao.impl.DatabaseTypeDao; +import com.databasir.dao.impl.ProjectDao; +import com.databasir.dao.tables.pojos.DatabaseTypePojo; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +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 java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +@Slf4j +public class DatabaseTypeService { + + private final DriverResources driverResources; + + private final DatabaseTypeDao databaseTypeDao; + + private final ProjectDao projectDao; + + private final DatabaseTypePojoConverter databaseTypePojoConverter; + + public Integer create(DatabaseTypeCreateRequest request) { + DatabaseTypePojo pojo = databaseTypePojoConverter.of(request); + try { + return databaseTypeDao.insertAndReturnId(pojo); + } catch (DuplicateKeyException e) { + throw DomainErrors.DATABASE_TYPE_NAME_DUPLICATE.exception(); + } + } + + @Transactional + public void update(DatabaseTypeUpdateRequest request) { + databaseTypeDao.selectOptionalById(request.getId()).ifPresent(data -> { + if (DatabaseTypes.has(data.getDatabaseType())) { + throw DomainErrors.MUST_NOT_MODIFY_SYSTEM_DEFAULT_DATABASE_TYPE.exception(); + } + + DatabaseTypePojo pojo = databaseTypePojoConverter.of(request); + try { + databaseTypeDao.updateById(pojo); + } catch (DuplicateKeyException e) { + throw DomainErrors.DATABASE_TYPE_NAME_DUPLICATE.exception(); + } + + // 名称修改,下载地址修改需要删除原有的 driver + if (!Objects.equals(request.getDatabaseType(), data.getDatabaseType()) + || !Objects.equals(request.getJdbcDriverFileUrl(), data.getJdbcDriverFileUrl())) { + driverResources.delete(data.getDatabaseType()); + } + }); + + } + + public void deleteById(Integer id) { + databaseTypeDao.selectOptionalById(id).ifPresent(data -> { + if (DatabaseTypes.has(data.getDatabaseType())) { + throw DomainErrors.MUST_NOT_MODIFY_SYSTEM_DEFAULT_DATABASE_TYPE.exception(); + } + databaseTypeDao.deleteById(id); + driverResources.delete(data.getDatabaseType()); + }); + } + + public Page findByPage(Pageable page, + DatabaseTypePageCondition condition) { + Page pageData = databaseTypeDao.selectByPage(page, condition.toCondition()); + List databaseTypes = pageData.map(DatabaseTypePojo::getDatabaseType).toList(); + Map projectCountMapByDatabaseType = projectDao.countByDatabaseTypes(databaseTypes); + return pageData + .map(data -> { + Integer count = projectCountMapByDatabaseType.getOrDefault(data.getDatabaseType(), 0); + return databaseTypePojoConverter.toPageResponse(data, count); + }); + } + + public List listSimpleDatabaseTypes() { + return databaseTypeDao.selectAll() + .stream() + .map(DatabaseTypePojo::getDatabaseType) + .collect(Collectors.toList()); + } + + public Optional selectOne(Integer id) { + return databaseTypeDao.selectOptionalById(id) + .map(databaseTypePojoConverter::toDetailResponse); + } +} diff --git a/core/src/main/java/com/databasir/core/domain/log/annotation/Operation.java b/core/src/main/java/com/databasir/core/domain/log/annotation/Operation.java index 9c3d0f5..cdbe291 100644 --- a/core/src/main/java/com/databasir/core/domain/log/annotation/Operation.java +++ b/core/src/main/java/com/databasir/core/domain/log/annotation/Operation.java @@ -35,6 +35,7 @@ public @interface Operation { String GROUP = "group"; String LOGIN_APP = "login_app"; String SETTING = "setting"; + String DATABASE_TYPE = "database_type"; } interface Types { 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 new file mode 100644 index 0000000..e815a6e --- /dev/null +++ b/core/src/main/java/com/databasir/core/infrastructure/connection/CustomDatabaseConnectionFactory.java @@ -0,0 +1,80 @@ +package com.databasir.core.infrastructure.connection; + +import com.databasir.core.domain.DomainErrors; +import com.databasir.core.infrastructure.driver.DriverResources; +import com.databasir.dao.impl.DatabaseTypeDao; +import com.databasir.dao.tables.pojos.DatabaseTypePojo; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.sql.Connection; +import java.sql.Driver; +import java.sql.SQLException; +import java.util.Properties; + +@Component +@RequiredArgsConstructor +@Slf4j +public class CustomDatabaseConnectionFactory implements DatabaseConnectionFactory { + + private final DatabaseTypeDao databaseTypeDao; + + private final DriverResources driverResources; + + @Override + public boolean support(String databaseType) { + return databaseTypeDao.existsByDatabaseType(databaseType); + } + + @Override + public Connection getConnection(Context context) throws SQLException { + DatabaseTypePojo type = databaseTypeDao.selectByDatabaseType(context.getDatabaseType()); + File driverFile; + try { + driverFile = driverResources.download(context.getDatabaseType(), type.getJdbcDriverFileUrl()); + } catch (IOException e) { + log.error("download driver error " + context, e); + throw DomainErrors.DOWNLOAD_DRIVER_ERROR.exception(e.getMessage()); + } + + URLClassLoader loader = null; + try { + loader = new URLClassLoader( + new URL[]{ + driverFile.toURI().toURL() + }, + this.getClass().getClassLoader() + ); + } catch (MalformedURLException e) { + log.error("load driver error " + context, e); + throw DomainErrors.CONNECT_DATABASE_FAILED.exception(e.getMessage()); + } + + Class clazz = null; + Driver driver = null; + try { + clazz = Class.forName(type.getJdbcDriverClassName(), true, loader); + driver = (Driver) clazz.getConstructor().newInstance(); + } catch (ClassNotFoundException e) { + log.error("init driver error", e); + throw DomainErrors.CONNECT_DATABASE_FAILED.exception("驱动初始化异常, 请检查 Driver name:" + e.getMessage()); + } catch (InvocationTargetException | InstantiationException | IllegalAccessException | NoSuchMethodException e) { + log.error("init driver error", e); + throw DomainErrors.CONNECT_DATABASE_FAILED.exception("驱动初始化异常:" + e.getMessage()); + } + + Properties info = new Properties(); + info.put("user", context.getUsername()); + info.put("password", context.getPassword()); + String jdbcUrl = type.getJdbcProtocol() + "://" + context.getUrl() + "/" + context.getSchema(); + return driver.connect(jdbcUrl, info); + } + +} diff --git a/core/src/main/java/com/databasir/core/infrastructure/connection/DatabaseConnectionFactory.java b/core/src/main/java/com/databasir/core/infrastructure/connection/DatabaseConnectionFactory.java index 6676a5a..87daeca 100644 --- a/core/src/main/java/com/databasir/core/infrastructure/connection/DatabaseConnectionFactory.java +++ b/core/src/main/java/com/databasir/core/infrastructure/connection/DatabaseConnectionFactory.java @@ -1,5 +1,8 @@ package com.databasir.core.infrastructure.connection; +import lombok.Builder; +import lombok.Data; + import java.sql.Connection; import java.sql.SQLException; import java.util.Properties; @@ -8,9 +11,22 @@ public interface DatabaseConnectionFactory { boolean support(String databaseType); - Connection getConnection(String username, - String password, - String url, - String schema, - Properties properties) throws SQLException; + Connection getConnection(Context context) throws SQLException; + + @Builder + @Data + class Context { + + private String databaseType; + + private String username; + + private String password; + + private String url; + + private String schema; + + private Properties properties; + } } diff --git a/core/src/main/java/com/databasir/core/infrastructure/connection/DatabaseConnectionService.java b/core/src/main/java/com/databasir/core/infrastructure/connection/DatabaseConnectionService.java index cb21e96..f89e578 100644 --- a/core/src/main/java/com/databasir/core/infrastructure/connection/DatabaseConnectionService.java +++ b/core/src/main/java/com/databasir/core/infrastructure/connection/DatabaseConnectionService.java @@ -32,11 +32,19 @@ public class DatabaseConnectionService { Properties info = new Properties(); dataSourceProperties.forEach(prop -> info.put(prop.getKey(), prop.getValue())); try { + DatabaseConnectionFactory.Context context = DatabaseConnectionFactory.Context.builder() + .username(username) + .password(password) + .url(url) + .schema(dataSource.getDatabaseName()) + .properties(info) + .databaseType(dataSource.getDatabaseType()) + .build(); return factories.stream() .filter(factory -> factory.support(dataSource.getDatabaseType())) .findFirst() .orElseThrow(DomainErrors.NOT_SUPPORT_DATABASE_TYPE::exception) - .getConnection(username, password, url, dataSource.getDatabaseName(), info); + .getConnection(context); } catch (SQLException e) { throw DomainErrors.CONNECT_DATABASE_FAILED.exception(e.getMessage(), e); } @@ -49,11 +57,19 @@ public class DatabaseConnectionService { String databaseType, Properties properties) { try { + DatabaseConnectionFactory.Context context = DatabaseConnectionFactory.Context.builder() + .username(username) + .password(password) + .url(url) + .schema(databaseName) + .properties(properties) + .databaseType(databaseType) + .build(); factories.stream() .filter(factory -> factory.support(databaseType)) .findFirst() .orElseThrow(DomainErrors.NOT_SUPPORT_DATABASE_TYPE::exception) - .getConnection(username, password, url, databaseName, properties); + .getConnection(context); } catch (SQLException e) { throw DomainErrors.CONNECT_DATABASE_FAILED.exception(e.getMessage(), e); } diff --git a/core/src/main/java/com/databasir/core/infrastructure/connection/DatabaseTypes.java b/core/src/main/java/com/databasir/core/infrastructure/connection/DatabaseTypes.java index e8b160f..349a825 100644 --- a/core/src/main/java/com/databasir/core/infrastructure/connection/DatabaseTypes.java +++ b/core/src/main/java/com/databasir/core/infrastructure/connection/DatabaseTypes.java @@ -1,10 +1,19 @@ package com.databasir.core.infrastructure.connection; +import java.util.Objects; + public interface DatabaseTypes { String MYSQL = "mysql"; String POSTGRESQL = "postgresql"; - String ORACLE = "oracle"; + static boolean has(String name) { + if (name == null) { + return false; + } + return Objects.equals(MYSQL, name.toLowerCase()) + || Objects.equals(POSTGRESQL, name.toLowerCase()); + } + } diff --git a/core/src/main/java/com/databasir/core/infrastructure/connection/MysqlDatabaseConnectionFactory.java b/core/src/main/java/com/databasir/core/infrastructure/connection/MysqlDatabaseConnectionFactory.java index 6e8e2d7..21b0178 100644 --- a/core/src/main/java/com/databasir/core/infrastructure/connection/MysqlDatabaseConnectionFactory.java +++ b/core/src/main/java/com/databasir/core/infrastructure/connection/MysqlDatabaseConnectionFactory.java @@ -16,11 +16,7 @@ public class MysqlDatabaseConnectionFactory implements DatabaseConnectionFactory } @Override - public Connection getConnection(String username, - String password, - String url, - String schema, - Properties properties) throws SQLException { + public Connection getConnection(Context context) throws SQLException { try { Class.forName("com.mysql.cj.jdbc.Driver"); } catch (ClassNotFoundException e) { @@ -28,10 +24,10 @@ public class MysqlDatabaseConnectionFactory implements DatabaseConnectionFactory } Properties info = new Properties(); - info.put("user", username); - info.put("password", password); - info.putAll(properties); - String jdbcUrl = "jdbc:mysql://" + url + "/" + schema; + info.put("user", context.getUsername()); + info.put("password", context.getPassword()); + info.putAll(context.getProperties()); + String jdbcUrl = "jdbc:mysql://" + context.getUrl() + "/" + context.getSchema(); return DriverManager.getConnection(jdbcUrl, info); } diff --git a/core/src/main/java/com/databasir/core/infrastructure/connection/PostgresqlDatabaseConnectionFactory.java b/core/src/main/java/com/databasir/core/infrastructure/connection/PostgresqlDatabaseConnectionFactory.java index 6d0bca1..3c33ba9 100644 --- a/core/src/main/java/com/databasir/core/infrastructure/connection/PostgresqlDatabaseConnectionFactory.java +++ b/core/src/main/java/com/databasir/core/infrastructure/connection/PostgresqlDatabaseConnectionFactory.java @@ -16,11 +16,7 @@ public class PostgresqlDatabaseConnectionFactory implements DatabaseConnectionFa } @Override - public Connection getConnection(String username, - String password, - String url, - String schema, - Properties properties) throws SQLException { + public Connection getConnection(Context context) throws SQLException { try { Class.forName("org.postgresql.Driver"); } catch (ClassNotFoundException e) { @@ -28,10 +24,10 @@ public class PostgresqlDatabaseConnectionFactory implements DatabaseConnectionFa } Properties info = new Properties(); - info.put("user", username); - info.put("password", password); - info.putAll(properties); - String jdbcUrl = "jdbc:postgresql://" + url + "/" + schema; + info.put("user", context.getUsername()); + info.put("password", context.getPassword()); + info.putAll(context.getProperties()); + String jdbcUrl = "jdbc:postgresql://" + context.getUrl() + "/" + context.getSchema(); return DriverManager.getConnection(jdbcUrl, info); } } 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 new file mode 100644 index 0000000..e637521 --- /dev/null +++ b/core/src/main/java/com/databasir/core/infrastructure/driver/DriverResources.java @@ -0,0 +1,84 @@ +package com.databasir.core.infrastructure.driver; + +import com.databasir.core.domain.DomainErrors; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpMethod; +import org.springframework.stereotype.Component; +import org.springframework.util.StreamUtils; +import org.springframework.web.client.RestTemplate; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +@Component +@Slf4j +@RequiredArgsConstructor +public class DriverResources { + + @Value("${databasir.db.driver-directory}") + private String driverBaseDirectory; + + private final RestTemplate restTemplate; + + public File download(String databaseType, String driverFileUrl) throws IOException { + // create parent directory + if (Files.notExists(Path.of(driverBaseDirectory))) { + Files.createDirectory(Path.of(driverBaseDirectory)); + } + + String filePath = driverPath(databaseType); + Path path = Path.of(filePath); + if (Files.exists(path)) { + // ignore + log.debug("{} already exists, ignore download from {}", filePath, driverFileUrl); + return path.toFile(); + } else { + // download + try { + return restTemplate.execute(driverFileUrl, HttpMethod.GET, null, response -> { + if (response.getStatusCode().is2xxSuccessful()) { + File file = path.toFile(); + StreamUtils.copy(response.getBody(), new FileOutputStream(file)); + log.info("{} download success ", filePath); + return file; + } else { + log.error("{} download error from {}: {} ", filePath, driverFileUrl, response); + throw DomainErrors.DOWNLOAD_DRIVER_ERROR.exception("驱动下载失败:" + + response.getStatusCode() + + ", " + + response.getStatusText()); + } + }); + } catch (IllegalArgumentException e) { + log.error(filePath + " download driver error", e); + throw DomainErrors.DOWNLOAD_DRIVER_ERROR.exception(e.getMessage()); + } + } + } + + public void delete(String databaseType) { + Path path = Paths.get(driverPath(databaseType)); + try { + Files.deleteIfExists(path); + } catch (IOException e) { + log.error("delete driver error " + databaseType, e); + } + } + + private String driverPath(String databaseType) { + String fileName = databaseType + ".jar"; + String filePath; + if (driverBaseDirectory.endsWith(File.separator)) { + filePath = driverBaseDirectory + fileName; + } else { + filePath = driverBaseDirectory + File.separator + fileName; + } + return filePath; + } +} diff --git a/core/src/main/java/com/databasir/core/infrastructure/meta/DatabaseMetaResolver.java b/core/src/main/java/com/databasir/core/infrastructure/meta/DatabaseMetaResolver.java deleted file mode 100644 index 182aacc..0000000 --- a/core/src/main/java/com/databasir/core/infrastructure/meta/DatabaseMetaResolver.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.databasir.core.infrastructure.meta; - -public class DatabaseMetaResolver { - -} diff --git a/dao/src/main/java/com/databasir/dao/impl/BaseDao.java b/dao/src/main/java/com/databasir/dao/impl/BaseDao.java index 0ab42b7..dbcb8fe 100644 --- a/dao/src/main/java/com/databasir/dao/impl/BaseDao.java +++ b/dao/src/main/java/com/databasir/dao/impl/BaseDao.java @@ -80,6 +80,20 @@ public abstract class BaseDao { new DataNotExistsException("data not exists in " + table.getName() + " with id = " + id)); } + public Optional selectOptionalOne(Condition condition) { + return getDslContext() + .select(table.fields()).from(table).where(condition) + .fetchOptionalInto(pojoType); + } + + public R selectOne(Condition condition) { + return selectOptionalOne(condition) + .orElseThrow(() -> new DataNotExistsException("data not exists in " + + table.getName() + + " with condition = " + + condition)); + } + public List selectInIds(List ids) { if (ids == null || ids.isEmpty()) { return Collections.emptyList(); diff --git a/dao/src/main/java/com/databasir/dao/impl/DatabaseTypeDao.java b/dao/src/main/java/com/databasir/dao/impl/DatabaseTypeDao.java new file mode 100644 index 0000000..48069d7 --- /dev/null +++ b/dao/src/main/java/com/databasir/dao/impl/DatabaseTypeDao.java @@ -0,0 +1,61 @@ +package com.databasir.dao.impl; + +import com.databasir.dao.tables.pojos.DatabaseTypePojo; +import lombok.Getter; +import org.jooq.DSLContext; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.Optional; + +import static com.databasir.dao.Tables.DATABASE_TYPE; + +@Repository +public class DatabaseTypeDao extends BaseDao { + + @Autowired + @Getter + private DSLContext dslContext; + + public DatabaseTypeDao() { + super(DATABASE_TYPE, DatabaseTypePojo.class); + } + + public boolean existsByDatabaseType(String databaseType) { + return exists(DATABASE_TYPE.DATABASE_TYPE_.eq(databaseType) + .and(DATABASE_TYPE.DELETED.eq(false))); + } + + public boolean existsById(Integer id) { + return exists(DATABASE_TYPE.ID.eq(id) + .and(DATABASE_TYPE.DELETED.eq(false))); + } + + public DatabaseTypePojo selectByDatabaseType(String databaseType) { + return this.selectOne(DATABASE_TYPE.DATABASE_TYPE_.eq(databaseType) + .and(DATABASE_TYPE.DELETED.eq(false))); + } + + @Override + public List selectAll() { + return this.getDslContext().selectFrom(DATABASE_TYPE) + .where(DATABASE_TYPE.DELETED.eq(false)) + .orderBy(DATABASE_TYPE.ID.desc()) + .fetchInto(DatabaseTypePojo.class); + } + + public int deleteById(Integer id) { + return this.getDslContext() + .update(DATABASE_TYPE) + .set(DATABASE_TYPE.DELETED, true) + .set(DATABASE_TYPE.DELETED_TOKEN, DATABASE_TYPE.ID) + .where(DATABASE_TYPE.ID.eq(id) + .and(DATABASE_TYPE.DELETED.eq(false))) + .execute(); + } + + public Optional selectOptionalById(Integer id) { + return super.selectOptionalOne(DATABASE_TYPE.DELETED.eq(false).and(DATABASE_TYPE.ID.eq(id))); + } +} \ No newline at end of file diff --git a/dao/src/main/java/com/databasir/dao/impl/ProjectDao.java b/dao/src/main/java/com/databasir/dao/impl/ProjectDao.java index 11f6533..ef61298 100644 --- a/dao/src/main/java/com/databasir/dao/impl/ProjectDao.java +++ b/dao/src/main/java/com/databasir/dao/impl/ProjectDao.java @@ -5,6 +5,7 @@ import com.databasir.dao.value.GroupProjectCountPojo; import lombok.Getter; import org.jooq.Condition; import org.jooq.DSLContext; +import org.jooq.impl.DSL; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; @@ -14,6 +15,7 @@ import org.springframework.stereotype.Repository; import java.io.Serializable; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; @@ -100,4 +102,17 @@ public class ProjectDao extends BaseDao { .where(PROJECT.GROUP_ID.eq(groupId).and(PROJECT.DELETED.eq(false))) .fetchInto(Integer.class); } + + public Map countByDatabaseTypes(List databaseTypes) { + if (databaseTypes == null || databaseTypes.isEmpty()) { + return Collections.emptyMap(); + } + return getDslContext() + .select(DSL.count(DATA_SOURCE), DATA_SOURCE.DATABASE_TYPE) + .from(DATA_SOURCE) + .innerJoin(PROJECT).on(PROJECT.ID.eq(DATA_SOURCE.PROJECT_ID)) + .where(PROJECT.DELETED.eq(false)).and(DATA_SOURCE.DATABASE_TYPE.in(databaseTypes)) + .groupBy(DATA_SOURCE.DATABASE_TYPE) + .fetchMap(DATA_SOURCE.DATABASE_TYPE, DSL.count(DATA_SOURCE)); + } } diff --git a/dao/src/main/resources/db/migration/V1.1__database_type.sql b/dao/src/main/resources/db/migration/V1.1__database_type.sql index 363a0bb..7332831 100644 --- a/dao/src/main/resources/db/migration/V1.1__database_type.sql +++ b/dao/src/main/resources/db/migration/V1.1__database_type.sql @@ -13,4 +13,10 @@ CREATE TABLE IF NOT EXISTS database_type create_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, CONSTRAINT uk_database_type_deleted_deleted_token UNIQUE (database_type, deleted, deleted_token) ) CHARSET utf8mb4 - COLLATE utf8mb4_unicode_ci COMMENT 'customer database types'; \ No newline at end of file + COLLATE utf8mb4_unicode_ci COMMENT 'customer database types'; + +REPLACE INTO databasir.database_type (id, database_type, icon, DESCRIPTION, jdbc_driver_file_url, + jdbc_driver_class_name, + jdbc_protocol) +VALUES (1, 'mysql', '', 'system default mysql', 'N/A', 'com.mysql.cj.jdbc.Driver', 'jdbc:mysql'), + (2, 'postgresql', '', 'system default postgresql', 'N/A', 'org.postgresql.Driver', 'jdbc:postgresql');