微信外网打开小程序(URL Scheme)
规则:
-
有效期最长 30 天;
-
链接生成后,每个独立的链接被用户访问后,仅此用户可以再次访问并打开对应小程序,其他用户无法再次通过相同链接打开该小程序;
-
单个小程序每天生成链接数(URL Scheme 和 URL Link 总数)上限为 50 万条。
微信文档:https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/url-scheme.html
业务端创建的单个链接,多个C端用户得到的都是一个同一个链接
B端流程:
员工/业务 - 点击创建(业务请求链接创建接口)- cc_link表生成一条记录
/** * 创建(这里的创建,是你自己的短链接域名) * @param array $params * @return array * @throws UserException * @throws \yii\base\InvalidConfigException */ public function create(array $params) { // 控制用户当前这一次的请求 $mutex = new Mutex(); $key = 'api_create_link:' . md5(json_encode($params)); if (!$mutex->acquire($key, 5)) throw new UserException("请求太频繁啦,请稍后再操作!"); //写入数据库 $transaction = Link::getDb()->beginTransaction(); try { $form = new LinkForm(); $form->setScenario(LinkForm::SCENARIO_CREATE); $form->load($params, ''); if (!$form->validate() || !$form->save()) { throw new UserException(current($form->getFirstErrors())); } //得到小程序的path,query访问(ps:这里可以在linkform中完,这里只是重点拿出来) list($path, $query) = $this->getSchemeUrl($form->toArray()); //修改数据库 $form->path = $path; $form->query = $query; if (!$form->save()) throw new UserException(current($form->getFirstErrors())); $transaction->commit(); } catch (\Exception $e) { $mutex->release($key); $transaction->rollBack(); throw new UserException($e->getMessage()); }$mutex->release($key); $short_link = $this->getShortLink($form->id);//返回短链接提供调用创建接口后使用 return ['id' => $form->id, 'short_link' => $short_link]; }
{ "code": 200, "msg": "ok", "data": { "id": 1, "short_link": "https://xxx/123", //链接 123是 getShortLink方法按照一定规则生成的 "expire_time": 1627458310 } }
C端访问流程:
/** * 获取详情 * @param $link_id * @param $storage * @param array $link */ public function getInfoByLinkIdStorage($tag='', $storage) { //根据tag查询link详情 $link_id = (int)BaseHelper::from62to10($tag); if (empty($id)) return ['scheme_type' => -1]; $linkInfo = $this->view($link_id); $key = LinkScheme::tableName() . $link_id . '_' . $storage; if ($cache = \Yii::$app->cache->get($key)) return $cache; /** @var LinkScheme $info */ $info = LinkScheme::find()->andWhere(['link_id' => $link_id, 'storage' => $storage])->delete()->orderBy(['id' => SORT_DESC])->limit(1)->one(); if (!$info) { //创建 $info = $this->create(array_merge($linkInfo, ['storage' => $storage, 'link_id' => $link_id])); } \Yii::$app->cache->getOrSet($key, function () use ($info) { return $info; }, mt_rand(3600 * 24 * 3, 3600 * 24 * 7)); $_time = time(); if ($info->expire_time > $_time) { //提前一天异步重置 if (empty($link['is_reset']) && $info->expire_time < ($_time + 86400)) $this->resetLink($info->id, true); return $info; } return $this->resetLink($info->id); }
/**
* 创建 * @param $params * @return LinkSchemeForm * @throws UserException * @throws \yii\base\InvalidConfigException */ public function create($params) { $form = new LinkSchemeForm(); $form->setScenario(LinkSchemeForm::SCENARIO_CREATE); $form->load($params, ''); if (!$form->validate()) { throw new UserException(current($form->getFirstErrors())); } /** * 最大不能超过30天(默认30天) */ $expire_day = (int)$form->expire_time; if ($expire_day > 30 || empty($expire_day)) $expire_day = 30; if ($expire_day && $expire_day < 1) $expire_day = 1; $form->expire_time = time() + 86400 * (int)$expire_day;
if (!$form->save()) { throw new UserException(current($form->getFirstErrors())); }
//请求微信获取到真正的短链接 $res = $this->getSchemeUrl($form->expire_time,$form->path, $form->query); if (!$res) { $form->delete(); throw new UserException('链接生成失败'); } //修改数据库,微信Url Scheme生成的这里没有长链接$form->open_link = $res['openlink']; if (!$form->save()) throw new UserException(current($form->getFirstErrors())); return $form; }
/** * 请求微信接口 * @param $expire_time 到期失效的scheme码的失效时间,为Unix时间戳。生成的到期失效scheme码在该时间前有效。最长有效期为1年。生成到期失效的scheme时必填。 * @param $path 通过scheme码进入的小程序页面路径,必须是已经发布的小程序存在的页面,不可携带query。path为空时会跳转小程序主页。 * @param $query 通过scheme码进入小程序时的query,最大1024个字符,只支持数字,大小写英文以及部分特殊字符:!#$&'()*+,/:;=?@-._~ * @return mixed * @throws Exception */ protected function getSchemeUrl($expire_time, $path, $query) { $arr = [ 'is_expire' => false, 'expire_time' => $expire_time, 'jump_wxa' => [ 'path' => $path, 'query' => $query, ] ]; //这里使用了 EasyWeChat ($app 小程序实例) $server = new BaseClient($app); $result = $server->httpPostJson('https://api.weixin.qq.com/wxa/generatescheme', $arr); if ($result['errcode'] != 0) { $msg = $result['errmsg'] . ',errcode:' . $result['errcode']; throw new Exception($msg); } return $result['openlink']; }
/** * 重置链接 * @param int $id * @param bool $async * @return LinkScheme|array|bool * @throws UserException * @throws \yii\base\InvalidConfigException */ public function resetLink(int $id, $async = false) { // 控制用户当前这一次的请求 $mutex = new Mutex(); $key = LinkScheme::tableName() . ':reset:' . $id; if (!$mutex->acquire($key, 5)) return false; if ($async) { \Yii::$app->queue->push(new AsyncJob([ 'class' => self::class, 'method' => 'resetLink', 'methodParams' => [$id, false] ])); return ['success']; } /** @var LinkScheme $info */ $info = LinkScheme::find()->andWhere(['id' => $id])->limit(1)->one(); if (empty($info)) return false; $expire_time = $info->expire_time + 86400 * 30; //请求Url Scheme访问 $schemeInfo = $this->getSchemeUrl($expire_time,$path,$query); if (!$schemeInfo) { throw new UserException('链接生成失败'); } $info->expire_time = $expire_time; if (!$info->save()) throw new UserException(current($info->getFirstErrors())); $key = LinkScheme::tableName() . $info->link_id . '_' . $info->storage; \Yii::$app->cache->delete($key); $mutex->release($key); return $info; }
{ "code": 200, "msg": "ok", "data": { "storage":"2222222", "open_link": "https://wxaurl.cn/3GTdIJNvyIn", //跳转链接(scheme_type=0不存在,其他的通过这个链接 302 跳转) } }
表设计:
CREATE TABLE `cc_link` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(50) NOT NULL DEFAULT '' COMMENT '链接名称', `relevance_type` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '关联类型', `relevance_name` varchar(300) NOT NULL DEFAULT '' COMMENT '关联内容名称(比如:活动就是活动名称)', `relevance_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '类型对应的id', `expire_time` int(10) unsigned DEFAULT '0' COMMENT '过期时间(存在是有时间限制,否则就是永久)', `is_del` tinyint(1) unsigned DEFAULT '0' COMMENT '是否已删除(0-否,1-是)', `created_at` int(10) unsigned DEFAULT NULL, `updated_at` int(10) unsigned DEFAULT NULL, `created_by` int(10) unsigned DEFAULT NULL, `updated_by` int(10) unsigned DEFAULT NULL, `is_show` tinyint(1) unsigned DEFAULT '1' COMMENT '是否在后台显示:1显示,0不显示', `from` tinyint(1) unsigned DEFAULT '0' COMMENT '来源:0集团,1门店,2app,3系统', `is_reset` tinyint(1) unsigned DEFAULT '0' COMMENT '过期是否重置:0重置,1不重置', `repeat_tag` varchar(32) DEFAULT '' COMMENT '重复tag(ps:如果存在每一个业务必须保证tag唯一)', `path` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '通过scheme码进入的小程序页面路径', `query` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT '通过scheme码进入小程序时的query,最大1024个字符,只支持数字,大小写英文以及部分特殊字符:!#$&''()*+,/:;=?@-._~', PRIMARY KEY (`id`), KEY `idx_group_id` (`group_id`), KEY `idx_relevance_type` (`relevance_type`) ) ENGINE=InnoDB COMMENT='url链接服务';
CREATE TABLE `cc_link_scheme` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `link_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '链接id(link表)', `open_link` varchar(1024) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '短链接', `expire_time` int(10) unsigned DEFAULT '0' COMMENT '过期时间(最长30天,为了防止过期,设置29天最好)', `storage` varchar(32) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '前端浏览器唯一的storage', `is_del` tinyint(1) unsigned DEFAULT '0' COMMENT '是否已删除(0-否,1-是)', `created_at` int(10) unsigned DEFAULT NULL, `updated_at` int(10) unsigned DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `idx_link_storage` (`link_id`,`storage`) USING BTREE ) ENGINE=InnoDB COMMENT='链接scheme';
在之前我们只能在微信内的网页中使用微信的开发标签-小程序跳转按钮 <wx-open-launch-weapp>
打开小程序,只有这样一种单一的场景。
而在实际的业务中,我们希望在给用户发送的营销短信、邮件或其他渠道如APP打开小程序,以快速获取用户流量,完成引流、导购等目的。
近期微信支持URL Scheme打开小程序
。
首先我们先来看一下目前微信官方提供的两种打开微信小程序的方式以及相关适用场景
打开方式 | 适用场景 | 场景值 | 使用方式 | 备注 | 官网链接 |
---|---|---|---|---|---|
URL Scheme | 短信、邮件、等微信外网页打开小程序 | 1065 | location.href = 'weixin://dl/business/?t= *TICKET*' |
TICKET由服务端接口返回(openlink) | https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/url-scheme/urlscheme.generate.html |
<wx-open-launch-weapp> |
微信内网页 | 1167 | 页面配置<wx-open-launch-weapp> 标签 |
需配置JS接口域名或云开发静态网站托管绑定的域名下网页 | https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_Open_Tag.html |
URL Scheme的获取
方案一通过服务端接口获取:
ps:怎样获取ACCESS_TOKEN,这里就不写了
请求地址:
POST https://api.weixin.qq.com/wxa/generatescheme?access_token=ACCESS_TOKEN
请求参数:
属性 | 类型 | 默认值 | 必填 | 说明 |
---|---|---|---|---|
access_token | string | 是 | 接口调用凭证 | |
jump_wxa | Object | 否 | 跳转到的目标小程序信息。 | |
is_expire | boolean | false | 否 | 生成的scheme码类型,到期失效:true,永久有效:false。 |
expire_time | number | 否 | 到期失效的scheme码的失效时间,为Unix时间戳。生成的到期失效scheme码在该时间前有效。最长有效期为1年。生成到期失效的scheme时必填。 |
jump_wxa 的结构:
属性 | 类型 | 默认值 | 必填 | 说明 |
---|---|---|---|---|
path | string | 是 | 通过scheme码进入的小程序页面路径,必须是已经发布的小程序存在的页面,不可携带query。path为空时会跳转小程序主页。 | |
query | string | 是 | 通过scheme码进入小程序时的query,最大1024个字符,只支持数字,大小写英文以及部分特殊字符:!#$&'()*+,/:;=?@-._~ |
请求示例:
{ "jump_wxa": { "path": "/pages/tab", "query": "xx=1&xxx=1" }, "is_expire":true, "expire_time":1614059318 }
返回示例:
{ "errcode": 0, "errmsg": "ok", "openlink":"weixin://dl/business/?t= *TICKET*", }
小程序URL Scheme的使用:
生成的URL Scheme如下所示:weixin://dl/business/?t= *TICKET*
iOS系统支持识别URL Scheme,可在短信等应用场景中直接通过Scheme跳转小程序。
Android系统不支持直接识别URL Scheme,用户无法通过Scheme正常打开小程序,开发者需要使用H5页面中转,再跳转到Scheme实现打开小程序,跳转代码示例如下:
location.href = 'weixin://dl/business/?t= *TICKET*'
端 | 使用方式 | 备注 |
---|---|---|
Android | location.href="weixin://dl/business/?t= *TICKET*" |
只有一种方式 |
IOS | 直接识别URL Scheme 或使用location.href方式 | 两种方式 |
But, 当我们进行短信,邮件等触达时,是无法确定用户所使用的的手机设备是IOS
还是Android
,
So, 我们从实际的业务触发,都需要一个H5页面进行中转处理。
小程序唤起业务流程图
通过短信打开小程序:
一般我们在短信中发送出去的链接如下:
https://www.cnblogs.com/jxxiaocao?tag=68627b50710d465c3db3f6c3edbfc0c1
这时在h5页面可以通过tag请求 getSchemeByTag($tag) 接口来获取 openlink 就可以通过
location.href = 'weixin://dl/business/?t= *TICKET*'
方法来打开小程序了(这里可以做一些过期等其他的判断处理)
当然也可以把openlink等包含在要发送的短信链接上,但总感觉这样不好