Compare commits
2 Commits
157afecd35
...
057a066404
Author | SHA1 | Date | |
---|---|---|---|
057a066404 | |||
158a93d608 |
@ -2,7 +2,7 @@ package co.jp.app.common;
|
|||||||
|
|
||||||
public enum ResultCode {
|
public enum ResultCode {
|
||||||
|
|
||||||
SUCCESS(200, "Success"), // 通常与 HTTP 200 OK 对应
|
SUCCESS(200, "Success"),
|
||||||
|
|
||||||
// 客户端错误段 (1000 - 1999)
|
// 客户端错误段 (1000 - 1999)
|
||||||
BAD_REQUEST(1000, "HTTP 400 Bad Request"),
|
BAD_REQUEST(1000, "HTTP 400 Bad Request"),
|
||||||
@ -12,11 +12,11 @@ public enum ResultCode {
|
|||||||
METHOD_NOT_ALLOWED(1004, "HTTP 405 Method Not Allowed"),
|
METHOD_NOT_ALLOWED(1004, "HTTP 405 Method Not Allowed"),
|
||||||
REQUEST_TIMEOUT(1005, "HTTP 408 Request Timeout"),
|
REQUEST_TIMEOUT(1005, "HTTP 408 Request Timeout"),
|
||||||
CONFLICT(1006, "HTTP 409 Conflict"),
|
CONFLICT(1006, "HTTP 409 Conflict"),
|
||||||
UNSUPPORTED_MEDIA_TYPE(1007, "HTTP 415 Unsupported Media Type"), // 对应
|
UNSUPPORTED_MEDIA_TYPE(1007, "HTTP 415 Unsupported Media Type"),
|
||||||
TOO_MANY_REQUESTS(1008, "HTTP 429 Too Many Requests"), // 对应
|
TOO_MANY_REQUESTS(1008, "HTTP 429 Too Many Requests"),
|
||||||
VALIDATION_ERROR(1009, "Parameter validation failure"), // 通常由 @Valid 触发,可包含更详细的字段错误信息
|
VALIDATION_ERROR(1009, "Parameter validation failure"),
|
||||||
|
|
||||||
// 服务端错误段 (2000 - 2999) - 通常建议与 HTTP 5xx 状态码含义相似
|
// 服务端错误段 (2000 - 2999)
|
||||||
INTERNAL_SERVER_ERROR(2000, "HTTP 500 Internal Server Error"),
|
INTERNAL_SERVER_ERROR(2000, "HTTP 500 Internal Server Error"),
|
||||||
SERVICE_UNAVAILABLE(2001, "HTTP 503 Service Unavailable"),
|
SERVICE_UNAVAILABLE(2001, "HTTP 503 Service Unavailable"),
|
||||||
GATEWAY_TIMEOUT(2002, "HTTP 504 Gateway Timeout"),
|
GATEWAY_TIMEOUT(2002, "HTTP 504 Gateway Timeout"),
|
||||||
@ -26,7 +26,7 @@ public enum ResultCode {
|
|||||||
|
|
||||||
// ================================== 用户模块状态码 (3000 - 3999) ==================================
|
// ================================== 用户模块状态码 (3000 - 3999) ==================================
|
||||||
// 注册相关
|
// 注册相关
|
||||||
// USER_REGISTRATION_SUCCESS(3000, "用户注册成功"), // 成功状态也可以用通用的 SUCCESS
|
// USER_REGISTRATION_SUCCESS(3000, "用户注册成功"),
|
||||||
USER_EMAIL_ALREADY_EXISTS(3001, "Email already exists"),
|
USER_EMAIL_ALREADY_EXISTS(3001, "Email already exists"),
|
||||||
// USER_USERNAME_ALREADY_EXISTS(3002, "Username"),
|
// USER_USERNAME_ALREADY_EXISTS(3002, "Username"),
|
||||||
USER_PASSWORD_TOO_SHORT(3003, "password too short"),
|
USER_PASSWORD_TOO_SHORT(3003, "password too short"),
|
||||||
@ -77,7 +77,6 @@ public enum ResultCode {
|
|||||||
return message;
|
return message;
|
||||||
}
|
}
|
||||||
|
|
||||||
// (可选) 允许覆盖默认消息的方法,用于更具体的场景
|
|
||||||
public String getMessage(String customDetail) {
|
public String getMessage(String customDetail) {
|
||||||
return this.message + (customDetail == null || customDetail.isEmpty() ? "" : " (" + customDetail + ")");
|
return this.message + (customDetail == null || customDetail.isEmpty() ? "" : " (" + customDetail + ")");
|
||||||
}
|
}
|
||||||
|
38
src/main/java/co/jp/app/exception/BusinessException.java
Normal file
38
src/main/java/co/jp/app/exception/BusinessException.java
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
package co.jp.app.exception;
|
||||||
|
|
||||||
|
import co.jp.app.common.ResultCode;
|
||||||
|
|
||||||
|
public class BusinessException extends RuntimeException {
|
||||||
|
|
||||||
|
private final ResultCode resultCode;
|
||||||
|
private final String detailMessage; // 附加详细信息
|
||||||
|
|
||||||
|
public BusinessException(ResultCode resultCode) {
|
||||||
|
super(resultCode.getMessage());
|
||||||
|
this.resultCode = resultCode;
|
||||||
|
this.detailMessage = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 有详细信息的构造函数
|
||||||
|
public BusinessException(ResultCode resultCode, String detailMessage) {
|
||||||
|
super(detailMessage != null && !detailMessage.isEmpty() ? detailMessage : resultCode.getMessage());
|
||||||
|
this.resultCode = resultCode;
|
||||||
|
this.detailMessage = detailMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 无详细信息的构造函数
|
||||||
|
public BusinessException(ResultCode resultCode, Throwable cause) {
|
||||||
|
super(resultCode.getMessage(), cause);
|
||||||
|
this.resultCode = resultCode;
|
||||||
|
this.detailMessage = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ResultCode getResultCode() {
|
||||||
|
return resultCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDetailMessage() {
|
||||||
|
return detailMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
127
src/main/java/co/jp/app/exception/GlobalExceptionHandler.java
Normal file
127
src/main/java/co/jp/app/exception/GlobalExceptionHandler.java
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
package co.jp.app.exception;
|
||||||
|
|
||||||
|
import co.jp.app.common.ApiResponse;
|
||||||
|
import co.jp.app.common.ResultCode;
|
||||||
|
import co.jp.app.exception.BusinessException;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.security.access.AccessDeniedException;
|
||||||
|
import org.springframework.security.authentication.BadCredentialsException;
|
||||||
|
import org.springframework.security.core.AuthenticationException;
|
||||||
|
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||||
|
import org.springframework.validation.BindException;
|
||||||
|
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||||
|
import org.springframework.web.bind.MissingServletRequestParameterException;
|
||||||
|
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||||
|
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||||
|
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@RestControllerAdvice
|
||||||
|
public class GlobalExceptionHandler {
|
||||||
|
|
||||||
|
// 日志记录器
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
|
||||||
|
|
||||||
|
// 业务异常
|
||||||
|
@ExceptionHandler(BusinessException.class)
|
||||||
|
public ResponseEntity<ApiResponse<Object>> handleBusinessException(BusinessException ex) {
|
||||||
|
logger.warn("业务异常: Code={}, Message={}, Detail={}", ex.getResultCode().getCode(), ex.getResultCode().getMessage(), ex.getDetailMessage(), ex);
|
||||||
|
|
||||||
|
ApiResponse<Object> body;
|
||||||
|
|
||||||
|
if (ex.getDetailMessage() != null && !ex.getDetailMessage().isEmpty()) {
|
||||||
|
body = ApiResponse.fail(ex.getResultCode(), ex.getDetailMessage());
|
||||||
|
} else {
|
||||||
|
body = ApiResponse.fail(ex.getResultCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpStatus httpStatus = determineHttpStatusFromResultCode(ex.getResultCode());
|
||||||
|
return new ResponseEntity<>(body, httpStatus);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 参数校验异常
|
||||||
|
@ExceptionHandler({MethodArgumentNotValidException.class, BindException.class})
|
||||||
|
public ResponseEntity<ApiResponse<Object>> handleValidationExceptions(BindException ex) { // BindException 是 MethodArgumentNotValidException 的父类
|
||||||
|
String errorMessage = ex.getBindingResult().getFieldErrors().stream()
|
||||||
|
.map(fieldError -> fieldError.getField() + ": " + fieldError.getDefaultMessage())
|
||||||
|
.collect(Collectors.joining("; "));
|
||||||
|
logger.warn("参数校验失败: {}", errorMessage, ex);
|
||||||
|
return new ResponseEntity<>(ApiResponse.fail(ResultCode.VALIDATION_ERROR, errorMessage), HttpStatus.BAD_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 参数缺失异常
|
||||||
|
@ExceptionHandler(MissingServletRequestParameterException.class)
|
||||||
|
public ResponseEntity<ApiResponse<Object>> handleMissingServletRequestParameterException(MissingServletRequestParameterException ex) {
|
||||||
|
String message = "请求参数 '" + ex.getParameterName() + "' 不能为空";
|
||||||
|
logger.warn(message, ex);
|
||||||
|
return new ResponseEntity<>(ApiResponse.fail(ResultCode.BAD_REQUEST, message), HttpStatus.BAD_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spring Security 用户认证异常
|
||||||
|
@ExceptionHandler(AuthenticationException.class)
|
||||||
|
public ResponseEntity<ApiResponse<Object>> handleAuthenticationException(AuthenticationException ex) {
|
||||||
|
logger.warn("认证失败: {}", ex.getMessage(), ex);
|
||||||
|
|
||||||
|
if (ex instanceof BadCredentialsException) {
|
||||||
|
|
||||||
|
return new ResponseEntity<>(ApiResponse.fail(ResultCode.USER_INVALID_CREDENTIALS), HttpStatus.UNAUTHORIZED);
|
||||||
|
} else if (ex instanceof UsernameNotFoundException) {
|
||||||
|
|
||||||
|
return new ResponseEntity<>(ApiResponse.fail(ResultCode.USER_INVALID_CREDENTIALS, "用户名或密码错误(用户不存在)"), HttpStatus.UNAUTHORIZED);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ResponseEntity<>(ApiResponse.fail(ResultCode.UNAUTHORIZED, ex.getMessage()), HttpStatus.UNAUTHORIZED);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spring Security 权限异常
|
||||||
|
// @ExceptionHandler(AccessDeniedException.class)
|
||||||
|
// public ResponseEntity<ApiResponse<Object>> handleAccessDeniedException(AccessDeniedException ex) {
|
||||||
|
// logger.warn("权限不足: {}", ex.getMessage(), ex);
|
||||||
|
// return new ResponseEntity<>(ApiResponse.fail(ResultCode.FORBIDDEN), HttpStatus.FORBIDDEN);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// 其他异常
|
||||||
|
@ExceptionHandler(Exception.class)
|
||||||
|
public ResponseEntity<ApiResponse<Object>> handleAllUncaughtException(Exception ex) {
|
||||||
|
logger.error("发生未捕获的服务器内部错误!", ex);
|
||||||
|
|
||||||
|
return new ResponseEntity<>(ApiResponse.fail(ResultCode.INTERNAL_SERVER_ERROR), HttpStatus.INTERNAL_SERVER_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
private HttpStatus determineHttpStatusFromResultCode(ResultCode resultCode) {
|
||||||
|
if (resultCode == null) return HttpStatus.INTERNAL_SERVER_ERROR;
|
||||||
|
|
||||||
|
switch (resultCode) {
|
||||||
|
case SUCCESS:
|
||||||
|
return HttpStatus.OK;
|
||||||
|
case UNAUTHORIZED:
|
||||||
|
case USER_INVALID_CREDENTIALS:
|
||||||
|
case USER_TOKEN_INVALID:
|
||||||
|
case USER_TOKEN_EXPIRED:
|
||||||
|
return HttpStatus.UNAUTHORIZED;
|
||||||
|
case FORBIDDEN:
|
||||||
|
return HttpStatus.FORBIDDEN;
|
||||||
|
case NOT_FOUND:
|
||||||
|
case USER_ACCOUNT_NOT_FOUND:
|
||||||
|
case USER_PROFILE_NOT_FOUND:
|
||||||
|
return HttpStatus.NOT_FOUND;
|
||||||
|
case CONFLICT:
|
||||||
|
case USER_EMAIL_ALREADY_EXISTS:
|
||||||
|
return HttpStatus.CONFLICT;
|
||||||
|
case BAD_REQUEST:
|
||||||
|
case VALIDATION_ERROR:
|
||||||
|
case USER_PASSWORD_TOO_SHORT:
|
||||||
|
return HttpStatus.BAD_REQUEST;
|
||||||
|
default:
|
||||||
|
if (resultCode.getCode() >= 2000 && resultCode.getCode() < 3000) {
|
||||||
|
return HttpStatus.INTERNAL_SERVER_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
return HttpStatus.BAD_REQUEST;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user