feat: HTTP状态码 + 业务码

This commit is contained in:
2026-06-24 12:00:17 +08:00
parent df6c7f6b10
commit 5d9f5b8ab7
2 changed files with 39 additions and 18 deletions

View File

@@ -4,11 +4,14 @@ import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.exception.NotPermissionException;
import cn.dev33.satoken.exception.NotRoleException;
import cn.dev33.satoken.exception.SaTokenContextException;
import com.alibaba.fastjson2.JSON;
import com.webgame.webgamebackend.common.dto.ApiResponse;
import com.webgame.webgamebackend.common.exception.BusinessException;
import com.webgame.webgamebackend.common.exception.ErrorCode;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
@@ -21,9 +24,14 @@ import org.springframework.web.method.annotation.MethodArgumentTypeMismatchExcep
import org.springframework.web.multipart.MaxUploadSizeExceededException;
import org.springframework.web.servlet.resource.NoResourceFoundException;
import java.io.IOException;
/**
* 全局异常处理器,返回 ResponseEntity<ApiResponse> 实现双层错误表达:
* 全局异常处理器,双层错误表达:
* HTTP 状态码表达错误大类 + ApiResponse.code 表达具体业务错误码
* <p>
* 业务异常(仅来自 REST JSON 请求)返回 ResponseEntity经过 Spring 内容协商。
* 框架级异常(可能来自 SSE/浏览器等任意请求类型)直接写 HttpServletResponse 绕过内容协商。
*/
@Slf4j
@RestControllerAdvice
@@ -47,7 +55,7 @@ public class GlobalExceptionHandler {
// SSE 响应已被客户端关闭,不写响应体
}
// ==================== 业务异常 ====================
// ==================== 业务异常REST JSON 请求) ====================
/**
* 业务异常 — HTTP 状态码从 ErrorCode 枚举自动派生
@@ -115,31 +123,46 @@ public class GlobalExceptionHandler {
.body(ApiResponse.fail(403, "权限不足"));
}
// ==================== 框架级异常 ====================
// ==================== 框架级异常(直接写响应,绕过内容协商) ====================
// 这类异常可能来自任意请求类型SSE Accept: text/event-stream、
// 浏览器 Accept: text/html 等),若走 ResponseEntity 会因 Accept 头
// 与 JSON 响应体不匹配而抛出 HttpMediaTypeNotAcceptableException。
@ExceptionHandler(NoResourceFoundException.class)
public ResponseEntity<ApiResponse<Void>> handleNoResourceFound(NoResourceFoundException ex) {
public void handleNoResourceFound(NoResourceFoundException ex, HttpServletResponse response) throws IOException {
log.warn("资源未找到: {}", ex.getMessage());
return ResponseEntity
.status(HttpStatus.NOT_FOUND)
.body(ApiResponse.fail(404, "资源未找到"));
writeJsonError(response, HttpStatus.NOT_FOUND, 404, "资源未找到");
}
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public ResponseEntity<ApiResponse<Void>> handleMethodNotSupported(HttpRequestMethodNotSupportedException ex) {
public void handleMethodNotSupported(HttpRequestMethodNotSupportedException ex,
HttpServletResponse response) throws IOException {
log.warn("请求方法不支持: {}", ex.getMessage());
return ResponseEntity
.status(HttpStatus.METHOD_NOT_ALLOWED)
.body(ApiResponse.fail(405, "请求方法不支持"));
writeJsonError(response, HttpStatus.METHOD_NOT_ALLOWED, 405, "请求方法不支持");
}
// ==================== 兜底 → HTTP 500 ====================
// ==================== 兜底 → HTTP 500(直接写响应,绕过内容协商) ====================
@ExceptionHandler(Exception.class)
public ResponseEntity<ApiResponse<Void>> handleException(Exception ex) {
public void handleException(Exception ex, HttpServletResponse response) throws IOException {
log.error("服务器内部错误", ex);
return ResponseEntity
.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(ApiResponse.fail(ErrorCode.INTERNAL_ERROR.getCode(), ErrorCode.INTERNAL_ERROR.getDefaultMessage()));
writeJsonError(response, HttpStatus.INTERNAL_SERVER_ERROR,
ErrorCode.INTERNAL_ERROR.getCode(), ErrorCode.INTERNAL_ERROR.getDefaultMessage());
}
// ==================== 工具方法 ====================
/**
* 直接向 HttpServletResponse 写入 JSON 格式的错误响应
* <p>
* 不依赖 Spring 的 MessageConverter 和内容协商机制,
* 确保 SSE/浏览器等非 JSON Accept 头的请求也能收到可读的错误信息。
*/
private static void writeJsonError(HttpServletResponse response, HttpStatus status,
int code, String message) throws IOException {
response.setStatus(status.value());
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setCharacterEncoding("UTF-8");
response.getWriter().write(JSON.toJSONString(ApiResponse.fail(code, message)));
}
}

View File

@@ -71,8 +71,6 @@ public class SseController {
lastEventId, userId, connectionId);
}
log.info("[SSE] 连接建立成功, userId={}, connectionId={}, onlineConnections={}",
userId, connectionId, connectionManager.getOnlineCount());
return emitter;
}