☰
鸿蒙App接入京东联盟:API直连与签名算法实战指南
2026/9/26 5:23:16 网站建设 项目流程

上个月,我们的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_grant",INTERNET属于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,中间不插分隔符。也有个别接口要求key=value1&key2=value2的形式,不同平台差异很大,必须以官方文档的签名字节为准。

第四步,在拼接串的首尾分别加上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): Promise<string> { 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: Record<string, string>, secret: string ): Promise<string> { 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 ): Promise<UnionApiResult> { const params: Record<string, 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-Agent:

header: { '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唤起、下单支付到最后后台出现可结算订单,完整跑通一两次。只要这条链路通了,后面再加大流量都没问题。我见过太多团队上线后才发现归因断了一环,佣金大量流失,回过头来排查链路的成本比一开始多花两天验证高得多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询