diff --git a/src/main/java/com/webgame/webgamebackend/common/exception/BusinessException.java b/src/main/java/com/webgame/webgamebackend/common/exception/BusinessException.java index b301bec..92afef3 100644 --- a/src/main/java/com/webgame/webgamebackend/common/exception/BusinessException.java +++ b/src/main/java/com/webgame/webgamebackend/common/exception/BusinessException.java @@ -3,26 +3,31 @@ package com.webgame.webgamebackend.common.exception; import lombok.Getter; /** - * 业务异常,Service 层抛出,由全局异常处理器转换为 ApiResponse + * 业务异常,Service 层抛出,由全局异常处理器转换为 ResponseEntity<ApiResponse> + *
+ * 同时携带业务错误码和对应的 HTTP 状态码,两者均从 ErrorCode 枚举自动派生。 */ @Getter public class BusinessException extends RuntimeException { private final int code; + private final int httpStatus; /** - * 使用 ErrorCode 枚举创建异常,消息使用枚举默认值 + * 使用 ErrorCode 枚举创建异常,消息和 HTTP 状态码从枚举默认值派生 */ public BusinessException(ErrorCode errorCode) { super(errorCode.getDefaultMessage()); this.code = errorCode.getCode(); + this.httpStatus = errorCode.getHttpStatus(); } /** - * 使用 ErrorCode 枚举 + 自定义消息创建异常 + * 使用 ErrorCode 枚举 + 自定义消息创建异常,HTTP 状态码从枚举派生 */ public BusinessException(ErrorCode errorCode, String message) { super(message); this.code = errorCode.getCode(); + this.httpStatus = errorCode.getHttpStatus(); } } diff --git a/src/main/java/com/webgame/webgamebackend/common/exception/ErrorCode.java b/src/main/java/com/webgame/webgamebackend/common/exception/ErrorCode.java index 706c48b..f8f8dc6 100644 --- a/src/main/java/com/webgame/webgamebackend/common/exception/ErrorCode.java +++ b/src/main/java/com/webgame/webgamebackend/common/exception/ErrorCode.java @@ -3,47 +3,52 @@ package com.webgame.webgamebackend.common.exception; import lombok.Getter; /** - * 业务错误码枚举,统一管理所有错误码和默认消息 + * 业务错误码枚举,统一管理所有错误码、默认消息和对应的 HTTP 状态码 + *
+ * HTTP 状态码表达错误大类(协议层),code 表达具体业务错误(应用层),
+ * 两者分工协作形成双层错误表达模式。
*/
@Getter
public enum ErrorCode {
// ========== 通用 ==========
- SUCCESS(0, "成功"),
- BAD_REQUEST(400, "参数校验失败"),
- INTERNAL_ERROR(5000, "服务器内部错误"),
+ SUCCESS(0, "成功", 200),
+ BAD_REQUEST(400, "参数校验失败", 400),
+ INTERNAL_ERROR(5000, "服务器内部错误", 500),
// ========== 账号模块 ==========
- USERNAME_EXISTS(1001, "用户名已存在"),
- BAD_CREDENTIALS(1002, "用户名或密码错误"),
- REFRESH_TOKEN_INVALID(1003, "刷新令牌无效或已过期"),
- REFRESH_TOKEN_MISSING(1004, "缺少刷新令牌"),
+ USERNAME_EXISTS(1001, "用户名已存在", 409),
+ BAD_CREDENTIALS(1002, "用户名或密码错误", 401),
+ REFRESH_TOKEN_INVALID(1003, "刷新令牌无效或已过期", 401),
+ REFRESH_TOKEN_MISSING(1004, "缺少刷新令牌", 400),
// ========== 用户模块 ==========
- USER_NOT_FOUND(2001, "用户不存在"),
- AVATAR_UPLOAD_FAILED(2002, "头像上传失败"),
- AVATAR_INVALID_TYPE(2003, "头像格式不支持,仅支持 JPG、PNG、WebP、GIF"),
- AVATAR_TOO_LARGE(2004, "头像文件大小不能超过 2MB"),
+ USER_NOT_FOUND(2001, "用户不存在", 404),
+ AVATAR_UPLOAD_FAILED(2002, "头像上传失败", 500),
+ AVATAR_INVALID_TYPE(2003, "头像格式不支持,仅支持 JPG、PNG、WebP、GIF", 400),
+ AVATAR_TOO_LARGE(2004, "头像文件大小不能超过 2MB", 400),
// ========== 游戏模块 ==========
- GAME_NOT_FOUND(3001, "游戏不存在或已下架"),
- ROOM_NOT_FOUND(3002, "房间不存在"),
- ROOM_FULL(3003, "房间已满"),
- ROOM_PASSWORD_ERROR(3004, "房间密码错误"),
- ROOM_ALREADY_STARTED(3005, "游戏已开始,无法加入"),
- NOT_ROOM_OWNER(3006, "非房主无权操作"),
- PLAYER_NOT_IN_ROOM(3007, "玩家不在该房间"),
- NOT_ALL_READY(3008, "有玩家尚未准备"),
- PLAYER_COUNT_INSUFFICIENT(3009, "人数不足,无法开始"),
- BALANCE_INSUFFICIENT(3010, "余额不足"),
- ALREADY_IN_ROOM(3011, "已在房间中"),
+ GAME_NOT_FOUND(3001, "游戏不存在或已下架", 404),
+ ROOM_NOT_FOUND(3002, "房间不存在", 404),
+ ROOM_FULL(3003, "房间已满", 409),
+ ROOM_PASSWORD_ERROR(3004, "房间密码错误", 401),
+ ROOM_ALREADY_STARTED(3005, "游戏已开始,无法加入", 409),
+ NOT_ROOM_OWNER(3006, "非房主无权操作", 403),
+ PLAYER_NOT_IN_ROOM(3007, "玩家不在该房间", 404),
+ NOT_ALL_READY(3008, "有玩家尚未准备", 409),
+ PLAYER_COUNT_INSUFFICIENT(3009, "人数不足,无法开始", 409),
+ BALANCE_INSUFFICIENT(3010, "余额不足", 409),
+ ALREADY_IN_ROOM(3011, "已在房间中", 409),
;
private final int code;
private final String defaultMessage;
+ private final int httpStatus;
- ErrorCode(int code, String defaultMessage) {
+ ErrorCode(int code, String defaultMessage, int httpStatus) {
this.code = code;
this.defaultMessage = defaultMessage;
+ this.httpStatus = httpStatus;
}
}
diff --git a/src/main/java/com/webgame/webgamebackend/common/handler/GlobalExceptionHandler.java b/src/main/java/com/webgame/webgamebackend/common/handler/GlobalExceptionHandler.java
index 70eaedd..7f93ffe 100644
--- a/src/main/java/com/webgame/webgamebackend/common/handler/GlobalExceptionHandler.java
+++ b/src/main/java/com/webgame/webgamebackend/common/handler/GlobalExceptionHandler.java
@@ -8,6 +8,8 @@ import com.webgame.webgamebackend.common.dto.ApiResponse;
import com.webgame.webgamebackend.common.exception.BusinessException;
import com.webgame.webgamebackend.common.exception.ErrorCode;
import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
@@ -16,81 +18,128 @@ import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.async.AsyncRequestNotUsableException;
import org.springframework.web.context.request.async.AsyncRequestTimeoutException;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
+import org.springframework.web.multipart.MaxUploadSizeExceededException;
import org.springframework.web.servlet.resource.NoResourceFoundException;
+/**
+ * 全局异常处理器,返回 ResponseEntity<ApiResponse> 实现双层错误表达:
+ * HTTP 状态码表达错误大类 + ApiResponse.code 表达具体业务错误码
+ */
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
+ // ==================== SSE / 长连接异常(不写响应体) ====================
+
@ExceptionHandler(AsyncRequestTimeoutException.class)
public void handleAsyncTimeout(AsyncRequestTimeoutException ex) {
- // SSE 超时在长连接场景下属于正常情况
+ // SSE 超时在长连接场景下属于正常情况,不写响应体
}
@ExceptionHandler(SaTokenContextException.class)
public void handleSaTokenContext(SaTokenContextException ex) {
log.warn("Sa-Token 上下文不可用: {}", ex.getMessage());
+ // 长连接场景,不写响应体
}
@ExceptionHandler(AsyncRequestNotUsableException.class)
public void handleAsyncRequestNotUsable(AsyncRequestNotUsableException ex) {
- // SSE 响应已被客户端关闭
+ // SSE 响应已被客户端关闭,不写响应体
}
+ // ==================== 业务异常 ====================
+
+ /**
+ * 业务异常 — HTTP 状态码从 ErrorCode 枚举自动派生
+ */
@ExceptionHandler(BusinessException.class)
- public ApiResponse