feat: init api
This commit is contained in:
parent
643d182d5f
commit
a924ae5bb8
|
@ -0,0 +1,5 @@
|
|||
rootProject.name = 'api'
|
||||
include 'plugin'
|
||||
include 'common'
|
||||
include 'dao'
|
||||
include 'core'
|
|
@ -0,0 +1,10 @@
|
|||
FROM openjdk:11.0.13-jre
|
||||
ARG service_name_folder
|
||||
WORKDIR /app
|
||||
ADD databasir.jar /app
|
||||
EXPOSE 8080
|
||||
|
||||
#-Ddatabasir.datasource.username=${databasir.datasource.username}
|
||||
#-Ddatabasir.datasource.password=${databasir.datasource.password}
|
||||
#-Ddatabasir.datasource.url=${databasir.datasource.url}
|
||||
ENTRYPOINT ["sh", "-c","java ${JAVA_OPTS} -jar /app/databasir.jar"]
|
|
@ -0,0 +1,41 @@
|
|||
plugins {
|
||||
id 'io.spring.dependency-management'
|
||||
id 'org.springframework.boot' apply false
|
||||
}
|
||||
|
||||
bootJar {
|
||||
archiveBaseName = 'databasir'
|
||||
archiveVersion = ''
|
||||
enabled = true
|
||||
}
|
||||
|
||||
bootBuildImage {
|
||||
imageName = "${project.group}/databasir:${project.version}"
|
||||
publish = false
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(":common")
|
||||
implementation project(":plugin")
|
||||
implementation project(":core")
|
||||
implementation project(":dao")
|
||||
|
||||
implementation 'org.springframework.boot:spring-boot-starter-web'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-validation'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-aop'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-security'
|
||||
implementation 'org.flywaydb:flyway-core'
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Docker
|
||||
*/
|
||||
task copyDockerfile(type: Copy) {
|
||||
from("Dockerfile")
|
||||
into("build/libs")
|
||||
}
|
||||
|
||||
bootJar.finalizedBy copyDockerfile
|
||||
assemble.finalizedBy copyDockerfile
|
|
@ -0,0 +1,13 @@
|
|||
package com.databasir;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.autoconfigure.r2dbc.R2dbcAutoConfiguration;
|
||||
|
||||
@SpringBootApplication(exclude = {R2dbcAutoConfiguration.class})
|
||||
public class DatabasirApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(DatabasirApplication.class, args);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
package com.databasir.api;
|
||||
|
||||
import com.databasir.common.JsonData;
|
||||
import com.databasir.core.domain.document.data.DatabaseDocumentResponse;
|
||||
import com.databasir.core.domain.document.data.DatabaseDocumentVersionResponse;
|
||||
import com.databasir.core.domain.document.service.DocumentService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.web.PageableDefault;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
@Validated
|
||||
public class DocumentController {
|
||||
|
||||
private final DocumentService documentService;
|
||||
|
||||
@PostMapping(Routes.Document.SYNC_ONE)
|
||||
public JsonData<Void> sync(@PathVariable Integer projectId) {
|
||||
documentService.syncByProjectId(projectId);
|
||||
return JsonData.ok();
|
||||
}
|
||||
|
||||
@GetMapping(Routes.Document.GET_ONE)
|
||||
public JsonData<DatabaseDocumentResponse> getByProjectId(@PathVariable Integer projectId,
|
||||
@RequestParam(required = false) Long version) {
|
||||
return documentService.getOneByProjectId(projectId, version)
|
||||
.map(JsonData::ok)
|
||||
.orElseGet(JsonData::ok);
|
||||
}
|
||||
|
||||
@GetMapping(Routes.Document.LIST_VERSIONS)
|
||||
public JsonData<Page<DatabaseDocumentVersionResponse>> getVersionsByProjectId(@PathVariable Integer projectId,
|
||||
@PageableDefault(sort = "id", direction = Sort.Direction.DESC)
|
||||
Pageable page) {
|
||||
return JsonData.ok(documentService.getVersionsBySchemaSourceId(projectId, page));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
package com.databasir.api;
|
||||
|
||||
import com.databasir.api.validator.UserOperationValidator;
|
||||
import com.databasir.common.JsonData;
|
||||
import com.databasir.core.domain.group.data.*;
|
||||
import com.databasir.core.domain.group.service.GroupService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.web.PageableDefault;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
@Validated
|
||||
public class GroupController {
|
||||
|
||||
private final GroupService groupService;
|
||||
|
||||
private final UserOperationValidator userOperationValidator;
|
||||
|
||||
@PostMapping(Routes.Group.CREATE)
|
||||
@PreAuthorize("hasAnyAuthority('SYS_OWNER')")
|
||||
public JsonData<Void> create(@RequestBody @Valid GroupCreateRequest request) {
|
||||
groupService.create(request);
|
||||
return JsonData.ok();
|
||||
}
|
||||
|
||||
@PatchMapping(Routes.Group.UPDATE)
|
||||
@PreAuthorize("hasAnyAuthority('SYS_OWNER', 'GROUP_OWNER'.concat('?groupId='.concat(#request.id)))")
|
||||
public JsonData<Void> update(@RequestBody @Valid GroupUpdateRequest request) {
|
||||
groupService.update(request);
|
||||
return JsonData.ok();
|
||||
}
|
||||
|
||||
@GetMapping(Routes.Group.LIST)
|
||||
public JsonData<Page<GroupPageResponse>> list(@PageableDefault(sort = "id", direction = Sort.Direction.DESC)
|
||||
Pageable page,
|
||||
GroupPageCondition condition) {
|
||||
return JsonData.ok(groupService.list(page, condition));
|
||||
}
|
||||
|
||||
@DeleteMapping(Routes.Group.DELETE)
|
||||
@PreAuthorize("hasAnyAuthority('SYS_OWNER', 'GROUP_OWNER'.concat('?groupId='.concat(#groupId)))")
|
||||
public JsonData<Void> deleteById(@PathVariable Integer groupId) {
|
||||
groupService.delete(groupId);
|
||||
return JsonData.ok();
|
||||
}
|
||||
|
||||
@GetMapping(Routes.Group.GET_ONE)
|
||||
public JsonData<GroupResponse> getOne(@PathVariable Integer groupId) {
|
||||
return JsonData.ok(groupService.get(groupId));
|
||||
}
|
||||
|
||||
@GetMapping(Routes.Group.MEMBERS)
|
||||
public JsonData<Page<GroupMemberPageResponse>> listGroupMembers(@PathVariable Integer groupId,
|
||||
@PageableDefault(sort = "user_role.create_at", direction = Sort.Direction.DESC)
|
||||
Pageable pageable,
|
||||
GroupMemberPageCondition condition) {
|
||||
return JsonData.ok(groupService.listGroupMembers(groupId, pageable, condition));
|
||||
}
|
||||
|
||||
@PostMapping(Routes.Group.ADD_MEMBER)
|
||||
@PreAuthorize("hasAnyAuthority('SYS_OWNER', 'GROUP_OWNER'.concat('?groupId='.concat(#groupId)))")
|
||||
public JsonData<Void> addGroupMember(@PathVariable Integer groupId,
|
||||
@RequestBody @Valid GroupMemberCreateRequest request) {
|
||||
userOperationValidator.forbiddenIfUpdateSelfRole(request.getUserId());
|
||||
List<String> groupRoles = Arrays.asList("GROUP_OWNER", "GROUP_MEMBER");
|
||||
if (!groupRoles.contains(request.getRole())) {
|
||||
throw new IllegalArgumentException("role should be GROUP_OWNER or GROUP_MEMBER");
|
||||
}
|
||||
groupService.addMember(groupId, request);
|
||||
return JsonData.ok();
|
||||
}
|
||||
|
||||
@DeleteMapping(Routes.Group.DELETE_MEMBER)
|
||||
@PreAuthorize("hasAnyAuthority('SYS_OWNER', 'GROUP_OWNER'.concat('?groupId='.concat(#groupId)))")
|
||||
public JsonData<Void> removeGroupMember(@PathVariable Integer groupId,
|
||||
@PathVariable Integer userId) {
|
||||
userOperationValidator.forbiddenIfUpdateSelfRole(userId);
|
||||
groupService.removeMember(groupId, userId);
|
||||
return JsonData.ok();
|
||||
}
|
||||
|
||||
@PatchMapping(Routes.Group.UPDATE_MEMBER)
|
||||
@PreAuthorize("hasAnyAuthority('SYS_OWNER', 'GROUP_OWNER'.concat('?groupId='.concat(#groupId)))")
|
||||
public JsonData<Void> updateGroupMemberRole(@PathVariable Integer groupId,
|
||||
@PathVariable Integer userId,
|
||||
@RequestBody GroupMemberRoleUpdateRequest request) {
|
||||
userOperationValidator.forbiddenIfUpdateSelfRole(userId);
|
||||
List<String> groupRoles = Arrays.asList("GROUP_OWNER", "GROUP_MEMBER");
|
||||
if (!groupRoles.contains(request.getRole())) {
|
||||
throw new IllegalArgumentException("role should be GROUP_OWNER or GROUP_MEMBER");
|
||||
}
|
||||
groupService.changeMemberRole(groupId, userId, request.getRole());
|
||||
return JsonData.ok();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
package com.databasir.api;
|
||||
|
||||
import com.databasir.common.DatabasirException;
|
||||
import com.databasir.common.JsonData;
|
||||
import com.databasir.common.exception.InvalidTokenException;
|
||||
import com.databasir.core.domain.DomainErrors;
|
||||
import com.databasir.core.domain.login.data.AccessTokenRefreshRequest;
|
||||
import com.databasir.core.domain.login.data.AccessTokenRefreshResponse;
|
||||
import com.databasir.core.domain.login.service.LoginService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.validation.Valid;
|
||||
import java.util.Objects;
|
||||
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
@Validated
|
||||
@Slf4j
|
||||
public class LoginController {
|
||||
|
||||
private final AuthenticationManager authenticationManager;
|
||||
|
||||
private final LoginService loginService;
|
||||
|
||||
@GetMapping(Routes.Login.LOGOUT)
|
||||
public JsonData<Void> logout() {
|
||||
SecurityContextHolder.clearContext();
|
||||
return JsonData.ok();
|
||||
}
|
||||
|
||||
@PostMapping(Routes.Login.REFRESH_ACCESS_TOKEN)
|
||||
public JsonData<AccessTokenRefreshResponse> refreshAccessTokens(@RequestBody @Valid AccessTokenRefreshRequest request,
|
||||
HttpServletResponse response) {
|
||||
try {
|
||||
return JsonData.ok(loginService.refreshAccessTokens(request));
|
||||
} catch (DatabasirException e) {
|
||||
if (Objects.equals(e.getErrCode(), DomainErrors.ACCESS_TOKEN_REFRESH_INVALID.getErrCode())) {
|
||||
throw new InvalidTokenException(DomainErrors.ACCESS_TOKEN_REFRESH_INVALID);
|
||||
}
|
||||
if (Objects.equals(e.getErrCode(), DomainErrors.REFRESH_TOKEN_EXPIRED.getErrCode())) {
|
||||
throw new InvalidTokenException(DomainErrors.REFRESH_TOKEN_EXPIRED);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
package com.databasir.api;
|
||||
|
||||
import com.databasir.common.JsonData;
|
||||
import com.databasir.core.domain.project.data.*;
|
||||
import com.databasir.core.domain.project.service.ProjectService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.web.PageableDefault;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.validation.Valid;
|
||||
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
@Validated
|
||||
public class ProjectController {
|
||||
|
||||
private final ProjectService projectService;
|
||||
|
||||
@PostMapping(Routes.GroupProject.CREATE)
|
||||
@PreAuthorize("hasAnyAuthority('SYS_OWNER', 'GROUP_OWNER?groupId='+#request.groupId, 'GROUP_MEMBER?groupId='+#request.groupId)")
|
||||
public JsonData<Void> create(@RequestBody @Valid ProjectCreateRequest request) {
|
||||
projectService.create(request);
|
||||
return JsonData.ok();
|
||||
}
|
||||
|
||||
@PatchMapping(Routes.GroupProject.UPDATE)
|
||||
@PreAuthorize("hasAnyAuthority('SYS_OWNER', 'GROUP_OWNER?groupId='+#groupId, 'GROUP_MEMBER?groupId='+#groupId)")
|
||||
public JsonData<Void> update(@RequestBody @Valid ProjectUpdateRequest request,
|
||||
@PathVariable Integer groupId) {
|
||||
projectService.update(groupId, request);
|
||||
return JsonData.ok();
|
||||
}
|
||||
|
||||
@DeleteMapping(Routes.GroupProject.DELETE)
|
||||
@PreAuthorize("hasAnyAuthority('SYS_OWNER', 'GROUP_OWNER?groupId='+#groupId, 'GROUP_MEMBER?groupId='+#groupId)")
|
||||
public JsonData<Void> delete(@PathVariable Integer groupId,
|
||||
@PathVariable Integer projectId) {
|
||||
projectService.delete(projectId);
|
||||
return JsonData.ok();
|
||||
}
|
||||
|
||||
@GetMapping(Routes.GroupProject.GET_ONE)
|
||||
public JsonData<ProjectDetailResponse> getOne(@PathVariable Integer projectId) {
|
||||
return JsonData.ok(projectService.getOne(projectId));
|
||||
}
|
||||
|
||||
@GetMapping(Routes.GroupProject.LIST)
|
||||
public JsonData<Page<ProjectSimpleResponse>> list(@PageableDefault(sort = "id", direction = Sort.Direction.DESC)
|
||||
Pageable page,
|
||||
ProjectListCondition condition) {
|
||||
return JsonData.ok(projectService.list(page, condition));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
package com.databasir.api;
|
||||
|
||||
public interface Routes {
|
||||
|
||||
String BASE = "/api/v1.0";
|
||||
|
||||
interface User {
|
||||
|
||||
String LIST = BASE + "/users";
|
||||
|
||||
String GET_ONE = BASE + "/users/{userId}";
|
||||
|
||||
String ENABLE = BASE + "/users/{userId}/enable";
|
||||
|
||||
String DISABLE = BASE + "/users/{userId}/disable";
|
||||
|
||||
String CREATE = BASE + "/users";
|
||||
|
||||
String UPDATE_PASSWORD = BASE + "/users/{userId}/password";
|
||||
|
||||
String UPDATE_NICKNAME = BASE + "/users/{userId}/nickname";
|
||||
|
||||
String RENEW_PASSWORD = BASE + "/users/{userId}/renew_password";
|
||||
|
||||
String ADD_OR_REMOVE_SYS_OWNER = BASE + "/users/{userId}/sys_owners";
|
||||
}
|
||||
|
||||
interface Group {
|
||||
|
||||
String LIST = BASE + "/groups";
|
||||
|
||||
String GET_ONE = BASE + "/groups/{groupId}";
|
||||
|
||||
String CREATE = BASE + "/groups";
|
||||
|
||||
String UPDATE = BASE + "/groups";
|
||||
|
||||
String DELETE = BASE + "/groups/{groupId}";
|
||||
|
||||
String MEMBERS = GET_ONE + "/members";
|
||||
|
||||
String DELETE_MEMBER = GET_ONE + "/members/{userId}";
|
||||
|
||||
String ADD_MEMBER = GET_ONE + "/members";
|
||||
|
||||
String UPDATE_MEMBER = GET_ONE + "/members/{userId}";
|
||||
}
|
||||
|
||||
interface GroupProject {
|
||||
|
||||
String LIST = BASE + "/projects";
|
||||
|
||||
String GET_ONE = BASE + "/projects/{projectId}";
|
||||
|
||||
String CREATE = BASE + "/projects";
|
||||
|
||||
String UPDATE = BASE + "/groups/{groupId}/projects";
|
||||
|
||||
String DELETE = BASE + "/groups/{groupId}/projects/{projectId}";
|
||||
}
|
||||
|
||||
interface Document {
|
||||
|
||||
String GET_ONE = BASE + "/projects/{projectId}/documents";
|
||||
|
||||
String SYNC_ONE = BASE + "/projects/{projectId}/documents";
|
||||
|
||||
String LIST_VERSIONS = BASE + "/projects/{projectId}/document_versions";
|
||||
}
|
||||
|
||||
interface Setting {
|
||||
|
||||
String GET_SYS_EMAIL = BASE + "/settings/sys_email";
|
||||
|
||||
String UPDATE_SYS_EMAIL = BASE + "/settings/sys_email";
|
||||
}
|
||||
|
||||
interface Login {
|
||||
|
||||
String LOGOUT = "/logout";
|
||||
|
||||
String REFRESH_ACCESS_TOKEN = "/access_tokens";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
package com.databasir.api;
|
||||
|
||||
|
||||
import com.databasir.common.JsonData;
|
||||
import com.databasir.core.domain.system.data.SystemEmailResponse;
|
||||
import com.databasir.core.domain.system.data.SystemEmailUpdateRequest;
|
||||
import com.databasir.core.domain.system.service.SystemService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.validation.Valid;
|
||||
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
@Validated
|
||||
@PreAuthorize("hasAnyAuthority('SYS_OWNER')")
|
||||
public class SettingController {
|
||||
|
||||
private final SystemService systemService;
|
||||
|
||||
@GetMapping(Routes.Setting.GET_SYS_EMAIL)
|
||||
public JsonData<SystemEmailResponse> getSystemEmailSetting() {
|
||||
return systemService.getEmailSetting()
|
||||
.map(JsonData::ok)
|
||||
.orElseGet(JsonData::ok);
|
||||
}
|
||||
|
||||
@PostMapping(Routes.Setting.UPDATE_SYS_EMAIL)
|
||||
public JsonData<Void> updateSystemEmailSetting(@RequestBody @Valid SystemEmailUpdateRequest request) {
|
||||
systemService.updateEmailSetting(request);
|
||||
return JsonData.ok();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
package com.databasir.api;
|
||||
|
||||
import com.databasir.api.validator.UserOperationValidator;
|
||||
import com.databasir.common.JsonData;
|
||||
import com.databasir.common.exception.Forbidden;
|
||||
import com.databasir.core.domain.user.data.*;
|
||||
import com.databasir.core.domain.user.service.UserService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.web.PageableDefault;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.validation.Valid;
|
||||
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
@Validated
|
||||
public class UserController {
|
||||
|
||||
private final UserService userService;
|
||||
|
||||
private final UserOperationValidator userOperationValidator;
|
||||
|
||||
@GetMapping(Routes.User.LIST)
|
||||
public JsonData<Page<UserPageResponse>> list(@PageableDefault(sort = "id", direction = Sort.Direction.DESC)
|
||||
Pageable pageable,
|
||||
UserPageCondition condition) {
|
||||
return JsonData.ok(userService.list(pageable, condition));
|
||||
}
|
||||
|
||||
@PostMapping(Routes.User.DISABLE)
|
||||
@PreAuthorize("hasAnyAuthority('SYS_OWNER')")
|
||||
public JsonData<Void> disableUser(@PathVariable Integer userId) {
|
||||
userService.switchEnableStatus(userId, false);
|
||||
return JsonData.ok();
|
||||
}
|
||||
|
||||
@PostMapping(Routes.User.ENABLE)
|
||||
@PreAuthorize("hasAnyAuthority('SYS_OWNER')")
|
||||
public JsonData<Void> enableUser(@PathVariable Integer userId) {
|
||||
userService.switchEnableStatus(userId, true);
|
||||
return JsonData.ok();
|
||||
}
|
||||
|
||||
@PostMapping(Routes.User.CREATE)
|
||||
@PreAuthorize("hasAnyAuthority('SYS_OWNER')")
|
||||
public JsonData<Void> create(@RequestBody @Valid UserCreateRequest request) {
|
||||
userService.create(request);
|
||||
return JsonData.ok();
|
||||
}
|
||||
|
||||
@GetMapping(Routes.User.GET_ONE)
|
||||
public JsonData<UserDetailResponse> getOne(@PathVariable Integer userId) {
|
||||
return JsonData.ok(userService.get(userId));
|
||||
}
|
||||
|
||||
@PostMapping(Routes.User.RENEW_PASSWORD)
|
||||
@PreAuthorize("hasAnyAuthority('SYS_OWNER')")
|
||||
public JsonData<Void> renewPassword(@PathVariable Integer userId) {
|
||||
userService.renewPassword(userId);
|
||||
return JsonData.ok();
|
||||
}
|
||||
|
||||
@PostMapping(Routes.User.ADD_OR_REMOVE_SYS_OWNER)
|
||||
@PreAuthorize("hasAnyAuthority('SYS_OWNER')")
|
||||
public JsonData<Void> addSysOwner(@PathVariable Integer userId) {
|
||||
userOperationValidator.forbiddenIfUpdateSelfRole(userId);
|
||||
userService.addSysOwnerTo(userId);
|
||||
return JsonData.ok();
|
||||
}
|
||||
|
||||
@DeleteMapping(Routes.User.ADD_OR_REMOVE_SYS_OWNER)
|
||||
@PreAuthorize("hasAnyAuthority('SYS_OWNER')")
|
||||
public JsonData<Void> removeSysOwner(@PathVariable Integer userId) {
|
||||
userOperationValidator.forbiddenIfUpdateSelfRole(userId);
|
||||
userService.removeSysOwnerFrom(userId);
|
||||
return JsonData.ok();
|
||||
}
|
||||
|
||||
@PostMapping(Routes.User.UPDATE_PASSWORD)
|
||||
public JsonData<Void> updatePassword(@PathVariable Integer userId,
|
||||
@RequestBody @Valid UserPasswordUpdateRequest request) {
|
||||
if (userOperationValidator.isMyself(userId)) {
|
||||
userService.updatePassword(userId, request);
|
||||
return JsonData.ok();
|
||||
} else {
|
||||
throw new Forbidden();
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping(Routes.User.UPDATE_NICKNAME)
|
||||
public JsonData<Void> updateNickname(@PathVariable Integer userId,
|
||||
@RequestBody @Valid UserNicknameUpdateRequest request) {
|
||||
if (userOperationValidator.isMyself(userId)) {
|
||||
userService.updateNickname(userId, request);
|
||||
return JsonData.ok();
|
||||
} else {
|
||||
throw new Forbidden();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,234 @@
|
|||
package com.databasir.api.advice;
|
||||
|
||||
|
||||
import com.databasir.common.DatabasirException;
|
||||
import com.databasir.common.JsonData;
|
||||
import com.databasir.common.SystemException;
|
||||
import com.databasir.common.exception.Forbidden;
|
||||
import com.databasir.common.exception.InvalidTokenException;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.TypeMismatchException;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.security.authentication.BadCredentialsException;
|
||||
import org.springframework.validation.BindException;
|
||||
import org.springframework.validation.BindingResult;
|
||||
import org.springframework.validation.FieldError;
|
||||
import org.springframework.validation.ObjectError;
|
||||
import org.springframework.web.HttpRequestMethodNotSupportedException;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.MissingServletRequestParameterException;
|
||||
import org.springframework.web.bind.ServletRequestBindingException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
import org.springframework.web.context.request.WebRequest;
|
||||
import org.springframework.web.multipart.support.MissingServletRequestPartException;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
|
||||
|
||||
import javax.validation.ConstraintViolation;
|
||||
import javax.validation.ConstraintViolationException;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
@RestControllerAdvice
|
||||
@Slf4j
|
||||
public class DatabasirExceptionAdvice extends ResponseEntityExceptionHandler {
|
||||
|
||||
@ExceptionHandler({ConstraintViolationException.class})
|
||||
public ResponseEntity<Object> handleConstraintViolationException(
|
||||
ConstraintViolationException constraintViolationException, WebRequest request) {
|
||||
|
||||
String errorMsg = "";
|
||||
String path = getPath(request);
|
||||
Set<ConstraintViolation<?>> violations = constraintViolationException.getConstraintViolations();
|
||||
for (ConstraintViolation<?> item : violations) {
|
||||
errorMsg = item.getMessage();
|
||||
log.warn("ConstraintViolationException, request: {}, exception: {}, invalid value: {}",
|
||||
path, errorMsg, item.getInvalidValue());
|
||||
break;
|
||||
}
|
||||
return handleNon200Response(errorMsg, HttpStatus.BAD_REQUEST, path);
|
||||
}
|
||||
|
||||
@ExceptionHandler({InvalidTokenException.class})
|
||||
protected ResponseEntity<Object> handleInvalidTokenException(InvalidTokenException ex, WebRequest request) {
|
||||
String path = getPath(request);
|
||||
log.warn("handle InvalidTokenException " + path + ", " + ex);
|
||||
JsonData<Object> data = JsonData.error(ex.getErrCode(), ex.getErrMessage());
|
||||
return handleNon200Response(ex.getMessage(), HttpStatus.UNAUTHORIZED, path, data);
|
||||
}
|
||||
|
||||
@ExceptionHandler({IllegalArgumentException.class})
|
||||
protected ResponseEntity<Object> handleIllegalArgument(IllegalArgumentException ex, WebRequest request) {
|
||||
String path = getPath(request);
|
||||
log.warn("handle illegalArgument " + path, ex);
|
||||
return handleNon200Response(ex.getMessage(), HttpStatus.BAD_REQUEST, path);
|
||||
}
|
||||
|
||||
@ExceptionHandler({AccessDeniedException.class})
|
||||
protected ResponseEntity<Object> handleAccessDeniedException(AccessDeniedException ex, WebRequest request) {
|
||||
String path = getPath(request);
|
||||
log.warn("AccessDeniedException, request: {}, exception: {}", path, ex.getMessage());
|
||||
return handleNon200Response(ex.getMessage(), HttpStatus.FORBIDDEN, path);
|
||||
}
|
||||
|
||||
@ExceptionHandler({Forbidden.class})
|
||||
protected ResponseEntity<Object> handleForbiddenException(Forbidden ex, WebRequest request) {
|
||||
String path = getPath(request);
|
||||
log.warn("Forbidden, request: {}, exception: {}", path, ex.getMessage());
|
||||
return handleNon200Response(ex.getMessage(), HttpStatus.FORBIDDEN, path);
|
||||
}
|
||||
|
||||
@ExceptionHandler(value = DatabasirException.class)
|
||||
public ResponseEntity<Object> handleBusinessException(
|
||||
DatabasirException databasirException, WebRequest request) {
|
||||
|
||||
String path = getPath(request);
|
||||
JsonData<Void> body = JsonData.error(databasirException.getErrCode(), databasirException.getErrMessage());
|
||||
if (databasirException.getCause() == null) {
|
||||
log.warn("BusinessException, request: {}, exception: {}", path, databasirException.getErrMessage());
|
||||
} else {
|
||||
log.warn("BusinessException, request: " + path, databasirException);
|
||||
}
|
||||
return ResponseEntity.ok().body(body);
|
||||
}
|
||||
|
||||
@ExceptionHandler({SystemException.class})
|
||||
public ResponseEntity<Object> handleSystemException(SystemException systemException, WebRequest request) {
|
||||
|
||||
String path = getPath(request);
|
||||
if (systemException.getCause() != null) {
|
||||
log.error("SystemException, request: " + path
|
||||
+ ", exception: " + systemException.getMessage() + ", caused by:", systemException.getCause());
|
||||
} else {
|
||||
log.error("SystemException, request: " + path + ", exception: " + systemException.getMessage());
|
||||
}
|
||||
return handleNon200Response(systemException.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR, path);
|
||||
}
|
||||
|
||||
@ExceptionHandler({Exception.class})
|
||||
public ResponseEntity<Object> handleUnspecificException(Exception ex, WebRequest request) {
|
||||
|
||||
String path = getPath(request);
|
||||
String errorMsg = ex.getMessage();
|
||||
log.error("Unspecific exception, request: " + path + ", exception: " + errorMsg + ":", ex);
|
||||
return handleNon200Response(errorMsg, HttpStatus.INTERNAL_SERVER_ERROR, path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseEntity<Object> handleBindException(
|
||||
BindException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
|
||||
|
||||
String errorMsg = buildMessages(ex.getBindingResult());
|
||||
log.warn("BindException, request: {}, exception: {}", getPath(request), errorMsg);
|
||||
return handleOverriddenException(ex, headers, status, request, errorMsg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseEntity<Object> handleMethodArgumentNotValid(
|
||||
MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
|
||||
|
||||
String errorMsg = buildMessages(ex.getBindingResult());
|
||||
log.warn("MethodArgumentNotValidException, request: {}, exception: {}", getPath(request), errorMsg);
|
||||
return handleOverriddenException(ex, headers, status, request, errorMsg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseEntity<Object> handleTypeMismatch(
|
||||
TypeMismatchException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
|
||||
|
||||
log.warn("TypeMismatchException, request: {}, exception: {}", getPath(request), ex.getMessage());
|
||||
return handleOverriddenException(ex, headers, status, request, ex.getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseEntity<Object> handleMissingServletRequestParameter(
|
||||
MissingServletRequestParameterException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
|
||||
|
||||
log.warn("MissingServletRequestParameterException, request: {}, exception: {}",
|
||||
getPath(request), ex.getMessage());
|
||||
return handleOverriddenException(ex, headers, status, request, ex.getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseEntity<Object> handleMissingServletRequestPart(
|
||||
MissingServletRequestPartException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
|
||||
|
||||
log.warn("MissingServletRequestPartException, request: {}, exception: {}", getPath(request), ex.getMessage());
|
||||
return handleOverriddenException(ex, headers, status, request, ex.getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseEntity<Object> handleHttpMessageNotReadable(
|
||||
HttpMessageNotReadableException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
|
||||
|
||||
String errorMsg = ex.getMostSpecificCause().getMessage();
|
||||
|
||||
log.warn("HttpMessageNotReadableException, request: {}, exception: {}", getPath(request), errorMsg);
|
||||
return handleOverriddenException(ex, headers, status, request, errorMsg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseEntity<Object> handleServletRequestBindingException(
|
||||
ServletRequestBindingException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
|
||||
|
||||
log.warn("ServletRequestBindingException, request: {}, exception: {}", getPath(request), ex.getMessage());
|
||||
return handleOverriddenException(ex, headers, status, request, ex.getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseEntity<Object> handleHttpRequestMethodNotSupported(
|
||||
HttpRequestMethodNotSupportedException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
|
||||
|
||||
String errorMsg = ex.getMessage();
|
||||
log.warn("HttpRequestMethodNotSupportedException, request: {}, exception: {}", getPath(request), errorMsg);
|
||||
return handleOverriddenException(ex, headers, status, request, ex.getMessage());
|
||||
}
|
||||
|
||||
@ExceptionHandler({BadCredentialsException.class})
|
||||
protected ResponseEntity<Object> handleBadCredentialsException(BadCredentialsException ex, WebRequest request) {
|
||||
String path = getPath(request);
|
||||
JsonData<Void> body = JsonData.error("-1", "用户名或密码错误");
|
||||
log.warn("BadCredentialsException, request: {}, exception: {}", path, ex.getMessage());
|
||||
return ResponseEntity.ok().body(body);
|
||||
}
|
||||
|
||||
private String buildMessages(BindingResult result) {
|
||||
|
||||
StringBuilder resultBuilder = new StringBuilder();
|
||||
List<ObjectError> errors = result.getAllErrors();
|
||||
for (ObjectError error : errors) {
|
||||
if (error instanceof FieldError) {
|
||||
FieldError fieldError = (FieldError) error;
|
||||
String fieldName = fieldError.getField();
|
||||
String fieldErrMsg = fieldError.getDefaultMessage();
|
||||
resultBuilder.append(fieldName).append(" ").append(fieldErrMsg);
|
||||
}
|
||||
}
|
||||
return resultBuilder.toString();
|
||||
}
|
||||
|
||||
private ResponseEntity<Object> handleNon200Response(String errorMsg, HttpStatus httpStatus, String path) {
|
||||
return ResponseEntity.status(httpStatus).body(null);
|
||||
}
|
||||
|
||||
private ResponseEntity<Object> handleNon200Response(String errorMsg,
|
||||
HttpStatus httpStatus,
|
||||
String path,
|
||||
Object body) {
|
||||
return ResponseEntity.status(httpStatus).body(body);
|
||||
}
|
||||
|
||||
private ResponseEntity<Object> handleOverriddenException(
|
||||
Exception ex, HttpHeaders headers, HttpStatus status, WebRequest request, String errorMsg) {
|
||||
return handleExceptionInternal(ex, null, headers, status, request);
|
||||
}
|
||||
|
||||
private String getPath(WebRequest request) {
|
||||
String description = request.getDescription(false);
|
||||
return description.startsWith("uri=") ? description.substring(4) : description;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
package com.databasir.api.config;
|
||||
|
||||
import com.databasir.api.Routes;
|
||||
import com.databasir.api.config.security.*;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||
|
||||
@Configuration
|
||||
@RequiredArgsConstructor
|
||||
@EnableGlobalMethodSecurity(prePostEnabled = true)
|
||||
public class SecurityConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
private final DatabasirUserDetailService databasirUserDetailService;
|
||||
|
||||
private final DatabasirAuthenticationEntryPoint databasirAuthenticationEntryPoint;
|
||||
|
||||
private final DatabasirJwtTokenFilter databasirJwtTokenFilter;
|
||||
|
||||
private final DatabasirAuthenticationFailureHandler databasirAuthenticationFailureHandler;
|
||||
|
||||
private final DatabasirAuthenticationSuccessHandler databasirAuthenticationSuccessHandler;
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http.headers().frameOptions().disable();
|
||||
http.csrf().disable();
|
||||
http.cors();
|
||||
|
||||
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
|
||||
.and()
|
||||
.formLogin()
|
||||
.loginProcessingUrl("/login")
|
||||
.failureHandler(databasirAuthenticationFailureHandler)
|
||||
.successHandler(databasirAuthenticationSuccessHandler)
|
||||
.and()
|
||||
.authorizeRequests()
|
||||
.antMatchers("/login", Routes.Login.REFRESH_ACCESS_TOKEN).permitAll()
|
||||
.anyRequest().authenticated()
|
||||
.and()
|
||||
.exceptionHandling().authenticationEntryPoint(databasirAuthenticationEntryPoint);
|
||||
|
||||
http.addFilterBefore(
|
||||
databasirJwtTokenFilter,
|
||||
UsernamePasswordAuthenticationFilter.class
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
|
||||
auth.userDetailsService(databasirUserDetailService)
|
||||
.passwordEncoder(bCryptPasswordEncoder());
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Override
|
||||
public AuthenticationManager authenticationManagerBean() throws Exception {
|
||||
return super.authenticationManagerBean();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public PasswordEncoder bCryptPasswordEncoder() {
|
||||
return new BCryptPasswordEncoder();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
package com.databasir.api.config;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.web.config.EnableSpringDataWebSupport;
|
||||
import org.springframework.web.servlet.config.annotation.CorsRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
|
||||
|
||||
@Configuration
|
||||
@EnableSpringDataWebSupport
|
||||
public class WebConfig extends WebMvcConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
public void addCorsMappings(CorsRegistry registry) {
|
||||
registry.addMapping("/**")
|
||||
.allowedOrigins("*")
|
||||
.allowedMethods("GET", "POST", "DELETE", "PATCH", "PUT");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package com.databasir.api.config.security;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.web.AuthenticationEntryPoint;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class DatabasirAuthenticationEntryPoint implements AuthenticationEntryPoint {
|
||||
|
||||
@Override
|
||||
public void commence(javax.servlet.http.HttpServletRequest request,
|
||||
javax.servlet.http.HttpServletResponse response,
|
||||
AuthenticationException authException) throws IOException, ServletException {
|
||||
log.warn("验证未通过. 提示信息 - {}", authException.getMessage());
|
||||
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException.getMessage());
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
package com.databasir.api.config.security;
|
||||
|
||||
import com.databasir.common.JsonData;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.authentication.BadCredentialsException;
|
||||
import org.springframework.security.authentication.DisabledException;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class DatabasirAuthenticationFailureHandler implements AuthenticationFailureHandler {
|
||||
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
@Override
|
||||
public void onAuthenticationFailure(HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
AuthenticationException exception) throws IOException, ServletException {
|
||||
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
|
||||
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
||||
if (exception instanceof BadCredentialsException) {
|
||||
JsonData<Void> data = JsonData.error("-1", "用户名或密码错误");
|
||||
String jsonString = objectMapper.writeValueAsString(data);
|
||||
response.setStatus(HttpStatus.OK.value());
|
||||
response.getOutputStream().write(jsonString.getBytes(StandardCharsets.UTF_8));
|
||||
} else if (exception instanceof DisabledException) {
|
||||
JsonData<Void> data = JsonData.error("-1", "用户已禁用");
|
||||
String jsonString = objectMapper.writeValueAsString(data);
|
||||
response.setStatus(HttpStatus.OK.value());
|
||||
response.getOutputStream().write(jsonString.getBytes(StandardCharsets.UTF_8));
|
||||
} else {
|
||||
JsonData<Void> data = JsonData.error("-1", "未登录或未授权用户");
|
||||
String jsonString = objectMapper.writeValueAsString(data);
|
||||
response.setStatus(HttpStatus.UNAUTHORIZED.value());
|
||||
response.getOutputStream().write(jsonString.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
package com.databasir.api.config.security;
|
||||
|
||||
import com.databasir.common.JsonData;
|
||||
import com.databasir.core.domain.login.data.LoginKeyResponse;
|
||||
import com.databasir.core.domain.login.service.LoginService;
|
||||
import com.databasir.core.domain.user.data.UserLoginResponse;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.ZoneId;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class DatabasirAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
|
||||
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
private final LoginService loginService;
|
||||
|
||||
@Override
|
||||
public void onAuthenticationSuccess(HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
Authentication authentication) throws IOException, ServletException {
|
||||
DatabasirUserDetails user = (DatabasirUserDetails) authentication.getPrincipal();
|
||||
List<UserLoginResponse.RoleResponse> roles = user.getRoles()
|
||||
.stream()
|
||||
.map(ur -> {
|
||||
UserLoginResponse.RoleResponse data = new UserLoginResponse.RoleResponse();
|
||||
data.setRole(ur.getRole());
|
||||
data.setGroupId(ur.getGroupId());
|
||||
return data;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
|
||||
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
||||
UserLoginResponse data = new UserLoginResponse();
|
||||
data.setId(user.getUserPojo().getId());
|
||||
data.setNickname(user.getUserPojo().getNickname());
|
||||
data.setEmail(user.getUserPojo().getEmail());
|
||||
data.setUsername(user.getUsername());
|
||||
|
||||
LoginKeyResponse loginKey = loginService.generate(user.getUserPojo().getId());
|
||||
data.setAccessToken(loginKey.getAccessToken());
|
||||
long expireAt = loginKey.getAccessTokenExpireAt().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
|
||||
data.setAccessTokenExpireAt(expireAt);
|
||||
data.setRefreshToken(loginKey.getRefreshToken());
|
||||
data.setRoles(roles);
|
||||
objectMapper.writeValue(response.getWriter(), JsonData.ok(data));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
package com.databasir.api.config.security;
|
||||
|
||||
import com.databasir.core.infrastructure.jwt.JwtTokens;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class DatabasirJwtTokenFilter extends OncePerRequestFilter {
|
||||
|
||||
private static final String SPACE = " ";
|
||||
|
||||
private final JwtTokens jwtTokens;
|
||||
|
||||
private final UserDetailsService userDetailsService;
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
FilterChain filterChain) throws ServletException, IOException {
|
||||
|
||||
final String header = request.getHeader(HttpHeaders.AUTHORIZATION);
|
||||
if (!StringUtils.hasText(header) || !header.startsWith(JwtTokens.TOKEN_PREFIX)) {
|
||||
filterChain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
final String token = header.split(SPACE)[1].trim();
|
||||
if (!jwtTokens.verify(token)) {
|
||||
filterChain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
String username = jwtTokens.getUsername(token);
|
||||
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
|
||||
UsernamePasswordAuthenticationToken authentication =
|
||||
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
|
||||
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
|
||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||
filterChain.doFilter(request, response);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
package com.databasir.api.config.security;
|
||||
|
||||
import com.databasir.dao.impl.LoginDao;
|
||||
import com.databasir.dao.impl.UserDao;
|
||||
import com.databasir.dao.impl.UserRoleDao;
|
||||
import com.databasir.dao.tables.pojos.UserPojo;
|
||||
import com.databasir.dao.tables.pojos.UserRolePojo;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class DatabasirUserDetailService implements UserDetailsService {
|
||||
|
||||
private final UserDao userDao;
|
||||
|
||||
private final UserRoleDao userRoleDao;
|
||||
|
||||
private final LoginDao loginDao;
|
||||
|
||||
@Override
|
||||
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
|
||||
UserPojo user = userDao.selectByEmailOrUsername(username)
|
||||
.orElseThrow(() -> new UsernameNotFoundException("用户名或密码错误"));
|
||||
List<UserRolePojo> roles = userRoleDao.selectByUserIds(Collections.singletonList(user.getId()));
|
||||
return new DatabasirUserDetails(user, roles);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
package com.databasir.api.config.security;
|
||||
|
||||
import com.databasir.dao.tables.pojos.UserPojo;
|
||||
import com.databasir.dao.tables.pojos.UserRolePojo;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class DatabasirUserDetails implements UserDetails {
|
||||
|
||||
@Getter
|
||||
private final UserPojo userPojo;
|
||||
|
||||
@Getter
|
||||
private final List<UserRolePojo> roles;
|
||||
|
||||
@Override
|
||||
public Collection<? extends GrantedAuthority> getAuthorities() {
|
||||
return roles.stream()
|
||||
.map(role -> {
|
||||
String expression = role.getRole();
|
||||
if (role.getGroupId() != null) {
|
||||
expression += "?groupId=" + role.getGroupId();
|
||||
}
|
||||
return new SimpleGrantedAuthority(expression);
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPassword() {
|
||||
return userPojo.getPassword();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsername() {
|
||||
return userPojo.getEmail();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAccountNonExpired() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAccountNonLocked() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCredentialsNonExpired() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return userPojo.getEnabled();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package com.databasir.api.validator;
|
||||
|
||||
import com.databasir.api.config.security.DatabasirUserDetails;
|
||||
import com.databasir.core.domain.DomainErrors;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class UserOperationValidator {
|
||||
|
||||
public void forbiddenIfUpdateSelfRole(Integer userId) {
|
||||
DatabasirUserDetails principal = (DatabasirUserDetails) SecurityContextHolder.getContext()
|
||||
.getAuthentication()
|
||||
.getPrincipal();
|
||||
if (principal.getUserPojo().getId().equals(userId)) {
|
||||
throw DomainErrors.CANNOT_UPDATE_SELF_ROLE.exception();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isMyself(Integer userId) {
|
||||
DatabasirUserDetails principal = (DatabasirUserDetails) SecurityContextHolder.getContext()
|
||||
.getAuthentication()
|
||||
.getPrincipal();
|
||||
return principal.getUserPojo().getId().equals(userId);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
server.port=8080
|
||||
logging.level.org.jooq=DEBUG
|
||||
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.flyway.enabled=true
|
||||
spring.flyway.baseline-on-migrate=true
|
||||
spring.flyway.locations=classpath:db/migration
|
|
@ -0,0 +1,12 @@
|
|||
server.port=8080
|
||||
# datasource
|
||||
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
|
||||
spring.datasource.username=${databasir.datasource.username}
|
||||
spring.datasource.password=${databasir.datasource.password}
|
||||
spring.datasource.url=jdbc:mysql://${databasir.datasource.url}/${databasir.datasource.database-name:databasir}
|
||||
# jooq
|
||||
spring.jooq.sql-dialect=mysql
|
||||
# flyway
|
||||
spring.flyway.enabled=true
|
||||
spring.flyway.baseline-on-migrate=true
|
||||
spring.flyway.locations=classpath:db/migration
|
30
build.gradle
30
build.gradle
|
@ -1,23 +1,38 @@
|
|||
plugins {
|
||||
id 'java'
|
||||
id 'idea'
|
||||
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
|
||||
id 'org.springframework.boot' version '2.5.8' apply false
|
||||
id 'nu.studer.jooq' version '6.0.1' apply false
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
mavenLocal()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
subprojects {
|
||||
|
||||
apply plugin: 'java'
|
||||
apply plugin: 'idea'
|
||||
apply plugin: 'io.spring.dependency-management'
|
||||
apply plugin: 'org.springframework.boot'
|
||||
apply plugin: 'nu.studer.jooq'
|
||||
|
||||
group 'com.databasir'
|
||||
version '1.0-SNAPSHOT'
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
version '1.0.0'
|
||||
|
||||
ext {
|
||||
lombokVersion = '1.18.22'
|
||||
mapstructVersion = '1.4.2.Final'
|
||||
junitVersion = '5.7.0'
|
||||
slf4jVersion = '1.7.32'
|
||||
jooqVersion = '3.15.5'
|
||||
mysqlConnectorVersion = '8.0.27'
|
||||
postgresqlConnectorVersion = '42.3.1'
|
||||
hikariVersion = '5.0.0'
|
||||
jacksonVersion = '2.13.1'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
@ -28,10 +43,9 @@ subprojects {
|
|||
annotationProcessor "org.projectlombok:lombok:${lombokVersion}"
|
||||
|
||||
implementation "org.mapstruct:mapstruct:${mapstructVersion}"
|
||||
annotationProcessor "org.mapstruct:mapstruct:${mapstructVersion}"
|
||||
annotationProcessor "org.mapstruct:mapstruct-processor:${mapstructVersion}"
|
||||
|
||||
implementation "org.slf4j:slf4j-api:${slf4jVersion}"
|
||||
|
||||
}
|
||||
|
||||
test {
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
dependencies {
|
||||
implementation 'org.apache.shiro:shiro-crypto-cipher:1.8.0'
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
package com.databasir.common;
|
||||
|
||||
public interface DatabasirErrors {
|
||||
|
||||
String getErrCode();
|
||||
|
||||
String getErrMessage();
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
package com.databasir.common;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@NoArgsConstructor
|
||||
public class DatabasirException extends RuntimeException {
|
||||
|
||||
@Getter
|
||||
private DatabasirErrors errorCodeMessage;
|
||||
|
||||
@Getter
|
||||
private String errCode;
|
||||
|
||||
@Getter
|
||||
private String errMessage;
|
||||
|
||||
/**
|
||||
* @param errorCodeMessage 错误信息
|
||||
*/
|
||||
public DatabasirException(DatabasirErrors errorCodeMessage) {
|
||||
super(errorCodeMessage.getErrMessage());
|
||||
this.errorCodeMessage = errorCodeMessage;
|
||||
this.errCode = errorCodeMessage.getErrCode();
|
||||
this.errMessage = errorCodeMessage.getErrMessage();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param errorCodeMessage 错误信息
|
||||
* @param overrideMessage 覆盖 message
|
||||
*/
|
||||
public DatabasirException(DatabasirErrors errorCodeMessage, String overrideMessage) {
|
||||
super(overrideMessage);
|
||||
this.errorCodeMessage = errorCodeMessage;
|
||||
this.errCode = errorCodeMessage.getErrCode();
|
||||
this.errMessage = overrideMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param errorCodeMessage 错误信息
|
||||
* @param cause root cause
|
||||
*/
|
||||
public DatabasirException(DatabasirErrors errorCodeMessage, Throwable cause) {
|
||||
super(errorCodeMessage.getErrMessage(), cause);
|
||||
this.errorCodeMessage = errorCodeMessage;
|
||||
this.errCode = errorCodeMessage.getErrCode();
|
||||
this.errMessage = errorCodeMessage.getErrMessage();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
package com.databasir.common;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class JsonData<T> {
|
||||
|
||||
/**
|
||||
* maybe null
|
||||
*/
|
||||
private T data;
|
||||
|
||||
/**
|
||||
* only exists when error happened
|
||||
*/
|
||||
private String errCode;
|
||||
|
||||
/**
|
||||
* only exists when error happened
|
||||
*/
|
||||
private String errMessage;
|
||||
|
||||
public static <T> JsonData<T> ok() {
|
||||
return ok(null);
|
||||
}
|
||||
|
||||
public static <T> JsonData<T> ok(T data) {
|
||||
JsonData<T> jsonData = new JsonData<>();
|
||||
jsonData.setData(data);
|
||||
return jsonData;
|
||||
}
|
||||
|
||||
public static <T> JsonData<T> error(String errorCode, String errMessage) {
|
||||
JsonData<T> jsonData = new JsonData<>();
|
||||
jsonData.setErrCode(errorCode);
|
||||
jsonData.setErrMessage(errMessage);
|
||||
return jsonData;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package com.databasir.common;
|
||||
|
||||
public class SystemException extends RuntimeException{
|
||||
|
||||
private static final String MSG_INTERNAL_SERVER_ERROR = "服务器开小差了,请稍后再试";
|
||||
|
||||
public static final SystemException INTERNAL_SERVER_ERROR = new SystemException(MSG_INTERNAL_SERVER_ERROR);
|
||||
|
||||
/**
|
||||
* @param msg the detail message
|
||||
*/
|
||||
public SystemException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param msg the detail message
|
||||
* @param cause the nested exception
|
||||
*/
|
||||
public SystemException(String msg, Throwable cause) {
|
||||
super(msg, cause);
|
||||
}
|
||||
|
||||
public static SystemException internalServerErrorWithCause(Throwable cause) {
|
||||
return new SystemException(MSG_INTERNAL_SERVER_ERROR, cause);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
package com.databasir.common.codec;
|
||||
|
||||
import org.apache.shiro.crypto.AesCipherService;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.Key;
|
||||
import java.util.Base64;
|
||||
|
||||
public class Aes {
|
||||
|
||||
public static String encryptToBase64Data(String data, String key) {
|
||||
return new AesCipherService()
|
||||
.encrypt(toBytes(data), toBytes(key))
|
||||
.toBase64();
|
||||
}
|
||||
|
||||
public static String decryptFromBase64Data(String encryptData, String key) {
|
||||
byte[] originEncrypted = Base64.getDecoder().decode(toBytes(encryptData));
|
||||
byte[] originKey = toBytes(key);
|
||||
return toString(new AesCipherService().decrypt(originEncrypted, originKey).getBytes());
|
||||
}
|
||||
|
||||
public static String randomBase64Key() {
|
||||
Key key = new AesCipherService().generateNewKey();
|
||||
return Base64.getEncoder().encodeToString(key.getEncoded());
|
||||
}
|
||||
|
||||
private static byte[] toBytes(String data) {
|
||||
return data.getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
private static String toString(byte[] bytes) {
|
||||
return new String(bytes, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
package com.databasir.common.codec;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.*;
|
||||
import java.security.spec.EncodedKeySpec;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
import java.util.Base64;
|
||||
|
||||
public class Rsa {
|
||||
|
||||
private static final String RSA = "RSA";
|
||||
|
||||
@SneakyThrows
|
||||
public static String encryptToBase64DataByPublicKey(String data, String publicBase64Key) {
|
||||
Cipher encryptCipher = Cipher.getInstance(RSA);
|
||||
encryptCipher.init(Cipher.ENCRYPT_MODE, decodeBase64PublicKey(publicBase64Key));
|
||||
byte[] dataBytes = data.getBytes(StandardCharsets.UTF_8);
|
||||
byte[] encryptedDataBytes = encryptCipher.doFinal(dataBytes);
|
||||
return Base64.getEncoder().encodeToString(encryptedDataBytes);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static String decryptFromBase64DataByPrivateKey(String data, String privateKey) {
|
||||
Cipher decryptCipher = Cipher.getInstance(RSA);
|
||||
decryptCipher.init(Cipher.DECRYPT_MODE, decodeBase64PrivateKey(privateKey));
|
||||
byte[] dataBytes = Base64.getDecoder().decode(data);
|
||||
byte[] decryptedBytes = decryptCipher.doFinal(dataBytes);
|
||||
return new String(decryptedBytes, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private static PublicKey decodeBase64PublicKey(String base64PublicKey) {
|
||||
byte[] keyBytes = Base64.getDecoder().decode(base64PublicKey);
|
||||
KeyFactory keyFactory = KeyFactory.getInstance(RSA);
|
||||
EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(keyBytes);
|
||||
return keyFactory.generatePublic(publicKeySpec);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private static PrivateKey decodeBase64PrivateKey(String base64PrivateKey) {
|
||||
byte[] keyBytes = Base64.getDecoder().decode(base64PrivateKey);
|
||||
KeyFactory keyFactory = KeyFactory.getInstance(RSA);
|
||||
EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(keyBytes);
|
||||
return keyFactory.generatePrivate(privateKeySpec);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static RsaBase64Key generateBase64Key() {
|
||||
KeyPairGenerator generator = KeyPairGenerator.getInstance(RSA);
|
||||
generator.initialize(2048);
|
||||
KeyPair keyPair = generator.generateKeyPair();
|
||||
String privateKey = Base64.getEncoder().encodeToString(keyPair.getPrivate().getEncoded());
|
||||
String publicKey = Base64.getEncoder().encodeToString(keyPair.getPublic().getEncoded());
|
||||
return new RsaBase64Key(privateKey, publicKey);
|
||||
}
|
||||
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public static class RsaBase64Key {
|
||||
|
||||
private final String privateBase64Key;
|
||||
|
||||
private final String publicBase64Key;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
package com.databasir.common.codec;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import org.apache.shiro.codec.Hex;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
|
||||
public class Sha {
|
||||
|
||||
@SneakyThrows
|
||||
public static String sha256(String data) {
|
||||
MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
|
||||
messageDigest.update(data.getBytes());
|
||||
byte[] bytes = messageDigest.digest();
|
||||
return Hex.encodeToString(bytes);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
package com.databasir.common.exception;
|
||||
|
||||
public class Forbidden extends RuntimeException{
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
package com.databasir.common.exception;
|
||||
|
||||
import com.databasir.common.DatabasirErrors;
|
||||
import com.databasir.common.DatabasirException;
|
||||
|
||||
public class InvalidTokenException extends DatabasirException {
|
||||
|
||||
public InvalidTokenException(DatabasirErrors errorCodeMessage) {
|
||||
super(errorCodeMessage);
|
||||
}
|
||||
|
||||
public InvalidTokenException(DatabasirErrors errorCodeMessage, String overrideMessage) {
|
||||
super(errorCodeMessage, overrideMessage);
|
||||
}
|
||||
|
||||
public InvalidTokenException(DatabasirErrors errorCodeMessage, Throwable cause) {
|
||||
super(errorCodeMessage, cause);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getErrCode() + ": " + getErrMessage();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
package com.databasir.common.codec;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class CodecTest {
|
||||
|
||||
@Test
|
||||
public void testAes() {
|
||||
String key = Aes.randomBase64Key();
|
||||
Assertions.assertNotNull(key);
|
||||
|
||||
String data = "hello world!";
|
||||
String encryptedData = Aes.encryptToBase64Data(data, key);
|
||||
Assertions.assertNotNull(encryptedData);
|
||||
|
||||
String decryptedData = Aes.decryptFromBase64Data(encryptedData, key);
|
||||
Assertions.assertEquals(data, decryptedData);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRsa() {
|
||||
Rsa.RsaBase64Key key = Rsa.generateBase64Key();
|
||||
Assertions.assertNotNull(key);
|
||||
Assertions.assertNotNull(key.getPrivateBase64Key());
|
||||
Assertions.assertNotNull(key.getPublicBase64Key());
|
||||
|
||||
String data = "Hello world!";
|
||||
String encrypted = Rsa.encryptToBase64DataByPublicKey(data, key.getPublicBase64Key());
|
||||
Assertions.assertNotNull(encrypted);
|
||||
|
||||
String decrypted = Rsa.decryptFromBase64DataByPrivateKey(encrypted, key.getPrivateBase64Key());
|
||||
Assertions.assertEquals(data, decrypted);
|
||||
}
|
||||
}
|
|
@ -1,5 +1,37 @@
|
|||
plugins {
|
||||
id 'io.spring.dependency-management'
|
||||
id 'org.springframework.boot' apply false
|
||||
}
|
||||
|
||||
dependencies {
|
||||
testImplementation 'mysql:mysql-connector-java:8.0.27'
|
||||
// internal module
|
||||
implementation project(":common")
|
||||
implementation project(":dao")
|
||||
implementation project(":plugin")
|
||||
|
||||
// spring boot
|
||||
implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-validation'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-jooq'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-mail'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-security'
|
||||
|
||||
// datasource
|
||||
implementation "com.zaxxer:HikariCP:${hikariVersion}"
|
||||
// jdbc driver
|
||||
implementation "mysql:mysql-connector-java:${mysqlConnectorVersion}"
|
||||
implementation "org.postgresql:postgresql:${postgresqlConnectorVersion}"
|
||||
|
||||
// jackson
|
||||
implementation "com.fasterxml.jackson.core:jackson-core:${jacksonVersion}"
|
||||
implementation "com.fasterxml.jackson.core:jackson-databind:${jacksonVersion}"
|
||||
|
||||
// others
|
||||
implementation 'com.auth0:java-jwt:3.18.3'
|
||||
implementation 'org.commonmark:commonmark:0.18.1'
|
||||
implementation 'org.freemarker:freemarker:2.3.31'
|
||||
|
||||
// test
|
||||
testImplementation "mysql:mysql-connector-java:${mysqlConnectorVersion}"
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
package com.databasir.core.domain;
|
||||
|
||||
import com.databasir.common.DatabasirErrors;
|
||||
import com.databasir.common.DatabasirException;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@Getter
|
||||
public enum DomainErrors implements DatabasirErrors {
|
||||
REFRESH_TOKEN_EXPIRED("X_0001", "refresh token expired"),
|
||||
ACCESS_TOKEN_REFRESH_INVALID("X_0002", "invalid refresh token operation"),
|
||||
|
||||
NOT_SUPPORT_DATABASE_TYPE("A_10000", "不支持的数据库类型, 请检查项目配置"),
|
||||
PROJECT_NOT_FOUND("A_10001", "项目不存在"),
|
||||
DATABASE_META_NOT_FOUND("A_10002", "获取数据库信息失败"),
|
||||
CONNECT_DATABASE_FAILED("A_10003", "连接数据库失败,请检查连接配置"),
|
||||
GROUP_OWNER_MUST_NOT_BE_EMPTY("A_10004", "请至少指定一个分组组长"),
|
||||
PASSWORD_MUST_NOT_BE_BLANK("A_10005", "密码不能为空"),
|
||||
USERNAME_OR_EMAIL_DUPLICATE("A_10006", "用户名或邮箱已存在"),
|
||||
USER_ROLE_DUPLICATE("A_10007", "用户角色已存在"),
|
||||
PROJECT_NAME_DUPLICATE("A_10008", "项目名称已被占用"),
|
||||
CANNOT_UPDATE_SELF_ROLE("A_10009", "无法对自己执行角色变更的操作"),
|
||||
UPDATE_PASSWORD_CONFIRM_FAILED("A_10010", "两次密码输入不一致"),
|
||||
ORIGIN_PASSWORD_NOT_CORRECT("A_10011", "原密码不正确");
|
||||
|
||||
private final String errCode;
|
||||
|
||||
private final String errMessage;
|
||||
|
||||
public DatabasirException exception() {
|
||||
return new DatabasirException(this);
|
||||
}
|
||||
|
||||
public DatabasirException exception(Throwable origin) {
|
||||
return new DatabasirException(this, origin);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package com.databasir.core.domain.document.converter;
|
||||
|
||||
public interface BaseConverter {
|
||||
|
||||
@NullToEmpty
|
||||
default String nullToEmpty(String s) {
|
||||
return s == null ? "" : s;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package com.databasir.core.domain.document.converter;
|
||||
|
||||
import com.databasir.core.domain.document.data.DatabaseDocumentResponse;
|
||||
import com.databasir.core.infrastructure.converter.JsonConverter;
|
||||
import com.databasir.dao.tables.pojos.DatabaseDocumentHistoryPojo;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.ReportingPolicy;
|
||||
|
||||
@Mapper(componentModel = "spring", uses = JsonConverter.class, unmappedTargetPolicy = ReportingPolicy.IGNORE)
|
||||
public interface DocumentHistoryPojoConverter {
|
||||
|
||||
@Mapping(target = "databaseDocumentObject", source = "databaseMetaObject")
|
||||
@Mapping(target = "id", ignore = true)
|
||||
@Mapping(target = "createAt", ignore = true)
|
||||
DatabaseDocumentHistoryPojo of(DatabaseDocumentResponse databaseMetaObject,
|
||||
Integer projectId,
|
||||
Integer databaseDocumentId,
|
||||
Long version);
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
package com.databasir.core.domain.document.converter;
|
||||
|
||||
import com.databasir.core.infrastructure.converter.JsonConverter;
|
||||
import com.databasir.core.meta.data.ColumnMeta;
|
||||
import com.databasir.core.meta.data.IndexMeta;
|
||||
import com.databasir.core.meta.data.TriggerMeta;
|
||||
import com.databasir.dao.tables.pojos.*;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.ReportingPolicy;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Mapper(componentModel = "spring", uses = JsonConverter.class, unmappedTargetPolicy = ReportingPolicy.WARN)
|
||||
public interface DocumentPojoConverter extends BaseConverter {
|
||||
|
||||
@Mapping(target = "databaseName", source = "meta.databaseName")
|
||||
DatabaseDocumentPojo toDatabasePojo(Integer projectId,
|
||||
com.databasir.core.meta.data.DatabaseMeta meta,
|
||||
Long version);
|
||||
|
||||
@Mapping(target = "databaseName", source = "meta.databaseName")
|
||||
DatabaseDocumentPojo toDatabasePojo(Integer projectId,
|
||||
com.databasir.core.meta.data.DatabaseMeta meta,
|
||||
Integer id,
|
||||
Long version);
|
||||
|
||||
@Mapping(target = "comment", qualifiedBy = NullToEmpty.class)
|
||||
TableDocumentPojo toTablePojo(Integer databaseDocumentId,
|
||||
com.databasir.core.meta.data.TableMeta meta);
|
||||
|
||||
default List<TableColumnDocumentPojo> toColumnPojo(Integer databaseDocumentId,
|
||||
Integer tableDocumentId,
|
||||
List<ColumnMeta> metaList) {
|
||||
return metaList.stream()
|
||||
.map(meta -> toColumnPojo(databaseDocumentId, tableDocumentId, meta))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Mapping(target = "comment", qualifiedBy = NullToEmpty.class)
|
||||
TableColumnDocumentPojo toColumnPojo(Integer databaseDocumentId,
|
||||
Integer tableDocumentId,
|
||||
ColumnMeta meta);
|
||||
|
||||
default List<TableIndexDocumentPojo> toIndexPojo(Integer databaseDocumentId,
|
||||
Integer tableDocumentId,
|
||||
List<IndexMeta> metaList) {
|
||||
return metaList.stream()
|
||||
.map(meta -> toIndexPojo(databaseDocumentId, tableDocumentId, meta))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Mapping(target = "isPrimary", source = "meta.isPrimaryKey")
|
||||
@Mapping(target = "isUnique", source = "meta.isUniqueKey")
|
||||
@Mapping(target = "columnNameArray", source = "meta.columnNames")
|
||||
TableIndexDocumentPojo toIndexPojo(Integer databaseDocumentId,
|
||||
Integer tableDocumentId,
|
||||
IndexMeta meta);
|
||||
|
||||
default List<TableTriggerDocumentPojo> toTriggerPojo(Integer databaseDocumentId,
|
||||
Integer tableDocumentId,
|
||||
List<TriggerMeta> metaList) {
|
||||
return metaList.stream()
|
||||
.map(meta -> toTriggerPojo(databaseDocumentId, tableDocumentId, meta))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Mapping(target = "triggerCreateAt", source = "meta.createAt")
|
||||
TableTriggerDocumentPojo toTriggerPojo(Integer databaseDocumentId,
|
||||
Integer tableDocumentId,
|
||||
TriggerMeta meta);
|
||||
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
package com.databasir.core.domain.document.converter;
|
||||
|
||||
import com.databasir.core.domain.document.data.DatabaseDocumentResponse;
|
||||
import com.databasir.core.infrastructure.converter.JsonConverter;
|
||||
import com.databasir.dao.tables.pojos.*;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.ReportingPolicy;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mapper(componentModel = "spring", uses = JsonConverter.class, unmappedTargetPolicy = ReportingPolicy.WARN)
|
||||
public interface DocumentResponseConverter {
|
||||
|
||||
@Mapping(target = "columns", source = "columns")
|
||||
@Mapping(target = "indexes", source = "indexes")
|
||||
@Mapping(target = "triggers", source = "triggers")
|
||||
DatabaseDocumentResponse.TableDocumentResponse of(TableDocumentPojo tableDocument,
|
||||
List<TableColumnDocumentPojo> columns,
|
||||
List<TableIndexDocumentPojo> indexes,
|
||||
List<TableTriggerDocumentPojo> triggers);
|
||||
|
||||
@Mapping(target = "columnNames", source = "columnNameArray")
|
||||
DatabaseDocumentResponse.TableDocumentResponse.IndexDocumentResponse of(TableIndexDocumentPojo indexDocument);
|
||||
|
||||
@Mapping(target = "id", source = "databaseDocument.id")
|
||||
@Mapping(target = "createAt", source = "databaseDocument.createAt")
|
||||
@Mapping(target = "documentVersion", source = "databaseDocument.version")
|
||||
DatabaseDocumentResponse of(DatabaseDocumentPojo databaseDocument,
|
||||
List<DatabaseDocumentResponse.TableDocumentResponse> tables);
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
package com.databasir.core.domain.document.converter;
|
||||
|
||||
import org.mapstruct.Qualifier;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Qualifier
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.CLASS)
|
||||
public @interface NullToEmpty {
|
||||
}
|
|
@ -0,0 +1,125 @@
|
|||
package com.databasir.core.domain.document.data;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class DatabaseDocumentResponse {
|
||||
|
||||
private Integer id;
|
||||
|
||||
private String databaseName;
|
||||
|
||||
private String productName;
|
||||
|
||||
private String productVersion;
|
||||
|
||||
private Integer documentVersion;
|
||||
|
||||
@Builder.Default
|
||||
private List<TableDocumentResponse> tables = new ArrayList<>();
|
||||
|
||||
private LocalDateTime createAt;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public static class TableDocumentResponse {
|
||||
|
||||
private Integer id;
|
||||
|
||||
private String name;
|
||||
|
||||
private String type;
|
||||
|
||||
private String comment;
|
||||
|
||||
@Builder.Default
|
||||
private List<ColumnDocumentResponse> columns = new ArrayList<>();
|
||||
|
||||
@Builder.Default
|
||||
private List<IndexDocumentResponse> indexes = new ArrayList<>();
|
||||
|
||||
@Builder.Default
|
||||
private List<TriggerDocumentResponse> triggers = new ArrayList<>();
|
||||
|
||||
private LocalDateTime createAt;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public static class ColumnDocumentResponse {
|
||||
private Integer id;
|
||||
|
||||
private String name;
|
||||
|
||||
private String type;
|
||||
|
||||
private Integer size;
|
||||
|
||||
private Integer decimalDigits;
|
||||
|
||||
private String comment;
|
||||
|
||||
private String nullable;
|
||||
|
||||
private String autoIncrement;
|
||||
|
||||
private String defaultValue;
|
||||
|
||||
private LocalDateTime createAt;
|
||||
}
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public static class IndexDocumentResponse {
|
||||
|
||||
private Integer id;
|
||||
|
||||
private String name;
|
||||
|
||||
private Boolean isPrimary;
|
||||
|
||||
private Boolean isUnique;
|
||||
|
||||
@Builder.Default
|
||||
private List<String> columnNames = new ArrayList<>();
|
||||
|
||||
private LocalDateTime createAt;
|
||||
}
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public static class TriggerDocumentResponse {
|
||||
|
||||
private Integer id;
|
||||
|
||||
private String name;
|
||||
|
||||
private String timing;
|
||||
|
||||
private String manipulation;
|
||||
|
||||
private String statement;
|
||||
|
||||
private String triggerCreateAt;
|
||||
|
||||
private LocalDateTime createAt;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package com.databasir.core.domain.document.data;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class DatabaseDocumentVersionResponse {
|
||||
|
||||
private Long version;
|
||||
|
||||
private LocalDateTime createAt;
|
||||
|
||||
}
|
|
@ -0,0 +1,186 @@
|
|||
package com.databasir.core.domain.document.service;
|
||||
|
||||
import com.databasir.core.Databasir;
|
||||
import com.databasir.core.DatabasirConfig;
|
||||
import com.databasir.core.domain.DomainErrors;
|
||||
import com.databasir.core.domain.document.converter.DocumentHistoryPojoConverter;
|
||||
import com.databasir.core.domain.document.converter.DocumentPojoConverter;
|
||||
import com.databasir.core.domain.document.converter.DocumentResponseConverter;
|
||||
import com.databasir.core.domain.document.data.DatabaseDocumentResponse;
|
||||
import com.databasir.core.domain.document.data.DatabaseDocumentVersionResponse;
|
||||
import com.databasir.core.infrastructure.connection.DatabaseConnectionService;
|
||||
import com.databasir.core.infrastructure.converter.JsonConverter;
|
||||
import com.databasir.core.meta.data.DatabaseMeta;
|
||||
import com.databasir.dao.impl.*;
|
||||
import com.databasir.dao.tables.pojos.*;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
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.sql.Connection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class DocumentService {
|
||||
|
||||
private final ProjectDao projectDao;
|
||||
|
||||
private final ProjectSyncRuleDao projectSyncRuleDao;
|
||||
|
||||
private final DataSourceDao dataSourceDao;
|
||||
|
||||
private final DataSourcePropertyDao dataSourcePropertyDao;
|
||||
|
||||
private final SysKeyDao sysKeyDao;
|
||||
|
||||
private final DatabaseConnectionService databaseConnectionService;
|
||||
|
||||
private final DatabaseDocumentDao databaseDocumentDao;
|
||||
|
||||
private final TableDocumentDao tableDocumentDao;
|
||||
|
||||
private final TableColumnDocumentDao tableColumnDocumentDao;
|
||||
|
||||
private final TableIndexDocumentDao tableIndexDocumentDao;
|
||||
|
||||
private final TableTriggerDocumentDao tableTriggerDocumentDao;
|
||||
|
||||
private final DatabaseDocumentHistoryDao databaseDocumentHistoryDao;
|
||||
|
||||
private final DocumentPojoConverter documentPojoConverter;
|
||||
|
||||
private final DocumentResponseConverter documentResponseConverter;
|
||||
|
||||
private final DocumentHistoryPojoConverter documentHistoryPojoConverter;
|
||||
|
||||
private final JsonConverter jsonConverter;
|
||||
|
||||
@Transactional
|
||||
public void syncByProjectId(Integer projectId) {
|
||||
ProjectPojo project = projectDao.selectOptionalById(projectId)
|
||||
.orElseThrow(DomainErrors.PROJECT_NOT_FOUND::exception);
|
||||
DatabaseMeta meta = retrieveDatabaseMeta(projectId);
|
||||
Optional<DatabaseDocumentPojo> historyDocumentOpt = databaseDocumentDao.selectOptionalByProjectId(projectId);
|
||||
if (historyDocumentOpt.isPresent()) {
|
||||
DatabaseDocumentPojo historyDocument = historyDocumentOpt.get();
|
||||
Integer previousDocumentId = historyDocument.getId();
|
||||
saveAsHistory(historyDocument);
|
||||
deleteDeprecatedDocument(previousDocumentId);
|
||||
saveNewDocument(meta, historyDocument.getVersion() + 1, historyDocument.getProjectId(), previousDocumentId);
|
||||
} else {
|
||||
saveNewDocument(meta, 1L, projectId, null);
|
||||
}
|
||||
}
|
||||
|
||||
private DatabaseMeta retrieveDatabaseMeta(Integer projectId) {
|
||||
ProjectSyncRulePojo rule = projectSyncRuleDao.selectByProjectId(projectId);
|
||||
DataSourcePojo dataSource = dataSourceDao.selectByProjectId(projectId);
|
||||
List<DataSourcePropertyPojo> properties = dataSourcePropertyDao.selectByDataSourceId(dataSource.getId());
|
||||
Connection jdbcConnection = databaseConnectionService.create(dataSource, properties);
|
||||
DatabasirConfig databasirConfig = new DatabasirConfig();
|
||||
databasirConfig.setIgnoreTableNameRegex(jsonConverter.fromJson(rule.getIgnoreTableNameRegexArray()));
|
||||
databasirConfig.setIgnoreTableColumnNameRegex(jsonConverter.fromJson(rule.getIgnoreColumnNameRegexArray()));
|
||||
return Databasir.of(databasirConfig)
|
||||
.get(jdbcConnection, dataSource.getDatabaseName())
|
||||
.orElseThrow(DomainErrors.DATABASE_META_NOT_FOUND::exception);
|
||||
}
|
||||
|
||||
private void saveAsHistory(DatabaseDocumentPojo databaseDocument) {
|
||||
// save history
|
||||
Integer projectId = databaseDocument.getProjectId();
|
||||
Integer databaseMetaId = databaseDocument.getId();
|
||||
DatabaseDocumentResponse databaseDocumentResponse = getOneByProjectId(projectId, null).orElse(null);
|
||||
Long currVersion = databaseDocument.getVersion();
|
||||
DatabaseDocumentHistoryPojo documentHistoryPojo =
|
||||
documentHistoryPojoConverter.of(databaseDocumentResponse, projectId, databaseMetaId, currVersion);
|
||||
databaseDocumentHistoryDao.insertAndReturnId(documentHistoryPojo);
|
||||
log.info("save old meta info to history success");
|
||||
}
|
||||
|
||||
private void deleteDeprecatedDocument(Integer databaseDocumentId) {
|
||||
// delete old meta info
|
||||
tableDocumentDao.deleteByDatabaseDocumentId(databaseDocumentId);
|
||||
tableColumnDocumentDao.deleteByDatabaseDocumentId(databaseDocumentId);
|
||||
tableIndexDocumentDao.deleteByDatabaseMetaId(databaseDocumentId);
|
||||
tableTriggerDocumentDao.deleteByDatabaseDocumentId(databaseDocumentId);
|
||||
log.info("delete old meta info success");
|
||||
}
|
||||
|
||||
private void saveNewDocument(DatabaseMeta meta,
|
||||
Long version,
|
||||
Integer projectId,
|
||||
Integer databaseDocumentId) {
|
||||
|
||||
Integer currentDatabaseDocumentId = databaseDocumentId;
|
||||
if (databaseDocumentId == null) {
|
||||
currentDatabaseDocumentId =
|
||||
databaseDocumentDao.insertAndReturnId(documentPojoConverter.toDatabasePojo(projectId, meta, 1L));
|
||||
} else {
|
||||
databaseDocumentDao.update(documentPojoConverter.toDatabasePojo(projectId, meta, databaseDocumentId, version));
|
||||
}
|
||||
|
||||
final Integer docId = currentDatabaseDocumentId;
|
||||
meta.getTables().forEach(table -> {
|
||||
TableDocumentPojo tableMeta =
|
||||
documentPojoConverter.toTablePojo(docId, table);
|
||||
Integer tableMetaId = tableDocumentDao.insertAndReturnId(tableMeta);
|
||||
List<TableColumnDocumentPojo> tableColumnMetas = documentPojoConverter.toColumnPojo(docId, tableMetaId, table.getColumns());
|
||||
tableColumnDocumentDao.batchInsert(tableColumnMetas);
|
||||
List<TableIndexDocumentPojo> tableIndexMetas = documentPojoConverter.toIndexPojo(docId, tableMetaId, table.getIndexes());
|
||||
tableIndexDocumentDao.batchInsert(tableIndexMetas);
|
||||
List<TableTriggerDocumentPojo> tableTriggerMetas = documentPojoConverter.toTriggerPojo(docId, tableMetaId, table.getTriggers());
|
||||
tableTriggerDocumentDao.batchInsert(tableTriggerMetas);
|
||||
});
|
||||
log.info("save new meta info success");
|
||||
}
|
||||
|
||||
public Optional<DatabaseDocumentResponse> getOneByProjectId(Integer projectId, Long version) {
|
||||
if (version == null) {
|
||||
return databaseDocumentDao.selectOptionalByProjectId(projectId)
|
||||
.map(document -> {
|
||||
Integer id = document.getId();
|
||||
List<TableDocumentPojo> tables = tableDocumentDao.selectByDatabaseDocumentId(id);
|
||||
List<TableColumnDocumentPojo> columns = tableColumnDocumentDao.selectByDatabaseDocumentId(id);
|
||||
List<TableIndexDocumentPojo> indexes = tableIndexDocumentDao.selectByDatabaseMetaId(id);
|
||||
List<TableTriggerDocumentPojo> triggers = tableTriggerDocumentDao.selectByDatabaseDocumentId(id);
|
||||
Map<Integer, List<TableColumnDocumentPojo>> columnsGroupByTableMetaId = columns.stream()
|
||||
.collect(Collectors.groupingBy(TableColumnDocumentPojo::getTableDocumentId));
|
||||
Map<Integer, List<TableIndexDocumentPojo>> indexesGroupByTableMetaId = indexes.stream()
|
||||
.collect(Collectors.groupingBy(TableIndexDocumentPojo::getTableDocumentId));
|
||||
Map<Integer, List<TableTriggerDocumentPojo>> triggersGroupByTableMetaId = triggers.stream()
|
||||
.collect(Collectors.groupingBy(TableTriggerDocumentPojo::getTableDocumentId));
|
||||
List<DatabaseDocumentResponse.TableDocumentResponse> tableDocumentResponseList = tables.stream()
|
||||
.map(table -> {
|
||||
List<TableColumnDocumentPojo> subColumns = columnsGroupByTableMetaId.getOrDefault(table.getId(), Collections.emptyList());
|
||||
List<TableIndexDocumentPojo> subIndexes = indexesGroupByTableMetaId.getOrDefault(table.getId(), Collections.emptyList());
|
||||
List<TableTriggerDocumentPojo> subTriggers = triggersGroupByTableMetaId.getOrDefault(table.getId(), Collections.emptyList());
|
||||
return documentResponseConverter.of(table, subColumns, subIndexes, subTriggers);
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
return documentResponseConverter.of(document, tableDocumentResponseList);
|
||||
});
|
||||
} else {
|
||||
return databaseDocumentHistoryDao.selectOptionalByProjectIdAndVersion(projectId, version)
|
||||
.map(obj -> jsonConverter.of(obj.getDatabaseDocumentObject()));
|
||||
}
|
||||
}
|
||||
|
||||
public Page<DatabaseDocumentVersionResponse> getVersionsBySchemaSourceId(Integer projectId, Pageable page) {
|
||||
return databaseDocumentDao.selectOptionalByProjectId(projectId)
|
||||
.map(schemaMeta -> databaseDocumentHistoryDao.selectPageByDatabaseDocumentId(page, schemaMeta.getId())
|
||||
.map(history -> DatabaseDocumentVersionResponse.builder()
|
||||
.version(history.getVersion())
|
||||
.createAt(history.getCreateAt())
|
||||
.build()))
|
||||
.orElseGet(Page::empty);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
package com.databasir.core.domain.group.converter;
|
||||
|
||||
import com.databasir.core.domain.group.data.GroupCreateRequest;
|
||||
import com.databasir.core.domain.group.data.GroupUpdateRequest;
|
||||
import com.databasir.dao.tables.pojos.GroupPojo;
|
||||
import org.mapstruct.Mapper;
|
||||
|
||||
@Mapper(componentModel = "spring")
|
||||
public interface GroupPojoConverter {
|
||||
|
||||
GroupPojo of(GroupCreateRequest groupCreateRequest);
|
||||
|
||||
GroupPojo of(GroupUpdateRequest groupUpdateRequest);
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
package com.databasir.core.domain.group.converter;
|
||||
|
||||
import com.databasir.core.domain.group.data.GroupMemberPageResponse;
|
||||
import com.databasir.core.domain.group.data.GroupPageResponse;
|
||||
import com.databasir.core.domain.group.data.GroupResponse;
|
||||
import com.databasir.dao.tables.pojos.GroupPojo;
|
||||
import com.databasir.dao.tables.pojos.UserPojo;
|
||||
import com.databasir.dao.value.GroupMemberDetailPojo;
|
||||
import org.mapstruct.Mapper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Mapper(componentModel = "spring")
|
||||
public interface GroupResponseConverter {
|
||||
|
||||
default List<GroupPageResponse> toResponse(List<GroupPojo> pojos,
|
||||
Map<Integer, List<String>> groupOwnerGroupByGroupId,
|
||||
Map<Integer, Integer> projectCountMapByGroupId) {
|
||||
return pojos.stream()
|
||||
.map(group -> {
|
||||
Integer groupId = group.getId();
|
||||
List<String> owners = groupOwnerGroupByGroupId.getOrDefault(groupId, new ArrayList<>());
|
||||
Integer projectCount = projectCountMapByGroupId.getOrDefault(groupId, 0);
|
||||
return toResponse(group, owners, projectCount);
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
GroupPageResponse toResponse(GroupPojo groupPojo,
|
||||
List<String> groupOwnerNames,
|
||||
Integer projectCount);
|
||||
|
||||
GroupResponse toResponse(GroupPojo groupPojo, List<UserPojo> groupOwners);
|
||||
|
||||
GroupMemberPageResponse toResponse(GroupMemberDetailPojo data);
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
package com.databasir.core.domain.group.data;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.Size;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class GroupCreateRequest {
|
||||
|
||||
@NotBlank
|
||||
private String name;
|
||||
|
||||
@NotBlank
|
||||
private String description;
|
||||
|
||||
@NotEmpty
|
||||
@Size(min = 1, max = 20, message = "一个分组的组长最多为 20 人,最少为 1 人")
|
||||
private List<Integer> groupOwnerUserIds = new ArrayList<>();
|
||||
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
package com.databasir.core.domain.group.data;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class GroupMemberCreateRequest {
|
||||
|
||||
private Integer userId;
|
||||
|
||||
private String role;
|
||||
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package com.databasir.core.domain.group.data;
|
||||
|
||||
import lombok.Data;
|
||||
import org.jooq.Condition;
|
||||
import org.jooq.impl.DSL;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static com.databasir.dao.Tables.USER;
|
||||
import static com.databasir.dao.Tables.USER_ROLE;
|
||||
|
||||
@Data
|
||||
public class GroupMemberPageCondition {
|
||||
|
||||
private String role;
|
||||
|
||||
private String nicknameOrUsernameOrEmailContains;
|
||||
|
||||
public Condition toCondition() {
|
||||
List<Condition> conditions = new ArrayList<>();
|
||||
if (role != null) {
|
||||
conditions.add(USER_ROLE.ROLE.eq(role));
|
||||
}
|
||||
if (nicknameOrUsernameOrEmailContains != null) {
|
||||
conditions.add(USER.USERNAME.contains(nicknameOrUsernameOrEmailContains)
|
||||
.or(USER.NICKNAME.contains(nicknameOrUsernameOrEmailContains))
|
||||
.or(USER.EMAIL.contains(nicknameOrUsernameOrEmailContains)));
|
||||
}
|
||||
return conditions.stream().reduce(Condition::and).orElse(DSL.trueCondition());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package com.databasir.core.domain.group.data;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
public class GroupMemberPageResponse {
|
||||
|
||||
private Integer userId;
|
||||
|
||||
private String role;
|
||||
|
||||
private String username;
|
||||
|
||||
private String nickname;
|
||||
|
||||
private String email;
|
||||
|
||||
private LocalDateTime createAt;
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
package com.databasir.core.domain.group.data;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.Pattern;
|
||||
|
||||
@Data
|
||||
public class GroupMemberRoleUpdateRequest {
|
||||
|
||||
@NotBlank
|
||||
private String role;
|
||||
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
package com.databasir.core.domain.group.data;
|
||||
|
||||
import com.databasir.dao.Tables;
|
||||
import lombok.Data;
|
||||
import org.jooq.Condition;
|
||||
import org.jooq.impl.DSL;
|
||||
|
||||
@Data
|
||||
public class GroupPageCondition {
|
||||
|
||||
private String groupNameContains;
|
||||
|
||||
public Condition toCondition() {
|
||||
if (groupNameContains != null) {
|
||||
return Tables.GROUP.NAME.contains(groupNameContains);
|
||||
}
|
||||
return DSL.trueCondition();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package com.databasir.core.domain.group.data;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class GroupPageResponse {
|
||||
|
||||
private Integer id;
|
||||
|
||||
private String name;
|
||||
|
||||
private String description;
|
||||
|
||||
private List<String> groupOwnerNames;
|
||||
|
||||
private Integer projectCount;
|
||||
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
package com.databasir.core.domain.group.data;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class GroupResponse {
|
||||
|
||||
private Integer id;
|
||||
|
||||
private String name;
|
||||
|
||||
private String description;
|
||||
|
||||
private List<GroupOwnerResponse> groupOwners = new ArrayList<>();
|
||||
|
||||
private LocalDateTime updateAt;
|
||||
|
||||
private LocalDateTime createAt;
|
||||
|
||||
@Data
|
||||
public static class GroupOwnerResponse {
|
||||
|
||||
private Integer id;
|
||||
|
||||
private String nickname;
|
||||
|
||||
private String email;
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package com.databasir.core.domain.group.data;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class GroupUpdateRequest {
|
||||
|
||||
@NotNull
|
||||
private Integer id;
|
||||
|
||||
@NotBlank
|
||||
private String name;
|
||||
|
||||
@NotBlank
|
||||
private String description;
|
||||
|
||||
@NotEmpty
|
||||
private List<Integer> groupOwnerUserIds = new ArrayList<>();
|
||||
|
||||
}
|
|
@ -0,0 +1,146 @@
|
|||
package com.databasir.core.domain.group.service;
|
||||
|
||||
import com.databasir.core.domain.DomainErrors;
|
||||
import com.databasir.core.domain.group.converter.GroupPojoConverter;
|
||||
import com.databasir.core.domain.group.converter.GroupResponseConverter;
|
||||
import com.databasir.core.domain.group.data.*;
|
||||
import com.databasir.dao.impl.GroupDao;
|
||||
import com.databasir.dao.impl.ProjectDao;
|
||||
import com.databasir.dao.impl.UserDao;
|
||||
import com.databasir.dao.impl.UserRoleDao;
|
||||
import com.databasir.dao.tables.pojos.GroupPojo;
|
||||
import com.databasir.dao.tables.pojos.UserPojo;
|
||||
import com.databasir.dao.tables.pojos.UserRolePojo;
|
||||
import com.databasir.dao.value.GroupMemberSimplePojo;
|
||||
import com.databasir.dao.value.GroupProjectCountPojo;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
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.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class GroupService {
|
||||
|
||||
private final GroupDao groupDao;
|
||||
|
||||
private final UserDao userDao;
|
||||
|
||||
private final UserRoleDao userRoleDao;
|
||||
|
||||
private final ProjectDao projectDao;
|
||||
|
||||
private final GroupPojoConverter groupPojoConverter;
|
||||
|
||||
private final GroupResponseConverter groupResponseConverter;
|
||||
|
||||
@Transactional
|
||||
public void create(GroupCreateRequest request) {
|
||||
GroupPojo groupPojo = groupPojoConverter.of(request);
|
||||
Integer groupId = groupDao.insertAndReturnId(groupPojo);
|
||||
List<UserRolePojo> roles = request.getGroupOwnerUserIds()
|
||||
.stream()
|
||||
.map(userId -> {
|
||||
UserRolePojo role = new UserRolePojo();
|
||||
role.setUserId(userId);
|
||||
role.setRole("GROUP_OWNER");
|
||||
role.setGroupId(groupId);
|
||||
return role;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
userRoleDao.batchInsert(roles);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void update(GroupUpdateRequest request) {
|
||||
GroupPojo groupPojo = groupPojoConverter.of(request);
|
||||
groupDao.updateById(groupPojo);
|
||||
userRoleDao.deleteByRoleAndGroupId("GROUP_OWNER", groupPojo.getId());
|
||||
List<UserRolePojo> roles = request.getGroupOwnerUserIds()
|
||||
.stream()
|
||||
.map(userId -> {
|
||||
UserRolePojo role = new UserRolePojo();
|
||||
role.setUserId(userId);
|
||||
role.setRole("GROUP_OWNER");
|
||||
role.setGroupId(groupPojo.getId());
|
||||
return role;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
userRoleDao.batchInsert(roles);
|
||||
}
|
||||
|
||||
public void delete(Integer groupId) {
|
||||
groupDao.deleteById(groupId);
|
||||
userRoleDao.deleteByGroupId(groupId);
|
||||
}
|
||||
|
||||
public Page<GroupPageResponse> list(Pageable pageable, GroupPageCondition condition) {
|
||||
Page<GroupPojo> page = groupDao.selectByPage(pageable, condition.toCondition());
|
||||
List<Integer> groupIdList = page.getContent()
|
||||
.stream()
|
||||
.map(GroupPojo::getId)
|
||||
.collect(Collectors.toList());
|
||||
Map<Integer, List<GroupMemberSimplePojo>> ownersGroupByGroupId = userRoleDao.selectOwnerNamesByGroupIdIn(groupIdList)
|
||||
.stream()
|
||||
.collect(Collectors.groupingBy(GroupMemberSimplePojo::getGroupId));
|
||||
Map<Integer, GroupProjectCountPojo> projectCountMapByGroupId = projectDao.selectCountByGroupIds(groupIdList)
|
||||
.stream()
|
||||
.collect(Collectors.toMap(GroupProjectCountPojo::getGroupId, v -> v));
|
||||
return page.map(groupPojo -> {
|
||||
Integer groupId = groupPojo.getId();
|
||||
List<String> owners = ownersGroupByGroupId.getOrDefault(groupId, new ArrayList<>())
|
||||
.stream()
|
||||
.map(GroupMemberSimplePojo::getNickname)
|
||||
.collect(Collectors.toList());
|
||||
GroupProjectCountPojo countPojo = projectCountMapByGroupId.get(groupId);
|
||||
Integer projectCount = countPojo == null ? 0 : countPojo.getCount();
|
||||
return groupResponseConverter.toResponse(groupPojo, owners, projectCount);
|
||||
});
|
||||
}
|
||||
|
||||
public Page<GroupMemberPageResponse> listGroupMembers(Integer groupId,
|
||||
Pageable pageable,
|
||||
GroupMemberPageCondition condition) {
|
||||
return userDao.selectGroupMembers(groupId, pageable, condition.toCondition())
|
||||
.map(groupResponseConverter::toResponse);
|
||||
}
|
||||
|
||||
public GroupResponse get(Integer groupId) {
|
||||
GroupPojo groupPojo = groupDao.selectById(groupId);
|
||||
List<UserPojo> users = userDao.selectLimitUsersByRoleAndGroup(groupId, "GROUP_OWNER", 50);
|
||||
return groupResponseConverter.toResponse(groupPojo, users);
|
||||
}
|
||||
|
||||
public void removeMember(Integer groupId, Integer userId) {
|
||||
userRoleDao.deleteByUserIdAndGroupId(userId, groupId);
|
||||
}
|
||||
|
||||
public void addMember(Integer groupId, GroupMemberCreateRequest request) {
|
||||
if (userRoleDao.hasRole(request.getUserId(), groupId)) {
|
||||
throw DomainErrors.USER_ROLE_DUPLICATE.exception();
|
||||
}
|
||||
UserRolePojo pojo = new UserRolePojo();
|
||||
pojo.setGroupId(groupId);
|
||||
pojo.setUserId(request.getUserId());
|
||||
pojo.setRole(request.getRole());
|
||||
userRoleDao.insertAndReturnId(pojo);
|
||||
}
|
||||
|
||||
public void changeMemberRole(Integer groupId, Integer userId, String role) {
|
||||
if (!userRoleDao.hasRole(userId, groupId, role)) {
|
||||
// TODO 最多 20 个组长
|
||||
userRoleDao.deleteByUserIdAndGroupId(userId, groupId);
|
||||
UserRolePojo pojo = new UserRolePojo();
|
||||
pojo.setUserId(userId);
|
||||
pojo.setGroupId(groupId);
|
||||
pojo.setRole(role);
|
||||
userRoleDao.insertAndReturnId(pojo);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
package com.databasir.core.domain.login.data;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
|
||||
@Data
|
||||
public class AccessTokenRefreshRequest {
|
||||
|
||||
@NotBlank
|
||||
private String refreshToken;
|
||||
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package com.databasir.core.domain.login.data;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class AccessTokenRefreshResponse {
|
||||
|
||||
private String accessToken;
|
||||
|
||||
private Long accessTokenExpireAt;
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
package com.databasir.core.domain.login.data;
|
||||
|
||||
import lombok.*;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@EqualsAndHashCode
|
||||
public class LoginKeyResponse {
|
||||
|
||||
private Integer userId;
|
||||
|
||||
private String accessToken;
|
||||
|
||||
private LocalDateTime accessTokenExpireAt;
|
||||
|
||||
private String refreshToken;
|
||||
|
||||
private LocalDateTime refreshTokenExpireAt;
|
||||
|
||||
public boolean accessTokenIsExpired() {
|
||||
return accessTokenExpireAt.isBefore(LocalDateTime.now());
|
||||
}
|
||||
|
||||
public boolean refreshTokenIsExpired() {
|
||||
return refreshTokenExpireAt.isBefore(LocalDateTime.now());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
package com.databasir.core.domain.login.service;
|
||||
|
||||
import com.databasir.core.domain.DomainErrors;
|
||||
import com.databasir.core.domain.login.data.AccessTokenRefreshRequest;
|
||||
import com.databasir.core.domain.login.data.AccessTokenRefreshResponse;
|
||||
import com.databasir.core.domain.login.data.LoginKeyResponse;
|
||||
import com.databasir.core.infrastructure.jwt.JwtTokens;
|
||||
import com.databasir.dao.impl.LoginDao;
|
||||
import com.databasir.dao.impl.UserDao;
|
||||
import com.databasir.dao.tables.pojos.LoginPojo;
|
||||
import com.databasir.dao.tables.pojos.UserPojo;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class LoginService {
|
||||
|
||||
private final LoginDao loginDao;
|
||||
|
||||
private final UserDao userDao;
|
||||
|
||||
private final JwtTokens jwtTokens;
|
||||
|
||||
public AccessTokenRefreshResponse refreshAccessTokens(AccessTokenRefreshRequest request) {
|
||||
LoginPojo login = loginDao.selectByRefreshToken(request.getRefreshToken())
|
||||
.orElseThrow(DomainErrors.ACCESS_TOKEN_REFRESH_INVALID::exception);
|
||||
// refresh-token 已过期
|
||||
if (login.getRefreshTokenExpireAt().isBefore(LocalDateTime.now())) {
|
||||
throw DomainErrors.REFRESH_TOKEN_EXPIRED.exception();
|
||||
}
|
||||
// access-token 未过期就开始刷新有可能是 refresh-token 泄露了,删除 refresh-token
|
||||
if (login.getAccessTokenExpireAt().isAfter(LocalDateTime.now())) {
|
||||
log.warn("invalid access token refresh operation: request = {}, login = {}", request, login);
|
||||
loginDao.deleteByUserId(login.getUserId());
|
||||
throw DomainErrors.ACCESS_TOKEN_REFRESH_INVALID.exception();
|
||||
}
|
||||
UserPojo user = userDao.selectById(login.getUserId());
|
||||
String accessToken = jwtTokens.accessToken(user.getEmail());
|
||||
LocalDateTime accessTokenExpireAt = jwtTokens.expireAt(accessToken);
|
||||
loginDao.updateAccessToken(accessToken, accessTokenExpireAt, user.getId());
|
||||
Instant instant = accessTokenExpireAt.atZone(ZoneId.systemDefault()).toInstant();
|
||||
long accessTokenExpireAtMilli = instant.toEpochMilli();
|
||||
return new AccessTokenRefreshResponse(accessToken, accessTokenExpireAtMilli);
|
||||
}
|
||||
|
||||
public Optional<LoginKeyResponse> getLoginKey(Integer userId) {
|
||||
return loginDao.selectByUserId(userId)
|
||||
.map(loginPojo -> LoginKeyResponse.builder()
|
||||
.accessToken(loginPojo.getAccessToken())
|
||||
.accessTokenExpireAt(loginPojo.getAccessTokenExpireAt())
|
||||
.refreshToken(loginPojo.getRefreshToken())
|
||||
.refreshTokenExpireAt(loginPojo.getRefreshTokenExpireAt())
|
||||
.build());
|
||||
}
|
||||
|
||||
public LoginKeyResponse generate(Integer userId) {
|
||||
UserPojo user = userDao.selectById(userId);
|
||||
String accessToken = jwtTokens.accessToken(user.getEmail());
|
||||
LocalDateTime accessTokenExpireAt = jwtTokens.expireAt(accessToken);
|
||||
String refreshToken = UUID.randomUUID().toString().replace("-", "");
|
||||
LocalDateTime refreshTokenExpireAt = LocalDateTime.now().plusDays(15);
|
||||
|
||||
LoginPojo loginPojo = new LoginPojo();
|
||||
loginPojo.setAccessToken(accessToken);
|
||||
loginPojo.setAccessTokenExpireAt(accessTokenExpireAt);
|
||||
loginPojo.setRefreshToken(refreshToken);
|
||||
loginPojo.setRefreshTokenExpireAt(refreshTokenExpireAt);
|
||||
loginPojo.setUserId(userId);
|
||||
loginDao.insertOnDuplicateKeyUpdate(loginPojo);
|
||||
|
||||
return LoginKeyResponse.builder()
|
||||
.userId(userId)
|
||||
.accessToken(accessToken)
|
||||
.accessTokenExpireAt(accessTokenExpireAt)
|
||||
.refreshToken(refreshToken)
|
||||
.refreshTokenExpireAt(refreshTokenExpireAt)
|
||||
.build();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package com.databasir.core.domain.project.converter;
|
||||
|
||||
import com.databasir.core.domain.project.data.DataSourcePropertyValue;
|
||||
import com.databasir.core.domain.project.data.ProjectCreateRequest;
|
||||
import com.databasir.core.domain.project.data.ProjectUpdateRequest;
|
||||
import com.databasir.dao.tables.pojos.DataSourcePojo;
|
||||
import com.databasir.dao.tables.pojos.DataSourcePropertyPojo;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Mapper(componentModel = "spring")
|
||||
public interface DataSourcePojoConverter {
|
||||
|
||||
@Mapping(target = "id", ignore = true)
|
||||
@Mapping(target = "createAt", ignore = true)
|
||||
@Mapping(target = "updateAt", ignore = true)
|
||||
@Mapping(target = "password", source = "password")
|
||||
DataSourcePojo of(ProjectCreateRequest.DataSourceCreateRequest request,
|
||||
String password,
|
||||
Integer projectId);
|
||||
|
||||
@Mapping(target = "updateAt", ignore = true)
|
||||
@Mapping(target = "createAt", ignore = true)
|
||||
@Mapping(target = "password", source = "password")
|
||||
DataSourcePojo of(ProjectUpdateRequest.DataSourceUpdateRequest request,
|
||||
String password,
|
||||
Integer projectId);
|
||||
|
||||
@Mapping(target = "id", ignore = true)
|
||||
@Mapping(target = "createAt", ignore = true)
|
||||
DataSourcePropertyPojo of(DataSourcePropertyValue propertyValues, Integer dataSourceId);
|
||||
|
||||
default List<DataSourcePropertyPojo> of(List<DataSourcePropertyValue> propertyValues,
|
||||
Integer dataSourceId) {
|
||||
return propertyValues.stream().map(value -> of(value, dataSourceId)).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
package com.databasir.core.domain.project.converter;
|
||||
|
||||
import com.databasir.core.domain.project.data.ProjectCreateRequest;
|
||||
import com.databasir.core.domain.project.data.ProjectDetailResponse;
|
||||
import com.databasir.core.domain.project.data.ProjectSimpleResponse;
|
||||
import com.databasir.core.domain.project.data.ProjectUpdateRequest;
|
||||
import com.databasir.core.infrastructure.converter.JsonConverter;
|
||||
import com.databasir.dao.tables.pojos.DataSourcePojo;
|
||||
import com.databasir.dao.tables.pojos.DataSourcePropertyPojo;
|
||||
import com.databasir.dao.tables.pojos.ProjectPojo;
|
||||
import com.databasir.dao.tables.pojos.ProjectSyncRulePojo;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.ReportingPolicy;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE, uses = JsonConverter.class)
|
||||
public interface ProjectPojoConverter {
|
||||
|
||||
ProjectPojo of(ProjectCreateRequest request);
|
||||
|
||||
ProjectPojo of(ProjectUpdateRequest request);
|
||||
|
||||
@Mapping(target = "ignoreTableNameRegexArray", source = "request.ignoreTableNameRegexes")
|
||||
@Mapping(target = "ignoreColumnNameRegexArray", source = "request.ignoreColumnNameRegexes")
|
||||
ProjectSyncRulePojo of(ProjectCreateRequest.ProjectSyncRuleCreateRequest request,
|
||||
Integer projectId);
|
||||
|
||||
@Mapping(target = "ignoreTableNameRegexArray", source = "request.ignoreTableNameRegexes")
|
||||
@Mapping(target = "ignoreColumnNameRegexArray", source = "request.ignoreColumnNameRegexes")
|
||||
ProjectSyncRulePojo of(ProjectUpdateRequest.ProjectSyncRuleUpdateRequest request,
|
||||
Integer projectId);
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
package com.databasir.core.domain.project.converter;
|
||||
|
||||
import com.databasir.core.domain.project.data.ProjectDetailResponse;
|
||||
import com.databasir.core.domain.project.data.ProjectSimpleResponse;
|
||||
import com.databasir.core.infrastructure.converter.JsonConverter;
|
||||
import com.databasir.dao.tables.pojos.DataSourcePojo;
|
||||
import com.databasir.dao.tables.pojos.DataSourcePropertyPojo;
|
||||
import com.databasir.dao.tables.pojos.ProjectPojo;
|
||||
import com.databasir.dao.tables.pojos.ProjectSyncRulePojo;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.ReportingPolicy;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE, uses = JsonConverter.class)
|
||||
public interface ProjectResponseConverter {
|
||||
|
||||
@Mapping(target = "id", source = "database.id")
|
||||
@Mapping(target = "createAt", source = "database.createAt")
|
||||
ProjectDetailResponse toResponse(ProjectPojo database,
|
||||
ProjectDetailResponse.DataSourceResponse dataSource,
|
||||
ProjectDetailResponse.ProjectSyncRuleResponse projectSyncRule);
|
||||
|
||||
ProjectDetailResponse.DataSourceResponse toResponse(DataSourcePojo dataSource,
|
||||
List<DataSourcePropertyPojo> properties);
|
||||
|
||||
@Mapping(target = "ignoreTableNameRegexes", source = "ignoreTableNameRegexArray")
|
||||
@Mapping(target = "ignoreColumnNameRegexes", source = "ignoreColumnNameRegexArray")
|
||||
ProjectDetailResponse.ProjectSyncRuleResponse toResponse(ProjectSyncRulePojo rule);
|
||||
|
||||
@Mapping(target = "id", source = "project.id")
|
||||
@Mapping(target = "createAt", source = "project.createAt")
|
||||
ProjectSimpleResponse toSimple(ProjectPojo project, DataSourcePojo dataSource);
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
package com.databasir.core.domain.project.data;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
|
||||
@Data
|
||||
public class DataSourcePropertyValue {
|
||||
|
||||
@NotBlank
|
||||
private String key;
|
||||
|
||||
@NotBlank
|
||||
private String value;
|
||||
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
package com.databasir.core.domain.project.data;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class ProjectCreateRequest {
|
||||
|
||||
@NotBlank
|
||||
private String name;
|
||||
|
||||
@NotBlank
|
||||
private String description;
|
||||
|
||||
@NotNull
|
||||
private Integer groupId;
|
||||
|
||||
@NotNull
|
||||
private ProjectCreateRequest.DataSourceCreateRequest dataSource;
|
||||
|
||||
@NotNull
|
||||
private ProjectCreateRequest.ProjectSyncRuleCreateRequest projectSyncRule;
|
||||
|
||||
@Data
|
||||
public static class DataSourceCreateRequest {
|
||||
|
||||
@NotBlank
|
||||
private String username;
|
||||
|
||||
@NotBlank
|
||||
private String password;
|
||||
|
||||
@NotBlank
|
||||
private String url;
|
||||
|
||||
@NotBlank
|
||||
private String databaseName;
|
||||
|
||||
@NotBlank
|
||||
private String databaseType;
|
||||
|
||||
private List<DataSourcePropertyValue> properties = new ArrayList<>();
|
||||
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class ProjectSyncRuleCreateRequest {
|
||||
|
||||
private List<String> ignoreTableNameRegexes = new ArrayList<>();
|
||||
|
||||
private List<String> ignoreColumnNameRegexes = new ArrayList<>();
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
package com.databasir.core.domain.project.data;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class ProjectDetailResponse {
|
||||
|
||||
private Integer id;
|
||||
|
||||
private String name;
|
||||
|
||||
private String description;
|
||||
|
||||
private DataSourceResponse dataSource;
|
||||
|
||||
private ProjectSyncRuleResponse projectSyncRule;
|
||||
|
||||
private LocalDateTime createAt;
|
||||
|
||||
@Data
|
||||
public static class DataSourceResponse {
|
||||
|
||||
private Integer id;
|
||||
|
||||
private String username;
|
||||
|
||||
private String url;
|
||||
|
||||
private String databaseName;
|
||||
|
||||
private String databaseType;
|
||||
|
||||
private List<DataSourcePropertyValue> properties = new ArrayList<>();
|
||||
|
||||
private LocalDateTime updateAt;
|
||||
|
||||
private LocalDateTime createAt;
|
||||
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class ProjectSyncRuleResponse {
|
||||
|
||||
private Integer id;
|
||||
|
||||
private List<String> ignoreTableNameRegexes = new ArrayList<>();
|
||||
|
||||
private List<String> ignoreColumnNameRegexes = new ArrayList<>();
|
||||
|
||||
private LocalDateTime createAt;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
package com.databasir.core.domain.project.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 ProjectListCondition {
|
||||
|
||||
private String nameContains;
|
||||
|
||||
private String databaseNameContains;
|
||||
|
||||
private String databaseType;
|
||||
|
||||
private Integer groupId;
|
||||
|
||||
public Condition toCondition() {
|
||||
List<Condition> conditions = new ArrayList<>();
|
||||
if (nameContains != null) {
|
||||
Condition condition = Tables.PROJECT.NAME.contains(nameContains);
|
||||
conditions.add(condition);
|
||||
}
|
||||
if (databaseNameContains != null) {
|
||||
Condition condition = Tables.DATA_SOURCE.DATABASE_NAME.contains(databaseNameContains);
|
||||
conditions.add(condition);
|
||||
}
|
||||
if (databaseType != null) {
|
||||
Condition condition = Tables.DATA_SOURCE.DATABASE_TYPE.eq(databaseType);
|
||||
conditions.add(condition);
|
||||
}
|
||||
if (groupId != null) {
|
||||
Condition condition = Tables.PROJECT.GROUP_ID.eq(groupId);
|
||||
conditions.add(condition);
|
||||
}
|
||||
conditions.add(Tables.PROJECT.DELETED.eq(false));
|
||||
return conditions.stream().reduce(Condition::and).orElse(DSL.trueCondition());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
package com.databasir.core.domain.project.data;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
public class ProjectSimpleResponse {
|
||||
|
||||
private Integer id;
|
||||
|
||||
private String name;
|
||||
|
||||
private String description;
|
||||
|
||||
private String databaseName;
|
||||
|
||||
private String databaseType;
|
||||
|
||||
private LocalDateTime createAt;
|
||||
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
package com.databasir.core.domain.project.data;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class ProjectUpdateRequest {
|
||||
|
||||
@NotNull
|
||||
private Integer id;
|
||||
|
||||
@NotBlank
|
||||
private String name;
|
||||
|
||||
@NotBlank
|
||||
private String description;
|
||||
|
||||
@NotNull
|
||||
private ProjectUpdateRequest.DataSourceUpdateRequest dataSource;
|
||||
|
||||
@NotNull
|
||||
private ProjectUpdateRequest.ProjectSyncRuleUpdateRequest projectSyncRule;
|
||||
|
||||
@Data
|
||||
public static class DataSourceUpdateRequest {
|
||||
|
||||
@NotBlank
|
||||
private String username;
|
||||
|
||||
private String password;
|
||||
|
||||
@NotBlank
|
||||
private String url;
|
||||
|
||||
@NotBlank
|
||||
private String databaseName;
|
||||
|
||||
@NotBlank
|
||||
private String databaseType;
|
||||
|
||||
private List<DataSourcePropertyValue> properties = new ArrayList<>();
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class ProjectSyncRuleUpdateRequest {
|
||||
|
||||
private List<String> ignoreTableNameRegexes = new ArrayList<>();
|
||||
|
||||
private List<String> ignoreColumnNameRegexes = new ArrayList<>();
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,141 @@
|
|||
package com.databasir.core.domain.project.service;
|
||||
|
||||
import com.databasir.common.codec.Aes;
|
||||
import com.databasir.core.domain.DomainErrors;
|
||||
import com.databasir.core.domain.project.converter.DataSourcePojoConverter;
|
||||
import com.databasir.core.domain.project.converter.ProjectPojoConverter;
|
||||
import com.databasir.core.domain.project.converter.ProjectResponseConverter;
|
||||
import com.databasir.core.domain.project.data.*;
|
||||
import com.databasir.dao.impl.*;
|
||||
import com.databasir.dao.tables.pojos.*;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.dao.DuplicateKeyException;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class ProjectService {
|
||||
|
||||
private final ProjectDao projectDao;
|
||||
|
||||
private final ProjectSyncRuleDao projectSyncRuleDao;
|
||||
|
||||
private final DataSourceDao dataSourceDao;
|
||||
|
||||
private final SysKeyDao sysKeyDao;
|
||||
|
||||
private final DataSourcePropertyDao dataSourcePropertyDao;
|
||||
|
||||
private final DataSourcePojoConverter dataSourcePojoConverter;
|
||||
|
||||
private final ProjectPojoConverter projectPojoConverter;
|
||||
|
||||
private final ProjectResponseConverter projectResponseConverter;
|
||||
|
||||
public ProjectDetailResponse getOne(Integer id) {
|
||||
return projectDao.selectOptionalById(id)
|
||||
.map(schemaSource -> {
|
||||
DataSourcePojo dataSource = dataSourceDao.selectByProjectId(id);
|
||||
List<DataSourcePropertyPojo> properties = dataSourcePropertyDao.selectByDataSourceId(dataSource.getId());
|
||||
ProjectDetailResponse.DataSourceResponse dataSourceResponse = projectResponseConverter.toResponse(dataSource, properties);
|
||||
ProjectSyncRulePojo rule = projectSyncRuleDao.selectByProjectId(id);
|
||||
ProjectDetailResponse.ProjectSyncRuleResponse ruleResponse = projectResponseConverter.toResponse(rule);
|
||||
return projectResponseConverter.toResponse(schemaSource, dataSourceResponse, ruleResponse);
|
||||
})
|
||||
.orElseThrow(DomainErrors.PROJECT_NOT_FOUND::exception);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void create(ProjectCreateRequest request) {
|
||||
ProjectPojo project = projectPojoConverter.of(request);
|
||||
Integer projectId = null;
|
||||
try {
|
||||
projectId = projectDao.insertAndReturnId(project);
|
||||
} catch (DuplicateKeyException e) {
|
||||
throw DomainErrors.PROJECT_NOT_FOUND.exception();
|
||||
}
|
||||
|
||||
String newPassword = encryptPassword(request.getDataSource().getPassword()).get();
|
||||
DataSourcePojo dataSource = dataSourcePojoConverter.of(request.getDataSource(), newPassword, projectId);
|
||||
Integer dataSourceId = dataSourceDao.insertAndReturnId(dataSource);
|
||||
|
||||
List<DataSourcePropertyValue> propertyValues = request.getDataSource().getProperties();
|
||||
List<DataSourcePropertyPojo> properties = dataSourcePojoConverter.of(propertyValues, dataSourceId);
|
||||
dataSourcePropertyDao.batchInsert(properties);
|
||||
|
||||
// TODO redesign it
|
||||
ProjectSyncRulePojo syncRule = projectPojoConverter.of(request.getProjectSyncRule(), projectId);
|
||||
projectSyncRuleDao.insertAndReturnId(syncRule);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void update(Integer groupId, ProjectUpdateRequest request) {
|
||||
Integer projectId = request.getId();
|
||||
if (projectDao.exists(groupId, projectId)) {
|
||||
// update dataSource
|
||||
String newPassword = encryptPassword(request.getDataSource().getPassword()).orElse(null);
|
||||
DataSourcePojo dataSource = dataSourcePojoConverter.of(request.getDataSource(), newPassword, projectId);
|
||||
dataSourceDao.updateByProjectId(dataSource);
|
||||
|
||||
// update connection property
|
||||
Integer dataSourceId = dataSourceDao.selectByProjectId(projectId).getId();
|
||||
List<DataSourcePropertyValue> propertyValues = request.getDataSource().getProperties();
|
||||
List<DataSourcePropertyPojo> properties = dataSourcePojoConverter.of(propertyValues, dataSourceId);
|
||||
if (properties.isEmpty()) {
|
||||
dataSourcePropertyDao.deleteByDataSourceId(dataSourceId);
|
||||
} else {
|
||||
dataSourcePropertyDao.deleteByDataSourceId(dataSourceId);
|
||||
dataSourcePropertyDao.batchInsert(properties);
|
||||
}
|
||||
|
||||
// update project sync rule TODO redesign it
|
||||
ProjectSyncRulePojo syncRule = projectPojoConverter.of(request.getProjectSyncRule(), projectId);
|
||||
projectSyncRuleDao.updateByProjectId(syncRule);
|
||||
|
||||
// update project info
|
||||
ProjectPojo project = projectPojoConverter.of(request);
|
||||
projectDao.updateById(project);
|
||||
} else {
|
||||
throw DomainErrors.PROJECT_NOT_FOUND.exception();
|
||||
}
|
||||
}
|
||||
|
||||
private Optional<String> encryptPassword(String password) {
|
||||
if (!StringUtils.hasText(password)) {
|
||||
return Optional.empty();
|
||||
}
|
||||
SysKeyPojo sysKey = sysKeyDao.selectTopOne();
|
||||
// String decryptedPassword = Rsa.decryptFromBase64DataByPrivateKey(password, sysKey.getRsaPrivateKey());
|
||||
return Optional.of(Aes.encryptToBase64Data(password, sysKey.getAesKey()));
|
||||
}
|
||||
|
||||
public void delete(Integer projectId) {
|
||||
projectDao.updateDeletedById(true, projectId);
|
||||
}
|
||||
|
||||
public Page<ProjectSimpleResponse> list(Pageable page, ProjectListCondition condition) {
|
||||
Page<ProjectPojo> pageData = projectDao.selectByCondition(page, condition.toCondition());
|
||||
List<Integer> projectIds = pageData.getContent()
|
||||
.stream()
|
||||
.map(ProjectPojo::getId)
|
||||
.collect(Collectors.toList());
|
||||
Map<Integer, DataSourcePojo> dataSourceMapByProjectId = dataSourceDao.selectInProjectIds(projectIds)
|
||||
.stream()
|
||||
.collect(Collectors.toMap(DataSourcePojo::getProjectId, Function.identity()));
|
||||
return pageData.map(project -> {
|
||||
DataSourcePojo dataSource = dataSourceMapByProjectId.get(project.getId());
|
||||
return projectResponseConverter.toSimple(project, dataSource);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package com.databasir.core.domain.system.data;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
public class SystemEmailResponse {
|
||||
|
||||
private String username;
|
||||
|
||||
private String smtpHost;
|
||||
|
||||
private Integer smtpPort;
|
||||
|
||||
private LocalDateTime createAt;
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
package com.databasir.core.domain.system.data;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.*;
|
||||
|
||||
@Data
|
||||
public class SystemEmailUpdateRequest {
|
||||
|
||||
@NotBlank
|
||||
@Email
|
||||
private String username;
|
||||
|
||||
private String password;
|
||||
|
||||
@NotBlank
|
||||
private String smtpHost;
|
||||
|
||||
@NotNull
|
||||
@Min(0L)
|
||||
@Max(65535L)
|
||||
private Integer smtpPort;
|
||||
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
package com.databasir.core.domain.system.service;
|
||||
|
||||
import com.databasir.common.codec.Aes;
|
||||
import com.databasir.common.codec.Rsa;
|
||||
import com.databasir.core.domain.DomainErrors;
|
||||
import com.databasir.core.domain.system.data.SystemEmailResponse;
|
||||
import com.databasir.core.domain.system.data.SystemEmailUpdateRequest;
|
||||
import com.databasir.dao.impl.SysKeyDao;
|
||||
import com.databasir.dao.impl.SysMailDao;
|
||||
import com.databasir.dao.impl.UserDao;
|
||||
import com.databasir.dao.impl.UserRoleDao;
|
||||
import com.databasir.dao.tables.pojos.SysKeyPojo;
|
||||
import com.databasir.dao.tables.pojos.SysMailPojo;
|
||||
import com.databasir.dao.tables.pojos.UserPojo;
|
||||
import com.databasir.dao.tables.pojos.UserRolePojo;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import java.util.Optional;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class SystemService {
|
||||
|
||||
private final SysKeyDao sysKeyDao;
|
||||
|
||||
private final SysMailDao sysMailDao;
|
||||
|
||||
private final UserDao userDao;
|
||||
|
||||
private final UserRoleDao userRoleDao;
|
||||
|
||||
private BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
|
||||
|
||||
@PostConstruct
|
||||
public void postInit() {
|
||||
sysKeyDao.selectOptionTopOne()
|
||||
.orElseGet(() -> {
|
||||
SysKeyPojo pojo = new SysKeyPojo();
|
||||
pojo.setAesKey(Aes.randomBase64Key());
|
||||
Rsa.RsaBase64Key key = Rsa.generateBase64Key();
|
||||
pojo.setRsaPublicKey(key.getPublicBase64Key());
|
||||
pojo.setRsaPrivateKey(key.getPrivateBase64Key());
|
||||
sysKeyDao.insertAndReturnId(pojo);
|
||||
return pojo;
|
||||
});
|
||||
|
||||
String email = "admin@databasir.com";
|
||||
String username = "databasir";
|
||||
Optional<UserPojo> userOpt = userDao.selectByEmail(email);
|
||||
if (!userOpt.isPresent()) {
|
||||
UserPojo admin = new UserPojo();
|
||||
admin.setEmail(email);
|
||||
admin.setUsername(username);
|
||||
admin.setPassword(bCryptPasswordEncoder.encode(username));
|
||||
admin.setEnabled(true);
|
||||
admin.setNickname("Databasir Admin");
|
||||
Integer userId = userDao.insertAndReturnId(admin);
|
||||
UserRolePojo role = new UserRolePojo();
|
||||
role.setUserId(userId);
|
||||
role.setRole("SYS_OWNER");
|
||||
userRoleDao.insertAndReturnId(role);
|
||||
}
|
||||
}
|
||||
|
||||
public void renewKey() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
public Optional<SystemEmailResponse> getEmailSetting() {
|
||||
return sysMailDao.selectOptionTopOne()
|
||||
.map(mail -> {
|
||||
SystemEmailResponse response = new SystemEmailResponse();
|
||||
response.setSmtpHost(mail.getSmtpHost());
|
||||
response.setSmtpPort(mail.getSmtpPort());
|
||||
response.setUsername(mail.getUsername());
|
||||
response.setCreateAt(mail.getCreateAt());
|
||||
return response;
|
||||
});
|
||||
}
|
||||
|
||||
public void updateEmailSetting(SystemEmailUpdateRequest request) {
|
||||
Optional<Integer> idOpt = sysMailDao.selectOptionTopOne().map(SysMailPojo::getId);
|
||||
SysMailPojo sysMailPojo = new SysMailPojo();
|
||||
sysMailPojo.setSmtpHost(request.getSmtpHost());
|
||||
sysMailPojo.setSmtpPort(request.getSmtpPort());
|
||||
sysMailPojo.setUsername(request.getUsername());
|
||||
idOpt.ifPresent(sysMailPojo::setId);
|
||||
if (request.getPassword() != null) {
|
||||
// TODO encrypt password ?
|
||||
sysMailPojo.setPassword(request.getPassword());
|
||||
}
|
||||
|
||||
if (idOpt.isPresent()) {
|
||||
if (!StringUtils.hasText(request.getPassword())) {
|
||||
throw DomainErrors.CONNECT_DATABASE_FAILED.exception();
|
||||
}
|
||||
sysMailDao.updateById(sysMailPojo);
|
||||
} else {
|
||||
sysMailDao.insertAndReturnId(sysMailPojo);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
package com.databasir.core.domain.user.converter;
|
||||
|
||||
import com.databasir.core.domain.user.data.UserCreateRequest;
|
||||
import com.databasir.dao.tables.pojos.UserPojo;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
|
||||
@Mapper(componentModel = "spring")
|
||||
public interface UserPojoConverter {
|
||||
|
||||
@Mapping(target = "password", source = "hashedPassword")
|
||||
UserPojo of(UserCreateRequest request, String hashedPassword);
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package com.databasir.core.domain.user.converter;
|
||||
|
||||
import com.databasir.core.domain.user.data.UserDetailResponse;
|
||||
import com.databasir.core.domain.user.data.UserPageResponse;
|
||||
import com.databasir.dao.tables.pojos.UserPojo;
|
||||
import com.databasir.dao.tables.pojos.UserRolePojo;
|
||||
import org.mapstruct.Mapper;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Mapper(componentModel = "spring")
|
||||
public interface UserResponseConverter {
|
||||
|
||||
|
||||
default UserDetailResponse detailResponse(UserPojo user,
|
||||
List<UserRolePojo> userRoles,
|
||||
Map<Integer, String> groupNameMapById) {
|
||||
List<UserDetailResponse.UserRoleDetailResponse> roles = userRoles.stream()
|
||||
.map(pojo -> userRoleDetailResponse(pojo, groupNameMapById.get(pojo.getGroupId())))
|
||||
.collect(Collectors.toList());
|
||||
return detailResponse(user, roles);
|
||||
}
|
||||
|
||||
UserDetailResponse detailResponse(UserPojo pojo, List<UserDetailResponse.UserRoleDetailResponse> roles);
|
||||
|
||||
UserDetailResponse.UserRoleDetailResponse userRoleDetailResponse(UserRolePojo pojo, String groupName);
|
||||
|
||||
UserPageResponse pageResponse(UserPojo pojo, Boolean isSysOwner, List<Integer> inGroupIds);
|
||||
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
package com.databasir.core.domain.user.data;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
@Data
|
||||
public class UserCreateRequest {
|
||||
|
||||
private String avatar;
|
||||
|
||||
@NotBlank
|
||||
private String username;
|
||||
|
||||
@NotBlank
|
||||
private String nickname;
|
||||
|
||||
@NotBlank
|
||||
private String email;
|
||||
|
||||
@NotBlank
|
||||
private String password;
|
||||
|
||||
@NotNull
|
||||
private Boolean enabled;
|
||||
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
package com.databasir.core.domain.user.data;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class UserDetailResponse {
|
||||
|
||||
private Integer id;
|
||||
|
||||
private String username;
|
||||
|
||||
private String nickname;
|
||||
|
||||
private String avatar;
|
||||
|
||||
private String email;
|
||||
|
||||
private Boolean enabled;
|
||||
|
||||
private List<UserRoleDetailResponse> roles = new ArrayList<>();
|
||||
|
||||
private LocalDateTime createAt;
|
||||
|
||||
@Data
|
||||
public static class UserRoleDetailResponse {
|
||||
|
||||
private String role;
|
||||
|
||||
private Integer groupId;
|
||||
|
||||
private String groupName;
|
||||
|
||||
private LocalDateTime createAt;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
package com.databasir.core.domain.user.data;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
|
||||
@Data
|
||||
public class UserLoginRequest {
|
||||
|
||||
@NotBlank
|
||||
private String username;
|
||||
|
||||
@NotBlank
|
||||
private String password;
|
||||
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package com.databasir.core.domain.user.data;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class UserLoginResponse {
|
||||
|
||||
private Integer id;
|
||||
|
||||
private String nickname;
|
||||
|
||||
private String email;
|
||||
|
||||
private String username;
|
||||
|
||||
private String accessToken;
|
||||
|
||||
private long accessTokenExpireAt;
|
||||
|
||||
private String refreshToken;
|
||||
|
||||
private List<RoleResponse> roles;
|
||||
|
||||
@Data
|
||||
public static class RoleResponse {
|
||||
private String role;
|
||||
private Integer groupId;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package com.databasir.core.domain.user.data;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.Size;
|
||||
|
||||
@Data
|
||||
public class UserNicknameUpdateRequest {
|
||||
|
||||
@NotBlank
|
||||
@Size(max = 32, min = 1)
|
||||
private String nickname;
|
||||
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
package com.databasir.core.domain.user.data;
|
||||
|
||||
import lombok.Data;
|
||||
import org.jooq.Condition;
|
||||
import org.jooq.impl.DSL;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static com.databasir.dao.Tables.USER;
|
||||
|
||||
@Data
|
||||
public class UserPageCondition {
|
||||
|
||||
private String nicknameContains;
|
||||
|
||||
private String usernameContains;
|
||||
|
||||
private String emailContains;
|
||||
|
||||
private String nicknameOrUsernameOrEmailContains;
|
||||
|
||||
private Boolean enabled;
|
||||
|
||||
public Condition toCondition() {
|
||||
List<Condition> conditions = new ArrayList<>();
|
||||
if (nicknameContains != null) {
|
||||
Condition condition = USER.NICKNAME.contains(nicknameContains);
|
||||
conditions.add(condition);
|
||||
}
|
||||
if (usernameContains != null) {
|
||||
Condition condition = USER.USERNAME.contains(usernameContains);
|
||||
conditions.add(condition);
|
||||
}
|
||||
if (emailContains != null) {
|
||||
Condition condition = USER.EMAIL.contains(emailContains);
|
||||
conditions.add(condition);
|
||||
}
|
||||
if (enabled != null) {
|
||||
Condition condition = USER.ENABLED.eq(enabled);
|
||||
conditions.add(condition);
|
||||
}
|
||||
if (nicknameOrUsernameOrEmailContains != null) {
|
||||
Condition condition = USER.EMAIL.contains(nicknameOrUsernameOrEmailContains)
|
||||
.or(USER.USERNAME.contains(nicknameOrUsernameOrEmailContains))
|
||||
.or(USER.EMAIL.contains(nicknameOrUsernameOrEmailContains));
|
||||
conditions.add(condition);
|
||||
}
|
||||
return conditions.stream().reduce(Condition::and).orElse(DSL.trueCondition());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package com.databasir.core.domain.user.data;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class UserPageResponse {
|
||||
|
||||
private Integer id;
|
||||
|
||||
private String username;
|
||||
|
||||
private String nickname;
|
||||
|
||||
private String email;
|
||||
|
||||
private Boolean enabled;
|
||||
|
||||
private Boolean isSysOwner;
|
||||
|
||||
private List<Integer> inGroupIds = new ArrayList<>();
|
||||
|
||||
private LocalDateTime createAt;
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
package com.databasir.core.domain.user.data;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.Size;
|
||||
|
||||
@Data
|
||||
public class UserPasswordUpdateRequest {
|
||||
|
||||
@NotBlank
|
||||
private String originPassword;
|
||||
|
||||
@NotBlank
|
||||
@Size(min = 6, max = 32)
|
||||
private String newPassword;
|
||||
|
||||
@NotBlank
|
||||
@Size(min = 6, max = 32)
|
||||
private String confirmNewPassword;
|
||||
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
package com.databasir.core.domain.user.data;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class UserRoleAssignRequest {
|
||||
|
||||
private Integer userId;
|
||||
|
||||
private String role;
|
||||
|
||||
private Integer groupId;
|
||||
}
|
|
@ -0,0 +1,146 @@
|
|||
package com.databasir.core.domain.user.service;
|
||||
|
||||
import com.databasir.core.domain.DomainErrors;
|
||||
import com.databasir.core.domain.user.converter.UserPojoConverter;
|
||||
import com.databasir.core.domain.user.converter.UserResponseConverter;
|
||||
import com.databasir.core.domain.user.data.*;
|
||||
import com.databasir.core.infrastructure.mail.MailSender;
|
||||
import com.databasir.dao.impl.GroupDao;
|
||||
import com.databasir.dao.impl.SysMailDao;
|
||||
import com.databasir.dao.impl.UserDao;
|
||||
import com.databasir.dao.impl.UserRoleDao;
|
||||
import com.databasir.dao.tables.pojos.GroupPojo;
|
||||
import com.databasir.dao.tables.pojos.UserPojo;
|
||||
import com.databasir.dao.tables.pojos.UserRolePojo;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.dao.DuplicateKeyException;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static java.util.stream.Collectors.*;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class UserService {
|
||||
|
||||
private final UserDao userDao;
|
||||
|
||||
private final UserRoleDao userRoleDao;
|
||||
|
||||
private final GroupDao groupDao;
|
||||
|
||||
private final SysMailDao sysMailDao;
|
||||
|
||||
private final UserPojoConverter userPojoConverter;
|
||||
|
||||
private final UserResponseConverter userResponseConverter;
|
||||
|
||||
private final MailSender mailSender;
|
||||
|
||||
private BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
|
||||
|
||||
public Page<UserPageResponse> list(Pageable pageable, UserPageCondition condition) {
|
||||
Page<UserPojo> users = userDao.selectByPage(pageable, condition.toCondition());
|
||||
List<Integer> userIds = users.getContent()
|
||||
.stream()
|
||||
.map(UserPojo::getId)
|
||||
.collect(toList());
|
||||
List<UserRolePojo> userRoles = userRoleDao.selectByUserIds(userIds);
|
||||
Map<Integer, List<Integer>> groupIdMapByUserId = userRoles
|
||||
.stream()
|
||||
.filter(ur -> ur.getGroupId() != null)
|
||||
.collect(groupingBy(UserRolePojo::getUserId, mapping(UserRolePojo::getGroupId, toList())));
|
||||
Map<Integer, List<UserRolePojo>> sysOwnerGroupByUserId = userRoles.stream()
|
||||
.filter(ur -> ur.getRole().equals("SYS_OWNER"))
|
||||
.collect(groupingBy(UserRolePojo::getUserId));
|
||||
return users.map(user ->
|
||||
userResponseConverter.pageResponse(user, sysOwnerGroupByUserId.containsKey(user.getId()),
|
||||
groupIdMapByUserId.get(user.getId())));
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void create(UserCreateRequest userCreateRequest) {
|
||||
userDao.selectByEmailOrUsername(userCreateRequest.getUsername()).ifPresent(data -> {
|
||||
throw DomainErrors.USERNAME_OR_EMAIL_DUPLICATE.exception();
|
||||
});
|
||||
String hashedPassword = bCryptPasswordEncoder.encode(userCreateRequest.getPassword());
|
||||
UserPojo pojo = userPojoConverter.of(userCreateRequest, hashedPassword);
|
||||
try {
|
||||
userDao.insertAndReturnId(pojo);
|
||||
} catch (DuplicateKeyException e) {
|
||||
throw DomainErrors.USERNAME_OR_EMAIL_DUPLICATE.exception();
|
||||
}
|
||||
}
|
||||
|
||||
public UserDetailResponse get(Integer userId) {
|
||||
UserPojo pojo = userDao.selectById(userId);
|
||||
List<UserRolePojo> roles = userRoleDao.selectByUserIds(Collections.singletonList(userId));
|
||||
List<Integer> groupIds = roles.stream()
|
||||
.map(UserRolePojo::getGroupId)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(toList());
|
||||
Map<Integer, String> groupNameMapById = groupDao.selectInIds(groupIds)
|
||||
.stream()
|
||||
.collect(toMap(GroupPojo::getId, GroupPojo::getName));
|
||||
return userResponseConverter.detailResponse(pojo, roles, groupNameMapById);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public String renewPassword(Integer userId) {
|
||||
UserPojo userPojo = userDao.selectById(userId);
|
||||
String randomPassword = UUID.randomUUID().toString()
|
||||
.replace("-", "")
|
||||
.substring(0, 8);
|
||||
String hashedPassword = bCryptPasswordEncoder.encode(randomPassword);
|
||||
userDao.updatePassword(userId, hashedPassword);
|
||||
sysMailDao.selectOptionTopOne()
|
||||
.ifPresent(mailPojo -> {
|
||||
String subject = "Databasir 密码重置提醒";
|
||||
String message = "您的密码已被重置,新密码为:" + randomPassword;
|
||||
mailSender.send(mailPojo, userPojo.getEmail(), subject, message);
|
||||
});
|
||||
return randomPassword;
|
||||
}
|
||||
|
||||
public void switchEnableStatus(Integer userId, Boolean enable) {
|
||||
userDao.updateEnabledByUserId(userId, enable);
|
||||
}
|
||||
|
||||
public void removeSysOwnerFrom(Integer userId) {
|
||||
if (userRoleDao.hasRole(userId, "SYS_OWNER")) {
|
||||
userRoleDao.deleteRole(userId, "SYS_OWNER");
|
||||
}
|
||||
}
|
||||
|
||||
public void addSysOwnerTo(Integer userId) {
|
||||
if (!userRoleDao.hasRole(userId, "SYS_OWNER")) {
|
||||
UserRolePojo role = new UserRolePojo();
|
||||
role.setUserId(userId);
|
||||
role.setRole("SYS_OWNER");
|
||||
userRoleDao.insertAndReturnId(role);
|
||||
}
|
||||
}
|
||||
|
||||
public void updatePassword(Integer userId, UserPasswordUpdateRequest request) {
|
||||
if (!Objects.equals(request.getNewPassword(), request.getConfirmNewPassword())) {
|
||||
throw DomainErrors.UPDATE_PASSWORD_CONFIRM_FAILED.exception();
|
||||
}
|
||||
UserPojo userPojo = userDao.selectById(userId);
|
||||
if (!bCryptPasswordEncoder.matches(request.getOriginPassword(), userPojo.getPassword())) {
|
||||
throw DomainErrors.ORIGIN_PASSWORD_NOT_CORRECT.exception();
|
||||
}
|
||||
String newHashedPassword = bCryptPasswordEncoder.encode(request.getNewPassword());
|
||||
userDao.updatePassword(userId, newHashedPassword);
|
||||
}
|
||||
|
||||
public void updateNickname(Integer userId, UserNicknameUpdateRequest request) {
|
||||
UserPojo userPojo = userDao.selectById(userId);
|
||||
userPojo.setNickname(request.getNickname());
|
||||
userDao.updateById(userPojo);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package com.databasir.core.infrastructure.connection;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.util.Properties;
|
||||
|
||||
public interface DatabaseConnectionFactory {
|
||||
|
||||
boolean support(String databaseType);
|
||||
|
||||
Connection getConnection(String username,
|
||||
String password,
|
||||
String url,
|
||||
String schema,
|
||||
Properties properties);
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
package com.databasir.core.infrastructure.connection;
|
||||
|
||||
import com.databasir.common.codec.Aes;
|
||||
import com.databasir.core.domain.DomainErrors;
|
||||
import com.databasir.dao.impl.SysKeyDao;
|
||||
import com.databasir.dao.tables.pojos.DataSourcePojo;
|
||||
import com.databasir.dao.tables.pojos.DataSourcePropertyPojo;
|
||||
import com.databasir.dao.tables.pojos.SysKeyPojo;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class DatabaseConnectionService {
|
||||
|
||||
private final List<DatabaseConnectionFactory> factories;
|
||||
|
||||
private final SysKeyDao sysKeyDao;
|
||||
|
||||
public Connection create(DataSourcePojo dataSource,
|
||||
List<DataSourcePropertyPojo> dataSourceProperties) {
|
||||
SysKeyPojo sysKey = sysKeyDao.selectTopOne();
|
||||
String username = dataSource.getUsername();
|
||||
String password = Aes.decryptFromBase64Data(dataSource.getPassword(), sysKey.getAesKey());
|
||||
String url = dataSource.getUrl();
|
||||
|
||||
Properties info = new Properties();
|
||||
dataSourceProperties.forEach(prop -> info.put(prop.getKey(), prop.getValue()));
|
||||
return factories.stream()
|
||||
.filter(factory -> factory.support(dataSource.getDatabaseType()))
|
||||
.findFirst()
|
||||
.orElseThrow(DomainErrors.NOT_SUPPORT_DATABASE_TYPE::exception)
|
||||
.getConnection(username, password, url, dataSource.getDatabaseName(), info);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package com.databasir.core.infrastructure.connection;
|
||||
|
||||
public interface DatabaseTypes {
|
||||
|
||||
String MYSQL = "mysql";
|
||||
|
||||
String POSTGRESQL = "postgresql";
|
||||
|
||||
String ORACLE = "oracle";
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
package com.databasir.core.infrastructure.connection;
|
||||
|
||||
import com.databasir.core.domain.DomainErrors;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Properties;
|
||||
|
||||
@Component
|
||||
public class MysqlDatabaseConnectionFactory implements DatabaseConnectionFactory {
|
||||
|
||||
@Override
|
||||
public boolean support(String databaseType) {
|
||||
return DatabaseTypes.MYSQL.equalsIgnoreCase(databaseType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Connection getConnection(String username, String password, String url, String schema, Properties properties) {
|
||||
try {
|
||||
Class.forName("com.mysql.cj.jdbc.Driver");
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
|
||||
Properties info = new Properties();
|
||||
info.put("user", username);
|
||||
info.put("password", password);
|
||||
info.putAll(properties);
|
||||
String jdbcUrl = "jdbc:mysql://" + url + "/" + schema;
|
||||
try {
|
||||
return DriverManager.getConnection(jdbcUrl, info);
|
||||
} catch (SQLException e) {
|
||||
throw DomainErrors.CONNECT_DATABASE_FAILED.exception(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
package com.databasir.core.infrastructure.connection;
|
||||
|
||||
|
||||
import com.databasir.core.domain.DomainErrors;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Properties;
|
||||
|
||||
@Component
|
||||
public class PostgresqlDatabaseConnectionFactory implements DatabaseConnectionFactory {
|
||||
|
||||
@Override
|
||||
public boolean support(String databaseType) {
|
||||
return DatabaseTypes.POSTGRESQL.equalsIgnoreCase(databaseType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Connection getConnection(String username, String password, String url, String schema, Properties properties) {
|
||||
try {
|
||||
Class.forName("org.postgresql.Driver");
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
|
||||
Properties info = new Properties();
|
||||
info.put("user", username);
|
||||
info.put("password", password);
|
||||
info.putAll(properties);
|
||||
String jdbcUrl = "jdbc:postgresql://" + url + "/" + schema;
|
||||
try {
|
||||
return DriverManager.getConnection(jdbcUrl, info);
|
||||
} catch (SQLException e) {
|
||||
throw DomainErrors.CONNECT_DATABASE_FAILED.exception(e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
package com.databasir.core.infrastructure.converter;
|
||||
|
||||
import com.databasir.core.domain.document.data.DatabaseDocumentResponse;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.jooq.JSON;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@Component
|
||||
public class JsonConverter {
|
||||
|
||||
@Autowired
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
public JSON toJson(List<String> array) {
|
||||
try {
|
||||
String json = objectMapper.writeValueAsString(array);
|
||||
return JSON.valueOf(json);
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public List<String> fromJson(JSON json) {
|
||||
String data = json.data();
|
||||
if (data == null) {
|
||||
return Collections.emptyList();
|
||||
} else {
|
||||
try {
|
||||
return objectMapper.readValue(data.getBytes(StandardCharsets.UTF_8),
|
||||
new TypeReference<List<String>>() {
|
||||
});
|
||||
} catch (IOException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public JSON toJson(DatabaseDocumentResponse response) {
|
||||
try {
|
||||
String json = objectMapper.writeValueAsString(response);
|
||||
return JSON.valueOf(json);
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public DatabaseDocumentResponse of(JSON json) {
|
||||
try {
|
||||
if (json == null) {
|
||||
return null;
|
||||
}
|
||||
return objectMapper.readValue(json.data().getBytes(StandardCharsets.UTF_8), DatabaseDocumentResponse.class);
|
||||
} catch (IOException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
package com.databasir.core.infrastructure.jwt;
|
||||
|
||||
import com.auth0.jwt.JWT;
|
||||
import com.auth0.jwt.algorithms.Algorithm;
|
||||
import com.auth0.jwt.exceptions.JWTVerificationException;
|
||||
import com.auth0.jwt.interfaces.JWTVerifier;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.Date;
|
||||
|
||||
@Component
|
||||
@Slf4j
|
||||
public class JwtTokens {
|
||||
|
||||
// 15 minutes
|
||||
private static final long ACCESS_EXPIRE_TIME = 1000 * 60 * 15;
|
||||
|
||||
public static final String TOKEN_PREFIX = "Bearer ";
|
||||
|
||||
private static final String ISSUER = "Databasir";
|
||||
|
||||
private static final String SECRET = "Databasir2022";
|
||||
|
||||
public String accessToken(String username) {
|
||||
Algorithm algorithm = Algorithm.HMAC256(SECRET);
|
||||
|
||||
return JWT.create()
|
||||
.withExpiresAt(new Date(new Date().getTime() + ACCESS_EXPIRE_TIME))
|
||||
.withIssuer(ISSUER)
|
||||
.withClaim("username", username)
|
||||
.sign(algorithm);
|
||||
}
|
||||
|
||||
public boolean verify(String token) {
|
||||
JWTVerifier verifier = JWT.require(Algorithm.HMAC256(SECRET))
|
||||
.withIssuer(ISSUER)
|
||||
.build();
|
||||
try {
|
||||
verifier.verify(token);
|
||||
return true;
|
||||
} catch (JWTVerificationException e) {
|
||||
log.warn("verify jwt token failed " + e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public String getUsername(String token) {
|
||||
return JWT.decode(token).getClaim("username").asString();
|
||||
}
|
||||
|
||||
public LocalDateTime expireAt(String token) {
|
||||
long time = JWT.decode(token).getExpiresAt().getTime();
|
||||
return Instant.ofEpochMilli(time)
|
||||
.atZone(ZoneId.systemDefault())
|
||||
.toLocalDateTime();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
package com.databasir.core.infrastructure.mail;
|
||||
|
||||
import com.databasir.dao.tables.pojos.SysMailPojo;
|
||||
import org.springframework.mail.SimpleMailMessage;
|
||||
import org.springframework.mail.javamail.JavaMailSender;
|
||||
import org.springframework.mail.javamail.JavaMailSenderImpl;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
@Component
|
||||
public class MailSender {
|
||||
|
||||
public void send(SysMailPojo mail, String to, String subject, String content) {
|
||||
JavaMailSender sender = initJavaMailSender(mail);
|
||||
SimpleMailMessage message = new SimpleMailMessage();
|
||||
message.setFrom(mail.getUsername());
|
||||
message.setTo(to);
|
||||
message.setSubject(subject);
|
||||
message.setText(content);
|
||||
sender.send(message);
|
||||
}
|
||||
|
||||
private JavaMailSender initJavaMailSender(SysMailPojo properties) {
|
||||
JavaMailSenderImpl sender = new JavaMailSenderImpl();
|
||||
sender.setHost(properties.getSmtpHost());
|
||||
if (properties.getSmtpPort() != null) {
|
||||
sender.setPort(properties.getSmtpPort());
|
||||
}
|
||||
sender.setUsername(properties.getUsername());
|
||||
sender.setPassword(properties.getPassword());
|
||||
sender.setProtocol("smtp");
|
||||
sender.setDefaultEncoding(StandardCharsets.UTF_8.name());
|
||||
return sender;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package com.databasir.core.infrastructure.meta;
|
||||
|
||||
public class DatabaseMetaResolver {
|
||||
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
plugins {
|
||||
id 'nu.studer.jooq'
|
||||
id 'io.spring.dependency-management'
|
||||
id 'org.springframework.boot' apply false
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "org.jooq:jooq:${jooqVersion}"
|
||||
implementation "org.jooq:jooq-codegen:${jooqVersion}"
|
||||
implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'
|
||||
jooqGenerator "mysql:mysql-connector-java:${mysqlConnectorVersion}"
|
||||
// include self to use strategy
|
||||
jooqGenerator project(':dao')
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
java.srcDirs += 'generated-src/jooq/main/java'
|
||||
}
|
||||
}
|
||||
|
||||
jooq {
|
||||
version = "${jooqVersion}"
|
||||
edition = nu.studer.gradle.jooq.JooqEdition.OSS
|
||||
|
||||
configurations {
|
||||
databasir {
|
||||
generateSchemaSourceOnCompilation = true
|
||||
generationTool {
|
||||
jdbc {
|
||||
driver = 'com.mysql.cj.jdbc.Driver'
|
||||
url = 'jdbc:mysql://localhost:3306/databasir'
|
||||
user = 'root'
|
||||
password = '123456'
|
||||
properties {
|
||||
property {
|
||||
key = 'useSSL'
|
||||
value = 'false'
|
||||
}
|
||||
property {
|
||||
key = 'allowPublicKeyRetrieval'
|
||||
value = 'true'
|
||||
}
|
||||
}
|
||||
}
|
||||
generator {
|
||||
name = 'org.jooq.codegen.DefaultGenerator'
|
||||
database {
|
||||
name = 'org.jooq.meta.mysql.MySQLDatabase'
|
||||
inputSchema = 'databasir'
|
||||
excludes = 'flyway.*'
|
||||
forcedTypes {
|
||||
forcedType {
|
||||
name = 'BOOLEAN'
|
||||
includeExpression = 'deleted|enabled|is.*'
|
||||
includeTypes = '.*'
|
||||
}
|
||||
forcedType {
|
||||
name = 'varchar'
|
||||
includeExpression = '.*'
|
||||
includeTypes = 'INET'
|
||||
}
|
||||
}
|
||||
}
|
||||
generate {
|
||||
pojos = true
|
||||
}
|
||||
target {
|
||||
packageName = 'com.databasir.dao'
|
||||
directory = 'generated-src/jooq/main/java'
|
||||
}
|
||||
strategy.name = 'com.databasir.dao.strategy.DatabasirPojoNamingStrategy'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,159 @@
|
|||
/*
|
||||
* This file is generated by jOOQ.
|
||||
*/
|
||||
package com.databasir.dao;
|
||||
|
||||
|
||||
import com.databasir.dao.tables.DataSource;
|
||||
import com.databasir.dao.tables.DataSourceProperty;
|
||||
import com.databasir.dao.tables.DatabaseDocument;
|
||||
import com.databasir.dao.tables.DatabaseDocumentHistory;
|
||||
import com.databasir.dao.tables.Group;
|
||||
import com.databasir.dao.tables.Login;
|
||||
import com.databasir.dao.tables.Project;
|
||||
import com.databasir.dao.tables.ProjectSyncRule;
|
||||
import com.databasir.dao.tables.SysKey;
|
||||
import com.databasir.dao.tables.SysMail;
|
||||
import com.databasir.dao.tables.TableColumnDocument;
|
||||
import com.databasir.dao.tables.TableDocument;
|
||||
import com.databasir.dao.tables.TableIndexDocument;
|
||||
import com.databasir.dao.tables.TableTriggerDocument;
|
||||
import com.databasir.dao.tables.User;
|
||||
import com.databasir.dao.tables.UserRole;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.jooq.Catalog;
|
||||
import org.jooq.Table;
|
||||
import org.jooq.impl.SchemaImpl;
|
||||
|
||||
|
||||
/**
|
||||
* This class is generated by jOOQ.
|
||||
*/
|
||||
@SuppressWarnings({ "all", "unchecked", "rawtypes" })
|
||||
public class Databasir extends SchemaImpl {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* The reference instance of <code>databasir</code>
|
||||
*/
|
||||
public static final Databasir DATABASIR = new Databasir();
|
||||
|
||||
/**
|
||||
* The table <code>databasir.data_source</code>.
|
||||
*/
|
||||
public final DataSource DATA_SOURCE = DataSource.DATA_SOURCE;
|
||||
|
||||
/**
|
||||
* The table <code>databasir.data_source_property</code>.
|
||||
*/
|
||||
public final DataSourceProperty DATA_SOURCE_PROPERTY = DataSourceProperty.DATA_SOURCE_PROPERTY;
|
||||
|
||||
/**
|
||||
* The table <code>databasir.database_document</code>.
|
||||
*/
|
||||
public final DatabaseDocument DATABASE_DOCUMENT = DatabaseDocument.DATABASE_DOCUMENT;
|
||||
|
||||
/**
|
||||
* The table <code>databasir.database_document_history</code>.
|
||||
*/
|
||||
public final DatabaseDocumentHistory DATABASE_DOCUMENT_HISTORY = DatabaseDocumentHistory.DATABASE_DOCUMENT_HISTORY;
|
||||
|
||||
/**
|
||||
* The table <code>databasir.group</code>.
|
||||
*/
|
||||
public final Group GROUP = Group.GROUP;
|
||||
|
||||
/**
|
||||
* The table <code>databasir.login</code>.
|
||||
*/
|
||||
public final Login LOGIN = Login.LOGIN;
|
||||
|
||||
/**
|
||||
* The table <code>databasir.project</code>.
|
||||
*/
|
||||
public final Project PROJECT = Project.PROJECT;
|
||||
|
||||
/**
|
||||
* The table <code>databasir.project_sync_rule</code>.
|
||||
*/
|
||||
public final ProjectSyncRule PROJECT_SYNC_RULE = ProjectSyncRule.PROJECT_SYNC_RULE;
|
||||
|
||||
/**
|
||||
* The table <code>databasir.sys_key</code>.
|
||||
*/
|
||||
public final SysKey SYS_KEY = SysKey.SYS_KEY;
|
||||
|
||||
/**
|
||||
* The table <code>databasir.sys_mail</code>.
|
||||
*/
|
||||
public final SysMail SYS_MAIL = SysMail.SYS_MAIL;
|
||||
|
||||
/**
|
||||
* The table <code>databasir.table_column_document</code>.
|
||||
*/
|
||||
public final TableColumnDocument TABLE_COLUMN_DOCUMENT = TableColumnDocument.TABLE_COLUMN_DOCUMENT;
|
||||
|
||||
/**
|
||||
* The table <code>databasir.table_document</code>.
|
||||
*/
|
||||
public final TableDocument TABLE_DOCUMENT = TableDocument.TABLE_DOCUMENT;
|
||||
|
||||
/**
|
||||
* The table <code>databasir.table_index_document</code>.
|
||||
*/
|
||||
public final TableIndexDocument TABLE_INDEX_DOCUMENT = TableIndexDocument.TABLE_INDEX_DOCUMENT;
|
||||
|
||||
/**
|
||||
* The table <code>databasir.table_trigger_document</code>.
|
||||
*/
|
||||
public final TableTriggerDocument TABLE_TRIGGER_DOCUMENT = TableTriggerDocument.TABLE_TRIGGER_DOCUMENT;
|
||||
|
||||
/**
|
||||
* The table <code>databasir.user</code>.
|
||||
*/
|
||||
public final User USER = User.USER;
|
||||
|
||||
/**
|
||||
* The table <code>databasir.user_role</code>.
|
||||
*/
|
||||
public final UserRole USER_ROLE = UserRole.USER_ROLE;
|
||||
|
||||
/**
|
||||
* No further instances allowed
|
||||
*/
|
||||
private Databasir() {
|
||||
super("databasir", null);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Catalog getCatalog() {
|
||||
return DefaultCatalog.DEFAULT_CATALOG;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final List<Table<?>> getTables() {
|
||||
return Arrays.asList(
|
||||
DataSource.DATA_SOURCE,
|
||||
DataSourceProperty.DATA_SOURCE_PROPERTY,
|
||||
DatabaseDocument.DATABASE_DOCUMENT,
|
||||
DatabaseDocumentHistory.DATABASE_DOCUMENT_HISTORY,
|
||||
Group.GROUP,
|
||||
Login.LOGIN,
|
||||
Project.PROJECT,
|
||||
ProjectSyncRule.PROJECT_SYNC_RULE,
|
||||
SysKey.SYS_KEY,
|
||||
SysMail.SYS_MAIL,
|
||||
TableColumnDocument.TABLE_COLUMN_DOCUMENT,
|
||||
TableDocument.TABLE_DOCUMENT,
|
||||
TableIndexDocument.TABLE_INDEX_DOCUMENT,
|
||||
TableTriggerDocument.TABLE_TRIGGER_DOCUMENT,
|
||||
User.USER,
|
||||
UserRole.USER_ROLE
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* This file is generated by jOOQ.
|
||||
*/
|
||||
package com.databasir.dao;
|
||||
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.jooq.Schema;
|
||||
import org.jooq.impl.CatalogImpl;
|
||||
|
||||
|
||||
/**
|
||||
* This class is generated by jOOQ.
|
||||
*/
|
||||
@SuppressWarnings({ "all", "unchecked", "rawtypes" })
|
||||
public class DefaultCatalog extends CatalogImpl {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* The reference instance of <code>DEFAULT_CATALOG</code>
|
||||
*/
|
||||
public static final DefaultCatalog DEFAULT_CATALOG = new DefaultCatalog();
|
||||
|
||||
/**
|
||||
* The schema <code>databasir</code>.
|
||||
*/
|
||||
public final Databasir DATABASIR = Databasir.DATABASIR;
|
||||
|
||||
/**
|
||||
* No further instances allowed
|
||||
*/
|
||||
private DefaultCatalog() {
|
||||
super("");
|
||||
}
|
||||
|
||||
@Override
|
||||
public final List<Schema> getSchemas() {
|
||||
return Arrays.asList(
|
||||
Databasir.DATABASIR
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* This file is generated by jOOQ.
|
||||
*/
|
||||
package com.databasir.dao;
|
||||
|
||||
|
||||
import com.databasir.dao.tables.DataSourceProperty;
|
||||
import com.databasir.dao.tables.DatabaseDocumentHistory;
|
||||
import com.databasir.dao.tables.TableColumnDocument;
|
||||
import com.databasir.dao.tables.TableDocument;
|
||||
import com.databasir.dao.tables.TableIndexDocument;
|
||||
import com.databasir.dao.tables.TableTriggerDocument;
|
||||
|
||||
import org.jooq.Index;
|
||||
import org.jooq.OrderField;
|
||||
import org.jooq.impl.DSL;
|
||||
import org.jooq.impl.Internal;
|
||||
|
||||
|
||||
/**
|
||||
* A class modelling indexes of tables in databasir.
|
||||
*/
|
||||
@SuppressWarnings({ "all", "unchecked", "rawtypes" })
|
||||
public class Indexes {
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// INDEX definitions
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public static final Index DATA_SOURCE_PROPERTY_IDX_DATA_SOURCE_ID = Internal.createIndex(DSL.name("idx_data_source_id"), DataSourceProperty.DATA_SOURCE_PROPERTY, new OrderField[] { DataSourceProperty.DATA_SOURCE_PROPERTY.DATA_SOURCE_ID }, false);
|
||||
public static final Index TABLE_COLUMN_DOCUMENT_IDX_DATABASE_DOCUMENT_ID = Internal.createIndex(DSL.name("idx_database_document_id"), TableColumnDocument.TABLE_COLUMN_DOCUMENT, new OrderField[] { TableColumnDocument.TABLE_COLUMN_DOCUMENT.DATABASE_DOCUMENT_ID }, false);
|
||||
public static final Index TABLE_DOCUMENT_IDX_DATABASE_DOCUMENT_ID = Internal.createIndex(DSL.name("idx_database_document_id"), TableDocument.TABLE_DOCUMENT, new OrderField[] { TableDocument.TABLE_DOCUMENT.DATABASE_DOCUMENT_ID }, false);
|
||||
public static final Index TABLE_INDEX_DOCUMENT_IDX_DATABASE_DOCUMENT_ID = Internal.createIndex(DSL.name("idx_database_document_id"), TableIndexDocument.TABLE_INDEX_DOCUMENT, new OrderField[] { TableIndexDocument.TABLE_INDEX_DOCUMENT.DATABASE_DOCUMENT_ID }, false);
|
||||
public static final Index TABLE_TRIGGER_DOCUMENT_IDX_DATABASE_DOCUMENT_ID = Internal.createIndex(DSL.name("idx_database_document_id"), TableTriggerDocument.TABLE_TRIGGER_DOCUMENT, new OrderField[] { TableTriggerDocument.TABLE_TRIGGER_DOCUMENT.DATABASE_DOCUMENT_ID }, false);
|
||||
public static final Index DATABASE_DOCUMENT_HISTORY_IDX_PROJECT_ID = Internal.createIndex(DSL.name("idx_project_id"), DatabaseDocumentHistory.DATABASE_DOCUMENT_HISTORY, new OrderField[] { DatabaseDocumentHistory.DATABASE_DOCUMENT_HISTORY.PROJECT_ID }, false);
|
||||
public static final Index TABLE_COLUMN_DOCUMENT_IDX_TABLE_DOCUMENT_ID = Internal.createIndex(DSL.name("idx_table_document_id"), TableColumnDocument.TABLE_COLUMN_DOCUMENT, new OrderField[] { TableColumnDocument.TABLE_COLUMN_DOCUMENT.TABLE_DOCUMENT_ID }, false);
|
||||
public static final Index TABLE_INDEX_DOCUMENT_IDX_TABLE_DOCUMENT_ID = Internal.createIndex(DSL.name("idx_table_document_id"), TableIndexDocument.TABLE_INDEX_DOCUMENT, new OrderField[] { TableIndexDocument.TABLE_INDEX_DOCUMENT.TABLE_DOCUMENT_ID }, false);
|
||||
public static final Index TABLE_TRIGGER_DOCUMENT_IDX_TABLE_DOCUMENT_ID = Internal.createIndex(DSL.name("idx_table_document_id"), TableTriggerDocument.TABLE_TRIGGER_DOCUMENT, new OrderField[] { TableTriggerDocument.TABLE_TRIGGER_DOCUMENT.TABLE_DOCUMENT_ID }, false);
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* This file is generated by jOOQ.
|
||||
*/
|
||||
package com.databasir.dao;
|
||||
|
||||
|
||||
import com.databasir.dao.tables.DataSource;
|
||||
import com.databasir.dao.tables.DataSourceProperty;
|
||||
import com.databasir.dao.tables.DatabaseDocument;
|
||||
import com.databasir.dao.tables.DatabaseDocumentHistory;
|
||||
import com.databasir.dao.tables.Group;
|
||||
import com.databasir.dao.tables.Login;
|
||||
import com.databasir.dao.tables.Project;
|
||||
import com.databasir.dao.tables.ProjectSyncRule;
|
||||
import com.databasir.dao.tables.SysKey;
|
||||
import com.databasir.dao.tables.SysMail;
|
||||
import com.databasir.dao.tables.TableColumnDocument;
|
||||
import com.databasir.dao.tables.TableDocument;
|
||||
import com.databasir.dao.tables.TableIndexDocument;
|
||||
import com.databasir.dao.tables.TableTriggerDocument;
|
||||
import com.databasir.dao.tables.User;
|
||||
import com.databasir.dao.tables.UserRole;
|
||||
import com.databasir.dao.tables.records.DataSourcePropertyRecord;
|
||||
import com.databasir.dao.tables.records.DataSourceRecord;
|
||||
import com.databasir.dao.tables.records.DatabaseDocumentHistoryRecord;
|
||||
import com.databasir.dao.tables.records.DatabaseDocumentRecord;
|
||||
import com.databasir.dao.tables.records.GroupRecord;
|
||||
import com.databasir.dao.tables.records.LoginRecord;
|
||||
import com.databasir.dao.tables.records.ProjectRecord;
|
||||
import com.databasir.dao.tables.records.ProjectSyncRuleRecord;
|
||||
import com.databasir.dao.tables.records.SysKeyRecord;
|
||||
import com.databasir.dao.tables.records.SysMailRecord;
|
||||
import com.databasir.dao.tables.records.TableColumnDocumentRecord;
|
||||
import com.databasir.dao.tables.records.TableDocumentRecord;
|
||||
import com.databasir.dao.tables.records.TableIndexDocumentRecord;
|
||||
import com.databasir.dao.tables.records.TableTriggerDocumentRecord;
|
||||
import com.databasir.dao.tables.records.UserRecord;
|
||||
import com.databasir.dao.tables.records.UserRoleRecord;
|
||||
|
||||
import org.jooq.TableField;
|
||||
import org.jooq.UniqueKey;
|
||||
import org.jooq.impl.DSL;
|
||||
import org.jooq.impl.Internal;
|
||||
|
||||
|
||||
/**
|
||||
* A class modelling foreign key relationships and constraints of tables in
|
||||
* databasir.
|
||||
*/
|
||||
@SuppressWarnings({ "all", "unchecked", "rawtypes" })
|
||||
public class Keys {
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// UNIQUE and PRIMARY KEY definitions
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public static final UniqueKey<DataSourceRecord> KEY_DATA_SOURCE_PRIMARY = Internal.createUniqueKey(DataSource.DATA_SOURCE, DSL.name("KEY_data_source_PRIMARY"), new TableField[] { DataSource.DATA_SOURCE.ID }, true);
|
||||
public static final UniqueKey<DataSourceRecord> KEY_DATA_SOURCE_UK_PROJECT_ID = Internal.createUniqueKey(DataSource.DATA_SOURCE, DSL.name("KEY_data_source_uk_project_id"), new TableField[] { DataSource.DATA_SOURCE.PROJECT_ID }, true);
|
||||
public static final UniqueKey<DataSourcePropertyRecord> KEY_DATA_SOURCE_PROPERTY_PRIMARY = Internal.createUniqueKey(DataSourceProperty.DATA_SOURCE_PROPERTY, DSL.name("KEY_data_source_property_PRIMARY"), new TableField[] { DataSourceProperty.DATA_SOURCE_PROPERTY.ID }, true);
|
||||
public static final UniqueKey<DatabaseDocumentRecord> KEY_DATABASE_DOCUMENT_PRIMARY = Internal.createUniqueKey(DatabaseDocument.DATABASE_DOCUMENT, DSL.name("KEY_database_document_PRIMARY"), new TableField[] { DatabaseDocument.DATABASE_DOCUMENT.ID }, true);
|
||||
public static final UniqueKey<DatabaseDocumentRecord> KEY_DATABASE_DOCUMENT_UK_PROJECT_ID = Internal.createUniqueKey(DatabaseDocument.DATABASE_DOCUMENT, DSL.name("KEY_database_document_uk_project_id"), new TableField[] { DatabaseDocument.DATABASE_DOCUMENT.PROJECT_ID }, true);
|
||||
public static final UniqueKey<DatabaseDocumentHistoryRecord> KEY_DATABASE_DOCUMENT_HISTORY_PRIMARY = Internal.createUniqueKey(DatabaseDocumentHistory.DATABASE_DOCUMENT_HISTORY, DSL.name("KEY_database_document_history_PRIMARY"), new TableField[] { DatabaseDocumentHistory.DATABASE_DOCUMENT_HISTORY.ID }, true);
|
||||
public static final UniqueKey<DatabaseDocumentHistoryRecord> KEY_DATABASE_DOCUMENT_HISTORY_UK_CONNECTION_ID_VERSION = Internal.createUniqueKey(DatabaseDocumentHistory.DATABASE_DOCUMENT_HISTORY, DSL.name("KEY_database_document_history_uk_connection_id_version"), new TableField[] { DatabaseDocumentHistory.DATABASE_DOCUMENT_HISTORY.DATABASE_DOCUMENT_ID, DatabaseDocumentHistory.DATABASE_DOCUMENT_HISTORY.VERSION }, true);
|
||||
public static final UniqueKey<GroupRecord> KEY_GROUP_PRIMARY = Internal.createUniqueKey(Group.GROUP, DSL.name("KEY_group_PRIMARY"), new TableField[] { Group.GROUP.ID }, true);
|
||||
public static final UniqueKey<GroupRecord> KEY_GROUP_UK_NAME = Internal.createUniqueKey(Group.GROUP, DSL.name("KEY_group_uk_name"), new TableField[] { Group.GROUP.NAME }, true);
|
||||
public static final UniqueKey<LoginRecord> KEY_LOGIN_PRIMARY = Internal.createUniqueKey(Login.LOGIN, DSL.name("KEY_login_PRIMARY"), new TableField[] { Login.LOGIN.ID }, true);
|
||||
public static final UniqueKey<LoginRecord> KEY_LOGIN_UK_USER_ID = Internal.createUniqueKey(Login.LOGIN, DSL.name("KEY_login_uk_user_id"), new TableField[] { Login.LOGIN.USER_ID }, true);
|
||||
public static final UniqueKey<ProjectRecord> KEY_PROJECT_PRIMARY = Internal.createUniqueKey(Project.PROJECT, DSL.name("KEY_project_PRIMARY"), new TableField[] { Project.PROJECT.ID }, true);
|
||||
public static final UniqueKey<ProjectRecord> KEY_PROJECT_UK_GROUP_ID_NAME = Internal.createUniqueKey(Project.PROJECT, DSL.name("KEY_project_uk_group_id_name"), new TableField[] { Project.PROJECT.GROUP_ID, Project.PROJECT.NAME }, true);
|
||||
public static final UniqueKey<ProjectSyncRuleRecord> KEY_PROJECT_SYNC_RULE_PRIMARY = Internal.createUniqueKey(ProjectSyncRule.PROJECT_SYNC_RULE, DSL.name("KEY_project_sync_rule_PRIMARY"), new TableField[] { ProjectSyncRule.PROJECT_SYNC_RULE.ID }, true);
|
||||
public static final UniqueKey<ProjectSyncRuleRecord> KEY_PROJECT_SYNC_RULE_UK_PROJECT_ID = Internal.createUniqueKey(ProjectSyncRule.PROJECT_SYNC_RULE, DSL.name("KEY_project_sync_rule_uk_project_id"), new TableField[] { ProjectSyncRule.PROJECT_SYNC_RULE.PROJECT_ID }, true);
|
||||
public static final UniqueKey<SysKeyRecord> KEY_SYS_KEY_PRIMARY = Internal.createUniqueKey(SysKey.SYS_KEY, DSL.name("KEY_sys_key_PRIMARY"), new TableField[] { SysKey.SYS_KEY.ID }, true);
|
||||
public static final UniqueKey<SysMailRecord> KEY_SYS_MAIL_PRIMARY = Internal.createUniqueKey(SysMail.SYS_MAIL, DSL.name("KEY_sys_mail_PRIMARY"), new TableField[] { SysMail.SYS_MAIL.ID }, true);
|
||||
public static final UniqueKey<TableColumnDocumentRecord> KEY_TABLE_COLUMN_DOCUMENT_PRIMARY = Internal.createUniqueKey(TableColumnDocument.TABLE_COLUMN_DOCUMENT, DSL.name("KEY_table_column_document_PRIMARY"), new TableField[] { TableColumnDocument.TABLE_COLUMN_DOCUMENT.ID }, true);
|
||||
public static final UniqueKey<TableDocumentRecord> KEY_TABLE_DOCUMENT_PRIMARY = Internal.createUniqueKey(TableDocument.TABLE_DOCUMENT, DSL.name("KEY_table_document_PRIMARY"), new TableField[] { TableDocument.TABLE_DOCUMENT.ID }, true);
|
||||
public static final UniqueKey<TableIndexDocumentRecord> KEY_TABLE_INDEX_DOCUMENT_PRIMARY = Internal.createUniqueKey(TableIndexDocument.TABLE_INDEX_DOCUMENT, DSL.name("KEY_table_index_document_PRIMARY"), new TableField[] { TableIndexDocument.TABLE_INDEX_DOCUMENT.ID }, true);
|
||||
public static final UniqueKey<TableTriggerDocumentRecord> KEY_TABLE_TRIGGER_DOCUMENT_PRIMARY = Internal.createUniqueKey(TableTriggerDocument.TABLE_TRIGGER_DOCUMENT, DSL.name("KEY_table_trigger_document_PRIMARY"), new TableField[] { TableTriggerDocument.TABLE_TRIGGER_DOCUMENT.ID }, true);
|
||||
public static final UniqueKey<UserRecord> KEY_USER_PRIMARY = Internal.createUniqueKey(User.USER, DSL.name("KEY_user_PRIMARY"), new TableField[] { User.USER.ID }, true);
|
||||
public static final UniqueKey<UserRecord> KEY_USER_UK_EMAIL = Internal.createUniqueKey(User.USER, DSL.name("KEY_user_uk_email"), new TableField[] { User.USER.EMAIL }, true);
|
||||
public static final UniqueKey<UserRecord> KEY_USER_UK_USERNAME = Internal.createUniqueKey(User.USER, DSL.name("KEY_user_uk_username"), new TableField[] { User.USER.USERNAME }, true);
|
||||
public static final UniqueKey<UserRoleRecord> KEY_USER_ROLE_PRIMARY = Internal.createUniqueKey(UserRole.USER_ROLE, DSL.name("KEY_user_role_PRIMARY"), new TableField[] { UserRole.USER_ROLE.ID }, true);
|
||||
public static final UniqueKey<UserRoleRecord> KEY_USER_ROLE_UK_USER_ID_GROUP_ID_ROLE = Internal.createUniqueKey(UserRole.USER_ROLE, DSL.name("KEY_user_role_uk_user_id_group_id_role"), new TableField[] { UserRole.USER_ROLE.USER_ID, UserRole.USER_ROLE.GROUP_ID, UserRole.USER_ROLE.ROLE }, true);
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue