refactor(auth): 统一异常处理和API响应规范

- 添加统一响应类 ApiResponse
- 添加错误码枚举 ErrorCode
- 添加业务异常类 BusinessException
- 添加全局异常处理器 GlobalExceptionHandler
- 登录接口改为POST body
- 统一登录错误信息,避免用户枚举
- 更新开发规范文档
This commit is contained in:
chiguyong 2026-03-17 22:48:44 +08:00
parent 2f8ac15434
commit 53381e2670
7 changed files with 220 additions and 49 deletions

View File

@ -2,7 +2,9 @@ package com.ether.pms.auth.controller;
import com.ether.pms.auth.service.LoginService;
import com.ether.pms.auth.util.JwtTokenProvider;
import com.ether.pms.common.ApiResponse;
import jakarta.servlet.http.HttpServletRequest;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@ -19,56 +21,55 @@ public class AuthController {
private final JwtTokenProvider jwtTokenProvider;
@PostMapping("/login")
public ResponseEntity<Map<String, Object>> login(
@RequestParam String username,
@RequestParam String password,
HttpServletRequest request) {
public ResponseEntity<ApiResponse<Map<String, Object>>> login(
@RequestBody LoginRequest request,
HttpServletRequest httpRequest) {
String ip = getClientIp(request);
Map<String, Object> result = loginService.login(username, password, ip);
return ResponseEntity.ok(result);
String ip = getClientIp(httpRequest);
Map<String, Object> result = loginService.login(request.getUsername(), request.getPassword(), ip);
return ResponseEntity.ok(ApiResponse.success(result));
}
@PostMapping("/logout")
public ResponseEntity<Void> logout(@RequestHeader(value = "Authorization", required = false) String token) {
return ResponseEntity.ok().build();
public ResponseEntity<ApiResponse<Void>> logout(@RequestHeader(value = "Authorization", required = false) String token) {
return ResponseEntity.ok(ApiResponse.success());
}
@GetMapping("/me")
public ResponseEntity<Map<String, Object>> getCurrentUser(
public ResponseEntity<ApiResponse<Map<String, Object>>> getCurrentUser(
@RequestHeader(value = "Authorization", required = false) String token) {
if (token == null || !token.startsWith("Bearer ")) {
return ResponseEntity.status(401).build();
return ResponseEntity.ok(ApiResponse.error(401, "未授权"));
}
String jwt = token.substring(7);
if (!jwtTokenProvider.validateToken(jwt)) {
return ResponseEntity.status(401).build();
return ResponseEntity.ok(ApiResponse.error(401, "Token无效"));
}
String username = jwtTokenProvider.getUsernameFromToken(jwt);
Map<String, Object> result = new HashMap<>();
result.put("username", username);
return ResponseEntity.ok(result);
return ResponseEntity.ok(ApiResponse.success(result));
}
@PostMapping("/refresh")
public ResponseEntity<Map<String, Object>> refreshToken(
public ResponseEntity<ApiResponse<Map<String, Object>>> refreshToken(
@RequestHeader(value = "Authorization", required = false) String token) {
if (token == null || !token.startsWith("Bearer ")) {
return ResponseEntity.status(401).build();
return ResponseEntity.ok(ApiResponse.error(401, "未授权"));
}
String jwt = token.substring(7);
if (!jwtTokenProvider.validateToken(jwt)) {
return ResponseEntity.status(401).build();
return ResponseEntity.ok(ApiResponse.error(401, "Token无效"));
}
if (jwtTokenProvider.isTokenExpired(jwt)) {
return ResponseEntity.status(401).build();
return ResponseEntity.ok(ApiResponse.error(401, "Token已过期"));
}
String username = jwtTokenProvider.getUsernameFromToken(jwt);
@ -78,7 +79,7 @@ public class AuthController {
Map<String, Object> result = new HashMap<>();
result.put("token", newToken);
return ResponseEntity.ok(result);
return ResponseEntity.ok(ApiResponse.success(result));
}
private String getClientIp(HttpServletRequest request) {
@ -91,4 +92,10 @@ public class AuthController {
}
return ip;
}
@Data
public static class LoginRequest {
private String username;
private String password;
}
}

View File

@ -3,6 +3,8 @@ package com.ether.pms.auth.service;
import com.ether.pms.auth.entity.User;
import com.ether.pms.auth.repository.UserRepository;
import com.ether.pms.auth.util.JwtTokenProvider;
import com.ether.pms.common.BusinessException;
import com.ether.pms.common.ErrorCode;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@ -18,34 +20,25 @@ public class LoginService {
private final PasswordService passwordService;
private final LoginAttemptService loginAttemptService;
private static final int MAX_ATTEMPTS = 5;
public Map<String, Object> login(String username, String password, String ip) {
if (loginAttemptService.isLockedOut(username)) {
int remaining = loginAttemptService.getRemainingAttempts(username);
throw new RuntimeException("账号已被锁定,请" + 10 + "分钟后再试");
throw new BusinessException(ErrorCode.AUTH_002);
}
User user = userRepository.findByUsername(username)
.orElseThrow(() -> {
User user = userRepository.findByUsername(username).orElse(null);
if (user == null || !passwordService.matches(password, user.getPassword())) {
if (user != null) {
loginAttemptService.recordFailure(username);
return new RuntimeException("用户名或密码错误");
});
}
throw new BusinessException(ErrorCode.AUTH_001);
}
if (user.getStatus() == User.UserStatus.LOCKED) {
throw new RuntimeException("账号已被锁定");
throw new BusinessException(ErrorCode.AUTH_002);
}
if (user.getStatus() == User.UserStatus.DISABLED) {
throw new RuntimeException("账号已被禁用");
}
if (!passwordService.matches(password, user.getPassword())) {
loginAttemptService.recordFailure(username);
int remaining = loginAttemptService.getRemainingAttempts(username);
if (remaining <= 0) {
throw new RuntimeException("账号已被锁定请10分钟后再试");
}
throw new RuntimeException("用户名或密码错误,剩余尝试次数: " + remaining);
throw new BusinessException(ErrorCode.AUTH_003);
}
loginAttemptService.recordSuccess(username);

View File

@ -4,6 +4,8 @@ import com.ether.pms.auth.entity.User;
import com.ether.pms.auth.entity.Role;
import com.ether.pms.auth.repository.UserRepository;
import com.ether.pms.auth.repository.RoleRepository;
import com.ether.pms.common.BusinessException;
import com.ether.pms.common.ErrorCode;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@ -26,27 +28,31 @@ public class UserService {
public User findById(UUID id) {
return userRepository.findById(id)
.orElseThrow(() -> new RuntimeException("用户不存在"));
.orElseThrow(() -> new BusinessException(ErrorCode.USER_003));
}
public User findByUsername(String username) {
return userRepository.findByUsername(username)
.orElseThrow(() -> new RuntimeException("用户不存在"));
.orElseThrow(() -> new BusinessException(ErrorCode.USER_003));
}
@Transactional
public User create(User user) {
if (userRepository.existsByUsername(user.getUsername())) {
throw new RuntimeException("用户名已存在");
throw new BusinessException(ErrorCode.USER_001);
}
if (user.getPhone() != null && userRepository.existsByPhone(user.getPhone())) {
throw new RuntimeException("手机号已存在");
throw new BusinessException(ErrorCode.USER_002);
}
try {
passwordService.validatePassword(user.getPassword());
} catch (IllegalArgumentException e) {
throw new BusinessException(ErrorCode.BAD_REQUEST, e.getMessage());
}
if (passwordService.isPasswordWeak(user.getPassword())) {
throw new RuntimeException("密码太弱,请使用更复杂的密码");
throw new BusinessException(ErrorCode.BAD_REQUEST, "密码太弱,请使用更复杂的密码");
}
user.setPassword(passwordService.encode(user.getPassword()));
@ -63,7 +69,7 @@ public class UserService {
}
if (user.getPhone() != null) {
if (!user.getPhone().equals(existing.getPhone()) && userRepository.existsByPhone(user.getPhone())) {
throw new RuntimeException("手机号已存在");
throw new BusinessException(ErrorCode.USER_002);
}
existing.setPhone(user.getPhone());
}
@ -85,13 +91,17 @@ public class UserService {
User user = findById(id);
if (!passwordService.matches(oldPassword, user.getPassword())) {
throw new RuntimeException("原密码错误");
throw new BusinessException(ErrorCode.USER_004);
}
try {
passwordService.validatePassword(newPassword);
} catch (IllegalArgumentException e) {
throw new BusinessException(ErrorCode.BAD_REQUEST, e.getMessage());
}
if (passwordService.isPasswordWeak(newPassword)) {
throw new RuntimeException("新密码太弱,请使用更复杂的密码");
throw new BusinessException(ErrorCode.BAD_REQUEST, "新密码太弱,请使用更复杂的密码");
}
user.setPassword(passwordService.encode(newPassword));
@ -102,10 +112,14 @@ public class UserService {
public void resetPassword(UUID id, String newPassword) {
User user = findById(id);
try {
passwordService.validatePassword(newPassword);
} catch (IllegalArgumentException e) {
throw new BusinessException(ErrorCode.BAD_REQUEST, e.getMessage());
}
if (passwordService.isPasswordWeak(newPassword)) {
throw new RuntimeException("密码太弱,请使用更复杂的密码");
throw new BusinessException(ErrorCode.BAD_REQUEST, "密码太弱,请使用更复杂的密码");
}
user.setPassword(passwordService.encode(newPassword));

View File

@ -0,0 +1,45 @@
package com.ether.pms.common;
import lombok.Data;
@Data
public class ApiResponse<T> {
private int code;
private String message;
private T data;
public ApiResponse() {
}
public ApiResponse(int code, String message) {
this.code = code;
this.message = message;
}
public ApiResponse(int code, String message, T data) {
this.code = code;
this.message = message;
this.data = data;
}
public static <T> ApiResponse<T> success() {
return new ApiResponse<>(200, "success");
}
public static <T> ApiResponse<T> success(T data) {
return new ApiResponse<>(200, "success", data);
}
public static <T> ApiResponse<T> success(String message, T data) {
return new ApiResponse<>(200, message, data);
}
public static <T> ApiResponse<T> error(int code, String message) {
return new ApiResponse<>(code, message);
}
public static <T> ApiResponse<T> error(String message) {
return new ApiResponse<>(500, message);
}
}

View File

@ -0,0 +1,28 @@
package com.ether.pms.common;
import lombok.Getter;
@Getter
public class BusinessException extends RuntimeException {
private final int code;
private final String message;
public BusinessException(ErrorCode errorCode) {
super(errorCode.getMessage());
this.code = errorCode.getCode();
this.message = errorCode.getMessage();
}
public BusinessException(ErrorCode errorCode, String customMessage) {
super(customMessage);
this.code = errorCode.getCode();
this.message = customMessage;
}
public BusinessException(int code, String message) {
super(message);
this.code = code;
this.message = message;
}
}

View File

@ -0,0 +1,48 @@
package com.ether.pms.common;
public enum ErrorCode {
SUCCESS(200, "success"),
BAD_REQUEST(400, "请求参数错误"),
UNAUTHORIZED(401, "未授权"),
FORBIDDEN(403, "禁止访问"),
NOT_FOUND(404, "资源不存在"),
AUTH_001(1001, "用户名或密码错误"),
AUTH_002(1002, "账号已被锁定"),
AUTH_003(1003, "账号已被禁用"),
AUTH_004(1004, "Token已过期"),
AUTH_005(1005, "Token无效"),
USER_001(2001, "用户名已存在"),
USER_002(2002, "手机号已存在"),
USER_003(2003, "用户不存在"),
USER_004(2004, "原密码错误"),
ROLE_001(3001, "角色编码已存在"),
ROLE_002(3002, "角色不存在"),
PERMISSION_001(4001, "权限编码已存在"),
PROJECT_001(5001, "项目编码已存在"),
PROJECT_002(5002, "项目不存在"),
SYSTEM_ERROR(9999, "系统错误");
private final int code;
private final String message;
ErrorCode(int code, String message) {
this.code = code;
this.message = message;
}
public int getCode() {
return code;
}
public String getMessage() {
return message;
}
}

View File

@ -0,0 +1,36 @@
package com.ether.pms.common;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
@ExceptionHandler(BusinessException.class)
public ResponseEntity<ApiResponse<Void>> handleBusinessException(BusinessException e) {
log.warn("业务异常: code={}, message={}", e.getCode(), e.getMessage());
return ResponseEntity
.status(HttpStatus.OK)
.body(ApiResponse.error(e.getCode(), e.getMessage()));
}
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<ApiResponse<Void>> handleIllegalArgumentException(IllegalArgumentException e) {
log.warn("参数异常: {}", e.getMessage());
return ResponseEntity
.status(HttpStatus.OK)
.body(ApiResponse.error(400, e.getMessage()));
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ApiResponse<Void>> handleException(Exception e) {
log.error("系统异常", e);
return ResponseEntity
.status(HttpStatus.OK)
.body(ApiResponse.error(ErrorCode.SYSTEM_ERROR.getCode(), "系统错误,请稍后重试"));
}
}