迁移合理目录,增加系统参数

This commit is contained in:
戚辰先生 2024-11-30 00:13:37 +08:00
parent 6655f120de
commit 39b26ac2f8
46 changed files with 410 additions and 188 deletions

View File

@ -1,8 +1,8 @@
package com.qqchen.deploy.backend.api;
import com.qqchen.deploy.backend.dto.TenantDTO;
import com.qqchen.deploy.backend.model.TenantDTO;
import com.qqchen.deploy.backend.framework.controller.BaseController;
import com.qqchen.deploy.backend.dto.query.TenantQuery;
import com.qqchen.deploy.backend.model.query.TenantQuery;
import com.qqchen.deploy.backend.entity.Tenant;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.bind.annotation.RequestMapping;

View File

@ -1,14 +1,14 @@
package com.qqchen.deploy.backend.api;
import com.qqchen.deploy.backend.dto.RoleDTO;
import com.qqchen.deploy.backend.dto.query.UserQuery;
import com.qqchen.deploy.backend.dto.request.UserRequest;
import com.qqchen.deploy.backend.model.RoleDTO;
import com.qqchen.deploy.backend.model.query.UserQuery;
import com.qqchen.deploy.backend.model.request.UserRequest;
import com.qqchen.deploy.backend.framework.controller.BaseController;
import com.qqchen.deploy.backend.framework.api.Response;
import com.qqchen.deploy.backend.entity.User;
import com.qqchen.deploy.backend.dto.UserDTO;
import com.qqchen.deploy.backend.dto.request.LoginRequest;
import com.qqchen.deploy.backend.dto.response.LoginResponse;
import com.qqchen.deploy.backend.model.UserDTO;
import com.qqchen.deploy.backend.model.request.LoginRequest;
import com.qqchen.deploy.backend.model.response.LoginResponse;
import com.qqchen.deploy.backend.service.IUserService;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.annotation.Resource;
@ -21,7 +21,7 @@ import java.io.OutputStream;
import java.util.List;
@RestController
@RequestMapping("/api/v1/users")
@RequestMapping("/api/v1/user")
public class UserApiController extends BaseController<User, UserDTO, Long, UserQuery> {
@Resource
@ -34,10 +34,10 @@ public class UserApiController extends BaseController<User, UserDTO, Long, UserQ
return Response.success(userService.login(request));
}
@Operation(summary = "用户注册")
@PostMapping("/register")
public Response<UserDTO> register(@Validated @RequestBody UserRequest request) {
return Response.success(userService.register(request));
@Operation(summary = "获取当前用户信息")
@GetMapping("/current")
public Response<LoginResponse> getCurrentUser() {
return Response.success(userService.getCurrentUser());
}
@Override
@ -52,15 +52,4 @@ public class UserApiController extends BaseController<User, UserDTO, Long, UserQ
}
}
@GetMapping("/roles")
@Operation(summary = "获取当前用户的角色")
public Response<List<RoleDTO>> getCurrentUserRoles() {
return Response.success(userService.getCurrentUserRoles());
}
@GetMapping("/{id}/roles")
@Operation(summary = "获取指定用户的角色")
public Response<List<RoleDTO>> getUserRoles(@PathVariable Long id) {
return Response.success(userService.getUserRoles(id));
}
}

View File

@ -1,9 +1,9 @@
package com.qqchen.deploy.backend.controller;
import com.qqchen.deploy.backend.dto.query.TenantQuery;
import com.qqchen.deploy.backend.model.query.TenantQuery;
import com.qqchen.deploy.backend.entity.Tenant;
import com.qqchen.deploy.backend.framework.controller.BaseController;
import com.qqchen.deploy.backend.dto.TenantDTO;
import com.qqchen.deploy.backend.model.TenantDTO;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

View File

