⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 juejin.cn/post/7488657770628939830 「有志小青年」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注微信公众号:【芋道源码】有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言都将得到认真回复。甚至不知道如何读源码也可以请教噢。
  4. 新的源码解析文章实时收到通知。每周更新一篇左右。
  5. 认真的源码交流微信群。

概述

本文介绍企业级Java项目中常用的日志链路追踪、统一异常处理、权限拦截等核心功能的实现方案。通过AOP切面、自定义拦截器、线程上下文管理等技术手段,实现以下核心功能:

  • 全链路日志追踪
  • 统一异常处理机制
  • 接口权限验证
  • 请求耗时监控
  • 标准化日志格式

一、核心功能模块

1.1 全局日志切面(WebControllerAop)

实现原理: 基于Spring AOP的环绕通知

核心功能:

  • 请求参数/响应结果格式化
  • 接口耗时计算(精确到秒)
  • 请求事件名称映射
  • 文件类型请求特殊处理
  • 线程上下文初始化
import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSON;
import ****.infrastructure.general.constants.Constants;
import ****.infrastructure.general.constants.ReqUrlConstants;
import ****.infrastructure.general.util.LogUtil;
import ****.infrastructure.general.util.ThreadLocalUtil;
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

/**
* @author: guohong
*/

@Slf4j
@Aspect
@Component
publicclass WebControllerAop {

/**
* 指定切点
* 匹配 com.example.demo.controller包及其子包下的所有类的所有方法
*/
@Pointcut("execution(public * ****.apis.controller.*.*(..)) || execution(public * ****.apis.*.controller.*.*(..))")
public void webLog() {
}

/**
* 环绕通知,环绕增强,相当于MethodInterceptor
*
* @param joinPoint
* @return
*/
@Around("webLog()")
public Object arround(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
//获取目标方法的参数信息
Signature signature = joinPoint.getSignature();
String args;
if (ReqUrlConstants.FILE_URL_MAP.containsKey(signature.getDeclaringTypeName())) {
args = "【文件流类型】";
} else {
args = formatArgs(joinPoint);
}

//AOP代理类的类(class)信息
signature.getDeclaringType();
// 接收到请求,记录请求内容
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attributes == null) {
Object o = joinPoint.proceed();
//记录日志
log.info("接收请求-{} \n 请求ID:{} \n 请求地址: {} \n token: {} \n 入参 : {} \n 出参 : {} \n 请求时间:{} \n 响应时间:{} \n 耗时:{}秒",
"", LogUtil.idEnd(), "", "", args, formatRet(o), DateUtil.date(startTime), DateUtil.now(), (System.currentTimeMillis() - startTime) / 1000d);
ThreadLocalUtil.remove();
return o;
}
HttpServletRequest req = attributes.getRequest();
String logId = req.getHeader(Constants.PROCESS_ID);
if (StringUtils.isBlank(logId)) {
LogUtil.createId();
} else {
ThreadLocalUtil.set(Constants.THREAD_NO, logId);
}
String token = req.getHeader(Constants.CAR_TOKEN);

String eventName = getEventName(req.getRequestURL().toString());
String reqName = "系统接口:" + eventName + " " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName() + "()";
String reqUrl = req.getMethod() + " " + req.getRequestURL().toString();
HashMap<String, String> map = Maps.newHashMap();
map.put(Constants.THREAD_REQ_NAME, reqName);
map.put(Constants.THREAD_REQ_URL, reqUrl);
map.put(Constants.THREAD_REQ_PARAM, args);
map.put(Constants.THREAD_REQ_TIME, startTime + "");
ThreadLocalUtil.set(Constants.THREAD_REQ_PARAM, map);

//执行目标方法
Object o = joinPoint.proceed();

//记录日志
log.info("接收请求:{} \n 请求ID:{} \n 请求地址: {} \n token: {} \n 入参 : {} \n 出参 : {} \n 请求时间:{} \n 响应时间:{} \n 耗时:{}秒",
eventName, LogUtil.idEnd(), reqUrl, token, args, formatRet(o), DateUtil.date(startTime), DateUtil.now(), (System.currentTimeMillis() - startTime) / 1000d);

//移除所有线程map,防止线程复用导致的变量错乱及内存溢出
ThreadLocalUtil.remove();
return o;
}

