看完本文可能会对你有帮助的点:
1,如何记录用户操作日志。
2,更加深入了解Spring的面向切面编程。
3,更加了解自定义注解类。
具体实现步骤大致四步:
1,创建日志记录实体和数据表。
2,自定义注解类。
3,创建切面类用于写日志记录的具体操作逻辑
4,在业务方法上添加自定义注解实现功能
效果图(本人是基于Springboot+JPA+Layui做的)
具体实现如下:
第一步,创建实体如下:
/**
* 系统操作日志记录实体
*/
@Table(name = "sys_operation_log ")
@Entity
public class SysOperationLog {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
/*数据创建时间*/
private Date createTime;
/*用户id*/
private Long userId;
/*用户昵称*/
private String userName;
/*操作类型*/
@Enumerated(EnumType.STRING)
private OperationType operationType;
/*访问方法*/
private String method;
/*方法参数*/
@Lob
@Column(columnDefinition="text")
private String params;
/*方法简述*/
private String operationDescribe;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public OperationType getOperationType() {
return operationType;
}
public void setOperationType(OperationType operationType) {
this.operationType = operationType;
}
public String getMethod() {
return method;
}
public void setMethod(String method) {
this.method = method;
}
public String getParams() {
return params;
}
public void setParams(String params) {
this.params = params;
}
public String getOperationDescribe() {
return operationDescribe;
}
public void setOperationDescribe(String operationDescribe) {
this.operationDescribe = operationDescribe;
}
}
//与实体操作类型属性关联的枚举
public enum OperationType {
/**
* 操作类型
*/
INSERT,//新增
DELETE,//删除
UPDATE,//更新
EXPORT,//导出
SELECT,//查询
LOGON,//登录
OTHER;//其他
}
第二步,自定义注解
/**
* 自定义注解类
*/
@Target(ElementType.METHOD) //注解放置的目标位置,METHOD是可注解在方法级别上
@Retention(RetentionPolicy.RUNTIME) //注解在哪个阶段执行
@Documented //生成文档
public @interface Log {
String describe() default "";//操作简述
OperationType operationType() default OperationType.OTHER;//操作类型
}
第三步,创建切面类,注意,此demo中重点和公共的导包已列出,有关公司的包路径未列出,需要客官自行写,见谅。
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import com.alibaba.fastjson.JSONObject;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.lang.reflect.Method;
import java.util.Date;
/**
* 系统日志:切面处理类
*/
@Aspect
@Component
public class SysLogAspects {
private static final Logger logger = LoggerFactory.getLogger(SysLogAspects.class);
@Autowired
private SysOperationLogJpaService sysOperationLogService;//这个service主要用来保存日志记录实体到数据库
@Autowired
private BaseController baseController;//系统公共controller
@Autowired
private UserJpaService userJpaService;//这个service主要用来保存和查询用户表
//定义切点 @Pointcut
//在注解的位置切入代码
@Pointcut("@annotation(com.baoji.log.service.Log)")
public void logPoinCut() {
}
/*切面 配置通知,各注解作用
*@Before:前置通知,在方法执行之前执行
*@After:后置通知,在方法执行之后执行
*@AfterRunning:返回通知,在方法返回结果之后执行,用这个注解参数会随方法改变,例新增一个实体,参数是一个id为null的,用这个注解后就会赋上真实的id
*@AfterThrowing:异常通知,在方法抛出异常之后执行
*@Around:环绕通知,围绕着方法执行
* */
@Before("logPoinCut()")
public void saveSysLog(JoinPoint joinPoint) {
//从切面织入点处通过反射机制获取织入点处的方法
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
//获取切入点所在的方法
Method method = signature.getMethod();
//操作简述
String describe="";
//操作类型的枚举
OperationType operationType=OperationType.OTHER;
Log log = method.getAnnotation(Log.class);
if (log != null) {
describe = log.describe()!=null? log.describe():"未知";
operationType= log.operationType()!=null?log.operationType():OperationType.OTHER;
}
//获取请求的类名
String className = joinPoint.getTarget().getClass().getName();
//获取请求的方法名
String methodName = method.getName();
//请求的参数
Object[] args = joinPoint.getArgs();
Object[] arguments = new Object[args.length];
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof ServletRequest || args[i] instanceof ServletResponse || args[i] instanceof MultipartFile) {
//ServletRequest不能序列化,从入参里排除,否则报异常:java.lang.IllegalStateException: It is illegal to call this method if the current request is not in asynchronous mode (i.e. isAsyncStarted() returns false)
//ServletResponse不能序列化 从入参里排除,否则报异常:java.lang.IllegalStateException: getOutputStream() has already been called for this response
continue;
}
arguments[i] = args[i];
}
//将参数所在的数组转换成json
String params = "";
if (arguments != null) {
try {
params = JSONObject.toJSONString(arguments);
} catch (Exception e) {
params = arguments.toString();
}
}
int userid=0;
String nickName="";
try {
/*用户还未登录*/
if(operationType==OperationType.LOGON){
/*获取将要登录的用户的用户名*/
String userName=StringUtil.safeToString(arguments[0],"");
/*根据用户名获取昵称*/
nickName=userJpaService.getNickNameByName(userName);
/*根据用户名获取用户id*/
userid=userJpaService.getUserIdByName(userName);
/*登录时参数包含用户名和密码等关键信息,为安全起见,不记录参数*/
params="******";
}else{
/*获取系统当前登录用户的id*/
userid= baseController.getLoginUserId();
/*获取系统当前登录用户的昵称*/
nickName=userJpaService.getUserNameById(userid);
}
} catch (OperationFailedException e) {
logger.info("获取当前用户出错:{}", e.getMessage());
}
//保存日志
SysOperationLog sysLog = new SysOperationLog();
sysLog.setOperationDescribe(describe);
sysLog.setMethod(className + "." + methodName);
sysLog.setParams(params);
sysLog.setUserId(new Long((long) userid));
sysLog.setUserName(nickName);
sysLog.setOperationType(operationType);
sysLog.setCreateTime(new Date());
logger.info("【操作记录】用户:{},在:{},进行了:{}操作,调用方法:{}",sysLog.getUserName(), DateUtil.formatDate(sysLog.getCreateTime()),sysLog.getOperationDescribe(),sysLog.getMethod());
sysOperationLogService.save(sysLog);
}
}
第四步,在业务方法上添加注解,如下
@PutMapping("/psw")
@Log(describe = "修改自己密码",operationType= OperationType.UPDATE)
public String updatePsw(String oldPsw, String newPsw)
//具体业务逻辑
return "修改失败";
}