问题:
项目系统需要记录用户的关键操作日志,以便后期的系统维护,方便的查看问题,及时排除等原因。
分析:
作为一个日志记录功能,首先数据库新建一张表保存用户的操作关键字段,用户名,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();
object = point.proceed();
}
catch (Exception 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配置
<dependency>
<groupId>org.springframework.boot
</groupId>
<artifactId>spring-boot-starter-aop
</artifactId>
</dependency>
在application.properties文件里加这样一条配置
spring.aop.auto=true
在springboot项目里加这两条配置即可,就可以开启aop功能
ok, 这样每次执行想要拦截关键日志的操作都会被切面拦截