惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

SecWiki News
SecWiki News
I
InfoQ
The Cloudflare Blog
人人都是产品经理
人人都是产品经理
博客园 - Franky
T
Tailwind CSS Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
量子位
博客园_首页
罗磊的独立博客
V
V2EX
李成银的技术随笔
大猫的无限游戏
大猫的无限游戏
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
T
True Tiger Recordings
Vercel News
Vercel News
Cyberwarzone
Cyberwarzone
Cisco Talos Blog
Cisco Talos Blog
F
Fox-IT International blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
M
Microsoft Research Blog - Microsoft Research
Know Your Adversary
Know Your Adversary
爱范儿
爱范儿
The Register - Security
The Register - Security
G
Google Developers Blog
The Hacker News
The Hacker News
Malwarebytes
Malwarebytes
S
Securelist
博客园 - 三生石上(FineUI控件)
Jina AI
Jina AI
T
Threat Research - Cisco Blogs
T
The Exploit Database - CXSecurity.com
S
SegmentFault 最新的问题
博客园 - 叶小钗
F
Fortinet All Blogs
Apple Machine Learning Research
Apple Machine Learning Research
宝玉的分享
宝玉的分享
博客园 - 聂微东
T
Threatpost
博客园 - 【当耐特】
D
Docker
P
Privacy & Cybersecurity Law Blog
www.infosecurity-magazine.com
www.infosecurity-magazine.com
G
GRAHAM CLULEY
V
Visual Studio Blog
C
Cisco Blogs
IT之家
IT之家
S
Security Archives - TechRepublic
Latest news
Latest news
阮一峰的网络日志
阮一峰的网络日志

元视角