private String formatArgs(JoinPoint joinPoint) {
String args = Arrays.toString(joinPoint.getArgs());
try {
StringBuilder argsBuild = new StringBuilder();
Object[] argsArray = joinPoint.getArgs();
if (argsArray.length >= 1) {
argsBuild.append(JSON.toJSONString(argsArray[0]));
}
if (StringUtils.length(argsBuild.toString()) > 0) {
return argsBuild.toString();
}
} catch (Exception ignored) {
return args;
}
return args;
}

private Object formatRet(Object ret) {
try {
String retJson = JSON.toJSONString(ret);
if (StringUtils.isNotBlank(retJson)) {
return retJson;
}
} catch (Exception ignored) {
return ret;
}
return ret;
}

/**
* 根据请求url 获取对应的业务名称
*
* @param requestURL
* @return
*/
private static String getEventName(String requestURL) {
if (StringUtils.isBlank(requestURL)) {
return"未知URL";
}
for (Map.Entry<String, String> entry : ReqUrlConstants.MAP.entrySet()) {
if (StringUtils.contains(requestURL, entry.getKey())) {
return entry.getValue();
}
}
return"未配置事件";
}

}

1.2 统一异常处理(GlobalExceptionHandler)

异常分类处理:

  • 文件上传异常(MultipartException)
  • 参数校验异常(MethodArgumentNotValidException)
  • 业务异常(ApiException)
  • 未捕获异常兜底处理

java代码:

import cn.hutool.core.date.DateUtil;
import ****.infrastructure.general.constants.Constants;
import ****.infrastructure.general.util.LogUtil;
import ****.infrastructure.general.util.ThreadLocalUtil;
import ****commons.base.Result;
import ****commons.exception.ApiException;
import ****commons.exception.ErrorResultCode;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.tomcat.util.http.fileupload.impl.FileSizeLimitExceededException;
import org.apache.tomcat.util.http.fileupload.impl.SizeLimitExceededException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.util.CollectionUtils;
import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import org.springframework.web.HttpMediaTypeNotSupportedException;
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.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.multipart.MultipartException;

import javax.servlet.http.HttpServletRequest;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.StringJoiner;

