分享业务规范

[toc]

分享业务规范

一、分享的链接/口令值获取时机:不适合提前获取再拼接而成,而是需要时候再请求接口

  • 推荐:需要请求接口。(目的是为了省得分享时候再请求)

  • 不推荐:提前给值,然后自己去拼接。(即启动后的某个时刻,请求全局分享配置信息,后续分享的时候使用该信息拼接。)

    好处:分享时候,完全不需要前端逻辑,而是完全请求接口,且不用去担心接口内部读库的频次操作。

    不推荐的原因/方案缺点:

    ①不利于后期维护。如要在大部分(但不是全部)分享链接中添加之前遗漏的分享者uid。

    ②特殊场景,无法达到省请求的目的。如当最终分享链接还要经过后台(如动态短链)时,原本希望不用再请求的最后分享值还是得再请求。

    不推荐的原因/方案缺点举例说明如下:

    可能形式①:固定的域名链接 + “固定属性参数(常为分享人uid)”+ 其他属性参数

    示例:http://www.company.com/app/shareGoods?uid=123&objectType=goodsDetail&objectId=goodsId

    可能形式②:动态短链(背景:以防被误封)

    示例:https://my5353.com/no3Er

二、分享请求接口的参数与返回值结构规范

1、结构规范

请求接口参数与返回值的示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// request
{
"shareScene": "shareGoodsInGoodsDetail", // 分享对象的类型及场景(能知道分享的具体是什么种类的对象的id、可能在多个地方都有分享商品)
"objectId": goodsId, // 分享对象的id
"senderId": userId, // 分享来源人的唯一标识符userId(📢不要💊使用token,因为token会过期)(有时候业务需要名字、头像等其他值,可通过id后台自己获取)
"requiredTyps": ["WebPageUrl", "PasswordText"], // 需要给什么值,默认都给。( WebPageUrl:分享链接(包括短链)、PasswordText:口令)
"shareInfoInCellParams": { // 可选参数。要显示在微信聊天页面的UI信息,不提供时候,后台就得通过传入的objectType和objectId获取了
"title": 标题,
"description": 描述,
"thumbnailUrl": 缩略图,
}
}

// response
{
"shareInfoInCellParams": { // 要显示在微信聊天页面的UI信息
"title": 标题,
"description": 描述,
"thumbnailUrl": 缩略图,
},
"shareWebPageUrl": "https://wwww.baidu.com?a=123&b=456", // 点击消息要打开的网页链接,📢这里有时候是短链
"shareWebPageMinimumUI": { // 保底UI:用于避免shareWebPageUrl网页能访问但界面布局未实现时候而一片空白,导致无法进行点击打开app的闭环操作
"title": 标题(一般用显示在微信聊天页面的UI信息里的标题),
"bgImageUrl": 背景图(一般用显示在微信聊天页面的UI信息里的图片),
"buttonText": 按钮文案(一般为打开app)
},
"sharePosterQrCodeUrl": "https://wwww.baidu.com?a=123&b=456", // 海报二维码的链接
"sharePasswordText": "{appName}{shareWebPageUrl} {randomString} {ui_title}\n{ui_shareFlag}" //"【淘宝】https://m.tb.cn/h.Us94Snr?tk=VFCpd9FVHPy CZ0001 「花花公子官网正品手表男士全自动机械表十大品牌防水男款学生腕表」\n点击链接直接打开 或者 淘宝搜索直接打开" //分享的口令火星文等,有些口令甚至内部还包含分享链接
}

代码可点击查看: 分享流程代码示例

2、request 结构可选入参是否从前端直接提供的衡量依据

分享时候,建议从前端直接提供的参数,以分享商品为例

可选参数 描述 是否从前端取 原因
goodsTitle 商品名称 建议 直接从前端取,可省去接口通过id去内部获取各种数据
goodsImageUrl 缩略图 建议 直接从前端取,可省去接口通过id去内部获取各种数据
goodsPrice 商品加个 不建议 价格不稳定,从前端取容易过时,即重要且动态的都不应从前端取

