问题:
项目系统需要记录用户的关键操作日志,以便后期的系统维护,方便的查看问题,及时排除等原因。
分析:
作为一个日志记录功能,首先数据库新建一张表保存用户的操作关键字段,用户名,ip,操作描述,时间,日志id
采用技术:
第一种:新建一个日志业务实现,在操作发生时进行联动同,缺点是耦合太紧密,无用代码增多,后期代码臃肿,改动时地方分散,不利于维护
第二种:使用spring 的 aop 技术进行切面切入由于本身系统结构不规范,参数,方法名没有一致的样式,使用正则匹配方法名不是很方便,本身日志记录也只是记录关键操作,api全部切入的话,就不需要这个功能了,必须有针对性的记录关键操作日志
第三种:使用spring 的 aop技术切到自定义注解上,针对不同注解标志进行参数解析,记录日志,缺点是要针对每个不同的注解标志进行分别取注解标志,获取参数进行日志记录输出
采用第三种方法
思路
1. 通过自定义注解,注解到需要aop切入的方法上
2. 声明一个aspect切入面,注入数据层dao, 将上面的注解类设成切入点, 通过反射获取到自定义注解上的某个属性,来区分是不同的记录日志需求。进行不同程度的封装Log实体对象。
3. 然后通过数据层,写入到日志表。
步骤
- 自定义方法注解类,注解到方法上,用于标识需要切入的点
/**
* 日志切面注解
*/
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MethodLog {
/**
* 该注解作用于方法上时需要备注信息
*/
String remark() default "";
String operType() default "0";
}
- 切面的实现
/**
* 日志切面实现
*/
@Component
@Aspect
public class LogService {
@Autowired
private OptLogDao dao;
public LogService() {
System.out.println("Aop");
}
/**
* 切点
*/
@Pointcut("@annotation(com.education.anno.MethodLog)")
public void methodCachePointcut() {
}
/**
* 切面
*
* @param point
* @return
* @throws Throwable
*/
@Around("methodCachePointcut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder
.getRequestAttributes()).getRequest();
String ip = getIp(request);
User user = UserUtil.getCurrentUser();
String methodRemark = getMthodRemark(point);
Object[] method_param;
Object object;
try {
method_param = point.getArgs(); //获取方法参数
// String param=(String) point.proceed(point.getArgs());
object = point.proceed();
} catch (Exception e) {
// 异常处理记录日志..log.error(e);
throw e;
}
LiveOptLog optLog = new LiveOptLog();
optLog.setUserId(user.getId().intValue());
optLog.setIp(ip);
optLog.setUserName(user.getUsername());
/**
* 课程操作
*/
if ("新增课程".equals(methodRemark) || "修改课程".equals(methodRemark) || "删除课程".equals(methodRemark)) {
if (method_param[0] instanceof LiveCourseInfo) {
LiveCourseInfo courseInfo = (LiveCourseInfo) method_param[0];
optLog.setDescription("用户 " + user.getUsername() + " " + methodRemark + "id: " + courseInfo.getId());
}
}
/**
* 课节操作
*/
if ("新增课节".equals(methodRemark) || "修改课节".equals(methodRemark) || "删除课节".equals(methodRemark)) {
if (method_param[0] instanceof LiveCourseLessonInfo) {
LiveCourseLessonInfo lessonInfo = (LiveCourseLessonInfo) method_param[0];
optLog.setDescription("用户 " + user.getUsername() + " " + methodRemark + "id: " + lessonInfo.getId());
}
}
dao.insertSelective(optLog);
return object;
}
/**
* 获取请求ip
*
* @param request
* @return
*/
public static String getIp(HttpServletRequest request) {
String ip = request.getHeader("X-Forwarded-For");
if (StringUtils.isNotEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)) {
int index = ip.indexOf(",");
if (index != -1) {
return ip.equals("0:0:0:0:0:0:0:1")?"127.0.0.1":ip.substring(0, index);
} else {
return ip.equals("0:0:0:0:0:0:0:1")?"127.0.0.1":ip;
}
}
ip = request.getHeader("X-Real-IP");
if (StringUtils.isNotEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)) {
return ip;
}
return request.getRemoteAddr().equals("0:0:0:0:0:0:0:1")?"127.0.0.1":ip;
}
/**
* 获取方法中的中文备注
*
* @param joinPoint
* @return
* @throws Exception
*/
public static String getMthodRemark(ProceedingJoinPoint joinPoint) throws Exception {
String targetName = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
Object[] arguments = joinPoint.getArgs();
Class targetClass = Class.forName(targetName);
Method[] method = targetClass.getMethods();
String methode = "";
for (Method m : method) {
if (m.getName().equals(methodName)) {
Class[] tmpCs = m.getParameterTypes();
if (tmpCs.length == arguments.length) {
MethodLog methodCache = m.getAnnotation(MethodLog.class);
if (methodCache != null) {
methode = methodCache.remark();
}
break;
}
}
}
return methode;
}
- 日志实体, 根据需求自定义
@Table(name = "opt_log")
@Data
@NoArgsConstructor
@AllArgsConstructor
@ExcelTitles({"序号", "ID" ,"操作人","描述", "时间", "IP"})
public class LiveOptLog extends BaseModel {
/**
* 日志类型
*/
private String type;
/**
* 用户id
*/
@Column(name = "user_id")
private Integer userId;
@Column(name = "user_name")
private String userName;
/**
* 内容
*/
private String description;
private String ip;
private Date createtime;
}
- 日志切入, 添加注解
@PostMapping
@ApiOperation(value = "保存")
@MethodLog(remark = "新增课程")
public CourseInfo save(@RequestBody CourseInfo courseInfo) {
courseInfoServiceImpl.save(courseInfo);
return courseInfo;
}
- 加入springboot配置
<!--spring切面aop依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
在application.properties文件里加这样一条配置
spring.aop.auto=true
在springboot项目里加这两条配置即可,就可以开启aop功能
ok, 这样每次执行想要拦截关键日志的操作都会被切面拦截