可正常启动
This commit is contained in:
parent
c913ab3cc5
commit
1afd981805
@ -4,7 +4,6 @@ import com.qqchen.deploy.backend.dto.TenantDTO;
|
||||
import com.qqchen.deploy.backend.framework.controller.BaseController;
|
||||
import com.qqchen.deploy.backend.dto.query.TenantQuery;
|
||||
import com.qqchen.deploy.backend.entity.Tenant;
|
||||
import com.qqchen.deploy.backend.service.ITenantService;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
@ -15,13 +14,6 @@ import java.util.List;
|
||||
@RequestMapping("/api/v1/tenant")
|
||||
public class TenantApiController extends BaseController<Tenant, TenantDTO, Long, TenantQuery> {
|
||||
|
||||
protected final ITenantService service;
|
||||
|
||||
public TenantApiController(ITenantService service) {
|
||||
super(service);
|
||||
this.service = service;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void exportData(HttpServletResponse response, List<TenantDTO> data) {
|
||||
|
||||
|
||||
@ -11,6 +11,7 @@ import com.qqchen.deploy.backend.dto.request.LoginRequest;
|
||||
import com.qqchen.deploy.backend.dto.response.LoginResponse;
|
||||
import com.qqchen.deploy.backend.service.IUserService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
@ -23,12 +24,9 @@ import java.util.List;
|
||||
@RequestMapping("/api/v1/users")
|
||||
public class UserApiController extends BaseController<User, UserDTO, Long, UserQuery> {
|
||||
|
||||
private final IUserService userService;
|
||||
@Resource
|
||||
private IUserService userService;
|
||||
|
||||
public UserApiController(IUserService userService) {
|
||||
super(userService);
|
||||
this.userService = userService;
|
||||
}
|
||||
|
||||
@Operation(summary = "用户登录")
|
||||
@PostMapping("/login")
|
||||
@ -46,7 +44,7 @@ public class UserApiController extends BaseController<User, UserDTO, Long, UserQ
|
||||
protected void exportData(HttpServletResponse response, List<UserDTO> data) {
|
||||
response.setContentType("application/vnd.ms-excel");
|
||||
response.setHeader("Content-Disposition", "attachment;filename=users.xlsx");
|
||||
|
||||
|
||||
try (OutputStream out = response.getOutputStream()) {
|
||||
// ExcelUtils.export(data, out);
|
||||
} catch (IOException e) {
|
||||
|
||||
@ -3,7 +3,6 @@ package com.qqchen.deploy.backend.controller;
|
||||
import com.qqchen.deploy.backend.dto.query.TenantQuery;
|
||||
import com.qqchen.deploy.backend.entity.Tenant;
|
||||
import com.qqchen.deploy.backend.framework.controller.BaseController;
|
||||
import com.qqchen.deploy.backend.service.ITenantService;
|
||||
import com.qqchen.deploy.backend.dto.TenantDTO;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
@ -14,13 +13,9 @@ import java.io.OutputStream;
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/tenants")
|
||||
@RequestMapping("/tenant")
|
||||
public class TenantController extends BaseController<Tenant, TenantDTO, Long, TenantQuery> {
|
||||
|
||||
public TenantController(ITenantService service) {
|
||||
super(service);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void exportData(HttpServletResponse response, List<TenantDTO> data) {
|
||||
response.setContentType("application/vnd.ms-excel");
|
||||
|
||||
@ -1,32 +1,19 @@
|
||||
//package com.qqchen.deploy.backend.controller;
|
||||
//
|
||||
//import com.qqchen.deploy.backend.api.UserApiController;
|
||||
//import com.qqchen.deploy.backend.framework.api.Response;
|
||||
//import com.qqchen.deploy.backend.converter.UserConverter;
|
||||
//import com.qqchen.deploy.backend.service.IUserService;
|
||||
//import org.springframework.web.bind.annotation.GetMapping;
|
||||
//import org.springframework.web.bind.annotation.RequestMapping;
|
||||
//import org.springframework.web.bind.annotation.RequestParam;
|
||||
//import org.springframework.web.bind.annotation.RestController;
|
||||
//
|
||||
//@RestController
|
||||
//@RequestMapping("/mgmt/users")
|
||||
//public class UserController extends UserApiController {
|
||||
//
|
||||
// protected final IUserService userService;
|
||||
//
|
||||
// public UserController(IUserService userService, UserConverter converter) {
|
||||
// super(userService, converter);
|
||||
// this.userService = userService;
|
||||
// }
|
||||
//
|
||||
// @GetMapping("/check-username")
|
||||
// public Response<Boolean> checkUsername(@RequestParam String username) {
|
||||
// return Response.success(userService.checkUsernameExists(username));
|
||||
// }
|
||||
//
|
||||
// @GetMapping("/check-email")
|
||||
// public Response<Boolean> checkEmail(@RequestParam String email) {
|
||||
// return Response.success(userService.checkEmailExists(email));
|
||||
// }
|
||||
//}
|
||||
package com.qqchen.deploy.backend.controller;
|
||||
|
||||
import com.qqchen.deploy.backend.api.UserApiController;
|
||||
import com.qqchen.deploy.backend.dto.UserDTO;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/mgmt/users")
|
||||
public class UserController extends UserApiController {
|
||||
|
||||
@Override
|
||||
protected void exportData(HttpServletResponse response, List<UserDTO> data) {
|
||||
|
||||
}
|
||||
}
|
||||
@ -7,8 +7,12 @@ import lombok.EqualsAndHashCode;
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class RoleDTO extends BaseDTO {
|
||||
|
||||
private String name;
|
||||
|
||||
private String code;
|
||||
|
||||
private String description;
|
||||
|
||||
private Integer sort;
|
||||
}
|
||||
@ -8,12 +8,20 @@ import lombok.EqualsAndHashCode;
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class TenantDTO extends BaseDTO {
|
||||
private String name;
|
||||
|
||||
private String code;
|
||||
|
||||
private String description;
|
||||
|
||||
private Boolean enabled;
|
||||
|
||||
private String contactName;
|
||||
|
||||
private String contactPhone;
|
||||
|
||||
private String contactEmail;
|
||||
|
||||
private String address;
|
||||
|
||||
private String remark;
|
||||
}
|
||||
@ -3,15 +3,21 @@ package com.qqchen.deploy.backend.dto;
|
||||
import com.qqchen.deploy.backend.framework.dto.BaseDTO;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class UserDTO extends BaseDTO {
|
||||
private String username;
|
||||
|
||||
private String nickname;
|
||||
|
||||
private String email;
|
||||
|
||||
private String phone;
|
||||
|
||||
private Boolean enabled;
|
||||
|
||||
private Set<RoleDTO> roles;
|
||||
}
|
||||
@ -0,0 +1,27 @@
|
||||
package com.qqchen.deploy.backend.framework.annotation;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface ServiceType {
|
||||
Type value() default Type.DATABASE;
|
||||
|
||||
enum Type {
|
||||
/**
|
||||
* 数据库服务,需要注入Repository和Converter
|
||||
*/
|
||||
DATABASE,
|
||||
|
||||
/**
|
||||
* 集成服务,不需要数据库操作
|
||||
*/
|
||||
INTEGRATION,
|
||||
|
||||
/**
|
||||
* 混合服务,可以选择性注入
|
||||
*/
|
||||
HYBRID
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,165 @@
|
||||
package com.qqchen.deploy.backend.framework.config;
|
||||
|
||||
import com.qqchen.deploy.backend.framework.controller.BaseController;
|
||||
import com.qqchen.deploy.backend.framework.domain.Entity;
|
||||
import com.qqchen.deploy.backend.framework.dto.BaseDTO;
|
||||
import com.qqchen.deploy.backend.framework.service.IBaseService;
|
||||
import com.qqchen.deploy.backend.framework.utils.PackageScanner;
|
||||
import com.qqchen.deploy.backend.framework.utils.ReflectionUtils;
|
||||
import com.qqchen.deploy.backend.framework.utils.SpringUtils;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.BeanCreationException;
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.core.GenericTypeResolver;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
@Component
|
||||
@Slf4j
|
||||
public class ControllerBeanPostProcessor implements BeanPostProcessor, DisposableBean {
|
||||
|
||||
private final ApplicationContext applicationContext;
|
||||
private final Map<Class<?>, IBaseService<?, ?, ?>> serviceCache = new ConcurrentHashMap<>();
|
||||
|
||||
public ControllerBeanPostProcessor(ApplicationContext applicationContext) {
|
||||
this.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
|
||||
if (!(bean instanceof BaseController<?, ?, ?, ?> controller)) {
|
||||
return bean;
|
||||
}
|
||||
|
||||
try {
|
||||
return injectService(controller, beanName);
|
||||
} catch (ServiceInjectionException e) {
|
||||
log.error("Service injection failed for controller: {}", beanName, e);
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
log.error("Unexpected error during service injection for controller: {}", beanName, e);
|
||||
throw new BeanCreationException("Failed to inject service for controller: " + beanName, e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T extends Entity<ID>, D extends BaseDTO, ID extends Serializable>
|
||||
Object injectService(BaseController<T, D, ID, ?> controller, String beanName) {
|
||||
Class<T> entityClass = resolveEntityClass(controller);
|
||||
if (entityClass == null) {
|
||||
return controller;
|
||||
}
|
||||
|
||||
IBaseService<?, ?, ?> cachedService = serviceCache.computeIfAbsent(
|
||||
entityClass,
|
||||
key -> findServiceForEntity(entityClass, beanName)
|
||||
);
|
||||
|
||||
if (cachedService == null) {
|
||||
throw new ServiceInjectionException("No service found for entity: " + entityClass.getSimpleName());
|
||||
}
|
||||
|
||||
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(),
|
||||
controller.getClass().getSimpleName());
|
||||
} catch (Exception e) {
|
||||
throw new ServiceInjectionException("Failed to inject service field", e);
|
||||
}
|
||||
|
||||
return controller;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T extends Entity<ID>, D extends BaseDTO, ID extends Serializable>
|
||||
Class<T> resolveEntityClass(BaseController<T, D, ID, ?> controller) {
|
||||
Class<?>[] genericTypes = GenericTypeResolver.resolveTypeArguments(
|
||||
controller.getClass(), BaseController.class);
|
||||
|
||||
if (genericTypes == null || genericTypes.length < 3) {
|
||||
log.warn("Could not resolve entity type for controller: {}",
|
||||
controller.getClass().getSimpleName());
|
||||
return null;
|
||||
}
|
||||
|
||||
return (Class<T>) genericTypes[0];
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T extends Entity<ID>, ID extends Serializable>
|
||||
IBaseService<?, ?, ?> findServiceForEntity(Class<T> entityClass, String controllerName) {
|
||||
// 先尝试通过命名约定查找
|
||||
String serviceBeanName = "i" + entityClass.getSimpleName() + "Service";
|
||||
log.debug("Looking for service bean: {}", serviceBeanName);
|
||||
|
||||
if (applicationContext.containsBean(serviceBeanName)) {
|
||||
return (IBaseService<?, ?, ?>) applicationContext.getBean(serviceBeanName);
|
||||
}
|
||||
|
||||
// 如果找不到,再通过类型匹配查找
|
||||
log.debug("Service not found by name, trying to find by type matching");
|
||||
return Arrays.stream(applicationContext.getBeanNamesForType(IBaseService.class))
|
||||
.map(name -> tryGetService(name, entityClass, controllerName))
|
||||
.filter(Objects::nonNull)
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new ServiceInjectionException(
|
||||
"No service found for entity: " + entityClass.getSimpleName() +
|
||||
" (tried bean name: " + serviceBeanName + " and type matching)"));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T extends Entity<ID>, ID extends Serializable>
|
||||
IBaseService<?, ?, ?> tryGetService(String beanName, Class<T> entityClass, String controllerName) {
|
||||
try {
|
||||
Class<?> serviceClass = applicationContext.getType(beanName);
|
||||
if (serviceClass == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Class<?>[] serviceGenericTypes = GenericTypeResolver.resolveTypeArguments(
|
||||
serviceClass, IBaseService.class);
|
||||
|
||||
if (serviceGenericTypes != null &&
|
||||
serviceGenericTypes.length > 0 &&
|
||||
entityClass.equals(serviceGenericTypes[0])) {
|
||||
log.debug("Found matching service: {} for entity: {}", beanName, entityClass.getSimpleName());
|
||||
return (IBaseService<?, ?, ?>) applicationContext.getBean(beanName);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to analyze service bean: {} for controller: {}", beanName, controllerName, e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
serviceCache.clear();
|
||||
}
|
||||
|
||||
public static class ServiceInjectionException extends BeanCreationException {
|
||||
public ServiceInjectionException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public ServiceInjectionException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,114 @@
|
||||
package com.qqchen.deploy.backend.framework.config;
|
||||
|
||||
import com.qqchen.deploy.backend.framework.domain.Entity;
|
||||
import com.qqchen.deploy.backend.framework.dto.BaseDTO;
|
||||
import com.qqchen.deploy.backend.framework.service.impl.BaseServiceImpl;
|
||||
import com.qqchen.deploy.backend.framework.repository.IBaseRepository;
|
||||
import com.qqchen.deploy.backend.framework.converter.BaseConverter;
|
||||
import com.qqchen.deploy.backend.framework.utils.ReflectionUtils;
|
||||
import com.querydsl.core.types.EntityPath;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.core.GenericTypeResolver;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.beans.factory.BeanCreationException;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
@Component
|
||||
@Slf4j
|
||||
public class ServiceBeanPostProcessor implements BeanPostProcessor {
|
||||
|
||||
private final ApplicationContext applicationContext;
|
||||
|
||||
public ServiceBeanPostProcessor(ApplicationContext applicationContext) {
|
||||
this.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
|
||||
if (!(bean instanceof BaseServiceImpl<?, ?, ?> service)) {
|
||||
return bean;
|
||||
}
|
||||
|
||||
try {
|
||||
return injectDependencies(service, beanName);
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to inject dependencies for service: {}", beanName, e);
|
||||
throw new BeanCreationException("Failed to inject dependencies for service: " + beanName, e);
|
||||
}
|
||||
}
|
||||
|
||||
private <T extends Entity<ID>, D extends BaseDTO, ID extends Serializable>
|
||||
Object injectDependencies(BaseServiceImpl<T, D, ID> service, String beanName) {
|
||||
Class<T> entityClass = resolveEntityClass(service);
|
||||
log.debug("Resolving dependencies for entity: {}", entityClass.getSimpleName());
|
||||
|
||||
try {
|
||||
// 注入 Repository
|
||||
String repositoryBeanName = "I" + entityClass.getSimpleName() + "Repository";
|
||||
log.debug("Looking for repository bean: {}", repositoryBeanName);
|
||||
if (!applicationContext.containsBean(repositoryBeanName)) {
|
||||
throw new ControllerBeanPostProcessor.ServiceInjectionException("Repository not found: " + repositoryBeanName);
|
||||
}
|
||||
IBaseRepository<T, ID> repository = (IBaseRepository<T, ID>) applicationContext.getBean(repositoryBeanName);
|
||||
ReflectionUtils.setField("repository", service, repository);
|
||||
|
||||
// 注入 Converter
|
||||
String converterBeanName = entityClass.getSimpleName().substring(0, 1).toLowerCase()
|
||||
+ entityClass.getSimpleName().substring(1) + "ConverterImpl";
|
||||
log.debug("Looking for converter bean: {}", converterBeanName);
|
||||
if (!applicationContext.containsBean(converterBeanName)) {
|
||||
throw new ControllerBeanPostProcessor.ServiceInjectionException("Converter not found: " + converterBeanName);
|
||||
}
|
||||
BaseConverter<T, D> converter = (BaseConverter<T, D>) applicationContext.getBean(converterBeanName);
|
||||
ReflectionUtils.setField("converter", service, converter);
|
||||
|
||||
// 初始化 EntityPath
|
||||
try {
|
||||
EntityPath<T> entityPath = initEntityPath(entityClass);
|
||||
ReflectionUtils.setField("entityPath", service, entityPath);
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to initialize EntityPath for {}", entityClass.getSimpleName(), e);
|
||||
throw new ControllerBeanPostProcessor.ServiceInjectionException("Failed to initialize EntityPath: " + e.getMessage());
|
||||
}
|
||||
|
||||
log.debug("Successfully injected all dependencies for service: {}", beanName);
|
||||
return service;
|
||||
} catch (ControllerBeanPostProcessor.ServiceInjectionException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
log.error("Unexpected error while injecting dependencies for {}", entityClass.getSimpleName(), e);
|
||||
throw new ControllerBeanPostProcessor.ServiceInjectionException("Failed to inject dependencies: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T extends Entity<ID>, ID extends Serializable> Class<T> resolveEntityClass(BaseServiceImpl<T, ?, ID> service) {
|
||||
Class<?>[] genericTypes = GenericTypeResolver.resolveTypeArguments(
|
||||
service.getClass(), BaseServiceImpl.class);
|
||||
|
||||
if (genericTypes == null || genericTypes.length < 3) {
|
||||
throw new IllegalStateException("Could not resolve entity type for service: "
|
||||
+ service.getClass().getSimpleName());
|
||||
}
|
||||
|
||||
return (Class<T>) genericTypes[0];
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T extends Entity<ID>, ID extends Serializable> EntityPath<T> initEntityPath(Class<T> entityClass) {
|
||||
try {
|
||||
String qClassName = entityClass.getPackageName() + ".Q" + entityClass.getSimpleName();
|
||||
Class<?> qClass = Class.forName(qClassName);
|
||||
Field instanceField = qClass.getDeclaredField(entityClass.getSimpleName().toLowerCase());
|
||||
return (EntityPath<T>) instanceField.get(null);
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("Failed to get Q class for " + entityClass.getName(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -6,7 +6,6 @@ import com.qqchen.deploy.backend.framework.query.BaseQuery;
|
||||
import com.qqchen.deploy.backend.framework.api.Response;
|
||||
import com.qqchen.deploy.backend.framework.service.IBaseService;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.core.GenericTypeResolver;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
@ -21,11 +20,7 @@ import java.util.concurrent.CompletableFuture;
|
||||
@Validated
|
||||
public abstract class BaseController<T extends Entity<ID>, D extends BaseDTO, ID extends Serializable, Q extends BaseQuery> {
|
||||
|
||||
protected final IBaseService<T, D, ID> service;
|
||||
|
||||
protected BaseController(IBaseService<T, D, ID> service) {
|
||||
this.service = service;
|
||||
}
|
||||
protected IBaseService<T, D, ID> service;
|
||||
|
||||
@PostMapping
|
||||
public Response<D> create(@Validated @RequestBody D dto) {
|
||||
@ -78,8 +73,9 @@ public abstract class BaseController<T extends Entity<ID>, D extends BaseDTO, ID
|
||||
|
||||
/**
|
||||
* 导出数据的具体实现
|
||||
*
|
||||
* @param response HTTP响应
|
||||
* @param data 要导出的数据
|
||||
* @param data 要导出的数据
|
||||
*/
|
||||
protected abstract void exportData(HttpServletResponse response, List<D> data);
|
||||
|
||||
|
||||
@ -1,20 +1,28 @@
|
||||
package com.qqchen.deploy.backend.framework.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Map;
|
||||
|
||||
@Data
|
||||
public class BaseDTO implements Serializable {
|
||||
|
||||
private Long id;
|
||||
|
||||
private LocalDateTime createTime;
|
||||
|
||||
private String createBy;
|
||||
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
private String updateBy;
|
||||
|
||||
private Integer version;
|
||||
|
||||
private Boolean deleted;
|
||||
|
||||
|
||||
// 扩展字段,用于存储额外的属性
|
||||
private Map<String, Object> extraData;
|
||||
}
|
||||
@ -43,45 +43,29 @@ import jakarta.persistence.PersistenceContext;
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
|
||||
import com.qqchen.deploy.backend.framework.annotation.ServiceType;
|
||||
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
@Slf4j
|
||||
public abstract class BaseServiceImpl<T extends Entity<ID>, D extends BaseDTO, ID extends Serializable>
|
||||
implements IBaseService<T, D, ID> {
|
||||
|
||||
protected final IBaseRepository<T, ID> repository;
|
||||
protected final BaseConverter<T, D> converter;
|
||||
protected final EntityPath<T> entityPath;
|
||||
protected IBaseRepository<T, ID> repository;
|
||||
protected BaseConverter<T, D> converter;
|
||||
protected EntityPath<T> entityPath;
|
||||
|
||||
@PersistenceContext
|
||||
protected EntityManager entityManager;
|
||||
|
||||
protected BaseServiceImpl(IBaseRepository<T, ID> repository, BaseConverter<T, D> converter) {
|
||||
this.repository = repository;
|
||||
this.converter = converter;
|
||||
this.entityPath = getEntityPath();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private EntityPath<T> getEntityPath() {
|
||||
Class<?>[] genericTypes = GenericTypeResolver.resolveTypeArguments(getClass(), BaseServiceImpl.class);
|
||||
if (genericTypes == null || genericTypes.length < 2) {
|
||||
throw new IllegalStateException("Could not resolve generic type arguments");
|
||||
}
|
||||
|
||||
Class<T> entityClass = (Class<T>) genericTypes[0];
|
||||
try {
|
||||
String qClassName = entityClass.getPackageName() + ".Q" + entityClass.getSimpleName();
|
||||
Class<?> qClass = Class.forName(qClassName);
|
||||
Field instanceField = qClass.getDeclaredField(entityClass.getSimpleName().toLowerCase());
|
||||
return (EntityPath<T>) instanceField.get(null);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to get Q class for " + entityClass.getName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public D create(D dto) {
|
||||
validateDatabaseOperation("create");
|
||||
T entity = converter.toEntity(dto);
|
||||
T savedEntity = repository.save(entity);
|
||||
return converter.toDto(savedEntity);
|
||||
@ -90,6 +74,7 @@ public abstract class BaseServiceImpl<T extends Entity<ID>, D extends BaseDTO, I
|
||||
@Override
|
||||
@Transactional
|
||||
public D update(ID id, D dto) {
|
||||
validateDatabaseOperation("update");
|
||||
T entity = findEntityById(id);
|
||||
converter.updateEntity(entity, dto);
|
||||
T updatedEntity = repository.save(entity);
|
||||
@ -99,25 +84,29 @@ public abstract class BaseServiceImpl<T extends Entity<ID>, D extends BaseDTO, I
|
||||
@Override
|
||||
@Transactional
|
||||
public void delete(ID id) {
|
||||
validateDatabaseOperation("delete");
|
||||
repository.deleteById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public D findById(ID id) {
|
||||
validateDatabaseOperation("findById");
|
||||
T entity = findEntityById(id);
|
||||
return converter.toDto(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<D> findAll() {
|
||||
validateDatabaseOperation("findAll");
|
||||
List<T> entities = repository.findAll();
|
||||
return converter.toDtoList(entities);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<D> findAll(BaseQuery query) {
|
||||
validateDatabaseOperation("findAll");
|
||||
if (query == null) {
|
||||
return findAll(); // 如果查询参数为空,返回所有数据
|
||||
return findAll();
|
||||
}
|
||||
BooleanBuilder builder = createQueryPredicate(query);
|
||||
Sort sort = createSort(query);
|
||||
@ -127,6 +116,7 @@ public abstract class BaseServiceImpl<T extends Entity<ID>, D extends BaseDTO, I
|
||||
|
||||
@Override
|
||||
public Page<D> page(BaseQuery query) {
|
||||
validateDatabaseOperation("page");
|
||||
Page<T> page = repository.findAll(createQueryPredicate(query), createPageRequest(query));
|
||||
return converter.toDtoPage(page);
|
||||
}
|
||||
@ -344,7 +334,7 @@ public abstract class BaseServiceImpl<T extends Entity<ID>, D extends BaseDTO, I
|
||||
public void batchProcess(List<D> dtos) {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
String operator = SecurityUtils.getCurrentUsername();
|
||||
|
||||
|
||||
List<T> entities = converter.toEntityList(dtos);
|
||||
Lists.partition(entities, 500).forEach(batch -> {
|
||||
try {
|
||||
@ -353,17 +343,17 @@ public abstract class BaseServiceImpl<T extends Entity<ID>, D extends BaseDTO, I
|
||||
.map(e -> repository.findById(e.getId())
|
||||
.orElseThrow(() -> new EntityNotFoundException(getEntityClass().getSimpleName(), e.getId())))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
|
||||
// 版本检查
|
||||
for (int i = 0; i < batch.size(); i++) {
|
||||
currentEntities.get(i).checkVersion(batch.get(i).getVersion());
|
||||
}
|
||||
|
||||
|
||||
// 批量更新
|
||||
List<ID> ids = currentEntities.stream()
|
||||
.map(Entity::getId)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
|
||||
repository.batchUpdate(ids, now, operator);
|
||||
repository.flush();
|
||||
entityManager.clear();
|
||||
@ -384,19 +374,20 @@ public abstract class BaseServiceImpl<T extends Entity<ID>, D extends BaseDTO, I
|
||||
try {
|
||||
return update(id, dto);
|
||||
} catch (OptimisticLockException e) {
|
||||
// 重试前先刷新实体
|
||||
entityManager.refresh(findEntityById(id));
|
||||
throw e; // 抛出异常触发重试
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
// 添加悲观锁查询方法
|
||||
@Override
|
||||
@Transactional
|
||||
public T findByIdWithLock(ID id) {
|
||||
validateDatabaseOperation("findByIdWithLock");
|
||||
return repository.findByIdWithLock(id)
|
||||
.orElseThrow(() -> new EntityNotFoundException(getEntityClass().getSimpleName(), id));
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public Class<T> getEntityClass() {
|
||||
Class<?>[] genericTypes = GenericTypeResolver.resolveTypeArguments(getClass(), BaseServiceImpl.class);
|
||||
@ -406,10 +397,18 @@ public abstract class BaseServiceImpl<T extends Entity<ID>, D extends BaseDTO, I
|
||||
throw new IllegalStateException("Could not resolve entity class");
|
||||
}
|
||||
|
||||
// 内部实体操作方法
|
||||
@Override
|
||||
public T findEntityById(ID id) {
|
||||
validateDatabaseOperation("findEntityById");
|
||||
return repository.findById(id)
|
||||
.orElseThrow(() -> new EntityNotFoundException(getEntityClass().getSimpleName(), id));
|
||||
}
|
||||
|
||||
protected void validateDatabaseOperation(String operation) {
|
||||
ServiceType serviceType = getClass().getAnnotation(ServiceType.class);
|
||||
if (serviceType != null && serviceType.value() == ServiceType.Type.INTEGRATION) {
|
||||
throw new UnsupportedOperationException(
|
||||
"Database operation '" + operation + "' is not supported in integration service");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,37 @@
|
||||
package com.qqchen.deploy.backend.framework.utils;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
|
||||
import org.springframework.core.type.filter.AnnotationTypeFilter;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
@Slf4j
|
||||
public class PackageScanner {
|
||||
|
||||
public static Set<String> scanControllerPackages(String basePackage) {
|
||||
Set<String> packages = new HashSet<>();
|
||||
|
||||
ClassPathScanningCandidateComponentProvider scanner =
|
||||
new ClassPathScanningCandidateComponentProvider(false);
|
||||
scanner.addIncludeFilter(new AnnotationTypeFilter(RestController.class));
|
||||
|
||||
Set<BeanDefinition> components = scanner.findCandidateComponents(basePackage);
|
||||
for (BeanDefinition component : components) {
|
||||
String className = component.getBeanClassName();
|
||||
if (className != null) {
|
||||
try {
|
||||
Class<?> clazz = Class.forName(className);
|
||||
packages.add(clazz.getPackage().getName());
|
||||
} catch (ClassNotFoundException e) {
|
||||
log.warn("Failed to load class: {}", className, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return packages;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,86 @@
|
||||
package com.qqchen.deploy.backend.framework.utils;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
@Slf4j
|
||||
public class ReflectionUtils {
|
||||
|
||||
/**
|
||||
* 设置字段值
|
||||
*
|
||||
* @param fieldName 字段名
|
||||
* @param target 目标对象
|
||||
* @param value 值
|
||||
*/
|
||||
public static void setField(String fieldName, Object target, Object value) {
|
||||
Assert.notNull(fieldName, "Field name must not be null");
|
||||
Assert.notNull(target, "Target object must not be null");
|
||||
|
||||
try {
|
||||
Field field = findField(target.getClass(), fieldName);
|
||||
if (field == null) {
|
||||
throw new IllegalArgumentException(
|
||||
"Could not find field [" + fieldName + "] on target [" + target.getClass().getName() + "]");
|
||||
}
|
||||
|
||||
makeAccessible(field);
|
||||
field.set(target, value);
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to set field [{}] on target [{}]", fieldName, target.getClass().getName(), e);
|
||||
throw new IllegalStateException("Failed to set field: " + fieldName, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取字段值
|
||||
*
|
||||
* @param fieldName 字段名
|
||||
* @param target 目标对象
|
||||
* @return 字段值
|
||||
*/
|
||||
public static Object getField(String fieldName, Object target) {
|
||||
Assert.notNull(fieldName, "Field name must not be null");
|
||||
Assert.notNull(target, "Target object must not be null");
|
||||
|
||||
try {
|
||||
Field field = findField(target.getClass(), fieldName);
|
||||
if (field == null) {
|
||||
throw new IllegalArgumentException(
|
||||
"Could not find field [" + fieldName + "] on target [" + target.getClass().getName() + "]");
|
||||
}
|
||||
|
||||
makeAccessible(field);
|
||||
return field.get(target);
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to get field [{}] on target [{}]", fieldName, target.getClass().getName(), e);
|
||||
throw new IllegalStateException("Failed to get field: " + fieldName, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找字段(包括父类)
|
||||
*/
|
||||
private static Field findField(Class<?> clazz, String fieldName) {
|
||||
Class<?> searchType = clazz;
|
||||
while (searchType != null && !Object.class.equals(searchType)) {
|
||||
try {
|
||||
return searchType.getDeclaredField(fieldName);
|
||||
} catch (NoSuchFieldException e) {
|
||||
searchType = searchType.getSuperclass();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 使字段可访问
|
||||
*/
|
||||
private static void makeAccessible(Field field) {
|
||||
if (!field.isAccessible()) {
|
||||
field.setAccessible(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,47 @@
|
||||
package com.qqchen.deploy.backend.framework.utils;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class SpringUtils implements ApplicationContextAware {
|
||||
private static ApplicationContext applicationContext;
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
SpringUtils.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T getBean(String name) {
|
||||
if (applicationContext == null) {
|
||||
throw new IllegalStateException("ApplicationContext has not been initialized");
|
||||
}
|
||||
try {
|
||||
return (T) applicationContext.getBean(name);
|
||||
} catch (BeansException e) {
|
||||
log.error("Failed to get bean by name: {}", name, e);
|
||||
throw new IllegalStateException("Could not find bean: " + name, e);
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> T getBean(Class<T> clazz) {
|
||||
if (applicationContext == null) {
|
||||
throw new IllegalStateException("ApplicationContext has not been initialized");
|
||||
}
|
||||
try {
|
||||
return applicationContext.getBean(clazz);
|
||||
} catch (BeansException e) {
|
||||
log.error("Failed to get bean by class: {}", clazz.getName(), e);
|
||||
throw new IllegalStateException("Could not find bean of type: " + clazz.getName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
public static ApplicationContext getApplicationContext() {
|
||||
return applicationContext;
|
||||
}
|
||||
}
|
||||
@ -14,9 +14,9 @@ import java.util.List;
|
||||
@Service
|
||||
public class TenantServiceImpl extends BaseServiceImpl<Tenant, TenantDTO, Long> implements ITenantService {
|
||||
|
||||
public TenantServiceImpl(ITenantRepository repository, TenantConverter converter) {
|
||||
super(repository, converter);
|
||||
}
|
||||
// public TenantServiceImpl(ITenantRepository repository, TenantConverter converter) {
|
||||
// super(repository, converter);
|
||||
// }
|
||||
|
||||
// @Override
|
||||
// public List<TenantDTO> findAll(BaseQuery query) {
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package com.qqchen.deploy.backend.service.impl;
|
||||
|
||||
import com.qqchen.deploy.backend.framework.annotation.ServiceType;
|
||||
import com.qqchen.deploy.backend.framework.audit.annotation.Audited;
|
||||
import com.qqchen.deploy.backend.framework.enums.ResponseCode;
|
||||
import com.qqchen.deploy.backend.framework.exception.BusinessException;
|
||||
@ -28,28 +29,22 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static com.qqchen.deploy.backend.framework.annotation.ServiceType.Type.DATABASE;
|
||||
|
||||
@Service
|
||||
@ServiceType(DATABASE)
|
||||
@Slf4j
|
||||
public class UserServiceImpl extends BaseServiceImpl<User, UserDTO, Long> implements IUserService {
|
||||
|
||||
private final UserConverter converter;
|
||||
private final RoleConverter roleConverter;
|
||||
@Resource
|
||||
private RoleConverter roleConverter;
|
||||
|
||||
@Resource
|
||||
private AuthenticationManager authenticationManager;
|
||||
|
||||
@Resource
|
||||
private IUserRepository userRepository;
|
||||
|
||||
@Resource
|
||||
private JwtTokenUtil jwtTokenUtil;
|
||||
|
||||
public UserServiceImpl(IUserRepository repository, UserConverter converter, RoleConverter roleConverter) {
|
||||
super(repository, converter);
|
||||
this.converter = converter;
|
||||
this.roleConverter = roleConverter;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = false)
|
||||
@Audited(action = "REGISTER", detail = "用户注册")
|
||||
@ -83,26 +78,27 @@ public class UserServiceImpl extends BaseServiceImpl<User, UserDTO, Long> implem
|
||||
@Transactional(readOnly = true)
|
||||
@Audited(action = "USER_LOGIN", detail = "登录")
|
||||
public LoginResponse login(LoginRequest request) {
|
||||
Authentication authentication = authenticationManager.authenticate(
|
||||
new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword())
|
||||
);
|
||||
|
||||
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
|
||||
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);
|
||||
|
||||
log.info("用户 {} ({}) 登录成功", user.getUsername(), user.getNickname());
|
||||
return response;
|
||||
// Authentication authentication = authenticationManager.authenticate(
|
||||
// new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword())
|
||||
// );
|
||||
//
|
||||
// UserDetails userDetails = (UserDetails) authentication.getPrincipal();
|
||||
// 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);
|
||||
//
|
||||
// log.info("用户 {} ({}) 登录成功", user.getUsername(), user.getNickname());
|
||||
// return response;
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
server:
|
||||
port: 8080
|
||||
|
||||
spring:
|
||||
datasource:
|
||||
url: jdbc:mysql://127.0.0.1:3306/deploy-ease-platform?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true
|
||||
|
||||
Loading…
Reference in New Issue
Block a user