init: UserBaseService (backend + deploy + scripts)
ci-userbase-service / build-and-push (push) Successful in 40s
ci-userbase-service / build-and-push (push) Successful in 40s
This commit is contained in:
@@ -0,0 +1,204 @@
|
||||
package com.yunhai.userbase.aspect;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.yunhai.userbase.common.annotation.OperationLog;
|
||||
import com.yunhai.userbase.common.context.UserContext;
|
||||
import com.yunhai.userbase.entity.SysOperationLog;
|
||||
import com.yunhai.userbase.service.ISysOperationLogService;
|
||||
import com.yunhai.userbase.utils.GzipUtil;
|
||||
import com.yunhai.userbase.utils.IpUtils;
|
||||
import com.yunhai.userbase.utils.OperationLogResolver;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.annotation.Around;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.reflect.MethodSignature;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
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 javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 操作日志切面。<br>
|
||||
* 拦截所有 Controller 方法,按如下规则自动采集并落库:<br>
|
||||
* - GET 请求:自动记录为「一般(查询)」,无需任何注解;<br>
|
||||
* - 非 GET 请求:仅当方法标注 @OperationLog 时才记录(沿用原注解式行为);<br>
|
||||
* - 排除日志查看接口自身(SysOperationLogController),避免递归与刷屏。<br>
|
||||
* 响应结果 GZIP 压缩 + 截断后,在 finally 中异步落库,失败不影响主业务。<br>
|
||||
*
|
||||
* @author chenx
|
||||
* @date 2026/7/28
|
||||
*/
|
||||
@Slf4j
|
||||
@Aspect
|
||||
@Component
|
||||
public class OperationLogAspect {
|
||||
|
||||
/** 不记录日志查询/下载接口自身,避免递归与刷屏。 */
|
||||
private static final String LOG_CONTROLLER = "com.yunhai.userbase.controller.api.SysOperationLogController";
|
||||
|
||||
/** GET 自动记录时使用的默认响应保存上限(10KB)。 */
|
||||
private static final int DEFAULT_MAX_RESPONSE_SIZE = 10240;
|
||||
|
||||
@Autowired
|
||||
private ISysOperationLogService sysOperationLogService;
|
||||
|
||||
@Around("execution(* com.yunhai.userbase.controller..*.*(..))")
|
||||
public Object around(ProceedingJoinPoint point) throws Throwable {
|
||||
// 排除日志查看接口自身
|
||||
if (LOG_CONTROLLER.equals(point.getSignature().getDeclaringTypeName())) {
|
||||
return point.proceed();
|
||||
}
|
||||
|
||||
HttpServletRequest request = getRequest();
|
||||
String httpMethod = request != null ? request.getMethod() : "";
|
||||
boolean isGet = "GET".equalsIgnoreCase(httpMethod);
|
||||
|
||||
// 解析方法上的 @OperationLog 注解(可能为 null)
|
||||
OperationLog operationLog = findAnnotation(point);
|
||||
|
||||
// 决策:GET 自动记录;非 GET 仅在有注解时记录
|
||||
if (!isGet && operationLog == null) {
|
||||
return point.proceed();
|
||||
}
|
||||
|
||||
long start = System.currentTimeMillis();
|
||||
SysOperationLog entity = buildEntity(point, request, httpMethod);
|
||||
|
||||
// GET 未标注注解时使用默认参数;标注了则优先用注解配置
|
||||
boolean saveResponse = operationLog != null ? operationLog.saveResponse() : true;
|
||||
int maxResponseSize = operationLog != null ? operationLog.maxResponseSize() : DEFAULT_MAX_RESPONSE_SIZE;
|
||||
|
||||
Object result = null;
|
||||
try {
|
||||
result = point.proceed();
|
||||
entity.setStatusCode(200);
|
||||
fillResponse(entity, result, saveResponse, maxResponseSize);
|
||||
} catch (Throwable e) {
|
||||
entity.setStatusCode(500);
|
||||
entity.setErrorMsg(truncate(e.getMessage(), 2000));
|
||||
throw e;
|
||||
} finally {
|
||||
entity.setCostTime(System.currentTimeMillis() - start);
|
||||
sysOperationLogService.saveAsync(entity);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从连接点解析方法上的 @OperationLog 注解。
|
||||
*/
|
||||
private OperationLog findAnnotation(ProceedingJoinPoint point) {
|
||||
try {
|
||||
MethodSignature signature = (MethodSignature) point.getSignature();
|
||||
Method method = signature.getMethod();
|
||||
return method.getAnnotation(OperationLog.class);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 组装日志实体(基础字段 + 级别/类型判定)。
|
||||
*/
|
||||
private SysOperationLog buildEntity(ProceedingJoinPoint point, HttpServletRequest request, String httpMethod) {
|
||||
SysOperationLog entity = new SysOperationLog();
|
||||
|
||||
Integer userId = UserContext.getCurrentUser();
|
||||
entity.setUserId(userId != null ? userId.longValue() : 0L);
|
||||
String username = request != null ? request.getRemoteUser() : null;
|
||||
entity.setUsername(StringUtils.isNotBlank(username) ? username : "anonymous");
|
||||
|
||||
String requestUri = "";
|
||||
if (request != null) {
|
||||
entity.setRequestIp(IpUtils.getIpAddr(request));
|
||||
requestUri = request.getRequestURI();
|
||||
entity.setUserAgent(request.getHeader("User-Agent"));
|
||||
entity.setRequestParams(buildRequestParams(point, request));
|
||||
} else {
|
||||
entity.setRequestParams(buildRequestParams(point, null));
|
||||
}
|
||||
entity.setRequestUri(requestUri);
|
||||
entity.setRequestMethod(httpMethod);
|
||||
|
||||
String methodName = point.getSignature().getName();
|
||||
entity.setLogLevel(OperationLogResolver.resolveLevel(httpMethod, requestUri, methodName).getCode());
|
||||
entity.setOperationType(OperationLogResolver.resolveOperationType(httpMethod, requestUri, methodName));
|
||||
entity.setClassMethod(point.getTarget().getClass().getName() + "." + methodName);
|
||||
return entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建请求参数。<br>
|
||||
* GET 优先使用 queryString;其余序列化方法入参,过滤掉请求/响应等不可序列化对象。<br>
|
||||
*/
|
||||
private String buildRequestParams(ProceedingJoinPoint point, HttpServletRequest request) {
|
||||
try {
|
||||
if (request != null && "GET".equalsIgnoreCase(request.getMethod())) {
|
||||
String qs = request.getQueryString();
|
||||
if (StringUtils.isNotBlank(qs)) {
|
||||
return qs;
|
||||
}
|
||||
}
|
||||
Object[] args = point.getArgs();
|
||||
if (args == null || args.length == 0) {
|
||||
return "";
|
||||
}
|
||||
List<Object> filtered = new ArrayList<Object>();
|
||||
for (Object arg : args) {
|
||||
if (arg == null) {
|
||||
continue;
|
||||
}
|
||||
if (arg instanceof HttpServletRequest || arg instanceof HttpServletResponse
|
||||
|| arg instanceof HttpSession) {
|
||||
continue;
|
||||
}
|
||||
filtered.add(arg);
|
||||
}
|
||||
if (filtered.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
return JSON.toJSONString(filtered);
|
||||
} catch (Exception e) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入响应结果(GZIP 压缩 + 截断)。
|
||||
*/
|
||||
private void fillResponse(SysOperationLog entity, Object result, boolean saveResponse, int maxResponseSize) {
|
||||
if (!saveResponse || result == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
String resp = JSON.toJSONString(result);
|
||||
if (resp.length() > maxResponseSize) {
|
||||
resp = resp.substring(0, maxResponseSize) + "...[截断]";
|
||||
}
|
||||
entity.setResponseData(GzipUtil.compress(resp));
|
||||
} catch (Exception e) {
|
||||
log.warn("操作日志响应结果序列化失败, uri={}", entity.getRequestUri(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private HttpServletRequest getRequest() {
|
||||
ServletRequestAttributes attrs = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
|
||||
return attrs != null ? attrs.getRequest() : null;
|
||||
}
|
||||
|
||||
private String truncate(String text, int maxLen) {
|
||||
if (text == null) {
|
||||
return null;
|
||||
}
|
||||
return text.length() > maxLen ? text.substring(0, maxLen) : text;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user