1.先写个注解,加在entity的字段上,标记要记录这个字段的更新记录,再加个变更记录表(你们可以用自己的日志表)

@Retention(RetentionPolicy.RUNTIME)
@Target({java.lang.annotation.ElementType.FIELD})
@Documented
public @interface ModifyAware {

    /**
     * 字段名称,这里直接写,不去找其他注解或者解析doc了,必填
     */
    public abstract String name();


}

@Data
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
@Accessors(chain = true)
@TableName("modifyrecord")
@ApiModel("变更记录表")
public class ModifyRecord extends CommonBaseEntity {
    private static final long serialVersionUID = 7314951651619250712L;
    /**
     * 变更表名
     */
    @TableField("tableName")
    @ApiModelProperty("变更表名/业务名称")
    private String tableName;

    /**
     * 变更记录Id
     */
    @TableField("recordKey")
    @ApiModelProperty("变更记录Id")
    private Integer recordKey;

    /**
     * 变更字段
     */
    @TableField("modifyColumn")
    @ApiModelProperty("变更字段")
    private String modifyColumn;

    /**
     * 变更前内容
     */
    @ApiModelProperty("变更前内容")
    @TableField("beforeContent")
    private String beforeContent;

    /**
     * 变更后内容
     */
    @TableField("afterContent")
    @ApiModelProperty("变更后内容")
    private String afterContent;

    /**
     * 变更时间
     */
    @TableField("modifyTime")
    @ApiModelProperty("变更时间")
    private Date modifyTime;

    /**
     * 变更原因
     */
    @TableField("modifyReason")
    @ApiModelProperty("变更原因")
    private String modifyReason;

    /**
     * 变更人
     */
    @TableField("modifier")
    @ApiModelProperty("变更人")
    private String modifier;

    /**
     * 变更人姓名
     */
    @TableField(exist = false)
    @ApiModelProperty("变更人姓名")
    private String modifierName;

    public ModifyRecord(String tableName, Integer recordKey, String modifyColumn, String beforeContent, String afterContent, Date modifyTime, String modifyReason, String modifier) {
        this.tableName = tableName;
        this.recordKey = recordKey;
        this.modifyColumn = modifyColumn;
        this.modifyTime = modifyTime;
        this.beforeContent = beforeContent;
        this.afterContent = afterContent;
        this.modifyReason = modifyReason;
        this.modifier = modifier;
    }
}

2.再在BaseService和impl实现中增加modify方法(如果没有就自己写,基本上用mybatis都会自己包一层service(继承IService)服务,多定义一些好用的增删改查的方法),等同update,实现变更记录的逻辑,最好前端增加一个变更原因字段,带到entity里面,做变更原因记录,我这里先写死;

PS:这里反射取值时,直接穷举了字段基本类型,常规开发就这些,暂不支持list,map等集合的变化,有需要自己多写个if分支拓展下。