Terraform 极简入门:从 AWS-CLI 到基础设施即代码(IaC) - 元视角 .NET 生态下的 Agent 框架选型:从 ReAct 到原生推理 - 元视角 从「能用」到「好用」:LLM 流式响应实现方式的探索之路 - 元视角 当我用 2000 条聊天记录,让 AI 为我画一幅自画像 - 元视角 基于 Supabase 的 AI 应用开发探索 - 元视角 盛世幻象:从荔枝看盛唐的兴衰 - 元视角 观影 - 元视角 听歌 - 元视角 个人作品 - 元视角 站点统计 - 元视角 读书 - 元视角 微博 × MCP:社交媒体新玩法解锁 - 元视角 四点钟海棠花未眠 - 元视角 留言 - 元视角 Semantic Kernel × MCP:智能体的上下文增强探索 - 元视角 命运、偏见与自由:《魔童之哪吒闹海》的终极抗争 - 元视角 基于 K-Means 聚类分析实现人脸照片的快速分类 - 元视角 容器技术驱动下的代码沙箱实践与思考 - 元视角 温故而知新:后端通用查询方案的再思考 - 元视角 浅议 CancellationToken 在前后端协同取消场景中的应用 - 元视角 Semantic Kernel 视角下的 Text2SQL 实践与思考 - 元视角 走走停停,允许一切发生 - 元视角 关于 ChatGPT 的流式传输,你需要知道的一切 - 元视角 从抖音看见世界的参差 - 元视角 俯仰之间:五一小长假出行记 - 元视角 RAG 的是与非、Rewrite 和 Rerank - 元视角 《你想活出怎样的人生》与宫崎骏的自我和解 - 元视角 使用 EFCore 和 PostgreSQL 实现向量存储及检索 - 元视角 基于 LLaMA 和 LangChain 实践本地 AI 知识库 - 元视角 使用 llama.cpp 在本地部署 AI 大模型的一次尝试 - 元视角 如何为 Git 配置多个 SSH Key - 元视角 C# 使用 LibUsbDotNet 实现 USB 设备检测 - 元视角 基于 C# 实现样式与数据分离的打印方案 - 元视角 基于 SVG 的图形交互方案实践 - 元视角 前端视频播放技术概览 - 元视角 你好,千寻小姐 - 元视角 温故而知新,再话 Python 动态导入 - 元视角 后 GPT 时代,NLP 不存在了? - 元视角 视频是不能 P 的系列:使用 Milvus 实现海量人脸快速检索 - 元视角 GDI+下字体大小自适应方案初探 - 元视角 小爱音箱集成 ChatGPT 的不完全教程 - 元视角 程序员视角下的三体世界随想 - 元视角 关于 Docker 容器配置信息的渐进式思考 - 元视角 在 Docker 容器内集成 Crontab 定时任务 - 元视角 为你的服务器集成 LDAP 认证 - 元视角 似花还似非花 - 元视角 视频是不能 P 的系列:使用 Dlib 实现人脸识别 - 元视角 浅议分布式链路追踪与日志的整合 - 元视角 关于 Git 大文件上传这件小事 - 元视角 .NET 进程内队列 Channel 的入门与应用 - 元视角 使用 Fody 实现 .NET 的静态编织 - 元视角 .NET Core + ELK 搭建可视化日志分析平台(下) - 元视角 聊一聊前端图片懒加载背后的故事 - 元视角 杂感·七月寄望 - 元视角 支持外部链接跳转的 Vue Router 扩展实现 - 元视角 视频是不能 P 的系列:OpenCV 和 Dlib 实现表情包 - 元视角 不得不说的 ASP.NET Core 集成测试 - 元视角 再议 DDD 视角下的 EFCore 与 领域事件 - 元视角 Vue.js 前端项目容器化部署实践极简教程 - 元视角 再见,人间四月天 - 元视角 Python 图像风格化迁移助力画家梦想 - 元视角 在 Vue.js 中使用 Mock.js 实现接口模拟 - 元视角 利用 ASP.NET Core 中的标头传播实现分布式链路追踪 - 元视角 读《一个叫欧维的男人决定去死》 - 元视角 利用 gRPC 实现文件的上传与下载 - 元视角 七种武器:延迟队列的原理和实现总结 - 元视角 gRPC 流式传输极简入门指南 - 元视角 烟波梦影,从天国王朝到刺客信条 - 元视角 关于 - 元视角 Envoy 集成 Jaeger 实现分布式链路追踪 - 元视角 浅议非典型 Web 应用场景下的身份认证 - 元视角 gRPC 借助 Any 类型实现接口的泛化调用 - 元视角 分布式丛林探险系列之 Redis 集群模式 - 元视角 写在冬阳升起以前 - 元视角 分布式丛林探险系列之 Redis 主从复制模式 - 元视角 通过 Python 预测 2021 年双十一交易额 - 元视角 从《失控玩家》中得到的启示 - 元视角 gRPC 搭配 Swagger 实现微服务文档化 - 元视角 SSL/TLS 加密传输与数字证书的前世今生 - 元视角 夕雾花园:从建筑中读出的爱情和美学 - 元视角 使用 Python 自动识别防疫健康码 - 元视角 你不可不知的容器编排进阶技巧 - 元视角 ASP.NET Core 搭载 Envoy 实现 gRPC 服务代理 - 元视角 再话 AOP,从简化缓存操作说起 - 元视角 洗衣随想曲 - 元视角 ASP.NET Core 搭载 Envoy 实现微服务身份认证(JWT) - 元视角 浪客剑心:一曲幕末时代的挽歌 - 元视角 ASP.NET Core 搭载 Envoy 实现微服务的监控预警 - 元视角 ASP.NET Core 搭载 Envoy 实现微服务的负载均衡 - 元视角 ASP.NET Core 搭载 Envoy 实现微服务的反向代理 - 元视角 ASP.NET Core gRPC 打通前端世界的尝试 - 元视角 EFCore 实体命名约定库:EFCore.NamingConventions - 元视角 ASP.NET Core gRPC 集成 Polly 实现优雅重试 - 元视角 ASP.NET Core gRPC 健康检查的探索与实现 - 元视角 ASP.NET Core gRPC 拦截器的使用技巧分享 - 元视角 SnowNLP 使用自定义语料进行模型训练 - 元视角 假如时间有温度 - 元视角 使用 HttpMessageHandler 实现 HttpClient 请求管道自定义 - 元视角 ABP vNext 的实体与服务扩展技巧分享 - 元视角 ABP vNext 对接 Ant Design Vue 实现分页查询 - 元视角
使用 Liquid 实现简单的数据交换 - 元视角
2019-12-22 · via 元视角

在平时的开发工作中,接口对接是一件无可避免的事情。虽然在“前后端分离”的大趋势下,后端的角色逐渐转换为数据接口的提供者,然而在实际的应用场景中,我们面对的往往是各种不同的“数据”,譬如企业应用中普遍使用的企业服务总线(ESB),这类服务要求服务接入者必须使用 WebService 来作为数据交换格式;再譬如电子数据交换(EDI)这种特定行业中使用的数据交换格式,从可读性上甚至还不如基于 XML 的 WebService……而更为普遍的则可能是需要使用 Word、Excel、CSV 来作为数据交换的媒介。顺着这个思路继续发散下去,进入我们失业的或许还有各种数据库,譬如 MySQL 和 MongoDB;各种大数据平台,譬如 Hadoop 和 Spark;各种消息队列,譬如 RabbitMQ 和 Kafka 等等。

