做霸王餐CPS系统时,最让我头疼的其实不是优惠券核销、佣金计算这类业务规则,而是“如何把一条订单消息安全、完整、可追溯地送达对方系统”。这个过程中,自定义接口协议和Java序列化方案几乎决定了整个平台的对接质量和故障率。这篇博客就聊聊我在这个项目里对协议设计的完整思考,以及Java序列化的选型、安全防护和性能实战,适合正在做本地生活、CPS结算类系统的Java工程师参考。
霸王餐CPS本身是个不错的商业闭环:商家拿出免费或特价套餐,用户吃完后产出真实评价和社交传播,平台再按有效订单给渠道方结算佣金。模式不复杂,复杂的是多方系统的数据交换怎么约定、怎么保证不扯皮。你在开发中遇到的大部分接口报错、数据对不上、核销不成功问题,根子往往都在协议没设计好、序列化没选对。下面我按从业务到代码的顺序,把整套思路逐步摊开。
1. 为什么霸王餐CPS系统需要自定义接口协议
1.1 先捋清楚业务链路,再谈协议
霸王餐CPS系统里至少有四个角色:平台后端、商家端系统、用户端小程序、渠道方(团长、达人、推广群主)。一次完整的业务流是:渠道方推广出去 → 用户报名 → 商家审核 → 用户到店出示核销码 → 商家确认核销 → 平台给渠道结算佣金。
这条链路里有两个非常敏感的点。第一是核销,一个核销码用一次,不能多核销也不能被伪造;第二是返佣,渠道方贡献了订单,平台必须准确记录并结算,每一笔钱都能追溯到具体的渠道、具体的订单。
因为涉及钱和资源,外界一个普通的HTTP回调我都不能轻易信任。如果只是自己平台内部模块之间调用,用内部RPC就完了,但商家端可能是PHP、可能是Java、甚至可能只有一个别人写好的接口给我们调。各家系统水平参差不齐,所以平台必须拿出一套统一的、有签名的、可验证的自定义接口协议,让所有参与方按同一套规则说话。
1.2 现成协议为什么不能直接套用
有同学会问:市面上开放平台那套OAuth2、API网关方案不是很成熟吗?直接搬过来不就行了?
理论上可以,但实际跑一圈后你会发现很多问题。开放平台级的协议往往非常重,授权、刷新、多环境、复杂的签名规则,对一个核心开发只有两三个人的项目来说,前期接入成本高得吓人,商家技术负责人一看文档就跑路了。
而完全裸奔的方案,比如大家约定一个JSON地址、POST过去就完事,又扛不住安全和可追溯要求。用户手机号泄露、订单被伪造、核销码被批量刷,任何一件都是运营事故。
所以我的判断是:不走开放平台的全套重型方案,也不走裸JSON方案,而是设计一套够用、不过度设计、看一眼能懂的自定义接口协议。它要覆盖三点:
- 所有参与方使用相同的数据结构,字段语义明确;
- 所有请求都经过签名和防重放校验,防止伪造和重复提交;
- 协议本身带版本号,后续功能演进不破坏老的对接方。
这套协议的受众不是外部上万名开发者,而是几十个合作商家和一批渠道方。协议设计得清楚,大家接入快;设计得绕,自己后面维护也痛苦。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 自定义接口协议的报文设计与签名机制
2.1 统一请求报文结构
我最终定稿的请求报文是一个JSON对象,所有参与方调平台接口时,都按这个结构来封装业务参数:
json复制{
"version": "1.0",
"appId": "mch_10001",
"timestamp": 1716000000,
"nonce": "8f2a9e01c3d44b5fa77c9e2e1a6b0d12",
"sign": "5b8f4c7e1a9d24e6f0ab3c9d8e7f6a5b4c3d2e1f0a1b2c3d4e5f60718293a4b5c",
"payload": {
"orderNo": "CPS20240518001",
"verifyCode": "6A9F2B",
"verifyTime": 1716000060
}
}
每个字段为什么存在,设计时都是有明确考虑的:
- version:协议版本号。将来字段结构变化时,老版本对接方可以继续用旧协议,新对接方用新协议,不用同一套结构打死所有人。
- appId:标识调用方身份,商家是mch_开头,渠道是promo_开头。服务端根据appId查到这个合作方的密钥。
- timestamp:请求发起时间,单位是秒。签名校验时会检查它和服务器当前时间是否在5分钟窗口内,超过就拒绝,这是防重放的第一道门槛。
- nonce:随机字符串,服务端在5分钟窗口内对nonce做去重,同一个nonce只能成功用一次。timestamp负责限制时间窗,nonce负责在窗口内防重复。
- sign:对整个签名串计算出来的签名值。
- payload:真正的业务参数。为什么业务参数要单独包一层?因为签名和业务字段解耦,后续payload内部怎么变化,签名结构和校验逻辑不用跟着频繁改。
有的团队会把业务参数直接平铺在报文外层,签名字段也混在一起,结果加一个字段就要重新验签,改起来非常痛苦。把协议元信息和业务数据分层,是这套设计的核心思路。
2.2 统一响应报文与错误码设计
请求有了规范,响应也得规范。我的响应报文结构如下:
json复制{
"code": 0,
"msg": "ok",
"traceId": "8f3b7c2e9a4d1e6f",
"data": {
"orderStatus": "VERIFIED",
"commission": "12.50"
}
}
吐槽一下,我看到过很多系统响应里只有code和msg,没有traceId。线上出问题时,商家拿着报错信息来找你,你根本不知道对应哪一条日志,查无可查。从第一期设计就加上traceId,后面联调会省掉大量时间。
错误码这块,我采用的是分段设计,不搞一个几千行的码表:
| 码段 | 含义 | 示例 |
|---|---|---|
| 0 | 成功 | 0: ok |
| 1xxx | 参数错误 | 1001: 缺少必填字段;1002: payload格式错误 |
| 2xxx | 业务状态错误 | 2001: 订单不存在;2002: 核销码已使用;2003: 订单已返佣 |
| 3xxx | 权限与签名错误 | 3001: appId不存在;3002: 签名校验失败;3003: 请求已过期 |
| 5xxx | 服务端异常 | 5000: 系统繁忙;5001: 数据访问异常 |
实际经验是:错误码一定不要定义太细,否则接入方要写大量错误码映射代码,很容易在网上抱怨你们平台难用;但也不能太粗,5000一把梭,排查问题全靠猜。按上面的分段控制在30个码以内是最舒服的。
2.3 签名算法选型,别一上来就上RSA
签名算法我选了HMAC-SHA256,而不是RSA。很多人一谈接口安全就要上RSA,我理解,但也要看场景。RSA是非对称签名,适合对完全不可信的第三方开放场景;而在霸王餐CPS这种联盟生态里,合作方是逐个签约的,平台给每个合作方发一个appSecret(对称密钥),双方各持一份,安全性足够。
对称签名的优点是计算快、实现简单,各个语言都有内建库,对方写代码对接时几乎没有门槛。
签名串
