ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

鸿蒙App接入京东联盟:API直连与签名算法实战指南

鸿蒙App接入京东联盟:API直连与签名算法实战指南 上个月我们的App鸿蒙版总算正式过审上架京东联盟模块也跟着跑通了。坦白说动手之前我是有点打鼓的京东联盟开放平台并没有提供鸿蒙原生SDK网上也找不到一篇像样的鸿蒙接入文档博客和技术社区里翻来翻去都是Android或者iOS的旧代码。但实际做下来这件事没有想象中那么复杂——真正的难点其实只有一个把京东联盟的接口签名规则搞清楚剩下的全是常规网络请求和参数拼装。这篇文章就写给准备在鸿蒙应用里接京东联盟的开发者。我会把从账号申请、密钥获取、签名算法、ArkTS代码实现到上线前的风控踩坑和归因核对的完整过程全部摊开讲清楚。你不需要认识京东联盟内部的人也不需要等官方SDK只要照着这里的思路走两天内跑通API直连是完全可以做到的。1. 鸿蒙环境下为什么选择API直连京东联盟SDK的现实情况京东联盟是京东官方的CPS推广平台开发者通过它拿到商品推广链接、生成专属推广位用户通过链接下单后开发者可以获得对应佣金。对做返利、优惠聚合、内容导购类App的团队来说这几乎是标配能力。鸿蒙生态起来之后很多团队把现有App往鸿蒙上迁移京东联盟自然也要跟着迁移。但这里就遇到一个现实问题京东联盟官方开放平台提供的SDK常见的是针对Android/iOS的版本鸿蒙NEXT不再兼容APK的纯血鸿蒙出来以后官方并没有第一时间跟进发布鸿蒙版原生SDK。网上甚至能看到不少人在问答社区里问京东联盟鸿蒙SDK什么时候出答案基本都是等通知。所以摆在面前的路有三种第一种等官方鸿蒙SDK。最省事但时间不可控版本迭代节奏也由别人决定对急着上架的项目来说不太现实。第二种用WebView加载京东联盟H5页面。能跑通但用户交互体验一般登录态、跳转、返利链路都要在WebView里处理很多原生能力用不上风控也容易出问题。第三种直接调用京东联盟开放平台的HTTP接口也就是routerjson网关自己在鸿蒙工程里实现签名、请求、解析。这也是我最终选择的方案。选择API直连的逻辑很清楚京东联盟开放平台本质上是标准的HTTP JSON接口只要密钥和签名正确任何语言任何平台都能调用。鸿蒙的ArkTS虽然不是Java也不是Kotlin但网络请求、字符串处理、MD5摘要这些基础能力一应俱全完全够用。既然官方没有SDK那API直连就是最务实、最可控的方案。另外还要说明一点京东联盟的接口分为面向媒体的CPS接口商品查询、订单查询、链接生成和面向企业应用的授权接口。大多数返利类App用到的是前者只需要AppKey和AppSecret不需要走OAuth授权流程。这个特性大大降低了接入门槛也是为什么API直连方案能快速落地的关键。2. 准备阶段应用凭证、网络权限与工程基础配置想清楚走API直连之后第一步不是写代码而是先到京东联盟后台把该申请的都申请好。这个环节看似简单但很多人到后面签名一直报错才发现问题根源其实是某个参数拿错了。2.1 京东联盟开放平台后台的操作路径注册京东联盟账号之后进入开放平台重点确认三样东西第一创建应用获取AppKey和AppSecret。在我的应用里创建一个应用创建成功后会生成一串AppKey和对应的AppSecret。AppSecret后面要参与签名计算属于绝密信息不要把它写死在客户端代码里更不要提交到Git仓库。我见过有人在demo里把secret明文写在页面里上传到社区结果第二天就被盗刷接口了。正确的做法是让secret留在服务端签名也在服务端算App只负责发请求如果是纯本地应用至少也要做好混淆和加固。第二申请接口权限。京东联盟的接口不是开通账号就全部开放的需要逐个申请。商品查询、链接生成、订单查询这几个常用的接口优先申请审核一般很快。没有权限时调用接口会返回权限类错误码这个在排查时容易被误认为是签名问题先确认权限再怀疑签名。第三创建推广位。推广位是京东联盟结算归因的最小单位每个App或渠道可以建多个推广位。后续生成推广链接时必须带上推广位ID否则订单可能无法正确归属佣金会跑丢。推广位的名称建议和渠道一一对应比如HarmonyOS主App这样后台看报表时能一眼分清哪个渠道带来多少单。2.2 module.json5网络权限与鸿蒙基础配置拿到凭证后回到代码工程。新建鸿蒙应用模块后默认工程不会开放网络权限需要手动在module.json5里添加。{ module: { name: entry, type: entry, requestPermissions: [ { name: ohos.permission.INTERNET } ] } }这里有一个容易踩的细节鸿蒙的权限声明区分system_grant和user_grantINTERNET属于system_grant正常配置后不需要弹窗确认也不需要在运行时动态申请。如果你发现请求接口时报无网络权限大概率是权限没有配置到请求对应的模块上检查一下是不是配到了其他module里。另外如果应用需要跳转京东App、唤起京东客户端后续还可能涉及查询应用安装状态的权限这个我在第六部分展开讲这里先不铺开。环境配置到这里就可以暂时告一段落接下来进入整篇接入过程中最核心的部分——签名算法。3. 签名协议是全部难点排序、加盐与MD5的坑京东联盟的HTTP接口使用签名机制来保证请求参数没有被篡改。服务端会根据你提交的参数和AppSecret重新计算一遍签名和你传上来的sign比对不一致就直接拒绝。理解了这套机制你就能明白为什么那么多人在签名上报错。3.1 参数签名规则拆解以我接入时的版本为例京东联盟签名的大致流程如下第一步把所有请求参数放在同一个字典里。包括公共参数method、app_key、timestamp、format、v、sign_method、param_json以及业务参数。注意业务参数通常是一个JSON字符串整体作为param_json这一个参数的值参与签名。第二步把参数的key按ASCII码升序排列。这个排序是字典序不是按参数的先后顺序也不是按拼音更不是按你脑子里写代码的顺序。凡是涉及排序的环节必须以排序后的结果为唯一标准。第三步把所有key和value拼接成一个连续字符串。常见规则是key1value1key2value2中间不插分隔符。也有个别接口要求keyvalue1key2value2的形式不同平台差异很大必须以官方文档的签名字节为准。第四步在拼接串的首尾分别加上AppSecret或者根据规则只加在尾部再对这个整体做MD5摘要得到最终的sign值。这里面有三个最容易出错的地方排序方式必须用ASCII码升序不是字符串长度不是首字母拼音。URL编码参数值里如果包含特殊字符是先编码再参与签名还是直接用原始值参与签名不同接口可能不同。摘要大小写MD5的结果要转成大写还是保持小写京东联盟接口通常要求小写但也有个别的开放平台要求大写甚至有的平台两个都能过纯粹看服务端实现。我把这三个问题统称为签名三兄弟。实际排查中大概90%的签名错误都出自这三个环节而不是算法本身写错。3.2 用ArkTS实现MD5签名函数鸿蒙NEXT的ArkTS提供了完整的密码学框架可以直接用它来做MD5摘要。以HarmonyOS 5.0/5.1环境为例代码如下import { cryptoFramework } from kit.CryptoArchitectureKit; import { buffer } from kit.ArkTS; function bytesToHex(bytes: Uint8Array): string { let result ; for (let i 0; i bytes.length; i) { result bytes[i].toString(16).padStart(2, 0); } return result; } async function md5String(input: string): Promisestring { const md cryptoFramework.createMd(MD5); const blob: cryptoFramework.DataBlob { data: new Uint8Array(buffer.from(input, utf-8).buffer) }; await md.update(blob); const digest: cryptoFramework.DataBlob await md.digest(); return bytesToHex(digest.data); }这里有个容易被坑的点cryptoFramework的update方法接收的是DataBlob对象需要传入Uint8Array不是直接传字符串。所以必须先把字符串转为UTF-8字节流。有的新手直接用buffer.from(input)里面的ArrayBuffer传给update类型对不上编译能过但运行时报错就是这个原因。签名生成函数如下async function buildSign( params: Recordstring, string, secret: string ): Promisestring { const keys Object.keys(params).sort(); let raw ; for (const key of keys) { raw key params[key]; } raw secret raw secret; return await md5String(raw); }这段代码里的排序用的是JavaScript默认的sort()默认就是按UTF-16码元排序对常规ASCII字符来说等价于ASCII码升序够用。如果你在排序后仍然签名不过去确认一下官方文档里是否要求编码后再参与签名。3.3 排查签名不一致的思路签名报错时不要慌张更不要一行一行瞎猜。我的排查思路是三步走第一把你的参数集合、排序结果、拼接字符串、最终sign打印到日志里或者本地文件里形成一条调试链路。第二拿其中一条完整的调试记录在服务端或者用Postman手动复算一遍看结果是否一致。如果手动算出来一模一样说明代码没问题问题在服务端拿到的参数和本地不一致比如timestamp被中间层修改了或者param_json在传输过程中被重新格式化导致键值顺序变化。第三如果手动算的结果也不一致那就要回头核对签名三兄弟排序、URL编码、大小写。一条一条试每次只改一个变量不要同时改三处。我还遇到过一种诡异情况本地签名计算完全正确但服务端一直报签名错误最后发现是HTTP请求把JSON参数里的加号变成了空格。param_json里如果携带了类似、这类特殊字符POST表单提交时没有做URL编码传输后服务端解码出来的字符串就和本地签名时的字符串不一致签名自然对不上。这个问题的解决办法是发送请求时对extraData里的每个参数值都做encodeURIComponent处理。4. 调用routerjson接口封装、解析与错误码对照签名算法搞定之后剩下的就是常规网络请求了。京东联盟开放平台的网关地址是https://api.jd.com/routerjson支持POST请求参数通过表单方式传递。4.1 ArkTS网络请求封装鸿蒙的kit.NetworkKit提供了http模块封装一个通用的请求函数并不难。下面是我在项目里用的版本做了简化但保留了核心流程import { http } from kit.NetworkKit; interface UnionApiResult { code: string; msg: string; data?: object; } async function requestUnionApi( method: string, paramJson: string, appKey: string, secret: string ): PromiseUnionApiResult { const params: Recordstring, string {}; params[method] method; params[app_key] appKey; params[timestamp] Math.floor(Date.now() / 1000).toString(); params[format] json; params[v] 1.0; params[sign_method] md5; params[param_json] paramJson; const sign await buildSign(params, secret); params[sign] sign; const httpRequest http.createHttp(); try { const body Object.keys(params) .map(key ${encodeURIComponent(key)}${encodeURIComponent(params[key])}) .join(); const resp await httpRequest.request(https://api.jd.com/routerjson, { method: http.RequestMethod.POST, header: { Content-Type: application/x-www-form-urlencoded }, extraData: body, expectDataType: http.HttpDataType.STRING, connectTimeout: 10000, readTimeout: 10000, }); if (resp.responseCode 200) { return JSON.parse(resp.result as string) as UnionApiResult; } throw new Error(HTTP error: ${resp.responseCode}); } finally { httpRequest.destroy(); } }注意一个细节httpRequest.destroy()要放在finally里确保请求完成后销毁会话避免连接泄漏。鸿蒙的http模块每次createHttp都会创建新的HTTP客户端如果频繁请求不销毁连接数会飙升导致后续请求排队超时。这个在低内存设备上尤其明显。另一个细节是时间戳。京东联盟要求timestamp是秒级不是毫秒级。如果直接用Date.now()得到的是13位毫秒数服务端解析后会发现时间对不上直接拒绝。我在测试时就被这个问题坑过返回的错误码是时间戳有效性问题排查了半天才发现是单位搞错了。4.2 业务参数组装示例商品查询有了通用请求函数具体业务调用就变成组装param_json的事情了。以商品查询接口为例const paramJson JSON.stringify({ goodsReqDTO: { keyword: 蓝牙耳机, pageIndex: 1, pageSize: 20, sortName: price, sortType: asc, }, }); const result await requestUnionApi( jd.union.open.goods.query, paramJson, appKey, secret );这里有一个容易忽略的问题param_json里字段名是区分大小写的必须严格按照京东联盟接口文档里的定义来写。比如goodsReqDTO、pageIndex、pageSize大小写错一个接口可能返回成功但数据为空或者直接报参数校验错误。另一个问题是JSON序列化后的键顺序。虽然从语义上讲JSON对象键的顺序无关紧要但因为param_json整体作为一个字符串参与了签名所以上下文中param_json的字符串内容必须和签名时完全一致。如果你在签名时用JSON.stringify生成一次在发送请求时又重新JSON.stringify了一次两次生成的字符串理论上是相同的相同对象稳定序列化但一旦中间对对象进行了修改、插入字段序列化结果就会改变签名立刻失效。所以建议的做法是先只组装一个对象用于签名签名完成后再发送同一个字符串不要中途使用不同的对象实例。4.3 响应解析与错误码对照京东联盟网关返回的JSON格式一般是这样的{ code: 0000, msg: 成功, data: { ... } }不同接口的成功码可能不同有些是0有些是0000所以不要写死判断条件务必以官方文档中该接口的说明为准。常见的错误码对照如下表错误码含义排查方向0000成功无需处理1001app_key无效检查应用的AppKey是否写错1003签名错误按签名三兄弟排查1004参数缺失检查param_json是否缺少必填字段1006无权限访问去开放平台申请该接口权限1013请求频率超限做本地缓存、降低调用频率这个表是我接入时实测过的通用版本具体数值可能会有调整遇到不认识的错误码优先去京东联盟开放平台的错误码文档里查不要凭经验去猜。5. 实测中踩过的坑认证失败、频控拦截与请求被重置代码跑通商品查询只是第一步真正磨人的是从demo到生产环境的这一段路。我们在这个阶段踩了不少坑有的花了几个小时才定位这里集中写出来给后来人省时间。5.1 坑一User-Agent被风控识别上线测试时发现HTTP请求偶尔会返回非法请求或者直接被重置连接但同一个参数在Postman里复现是正常的。一开始怀疑是签名问题反复核对没有变化后来抓包对比才发现默认UA里带着环境特征触发了网关风控。解决方案是在请求头里固定设置一个业务化的User-Agentheader: { Content-Type: application/x-www-form-urlencoded, User-Agent: MyUnionApp/1.0 (HarmonyOS; compatible) }这个做法不是伪造UA而是给网关一个明确的客户端身份标识。网关对完全没有UA的请求天然会更警惕有一个清晰的自定义UA反而有助于降低误拦概率。5.2 坑二timestamp与服务器时间偏差这个我在前面提到过一次但因为它太容易被忽略值得单独说。京东联盟网关对timestamp的宽容窗口一般在几分钟左右如果服务器时间和你本机时间偏差超过这个范围请求会被拒绝。问题在于有的用户手机时间不准或者时区设置混乱导致App生成的时间戳超出窗口。解决方法是不要在客户端拿本地时间而是通过一个简单的接口先获取服务器标准时间或者至少做一次服务端时间校准。我们上线后收到过几个海外用户的反馈排查下来全是这个原因。5.3 坑三HttpResponse返回被截断或编码异常鸿蒙的http模块在解析JSON时如果返回内容里带了BOM头使用JSON.parse会直接抛异常。我们当时遇到的症状是部分请求返回的字符串以\ufeff开头肉眼看不见但一parse就报错。解决办法是在parse之前对字符串做一次清理const raw resp.result as string; const cleaned raw.replace(/^\uFEFF/, ); const json JSON.parse(cleaned) as UnionApiResult;这算是一个边缘情况但碰到一次就能折腾半天写在这里供参考。5.4 坑四接口频控京东联盟的接口都有调用频率上限不同接口的阈值不同。商品查询这类高频率接口如果用户每次进入页面都实时请求很容易打满频控返回1013。我们的策略是在业务层加了两层缓存第一层是内存缓存缓存时间5分钟第二层是持久化缓存缓存时间30分钟。商品信息本身不是强实时数据稍微有点延迟完全不影响用户体验但接口被限流导致的查询失败体验影响就大多了。6. 归因与验收确认鸿蒙端的推广订单能正常结算接口通了、数据能取了但接入京东联盟的最终目的是拿到佣金。归因链路如果不通前面所有工作都白做。这一部分讲清楚鸿蒙端怎么把用户、推广链接和订单串起来。6.1 推广链接与推广位ID的传递通过京东联盟的链接生成接口可以拿到商品或活动对应的推广链接这个链接里通常包含推广位信息。在鸿蒙App里我们一般不会直接把原链接丢给用户而是通过接口返回的短链接或跳转参数在应用内完成中转。这里要注意推广位ID必须是你自己的并且要和App绑定。如果链接生成时没带推广位或者带成了别人的推广位订单会归到别的账号名下佣金跟你无关。我们上线前专门写了一个自检脚本批量生成链接后检查其中的推广位参数是否与预期一致。6.2 跳转京东App与Web兜底用户在鸿蒙App里看到商品点击去购买通常期望直接唤起京东App。HarmonyOS上可以通过scheme方式尝试拉起京东客户端如果检测到未安装则回退到WebView打开H5页面。检测应用是否安装鸿蒙端可以通过系统能力查询应用信息。这个方法在不同版本的系统上略有差异建议封装一个工具函数把异常捕获住。这样即使查询失败也能直接走Web兜底不会卡死用户流程。跳转链接的传递要注意一个问题很多京东推广链接是带参数的URL直接传给openUrl时如果参数里包含#、?等特殊字符可能会被截断。最好先用encodeURI编码后再跳转到了京东那边再解码。6.3 隐私合规与后台结算核对从合规角度说应用集成了京东联盟能力需要在隐私政策中如实披露第三方合作方信息包括京东联盟的名称、数据用途如设备信息用于风险识别、订单用于结算等。鸿蒙应用市场审核时对隐私政策的要求非常高如果你漏掉了第三方披露轻则审核打回重则下架整改。结算核对方面京东联盟后台有订单明细报表但订单状态从下单到完成可能需要几天甚至更长时间。我们的做法是每天凌晨跑一次对账任务把当天产生的订单ID和京东联盟后台的报表做比对重点检查是否存在未归因的订单——这类订单大概率是推广位ID没传对或者用户在跳转过程中丢失了来源参数。发现未归因订单后也不要急着改代码。先看用户的操作路径是从哪个页面出去的、跳转时URL带了什么参数在服务端日志里反查当时生成的推广链接。我遇到过一种情况用户通过鸿蒙App看到商品复制链接到浏览器购买浏览器里登录的京东账号和设备上的账号不是同一个导致归因失败。这种属于用户行为边界问题不是代码bug但通过日志分析能把原因定位清楚。最后再分享一个小技巧上线第一周不要急着大量投放先让核心用户真实走几单从商品曝光、链接点击、京东App唤起、下单支付到最后后台出现可结算订单完整跑通一两次。只要这条链路通了后面再加大流量都没问题。我见过太多团队上线后才发现归因断了一环佣金大量流失回过头来排查链路的成本比一开始多花两天验证高得多。
返回列表