@Slf4j
@RestControllerAdvice
@Order(Ordered.HIGHEST_PRECEDENCE) // 设置最高优先级
publicclass GlobalExceptionHandler {
@Value(value = "${spring.servlet.multipart.max-file-size}")
String singleMaxFileSize;
@Value(value = "${spring.servlet.multipart.max-request-size}")
String maxRequestSize;


/**
* 文件上传错误
* @param req
* @param ex
* @return
*/
@ResponseBody
@ExceptionHandler(value = MultipartException.class)
@ResponseStatus(HttpStatus.OK)
public Result<Object> MultipartExceptionHandler(HttpServletRequest req, MultipartException ex) {

if (ex.getCause().getCause() instanceof FileSizeLimitExceededException) {
return formatResult(ErrorResultCode.CLIENT_DATA_EXE.getErrorCode(), "单个文件上传大小不能超过" + singleMaxFileSize, ex);
} elseif (ex.getCause().getCause() instanceof SizeLimitExceededException) {
return formatResult(ErrorResultCode.CLIENT_DATA_EXE.getErrorCode(), "请求的总上传文件大小不能超过" + maxRequestSize, ex);
} else {
return formatResult(ErrorResultCode.CLIENT_DATA_EXE.getErrorCode(), "上传文件失败", ex);
}
}
/**
* 其他错误
*/
@ResponseBody
@ExceptionHandler({Exception.class})
@ResponseStatus(HttpStatus.OK)
public Result<Object> exception(HttpServletRequest req, Exception ex) {
return formatResult(ErrorResultCode.SYSTEM_ERROR.getErrorCode(), "系统异常:" + ex.getMessage(), ex);
}
//运行时异常
@ResponseBody
@ExceptionHandler(RuntimeException.class)
@ResponseStatus(HttpStatus.OK)
public Result<Object> runtimeExceptionHandler(HttpServletRequest req, RuntimeException ex) {
return formatResult(ErrorResultCode.SYSTEM_ERROR.getErrorCode(), "执行异常:" + ex.getMessage(), ex);
}
/**
* 业务异常
*/
@ResponseBody
@ExceptionHandler({ApiException.class})
@ResponseStatus(HttpStatus.OK)
public Result<Object> ApiExceptionHandler(ApiException e) {
log.error("Message: {}", e.getMessage(), e);
return formatResult(e.getResultCode().getErrorCode(), e.getResultCode().getError(), e);
}

@ResponseBody
@ExceptionHandler({MissingServletRequestParameterException.class})
@ResponseStatus(HttpStatus.OK)
public Result<Object> missingServletRequestParameterExceptionHandler(MissingServletRequestParameterException e) {
log.error("Message: {}", e.getMessage(), e);
return formatResult(ErrorResultCode.PARAM_REQUIRED.getErrorCode(), String.format(ErrorResultCode.PARAM_REQUIRED.getError(), e.getParameterName()), e);
}

@ResponseBody
@ExceptionHandler({MethodArgumentNotValidException.class})
@ResponseStatus(HttpStatus.OK)
public Result<Object> methodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e) {
log.error("Message: {}", e.getMessage(), e);
return formatResult(ErrorResultCode.PARAM_REQUIRED.getErrorCode(), String.format(ErrorResultCode.PARAM_REQUIRED.getError(), this.getBindingResultErrors(e.getBindingResult())), e);
}

@ResponseBody
@ExceptionHandler({HttpMediaTypeNotSupportedException.class})
@ResponseStatus(HttpStatus.OK)
public Result<Object> httpMediaTypeNotSupportedExceptionHandler(HttpMediaTypeNotSupportedException e) {
log.error("Message: {}", e.getMessage(), e);
return formatResult(ErrorResultCode.REQUEST_TYPE_ERROR.getErrorCode(), ErrorResultCode.REQUEST_TYPE_ERROR.getError(), e);
}


private <T extends Throwable> Result<Object> formatResult(String errorCode, String errorMsg, T ex) {

Map<String, String> reqMap = ThreadLocalUtil.get(Constants.THREAD_REQ_PARAM);
long startTime = System.currentTimeMillis();
String reqName = "内部处理";
String url = "";
String param = "";
if (!CollectionUtils.isEmpty(reqMap)){
if (StringUtils.isNotBlank(reqMap.get(Constants.THREAD_REQ_TIME))) {
startTime = Long.parseLong(reqMap.get(Constants.THREAD_REQ_TIME));
}
if (StringUtils.isNotBlank(reqMap.get(Constants.THREAD_REQ_NAME))) {
reqName = reqMap.get(Constants.THREAD_REQ_NAME);
}
if (StringUtils.isNotBlank(reqMap.get(Constants.THREAD_REQ_URL))) {
url = reqMap.get(Constants.THREAD_REQ_URL);
}
if (StringUtils.isNotBlank(reqMap.get(Constants.THREAD_REQ_PARAM))) {
param = reqMap.get(Constants.THREAD_REQ_PARAM);
}
}

//获取异常信息
StringJoiner err = new StringJoiner("/n/r ");
StackTraceElement[] stackTrace = ex.getStackTrace();
if (null != stackTrace) {
for (int i = 0; i < (Math.min(stackTrace.length, 100)); i++) {
err.add(stackTrace[i].toString());
}
}
//记录日志
log.info("响应异常-{} \n 请求ID:{} \n 请求url: {} \n 入参 : {} \n 出参 : {} \n 请求时间:{} \n 响应时间: {} \n 耗时:{}秒",
reqName, LogUtil.idEnd(),url, param,
"异常代码:" + errorCode + " 异常信息:" + errorMsg + "-" + ex.getMessage()+"/n/r" + err,
startTime, DateUtil.now(), (System.currentTimeMillis() - startTime) / 1000);

//移除所有线程map,防止线程复用导致的变量错乱及内存溢出
ThreadLocalUtil.remove();
returnnew Result(errorCode, errorMsg);
}


