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的技術微信公眾號,最新的技術文章、技術課程以及技術專欄將會第一時間在公眾號發布哦!