feat: support custom database types
This commit is contained in:
parent
91c3b0184d
commit
0333ecc8ae
|
@ -2,3 +2,4 @@
|
||||||
**/build/**
|
**/build/**
|
||||||
.idea/**
|
.idea/**
|
||||||
**/.DS_Store
|
**/.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}";
|
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
|
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
|
spring.jooq.sql-dialect=mysql
|
||||||
|
# flyway
|
||||||
spring.flyway.enabled=true
|
spring.flyway.enabled=true
|
||||||
spring.flyway.baseline-on-migrate=true
|
spring.flyway.baseline-on-migrate=true
|
||||||
spring.flyway.locations=classpath:db/migration
|
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 表达式"),
|
INVALID_CRON_EXPRESSION("A_10012", "不合法的 cron 表达式"),
|
||||||
REGISTRATION_ID_DUPLICATE("A_10013", "应用注册 ID 不能重复"),
|
REGISTRATION_ID_DUPLICATE("A_10013", "应用注册 ID 不能重复"),
|
||||||
REGISTRATION_ID_NOT_FOUND("A_10014", "应用 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;
|
private final String errCode;
|
||||||
|
|
||||||
|
@ -46,6 +50,6 @@ public enum DomainErrors implements DatabasirErrors {
|
||||||
}
|
}
|
||||||
|
|
||||||
public DatabasirException exception(String s) {
|
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 GROUP = "group";
|
||||||
String LOGIN_APP = "login_app";
|
String LOGIN_APP = "login_app";
|
||||||
String SETTING = "setting";
|
String SETTING = "setting";
|
||||||
|
String DATABASE_TYPE = "database_type";
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Types {
|
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;
|
package com.databasir.core.infrastructure.connection;
|
||||||
|
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
import java.sql.Connection;
|
import java.sql.Connection;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
|
@ -8,9 +11,22 @@ public interface DatabaseConnectionFactory {
|
||||||
|
|
||||||
boolean support(String databaseType);
|
boolean support(String databaseType);
|
||||||
|
|
||||||
Connection getConnection(String username,
|
Connection getConnection(Context context) throws SQLException;
|
||||||
String password,
|
|
||||||
String url,
|
@Builder
|
||||||
String schema,
|
@Data
|
||||||
Properties properties) throws SQLException;
|
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();
|
Properties info = new Properties();
|
||||||
dataSourceProperties.forEach(prop -> info.put(prop.getKey(), prop.getValue()));
|
dataSourceProperties.forEach(prop -> info.put(prop.getKey(), prop.getValue()));
|
||||||
try {
|
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()
|
return factories.stream()
|
||||||
.filter(factory -> factory.support(dataSource.getDatabaseType()))
|
.filter(factory -> factory.support(dataSource.getDatabaseType()))
|
||||||
.findFirst()
|
.findFirst()
|
||||||
.orElseThrow(DomainErrors.NOT_SUPPORT_DATABASE_TYPE::exception)
|
.orElseThrow(DomainErrors.NOT_SUPPORT_DATABASE_TYPE::exception)
|
||||||
.getConnection(username, password, url, dataSource.getDatabaseName(), info);
|
.getConnection(context);
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
throw DomainErrors.CONNECT_DATABASE_FAILED.exception(e.getMessage(), e);
|
throw DomainErrors.CONNECT_DATABASE_FAILED.exception(e.getMessage(), e);
|
||||||
}
|
}
|
||||||
|
@ -49,11 +57,19 @@ public class DatabaseConnectionService {
|
||||||
String databaseType,
|
String databaseType,
|
||||||
Properties properties) {
|
Properties properties) {
|
||||||
try {
|
try {
|
||||||
|
DatabaseConnectionFactory.Context context = DatabaseConnectionFactory.Context.builder()
|
||||||
|
.username(username)
|
||||||
|
.password(password)
|
||||||
|
.url(url)
|
||||||
|
.schema(databaseName)
|
||||||
|
.properties(properties)
|
||||||
|
.databaseType(databaseType)
|
||||||
|
.build();
|
||||||
factories.stream()
|
factories.stream()
|
||||||
.filter(factory -> factory.support(databaseType))
|
.filter(factory -> factory.support(databaseType))
|
||||||
.findFirst()
|
.findFirst()
|
||||||
.orElseThrow(DomainErrors.NOT_SUPPORT_DATABASE_TYPE::exception)
|
.orElseThrow(DomainErrors.NOT_SUPPORT_DATABASE_TYPE::exception)
|
||||||
.getConnection(username, password, url, databaseName, properties);
|
.getConnection(context);
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
throw DomainErrors.CONNECT_DATABASE_FAILED.exception(e.getMessage(), e);
|
throw DomainErrors.CONNECT_DATABASE_FAILED.exception(e.getMessage(), e);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,19 @@
|
||||||
package com.databasir.core.infrastructure.connection;
|
package com.databasir.core.infrastructure.connection;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
public interface DatabaseTypes {
|
public interface DatabaseTypes {
|
||||||
|
|
||||||
String MYSQL = "mysql";
|
String MYSQL = "mysql";
|
||||||
|
|
||||||
String POSTGRESQL = "postgresql";
|
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
|
@Override
|
||||||
public Connection getConnection(String username,
|
public Connection getConnection(Context context) throws SQLException {
|
||||||
String password,
|
|
||||||
String url,
|
|
||||||
String schema,
|
|
||||||
Properties properties) throws SQLException {
|
|
||||||
try {
|
try {
|
||||||
Class.forName("com.mysql.cj.jdbc.Driver");
|
Class.forName("com.mysql.cj.jdbc.Driver");
|
||||||
} catch (ClassNotFoundException e) {
|
} catch (ClassNotFoundException e) {
|
||||||
|
@ -28,10 +24,10 @@ public class MysqlDatabaseConnectionFactory implements DatabaseConnectionFactory
|
||||||
}
|
}
|
||||||
|
|
||||||
Properties info = new Properties();
|
Properties info = new Properties();
|
||||||
info.put("user", username);
|
info.put("user", context.getUsername());
|
||||||
info.put("password", password);
|
info.put("password", context.getPassword());
|
||||||
info.putAll(properties);
|
info.putAll(context.getProperties());
|
||||||
String jdbcUrl = "jdbc:mysql://" + url + "/" + schema;
|
String jdbcUrl = "jdbc:mysql://" + context.getUrl() + "/" + context.getSchema();
|
||||||
return DriverManager.getConnection(jdbcUrl, info);
|
return DriverManager.getConnection(jdbcUrl, info);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,11 +16,7 @@ public class PostgresqlDatabaseConnectionFactory implements DatabaseConnectionFa
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Connection getConnection(String username,
|
public Connection getConnection(Context context) throws SQLException {
|
||||||
String password,
|
|
||||||
String url,
|
|
||||||
String schema,
|
|
||||||
Properties properties) throws SQLException {
|
|
||||||
try {
|
try {
|
||||||
Class.forName("org.postgresql.Driver");
|
Class.forName("org.postgresql.Driver");
|
||||||
} catch (ClassNotFoundException e) {
|
} catch (ClassNotFoundException e) {
|
||||||
|
@ -28,10 +24,10 @@ public class PostgresqlDatabaseConnectionFactory implements DatabaseConnectionFa
|
||||||
}
|
}
|
||||||
|
|
||||||
Properties info = new Properties();
|
Properties info = new Properties();
|
||||||
info.put("user", username);
|
info.put("user", context.getUsername());
|
||||||
info.put("password", password);
|
info.put("password", context.getPassword());
|
||||||
info.putAll(properties);
|
info.putAll(context.getProperties());
|
||||||
String jdbcUrl = "jdbc:postgresql://" + url + "/" + schema;
|
String jdbcUrl = "jdbc:postgresql://" + context.getUrl() + "/" + context.getSchema();
|
||||||
return DriverManager.getConnection(jdbcUrl, info);
|
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));
|
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) {
|
public List<R> selectInIds(List<? extends Serializable> ids) {
|
||||||
if (ids == null || ids.isEmpty()) {
|
if (ids == null || ids.isEmpty()) {
|
||||||
return Collections.emptyList();
|
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 lombok.Getter;
|
||||||
import org.jooq.Condition;
|
import org.jooq.Condition;
|
||||||
import org.jooq.DSLContext;
|
import org.jooq.DSLContext;
|
||||||
|
import org.jooq.impl.DSL;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.data.domain.Page;
|
import org.springframework.data.domain.Page;
|
||||||
import org.springframework.data.domain.PageImpl;
|
import org.springframework.data.domain.PageImpl;
|
||||||
|
@ -14,6 +15,7 @@ import org.springframework.stereotype.Repository;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.stream.Collectors;
|
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)))
|
.where(PROJECT.GROUP_ID.eq(groupId).and(PROJECT.DELETED.eq(false)))
|
||||||
.fetchInto(Integer.class);
|
.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)
|
CONSTRAINT uk_database_type_deleted_deleted_token UNIQUE (database_type, deleted, deleted_token)
|
||||||
) CHARSET utf8mb4
|
) CHARSET utf8mb4
|
||||||
COLLATE utf8mb4_unicode_ci COMMENT 'customer database types';
|
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