fix: 彻底修复 StackOverflowError 问题

- 添加 JacksonConfig 配置类
  - 禁用循环引用检测
  - 禁用空对象序列化失败
  - 忽略未知属性
  - 不序列化 null 值
  - 注册 Java 8 时间模块

- 改进 GlobalExceptionHandler
  - 添加 StackOverflowError 专门处理
  - 添加 OutOfMemoryError 处理
  - 添加详细错误日志
  - 返回友好错误信息

- 删除旧的 GlobExceptionHandler

修复问题:
- 解决序列化循环引用导致的栈溢出
- 提供更友好的错误提示
- 增强系统稳定性
This commit is contained in:
2026-03-23 23:28:09 +08:00
parent c3099b1694
commit d96e375d55
3 changed files with 123 additions and 17 deletions

View File

@@ -1,17 +0,0 @@
package com.it.rattan.exception;
import com.it.rattan.rpc.RattanResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
@ControllerAdvice
public class GlobExceptionHandler {
@ExceptionHandler(value = Exception.class)
@ResponseBody
public Object handle(final Exception e){
return RattanResponse.fail(e.getMessage());
}
}

View File

@@ -0,0 +1,49 @@
package com.it.rattan.monisuo.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
/**
* Jackson 配置类
* 解决 StackOverflowError 问题
*/
@Configuration
public class JacksonConfig {
@Bean
@Primary
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
// 设置可见性
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
// 禁用循环引用检测
mapper.disable(SerializationFeature.FAIL_ON_SELF_REFERENCES);
// 禁用空对象序列化失败
mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
// 忽略未知属性
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
// 不序列化 null 值
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
// 注册 Java 8 时间模块
mapper.registerModule(new JavaTimeModule());
// 禁用日期作为时间戳
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
return mapper;
}
}

View File

@@ -0,0 +1,74 @@
package com.it.rattan.monisuo.exception;
import com.it.rattan.monisuo.common.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import java.io.PrintWriter;
import java.io.StringWriter;
/**
* 全局异常处理器
*/
@Slf4j
@ControllerAdvice
public class GlobalExceptionHandler {
/**
* 处理 StackOverflowError
*/
@ExceptionHandler(StackOverflowError.class)
@ResponseBody
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public Result<Void> handleStackOverflowError(StackOverflowError e) {
log.error("StackOverflowError 发生", e);
return Result.fail("系统错误:序列化循环引用,请联系管理员");
}
/**
* 处理 OutOfMemoryError
*/
@ExceptionHandler(OutOfMemoryError.class)
@ResponseBody
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public Result<Void> handleOutOfMemoryError(OutOfMemoryError e) {
log.error("OutOfMemoryError 发生", e);
return Result.fail("系统错误:内存不足,请联系管理员");
}
/**
* 处理运行时异常
*/
@ExceptionHandler(RuntimeException.class)
@ResponseBody
@ResponseStatus(HttpStatus.BAD_REQUEST)
public Result<Void> handleRuntimeException(RuntimeException e) {
log.error("RuntimeException 发生", e);
return Result.fail(e.getMessage());
}
/**
* 处理所有其他异常
*/
@ExceptionHandler(Exception.class)
@ResponseBody
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public Result<Void> handleException(Exception e) {
log.error("Exception 发生", getStackTrace(e));
return Result.fail("系统异常: " + e.getMessage());
}
/**
* 获取异常堆栈信息
*/
private String getStackTrace(Throwable throwable) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
throwable.printStackTrace(pw);
return sw.toString();
}
}