注意到,这里反复提到的一个概念是数据交换(Data Switching),它是指在多个数据终端设备间,为任意两个终端设备建立数据通信临时互联通路的过程。自从阿里提出“中台”的概念以来,越来越多的公司开始跟风“中台”概念,并随之衍生出譬如组织中台、数据中台、业务中台、内容中台等等的概念。今天这篇博客,我并不打算故弄玄虚地扯这些概念,我的落脚点是接口级别的数据交换,主要通过 Liquid 这款模板引擎来实现。它对应我在这篇博客开头提到的场景:一个对外提供 RESful 风格 API 的系统,如何快速地和一个 WebService 实现对接。总而言之,希望能对这篇博客对大家有所启发吧!

关于 Liquid

首先,我们来介绍Liquid,通过它的官方网站,我们应该它是一门模板语言。对于模板语言,我们应该是非常熟悉啦,JavaScript 里的HandlebarsEjs就是非常著名的模板语言。如大家所见,这个博客就是用 Ejs 模板渲染出来的。而到了三大前端框架并驾齐驱的时代,模版语法依然被保留了下来,比如 Vue{% raw %}{{model.userName}}{% endraw %} 标记常常用来做文本插值。所以,如果要认真追溯起来的话,也许这些框架都或多或少的收到了 Liquid 的影响,因为它的基本语法如下:

// 使用page实例的title属性插值
{{ page.title}}

假设 page 是一个对象,它的 title 属性值为:Introduction,此时,渲染后的结果即为:Introduction。是不是感觉非常简单呢? 我们继续往下看。除了基本的“插值”语法以外,我们可以用 {% raw %}{% tag %}{% endraw %} 这种结构(Liquid 称之为 Tag):

// 声称变量author并赋值
{% sssign author = '猫先森' %}
// 条件语句
{% if author == '猫先森' %}
帅哥,你好
{% endif %}
// 循环语句
{% for post in posts %}
{{post.date}}-{{post.title}}
{% endfor %}

这里仅仅展示了一部分Liquid的特性,但对于我们了解一门“语言”已经足够了,因为对于一门编程语言来说,只要学会顺序、条件和循环三种结构足矣。言下之意呢,像常规elseelseifbreakcontinueLiquid都是支持的,这样子是不是更有编程语言的感觉了呢?除此之外,它还支持像 tablerow 这样的 Tag,主要用来渲染 HTML 里的表格。

也许有人想说,这玩意儿有什么用呢?抱歉啊,这玩意儿还真有用。像发送邮件、发送短信这种一般都需要写个字符串模板的,简单的大家可以用 String.Format() 或者 $ 来搞定,可一旦遇上循环的场景,这种基于字符串替换的方式就有点力不从心了。不开玩笑地说,在代码里用 StringBuilder 拼接 HTML 的方式,实在是太傻逼了。如果用 Liquid 写可能就是:

亲爱的{{ model.UserID }}:
   您好!您有以下设备即将超过校验有效期,请及时采取有效行动。
   {% for equipment in model.Equipments %}
   {{ equipment.EquipmentID }}
   {% endfor %}
   
{{ model.SendBy }}

显然,这个代码比拼接字符串要优雅很多。博主曾经在一个前端页面看到过大量的 HTML 拼接操作,果然是 jQuery 操作 DOM 一时爽,jQuery 操作 DOM 一直爽,可明明前端就有 HandlebarsEjs 这样的模板语言。最近一位同事写前端页面的经历不由得让我感慨,眼睛觉得简单的事情,为什么总是要求手去做呢?直接操作 DOM 带来的弊端就是,业务逻辑永远和 DOM 纠缠在一起,那些没有人敢改的 JavaScript 代码,那些未经模块化全局引入的 JavaScript 代码,虽然马上就要 2020 年了,写下这些句子的时候还是感到魔幻,可能这就是所谓的魔幻现实主义吧。

OK, 我们把思绪拉回到 Liquid 。除了使用各种 Tag 实现流程控制以外, Liquid 中还提供了过滤器(Filter)的概念,过滤器主要是配合 {% raw %}{{ variable | filter }}{% endraw %} 语法来使用的。比如说,数据层返回了一个负数,而展示层希望展示正数,在不确定这个数值是否被别人使用的情况下,贸然去修改数据层的返回值是件危险的事情。此时,我们可以:

