diff --git a/backend/src/main/java/com/qqchen/deploy/backend/api/TenantApiController.java b/backend/src/main/java/com/qqchen/deploy/backend/api/TenantApiController.java index 31efceba..36ed715a 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/api/TenantApiController.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/api/TenantApiController.java @@ -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 { - protected final ITenantService service; - - public TenantApiController(ITenantService service) { - super(service); - this.service = service; - } - @Override protected void exportData(HttpServletResponse response, List data) { diff --git a/backend/src/main/java/com/qqchen/deploy/backend/api/UserApiController.java b/backend/src/main/java/com/qqchen/deploy/backend/api/UserApiController.java index a063ae2a..8dcb1bd4 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/api/UserApiController.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/api/UserApiController.java @@ -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 { - 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 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) { diff --git a/backend/src/main/java/com/qqchen/deploy/backend/controller/TenantController.java b/backend/src/main/java/com/qqchen/deploy/backend/controller/TenantController.java index ba4e99b7..ce156bc1 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/controller/TenantController.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/controller/TenantController.java @@ -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 { - public TenantController(ITenantService service) { - super(service); - } - @Override protected void exportData(HttpServletResponse response, List data) { response.setContentType("application/vnd.ms-excel"); diff --git a/backend/src/main/java/com/qqchen/deploy/backend/controller/UserController.java b/backend/src/main/java/com/qqchen/deploy/backend/controller/UserController.java index d5abfcef..061d5918 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/controller/UserController.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/controller/UserController.java @@ -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 checkUsername(@RequestParam String username) { -// return Response.success(userService.checkUsernameExists(username)); -// } -// -// @GetMapping("/check-email") -// public Response checkEmail(@RequestParam String email) { -// return Response.success(userService.checkEmailExists(email)); -// } -//} \ No newline at end of file +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 data) { + + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/dto/RoleDTO.java b/backend/src/main/java/com/qqchen/deploy/backend/dto/RoleDTO.java index 9b789356..58e59792 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/dto/RoleDTO.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/dto/RoleDTO.java @@ -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; } \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/dto/TenantDTO.java b/backend/src/main/java/com/qqchen/deploy/backend/dto/TenantDTO.java index 53c316f6..0280f4a5 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/dto/TenantDTO.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/dto/TenantDTO.java @@ -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; } \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/dto/UserDTO.java b/backend/src/main/java/com/qqchen/deploy/backend/dto/UserDTO.java index cf40bfd8..6fa5d58d 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/dto/UserDTO.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/dto/UserDTO.java @@ -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 roles; } \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/framework/annotation/ServiceType.java b/backend/src/main/java/com/qqchen/deploy/backend/framework/annotation/ServiceType.java new file mode 100644 index 00000000..70de56f5 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/framework/annotation/ServiceType.java @@ -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 + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/framework/config/ControllerBeanPostProcessor.java b/backend/src/main/java/com/qqchen/deploy/backend/framework/config/ControllerBeanPostProcessor.java new file mode 100644 index 00000000..c2954014 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/framework/config/ControllerBeanPostProcessor.java @@ -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, 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 , D extends BaseDTO, ID extends Serializable> + Object injectService(BaseController controller, String beanName) { + Class 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 service = (IBaseService) 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 , D extends BaseDTO, ID extends Serializable> + Class resolveEntityClass(BaseController 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) genericTypes[0]; + } + + @SuppressWarnings("unchecked") + private , ID extends Serializable> + IBaseService findServiceForEntity(Class 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 , ID extends Serializable> + IBaseService tryGetService(String beanName, Class 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); + } + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/framework/config/ServiceBeanPostProcessor.java b/backend/src/main/java/com/qqchen/deploy/backend/framework/config/ServiceBeanPostProcessor.java new file mode 100644 index 00000000..6a11152e --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/framework/config/ServiceBeanPostProcessor.java @@ -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 , D extends BaseDTO, ID extends Serializable> + Object injectDependencies(BaseServiceImpl service, String beanName) { + Class 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 repository = (IBaseRepository) 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 converter = (BaseConverter) applicationContext.getBean(converterBeanName); + ReflectionUtils.setField("converter", service, converter); + + // 初始化 EntityPath + try { + EntityPath 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 , ID extends Serializable> Class resolveEntityClass(BaseServiceImpl 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) genericTypes[0]; + } + + @SuppressWarnings("unchecked") + private , ID extends Serializable> EntityPath initEntityPath(Class entityClass) { + try { + String qClassName = entityClass.getPackageName() + ".Q" + entityClass.getSimpleName(); + Class qClass = Class.forName(qClassName); + Field instanceField = qClass.getDeclaredField(entityClass.getSimpleName().toLowerCase()); + return (EntityPath) instanceField.get(null); + } catch (Exception e) { + throw new IllegalStateException("Failed to get Q class for " + entityClass.getName(), e); + } + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/framework/controller/BaseController.java b/backend/src/main/java/com/qqchen/deploy/backend/framework/controller/BaseController.java index 4b55cf14..e09b400b 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/framework/controller/BaseController.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/framework/controller/BaseController.java @@ -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, D extends BaseDTO, ID extends Serializable, Q extends BaseQuery> { - protected final IBaseService service; - - protected BaseController(IBaseService service) { - this.service = service; - } + protected IBaseService service; @PostMapping public Response create(@Validated @RequestBody D dto) { @@ -78,8 +73,9 @@ public abstract class BaseController, D extends BaseDTO, ID /** * 导出数据的具体实现 + * * @param response HTTP响应 - * @param data 要导出的数据 + * @param data 要导出的数据 */ protected abstract void exportData(HttpServletResponse response, List data); diff --git a/backend/src/main/java/com/qqchen/deploy/backend/framework/dto/BaseDTO.java b/backend/src/main/java/com/qqchen/deploy/backend/framework/dto/BaseDTO.java index c0b62ae1..dee7d80c 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/framework/dto/BaseDTO.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/framework/dto/BaseDTO.java @@ -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 extraData; } \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/framework/service/impl/BaseServiceImpl.java b/backend/src/main/java/com/qqchen/deploy/backend/framework/service/impl/BaseServiceImpl.java index 0e1768ff..1f06021a 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/framework/service/impl/BaseServiceImpl.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/framework/service/impl/BaseServiceImpl.java @@ -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, D extends BaseDTO, ID extends Serializable> implements IBaseService { - protected final IBaseRepository repository; - protected final BaseConverter converter; - protected final EntityPath entityPath; + protected IBaseRepository repository; + protected BaseConverter converter; + protected EntityPath entityPath; @PersistenceContext protected EntityManager entityManager; - protected BaseServiceImpl(IBaseRepository repository, BaseConverter converter) { - this.repository = repository; - this.converter = converter; - this.entityPath = getEntityPath(); - } - - @SuppressWarnings("unchecked") - private EntityPath getEntityPath() { - Class[] genericTypes = GenericTypeResolver.resolveTypeArguments(getClass(), BaseServiceImpl.class); - if (genericTypes == null || genericTypes.length < 2) { - throw new IllegalStateException("Could not resolve generic type arguments"); - } - - Class entityClass = (Class) genericTypes[0]; - try { - String qClassName = entityClass.getPackageName() + ".Q" + entityClass.getSimpleName(); - Class qClass = Class.forName(qClassName); - Field instanceField = qClass.getDeclaredField(entityClass.getSimpleName().toLowerCase()); - return (EntityPath) 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, 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, 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 findAll() { + validateDatabaseOperation("findAll"); List entities = repository.findAll(); return converter.toDtoList(entities); } @Override public List 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, D extends BaseDTO, I @Override public Page page(BaseQuery query) { + validateDatabaseOperation("page"); Page page = repository.findAll(createQueryPredicate(query), createPageRequest(query)); return converter.toDtoPage(page); } @@ -344,7 +334,7 @@ public abstract class BaseServiceImpl, D extends BaseDTO, I public void batchProcess(List dtos) { LocalDateTime now = LocalDateTime.now(); String operator = SecurityUtils.getCurrentUsername(); - + List entities = converter.toEntityList(dtos); Lists.partition(entities, 500).forEach(batch -> { try { @@ -353,17 +343,17 @@ public abstract class BaseServiceImpl, 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 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, 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 getEntityClass() { Class[] genericTypes = GenericTypeResolver.resolveTypeArguments(getClass(), BaseServiceImpl.class); @@ -406,10 +397,18 @@ public abstract class BaseServiceImpl, 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"); + } + } } \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/framework/utils/PackageScanner.java b/backend/src/main/java/com/qqchen/deploy/backend/framework/utils/PackageScanner.java new file mode 100644 index 00000000..1f7cba96 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/framework/utils/PackageScanner.java @@ -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 scanControllerPackages(String basePackage) { + Set packages = new HashSet<>(); + + ClassPathScanningCandidateComponentProvider scanner = + new ClassPathScanningCandidateComponentProvider(false); + scanner.addIncludeFilter(new AnnotationTypeFilter(RestController.class)); + + Set 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; + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/framework/utils/ReflectionUtils.java b/backend/src/main/java/com/qqchen/deploy/backend/framework/utils/ReflectionUtils.java new file mode 100644 index 00000000..50b4bc65 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/framework/utils/ReflectionUtils.java @@ -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); + } + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/framework/utils/SpringUtils.java b/backend/src/main/java/com/qqchen/deploy/backend/framework/utils/SpringUtils.java new file mode 100644 index 00000000..1cde173d --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/framework/utils/SpringUtils.java @@ -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 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 getBean(Class 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; + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/service/impl/TenantServiceImpl.java b/backend/src/main/java/com/qqchen/deploy/backend/service/impl/TenantServiceImpl.java index 71754882..a97241d2 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/service/impl/TenantServiceImpl.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/service/impl/TenantServiceImpl.java @@ -14,9 +14,9 @@ import java.util.List; @Service public class TenantServiceImpl extends BaseServiceImpl implements ITenantService { - public TenantServiceImpl(ITenantRepository repository, TenantConverter converter) { - super(repository, converter); - } +// public TenantServiceImpl(ITenantRepository repository, TenantConverter converter) { +// super(repository, converter); +// } // @Override // public List findAll(BaseQuery query) { diff --git a/backend/src/main/java/com/qqchen/deploy/backend/service/impl/UserServiceImpl.java b/backend/src/main/java/com/qqchen/deploy/backend/service/impl/UserServiceImpl.java index 9b6d24c2..cf1c0ff2 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/service/impl/UserServiceImpl.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/service/impl/UserServiceImpl.java @@ -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 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 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 diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml index 0eebd610..63e90926 100644 --- a/backend/src/main/resources/application.yml +++ b/backend/src/main/resources/application.yml @@ -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