三、复制分享的口令回APP

1、复制分享的口令回APP的流程图

share_watchword_flow1

图片来源:《分享相关(含微信、粘贴板).graffle.graffle)》中的【粘贴板】

附1、口令介绍

实际口令举例:

1
2
3
{
"sharePasswordTextFormatter": "【淘宝】https://m.tb.cn/h.Us94Snr?tk=VFCpd9FVHPy CZ0001 「花花公子官网正品手表男士全自动机械表十大品牌防水男款学生腕表」\n点击链接直接打开 或者 淘宝搜索直接打开",
}

理论口令格式(文案为火星文等,且有些口令有内部包含分享链接的情况):

1
2
3
{ 
"sharePasswordTextFormatter": "{appName}{shareWebPageUrl} {randomString} {ui_title}\n{ui_shareFlag}",
}

实际口令格式(文案为火星文等,且有些口令有内部包含分享链接的情况):

1
2
3
{ 
"sharePasswordTextFormatter":"【淘宝】{shareWebPageUrl} {randomString} {ui_title}\n点击链接直接打开 或者 淘宝搜索直接打开",
}

2、查找哪些SDK使用了粘贴板

详见:查找哪些SDK使用了粘贴板

四、微信内网页跳转 APP 功能

微信官方文档:微信内网页跳转 APP 功能

五、未上架应用的微信每日分享100次限制及分享域名被屏蔽的解决方案

100次限制规定见官方文档:分享与收藏功能

官方对分享的其他问题,请看: 分享与收藏接口

其他解决方案的相关搜索文档:

一个不建议用的方案(未落地过):《分享相关(含微信、粘贴板).graffle.graffle)》中的【未上架应用的微信每日分享100次限制及分享域名被屏蔽的解决方案】

对于iOS开发中未上架应用的微信分享限制问题,微信平台规定若移动应用未上架,则每天的分享量受到限制为100次,这包括分享到会话和朋友圈,主要用于满足调试需求 。针对分享域名被屏蔽的问题,可以采取以下几种解决方案:

  1. 域名备案:确保分享链接的域名已经完成备案,并且加入微信白名单,避免因备案问题导致域名被封 。
  2. 使用中转页跳转:设置中转页进行跳转,同时去除来源,保证即使中转页面被封,流量不会流失 。
  3. 分散域名访问:通过分散和切换域名来降低单一域名的曝光率,减少被封风险 。
  4. 域名监控系统:使用域名监控系统自动检测域名是否被封,避免使用有不良记录的域名 。

请注意,这些方法可以在一定程度上缓解域名被封的情况,但无法完全避免。开发者应持续关注微信的相关政策更新,以确保分享活动符合规定 。

