文章目录
日志记录
1.为什么要使用日志
在实际使用中我们需要记录哪个操作者在哪个时间点调用了哪个方法对哪个对象进行了修改,这个操作过程持续了多场时间以及方法的返回值是多少,以便于在出现问题时可以及时定位问题位置并作出应对.
2.日志包含的信息
日志信息包含:操作人、操作时间、执行方法的全类名、执行方法名、方法作用、方法运行时参数、返回值、方法执行时长
具体日志信息要根据需求进行修改
3. 创建日志准备工作
3.1 创建数据表
-- 操作日志表
create table operate_log(
id bigint unsigned primary key auto_increment comment 'ID',
class_name varchar(100) comment '操作的类名',
method_name varchar(100) comment '操作的方法名',
method_desc varchar(100) comment '方法用途',
method_params varchar(1000) comment '方法参数',
return_value varchar(2000) comment '返回值',
operate_user int unsigned comment '操作人ID',
operate_time datetime comment '操作时间',
cost_time bigint comment '方法执行耗时, 单位:ms'
) comment '操作日志表';
3.2 创建日志类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class OperateLog {
private Integer id; //ID
private String className; //操作类名
private String methodName; //操作方法名
private String methodDesc; //方法用途
private String methodParams; //操作方法参数
private String returnValue; //操作方法返回值
private Integer operateUser; //操作人ID
private LocalDateTime operateTime; //操作时间
private Long costTime; //操作耗时
}
3.3 创建日志的mapper
@Mapper
public interface OperateLogMapper {
//插入日志数据
@Insert("insert into operate_log (operate_user, operate_time, class_name, method_name,method_desc, method_params, return_value, cost_time) " +
"values (#{operateUser}, #{operateTime}, #{className}, #{methodName},#{methodDesc}, #{methodParams}, #{returnValue}, #{costTime});")
public void insert(OperateLog log);
}
4. 制作切面
4.1 自定义注解
//自定义注解
@Retention(RetentionPolicy.RUNTIME)//此注解保留到运行阶段
@Target(ElementType.METHOD)//此注解标注在方法上
public @interface LogAnno {
String methodDesc() default "";//用于声明方法的作用
}
4.2 标识切点
//使用注解标识在需要操作的方法上,以增删改查为例,我们需要标注的只有增删改
//使用方法
@LogAnno(methodDesc = "方法作用")
4.3 创建切面
@Component
@Aspect //切面
public class LogAspect {
@Autowired
private OperateLogMapper operateLogMapper;
//定义切点
@Pointcut("@annotation(com.itheima.anno.LogAnno)")
public void pt() {
}
//日志信息包含:操作时间,执行方法的全类名,执行方法名,方法运行参数,返回值,方法执行时长 操作人,方法作用
//定义环绕通知
@Around("pt()")
public Object around(ProceedingJoinPoint pjp) {
OperateLog log = new OperateLog();
//操作时间
log.setOperateTime(LocalDateTime.now());
//全类名
log.setClassName(pjp.getTarget().getClass().getName());
//获取 MethodSignature,并通过 MethodSignature获取方法
MethodSignature ms = (MethodSignature) pjp.getSignature();
Method method = ms.getMethod();
//方法名
log.setMethodName(method.getName());
//方法运行时参数
log.setMethodParams(Arrays.toString(pjp.getArgs()));
//方法作用(从LogAnno注解属性中获取)
log.setMethodDesc(method.getAnnotation(LogAnno.class).methodDesc());
//操作人id
log.setOperateUser(UserHolder.get());
long begin = 0;
try {
//开始时间
begin = System.currentTimeMillis();
//调用切点
Object result = pjp.proceed();
//返回值
log.setReturnValue(result.toString());
return result;
} catch (Throwable e) {
throw new RuntimeException(e);
} finally {
//结束时间
long end = System.currentTimeMillis();
//耗时
log.setCostTime(end - begin);
//保存
operateLogMapper.insert(log);
}
}
}
5.ThreadLocal
5.1 介绍
ThreadLocal称为线程局部变量,可以为每个线程单独提供一份存储空间
* 特点:线程之内,数据共享;线程之间,数据隔离
* 方法:
public void set(T value) 设置当前线程的线程局部变量的值
public T get() 返回当前线程所对应的线程局部变量的值
public void remove() 移除当前线程的线程局部变量
5.2 作用
由于ThreadLocal 在线程内数据共享,所以我们就可以在解析token时将token中的标识存储进 ThreadLocal,以便于我们可以在进行日志记录时取出,作为操作人信息的记录标志.
以下代码为ThreadLocal的一个应用
//添加工具类
//重写ThreadLocal 的方法,以便于使用
public class UserHolder {
//声明一个ThreadLocal
public static ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
//存储
public static void set(Integer id){
threadLocal.set(id);
}
//获取
public static Integer get(){
return threadLocal.get();
}
//移除
public static void remove(){
threadLocal.remove();
}
}
//在拦截器 解析 token 时获取标识
@Component
@Slf4j
public class LoginCheckInterceptor implements HandlerInterceptor {
@Autowired
private Gson gson;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//1.获取token
String token = request.getHeader("token");
//2.校验token
try {
Claims claims = JwtUtils.parseJWT(token);
//获取id
Integer id = (Integer) claims.get("id");
//将id存入threadLocal
UserHolder.set(id);
log.info(gson.toJson(claims));
}catch (Exception e){
//返回错误信息
String json = gson.toJson(Result.error("NOT_LOGIN"));
response.setContentType("application/json;charset=utf-8"); //返回格式和编码
response.getWriter().write(json);
return false;//禁止通行
}
return true;
}
//在请求结束时移除ThreadLocal,不然会与线程共享生命周期,因为key是弱引用,会在垃圾回收时被清理,而value是强引用,不会被清理,会造成key为null的value,很可能会造成内存泄露
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
UserHolder.remove();
}
}
在切面记录日志信息时使用,用于保存操作者的信息
//操作人id
log.setOperateUser(UserHolder.get());