编写自定义 Mock 响应:动态响应模板与 Handlebars 语法
静态 Mock 响应只能返回固定数据,无法模拟真实 API 根据请求参数、路径、请求头等动态返回不同结果。本章通过引入 Handlebars 模板引擎,实现“请求参数 → 模板变量 → 动态响应”的流水线,让 Mock 服务在开发环境中更贴近真实后端行为。
4.1 核心原理
动态响应模板的工作分为三个阶段:
- 注入:将 HTTP 请求中的
query、params、body、headers等数据提取为模板变量。 - 编译:使用 Handlebars 预编译模板字符串,将变量占位符替换为实际值。
- 输出:生成最终的 JSON/XML/Text 响应体,返回给客户端。
关键点:Handlebars 是逻辑较少的模板语言,支持
{{变量}}、{{#each}}、{{#if}} 等基础语法,天然适合 JSON 数据驱动。
4.2 实现流程
| 步骤 | 操作 | 说明 |
|---|---|---|
| 1 | 安装 Handlebars | npm install handlebars 或引入 CDN |
| 2 | 定义模板字符串 | 在 Mock 配置或独立文件中编写 {{}} 模板 |
| 3 | 编写请求参数映射器 | 从 req 中提取需要的字段 |
| 4 | 编译并渲染 | Handlebars.compile(template)(data) |
| 5 | 返回响应 | 将渲染结果以 JSON/其他格式返回 |
| 6 | 调试与缓存 | 验证模板与参数,优化编译性能 |
4.3 代码示例:基于 Express 的 Mock 中间件
以下示例演示如何在 Node.js (Express) 中实现动态 Mock 响应:
const Handlebars = require('handlebars');
const express = require('express');
const app = express();
app.use(express.json());
// 预定义模板
const templates = {
user: Handlebars.compile('{"id":{{id}},"name":"{{name}}","role":"{{role}}"}'),
list: Handlebars.compile('[{{#each items}}{"id":{{id}},"title":"{{title}}"}{{#unless @last}},{{/unless}}{{/each}}]')
};
app.get('/api/user/:id', (req, res) => {
// 注入请求参数
const data = {
id: req.params.id,
name: req.query.name || 'default',
role: req.headers['x-role'] || 'user'
};
// 渲染
const resp = templates.user(data);
res.set('Content-Type', 'application/json');
res.send(resp);
});
app.post('/api/list', (req, res) => {
const items = req.body.items || [{ id: 1, title: 'item1' }];
const resp = templates.list({ items });
res.json(JSON.parse(resp));
});
app.listen(3000);
模板变量来源:
req.params(路径参数)、req.query(查询参数)、req.body(POST body)、req.headers(请求头)。
4.4 调试技巧
- 打印原始模板:在编译前输出模板字符串,检查
{{}}是否匹配。 - 使用调试中间件:在渲染前后打印
data对象和渲染结果。 - Handlebars 已知问题:如果变量不存在不会报错,会输出空字符串。可用
{{! 注释}}调试。
4.5 常见错误与修复
| 错误 | 原因 | 修复 |
|---|---|---|
模板报错 Expecting 'STRING', 'NUMBER', 'BOOLEAN' |
JSON 中手写逗号、括号不合法 | 模板字符串本身应为合法 JSON 格式,或用 {{#if}} 控制 |
| 变量输出为空 | 变量名拼错或未从请求中提取 | 打印 data 对象,确认 key 名称 |
| 循环结果不完整 | {{#each}} 未正确闭合 |
确保 {{/each}} 存在且数据为数组 |
安全注意:不要在模板中直接拼接用户输入(如
req.query.name)作为 Handlebars 部分,防止模板注入。应始终通过数据对象传递,并对字符串做转义。
4.6 性能优化
- 预编译模板:在服务启动时用
Handlebars.compile()一次,不要每次请求都编译。 - 缓存编译结果:如果模板数量多,使用 LRU 缓存;Handlebars 编译 CPU 开销较高。
- 减少模板中的逻辑:复杂的
{{#if}}链建议拆分到数据预处理中。 - 使用
Handlebars.precompile():将模板预编译为 JS 函数,可用于构建时优化。
4.7 内联 SVG 流程图:动态响应模板处理流程
下图展示了从 HTTP 请求到渲染响应的完整路径:
4.8 部署建议
- 将模板与数据映射逻辑分离,存入独立 JSON 文件。
- 使用环境变量控制模板目录路径,便于多环境复用。
- 在 CI/CD 中加入模板语法校验(如
Handlebars.parse()检测错误)。
练习:修改上面示例,增加
{{#if}} 分支,使得当 role === 'admin' 时返回额外字段 "admin":true。