一、AopLog介绍与使用
AopLog是基于SpringAop和ThreadLocal实现的一个对请求方法埋点信息收集与处理的日志工具包。git地址(有详细的介绍和使用方法):跳转
使用方法:
1、先在pom中引入依赖:
<dependency> <groupId>com.github.ealenxie</groupId> <artifactId>aop-log</artifactId> <version>2.5</version> </dependency>
讯享网
2、新建自定义全局的日志收集器实现收集 LogCollector:
讯享网@Slf4j // 向Spring容器中注册bean @Component // 继承LogCollector public class AopLogCollector implements LogCollector { @Autowired // 注入数据库持久层(项目使用的是Spring-Data-JPA) private SysLogDataRepository sysLogDataRepository; / * 实现LogCollector的collect方法 * @param logData 埋点日志对象,存储各种埋点数据 */ @Override public void collect(LogData logData) { // 项目中的DO类,属性参数类似LogData SysLogData sysLogData = new SysLogData(); // 将LogData中的属性拷贝到DO对象中 BeanUtil.copyProperties(logData, sysLogData); // 获取当前的操作用户 String username = SysJwtTokenUtil.getUsername(); // 设置当前操作用户 sysLogData.setUserName(username); // 设置创建时间(操作时间) sysLogData.setCreateTime(new Date()); // 存入数据库 sysLogDataRepository.saveAndFlush(sysLogData); } }
跳转到埋点日志对象logdata属性说明
3、使用AopLog注解进行埋点收集(这里使用项目中的删除接口为例):
@Slf4j @RestController public class AppBannerController extends AdminController { @Autowired private AppBannerService appBannerService; // 使用@AopLog注解进行埋点收集 @AopLog(type = "banner配置删除操作", headers = "token") @PostMapping("/banner/del") public ApiResult delete(@RequestBody AppBanner appBanner) { if (appBanner.getId() == null || appBanner.getId() < 1) { return ApiResult.fail(-1, "id错误"); } appBannerService.delete(appBanner.getId()); // 将删除的详情日志使用step方法加入到日志中 LogData.step("删除banner配置,id=" + appBanner.getId()); return ApiResult.ok("操作成功!"); } }
效果图:
当调用/banner/del接口时,会自动将日志数据持久化到数据库中

讯享网
二、equator介绍与使用
Equator 基于`Spring Boot`对象比较框架,可以将对象之间的区别比较出来生成出一个list列表。git地址(有详细的介绍和使用方法):跳转
将equator用在AopLog打印对象的修改详情日志中:
1、在pom中引入依赖:

讯享网 <dependency> <groupId>com.github.dadiyang</groupId> <artifactId>equator</artifactId> <version>1.0.4</version> </dependency>
2、新建一个注解,用来存放字段的中文备注以及字段值的中文映射,用来打印出可读性更好的中文字段(值)说明:
@Target({ElementType.FIELD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface FieldLabel { // 中文备注 String value() default ""; // 是否打印日志 boolean isDisplay() default true; / * 字段中文映射关系对应关系 * 格式 : 映射前字段值:映射后字段值(多个用逗号","隔开) * 例 : 0:初始化,1:启用,2:禁用 * @return */ String mappingString() default ""; }
3、新建一个工具类,用来生成对象修改的具体详情:
讯享网@Slf4j public class LogUtils { // 由于SimpleDateFormat不是线程安全的,所以使用ThreadLocal来存储 private static ThreadLocal<DateFormat> dateFormatThreadLocal = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")); // 将对象字段注解的FieldLabel缓存进Map中,第二次可以直接从Map中获取 private static HashMap<String, HashMap<String, FieldLabel>> classAnnotationMap = new HashMap<>(); / * 生成修改操作详情 * @param first 修改前的对象 * @param second 修改后的对象 * @return */ public static String genDiffFieldsJSONString(Object first, Object second) { StringBuilder builder = new StringBuilder(); // 这里使用了比较粗暴的方式将对象的id取出来 String id = JSONObject.parseObject(JSONObject.toJSONString(first)).getString("id"); if (StringUtils.isNotBlank(id)) { builder.append(String.format("id : %s\n", id)); } // 使用Equator对比修改前后的属性变化 Equator equator = new GetterBaseEquator(); List<FieldInfo> fieldInfoList = equator.getDiffFields(first, second); // 若列表为空或者列表size为0,则表示修改前后的数据相同,之间返回 if (fieldInfoList == null || fieldInfoList.size() == 0) { builder.append("-"); return builder.toString(); } // 获取类的class Class<?> firstClass = first.getClass(); for (int i = 0; i < fieldInfoList.size(); i++) { try { FieldInfo fieldInfo = fieldInfoList.get(i); // 获取对象字段的FieldLabel注解 FieldLabel annotation = getAndSetAnnotation(firstClass, fieldInfo.getFieldName()); // 是否打印 if (!annotation.isDisplay()) { continue; } Object firstVal = fieldInfo.getFirstVal(); Object secondVal = fieldInfo.getSecondVal(); // 处理时间格式 if ("java.util.Date".equals(fieldInfo.getFirstFieldType().getName()) && firstVal != null) { firstVal = dateFormatThreadLocal.get().format(firstVal); } if ("java.util.Date".equals(fieldInfo.getSecondFieldType().getName()) && secondVal != null) { secondVal = dateFormatThreadLocal.get().format(secondVal); } // 处理映射关系 if (StringUtils.isNotBlank(annotation.mappingString())) { Map<String, String> mappingMap = mappingStringToMap(annotation.mappingString()); String firstMappingVal = mappingMap.get(firstVal.toString()); if (StringUtils.isNotBlank(firstMappingVal)) { firstVal = String.format("%s (%s)", firstMappingVal, firstVal); } String secondMappingVal = mappingMap.get(secondVal.toString()); if (StringUtils.isNotBlank(secondMappingVal)) { secondVal = String.format("%s (%s)", secondMappingVal, secondVal); } } // 将处理后的日志append进去 builder.append(String.format("%s [%s] : %s → %s", annotation.value(), fieldInfo.getFieldName(), firstVal, secondVal)); // 列表遍历到最后一个时不加回车 if (i < (fieldInfoList.size() - 1)) { builder.append("\n"); } } catch (Exception e) { log.warn("日志保存报错:", e); } } dateFormatThreadLocal.remove(); return builder.toString(); } / * 获取对象字段的FieldLabel注解并缓存 * @param ClassInfo * @param annotationName * @return */ public static FieldLabel getAndSetAnnotation(Class<?> ClassInfo, String annotationName) { String classInfoName = ClassInfo.getName(); // 通过对象名获取对象的字段注解Map HashMap<String, FieldLabel> annotationMap = classAnnotationMap.get(classInfoName); // 不存在则put进去一个新的Map if (annotationMap == null) { annotationMap = new HashMap<>(); classAnnotationMap.put(classInfoName, annotationMap); } // 从对象的字段注解Map中取注解 FieldLabel fieldLabel = annotationMap.get(annotationName); // 取不到,则通过反射获取并加入缓存 if (fieldLabel == null) { try { fieldLabel = ClassInfo.getDeclaredField(annotationName).getAnnotation(FieldLabel.class); if (fieldLabel != null) { annotationMap.put(annotationName, fieldLabel); } } catch (Exception e) { log.warn("日志取得注解信息报错:", e); } } return fieldLabel; } / * 将映字段中文映射关系对应关系使用split和流转化为hashMap * @param str * @return */ public static Map<String, String> mappingStringToMap(String str) { return Arrays.asList(str.trim().split(",|,")).stream().map(item -> item.trim().split(":|:")) .collect(Collectors.toMap(e -> e.length > 0 ? e[0].trim() : "", e -> e.length > 1 ? e[1].trim() : "")); } }
4、在想要使用的对象字段中加入@FieldLabel注解:
@Data public class MarketingPageJump implements Serializable { private static final long serialVersionUID = L; private Long id; @FieldLabel(value = "配置标识") private String code; @FieldLabel(value = "标题") private String title; @FieldLabel(value = "关联优惠券礼包ID") private String couponPackId; @FieldLabel(value = "封面图链接") private String imgUrl; @FieldLabel(value = "目标跳转链接") private String targetUrl; @FieldLabel(value = "状态", mappingString = "0:初始化,1:启用,2:禁用") private Integer status; @FieldLabel(value = "创建时间") private Date createTime; @FieldLabel(value = "更新时间") private Date updateTime; }
5、在接口中设置埋点:
讯享网@AopLog(type = "营销弹幕更新状态", headers = "token") @PostMapping("/marketingPageJump/updateStatus") public ApiResult updateStatus(@RequestBody MarketingPageJump marketingPageJump) { MarketingPageJump marketingPageJumpDB = marketingPageJumpService.getById(marketingPageJump.getId()); if (marketingPageJumpDB == null) { return ApiResult.fail("未找到对应记录!"); } Integer status = marketingPageJump.getStatus(); if (marketingPageJumpDB.getStatus() == status) { return ApiResult.ok("未作任何修改"); } BeanUtils.copyProperties(marketingPageJumpDB, marketingPageJump); marketingPageJump.setStatus(status); marketingPageJump.setUpdateTime(new Date()); // 生成对象更改前后的详情数据并使用step方法写到日志中 LogData.step(LogUtils.genDiffFieldsJSONString(marketingPageJumpDB, marketingPageJump)); marketingPageJumpService.save(marketingPageJump); return ApiResult.ok(); }
效果图:
调用/marketingPageJump/updateStatus接口修改状态时,会生成一条对象修改的详细记录


版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/37906.html