22-DataCenter报文序列化

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。

相关推荐
2601_9620652532 分钟前
[MySQL] SQL优化之性能分析
java·sql·mysql
小范同学_1 小时前
JDK1.7 与 JDK1.8 HashMap 底层原理对比 + 数组并发扩容死循环详解
java·开发语言
予昊2 小时前
从零实现“在线五子棋对战“:WebSocket 实时通信 + 段位匹配
java·开发语言·网络·websocket
2601_962203512 小时前
【SpringAI入门】初识SpringAI
java
weixin_461408582 小时前
Mybatis-flex小记
java·开发语言·mybatis
zabr2 小时前
Agent少问你,不代表控制更少
架构·aigc·ai编程
2601_962177133 小时前
【MySQL篇】聚合查询,联合查询
android·java·mysql
それども3 小时前
IDEA接入Claude完整流程
java·ai·intellij-idea
小鹿的周先生3 小时前
Spring-AI-第2篇-ChatClient 实战:使用 DeepSeek 完成第一次 AI 对话
java·人工智能·spring