当前位置: 首页 > news >正文

不用JSON-RPC和GraphQL:自研DataCenter统一数据协议,一套格式管全部

不用JSON-RPC和GraphQL:自研DataCenter统一数据协议,一套格式管全部

文章目录

  • 不用JSON-RPC和GraphQL:自研DataCenter统一数据协议,一套格式管全部
    • 一、问题:前后端数据交互的格式碎片化
    • 二、答案:DataCenter统一数据协议
    • 三、为什么这么做:三个核心设计决策
      • 决策一:多数据块放一个响应里
      • 决策二:Row 自带状态追踪
      • 决策三:查询参数原样带回
    • 四、RowSet 的增删改模型
    • 五、VtoH:一个意外的设计
    • 六、Header 的设计:简单但有底线
    • 七、这套协议运行了多少年

一、问题:前后端数据交互的格式碎片化

做政务系统,前后端数据交互有个特点:一个页面经常需要同时组装多块数据

比如社保个人信息页面,要同时展示三个面板:上方是基本信息(姓名、身份证、参保状态),中间是缴费记录列表,下面是待遇发放记录列表。每块数据都有自己的查询条件、分页参数、行数据。

如果每次请求只返回一个数据块,这个页面至少要发三次HTTP请求。更麻烦的是——三个请求是独立的,前端要自己管理三个异步回调,等全部返回了再渲染。

我们当时没有Spring Boot,没有页面前后端分离,JSP页面在服务端渲染,但Ajax交互也很多。需要一个办法:一个请求,返回多块数据,前端只解析一种格式

二、答案:DataCenter统一数据协议

这个协议的核心结构只有三层:

DataCenter ├── Header 状态码 + 消息 └── Body ├── dataStores 多个数据块,按名字索引 │ └── DataStore │ ├── RowSet { primary[], delete[] } │ │ └── Row { _t状态, map字段值, _o旧值 } │ ├── name 数据块名称 │ ├── pageSize 每页条数 │ ├── pageNumber 当前页 │ └── parameters 查询条件(原样带回) └── parameters 全局参数(如当前登录用户信息)

一个完整的JSON响应长这样:

{"header":{"code":1,"message":{"title":"查询成功","detail":""}},"body":{"dataStores":{"personInfo":{"rowSet":{"primary":[{"aac001":"10001","aac003":"张三","_t":0}],"delete":[]},"name":"personInfo","pageSize":50,"pageNumber":1,"recordCount":1},"payList":{"rowSet":{"primary":[{"aae002":"202601","aae019":"1234.56","_t":0},{"aae002":"202602","aae019":"1234.56","_t":0}],"delete":[]},"name":"payList","pageSize":20,"pageNumber":1,"recordCount":48,"parameters":{"aac001":"10001"}}},"parameters":{"loginUser":"admin"}}}

一个请求,返回了两块数据:personInfo 是一次请求的数据,payList 是分页列表。前端拿到这个JSON,按 dataStores 的名字取对应的数据块渲染到各自的面板。

三、为什么这么做:三个核心设计决策

决策一:多数据块放一个响应里

当时业界有几种方案:

  • 发多个HTTP请求——并发管理复杂
  • 返回值拍成一个大XML——XML前端解析重
  • 自定义分隔符拼字符串——太脆弱

我们的方案:一个JSON,多DataStore。前端代码变成统一的模式:

vardc=JSON.parse(response);varpersonInfo=dc.body.dataStores["personInfo"];varpayList=dc.body.dataStores["payList"];

关键是——后端的 Java 代码也统一了:

DataCenterdc=newDataCenter();dc.setCode(1);DataStoredsPerson=newDataStore("personInfo");dsPerson.getRowset().getPrimary().add(row);dc.addStore(dsPerson);dc.addStore(dsPay);// 另一个DataStoreStringjson=dc.toJson();

每个业务方法只管往自己的 DataStore 里塞数据,最后统一序列化。

决策二:Row 自带状态追踪

Row 不是简单的 HashMap,它有三个关键字段:

字段含义
_t = 0未修改,从数据库查出来的原始数据
_t = 1新增,前端新增的行
_t = 3修改,前端改了某个字段的值
_oHashMap,原始值——修改前字段的值被记录到这里

这是一个迷你ActiveRecord,核心方法是setItemValue