public int modify(T entity) {
        Integer id = (Integer) EntityUtils.getFieldValueByFieldName("id", entity);
        //获取旧数据
        T old = this.getById(id);
        //执行更新,保证业务执行
        int num = getBaseMapper().updateById(entity);
        //获取加了注解的字段
        Field[] fields = entity.getClass().getDeclaredFields();
        String className = entity.getClass().getSimpleName();
        List<ModifyRecord> modifyRecordList = Lists.newArrayList();
        for (Field field : fields) {
            field.setAccessible(true);
            if (!field.isAnnotationPresent(ModifyAware.class)) {
                continue;
            }
            ModifyAware attr = field.getAnnotation(ModifyAware.class);
            //获取实体旧值
            Object oldValue = EntityUtils.getFieldValueByFieldName(field.getName(), old);
            //获取新值
            Object newValue = EntityUtils.getFieldValueByFieldName(field.getName(), entity);
            //新旧比较
            String beforeContent = "";
            String afterContent = "";
            if (null == oldValue && null == newValue) {
                continue;
            } else if (null == oldValue) {
                afterContent = getFieldValue(field, newValue);
            } else if (null == newValue) {
                beforeContent = getFieldValue(field, oldValue);
            } else {
                beforeContent = getFieldValue(field, oldValue);
                afterContent = getFieldValue(field, newValue);
            }
            if (!beforeContent.equals(afterContent)) {//前后不一致
                //保存变更
                modifyRecordList.add(new ModifyRecord(className, id, attr.name(), beforeContent, afterContent, new Date(), "信息更新", LoginUtil.getLoginName()));
            }
        }
        //无则直接返回
        if (CollectionUtils.isEmpty(modifyRecordList)) {
            return num;
        }
        //有直接存储
        ModifyRecordMapper modifyRecordMapper = SpringContextUtil.getBean(ModifyRecordMapper.class);
        modifyRecordMapper.insertBatch(modifyRecordList);
        return num;
    }

    /**
     * 获取字段值,并转成string
     *
     * @param field 字段
     * @param value value
     * @return 字符串类型的值
     */
    private static String getFieldValue(Field field, Object value) {
        String typeName = field.getType().getSimpleName();
        if ("Integer".equalsIgnoreCase(typeName)) {//数字类型
            return String.valueOf(value);
        } else if ("BigInteger".equalsIgnoreCase(typeName)) {//数字类型
            return String.valueOf(value);
        } else if ("Short".equalsIgnoreCase(typeName)) {//数字类型
            return String.valueOf(value);
        } else if ("Float".equalsIgnoreCase(typeName)) {//数字类型
            return String.valueOf(value);
        } else if ("Double".equalsIgnoreCase(typeName)) {//数字类型
            return String.valueOf(value);
        } else if ("Long".equalsIgnoreCase(typeName)) {//数字类型
            return String.valueOf(value);
        } else if ("BigDecimal".equalsIgnoreCase(typeName)) {//数字类型
            return ((BigDecimal) value).toPlainString();
        } else if ("String".equalsIgnoreCase(typeName)) {//字符串
            return value.toString();
        } else if ("Date".equalsIgnoreCase(typeName)) {//日期
            SimpleDateFormat sdf = new SimpleDateFormat(DatePattern.YYYY_MM_DD_HH_MM_SS);
            return sdf.format(value);
        }
        return "";
    }

工具类直接用 


import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.apache.commons.lang3.StringUtils;

import java.lang.reflect.Field;
import java.util.Collection;
import java.util.List;
import java.util.Map;

/**
 * 实体操作工具
 *
 * @author storm
 * @since 2020/11/10 21:15
 */
public class EntityUtils {
    /**
     * 实体集合转为id,entity的map
     *
     * @param entityList 集合
     * @param <T>        泛型
     * @return map
     */
    public static <T> Map<Integer, T> toMap(List<T> entityList) {
        Map<Integer, T> rMap = Maps.newHashMap();
        for (T entity : entityList) {
            rMap.put((Integer) EntityUtils.getFieldValueByFieldName("id", entity), entity);
        }
        return rMap;
    }

