问题:
系统需要记录用户的关键操作日志,以便后期的系统维护,方便的查看问题,及时排除
分析:
(1)保存字段:作为一个日志记录功能,首先数据库新建一张表保存用户的操作关键字段,
用户名,ip,操作描述,时间,日志id
(2)采用技术:
第一种:新建一个日志业务实现,在操作发生时进行联动
缺点是耦合太紧密,无用代码增多,后期代码臃肿,改动时地方分散,不利于维护
第二种:使用spring 的 aop 技术进行切面切入
由于本身系统结构不规范,参数,方法名没有一致的样式,使用正则匹配方法名不是很方便
本身日志记录也只是记录关键操作,api全部切入的话,就不需要这个功能了
必须有针对性的记录关键操作日志
第三种:使用spring 的 aop 技术切到自定义注解上,针对不同注解标志进行参数解析,记录日志
缺点是要针对每个不同的注解标志进行分别取注解标志,获取参数进行日志记录输出
解决:
由于本身系统的不规范,目前采用第三种办法进行关键日志的记录
我的日志记录表sql:
CREATE TABLE `ts_sys_operation_log` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`login_num` varchar(30) DEFAULT NULL COMMENT '登录工号',
`login_name` varchar(32) DEFAULT NULL COMMENT '登录用户名',
`method_name` varchar(500) NOT NULL COMMENT '方法名',
`method_remark` varchar(100) DEFAULT NULL COMMENT '方法描述',
`operation_content` varchar(1000) DEFAULT NULL COMMENT '操作内容',
`ip_address` varchar(30) NOT NULL COMMENT 'IP地址',
`create_tm` varchar(30) NOT NULL COMMENT '创建时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='系统操作日志表';
(1)首先新建 注解类【MethodLog】
package org.triber.portal.service.logAop;
import java.lang.annotation.*;
/**
* 日志切面注解
*/
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MethodLog {
String remark() default "";
String operType() default "0";
// String desc() default "";
}
(2)新建切面实现类【LogService】
import com.alibaba.fastjson.JSON;
import com.github.pagehelper.StringUtil;
import com.google.common.collect.Maps;
import com.sf.ucs.nav.mapper.logaop.SysOperationLogMapper;
import com.sf.ucs.nav.mapper.prd.ProduceMapper;
import com.sf.ucs.nav.model.logaop.SysOperationLog;
import com.sf.ucs.sac.date.DateUtil;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cglib.beans.BeanMap;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* 日志切面实现
*/
@Component
@Aspect
public class LogService {
private Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
private SysOperationLogMapper sysOperationLogMapper;
@Autowired
private ProduceMapper produceMapper;
public LogService() {
}
/**
* 切点
*/
@Pointcut("@annotation(com.sf.ucs.nav.service.logaop.MethodLog)")
public void methodCachePointcut() { }
/**
* 切面
*
* @param point
* @return
* @throws Throwable
*/
@Around("methodCachePointcut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
Object object = point.proceed();
try {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder
.getRequestAttributes()).getRequest();
String ip = getIp(request);
String methodRemark = getMthodRemark(point);
String methodName = point.getSignature().getName();
String packages = point.getThis().getClass().getName();
if (packages.indexOf("$$EnhancerBySpringCGLIB$$") > -1) { // 如果是CGLIB动态生成的类
packages = packages.substring(0, packages.indexOf("$$"));
}
Object[] method_param = point.getArgs(); //获取方法参数
Map<String,Object> parammMap = null;
if(method_param[0] instanceof Map){
parammMap = (Map) method_param[0];
}else if(method_param[0].getClass() instanceof Class){
parammMap = beanToMap(method_param[0]);
}else {
parammMap = Maps.newHashMap();
}
String loginNum;
String loginName = "";
if(parammMap.get("creater") != null){
loginNum = (String) parammMap.get("creater");
}else if(parammMap.get("updater") != null){
loginNum = (String) parammMap.get("updater");
}else if(parammMap.get("createEmp") != null){
loginNum = (String) parammMap.get("createEmp");
}else if(parammMap.get("updaterEmp") != null){
loginNum = (String) parammMap.get("updaterEmp");
}else {
loginNum = "匿名用户";
}
if(!"匿名用户".equals(loginNum)){
Map<String, String> userMap = produceMapper.getUserInfo(loginNum);
if(userMap != null){
loginName = userMap.get("userName");
}
}
SysOperationLog sysLog = new SysOperationLog();
sysLog.setLoginNum(loginNum);
sysLog.setLoginName(loginName);
sysLog.setMethodName(packages + "." + methodName);
sysLog.setMethodRemark(methodRemark);
sysLog.setIpAddress(ip);
sysLog.setCreateTm(DateUtil.getNowStr());
/**
* 操作
*/
if("".equals(methodRemark) || null == methodRemark){
sysLog.setOperatingContent(methodRemark + ", 操作参数: " + JSON.toJSON(method_param[0]));
}else if(methodRemark.startsWith("新增")){
sysLog.setOperatingContent(methodRemark + ", 操作参数: " + JSON.toJSON(method_param[0]));
}else if(methodRemark.startsWith("修改")){
sysLog.setOperatingContent(methodRemark + ", 操作参数: " + JSON.toJSON(method_param[0]));
}else if(methodRemark.startsWith("删除")){
sysLog.setOperatingContent(methodRemark + ", 操作参数: " + JSON.toJSON(method_param[0]));
}else if(methodRemark.startsWith("查询")){
sysLog.setOperatingContent(methodRemark + ", 操作参数: " + JSON.toJSON(method_param[0]));
}else if(methodRemark.startsWith("导出")){
sysLog.setOperatingContent(methodRemark + ", 操作参数: " + JSON.toJSON(method_param[0]));
}else {
sysLog.setOperatingContent(methodRemark + ", 操作参数: " + JSON.toJSON(method_param[0]));
}
sysOperationLogMapper.insert(sysLog);
logger.info("日志实体:"+sysLog.getLoginName()+sysLog.getMethodRemark()+sysLog.getOperatingContent());
} catch (Exception e) {
// 异常处理记录日志..log.error(e);
logger.error("系统操作日志新增异常:{}", e.getMessage());
}
return object;
}
/**
* 将对象装换为map
*
* @param bean
* @return
*/
public static <T> Map<String, Object> beanToMap(T bean) {
Map<String, Object> map = Maps.newHashMap();
if (bean != null) {
BeanMap beanMap = BeanMap.create(bean);
for (Object key : beanMap.keySet()) {
map.put(key + "", beanMap.get(key));
}
}
return map;
}
/**
* 方法异常时调用
*
* @param ex
*/
public void afterThrowing(Exception ex) {
System.out.println("afterThrowing");
System.out.println(ex);
}
/**
* 获取方法中的中文备注
*
* @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;
}
/**
* 获取请求ip
*
* @param request
* @return
*/
public static String getIp(HttpServletRequest request) {
String ip = request.getHeader("X-Forwarded-For");
if (StringUtil.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 (StringUtil.isNotEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)) {
return ip;
}
return request.getRemoteAddr().equals("0:0:0:0:0:0:0:1")?"127.0.0.1":ip;
}
}
(3)新建日志实体【SysOperationLog】
package com.sf.ucs.nav.model.logaop;
import java.io.Serializable;
/**
* @Description: 操作日志实体类
* @author jacky
* @date 2019年8月27日 下午4:06:45
*/
public class SysOperationLog implements Serializable{
private static final long serialVersionUID = 6791612340498352363L;
private Integer id;
private String loginNum;
private String loginName;
private String ipAddress;
private String methodName;
private String methodRemark;
private String operatingContent;
private String createTm;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getLoginNum() {
return loginNum;
}
public void setLoginNum(String loginNum) {
this.loginNum = loginNum;
}
public String getLoginName() {
return loginName;
}
public void setLoginName(String loginName) {
this.loginName = loginName;
}
public String getIpAddress() {
return ipAddress;
}
public void setIpAddress(String ipAddress) {
this.ipAddress = ipAddress;
}
public String getMethodName() {
return methodName;
}
public void setMethodName(String methodName) {
this.methodName = methodName;
}
public String getMethodRemark() {
return methodRemark;
}
public void setMethodRemark(String methodRemark) {
this.methodRemark = methodRemark;
}
public String getOperatingContent() {
return operatingContent;
}
public void setOperatingContent(String operatingContent) {
this.operatingContent = operatingContent;
}
public String getCreateTm() {
return createTm;
}
public void setCreateTm(String createTm) {
this.createTm = createTm;
}
}
(4)日志切入【添加注解标志即可】
/**
* 删除金融机构
* @param request
* @param response
* @param operateVersionId 机构版本
* @param orgId 机构iD
* @param orgLvl 机构层级
* @return
* @throws Exception
*/
@RequestMapping(value = "delOrgByFlagAndOrgId", method = RequestMethod.POST, produces = {"application/json"})
@MethodLog(remark = "删除机构")
public String delOrgByFlagAndOrgId(HttpServletRequest request,
HttpServletResponse response,
@RequestParam(value = "operateVersionId", required = false) String operateVersionId,
@RequestParam(value = "orgId", required = false) String orgId,
@RequestParam(value = "orgLvl", required = false) String orgLvl) throws Exception{
orgService.delOrgByFlagAndOrgId(areaIdPrefix,orgId, orgLvl, operateVersionId);
return "0";
}
(5)maven注入aop依赖
<!--spring切面aop依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
在application.properties文件里加这样一条配置
spring.aop.auto=true
在springboot项目里加这两条配置即可,就可以开启aop功能
总结:
至此,每个重要方法可以开始加注解标志,获取参数,记录关键信息,输出日志了。。。