同源异构:Jolt如何解决质检报告的差异化展示难题
1 背景 2 Jolt简介 2.1 什么是Jolt? 2.2 为什么Jolt与众不同? 2.3 Jolt的核心优势 2.4 核心概念一览 2.5 Jolt的典型应用场景 3 场景化实践 3.1 改造蓝图:基于Jolt的报告生成流程 3.2 Jolt语法速成: 快速掌握核心操作 3.3 在Spring Boot中集成Jolt 4 未来规划
1 背景
在二手奢侈品行业,一份专业的质检报告是建立信任的基石。然而,随着业务的深入发展,我们发现了一个有趣的现象:
同样一份质检数据,在不同人眼中却有着完全不同的价值。
B端商户 更关心商品的真伪鉴定结果、成色等级评定等影响定价的核心信息。 C端卖家 则更在意验货流程的透明度、瑕疵详情等影响交易决策的要素。 平台运营 需要从差异分析角度,持续优化验货标准和流程。
更棘手的是,这些关注点并非一成不变。市场在变,用户在变,需求自然也在变。昨天还是次要信息的配件完整度,今天可能就成了用户最关心的焦点。
面对频繁变化的展示需求,我们曾经试过最"简单直接"的方法——改代码。结果可想而知:开发疲于奔命,业务等待焦急,系统越来越难维护。
如果展示逻辑可以像配置文件一样灵活调整,如果业务人员也能参与报告格式的设计,如果一份数据真的可以"随需而变"……
这正是Jolt给我们带来的改变。
2 Jolt简介
2.1 什么是Jolt?
Jolt是一个专注于JSON到JSON转换的Java库,由Bazaarvoice公司开源。它的核心理念很简单:用JSON来描述JSON的转换规则。
想象一下,如果数据转换也能像配置文件一样简单——这正是Jolt带来的革命性改变。
2.2 为什么Jolt与众不同?
在传统的开发模式中,数据转换往往意味着:
// 传统方式:硬编码的痛苦,需要隐藏数据或者调整数据位置都需要重新部署if (Objects.equals("B2B",userType)) { merchantQualityReport.setAdditionalInfo(base.getAd()); merchantQualityReport.setBasicFunctionInfo(base.getBa());// ... 几十行甚至上百行的处理结构映射的代码逻辑} elseif (Objects.equals("B2C",userType)) { qualityReport.setFinenessLevel(base.getFin()); qualityReport.setOtherDetailInfo(base.getOther());// ... 又是一大段不同的映射逻辑}而使用Jolt,同样的逻辑变成了:
// 我们通过声明或内置指令可以实现动态转换和展示{"operation": "shift","spec": {"ad": "additionalInfo","base": "basicFunctionInfo" }}2.3 Jolt的核心优势
1. 配置化管理
转换逻辑存储在JSON文件中,可以把转换规则放在配置中心 无需重新编译,随时调整
2. 强大的转换能力
支持复杂的嵌套结构转换,支持自定义操作符 提供丰富的内置操作符 可以处理数组、条件转换等高级场景
3. 链式操作
多个转换可以串联执行 每个转换专注做一件事 复杂逻辑通过简单操作组合实现
2.4 核心概念一览
Jolt提供了几种基础操作,就像积木块一样,可以组合出强大的转换能力:
| shift | ||
| default | ||
| remove | ||
| sort | ||
| cardinality |
2.5 Jolt的典型应用场景
API网关:统一不同微服务的数据格式 数据迁移:新旧系统的数据格式转换 报表系统:灵活的数据展示需求
3 场景化实践
3.1 改造蓝图:基于Jolt的报告生成流程
传统架构的问题分析
在引入Jolt之前,我们的质检报告面临着典型的"硬编码困境":
// 传统方式:每种报告都需要独立的转换类publicclassB2BReportConverter{public B2BReport convert(InspectionData data){ B2BReport report = new B2BReport(); report.setProductCode(data.getBasicInfo().getCode()); report.setAuthResult(data.getAuthentication().getResult());// ... 数百行的字段映射代码 }}publicclassB2CReportConverter{public B2CReport convert(InspectionData data){// 又是一套完全不同的转换逻辑 }}痛点总结:
每新增一种报告格式,需要新增一个转换类,每个转换类需要写大量逻辑。 质检报告中质检项调整需要修改代码、测试、重新上线。
新流程设计思路
基于Jolt将转换逻辑从代码中解耦出来:针对不同的报告需求定义其对应的业务code,针对业务code编写Jolt规则。将所有Jolt规则统一存储在配置中心,支持热更新和版本管理的功能。
3.2 Jolt语法速成: 快速掌握核心操作
让我们先通过简单的示例快速掌握Jolt的核心语法。
3.2.1 shift操作 - 数据的搬运工
shift是Jolt最常用的操作,它就像一个精确的数据搬运工,可以将数据从一个位置移动到另一个位置。
基础示例:字段重命名
// 原始数据{"productName": "LV Neverfull","productPrice": 8500}// Jolt规则[ {"operation": "shift","spec": {"productName": "name", // 将productName移动到name"productPrice": "price"// 将productPrice移动到price } }]// 输出结果{"name": "LV Neverfull","price": 8500}进阶示例:数据重组
// 原始数据{"brand": "Hermès","model": "Birkin 30","color": "黑色","material": "Togo皮"}// Jolt规则 - 将平铺的字段组织成嵌套结构[ {"operation": "shift","spec": {"brand": "basicInfo.brand","model": "basicInfo.model","color": "appearance.color","material": "appearance.material" } }]// 输出结果{"basicInfo": {"brand": "Hermès","model": "Birkin 30" },"appearance": {"color": "黑色","material": "Togo皮" }}3.2.2 default操作 - 默认值填充器
default操作用于为缺失的字段添加默认值,确保输出数据的完整性。
// 原始数据{"productId": "LX001","authenticity": "真品"}// Jolt规则 - 添加默认值[ {"operation": "default","spec": {"status": "待审核","priority": "normal","tags": ["二手奢侈品"],"inspection": {"required": true,"level": "standard" } } }]// 输出结果{"productId": "LX001","authenticity": "真品","status": "待审核","priority": "normal","tags": ["二手奢侈品"],"inspection": {"required": true,"level": "standard" }}3.2.3 remove操作 - 敏感信息过滤器
remove操作用于删除不需要的字段,特别适合过滤敏感信息。
// 原始数据{"orderId": "2024001","customerName": "张女士","customerPhone": "138****1234","internalCost": 5000,"sellingPrice": 8500,"profit": 3500}// Jolt规则 - 删除内部敏感信息[ {"operation": "remove","spec": {"internalCost": "","profit": "" } }]// 输出结果{"orderId": "2024001","customerName": "张女士","customerPhone": "138****1234","sellingPrice": 8500}3.2.4 modify-overwrite-beta操作 - 内容转换器
modify-overwrite-beta时Jolt提供数据转换的操作符,常用于数据格式化、类型转换等场景。
modify支持一系列函数,函数其本质是Java自带的,在Spec以下是函数及其对应的源码:
privatestaticfinal Map<String, Function> STOCK_FUNCTIONS = new HashMap<>( );static { STOCK_FUNCTIONS.put( "toLower", new Strings.toLowerCase() ); STOCK_FUNCTIONS.put( "toUpper", new Strings.toUpperCase() ); STOCK_FUNCTIONS.put( "concat", new Strings.concat() ); STOCK_FUNCTIONS.put( "join", new Strings.join() ); STOCK_FUNCTIONS.put( "split", new Strings.split() ); STOCK_FUNCTIONS.put( "substring", new Strings.substring() ); STOCK_FUNCTIONS.put( "trim", new Strings.trim() ); STOCK_FUNCTIONS.put( "leftPad", new Strings.leftPad() ); STOCK_FUNCTIONS.put( "rightPad", new Strings.rightPad() ); .....更多函数不多赘述 }modify修改数据示例
/ 原始数据 - 质检员录入的原始数据{"brand": "LV","model": "Neverfull MM","price": 12800,"authenticity": true,"condition": "A"}// Jolt规则 - 转换为用户友好的展示格式[ {"operation": "modify-overwrite-beta", "spec": {"brand": "=concat(@(1,brand), ' - 路易威登')","model": "=toUpper(@(1,model))","price": "=concat('¥', @(1,price), '.00')","authenticity": "=toString(@(1,authenticity))","condition": "=concat('成色:', @(1,condition), '级')" } }]// 输出结果 - 格式化后的展示数据{"brand": "LV - 路易威登","model": "NEVERFULL MM","price": "¥12800.00","authenticity": "true","condition": "成色:A级"}3.2.5 自定义操作符
在实际场景中,上面modify提供的操作符不能完全满足业务需求,因此我们就需要扩展自定义操作符。
首先定义方法实现
//一个equals的示例publicclassEqualsFunctionimplementsFunction{@Overridepublic Optional<Object> apply(Object... args){if (args == null || args.length < NumberConstant.NUMBER_2) {return Optional.empty(); }if (args[0] == null || args[1] == null) {return Optional.of(false); }return Optional.of(Objects.equals(args[0], args[1])); }}然后需要自定义Transform
/** * 自定义Jolt转换器 - 支持自定义函数的数据转换 * 实现了SpecDriven接口(支持JSON配置)和ContextualTransform接口(支持上下文传递) */publicclassCustomizeTransformimplementsSpecDriven, ContextualTransform{// 根节点的转换规则对象,用于存储和执行转换逻辑privatefinal ModifierCompositeSpec rootSpec;/** * 构造函数 - 初始化自定义转换器 * @param spec JSON格式的转换规则配置 */publicCustomizeTransform(Object spec){// 创建自定义的equals函数实例 EqualsFunction equalsFunction = new EqualsFunction();// 创建函数映射表,用于注册自定义函数 Map<String, Function> functionsMap = new HashMap<>();// 注册equals函数,使其可以在Jolt规则中通过"=equals(...)"调用 functionsMap.put("equals", equalsFunction);// 将函数映射表设为不可修改,确保线程安全 functionsMap = Collections.unmodifiableMap(functionsMap);// 创建模板规则构建器,指定操作模式为OVERWRITE(覆盖模式)// 并传入自定义函数映射表 TemplatrSpecBuilder templatrSpecBuilder = new TemplatrSpecBuilder(OpMode.OVERWRITR, functionsMap);// 构建根节点的转换规则// ROOT_KEY: 根节点标识符// spec: 用户提供的转换规则// OpMode.OVERWRITR: 覆盖模式,会替换已存在的值 rootSpec = new ModifierCompositeSpec(ROOT_KEY, (Map<String, Object>) spec, OpMode.OVERWRITR, templatrSpecBuilder); }/** * 执行转换操作 * @param input 输入的JSON数据 * @param context 上下文信息(可包含额外的变量供转换时使用) * @return 转换后的数据 */@Overridepublic Object transform(Object input, Map<String, Object> context){// 创建上下文包装器,将传入的上下文放在ROOT_KEY下// 这样可以在转换规则中通过路径访问上下文数据 Map<String, Object> contextWrapper = new HashMap<>(); contextWrapper.put(ROOT_KEY, context);// 创建根元素的匹配对象 MatchedElement rootLpe = new MatchedElement(ROOT_KEY);// 创建路径追踪器,用于记录当前处理的数据路径 WalkedPath walkedPath = new WalkedPath();// 将输入数据和根元素添加到路径中 walkedPath.add(input, rootLpe);// 应用转换规则// ROOT_KEY: 当前处理的键名// Optional.of(input): 包装的输入数据// walkedPath: 当前路径信息// null: 输出路径(这里不需要)// contextWrapper: 包含上下文的数据 rootSpec.apply(ROOT_KEY, Optional.of(input), walkedPath, null, contextWrapper);// 返回转换后的数据(原地修改)return input; }}最后就是jolt规则使用自定义操作符
[ {//自定义类"operation": "com.luxuryqc.jolt.CustomizeTransform", "spec": {// 生成总体评级"assessment.overallRating": "=equals(@(1,assessment.authenticityConfirmed), true) && =equals(@(1,assessment.qualityGrade), true) ? 'S级精品' : 'A级优品'" }]3.3 在Spring Boot中集成Jolt
将Jolt集成到Spring Boot项目中非常简单:
3.3.1 添加依赖
<dependency><groupId>com.bazaarvoice.jolt</groupId><artifactId>jolt-core</artifactId><version>0.1.6</version></dependency>3.3.2 创建转换服务
@ServicepublicclassJoltTransformService{public <T> T transformData(QualityReportDo reportDo, String key, TypeReference<T> typeReference){try {//根据规则初始化chainr Chainr chainr = Chainr.fromSpec(getRuleInfo(key));//进行规则转换 Object transform = chainr.transform(reportDo);if (transform == null) {thrownew BusinessException("transform is null"); }//返回数据return JsonUtil.silentString2Object(JsonUtil.silentObject2String(transform), typeReference); } catch (Exception e) { log.error("transformData error key: {} ", key, e);thrownew BusinessException("数据规则转换异常"); } }}public List<Object> getRuleInfo(String key){//拉取jolt规则配置,可以配置在配置中心或者自定义数据表中 String rule = getRule(key);return JsonUtils.jsonToList(rule);}3.3.3 调用侧使用
@RestController@RequestMapping("/api/report")publicclassReportController{@Autowiredprivate JoltTransformService transformService;public MerchantReport generateMerchantReport(String reportId){// 1. 获取原始质检数据 QualityReportDo reportDo = getQualityReport(reportId);// 2. 使用Jolt转换 MerchantReport merchantReport = joltTransformService.transformData( reportDo, "MERCHANT_REPORT_RULE", // 配置中心的规则keynew TypeReference<MerchantReport>() {} );// 3. 业务后处理(如需要) postProcessMerchantReport(merchantReport);return merchantReport; }}4 未来规划
虽然当前基于Jolt的质检报告方案已经满足了大部分业务灵活调整的需求,实现了从硬编码到配置化的重要跨越,但仍存在一定的技术门槛。目前的JSON配置方式要求使用者理解Jolt语法、熟悉JSON结构、掌握路径表达式等技术细节,这对于非技术背景的业务同学来说是一个不小的挑战。
因此,我们计划在下一阶段推出可视化拖拽组件库,彻底打破技术壁垒,真正实现"零代码"配置。通过直观的图形界面和拖拽交互,让业务同学能够像搭积木一样轻松定制质检报告,实现以下愿景: 未来的可视化方案将带来:
所见即所得:实时预览报告效果,即时验证配置结果 降低门槛:业务人员无需编程知识即可独立完成配置 提升效率:从"天"级别的开发周期缩短到"分钟"级别的配置时间 减少错误:通过可视化约束和智能提示,避免配置错误 赋能业务:让最懂业务的人直接参与产品优化,快速响应市场变化
刘斌 转转回收技术部研发工程师
想了解更多转转公司的业务实践,欢迎点击关注下方公众号: