一个演示环境,我居然搞了1个月
最近搞了一个演示环境,前前后后花了一个月。
原因很简单,系统做好了,总得给用户看看。最好还能自己上手操作一下。但是生产环境里有真实数据、账号和文件,这些东西直接给别人用,谁知道会发生什么。
那么我们就需要一个专门的演示环境。
我一开始就知道,这个工作量小不了。页面要和原来的系统一样,用户可以登录、查询、修改、上传,数据还得前后对得上。同时,生产环境里的数据一条都不能带过去。
还有一个要求:业务前端继续用原来的代码。 我可不想为了演示,再养一套前端。以后正常系统改一个功能,演示环境还得跟着改一遍,想想都累。
所以,怎么把这个东西做得优雅一点?先想方案。
先把方案想清楚
大致想了一下,有这么几种做法:
| 方案 | 优点 | 安全隐患 |
|---|---|---|
| 单独做一套 HTML | 快,页面想怎么展示就怎么展示,适合介绍产品 | 静态包发出去就能被人查看,放进去的数据也一样;账号和真实接口凭据都得清理 |
| 全量 Mock | 可以直接接现有前端,返回什么数据自己说了算 | 拦截漏了、接口地址配错了,仍可能请求真实环境;假数据也要检查来源 |
| 在原系统开分支 | 页面和后端都有,现成的代码可以接着用 | 配置、账号或数据库接错,就可能碰到生产数据;部署权限和凭据也得重新隔离 |
| 单独做演示服务 | 沿用原前端,后端和数据自己控制,可以独立部署 | 管理端权限、默认账号、数据目录和发布凭据,都需要自己处理好 |
HTML 很快,不过后面系统改版了,这些页面也得跟着改。做到最后,又多养了一套东西。
开分支也考虑过。代码可以复制,后面的维护怎么办?今天加一点演示逻辑,明天主系统又升级了,合并的时候还得把它们分清楚。更麻烦的是,原来的环境配置、数据库和外部服务也会一起跟过来,每一项都得检查。
想了一圈,我选了专门做一套演示服务。前端照常开发,演示服务负责接它的请求,数据单独生成,部署也单独走。以后更新前端的时候,这边把接口跟上就行。
工作量集中在后端,但这笔账我觉得划算。
flowchart TD
A[准备给用户体验系统] --> B{只需要展示页面?}
B -->|是| C[HTML 或 Mock 可以满足]
B -->|否,需要实际操作| D{沿用原系统分支?}
D -->|是| E[隔离配置、数据和外部服务]
E --> F{能接受长期维护演示分支?}
F -->|能| G[维护独立演示分支]
F -->|不能| H[单独做演示服务]
D -->|否| H
H --> I[沿用原前端,单独准备 API 和数据库]
为什么这次放弃全量 Mock
Mock 也认真想过。这个最省事,尤其我是做前端的,写几份 JSON 接到页面上,很快就能看到效果。
如果你没用过,可以把它理解成一个接口替身。前端发请求,Mock 根据请求方法、地址和参数找到对应规则,然后返回准备好的数据。在开发服务器里拦截、用 Service Worker 拦截,或者单独起一个 Mock 服务,都可以。
比如请求列表,就返回十条记录;点保存,就返回成功。后端还没开发完的时候,用这个联调很方便。需要展示某个特殊状态,也可以直接写一份数据出来。
但我这次需要用户接着往下操作。保存以后刷新,刚才改的东西要还在;换一个账号,看到的数据和权限要跟着变;上传一个文件,也要真的处理这个文件。
当然,Mock 里也可以加这些逻辑。加状态,存数据,做权限,再补文件处理。写着写着,后端该干的活一样都没少,那我干脆把后端好好做了。
所以这次我放弃了全量 Mock。本地的操作走真实 API 和数据库,接不上的外部服务再单独适配。
外部结果由本地生成时,我还会带上这样的标记:
{
"source": "local-synthetic",
"virtual": true,
"externalConnected": false,
"sourceDigest": "sha256:...",
"ruleVersion": "demo-v1"
}2
3
4
5
6
7
看一眼就知道,这份结果是本地生成的,没接外部服务,生成规则是哪一版。以后查问题也方便,省得对着一份演示数据去排查生产接口。
前端照旧,压力给到后端
前端继续用原来的,那么接口地址、参数、返回值,还有权限判断,都得和它对得上。这些约定放在一起,就是接口契约。
我从前端实际调用的地方往回找。一个页面点下去,到底调用了哪个方法,经过什么封装,最后发到哪里,都得查清楚。
这里有个容易漏的地方:有些请求走统一封装,有些上传直接用了原生 fetch。只搜一遍 http.post,很容易觉得自己找全了,等用户真点到那个按钮,又漏一个。
flowchart TD
A[页面触发一个操作] --> B{找到真实调用入口了吗?}
B -->|没有| C[继续追踪导入链、原生 fetch、上传封装]
C --> A
B -->|找到| D{method + path 能唯一识别吗?}
D -->|不能| E[补 commonCall 或 urlCode]
E --> F[生成 operationKey]
D -->|能| F
F --> G{响应、权限、数据范围都吻合吗?}
G -->|不吻合| H[契约检查失败,阻断发布]
G -->|吻合| I[登记接口契约]
I --> J[接入演示 API]
找到以后,我给每个操作记了一个 operationKey:
operationKey = method + path + commonCall/urlCode这里为什么还要加 commonCall/urlCode?因为有些接口共用一个 URL,具体干什么由参数决定。地址一样,一个可能在查详情,另一个可能在导出文件。只按 URL 登记,这两个操作就被当成一个了。
既然调用关系都找出来了,我的想法可以再大胆一点。给它们算一个指纹。
前端调用文件、导入关系、后端处理函数、鉴权入口和返回结构都算进去。以后这些地方改了,指纹跟着变,检查就会提醒这条接口需要重新核对。
这样前端升级以后,我就知道演示服务该检查哪里。总比等用户说“这个按钮怎么没反应”再去找强。
上传也算接口
登记接口时,把上传地址和上传封装也带上。普通查询都通了,上传按钮一点击还在找原来的服务器,这个就尴尬了。
数据自己生成,运营自己操作
接口有了,数据从哪儿来?
生产数据库肯定不能直接拷过去。我也不想每次演示前,自己打开数据库,给这里补几条、那里改一下。演示一次,准备半天,这还玩什么。
所以我又加了数据自动生成。按照演示需要,生成一套有关系、有状态的数据,写进独立数据库。用户在页面上查到的、改到的,就都是这里面的数据。
这里要注意,随便生成一堆随机数很简单,但页面要能顺着操作下去,数据之间的关系就得对。列表里有的记录,点进详情也得有;修改以后,再查出来的也得是修改后的结果。
有了生成流程,新机器部署时自己准备数据,演示过程中被改乱了,也能重新生成一套。发布包里就用不着塞我的本地数据库了。
不过,如果生成数据还得找我敲命令,那这个事情显然还没做完。
于是我专门加了管理端页面,把数据生成、现场检查和环境维护放进去。管理端和业务页面用同一套 API、同一个数据库,看到的就是服务里实际的状态。
好了,现在可以把锅甩到运营身上了。数据要重新准备,自己进管理端操作,以后就不用每次都找我了。哈哈哈哈。
后端怎么拆
后端这边,我要它能接住原来的前端请求,数据可以保存和读取,账号之间的权限也能分清楚。最后还得整个搬到另一台机器上运行。
所以代码按干的活拆开:
| 放在哪里 | 负责什么 |
|---|---|
api | 接 HTTP 请求、检查身份、返回结果 |
domains | 处理具体操作和状态变化 |
infra | 数据库、文件、安全和运行环境 |
root | 把这些东西组装起来,读取配置,启动服务 |
以后接口参数变了,就去看 API 这一层;数据库要备份,就去处理数据库。管理端新加一个操作,也接到同一套服务里。
前端这边只需要通过构建参数换接口地址,部署脚本和外部服务适配放在演示环境里处理。页面还是原来的页面,平时该怎么开发就怎么开发。
数据库?当然是伟大的 SQLite
数据库也得选一下。常见的 PostgreSQL、MySQL、SQLite 都可以考虑,主要看愿意花多少精力部署和维护。
| 数据库 | 优点 | 代价 |
|---|---|---|
| PostgreSQL | 并发能力强,类型、索引和扩展丰富,和大型生产系统接近 | 需要独立数据库服务,部署、备份和版本管理更重 |
| MySQL | 生态成熟,团队熟悉度高,生产案例很多 | 同样需要外部服务,演示环境的安装和迁移成本更高 |
| SQLite | 主要数据放在一个文件里,不用单独部署数据库服务,复制和备份方便 | 写入需要串行处理,高并发写入和多机共享要另外评估 |
我这个规模,用 SQLite 正好。API、管理端和数据库放在一台机器上,装好就能跑。多部署一个数据库服务,就多一份配置、多一次连接排查,换机器的时候还得一起搬。
SQLite 的事务、索引、外键、WAL 和约束也够用。该保存的数据能保存,该回滚的时候能回滚,这就很舒服。
当然,文件方便复制,运行中的数据库也得按规则备份。特别是启用了 WAL,直接去拷一个主文件,可能就把还在日志里的内容漏掉了。
换版本的时候,旧数据库怎么办
演示数据有两部分:第一次生成的种子数据,还有用户操作以后改出来的状态。升级服务时,这些状态还在,表结构却可能已经变了。
我给数据库结构也算了摘要,表、索引、字段和约束都参与。服务启动时先对一遍,确认是认识的结构,再执行对应的迁移。遇到陌生结构,就停下来,让人检查。
迁移放进事务里,中间失败就一起回滚,全部成功以后再写版本标记。里面还有一层初始化时,用保存点处理,避免内层把外层事务提前提交了。
备份也是同样的想法:先拿到一致的数据库快照,检查完整性,再把临时备份文件原子替换成正式备份,放到代码目录之外。这样更新代码时,备份还好好留着。
重置和升级要分开
重新准备演示数据,和升级数据库结构,是两件事。升级时把用户操作过的状态清掉,演示到一半的人可不会觉得这是个小问题。
前端更新了,这边也得跟上
前端照常开发,演示环境就得知道什么时候该更新。不然正常系统都加了新功能,演示页面还停在上个月,看着也挺难受的。
我的做法是把源码摘要、构建产物和运行版本连起来。源码变了就重新构建,把版本信息带进产物,同时记到发布清单里。
运行时再检查:现在这个前端,配的是不是对应的 API?对不上,就换到匹配的产物,或者停止使用旧版本,先把原因报出来。
浏览器最后加载到哪一版,也就有地方查了。省得前端说更新了,服务器说部署了,用户打开还是旧的,然后大家一起猜缓存。
开发环境好好的,打包就出事
这次还碰到了一个很典型的问题。开发目录里能启动,换到干净的目录就挂了。
查下来,是本地残留了旧的 dist。测试能找到,发布构建器也能找到,看起来一切正常。可是在冻结的源码目录里,那份主题产物根本没有生成,发布包就少了一块。
好家伙,原来是上一次的构建结果在替这一次上班。
所以发布这块,我参考了 Vercel 的一些思路:先固定这次要构建的源码,再生成对应产物,构建完就固定下来。运行的时候用这份产物,来源和版本都写进清单里。
具体到部署脚本,就按下面这个顺序检查:
flowchart TD
A[Git SHA] --> B[冻结源码快照]
B --> C[构建前端、API 和管理端]
C --> D{产物是否完整?}
D -->|否| E[构建失败,禁止打包]
D -->|是| F[生成 build receipt]
F --> G[生成 release manifest]
G --> H{摘要是否一致?}
H -->|否| I[发布失败,保留诊断信息]
H -->|是| J[上传到临时目录]
J --> K{目标主机健康检查通过?}
K -->|否| L[停止新版本,回滚旧版本]
K -->|是| M[切换版本并保留回滚点]
build receipt 记这次构建出了什么,release manifest 记这次发布用了什么。源码、前端、API、管理端的摘要,还有配置来源和数据库基线,都一起带上。
以后出问题,先查清单:这台机器跑的是哪一版?前端和 API 是一套吗?数据库跟得上吗?至少能知道从哪里查起。
新版本先上传到临时目录,健康检查通过再切过去;失败就留住旧版本,回滚。这些都交给脚本,演示前临时手动补文件这种事,还是少来一点。
感谢 GPT-6.1-Sol,36 亿 token
这次还搞出了 8000 多条测试用例,光前端单测和组件测试就接近 5000 项,后端、系统、工具、登录、浏览器操作和独立发布也都有检查。
感谢 GPT-6.1-Sol,跑了 36 亿 token。看到这个量,再看看一个月的时间,多少有点感慨。
以前这种工作,我感觉半年都不一定能搞完。后端接口、架构、数据库、管理端、测试,再加上打包部署,每一项展开都有一堆事。现在有 AI 帮着写代码、补测试、查问题,才有可能这样集中往前推。
当然,我还是得把要求想清楚。比如一次保存,参数和权限要对,写完以后还要能重新读到;一个发布包,要搬出工作区,在目标机器上启动成功。这些要求确定了,再让它去补实现和测试。
所以测试也分开跑。接口约定有没有变、数据库能不能读写、浏览器能不能操作、发布包能不能启动,各查各的,失败在哪一层就回去修哪一层。
flowchart TD
A[源码发现] --> B{接口契约通过?}
B -->|否| C[阻断:前后端调用已经漂移]
B -->|是| D[服务和数据库检查]
D --> E{真实状态能回读?}
E -->|否| F[阻断:保存、权限或迁移有问题]
E -->|是| G[真实浏览器流程]
G --> H{独立运行包能启动?}
H -->|否| I[阻断:开发目录掩盖了发布问题]
H -->|是| J[目标主机部署检查]
J --> K{健康检查通过?}
K -->|否| L[阻断:不允许把问题带到演示现场]
K -->|是| M[允许交付]
这里我最关心的是,测试失败的时候,能不能告诉我具体哪里出了问题。保存成功却读不到数据,就查写入和查询;干净目录启动失败,就查构建产物。这样修起来才有方向。
完活
现在用户可以打开原来的业务页面,登录、查数据、修改、上传,后面接的是独立的演示服务。生产数据留在生产环境,演示数据自己生成,运营需要重新准备的时候,去管理端操作。
我觉得这次做得比较舒服的地方,就是业务前端还在正常往前走,演示环境也有自己的后端、数据库和发布方式。后面要更新,就按同一套检查和脚本来。
一个月搞完这些,我觉得还行。以后再有人找我准备演示数据,我就可以把管理端地址发给他了。
