- 小程序收藏问题
小程序webview打开的链接,收藏后无法从微信-我的-收藏打开,链接被拼接了http,怎么修复?[图片]
2023-06-28 - Android微信内网页音频自动播放能力调整
各位开发者: 微信内网页在不经过用户允许的情况下自动播放音频,会导致用户没有预期、用户体验差等问题。此外,音频自动播放可能会在用户不知情的情况下给用户带来较大的流量消耗。基于此,微信公众平台将对微信内网页自动播放音频能力进行如下调整: 自2020年04月28日起,用户打开微信内网页时将无法自动播放音频。必须在得到用户手动允许(比如触摸屏幕,单击按钮等)的情况下,才能播放音频,开发者可以提前进行适配调试。 具体的适配调试方法如下: 1、下载安装包:http://dldir1.qq.com/weixin/android/wechat_0x27000C70_1620_autoplay.apk 2、扫描二维码安装浏览器内核 [图片] 3、打开网页,点击右上方菜单,第二行最后一项禁止自动播放(注:再点一次可以变回允许自动播放) [图片] 4、退出页面,重新进入即可体验禁止自动播放的效果。 附测试用例:https://docs.qq.com/sheet/DWlduYWxhVEFkRkVa?tab=BB08J2&c=B12A0F0 微信团队 2020年03月30日
2020-03-30 - 非微信环境网页如何拉起微信登录
前言:微信网页登录文档 A https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html B https://developers.weixin.qq.com/doc/oplatform/Website_App/WeChat_Login/Wechat_Login.html 1、 检测网页环境 https://www.cnblogs.com/wangtz/p/12538850.html 2、 若pc调用 上面B的API即可实现 3 、若h5调用 则需要调以下的链接格式 https://open.weixin.qq.com/sns/explorer_broker?appid=wx62ba64796e13f092&redirect_uri=https%3A%2F%2Faccount.xiaomi.com%2Fpass%2Fsns%2Flogin%2Fload&response_type=code&scope=snsapi_userinfo&state=STATE_685333#wechat_redirect 4、小米案例 a、登录链接 https://account.xiaomi.com/pass/serviceLogin?callback=https%3A%2F%2Fsupport.kefu.mi.com%2Fsts%3Fsign%3DjorTe2AgW3OD2juZGL2HJC73mjQ%253D%26followup%3Dhttp%253A%252F%252Fsupport.kefu.mi.com%252Fpage%252Findex%252Fv2%253Ftag%253Dcn%2526token%253DY24ud2ViLm1pLmh0dHBzLm1vYmlsZSNyZWZlcmVy%2526productId%253D9452%2526goodsId%253D2191000003&sid=mcc_chat_fe b、pc拦截 https://open.weixin.qq.com/connect/qrconnect?appid=wxa21de3acc0d5e79b&redirect_uri=https%3A%2F%2Fsns.account.xiaomi.com%2Fpass%2Fsns%2Flogin%2Fload&response_type=code&scope=snsapi_login&state=STATE_889829#wechat_redirect c、h5的拦截链接 https://open.weixin.qq.com/sns/explorer_broker?appid=wx62ba64796e13f092&redirect_uri=https%3A%2F%2Faccount.xiaomi.com%2Fpass%2Fsns%2Flogin%2Fload&response_type=code&scope=snsapi_userinfo&state=STATE_685333#wechat_redirect 附:参考案例博客 https://blog.csdn.net/tiantian082054/article/details/90711900 https://www.jianshu.com/p/a1f357152dca 备注:主来自一位社区开发者的问题解决的整理:https://developers.weixin.qq.com/community/develop/doc/0004caea6a836871524a7402156000
2020-05-21 - 基于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 - vue本地开发,使用mplogin登录方案实现便捷网页授权流程方案。
因为mplogin必须是https请求,所以vue本地项目如何加载ssl证书实现https正常访问的问题给我折腾了两天,终于算是解决了。 两个注意点: 1,本机记得安装nginx,用于实现监听443端口以及加载ssl证书; 2,授权页面循环跳转的问题,记得配置/__wx__/的转发规则,在nginx上将/__wx__/转发到https://servicewechat.com/wxa-qbase/ 实现流程: 假设mp后台配置得授权回调域名是:www.weixin.com; 1,修改host文件,将www.weixin.com域名指向本机127.0.0.1,如果不修改也可以,使用内网穿透工具进行端口映射,也可以将你的域名与本机ip+端口绑定。不过还是修改host文件来的方便; 2,为你的域名申请一个免费的ssl证书,可以再腾讯云去申请,一个域名可申请50个免费证书; 3,本机安装nginx,修改nginx配置文件,在server层增加443端口的监听,同时配置你的域名以及你的ssl证书路径; 4,在nginx中添加反向代理,将/__wx__/转发到https://servicewechat.com/wxa-qbase/ 如果有人遇到该问题不知道如何处理的话记得联系我,作为程序员,大家一定是以相互分享技术为快乐的。 可以联系我的微信:note744917
2022-06-15 - 使用echarts实现地图下钻功能
使用echarts实现地图下钻功能 1.支持点击地图区域展示其子内容,如点击四川展示四川的市 2.支持点击左上角多级导航,跳转回对应层级 由于代码片段最多只能支持1M,所以数据部分需要在启动后自行下载替换 数据源:https://datav.aliyun.com/portal/school/atlas/area_selector 效果展示: [图片][图片][图片] 代码片段:https://developers.weixin.qq.com/s/wiTATamT7UAl
2022-06-21 - 小程序用户头像昵称获取规则调整公告
更新时间:2022年11月9日由于 PC/macOS 平台「头像昵称填写能力」存在兼容性问题,对于来自低于2.27.1版本的访问,小程序通过 wx.getUserProfile 接口将正常返回用户头像昵称,插件通过 wx.getUserInfo 接口将正常返回用户头像昵称。 更新时间:2022年9月28日考虑到近期开发者对小程序用户头像昵称获取规则调整的相关反馈,平台将接口回收的截止时间由2022年10月25日延期至2022年11月8日24时。 调整背景在小程序内,开发者可以通过 wx.login 接口直接获取用户的 openId 与 unionId 信息,实现微信身份登录,支持开发者在多个小程序或其它应用间匿名关联同一用户。 同时,为了满足部分小程序业务中需要创建用户的昵称与头像的诉求,平台提供了 wx.getUserProfile 接口,支持在用户授权的前提下,快速使用自己的微信昵称头像。 但实践中发现有部分小程序,在用户刚打开小程序时就要求收集用户的微信昵称头像,或者在支付前等不合理路径上要求授权。如果用户拒绝授权,则无法使用小程序或相关功能。在已经获取用户的 openId 与 unionId 信息情况下,用户的微信昵称与头像并不是用户使用小程序的必要条件。为减少此类不合理的强迫授权情况,作出如下调整。 调整说明自 2022 年 10 月 25 日 24 时后(以下统称 “生效期” ),用户头像昵称获取规则将进行如下调整: 自生效期起,小程序 wx.getUserProfile 接口将被收回:生效期后发布的小程序新版本,通过 wx.getUserProfile 接口获取用户头像将统一返回默认灰色头像,昵称将统一返回 “微信用户”。生效期前发布的小程序版本不受影响,但如果要进行版本更新则需要进行适配。自生效期起,插件通过 wx.getUserInfo 接口获取用户昵称头像将被收回:生效期后发布的插件新版本,通过 wx.getUserInfo 接口获取用户头像将统一返回默认灰色头像,昵称将统一返回 “微信用户”。生效期前发布的插件版本不受影响,但如果要进行版本更新则需要进行适配。通过 wx.login 与 wx.getUserInfo 接口获取 openId、unionId 能力不受影响。「头像昵称填写能力」支持获取用户头像昵称:如业务需获取用户头像昵称,可以使用「头像昵称填写能力」(基础库 2.21.2 版本开始支持,覆盖iOS与安卓微信 8.0.16 以上版本),具体实践可见下方《最佳实践》。小程序 wx.getUserProfile 与插件 wx.getUserInfo 接口兼容基础库 2.27.1 以下版本的头像昵称获取需求:对于来自低版本的基础库与微信客户端的访问,小程序通过 wx.getUserProfile 接口将正常返回用户头像昵称,插件通过 wx.getUserInfo 接口将正常返回用户头像昵称,开发者可继续使用以上能力做向下兼容。对于上述 3,wx.getUserProfile 接口、wx.getUserInfo 接口、头像昵称填写能力的基础库版本支持能力详细对比见下表: [图片] *针对低版本基础库,兼容处理可参考 兼容文档 请已使用 wx.getUserProfile 接口的小程序开发者和已使用 wx.getUserInfo 接口的插件开发者尽快适配。小游戏不受本次调整影响。 最佳实践小程序可在个人中心或设置等页面使用头像昵称填写能力让用户完善个人资料: [图片] 微信团队 2022年5月9日
2023-09-26 - 【小程序代码自查】小程序闪退-内存泄露导致
背景用户经常出现闪退的情况,并提示内存不足。根据用户操作场景,猜测页面存在内存泄露。 内存泄露是什么?内存泄露是程序运行过程中产生的内存变量会一直存在,不会被垃圾回收机制检测到,导致一直不会被销毁,内存占用会越来越大。 比如说: 我们在运行小程序的时候会产生一个页面,小程序会给这个页面创建一个实例,当这个页面销毁的时候,这个实例应该会被销毁。 但是如果我们有个定时器(setInterval),定时器里面对这个页面实例存在引用,那这个页面实例就不会被销毁,因为有被用到。 当存在内存泄露的情况,用户长期使用我们的小程序会导致小程序占用的内存越来越大,最后会导致小程序闪退(被微信强制销毁) 排查内存泄露用到的工具-weakSet先简单描述一下weakSet,让大家有个简单的认识,详细需要去看下文档。 weakSet 是一个可以存储唯一变量的集合,和Set不一样的是,weakSet存储的变量都是弱引用,就是不会影响垃圾回收,如果存储的变量被回收了,在这个集合里面就找不到。 所以weakSet不能被遍历,也没有长度的概念。但是我们可以通过控制台打印weakset的指向,知道里面有多少个元素。如下图: [图片] 通过展开,我们可以知道里面是哪个页面的实例,但是我们在控制台展开就意味着我们对这个页面实例存在引用,则无法被垃圾回收。所以在执行垃圾回收之前需要清空控制台的输出。 如何确定页面是否存在内存泄露如果页面存在内存泄露则不会销毁页面实例。我们只需要判断页面实例有没有被销毁即可。 我们在一开始就把页面实例加到weakSet里面,当执行多次跳转页面之后,会存在多个页面实例,最后回到首页,触发小程序的垃圾回收。 如果不存在内存泄露,那weakSet集合里面只会存在两个页面实例(当前页面实例+返回回来的页面实例),比如下图的页面A和页面B。 如果存在内存泄露,那weakSet集合里面会存在多个页面实例(当前页面实例+存在内存泄露的页面实例*n),比如下图的页面A、页面B、页面C和页面D. 具体如下图: [图片] 如何主动触发小程序的垃圾回收小程序没有api可以让我们触发小程序的垃圾回收,我们目前可以通过开发者工具的performance面板或memory的垃圾回收(collect garbage 垃圾桶图标)按钮。 [图片] [图片] 触发垃圾回收之后的结果如图: [图片] 这个需要手动触发才可以,我们在测试的时候需要手动点击,无法自动触发,所以我们想了个方案自动触发垃圾回收。 通过给内存塞很多数据,然后将这些数据标为无用的,当内存达到500m左右小程序就会触发垃圾回收。这个办法会导致我们内存一段时间激增,建议尽量在跳转页面的时候不要开启,只有在最后页面跳转回首页才进行。 // 主动触发垃圾回收 setInterval(()=>{ if(!global.startGC){ return } let a = [] for (let i = 0; i < 10000000; i++) { a.push({ name: "pling", age: Math.random() * 10000 }) } console.log("length", a.length) a = [] }, 3000) 如何定位页面内存泄露的原因内存泄露的情况举例: global.list = [] Page({ // ... onLoad() { // ... 省略其他代码 // 将页面实例挂载到全局对象,如没有清理,则页面实例会一直不被销毁 global.list.push(this) // 存在Interval计时器,则会一直存在对页面实例的引用 setInterval(() => { console.log("test", this.data) }, 5000); // 通过settimeout的循环调用,实现了类似于interval的效果也会导致页面实例不会被销毁 this.testLoop() const that = this function test(){ console.log(that.data) } // 将内部函数挂载到全局变量,则会导致函数的作用域链都会存在引用,不会被销毁 global.logThis = test }, testLoop(){ setTimeout(() => { this.testLoop() }, 10000); } }) 通过上面我们可以知道一般会有上面四种情况导致内存泄露。 将对象挂载到全局对象上,页面写在没有清楚通过暴露内部函数给外部对象,导致存在作用域的引用,页面卸载没有清楚内部函数存在定时执行的函数存在对页面实例的引用,页面销毁没有清除定时器通过延时执行的函数循环调用,并存在对页面实例的引用,页面销毁没有停止调用。第一第二种情况会比较少出现,目前暂时还没考虑如何去排查。 第三第四种都会对页面实例存在调用,所以我们在页面实例销毁之后对页面实例上的属性进行监听,如果一直存在调用则会有问题。 [图片] 具体实现代码: // 检查页面卸载后对页面实例调用 Page({ data: { test: "111" }, onLoad() { global.pageSet.add(this) setInterval(() => { console.log("test", this.__wxExparserNodeId__, this.data.test) }, 5000); }, // .... onUnload(){ console.log("unload"); const that = this // 获得可以枚举的属性列表 const keys = Object.keys(that) // 加入data 因为data 不是可以枚举的属性 keys.push("data") console.log(keys); keys.map(key=>{ // 获得原本的属性描述 const property = Object.getOwnPropertyDescriptor(that, key) // 保留原有的值 const origin = that[key]; // 获得属性的get方法 有可能没有 const getter = property && property.get // 获得属性的set方法 有可能没有 const setter = property && property.set const isFunction = typeof origin === "function" // 如果是function的话 需要绑定this if(isFunction){ origin.bind(that) } const newThis = {} // 拦截属性 Object.defineProperty(that, key, { get: function(){ console.log(`调用了this.${key}的getter`); // 有getter 调用getter if(getter){ return getter.call(that) } return newThis[key] || origin }, set: function(newVal){ console.log(`调用了this.${key}的setter`); if(setter){ return setter.call(that, newVal) } newThis[key] = newVal } }) }) } }) 测试demo我们在自己项目里面测试会比较麻烦,一开始可能会有干扰,所以我这边弄了个代码片段,先校验一下这个方法是否可行,如果可行再加到自己的项目里面。 小程序代码片段
2021-05-12 - 小程序中的global
在开发过程中,发现可以用global存放一些东西。 比如 global.regeneratorRuntime = require('./utils/plugins/regenerator-runtime/runtime.js'); 又或者 global.userInfo = {}; 但是还有一种全局变量 App({ globalData:{ } }) 是官方文档上提供的。 因为没在官方文档中看到global变量,但实在又比较好用。就想问一下,使用global 会有什么问题,会出现什么bug,或者说会有什么坑吗? 用global跟用App({globalData})有什么区别吗? 求官方解答下
2019-03-12 - ios相机拍摄出来的照片直接上传后小程序显示旋转90度
实际是一张竖着的图片,在小程序里面Image显示就是90度选择横过来[图片] 了。
2018-12-06 - 开发文档窗口宽度为1080px时无法展开导航
如:https://developers.weixin.qq.com/miniprogram/dev/component/ 窗口宽度为1080px时点击【组件】无法展开导航 [图片][图片]
2021-04-20 - 小程序登录、用户信息相关接口调整说明
公告更新时间:2021年04月15日考虑到近期开发者对小程序登录、用户信息相关接口调整的相关反馈,为优化开发者调整接口的体验,回收wx.getUserInfo接口可获取用户授权的个人信息能力的截止时间由2021年4月13日调整至2021年4月28日24时。为优化用户的使用体验,平台将进行以下调整: 2021年2月23日起,若小程序已在微信开放平台进行绑定,则通过wx.login接口获取的登录凭证可直接换取unionID2021年4月28日24时后发布的小程序新版本,无法通过wx.getUserInfo与<button open-type="getUserInfo"/>获取用户个人信息(头像、昵称、性别与地区),将直接获取匿名数据(包括userInfo与encryptedData中的用户个人信息),获取加密后的openID与unionID数据的能力不做调整。此前发布的小程序版本不受影响,但如果要进行版本更新则需要进行适配。新增getUserProfile接口(基础库2.10.4版本开始支持),可获取用户头像、昵称、性别及地区信息,开发者每次通过该接口获取用户个人信息均需用户确认。具体接口文档:《getUserProfile接口文档》由于getUserProfile接口从2.10.4版本基础库开始支持(覆盖微信7.0.9以上版本),考虑到开发者在低版本中有获取用户头像昵称的诉求,对于未支持getUserProfile的情况下,开发者可继续使用getUserInfo能力。开发者可参考getUserProfile接口文档中的示例代码进行适配。请使用了wx.getUserInfo接口或<button open-type="getUserInfo"/>的开发者尽快适配。开发者工具1.05.2103022版本开始支持getUserProfile接口调试,开发者可下载该版本进行改造。 小游戏不受本次调整影响。 一、调整背景很多开发者在打开小程序时就通过组件方式唤起getUserInfo弹窗,如果用户点击拒绝,无法使用小程序,这种做法打断了用户正常使用小程序的流程,同时也不利于小程序获取新用户。 二、调整说明通过wx.login接口获取的登录凭证可直接换取unionID 若小程序已在微信开放平台进行绑定,原wx.login接口获取的登录凭证若需换取unionID需满足以下条件: 如果开发者帐号下存在同主体的公众号,并且该用户已经关注了该公众号如果开发者帐号下存在同主体的公众号或移动应用,并且该用户已经授权登录过该公众号或移动应用2月23日后,开发者调用wx.login获取的登录凭证可以直接换取unionID,无需满足以上条件。 回收wx.getUserInfo接口可获取用户个人信息能力 4月28日24时后发布的新版本小程序,开发者调用wx.getUserInfo或<button open-type="getUserInfo"/>将不再弹出弹窗,直接返回匿名的用户个人信息,获取加密后的openID、unionID数据的能力不做调整。 具体变化如下表: [图片] 即wx.getUserInfo接口的返回参数不变,但开发者获取的userInfo为匿名信息。 [图片] 此外,针对scope.userInfo将做如下调整: 若开发者调用wx.authorize接口请求scope.userInfo授权,用户侧不会触发授权弹框,直接返回授权成功若开发者调用wx.getSetting接口请求用户的授权状态,会直接读取到scope.userInfo为true新增getUserProfile接口 若开发者需要获取用户的个人信息(头像、昵称、性别与地区),可以通过wx.getUserProfile接口进行获取,该接口从基础库2.10.4版本开始支持,该接口只返回用户个人信息,不包含用户身份标识符。该接口中desc属性(声明获取用户个人信息后的用途)后续会展示在弹窗中,请开发者谨慎填写。开发者每次通过该接口获取用户个人信息均需用户确认,请开发者妥善保管用户快速填写的头像昵称,避免重复弹窗。 插件用户信息功能页 插件申请获取用户头像昵称与用户身份标识符仍保留功能页的形式,不作调整。用户在用户信息功能页中授权之后,插件就可以直接调用 wx.login 和 wx.getUserInfo 。 三、最佳实践调整后,开发者如需获取用户身份标识符只需要调用wx.login接口即可。 开发者若需要在界面中展示用户的头像昵称信息,可以通过<open-data>组件进行渲染,该组件无需用户确认,可以在界面中直接展示。 在部分场景(如社交类小程序)中,开发者需要在获取用户的头像昵称信息,可调用wx.getUserProfile接口,开发者每次通过该接口均需用户确认,请开发者妥善处理调用接口的时机,避免过度弹出弹窗骚扰用户。 微信团队 2021年4月15日
2021-04-15