publicvoidsetItemValue(Objectkey,Objectvalue){if(map.get(key)==null){if(_t==0){_t=1;}// 之前是空,填了一个值——新增}else{if(_t==0){// 之前有值,先记下来,再改_t=3;// 标记为修改_o.put(key,map.get(key));// 备份旧值}}map.put(key.toString(),value);}

前端不需要知道自己是在"新增行"还是在"编辑行"。修改一个单元格,Row 自动把_t从0变成3,并把旧值存入_o。提交时,后端遍历 RowSet 的 primary 列表,根据_t判断:_t==1调 insert,_t==3调 update。一条 save() 搞定增删改。

决策三:查询参数原样带回

看 payList 这个 DataStore,它在查询请求时传入了parameters: {"aac001": "10001"}。返回时,这些参数原封不动地带着。

这不是冗余。前端翻页时,不需要重新组装查询条件——直接从 DataStore 里取 parameters 再发出去就行:

// 翻到第2页pageData.body.dataStores["payList"].pageNumber=2;// parameters不用重新填,round-trip保证了它还在ajax.post("/query",pageData);

参数的"round-trip"设计让前端彻底解耦了查询条件的管理。查询条件是谁填的、从哪里来的、中间有没有被用户改过——前端不需要知道,后端给什么前端就用什么。

四、RowSet 的增删改模型

RowSet 用三个 Vector 管理行数据:

向量用途
primary当前数据行,含新增、修改和未改动行
delete被删除的行,从primary移入这里
filter预留的过滤结果集

为什么 delete 不是标记删除,而是物理移动到另一个集合?

因为前端渲染时,delete集合里的行是不显示的(它们已经被移出了 primary)。提交时,后端同时处理primary(新增+修改)和delete(物理删除)——一个 RowSet 就包含了本次操作的完整变更集。

resetUpdate()方法更体现了这个思想——提交成功后,把所有行的_t重置为0,清空_odelete,RowSet 恢复为"干净的查询结果"状态。

五、VtoH:一个意外的设计

RowSet 还有一个神奇的方法VtoH——纵向转横向。把一个列式存储的数据集变成行式:

输入(纵向): 输出(横向): col_name col_vale aac001 aac002 aac003 aac001 10001 → 10001 xxxx 张三 aac002 xxxx 101 yyyy 李四 aac003 张三 aac001 101 aac002 yyyy aac003 李四

数据审计时,变更记录通常以"字段名+新旧值"的列格式存储。VtoH 一键转成用户能看懂的表格。这个方法只有十几行,但解决了一个在政务系统中反复出现的问题——审计数据的横向展示。

六、Header 的设计:简单但有底线

publicclassHeader{privateintcode;privateHashMapmessage=newHashMap();// title + detail + 自定义}

code 是状态码(1成功,负数是具体错误码),message 里的 title 是对用户的标题(“保存成功”、“参保人不存在”),detail 是给技术人员的详细信息。

这个设计在今天看来普通,但在当时有一个细节:code 永远不是 HTTP 状态码。即使业务逻辑报错(“该参保人已存在”),HTTP Status 依然是200,错误信息通过 Header.code 传递。因为我们的前端只认 Header.code,不认 HTTP 状态——换了一种错误传递方式前端就崩了。

七、这套协议运行了多少年

从2010年左右设计出来,到系统2023年下线,这套 DataCenter 协议跑了十多年。

它不是什么高深的技术——没有 schema 校验,没有类型系统,没有缓存策略。但它在政务系统的实际约束下解决了一个反复出现的问题:前后端数据交互的统一格式

今天回头看,这套协议有点像简化版的 GraphQL——一个请求返回多个命名的数据集,前端按需取用。区别在于 GraphQL 有完整的类型系统和查询语言,而 DataCenter 只有一个 JSON 结构和一套 Java 类。前者是工业标准,后者是在约束条件下的实用解

最后说一句——这套协议存在了十多年,不是因为没有人想过要换。而是每次有人提"要不要改成 RESTful",改完一个页面后发现其他几百个页面都依赖这个格式,就算了。一个设计能活下来的标志,不是没人反对,是反对的人改了之后又改回来了。

http://www.gsyq.cn/news/1598982.html

相关文章:

  • TICC协议:量子相位估计的高效实现与优化
  • 三步解密加密音频:从技术分析到通用格式转换实战
  • Codeforces Round 1065
  • RL78单片机Flash内存操作:从硬件序列器到安全编程实践
  • 贝叶斯优化在机器人路径跟随控制中的应用实践
  • 5个关键步骤:全面解锁《Honey Select 2》游戏潜力
  • 终极桌面待办工具:3分钟快速上手的跨平台免费神器
  • Vim效率革命:一键生成智能文件头与实时时间戳
  • 空洞骑士模组管理器Scarab:终极安装指南与使用教程
  • FADiff框架:DNN加速器调度的统一优化方法
  • RRAM模拟矩阵计算加速6G大规模MIMO信号处理
  • 勒索病毒应急自救指南:从隔离诊断到数据恢复的完整方案
  • 如何永久保存微信聊天记录:WeChatMsg完整指南与数据备份解决方案
  • 3分钟掌握N_m3u8DL-RE:跨平台流媒体下载的终极解决方案
  • 量子保密通信中的玻色窃听信道与保密容量分析
  • 3步掌握SRWE:彻底解决游戏窗口尺寸限制的完整指南
  • AI设计指南:Adobe Illustrator核心工具与实战场景解析
  • Wand-Enhancer技术深度解析:现代游戏模组增强平台的架构设计与实现
  • 如何用PiliPlus打造你的专属B站体验?
  • 量子计算在分子模拟中的应用与VQE算法实践
  • 从酷狗音乐到MoeKoe Music:一个二次元音乐爱好者的技术突围之路
  • BetterNCM插件管理器:Rust技术栈打造的高效网易云音乐扩展方案
  • 流式输出(Streaming)原理与踩坑经验
  • 如何解决AMD Ryzen硬件调试中的5大难题:高级工具实战指南
  • 5个实用技巧让EhViewer漫画阅读体验全面升级
  • macOS NVIDIA显卡驱动终极指南:一键安装与智能管理全解析
  • Translumo:Windows平台终极实时屏幕翻译神器,3分钟开启无障碍游戏体验
  • 如何用项目经验打动Java面试官
  • 离线漫画收藏的艺术:picacomic-downloader如何重新定义你的数字阅读体验
  • 2026年揭秘!市面上热门的伺服电力测功机工厂口碑究竟如何?