Spring Boot 通过AOP+自定义注解实现日志管理
操作日志的记录:
记录用户的行为(Controller 请求日志),这叫做业务运行日志
业务运行日志的作用:
1,记录用户的行为 用于后续的分析
2,记录用户的所有的操作
1:操作日志数据结构表
SET NAMES utf8mb4;SET FOREIGN_KEY_CHECKS=0;DROPTABLEIFEXISTS`xcy_operation_log`;CREATETABLE`xcy_operation_log`(`id`bigint(10)UNSIGNEDNOTNULLAUTO_INCREMENTCOMMENT'操作日志自增id',`user_id`bigint(10)UNSIGNEDNOTNULLDEFAULT0COMMENT'操作人id',`ad_account`varchar(50)CHARACTERSET utf8mb4COLLATE utf8mb4_general_ciNOTNULLCOMMENT'操作人AD账号',`operation_ip`varchar(100)CHARACTERSET utf8mb4COLLATE utf8mb4_general_ciNOTNULLDEFAULT''COMMENT'请求ip地址',`operation_model`varchar(100)CHARACTERSET utf8mb4COLLATE utf8mb4_general_ciNOTNULLDEFAULT''COMMENT'操作模块',`operation_type`varchar(100)CHARACTERSET utf8mb4COLLATE utf8mb4_general_ciNOTNULLDEFAULT''COMMENT'操作类型',`operation_desc`varchar(255)CHARACTERSET utf8mb4COLLATE utf8mb4_general_ciNOTNULLDEFAULT''COMMENT'操作描述',`operation_time`datetime(0)NOTNULLDEFAULTCURRENT_TIMESTAMP(0)COMMENT'操作时间',`operation_uri`varchar(255)CHARACTERSET utf8mb4COLLATE utf8mb4_general_ciNOTNULLDEFAULT''COMMENT'请求uri',`operation_method`varchar(255)CHARACTERSET utf8mb4COLLATE utf8mb4_general_ciNOTNULLDEFAULT''COMMENT'请求方法',`request_param`textCHARACTERSET utf8mb4COLLATE utf8mb4_general_ciNULLCOMMENT'请求参数',`response_param`longtextCHARACTERSET utf8mb4COLLATE utf8mb4_general_ciNULLCOMMENT'响应参数',`result`textCHARACTERSET utf8mb4COLLATE utf8mb4_general_ciNULLCOMMENT'操作结果',`create_time`datetime(0)NOTNULLCOMMENT'创建时间',`update_time`datetime(0)NOTNULLCOMMENT'修改时间',PRIMARYKEY(`id`)USINGBTREE,INDEX`user_id`(`user_id`)USINGBTREE,INDEX`operation_model`(`operation_model`)USINGBTREE,INDEX`ad_account`(`ad_account`)USINGBTREE,INDEX`id`(`id`)USINGBTREE)ENGINE=InnoDBAUTO_INCREMENT=1CHARACTERSET= utf8mb4COLLATE= utf8mb4_general_ciCOMMENT='操作日志表' ROW_FORMAT= Dynamic;SET FOREIGN_KEY_CHECKS=1;
2:操作日志实体类
@Data@EqualsAndHashCode(callSuper=false)@Accessors(chain=true)@TableName("xcy_operation_log")publicclassOperationLogimplementsSerializable{privatestaticfinallong serialVersionUID=1L;/**
* 操作日志自增id
*/@TableId(value="id", type=IdType.AUTO)privateLong id;/**
* 操作人id
*/privateLong userId;/**
* 操作人AD账号
*/privateString adAccount;/**
* 请求ip地址
*/privateString operationIp;/**
* 操作模块
*/privateString operationModel;/**
* 操作类型
*/privateString operationType;/**
* 操作描述
*/privateString operationDesc;/**
* 操作时间
*/privateLocalDateTime operationTime;/**
* 请求uri
*/privateString operationUri;/**
* 请求方法
*/privateString operationMethod;/**
* 请求参数
*/privateString requestParam;/**
* 响应参数
*/privateString responseParam;/**
* 操作结果
*/privateString result;/**
* 创建时间
*/privateLocalDateTime createTime;/**
* 修改时间
*/privateLocalDateTime updateTime;}
3:操作日志服务类
publicinterfaceOperationLogServiceextendsIService<OperationLog>{}
4:操作日志实现类
@ServicepublicclassOperationLogServiceImplextendsServiceImpl<OperationLogMapper,OperationLog>implementsOperationLogService{}
5:mapper接口层
publicinterfaceOperationLogMapperextendsBaseMapper<OperationLog>{}
6:mapper.xml层
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPEmapperPUBLIC"-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mappernamespace="com.xc.contract.dao.mapper.OperationLogMapper"></mapper>
7:操作日志注解类
@Target(ElementType.METHOD)//注解放置的目标位置即方法级别@Retention(RetentionPolicy.RUNTIME)//注解在哪个阶段执行@Documentedpublic@interfaceOperLog{/**
* 操作模块
* @return
*/Stringmodule()default"";/**
* 操作类型
* @return
*/Stringtype()default"";/**
* 操作说明
* @return
*/Stringdesc()default"";}
8:操作日志切面处理类
@Slf4j@Aspect@ComponentpublicclassOperationLogAspect{@AutowiredprivateOperationLogService logService;/**
* 设置操作日志切入点 记录操作日志 在注解的位置切入代码
*/@Pointcut("@annotation(OperLog)")publicvoidoperLogPoinCut(){}/**
* 正常返回通知,拦截用户操作日志,连接点正常执行完成后执行, 如果连接点抛出异常,则不会执行
* @param joinPoint 切入点
* @param data 返回结果
*/@AfterReturning(value="operLogPoinCut()", returning="data")publicvoidsaveOperLog(JoinPoint joinPoint,Object data){SessionPayload sessionPayload=SessionPayload.current();// 获取RequestAttributesRequestAttributes requestAttributes=RequestContextHolder.getRequestAttributes();// 从获取RequestAttributes中获取HttpServletRequest的信息HttpServletRequest request=(HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);//操作日志OperationLog operationLog=newOperationLog();try{// 从切面织入点处通过反射机制获取织入点处的方法MethodSignature signature=(MethodSignature) joinPoint.getSignature();//获取切入点所在的方法Method method= signature.getMethod();OperLog annotation= method.getAnnotation(OperLog.class);if(annotation!=null){//操作模块
operationLog.setOperationModel(annotation.module());//操作类型
operationLog.setOperationType(annotation.type());//操作描述
operationLog.setOperationDesc(annotation.desc());}//获取请求的类名String className= joinPoint.getTarget().getClass().getName();String methodName= method.getName();//请求的方法名
methodName= className+"."+methodName;//获取请求参数Map<String,String> map=converMap(request.getParameterMap());String requestParam=JsonUtil.toJson(map);//操作方法
operationLog.setOperationMethod(methodName);//请求参数
operationLog.setRequestParam(requestParam);//响应参数
operationLog.setResponseParam(JsonUtil.toJson(data));//操作员
operationLog.setUserId(sessionPayload.getUserId());
operationLog.setAdAccount(sessionPayload.getAccount());
operationLog.setOperationTime(LocalDateTime.now());//请求uri
operationLog.setOperationUri(request.getRequestURI());//请求ip
operationLog.setOperationIp(IpAddressUtil.getIpAddr(request));//操作结果ReturnData returnData=(ReturnData) data;if(returnData!=null){
operationLog.setResult(returnData.getMessage());}else{
operationLog.setResult("");}//创建时间
operationLog.setCreateTime(LocalDateTime.now());//修改时间
operationLog.setUpdateTime(LocalDateTime.now());//保存日志
logService.save(operationLog);}catch(Exception e){
log.error(e.getMessage());}}/**
* 转换request参数
* @param paramMap
* @return
*/publicMap<String,String>converMap(Map<String,String[]> paramMap){Map<String,String> rtnMap=newHashMap<String,String>();for(String key: paramMap.keySet()){
rtnMap.put(key, paramMap.get(key)[0]);}return rtnMap;}}
9:RequestWrapper类(无需)
RequestWrapper继承了HttpServletRequestWrapper,初始化的时候读取Request的整个请求体。然后重载了getInputStream和getReader方法,这两个方法会从类变量body中读取内容
importjavax.servlet.ReadListener;importjavax.servlet.ServletInputStream;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletRequestWrapper;importjava.io.*;publicclassRequestWrapperextendsHttpServletRequestWrapper{privatefinalString body;publicRequestWrapper(HttpServletRequest request){super(request);StringBuilder stringBuilder=newStringBuilder();BufferedReader bufferedReader=null;InputStream inputStream=null;try{
inputStream= request.getInputStream();if(inputStream!=null){
bufferedReader=newBufferedReader(newInputStreamReader(inputStream));char[] charBuffer=newchar[128];int bytesRead=-1;while((bytesRead= bufferedReader.read(charBuffer))>0){
stringBuilder.append(charBuffer,0, bytesRead);}}else{
stringBuilder.append("");}}catch(IOException ex){}finally{if(inputStream!=null){try{
inputStream.close();}catch(IOException e){
e.printStackTrace();}}if(bufferedReader!=null){try{
bufferedReader.close();}catch(IOException e){
e.printStackTrace();}}}
body= stringBuilder.toString();}@OverridepublicServletInputStreamgetInputStream()throwsIOException{finalByteArrayInputStream byteArrayInputStream=newByteArrayInputStream(body.getBytes());ServletInputStream servletInputStream=newServletInputStream(){@OverridepublicbooleanisFinished(){returnfalse;}@OverridepublicbooleanisReady(){returnfalse;}@OverridepublicvoidsetReadListener(ReadListener readListener){}@Overridepublicintread()throwsIOException{return byteArrayInputStream.read();}};return servletInputStream;}@OverridepublicBufferedReadergetReader()throwsIOException{returnnewBufferedReader(newInputStreamReader(this.getInputStream()));}publicStringgetBody(){returnthis.body;}}
10:使用你定义的自定义注解