第1节:h5js

[toc]

测试app中的h5调用app的网页

一、测试h5调用app的网页

1、测试h5调用app的网页1(硬编码,不推荐使用)

网页地址及内容:请点击test_h5js_demo.html查看

支持的功能:

  • 点击对应按钮,让h5调用app提供的方法,并传递对应的参数。

2、测试h5调用app的网页2(自动编码,推荐使用,能够根据json文件自动化布局)

网页地址及内容:请点击dvlp_h5js_demo.html查看。

demo使用的json数据dvlp_h5js_demo.json

新增支持内容如下:根据json数据,自动布局html内容

  • [x] 1、方法名、参数、参数说明,输入框化,以支持任意桥接方法

    • 字符串转方法
  • [x] 2、输入框内容,json化

    • json数据,先内嵌在html中。因为先只支持一个,所以直接更新到文本框中(内容:方法名、参数、参数说明)
  • [x] 3、输入框内容,支持从url中获取单个示例的数据(因为只有一个,所以直接通过dataJson传入输入框中的各个参数)

    • [x] 网页副标题增加数据来源标识,以区分数据是html本身内嵌的数据,还是从url中获取

    • [x] 因为参数是从url中获取的,所以需要考虑编码问题。(虽然放在系统浏览器中可以通过)

      • 参数值支持非中文的字符串,可以不用编码
      • 参数值支持中文,需编码
      • 参数值支持json对象,需编码
    • 直接将从url中获取到的参数值,更新到文本框中,以可直接执行
    • app中将输入框中的各个参数组成json,提供给h5。提供方式为将各参数添加到url中(每个url的参数,都需要先编码,否则当参数中含中文,或者是非字符串对象的时候,解析的时候会有问题,虽然放在系统浏览器中可以通过)
    • app中提供json数据,并列表化。app选中对应项,将所选项对应的json数据传递给url的参数。
    • json数据支持section形式,以区分不同类型的示例。
  • [x] 4、输入框内容,通过之前支持的json,提供各种demo数据功能

    • json数据,先内嵌在html中。根据json数据,展示可选操作。
    • 选中任意可选操作,更新文本框内容:方法名、参数、参数说明
  • [x] 5、输入框内容,支持从url中获取多个示例的数据(因为要支持多个,所以是通过fileUrl传入json文件路径)

    • [x] 网页副标题增加数据来源标识,以区分数据是html本身内嵌的数据,还是从url中获取。如果是url中获取的,那来源是单个示例使用的dataJson各参数,还是多个示例使用的fileUrl参数。

    • [x] 根据json数据,展示示例列表。(若只有一个数据,不显示列表,直接使用;若有多个,默认将第一个更新到文本框中)

  • 6、网页html支持跳转到demo示例
  • 7、增加h5调用app后,app返回给h5的回传值显示及示例。

风控相关

风控相关

前言

1、相关文档

问1:同盾和数美的风控为什么返回的是各个不同的加密blackBox,而不是其解密后都对应到的统一deviceId?

答:已知:同一设备同一用户账号,7月1号使用时候,app取到风控sdk返回的设备指纹1;7月2号使用时候,app取到风控sdk返回的设备指纹2;

作用1:由于后台请求都是使用设备指纹,如果设备指纹1和设备指纹2是一样的,则会导致某个指纹过期了,却一直在用。其实可以简单理解为该设备指纹使用blackBox,等价于app中的accesstoken

一、属性列表

deviceLabels

序号 描述 属性名
1 PC模拟器 fake_device.b_pc_emulator
2 adb开启 device_suspicious_labels.b_adb_enable

二、属性介绍

1、PC模拟器:b_pc_emulator

image-20230524134912885

2、adb开启 b_adb_enable

image-20230524134833287

device_suspicious_labels

image-20230524134739399

相关文档

以下来自三方参考

image-20230526170924306

image-20230526171036378

image-20230526171756442

image-20230526172239451

image-20230526174526595

image-20230526174620225

打包

[toc]

安卓打包

1
2
3
4
5
6
7
8
9
10
11
12
13
# 查看是否已安装apktool
apktool --version

# 1.1未安装的话,即出现
`zsh: command not found: apktool`
# 则执行以下命令,进行安装
brew install apktool

# 1.2未正确安装,即出现
`/usr/local/bin/apktool: line 3: /Users/qian/@@HOMEBREW_JAVA@@/bin/java: No such file or directory`
`/usr/local/bin/apktool: line 3: exec: /Users/qian/@@HOMEBREW_JAVA@@/bin/java: cannot execute: No such file or directory`
# 则执行以下命令,进行安装
HOMEBREW_BOTTLE_DOMAIN= brew reinstall kafka

反编译apk

执行命令

1
2
# 解压
apktool d -f $OriginPath -o $ChannelHome/origin_apk
  • ddecode的简写
  • -f/--force:强制删除目标文件夹,即会先清空$ChannelHome/origin_apk,然后再执行decode操作
  • $OriginPath是当前要反编译的apk
  • -o--output的简写,代表输出路径

结束语

发布业务规范

[toc]

发布业务规范

一、流程

1、方案

参考:

问题:为了优化上传耗时,我们将上传所需的图片压缩提前到用户选择完图片之后。那么当点击上传的时候,需要做哪些处理?

答:1、需要先判断之前的压缩任务是否

publish_main_flowchart

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
class BaseAssetModel {
AssetEntity? assetEntity;
}


mixin AssetEntityCompressProtocol {
/// 🚗: 要压缩的文件 assetEntity 是一个公共的属性,其他功能也需要使用到。
/// 🚗: 所以此类为了能够使用 assetEntity ,将在使用时【通过方法的参数传入】,以避免和其他功能冲突重复定义该属性,
/// 🚗: 并在使用后额外将其赋值给 compressAssetEntity 变量,方便主控制器通过判断该值非空来得出有在处理本功能业务。
AssetEntity? compressAssetEntity;
// 1、选择完图片的时候就处理,避免进行上传的时候才去压缩导致时长增加
Future<void> checkAndBeginCompress(AssetEntity assetEntity) async {
compressAssetEntity = assetEntity;
.......
}

// 2、选择完显示时候调用,用于获取要显示在列表上的图片
ImageProvider? get compressedImageOrVideoThumbnailProvider {
......
}

// 3、点击发布的时候(进行上传文件、创建内容单)
// 获取最后要上传的图片文件(会自动等待前面的压缩结束)
Future<String?> lastUploadImagePath() async {
_log("image choose bean hashCode = $hashCode");
// 草稿里的图片已有压缩数据
if (compressInfoProcess == CompressInfoProcess.finishCompress) {
File resultFile = imageCompressResponseBean?.reslut;
return resultFile.path;
}

await _compressCompleter.future; // 优化压缩流程,上传时候未完成压缩会自动等待,并在完成压缩后,自动继续
File resultFile = imageCompressResponseBean?.reslut;
return resultFile.path;
}

结束语

分享业务规范

[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点击链接直接打开 或者 淘宝搜索直接打开";

结束语