SpringBoot系列(十):基于Equator组件记录对象字段 值修改前后的变化


作者: 修罗debug
版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。

摘要:在开发企业级应用项目业务模块期间,相信很多小伙伴都实现过“记录用户的操作日志”的功能需求,此种方式可以基于Spring AOP的方式加以实现。然后,本文并非介绍如何记录用户的操作日志,而是实现用户在操作某个实体时对比实体对象字段值修改前后是否发生了变化并进行记录。

内容:在企业级应用项目开发过程中,“记录用户的操作日志”这一功能相信很多小伙伴都实现过,然后,有时候产品经理可能会“脑袋一发热”,提了个新的功能需求,即除了记录用户的操作日志之外,还需要重点记录某个实体对象的某些字段在修改前后值是否发生了变化,若发生了变化,则记录到数据库表中用于明细报表的展示。

身为一名资深程序猿/程序媛、或者攻城狮,第一时间心里一般就三个字“mmp”,但是又不能明目张胆地跟人家产品对着干,于是乎,只能硬着头皮网上搜搜开源项目,看看有没有一些大牛实现过。

或许是踩到了狗屎运,果然还真有这样的大牛实现了这种轻量级的开源组件,即Equator,其github的id为“dadiyang”,一位年轻而又爱好分享的大神!

好了,废话不多说,下面,我们用实际的案例进行代码实战吧!

(1)首先,我们需要创建两张数据库表,一张是“实体表”item,其DDL如下所示:

CREATE TABLE `item` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL COMMENT '商品名',
`code` varchar(255) DEFAULT NULL COMMENT '商品编号',
`stock` bigint(20) DEFAULT NULL COMMENT '库存',
`purchase_time` date DEFAULT NULL COMMENT '采购时间',
`is_active` int(11) DEFAULT '1' COMMENT '是否有效(1=是;0=否)',
`create_time` datetime DEFAULT NULL,
`update_time` timestamp NULL DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COMMENT='商品表';

另外一张是“日志比较结果记录表”compare_log,其DDL如下所示:  

