From 75c35369fc3701480c5c54e9235ae4560000c4f8 Mon Sep 17 00:00:00 2001 From: yulinling <2712495353@qq.com> Date: Tue, 5 Aug 2025 23:02:47 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=87=AA=E5=AE=9A=E4=B9=89=E6=A0=A1?= =?UTF-8?q?=E9=AA=8C=20fix:=20=E4=BF=AE=E5=A4=8Dvalid?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 19 ++++ .../workflow/annotation/FieldMatch.java | 39 ++++++++ .../validator/FieldMatchValidator.java | 41 ++++++++ .../workflow/controller/AuthController.java | 26 ++--- .../workflow/dto/request/LoginRequest.java | 2 + .../workflow/dto/request/RegisterRequest.java | 2 + .../handler/GlobalExceptionHandler.java | 97 +++++++++++++------ .../workflow/service/AuthService.java | 2 +- .../service/impl/AuthServiceImpl.java | 49 ++++------ 9 files changed, 204 insertions(+), 73 deletions(-) create mode 100644 src/main/java/asia/yulinling/workflow/annotation/FieldMatch.java create mode 100644 src/main/java/asia/yulinling/workflow/annotation/validator/FieldMatchValidator.java diff --git a/pom.xml b/pom.xml index f41f6cf..87359ee 100644 --- a/pom.xml +++ b/pom.xml @@ -201,6 +201,25 @@ 3.0.2 + + org.hibernate.validator + hibernate-validator + 8.0.1.Final + + + + + commons-beanutils + commons-beanutils + 1.9.4 + + + + commons-logging + commons-logging + 1.2 + + diff --git a/src/main/java/asia/yulinling/workflow/annotation/FieldMatch.java b/src/main/java/asia/yulinling/workflow/annotation/FieldMatch.java new file mode 100644 index 0000000..7f59454 --- /dev/null +++ b/src/main/java/asia/yulinling/workflow/annotation/FieldMatch.java @@ -0,0 +1,39 @@ +package asia.yulinling.workflow.annotation; + +import asia.yulinling.workflow.annotation.validator.FieldMatchValidator; +import jakarta.validation.Constraint; +import jakarta.validation.Payload; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + *

+ * 字段校验 + *

