模板能力
这页聚焦模板表达式、能力边界和签名前置数据准备。
先判断是不是这页的范围
- 你要在
request / env / template_vars / items[].request里准备动态值:看这里 - 你要做时间戳、nonce、query 规范化、轻量编码转换:看这里
- 你要做摘要、JWT、RSA、AES、HMAC 等确定性安全计算:优先去安全工具文档
- 你只是想确认协议字段该怎么写:先回接口文档,不必先把模板文档读完
怎么读这页
- 先看“设计定位”,确认模板层到底承接什么,不承接什么
- 再看“能放在哪些位置”和模板表达式分类,判断动态值应该写在哪里
- 最后看组合写法和模板层与安全工具的分工,不要一开始就往模板层堆所有逻辑
这页解决什么问题
- 模板层到底能做什么
- 哪些逻辑适合放模板里
- 哪些逻辑应该交给专用安全能力
request / env / template_vars / items[].request里怎么用模板
设计定位
当前模板能力已经不是一个“纯字符串替换器”,而是一个轻量的数据准备层。
它适合做:
- 时间戳、随机值、请求 ID、nonce
- JSON / Base64 / Hex / URL 编解码
- query 拼装、字典排序、稳定 query 串生成
- 轻量 request/env 物化与合并
- 在模板里做纯函数、无副作用的数据准备
它不适合做:
- 复杂业务流程判断
- 带外部副作用的操作
- 重型安全实现的替代品
一句话理解:
- 模板层负责准备材料
- 安全层负责做确定性计算
能放在哪些位置
模板表达式 {{ ... }} 可以放在:
requestenvtemplate_varsitems[].request
常见使用方式:
request = {
"url": "/api/{{ tenant }}/profile",
"headers": {
"X-Request-Id": "{{ uuid4() }}",
"X-Timestamp": "{{ now_s() }}"
}
}
- 这层只负责把动态值准备好,不负责描述完整请求结构
- batch 只有一份全局
template_vars;如果每个 item 都有独立值,必须直接写进各自的items[].request
SSE batch 特别注意:
- 不要把每条 case 的
user_input放进全局template_vars - 不要依赖
{{user_input}}在 batch 中按 item 自动替换 - 不要把每条 case 的
current_time放进env.json - 这类 case 级字段应在提交前先展开成字面值,再写入
items[].request.json env和items必须传原生结构化对象,不要传字符串化 JSONconcurrency和fail_fast应按预期行为显式传值,不要在校验失败后通过省略字段回退默认值render / validate / execute现在共享同一套env + request物化语义;不要再假设预执行和执行期会看到不同请求
Helper 分类
1. 标识与时间
now_s()返回当前 Unix 秒级时间戳。适合请求头时间、轻量时间字段、简单时序标记。now_ms()返回当前 Unix 毫秒级时间戳。适合请求体时间、事件打点、需要更细粒度时间的字段。now_iso()返回当前 ISO 8601 时间字符串。适合可读时间字段、日志时间、要求 ISO 格式的接口。format_ts(ts, fmt="%Y-%m-%dT%H:%M:%SZ")把已有时间戳格式化成指定字符串格式。适合把上游时间值转成接口要求的时间文本。offset_ts(ts=None, seconds=0, minutes=0, hours=0, days=0, out="iso")基于当前时间或指定时间做偏移,并按目标格式输出。适合生成过期时间、未来时间、窗口起止时间。uuid4()生成随机 UUID。适合请求 ID、trace ID、幂等键、关联标识。nonce(length=16, alphabet=None)生成指定长度的随机字符串。适合防重放 nonce、临时随机参数、一次性标识。
2. 取值与回退
pick(obj, "a.b.0", default=None)按路径从对象中安全取值,取不到时回退到默认值。适合从template_vars / env / request中读取嵌套字段。coalesce(a, b, c, ...)返回第一个非空值。适合多来源 fallback,例如优先业务值,缺失时回退到环境值或默认值。
示例:
{{ coalesce(pick(template_vars, "token"), pick(env, "token"), "default-token") }}
3. 编解码
b64encode(v)把值编码成 Base64。适合基础认证、二进制转文本、接口要求 Base64 载荷的场景。b64decode(v, as_text=True)把 Base64 文本解码回原值。适合服务端返回 Base64 内容后先还原再参与后续拼装。hex_encode(v)把值编码成十六进制。适合原始字节、协议字段要求 hex 文本、签名前的中间值表达。hex_decode(v, as_text=True)把十六进制文本解码回原值。适合服务端返回 hex 内容后继续参与模板计算。json_dumps(v, ensure_ascii=False, sort_keys=False)把对象序列化成 JSON 字符串。适合接口字段要求字符串化 JSON,而不是原生对象时使用。json_loads(v)把 JSON 字符串解析成对象。适合上游给的是 JSON 文本,但当前模板逻辑需要读取内部字段时使用。
4. URL 与结构
urlencode(obj)把对象编码成 URL query 字符串。适合拼查询串、表单串、回调参数。urldecode(text)把 URL 编码文本解码回可读内容。适合先还原已有 query 或编码参数再继续处理。dict_merge(a, b, c, ...)合并多个对象,后者覆盖前者。适合构造 headers、params、variables 等共享默认值叠加场景。sort_keys(obj)按 key 排序对象。适合生成稳定结构,便于签名前预处理或做稳定比较。canonical_query(obj)把对象转成稳定排序的 query 字符串。适合签名前 query 归一化、稳定 URL 参数生成。
示例:
{{ canonical_query(dict_merge(env.query_defaults, request.params)) }}
5. 压缩与二进制转换
gzip_encode(v, ...)用 gzip 压缩值。适合服务端要求 gzip 输入,或要模拟压缩请求体的场景。gzip_decode(v, ...)解 gzip 内容。适合服务端返回 gzip 文本或上游给的是压缩内容时先恢复原值。zlib_encode(v, ...)用 zlib 压缩值。适合协议明确要求 zlib 编码时使用。zlib_decode(v, ...)解 zlib 内容。适合继续处理 zlib 压缩后的响应或中间值。
常见组合写法
生成时间戳与请求 ID
{
"headers": {
"X-Req-Id": "{{ uuid4() }}",
"X-Ts": "{{ now_ms() }}"
}
}
稳定 query 串
{{ canonical_query(request.params or {}) }}
合并公共头
{{ dict_merge(env.headers or {}, request.headers or {}) }}
生成 Basic 认证原文
{{ b64encode(coalesce(env.username, '') + ':' + coalesce(env.password, '')) }}
模板层与安全工具怎么分工
推荐分工:
- 模板层:准备输入材料
- 专用安全能力:负责摘要、HMAC、JWT、RSA、AES 这类确定性安全能力
例如 HMAC 场景:
- 模板层生成 canonical query / body string / timestamp
- 专用安全能力计算
hmac_sha256 - 把签名结果回填到请求头
所以:
- query 排序、JSON 稳定序列化:放模板层
hmac_sha256 / jwt / rsa_sign:放专用安全能力
为什么不建议继续把所有能力塞进模板层
template.py 现在已经偏大而全。
继续往里塞模板表达式会带来这些问题:
- 责任边界模糊
- 文档和可发现性下降
- 用户不知道该在模板层还是安全层处理
新增模板表达式的标准应该尽量收紧:
- 纯函数
- 无副作用
- 明显属于“请求前的数据准备”
- 不重复实现专用安全能力
什么时候优先看这页
- 你在写协议与校验类星图
- 你要做 query / body / header 的模板化构造
- 你要准备签名前置材料
- 你想知道模板层和专用安全能力的边界