更令人头疼的是 AI 侧的测试场景。你试图让 AI 协助测试某个接口,不得不花大量精力向它解释签名规则。它似乎听懂了,成功计算了一次签名。但到了下一次对话,它又忘记了 nonce 每次必须刷新,导致签名验证再次失败。你在提示词中塞入了一大段签名逻辑,结果 token 长度超限,模型开始丢失关键信息,回答变得答非所问。
然而,越来越多的人开始意识到:签名并非业务逻辑,而是基础设施。基础设施应当被妥善封装,而不是像现在这样散落在每一个测试脚本和提示词里,形成一团乱麻。
因此,本文不讨论抽象概念。直接动手,从零开始,手把手教你编写一个"接口签名 Skill"。完成后,任何需要签名的接口,只需一行代码即可调用。AI 也能立即理解——它只需要知道"有一个工具叫 sign_request",然后传入参数,获取签名,任务完成。
目录
一、现象:签名逻辑正在拖垮测试脚本的可维护性
二、本质变化:把"算法"封装成"能力"才是工程思维
三、核心机制拆解:一个签名 Skill 的三个核心模块
四、典型案例 / 对比:30 行重复代码 vs 1 次 Skill 调用
五、工程落地启示:Skill 优先于函数,函数优先于复制粘贴
六、结尾:你数过自己写了多少遍签名代码吗
一、现象:签名逻辑正在拖垮测试脚本的可维护性
先看一个真实的代码片段。这是一个典型测试脚本中计算 API 签名的部分:
def call_api(params):
timestamp = int(time.time())
nonce = random.randint(100000, 999999)
params['timestamp'] = timestamp
params['nonce'] = nonce
sorted_keys = sorted(params.keys())
sign_str = ''
for k in sorted_keys:
sign_str += f'{k}={params[k]}&'
sign_str = sign_str.rstrip('&')
sign_str += '&key=your_secret_key'
sign = hashlib.sha256(sign_str.encode()).hexdigest().upper()
params['sign'] = sign
return requests.post(url, json=params)
这段代码有什么问题?它反复出现在每一个需要签名的测试用例中。有人复制粘贴,有人写了一个公共函数,但每个项目的签名规则各不相同,这个公共函数最终逐渐演变成一堆 if else 的怪物。
更麻烦的是,签名规则是会变化的。某天产品过来说要增加一个字段"version"参与签名。你就得在几十个测试文件里找到所有签名代码,逐个修改。漏掉一个,那个用例就会一直签名校验失败,查得你头皮发麻。
AI 遇到签名更是灾难现场。你让 AI 调用一个需要签名的接口,它十有八九会直接忽略签名,发送一个裸请求过去,然后被服务端干脆利落地拒绝。你教它如何计算签名,它则在长上下文里开始搞混参数的顺序。你写一个固定的签名脚本给它,它又不会根据不同的请求参数动态调整。
本质问题是:签名是一种"算法能力",而非"业务数据"。普通的提示词和脚本,很难把这种算法抽象出来,并让它被干净地复用。
二、本质变化:把"算法"封装成"能力"才是工程思维
Skill 的核心理念很简单:把一段可复用的算法逻辑,封装成一个有明确输入输出的工具,供 AI 或其他代码调用。
对于接口签名来说,一个 Skill 应该只做三件事:
- 接收原始请求参数和密钥配置。
- 按照约定的算法计算出签名。
- 返回带签名的完整请求体。
调用方不需要知道背后用了什么哈希算法,不需要知道参数怎么排序,更不需要知道 nonce 是怎么生成的。调用方只做一件事:说"我要调用这个接口,参数是这些",然后 Skill 默默地把签名给你补上。
这样做带来的变化是巨大的:签名算法只需要维护一份代码。算法升级了,只改 Skill 内部。所有测试脚本和 AI 对话,就能自动获得新行为。
Skill 不是高级函数。函数封装的是代码逻辑,而 Skill 封装的是"可发现、可组合、可热替换"的能力单元。函数是在代码里硬编码调用的,而 Skill 可以被 AI 通过名字动态发现,并自主决定何时使用。
三、核心机制拆解:一个签名 Skill 的三个核心模块
以最常见的 HMAC-SHA256 签名方案为例。大部分开放平台的签名规则都差不多:参数排序、拼接、加密钥、哈希、转大写。
一个完整的签名 Skill,可以拆成三个核心模块:
- 模块一:参数预处理。 接收原始参数字典,剔除 sign 字段本身(避免自己签自己),然后按 key 的 ASCII 码升序排序。这一步保证服务端和客户端的排序结果一致,这是通信的基石。
- 模块二:签名串构造。 把排序后的参数拼接成
key1=value1&key2=value2的格式,最后再加上&key=密钥。注意 value 到底需要 URL 编码还是原值,取决于具体协议,大多数情况下使用原值。 - 模块三:签名计算与回填。 对签名串做 HMAC-SHA256,输出十六进制字符串,转大写,然后把 sign 字段塞回到参数里。
画一个流程图,能更清晰地看到从输入到输出的全过程:
核心代码示例(Python 版,结构清晰):
class HmacSha256SignSkill:
def __init__(self, secret_key):
self.secret_key = secret_key
def execute(self, params):
# 模块一:参数预处理
params_copy = {k: v for k, v in params.items() if k != 'sign'}
sorted_keys = sorted(params_copy.keys())
# 模块二:构造签名串
sign_str = '&'.join([f'{k}={params_copy[k]}' for k in sorted_keys])
sign_str += f'&key={self.secret_key}'
# 模块三:计算签名
signature = hmac.new(
self.secret_key.encode(),
sign_str.encode(),
hashlib.sha256
).hexdigest().upper()
# 回填
params_copy['sign'] = signature
return params_copy
这个 Skill 怎么被调用?简单到只有一行:
signed_params = sign_skill.execute({'name': 'test', 'id': 123})
requests.post(url, json=signed_params)
AI 调用这个 Skill 的方式也不复杂:把 Skill 注册到 AI Agent 的工具列表里。当 AI 看到用户说"调用用户查询接口,参数 name=张三",它就会自动匹配到签名 Skill,执行后带着签名发请求。
为什么这么设计? 因为签名算法的每一个变种(不同排序规则、不同哈希算法、不同编码方式)都应该是一个独立的 Skill 实例,而不是一个函数里的 if 分支。这样 AI 才能准确选择,不出错。
四、典型案例 / 对比:30 行重复代码 vs 1 次 Skill 调用
对比一个真实的工作流。
场景:测试一个电商系统的三个接口——获取商品、创建订单、查询订单。三个接口都需要签名,签名规则完全一致。
不用 Skill 的做法:
- 每个接口的测试用例里,都要写一遍前面那 30 行签名代码。三个接口就是 90 行重复代码。
- 换一个环境(从测试环境切到预发布),密钥变了,你要去三个地方改。
- 维护成本随着接口数量线性增长。十个人同时开发不同接口的测试,每个人复制一份签名代码,最终能改出一百种细微差异。有人忘了对 value 做 URL 编码,有人用了 MD5 而不是 SHA256,有人把 timestamp 格式写成了毫秒。排查的时候,你根本不知道哪个版本的签名才是对的。
用 Skill 的做法:
- 写一个签名 Skill,注入当前环境的密钥。三个接口的测试代码统一变成:
signed_params = sign_skill.execute(original_params)
- 环境切换,只需要改 Skill 初始化时的密钥。算法升级,只改 Skill 内部。新加入的同事根本不需要知道签名规则,直接调用 Skill 就行了。
AI 场景的对比更明显。 没有 Skill 时,你让 AI"帮我测一下创建订单接口,参数是商品 ID=100,数量=2"。AI 会发一个不带签名的请求,然后返回 401。你解释一遍签名规则,它开始算。对话一长,它忘了 nonce 要每次刷新,签名校验又挂掉。
有 Skill 时,你告诉 AI"你有签名工具,直接调用它就行"。AI 做的事情就是:把你的参数交给 Skill,拿到签名后的请求,然后发送。AI 不需要理解签名算法,出错概率直接降为零。
这个对比说明:Skill 让复杂逻辑对调用方完全透明。 这是任何其他封装形式都做不到的,因为 AI 能够根据 Skill 的描述,自主决定何时使用它。
五、工程落地启示:Skill 优先于函数,函数优先于复制粘贴
如果你现在还在每个测试脚本里手写签名,有几件事可以立刻做起来。
- 第一,识别你项目中重复出现的"算法类"逻辑。 签名是典型,还有数据加解密、文件格式转换、数据库连接池管理。只要算法稳定、输入输出明确、需要复用,就应该封装成 Skill。
- 第二,Skill 的粒度要适中。 太细了,比如"生成一个随机数"这种,就没必要封装。太粗了,比如"完整的下单流程",那是业务场景而不是通用能力。判断标准很简单:这个逻辑是否可能被多个不相关的场景同时用到。
- 第三,Skill 要自包含,无副作用。 签名 Skill 只做一件事:输入参数,输出签名后的参数。它不应该写日志到某个固定文件,不应该修改全局变量,也不应该依赖外部配置(除了初始化时注入的密钥)。这样才能保证多个 Skill 在并行调用时不会互相冲突。
对在校生来说,写一个签名 Skill 是最好的练手项目。它不复杂,但涉及参数处理、算法调用、异常处理。做完之后放到简历上,比写一句"熟悉接口测试"有说服力得多。
对初级工程师来说,这件事的启发是:不要满足于"能跑通"。你的代码里有多少段重复的逻辑,就有多少个抽成 Skill 的机会。
六、结尾:你数过自己写了多少遍签名代码吗
有人算过一笔账。过去五年里,至少在不同的项目里写了四十多次签名逻辑。有时候是 Python,有时候是 Java,有时候是 Shell。每次写的时候都觉得"这次应该是最后一次了",但下次还是从头开始。
Skill 不能消除所有重复,但它可以把"算法类"的重复降为零。因为 Skill 的封装粒度足够大,大到算法变化时,你完全不用去改调用方。
现在,我想问一个更实际的问题:
打开你最近写的三个测试脚本,数一数里面有多少行代码是在做"签名""加密""格式化"这种通用逻辑。如果把所有这些代码抽成 Skill,你的脚本会减少多少行?
有人会说,这个数字可能会让自己吓一跳,发现自己其实比想象中更早需要 Skill。