//对绑定的变量或者值取绝对值
{{ -17 | abs}}
// 保留小数位
{{ 183.357 | round: 2 }}
// 日期/时间格式
{{ article.created_date | data: %b %d, %Y}}

类似小数点位数、日期/时间格式等问题,均可以在 Liquid 中找到相应的过滤器。需要说明的是, Liquid 使用 Ruby 进行开发的。也许在读到这篇博客前,大家都没有听说过 Liquid ,那么至少听说过 Jekyll 这个著名的静态博客生成器吧。实际上,在我写这篇博客的时候,我刚刚了解到一件事情, Jekyll 就是基于 Liquid 而开发的,想到当初搭建这个博客时被 Ruby 劝退的回忆,我大概想不到有一天会再次接触它吧,不得不说,人生还真是奇妙啊!

一个简单的想法

好了,关于 Liquid 的介绍我们先了解到这里。写到这里,再回头去看我们一开始的问题,即:怎么把上游的数据(Model)转化为下游的数据(Template)。这里暂且抛开它到底是 XML、JSON 还是 EDI 这种细节性的问题,我想我们大概会有一个简单的想法,如果把需要传输给对方的接口报文做成模板,然后通过Liquid语法完成数据的绑定,那么数据映射这一层的工作就可以减轻不少,毕竟写 A.XXX=B.XXX 这种赋值语句是没什么前途的啦,而 AutoMapper 则需要提前写好 Map 并注册,经过一番权衡,我们来验证一下我们的想法吧!

这段时间一直在和金蝶 K3Cloud 接口做对接,坦白说我觉得金蝶的接口设计得非常糟糕,从它那个奇葩的 FNumber 字段就能看出来,而且它试图用一个接口做完所有事情的做法恕我不敢苟同,在我看来它违反了单一职责原则。因为要对接的接口数量多、字段多,我首先根据字段对应关系制作了一份 Liquid 模板,并根据业务上的需要,用主表(Main) + 明细表(Details)的方式来定义数据,这意味着我接下来只需要根据业务实现不同的数据源即可:

基于Liquid的JSON报文模板 基于Liquid的JSON报文模板

好了,现在我们使用 Liquid 的 .NET 版本 DotLiquid 来负责模板的解析和渲染,这个库可以直接通过 Nuget 安装,可以注意到这个代码非常的简单:

string RenderTpl(string filePath, dynamic model)
{
  var content = File.ReadAllText(filePath);
  var template = Template.Parse(content);
  var output = template.Render(Hash.FromAnonymousObject(model));
  return output;
}

实际上渲染后的文本就是对方需要的接口报文了,此时,该怎么样就怎么样处理,只需要把这个报文发送给对方就可以了。唯一需要花时间的就是对字段、写绑定,相比写实体类的方式效率要高更多。这种方式的话,我个人觉得更适合分工合作,如果需要数据加字段,那在数据层(Model)里增加就好了,而像改字段映射关系、字段默认值都可以由别人来完成。我一直相信,开发并不是帮别人做越多事情越好,而是可以提供一种能力让别人去做更多的事情,这就是我们常常听到的“赋能”。继续延伸下去的话,传统的 MVC 其实和Liquid是一个道理,都是根据数据去生成视图,无非是我们这里的"视图"变成了数据报文。

本文小结

通过日常工作中的接口对接这一典型场景,我们引出了“数据交换”的概念,而最低层级的数据交换实际上是接口报文的交换。为此,我们介绍了 Liquid 模板引擎,它提供的语法可以让我们完成一系列的绑定,顺着这个思路,博主为大家展示了这种想法的可行性。 Liquid 是一个非常成熟的模板引擎,无论是编写邮件、短信的文本模板,还是轻量级的文本表达式实现,都是一个非常不错的选择。即使是做一个 ApiCaller,一定要做一个有头脑的 ApiCaller。好了,以上就是这篇博客的全部内容啦,欢迎大家留言,谢谢大家。

2020-01-09 更新

在组织 JSON 中的数组结构时,需要在各元素间添加,,同时最后一个元素不需要,,此时,可以使用以下语法:

"FEntity": [
  {% for Detail in Details %}
    {
      "FCOSTID": {
        "FNumber": "{{Detail.FCOSTID}}"
      },
      "FCOSTDEPARTMENTID": {
        "FNumber": "BM000005"
      },
      "FINVOICETYPE": "0",
      "FTOTALAMOUNTFOR": {{Detail.FEE_AMOUNT}},
    }
    {% if forloop.last == false %},{% endif %}
  {% endfor %}
]