feat: support custom database types
This commit is contained in:
parent
91c3b0184d
commit
0333ecc8ae
|
@ -2,3 +2,4 @@
|
|||
**/build/**
|
||||
.idea/**
|
||||
**/.DS_Store
|
||||
drivers/**
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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";
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
package com.databasir.core.infrastructure.meta;
|
||||
|
||||
public class DatabaseMetaResolver {
|
||||
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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)));
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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…
Reference in New Issue