个人案例
场景零售
场景零售
场景零售扫码体验
- 小程序深度合成服务在用证明获取指引
step1:在微信服务市场选择深度合成服务接入 路径:微信服务市场—接口和插件—深度合成,选择深度合成服务接入 微信服务市场链接:微信服务市场 [图片] step2:获取深度合成服务订单完成凭证,将该凭证上传小程序类目资质审核 [图片]
2023-12-22 - 微信小程序可以播放二进制音频流吗?
在项目中开发中,遇到这样的需求:有一段文字,需要通过后台接口转成语音传到前端进行播放。 因为文字是实时生成的,为保证实时性,需要在生成文字的过程中,转为一段一段的音频流通过websocket传递到前端,前端拿到音频流后立即开始播放,接收到后续的音频流后追加到播放音频里继续播放,达到实时生成文字,实时转换音频流,前端实时播放的效果。 类似于腾讯元宝小程序。这个怎么实现
2024-06-13 - 小程序“本地组件”(原自定义交易组件本地生活平台)接入指引(不定期更新,文明交流勿喷)
本地组件 之前曾写过“新版自定义交易组件接入指引”,广受好评,解决了很多开发者接入时遇到的问题,当然,其中也不乏一些喂不饱的白眼狼,得到便利还反咬一口,无耻至极。 本文主要介绍本地组件产品的业务流程及内测期间各流程的注意事项,文档篇幅较长,如无需查看完整文档可以使用浏览器自带页面搜索功能进行关键字搜索(快捷键Ctrl+F ),组件目前还在内测中,如无法获取内测权限请耐心等待正式上线(因未给某人提供申请方式,大佬都被喷退群了,太令人失望啦)。 本文由Memory的小跟班编写,内容如有错误请指正,勿喷。 良言一句三冬暖,恶语伤人六月寒。 1、产品介绍 本地生活服务行业的商家,可通过"本地组件" (无需新申请专用商户号)将小程序中的商品(兑换券)上架到视频号橱窗,用户在视频号购买兑换券后可在线上/线下二次核销,实现到店核销(自提)/到家配送(同城)的业务场景。 业务流程图 [图片] 用户交互示意 [图片] 2、开通方式 2.1 权限开通 商家符合接入要求或收到内测邀请后需要准备好以下资料:商家名称、视频号名称、小程序名称、小程序APPID等填写报名申请表,此处不作报名表单提供,请自行通过自有资源获取,提交表单后,正常审核时效为1个工作日。 2.2 资质要求 内测期间,商家需要发邮件提交准入资质材料用于资质审核;组件正式上线后可通过MP后台「交易组件」入口开通的「本地组件」提交对应资质审核 2.2.1 商超购百行业(如:万达、天虹) 本规则适用于入驻视频号橱窗的所有需要使用本地组件的商超购百商家 商家类型 资质类型 资质要求 商超购百 营业执照 营业执照 商超购百 商标 1.资质主体与入驻主体(小程序)一致 <br> 2.需提供35类服务商标 <br> 3.若为关联公司或代运营公司入驻,需提供上述商标权利人的委托函或授权书 商超购百 门店数量要求 1.线下业务:以服务类商标命名的实体门店>1家 <br> 2.线上业务:以服务类商标命名的官方网站、独立app或其他平台 商超购百 门店品牌授权要求 提供不少于3个,在经营销售的合作或授权经销协议(协议方需为京东/天猫官方旗舰店或旗舰店品牌) 商超购百 承诺函 承诺在视频号橱窗中所售商品均为正规渠道授权经销商品,保证商品质量,遇到售后和纠纷问题协助视频号团队友好协商处理。 2.2.2 酒旅景区行业(如:万豪、维也纳、迪士尼、长隆) 本规则适用于入驻视频号橱窗的所有需要使用本地组件的酒店/景区商家,暂不支持旅行社 商家类型 资质类型 资质要求 酒旅商家 营业执照 营业执照 酒旅商家 商标 1.资质主体与入驻主体(小程序)一致 <br> 2.品牌商标 <br> 3.若为关联公司或代运营公司入驻,需提供上述商标权利人的委托函或授权书 酒旅商家 其他资质 「单体酒店」需提供:1. 卫生许可证 <br> 2.特种行业许可证 <br> 3.如涉及自助餐券需提交餐饮服务许可(如已三证合一可提交食品经营许可)<br> 提供四星级/五星级酒店证书 <br> 「酒店/度假村集团」需提供:1. 集团旗下一家酒店的资质(同单体酒店)<br> 2. 集团与该提供资质的酒店之间的管理关系证明并附集团旗下所有酒店名单 <br> 「全国/全球连锁主题乐园」主体需提供:1. 娱乐经营许可证 <br>「景区」需提供(任选其一):1.全国旅游景区质量等级评定委员会出具的《旅游景区质量等级证书》 <br> 2. 旅游局景区等级评定委员会红头文件 <br> 3.提供4A以上景区证书 酒旅商家 承诺函 承诺在视频号橱窗中开展相关业务,保证为视频号用户提供商品购买后的线下履约能力,遇到售后和纠纷问题协助视频号团队友好协商处理。 2.2.3 餐饮行业(如:麦当劳、肯德基、喜茶、瑞幸) 本规则适用于入驻视频号橱窗的所有需要使用本地组件的餐饮商家 商家类型 资质类型 资质要求 餐饮商家 营业执照 营业执照 餐饮商家 商标 1.资质主体与入驻主体(小程序)一致 <br> 2.品牌商标 <br> 3.若为关联公司或代运营公司入驻,需提供上述商标权利人的委托函或授权书 餐饮商家 门店数量要求 以上述商标命名的实体门店,全国实体店数量≥100家(美团/点评门店信息截图或百度/高德POI信息截图) 餐饮商家 其他资质 1.餐饮店主体: 需提供申请主体的「食品经营许可证(经营范围需包含餐饮制作相关项目)扫描件」 <br> 2. 非餐饮主体需提供:a) 《餐饮平台与门店的管理关系声明》 b) 《餐饮门店运营资质和责任承诺函》(含旗下所有门店名单) c)卡券使用门店的「食品经营许可证(经营范围需包含餐饮制作相关项目)扫描件」 餐饮商家 承诺函 承诺在视频号橱窗中开展相关业务,保证为视频号用户提供商品购买后的线下履约能力,遇到售后和纠纷问题协助视频号团队友好协商处理。 2.2.4 其他行业(如:电影、演出、健身、体检、买菜等) 本规则适用于提供本地生活服务的商家,如电影、演出、健身、体检、买菜、按摩、KTV等线下服务商家。 商家类型 资质类型 资质要求 到店综合 营业执照 营业执照 到店综合 商标 1.资质主体与入驻主体(小程序)一致 <br> 2.品牌商标 <br> 3.若为关联公司或代运营公司入驻,需提供上述商标权利人的委托函或授权书 到店综合 门店数量要求 1.线上平台经营型商家(美团、携程、猫眼电影等)需提供官网,需要有开设以35类服务类型品牌商标命名的官方网站、独立APP或在其他平台上经营卖场型店铺。 <br> 2.线下经营商家(如爱康国宾体检)以上述品牌商标命名的实体门店,全国实体店数量≥100家(美团/点评门店信息截图或百度/高德POI信息截图) 到店综合 其他资质 1.餐饮店主体: 需提供申请主体的「食品经营许可证(经营范围需包含餐饮制作相关项目)扫描件」 <br> 2. 非餐饮主体需提供:a) 《餐饮平台与门店的管理关系声明》 b) 《餐饮门店运营资质和责任承诺函》(含旗下所有门店名单) c)卡券使用门店的「食品经营许可证(经营范围需包含餐饮制作相关项目)扫描件」 到店综合 承诺函 承诺在视频号橱窗中开展相关业务,保证为视频号用户提供商品购买后的线下履约能力,遇到售后和纠纷问题协助视频号团队友好协商处理。 3、协议相关 本地组件正式上线后,商家在开通本地组件时,会查看和签订本地组件协议,协议将定义本地组件业务模式、技术服务费比例、对账周期、打款周期、打款账号等内容. 本地组件正式上线后,预计会按「月」为周期结算,收取商家在视频号场景下支付GMV*1%的技术服务费。 4、文档接口 暂不在本文提供,后续更新接入时的一些问题 5、带货测试 上架商品,商品审核,保证金提交,直播卖货,支付,订单发货,退货等环节测试。 6、对账打款测试 双方进行账单对账,技术服务费对账,商家请求打款,技术服务费打款测试。 内测期问,预计月初通过邮件将账单发给商家对接人进行对账,商家完成小额打款测试, 正式上线后,平台预计在每个月1号出对账单,商家在5个工作日确认对账单无误后,在20个工作日内完成打款。 [图片] 7、异常情况测试 保证金延迟提交会限制带货能力,佣金延迟打款限制带货能力等一场逻辑测试。 8、 FAQ 8.1 视频号小店、自定义交易组件、新版自定义交易组件、视频号交易组件与本地组件的区别 能力 商户号 业务类型 发货类型 技术服务费 本地组件 原小程序商户号 团购兑换券 本地配送/到店自提 支付口径/统一比例 自定义交易组件 原小程序商户号 商品/兑换券 快递/同城/到店 无 新版自定义交易组件 新申请二级商户号 商品/兑换券 快递/同城/到店 结算口径/分类目 视频号交易组件 新申请二级商户号 商品 快递/免物流 结算口径/分类目 视频号小店 新申请二级商户号 商品 快递/免物流 结算口径/分类目 自定义交易组件将会在4月底下限,届时通过自定义交易组件上架的商品将会失效,若商家在视频号继续经营卖货,需要接入「本地组件」或者「视频号交易组件/视频号小店」 8.2 本地组件的商家,如何售卖电商物流发货商品? 目前一个视频号只能关联一个视频号小店 或一个组件,内测期间,用新的视频号开通视频号小店,上架标准电商发货商品,上传至“优选联盟”。商家视频号通过优选联盟分销电商商品,区别本地生活售卖券,(也可以通过带货团长来实现)未来会支持视频号可同时关联本地组件和视频号小店。 8.3 本地组件的商家,如何缴纳保证金,缴纳多少? 本地组件商家保证金沿用已发布的橱窗保证金规则,自营账户交3万元保证金,其他推广员交100元保证金。具体规则可参考「视频号橱窗保证金条款 」 未完待续······
2023-04-21 - 【实战记录】使用新版接口获取手机号过程中,对小程序的基础库版本的了解
描述: 老版文档:https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/deprecatedGetPhoneNumber.html 新版文档:https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/getPhoneNumber.html 如题所示,最近项目中对获取手机号的接口进行升级,这里先展示下老版的写法 wxml部分: <button open-type="getPhoneNumber" bindgetphonenumber="getPhoneNumber"></button> js部分: getphonenumber(e) { wx.login({ success (res) { if (res.code) { //发起网络请求 wx.request({ url: 'https://example.com/onLogin', data: { code: res.code }, success (res) { const data = { ..., encryptedData: e.detail.encryptedData, iv: e.detail.iv, sessionKey: res.data.session_key } wx.request({ url: 'https://example.com/getphonenumber', data, success (res) { // todo } }) } }) } } }) } 新版写法: getphonenumber(e) { wx.request({ url: 'https://example.com/getphonenumber', data: { code: e.detail.code }, success (res) { // todo } }) } 分析: 1、如文档所说旧版用户使用不当会存在下图所示问题,本身项目定位也是做自己的产品,也本着长期发展的目标,决定进行此次更新。 [图片] 2、新版代码简洁逻辑清晰,但若在项目中替换的话,还需要考虑到用户版本库使用情况,原因是:从基础库 2.21.2 开始,对获取手机号的接口进行了安全升级,这时需要登录下我们的小程序管理后台,设置-》基本设置-》基础库最低版本设置;查看下当前小程序用户版本库使用的占比情况,如下图所示,由于我们当前的小程序自2.21.3以下没有用户再使用,故直接设置了2.21.2,这样设置后,假设后面有低版本用户访问我们小程序的话,微信侧会提示用户去更新微信版本 [图片] 3、考虑如果其他同学项目发现有部分用户的版本是低于2.21.2的,这时可能就需要做兼容处理了。 小结: 获取手机号虽然api更简洁了,但也注意不能滥用哦~,好吧就到这里了,如有描述的不到位的地方,欢迎大家指正哈~ [图片]
2022-06-05 - 基于Minium框架的小程序自动化落地实践
致谢 本文的技术实现细节,要特别感谢微信团队中的严烨,乃华,冯永鹏等小伙伴的热心帮忙和技术指导。 Minium框架简介 框架优点 微信小程序官方推出的小程序自动化框架,是为小程序专门开发的自动化框架, 提供了 Python 和 JavaScript 版本。 支持一套脚本,iOS & Android & 模拟器,三端运行 提供丰富的页面跳转方式,看不到也能去得到 可以获取和设置小程序页面数据,让测试不止点点点 支持往 AppSerive 注入代码片段 可以使用 minium 来进行函数的 mock, 可以直接跳转到小程序某个页面并设置页面数据, 做针对性的全面测试 框架缺点 暂不支持H5页面的调试; 暂不支持插件内wx接口调用; 技术选型 minium支持Python 和 JavaScript 版本,而且有专门的团队定期维护,遇到问题可以在微信开发者社区进行提问,因此选择了minium。 Minium框架原理 minium提供一个基于unittest封装好的测试框架,利用这个简单的框架对小程序测试可以起到事半功倍的效果。 测试基类Minitest会根据测试配置进行测试,minitest向上继承了unittest.TestCase,并做了以下改动: 加载读取测试配置 在合适的时机初始化minium.Minium、minium.App和minium.Native 根据配置打开IDE,拉起小程序项目和或自动打开真机调试 拦截assert调用,记录检验结果 记录运行时数据和截图,用于测试报告生成 使用MiniTest可以大大降低小程序测试成本。 Properties 名称类型默认值说明 appminium.AppNoneApp实例,可直接调用minium.App中的方法 miniminium.MiniumNoneMinium实例,可直接调用minium.Minium中的方法 nativeminium.NativeNoneNative实例,可直接调用minium.Native中的方法 环境搭建 搭建Minium Doc 微信技术团队伙伴已告知,无需进行本地搭建,本地搭建的都是错的并且还是旧的文档) 无需构建,直接访问官方文档(https://minitest.weixin.qq.com/#/) 安装软件 下载并安装python 3.8+,地址:https://www.python.org/downloads/release/python-390/ 下载并安装微信开发者工具,地址:https://developers.weixin.qq.com/miniprogram/dev/devtools/download.html 安装minium 下载 https://minitest.weixin.qq.com/minium/Python/dist/minium-latest.zip,然后执行 第一步:解压,解压路径自己记住(我这里解压到 C:\minium-1.2.6) [图片] 第二步:切换到文件目录下 第三步:执行即可 (如下图) [图片] 第四步:测试 [图片] 特别说明 如果新手通过本地解压再执行,遇到以下问题 [图片] 请直接使用以下命令,即可一次性成功安装 pip3 install https://minitest.weixin.qq.com/minium/Python/dist/minium-latest.zip [图片] [图片] 配置微信开发者工具 安装微信开发者工具(我本机使用的版本是1.06.2205312),并打开安全模式: 设置 -> 安全设置 -> 服务端口: 打开 [图片] 在工具栏菜单中点击设置,选择项目设置,切换到“本地设置”,将调试基础库选择大于2.7.3的库; [图片] 开启微信工具安全设置中的 CLI/HTTP (提供了命令行和HTTP两种调用方式)调用功能。 开启被测试项目的自动化端口号 [代码]"path/to/cli" auto --project "path/to/project" --auto-port 9420 [代码] 路径说明 path/to/project: 指代填写存放小程序源码的目录地址,文件夹中需要包含有 project.config.json 文件 [图片] path/to/cli: 指代开发者工具cli命令路径 [图片] 与下图一致证明开启成功 [代码]"C:/Program Files (x86)/Tencent/微信web开发者工具/cli" auto --project "C:/WeChatProjects/miniprogram-1" --auto-port 9420 [代码] [图片] 配置信息 代码结构目录 [图片] 模拟器Config.json [代码]{ "project_path": "C:\\WeChatProjects\\miniprogram-1", "dev_tool_path": "C:\\Program Files (x86)\\Tencent\\微信web开发者工具\\cli.bat", "debug_mode": "debug", "test_port": 9420, "platform": "ide", "app": "wx", "assert_capture": true, "request_timeout":60, "remote_connect_timeout": 300, "auto_relaunch": true } [代码] 真机Config.json [代码]{ "project_path": "C:\\WeChatProjects\\xxx_chinamobile-pmc_migration2\\unpackage\\dist\\build\\mp-weixin", "dev_tool_path": "C:\\Program Files (x86)\\Tencent\\微信web开发者工具\\cli.bat", "debug_mode": "debug", "test_port": 9420, "platform": "Android", "app": "wx", "enable_app_log": true, "device_desire": { "serial": "d310bf55" }, "assert_capture": true, "request_timeout":60, "remote_connect_timeout": 300, "auto_relaunch": true } [代码] Suite.json [代码]{ "pkg_list": [ { "case_list": [ "test_*" ], "pkg": "testcase.*_test" } ] } [代码] 测试用例 first_test.py [代码]# !/usr/bin/python # -*- coding: utf-8 -*- """ @File : first_test.py @Create Time: 2022-06-01 16:17 @Description: """ import minium class FirstTest(minium.MiniTest): def test_get_system_info(self): sys_info = self.mini.get_system_info() print("FirstTest: ", sys_info) self.assertIn("SDKVersion", sys_info) if __name__ == "__main__": import unittest loaded_suite = unittest.TestLoader().loadTestsFromTestCase(FirstTest) result = unittest.TextTestRunner().run(loaded_suite) print(result) [代码] 主程序入口 run.py [代码]# !/usr/bin/python # -*- coding: utf-8 -*- """ @File : run.py @Create Time: 2022-06-01 17:21 @Description: """ import os # 运行执行class文件中的指定用例 cmd0 = "minitest -m testcase.first_test --case test_get_system_info -c config.json -g" # 运行执行testcase文件中的指定用例 cmd1 = "minitest -m testcase.first_test -c config.json -g" # 按照suite配置执行用例 cmd2 = "minitest -s suite.json -c config.json -g" os.system(cmd0) [代码] 执行用例 [图片] 测试报告 测试结果存储在outputs下,运行命令 [代码]python -m http.server 12345 -d outputs [代码] 然后在浏览器上访问 http://localhost:12345 即可查看报告 [图片] 下载项目源代码 申请GitLab项目代码管理权限,下载小程序源代码 [图片] [图片] [图片] 可选项(注意!此部分内容只适用于我司内部产品) 下载Node.js 地址:http://nodejs.cn/download/current/ [图片] 下载并安装编译软件HBuilderX 下载HBuilderX 地址:https://dcloud.io/hbuilderx.html 安装HBuilderX [图片] [图片] 编译源代码 [图片] 报错的解决方案,进入源代码目录下,执行NPM INSTALL [图片] 编译并发布小程序 因为公司的小程序是通过Vue.js进行编写,发布项目需要先通过HBuilderX工具进行编译。 [图片] [图片] [图片] [图片] [图片] 执行测试脚本 [图片] 输出测试报告 用例执行成功 [图片] 用例执行失败 [图片] [图片] 用例执行异常 [图片] [图片] Nginx简介 Nginx (engine x) 是一个高性能的HTTP和反向代理服务器,也是一个IMAP/POP3/SMTP服务器。Nginx是由伊戈尔·赛索耶夫为俄罗斯访问量第二的Rambler.ru 站点开发的。 它也是一种轻量级的Web服务器,可以作为独立的服务器部署网站(类似Tomcat)。它高性能和低消耗内存的结构受到很多大公司青睐,如淘宝网站架设。 Nginx配置 Minium框架的Nginx配置 [代码] server { listen 80; server_name your.domain.com; location / { alias /path/to/dir/of/report; index index.html; } } [代码] [图片] Nginx下载 下载Nginx 下载地址:https://nginx.org/en/download.html [图片] Nginx安装部署 下载完成后,解压缩,运行cmd,使用命令进行操作,不要直接双击nginx.exe,不要直接双击nginx.exe,不要直接双击nginx.exe 使用命令到达nginx的加压缩后的目录 [代码]cd C:\nginx-1.22.0 [代码] [图片] 启动nginx服务,启动时会一闪而过是正常的 [代码]start nginx [代码] [图片] 打开任务管理器在进程中看不到nginx.exe的进程(双击nginx.exe时会显示在这里),需要打开详细信息里面能看到隐藏的nginx.exe进程 [图片] 如果都没有可能是启动报错了查看一下日志,在nginx目录中的logs文件夹下error.log是日志文件 [图片] [图片] 修改配置文件,进入解压缩目录,直接文件夹点击进去即可,不需要从dos操作 [图片] 在conf目录下找到nginx.conf使用txt文本打开即可,找到server这个节点,修改端口号,如果有需求可以修改主页目录没有就不用修改 [图片] [图片] 修改完成后保存,使用以下命令检查一下配置文件是否正确,后面是nginx.conf文件的路径,successful就说明正确了 [代码]nginx -t -c /nginx-1.22.0/conf/nginx.conf [代码] [图片] 如果程序没启动就直接start nginx启动,如果已经启动了就使用以下命令重新加载配置文件并重启 [代码]nginx -s reload [代码] [图片] 关闭nginx服务使用以下命令,同样也是一闪而过是正常的,看一下是否进程已消失即可。 [代码]nginx -s stop #强制停止Nginx服务:常用 nginx -s quit #优雅地停止Nginx服务(即处理完所有请求后再停止服务) [代码] Nginx常见命令 [代码]nginx -s reopen #重启Nginx nginx -s reload #重新加载Nginx配置文件,然后以优雅的方式重启Nginx nginx -s stop #强制停止Nginx服务:常用 nginx -s quit #优雅地停止Nginx服务(即处理完所有请求后再停止服务) nginx -t #检测配置文件是否有语法错误,然后退出 nginx -?,-h #打开帮助信息 nginx -v #显示版本信息并退出 nginx -V #显示版本和配置选项信息,然后退出 nginx -t #检测配置文件是否有语法错误,然后退出 nginx -T #检测配置文件是否有语法错误,转储并退出 [代码] 在线查看 打开浏览器,访问 http://ip:port/,在线查看报告是否可以正常展示 [图片] 常见问题 (1)端口号被占用 解决方法:https://blog.csdn.net/qq_32265203/article/details/110088489 (2)nginx文件夹路径含中文 解决方法:中文路径改为英文路径,或者换一个不包含中文的路径 其他错误就详细看log中的描述 解决方法:百度 优化配置 [代码]#user nobody; #==工作进程数,一般设置为cpu核心数 worker_processes 1; #error_log logs/error.log; #error_log logs/error.log notice; #error_log logs/error.log info; #pid logs/nginx.pid; events { #==最大连接数,一般设置为cpu*2048 worker_connections 1024; } http { include mime.types; default_type application/octet-stream; #log_format main '$remote_addr - $remote_user [$time_local] "$request" ' # '$status $body_bytes_sent "$http_referer" ' # '"$http_user_agent" "$http_x_forwarded_for"'; #access_log logs/access.log main; sendfile on; #tcp_nopush on; #keepalive_timeout 0; #==客户端链接超时时间 keepalive_timeout 65; #gzip on; #当配置多个server节点时,默认server names的缓存区大小就不够了,需要手动设置大一点 server_names_hash_bucket_size 512; #server表示虚拟主机可以理解为一个站点,可以配置多个server节点搭建多个站点 #每一个请求进来确定使用哪个server由server_name确定 server { #站点监听端口 listen 8800; #站点访问域名 server_name localhost; #编码格式,避免url参数乱码 charset utf-8; #access_log logs/host.access.log main; #location用来匹配同一域名下多个URI的访问规则 #比如动态资源如何跳转,静态资源如何跳转等 #location后面跟着的/代表匹配规则 location / { #站点根目录,可以是相对路径,也可以使绝对路径 root html; #默认主页 index index.html index.htm; #转发后端站点地址,一般用于做软负载,轮询后端服务器 #proxy_pass http://10.11.12.237:8080; #拒绝请求,返回403,一般用于某些目录禁止访问 #deny all; #允许请求 #allow all; add_header 'Access-Control-Allow-Origin' '*'; add_header 'Access-Control-Allow-Credentials' 'true'; add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type'; #重新定义或者添加发往后端服务器的请求头 #给请求头中添加客户请求主机名 proxy_set_header Host $host; #给请求头中添加客户端IP proxy_set_header X-Real-IP $remote_addr; #将$remote_addr变量值添加在客户端“X-Forwarded-For”请求头的后面,并以逗号分隔。 如果客户端请求未携带“X-Forwarded-For”请求头,$proxy_add_x_forwarded_for变量值将与$remote_addr变量相同 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; #给请求头中添加客户端的Cookie proxy_set_header Cookie $http_cookie; #将使用代理服务器的主域名和端口号来替换。如果端口是80,可以不加。 proxy_redirect off; #浏览器对 Cookie 有很多限制,如果 Cookie 的 Domain 部分与当前页面的 Domain 不匹配就无法写入。 #所以如果请求 A 域名,服务器 proxy_pass 到 B 域名,然后 B 服务器输出 Domian=B 的 Cookie, #前端的页面依然停留在 A 域名上,于是浏览器就无法将 Cookie 写入。 #不仅是域名,浏览器对 Path 也有限制。我们经常会 proxy_pass 到目标服务器的某个 Path 下, #不把这个 Path 暴露给浏览器。这时候如果目标服务器的 Cookie 写死了 Path 也会出现 Cookie 无法写入的问题。 #设置“Set-Cookie”响应头中的domain属性的替换文本,其值可以为一个字符串、正则表达式的模式或一个引用的变量 #转发后端服务器如果需要Cookie则需要将cookie domain也进行转换,否则前端域名与后端域名不一致cookie就会无法存取 #配置规则:proxy_cookie_domain serverDomain(后端服务器域) nginxDomain(nginx服务器域) proxy_cookie_domain localhost .testcaigou800.com; #取消当前配置级别的所有proxy_cookie_domain指令 #proxy_cookie_domain off; #与后端服务器建立连接的超时时间。一般不可能大于75秒; proxy_connect_timeout 30; } #error_page 404 /404.html; # redirect server error pages to the static page /50x.html # error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } } #当需要对同一端口监听多个域名时,使用如下配置,端口相同域名不同,server_name也可以使用正则进行配置 #但要注意server过多需要手动扩大server_names_hash_bucket_size缓存区大小 server { listen 80; server_name www.abc.com; charset utf-8; location / { proxy_pass http://localhost:10001; } } server { listen 80; server_name aaa.abc.com; charset utf-8; location / { proxy_pass http://localhost:20002; } } } [代码] 创建数据库和数据表 数据库:XXXXX [图片] 数据表:device [图片] 存储数据信息 [图片] SQL 语句如下 [代码]CREATE TABLE `device` ( `id` int(11) NOT NULL AUTO_INCREMENT, `execid` int(11) NOT NULL, `device_number` int(11) DEFAULT NULL, `project_name` varchar(255) DEFAULT NULL, `case_field` varchar(255) DEFAULT NULL, `case_content` varchar(255) DEFAULT NULL, `triggertime` datetime DEFAULT NULL, `showtime` datetime DEFAULT NULL, `offlinetime` datetime DEFAULT NULL, `starttime` datetime DEFAULT NULL, `endtime` datetime DEFAULT NULL, `status` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=30 DEFAULT CHARSET=gb18030; [代码] 创建 Flask 服务 新建 db_func.py [代码]# !/usr/bin/python # -*- coding: utf-8 -*- """ @Author : Charles @File : db_func.py @Create Time: 2022-06-21 09:18 @Description: """ import time import pymysql def connect_db(): url = "127.0.0.1" name = "root" pwd = "123456" dataBase = "XXXXX" return pymysql.connect(host=url, port=3306, user=name, passwd=pwd, db=dataBase) def query_database(db, sql): cursor = db.cursor() try: if isinstance(sql, str): cursor.execute(sql) result = list(cursor.fetchall()) else: result = [] for sq in sql: cursor.execute(sq) result.append(list(cursor.fetchall())) except Exception as err: result = ''.join(('An db query exception happened: ', str(err))) # db.close() # 关闭数据库连接 return result def update_db(db, sql): cursor = db.cursor() try: if isinstance(sql, str): cursor.execute(sql) db.commit() else: print('sql 不是一个合格的字符串:{}'.format(sql)) except Exception as err: result = ''.join(('An db update exception happened: ', str(err))) db.rollback() print(result) # 数据库数据插入更新 def db_insert(db, sql): cursor = db.cursor() i = 0 try: cursor.execute(sql) db.commit() result = 'db insert success' except Exception as err: db.rollback() result = 'An db insert exception happened: ' + str(err) + ' ' + str(i) db.close() # 关闭数据库连接 return result def close_db(db): try: db.close() except Exception as err: result = ''.join(('An db closed exception happened: ', str(err))) def get_current_time(): return time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time())) [代码] 新建 project_index.py [代码]# !/usr/bin/python # -*- coding: utf-8 -*- """ @Author : Charles @File : project_index.py @Create Time: 2022-06-21 09:53 @Description: """ import datetime import time from flask import * from common import db_func app = Flask(__name__) @app.route('/') def display(): """ 最新一轮回归测试的整体页面展示 :return: """ db = db_func.connect_db() # 如果实时数据正常显示,就把设备掉线时间默认设置为"1970-01-01 08:00:00" ,实时数据加载时长=数据显示时间-设备被触发时间 # 如果设备显示掉线页面,就把实时数据时间默认设置为"1970-01-01 08:00:00" ,实时数据加载时长=设备掉线时间-设备被触发时间 sql_all = 'SELECT device_number, project_name, case_field, case_content, ' \ '(CASE WHEN offlinetime ="1970-01-01 08:00:00" THEN TIMESTAMPDIFF(SECOND,triggertime,showtime) ' \ 'WHEN showtime ="1970-01-01 08:00:00" THEN TIMESTAMPDIFF(SECOND,triggertime,offlinetime) else 0 ' \ 'END) as duration, triggertime, showtime, offlinetime, starttime, endtime, status FROM device ' \ 'where execid in (select max(execid) from device) ;' # 如果实时数据正常显示,就把设备掉线时间默认设置为"1970-01-01 08:00:00" ,实时数据加载时长=数据显示时间-设备被触发时间 # 如果设备显示掉线页面,就把实时数据时间默认设置为"1970-01-01 08:00:00" ,实时数据加载时长=设备掉线时间-设备被触发时间 sql_fail = 'SELECT device_number, project_name, case_field, case_content, ' \ '(CASE WHEN offlinetime ="1970-01-01 08:00:00" THEN TIMESTAMPDIFF(SECOND,triggertime,showtime) ' \ 'WHEN showtime ="1970-01-01 08:00:00" THEN TIMESTAMPDIFF(SECOND,triggertime,offlinetime) else 0 ' \ 'END) as duration, starttime, endtime, status FROM device ' \ 'where status="fail" and execid in (select max(execid) from device) ;' pass_total_duration = 'SELECT CAST(SUM(TIMESTAMPDIFF(SECOND,triggertime,showtime)) AS CHAR) AS duration FROM device ' \ 'WHERE case_field="当前楼层" AND STATUS="pass" AND TIMESTAMPDIFF(SECOND,triggertime,showtime) <= "10" ' \ 'AND execid IN (SELECT MAX(execid) FROM device) ' fail_total_duration = 'SELECT CAST(SUM(TIMESTAMPDIFF(SECOND,triggertime,offlinetime)) AS CHAR) AS duration FROM device ' \ 'WHERE case_field="当前楼层" AND STATUS="fail" AND TIMESTAMPDIFF(SECOND,triggertime,offlinetime) > "10" ' \ 'AND execid IN (SELECT MAX(execid) FROM device) ' device_total_number = 'SELECT COUNT(*) FROM device WHERE case_field="当前楼层" AND execid IN (SELECT MAX(execid) FROM device) ' device_pass_number = 'SELECT COUNT(*) FROM device WHERE case_field="当前楼层" AND STATUS="pass" AND execid IN (SELECT MAX(execid) FROM device) ' device_fail_number = 'SELECT COUNT(*) FROM device WHERE case_field="当前楼层" AND STATUS="fail" AND execid IN (SELECT MAX(execid) FROM device) ' db_res_all = db_func.query_database(db, sql_all) de_res_fail = db_func.query_database(db, sql_fail) db_pass_total_duration = db_func.query_database(db, pass_total_duration) db_device_total_number = db_func.query_database(db, device_total_number) db_device_pass_number = db_func.query_database(db, device_pass_number) db_device_fail_number = db_func.query_database(db, device_fail_number) # Average Loading Time if db_device_pass_number[0][0] == 0: average_loading_time = "%.2f" % 0 else: average_loading_time = "%.2f" % (int(db_pass_total_duration[0][0]) / int(db_device_pass_number[0][0])) print("Average_loading_time: ", average_loading_time) # Averavge Loading Time Less Than 10s if db_device_pass_number[0][0] == 0: average_less_than_10s = "%.2f" % 0 else: average_less_than_10s = "%.2f" % (int(db_pass_lessthan_total_duration[0][0])/int(db_device_pass_number[0][0])) print("Averavge Loading Time Less Than 10s: ", average_less_than_10s) # Percentage of Loading Time Less Than 10s if db_device_pass_number[0][0] == 0: percentage_less_than_10s = "{:.2%}".format(0) else: percentage_less_than_10s = "{:.2%}".format(int(db_device_lessthan_pass_number[0][0])/int(db_device_pass_number[0][0])) print("Percentage of Loading Time Less Than 10s: ", percentage_less_than_10s) # Percentage of Loading Time Over 10s if db_device_pass_number[0][0] == 0: percentage_over_10s = "{:.2%}".format(0) else: percentage_over_10s = "{:.2%}".format(int(db_device_over_pass_number[0][0])/int(db_device_pass_number[0][0])) print("Percentage of Loading Time Over 10s: ", percentage_over_10s) # 获得SQL语句查询内容 db_res = [average_loading_time, average_less_than_10s, percentage_less_than_10s, percentage_over_10s, db_res_fail, db_res_pass_over_10s, db_res_all] if db_res: return render_template("/koneview_page.html", content=db_res) else: pass if __name__ == "__main__": app.run(host='xx.xx.xx.xx', port=5500, debug=True) [代码] 创建 HMTL 网页 新建 project_page.html [代码]<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>MyProject Monitor Platform</title> <link rel="icon" href="https://www.myproject.cn/zh/Images/myproject-logo-76x52_tcm156-8930.png"> <script src="static/lib/jquery-1.11.1.min.js" type="text/javascript"></script> <div class="navi-brand"> <a class="logo" onclick="zhugeTrack('顶栏按钮点击',{'按钮名称':'LOGO'});" href="https://www.myproject.com/en/" target="_blank"> <img src="/static/images/myprojectLOGO.png" style="text-align: left; width: 265px; height: 50px; position: absolute; left: 62px; top: 30px; margin-left: -60px;margin-top: -20px;"></a> </div> <style type="text/css"> .bg{ background-image: url("https://pic4.zhimg.com/80/v2-052324a9dae7a6feb2e5ddf6f68ad8c6_720w.jpg"); background-repeat: repeat; } em{ color: red; font-style: normal; } table{ border-spacing: 0; width: 100%; border: 1px solid black; } thead th { border: 1px solid black; /*text-align: center;*/ width: auto; color: blueviolet; } th{ border: 1px solid black; text-align: left; width: auto; background-color: #69ABD6; } td{ border: 1px solid black; text-align: left; } </style> </head> <body class="bg"> <table style="margin-bottom: -1px" class="left" id="table_top_header"> <h1 align="center" style="color:#0071B9 ; font-size:30px">MyProject Monitor Platform</h1> <h3 align="left" style="color:#0071B9 ; font-size:20px">Basic Information</h3> <tr> <th>Owner</th> <th>Project Name</th> <th>Running Environment</th> <th>Running System</th> <th>Running Equipment</th> <th>Testing Phase</th> <th>Monitor Frequency</th> <th>Test Report</th> </tr> <tr> <td>Charles</td> <td>MyProject</td> <td>Production</td> <td>Windows 10 64bit</td> <td>Wechat DevTools</td> <td>Regression Test</td> <td>One day/time</td> <td><a style="background: #eee4a6" href="http://ip:port/" target="_blank">Minium</a></td> </tr> </table> <table style="margin-bottom: -1px" class="left" id="table_analysis"> <tbody> <h3 align="left" style="color:#0071B9 ; font-size:20px">LiveData Analysis</h3> <tr> <th>Average Loading Time(sec)</th> <th>Percentage of Loading Time Less Than 10s</th> <th>Percentage of Loading Time Over 10s</th> </tr> <div > <div id="ChangelistTable0"> <tr> <td class="text-center" > <div> {% if content[2] %} <span style="background: #e8e272"> <a>{{content[2]}}</a> </span> {% else %} {% endif %} </div> </td> <td class="text-center" > <div> {% if content[3] %} <span style="background: #7ad9f4"> <a>{{content[3]}}</a> </span> {% else %} {% endif %} </div> </td> <td class="text-center" > <div> {% if content[4] %} <span style="background: #e999d4"> <a>{{content[4]}}</a> </span> {% else %} {% endif %} </div> </td> </tr> </div> </div> </tbody> </table> <table style="margin-bottom: -1px" class="left" id="table_header"> <tbody> <h3 align="left" style="color:#0071B9 ; font-size:20px">TestCase Failed</h3> <tr> <th>No</th> <th>Device Number</th> <th>Project Name</th> <th>Monitor Field</th> <th>Live Data</th> <th>Duration(s)</th> <th>Latest Case Executed Time</th> <th>Latest Case Ended Time</th> <th>Latest Case Result</th> </tr> <div > <div id="ChangelistTable1"> {% for i in range(content[1]| length) %} <tr> <td> {{ i+1 }} </td> <td class="text-center" > <div> {% if content[1][i][0] %} {{ content[1][i][0]| safe }} {% else %} {% endif %} </div> </td> <td> <div> {% if content[1][i][1] %} {{ content[1][i][1]| safe }} {% else %} {% endif %} </div> </td> <td class="text-center" > <div> {% if content[1][i][2] %} {{ content[1][i][2]| safe }} {% else %} {% endif %} </div> </td> <td class="text-center" > <div> {% if content[1][i][3] %} {{ content[1][i][3]| safe }} {% else %} {% endif %} </div> </td> <td class="text-center" > <div> {% if content[1][i][4] %} {% if content[1][i][4]<=10 %} <span style="background: lightseagreen"> <a>{{content[1][i][4]}}</a> </span> {% else %} <span style="background: lightcoral"> <a>{{content[1][i][4]}}</a></span> {% endif %} {% else %} {% endif %} </div> </td> <td class="text-center" > <div> {% if content[1][i][5] %} {{ content[1][i][5]| safe }} {% else %} {% endif %} </div> </td> <td class="text-center" > <div> {% if content[1][i][6] %} {{ content[1][i][6]| safe }} {% else %} {% endif %} </div> </td> <td class="text-center" > <div> {% if content[1][i][7] %} {% if content[1][i][7]=='pass' %} <span style="background: mediumseagreen"> <a>PASS</a> </span> <!-- <span style="background: #50d2c8"> <a href={{content[0][i][6]}} target="_blank">Pass</a> </span>--> {% elif content[1][i][7]=='fail' %} <span style="background: orangered"> <a>FAIL</a> </span> <!-- <span style="background: red"> <a href={{content[0][i][6]}} target="_blank">Fail</a> </span>--> {% else %} <span style="background: sandybrown"> <a>Scheduled</a> </span> {% endif %} {% else %} {% endif %} </div> </td> </tr> {% endfor %} </div> </div> </tbody> </table> <table style="margin-bottom: -1px" class="left" id="table_list"> <tbody> <h3 align="left" style="color:#0071B9 ; font-size:20px">TestCase Description</h3> <tr> <th>No</th> <th>Device Number</th> <th>Project Name</th> <th>Monitor Field</th> <th>Live Data</th> <th>Duration(s)</th> <th>Latest Triggered Time</th> <th>Latest Showed Time</th> <th>Latest Dropped Time</th> <th>Latest Case Executed Time</th> <th>Latest Case Ended Time</th> <th>Latest Case Result</th> </tr> <div > <div id="ChangelistTable"> {% for i in range(content[0]| length) %} <tr> <td> {{ i+1 }} </td> <td class="text-center" > <div>{{ content[0][i][0]| safe }}</div> </td> <td> <div>{{ content[0][i][1]| safe }}</div> </td> <td class="text-center" > <div>{{ content[0][i][2]| safe }}</div> </td> <td class="text-center" > <div> {% if content[0][i][3] %} {{ content[0][i][3]| safe }} {% else %} {% endif %} </div> </td> <td class="text-center" > <div> {% if content[0][i][4] %} {% if content[0][i][4]<=10 %} <span style="background: lightseagreen"> <a>{{content[0][i][4]}}</a> </span> {% else %} <span style="background: lightcoral"> <a>{{content[0][i][4]}}</a></span> {% endif %} {% else %} {% endif %} </div> </td> <td class="text-center" > <div> {% if content[0][i][5] %} {{ content[0][i][5]| safe }} {% else %} {% endif %} </div> </td> <td class="text-center" > <div> {% if content[0][i][6] %} {{ content[0][i][6]| safe }} {% else %} {% endif %} </div> </td> <td class="text-center" > <div> {% if content[0][i][7] %} {{ content[0][i][7]| safe }} {% else %} {% endif %} </div> </td> <td class="text-center" > <div> {% if content[0][i][8] %} {{ content[0][i][8]| safe }} {% else %} {% endif %} </div> </td> <td class="text-center" > <div> {% if content[0][i][9] %} {{ content[0][i][9]| safe }} {% else %} {% endif %} </div> </td> <td class="text-center" > <div> {% if content[0][i][10] %} {% if content[0][i][10]=='pass' %} <span style="background: mediumseagreen"> <a>PASS</a> </span> <!-- <span style="background: #50d2c8"> <a href={{content[0][i][6]}} target="_blank">Pass</a> </span>--> {% elif content[0][i][10]=='fail' %} <span style="background: orangered"> <a>FAIL</a> </span> <!-- <span style="background: red"> <a href={{content[0][i][6]}} target="_blank">Fail</a> </span>--> {% else %} <span style="background: sandybrown"> <a>Scheduled</a> </span> {% endif %} {% else %} {% endif %} </div> </td> </tr> {% endfor %} </div> </div> </tbody> </table> </body> </html> [代码] 在线查看报告 打开http://IP:PORT/,在线查看网页报告 [图片] 项目迁移到Jenkins平台 [图片] 创建一个新的Node节点 [图片] 节点名称为Application,标签为Monitoring [图片] 标签为Monitoring下的所有节点列表 [图片] 节点Application上的项目列表 [图片] 节点Monitoring_01上的项目列表 [图片] 测试机上部署Agent服务 将Jenkins平台上自动生成的jar包,放入对应的测试机上 [图片] 输入Jenkins平台上自动生成的命令,进行命令行启动 [代码]java -jar agent.jar -jnlpUrl http://XX.XX.XXX.XXX:8080/computer/Applications/jenkins-agent.jnlp -secret XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -workDir "C:\JenkinsWorkSpace\247" [代码] 验证Agent服务,是否成功连到Jenkins平台上 [图片] Jenkins上进行项目的配置部署 [图片] [图片] [图片] [图片] [图片] 通过Publish HTML reports插件,查看报告 [图片] [图片] 通过http://x.x.x.x:xxxx/ ,查看报告 [图片] 参考资料 https://git.weixin.qq.com/groups/minitest https://blog.csdn.net/jiangjunsss/article/details/120228371 https://blog.csdn.net/baguenaudier/article/details/124478687 https://blog.csdn.net/weixin_49546967/article/details/119858529 https://minitest.weixin.qq.com/#/minium/Python/readme https://developers.weixin.qq.com/community/develop/article/doc/0000cae3a58748ed7f2c8975351413 https://www.cnblogs.com/taiyonghai/p/9402734.html https://www.cnblogs.com/zpcdbky/p/15339213.html https://blog.csdn.net/qq_32265203/article/details/110088489 https://blog.csdn.net/qq_43813373/article/details/123116268
2022-08-22 - 小程序自动化实践方案总结:Minium + 云测
本文旨在为大家在着手做小程序自动化前提供参考,少踩坑。里面不会涉及细节的实践操作,主要讲解流程让大家对小程序自动化有个整体的印象。本文中存在的不足还望指出,多谢~😄 背景:由于每次迭代我们需要手动进行大量小程序用例回归,其中很多都是简单的组件操作,为了提升回归效率将更多的人力放在复杂场景回归和探索式测试中,因此希望通过小程序自动化来实现简单场景的回归用例。 要进行小程序自动化落地,需要先对下面2点进行了解: 全方位了解微信官方提供的小程序自动化能力的特点(云测、录制回放、Minium、虚拟账号)熟悉自己项目小程序的特性:是否支持一键登录、项目代码编译后元素class属性值是否会变、自己的公司是否是第三方服务商在了解小程序自动化能力之后,结合自己小程序特性,选择合适的方案进行落地。 下面陆续讲述的内容会涉及上面2点。 一、小程序自动化能力简介 1、云测整体流程 云测平台可以基于你上传的用例创建相应的测试计划,然后选择小程序不同版本(开发版、体验版、线上版)、手机系统类型(安卓和ios)、测试账号等根据测试计划创建任务,跑测完毕可以通过短信和邮件通知测试结果。 [图片] [图片] 2、录制回放 如果想要把录制回放的用例上传到云测,必须使用虚拟账号进行录制。这里有篇腾讯团队内部实践的文章。 3、Minium 要进行Minium用例编写,前提是搭建好小程序开发环境,这个可以询问自己项目组的前端开发。下图是以我们组内实践为例画的流程图,让大家对如何运行Minium脚本有个概念。图内的切换小程序,若你们不是第三方服务商则不涉及;云盾是我们公司的用例管理执行平台,同理也可以替换为你们自己的用例平台,将跑测完的结果同步到用例平台。 编写用例也是采用的PO模式,在微信开发中工具上进行元素定位,整体思路与写web自动化差异不大,这里可以参考有赞的文章,就不赘述了。 特别注意:若使用的是wxss选择器定位元素,一定要确认元素的class属性值会不会在每次编译小程序的时候发生改变,若会的话强烈建议使用xpath进行定位。 [图片] 4、虚拟账号 虚拟账号常用在录制回放和云测跑测试任务的时候。 若小程序必须登录才能使用,且符合截图中标记的这3项之一,就不能通过云测平台来跑自己录制或编写的用例了。 [图片] 5、持续集成 目前提供了在云测平台创建测试任务和获取测试任务结果的接口,所以可以搭配jenkins使用。举个例子:调用接口创建测试任务----调用接口查询任务结果----将结果同步到用例平台或定制化测试结果邮件 二、方案选择 1、3种自动化方式对比 [图片] 2、方案选择 [图片] 页面多少决定选择录制还是minium,是否适用虚拟账号决定能否用云测,用例执行时间决定本地还是云测运行更方便(当然全部跑本地也行啊~) 选择云测平台跑测的一个好处是基于真机去跑,据了解后面可能会支持付费选择手机机型和付费增加跑测时间。还有一点就是不用自己去倒腾本地配置真机运行(😂我现在都还没实践本地真机运行)。 3、举我们产品这个例子 我们作为小程序服务提供商小程序页面有上百个,绝大多数场景对账号没有要求,落地方案是:minium+云测。 所以每次迭代发布,只需运行一下测试任务的脚本便可坐等测试结果就好。 三、基于项目编写Minium用例 1、目录结构 [图片] 2、用例编写流程 若小程序页面不是非配置化的,可以省去配置这一步;若不需要同步测试结果到用例平台,可以省去最后一步。 [图片] 3、基于下面这个页面编写用例,步骤如下。 分别在page、logic、case目录下,分别创建allHouseListPage、AllHouseListLogic、AllHouseListCaseAllHouseListPage继承BasePage类,编写页面中各个组件的元素定位和操作方法AllHouseListLogic编写操作流程AllHouseListCase继承minium.MiniTest类,因为是固定在该页面执行用例,每个组件操作的前置步骤都是先进入该页面,所以在setUp里面添加进入该页面的前置操作(举一反三,其它页面的case的前置操作也是如此)。[图片] 4、tips 页面跳转后最好多用wait_for等待元素加载完,不然用例出现找不到元素的报错定位的元素必须绑定了事件,才能触发操作不熟悉怎么写的时候,可以先通过录制脚本同步到云测平台后,查看录制脚本进行参考 四、快速Monkey 只有服务商才有快速Monkey入口,这个我目前用的少,个人见解是每次有小的改动发了bg分支时,可以挑选一些流量大的甲方爸爸小程序进行冒烟测试。
2022-08-08 - 小程序自动销毁后的使用体验优化
2023年12月24日更新 经过测试发现,官方提供的onSaveExitState有一些问题,不太符合官方文档提出的预期,大家暂时慎重使用onSaveExitState功能,用其他方案吧。 ======================================================================== 假设用户在小程序内进行一个答题的活动,或者进行一个测试,这个活动或测试的时间比较长,大概需要10分钟的时间。当用户答题进行到一半的时候,来了一个重要的电话,电话打了十几分钟,回来之后想着继续进行操作,发现小程序是重新打开的状态。之前答题答了5分钟,白费了。这样,用户需要重新进行答题。 问题场景分析 用户离开小程序时间太久(官方说30分钟以上,但测试十几分钟分钟以上)或者手机内存不够用的时候,小程序会被销毁,也就是完全终止运行了。此时用户再想进入小程序进行之前的操作,只能重新操作一遍。 解决方案 以本场景为例,如果用户正在答题,在用户退出小程序的时候,将当前页面的答题进度数据进行一个保存,当用户再重新进入小程序的时候,检查是否有答题进行一半的数据。如果有,自动跳转到答题的页面,并且在onload中恢复退出之前状态的数据,让用户继续进行答题的操作。 微信小程序有一个非常好用的回调函数onSaveExitState。 退出状态onSaveExitState 每当小程序可能被销毁之前,页面回调函数 [代码]onSaveExitState[代码] 会被调用。如果想保留页面中的状态,可以在这个回调函数中“保存”一些数据,下次启动时可以通过 [代码]exitState[代码] 获得这些已保存数据。 代码示例: { "restartStrategy": "homePageAndLatestPage" } Page({ onLoad: function() { var prevExitState = this.exitState // 尝试获得上一次退出前 onSaveExitState 保存的数据 if (prevExitState !== undefined) { // 如果是根据 restartStrategy 配置进行的冷启动,就可以获取到 prevExitState.myDataField === 'myData' } }, onSaveExitState: function() { var exitState = { myDataField: 'myData' } // 需要保存的数据 return { data: exitState, expireTimeStamp: Date.now() + 24 * 60 * 60 * 1000 // 超时时刻 } } }) onSaveExitState 返回值可以包含两项: 字段名 类型 含义 data Any 需要保存的数据(只能是 JSON 兼容的数据) expireTimeStamp Number 超时时刻,在这个时刻后,保存的数据保证一定被丢弃,默认为 (当前时刻 + 1 天) 一个更完整的示例:在开发者工具中预览效果 注意事项如果超过 [代码]expireTimeStamp[代码] ,保存的数据将被丢弃,且冷启动时不遵循 [代码]restartStrategy[代码] 的配置,而是直接从首页冷启动。[代码]expireTimeStamp[代码] 有可能被自动提前,如微信客户端需要清理数据的时候。在小程序存活期间, [代码]onSaveExitState[代码] 可能会被多次调用,此时以最后一次的调用结果作为最终结果。在某些特殊情况下(如微信客户端直接被系统杀死),这个方法将不会被调用,下次冷启动也不遵循 [代码]restartStrategy[代码] 的配置,而是直接从首页冷启动。
2023-12-24 - 小程序销毁的时机
小程序会被销毁的三大场景: 1 当钱小程序进入后台后,如果很长时间-目前是 30 分钟-后没有再次进入,小程序会被销毁。 2 当小程序占用系统资源过高,会被系统销毁或被微信客户端主动回收。 3 在 iOS 上,当微信客户端在一定时间间隔内连续收到系统内存告警时,会根据一定的策略,主动销毁小程序,并提示用户 (运行内存不足,请重新打开该小程序)。 如果小程序中有过多占用内存的场景,建议使用 wx.onMemoryWarning 监听内存告警事件,进行必要的内存清理。
2022-05-21 - 微信小程序适配企业微信的通讯录组件 ww-open-data 【踩坑记录】
微信原生小程序中使用 第一步:在 app.json 中配置通讯录插件 [代码]"plugins": { "contactPlugin": { "version": "1.2.3", "provider": "wx5917c8c26f85c588" } } [代码] 第二步:在使用 ww-open-data 组件的页面 json文件中引入插件 [代码]"usingComponents": { "ww-open-data": "plugin://contactPlugin/ww-open-data" } [代码] uni-app 小程序中使用 第一步:在 manifest.json 微信模块中配置通讯录插件 [代码]"mp-weixin" : { "plugins": { "contactPlugin": { "version": "1.2.3", "provider": "wx5917c8c26f85c588" } } } [代码] 第二步:在 pages.json 中全局引入插件 [代码]"globalStyle": { "usingComponents": { "ww-open-data":"plugin://contactPlugin/ww-open-data" } } [代码] 踩坑记录 Q1:微信插件接口报错 【result: -300】 A1:小程序未在对应的企业下安装测试【具体安装步骤见下方:(企业微信安装测试小程序)】 Q2:微信插件接口报错 【result: {errCode: 1, humanMessage: “数据不存在”}】 A2:一定要按照官方企微通讯录文档中的版本使用,【我用的是 1.2.3/1.2.1这两个版本都可以】文档地址:https://developer.work.weixin.qq.com/document/path/91958#3-小程序方案 Q3:微信开发工具提示【查询失败,请退出重试】 [图片] A3:同 Q1 解决方式相同 企业微信安装测试小程序 第一步:在企微管理后台进入服务商后台 第二步:在服务商后台关联微信小程序 [图片] 第三步:在关联小程序中安装测试企业 [图片] 注意 1、只有在关联小程序中安装的测试企业,测试企业中的通讯录便可在微信小程序中正常显示 2、本次适配微信小程序开发工具使用小程序模式,而非企业微信小程序模式 文档地址 企微官方文档
2022-05-19 - 07.Taro框架获取用户手机号解决方案
流程图 [图片] 核心代码 PhoneAuth 组件 [代码]import Taro from '@tarojs/taro'; import React, { useCallback, useEffect, useState } from 'react'; import { View, Button } from '@tarojs/components'; import { PLATFORM_TYPE } from '@/constant/index'; import tools from '@/utils/tools'; import './index.less'; // 工具方法 const PhoneManager: TaroMiniApp.IUtilsPhoneManager = { // 老版本: https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/deprecatedGetPhoneNumber.html async oldVersionGetPhoneNumber(evt: TaroMiniApp.TGetPhoneNumberEvtDetail): Promise<TaroMiniApp.TaroApiResult> { return Taro.login() .then((codeRes: Taro.login.SuccessCallbackResult) => { // console.log(codeRes, evt); return this.getPhoneNumber(evt, codeRes.code) }) }, // 新版本: https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/getPhoneNumber.html async newVersionGetPhoneNumber(evt: TaroMiniApp.TGetPhoneNumberEvtDetail): Promise<any> { console.log(evt); return this.getPhoneNumber(evt); }, // 获取支付宝绑定的手机号 async handleGetAliPayPhoneNumber(): Promise<TaroMiniApp.TaroApiResult> { try { const res = await (Taro as any).getPhoneNumber(); const data = JSON.parse(res.response); if (data.response.code !== 0) { return Promise.reject({ code: data.response.code, errMsg: 'getPhoneNumber:fail', data: { error: data.response }, }) } // TODO: 支付宝 return this.getPhoneNumber(data); } catch (error) { return Promise.reject({ code: 10, errMsg: 'getPhoneNumber:fail', data: { error: error }, }) } }, // TODO: 对接后端服务 https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/getPhoneNumber.html getPhoneNumber(evtDetail: TaroMiniApp.TGetPhoneNumberEvtDetail, code?: string): Promise<TaroMiniApp.TaroApiResult> { // console.log(evtDetail, code); return Promise.resolve({ code: 0, errMsg: evtDetail?.detail?.errMsg, data: { phone: `121212-${code}` }, }) }, // 处理获取手机号事件 async handleGetPhoneNumberEvt(evt?: TaroMiniApp.TGetPhoneNumberEvtDetail): Promise<TaroMiniApp.TGetPhoneNumberResult> { if (PLATFORM_TYPE === 'WEAPP') { if (evt?.detail?.errMsg.indexOf('deny') !== -1) { return Promise.reject({ errMsg: evt?.detail.errMsg, code: 10, }) } // 获取系统信息 const { SDKVersion } = tools.getSystemInfo(); // console.log(SDKVersion, tools.compareVersion(SDKVersion, '2.21.2')); // 版本号对比 if (tools.compareVersion(SDKVersion, '2.21.2') === -1) { return this.oldVersionGetPhoneNumber(evt); } // new version return this.newVersionGetPhoneNumber(evt); } if (PLATFORM_TYPE === 'ALIPAY') { return this.handleGetAliPayPhoneNumber(); } const res = { code: 10, errMsg: 'getPhoneNumber: noSupport', data: {}, }; return Promise.resolve(res); }, } const PhoneAuth = (props: TaroMiniApp.IComponentsPhoneAuthProps) => { const [phoneNumberAuth, setPhoneNumber] = useState(false); useEffect(() => { if (PLATFORM_TYPE === 'ALIPAY') { Taro.getSetting() .then((res: Taro.getSetting.SuccessCallbackResult) => { setPhoneNumber((res.authSetting as any).phoneNumber); }); } }, []); const handleGetPhoneNumber = useCallback(async (evt) => { if (PLATFORM_TYPE === 'WEAPP') { PhoneManager.handleGetPhoneNumberEvt(evt).then((res) => { props.onSuccess(res) }).catch((err) => { props.onFail(err) }) return; } if (PLATFORM_TYPE === 'ALIPAY') { PhoneManager.handleGetPhoneNumberEvt().then((res) => { props.onSuccess(res) }).catch((err) => { props.onFail(err) }) } }, []); const getManualPhoneNumber = useCallback(async () => { PhoneManager.handleGetPhoneNumberEvt().then((res) => { props.onSuccess(res) }).catch((err) => { props.onFail(err) }) }, []); const onAuthError = useCallback((err) => { props.onFail({ code: 11, errMsg: 'getPhoneNumber: deny', data: err.detail }) }, []); const btnTxt = props.children || props.btnText || '手机号登录'; if (PLATFORM_TYPE !== 'ALIPAY' && PLATFORM_TYPE !== 'WEAPP') { return <View>不支持</View> } return ( <View className="phoneAuthContainer"> { PLATFORM_TYPE === 'WEAPP' && ( <Button style={`${props.btnStyle}`} className={`${props.uesSlot ? 'resetBtn' : ''}`} openType="getPhoneNumber" onGetPhoneNumber={handleGetPhoneNumber} >{btnTxt}</Button> ) } {/* 支付宝已授权用户 */} { PLATFORM_TYPE === 'ALIPAY' && !phoneNumberAuth && ( <Button style={`${props.btnStyle}`} className={`${props.uesSlot ? 'resetBtn' : ''}`} openType="getAuthorize" scope='phoneNumber' onGetAuthorize={handleGetPhoneNumber} onError={onAuthError} >{btnTxt}</Button> ) } {/* 支付宝未授权用户 */} { PLATFORM_TYPE === 'ALIPAY' && phoneNumberAuth && ( <Button style={`${props.btnStyle}`} className={`${props.uesSlot ? 'resetBtn' : ''}`} onClick={getManualPhoneNumber} >{btnTxt}</Button> ) } </View> ); } export default PhoneAuth; [代码] 如何使用 [代码]import React, { useCallback } from 'react'; import { View, } from '@tarojs/components'; // 引入的 PhoneAuth 组件 import { PhoneAuth } from '@/components/index'; import './index.less'; export default function PhoneAuthDemo() { const handleSuccess = useCallback((res) => { console.log('success:', res); }, []); const handleFail = useCallback((err) => { console.log('fail:', err); }, []); const resetBtnStyle = "display: flex; background: #eee"; return ( <View className="page"> <View className='wrap'> <PhoneAuth onSuccess={handleSuccess} onFail={handleFail} /> </View> <View className='wrap'> <PhoneAuth onSuccess={handleSuccess} onFail={handleFail} btnText="自定义文本" /> </View> <View className='wrap'> <PhoneAuth onSuccess={handleSuccess} onFail={handleFail} btnStyle={resetBtnStyle} uesSlot> <View>我是自定义 slot</View> </PhoneAuth> </View> </View> ) } [代码]
2022-03-03 - 小程序在web-view中跳转小程序,提示“当前小程序无法打开***小程序”
每个小程序可跳转的其他小程序数量限制为不超过10个,在web-view中打开的小程序也包含在这个限制中。 从 基础库2.4.0 版本以及指定日期(参考公告)开始,开发者提交新版小程序代码时,如使用了跳转其他小程序功能,则需要在代码配置中声明将要跳转的小程序名单,限定不超过 10 个,否则将无法通过审核。该名单可在发布新版时更新,不支持动态修改。配置方法详见 小程序全局配置。
2019-11-11 - 小程序官方压测工具上线通知
“小程序压测”工具是微信小程序团队为开发者提供小程序压力测试解决方案,帮助企业最大限度真实还原业务的海量高并发等复杂场景,提早发现并解决问题,大幅降低传统测试成本,高效检验和管理业务性能,为业务保驾护航。 一、应用场景 适用于促销抢购、限量秒杀、直播卖货等流量峰值明显的业务,可模拟海量高并发,提前发现流量峰值问题、降低传统测试成本二、能力优势 真实模拟:微信独家小程序爬虫技术真实模拟微信用户打开小程序的行为,实现全链路压测的功能报告详细:压测报告包含页面加载、网络请求的多种结果指标,帮助开发者更好发现瓶颈、定位问题操作简单:全界面化操作,可灵活配置并发数、压测时间长,支持选择不同版本进行压测,更好地适配不同业务场景三、使用指南 工具免费使用,详情请在PC端访问:https://fuwu.weixin.qq.com/service/detail/000c24bafd0bb8e3794cbfa505c015 操作指南请访问:https://developers.weixin.qq.com/doc/oplatform/service_market/buyer_guideline/tool/use.html
2021-06-17 - 手机号授权,有时需要验证码
正常逻辑,一段时间内未验证的手机号,就需要重新验证。
2020-10-16 - 云开发短信跳小程序(自定义开发版)教程
写在前面如果你想要自主开发,但没有云开发相关经验,可以采用演示视频来学习本教程: [视频] 一、能力介绍境内非个人主体的认证的小程序,开通静态网站后,可以免鉴权下发支持跳转到相应小程序的短信。短信中会包含支持在微信内或微信外打开的静态网站链接,用户打开页面后可一键跳转至你的小程序。 这个链接的网页在外部浏览器是通过 URL Scheme 的方式来拉起微信打开主体小程序的。 总之,短信跳转能力的实现分为两个步骤,「配置拉起网页」和「发送短信」。本教程将介绍如何执行操作完成短信跳转小程序的能力。 如果你想要无需写代码就能完成短信跳转小程序的能力,可以参照无代码版教程进行逐步实现。 二、操作指引1、网页创建首先我们需要构建一个基础的网页应用,在任何代码编辑器创建一个 html 文件,在教程这里命名为 index.html 在这个 html 文件中输入如下代码,并根据注释提示更换自己的信息: window.onload = function(){ window.web2weapp.init({ appId: 'wx999999', //替换为自己小程序的AppID gh_ID: 'gh_999999',//替换为自己小程序的原始ID env_ID: 'tcb-env',//替换小程序底下云开发环境ID function: { name:'openMini',//提供UrlScheme服务的云函数名称 data:{} //向这个云函数中传入的自定义参数 }, path: 'pages/index/index.html' //打开小程序时的路径 }) } 以上引入的 web2weapp.js 文件是教程封装的有关拉起微信小程序的极简应用,我们直接引用即可轻松使用。 如果你想进一步学习和修改其中的一些WEB展示信息,可以前往 github 获取源码并做修改。 有关于网页拉起小程序的更多信息可以访问官方文档 如果你只想体验短信跳转功能,在执行完上述文件创建操作后,继续以下步骤。 2、创建服务云函数在上面创建网页的过程中,需要填写一个UrlScheme服务云函数。这个云函数主要用来调用微信服务端能力,获取对应的Scheme信息返回给调用前端。 我们在示例中填写的是 openMini 这个命名的云函数。 我们前往微信开发者工具,定位对应的云开发环境,创建一个云函数,名称叫做 openMini 。 在云函数目录中 index.js 文件替换输入以下代码: const cloud = require('wx-server-sdk') cloud.init() exports.main = async (event, context) => { return cloud.openapi.urlscheme.generate({ jumpWxa: { path: '', // 打开小程序时访问路径,为空则会进入主页 query: '',// 可以使用 event 传入的数据制作特定参数,无需求则为空 }, isExpire: true, //是否到期失效,如果为true需要填写到期时间,默认false expire_time: Math.round(new Date().getTime()/1000) + 3600 //我们设置为当前时间3600秒后,也就是1小时后失效 //无需求可以去掉这两个参数(isExpire,expire_time) }) } 保存代码后,在 index.js 右键,选择增量更新文件即可更新成功。 接下来,我们需要开启云函数的未登录访问权限。进入小程序云开发控制台,转到设置-权限设置,找到下方未登录,选择上几步我们统一操作的那个云开发环境(注意:第一步配置的云开发环境和云函数所在的环境,还有此步操作的环境要一致),勾选打开未登录 [图片] 接下来,前往云函数控制台,点击云函数权限,安全规则最后的修改,在弹出框中按如下配置: [图片] 3、本地测试我们在本地浏览器打开第一步创建的 index.html ;唤出控制台,如果效果如下图则证明成功! 需要注意,此处本地打开需要时HTTP协议,建议使用live server等扩展打开。不要直接在资源管理器打开到浏览器,会有跨域的问题! [图片] 4、上传本地创建好的 index.html 至静态网站托管将本地创建好的 index.html 上传至静态网站托管,在这里静态托管需要是小程序本身的云开发环境里的静态托管。 如果你上传至其他静态托管或者是服务器,你仍然可以使用外部浏览器拉起小程序的能力,但会丧失在微信浏览器用开放标签拉起小程序的功能,也不会享受到云开发短信发送跳转链接的能力。 如果你的目标小程序底下有多个云开发环境,则不需要保证云函数和静态托管在一个环境中,无所谓。 比如你有A、B两个环境,A部署了上述的云函数,但是把 index.html 部署到B的环境静态托管中了,这个是没问题的,符合各项能力要求。只需要保证第一步 index.html 网页中的云开发环境配置是云函数所在环境即可。 部署成功后,你便可以访问静态托管的所在地址了,可以通过手机外部浏览器以及微信内部浏览器测试打开小程序的能力了。 5、短信发送云函数的配置在上面创建 openMini 云函数的环境中再来一个云函数,名字叫 sendsms 。 在此云函数 index.js 中配置如下代码: const cloud = require('wx-server-sdk') cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV, }) exports.main = async (event, context) => { try { const config = { env: event.env, content: event.content ? event.content : '发布了短信跳转小程序的新能力', path: event.path, phoneNumberList: event.number } const result = await cloud.openapi.cloudbase.sendSms(config) return result } catch (err) { return err } } 保存代码后,在 index.js 右键,选择增量更新文件即可更新成功。 6、测试短信发送能力在小程序代码中,在 app.js 初始化云开发后,调用云函数,示例代码如下: App({ onLaunch: function () { wx.cloud.init({ env:"tcb-env", //短信云函数所在环境ID traceUser: true }) wx.cloud.callFunction({ name:'sendsms', data:{ "env": "tcb-env",//网页上传的静态托管的环境ID "path":"/index.html",//上传的网页相对根目录的地址,如果是根目录则为/index.html "number":[ "+8616599997777" //你要发送短信的目标手机,前面需要添加「+86」 ] },success(res){ console.log(res) } }) } }) 重新编译运行后,在控制台中看到如下输出,即为测试成功: [图片] 你会在发送的目标手机中收到短信,因为短信中包含「退订回复T」字段,可能会触发手机的自动拦截机制,需要手动在拦截短信中查看。 需要注意:你可以把短信云函数和URLScheme云函数分别放置在不同云开发环境中,但必须保证所放置的云开发环境属于你操作的小程序 另外,出于防止滥用考虑,短信发送的云调用能力需要真实小程序用户访问才可以生效,你不能使用云端测试、云开发JS-SDK以及其他非wx.cloud调用方式(微信侧WEB-SDK除外),会提示如下错误: [图片] 如果你想在其他处使用此能力,可以使用服务端API来做正常HTTP调用,具体访问官方文档 7、查看短信监控图表进入 云开发控制台 > 运营分析 > 监控图表 > 短信监控,即可查看短信监控曲线图、短信发送记录。 [图片] 三、总结短信跳转小程序核心是静态网站中配置的可跳转网页,外部浏览器通过URL Scheme 来实现的,这个方式不适用于微信浏览器,需要使用开放标签才可以URL Scheme的生成是云调用能力,需要是目标小程序的云开发环境的云函数中使用才可以。并且生成的URL Scheme只能是自己小程序的打开链接,不能是任意小程序(和开放标签的任意不一致)短信发送能力的体验是每个有免费配额的环境首月100条,如有超过额度的需求可前往开发者工具-云开发控制台-对应按量付费环境-资源包-短信资源包,进行购买。如当前资源包无法满足需求也可通过云开发 工单 提交申请[图片]短信发送也是云调用能力,需要真实小程序用户调用才可以正常触发,其他方式均报错返回参数错误,出于防止滥用考虑云函数和网页的放置可以不在同一个环境中,只需要保证所属小程序一致即可。(需要保证对应环境ID都能接通)如果你不需要短信能力,可以忽略最后两个步骤CMS配置渠道投放、数据统计可参考官方文档
2021-04-07 - 绑定在一个开发者帐号下,同个用户获取到的unionID不一样
只要绑在一个开发者帐号下,即使主体不一样,也允许获取到统一的unionID。绑定同一个微信开放平台帐号下,同一个用户的unionID如果不同的,原因只能是开发者搞混openid。openid要对应所属的AppID,才会相同。 举个例子: 1. 小程序AppID:wxc104eb635b8cxxxx ——帐号A, 公众号AppID:wx311a2a9a8e1dxxxx ——帐号B, 2.核实帐号A和帐号B 绑定同一个微信开放平台帐号是:xxxxxx@sina.com ,所以用一个用户的unionID相同, 3.而开发者所反馈的出现unionID不同,原因是:所提供的openid不属于帐号A,也不属于帐号B,而是属于帐号C或帐号D,而帐号C或帐号D并没有绑定在同一个微信开放平台帐号下,所以unionID不同
2019-09-16 - 小程序如何上架到企业微信中的第三方?
小程序需先上线再上架,小程序上架流程与H5应用上架流程一致。 企业微信上架流程可参考: https://work.weixin.qq.com/api/doc#90001/90146/90569 https://work.weixin.qq.com/api/doc#90001/90142/90595
2019-12-31 - “小程序直播”支持服务商接入
各位微信开发者: “小程序直播” 功能正在公测中,目前已支持服务商接入。 服务商申请权限之后,可帮助商户快速实现小程序直播功能。 功能简介 “小程序直播” 是微信官方提供的商家经营工具。符合接入要求的商家,通过直播组件,可以在小程序中实现直播互动与商品销售的闭环。 商户准入要求 满足以下条件的电商平台、自营商家,即有机会被邀请到小程序直播公测中来: (同时满足以下1、2、3条件,加上4、5、6条件的其中之一即可。) 1. 属于小程序直播开放类目,具体见《微信小程序直播功能准入要求》 2. 主体下小程序近半年没有严重违规 3. 小程序近90天存在支付行为 4. 主体下公众号累计粉丝数大于100 5. 主体下小程序近7日dau大于100 6. 主体在微信生态内近一年广告投放实际消耗金额大于1w 服务商接入指引 具体接入指引请参考《【小程序直播】服务商接入指引》,以下为服务商接入步骤。 1. 权限申请 1) 在问卷《服务商“小程序直播”接入申请》填写相关信息并等待权限开通,发送申请后7个工作日内,可登陆微信开放平台查看第三方平台权限集并勾选 “小程序直播” 能力; 2) 开通后,即可登陆 “微信开放平台” (open.weixin.qq.com)勾选 “小程序直播” 第三方权限集并全网发布; 2.功能开发 小程序直播需要实现【直播组件】与【后台API】两个部分,其中组件部分需要在小程序中进行配置开发。 具体开发文档,请参考《小程序直播组件接入指引》。
2020-05-18 - 小程序加急审核流程上线
为优化小程序审核体验,配合各位开发者解决小程序的紧急迭代需求。平台上线了加急审核流程,开发者可根据自身业务情况进行审核加速申请。 1.加急申请入口 符合条件用户在审核提交页面【审核加急】,选择【加急】并填写【“加急类型”“ 加急说明”】情况后提交审核。 [图片] 2.加急次数说明 (1)非个人主体类型:每个自然年有3次申请加急机会 (2)个人主体类型:每个自然年有1次申请加急机会 注:①提审勾选加急后,如在审核前撤回,机会不被消耗;如加急审核单已被审核,无论审核结果通过/不通过,加急机会都将被消耗。请开发者谨慎合理使用加急机会; ②如勾选加急后,审核单被驳回。开发者在12小时内再次提交审核或者通过驳回站内信内的【前往反馈页面】提交反馈,可获得相对加急的审核队列。 3.加急审核时间段与审核时长 (1)非个人主体类型:00:00-23:59 (2)个人主体类型:9:00-22:00 审核时长:正常加急审核预计2小时内完成。请开发者结合审核工作时间及加急单等待时长综合评估提审加急单的时间。 注意:如遇节假日如春节假期前等加急提审队列拥挤,或小程序代码包含复杂逻辑等特殊情况,将无法保证加急审核在2小时内完成。 4. 以下情形的代码提审单暂不支持加急审核 选择国内主体的以下类目或选择海外主体后首次提交代码审核,需报属地网信部门复核,预计审核时长7天左右,暂不支持加急审核。 [图片] 加急审核机会是根据平台审核资源调配,配合开发者遇重大提审节点快速审核迭代的体验优化。每个小程序的加急额度是有限的,请提交前自行检查,确保加速版本的小程序符合法律法规和平台规则,避免浪费有效加急机会。同时,开发者也可以通过小程序评测达标来获取更多的加急机会。 加急审核机制上线后,我们会根据开发者的使用额度情况及审核资源情况等,对目前加速审核机制进行动态调整与优化。
2022-07-26