u
This commit is contained in:
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(
|
||||
|
||||
+45
@@ -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;
|
||||
}
|
||||
+33
@@ -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);
|
||||
}
|
||||
}
|
||||
+60
@@ -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);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user