09-SSO 微服务工程中用户行为日志的记录 (2107~2108~2109
import org.apache.ibatis.annotations.Mapper;
/**
用户行为日志数据层对象
*/
@Mapper
public interface LogMapper extends BaseMapper<Log> {
}
第二步:定义单元测试,对数据层方法进行单元测试
package com.jt;
import com.jt.system.dao.LogMapper;
import com.jt.system.pojo.Log;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.Date;
@SpringBootTest
public class LogMapperTests {
@Autowired
private LogMapper logMapper;
@Test
void testInsert(){
//构建用户行为日志对象(基于此对象存储一些用户行为日志,先用假数据)
Log log=new Log();
log.setUsername("cgb2107");
log.setIp("192.168.100.200");
log.setOperation("查询资源");
log.setMethod("pkg.ResourceController.doSelect");
log.setParams("");
log.setStatus(1);
log.setTime(100L);
log.setCreatedTime(new Date());
//将日志持久化到数据库
logMapper.insert(log);
}
}
[](()Service 逻辑实现
第一步:定义日志业务接口,例如:
package com.jt.system.service;
import com.jt.system.pojo.Log;
/**
用户行为日志业务逻辑接口定义
*/
public interface LogService {
/**
保存用户行为日志.
@param log
*/
void insertLog(Log log);
//.....
}
第二步:定义日志业务接口实现类,例如:
package com.jt.system.service.impl;
import com.jt.system.dao.LogMapper;
import com.jt.system.pojo.Log;
import com.jt.system.service.LogService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
@Service
public class LogServiceImpl implements LogService {
@Autowired
private LogMapper logMapper;
/**
@Async 描述的方法底层会异步执行(不由 web 服务线程执行,
而是交给 spring 自带的线程池中的线程去执行)但是 @Async 注解的
应用有个前提,需要启动类上启动异步执行(添加 @EnableAsync 注解描述).
优点:不会长时间阻塞 web 服务(例如 tomcat)线程
*/
@Async
@Override
public void insertLog(Log log) {
logMapper.insert(log);
}
}
[](()Controller 逻辑实现
第一步:定义 LogController 对象,用于实现日志控制逻辑,例如:
package com.jt.system.controller;
import com.jt.system.pojo.Log;
import com.jt.system.service.LogService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/log")
public class LogController {
@Autowired
private LogService logService;
@PostMapping
public void doInsertLog(@RequestBody Log log){
logService.insertLog(log);
}
}
第二步:启动服务,基于 postman 进行访问测试,例如:
[](()资源服务中行为日志操作设计
=======================================================================
[](()业务描述
在不修改目标业务方法代码实现的基础之上,访问目标方法时,获取用户行为日志.
[](()Pojo 逻辑对象定义
------------------------------------------------------------------- 《一线大厂 Java 面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义》无偿开源 威信搜索公众号【编程进阶路】 -
定义日志对象,用户封装获取到的用户行为日志,例如:
package com.jt.resource.pojo;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
基于此对象封装用户行为日志?
谁在什么时间执行了什么操作,访问了什么方法,传递了什么参数,访问时长是多少.
*/
@Data
public class Log implements Serializable {
private static final long serialVersionUID = 3054471551801044482L;
private Long id;
private String username;
private String operation;
private String method;
private String params;
private Long time;
private String ip;
private Date createdTime;
private Integer status;
private String error;
}
[](()切入点注解定义
构建一个自定义注解,名字为 RequiredLog,后续会基于此注解描述作为切入点,定义切入点方法.例如:
package com.jt.resource.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
定义 RequiredLog 注解,通过此注解对需要
进行日志记录的方法进行描述
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RequiredLog {
String value() default "";
}
[](()AOP 方式获取并记录日志
定义一个日志切面,基于此切面中的通知方法实现用户行为日志的获取和记录
package com.jt.resource.aspect;
import com.jt.resource.annotation.RequiredLog;
import com.jt.resource.pojo.Log;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.codehaus.jackson.map.ObjectMapper;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import java.lang.reflect.Method;
import java.util.Date;
/**
@Aspect 注解描述的类型为一个切面类型,在此类中可以定义:
1)切入点(切入扩展逻辑的位置~例如权限控制,日志记录,事务处理的位置),在
@Aspect 描述的类中,通常使用 @Pointcut 注解进行定义.使用切入点描述的方法为切入点方法.
2)通知方法(在切入点对应的目标方法执行前后要执行逻辑需要写到这样的方法中),在
@Aspect 描述的类中,通过 @Before,@After,@Aroud,@AfterReturning,@AfterThrowing
这样的注解进行描述.
a: @Before 切入点方法执行之前执行
b: @After 切入点方法执行之后执行(不管切入点方法是否执行成功了,它都会执行)
c: @Aroud 切入点方法执行之前和之后都可以执行(最重要)
d: @AfterReturning 切入点方法成功执行之后执行
e: @AfterThrowing 切入点方法执行时出了异常会执行
*/
@Aspect
@Component
public class LogAspect {
/**
@Pointcut 注解用于定义切入点,此注解中的内容为切入点表达式
@annotation 为注解方式的切入点表达式,此方式的表达式为一种细粒度的切入点表达式,
因为它可以精确到方法,例如我们现在使用 RequiredLog 注解描述方法时,由它描述的方法
就是一个切入点方法.
*/
@Pointcut("@annotation(com.jt.resource.annotation.RequiredLog)")
public void doLog(){
//此方法中不需要写任何内容,只负责承载 @Pointcut 注解
}
/**
@Around 注解描述的方法为 Aspect 中的一个环绕通知方法,在此方法
内部可以控制对目标方法的调用.
@param joinPoint 连接点对象,此对象封装了你要执行的切入点方法信息,可以基于
@return 目标执行链中切入点方法的返回值.
@throws Throwable
*/
@Around("doLog()")
public Object doAround(ProceedingJoinPoint joinPoint)throws Throwable{
int status=1;//状态
String error=null;//错误信息
long time=0l;//执行时长
long t1=System.currentTimeMillis();
try {
//手动调用目标执行链(这个执行链中包含切入点方法~目标方法)
Object result = joinPoint.proceed();
long t2=System.currentTimeMillis();
time=t2-t1;
return result;
}catch (Throwable e){
long t3=System.currentTimeMillis();
time=t3-t1;
status=0;
error=e.getMessage();
throw e;
}finally {
saveLog(joinPoint,time,status,error);
}
}
//存储用户行为日志
private void saveLog(ProceedingJoinPoint joinPoint,long time,
int status,String error)throws Throwable{
//1.获取用户行为日志
//1.1 获取目标对象类型(切入点方法所在类的类型)
Class<?> targetClass = joinPoint.getTarget().getClass();
//1.2.获取目标方法
//1.2.1 获取方法签名(包含方法信息,....)
MethodSignature signature = (MethodSignature)joinPoint.getSignature();
//1.2.2 获取方法对象
Method targetMethod=
评论