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.NotPermissionException;
|
||||||
import cn.dev33.satoken.exception.NotRoleException;
|
import cn.dev33.satoken.exception.NotRoleException;
|
||||||
import cn.dev33.satoken.exception.SaTokenContextException;
|
import cn.dev33.satoken.exception.SaTokenContextException;
|
||||||
|
import com.alibaba.fastjson2.JSON;
|
||||||
import com.webgame.webgamebackend.common.dto.ApiResponse;
|
import com.webgame.webgamebackend.common.dto.ApiResponse;
|
||||||
import com.webgame.webgamebackend.common.exception.BusinessException;
|
import com.webgame.webgamebackend.common.exception.BusinessException;
|
||||||
import com.webgame.webgamebackend.common.exception.ErrorCode;
|
import com.webgame.webgamebackend.common.exception.ErrorCode;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||||
import org.springframework.web.HttpRequestMethodNotSupportedException;
|
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.multipart.MaxUploadSizeExceededException;
|
||||||
import org.springframework.web.servlet.resource.NoResourceFoundException;
|
import org.springframework.web.servlet.resource.NoResourceFoundException;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 全局异常处理器,返回 ResponseEntity<ApiResponse> 实现双层错误表达:
|
* 全局异常处理器,双层错误表达:
|
||||||
* HTTP 状态码表达错误大类 + ApiResponse.code 表达具体业务错误码
|
* HTTP 状态码表达错误大类 + ApiResponse.code 表达具体业务错误码
|
||||||
|
* <p>
|
||||||
|
* 业务异常(仅来自 REST JSON 请求)返回 ResponseEntity,经过 Spring 内容协商。
|
||||||
|
* 框架级异常(可能来自 SSE/浏览器等任意请求类型)直接写 HttpServletResponse 绕过内容协商。
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@RestControllerAdvice
|
@RestControllerAdvice
|
||||||
@@ -47,7 +55,7 @@ public class GlobalExceptionHandler {
|
|||||||
// SSE 响应已被客户端关闭,不写响应体
|
// SSE 响应已被客户端关闭,不写响应体
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== 业务异常 ====================
|
// ==================== 业务异常(REST JSON 请求) ====================
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 业务异常 — HTTP 状态码从 ErrorCode 枚举自动派生
|
* 业务异常 — HTTP 状态码从 ErrorCode 枚举自动派生
|
||||||
@@ -115,31 +123,46 @@ public class GlobalExceptionHandler {
|
|||||||
.body(ApiResponse.fail(403, "权限不足"));
|
.body(ApiResponse.fail(403, "权限不足"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== 框架级异常 ====================
|
// ==================== 框架级异常(直接写响应,绕过内容协商) ====================
|
||||||
|
// 这类异常可能来自任意请求类型(SSE Accept: text/event-stream、
|
||||||
|
// 浏览器 Accept: text/html 等),若走 ResponseEntity 会因 Accept 头
|
||||||
|
// 与 JSON 响应体不匹配而抛出 HttpMediaTypeNotAcceptableException。
|
||||||
|
|
||||||
@ExceptionHandler(NoResourceFoundException.class)
|
@ExceptionHandler(NoResourceFoundException.class)
|
||||||
public ResponseEntity<ApiResponse<Void>> handleNoResourceFound(NoResourceFoundException ex) {
|
public void handleNoResourceFound(NoResourceFoundException ex, HttpServletResponse response) throws IOException {
|
||||||
log.warn("资源未找到: {}", ex.getMessage());
|
log.warn("资源未找到: {}", ex.getMessage());
|
||||||
return ResponseEntity
|
writeJsonError(response, HttpStatus.NOT_FOUND, 404, "资源未找到");
|
||||||
.status(HttpStatus.NOT_FOUND)
|
|
||||||
.body(ApiResponse.fail(404, "资源未找到"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
|
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
|
||||||
public ResponseEntity<ApiResponse<Void>> handleMethodNotSupported(HttpRequestMethodNotSupportedException ex) {
|
public void handleMethodNotSupported(HttpRequestMethodNotSupportedException ex,
|
||||||
|
HttpServletResponse response) throws IOException {
|
||||||
log.warn("请求方法不支持: {}", ex.getMessage());
|
log.warn("请求方法不支持: {}", ex.getMessage());
|
||||||
return ResponseEntity
|
writeJsonError(response, HttpStatus.METHOD_NOT_ALLOWED, 405, "请求方法不支持");
|
||||||
.status(HttpStatus.METHOD_NOT_ALLOWED)
|
|
||||||
.body(ApiResponse.fail(405, "请求方法不支持"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== 兜底 → HTTP 500 ====================
|
// ==================== 兜底 → HTTP 500(直接写响应,绕过内容协商) ====================
|
||||||
|
|
||||||
@ExceptionHandler(Exception.class)
|
@ExceptionHandler(Exception.class)
|
||||||
public ResponseEntity<ApiResponse<Void>> handleException(Exception ex) {
|
public void handleException(Exception ex, HttpServletResponse response) throws IOException {
|
||||||
log.error("服务器内部错误", ex);
|
log.error("服务器内部错误", ex);
|
||||||
return ResponseEntity
|
writeJsonError(response, HttpStatus.INTERNAL_SERVER_ERROR,
|
||||||
.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
ErrorCode.INTERNAL_ERROR.getCode(), ErrorCode.INTERNAL_ERROR.getDefaultMessage());
|
||||||
.body(ApiResponse.fail(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);
|
lastEventId, userId, connectionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info("[SSE] 连接建立成功, userId={}, connectionId={}, onlineConnections={}",
|
|
||||||
userId, connectionId, connectionManager.getOnlineCount());
|
|
||||||
return emitter;
|
return emitter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user