22-DataCenter.toPayload:脏数据报文序列化
多个DataStore打包成一个DataCenter,一次请求提交多张表的变更------这是14年协议的顶层封装。toPayload把内存行集序列化成提交报文,fromResponse把响应还原回行集。这篇拆这两个方法及"一屏多store"的典型场景。
文章目录
源码:
browise-data/src/main/java/com/browise/data/DataCenter.java(163行)
一、三层容器嵌套
DataCenter
├── stores: LinkedHashMap<String, DataStore> ← 命名store集合
│ ├── "userStore" → DataStore
│ │ ├── parameters: Map ← store级参数
│ │ └── rowset: RowSet ← 行集
│ └── "unitStore" → DataStore
└── parameters: Map ← 全局参数(_params)
为什么需要三层 ------一个编辑界面往往不止一张表:参保人页面上半是基本信息(ac01)、下半是缴费记录列表(ic50)、角落还有银行账户(ae03)。三个数据集、三种变更、一个保存按钮。
没有DataCenter:前端要发三个请求、管理三个回调、后端三个事务------中途失败就出现"人保存了缴费没保存"的半截状态。有DataCenter:三个store一次提交、后端一个事务。
二、getOrCreateStore:幂等注册
java
public DataStore getOrCreateStore(String name) {
return stores.computeIfAbsent(name, DataStore::new);
}
computeIfAbsent------存在返回已有的,不存在创建并注册。前端页面重复初始化(路由切换回来)不会覆盖已有store的数据。对比addStore(无条件new覆盖)------addStore用于"明确要重建"的场景,getOrCreate用于"确保存在"的场景。一对方法区分两种意图。
三、toPayload:序列化成什么
java
public Map<String, Object> toPayload() {
Map<String, Object> payload = new LinkedHashMap<>();
for (Map.Entry<String, DataStore> entry : stores.entrySet()) {
Map<String, Object> storeMap = new LinkedHashMap<>();
storeMap.put("parameters", entry.getValue().getParameters());
storeMap.put("rows", entry.getValue().getRowset().toRawList()); // ←关键
payload.put(entry.getKey(), storeMap);
}
payload.put("_params", parameters);
return payload;
}
输出的JSON结构:
json
{
"userStore": {
"parameters": { "sqlId": "AC01_s" },
"rows": [
{ "psnName": "李四", "psnAge": 31 }
]
},
"payStore": {
"parameters": { },
"rows": [ ]
},
"_params": { "minRow": 1, "maxRow": 20 }
}
注意rows里的行------toRawList()不带_t/_o ?这是DataCenter级toPayload(查询响应用)。提交用的脏数据收集在前端useCenter.toPayload() ------那边的rows是collect('dirty')的行、保留_t/_o、还带ins/up/del三个sqlId(第02篇的报文示例)。两个同名方法服务两个方向:
| data模块DataCenter.toPayload | 前端useCenter.toPayload | |
|---|---|---|
| 方向 | 后端→前端(响应) | 前端→后端(提交) |
| rows内容 | toRawList(干净行) | collect脏行(带_t/_o) |
| 附带 | store参数 | ins/up/del sqlId |
| 消费方 | fromResponse还原行集 | commonSave执行 |
Java侧的DataCenter.toPayload主要给服务间调用/导出场景------HTTP提交报文的组装在前端composable完成。同一个名字两种报文是历史命名重合,读代码时看调用方分清。
四、fromResponse:防健壮性解析
java
public static DataCenter fromResponse(Map<String, Object> payload) {
DataCenter center = new DataCenter();
for (Map.Entry<String, Object> entry : payload.entrySet()) {
if ("_params".equals(entry.getKey())) {
// 全局参数
center.parameters.putAll((Map) entry.getValue());
continue;
}
if (entry.getValue() instanceof Map) { // 只处理Map类型
DataStore ds = center.addStore(entry.getKey());
Object params = storeMap.get("parameters");
if (params instanceof Map) { ds.getParameters().putAll(...); } // 不是Map就跳过
Object rows = storeMap.get("rows");
if (rows instanceof List) {
for (Object item : (List<?>) rows) {
if (item instanceof Map) { // 行不是Map的跳过
rowList.add((Map) item);
}
}
ds.getRowset().reset(rowList);
}
}
}
return center;
}
每一层都做instanceof检查,不匹配就跳过 ------JSON反序列化的产物类型不可控(fastjson/jackson对空数组、null值、数字类型的处理有差异)。防御性解析的哲学:能收多少收多少,坏一片不坏整体。
代价:坏数据静默丢弃,排查"为什么这个字段没了"时看不出是解析跳过的。生产中遇到过------前端传的rows里混了null元素(稀疏数组序列化),后端跳过后行数对不上。修复在前端保证不产生null元素,解析侧维持防御。
五、_params的转义命名
_params下划线开头------与_t/_o同一个保留命名空间(第17篇)。store的名字不能用_params(会被当成全局参数解析掉)。LinkedHashMap保序------_params永远最后序列化(put在循环后),解析侧遇到时continue不影响store循环------顺序无关但习惯上收尾。
六、14年协议的简化痕迹
对比wiserise时代的DataCenter(第01篇讲过旧版):
wiserise: DataCenter = { header:{code,detail,title}, body:{parameters, dataStores:{...}} }
browise: DataCenter = { storeName:{parameters,rows}, ..., _params:{} }
header没了 ------HTTP状态码+统一Result{code,msg,data}替代了自定义header。body没了 ------一层嵌套被拍平。DataStore的pageSize/pageNumber/recordCount没了------分页信息改由PageResult独立承载(第24篇)。
三次简化都是"标准生态已提供的能力不再自定义"------REST响应规范(Result)吃掉header、通用分页对象吃掉内嵌分页字段。协议活的越久越薄------没删的只剩真正不可替代的:命名store的聚合结构。
✅ 亮点:三层容器为什么存在(一屏多store一个事务)、toPayload前后端两个同名方法的方向差异表、fromResponse层层instanceof的防御解析与静默丢弃的代价、wiserise→browise三次协议简化的"生态替代自定义"逻辑。适合设计前后端数据协议的人。扩展方向:第23篇commonSave消费提交报文、第24篇MyBatisHelper分页、第46篇前端useCenter。