- 添加 GlobalExceptionHandler 全局异常处理 - 添加 ApiController REST API 控制器 - 更新 WebConfig 跨域配置和 ProductRepository 查询方法 - 新增 monitor/product-detail/profile JSP 视图页面 - 添加 FlashSaleServiceTest 秒杀服务单元测试 - 更新 application.yml 配置
414 lines
15 KiB
Java
414 lines
15 KiB
Java
package com.org.flashsalesystem.config;
|
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
import org.springframework.http.HttpStatus;
|
|
import org.springframework.http.ResponseEntity;
|
|
import org.springframework.validation.BindException;
|
|
import org.springframework.validation.ObjectError;
|
|
import org.springframework.web.bind.MethodArgumentNotValidException;
|
|
import org.springframework.web.bind.annotation.ExceptionHandler;
|
|
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
|
|
|
import javax.servlet.http.HttpServletRequest;
|
|
import javax.validation.ConstraintViolation;
|
|
import javax.validation.ConstraintViolationException;
|
|
import java.time.LocalDateTime;
|
|
import java.util.HashMap;
|
|
import java.util.Map;
|
|
import java.util.Set;
|
|
import java.util.concurrent.TimeoutException;
|
|
|
|
/**
|
|
* 全局异常处理器
|
|
* 统一处理应用中的各种异常,提供一致的错误响应格式
|
|
*/
|
|
@RestControllerAdvice
|
|
@Slf4j
|
|
public class GlobalExceptionHandler {
|
|
|
|
/**
|
|
* 业务异常处理
|
|
*/
|
|
@ExceptionHandler(BusinessException.class)
|
|
public ResponseEntity<ErrorResponse> handleBusinessException(BusinessException e, HttpServletRequest request) {
|
|
log.warn("业务异常: {} - {}", e.getErrorCode(), e.getMessage(), e);
|
|
|
|
ErrorResponse errorResponse = ErrorResponse.builder()
|
|
.timestamp(LocalDateTime.now())
|
|
.status(HttpStatus.BAD_REQUEST.value())
|
|
.error("Business Error")
|
|
.message(e.getMessage())
|
|
.errorCode(e.getErrorCode())
|
|
.path(request.getRequestURI())
|
|
.build();
|
|
|
|
return ResponseEntity.badRequest().body(errorResponse);
|
|
}
|
|
|
|
/**
|
|
* 秒杀相关异常处理
|
|
*/
|
|
@ExceptionHandler(FlashSaleException.class)
|
|
public ResponseEntity<ErrorResponse> handleFlashSaleException(FlashSaleException e, HttpServletRequest request) {
|
|
log.warn("秒杀异常: {} - {}", e.getErrorCode(), e.getMessage(), e);
|
|
|
|
ErrorResponse errorResponse = ErrorResponse.builder()
|
|
.timestamp(LocalDateTime.now())
|
|
.status(HttpStatus.BAD_REQUEST.value())
|
|
.error("Flash Sale Error")
|
|
.message(e.getMessage())
|
|
.errorCode(e.getErrorCode())
|
|
.path(request.getRequestURI())
|
|
.build();
|
|
|
|
return ResponseEntity.badRequest().body(errorResponse);
|
|
}
|
|
|
|
/**
|
|
* 限流异常处理
|
|
*/
|
|
@ExceptionHandler(RateLimitException.class)
|
|
public ResponseEntity<ErrorResponse> handleRateLimitException(RateLimitException e, HttpServletRequest request) {
|
|
log.warn("限流异常: {}", e.getMessage(), e);
|
|
|
|
ErrorResponse errorResponse = ErrorResponse.builder()
|
|
.timestamp(LocalDateTime.now())
|
|
.status(HttpStatus.TOO_MANY_REQUESTS.value())
|
|
.error("Rate Limit Exceeded")
|
|
.message(e.getMessage())
|
|
.errorCode("RATE_LIMIT_EXCEEDED")
|
|
.path(request.getRequestURI())
|
|
.build();
|
|
|
|
return ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS).body(errorResponse);
|
|
}
|
|
|
|
/**
|
|
* 数据验证异常处理
|
|
*/
|
|
@ExceptionHandler(MethodArgumentNotValidException.class)
|
|
public ResponseEntity<ErrorResponse> handleValidationException(MethodArgumentNotValidException e, HttpServletRequest request) {
|
|
log.warn("数据验证异常: {}", e.getMessage());
|
|
|
|
StringBuilder errorMessages = new StringBuilder();
|
|
for (ObjectError error : e.getBindingResult().getAllErrors()) {
|
|
if (errorMessages.length() > 0) {
|
|
errorMessages.append("; ");
|
|
}
|
|
errorMessages.append(error.getDefaultMessage());
|
|
}
|
|
|
|
ErrorResponse errorResponse = ErrorResponse.builder()
|
|
.timestamp(LocalDateTime.now())
|
|
.status(HttpStatus.BAD_REQUEST.value())
|
|
.error("Validation Error")
|
|
.message(errorMessages.toString())
|
|
.errorCode("VALIDATION_ERROR")
|
|
.path(request.getRequestURI())
|
|
.build();
|
|
|
|
return ResponseEntity.badRequest().body(errorResponse);
|
|
}
|
|
|
|
/**
|
|
* 参数绑定异常处理
|
|
*/
|
|
@ExceptionHandler(BindException.class)
|
|
public ResponseEntity<ErrorResponse> handleBindException(BindException e, HttpServletRequest request) {
|
|
log.warn("参数绑定异常: {}", e.getMessage());
|
|
|
|
StringBuilder errorMessages = new StringBuilder();
|
|
for (ObjectError error : e.getBindingResult().getAllErrors()) {
|
|
if (errorMessages.length() > 0) {
|
|
errorMessages.append("; ");
|
|
}
|
|
errorMessages.append(error.getDefaultMessage());
|
|
}
|
|
|
|
ErrorResponse errorResponse = ErrorResponse.builder()
|
|
.timestamp(LocalDateTime.now())
|
|
.status(HttpStatus.BAD_REQUEST.value())
|
|
.error("Parameter Binding Error")
|
|
.message(errorMessages.toString())
|
|
.errorCode("BINDING_ERROR")
|
|
.path(request.getRequestURI())
|
|
.build();
|
|
|
|
return ResponseEntity.badRequest().body(errorResponse);
|
|
}
|
|
|
|
/**
|
|
* 约束违规异常处理
|
|
*/
|
|
@ExceptionHandler(ConstraintViolationException.class)
|
|
public ResponseEntity<ErrorResponse> handleConstraintViolationException(ConstraintViolationException e, HttpServletRequest request) {
|
|
log.warn("约束违规异常: {}", e.getMessage());
|
|
|
|
StringBuilder errorMessages = new StringBuilder();
|
|
Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
|
|
for (ConstraintViolation<?> violation : violations) {
|
|
if (errorMessages.length() > 0) {
|
|
errorMessages.append("; ");
|
|
}
|
|
errorMessages.append(violation.getMessage());
|
|
}
|
|
|
|
ErrorResponse errorResponse = ErrorResponse.builder()
|
|
.timestamp(LocalDateTime.now())
|
|
.status(HttpStatus.BAD_REQUEST.value())
|
|
.error("Constraint Violation")
|
|
.message(errorMessages.toString())
|
|
.errorCode("CONSTRAINT_VIOLATION")
|
|
.path(request.getRequestURI())
|
|
.build();
|
|
|
|
return ResponseEntity.badRequest().body(errorResponse);
|
|
}
|
|
|
|
/**
|
|
* 超时异常处理
|
|
*/
|
|
@ExceptionHandler(TimeoutException.class)
|
|
public ResponseEntity<ErrorResponse> handleTimeoutException(TimeoutException e, HttpServletRequest request) {
|
|
log.error("超时异常: {}", e.getMessage(), e);
|
|
|
|
ErrorResponse errorResponse = ErrorResponse.builder()
|
|
.timestamp(LocalDateTime.now())
|
|
.status(HttpStatus.REQUEST_TIMEOUT.value())
|
|
.error("Timeout Error")
|
|
.message("请求超时,请稍后重试")
|
|
.errorCode("TIMEOUT_ERROR")
|
|
.path(request.getRequestURI())
|
|
.build();
|
|
|
|
return ResponseEntity.status(HttpStatus.REQUEST_TIMEOUT).body(errorResponse);
|
|
}
|
|
|
|
/**
|
|
* 非法参数异常处理
|
|
*/
|
|
@ExceptionHandler(IllegalArgumentException.class)
|
|
public ResponseEntity<ErrorResponse> handleIllegalArgumentException(IllegalArgumentException e, HttpServletRequest request) {
|
|
log.warn("非法参数异常: {}", e.getMessage(), e);
|
|
|
|
ErrorResponse errorResponse = ErrorResponse.builder()
|
|
.timestamp(LocalDateTime.now())
|
|
.status(HttpStatus.BAD_REQUEST.value())
|
|
.error("Illegal Argument")
|
|
.message(e.getMessage())
|
|
.errorCode("ILLEGAL_ARGUMENT")
|
|
.path(request.getRequestURI())
|
|
.build();
|
|
|
|
return ResponseEntity.badRequest().body(errorResponse);
|
|
}
|
|
|
|
/**
|
|
* 空指针异常处理
|
|
*/
|
|
@ExceptionHandler(NullPointerException.class)
|
|
public ResponseEntity<ErrorResponse> handleNullPointerException(NullPointerException e, HttpServletRequest request) {
|
|
log.error("空指针异常: {}", e.getMessage(), e);
|
|
|
|
ErrorResponse errorResponse = ErrorResponse.builder()
|
|
.timestamp(LocalDateTime.now())
|
|
.status(HttpStatus.INTERNAL_SERVER_ERROR.value())
|
|
.error("Null Pointer Error")
|
|
.message("系统内部错误,请联系管理员")
|
|
.errorCode("NULL_POINTER_ERROR")
|
|
.path(request.getRequestURI())
|
|
.build();
|
|
|
|
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResponse);
|
|
}
|
|
|
|
/**
|
|
* 运行时异常处理
|
|
*/
|
|
@ExceptionHandler(RuntimeException.class)
|
|
public ResponseEntity<ErrorResponse> handleRuntimeException(RuntimeException e, HttpServletRequest request) {
|
|
log.error("运行时异常: {}", e.getMessage(), e);
|
|
|
|
// 对于已知的业务异常,使用友好的错误信息
|
|
String message = e.getMessage();
|
|
if (message == null || message.trim().isEmpty()) {
|
|
message = "系统繁忙,请稍后重试";
|
|
}
|
|
|
|
ErrorResponse errorResponse = ErrorResponse.builder()
|
|
.timestamp(LocalDateTime.now())
|
|
.status(HttpStatus.INTERNAL_SERVER_ERROR.value())
|
|
.error("Runtime Error")
|
|
.message(message)
|
|
.errorCode("RUNTIME_ERROR")
|
|
.path(request.getRequestURI())
|
|
.build();
|
|
|
|
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResponse);
|
|
}
|
|
|
|
/**
|
|
* 通用异常处理
|
|
*/
|
|
@ExceptionHandler(Exception.class)
|
|
public ResponseEntity<ErrorResponse> handleGenericException(Exception e, HttpServletRequest request) {
|
|
log.error("系统异常: {}", e.getMessage(), e);
|
|
|
|
ErrorResponse errorResponse = ErrorResponse.builder()
|
|
.timestamp(LocalDateTime.now())
|
|
.status(HttpStatus.INTERNAL_SERVER_ERROR.value())
|
|
.error("System Error")
|
|
.message("系统异常,请联系管理员")
|
|
.errorCode("SYSTEM_ERROR")
|
|
.path(request.getRequestURI())
|
|
.build();
|
|
|
|
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResponse);
|
|
}
|
|
|
|
/**
|
|
* 统一错误响应格式
|
|
*/
|
|
public static class ErrorResponse {
|
|
private LocalDateTime timestamp;
|
|
private int status;
|
|
private String error;
|
|
private String message;
|
|
private String errorCode;
|
|
private String path;
|
|
private Map<String, Object> details;
|
|
|
|
public ErrorResponse() {
|
|
this.details = new HashMap<>();
|
|
}
|
|
|
|
public static ErrorResponseBuilder builder() {
|
|
return new ErrorResponseBuilder();
|
|
}
|
|
|
|
// Getters and Setters
|
|
public LocalDateTime getTimestamp() { return timestamp; }
|
|
public void setTimestamp(LocalDateTime timestamp) { this.timestamp = timestamp; }
|
|
|
|
public int getStatus() { return status; }
|
|
public void setStatus(int status) { this.status = status; }
|
|
|
|
public String getError() { return error; }
|
|
public void setError(String error) { this.error = error; }
|
|
|
|
public String getMessage() { return message; }
|
|
public void setMessage(String message) { this.message = message; }
|
|
|
|
public String getErrorCode() { return errorCode; }
|
|
public void setErrorCode(String errorCode) { this.errorCode = errorCode; }
|
|
|
|
public String getPath() { return path; }
|
|
public void setPath(String path) { this.path = path; }
|
|
|
|
public Map<String, Object> getDetails() { return details; }
|
|
public void setDetails(Map<String, Object> details) { this.details = details; }
|
|
|
|
public static class ErrorResponseBuilder {
|
|
private ErrorResponse errorResponse = new ErrorResponse();
|
|
|
|
public ErrorResponseBuilder timestamp(LocalDateTime timestamp) {
|
|
errorResponse.setTimestamp(timestamp);
|
|
return this;
|
|
}
|
|
|
|
public ErrorResponseBuilder status(int status) {
|
|
errorResponse.setStatus(status);
|
|
return this;
|
|
}
|
|
|
|
public ErrorResponseBuilder error(String error) {
|
|
errorResponse.setError(error);
|
|
return this;
|
|
}
|
|
|
|
public ErrorResponseBuilder message(String message) {
|
|
errorResponse.setMessage(message);
|
|
return this;
|
|
}
|
|
|
|
public ErrorResponseBuilder errorCode(String errorCode) {
|
|
errorResponse.setErrorCode(errorCode);
|
|
return this;
|
|
}
|
|
|
|
public ErrorResponseBuilder path(String path) {
|
|
errorResponse.setPath(path);
|
|
return this;
|
|
}
|
|
|
|
public ErrorResponseBuilder detail(String key, Object value) {
|
|
errorResponse.getDetails().put(key, value);
|
|
return this;
|
|
}
|
|
|
|
public ErrorResponse build() {
|
|
return errorResponse;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 业务异常类
|
|
*/
|
|
public static class BusinessException extends RuntimeException {
|
|
private final String errorCode;
|
|
|
|
public BusinessException(String message) {
|
|
super(message);
|
|
this.errorCode = "BUSINESS_ERROR";
|
|
}
|
|
|
|
public BusinessException(String errorCode, String message) {
|
|
super(message);
|
|
this.errorCode = errorCode;
|
|
}
|
|
|
|
public BusinessException(String message, Throwable cause) {
|
|
super(message, cause);
|
|
this.errorCode = "BUSINESS_ERROR";
|
|
}
|
|
|
|
public String getErrorCode() {
|
|
return errorCode;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 秒杀异常类
|
|
*/
|
|
public static class FlashSaleException extends RuntimeException {
|
|
private final String errorCode;
|
|
|
|
public FlashSaleException(String message) {
|
|
super(message);
|
|
this.errorCode = "FLASH_SALE_ERROR";
|
|
}
|
|
|
|
public FlashSaleException(String errorCode, String message) {
|
|
super(message);
|
|
this.errorCode = errorCode;
|
|
}
|
|
|
|
public String getErrorCode() {
|
|
return errorCode;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 限流异常类
|
|
*/
|
|
public static class RateLimitException extends RuntimeException {
|
|
public RateLimitException(String message) {
|
|
super(message);
|
|
}
|
|
|
|
public RateLimitException(String message, Throwable cause) {
|
|
super(message, cause);
|
|
}
|
|
}
|
|
} |