一、前言
我们写完一个项目,运维时,如果出现了bug,我们需要查看控制台的日志,但是那个日志无关方法太多,查找不是很方便,还有就是一个项目上线之后,我们需要记录谁操作了那些功能,以防出现矛盾知道是谁点了这个功能造成的问题,由谁来负责,为了解决这两个问题,我在SpringBoot项目中使用了对控制层切面+注解的方法来实现将日志存储在数据库里面
二、实现详细源码
1、相关依赖
- springboot项目的依赖需要,还需要一个aop切面的依赖,mybatis的依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- aop -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
</dependencies>
2、配置文件
在application.properties文件里加这样一条配置
server.port: 8080
spring.aop.auto=true
3、我们首先需要准备mysql表
DROP TABLE IF EXISTS `syslog`;
CREATE TABLE `syslog` (
`id` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '主键',
`username` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户名',
`operation` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '操作',
`method` varchar(250) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '方法名',
`createDate` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '日志' ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;
4、创建数据库表之后就需要写实体类了
package com.sgsg.verification.entity;
/**
* @author 王恒杰
* @date 2022/10/25 14:54
* @Description:
*/
import java.io.Serializable;
public class Syslog implements Serializable {
private String id; //我用的UUIT
private String username; //用户名
private String operation; //操作
private String method; //方法名
private String createDate; //操作时间,这里可以使用Date来实现。我写的有个工具类。用的String接收
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getOperation() {
return operation;
}
public void setOperation(String operation) {
this.operation = operation;
}
public String getMethod() {
return method;
}
public void setMethod(String method) {
this.method = method;
}
public String getCreateDate() {
return createDate;
}
public void setCreateDate(String createDate) {
this.createDate = createDate;
}
}
- 这里如果用lombok的话,只需要一个@Data注解
@Data
public class Syslog implements Serializable {
private String id; //我用的全宇宙唯一的子串串、也是直接用的工具类
private String username; //用户名
private String operation; //操作
private String method; //方法名
private String createDate; //操作时间,这里可以使用Date来实现。我写的有个工具类。用的String接收
}
5、先写dao层和mapper层
(1)dao层
/**
* @author 王恒杰
* @date 2022/10/25 14:59
* @Description:
*/
@Mapper
public interface SysLogMapper {
/**
* 写入日志
* @param syslog
* @return
*/
int addLog(Syslog syslog);
}
(2)mapper层
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.sgsg.verification.dao.SysLogMapper">
<insert id="addLog" parameterType="com.sgsg.verification.entity.Syslog">
insert into syslog(id, username, operation, method, createDate)
values (#{id}, #{username}, #{operation}, #{method}, #{createDate})
</insert>
</mapper>
6、写业务层
- 接口层
import com.sgsg.verification.entity.Syslog;
/**
* @author 王恒杰
* @date 2022/10/25 14:58
* @Description:
*/
public interface SysLogService {
//写入日志
int addLog(Syslog syslog);
}
- 实现类
/**
* @author 王恒杰
* @date 2022/10/25 14:58
* @Description:
*/
@Service
public class SysLogServiceImpl implements SysLogService {
@Autowired
public SysLogMapper sysLogMapper;
//写入日志
@Override
public int addLog(Syslog syslog) {
return sysLogMapper.addLog(syslog);
}
}
7、切面核心方法
package com.sgsg.verification.aspect;
/**
* @author 王恒杰
* @date 2022/10/25 14:57
* @Description:
*/
import com.auth0.jwt.interfaces.Claim;
import com.sgsg.verification.dao.UserDao;
import com.sgsg.verification.entity.Result;
import com.sgsg.verification.entity.Syslog;
import com.sgsg.verification.entity.UserInfoEntity;
import com.sgsg.verification.log.Mylog;
import com.sgsg.verification.service.SysLogService;
import com.sgsg.verification.service.UserService;
import com.sgsg.verification.utils.JwtUtil;
import com.sgsg.verification.utils.UserInfoUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
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 java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import java.util.UUID;
/**
* 系统日志:切面处理类
*/
@Aspect
@Component
public class SysLogAspect {
@Autowired
private SysLogService sysLogService;//将数据写入数据库的操作
@Autowired
private UserDao userDao;
@Autowired
private JwtUtil jwtUtil;
//定义切点 @Pointcut
//在注解的位置切入代码
@Pointcut("@annotation(com.sgsg.verification.log.Mylog ))")
public void logPoinCut() {
}
//切面 配置通知
@AfterReturning("logPoinCut()")
public void saveSysLog(JoinPoint joinPoint) {
System.out.println("切面。。。。。");
//保存日志
Syslog sysLog = new Syslog();
//从切面织入点处通过反射机制获取织入点处的方法
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
//获取切入点所在的方法
Method method = signature.getMethod();
//获取操作
Mylog myLog = method.getAnnotation(Mylog.class);
if (myLog != null) {
String value = myLog.value();
sysLog.setOperation(value);//保存获取的操作
}
//设置id
String id = UUID.randomUUID().toString();
sysLog.setId(id);
//获取请求的类名
String className = joinPoint.getTarget().getClass().getName();
//获取请求的方法名
String methodName = method.getName();
sysLog.setMethod(className + "." + methodName);
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy年MM月dd日");
sysLog.setCreateDate(simpleDateFormat.format(new Date()));
//获取用户名
//拿到当前用户的信息、我这里使用的Token。直接从Token中获取当前用户信息
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
assert attributes != null;
HttpServletRequest request = attributes.getRequest();
// 从 http 请求头中取出 token
String token = request.getHeader("authorization");
// 解析token并获取token中的用户信息
Map<String, Claim> claims = jwtUtil.verity(token);
Claim userId = claims.get("userId");
//获取用户
UserInfoEntity user = userDao.getInfoById(userId.asInt());
sysLog.setUsername(user.getCname());
//调用service保存SysLog实体类到数据库
sysLogService.addLog(sysLog);
}
}
8、我们自己定义注解类
package com.sgsg.verification.log;
/**
* @author 王恒杰
* @date 2022/10/25 14:56
* @Description: 自定义注解类
*/
import java.lang.annotation.*;
@Target(ElementType.METHOD) //注解放置的目标位置,METHOD是可注解在方法级别上
@Retention(RetentionPolicy.RUNTIME) //注解在哪个阶段执行
@Documented //生成文档
public @interface Mylog {
String value() default "";
}
9、在控制层使用我们自定义的注解
package com.sgsg.verification.controller;
import com.sgsg.verification.entity.Result;
import com.sgsg.verification.log.Mylog;
import com.sgsg.verification.service.EnumService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author 王恒杰
* @date 2022/10/6 12:30
* @Description:
*/
@Slf4j
@RestController
@RequestMapping("/enums")
public class EnumController {
@Autowired
private EnumService enumService;
@Mylog(value = "获取枚举")
@GetMapping("/selectAll")
public Result selectAll() {
return enumService.selectAll();
}
}