CREATE TABLE `compare_log` (
`id` int(11) NOT NULL,
`code` varchar(255) DEFAULT NULL,
`name` varchar(255) DEFAULT NULL,
`old_val` varchar(255) DEFAULT NULL,
`new_val` varchar(255) DEFAULT NULL,
`create_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='比较日志';

然后,采用Mybatis逆向工程生成相应的Entity、Mapper、Mapper.xml代码!

(2)下图为 id 为1对应的商品实体对应的各个字段的初始值,我们将以这个商品实体为案例,对其相应的字段进行“更新”,并记录到“比较日志表”中,如下图所示:


开发一个FieldController类以及相应的请求方法,用于接收前端“更新”请求对应的数据,其完整代码如下所示:  

@RestController
@RequestMapping("field")
public class FieldController {
private static final Logger log= LoggerFactory.getLogger(FieldController.class);

@Autowired
private FieldService fieldService;

@RequestMapping(value = "compare",method = RequestMethod.POST,consumes = MediaType.APPLICATION_JSON_UTF8_VALUE)
public BaseResponse compareFields(@RequestBody @Validated ItemDto dto, BindingResult result){
if (result.hasErrors()){
return new BaseResponse(StatusCode.InvalidParams);
}
BaseResponse response=new BaseResponse(StatusCode.Success);
try {
response.setData(fieldService.compare(dto));
}catch (Exception e){
response=new BaseResponse(StatusCode.Fail.getCode(),e.getMessage());
}
return response;
}
}

其中ItemDto类用于接收前端请求的数据,其定义如下所示:  

@Data
public class ItemDto implements Serializable{
@NotNull
private Integer id;

@NotBlank
private String name;

@NotBlank
private String code;
@NotNull
private Long stock;

@DateTimeFormat(pattern = "yyyy年MM月dd日")
private String purchaseTime;

private Integer isActive;
}

其中,FieldService的compare方法主要是借助 Equator 组件提供的API比较两个对象字段值的变化,其完整源代码如下所示:  

@Service
public class FieldService {
private static final Logger log= LoggerFactory.getLogger(FieldService.class);

private static final SimpleDateFormat FORMAT=new SimpleDateFormat("yyyy-MM-dd");

@Autowired
private ItemMapper itemMapper;

@Autowired
private Equator equator;

public List<FieldInfo> compare(ItemDto dto) throws Exception{
log.info("---新对象内容:{}",dto);

Item entity=itemMapper.selectByPrimaryKey(dto.getId());
ItemDto old=new ItemDto();
BeanUtils.copyProperties(entity,old);
old.setPurchaseTime(FORMAT.format(entity.getPurchaseTime()));
log.info("---旧对象内容:{}",old);

//执行更新
BeanUtils.copyProperties(dto,entity);
entity.setUpdateTime(new Date());
entity.setPurchaseTime(FORMAT.parse(dto.getPurchaseTime()));
itemMapper.updateByPrimaryKey(entity);

//执行对象比较
List<FieldInfo> infos=equator.getDiffFields(old,dto);
log.info("---比较结果:{}",infos);

//记录比较结果
this.logCompareResult(infos);
return infos;
}

在这里,我们有必要交代一下,我们使用的Equator的组件是FieldBaseEquator的实例,其配置代码如下所示:  

@Configuration
public class CommonConfig {
@Bean
public Equator equator(){
Equator equator=new FieldBaseEquator();
return equator;
}
}

除此之外,从该源代码中可以看到,其核心的API如下所示,比较的结果即为我们最终所需要的:

//执行对象比较
List<FieldInfo> infos=equator.getDiffFields(old,dto);

(4)logCompareResult()方法的功能在于记录“对象字段 值 修改前后的变化”,其源代码如下所示:  

@Autowired
private CompareLogMapper logMapper;

private void logCompareResult(List<FieldInfo> infos) throws Exception{
if (infos!=null && !infos.isEmpty()){
infos.stream().forEach(info -> {
CompareLog compareLog=new CompareLog();
String code=info.getFieldName();
compareLog.setCode(code);
compareLog.setName(String.valueOf(ItemCompareEnum.getFieldMap().get(code)));
compareLog.setOldVal(String.valueOf(info.getFirstVal()));
compareLog.setNewVal(String.valueOf(info.getSecondVal()));
compareLog.setCreateTime(new Date());
logMapper.insert(compareLog);
});
}
}

为了能更好、直观的在数据库表中体现被修改前后的字段的“名称”,我们特地做了一层“字段名-字段中文名 的 映射”,如下所示:  

public class ItemCompareEnum {
private static Map<String,Object> fieldMap;

static{
fieldMap=Maps.newConcurrentMap();
fieldMap.put("name","商品名称");
fieldMap.put("code","商品编码");
fieldMap.put("stock","商品库存");
fieldMap.put("purchaseTime","采购时间");
fieldMap.put("isActive","是否有效");
}

public static Map<String,Object> getFieldMap(){
return fieldMap;
}
}

最后,让我们一起进入测试环节吧,废话不多讲,直接上Postman请求示意图吧:  


点击Send操作,观察数据库表记录字段取值的变化,最终如下图所示:  


实体对象字段值修改前后,compare_log数据库表成功记录字段值的前后变化!  


好了,本篇文章我们就介绍到这里了,感兴趣的小伙伴可以关注底部Debug的技术公众号,或者加Debug的微信,拉你进“微信版”的真正技术交流群!一起学习、共同成长!

补充:

1、本文涉及到的相关的源代码可以到此地址,check出来进行查看学习:

https://gitee.com/steadyjack/SpringBootTechnology

2、目前Debug已将本文所涉及的内容整理录制成视频教程,感兴趣的小伙伴可以前往观看学习:

https://www.fightjava.com/web/index/course/detail/5

3、关注一下Debug的技术微信公众号,最新的技术文章、技术课程以及技术专栏将会第一时间在公众号发布哦!