init: UserBaseService (backend + deploy + scripts)
ci-userbase-service / build-and-push (push) Successful in 40s

This commit is contained in:
chenxing
2026-08-10 14:50:22 +08:00
commit 4ec19486e0
234 changed files with 15604 additions and 0 deletions
@@ -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;
}
}