SpringBoot系列(十):基于Equator組件記錄對象字段 值修改前后的變化
作者:
修羅debug
版權(quán)聲明:本文為博主原創(chuàng)文章,遵循 CC 4.0 by-sa 版權(quán)協(xié)議,轉(zhuǎn)載請附上原文出處鏈接和本聲明。
摘要:在開發(fā)企業(yè)級應(yīng)用項目業(yè)務(wù)模塊期間,相信很多小伙伴都實現(xiàn)過“記錄用戶的操作日志”的功能需求,此種方式可以基于Spring AOP的方式加以實現(xiàn)。然后,本文并非介紹如何記錄用戶的操作日志,而是實現(xiàn)用戶在操作某個實體時對比實體對象字段值修改前后是否發(fā)生了變化并進(jìn)行記錄。
內(nèi)容:在企業(yè)級應(yīng)用項目開發(fā)過程中,“記錄用戶的操作日志”這一功能相信很多小伙伴都實現(xiàn)過,然后,有時候產(chǎn)品經(jīng)理可能會“腦袋一發(fā)熱”,提了個新的功能需求,即除了記錄用戶的操作日志之外,還需要重點記錄某個實體對象的某些字段在修改前后值是否發(fā)生了變化,若發(fā)生了變化,則記錄到數(shù)據(jù)庫表中用于明細(xì)報表的展示。
身為一名資深程序猿/程序媛、或者攻城獅,第一時間心里一般就三個字“mmp”,但是又不能明目張膽地跟人家產(chǎn)品對著干,于是乎,只能硬著頭皮網(wǎng)上搜搜開源項目,看看有沒有一些大牛實現(xiàn)過。
或許是踩到了狗屎運,果然還真有這樣的大牛實現(xiàn)了這種輕量級的開源組件,即Equator,其github的id為“dadiyang”,一位年輕而又愛好分享的大神!
好了,廢話不多說,下面,我們用實際的案例進(jìn)行代碼實戰(zhàn)吧!
(1)首先,我們需要創(chuàng)建兩張數(shù)據(jù)庫表,一張是“實體表”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='商品表';
另外一張是“日志比較結(jié)果記錄表”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逆向工程生成相應(yīng)的Entity、Mapper、Mapper.xml代碼!
(2)下圖為 id 為1對應(yīng)的商品實體對應(yīng)的各個字段的初始值,我們將以這個商品實體為案例,對其相應(yīng)的字段進(jìn)行“更新”,并記錄到“比較日志表”中,如下圖所示:
開發(fā)一個FieldController類以及相應(yīng)的請求方法,用于接收前端“更新”請求對應(yīng)的數(shù)據(jù),其完整代碼如下所示:
@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類用于接收前端請求的數(shù)據(jù),其定義如下所示:
@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;
}
其中,F(xiàn)ieldService的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("---新對象內(nèi)容:{}",dto);
Item entity=itemMapper.selectByPrimaryKey(dto.getId());
ItemDto old=new ItemDto();
BeanUtils.copyProperties(entity,old);
old.setPurchaseTime(FORMAT.format(entity.getPurchaseTime()));
log.info("---舊對象內(nèi)容:{}",old);
//執(zhí)行更新
BeanUtils.copyProperties(dto,entity);
entity.setUpdateTime(new Date());
entity.setPurchaseTime(FORMAT.parse(dto.getPurchaseTime()));
itemMapper.updateByPrimaryKey(entity);
//執(zhí)行對象比較
List<FieldInfo> infos=equator.getDiffFields(old,dto);
log.info("---比較結(jié)果:{}",infos);
//記錄比較結(jié)果
this.logCompareResult(infos);
return infos;
}
在這里,我們有必要交代一下,我們使用的Equator的組件是FieldBaseEquator的實例,其配置代碼如下所示:
@Configuration
public class CommonConfig {
@Bean
public Equator equator(){
Equator equator=new FieldBaseEquator();
return equator;
}
}
除此之外,從該源代碼中可以看到,其核心的API如下所示,比較的結(jié)果即為我們最終所需要的:
//執(zhí)行對象比較
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);
});
}
}
為了能更好、直觀的在數(shù)據(jù)庫表中體現(xiàn)被修改前后的字段的“名稱”,我們特地做了一層“字段名-字段中文名 的 映射”,如下所示:
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;
}
}
最后,讓我們一起進(jìn)入測試環(huán)節(jié)吧,廢話不多講,直接上Postman請求示意圖吧:
點擊Send操作,觀察數(shù)據(jù)庫表記錄字段取值的變化,最終如下圖所示:
實體對象字段值修改前后,compare_log數(shù)據(jù)庫表成功記錄字段值的前后變化!
好了,本篇文章我們就介紹到這里了,感興趣的小伙伴可以關(guān)注底部Debug的技術(shù)公眾號,或者加Debug的微信,拉你進(jìn)“微信版”的真正技術(shù)交流群!一起學(xué)習(xí)、共同成長!
補充:
1、本文涉及到的相關(guān)的源代碼可以到此地址,check出來進(jìn)行查看學(xué)習(xí):
https://gitee.com/steadyjack/SpringBootTechnology
2、目前Debug已將本文所涉及的內(nèi)容整理錄制成視頻教程,感興趣的小伙伴可以前往觀看學(xué)習(xí):
https://www.fightjava.com/web/index/course/detail/5
3、關(guān)注一下Debug的技術(shù)微信公眾號,最新的技術(shù)文章、技術(shù)課程以及技術(shù)專欄將會第一時間在公眾號發(fā)布哦!