refactor(auth): 统一异常处理和API响应规范
- 添加统一响应类 ApiResponse - 添加错误码枚举 ErrorCode - 添加业务异常类 BusinessException - 添加全局异常处理器 GlobalExceptionHandler - 登录接口改为POST body - 统一登录错误信息,避免用户枚举 - 更新开发规范文档
This commit is contained in:
parent
2f8ac15434
commit
53381e2670
|
|
@ -2,7 +2,9 @@ package com.ether.pms.auth.controller;
|
||||||
|
|
||||||
import com.ether.pms.auth.service.LoginService;
|
import com.ether.pms.auth.service.LoginService;
|
||||||
import com.ether.pms.auth.util.JwtTokenProvider;
|
import com.ether.pms.auth.util.JwtTokenProvider;
|
||||||
|
import com.ether.pms.common.ApiResponse;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import lombok.Data;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
@ -19,56 +21,55 @@ public class AuthController {
|
||||||
private final JwtTokenProvider jwtTokenProvider;
|
private final JwtTokenProvider jwtTokenProvider;
|
||||||
|
|
||||||
@PostMapping("/login")
|
@PostMapping("/login")
|
||||||
public ResponseEntity<Map<String, Object>> login(
|
public ResponseEntity<ApiResponse<Map<String, Object>>> login(
|
||||||
@RequestParam String username,
|
@RequestBody LoginRequest request,
|
||||||
@RequestParam String password,
|
HttpServletRequest httpRequest) {
|
||||||
HttpServletRequest request) {
|
|
||||||
|
|
||||||
String ip = getClientIp(request);
|
String ip = getClientIp(httpRequest);
|
||||||
Map<String, Object> result = loginService.login(username, password, ip);
|
Map<String, Object> result = loginService.login(request.getUsername(), request.getPassword(), ip);
|
||||||
return ResponseEntity.ok(result);
|
return ResponseEntity.ok(ApiResponse.success(result));
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/logout")
|
@PostMapping("/logout")
|
||||||
public ResponseEntity<Void> logout(@RequestHeader(value = "Authorization", required = false) String token) {
|
public ResponseEntity<ApiResponse<Void>> logout(@RequestHeader(value = "Authorization", required = false) String token) {
|
||||||
return ResponseEntity.ok().build();
|
return ResponseEntity.ok(ApiResponse.success());
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/me")
|
@GetMapping("/me")
|
||||||
public ResponseEntity<Map<String, Object>> getCurrentUser(
|
public ResponseEntity<ApiResponse<Map<String, Object>>> getCurrentUser(
|
||||||
@RequestHeader(value = "Authorization", required = false) String token) {
|
@RequestHeader(value = "Authorization", required = false) String token) {
|
||||||
|
|
||||||
if (token == null || !token.startsWith("Bearer ")) {
|
if (token == null || !token.startsWith("Bearer ")) {
|
||||||
return ResponseEntity.status(401).build();
|
return ResponseEntity.ok(ApiResponse.error(401, "未授权"));
|
||||||
}
|
}
|
||||||
|
|
||||||
String jwt = token.substring(7);
|
String jwt = token.substring(7);
|
||||||
if (!jwtTokenProvider.validateToken(jwt)) {
|
if (!jwtTokenProvider.validateToken(jwt)) {
|
||||||
return ResponseEntity.status(401).build();
|
return ResponseEntity.ok(ApiResponse.error(401, "Token无效"));
|
||||||
}
|
}
|
||||||
|
|
||||||
String username = jwtTokenProvider.getUsernameFromToken(jwt);
|
String username = jwtTokenProvider.getUsernameFromToken(jwt);
|
||||||
Map<String, Object> result = new HashMap<>();
|
Map<String, Object> result = new HashMap<>();
|
||||||
result.put("username", username);
|
result.put("username", username);
|
||||||
|
|
||||||
return ResponseEntity.ok(result);
|
return ResponseEntity.ok(ApiResponse.success(result));
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/refresh")
|
@PostMapping("/refresh")
|
||||||
public ResponseEntity<Map<String, Object>> refreshToken(
|
public ResponseEntity<ApiResponse<Map<String, Object>>> refreshToken(
|
||||||
@RequestHeader(value = "Authorization", required = false) String token) {
|
@RequestHeader(value = "Authorization", required = false) String token) {
|
||||||
|
|
||||||
if (token == null || !token.startsWith("Bearer ")) {
|
if (token == null || !token.startsWith("Bearer ")) {
|
||||||
return ResponseEntity.status(401).build();
|
return ResponseEntity.ok(ApiResponse.error(401, "未授权"));
|
||||||
}
|
}
|
||||||
|
|
||||||
String jwt = token.substring(7);
|
String jwt = token.substring(7);
|
||||||
if (!jwtTokenProvider.validateToken(jwt)) {
|
if (!jwtTokenProvider.validateToken(jwt)) {
|
||||||
return ResponseEntity.status(401).build();
|
return ResponseEntity.ok(ApiResponse.error(401, "Token无效"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (jwtTokenProvider.isTokenExpired(jwt)) {
|
if (jwtTokenProvider.isTokenExpired(jwt)) {
|
||||||
return ResponseEntity.status(401).build();
|
return ResponseEntity.ok(ApiResponse.error(401, "Token已过期"));
|
||||||
}
|
}
|
||||||
|
|
||||||
String username = jwtTokenProvider.getUsernameFromToken(jwt);
|
String username = jwtTokenProvider.getUsernameFromToken(jwt);
|
||||||
|
|
@ -78,7 +79,7 @@ public class AuthController {
|
||||||
Map<String, Object> result = new HashMap<>();
|
Map<String, Object> result = new HashMap<>();
|
||||||
result.put("token", newToken);
|
result.put("token", newToken);
|
||||||
|
|
||||||
return ResponseEntity.ok(result);
|
return ResponseEntity.ok(ApiResponse.success(result));
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getClientIp(HttpServletRequest request) {
|
private String getClientIp(HttpServletRequest request) {
|
||||||
|
|
@ -91,4 +92,10 @@ public class AuthController {
|
||||||
}
|
}
|
||||||
return ip;
|
return ip;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class LoginRequest {
|
||||||
|
private String username;
|
||||||
|
private String password;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@ package com.ether.pms.auth.service;
|
||||||
import com.ether.pms.auth.entity.User;
|
import com.ether.pms.auth.entity.User;
|
||||||
import com.ether.pms.auth.repository.UserRepository;
|
import com.ether.pms.auth.repository.UserRepository;
|
||||||
import com.ether.pms.auth.util.JwtTokenProvider;
|
import com.ether.pms.auth.util.JwtTokenProvider;
|
||||||
|
import com.ether.pms.common.BusinessException;
|
||||||
|
import com.ether.pms.common.ErrorCode;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
|
@ -18,34 +20,25 @@ public class LoginService {
|
||||||
private final PasswordService passwordService;
|
private final PasswordService passwordService;
|
||||||
private final LoginAttemptService loginAttemptService;
|
private final LoginAttemptService loginAttemptService;
|
||||||
|
|
||||||
private static final int MAX_ATTEMPTS = 5;
|
|
||||||
|
|
||||||
public Map<String, Object> login(String username, String password, String ip) {
|
public Map<String, Object> login(String username, String password, String ip) {
|
||||||
if (loginAttemptService.isLockedOut(username)) {
|
if (loginAttemptService.isLockedOut(username)) {
|
||||||
int remaining = loginAttemptService.getRemainingAttempts(username);
|
throw new BusinessException(ErrorCode.AUTH_002);
|
||||||
throw new RuntimeException("账号已被锁定,请" + 10 + "分钟后再试");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
User user = userRepository.findByUsername(username)
|
User user = userRepository.findByUsername(username).orElse(null);
|
||||||
.orElseThrow(() -> {
|
|
||||||
|
if (user == null || !passwordService.matches(password, user.getPassword())) {
|
||||||
|
if (user != null) {
|
||||||
loginAttemptService.recordFailure(username);
|
loginAttemptService.recordFailure(username);
|
||||||
return new RuntimeException("用户名或密码错误");
|
}
|
||||||
});
|
throw new BusinessException(ErrorCode.AUTH_001);
|
||||||
|
}
|
||||||
|
|
||||||
if (user.getStatus() == User.UserStatus.LOCKED) {
|
if (user.getStatus() == User.UserStatus.LOCKED) {
|
||||||
throw new RuntimeException("账号已被锁定");
|
throw new BusinessException(ErrorCode.AUTH_002);
|
||||||
}
|
}
|
||||||
if (user.getStatus() == User.UserStatus.DISABLED) {
|
if (user.getStatus() == User.UserStatus.DISABLED) {
|
||||||
throw new RuntimeException("账号已被禁用");
|
throw new BusinessException(ErrorCode.AUTH_003);
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
loginAttemptService.recordSuccess(username);
|
loginAttemptService.recordSuccess(username);
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@ import com.ether.pms.auth.entity.User;
|
||||||
import com.ether.pms.auth.entity.Role;
|
import com.ether.pms.auth.entity.Role;
|
||||||
import com.ether.pms.auth.repository.UserRepository;
|
import com.ether.pms.auth.repository.UserRepository;
|
||||||
import com.ether.pms.auth.repository.RoleRepository;
|
import com.ether.pms.auth.repository.RoleRepository;
|
||||||
|
import com.ether.pms.common.BusinessException;
|
||||||
|
import com.ether.pms.common.ErrorCode;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
@ -26,27 +28,31 @@ public class UserService {
|
||||||
|
|
||||||
public User findById(UUID id) {
|
public User findById(UUID id) {
|
||||||
return userRepository.findById(id)
|
return userRepository.findById(id)
|
||||||
.orElseThrow(() -> new RuntimeException("用户不存在"));
|
.orElseThrow(() -> new BusinessException(ErrorCode.USER_003));
|
||||||
}
|
}
|
||||||
|
|
||||||
public User findByUsername(String username) {
|
public User findByUsername(String username) {
|
||||||
return userRepository.findByUsername(username)
|
return userRepository.findByUsername(username)
|
||||||
.orElseThrow(() -> new RuntimeException("用户不存在"));
|
.orElseThrow(() -> new BusinessException(ErrorCode.USER_003));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public User create(User user) {
|
public User create(User user) {
|
||||||
if (userRepository.existsByUsername(user.getUsername())) {
|
if (userRepository.existsByUsername(user.getUsername())) {
|
||||||
throw new RuntimeException("用户名已存在");
|
throw new BusinessException(ErrorCode.USER_001);
|
||||||
}
|
}
|
||||||
if (user.getPhone() != null && userRepository.existsByPhone(user.getPhone())) {
|
if (user.getPhone() != null && userRepository.existsByPhone(user.getPhone())) {
|
||||||
throw new RuntimeException("手机号已存在");
|
throw new BusinessException(ErrorCode.USER_002);
|
||||||
}
|
}
|
||||||
|
|
||||||
passwordService.validatePassword(user.getPassword());
|
try {
|
||||||
|
passwordService.validatePassword(user.getPassword());
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
throw new BusinessException(ErrorCode.BAD_REQUEST, e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
if (passwordService.isPasswordWeak(user.getPassword())) {
|
if (passwordService.isPasswordWeak(user.getPassword())) {
|
||||||
throw new RuntimeException("密码太弱,请使用更复杂的密码");
|
throw new BusinessException(ErrorCode.BAD_REQUEST, "密码太弱,请使用更复杂的密码");
|
||||||
}
|
}
|
||||||
|
|
||||||
user.setPassword(passwordService.encode(user.getPassword()));
|
user.setPassword(passwordService.encode(user.getPassword()));
|
||||||
|
|
@ -63,7 +69,7 @@ public class UserService {
|
||||||
}
|
}
|
||||||
if (user.getPhone() != null) {
|
if (user.getPhone() != null) {
|
||||||
if (!user.getPhone().equals(existing.getPhone()) && userRepository.existsByPhone(user.getPhone())) {
|
if (!user.getPhone().equals(existing.getPhone()) && userRepository.existsByPhone(user.getPhone())) {
|
||||||
throw new RuntimeException("手机号已存在");
|
throw new BusinessException(ErrorCode.USER_002);
|
||||||
}
|
}
|
||||||
existing.setPhone(user.getPhone());
|
existing.setPhone(user.getPhone());
|
||||||
}
|
}
|
||||||
|
|
@ -85,13 +91,17 @@ public class UserService {
|
||||||
User user = findById(id);
|
User user = findById(id);
|
||||||
|
|
||||||
if (!passwordService.matches(oldPassword, user.getPassword())) {
|
if (!passwordService.matches(oldPassword, user.getPassword())) {
|
||||||
throw new RuntimeException("原密码错误");
|
throw new BusinessException(ErrorCode.USER_004);
|
||||||
}
|
}
|
||||||
|
|
||||||
passwordService.validatePassword(newPassword);
|
try {
|
||||||
|
passwordService.validatePassword(newPassword);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
throw new BusinessException(ErrorCode.BAD_REQUEST, e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
if (passwordService.isPasswordWeak(newPassword)) {
|
if (passwordService.isPasswordWeak(newPassword)) {
|
||||||
throw new RuntimeException("新密码太弱,请使用更复杂的密码");
|
throw new BusinessException(ErrorCode.BAD_REQUEST, "新密码太弱,请使用更复杂的密码");
|
||||||
}
|
}
|
||||||
|
|
||||||
user.setPassword(passwordService.encode(newPassword));
|
user.setPassword(passwordService.encode(newPassword));
|
||||||
|
|
@ -102,10 +112,14 @@ public class UserService {
|
||||||
public void resetPassword(UUID id, String newPassword) {
|
public void resetPassword(UUID id, String newPassword) {
|
||||||
User user = findById(id);
|
User user = findById(id);
|
||||||
|
|
||||||
passwordService.validatePassword(newPassword);
|
try {
|
||||||
|
passwordService.validatePassword(newPassword);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
throw new BusinessException(ErrorCode.BAD_REQUEST, e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
if (passwordService.isPasswordWeak(newPassword)) {
|
if (passwordService.isPasswordWeak(newPassword)) {
|
||||||
throw new RuntimeException("密码太弱,请使用更复杂的密码");
|
throw new BusinessException(ErrorCode.BAD_REQUEST, "密码太弱,请使用更复杂的密码");
|
||||||
}
|
}
|
||||||
|
|
||||||
user.setPassword(passwordService.encode(newPassword));
|
user.setPassword(passwordService.encode(newPassword));
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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(), "系统错误,请稍后重试"));
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue