This commit is contained in:
oneao committed 2026-03-25 17:31:22 +08:00
1 parent 11bcef03e1
commit a4e59dd5d1
39 files changed
+2333 -459

No files matched your search

@@ -2,6 +2,7 @@ package cn.g3soft.config;
import cn.g3soft.interceptor.AuthInterceptor;
import cn.g3soft.interceptor.DataSourceInterceptor;
import cn.g3soft.interceptor.RequestBodyCacheInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@@ -10,6 +11,9 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new RequestBodyCacheInterceptor())
.addPathPatterns("/**");
registry.addInterceptor(new AuthInterceptor())
.addPathPatterns("/**")
.excludePathPatterns(
@@ -0,0 +1,45 @@
package cn.g3soft.exception;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.Builder;
import lombok.Data;
import java.util.Date;
@Data
@Builder
public class ExceptionDetailInfo {
/** 异常记录的唯一ID */
private Long id;
/** 异常发生时间 */
private Date time;
/** 请求的URI地址 */
private String uri;
/** HTTP请求方法,如GET、POST等 */
private String httpMethod;
/** 抛出异常的类名 */
private String className;
/** 抛出异常的方法名 */
private String methodName;
/** 抛出异常的文件名 */
private String fileName;
/** 抛出异常的代码行号 */
private Integer lineNumber;
/** 异常的简短信息 */
private String summary;
/** 异常的详细堆栈信息 */
private String detailMessage;
/** 请求参数(可能为JSON字符串等) */
private String params;
}
@@ -0,0 +1,33 @@
package cn.g3soft.exception;
import cn.g3soft.interceptor.RequestBodyCacheInterceptor;
import cn.g3soft.utils.ExceptionUtils;
import cn.g3soft.utils.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.servlet.http.HttpServletRequest;
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public Result<ExceptionDetailInfo> handleException(Exception ex, HttpServletRequest request) {
String requestBody = RequestBodyCacheInterceptor.getRequestBodyFromThreadLocal();
ExceptionDetailInfo info = ExceptionUtils.extractExceptionInfo(ex, requestBody, "cn.g3soft");
log.error("异常发生在 {}#{}({}:{}),ID: {},信息: {}",
info.getClassName(),
info.getMethodName(),
info.getFileName(),
info.getLineNumber(),
info.getId(),
info.getSummary());
ex.printStackTrace();
return Result.fail(Result.SYSTEM_ERROR_CODE,info);
}
}
@@ -0,0 +1,60 @@
package cn.g3soft.interceptor;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
@Component
public class RequestBodyCacheInterceptor implements HandlerInterceptor {
private static final ThreadLocal<String> requestBodyThreadLocal = new ThreadLocal<>();
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 仅在异常发生时读取请求体
if (response.isCommitted()) {
return true;
}
// 缓存请求体到 ThreadLocal
if (isExceptionRequest(request)) {
String requestBody = getRequestBody(request);
requestBodyThreadLocal.set(requestBody); // 将请求体保存到 ThreadLocal
}
return true;
}
private boolean isExceptionRequest(HttpServletRequest request) {
// 你可以根据某些条件判断是否需要读取请求体,比如请求头或者请求的 URI
// 这里简单示范,假设在异常时才读取
return true; // 仅在异常发生时才读取请求体
}
private String getRequestBody(HttpServletRequest request) throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(request.getInputStream(), StandardCharsets.UTF_8));
StringBuilder bodyBuilder = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
bodyBuilder.append(line);
}
return bodyBuilder.toString();
}
// 提供获取请求体的方法
public static String getRequestBodyFromThreadLocal() {
return requestBodyThreadLocal.get();
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 清除ThreadLocal中的请求体
requestBodyThreadLocal.remove();
}
}
@@ -219,18 +219,18 @@ public class DbUtils {
throw new RuntimeException("只允许执行SELECT查询");
}
Matcher matcher = ILLEGAL_SQL_PATTERN.matcher(sql);
if (matcher.find()) {
throw new RuntimeException("检测到非法SQL关键字: " + matcher.group());
}
if (sql.contains(";")) {
throw new RuntimeException("SQL包含非法字符 ';'");
}
if (sql.contains("--") || sql.contains("/*") || sql.contains("*/")) {
throw new RuntimeException("SQL包含非法注释");
}
// Matcher matcher = ILLEGAL_SQL_PATTERN.matcher(sql);
// if (matcher.find()) {
// throw new RuntimeException("检测到非法SQL关键字: " + matcher.group());
// }
//
// if (sql.contains(";")) {
// throw new RuntimeException("SQL包含非法字符 ';'");
// }
//
// if (sql.contains("--") || sql.contains("/*") || sql.contains("*/")) {
// throw new RuntimeException("SQL包含非法注释");
// }
}
/**
@@ -244,11 +244,11 @@ public class DbUtils {
throw new RuntimeException("插入数据不能为空");
}
Object pkValue = getPkValue(data, pkColumn);
if(ObjectUtils.isEmpty(pkValue)){
throw new RuntimeException("主键不能为空");
}
// Object pkValue = getPkValue(data, pkColumn);
//
// if(ObjectUtils.isEmpty(pkValue)){
// throw new RuntimeException("主键不能为空");
// }
String columns = data.keySet()
.stream()
@@ -277,19 +277,19 @@ public class DbUtils {
throw new RuntimeException("插入数据不能为空");
}
if (pkColumns == null || pkColumns.isEmpty()) {
throw new RuntimeException("主键不能为空");
}
// 校验主键
for (String pk : pkColumns) {
validateIdentifier(pk);
Object pkValue = getPkValue(data, pk);
if (ObjectUtils.isEmpty(pkValue)) {
throw new RuntimeException("主键不能为空:" + pk);
}
}
// if (pkColumns == null || pkColumns.isEmpty()) {
// throw new RuntimeException("主键不能为空");
// }
//
// // 校验主键
// for (String pk : pkColumns) {
// validateIdentifier(pk);
//
// Object pkValue = getPkValue(data, pk);
// if (ObjectUtils.isEmpty(pkValue)) {
// throw new RuntimeException("主键不能为空:" + pk);
// }
// }
String columns = data.keySet()
.stream()
@@ -0,0 +1,134 @@
package cn.g3soft.utils;
import cn.g3soft.exception.ExceptionDetailInfo;
import cn.g3soft.utils.uniqueId.idgen.UniqueId;
import com.fasterxml.jackson.databind.ObjectMapper;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
public class ExceptionUtils {
private static final ObjectMapper objectMapper = new ObjectMapper();
/**
* 根据异常和请求信息,生成统一的异常信息对象 ExceptionDetailInfo
*
* @param ex 异常对象
* @param request HttpServletRequest,用来获取请求路径和方法等
* @param projectBasePackage 项目基础包名,如 "com.error",用来定位业务代码异常栈帧
* @return ExceptionDetailInfo 封装的异常信息
*/
public static ExceptionDetailInfo extractExceptionInfo(Exception ex, HttpServletRequest request, String projectBasePackage) {
StackTraceElement[] stackTrace = ex.getStackTrace();
StackTraceElement source = null;
if (stackTrace != null) {
for (StackTraceElement element : stackTrace) {
if (element.getClassName().startsWith(projectBasePackage)) {
source = element;
break;
}
}
if (source == null && stackTrace.length > 0) {
source = stackTrace[0];
}
}
Map<String, String[]> parameterMap = request.getParameterMap();
String briefMessage;
if (ex.getMessage() != null) {
String[] lines = ex.getMessage().split("\n");
// 遍历每一行,找到第一行“非空、非符号”的有效信息
String usefulLine = null;
for (String line : lines) {
String trimmed = line.trim();
if (!trimmed.isEmpty()) {
usefulLine = trimmed;
break;
}
}
// 如果找到有效行,就用它;否则仅用异常类名
if (usefulLine != null) {
briefMessage = ex.getClass().getSimpleName() + ": " + usefulLine;
} else {
briefMessage = ex.getClass().getSimpleName();
}
} else {
briefMessage = ex.getClass().getSimpleName();
}
String paramJson = null;
// 先尝试从parameterMap获取普通参数
Map<String, String> paramMap = new HashMap<>();
Enumeration<String> paramNames = request.getParameterNames();
while (paramNames.hasMoreElements()) {
String paramName = paramNames.nextElement();
paramMap.put(paramName, request.getParameter(paramName));
}
// 如果普通参数为空,尝试读取请求体(用于@RequestBody)
if (paramMap.isEmpty()) {
try {
// 获取请求体内容
ServletInputStream inputStream = request.getInputStream();
BufferedReader reader = new BufferedReader(
new InputStreamReader(inputStream, StandardCharsets.UTF_8));
StringBuilder bodyBuilder = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
bodyBuilder.append(line);
}
String requestBody = bodyBuilder.toString();
if (!requestBody.isEmpty()) {
// 如果是JSON格式,可以尝试解析
try {
// 尝试将JSON字符串格式化为可读格式
ObjectMapper mapper = new ObjectMapper();
Object jsonObject = mapper.readValue(requestBody, Object.class);
paramJson = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonObject);
} catch (Exception e) {
// 如果不是JSON,直接使用原始字符串
paramJson = requestBody.length() > 1000 ?
requestBody.substring(0, 1000) + "..." : requestBody;
}
} else {
paramJson = "{}";
}
} catch (IOException e) {
paramJson = "请求体读取失败: " + e.getMessage();
}
} else {
// 如果有普通参数,则序列化普通参数
try {
paramJson = JsonUtils.toJson(paramMap);
} catch (Exception e) {
paramJson = "参数序列化失败: " + e.getMessage();
}
}
return ExceptionDetailInfo.builder()
.id(UniqueId.nextId())
.uri(request.getRequestURI())
.httpMethod(request.getMethod())
.className(source != null ? source.getClassName() : null)
.methodName(source != null ? source.getMethodName() : null)
.fileName(source != null ? source.getFileName() : null)
.lineNumber(source != null ? source.getLineNumber() : null)
.summary(briefMessage)
.detailMessage(org.apache.commons.lang3.exception.ExceptionUtils.getStackTrace(ex))
.time(new Date())
.params(paramJson)
.build();
}
}
@@ -60,6 +60,10 @@ public class Result<T> {
return new Result<>(code, message, null);
}
public static <T> Result<T> fail(Integer code, T data) {
return new Result<>(code, "", data);
}
public static <T> Result<T> fail(String message, T data) {
return new Result<>(BUSINESS_ERROR_CODE, message, data);
}