private String getBindingResultErrors(BindingResult bindingResult) {
StringBuilder sb = new StringBuilder();
if (bindingResult.hasErrors()) {
List<ObjectError> list = bindingResult.getAllErrors();
Iterator var4 = list.iterator();

while(var4.hasNext()) {
ObjectError error = (ObjectError)var4.next();
sb.append(error.getDefaultMessage() + ",");
}
}

return sb.toString();
}
}

统一响应格式:

{"code":"ERROR_001","msg":"参数校验失败"}

1.3 权限拦截器(RequestInterceptor)

安全校验流程:

  1. Token有效性验证
  2. Redis缓存用户信息
  3. 权限服务远程调用
  4. 用户上下文传递

支持功能:

  • 外部用户权限标识
  • Token自动续期(60天)
  • 用户信息线程级缓存

配置类:

import cn.hutool.extra.spring.SpringUtil;
import ****.aop.RequestInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
publicclass InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
//连接所有
registry.addInterceptor(SpringUtil.getBean(RequestInterceptor.class))
.addPathPatterns("/**")
.excludePathPatterns(
"/login"
,"/we-chat/**"
);
}

}

拦截器:

import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import ****.apis.controller.BaseController;
import ****.client.model.UserAuthDto;
import ****.client.model.UserInfoDto;
import ****.infrastructure.general.constants.Constants;
import ****.infrastructure.redis.RedisUtil;
import ****.util.JsonUtils;
import ****commons.exception.ApiException;
import ****commons.exception.ErrorResultCode;
import ****skyauth.dto.R;
import ****skyauth.dto.UserAuthBaseDto;
import ****skyauth.service.AuthService;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.concurrent.TimeUnit;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

@Component
publicclass RequestInterceptor implements HandlerInterceptor {

@Autowired
RedisUtil redisUtil;

@Autowired
BaseController baseController;

@Autowired
AuthService authService;

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
boolean result = false;
String token = request.getHeader(Constants.TOKEN);
if(ObjectUtil.isEmpty(token)){
ApiException.newThrow(ErrorResultCode.TOKEN_IS_NULL);
}
//从缓存中查找此token有没有存在
UserInfoDto userInfo = JSONObject.parseObject(String.valueOf(redisUtil.getCache(token)), UserInfoDto.class);
//如果redis缓存为空,去查一次天权系统查看token,然后放入redis中
if(ObjectUtil.isEmpty(userInfo)){
R r = authService.userPermission(Constants.SKYAUTH_APPID, token, new UserAuthBaseDto());
Integer code = (Integer)r.get("code");
//feign调用成功
if(****.infrastructure.general.constants.HttpStatus.SUCCESS==code){
Object data = r.get("data");
if(ObjectUtil.isNotEmpty(data)){
UserAuthDto userAuthDto = JSON.parseObject(JSON.toJSONString(data), UserAuthDto.class);
userInfo = new UserInfoDto();
//添加外部用户权限标识
ObjectMapper objectMapper = new ObjectMapper();
JsonNode jsonNode = objectMapper.readTree(userAuthDto.getDepts().toString());
if (JsonUtils.containsFuncNormalList(jsonNode, Constants.IS_EXT_USER_RIGHTS)) {
userInfo.setIsExternal("1");
}
BeanUtils.copyProperties(userAuthDto,userInfo);
//feign调用成功且token依然有效
redisUtil.setCacheWithExpiration(token,JSONObject.toJSONString(userInfo),60, TimeUnit.DAYS);
request.setAttribute(Constants.USER_NAME,userInfo.getUserName());
request.setAttribute(Constants.USER_ID,userInfo.getUserId());
request.setAttribute(Constants.IS_EXTERNAL, userInfo.getIsExternal());
result = true;
}else {
//这里是调用feign成功但是token校验失败了 返回401
ApiException.newThrow(ErrorResultCode.LOGIN_FAILED,"登陆已超时,请重新登录");
}
}else {
//feign调用失败
ApiException.newThrow(ErrorResultCode.CLIENT_DATA_EXE,(String)r.get("msg"));
}
}else{
request.setAttribute(Constants.USER_NAME, userInfo.getUserName());
request.setAttribute(Constants.USER_ID, userInfo.getUserId());
request.setAttribute(Constants.IS_EXTERNAL, userInfo.getIsExternal());
result = true;
}
return result;
}

