主题切换
🔍 对象差异对比工具(ObjectCompareHelper)
📖 功能介绍
TIP
ObjectCompareHelper 是一个强大的对象差异对比工具类,支持通过注解配置字段对比规则,提供字典转换、格式化、自定义解析等功能,适用于数据变更记录、审计日志等场景。
🛠️ 核心功能
1. 对象对比功能
功能 | 说明 | 支持类型 |
---|---|---|
字段值对比 | 检测对象字段值变化 | 基本类型、对象类型 |
字典转换 | 将编码值转换为可读文本 | String、Integer等 |
格式化显示 | 按指定格式显示值 | Date、Number等 |
自定义解析 | 通过自定义解析器处理特殊值 | 任意类型 |
📝 注解说明
@CompareField 注解
用于标记需要参与对比的字段,并配置对比规则和显示格式。
java
/**
* 对象字段对比注解
* @author: fallsea
* @version 1.0
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CompareField {
/**
* 字段中文描述名称
*/
String name();
/**
* 是否参与对比,默认true
*/
boolean compare() default true;
/**
* 字典类型,用于值转换显示
*/
String dictType() default "";
/**
* 单位,如:元、个、天等
*/
String unit() default "";
/**
* 日期格式化,默认yyyy-MM-dd HH:mm:ss
*/
String dateFormat() default "yyyy-MM-dd HH:mm:ss";
/**
* 时区,默认为系统时区
* 支持格式:
* 1. 时区ID:如 "Asia/Shanghai", "UTC", "GMT+8"
* 2. 空字符串表示使用系统默认时区
*/
String timeZone() default "";
/**
* 数字格式化,如保留小数位数
*/
String numberFormat() default "";
/**
* 排序权重,数字越小越靠前
*/
int order() default 0;
/**
* 自定义解析器类
* 如果指定了自定义解析器,将优先使用自定义解析器进行值转换
*/
Class<? extends ValueParser> parser() default ValueParser.class;
}
注解属性详解
属性 | 类型 | 默认值 | 说明 | 示例 |
---|---|---|---|---|
name | String | 必填 | 字段中文描述名称 | "用户状态" |
compare | boolean | true | 是否参与对比 | true/false |
dictType | String | "" | 字典类型编码 | "user_status" |
unit | String | "" | 显示单位 | "元"、"个"、"天" |
dateFormat | String | "yyyy-MM-dd HH:mm:ss" | 日期格式 | "yyyy-MM-dd" |
timeZone | String | "" | 时区设置 | "Asia/Shanghai" |
numberFormat | String | "" | 数字格式 | "#.##" |
order | int | 0 | 排序权重 | 1, 2, 3... |
parser | Class | ValueParser.class | 自定义解析器 | CustomParser.class |
💡 使用示例
1. 基础对比示例
java
public class User {
@CompareField(name = "用户名", order = 1)
private String username;
@CompareField(name = "用户状态", dictType = "user_status", order = 2)
private Integer status;
@CompareField(name = "余额", unit = "元", numberFormat = "#.##", order = 3)
private BigDecimal balance;
@CompareField(name = "创建时间", dateFormat = "yyyy-MM-dd HH:mm:ss",
timeZone = "Asia/Shanghai", order = 4)
private Date createTime;
@CompareField(compare = false) // 不参与对比
private String password;
// getter/setter...
}
2. 字典转换示例
字典服务接口
对象对比工具支持通过字典服务进行值转换,需要实现以下接口:
java
/**
* 字典服务接口
* @author: fallsea
* @version 1.0
*/
public interface ObjectCompareDictService {
/**
* 根据字典类型和值获取显示文本
* @param dictType 字典类型
* @param value 字典值
* @return 显示文本
*/
String getDictText(String dictType, Object value);
}
字典服务实现示例
java
@Service
public class ObjectCompareDictServiceImpl implements ObjectCompareDictService {
@Override
public String getDictText(String dictType, Object value) {
// 返回字典文本,如果找不到则返回原值
return "";
}
}
使用示例
java
// 用户状态字典配置
// user_status: 1-正常, 2-禁用, 3-锁定
User oldUser = new User();
oldUser.setStatus(1); // 正常
User newUser = new User();
newUser.setStatus(2); // 禁用
// 对比结果会显示:
// 用户状态: 正常 → 禁用
List<CompareResult> results = ObjectCompareHelper.compare(oldUser, newUser);
3. 自定义解析器示例
java
// 自定义解析器
public class StatusParser implements ValueParser {
@Override
public String parse(Object value, CompareField field) {
if (value instanceof Integer) {
Integer status = (Integer) value;
switch (status) {
case 1: return "激活";
case 2: return "停用";
default: return "未知";
}
}
return String.valueOf(value);
}
}
// 使用自定义解析器
@CompareField(name = "账户状态", parser = StatusParser.class)
private Integer accountStatus;
4. 复杂对象对比
java
public class Order {
@CompareField(name = "订单号", order = 1)
private String orderNo;
@CompareField(name = "订单状态", dictType = "order_status", order = 2)
private String status;
@CompareField(name = "订单金额", unit = "元", numberFormat = "#,##0.00", order = 3)
private BigDecimal amount;
@CompareField(name = "下单时间", dateFormat = "yyyy-MM-dd HH:mm",
timeZone = "Asia/Shanghai", order = 4)
private LocalDateTime orderTime;
@CompareField(name = "有效天数", unit = "天", order = 5)
private Integer validDays;
// getter/setter...
}
// 使用示例
Order oldOrder = new Order();
oldOrder.setAmount(new BigDecimal("1000.50"));
oldOrder.setStatus("PENDING");
Order newOrder = new Order();
newOrder.setAmount(new BigDecimal("1200.80"));
newOrder.setStatus("CONFIRMED");
List<CompareResult> results = ObjectCompareHelper.compare(oldOrder, newOrder);
// 输出:
// 订单状态: 待确认 → 已确认
// 订单金额: 1,000.50元 → 1,200.80元
🔧 高级功能
1. 时区处理
java
@CompareField(name = "创建时间",
dateFormat = "yyyy-MM-dd HH:mm:ss",
timeZone = "Asia/Shanghai")
private Date createTime;
@CompareField(name = "更新时间",
dateFormat = "yyyy-MM-dd HH:mm:ss",
timeZone = "UTC")
private Date updateTime;
2. 数字格式化
java
@CompareField(name = "价格", unit = "元", numberFormat = "#,##0.00")
private BigDecimal price;
@CompareField(name = "百分比", unit = "%", numberFormat = "#.##")
private Double percentage;
@CompareField(name = "数量", unit = "个", numberFormat = "#,###")
private Integer quantity;
3. 排序控制
java
public class Product {
@CompareField(name = "商品名称", order = 1)
private String name;
@CompareField(name = "商品价格", order = 2, unit = "元")
private BigDecimal price;
@CompareField(name = "商品状态", order = 3, dictType = "product_status")
private Integer status;
@CompareField(name = "创建时间", order = 10)
private Date createTime;
}
⚠️ 注意事项
注解配置
name
属性为必填项- 字典类型需要在系统中预先配置
- 自定义解析器需要实现
ValueParser
接口
性能考虑
- 避免对大量对象进行频繁对比
- 字典查询建议使用缓存
- 自定义解析器避免复杂计算
数据类型支持
- 基本数据类型及其包装类
- 日期时间类型(Date、LocalDateTime等)
- 数字类型(BigDecimal、Double等)
- 字符串类型
字典配置
- 确保字典类型在系统中存在
- 字典值应覆盖所有可能的字段值
- 建议使用缓存提高查询性能
💡 最佳实践
1. 实体类配置
java
@Entity
public class UserProfile {
@CompareField(name = "用户ID", order = 1)
private Long userId;
@CompareField(name = "真实姓名", order = 2)
private String realName;
@CompareField(name = "性别", dictType = "gender", order = 3)
private String gender;
@CompareField(name = "年龄", unit = "岁", order = 4)
private Integer age;
@CompareField(name = "认证状态", dictType = "auth_status", order = 5)
private Integer authStatus;
@CompareField(name = "最后登录", dateFormat = "yyyy-MM-dd HH:mm",
timeZone = "Asia/Shanghai", order = 6)
private Date lastLoginTime;
@CompareField(compare = false) // 敏感信息不参与对比
private String idCard;
}
2. 对比结果处理
java
public class AuditService {
public void recordChanges(Object oldObj, Object newObj, String operator) {
List<CompareResult> results = ObjectCompareHelper.compare(oldObj, newObj);
for (CompareResult result : results) {
AuditLog log = new AuditLog();
log.setFieldName(result.getFieldName());
log.setOldValue(result.getOldValue());
log.setNewValue(result.getNewValue());
log.setOperator(operator);
log.setOperateTime(new Date());
auditLogRepository.save(log);
}
}
}
3. 自定义解析器最佳实践
java
public class MoneyParser implements ValueParser {
@Override
public String parse(Object value, CompareField field) {
if (value == null) {
return "0.00" + field.unit();
}
if (value instanceof BigDecimal) {
BigDecimal money = (BigDecimal) value;
DecimalFormat df = new DecimalFormat("#,##0.00");
return df.format(money) + field.unit();
}
return String.valueOf(value) + field.unit();
}
}