同源異構: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 未來規劃
一、背景
在二手奢侈品行業,一份專業的質檢報告是建立信任的基石。然而,隨著業務的深入發展,我們發現了一個有趣的現象:
同樣一份質檢數據,在不同人眼中卻有著完全不同的價值。
- B端商戶 更關心商品的真偽鑒定結果、成色等級評定等影響定價的核心信息。
- C端賣家 則更在意驗貨流程的透明度、瑕疵詳情等影響交易決策的要素。
- 平臺運營 需要從差異分析角度,持續優化驗貨標準和流程。
更棘手的是,這些關注點并非一成不變。市場在變,用戶在變,需求自然也在變。昨天還是次要信息的配件完整度,今天可能就成了用戶最關心的焦點。
面對頻繁變化的展示需求,我們曾經試過最"簡單直接"的方法——改代碼。結果可想而知:開發疲于奔命,業務等待焦急,系統越來越難維護。
如果展示邏輯可以像配置文件一樣靈活調整,如果業務人員也能參與報告格式的設計,如果一份數據真的可以"隨需而變"……
這正是Jolt給我們帶來的改變。
二、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());
// ... 幾十行甚至上百行的處理結構映射的代碼邏輯
} else if (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 | 移動和重組數據 | 調整JSON結構,字段重命名 |
default | 添加默認值 | 補充缺失字段 |
remove | 刪除字段 | 過濾敏感信息 |
sort | 排序字段 | 規范化輸出格式 |
cardinality | 轉換數據 | 單值與數組互轉 |
2.5 Jolt的典型應用場景
- API網關:統一不同微服務的數據格式
- 數據遷移:新舊系統的數據格式轉換
- 報表系統:靈活的數據展示需求
三、場景化實踐
3.1 改造藍圖:基于Jolt的報告生成流程
傳統架構的問題分析
在引入Jolt之前,我們的質檢報告面臨著典型的"硬編碼困境":
// 傳統方式:每種報告都需要獨立的轉換類
publicclass B2BReportConverter {
public B2BReport convert(InspectionData data) {
B2BReport report = new B2BReport();
report.setProductCode(data.getBasicInfo().getCode());
report.setAuthResult(data.getAuthentication().getResult());
// ... 數百行的字段映射代碼
}
}
publicclass B2CReportConverter {
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以下是函數及其對應的源碼:
private staticfinal 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的示例
public class EqualsFunction implements Function {
@Override
public 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接口(支持上下文傳遞)
*/
publicclass CustomizeTransform implements SpecDriven, ContextualTransform {
// 根節點的轉換規則對象,用于存儲和執行轉換邏輯
privatefinal ModifierCompositeSpec rootSpec;
/**
* 構造函數 - 初始化自定義轉換器
* @param spec JSON格式的轉換規則配置
*/
public CustomizeTransform(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 轉換后的數據
*/
@Override
public 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 創建轉換服務
@Service
publicclass JoltTransformService {
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")
publicclass ReportController {
@Autowired
private JoltTransformService transformService;
public MerchantReport generateMerchantReport(String reportId) {
// 1. 獲取原始質檢數據
QualityReportDo reportDo = getQualityReport(reportId);
// 2. 使用Jolt轉換
MerchantReport merchantReport = joltTransformService.transformData(
reportDo,
"MERCHANT_REPORT_RULE", // 配置中心的規則key
new TypeReference<MerchantReport>() {}
);
// 3. 業務后處理(如需要)
postProcessMerchantReport(merchantReport);
return merchantReport;
}
}
四、未來規劃
雖然當前基于Jolt的質檢報告方案已經滿足了大部分業務靈活調整的需求,實現了從硬編碼到配置化的重要跨越,但仍存在一定的技術門檻。目前的JSON配置方式要求使用者理解Jolt語法、熟悉JSON結構、掌握路徑表達式等技術細節,這對于非技術背景的業務同學來說是一個不小的挑戰。
因此,我們計劃在下一階段推出可視化拖拽組件庫,徹底打破技術壁壘,真正實現"零代碼"配置。通過直觀的圖形界面和拖拽交互,讓業務同學能夠像搭積木一樣輕松定制質檢報告,實現以下愿景: 未來的可視化方案將帶來:
- 所見即所得:實時預覽報告效果,即時驗證配置結果
- 降低門檻:業務人員無需編程知識即可獨立完成配置
- 提升效率:從"天"級別的開發周期縮短到"分鐘"級別的配置時間
- 減少錯誤:通過可視化約束和智能提示,避免配置錯誤
- 賦能業務:讓最懂業務的人直接參與產品優化,快速響應市場變化