前言
古茗的供应链项目贯穿多个业务部门,在开发阶段我们需要完成大多数业务场景的开发和自测,这样才能保证联调阶段上下游业务的足够通顺。但在日常项目开发中,我们发现和接口有太多的对接和联调,如若要满足上述要求,我们就需要有一个接口工具来满足以下需求:
- 接口抓包:移动端调试的时候需要查看接口的入参和返回内容
- 接口 Mock: 联调前,前端需要通过接口 Mock 的形式来完成页面开发
目前古茗内部使用了 yapi 作为接口管理平台,yapi 在提供接口文档的同时还提供了基于接口元数据的 Mock 能力。我们期望围绕着现有的 yapi 平台打造一份既能实现接口抓包,也能提供接口 Mock 的前端工具。
现状
此前古茗内部团队已经陆续尝试过不同形式的接口抓包和 Mock 工具了,如下所示:
接口抓包
| 方式 | 适用范围 | 使用成本 | 优点 | 缺点 |
|---|---|---|---|---|
| 小程序开发者工具 - 真机调试 | + 小程序 + webview | 简单 | + 无需三方工具 + 配置简单 + 对业务代码无任何侵入性 | + 需要本地编译,不能直接监控正式版小程序 + 真机调试体验不好,经常卡 |
| vconsole | + 小程序 + webview + H5 | 简单 | + 配置简单 | + 移动端控制台查看效果不佳 + 需要侵入性安装三方 npm + 需要对全局接口进行拦截 |
| charles | + 小程序 + webview + H5 | 较难 | + 适配广、功能强大 + 对业务代码无任何侵入性 | + 配置麻烦 |
接口 Mock
| 方式 | 使用成本 | 优点 | 缺点 |
|---|---|---|---|
| 使用静态数据 | 简单 | + 方便 | + 对业务代码有侵入性 + Mock 成本高(所有字段需要一一定义) + Mock 后忘记撤回导致线上问题 |
| 使用 NPM 包 Mock | 简单 | + 直接 mock,不需要一一定义 | + 对业务代码有侵入性 + 需要安装插件 |
| charles | 较难 | + mock 功能强大且灵活 | + Mock 成本高(所有字段需要一一定义) + 配置麻烦 |
| 维斯 mock 工具 | 一般 | + 打通 yapi 一键 mock + 对业务代码无侵入性 | + 影响翻墙工具 + 只能基于 yapi project mock,无法单接口简单 mock + 接口需要二次调整后才能使用 |
| Apifox 等三方工具 | 简单 | + 功能完善、支持本地和云端 | + 不能与私有化的 yapi 打通 + Mock 成本高(所有字段需要一一定义) + 对业务代码有侵入性 |
目标
基于上述背景和分析,我们细化了接口工具的需求点:
- 对代码无侵入性
- 集接口监控和 Mock 于一身,能够适配小程序和 pc 端
- 简单配置
- 不影响开发者本地的翻墙
- 打通 yapi,提供一站式的接口 Mock 和二次编辑能力
- 能对 Mock 数据进行一些通用的业务处理(例如将返回的 code 置为 0)
正是在这种背景下,供应链内部的接口抓包 & Mock 解决方案 plug应运而生,它极大程度的贴合了我们古茗的接口开发习惯,接下来会为大家核心介绍下
plug 功能概述
plug 在英文中代表 插头 🔌,生动形象的表达了这个工具的定位(数据衔接)
plug 提供了网页端和 APP 端的管理后台,主要包含以下功能
接口抓包
和大多数抓包工具类似,plug 提供了 http``https``ws协议的抓包能力,主要用于移动端调试和问题定位:
接口 Mock
Mock 能力是 plug 的核心功能,plug 打通了 yapi,可以基于项目批量 Mock 和单接口 Mock。
同时我们提供了对于 Mock 数据的二次编辑能力
plug 默认会使用 yapi 的 mock 能力,二次编辑后便会使用编辑后的数据进行 mock
plug 实现原理
plug 的核心就是接口代理,无论是 mock还是抓包,本质上都需要经过代理后再进行二次处理
http 接口代理
http 接口代理如下所示,plug 会拦截所有请求,并有选择性的转发到源服务器,或是 mock 服务上。整个过程都会受到 plug 的网络监控,并实时同步到管理后台上
https 接口代理
在 http 下,代理都显得那么自然并且简单,直到有了 https,一切都变了,我们不能再用 http 代理的方式去做 https 接口的代理
But Why ?为什么 https 这么特殊呢?我们先通过下图简单介绍下 https 下的数据通信过程:
由于 https 里增加了 「证书验证」和「数据加密」环节,导致普通的中间人根本无法拿到 https 传输过程中的数据,所以我们要换个思路来解决这个问题。通过中间人伪造证书,同时和 「浏览器」、「服务器」进行双向的 「证书验证」和「数据加密」,调整后的整个流程如下图所示:
这里只介绍了 plug 实现的核心流程,隐藏了部分 https 通信的细节,如果对 https 、证书、加密感兴趣的可以查阅网上相关资料
CA 证书
CA 是 Certificate Authority 的缩写,也叫“证书授权中心”
在 https 安全层建立环节中,CA 证书起到了至关重要的环节。如果要建立一个可以同时与客户端和服务端进行通信的网络服务,plug必须要「伪造」一个根证书以及具备生成不同子证书的能力。
讲到这里,大家一定会好奇,为什么 plug需要「伪造」根证书,不能像普通的网站一样去申请证书吗?
很遗憾,答案是基本不可能。But why ?
我们需要先通过一张图来简单介绍下 CA 证书的递进关系:
证书信任链:如果你的系统信任了根证书,那么会自动信任根证书颁发的所有子证书
这里回答一下 ”为什么 plug需要「伪造」根证书?“
- 1、理论上 plug 要申请所有被代理的域名的证书
- 2、正牌的证书申请会校验你对域名是否拥有实际支配权
最后我们看下 plug内部是如何管理证书的:
兼顾系统代理
在没有使用 plug之前,大家的电脑基本都使用了各种 ”科学上网“ 的工具。在使用 plug工具之后,请求都代理到了plug上并进行转发,那 ”科学上网“ 不就没用了吗?在这种背景下,plug适合兼顾 “科学上网” 呢?
其实有挺多三方插件都能实现这个诉求,我们通过下图来简单描述下其背后的实现原理:
Node Agent 对 tcp 的连接进行了池化管理,并做了连接复用,正式利用了这个特点,我们在 tcp 阶段和代理服务进行连接
plug & 大模型
plug的 Mock 功能在使用过程中发现了几个问题:
- Mock 数据太不真实了,基本都需要二次编辑调整,这样带来的使用成本又增高了
- 部分枚举字段的枚举值记录在字段的 description 里,Mock 的时候完全不会采取
- Mock 的分页数据逻辑有问题,分页数量和
data.length完全对不上
以下内容是 plug 内部调用 yapi 的 Mock 服务所生成的
探索大模型协助 Mock
针对上述问题,我们发现问题 3 比较好解决,因为古茗内部的接口已经遵循一定的格式规范,我们可以在 Mock 服务端代码里来处理分页逻辑的问题。但是对于问题 1、2,以现有的 yapi 或是三方插件都不能妥善的解决 (它需要能理解我们的字段 key 的含义,同时需要借鉴字段的 description 内容)。
随后我们考虑是否可以让大模型来辅助我们进行 Mock,我们尝试将接口的 json schema 定义通过 prompt 发送给大模型,让大模型来 Mock 上述接口
以下内容是通过调用 deepseek 官网提供的 API 接口所生成的
相比之后我们发现大模型 Mock 的字段足够语义化、且能解决枚举字段的问题。再配合 Mock 服务的分页逻辑处理,便能很好的解决上述 3 个问题。
既然大模型的 Mock 结果如此理想,我们就考虑如何把 plug 和大模型结合起来,这时就产生了两个方案:「使用大模型的开放 API 服务」、「使用本地部署的大模型」
使用大模型的开放 API 服务
使用大模型提供的 API 服务,能带来以下优势:
- 享受全尺寸大模型能力
- 能够自动配置一些模型参数:比如温度参数、随机种子等,同时对 prompt 也会有更好的优化
- 返回的 Mock 内容比较稳定(从内容随机性的角度来看)
但与此同时也会带来一些问题 (以 deepseek 官网提供的 API 能力为例):
- 调用大模型的 API 接口的 RT 时间基本都在 15s 以上 (有时甚至更久)
- 模型的 API 接口不稳定,可能会返回空的字符串
针对上述问题,我们尝试了以下方案来 “缓解” 问题带来的影响:
- 问题 1 我们在 plug 内部采用了「预热」的形式做提前 Mock,然后将 Mock 的内容缓存到本地,只有当用户点击界面上的同步按钮之后,我们才会再次调用大模型进行 Mock
- 问题 2 有两种解决方式:
- 降级模式:如果大模型返回的数据异常,我们就降级使用 yapi 的 Mock 服务,当然我们也在不断的调整 prompt 内容,让大模型能够更好的理解需求
- 火上引擎:不调用 deepseek 官网提供的 API 服务,而是用火山引擎提供的 deepseek API 服务。相比于 deepseek 官网,火山引擎提供的 API 服务更贵、更稳定(RT 基本也稳定在 10 - 20 s 之间)
火上引擎上 deepseek V3 的收费标准
deepseek 官网提供的收费标准
使用本地部署的大模型
我们尝试在本地通过 ollama部署了蒸馏版的大模型,并通过 ollama提供的 API 服务调用本地部署好的大模型。
我们可以通过其他方式在本地部署大模型,笔者采用 ollama 部署的主要原因是因为 ollama 比较方便而且有 API 可以调用,另外由于机器受限,部署的大模型基本都是蒸馏版本
部署之后,我们用同样的参数和 prompt 调用本地的模型,发现接口的 RT 基本都能控制在 10s 以内(这对你的 prompt 有一定的要求),但是大模型返回的 Mock 内容有以下问题:
- 内容随机性太高:对同样的接口,大模型返回的 Mock 内容不固定,而且经常会遗漏对某个字段的 Mock
- 对 json 的支持度不高:当配置了
format: json时,返回的内容乱七不糟,有时甚至直接返回输入的 prompt (罢工了)
解决内容随机性问题
我们翻阅了文档,发现以下参数或配置会影响大模型的生成文本的随机性:
- 温度参数、随机种子、top-k、top-p 等模型参数
- 上下文依赖、模型版本
- 硬件差异
2、3 基本可以排除了,看来主要是 1 的问题,1 里面涉及的关键词较多,这边只列出几个关键的参数说明:
- 温度参数:温度参数作用于模型的输出概率分布。模型会为每个可能的词生成一个概率分布,温度参数通过调整这些概率值,影响最终生成的文本。
- 温度 < 1:高概率的词权重放大,生成的文本更保守,适合需要稳定输出的服务
- 温度 > 1:低概率的词权重放大,生成的文本多样化,适合需要创建多样性的服务
- 温度 = 1:不改变原始概率分布,模型按原始概率生成文本
- 温度 -> 0:输出完全确定性,但可能缺乏多样性
- 随机种子:随机种子是控制随机过程的关键参数,通过固定随机种子可以确保实验的可复现性。在文本生成中,随机种子影响采样方法的结果,固定种子可以确保相同的输入和参数下生成相同的输出
通过固定这两个参数,我们发现模型返回的 Mock 内容较为稳定了,基本都是返回以下的内容:
解决对 json 的支持度不高问题
Mock 的结果基本都是 json 格式的,但是如果我们直接传递 format: json给大模型,其返回内容就不太理想了:
返回的内容并不是预期的 Mock 结果,由于本地部署的大模型中间包了一层 ollama,不知道是哪一层支持有问题,随后便考虑使用更完善的
json schema 定义,把 formt: json改成了如下的 json shcema 配置:
"format": {
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"type": "object",
"properties": {
"messageBizType": {
"type": "string",
},
// 其他字段的 schema 配置
},
}
}
}
},
调整后再次调用大模型,返回的 json 内容就比较稳定了,基本都能返回以下内容:
至此为止,本地模型的 Mock 已经达到了较为稳定的情况,返回的 Mock 内容也返回预期情况,同时 RT 基本能控制在 10s 以内
额外说明:
- 关于调用模型的优化还包含了其他方面(比如说基于不同接口的 prompt 优化),文章中没有一一阐述
- 调用本地的模型时,首次 RT 会稍久些,后面调用的 RT 能控制在 10s 内(主要是因为 ollama 提供的 keep_alive 能力,让模型缓存在内存中)
plug 的大模型选择
在 plug最新的 beta 版里,我们实验性的开启了基于大模型的智能 Mock 功能。同时我们采取了优先使用「本地部署的大模型」,可选择性启用 「火山引擎的大模型的开放 API 服务」
总结
本文通过从功能到原理的形式,介绍了古茗供应链团队接口 Mock 解决方案 - plug。并结合现在流行的大模型解决了 Mock 数据不真实的问题,虽然这个功能还在试验中,且会出现不稳定的问题,但我们会持续优化plug的功能和稳定性,让 Mock 能力更加便捷、有效。