介绍
我们在日常开发工作中,肯定逃不开与日志接触,一些比较严谨的后台管理系统里面会涉及到一些比较重要的资料,有些公司为了知道有哪些人登录了系统,是谁在什么时候修改了用户信息或者资料,所以就有了操作日志这么个需求。
此文章介绍的是SpringBoot下如何通过注解的形式实现操作日志,仅供学习参考,不喜勿喷。
具体实现
pom依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><!--aspectj依赖--><dependency><groupId>org.springframework</groupId><artifactId>spring-aspects</artifactId><version>5.2.25.RELEASE</version></dependency><!--swagger依赖--><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId><version>2.8.0</version></dependency><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger-ui</artifactId><version>2.8.0</version></dependency><!--工具依赖--><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId><version>3.9</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>2.0.32</version></dependency>
代码实现
- 系统日志注解
/**
* 系统日志注解
*
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SysOperateLog {//用户操作String value();//保存入参boolean isSaveParams() default true;//保存返回结果boolean isSaveResult() default false;//异常信息boolean isRecordError() default false;//忽略属性String [] ignoreFields() default {};}
- 切面处理类
/**
* 系统日志,切面处理类
*/
@Component
@Aspect
public class SysLogAspect {private static final Logger logger = LoggerFactory.getLogger(SysLogAspect.class);@Resourceprivate SysLogService sysLogService;// 定义切点,被@SysOperateLog注解标注的方法作为切点@Pointcut("@annotation(com.ou.common.annotation.SysOperateLog)")public void logPointCut() {}@Around("logPointCut()")public Object around(ProceedingJoinPoint point) throws Throwable {long beginTime = System.currentTimeMillis();//执行方法Object result = null;try {result = point.proceed();} catch (Throwable throwable) {//异常日志记录MethodSignature signature = (MethodSignature) point.getSignature();Method method = signature.getMethod();SysOperateLog syslog = method.getAnnotation(SysOperateLog.class);if(syslog != null && syslog.isRecordError()){//执行时长(毫秒)long time = System.currentTimeMillis() - beginTime;result = "异常返回," + throwable.getMessage();sysLogService.aopTransactionalSysLog(point,"error!!", time);}throw throwable;}//执行时长(毫秒)long time = System.currentTimeMillis() - beginTime;//保存日志sysLogService.aopSysLog(point,result, time);return result;}}
- 过滤器
public class FastJsonByteArrayValueFilter implements ValueFilter {public FastJsonByteArrayValueFilter() {}public Object process(Object object, String name, Object value) {try {if (value == null) {return value;}Field declaredField = object.getClass().getDeclaredField(name);Class<?> type = declaredField.getType();if (type.toString().equals("class [B")) {return null;}} catch (NoSuchFieldException var6) {}return value;}
}
- 日志实体类
/*** 系统操作日志实体类*/
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@ApiModel(value = "SysLog对象", description = "系统操作日志")
public class SysLog implements Serializable {private static final long serialVersionUID = 1L;@ApiModelProperty(value = "日志主键ID")private String id;@ApiModelProperty(value = "用户ID")private String userId;private String userName;@ApiModelProperty(value = "用户操作")private String operation;@ApiModelProperty(value = "请求方法")private String method;private String params;private String result;@ApiModelProperty(value = "执行时长")private Long time;@ApiModelProperty(value = "IP地址")private String ip;@ApiModelProperty(value = "逻辑删除标志,0:正常,1:删除")private Integer delFlag;@ApiModelProperty(value = "创建人")private String createUser;@ApiModelProperty(value = "创建时间")private Date createTime;@ApiModelProperty(value = "更新人")private String updateUser;@ApiModelProperty(value = "更新时间")private Date updateTime;}
- 业务层接口
/**
* 系统操作日志
*/
public interface SysLogService{void aopTransactionalSysLog(ProceedingJoinPoint joinPoint, Object result, long time);void aopSysLog(ProceedingJoinPoint joinPoint, Object result, long time);
}
- 业务层实现类
/**
* 系统操作日志 服务实现类
*/
@Service
public class SysLogServiceImpl implements SysLogService {private static final Logger logger = LoggerFactory.getLogger(SysLogServiceImpl.class);//MediumTextprivate static final int MAX_BTYE_LENGTH = 16777215;//@Transactional(propagation = Propagation.REQUIRES_NEW,rollbackFor = Exception.class)@Overridepublic void aopTransactionalSysLog(ProceedingJoinPoint joinPoint, Object result, long time){aopSysLog(joinPoint,result,time);}/*** ProceedingJoinPoint继承了JoinPoint* JoinPoint.getSignature() = 获取到了方法的【修饰符 + 包名 + 组件名(类名) +方法】*/@Overridepublic void aopSysLog(ProceedingJoinPoint joinPoint, Object result, long time) {MethodSignature signature = (MethodSignature) joinPoint.getSignature();Method method = signature.getMethod();SysLog sysLog = new SysLog();//这里我是用UUID来模仿ID,可自行设置//sysLog.setId(UUIDCreater.create());SysOperateLog sysOperateLog = method.getAnnotation(SysOperateLog.class);boolean isSaveResult = false;boolean isSaveParams = false;if(sysOperateLog != null){//注解上的描述sysLog.setOperation(sysOperateLog.value());isSaveResult = sysOperateLog.isSaveResult();isSaveParams = sysOperateLog.isSaveParams();}//请求的方法名String className = joinPoint.getTarget().getClass().getName();String methodName = signature.getName();String[] parameterNames = signature.getParameterNames();// 设置 com.ou.controller.SysLogController.testParam(map)sysLog.setMethod(className + "." + methodName + "("+ StringUtils.join(parameterNames,",")+")");try{//保存参数信息if(isSaveParams){Object[] args = joinPoint.getArgs();StringBuffer params = new StringBuffer();for(int i = 0 ; i < args.length; i++){if(args[i] instanceof ServletResponse){continue;}else if(args[i] instanceof Class){continue;}else if(args[i] instanceof byte[]){continue;} else if(args[i] instanceof ServletRequest){ServletRequest request = (ServletRequest)args[i];params.append(JSON.toJSONString(request.getParameterMap()));}else {Object json = JSON.toJSON(args[i]);String jsonString = JSON.toJSONString(json, new ValueFilter[]{new FastJsonByteArrayValueFilter()});params.append(jsonString);}if(i < args.length - 1){params.append("; ");}}byte[] paramsBytes = params.toString().getBytes();if(paramsBytes.length > MAX_BTYE_LENGTH){byte[] subBytes = new byte[MAX_BTYE_LENGTH];System.arraycopy(paramsBytes, 0, subBytes, 0, MAX_BTYE_LENGTH);sysLog.setParams(new String(subBytes));}else{sysLog.setParams(params.toString());}}//保存返回结果if(isSaveResult && result != null){Object json = JSON.toJSON(result);String[] ignoreFields = sysOperateLog.ignoreFields();if(ignoreFields.length > 0){if(json instanceof JSONObject){JSONObject jsonObject = (JSONObject) json;for(String field : ignoreFields){jsonObject.remove(field);}}else if(json instanceof JSONArray){JSONArray jsonArray = (JSONArray) json;for(Object obj : jsonArray){if(obj instanceof JSONObject){JSONObject jsonObject = (JSONObject) obj;for(String field : ignoreFields){jsonObject.remove(field);}}}}}String transferResult = JSON.toJSONString(json, new ValueFilter[]{new FastJsonByteArrayValueFilter()});byte[] transferResultBytes = transferResult.toString().getBytes();if(transferResultBytes.length > MAX_BTYE_LENGTH){byte[] subBytes = new byte[MAX_BTYE_LENGTH];System.arraycopy(transferResultBytes, 0, subBytes, 0, MAX_BTYE_LENGTH);sysLog.setResult(new String(subBytes));}else{sysLog.setResult(transferResult);}}try {//获取requestHttpServletRequest request = HttpContextUtils.getHttpServletRequest();//设置IP地址sysLog.setIp(IPUtils.getIpAddr(request));} catch (Exception e) {}sysLog.setTime(time);//保存系统日志--这里是打印logger.info("系统日志:{}",JSONObject.toJSONString(sysLog));}catch (Exception e){logger.error("系统日志记录失败:",e);}}
}
- 工具类
public class HttpContextUtils {public static HttpServletRequest getHttpServletRequest() {return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();}
}/**
* 获取IP地址的工具类
*/
public class IPUtils {private static Logger logger = LoggerFactory.getLogger(IPUtils.class);/*** 获取IP地址*/public static String getIpAddr(HttpServletRequest request) {String ip = null;try {ip = request.getHeader("x-forwarded-for");if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeader("Proxy-Client-IP");}if (StringUtils.isEmpty(ip) || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeader("WL-Proxy-Client-IP");}if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeader("HTTP_CLIENT_IP");}if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeader("HTTP_X_FORWARDED_FOR");}if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {ip = request.getRemoteAddr();}} catch (Exception e) {logger.error("IPUtils ERROR ", e);}return ip;}}
- Controller接口
@RestController
@RequestMapping("/sys/log")
public class SysLogController {/*** 触发操作日志*/@SysOperateLog("触发-操作日志接口-不带参数")@RequestMapping("/test")public String test() {return "OK";}/*** 触发操作日志*/@SysOperateLog("触发-操作日志接口-带参数")@RequestMapping("/testParam")public String testParam(@RequestBody Map map) {return "OK";}
}
测试
控制台输出:
系统日志:{"id":"9e088286efe64183aa5992f30a8aa34b","ip":"127.0.0.1","method":"com.ou.controller.SysLogController.testParam(map)","operation":"触发-操作日志接口-带参数","params":"{\"sex\":\"男\",\"name\":\"张三\",\"age\":18,\"desc\":\"描述啊啊啊\"}","time":10}
我这里是直接输出日志,具体怎么存库还是由各自的业务来进行修改。
--------END