Browse Source

feat: support custom database types

master
vran 3 years ago
parent
commit
0333ecc8ae
  1. 1
      .gitignore
  2. 67
      api/src/main/java/com/databasir/api/DatabaseTypeController.java
  3. 15
      api/src/main/java/com/databasir/api/Routes.java
  4. 11
      api/src/main/resources/application-local.properties
  5. 32
      core/src/main/java/com/databasir/core/config/RestTemplateConfig.java
  6. 8
      core/src/main/java/com/databasir/core/domain/DomainErrors.java
  7. 20
      core/src/main/java/com/databasir/core/domain/database/converter/DatabaseTypePojoConverter.java
  8. 26
      core/src/main/java/com/databasir/core/domain/database/data/DatabaseTypeCreateRequest.java
  9. 31
      core/src/main/java/com/databasir/core/domain/database/data/DatabaseTypeDetailResponse.java
  10. 26
      core/src/main/java/com/databasir/core/domain/database/data/DatabaseTypePageCondition.java
  11. 33
      core/src/main/java/com/databasir/core/domain/database/data/DatabaseTypePageResponse.java
  12. 30
      core/src/main/java/com/databasir/core/domain/database/data/DatabaseTypeUpdateRequest.java
  13. 103
      core/src/main/java/com/databasir/core/domain/database/service/DatabaseTypeService.java
  14. 1
      core/src/main/java/com/databasir/core/domain/log/annotation/Operation.java
  15. 80
      core/src/main/java/com/databasir/core/infrastructure/connection/CustomDatabaseConnectionFactory.java
  16. 26
      core/src/main/java/com/databasir/core/infrastructure/connection/DatabaseConnectionFactory.java
  17. 20
      core/src/main/java/com/databasir/core/infrastructure/connection/DatabaseConnectionService.java
  18. 11
      core/src/main/java/com/databasir/core/infrastructure/connection/DatabaseTypes.java
  19. 14
      core/src/main/java/com/databasir/core/infrastructure/connection/MysqlDatabaseConnectionFactory.java
  20. 14
      core/src/main/java/com/databasir/core/infrastructure/connection/PostgresqlDatabaseConnectionFactory.java
  21. 84
      core/src/main/java/com/databasir/core/infrastructure/driver/DriverResources.java
  22. 5
      core/src/main/java/com/databasir/core/infrastructure/meta/DatabaseMetaResolver.java
  23. 14
      dao/src/main/java/com/databasir/dao/impl/BaseDao.java
  24. 61
      dao/src/main/java/com/databasir/dao/impl/DatabaseTypeDao.java
  25. 15
      dao/src/main/java/com/databasir/dao/impl/ProjectDao.java
  26. 6
      dao/src/main/resources/db/migration/V1.1__database_type.sql

1
.gitignore

@ -2,3 +2,4 @@
**/build/**
.idea/**
**/.DS_Store
drivers/**

67
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<List<String>> listSimpleDatabaseTypes() {
List<String> types = databaseTypeService.listSimpleDatabaseTypes();
return JsonData.ok(types);
}
@GetMapping(Routes.DatabaseType.LIST_PAGE)
public JsonData<Page<DatabaseTypePageResponse>> listPage(@PageableDefault(sort = "id", direction = DESC)
Pageable page,
DatabaseTypePageCondition condition) {
Page<DatabaseTypePageResponse> data = databaseTypeService.findByPage(page, condition);
return JsonData.ok(data);
}
@PostMapping(Routes.DatabaseType.CREATE)
@Operation(module = Operation.Modules.DATABASE_TYPE, name = "创建数据库类型")
public JsonData<Integer> 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<Void> 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<Void> delete(@PathVariable Integer id) {
databaseTypeService.deleteById(id);
return JsonData.ok();
}
@GetMapping(Routes.DatabaseType.GET_ONE)
public JsonData<DatabaseTypeDetailResponse> getOne(@PathVariable Integer id) {
Optional<DatabaseTypeDetailResponse> data = databaseTypeService.selectOne(id);
return JsonData.ok(data);
}
}

15
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";
}
}

11
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

32
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;
}
}

8
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);
}
}

20
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);
}

26
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;
}

31
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;
}

26
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<Condition> 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());
}
}

33
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;
}

30
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;
}

103
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<DatabaseTypePageResponse> findByPage(Pageable page,
DatabaseTypePageCondition condition) {
Page<DatabaseTypePojo> pageData = databaseTypeDao.selectByPage(page, condition.toCondition());
List<String> databaseTypes = pageData.map(DatabaseTypePojo::getDatabaseType).toList();
Map<String, Integer> projectCountMapByDatabaseType = projectDao.countByDatabaseTypes(databaseTypes);
return pageData
.map(data -> {
Integer count = projectCountMapByDatabaseType.getOrDefault(data.getDatabaseType(), 0);
return databaseTypePojoConverter.toPageResponse(data, count);
});
}
public List<String> listSimpleDatabaseTypes() {
return databaseTypeDao.selectAll()
.stream()
.map(DatabaseTypePojo::getDatabaseType)
.collect(Collectors.toList());
}
public Optional<DatabaseTypeDetailResponse> selectOne(Integer id) {
return databaseTypeDao.selectOptionalById(id)
.map(databaseTypePojoConverter::toDetailResponse);
}
}

1
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 {

80
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);
}
}

26
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;
}
}

20
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);
}

11
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());
}
}

14
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);
}

14
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);
}
}

84
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;
}
}

5
core/src/main/java/com/databasir/core/infrastructure/meta/DatabaseMetaResolver.java

@ -1,5 +0,0 @@
package com.databasir.core.infrastructure.meta;
public class DatabaseMetaResolver {
}

14
dao/src/main/java/com/databasir/dao/impl/BaseDao.java

@ -80,6 +80,20 @@ public abstract class BaseDao<R> {
new DataNotExistsException("data not exists in " + table.getName() + " with id = " + id));
}
public Optional<R> 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<R> selectInIds(List<? extends Serializable> ids) {
if (ids == null || ids.isEmpty()) {
return Collections.emptyList();

61
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<DatabaseTypePojo> {
@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<DatabaseTypePojo> 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<DatabaseTypePojo> selectOptionalById(Integer id) {
return super.selectOptionalOne(DATABASE_TYPE.DELETED.eq(false).and(DATABASE_TYPE.ID.eq(id)));
}
}

15
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<ProjectPojo> {
.where(PROJECT.GROUP_ID.eq(groupId).and(PROJECT.DELETED.eq(false)))
.fetchInto(Integer.class);
}
public Map<String, Integer> countByDatabaseTypes(List<String> 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));
}
}

6
dao/src/main/resources/db/migration/V1.1__database_type.sql

@ -14,3 +14,9 @@ CREATE TABLE IF NOT EXISTS database_type
CONSTRAINT uk_database_type_deleted_deleted_token UNIQUE (database_type, deleted, deleted_token)
) CHARSET utf8mb4
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');

Loading…
Cancel
Save