springboot数据修改历史记录
在一些领域,记录数据的变更历史是非常重要的。比如工业采集系统…
需要记录指标的信息。再比如一些非常注重安全的系统,希望在必要时可以对所有的历史操作追根溯源,有据可查。
0.前言
比如,修改一个人的姓名从“张三”变为了“李四”,那么在进行记录的时候,记录的信息可能如下:
这样就很好的体现出了修改了哪个字段,修改前后的数据分别是什么。
关键的信息无论怎么修改都会有据可查,时间、人物、修改数据前后信息等。
设计思路:
1、获取到两个对象中属性列表,
2、遍历对比,
3、属性名相同属性值不同的把属性名及两个对象的属性值保存进Map<String,Object>里,
4、返回List<Map<String,Object>对象
1.新建FieldMeta 类:
底层就是利用的java反射机制,通过自定义注解实现。
新增自定义注解FieldMeta :
/**
* @description: 底层就是利用的java反射机制,通过自定义注解实现
* @author: 黑猫
* @date: 2023/4/17 10:59
* @version:1.0
*/
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD,ElementType.METHOD})
@Documented
public @interface FieldMeta {
String name() default "";
String description() default "";
}
在需要记录修改历史的字段上添加@FieldMeta注解,标识它,只要变化,就会记录它的信息。
2.新建CompareObjectUtils类:
用于 对比两个对象中同名属性的值是否相同。
源码:
package com.bonc.boot.module.web.util;
/**
* @description:
* @author: 黑猫
* @date: 2023/4/17 10:59
* @version:1.0
*/
import com.bonc.boot.module.web.annotation.FieldMeta;
import org.apache.http.client.utils.DateUtils;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.lang.reflect.Field;
import java.util.*;
@Component
public class CompareObjectUtils {
private static CompareObjectUtils compareObjectUtils;
@PostConstruct
public void init() {
compareObjectUtils = this;
}
/**
* 获取两个对象同名属性内容不相同的列表
* @param class1 对象1
* @param class2 对象2
* @return
* @throws ClassNotFoundException
* @throws IllegalAccessException
*/
public static List<Map<String, Object>> compareTwoClass(Object class1, Object class2) throws ClassNotFoundException, IllegalAccessException {
List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();
//获取对象的class
Class<?> clazz1 = class1.getClass();
Class<?> clazz2 = class2.getClass();
//获取对象的属性列表
Field[] field1 = clazz1.getDeclaredFields();
Field[] field2 = clazz2.getDeclaredFields();
//遍历属性列表field1
for (int i = 0; i < field1.length; i++) {
if(field1[i].isAnnotationPresent(FieldMeta.class))
//遍历属性列表field2
for (int j = 0; j < field2.length; j++) {
//如果field1[i]属性名与field2[j]属性名内容相同
if (field1[i].getName().equals(field2[j].getName())) {
field1[i].setAccessible(true);
field2[j].setAccessible(true);
//如果field1[i]属性值与field2[j]属性值内容不相同
if (!compareTwo(field1[i].get(class1), field2[j].get(class2)) && field1[i].isAnnotationPresent(FieldMeta.class) && field2[j].isAnnotationPresent(FieldMeta.class)) {
FieldMeta metaAnnotation = field1[i].getAnnotation(FieldMeta.class);
Map<String, Object> map2 = new HashMap<String, Object>();
map2.put("name", metaAnnotation.name());
map2.put("old", field1[i].get(class1) == null ? "" : field1[i].get(class1) );
map2.put("new", field2[j].get(class2));
//解决时间格式化问题-bean上加了@DateTimeFormat(pattern="yyyy-MM-dd")
if(field1[i].isAnnotationPresent(DateTimeFormat.class) && field2[j].isAnnotationPresent(DateTimeFormat.class) ){
String old = DateUtils.formatDate((Date) field1[i].get(class1),field1[i].getAnnotation(DateTimeFormat.class).pattern());
map2.put("old",old == null ? "": old);
map2.put("new", DateUtils.formatDate((Date) field2[j].get(class2),field2[j].getAnnotation(DateTimeFormat.class).pattern()));
}
//解决数据字典text/value转换问题-bean上加了@Dict(dicCode = "groupField",isCommon = false) 忽略以下代码因为本人不用
list.add(map2);
}
break;
}
}
}
return list;
}
//对比两个数据是否内容相同
public static boolean compareTwo(Object object1, Object object2) {
if (object1 == null && object2 == null) {
return true;
}
if (object1 == null && object2 != null) {
return false;
}
if (object1.equals(object2)) {
return true;
}
return false;
}
}
3.调用方式:
在web项目的impl中的方法,controller调用即可。
/**
* @description: 数据填报控制层
* @author: 黑猫
* @date: 2023/4/15 22:22
* @version:1.0
*/
@Tag(name = "管理后台 - 数据填报")
@RestController
@RequestMapping("/dataFill")
@Validated
@CrossOrigin
public class DataFillController extends BaseController {
@PostMapping("/excavateInfo/save")
@Operation(summary = "保存采掘信息")
@PreAuthorize("@ss.hasPermission('indexmgr:index:update')")
public CommonResult<Integer> saveExcavateInfo(@Validated @RequestBody ExcavateInfoEntity excavateInfoEntity) {
//判断id是否为空,如果不为空进行更新操作,否则进行新增操作
if(excavateInfoEntity.getId()!=0){
dataFillingService.updateExcavateInfo(excavateInfoEntity);
}else{
dataFillingService.saveExcavateInfo(excavateInfoEntity);
}
return CommonResult.success(excavateInfoEntity.getId());
}
}
比如:新建一个DataFillServiceImpl .java 调用
/**
* @description: 数据填报Service实现类
* @author: 黑猫
* @date: 2023/4/15 22:44
* @version:1.0
*/
@Service
@Validated
@Slf4j
public class DataFillServiceImpl implements DataFillService {
@Resource
private DataFillMapper dataFillMapper;
@Override
public int saveExcavateInfo(ExcavateInfoEntity excavateInfoEntity) {
excavateInfoEntity.setCreator(SecurityFrameworkUtils.getLoginUserId().toString());
return dataFillMapper.saveExcavateInfo(excavateInfoEntity);
}
@Override
public void updateExcavateInfo(ExcavateInfoEntity model) {
try {
//第一步:查旧数据
ExcavateInfoEntity oldModel = dataFillMapper.selectExcavateInfoById(model.getId());
List<Map<String, Object>> list = new ArrayList<>();
//第二步:旧新数据对比
list = CompareObjectUtils.compareTwoClass(oldModel, model);
String content = ""; // 定义变更字符串
for (Map<String, Object> map : list) {
if (map.get("old") == null) {
map.put("old", "无");
}
content += map.get("name") + ":" + map.get("old") + " 变更为 " + map.get("new") + "。";
}
//第三步:记录表新增历史
if (content.length() > 0) {
KpiOperateLogEntity item = new KpiOperateLogEntity(); // 数据变更实体对象
item.setCollectDay(model.getCollectDay());
item.setChangeContent(content); //变更内容
item.setCreator(SecurityFrameworkUtils.getLoginUserId().toString());
dataFillMapper.insertOperateLog(item); //记录表新增历史
}
} catch (Exception e) {
e.printStackTrace();
}
//第四步:更新操作
model.setUpdater(SecurityFrameworkUtils.getLoginUserId().toString());
dataFillMapper.updateExcavateInfo(model);
}
}
mybatis 查旧数据
<select id="selectExcavateInfoById" resultType="com.bonc.boot.module.web.entity.ExcavateInfoEntity">
select * from nmzj_excavate_info where id = #{id}
</select>
插入操作日志sql
<insert id="insertOperateLog" parameterType="com.bonc.boot.module.web.entity.KpiOperateLogEntity">
insert into nmzj_kpi_operate_log(collect_day,change_content,creator) values(#{collectDay},#{changeContent},#{creator})
</insert>
业务实体类
package com.bonc.boot.module.web.entity;
import com.bonc.boot.module.web.annotation.FieldMeta;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
* @description: 采掘信息实体类 注意:对比的字段需要填写@FieldMeta
* @author: 黑猫
* @date: 2023/4/15 22:27
* @version:1.0
*/
@Data
public class ExcavateInfoEntity implements Serializable {
//序列
private int id;
//工作面名称
@FieldMeta(name = "工作面名称")
private String workSurfaceName;
//资源储量-总储量
@FieldMeta(name = "资源储量-总储量")
private String resReservesTotal;
//资源储量-已采
@FieldMeta(name = "资源储量-已采")
private String resReservesCollected;
//资源储量-剩余
@FieldMeta(name = "资源储量-剩余")
private String resReservesSurplus;
//掘进长度-目标
@FieldMeta(name = "掘进长度-目标")
private String drivingLengthTarget;
//掘进长度-已掘
@FieldMeta(name = "掘进长度-已掘")
private String drivingLengthExcavated;
//掘进长度-剩余
@FieldMeta(name = "掘进长度-剩余")
private String drivingLengthSurplus;
//采面征迁
@FieldMeta(name = "采面征迁")
private String drivingLengthCollectSurfaceMigration;
//创建时间
private Date createTime;
//更新时间
private Date updateTime;
//逻辑删除
private int deleted;
//创建者
private String creator;
//更新者
private String updater;
//状态
private String state;
//指标日期
private String collectDay;
}
同一个实体中,可以添加多个该注解给不同的字段,达到比较多个字段的效果。
操作日志记录表实体类:
package com.bonc.boot.module.web.entity;
import lombok.Data;
/**
* @description:
* @author: 黑猫
* @date: 2023/4/17 11:29
* @version:1.0
*/
@Data
public class KpiOperateLogEntity {
private int id; //主键ID
private String collectDay; //指标日期
private String changeContent; //变更内容
private String creator; //更新人
private String createTime; //修改时间
}
创建指标操作记录表sql
CREATE TABLE `your_dbname`.`kpi_operate_log` (
`id` bigint(255) NOT NULL AUTO_INCREMENT COMMENT '主键序列',
`collect_day` varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '指标日期',
`change_content` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '变更内容',
`creator` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '修改人',
`create_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP(0) ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '修改时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 44 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = DYNAMIC;
完成这些之后,可以启动项目,查看了。
效果如图:
本文到此结束了,如果有什么建议请提出来。
本文来自博客园,作者:星星之草%,转载请注明原文链接:https://www.cnblogs.com/zhaodefu/p/17332623.html