分享微信小程序到QQ
1. 互联通用通道
第三方应用的诉求是拉起手q,把json数据带给手q内的业务,互联在此过程中负责校验数据安全和来源,把数据透传到手q,根据serviceId把数据分发给对应的业务去处理。
第三方应用需要确定serviceId和json数据相关的字段,调用互联SDK的接口把数据发送给手q。
1.1. 版本要求:
第三方应用首先需要接入互联SDK,互联SDK和QQ的版本要求:
平台 | SDK最低版本 | 手q最低版本 |
安卓 | 3.5.11 | 9.2.5 |
iOS | 3.5.10 | 9.2.5 |
手Q版本不满足要求时(见分享结果回调),可以通过以下方式分享或取消分享:
1.2. 白名单配置
1.2.1. (iOS)Universal Links白名单
2. 调用流程
安卓和iOS接口均要求两个参数
- ● 用于区分业务的数字(以下称为serviceID,固定填3)
- ● 业务使用到的json格式数据(以下称为extraInfo)
构造出extraInfo后调用互联sdk的通用通道接口即可发送消息到手Q
出于安全考虑,分享消息的调用要求应用签名,所以需要先根据业务数据生成shareJson,再签名后构造extraInfo
2.1. 根据分享内容生成shareJson
由于shareJson需要整体签名,为避免传输过程中json字段顺序改变、空白符删增等本应不影响json内容的格式改变导致签名结果不一致,需要生成shareJson后将其转为字符串,后续都以字符串形式传递。
shareJson字段定义:
字段名 | 类型与含义 | 是否必填 |
msg_style | 数字,固定填12 | 必填 |
title | 字符串,分享模板消息的标题 | 必填 |
description | 字符串,分享模板消息的描述 | 必填 |
picture_url | 字符串,分享小程序缩略图URL | 必填 |
url | 字符串,兼容低版本的网页链接 | 必填 |
mini_program_path | 字符串,小程序展示路径 | 必填 |
mini_program_appid | 字符串,小程序appid | 必填 |
mini_program_type | 数字,小程序类型,正式版填3,测试版填1 | 必填 |
2.2. 使用appkey对shareJson签名,得到shareJsonSign
将转为字符串的shareJson与当前时间戳、随机数字和appkey整体签名,base64编码后得到签名信息。
base64应当直接作用于签名得到的byte数组上,而不是byte数组先转为16进制表示的字符串、再转成的byte数组上。
签名方案:QQ 互联分享签名分享方案
golang签名例程:
package main
import (
"crypto/hmac"
"crypto/sha1"
"encoding/base64"
"fmt"
)
func main() {
appkey := // appkey
appid := // appid
nonce := // nonce
now := // time
bizJson := // 分享内容json
srcStr := fmt.Sprintf(
"POSTconnect.qq.com/share?appid=%s&nonce=%s&ts=%d&%s",
appid,
nonce,
now,
bizJson,
)
// sha1
hmacObj := hmac.New(sha1.New, []byte(appkey))
hmacObj.Write([]byte(srcStr))
// base64
sign := base64.StdEncoding.EncodeToString(hmacObj.Sum(nil))
fmt.Printf("nonce: %s, timestamp: %d, shareJsonSign: %s", nonce, now, sign)
}
签名过程需要放在后台以避免appkey泄露
2.3. 构造extraInfo
整体数据:
字段名 | 类型与含义 | 是否必填 |
shareJson | 字符串,分享信息json转为字符串的结果 | 必填 |
shareJsonSign | 字符串,上一步得到的shareJson签名 | 必填 |
nonce | uint32范围内的正整数,上一步使用的不重复的随机数字 | 必填 |
timestamp | 正整数,上一步使用的签名时间戳;24小时内有效 | 必填 |
最终json示例:
{
"shareJson": "{\"msg_style\":12,\"title\":\"跳一跳\",\"summary\":\"一起来,跳一跳\",\"picture_url\":\"https://mmbiz.qpic.cn/mmbiz_png/icTdbqWNOwNTTiaKet81gQJHS4AOib6MiaPhwEaqw1NPcZGtgAGTlVJ4lrBAGtchhnXanMyo7q7toRpD4DukV5F2TA/0?wx_fmt=png\",\"url\":\"www.qq.com\",\"mini_program_path\":\"\",\"mini_program_appid\":\"wx0fe996f1d2e8b691\",\"mini_program_type\":3}",
"shareJsonSign": "aaaaaaaaaaaaa=",
"nonce": 123,
"timestamp": 1752636548
}
2.4. 调用互联sdk接口
2.4.1. 安卓
// 小程序分享到qq好友
Bundle bundle = new Bundle();
bundle.putInt("src_id", 3);
// 第二部分:业务相关数据(json格式)
JSONObject jsonObject = new JSONObject();
try {
JSONObject shareJson = new JSONObject();
shareJson.put("title", "");
shareJson.put("msg_style", "");
shareJson.put("description", "");
shareJson.put("picture_url", "");
shareJson.put("url", "");
shareJson.put("mini_program_path", "");
shareJson.put("mini_program_appid", "");
shareJson.put("mini_program_type", "");
String shareJsonStr = shareJson.toString();
// 对分享内容json字符串签名
int nonce = getNonce();
int timestamp = getTimeStamp();
String jsonSign = getSign(shareJsonStr, nonce, timestamp);
jsonObject.put("shareJson", shareJsonStr); // 分享内容json字符串
jsonObject.put("shareJsonSign", jsonSign); // 签名
jsonObject.put("nonce", nonce); // 整数随机数
jsonObject.put("timestamp", timestamp); // 时间戳(秒)
} catch (JSONException e) {
e.printStackTrace();
}
bundle.putString("opensdk_ext_data", jsonObject.toString());
// step-2: 调用接口启动QQ.
mTencent.callCommonChannelApi(MainActivity.this, bundle, commonChannelApiListener);
2.4.2. iOS
#import "TencentOpenAPI/QQApiInterface.h"
NSDictionary *extendInfo = [NSDictionary dictionaryWithDictionary:@{@"name": @"zhangsan", @"age": @"18"}]
QQApiCommonServiceObject *object = [[QQApiCommonServiceObject alloc] initWithOpenID:openId serviceID:serviceId extendInfo:extendInfo];
SendMessageToQQReq *req = [SendMessageToQQReq reqWithContent:object];
QQApiSendResultCode sent = [QQApiInterface sendReq:req];
[self handleSendResult:sent];
QQApiCommonServiceObject字段说明:
字段名 | 类型 | 简介 | 是否必填 |
serviceID | NSString 字符串 | 服务id,本需求填3。 | 必填 |
extraInfo | NSDictionary 字典 | 可扩展参数 | 必填 |
2.5. 处理分享结果回调
2.5.1. 安卓回调结果
需要在调用接口的Activity的onActivityResult() 中判断 requestCode 为Constants.REQUEST_COMMON_CHANNEL 时调用以下代码处理结果
Tencent.onActivityResultData(requestCode, resultCode, data, commonChannelApiListener);
由于SDK设计,只能向第三方返回分享成功与分享取消两种结果,错误原因需要以message区分。分享错误时,不建议将message直接提示给用户。
注意:当版本不支持时返回结果为“分享成功”,但是不会实际分享出消息;只有当结果为分享成功且错误信息为空字符串时才是实际上的分享成功
场景 | 返回结果 | 错误信息 |
分享成功 | 分享成功 | "" |
手Q版本不支持 | 分享成功 | "当前版本不支持" |
接口鉴权超时 | 分享取消 | "请求超时,请稍后再试。" |
接口鉴权失败 | 分享取消 | "errorCode = $errorCode" |
请求参数不正确 | 分享取消 | "参数错误" |
用户在手Q内取消分享 | 分享取消 | "onCancel" |
2.5.2. iOS回调结果
在AppDelegate中的回调方法中调用互联的方法:
Scheme调起的系统回调处理:
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation;
- (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url;
Universal Link调起的系统回调处理:
- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void(^)(NSArray<id<UIUserActivityRestoring>> * __nullable restorableObjects))restorationHandler NS_AVAILABLE_IOS(8_0);
在上面介绍的回调中,调用QQApiInterface类的以下方法:
/**
处理由手Q唤起的普通跳转请求
\param url 待处理的url跳转请求,直接传入系统回调的URL
\param delegate 第三方应用用于处理来至QQ请求及响应的委托对象
\return 跳转请求处理结果,YES表示成功处理,NO表示不支持的请求协议或处理失败
*/
+ (BOOL)handleOpenURL:(NSURL *)url delegate:(id<QQApiInterfaceDelegate>)delegate;
在delegate中接收回调结果
最终互联会调用代理的以下方法:
/**
处理来至QQ的响应
*/
- (void)onResp:(QQBaseResp *)resp;
字段 | 说明 |
rthCode | 返回错误码(success==0) |
rthMsg | 返回错误信息 |
3. FAQ
- 1. 消息能分享出去,但是点击时提示小程序内测中
正式版小程序mini_program_type应填3
- 2. 提示签名检查不通过
排查以下问题:
a. 未申请appkey/签名使用了不正确的appkey
b. shareJson未转为字符串,直接以json格式传入了extraInfo中
- 3. 分享消息时选中聊天后弹出的的预览dialog看不到消息
排查shareJson是否有参数缺失