@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}

@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}

}

1.4 线程上下文管理

ThreadLocalUtil:

  • 线程安全的数据存储
  • 支持嵌套调用上下文传递
  • 自动清理机制
import java.util.HashMap;
import java.util.Map;

/**
* @Description: 线程共享类
*/
publicclass ThreadLocalUtil {

privatestatic ThreadLocal<Map<String, Object>> threadLocal = new ThreadLocal() {
protected Map<String, Object> initialValue() {
returnnew HashMap(4);
}
};

public static Map<String, Object> getThreadLocal(){
return threadLocal.get();
}

publicstatic <T> T get(String key) {
Map map = threadLocal.get();
return (T)map.get(key);
}

publicstatic <T> T get(String key,T defaultValue) {
Map map = threadLocal.get();
return (T)map.get(key) == null ? defaultValue : (T)map.get(key);
}

public static void set(String key, Object value) {
Map map = threadLocal.get();
map.put(key, value);
}

public static void set(Map<String, Object> keyValueMap) {
Map map = threadLocal.get();
map.putAll(keyValueMap);
}

public static void remove() {
threadLocal.remove();
}

publicstatic <T> T remove(String key) {
Map map = threadLocal.get();
return (T)map.remove(key);
}
}

LogUtil:

  • 唯一日志ID生成(时间戳+UUID)
  • 多线程ID继承机制
  • 日志前缀自动追加
import cn.hutool.core.date.DateUtil;
import ****.infrastructure.general.constants.Constants;
import org.apache.commons.lang3.StringUtils;

import java.util.Date;
import java.util.UUID;

/**
* @Description: 线程日志处理类
*/
publicclass LogUtil {

/**
* 返回当前日志ID,如果为空则生成。拼接-和序号
*/
public static String id() {

String logId = ThreadLocalUtil.get(Constants.THREAD_NO);
if (StringUtils.isEmpty(logId)) {
logId = DateUtil.format(new Date(), "yyyyMMddHHmmss") + UUID.randomUUID().toString().replaceAll("-", "").substring(0, 16);
}
Long seq = ThreadLocalUtil.get(Constants.THREAD_SEQ);
if (seq == null) {
seq = 1L;
ThreadLocalUtil.set(Constants.THREAD_SEQ, seq);
} else {
seq = seq + 1;
ThreadLocalUtil.set(Constants.THREAD_SEQ, seq);
}
return logId + "^" + seq +"-";
}

/**
* 返回当前日志ID,如果为空则生成。拼接-和序号
*/
public static String idEnd() {
return StringUtils.removeEnd(id(), "-");
}

/**
* 返回当前日志ID,如果为空则生成。拼接-和序号
*/
public static void createId() {
String logId = ThreadLocalUtil.get(Constants.THREAD_NO);
if (StringUtils.isEmpty(logId)) {
logId = DateUtil.format(new Date(), "yyyyMMddHHmmss") + UUID.randomUUID().toString().replaceAll("-", "").substring(0, 16);
ThreadLocalUtil.set(Constants.THREAD_NO, logId);
}
}
}