@ -1,7 +1,7 @@
package com.qqchen.deploy.backend.controller;
import com.qqchen.deploy.backend.api.UserApiController;
import com.qqchen.deploy.backend.dto.UserDTO;
import com.qqchen.deploy.backend.model.UserDTO;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@ -9,7 +9,7 @@ import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequestMapping("/mgmt/users")
@RequestMapping("/mgmt/user")
public class UserController extends UserApiController {
@Override

View File

@ -1,6 +1,6 @@
package com.qqchen.deploy.backend.converter;
import com.qqchen.deploy.backend.dto.RoleDTO;
import com.qqchen.deploy.backend.model.RoleDTO;
import com.qqchen.deploy.backend.entity.Role;
import com.qqchen.deploy.backend.framework.converter.BaseConverter;
import org.mapstruct.Mapper;

View File

@ -0,0 +1,10 @@
package com.qqchen.deploy.backend.converter;
import com.qqchen.deploy.backend.entity.SysParam;
import com.qqchen.deploy.backend.framework.converter.BaseConverter;
import com.qqchen.deploy.backend.model.SysParamDTO;
import org.mapstruct.Mapper;
@Mapper(config = BaseConverter.class)
public interface SysParamConverter extends BaseConverter<SysParam, SysParamDTO> {
}

View File

@ -1,6 +1,6 @@
package com.qqchen.deploy.backend.converter;
import com.qqchen.deploy.backend.dto.TenantDTO;
import com.qqchen.deploy.backend.model.TenantDTO;
import com.qqchen.deploy.backend.entity.Tenant;
import com.qqchen.deploy.backend.framework.converter.BaseConverter;
import org.mapstruct.Mapper;

View File

@ -1,11 +1,19 @@
package com.qqchen.deploy.backend.converter;
import com.qqchen.deploy.backend.framework.converter.BaseConverter;
import com.qqchen.deploy.backend.entity.User;
import com.qqchen.deploy.backend.dto.UserDTO;
import com.qqchen.deploy.backend.framework.converter.BaseConverter;
import com.qqchen.deploy.backend.model.UserDTO;
import com.qqchen.deploy.backend.model.response.LoginResponse;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
@Mapper(config = BaseConverter.class)
public interface UserConverter extends BaseConverter<User, UserDTO> {
// MapStruct 会自动实现所有方法
@Mapping(target = "token", ignore = true)
LoginResponse toLoginResponse(User user);
@Mapping(target = "token", source = "token")
LoginResponse toLoginResponse(User user, String token);
}

View File

@ -0,0 +1,32 @@
package com.qqchen.deploy.backend.entity;
import com.qqchen.deploy.backend.framework.annotation.LogicDelete;
import com.qqchen.deploy.backend.framework.domain.Entity;
import jakarta.persistence.Column;
import jakarta.persistence.Table;
import lombok.Data;
import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true)
@jakarta.persistence.Entity
@Table(name = "sys_param")
@LogicDelete
public class SysParam extends Entity<Long> {
@Column(nullable = false, length = 100)
private String code;
@Column(nullable = false, length = 100)
private String name;
@Column(columnDefinition = "TEXT")
private String value;
@Column(nullable = false, length = 50)
private String type;
private String description;
private Boolean enabled;
}

View File

@ -1,4 +1,4 @@
package com.qqchen.deploy.backend.framework.audit.annotation;
package com.qqchen.deploy.backend.framework.annotation;
import java.lang.annotation.*;

View File