    /**
     * 根据属性名获取属性值
     *
     * @param fieldName 属性名称
     * @param object    对象
     * @return 属性值
     */
    public static Object getFieldValueByFieldName(String fieldName, Object object) {
        try {
            Field field = getFieldMap(object.getClass(), fieldName).get(fieldName);
            if (null == field) {
                return null;
            }
            //设置对象的访问权限,保证对private的属性的访问
            field.setAccessible(true);
            return field.get(object);
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * 递归获取所有访问字段/搜索指定field并返回
     *
     * @param tempClass class
     * @return map结果
     */
    public static Map<String, Field> getFieldMap(Class<?> tempClass, String fieldName) {
        Map<String, Field> rMap = Maps.newHashMap();
        w:while (tempClass != null && !tempClass.isAssignableFrom(Object.class)) {//当父类为null的时候说明到达了最上层的父类(Object类).
            for (Field field : tempClass.getDeclaredFields()) {
                if (StringUtils.isNotEmpty(fieldName)) {
                    if (fieldName.equals(field.getName())) {
                        rMap.put(field.getName(), field);
                        break w;
                    }
                } else {
                    rMap.put(field.getName(), field);
                }
            }
            tempClass = tempClass.getSuperclass(); //得到父类继续循环
        }
        return rMap;
    }


    /**
     * 获取实体的属性名集合(包含父级)
     *
     * @param tempClass 实体class
     * @return 所有字段的arr
     */
    public static String[] getAllFiledNameArr(Class<?> tempClass) {
        List<String> fieldFiledNameList = Lists.newArrayList();
        while (tempClass != null && !tempClass.isAssignableFrom(Object.class)) {//当父类为null的时候说明到达了最上层的父类(Object类).
            for (Field field : tempClass.getDeclaredFields()) {
                if (!"serialVersionUID".equals(field.getName())) {//排除序列号
                    fieldFiledNameList.add(field.getName());
                }
            }
            tempClass = tempClass.getSuperclass(); //得到父类继续循环
        }
        return fieldFiledNameList.toArray(new String[0]);
    }

    /**
     * 获取实体的属性名集合(不包含父级字段)
     *
     * @param entityClass 实体class
     * @return 数据
     */
    public static String[] getFiledNameArr(Class<?> entityClass) {
        Field[] fields = entityClass.getDeclaredFields();
        List<String> fieldFiledNameList = Lists.newArrayList();
        for (Field field : fields) {
            if (!"serialVersionUID".equals(field.getName())){
                fieldFiledNameList.add(field.getName());
            }
        }
        return fieldFiledNameList.toArray(new String[0]);
    }

    /**
     * 获取实体的属性名集合(不包含父级字段和集合和map)
     *
     * @param entityClass 实体class
     * @return 数据
     */
    public static String[] getFiledNameArrWithoutCollection(Class<?> entityClass) {
        Field[] fields = entityClass.getDeclaredFields();
        List<String> fieldFiledNameList = Lists.newArrayList();
        for (Field field : fields) {
            if (!"serialVersionUID".equals(field.getName())&&!Collection.class.isAssignableFrom(field.getType())
                    &&!Map.class.isAssignableFrom(field.getType())) {//排除序列号,List和Map
                fieldFiledNameList.add(field.getName());
            }
        }
        return fieldFiledNameList.toArray(new String[0]);
    }

    /**
     *
     * @description: 实体类转Map
     * @param object
     * @return
     */
    public static Map<String, Object> entityToMap(Object object) {
        Map<String, Object> map = Maps.newHashMap();
        for (Field field : object.getClass().getDeclaredFields()) {
            try {
                boolean flag = field.isAccessible();
                field.setAccessible(true);
                Object o = field.get(object);
                map.put(field.getName(), o);
                field.setAccessible(flag);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return map;
    }

    /**
     *
     * @description: Map转实体类
     * @param <T>
     * @param map    需要初始化的数据,key字段必须与实体类的成员名字一样,否则赋值为空
     * @param entity 需要转化成的实体类
     * @return
     */
    public static <T> T mapToEntity(Map<String, Object> map, Class<T> entity) {
        T t = null;
        try {
            t = entity.newInstance();
            for (Field field : entity.getDeclaredFields()) {
                if (map.containsKey(field.getName())) {
                    boolean flag = field.isAccessible();
                    field.setAccessible(true);
                    Object object = map.get(field.getName());
                    if (object != null && field.getType().isAssignableFrom(object.getClass())) {
                        field.set(t, object);
                    }
                    field.setAccessible(flag);
                }
            }
            return t;
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return t;
    }
}

3.最后在业务数据中展示这些变更日志时,可能还要在查询时转换下,必须存储的项目Id,实际日志要展示项目中文,那就在日志列表的接口中自己识别转换下,最后效果如下:

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