附1、分享流程代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class ShareUtil {
// 1、弹出分享弹窗
static showGoodsDetailShareView({
required String h5Title,
required String goodsID,
required String goodsName,
required String goodsThumbnailUrl,
WeChatScene? scene,
}) async {
Map<String, dynamic>? params = {
"h5Title": h5Title,
};
ShareWebUrlModel shareUrlModel = await _requestShareUrl(objectType: "GoodsDetail",
objectId: "124",
params: params,
requiredTyps: ["WebPageUrl"]); // "PasswordText"

}

附:分享网页的代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
class ShareInfoRequestAndSendUtil {
// 1、从服务端请求获取分享信息 shareUrlModel
static Future<ShareWebUrlModel> requestShareUrl({
required String objectType,
required String objectId,
Map<String, dynamic>? params
}) async {
params ??= {};
params.addAll({
"objectType": objectType,
"objectId": objectId,
});

return AppNetworkUtil.post(Url, params).then((ResponseModel responseModel) {
....
ShareWebUrlModel shareWebUrlModel = xxxx;
return shareWebUrlModel;
});
}

// 2、分享从服务端得到的分享信息 shareUrlModel
static shareWebPage({
required ShareWebUrlModel shareUrlModel,
WeChatScene? scene,
}) async {
ShareUtil.shareWebPageUrl(
shareUrlModel.shareWebPageUrl, {
title: shareUrlModel.shareInfoInCellParams.title
description: shareUrlModel.shareInfoInCellParams.description,
thumbnailUrl: shareUrlModel.shareInfoInCellParams.thumbnailUrl,
shareTo: scene?.toString() ?? "WeChatScene.SESSION",
});
}
}

class ShareUtil {
// 3、分享任意分享信息
static shareWebPageUrl({
required String webPage,
String shareTitle = "",
String? shareDescription,
String shareThumbnailUrl = '',
String shareTo = "WeChatScene.SESSION",
}) async {
var isInstalled = await isWeChatInstalled;
if (!isInstalled) {
ToastUtil.showMessage("未安装微信");
return;
}
var shortUrl = await _getShortUrl(webPage);

WeChatScene scene = WeChatScene.SESSION;
if (shareTo == "WeChatScene.TIMELINE") {
scene = WeChatScene.TIMELINE;
} else if (shareTo == "WeChatScene.FAVORITE") {
scene = WeChatScene.FAVORITE;
} else {
scene = WeChatScene.SESSION;
}

var model = WeChatShareWebPageModel(
shortUrl,
title: shareTitle,
description: shareDescription,
thumbnail: WeChatImage.network(shareThumbnailUrl),
scene: scene,
);
shareToWeChat(model);
}
}

附2(不用看):分享时候,完全不需要请求接口,而是完全由前端拼接出完整分享的返回结构值

1.1、本地入参1:启动/前后台切换得到的全局Gobal信息中的分享配置模型 objectShareConfig

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Map<String, dynamic> objectShareConfig = {
// 要显示在微信聊天页面的不会随objectId值变的固定UI信息(有些场景objectId值变了,它们也不变,且可由后台配置)
"shareInfoUIFixedParams": {
"title": "不随objectId值变的标题(eg:快来XXX app ,和我一起玩)",
"description": "不随objectId值变的描述(eg:点击链接,就能我和一起玩啦)",
"thumbnailUrl": "不随objectId值变的缩略图地址(eg:游戏缩略图)",
},
// 点击分享消息要打开的h5地址的公共路径path部分(有其他参数拼接在此path后)(此值不一定是真实固定值,可为动态用于防封)
"shareWebPageUrlFixedPath": "点击分享消息要打开的h5地址的[固定]公共路径path部分(有其他参数拼接在此path后)",
// 点击分享消息要打开的h5地址的公共参数params部分(用于拼接在公共路径path后,来作为组成完整分享路径的一部分)
"shareWebPageUrlFixedParams": {
"objectIdKey/objectType": "分享对象使用的id/分享对象的类型,因为需要知道分享的是什么种类(分享商品详情:goodsDetail、分享订单详情:orderDetail、推荐名片:recommendUser、邀请好友:inviteCode等),同时为了解耦,省去前端和后台要硬编码匹配这个值"
},
// 不随object变的保底UI:用于避免shareWebPageUrl网页能访问但界面布局未实现时候而一片空白,导致无法进行点击打开app的闭环操作
"shareWebPageFixedMinimumUI": {
"title": "一般等同于 shareInfoUI 中的 title",
"bgImageUrl": "一般等同于 shareInfoUI 中的 thumbnailUrl",
"buttonText": "打开app"
},

// 口令分享格式
"sharePasswordTextFormatter":"【淘宝】{shareWebPageUrl} {randomString} {ui_title}\n点击链接直接打开 或者 淘宝搜索直接打开"
}
};

