第一步:Common 包里定义好注解和事件(只做一次) 1. 定义注解 @Log
package com.odb.core.log.annotation;
import java.lang.annotation.*;
import com.odb.core.log.enums.BusinessType;
@Target({ ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log
{
/** 模块名称 */
String title() default "";
/** 业务类型(0其它 1新增 2修改 3删除) */
BusinessType businessType() default BusinessType.OTHER;
/** 是否保存请求参数 */
boolean isSaveRequestData() default true;
/** 是否保存响应参数 */
boolean isSaveResponseData() default true;
}
- 定义切面 LogAspect (负责发布事件)
package com.odb.core.log.aspects;
import com.odb.core.log.SysOperLog;
import com.odb.core.log.annotation.Log;
import com.odb.core.log.event.SysOperLogEvent; // 事件类
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
// ... 其他 import 略
@Aspect
@Component
package com.odb.log.aspects;
import java.net.InetAddress; import java.net.UnknownHostException;
// 注意这里换成了 jakarta import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse;
import com.alibaba.fastjson2.JSON; import com.odb.log.SysOperLog; import com.odb.log.annotation.Log; import com.odb.log.service.AsyncLogService; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.slf4j.Logger; import org.slf4j.LoggerFactory; 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 org.springframework.web.multipart.MultipartFile;
@Aspect @Component public class LogAspect { private static final Logger log = LoggerFactory.getLogger(LogAspect.class);
private static final ThreadLocal<Long> TIME_THREADLOCAL = new ThreadLocal<>();
@Autowired
private AsyncLogService asyncLogService;
@Before("@annotation(controllerLog)")
public void doBefore(JoinPoint joinPoint, Log controllerLog)
{
TIME_THREADLOCAL.set(System.currentTimeMillis());
}
@AfterReturning(pointcut = "@annotation(controllerLog)", returning = "jsonResult")
public void doAfterReturning(JoinPoint joinPoint, Log controllerLog, Object jsonResult)
{
handleLog(joinPoint, controllerLog, null, jsonResult);
}
@AfterThrowing(value = "@annotation(controllerLog)", throwing = "e")
public void doAfterThrowing(JoinPoint joinPoint, Log controllerLog, Exception e)
{
handleLog(joinPoint, controllerLog, e, null);
}
protected void handleLog(final JoinPoint joinPoint, Log controllerLog, final Exception e, Object jsonResult)
{
try
{
// 获取 Request (Spring 自动适配 Jakarta)
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = (attributes != null) ? attributes.getRequest() : null;
SysOperLog operLog = new SysOperLog();
operLog.setStatus(1);
if (request != null)
{
operLog.setOperIp(getIpAddr(request));
operLog.setOperUrl(request.getRequestURI());
operLog.setRequestMethod(request.getMethod());
}
if (e != null)
{
operLog.setStatus(0);
operLog.setErrorMsg(e.getMessage());
}
String className = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
operLog.setMethod(className + "." + methodName + "()");
if (controllerLog != null)
{
operLog.setTitle(controllerLog.title());
operLog.setBusinessType(controllerLog.businessType().ordinal());
if (controllerLog.isSaveRequestData() && request != null)
{
Object[] args = joinPoint.getArgs();
String params = argsArrayToString(args);
operLog.setOperParam(params.length() > 2000 ? params.substring(0, 2000) : params);
}
}
if (jsonResult != null)
{
String json = JSON.toJSONString(jsonResult);
operLog.setJsonResult(json.length() > 2000 ? json.substring(0, 2000) : json);
}
Long startTime = TIME_THREADLOCAL.get();
if (startTime != null)
{
operLog.setCostTime(System.currentTimeMillis() - startTime);
}
// operLog.setOperName("admin");
asyncLogService.saveSysLog(operLog);
}
catch (Exception exp)
{
log.error("==前置通知异常==", exp);
}
finally
{
TIME_THREADLOCAL.remove();
}
}
private String argsArrayToString(Object[] paramsArray)
{
if (paramsArray == null || paramsArray.length == 0)
{
return "";
}
StringBuilder params = new StringBuilder();
for (Object o : paramsArray)
{
if (o != null && !isFilterObject(o))
{
try
{
params.append(JSON.toJSONString(o)).append(" ");
}
catch (Exception e)
{
}
}
}
return params.toString().trim();
}
public boolean isFilterObject(final Object o)
{
return o instanceof MultipartFile
|| o instanceof HttpServletRequest
|| o instanceof HttpServletResponse;
}
public static String getIpAddr(HttpServletRequest request)
{
if (request == null)
{
return "unknown";
}
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
{
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
{
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
{
ip = request.getRemoteAddr();
if ("127.0.0.1".equals(ip) || "0:0:0:0:0:0:0:1".equals(ip))
{
try
{
ip = InetAddress.getLocalHost().getHostAddress();
}
catch (UnknownHostException e)
{
e.printStackTrace();
}
}
}
return ip;
}
}
- 定义事件类 SysOperLogEvent
package com.odb.core.log.event;
import com.odb.core.log.SysOperLog;
import org.springframework.context.ApplicationEvent;
public class SysOperLogEvent extends ApplicationEvent
{
public SysOperLogEvent(SysOperLog source)
{
super(source);
}
public SysOperLog getSysOperLog()
{
return (SysOperLog) getSource();
}
}
第二步:在业务项目中使用(如 odb-admin) 1. 引入 Common 包依赖
<dependency>
<groupId>com.odb</groupId>
<artifactId>odb-common-core</artifactId>
<version>1.0.0</version>
</dependency>
- 在 Controller 方法上加注解 这是开发者日常使用的方式。
@RestController
@RequestMapping("/system/user")
public class SysUserController
{
@Autowired
private ISysUserService userService;
/**
* 新增用户
*/
@Log(title = "用户管理", businessType = BusinessType.INSERT) // 加在这里
@PostMapping
public AjaxResult add(@Validated @RequestBody SysUser user)
{
return toAjax(userService.insertUser(user));
}
/**
* 修改用户
*/
@Log(title = "用户管理", businessType = BusinessType.UPDATE) // 加在这里
@PutMapping
public AjaxResult edit(@Validated @RequestBody SysUser user)
{
return toAjax(userService.updateUser(user));
}
}
- 实现日志监听器(负责入库) 这是业务项目必须实现的,否则日志只会被发布,没人处理。
package com.odb.web.listener;
import com.odb.core.log.event.SysOperLogEvent;
import com.odb.core.log.SysOperLog;
import com.odb.system.service.ISysOperLogService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
/**
* 异步监听日志事件
*/
@Component
public class SysLogListener {
@Autowired
private SysOperLogMapper operLogMapper; // 业务项目的 Mapper
@Async // 异步处理
@EventListener
public void saveLog(SysOperLogEvent event) {
SysOperLog sysLog = event.getSysOperLog();
// 1. 填充操作人(只有业务项目才知道当前是谁登录)
try {
String username = SecurityUtils.getUsername();
sysLog.setOperName(username);
} catch (Exception e) {
sysLog.setOperName("unknown");
}
// 2. 入库
operLogMapper.insert(sysLog);
}
}
- 开启异步支持 在启动类上加 @EnableAsync
@EnableAsync
@SpringBootApplication
public class OdbApplication { ... }