开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第8天, 点击查看活动详情
在日常开发过程中,我们写的最多的莫过于各式各样的CRUD接口,通常我们需要知道接口的调用状态、时间、结果,以便于我们发现问题的时候去排查问题,但是在接口的实现方法里面手动写sql去完成记录日志,这种做法显然是大可不必的,记录日志这种简单的事情,还是让我们交给AOP来实现吧。
AOP切面
熟悉AOP的小伙伴都知道,AOP是基于jdk的动态代理和cglib代理,其实我们使用最多的本地事务、切面注解的方法,都是基于cglib的,通过定义@Aspect 修饰的切面类,来对前、中、后期、环绕、异常的情况进行切入,这里我只需要定义@AfterReturning,在接口调用后记录操作日志。
定义@OpeLog注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OpeLog {
* 操作模块
String operationType() default "";
* 操作项(例:更新)
String operationItem() default "";
* 操作内容(知道更新具体内容的 -【未审核】 改为【已审核】)不知道更新内容的 存储请求参数
String operationContent() default "";
* 业务单号 - 字段名
* @return
String[] businessNoField() default {};
定义切面类
·通过反射机制获取类的方法,并且获取方法的属性,在获取接口请求方式post、get不同的传参方式,拿到请求入参保存到数据库对应的reqParam字段上,最后将字段保存在operationLog对象里,调用insert方法进行保存。
@Aspect
@Slf4j
@Component
public class OperationLogAspect extends AbstractController {
@Autowired
private OperationLogMapper operationLogMapper;
@AfterReturning(pointcut = "@annotation(com.xxx.annotation.OpeLog)", returning = "result")
public void saveOperationLog(JoinPoint joinPoint, Object result) {
try {
//获取签名
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
//获取方法
Method method = signature.getMethod();
//通过方法获取注解信息
OpeLog log = method.getAnnotation(OpeLog.class);
String operationType = log.operationType();
String operationItem = log.operationItem();
String operationContent = log.operationContent();
String[] businessNoField = log.businessNoField();
OperationLog operationLog = new OperationLog();
operationLog.setOperationType(operationType);
operationLog.setOperationItem(operationItem);
operationLog.setOperationContent(operationContent);
//获取当前用户信息
OpenSysUser currentUser = null;
//获取请求参数
Object[] args = joinPoint.getArgs();
for (Object arg : args) {
if (arg instanceof OpenSysUser) {
currentUser = (OpenSysUser) arg;
break;
if (currentUser == null) {
currentUser = this.getCurrentUser(false);
operationLog.setOrgNo(currentUser.getOrgNo());
operationLog.setOrgName(currentUser.getOrgName());
operationLog.setMainOrgName(currentUser.getMainOrgName());
operationLog.setMainOrgNo(currentUser.getMainOrgNo());
operationLog.setCreateUserNo(currentUser.getAccount());
operationLog.setCreateUserName(currentUser.getName());
operationLog.setCreateUserPhone(currentUser.getPhone());
* 保存请求参数
* 排除httpRequest和httpResponse
Stream<?> stream = ArrayUtils.isEmpty(args) ? Stream.empty() : Arrays.stream(args);
List<Object> logArgs = stream
.filter(arg -> (!(arg instanceof HttpServletRequest) && !(arg instanceof HttpServletResponse) && !(arg instanceof OpenSysUser)))
.collect(Collectors.toList());
String reqParams = JSON.toJSONString(logArgs);
operationLog.setReqParams(reqParams);
List<String> fieldList = new ArrayList<>(Arrays.asList(businessNoField));
if (!StringUtil.hasText(operationLog.getOperationContent())) {
fieldList.add("operationContent");
JSONArray jsonArray = JSONArray.parseArray(reqParams);
for (int i = 0; i < fieldList.size(); i++) {
for (int j = 0; j < jsonArray.size(); j++) {
String fieldName = fieldList.get(i);
Object obj = jsonArray.get(j);
if (obj != null) {
if (obj instanceof JSON) {
String jsonString = JSON.toJSONString(obj);
JSONObject jsonObject = JSON.parseObject(jsonString);
Object value = jsonObject.get(fieldName);
//设置属性值
this.setValue(i, operationLog, fieldName, value);
} else {
//设置属性值
this.setValue(i, operationLog, fieldName, obj);
// 因为get请求是按照属性的顺序设值的,所以设值完删掉该属性
jsonArray.remove(j);
break;
this.operationLogMapper.insert(operationLog);
} catch (Exception e) {
log.error("保存日志异常!", e);
* 设置属性值
* @param i
* @param operationLog
* @param fieldName
* @param value
private void setValue(int i, OperationLog operationLog, String fieldName, Object value) {
if (value != null && !"".equals(value)) {
if (i == 0) {
operationLog.setBusinessNo(String.valueOf(value));
} else {
ReflectUtil.setFieldValue(operationLog, fieldName, value);
日志表结构
Controller方法定义
在方法头加入@OpeLog注解,依次定义注解里面的属性,主要是对该方法的描述。
* 收款单导出
* @param
@PostMapping("/export")
@ApiOperationSupport(order = 2)
@ApiOperation(value = "收款单-导出", notes = "传入receiptVoucherExportDTO对象")
@OpeLog(operationType = "收款单", operationItem = "导出", operationContent = "导出收款单", businessNoField = {"receiptVoucherNo"})
public void export(@Valid @RequestBody ReceiptVoucherExportDTO receiptVoucherExportDTO, HttpServletResponse response) {
log.info("导出收款单 【入参】 export receiptVoucherExportDTO={}", receiptVoucherExportDTO);
receiptVoucherService.export(receiptVoucherExportDTO, this.getCurrentUser(true), response);
调用这个导出方法后,看控制台输出,执行完方法的时候记录操作日志
目前用这个切面记录操作日志的方式,还是比较常见的,通过mysql数据库进行操作记录的持久化,如果请求量比较大的情况下就不太支持这种方式了,还是建议使用ELK这样的日志搜索引擎,但是对于并发量不是很大的B端项目来说,这种记录操作日志的方式还是比较便捷的。