SpringBoot + 规则版本对比 + 差异高亮:新旧规则效果一目了然,降低上线风险!
问题背景
在业务系统中,规则引擎是核心组件之一,用于实现业务逻辑的灵活配置和快速调整。然而,规则的修改和上线往往伴随着风险:
- 规则复杂度高:业务规则通常包含多个条件和动作,逻辑复杂,难以直观理解
- 修改影响范围大:规则修改可能影响大量业务场景,难以全面评估影响
- 测试覆盖不足:规则测试往往依赖人工验证,容易遗漏边界情况
- 上线风险高:规则上线后发现问题,回滚成本高,影响业务连续性
- 版本管理混乱:缺乏有效的规则版本管理,难以追溯历史变更
这些问题在规则频繁更新的场景下尤为突出,比如电商平台的促销规则、风控系统的风控规则、推荐系统的推荐规则等。如何降低规则上线的风险,提高规则管理的效率,是业务系统面临的重要挑战。
核心概念
1. 规则版本管理
定义:对业务规则进行版本化管理,记录每次规则变更的历史信息,包括规则内容、修改时间、修改人、变更原因等。
优势:
- 可追溯性:能够追溯规则的历史变更,了解规则的演进过程
- 可回滚:当新规则出现问题时,可以快速回滚到历史版本
- 可对比:能够对比不同版本的规则差异,便于审核和验证
2. 规则对比
定义:对比新旧规则版本的内容差异,识别规则变更的具体内容,包括新增、修改和删除的条件和动作。
对比维度:
- 条件差异:对比规则条件的变化,如新增条件、修改条件、删除条件
- 动作差异:对比规则动作的变化,如新增动作、修改动作、删除动作
- 参数差异:对比规则参数的变化,如参数值的变化、参数类型的变化
3. 差异高亮
定义:通过视觉化的方式,高亮显示规则变更的具体内容,让规则差异一目了然。
高亮方式:
- 颜色区分:使用不同颜色表示不同类型的变更,如红色表示删除,绿色表示新增,黄色表示修改
- 标记符号:使用符号标记变更内容,如"+"表示新增,"-"表示删除,"~"表示修改
- 位置标注:在规则内容中标注变更的位置,便于快速定位
4. 规则测试
定义:在规则上线前,对新规则进行充分测试,验证规则的正确性和效果。
测试类型:
- 单元测试:测试规则的各个条件和动作是否按预期工作
- 集成测试:测试规则与其他系统的集成是否正常
- 回归测试:测试规则修改后,原有功能是否受到影响
- 性能测试:测试规则的执行性能是否满足要求
实现方案
1. 技术栈选择
- Spring Boot 2.7.5:提供基础框架支持
- Spring Web:提供REST API
- Spring Data JPA:操作数据库
- MySQL 8.0:存储规则版本和对比结果
- Lombok:简化代码
- DiffUtils:规则内容对比
- Bootstrap:Web界面样式
2. 数据库设计
规则版本表(rule_version)
| 字段名 | 数据类型 | 描述 |
|---|---|---|
| id | bigint(20) | 主键ID |
| rule_id | varchar(100) | 规则ID |
| rule_name | varchar(255) | 规则名称 |
| rule_content | text | 规则内容(JSON格式) |
| version | int(11) | 版本号 |
| status | tinyint(4) | 状态:0-草稿,1-已发布,2-已废弃 |
| create_time | datetime | 创建时间 |
| update_time | datetime | 更新时间 |
| creator | varchar(100) | 创建人 |
| updater | varchar(100) | 更新人 |
| change_reason | varchar(500) | 变更原因 |
规则对比结果表(rule_diff)
| 字段名 | 数据类型 | 描述 |
|---|---|---|
| id | bigint(20) | 主键ID |
| rule_id | varchar(100) | 规则ID |
| old_version | int(11) | 旧版本号 |
| new_version | int(11) | 新版本号 |
| diff_content | text | 差异内容(JSON格式) |
| create_time | datetime | 创建时间 |
| creator | varchar(100) | 创建人 |
3. 核心代码实现
3.1 规则版本实体类
@Entity
@Table(name = "rule_version")
public class RuleVersion {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "rule_id", nullable = false, length = 100)
private String ruleId;
@Column(name = "rule_name", nullable = false, length = 255)
private String ruleName;
@Column(name = "rule_content", nullable = false, columnDefinition = "text")
private String ruleContent;
@Column(name = "version", nullable = false)
private Integer version;
@Column(name = "status", nullable = false)
private Integer status;
@Column(name = "create_time", nullable = false, updatable = false)
private LocalDateTime createTime;
@Column(name = "update_time", nullable = false)
private LocalDateTime updateTime;
@Column(name = "creator", length = 100)
private String creator;
@Column(name = "updater", length = 100)
private String updater;
@Column(name = "change_reason", length = 500)
private String changeReason;
// getter/setter 方法
}
3.2 规则对比服务类
@Service
public class RuleDiffService {
@Autowired
private RuleVersionRepository ruleVersionRepository;
@Autowired
private RuleDiffRepository ruleDiffRepository;
/**
* 对比两个版本的规则
*/
public RuleDiff compareVersions(String ruleId, Integer oldVersion, Integer newVersion) {
// 查询两个版本的规则
RuleVersion oldRule = ruleVersionRepository.findByRuleIdAndVersion(ruleId, oldVersion);
RuleVersion newRule = ruleVersionRepository.findByRuleIdAndVersion(ruleId, newVersion);
if (oldRule == null || newRule == null) {
throw new RuntimeException("规则版本不存在");
}
// 解析规则内容
Map<String, Object> oldContent = parseRuleContent(oldRule.getRuleContent());
Map<String, Object> newContent = parseRuleContent(newRule.getRuleContent());
// 对比规则内容
Map<String, Object> diff = compareRuleContent(oldContent, newContent);
// 保存对比结果
RuleDiff ruleDiff = new RuleDiff();
ruleDiff.setRuleId(ruleId);
ruleDiff.setOldVersion(oldVersion);
ruleDiff.setNewVersion(newVersion);
ruleDiff.setDiffContent(serializeDiff(diff));
ruleDiff.setCreateTime(LocalDateTime.now());
ruleDiff.setCreator(getCurrentUser());
return ruleDiffRepository.save(ruleDiff);
}
/**
* 对比规则内容
*/
private Map<String, Object> compareRuleContent(Map<String, Object> oldContent, Map<String, Object> newContent) {
Map<String, Object> diff = new HashMap<>();
// 对比条件
List<Map<String, Object>> oldConditions = (List<Map<String, Object>>) oldContent.get("conditions");
List<Map<String, Object>> newConditions = (List<Map<String, Object>>) newContent.get("conditions");
diff.put("conditions", compareConditions(oldConditions, newConditions));
// 对比动作
List<Map<String, Object>> oldActions = (List<Map<String, Object>>) oldContent.get("actions");
List<Map<String, Object>> newActions = (List<Map<String, Object>>) newContent.get("actions");
diff.put("actions", compareActions(oldActions, newActions));
return diff;
}
/**
* 对比条件
*/
private List<Map<String, Object>> compareConditions(List<Map<String, Object>> oldConditions, List<Map<String, Object>> newConditions) {
List<Map<String, Object>> diff = new ArrayList<>();
// 找出新增的条件
for (Map<String, Object> newCondition : newConditions) {
if (!oldConditions.contains(newCondition)) {
Map<String, Object> item = new HashMap<>();
item.put("type", "add");
item.put("content", newCondition);
diff.add(item);
}
}
// 找出删除的条件
for (Map<String, Object> oldCondition : oldConditions) {
if (!newConditions.contains(oldCondition)) {
Map<String, Object> item = new HashMap<>();
item.put("type", "delete");
item.put("content", oldCondition);
diff.add(item);
}
}
// 找出修改的条件
for (Map<String, Object> oldCondition : oldConditions) {
for (Map<String, Object> newCondition : newConditions) {
if (oldCondition.get("id").equals(newCondition.get("id")) && !oldCondition.equals(newCondition)) {
Map<String, Object> item = new HashMap<>();
item.put("type", "modify");
item.put("oldContent", oldCondition);
item.put("newContent", newCondition);
diff.add(item);
}
}
}
return diff;
}
// 其他方法:compareActions、parseRuleContent、serializeDiff等
}
3.3 规则管理控制器
@RestController
@RequestMapping("/rules")
public class RuleController {
@Autowired
private RuleVersionRepository ruleVersionRepository;
@Autowired
private RuleDiffService ruleDiffService;
/**
* 创建新规则版本
*/
@PostMapping
public ResponseEntity<Result> createRule(@RequestBody RuleCreateRequest request) {
try {
// 获取当前最大版本号
Integer maxVersion = ruleVersionRepository.findMaxVersionByRuleId(request.getRuleId());
Integer newVersion = maxVersion == null ? 1 : maxVersion + 1;
// 创建新规则版本
RuleVersion ruleVersion = new RuleVersion();
ruleVersion.setRuleId(request.getRuleId());
ruleVersion.setRuleName(request.getRuleName());
ruleVersion.setRuleContent(request.getRuleContent());
ruleVersion.setVersion(newVersion);
ruleVersion.setStatus(0); // 草稿状态
ruleVersion.setCreator(getCurrentUser());
ruleVersion.setChangeReason(request.getChangeReason());
ruleVersionRepository.save(ruleVersion);
return ResponseEntity.ok(Result.success(ruleVersion));
} catch (Exception e) {
log.error("创建规则失败", e);
return ResponseEntity.ok(Result.error("创建规则失败:" + e.getMessage()));
}
}
/**
* 对比规则版本
*/
@GetMapping("/{ruleId}/diff")
public ResponseEntity<Result> compareVersions(
@PathVariable String ruleId,
@RequestParam Integer oldVersion,
@RequestParam Integer newVersion) {
try {
RuleDiff ruleDiff = ruleDiffService.compareVersions(ruleId, oldVersion, newVersion);
return ResponseEntity.ok(Result.success(ruleDiff));
} catch (Exception e) {
log.error("对比规则版本失败", e);
return ResponseEntity.ok(Result.error("对比规则版本失败:" + e.getMessage()));
}
}
/**
* 发布规则版本
*/
@PutMapping("/{ruleId}/publish")
public ResponseEntity<Result> publishRule(
@PathVariable String ruleId,
@RequestParam Integer version) {
try {
// 废弃旧版本
ruleVersionRepository.updateStatusByRuleId(ruleId, 2);
// 发布新版本
RuleVersion ruleVersion = ruleVersionRepository.findByRuleIdAndVersion(ruleId, version);
ruleVersion.setStatus(1);
ruleVersion.setUpdater(getCurrentUser());
ruleVersionRepository.save(ruleVersion);
return ResponseEntity.ok(Result.success("发布规则成功"));
} catch (Exception e) {
log.error("发布规则失败", e);
return ResponseEntity.ok(Result.error("发布规则失败:" + e.getMessage()));
}
}
// 其他方法:getRuleVersions、getRuleVersion等
}
3.4 Web界面
Web界面使用HTML、CSS和JavaScript实现,包括规则列表展示、规则版本对比、差异高亮显示等功能。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>规则版本对比</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css">
<script src="https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
<style>
.diff-add {
background-color: #d4edda;
color: #155724;
padding: 10px;
margin: 5px 0;
border-left: 4px solid #28a745;
}
.diff-delete {
background-color: #f8d7da;
color: #721c24;
padding: 10px;
margin: 5px 0;
border-left: 4px solid #dc3545;
}
.diff-modify {
background-color: #fff3cd;
color: #856404;
padding: 10px;
margin: 5px 0;
border-left: 4px solid #ffc107;
}
.diff-content {
font-family: monospace;
white-space: pre-wrap;
}
</style>
</head>
<body>
<div class="container mt-5">
<h1 class="mb-4">规则版本对比</h1>
<!-- 规则选择 -->
<div class="card mb-4">
<div class="card-body">
<div class="row">
<div class="col-md-4">
<label for="ruleSelect" class="form-label">选择规则</label>
<select class="form-select" id="ruleSelect">
<option value="">请选择规则</option>
</select>
</div>
<div class="col-md-3">
<label for="oldVersion" class="form-label">旧版本</label>
<select class="form-select" id="oldVersion">
<option value="">请选择版本</option>
</select>
</div>
<div class="col-md-3">
<label for="newVersion" class="form-label">新版本</label>
<select class="form-select" id="newVersion">
<option value="">请选择版本</option>
</select>
</div>
<div class="col-md-2 d-flex align-items-end">
<button class="btn btn-primary w-100" id="compareBtn">对比</button>
</div>
</div>
</div>
</div>
<!-- 对比结果 -->
<div class="card" id="diffResult" style="display: none;">
<div class="card-header">
<h5 class="mb-0">对比结果</h5>
</div>
<div class="card-body">
<div id="diffContent">
<!-- 对比内容将通过JavaScript动态生成 -->
</div>
</div>
</div>
</div>
<script>
// 页面加载完成后加载规则列表
$(document).ready(function() {
loadRules();
});
// 加载规则列表
function loadRules() {
$.ajax({
url: '/api/rules',
type: 'GET',
success: function(response) {
if (response.code === 200) {
var rules = response.data;
var select = $('#ruleSelect');
select.empty();
select.append('<option value="">请选择规则</option>');
rules.forEach(function(rule) {
select.append('<option value="' + rule.ruleId + '">' + rule.ruleName + '</option>');
});
} else {
alert('加载规则列表失败:' + response.message);
}
},
error: function() {
alert('加载规则列表失败');
}
});
}
// 规则选择变化时加载版本列表
$('#ruleSelect').change(function() {
var ruleId = $(this).val();
loadVersions(ruleId);
});
// 加载版本列表
function loadVersions(ruleId) {
if (!ruleId) {
$('#oldVersion').empty().append('<option value="">请选择版本</option>');
$('#newVersion').empty().append('<option value="">请选择版本</option>');
return;
}
$.ajax({
url: '/api/rules/' + ruleId + '/versions',
type: 'GET',
success: function(response) {
if (response.code === 200) {
var versions = response.data;
var oldSelect = $('#oldVersion');
var newSelect = $('#newVersion');
oldSelect.empty().append('<option value="">请选择版本</option>');
newSelect.empty().append('<option value="">请选择版本</option>');
versions.forEach(function(version) {
var option = '<option value="' + version.version + '">v' + version.version + ' - ' + version.status + ' - ' + version.createTime + '</option>';
oldSelect.append(option);
newSelect.append(option);
});
} else {
alert('加载版本列表失败:' + response.message);
}
},
error: function() {
alert('加载版本列表失败');
}
});
}
// 对比按钮点击事件
$('#compareBtn').click(function() {
var ruleId = $('#ruleSelect').val();
var oldVersion = $('#oldVersion').val();
var newVersion = $('#newVersion').val();
if (!ruleId || !oldVersion || !newVersion) {
alert('请选择规则和版本');
return;
}
compareVersions(ruleId, oldVersion, newVersion);
});
// 对比版本
function compareVersions(ruleId, oldVersion, newVersion) {
$.ajax({
url: '/api/rules/' + ruleId + '/diff',
type: 'GET',
data: {
oldVersion: oldVersion,
newVersion: newVersion
},
success: function(response) {
if (response.code === 200) {
var diff = response.data;
displayDiff(diff);
} else {
alert('对比版本失败:' + response.message);
}
},
error: function() {
alert('对比版本失败');
}
});
}
// 显示对比结果
function displayDiff(diff) {
var diffContent = $('#diffContent');
diffContent.empty();
// 显示条件差异
var conditionsDiff = diff.conditions;
if (conditionsDiff && conditionsDiff.length > 0) {
var conditionsHtml = '<h6>条件差异</h6>';
conditionsDiff.forEach(function(item) {
if (item.type === 'add') {
conditionsHtml += '<div class="diff-add"><strong>新增条件:</strong><div class="diff-content">' + JSON.stringify(item.content, null, 2) + '</div></div>';
} else if (item.type === 'delete') {
conditionsHtml += '<div class="diff-delete"><strong>删除条件:</strong><div class="diff-content">' + JSON.stringify(item.content, null, 2) + '</div></div>';
} else if (item.type === 'modify') {
conditionsHtml += '<div class="diff-modify"><strong>修改条件:</strong><div class="diff-content">旧值:' + JSON.stringify(item.oldContent, null, 2) + '</div><div class="diff-content">新值:' + JSON.stringify(item.newContent, null, 2) + '</div></div>';
}
});
diffContent.append(conditionsHtml);
}
// 显示动作差异
var actionsDiff = diff.actions;
if (actionsDiff && actionsDiff.length > 0) {
var actionsHtml = '<h6>动作差异</h6>';
actionsDiff.forEach(function(item) {
if (item.type === 'add') {
actionsHtml += '<div class="diff-add"><strong>新增动作:</strong><div class="diff-content">' + JSON.stringify(item.content, null, 2) + '</div></div>';
} else if (item.type === 'delete') {
actionsHtml += '<div class="diff-delete"><strong>删除动作:</strong><div class="diff-content">' + JSON.stringify(item.content, null, 2) + '</div></div>';
} else if (item.type === 'modify') {
actionsHtml += '<div class="diff-modify"><strong>修改动作:</strong><div class="diff-content">旧值:' + JSON.stringify(item.oldContent, null, 2) + '</div><div class="diff-content">新值:' + JSON.stringify(item.newContent, null, 2) + '</div></div>';
}
});
diffContent.append(actionsHtml);
}
$('#diffResult').show();
}
</script>
</body>
</html>
最佳实践
1. 规则版本管理
- 版本号管理:使用递增的版本号,确保版本的唯一性和可追溯性
- 状态管理:明确规则的状态(草稿、已发布、已废弃),避免误操作
- 变更记录:记录规则变更的原因和修改人,便于追溯和审计
- 权限控制:根据角色分配规则管理权限,确保规则的安全性
2. 规则对比
- 全面对比:对比规则的所有内容,包括条件、动作、参数等
- 精确识别:准确识别规则变更的类型(新增、修改、删除)
- 差异标注:清晰标注差异的位置和内容,便于理解和审核
- 对比结果保存:保存对比结果,便于后续查看和审计
3. 差异高亮
- 颜色区分:使用不同颜色表示不同类型的变更,提高可读性
- 符号标记:使用符号标记变更内容,便于快速识别
- 位置标注:在规则内容中标注变更的位置,便于定位
- 格式化显示:格式化显示规则内容,提高可读性
4. 规则测试
- 充分测试:在规则上线前进行充分的测试,验证规则的正确性
- 回归测试:测试规则修改后,原有功能是否受到影响
- 性能测试:测试规则的执行性能是否满足要求
- 灰度发布:采用灰度发布策略,逐步放量,降低上线风险
5. 上线流程
- 审核机制:建立规则审核机制,确保规则变更的合理性
- 发布流程:建立规范的发布流程,包括测试、审核、发布、监控等环节
- 回滚机制:建立快速回滚机制,当规则出现问题时能够快速回滚
- 监控告警:监控规则执行情况,及时发现和解决问题
总结与展望
总结
本方案通过SpringBoot + 规则版本对比 + 差异高亮的组合,实现了规则版本的可视化对比和差异高亮显示,有效降低了规则上线的风险。主要优势包括:
- 可视化对比:通过Web界面直观地展示规则差异,便于理解和审核
- 差异高亮:使用颜色和符号标记差异内容,提高可读性
- 版本管理:完善的规则版本管理,支持版本追溯和回滚
- 降低风险:通过规则对比和测试,降低规则上线的风险
- 提高效率:自动化的规则对比和差异高亮,提高规则管理的效率
展望
- 智能对比:引入AI技术,智能识别规则变更的影响范围和风险
- 自动化测试:自动生成测试用例,自动执行规则测试
- 实时监控:实时监控规则执行情况,及时发现和解决问题
- 预测分析:基于历史数据,预测规则变更的影响和效果
- 可视化展示:提供更丰富的可视化展示方式,如图表、流程图等
结语
规则版本对比和差异高亮是规则管理的重要功能,能够有效降低规则上线的风险,提高规则管理的效率。通过本方案的实现,我们可以让规则变更更加透明和可控,确保规则的质量和稳定性。
希望本方案能够为您的项目提供参考,帮助您实现更加安全、高效的规则管理系统。
公众号:服务端技术精选
如果您有任何问题或建议,欢迎在评论区留言讨论。
标题:SpringBoot + 规则版本对比 + 差异高亮:新旧规则效果一目了然,降低上线风险!
作者:jiangyi
地址:http://jiangyi.space/articles/2026/03/06/1772636807424.html
公众号:服务端技术精选
评论
0 评论