+ * + * @author YLL + * @since 2025/8/5 + */ +@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Constraint(validatedBy = FieldMatchValidator.class) +public @interface FieldMatch { + String message() default "字段值不匹配"; + + Class[] groups() default {}; + + Class[] payload() default {}; + + String first(); + + String second(); + + @Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE}) + @Retention(RetentionPolicy.RUNTIME) + @interface List { + FieldMatch[] value(); + } +} diff --git a/src/main/java/asia/yulinling/workflow/annotation/validator/FieldMatchValidator.java b/src/main/java/asia/yulinling/workflow/annotation/validator/FieldMatchValidator.java new file mode 100644 index 0000000..b5556cf --- /dev/null +++ b/src/main/java/asia/yulinling/workflow/annotation/validator/FieldMatchValidator.java @@ -0,0 +1,41 @@ +package asia.yulinling.workflow.annotation.validator; + +import asia.yulinling.workflow.annotation.FieldMatch; +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.beanutils.BeanUtils; + +/** + *

+ * 校验逻辑 + *

+ * + * @author YLL + * @since 2025/8/5 + */ +@Slf4j +public class FieldMatchValidator implements ConstraintValidator { + private String firstFieldName; + private String secondFieldName; + + @Override + public void initialize(FieldMatch constraintAnnotation) { + firstFieldName = constraintAnnotation.first(); + secondFieldName = constraintAnnotation.second(); + } + + @Override + public boolean isValid(Object o, ConstraintValidatorContext constraintValidatorContext) { + try { + final Object firstObj = BeanUtils.getProperty(o, firstFieldName); + final Object secondObj = BeanUtils.getProperty(o, secondFieldName); + + return firstObj == null && secondObj == null || + firstObj != null && firstObj.equals(secondObj); + } catch (final Exception ignore) { + // ignore + } + return true; + } +} diff --git a/src/main/java/asia/yulinling/workflow/controller/AuthController.java b/src/main/java/asia/yulinling/workflow/controller/AuthController.java index 493c037..20b6403 100644 --- a/src/main/java/asia/yulinling/workflow/controller/AuthController.java +++ b/src/main/java/asia/yulinling/workflow/controller/AuthController.java @@ -8,18 +8,16 @@ import asia.yulinling.workflow.model.vo.LoginVO; import asia.yulinling.workflow.model.vo.RegisterVO; import asia.yulinling.workflow.service.AuthService; import asia.yulinling.workflow.utils.JwtUtil; -import com.alibaba.druid.support.spring.stat.annotation.Stat; import jakarta.servlet.http.HttpServletRequest; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; -import org.springframework.http.HttpStatus; +import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.LockedException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -33,6 +31,7 @@ import org.springframework.web.bind.annotation.RestController; * @author YLL * @since 2025/6/13 */ +@Slf4j @RestController @RequiredArgsConstructor @RequestMapping("/auth") @@ -44,18 +43,12 @@ public class AuthController { @PostMapping("/login") public ResponseEntity> login(@Valid @RequestBody LoginRequest loginRequest) { - try { - Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken( - loginRequest.getUsername(), loginRequest.getPassword() - )); + Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken( + loginRequest.getUsername(), loginRequest.getPassword() + )); - LoginVO loginVO = authService.login(authentication, loginRequest.getRememberMe()); - return ResponseEntity.ok().body(ApiResponse.ofStatus(Status.LOGIN_SUCCESS, loginVO)); - } catch (BadCredentialsException e) { - return ResponseEntity.ok().body(ApiResponse.ofStatus(Status.USERNAME_PASSWORD_ERROR)); - } catch (LockedException e) { - return ResponseEntity.ok().body(ApiResponse.ofStatus(Status.ACCOUNT_LOCKED)); - } + LoginVO loginVO = authService.login(authentication, loginRequest.getRememberMe()); + return ResponseEntity.ok().body(ApiResponse.ofStatus(Status.LOGIN_SUCCESS, loginVO)); } @PostMapping("/logout") @@ -69,8 +62,9 @@ public class AuthController { } @PostMapping("/register") - public ApiResponse register(@RequestBody RegisterRequest registerRequest) throws Exception { - return authService.register(registerRequest); + public ApiResponse register(@Valid @RequestBody RegisterRequest registerRequest) throws Exception { + authService.register(registerRequest); + return ApiResponse.ofStatus(Status.SUCCESS); } } diff --git a/src/main/java/asia/yulinling/workflow/dto/request/LoginRequest.java b/src/main/java/asia/yulinling/workflow/dto/request/LoginRequest.java index cf36ab3..52be9a2 100644 --- a/src/main/java/asia/yulinling/workflow/dto/request/LoginRequest.java +++ b/src/main/java/asia/yulinling/workflow/dto/request/LoginRequest.java @@ -1,6 +1,7 @@ package asia.yulinling.workflow.dto.request; import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; import lombok.Data; /** *

@@ -27,5 +28,6 @@ public class LoginRequest { /** * 记住我 */ + @NotNull(message = "记住我不能为空") private Boolean rememberMe = false; } diff --git a/src/main/java/asia/yulinling/workflow/dto/request/RegisterRequest.java b/src/main/java/asia/yulinling/workflow/dto/request/RegisterRequest.java index 0adcb5c..a121cf3 100644 --- a/src/main/java/asia/yulinling/workflow/dto/request/RegisterRequest.java +++ b/src/main/java/asia/yulinling/workflow/dto/request/RegisterRequest.java @@ -1,5 +1,6 @@ package asia.yulinling.workflow.dto.request; +import asia.yulinling.workflow.annotation.FieldMatch; import jakarta.validation.constraints.Email; import jakarta.validation.constraints.Max; import jakarta.validation.constraints.Min; @@ -15,6 +16,7 @@ import lombok.Data; * @since 2025/6/19 */ @Data +@FieldMatch(first = "password", second = "confirmPassword", message = "密码不一致") public class RegisterRequest { /** diff --git a/src/main/java/asia/yulinling/workflow/exception/handler/GlobalExceptionHandler.java b/src/main/java/asia/yulinling/workflow/exception/handler/GlobalExceptionHandler.java index 0c568d7..56ad670 100644 --- a/src/main/java/asia/yulinling/workflow/exception/handler/GlobalExceptionHandler.java +++ b/src/main/java/asia/yulinling/workflow/exception/handler/GlobalExceptionHandler.java @@ -4,11 +4,23 @@ import asia.yulinling.workflow.exception.PageException; import asia.yulinling.workflow.exception.ServiceException; import asia.yulinling.workflow.model.ApiResponse; import lombok.extern.slf4j.Slf4j; +import org.springframework.context.support.DefaultMessageSourceResolvable; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.FieldError; +import org.springframework.validation.ObjectError; +import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.servlet.ModelAndView; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.stream.Collectors; + /** *

* 全局异常处理 @@ -22,6 +34,63 @@ import org.springframework.web.servlet.ModelAndView; public class GlobalExceptionHandler { private static final String DEFAULT_ERROR_VIEW = "error"; + /** + * 统一 ServiceException 异常处理 + * + * @param e ServiceException + * @return 统一返回 json 格式 + */ + @ExceptionHandler(ServiceException.class) + @ResponseBody + public ApiResponse catchErrorHandler(ServiceException e) { + log.error("service error:{}", e.getMessage()); + return ApiResponse.of(HttpStatus.INTERNAL_SERVER_ERROR.value(), e.getMessage(), null); + } + + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity> handleValidationExceptions( + MethodArgumentNotValidException ex) { + +// Map errors = new LinkedHashMap<>(); +// +// ex.getBindingResult().getAllErrors().forEach(error -> { +// String fieldName; +// +// if (error instanceof FieldError) { +// // 处理字段级错误(如 @NotBlank, @Size) +// fieldName = ((FieldError) error).getField(); +// } else { +// // 处理对象级错误(如 @FieldMatch) +// fieldName = error.getObjectName(); // 返回对象名,如 "registerRequest" +// } +// +// String errorMessage = error.getDefaultMessage(); // 获取默认错误消息 +// errors.put(fieldName, errorMessage); +// }); + + ObjectError objectError = ex.getBindingResult().getAllErrors().get(0); + String defaultMessage = objectError.getDefaultMessage(); + + ApiResponse response = ApiResponse.of(HttpStatus.BAD_REQUEST.value(), defaultMessage, null); + + return ResponseEntity.badRequest().body(response); + } + + /** + * 统一 页面 异常处理 + * + * @param e PageException + * @return 统一跳转到异常页面 + */ + @ExceptionHandler(PageException.class) + public ModelAndView pageErrorHandler(PageException e) { + log.error("test" + e.getMessage()); + ModelAndView view = new ModelAndView(); + view.addObject("exception", e); + view.setViewName(DEFAULT_ERROR_VIEW); + return view; + } + /** * 统一 json 异常处理 * @@ -34,32 +103,4 @@ public class GlobalExceptionHandler { log.error("json error:{}", e.getMessage()); return ApiResponse.of(500, e.getMessage(), null); } - - /** - * 统一 ServiceException 异常处理 - * - * @param e ServiceException - * @return 统一返回 json 格式 - */ - @ExceptionHandler(ServiceException.class) - @ResponseBody - public ApiResponse catchErrorHandler(ServiceException e) { - log.error("service error:{}", e.getMessage()); - return ApiResponse.of(500, e.getMessage(), null); - } - - /** - * 统一 页面 异常处理 - * - * @param e PageException - * @return 统一跳转到异常页面 - */ - @ExceptionHandler(PageException.class) - public ModelAndView pageErrorHandler(PageException e) { - log.error(e.getMessage()); - ModelAndView view = new ModelAndView(); - view.addObject("exception", e); - view.setViewName(DEFAULT_ERROR_VIEW); - return view; - } } diff --git a/src/main/java/asia/yulinling/workflow/service/AuthService.java b/src/main/java/asia/yulinling/workflow/service/AuthService.java index 4002cdd..b813dfc 100644 --- a/src/main/java/asia/yulinling/workflow/service/AuthService.java +++ b/src/main/java/asia/yulinling/workflow/service/AuthService.java @@ -37,5 +37,5 @@ public interface AuthService { * @param request 注册请求 * @return 请求结果 */ - ApiResponse register(RegisterRequest request) throws Exception; + RegisterVO register(RegisterRequest request) throws Exception; } diff --git a/src/main/java/asia/yulinling/workflow/service/impl/AuthServiceImpl.java b/src/main/java/asia/yulinling/workflow/service/impl/AuthServiceImpl.java index 7e876ed..f0fefe8 100644 --- a/src/main/java/asia/yulinling/workflow/service/impl/AuthServiceImpl.java +++ b/src/main/java/asia/yulinling/workflow/service/impl/AuthServiceImpl.java @@ -1,7 +1,6 @@ package asia.yulinling.workflow.service.impl; import asia.yulinling.workflow.constant.Status; -import asia.yulinling.workflow.dto.request.LoginRequest; import asia.yulinling.workflow.dto.request.RegisterRequest; import asia.yulinling.workflow.mapper.UserMapper; import asia.yulinling.workflow.model.ApiResponse; @@ -13,14 +12,10 @@ import asia.yulinling.workflow.service.AuthService; import asia.yulinling.workflow.utils.JwtUtil; import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; -import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -52,18 +47,18 @@ public class AuthServiceImpl implements AuthService { */ @Override public LoginVO login(Authentication authentication, boolean rememberMe) { - UserPrincipal userPrincipal = (UserPrincipal) authentication.getPrincipal(); + UserPrincipal userPrincipal = (UserPrincipal) authentication.getPrincipal(); - String accessToken = jwtUtil.generateToken(authentication, rememberMe); - Long expiresIn = jwtUtil.getExpiresIn(rememberMe); + String accessToken = jwtUtil.generateToken(authentication, rememberMe); + Long expiresIn = jwtUtil.getExpiresIn(rememberMe); log.info("用户登录成功: {}", userPrincipal.getUsername()); return LoginVO.builder() - .userId(userPrincipal.getId()) - .username(userPrincipal.getUsername()) - .accessToken(accessToken) - .expiresIn(expiresIn) - .build(); + .userId(userPrincipal.getId()) + .username(userPrincipal.getUsername()) + .accessToken(accessToken) + .expiresIn(expiresIn) + .build(); } /** @@ -77,6 +72,10 @@ public class AuthServiceImpl implements AuthService { jwtUtil.invalidateToken(token); } catch (SecurityException e) { log.info("invalidateToken: {}", e.getMessage()); + throw e; + } catch (Exception e) { + log.error("Unexpected error during token invalidation: {}", e.getMessage(), e); + throw new SecurityException("Failed to invalidate token", e); } } @@ -88,20 +87,14 @@ public class AuthServiceImpl implements AuthService { */ @Override @Transactional(rollbackFor = Exception.class) - public ApiResponse register(RegisterRequest request) throws Exception { - - if (!StrUtil.equals(request.getConfirmPassword(), request.getPassword())) { - log.info("password {}, confirmPassword {}", request.getConfirmPassword(), request.getPassword()); - return ApiResponse.of(400, "密码不一致", null); - } - - if (userMapper.exists(new LambdaQueryWrapper().eq(User::getUsername, request.getUsername()))) { - return ApiResponse.of(400, "用户名已存在", null); - } - - if (userMapper.exists(new LambdaQueryWrapper().eq(User::getEmail, request.getEmail()))) { - return ApiResponse.of(400, "邮箱已注册", null); - } + public RegisterVO register(RegisterRequest request) throws Exception { +// if (userMapper.exists(new LambdaQueryWrapper().eq(User::getUsername, request.getUsername()))) { +// return ApiResponse.of(400, "用户名已存在", null); +// } +// +// if (userMapper.exists(new LambdaQueryWrapper().eq(User::getEmail, request.getEmail()))) { +// return ApiResponse.of(400, "邮箱已注册", null); +// } User user = new User(); user.setUsername(request.getUsername()); @@ -119,7 +112,7 @@ public class AuthServiceImpl implements AuthService { log.info("insert user after: {}", user); RegisterVO registerVO = new RegisterVO(); registerVO.setUserId(user.getId()); - return ApiResponse.ofStatus(Status.REGISTER_SUCCESS, registerVO); + return registerVO; } catch (Exception e) { log.error("数据插入用户数据失败: {}", e.getMessage()); throw new RuntimeException(e);