1.2、本地入参2:为具体object选择分享时候,提供的入参模型 dynamicShareParams

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Map<String, dynamic> dynamicShareParams = {
// 分享到哪
"shareTo": "微信/朋友圈等",
// 要显示在微信聊天页面的会随objectId值变的动态UI信息(有些场景objectId值变标题就得变,eg:商品名)
"shareInfoUIDynamicParams": {
"title": "要自定义的标题",
"description": "要自定义的描述",
"thumbnailUrl": "要自定义的缩略图地址",
},
// 点击分享消息要打开的h5地址的非公共参数部分(用于拼接在公共部分path后,来作为完整分享路径的一部分)
"shareWebPageUrlDynamicParams": {
"objectId": "分享对象的id值(分享商品goodsId值、分享订单orderId值、推荐名片userId值、邀请好友inviteCode值)",
"senderId": "分享人的用户id(以用来确定分享是谁分享的,邀请码的时候一定会用,其他需求也有要显示分享人的可能)"
},
// 会随object变的保底UI:用于避免shareWebPageUrl网页能访问但界面布局未实现时候而一片空白,导致无法进行点击打开app的闭环操作
"shareWebPageDynamicMinimumUI": {
"title": "一般等同于 shareInfoUI 中的 title",
"bgImageUrl": "一般等同于 shareInfoUI 中的 thumbnailUrl",
"buttonText": "打开app"
}
};

1.3、计算得到最终分享信息的结构模型(=本地入参1+本地入参2)

1.3.1、计算过程如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
"shareTo": dynamicShareParams["shareTo"], // 分享到哪

Map<String, dynamic> lastShareParams = {
// 要显示在微信聊天页面的UI信息(有些场景objectId值变标题就得变,eg:商品名;而有些场景objectId值变标题也不用变,eg:这个游戏很好玩,推荐给你)
"shareInfoUIParams": {
"title": ui_title,
"description": dynamicShareParams["shareInfoUIDynamicParams"]["description"] ?? objectShareConfig["shareInfoUIFixedParams"]["description"],
"thumbnailUrl": dynamicShareParams["shareInfoUIDynamicParams"]["thumbnailUrl"] ?? objectShareConfig["shareInfoUIFixedParams"]["thumbnailUrl"],
},
// 点击分享消息要打开的h5地址
"shareWebPageUrl": shareWebPageUrl,
// 保底UI:用于避免shareWebPageUrl网页能访问但界面布局未实现时候而一片空白,导致无法进行点击打开app的闭环操作
"shareWebPageMinimumUI": {
"title": dynamicShareParams["shareWebPageDynamicMinimumUI"]["title"] ?? objectShareConfig["shareWebPageFixedMinimumUI"]["title"],
"bgImageUrl": dynamicShareParams["shareWebPageDynamicMinimumUI"]["bgImageUrl"] ?? objectShareConfig["shareWebPageFixedMinimumUI"]["bgImageUrl"],
"buttonText": dynamicShareParams["shareWebPageDynamicMinimumUI"]["buttonText"] ?? objectShareConfig["shareWebPageFixedMinimumUI"]["buttonText"]
},
// 口令
"sharePasswordText": "【淘宝】${shareWebPageUrl} ${randomString} ${ui_title}\n点击链接直接打开 或者 淘宝搜索直接打开",
};
附,上述各种分享都必备的值,放于临时变量供取及加工,示例如下:
1
2
3
4
5
6
7
8
// 各种分享都必备的值
// 各种分享都必备的值-标题(口令中也需要)
String ui_title = dynamicShareParams["shareInfoUIDynamicParams"]["title"] ?? objectShareConfig["shareInfoUIFixedParams"]["title"];
// 各种分享都必备的值-要打开的分享链接(口令中也需要)
String shareWebPageUrl = objectShareConfig["shareWebPageUrlFixedPath"] +
dynamicShareParams["shareWebPageUrlDynamicParams"]["objectId"] +
dynamicShareParams["shareWebPageUrlDynamicParams"]["senderId"] +
objectShareConfig["shareWebPageUrlFixedParams"]["objectType"];
口令分享值的获取过程如下:
1
2
3
// 口令分享需要的值;
String randomString = "CZ0001";
String sharePasswordText ="【淘宝】{shareWebPageUrl} {randomString} {ui_title}\n点击链接直接打开 或者 淘宝搜索直接打开";

结束语