feat: HTTP状态码 + 业务码
This commit is contained in:
@@ -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)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,8 +71,6 @@ public class SseController {
|
||||
lastEventId, userId, connectionId);
|
||||
}
|
||||
|
||||
log.info("[SSE] 连接建立成功, userId={}, connectionId={}, onlineConnections={}",
|
||||
userId, connectionId, connectionManager.getOnlineCount());
|
||||
return emitter;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user