diff --git a/backend/src/main/java/com/qqchen/deploy/backend/framework/exception/UniqueConstraintException.java b/backend/src/main/java/com/qqchen/deploy/backend/framework/exception/UniqueConstraintException.java new file mode 100644 index 00000000..10c5ee88 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/framework/exception/UniqueConstraintException.java @@ -0,0 +1,16 @@ +package com.qqchen.deploy.backend.framework.exception; + +import com.qqchen.deploy.backend.framework.enums.ResponseCode; +import lombok.Getter; + +@Getter +public class UniqueConstraintException extends BusinessException { + private final String field; + private final String value; + + public UniqueConstraintException(ResponseCode code, String field, String value) { + super(code); + this.field = field; + this.value = value; + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/framework/handler/GlobalExceptionHandler.java b/backend/src/main/java/com/qqchen/deploy/backend/framework/handler/GlobalExceptionHandler.java index e4152b07..53e8d5f8 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/framework/handler/GlobalExceptionHandler.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/framework/handler/GlobalExceptionHandler.java @@ -4,11 +4,13 @@ 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 com.qqchen.deploy.backend.framework.exception.UniqueConstraintException; 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.security.authentication.BadCredentialsException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; @@ -56,7 +58,7 @@ public class GlobalExceptionHandler { @ExceptionHandler(MethodArgumentTypeMismatchException.class) public Response handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e) { - String message = String.format("参数[%s]类型错误, 期望类型: %s, 实际值: %s", + String message = String.format("参数[%s]类型错误, 期望类型: %s, 实际值: %s", e.getName(), e.getRequiredType().getSimpleName(), e.getValue()); @@ -76,4 +78,11 @@ public class GlobalExceptionHandler { log.error("Unexpected error occurred", e); return Response.error(ResponseCode.ERROR); } + + @ExceptionHandler(UniqueConstraintException.class) + public Response handleUniqueConstraintException(UniqueConstraintException e) { + String message = messageSource.getMessage(e.getErrorCode().getMessageKey(), new Object[] {e.getField(), e.getValue()}, LocaleContextHolder.getLocale()); + log.warn("Unique constraint violation: {} = {}", e.getField(), e.getValue()); + return Response.error(e.getErrorCode(), message); + } } \ 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 e79e9a64..9f2caef7 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 @@ -66,6 +66,7 @@ public abstract class BaseServiceImpl, D extends BaseDTO, I @Transactional public D create(D dto) { validateDatabaseOperation("create"); + validateUniqueConstraints(dto); T entity = converter.toEntity(dto); T savedEntity = repository.save(entity); return converter.toDto(savedEntity); @@ -416,4 +417,12 @@ public abstract class BaseServiceImpl, D extends BaseDTO, I "Database operation '" + operation + "' is not supported in integration service"); } } + + /** + * 校验唯一约束 + * 子类可以重写此方法实现具体的唯一性检查 + */ + protected void validateUniqueConstraints(D dto) { + // 默认不做任何检查 + } } \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/repository/IUserRepository.java b/backend/src/main/java/com/qqchen/deploy/backend/repository/IUserRepository.java index d82b2126..f1f7d5e7 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/repository/IUserRepository.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/repository/IUserRepository.java @@ -1,13 +1,26 @@ package com.qqchen.deploy.backend.repository; -import com.qqchen.deploy.backend.framework.repository.IBaseRepository; import com.qqchen.deploy.backend.entity.User; +import com.qqchen.deploy.backend.framework.repository.IBaseRepository; import org.springframework.stereotype.Repository; import java.util.Optional; @Repository public interface IUserRepository extends IBaseRepository { - + + /** + * 根据用户名查找未删除的用户(用于登录等业务操作) + */ Optional findByUsernameAndDeletedFalse(String username); + + /** + * 检查用户名是否存在(包括已删除的记录) + */ + boolean existsByUsername(String username); + + /** + * 检查邮箱是否存在(包括已删除的记录) + */ + boolean existsByEmail(String email); } \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/service/impl/RoleServiceImpl.java b/backend/src/main/java/com/qqchen/deploy/backend/service/impl/RoleServiceImpl.java index 3f35eb41..d4b2f607 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/service/impl/RoleServiceImpl.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/service/impl/RoleServiceImpl.java @@ -3,6 +3,7 @@ package com.qqchen.deploy.backend.service.impl; import com.qqchen.deploy.backend.entity.Role; import com.qqchen.deploy.backend.framework.enums.ResponseCode; import com.qqchen.deploy.backend.framework.exception.BusinessException; +import com.qqchen.deploy.backend.framework.exception.UniqueConstraintException; import com.qqchen.deploy.backend.model.RoleDTO; import com.qqchen.deploy.backend.framework.service.impl.BaseServiceImpl; import com.qqchen.deploy.backend.repository.IRoleRepository; @@ -42,4 +43,17 @@ public class RoleServiceImpl extends BaseServiceImpl implem throw new BusinessException(ResponseCode.ROLE_NAME_EXISTS); } } + + @Override + protected void validateUniqueConstraints(RoleDTO dto) { + // 检查角色编码唯一性 + if (repository.existsByCodeAndDeletedFalse(dto.getCode())) { + throw new UniqueConstraintException(ResponseCode.ROLE_CODE_EXISTS, "code", dto.getCode()); + } + + // 检查角色名称唯一性 + if (repository.existsByNameAndDeletedFalse(dto.getName())) { + throw new UniqueConstraintException(ResponseCode.ROLE_NAME_EXISTS, "name", dto.getName()); + } + } } \ No newline at end of file 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 5104af85..eec30a36 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 @@ -5,6 +5,7 @@ import com.qqchen.deploy.backend.framework.annotation.ServiceType; 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.exception.UniqueConstraintException; 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; @@ -27,6 +28,7 @@ import org.springframework.security.core.Authentication; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.StringUtils; import java.util.ArrayList; import java.util.List; @@ -160,4 +162,17 @@ public class UserServiceImpl extends BaseServiceImpl implem repository.save(user); log.info("用户 {} 密码已重置", user.getUsername()); } + + @Override + protected void validateUniqueConstraints(UserDTO dto) { + // 检查用户名唯一性(包括已删除的记录) + if (userRepository.existsByUsername(dto.getUsername())) { + throw new UniqueConstraintException(ResponseCode.USERNAME_EXISTS, "username", dto.getUsername()); + } + + // 检查邮箱唯一性(包括已删除的记录) + if (StringUtils.hasText(dto.getEmail()) && userRepository.existsByEmail(dto.getEmail())) { + throw new UniqueConstraintException(ResponseCode.EMAIL_EXISTS, "email", dto.getEmail()); + } + } } \ No newline at end of file diff --git a/backend/src/main/resources/messages.properties b/backend/src/main/resources/messages.properties index 4999ec83..760340d4 100644 --- a/backend/src/main/resources/messages.properties +++ b/backend/src/main/resources/messages.properties @@ -14,8 +14,8 @@ data.not.found=找不到ID为{0}的{1} # 用户相关 user.not.found=用户不存在 -user.username.exists=用户名已存在 -user.email.exists=邮箱已存在 +user.username.exists=用户名"{0}"已存在 +user.email.exists=邮箱"{0}"已存在 user.login.error=用户名或密码错误 # 系统异常消息 @@ -42,8 +42,8 @@ jwt.token.missing=未提供登录凭证 # 角色相关错误消息 role.not.found=角色不存在 -role.code.exists=角色编码已存在 -role.name.exists=角色名称已存在 +role.code.exists=角色编码"{0}"已存在 +role.name.exists=角色名称"{0}"已存在 role.in.use=角色正在使用中,无法删除 role.admin.cannot.delete=不能删除超级管理员角色 role.admin.cannot.update=不能修改超级管理员角色 \ No newline at end of file