全局异常处理完善 #159

Merged
hnu202326010122 merged 1 commits from jingyou_branch into develop 2 weeks ago

@ -1,19 +1,35 @@
package com.campus.water.controller;
import com.campus.water.entity.BusinessException;
import com.campus.water.util.ResultVO;
import org.springframework.security.access.AccessDeniedException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
import org.springframework.web.servlet.NoHandlerFoundException;
import java.time.format.DateTimeParseException;
import java.util.Objects;
import java.io.IOException;
import java.nio.file.AccessDeniedException;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.stream.Collectors;
/**
* -
*/
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
/**
@ -26,6 +42,10 @@ public class GlobalExceptionHandler {
if (msg.contains("AlertLevel") || msg.contains("AlertStatus")) {
msg = "参数错误:告警级别可选值(info/warning/error/critical),告警状态可选值(pending/resolved/closed)";
}
// 设备ID格式错误特殊处理
if (msg.contains("设备ID") || msg.contains("deviceId")) {
msg = "设备ID格式错误正确格式为WM/WS开头+3位数字如WM001、WS123";
}
return ResultVO.error(400, "参数错误:" + msg);
}
@ -36,8 +56,15 @@ public class GlobalExceptionHandler {
public ResultVO<Void> handleTypeMismatch(MethodArgumentTypeMismatchException e) {
String errorMsg;
// 特殊处理时间格式错误(告警查询的时间参数)
if (e.getCause() instanceof DateTimeParseException) {
if (e.getCause() instanceof java.time.format.DateTimeParseException) {
errorMsg = "时间参数格式错误正确格式yyyy-MM-dd HH:mm:ss示例2025-12-05 10:30:00";
} else if (e.getRequiredType() != null && e.getRequiredType().isEnum()) {
// 枚举类型转换错误处理
errorMsg = String.format(
"参数[%s]枚举值错误,允许值:%s",
e.getName(),
getEnumValues(e.getRequiredType())
);
} else {
// 通用类型不匹配提示
errorMsg = String.format(
@ -51,21 +78,35 @@ public class GlobalExceptionHandler {
}
/**
* /访
* /访
*/
@ExceptionHandler(AccessDeniedException.class)
public ResultVO<Void> handleAccessDenied(AccessDeniedException e) {
return ResultVO.error(403, "权限不足:仅管理员/维修人员可访问告警相关功能");
@ExceptionHandler({AccessDeniedException.class, org.springframework.security.access.AccessDeniedException.class})
public ResultVO<Void> handleAccessDenied(Exception e) {
String roleMsg = "仅超级管理员可操作";
// 区分不同接口的权限提示
if (e.getMessage().contains("AREA_ADMIN")) {
roleMsg = "仅区域管理员及以上权限可操作";
} else if (e.getMessage().contains("REPAIRMAN")) {
roleMsg = "仅维修人员及管理员可操作";
}
return ResultVO.error(403, "权限不足:" + roleMsg);
}
/**
*
* /
*/
@ExceptionHandler(RuntimeException.class)
public ResultVO<Void> handleRuntimeException(RuntimeException e) {
// 生产环境建议添加日志记录,此处简化
// log.error("服务器运行时异常", e);
return ResultVO.error(500, "服务器内部错误:" + e.getMessage());
@ExceptionHandler(NoSuchElementException.class)
public ResultVO<Void> handleNoSuchElement(NoSuchElementException e) {
String msg = e.getMessage();
// 标准化资源不存在提示
if (msg.contains("设备")) {
return ResultVO.error(404, "设备不存在:" + msg.replace("No value present", "").trim());
} else if (msg.contains("管理员") || msg.contains("Admin")) {
return ResultVO.error(404, "管理员不存在:" + msg.replace("No value present", "").trim());
} else if (msg.contains("区域") || msg.contains("Area")) {
return ResultVO.error(404, "区域不存在:" + msg.replace("No value present", "").trim());
}
return ResultVO.error(404, "请求的资源不存在:" + msg);
}
/**
@ -73,8 +114,176 @@ public class GlobalExceptionHandler {
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResultVO<Void> handleMethodArgumentNotValid(MethodArgumentNotValidException e) {
// 获取第一个验证失败的字段和消息
String errorMsg = Objects.requireNonNull(e.getBindingResult().getFieldError()).getDefaultMessage();
return ResultVO.badRequest(errorMsg); // 返回400状态码和具体错误信息
// 收集所有验证失败的字段和消息
List<String> errorMessages = e.getBindingResult().getFieldErrors().stream()
.map(error -> error.getField() + "" + error.getDefaultMessage())
.collect(Collectors.toList());
return ResultVO.error(400, "参数验证失败:" + String.join("", errorMessages));
}
/**
* @RequestBody
*/
@ExceptionHandler(BindException.class)
public ResultVO<Void> handleBindException(BindException e) {
FieldError firstError = e.getBindingResult().getFieldError();
String errorMsg = firstError != null ?
firstError.getField() + "" + firstError.getDefaultMessage() :
"参数绑定失败";
return ResultVO.error(400, "表单参数错误:" + errorMsg);
}
/**
*
*/
@ExceptionHandler(MissingServletRequestParameterException.class)
public ResultVO<Void> handleMissingParam(MissingServletRequestParameterException e) {
return ResultVO.error(400,
String.format("缺少必填参数:%s类型%s",
e.getParameterName(),
e.getParameterType()));
}
/**
*
*/
@ExceptionHandler(DuplicateKeyException.class)
public ResultVO<Void> handleDuplicateKey(DuplicateKeyException e) {
log.error("数据库唯一约束冲突", e);
String msg = "数据已存在,无法重复添加";
// 针对设备ID冲突特殊处理
if (e.getMessage().contains("device_id")) {
msg = "设备ID已存在请更换设备ID后重试";
} else if (e.getMessage().contains("admin_name")) {
msg = "管理员用户名已存在,请更换用户名";
}
return ResultVO.error(409, msg);
}
/**
*
*/
@ExceptionHandler(DataIntegrityViolationException.class)
public ResultVO<Void> handleDataIntegrityViolation(DataIntegrityViolationException e) {
log.error("数据库完整性约束异常", e);
String msg = "数据操作失败,可能存在关联数据";
if (e.getMessage().contains("foreign key constraint")) {
msg = "无法删除,该数据已被其他记录关联引用";
} else if (e.getMessage().contains("not null")) {
msg = "必填字段不能为空,请检查输入";
}
return ResultVO.error(400, msg);
}
/**
* JSON
*/
@ExceptionHandler(HttpMessageNotReadableException.class)
public ResultVO<Void> handleHttpMessageNotReadable(HttpMessageNotReadableException e) {
log.error("请求体解析失败", e);
String msg = "请求数据格式错误请检查JSON格式是否正确";
if (e.getMessage().contains("date-time")) {
msg = "日期时间格式错误正确格式yyyy-MM-dd HH:mm:ss";
}
return ResultVO.error(400, msg);
}
/**
* HTTP
*/
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public ResultVO<Void> handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e) {
return ResultVO.error(405,
String.format("不支持的请求方法:%s支持的方法%s",
e.getMethod(),
String.join(",", e.getSupportedMethods())));
}
/**
*
*/
@ExceptionHandler(HttpMediaTypeNotSupportedException.class)
public ResultVO<Void> handleHttpMediaTypeNotSupported(HttpMediaTypeNotSupportedException e) {
return ResultVO.error(415,
String.format("不支持的媒体类型:%s支持的类型%s",
e.getContentType(),
e.getSupportedMediaTypes()));
}
/**
*
*/
@ExceptionHandler(MaxUploadSizeExceededException.class)
public ResultVO<Void> handleMaxUploadSizeExceeded(MaxUploadSizeExceededException e) {
long maxSizeMB = e.getMaxUploadSize() / (1024 * 1024);
return ResultVO.error(413,
String.format("文件大小超限,最大支持:%dMB", maxSizeMB));
}
/**
* IO
*/
@ExceptionHandler(IOException.class)
public ResultVO<Void> handleIOException(IOException e) {
log.error("IO操作异常", e);
String msg = "文件操作失败:" + e.getMessage();
if (e.getMessage().contains("Permission denied")) {
msg = "文件操作权限不足";
}
return ResultVO.error(500, msg);
}
/**
*
*/
@ExceptionHandler(BusinessException.class)
public ResultVO<Void> handleBusinessException(BusinessException e) {
// 业务异常自带状态码和消息
return ResultVO.error(e.getCode(), e.getMessage());
}
/**
* 404
*/
@ExceptionHandler(NoHandlerFoundException.class)
public ResultVO<Void> handleNoHandlerFound(NoHandlerFoundException e) {
return ResultVO.error(404,
String.format("请求的接口不存在:%s %s",
e.getHttpMethod(),
e.getRequestURL()));
}
/**
*
*/
@ExceptionHandler(RuntimeException.class)
public ResultVO<Void> handleRuntimeException(RuntimeException e) {
log.error("服务器运行时异常", e);
// 生产环境可根据异常类型返回更友好的提示
String msg = "服务器内部错误:" + e.getMessage();
// 对常见运行时异常进行特殊处理
if (e instanceof NullPointerException) {
msg = "系统处理异常:数据为空";
} else if (e instanceof IndexOutOfBoundsException) {
msg = "系统处理异常:数据索引越界";
}
return ResultVO.error(500, msg);
}
/**
*
*/
private String getEnumValues(Class<?> enumClass) {
if (!enumClass.isEnum()) {
return "未知";
}
StringBuilder values = new StringBuilder();
for (Object enumConstant : enumClass.getEnumConstants()) {
values.append(enumConstant).append(",");
}
if (values.length() > 0) {
values.deleteCharAt(values.length() - 1);
}
return values.toString();
}
}

@ -0,0 +1,55 @@
package com.campus.water.entity; // 请确保这个包名与你的项目结构一致
import org.springframework.http.HttpStatus;
/**
*
* <p>
*
*
* </p>
*/
public class BusinessException extends RuntimeException {
private static final long serialVersionUID = 1L;
/**
* HTTP
*/
private int code;
/**
*
*/
private String message;
/**
*
* @param code
* @param message
*/
public BusinessException(int code, String message) {
super(message);
this.code = code;
this.message = message;
}
/**
* 使 400 Bad Request
* @param message
*/
public BusinessException(String message) {
this(HttpStatus.BAD_REQUEST.value(), message);
}
// --- Getters ---
public int getCode() {
return code;
}
@Override
public String getMessage() {
return message;
}
}

@ -58,4 +58,6 @@ public interface AlertRepository extends JpaRepository<Alert, Long> {
List<Alert.AlertStatus> activeStatus,
LocalDateTime timestamp
);
List<Alert> findByResolvedByAndStatus(String repairmanId, Alert.AlertStatus alertStatus);
}
Loading…
Cancel
Save