调用示例:

@PostMapping("/pullWxChatRecordScheduleTask")
public String dingshirenwu(){
try{
//拉取聊天记录
wxCompanyChatService.pullWxChatRecord(null);

//查询时间倒序,配置时间内的(例如:三分钟)内里未被大模型扫描的群聊id集合
List<String> groupIdList = wxCompanyChatService.getGroupId();
log.info(LogUtil.id() + "获取有问题反馈的群ID {}", groupIdList);

//启动线程池任务
ExecutorService threadPool = LLmAnalysisChatRecordThread.getLlmAnalySisChatRecordThreadPool();
for (String groupId : groupIdList) {
String logId = ThreadLocalUtil.get(Constants.THREAD_NO);
Long seq = ThreadLocalUtil.get(Constants.THREAD_SEQ);
threadPool.submit(() -> {
//日志ID :使用主线程日志ID + 子线程ID
ThreadLocalUtil.set(Constants.THREAD_NO, logId + "-" + Thread.currentThread().getId());
ThreadLocalUtil.set(Constants.THREAD_SEQ, seq);
wxGroupChatService.analysisGroupChatRecordByGroupId(groupId);
});
}
}catch (Exception e){
log.error(LogUtil.id() + "异常捕捉", e);
return"失败";
}
return"成功";
}

二、核心设计亮点

2.1 日志链路追踪方案

  • 唯一TraceID生成规则:yyyyMMddHHmmss+16位随机数
  • 多线程ID继承:主线程ID + 子线程ID
  • 日志要素包含:[请求时间] [序号][响应时间][耗时][入参][出参][异常堆栈]

2.2 性能优化策略

  • 文件类型请求参数特殊处理
  • Redis缓存用户信息(60天)
  • 异常堆栈智能截取(保留前100行)

2.3 安全控制

  • 白名单路径配置
  • 双重Token验证机制(本地缓存+远程验证)
  • 外部用户权限标识隔离

三、使用示例

3.1 基础日志记录

log.info(LogUtil.id() + "获取有问题反馈的群ID {}", groupIdList);

3.2 多线程场景

threadPool.submit(() -> {
ThreadLocalUtil.set(Constants.THREAD_NO, logId + "-" + Thread.currentThread().getId());
// 业务逻辑
});

四、部署注意事项

  • 需要配置Redis连接信息
  • 权限服务地址配置
  • 文件上传大小限制参数:
spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=100MB

五、总结

本方案实现了企业级项目的三个核心需求:

  • 可追踪性: 全链路日志追踪能力
  • 稳定性: 统一异常处理机制
  • 安全性: 完善的权限验证体系

通过合理的线程上下文管理、AOP切面设计、异常处理规范,显著提升了系统的可维护性和问题排查效率。实际使用中可根据业务需求扩展日志采集维度,集成APM系统实现更完善的监控体系。

文章目录
  1. 1. 概述
  2. 2. 一、核心功能模块
    1. 2.0.1. 1.1 全局日志切面(WebControllerAop)
    2. 2.0.2. 1.2 统一异常处理(GlobalExceptionHandler)
    3. 2.0.3. 1.3 权限拦截器(RequestInterceptor)
    4. 2.0.4. 1.4 线程上下文管理
  • 3. 二、核心设计亮点
    1. 3.0.1. 2.1 日志链路追踪方案
    2. 3.0.2. 2.2 性能优化策略
    3. 3.0.3. 2.3 安全控制
  • 4. 三、使用示例
    1. 4.0.1. 3.1 基础日志记录
    2. 4.0.2. 3.2 多线程场景
  • 5. 四、部署注意事项
  • 6. 五、总结