@ -1,7 +1,7 @@
package com.qqchen.deploy.backend.framework.audit.aspect;
import com.qqchen.deploy.backend.framework.audit.AuditMetadata;
import com.qqchen.deploy.backend.framework.audit.annotation.Audited;
import com.qqchen.deploy.backend.framework.annotation.Audited;
import com.qqchen.deploy.backend.framework.audit.event.AuditEvent;
import com.qqchen.deploy.backend.framework.security.SecurityUtils;
import jakarta.servlet.http.HttpServletRequest;
@ -26,7 +26,7 @@ public class AuditAspect {
private final ApplicationEventPublisher eventPublisher;
@Around("@annotation(com.qqchen.deploy.backend.framework.audit.annotation.Audited)")
@Around("@annotation(com.qqchen.deploy.backend.framework.annotation.Audited)")
public Object auditMethod(ProceedingJoinPoint joinPoint) throws Throwable {
// 获取审计元数据
AuditMetadata metadata = extractAuditMetadata(joinPoint);

View File

@ -1,4 +1,4 @@
package com.qqchen.deploy.backend.framework.config;
package com.qqchen.deploy.backend.framework.audit.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

View File

@ -1,6 +1,5 @@
package com.qqchen.deploy.backend.framework.config;
import com.qqchen.deploy.backend.framework.interceptor.TenantInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;

View File

@ -55,14 +55,11 @@ public class DependencyInjectionPostProcessor implements BeanPostProcessor, Disp
if (bean instanceof BaseServiceImpl<?, ?, ?> service) {
return injectServiceDependencies(service, beanName);
}
} catch (DependencyInjectionException e) {
log.error("Dependency injection failed for bean: {}", beanName, e);
throw e;
} catch (Exception e) {
log.error("Unexpected error during dependency injection for bean: {}", beanName, e);
throw new DependencyInjectionException(
String.format("Failed to inject dependencies for bean: %s", beanName),
e);
String errorMsg = String.format("依赖注入失败 - Bean: %s, 类型: %s, 原因: %s",
beanName, bean.getClass().getName(), e.getMessage());
log.error(errorMsg, e);
throw new DependencyInjectionException(errorMsg, e);
}
return bean;
}
@ -83,12 +80,12 @@ public class DependencyInjectionPostProcessor implements BeanPostProcessor, Disp
try {
IBaseService<T, D, ID> service = (IBaseService<T, D, ID>) cachedService;
ReflectionUtils.setField("service", controller, service);
log.debug("Successfully injected service {} for controller {}",
service.getClass().getSimpleName(),
log.debug("Successfully injected service {} for controller {}",
service.getClass().getSimpleName(),
controller.getClass().getSimpleName());
} catch (Exception e) {
throw new DependencyInjectionException(
String.format("Failed to inject service for controller: %s", controller.getClass().getSimpleName()),
String.format("Failed to inject service for controller: %s", controller.getClass().getSimpleName()),
e);
}
@ -151,12 +148,9 @@ public class DependencyInjectionPostProcessor implements BeanPostProcessor, Disp
log.debug("Successfully injected all dependencies for service: {}", beanName);
return service;
} catch (DependencyInjectionException e) {
throw e;
} catch (Exception e) {
throw new DependencyInjectionException(
String.format("Failed to inject dependencies for service: %s", service.getClass().getSimpleName()),
e);
String.format("Failed to inject dependencies for service: %s", service.getClass().getSimpleName()), e);
}
}
@ -189,20 +183,28 @@ public class DependencyInjectionPostProcessor implements BeanPostProcessor, Disp
private <T extends Entity<ID>, D extends BaseDTO, ID extends Serializable>
void injectRepository(BaseServiceImpl<T, D, ID> service, Class<T> entityClass) {
String repositoryBeanName = "I" + entityClass.getSimpleName() + "Repository";
log.debug("Looking for repository bean: {}", repositoryBeanName);
log.debug("正在查找 Repository: {}", repositoryBeanName);
IBaseRepository<T, ID> repository = (IBaseRepository<T, ID>) repositoryCache.computeIfAbsent(
entityClass,
key -> {
if (!applicationContext.containsBean(repositoryBeanName)) {
throw new BusinessException(
ResponseCode.DEPENDENCY_INJECTION_REPOSITORY_NOT_FOUND,
new Object[] {key.getSimpleName(), repositoryBeanName});
}
return (IBaseRepository<?, ?>) applicationContext.getBean(repositoryBeanName);
});
try {
IBaseRepository<T, ID> repository = (IBaseRepository<T, ID>) repositoryCache.computeIfAbsent(
entityClass,
key -> {
if (!applicationContext.containsBean(repositoryBeanName)) {
String errorMsg = String.format("找不到 Repository - 服务类: %s, 实体类: %s, Repository名称: %s",
service.getClass().getName(), key.getSimpleName(), repositoryBeanName);
log.error(errorMsg);
throw new DependencyInjectionException(errorMsg, new RuntimeException(errorMsg));
}
return (IBaseRepository<?, ?>) applicationContext.getBean(repositoryBeanName);
});
ReflectionUtils.setField("repository", service, repository);
ReflectionUtils.setField("repository", service, repository);
} catch (Exception e) {
String errorMsg = String.format("注入 Repository 失败 - 服务类: %s, 实体类: %s, Repository名称: %s, 原因: %s",
service.getClass().getName(), entityClass.getSimpleName(), repositoryBeanName, e.getMessage());
log.error(errorMsg, e);
throw new DependencyInjectionException(errorMsg, e);
}
}
@SuppressWarnings("unchecked")
@ -210,44 +212,68 @@ public class DependencyInjectionPostProcessor implements BeanPostProcessor, Disp
void injectConverter(BaseServiceImpl<T, D, ID> service, Class<T> entityClass) {
String converterBeanName = entityClass.getSimpleName().substring(0, 1).toLowerCase()
+ entityClass.getSimpleName().substring(1) + "ConverterImpl";
log.debug("Looking for converter bean: {}", converterBeanName);
log.debug("正在查找 Converter: {}", converterBeanName);
BaseConverter<T, D> converter = (BaseConverter<T, D>) converterCache.computeIfAbsent(
entityClass,
key -> {
if (!applicationContext.containsBean(converterBeanName)) {
throw new BusinessException(
ResponseCode.DEPENDENCY_INJECTION_CONVERTER_NOT_FOUND,
new Object[] {key.getSimpleName(), converterBeanName});
}
return (BaseConverter<?, ?>) applicationContext.getBean(converterBeanName);
});
try {
BaseConverter<T, D> converter = (BaseConverter<T, D>) converterCache.computeIfAbsent(
entityClass,
key -> {
if (!applicationContext.containsBean(converterBeanName)) {
String errorMsg = String.format("找不到 Converter - 服务类: %s, 实体类: %s, Converter名称: %s",
service.getClass().getName(), key.getSimpleName(), converterBeanName);
log.error(errorMsg);
throw new DependencyInjectionException(errorMsg, new RuntimeException(errorMsg));
}
return (BaseConverter<?, ?>) applicationContext.getBean(converterBeanName);
});
ReflectionUtils.setField("converter", service, converter);
ReflectionUtils.setField("converter", service, converter);
} catch (Exception e) {
String errorMsg = String.format("注入 Converter 失败 - 服务类: %s, 实体类: %s, Converter名称: %s, 原因: %s",
service.getClass().getName(), entityClass.getSimpleName(), converterBeanName, e.getMessage());
log.error(errorMsg, e);
throw new DependencyInjectionException(errorMsg, e);
}
}
@SuppressWarnings("unchecked")
private <T extends Entity<ID>, D extends BaseDTO, ID extends Serializable> void injectEntityPath(BaseServiceImpl<T, D, ID> service, Class<T> entityClass) {
private <T extends Entity<ID>, D extends BaseDTO, ID extends Serializable>
void injectEntityPath(BaseServiceImpl<T, D, ID> service, Class<T> entityClass) {
try {
EntityPath<T> entityPath = (EntityPath<T>) entityPathCache.computeIfAbsent(
entityClass,
key -> {
String qClassName = key.getPackageName() + ".Q" + key.getSimpleName();
try {
String qClassName = key.getPackageName() + ".Q" + key.getSimpleName();
Class<?> qClass = Class.forName(qClassName);
Field instanceField = qClass.getDeclaredField(key.getSimpleName().toLowerCase());
String fieldName = key.getSimpleName();
fieldName = fieldName.substring(0, 1).toLowerCase() + fieldName.substring(1);
Field instanceField = qClass.getDeclaredField(fieldName);
return (EntityPath<?>) instanceField.get(null);
} catch (ClassNotFoundException e) {
String errorMsg = String.format("找不到 QueryDSL 实体类 - 服务类: %s, 实体类: %s, Q类名: %s",
service.getClass().getName(), key.getSimpleName(), qClassName);
log.error(errorMsg, e);
throw new DependencyInjectionException(errorMsg, e);
} catch (NoSuchFieldException e) {
String errorMsg = String.format("找不到 QueryDSL 实体字段 - 服务类: %s, 实体类: %s, Q类名: %s",
service.getClass().getName(), key.getSimpleName(), qClassName);
log.error(errorMsg, e);
throw new DependencyInjectionException(errorMsg, e);
} catch (Exception e) {
throw new BusinessException(
ResponseCode.DEPENDENCY_INJECTION_ENTITYPATH_FAILED,
new Object[] {key.getSimpleName(), e.getMessage()});
String errorMsg = String.format("获取 QueryDSL 实体失败 - 服务类: %s, 实体类: %s, Q类名: %s, 原因: %s",
service.getClass().getName(), key.getSimpleName(), qClassName, e.getMessage());
log.error(errorMsg, e);
throw new DependencyInjectionException(errorMsg, e);
}
});
ReflectionUtils.setField("entityPath", service, entityPath);
} catch (BusinessException e) {
throw e;
} catch (Exception e) {
throw new BusinessException(ResponseCode.DEPENDENCY_INJECTION_ENTITYPATH_FAILED, new Object[] {entityClass.getSimpleName(), e.getMessage()});
String errorMsg = String.format("注入 EntityPath 失败 - 服务类: %s, 实体类: %s, 原因: %s",
service.getClass().getName(), entityClass.getSimpleName(), e.getMessage());
log.error(errorMsg, e);
throw new DependencyInjectionException(errorMsg, e);
}
}
}

View File

@ -1,14 +1,12 @@
package com.qqchen.deploy.backend.framework.filter;
import com.qqchen.deploy.backend.framework.context.TenantContext;
import com.qqchen.deploy.backend.framework.tenant.TenantContext;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.io.IOException;

View File

@ -1,13 +1,14 @@
package com.qqchen.deploy.backend.framework.exception;
package com.qqchen.deploy.backend.framework.handler;
import com.qqchen.deploy.backend.framework.api.Response;
import com.qqchen.deploy.backend.framework.enums.ResponseCode;
import com.qqchen.deploy.backend.framework.exception.BusinessException;
import com.qqchen.deploy.backend.framework.exception.SystemException;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.MalformedJwtException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

View File

@ -1,4 +1,4 @@
package com.qqchen.deploy.backend.framework.config;
package com.qqchen.deploy.backend.framework.i18n;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;

View File

@ -1,4 +1,4 @@
package com.qqchen.deploy.backend.framework.config;
package com.qqchen.deploy.backend.framework.json;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;

View File

@ -22,48 +22,45 @@ import org.springframework.security.web.authentication.UsernamePasswordAuthentic
@RequiredArgsConstructor
public class SecurityConfig {
private final CustomAuthenticationEntryPoint authenticationEntryPoint;
private final CustomAuthenticationEntryPoint customAuthenticationEntryPoint;
private final JwtTokenUtil jwtTokenUtil;
private final UserDetailsService userDetailsService;
private final JwtTokenUtil jwtTokenUtil;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.cors(cors -> cors.disable())
.sessionManagement(session ->
session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
.authorizeHttpRequests(auth -> auth
.requestMatchers(
"/api/v1/user/login",
"/api/v1/user/register",
"/api/v1/tenant/list",
"/swagger-ui/**",
"/v3/api-docs/**",
"/actuator/health"
).permitAll()
.anyRequest().authenticated()
)
.addFilterBefore(
jwtAuthenticationFilter(),
UsernamePasswordAuthenticationFilter.class
)
.exceptionHandling(ex -> ex
.authenticationEntryPoint(customAuthenticationEntryPoint)
);
return http.build();
}
@Bean
public JwtAuthenticationFilter jwtAuthenticationFilter() {
return new JwtAuthenticationFilter(jwtTokenUtil, userDetailsService);
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
// .cors(cors -> cors.configurationSource(corsConfigurationSource()))
.csrf(csrf -> csrf.disable())
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.exceptionHandling(exceptions -> exceptions
.authenticationEntryPoint(authenticationEntryPoint))
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/v1/users/login", "/api/v1/users/register", "/api/v1/tenant/list").permitAll()
.requestMatchers("/swagger-ui/**", "/v3/api-docs/**").permitAll()
.requestMatchers("/actuator/health").permitAll()
.anyRequest().authenticated()
)
.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
//TODO 这里会导致无限递归循环报错
// @Bean
// public UserDetailsService userDetailsService() {
// UserDetails user = User.withUsername("anonymous")
// .password("{noop}")
// .roles("ANONYMOUS")
// .build();
// return new InMemoryUserDetailsManager(user);
// }
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();

View File

@ -2,7 +2,7 @@ package com.qqchen.deploy.backend.framework.security.filter;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.qqchen.deploy.backend.framework.api.Response;
import com.qqchen.deploy.backend.framework.context.TenantContext;
import com.qqchen.deploy.backend.framework.tenant.TenantContext;
import com.qqchen.deploy.backend.framework.enums.ResponseCode;
import com.qqchen.deploy.backend.framework.exception.BusinessException;
import com.qqchen.deploy.backend.framework.exception.JwtAuthenticationException;
@ -39,8 +39,8 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final UserDetailsService userDetailsService;
private static final List<String> WHITELIST = Arrays.asList(
"/api/v1/users/login",
"/api/v1/users/register",
"/api/v1/user/login",
"/api/v1/user/register",
"/api/v1/tenant/list",
"/swagger-ui/**",
"/v3/api-docs/**",
@ -59,10 +59,8 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
String jwt = getJwtFromRequest(request);
if (StringUtils.hasText(jwt)) {
String username = jwtTokenUtil.getUsernameFromToken(jwt);
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
if (jwtTokenUtil.validateToken(jwt, userDetails)) {
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());

View File

@ -39,7 +39,18 @@ public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
Response<?> errorResponse = Response.error(ResponseCode.UNAUTHORIZED);
Response<?> errorResponse;
if (authException instanceof BadCredentialsException) {
errorResponse = Response.error(ResponseCode.LOGIN_ERROR);
} else if (authException instanceof InternalAuthenticationServiceException) {
errorResponse = Response.error(ResponseCode.ERROR);
} else if (authException instanceof InsufficientAuthenticationException) {
errorResponse = Response.error(ResponseCode.AUTH_REQUIRED);
} else {
errorResponse = Response.error(ResponseCode.UNAUTHORIZED);
}
objectMapper.writeValue(response.getOutputStream(), errorResponse);
}

View File

@ -1,4 +1,4 @@
package com.qqchen.deploy.backend.framework.context;
package com.qqchen.deploy.backend.framework.tenant;
public class TenantContext {
private static final ThreadLocal<String> CURRENT_TENANT = new ThreadLocal<>();

View File

@ -1,11 +1,9 @@
package com.qqchen.deploy.backend.framework.interceptor;
package com.qqchen.deploy.backend.framework.tenant;
import com.qqchen.deploy.backend.framework.context.TenantContext;
import com.qqchen.deploy.backend.framework.enums.ResponseCode;
import com.qqchen.deploy.backend.framework.exception.BusinessException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.servlet.HandlerInterceptor;

View File

@ -1,4 +1,4 @@
package com.qqchen.deploy.backend.dto;
package com.qqchen.deploy.backend.model;
import com.qqchen.deploy.backend.framework.dto.BaseDTO;
import lombok.Data;

View File

@ -0,0 +1,20 @@
package com.qqchen.deploy.backend.model;
import com.qqchen.deploy.backend.framework.dto.BaseDTO;
import lombok.Data;
@Data
public class SysParamDTO extends BaseDTO {
private String code;
private String name;
private String value;
private String type;
private String description;
private Boolean enabled;
}

View File

@ -1,4 +1,4 @@
package com.qqchen.deploy.backend.dto;
package com.qqchen.deploy.backend.model;
import com.qqchen.deploy.backend.framework.dto.BaseDTO;
import lombok.Data;

View File

@ -1,4 +1,4 @@
package com.qqchen.deploy.backend.dto;
package com.qqchen.deploy.backend.model;
import com.qqchen.deploy.backend.framework.dto.BaseDTO;
import lombok.Data;

View File

@ -0,0 +1,16 @@
package com.qqchen.deploy.backend.model.query;
import com.qqchen.deploy.backend.framework.query.BaseQuery;
import lombok.Data;
@Data
public class SysParamQuery extends BaseQuery {
private String code;
private String name;
private String type;
private Boolean enabled;
}

View File

@ -1,4 +1,4 @@
package com.qqchen.deploy.backend.dto.query;
package com.qqchen.deploy.backend.model.query;
import com.qqchen.deploy.backend.framework.annotation.QueryField;
import com.qqchen.deploy.backend.framework.enums.QueryType;

View File

@ -1,4 +1,4 @@
package com.qqchen.deploy.backend.dto.query;
package com.qqchen.deploy.backend.model.query;
import com.qqchen.deploy.backend.framework.annotation.QueryField;
import com.qqchen.deploy.backend.framework.query.BaseQuery;

View File

@ -1,4 +1,4 @@
package com.qqchen.deploy.backend.dto.request;
package com.qqchen.deploy.backend.model.request;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
@ -11,4 +11,7 @@ public class LoginRequest {
@NotBlank(message = "密码不能为空")
private String password;
@NotBlank(message = "租户不能为空")
private String tenantId;
}

View File

@ -1,4 +1,4 @@
package com.qqchen.deploy.backend.dto.request;
package com.qqchen.deploy.backend.model.request;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;

View File

@ -1,10 +1,9 @@
package com.qqchen.deploy.backend.dto.request;
package com.qqchen.deploy.backend.model.request;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
public class UserRequest {

View File

@ -1,4 +1,4 @@
package com.qqchen.deploy.backend.dto.response;
package com.qqchen.deploy.backend.model.response;
import lombok.Data;
import lombok.EqualsAndHashCode;

View File

@ -1,4 +1,4 @@
package com.qqchen.deploy.backend.dto.response;
package com.qqchen.deploy.backend.model.response;
import com.qqchen.deploy.backend.framework.dto.BaseResponse;
import lombok.Data;

View File

@ -1,4 +1,4 @@
package com.qqchen.deploy.backend.dto.response;
package com.qqchen.deploy.backend.model.response;
import com.qqchen.deploy.backend.framework.dto.BaseResponse;
import lombok.Data;
@ -9,8 +9,6 @@ import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true)
public class UserResponse extends BaseResponse {
private Long id;
private String username;
private String email;
@ -21,5 +19,4 @@ public class UserResponse extends BaseResponse {
private Boolean enabled;
private String password;
}

View File

@ -0,0 +1,39 @@
package com.qqchen.deploy.backend.repository;
import com.qqchen.deploy.backend.entity.SysParam;
import com.qqchen.deploy.backend.framework.repository.IBaseRepository;
import java.util.List;
import java.util.Optional;
public interface ISysParamRepository extends IBaseRepository<SysParam, Long> {
/**
* 根据参数编码查询参数
* @param code 参数编码
* @return 参数对象
*/
Optional<SysParam> findByCodeAndDeletedFalse(String code);
/**
* 根据参数编码和启用状态查询参数
* @param code 参数编码
* @param enabled 启用状态
* @return 参数对象
*/
Optional<SysParam> findByCodeAndEnabledAndDeletedFalse(String code, Boolean enabled);
/**
* 根据参数类型查询参数列表
* @param type 参数类型
* @return 参数列表
*/
List<SysParam> findByTypeAndDeletedFalse(String type);
/**
* 检查参数编码是否存在
* @param code 参数编码
* @return 是否存在
*/
boolean existsByCodeAndDeletedFalse(String code);
}

View File

@ -0,0 +1,8 @@
package com.qqchen.deploy.backend.service;
import com.qqchen.deploy.backend.entity.SysParam;
import com.qqchen.deploy.backend.framework.service.IBaseService;
import com.qqchen.deploy.backend.model.SysParamDTO;
public interface ISysParamService extends IBaseService<SysParam, SysParamDTO, Long> {
}

View File

@ -2,7 +2,7 @@ package com.qqchen.deploy.backend.service;
import com.qqchen.deploy.backend.entity.Tenant;
import com.qqchen.deploy.backend.framework.service.IBaseService;
import com.qqchen.deploy.backend.dto.TenantDTO;
import com.qqchen.deploy.backend.model.TenantDTO;
public interface ITenantService extends IBaseService<Tenant, TenantDTO, Long> {
// 添加租户特有的业务方法

View File

@ -1,13 +1,13 @@
package com.qqchen.deploy.backend.service;
import com.qqchen.deploy.backend.dto.UserDTO;
import com.qqchen.deploy.backend.framework.audit.annotation.Audited;
import com.qqchen.deploy.backend.model.UserDTO;
import com.qqchen.deploy.backend.framework.annotation.Audited;
import com.qqchen.deploy.backend.framework.service.IBaseService;
import com.qqchen.deploy.backend.dto.request.LoginRequest;
import com.qqchen.deploy.backend.dto.response.LoginResponse;
import com.qqchen.deploy.backend.model.request.LoginRequest;
import com.qqchen.deploy.backend.model.response.LoginResponse;
import com.qqchen.deploy.backend.entity.User;
import com.qqchen.deploy.backend.dto.request.UserRequest;
import com.qqchen.deploy.backend.dto.RoleDTO;
import com.qqchen.deploy.backend.model.request.UserRequest;
import com.qqchen.deploy.backend.model.RoleDTO;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@ -23,4 +23,7 @@ public interface IUserService extends IBaseService<User, UserDTO, Long> {
@Transactional(readOnly = false)
@Audited(action = "CHANGE_PASSWORD", detail = "修改密码")
void changePassword(Long userId, String oldPassword, String newPassword);
@Transactional(readOnly = true)
LoginResponse getCurrentUser();
}

View File

@ -0,0 +1,26 @@
package com.qqchen.deploy.backend.service.impl;
import com.qqchen.deploy.backend.converter.SysParamConverter;
import com.qqchen.deploy.backend.entity.SysParam;
import com.qqchen.deploy.backend.framework.annotation.ServiceType;
import com.qqchen.deploy.backend.framework.service.impl.BaseServiceImpl;
import com.qqchen.deploy.backend.model.SysParamDTO;
import com.qqchen.deploy.backend.repository.ISysParamRepository;
import com.qqchen.deploy.backend.service.ISysParamService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import static com.qqchen.deploy.backend.framework.annotation.ServiceType.Type.DATABASE;
@Slf4j
@Service
@ServiceType(DATABASE)
public class SysParamServiceImpl extends BaseServiceImpl<SysParam, SysParamDTO, Long> implements ISysParamService {
@Resource
private SysParamConverter converter;
@Resource
private ISysParamRepository repository;
}

View File

@ -1,16 +1,11 @@
package com.qqchen.deploy.backend.service.impl;
import com.qqchen.deploy.backend.entity.Tenant;
import com.qqchen.deploy.backend.framework.query.BaseQuery;
import com.qqchen.deploy.backend.framework.service.impl.BaseServiceImpl;
import com.qqchen.deploy.backend.repository.ITenantRepository;
import com.qqchen.deploy.backend.service.ITenantService;
import com.qqchen.deploy.backend.dto.TenantDTO;
import com.qqchen.deploy.backend.converter.TenantConverter;
import com.qqchen.deploy.backend.model.TenantDTO;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class TenantServiceImpl extends BaseServiceImpl<Tenant, TenantDTO, Long> implements ITenantService {

View File

@ -1,21 +1,22 @@
package com.qqchen.deploy.backend.service.impl;
import com.qqchen.deploy.backend.converter.UserConverter;
import com.qqchen.deploy.backend.framework.annotation.ServiceType;
import com.qqchen.deploy.backend.framework.audit.annotation.Audited;
import com.qqchen.deploy.backend.framework.annotation.Audited;
import com.qqchen.deploy.backend.framework.enums.ResponseCode;
import com.qqchen.deploy.backend.framework.exception.BusinessException;
import com.qqchen.deploy.backend.framework.security.SecurityUtils;
import com.qqchen.deploy.backend.framework.security.util.JwtTokenUtil;
import com.qqchen.deploy.backend.framework.service.impl.BaseServiceImpl;
import com.qqchen.deploy.backend.dto.request.LoginRequest;
import com.qqchen.deploy.backend.dto.request.UserRequest;
import com.qqchen.deploy.backend.dto.response.LoginResponse;
import com.qqchen.deploy.backend.model.request.LoginRequest;
import com.qqchen.deploy.backend.model.request.UserRequest;
import com.qqchen.deploy.backend.model.response.LoginResponse;
import com.qqchen.deploy.backend.entity.User;
import com.qqchen.deploy.backend.repository.IUserRepository;
import com.qqchen.deploy.backend.service.IUserService;
import com.qqchen.deploy.backend.converter.RoleConverter;
import com.qqchen.deploy.backend.converter.UserConverter;
import com.qqchen.deploy.backend.dto.UserDTO;
import com.qqchen.deploy.backend.dto.RoleDTO;
import com.qqchen.deploy.backend.model.UserDTO;
import com.qqchen.deploy.backend.model.RoleDTO;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.Hibernate;
@ -38,6 +39,9 @@ public class UserServiceImpl extends BaseServiceImpl<User, UserDTO, Long> implem
@Resource
private RoleConverter roleConverter;
@Resource
private UserConverter userConverter;
@Resource
private AuthenticationManager authenticationManager;
@ -88,13 +92,8 @@ public class UserServiceImpl extends BaseServiceImpl<User, UserDTO, Long> implem
String token = jwtTokenUtil.generateToken(userDetails);
User user = userRepository.findByUsernameAndDeletedFalse(userDetails.getUsername())
.orElseThrow(() -> new BusinessException(ResponseCode.USER_NOT_FOUND));
LoginResponse response = new LoginResponse();
response.setId(user.getId());
response.setUsername(user.getUsername());
response.setNickname(user.getNickname());
response.setEmail(user.getEmail());
response.setPhone(user.getPhone());
response.setToken(token);
LoginResponse response = userConverter.toLoginResponse(user, token);
log.info("用户 {} ({}) 登录成功", user.getUsername(), user.getNickname());
return response;
}
@ -129,4 +128,17 @@ public class UserServiceImpl extends BaseServiceImpl<User, UserDTO, Long> implem
public void changePassword(Long userId, String oldPassword, String newPassword) {
// ... 密码修改逻辑 ...
}
@Override
@Transactional(readOnly = true)
@Audited(action = "GET_CURRENT_USER", detail = "获取当前用户信息")
public LoginResponse getCurrentUser() {
String username = SecurityUtils.getCurrentUsername();
User user = userRepository.findByUsernameAndDeletedFalse(username)
.orElseThrow(() -> new BusinessException(ResponseCode.USER_NOT_FOUND));
LoginResponse response = userConverter.toLoginResponse(user);
log.debug("获取当前用户信息: {}", user.getUsername());
return response;
}
}

View File

@ -2,9 +2,9 @@ server:
port: 8080
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/deploy-ease-platform?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true
username: root
password: root
url: jdbc:mysql://192.168.1.111:3306/deploy-ease-platform?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true
username: deploy-ease-platform
password: qichen5210523
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
hibernate:
@ -46,4 +46,11 @@ jwt:
expiration: 86400
jackson:
time-zone: Asia/Shanghai
time-zone: Asia/Shanghai
flyway:
enabled: true
baseline-on-migrate: true
locations: classpath:db/migration
table: flyway_schema_history
baseline-version: 0
validate-on-migrate: true

View File

@ -33,4 +33,25 @@ CREATE TABLE IF NOT EXISTS sys_user (
phone VARCHAR(255) NULL,
username VARCHAR(255) NOT NULL,
CONSTRAINT UK_user_username UNIQUE (username)
);
);
-- 创建系统参数表
CREATE TABLE sys_param (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
create_by VARCHAR(255),
create_time DATETIME(6),
deleted BIT NOT NULL DEFAULT 0,
update_by VARCHAR(255),
update_time DATETIME(6),
version INT NOT NULL DEFAULT 0,
code VARCHAR(100) NOT NULL COMMENT '参数编码',
name VARCHAR(100) NOT NULL COMMENT '参数名称',
value TEXT COMMENT '参数值',
type VARCHAR(50) NOT NULL COMMENT '参数类型',
description VARCHAR(255) COMMENT '参数描述',
enabled BIT NOT NULL DEFAULT 1 COMMENT '是否启用',
CONSTRAINT UK_sys_param_code UNIQUE (code)
) COMMENT '系统参数表';

View File

@ -3,15 +3,24 @@ INSERT INTO sys_tenant
(create_by, create_time, deleted, update_by, update_time, version,
address, code, contact_name, contact_phone, email, enabled, name)
VALUES
('system', NOW(), 0, 'system', NOW(), 0,
'北京市朝阳区xxx街道', 'default', '张三', '13900000001', 'default@example.com', 1, '默认租户');
('system', '2024-01-01 00:00:00', 0, 'system', '2024-01-01 00:00:00', 0,
'北京市朝阳区望京SOHO T1 C座', 'default', '张三', '13900000001',
'admin@deploy-ease.com', 1, '默认租户');
-- 插入管理员用户
INSERT INTO sys_user
(create_by, create_time, deleted, update_by, update_time, version,
email, enabled, nickname, password, phone, username)
VALUES
('system', NOW(), 0, 'system', NOW(), 0,
'admin@example.com', 1, '系统管理员',
'$2a$10$mW/yJPHjyueQ1g82qWXg8eYqyUVNxFQPagkUvqtWPhKhqB8Z3Vw2y',
'13800138000', 'admin');
('system', '2024-01-01 00:00:00', 0, 'system', '2024-01-01 00:00:00', 0,
'admin@deploy-ease.com', 1, '系统管理员',
'$2a$10$B5qWUPyWyXxfm6Jq4JKcAOp/XcQLjtv3jifafsqPqZvzY9owae2ve', -- 密码: admin123
'13800138000', 'admin');
-- 系统参数初始化
INSERT INTO sys_param (code, name, value, type, description, enabled, create_by, create_time)
VALUES
('SYSTEM_NAME', '系统名称', 'DevOps平台', 'TEXT', '系统显示名称', 1, 'system', '2024-01-01 00:00:00'),
('SYSTEM_LOGO', '系统Logo', '/logo.png', 'TEXT', '系统Logo路径', 1, 'system', '2024-01-01 00:00:00'),
('GENDER_ENUM', '性别枚举', '[{"code":"1","name":"男"},{"code":"2","name":"女"}]', 'ENUM', '性别枚举值', 1, 'system', '2024-01-01 00:00:00'),
('USER_STATUS_ENUM', '用户状态枚举', '[{"code":"0","name":"禁用"},{"code":"1","name":"启用"}]', 'ENUM', '用户状态枚举值', 1, 'system', '2024-01-01 00:00:00');