- 小程序请求数据双向混合加密和防篡改+防重放攻击的实现
前言 大家好,借着中秋放假明日又要上班的这个晚上,平常又没空,趁这个时间点就决定来一篇。 [图片] 我们都知道微信小程序的服务端API上,官方可谓是做足了心思,对用户的数据进行加密,虽然对我们开发者来说似乎是一种麻烦,但是从长远角度来看,是十分有必要的,用户隐私高于一切。 那么在小程序的开发过程中与后端对接接口时是否有想过这样的问题呢? HTTPS真的百分百安全吗? 数据被成功抓包后安全吗? 数据被篡改后还有效吗? 请求被重放后安全吗? 世界上没有绝对安全的系统,但我们可以让它被破解的成本变高。 本篇文章专业性并不高,如果存在错误请大家为我指出来,以免误导别人,谢谢! 以下我们将小程序端称为C端,服务端称为S端,服务端代码是Node.js,仅供参考,但原理都一样,后端可以是其它语言。 思考 围绕着以上的问题,探究一下问题的答案? 本部分只对问题做思考,具体实现请参考下面的实现部分。 HTTPS真的百分百安全吗?只能说是相对安全,当然,在微信小程序的沙箱环境里,HTTPS通信会更加安全,否则官方可能会要求我们对请求加密了对吧。但百密一疏,C端是否存在漏洞,假设C端安全了难道S端就安全嘛?细思恐极,退一万步讲,百分百安全是不存在的,由于篇幅问题相关漏洞大家可以搜索探究。 假设数据被中间人成功抓包,如果数据是明文传输,那么将导致数据泄露,因此对数据进行加密是必要的,但应该如何加密呢?如何做到密钥安全?C端和S端如何进行数据的加解密?RSA和AES加密应该使用哪种加密明文?如何充分发挥RSA和AES两个加密算法的特长? 假设数据被篡改,如果因为请求数据被篡改而导致严重后果,那么很大程度上其实是代码设计有问题,正常设计中安全应被摆在重要的位置,至少不应该出现购买某样商品时是通过C端发送商品价格给S端调起支付那样(只需篡改商品价格即可支付极少的钱购买商品),如果代码设计并不存在严重问题,那么数据被篡改也是不可忽视的,我们需要进行数据签名,让不同的数据拥有唯一的MD5哈希值,如果数据被篡改,通过哈希值即可判断数据是否被篡改,当然也可能会有人问,既然数据被篡改,那攻击者会不会重新生成一个哈希值代替?是的。但是我们有接下来会说到的key,key会作为签名的数据变量之一,由于攻击者并不知道key值因此无法重新签名,key值建议不是固定值而是周期性更换的随机值,例如随着用户的登录态而产生并随之抹除。 假设请求被重放,大部分时候由于数据被加密和防篡改处理过,攻击者并无法直接获得数据或篡改,但如果通过劫持到的登录认证请求的原始数据并重新发起该请求,则攻击者将可能获得重要的认证数据,使得系统将攻击者作为正常用户处理,后续的请求攻击者仍能伪装成正常的用户进行后续攻击。 期望效果 在开始实现之前先看一下完成后的效果,以下是截取了Network中其中一个GET请求发送的数据: [图片] 实际上发送的数据是如下图所示: [图片] 然后是该请求返回的数据: [图片] 实际上返回的数据是如下图所示: [图片] 整个流程: [图片] 实现 由于整个流程在C端的实现上顺序反过来的,因此下面的步骤也将是反向而行。 工具库下载 工欲善其事,必先利其器!这三个库是经过修改压缩的,支持在小程序上使用并且体积可观(总共69.1KB,如果不涉及密码哈希处理只需要前两个库,体积只有65.3KB),接下来的实现操作将会使用到,建议大家可以根据实际情况对功能进行二次封装: CryptoJS.js:点击下载 RSA.js:点击下载 SHA256.js(可选):点击下载 在线的各类加解密工具(可选,可以收藏起来,平时测试挺有用):点击访问 请求数据防重放 要防范请求重放攻击,首先需要了解Unix时间戳 timestamp概念,和时间戳不一样的是它的单位是秒,事实上这个需求也只需要秒级即可。除此之外还将用到另一个值:nonce,它是一个随机产生并只能被使用一次的值,长度自定,请求越频繁长度需要越长(降低同一时间产生相同nonce的几率),C端发送请求时需要将timestamp和生成的nonce加入发送的参数中。那么,如何将两者结合呢?我这为防重放的目标下了一个简单的定义。 同样的请求只能发生一次,且请求必须在规定时间内发出。 何为同样的请求?不是指两次发送的参数一致就是一样的,而是连timestamp和nonce也一样才算是同样的请求。 那么S端如何确认其是同样的请求呢? S端每次接收到一个请求,都会将该请求的nonce存入缓存并保持60秒(这个阈值不一定是60秒,可以根据实际需要定义),时间过后该值将被移除,建议S端采用Redis存储nonce,这样可省去检测和移除nonce的代码。如果S端发现当前请求的nonce存在于已存储的nonce之中,则此请求发生重复,那么timestamp有何用? 如果只使用nonce我们只能保证该请求60秒内不会重复,但60秒后依然任人宰割,这不是要的结果。所以timestamp将用来限制时间,S端时间戳减去C端发送请求的时间戳,得到的差值为N秒,如果N秒大于60秒则此请求过期,那么则可以保证,60秒内因为nonce相同而被判为请求重放,60秒后因为时间差超过而被判为请求已过期,因此确保了请求不会被重放。。 以下展示三种情况: [代码]C端时间戳:1568487720 //2019/9/15 03:02:00 C端NONCE:5rKbMs2Fm3 C端发送请求 -> S端接收请求 S端时间戳:1568487722 //2019/9/15 03:02:02 CS端时间差:1568487722 - 1568487720 = 2 C端NONCE是否存在于缓存:false 【重放校验通过】 [代码] [代码]C端时间戳:1568487722 //2019/9/15 03:05:22 C端NONCE:IzFEs52bAC C端发送请求 -> S端接收请求 S端时间戳:1568487922 //2019/9/15 03:02:00 CS端时间差:1568487922 - 1568487722 = 200 C端NONCE是否存在于缓存:false 【重放校验不通过,请求已过期,因为时间差超过60秒】 [代码] [代码]C端时间戳:1568487720 //2019/9/15 03:02:00 C端NONCE:IxwPHQU0nA C端发送请求 -> S端接收请求 S端时间戳:1568487722 //2019/9/15 03:02:02 CS端时间差:1568487722 - 1568487720 = 2 C端NONCE是否存在于缓存:true 【重放校验不通过,此请求为重放请求,因为nonce已经存在,此请求已经完成,不可重复】 [代码] timestamp和nonce将作为参数参与下面部分的签名。 请求数据防篡改 C端数据签名 首先通过对参数按照参数名进行字典排序(调过一些第三方API的朋友应该明白),假设当前需要传输的参数如下: [代码]{ "c": 123, "b": 456, "a": 789, "timestamp": 1568487720, "nonce": "5rKbMs2Fm3" } [代码] 进行字典排序,参数名顺序应为: [代码]const keys = Object.keys(data); //获得参数名数组 keys.sort(); //字典排序 console.log(key); //["a", "b", "c", "nonce", "timestamp"]; [代码] 参数字典排序后应和参数一起拼接为字符串,至于使用什么拼接符就要与S端商量了,如果参数值是一个数组或一个对象(如c为[1,2,3])那么可以将数据值转为JSON字符串再拼接。以上参数拼接后字符串如下: [代码]a=789&b=456&c=123&nonce=5rKbMs2Fm3×tamp=1568487720 [代码] 下一步是计算拼接字符串的MD5哈希值了嘛? 不是。因为这样拼接的字符串很容易被攻击者伪造签名并篡改数据,这样就失去了签名的意义了。也就是说缺了一个key值。key值又从何而来?上面思考部分有提到建议从登录后发放,并且这个key与该用户的登录态绑定,登录态有效期间,将使用这个key进行请求的签名与验签,至于如何鉴别用户,我们会在最终发送给S端的参数加入一个sessionId作为登录态唯一标识,这里不加是因为这部分数据是需要参与后续的加密的,而sessionId不参与加密。 但是可能又会有一个问题,登录前没有key怎么实现的登录请求?事实上,登录请求并不怕篡改,因为攻击者自己也不知道账号密码,所以无需提供key用于登录请求的签名。 提到登录密码这个需要注意一点,密码不能明文传输,请计算哈希值后传输,S端比对账户密码哈希值即可确认是否正确,同样S端非特殊情况也不能明文存储密码,建议SHA-1或更高级的SHA-256计算后的值,MD5值可能被使用彩虹表(一种为各种常见密码建立的MD5映射表)破解。SHA256计算的库已在上面工具库下载提供。 拼接上我们登录时随机生成的key: [代码]a=789&b=456&c=123&nonce=5rKbMs2Fm3×tamp=1568487720&key=gUelv79KTcFaCkVB [代码] 接下来计算32位MD5哈希值 为什么上面说MD5会被破解而这里却用MD5计算?因为此处计算MD5的目的并不是为了隐藏明文数据,而只是用于数据校验 此处引入了CryptoJS.js [代码]const CryptoJS = require('./CryptoJS'); const signStr = "a=789&b=456&c=123&nonce=5rKbMs2Fm3×tamp=1568487720&key=gUelv79KTcFaCkVB"; const sign = CryptoJS.MD5(signStr).toString(); //a42af0962de99e698d27030c5c9d3b0e [代码] 这么一来我们的数据签名阶段就完成了,然后需要把签名加入参数之中,将和参数一起传输,需要注意的是传输参数无需在意参数名排序。以下是当前的参数处理结果: [代码]{ "c": 123, "b": 456, "a": 789, "timestamp": 1568487720, "nonce": "5rKbMs2Fm3", "sign": "a42af0962de99e698d27030c5c9d3b0e" } [代码] 其实从上面这两部分内容来看,会发现防重放和防篡改是相辅相成的,就像两兄弟一样,少了谁都干不好这件事。 S端验证签名 既然有签名那必定也有验签,验签流程其实就是重复C端的前面流程并比对CS两端得出的签名值是否一致。S端取得请求数据后(假设数据未加密,暂时不讨论解密),将除了sign之外的参数名进行字典排序,sign用一个临时变量存下,然后排好序的参数和C端一样拼接得到字符串。 接下来根据上面部分提到的未加密参数sessionId获得用户登录态并获取到该状态的临时key拼接到字符串末尾,接下来进行MD5计算即可得到S端方获得的签名,此时与请求中携带的sign比较是否一致则可确定签名是否有效,如果不一致返回签名错误。具体流程请参考以下: [代码]C端发送请求 -> S端接收请求 //请求参数为data const _sign = data.sign; //排序并拼接除sign外的参数 let signStr = a=789&b=456&c=123&nonce=5rKbMs2Fm3×tamp=1568487720 const sessionId = ...; //用户的sessionId const sessionData = getUserSessionData(sessionId);//根据sessionId查询用户登录态sessionData并取出key,并拼接到字符串尾部 const key = sessionData.key signStr += key; //MD5计算并比对,代码仅供参考 const sign = crypto.md5(signStr); if(sign !== _sign) { //签名错误! } [代码] 请求数据加解密 有了上面部分的参数处理铺垫后,接下来就该开始本文章最核心的加解密,在这之前我们先了解AES和RSA两种加密算法。了解概念后我们再来思考一下两者的一些特性。 AES加解密需要密钥,除某些模式外还需要提供初始化向量,可使用密钥解出明文,是对称加密算法。 RSA加解密需要一对密钥,分别为公钥和私钥,公钥加密,私钥解密,是非对称加密算法。 两者在加密长文本性能上AES占优势。 根据这些让我们发现,他们可以形成互补关系,RSA加解密安全性高但长文本处理性能不及AES。AES加解密长文本性能优于RSA但需要明文密钥和向量加解密,密钥的安全性成问题。 那么在C端生成随机的AES密钥和向量,使用密钥和向量使用aes-128-cbc加密模式(也可以根据实际需要采用其它的模式)加密真正需要传输的参数(参数则是经过防重放+防篡改处理的参数)得到encryptedData,然后将该密钥使用RSA公钥加密得到encryptedKey,下面我顺便把AES加密的向量也一起加密了得到encryptedIV,这样就完美的互补了对方的缺点,既能够较快的完成数据加密又能保证密钥安全性,两全其美。 整个个加解密流程如下图所示: [图片] 下面的四个小章节将逐一描述流程实现: C端加密数据 C端加密后的数据应如下(并非固定格式,根据自己需要定制): [代码]{ "sessionId": "xxxxxxxx", "encryptedData": "xxxxxx", "encryptedKey": "xxxxxx", "encryptedIV": "xxxxxxx" } [代码] 但RSA公钥又是怎么发放到C端的呢?答案是在登录认证的时候服务器下发的,登录成功时服务器会创建RSA密钥对并把公钥发放给C端,私钥存在服务器上该用户的登录态数据中。流程如下所示: [代码]C端发起登录请求 -> S端接收登录请求 S端登录认证是否通过 true S端生成RSA密钥对 - publicKey , privateKey S端查询相关用户信息 S端生成登录态信息 -> 向登录态信息存入privateKey私钥,登录态信息类似如下 "xxxxxx": { id: "xxxxx", authData: { key: "xxxxxx", privateKey: "xxxxxx" } } S端返回登录态唯一标识sessionId和publicKey公钥以及相关用户信息 -> C端 C端存储登录态信息于本地,后续请求将使用服务器提供的公钥进行加密 [代码] 其中privateKey就是该用户当前登录态所使用的解密私钥,C端通过公钥加密后的AES密钥数据只能用该私钥解密。 如果希望登录阶段的请求也加密,那么可以手动生成一个RSA密钥对,然后客户端放置一个固定的公钥,服务器也使用一个固定的私钥进行登录阶段的加解密。 具体实现如下: 此处引入了CryptoJS.js和RSA.js [代码]const CryptoJS = require('./CryptoJS'); const RSA = require('./RSA.js'); //假设当前已登录成功并获得S端下发的RSA公钥且已存入本地存储 //createRandomStr为生成随机大小写英文和数字的字符串 const aesKey = createRandomStr(16); //生成AES128位密钥 16字节=128位 const aesIV = createRandomStr(16); //生成初始化向量IV const raw = JSON.stringfly({ "c": 123, "b": 456, "a": 789, "timestamp": 1568487720, "nonce": "5rKbMs2Fm3", "sign": "a42af0962de99e698d27030c5c9d3b0e" }); const encryptedData = CryptoJS.AES.encrypt(data: raw, key: aesKey, { iv: aesIV, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 }); //使用CBC模式和Pkcs7填充加密 const authData = wx.getStorageSync("authData"); //读取本地存的RSA公钥 RSA.setPublicKey(authData.publicKey); //设置RSA公钥 const encryptedKey = RSA.encrypt(aesKey); //RSA加密AES加密密钥 const encryptedIV = RSA.encrypt(aesIV); //RSA加密AES加密初始化向量,是否加密向量可由自己决定 //最后的处理结果 const result = { sessionId: authData.sessionId, encryptedData, encryptedKey, encryptedIV }; [代码] S端解密数据 [代码]const crypto = require('crypto'); const cryptojs = require('crypto-js'); const { sessionId, encryptedData, encryptedKey, encryptedIV } = requestData; const authData = getUserSessionData(sessionId); //获取用户登录态数据 const privateKey = authData.privateKey; const aesKey = crypto.privateDecrypt({ key: privateKey, padding: crypto.constants.RSA_PKCS1_PADDING }, encryptedKey); //使用私钥解密得到AES密钥 const aesIV = crypto.privateDecrypt({ key: privateKey, padding: crypto.constants.RSA_PKCS1_PADDING }, encryptedIV); //使用私钥解密得到AES iv向量 const key = cryptojs.enc.Base64.parse(aesKey); const iv = cryptojs.enc.Utf8.parse(aesIV); const decryptedData = cryptojs.AES.decrypt(encryptedData, key, { iv, mode: cryptojs.mode.CBC, padding: cryptojs.pad.Pkcs7 }); //采用与加密统一的模式和填充进行解密 const { "c": 123, "b": 456, "a": 789, "timestamp": 1568487720, "nonce": "5rKbMs2Fm3", "sign": "a42af0962de99e698d27030c5c9d3b0e" } = decryptedData; //至此解密得到C端传来的数据 [代码] S端加密数据 当S端处理完C端的请求后应加密响应数据,那么加密响应数据应该使用什么密钥呢?既然C端已经将加密的密钥发送过来了,那么干脆将C端使用的AES密钥拿来加密响应数据就可以了。加密的数据传回C端后,C端只需使用该请求所使用的AES加密密钥进行解密即可。响应的加密数据如下: [代码]const cryptojs = require('crypto-js'); const responseData = JSON.stringify(...); //此为S端需要返回给C端的数据 const aesKey = ...; //此为之前C端用来加密数据的AES密钥 const aesIV = createRandomStr(16); //生成初始化向量IV const encryptedData = cryptojs.AES.encrypt(data, aesKey, { iv: aesIV, mode: cryptojs.mode.CBC, padding: cryptojs.pad.Pkcs7 }); //加密响应数据 const encryptedResponse = { "encryptedData": encryptedData, "iv": aesIV }; //得到加密后的响应数据并返回给C端 [代码] C端解密数据 C端接收到S端的响应数据后应对加密的数据进行解密,此次解密就是单纯的AES解密了,使用发起请求时用于加密数据的AES密钥配合响应数据的iv向量对encryptedData进行解密,得到解密后的数据即为S端真正的响应数据。实现过程如下: 此处引入了CryptoJS.js [代码]const CryptoJS = require('./CryptoJS'); const key = ...; //之前用于加密请求参数的AES加密密钥 const { encryptedData, iv } = responseData; const aesIV = CryptoJS.enc.Utf8.parse(iv); const aesKey = CryptoJS.enc.Utf8.parse(key); const decryptedData = CryptoJS.AES.decrypt(encryptedData, aesKey, { iv: aesIV, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 }); //AES解密响应数据 const { ... } = decryptedData; //得到解密后的响应数据 [代码] 结语 第一次写这么长的文章,可能存在大量纰漏,如果大佬发现问题欢迎指出(`・ω・´)我会马上修改。 也许小程序的运行环境会比我想象中的更安全 也许HTTPS也会比我想象中的更安全 也许Web服务器引擎也比我想象中的更安全 但,安全不总是先行一步的吗?
2019-10-24 - canvas绘制好怎么旋转?
用的2D绘制的签名,横屏签名的,需要上传旋转-90的canvas图,这要怎么操作,wx.canvasToTempFilePath之后再旋转绘制一遍?然后再走一遍wx.canvasToTempFilePath?谢谢
2021-12-10 - canvas绘制完成如何旋转图片?
页面竖屏,canvas竖屏,但是用户横屏绘制(页面还是竖屏),然后我保存图片 需要给他转90°然后上传,请问怎么旋转呢?
2020-05-07 - 你好,你的小程序涉及收集、使用和存储用户个人信息?
1: 小程序补充文档存在不合规行为,包括但不限于存在引流推广行为、违法违规行为、不清晰描述等,请修改后,再提交代码审核。 你好,你的小程序涉及收集、使用和存储用户个人信息,请增加《用户服务协议》及《隐私政策》,明确告知收集用户个人信息的使用目的、方式和用途 ,并取得用户授权同意后,才能获取用户收集用户个人信息。
2021-12-15 - 能不能把所有的异步方法改成Promise的
- 需求的场景描述(希望解决的问题) 每一次用异步的时候都要写一堆的回调,比如登录吧 [代码]wx.login({[代码][代码] [代码][代码]success: res => {[代码]}[代码]});[代码]当然还有其他的异步都需要这样的回调,不觉得很low吗? 说实话,这种写法都是几年前的方式了,既然时代在进步,为什么这些异步的方法不可以全部换成Promise的呢? - 希望提供的能力 所有异步操作统一处理为Promise返回,例如登录 [代码]wx.login().then(res=>{[代码][代码]})[代码][代码] [代码]
2018-07-16 - 无法找到模块“miniprogram-api-promise”的声明文件
[图片][图片] 发现没有别人和我有一样的问题,而且从教程重新走了几遍了,他一直报错。
2022-06-16 - 小程序异步方法已支持Promise的then回调miniprogram-api-promise怎用?
微信提供了一个将异步API Promise化的库miniprogram-api-promise,我发现现有的小程序异步api就已经支持then写法了啊;这个工具的意义是什么?文档写的很简略; wx.getSystemInfo().then(res => console.log(res)) //获取成功
2021-07-30 - Searchbar 取消事件监听文档未更新?
[图片]目前已经可以通过 bindcancel 监听点击取消按钮,但是官方文档未更新。 https://developers.weixin.qq.com/miniprogram/dev/extended/weui/search.html [图片]
2021-08-01 - 羊谷故事-2022微信小程序应用开发赛国一产品经验分享
羊谷故事-2022微信小程序应用开发赛国一产品经验分享 写在前面 在写这篇文章之前,笔者跟大多数人一样,在焦虑工作,焦虑考研。写下这些,也是对自己的总结,希望可以继续前行。文章底部我会开源所有的参赛资料,希望各位指正。 五年前,我是半个软件工程师,相比于其他优秀的工程师,我基础并不扎实(以至于在代码技术层面差了一截),反而是很喜欢给程序堆料,希望可以惠及更多人。于是就有了这个 [图片] 这是五年前我给自己程序做的海报,功能也十分简单。今天参加这个比赛,我也采用了相同的方式。有了一定的履历,或许自己的思考和表现力有了一些提升,PPT和文档写的可能好了一些。 [图片] 下面的分享仅仅对于这里可能对处于学生阶段学技术,但又想要做一个产品的你有所帮助,如果是企业里的产品经理则是需要另一种思维方式,需要对业务场景更专业,这里各位请注意区分。这些经验仅针对作品类比赛(小程序赛、网络技术挑战赛、人工智能创新赛等)。 做好调研 挖掘产品创新 调研是很关键的一步,不论是日后做学术相关的研究,还是从事其他创新性质工作,都需要进行调研。这里的调研,并不是搜索「某某教程视频讲解」,也不是简单的看看前一届的比赛作品。调研是需要有广度的(这种综合比赛甚至需要跨学科),也需要有深度(专注特定场景)。 小程序赛类似于小型的互联网+比赛,对于产品的定位和包装非常关键。我想这一类赛事,或许要么做别人没做过的(尽量做体系创新),做别人做过但没做好的(专注解决某个场景痛点),这次乡村振兴主题的比赛,我们的程序专注在于「探索数字化溯源」的命题上,同时使演示文稿和宣传片处于同一个风格上。原本「溯源」是个很严谨的东西,那我们怎么把他下放到一个简单「扁平化」的新场景中呢? 站在巨人的肩膀上 说到写文档,你们是不是第一个想到的是用word「分点作答」?产品文档并非是实验报告文档,而是深度浓缩的精华,在初赛的时候非常关键,因为初赛没有答辩。产品定位可以体会不同大厂传递出来的价值观,需要有自己产品的价值观,最好可以造出自己的IP。 [图片] 所有的可视手段要围绕着产品定位,不可脱离设计价值观,比如说很多人喜欢下载一些“漂亮”商业模版,套在自己的ppt上,这样可能就违反了设计价值观。写文档的时候可以关注比如说腾讯云的产品文档,字节跳动可持续报告的撰写。而对于PPT,要基于模版,高于模版,可以关注苹果公司每年发布会的文稿视觉设计,那些朋友圈的「精美模版」实际上同质化程度很高,切忌模版。同时,PPT是需要跟演讲者联动起来的,不可割离,条件允许可以注意细节。 [图片] 产品原型不可缺少,需要有自己的UI风格。同时可以在答辩的时候拍产品的微电影,在答辩的时候可以让评委看到更多价值,省赛的时候我回去乡村取景剪辑,最后答辩也有了很好的效果。这里的视频不只可以局限于功能本身,更多的是与人文的勾连,这里推荐去看看陈可辛导演的《三分钟》以及腾讯为村实验室做的视频。 而在答辩演讲方面,我很喜欢TED Talks 以故事的引入方式。对于原型设计,可以参考谷歌的material design风格和最新的Material You标准。 其实每一个方向都有很深的研讨方向,并非简单的「PPT吹水」。站在这些巨人的肩膀上,多关注国内外优秀的作品,在他们的基础上创新,不可闭门造车,可以有新的体会。 提高技术价值 什么是技术创新?我想每个人定义不同。在笔者这里,将一个环境的技术,迁移到另一个环境去使用,或者提升精准度、优化精度都可以算技术创新。 创新的技术跟产品是相互促进的,比如21年的第一名港中文的博士在小程序里嵌入了3d模型,今年北大的一位同学做了艺术元宇宙,做到了技术上的创新,这对产品的发展是有益的。 [图片] 使用专业的工具 对于所使用的工具,推荐使用Mac全家桶(keynote演示,ipages文档),ps或许是人均水平,最好会Adobe Illustrator和C4D,以及原型设计的sketch。熟练使用工具可以把自己的想法表现出来,使产品更具有生命力。当然,工具只是一种实现方式,本质上都可以产出。比如说用Vim也可以写代码,笔者就挺喜欢vim。 体会程序与人文的高度统一 最后,容许我写一句抽象的话,程序最终需要服务于人,往往是人文与哲学的高度融合,海外的团队这一方面做的非常好,也是我等所不能及的。在我们眼里或许程序是简单的业务,但在某些人眼里,程序是一段故事,是人文与情怀。 以上或许都是侃侃而谈,每一个细节都可以仔细研究。诚然,对于一名优秀的产品经理而言,这些还远远不够,用于应对此类的作品类竞赛或许很适用。 最后挂一下我们拿到一等奖的时刻。 [图片] 希望未来我们还能如此幸运。 开源 羊谷赛事经验仓库 https://github.com/Crop-Group/open-source 参考 字节跳动社会可持续报告 http://p3-bd-official.byteimg.com/obj/bytedance-cn/2021北京字节跳动企业社会责任报告.pdf Google-Material You规范 https://material.io/blog/announcing-material-you 方仔照相馆(Decamp 2020 champion project) https://www.youtube.com/watch?v=Sfxbe84bYck 张小龙:微信十年的产品思考 https://finance.sina.com.cn/tech/2021-01-19/doc-ikftpnnx9427864.shtml
2022-08-29 - 小程序API Promise化如何引用?
[图片][图片] 按照官方的引入,不知道为什么会报错??npm install --save miniprogram-api-promise 小程序工具中构建npm
2021-05-24 - (4)获取用户信息
背景 我们发现大部分小程序都会使用 [代码]wx.getUserInfo[代码] 接口,来获取用户信息。原本设计这个接口时,我们希望开发者在真正需要用户信息的情况下才去调取这个接口,但很多开发者会直接调用这个接口,导致用户在使用小程序的时候产生困扰,归结起来有几点: 开发者在小程序首页直接调用 [代码]wx.getUserInfo[代码] 进行授权,弹框获取用户信息,会使得一部分用户点击“拒绝”按钮。 在开发者没有处理用户拒绝弹框的情况下,用户必须授权头像昵称等信息才能继续使用小程序,会导致某些用户放弃使用该小程序。 用户没有很好的方式重新授权,尽管我们增加了[代码]设置[代码]页面,可以让用户选择重新授权,但很多用户并不知道可以这么操作。 此外,我们发现开发者默认将 [代码]wx.login[代码] 和 [代码]wx.getUserInfo[代码] 绑定使用,这个是由于我们一开始的设计缺陷和实例代码导致的([代码]wx.getUserInfo[代码] 必须通过 [代码]wx.login[代码] 在后台生成 [代码]session_key[代码]后才能调用)。同时,我们收到开发者的反馈,希望用户进入小程序首页便能获取到用户的 [代码]unionId[代码],以便识别到用户是否以前关注了同主体公众号或使用过同主体的App 。 为了解决以上问题,针对获取用户信息我们更新了三个能力: 1.使用组件来获取用户信息 2.若用户满足一定条件,则可以用[代码]wx.login[代码] 获取到的[代码]code[代码]直接换到[代码]unionId[代码] 3.[代码]wx.getUserInfo[代码] 不需要依赖 [代码]wx.login[代码] 就能调用得到数据 获取用户信息组件介绍 [代码][代码] 组件变化: [代码]open-type [代码]属性增加 [代码]getUserInfo[代码] :用户点击时候会触发 [代码]bindgetuserinfo[代码] 事件。 新增事件 [代码]bindgetuserinfo[代码] :当 [代码]open-type[代码]为 [代码]getUserInfo[代码] 时,用户点击会触发。可以从事件返回参数的 [代码]detail[代码] 字段中获取到和 [代码]wx.getUserInfo[代码] 返回参数相同的数据。 示例: [代码]<button open-type="getUserInfo" bindgetuserinfo="userInfoHandler"> Click me button>[代码]和 [代码]wx.getUserInfo[代码] 不同之处在于: 1.API [代码]wx.getUserInfo[代码] 只会弹一次框,用户拒绝授权之后,再次调用将不会弹框; 2.组件 [代码][代码][代码][代码] 由于是用户主动触发,不受弹框次数限制,只要用户没有授权,都会再次弹框。 通过获取用户信息的组件,就可以解决用户再次授权的问题。 直接获取unionId开发者申请 [代码]userinfo[代码] 授权主要为了获取 [代码]unionid[代码],我们鼓励开发者在不骚扰用户的情况下合理获得[代码]unionid[代码],而仅在必要时才向用户弹窗申请使用昵称头像。为此,凡使用“获取用户信息组件”获取用户昵称头像的小程序,在满足以下全部条件时,将可以静默获得 [代码]unionid[代码]: 1.在微信开放平台下存在同主体的App、公众号、小程序。 2.用户关注了某个相同主体公众号,或曾经在某个相同主体App、公众号上进行过微信登录授权。 这样可让其他同主体的App、公众号、小程序的开发者快速获得已有用户的数据。 不依赖登录的用户信息获取某些工具类的轻量小程序不需要登录行为,但是也想获取用户信息,那么就可以在 [代码]wx.getUserInfo[代码] 的时候加一个参数 [代码]withCredentials: false[代码] 直接获取到用户信息,可以少一次网络请求。 这样可以在不给用户弹窗授权的情况下直接展示用户的信息。 最佳实践 1.调用 [代码]wx.login[代码] 获取 [代码]code[代码],然后从微信后端换取到 [代码]session_key[代码],用于解密 [代码]getUserInfo[代码]返回的敏感数据。 2.使用 [代码]wx.getSetting[代码] 获取用户的授权情况 1) 如果用户已经授权,直接调用 API [代码]wx.getUserInfo[代码] 获取用户最新的信息; 2) 用户未授权,在界面中显示一个按钮提示用户登入,当用户点击并授权后就获取到用户的最新信息。 3.获取到用户数据后可以进行展示或者发送给自己的后端。 One More Thing 除了获取用户方案介绍之外,再聊一聊很多初次接触微信小程序的开发者所不容易理解的一些概念: 1.关于OpenId和UnionId [代码]OpenId[代码] 是一个用户对于一个小程序/公众号的标识,开发者可以通过这个标识识别出用户。 [代码]UnionId[代码] 是一个用户对于同主体微信小程序/公众号/APP的标识,开发者需要在微信开放平台下绑定相同账号的主体。开发者可通过[代码]UnionId[代码],实现多个小程序、公众号、甚至APP 之间的数据互通了。 同一个用户的这两个 ID 对于同一个小程序来说是永久不变的,就算用户删了小程序,下次用户进入小程序,开发者依旧可以通过后台的记录标识出来。 2.关于 getUserInfo 和 login 很多开发者会把 [代码]login[代码] 和 [代码]getUserInfo[代码] 捆绑调用当成登录使用,其实 [代码]login[代码] 已经可以完成登录,[代码]getUserInfo[代码] 只是获取额外的用户信息。 在 [代码]login[代码] 获取到 [代码]code[代码] 后,会发送到开发者后端,开发者后端通过接口去微信后端换取到 [代码]openid[代码] 和[代码]sessionKey[代码](现在会将 [代码]unionid[代码] 也一并返回)后,把自定义登录态 [代码]3rd_session[代码]返回给前端,就已经完成登录行为了。而 [代码]login[代码] 行为是静默,不必授权的,用户不会察觉。 [代码]getUserInfo[代码] 只是为了提供更优质的服务而存在,比如展示头像昵称,判断性别,开发者可通过 [代码]unionId[代码] 和其他公众号上已有的用户画像结合来提供历史数据。因此开发者不必在用户刚刚进入小程序的时候就强制要求授权。 可以在官方的文档中看到 [代码]login[代码] 的最佳实践: [图片] Q & A Q1: 为什么 login 的时候不直接返回 openid,而是要用这么复杂的方式来经过后台好几层处理之后才能拿到? A: 为了防止坏人在网络链路上做手脚,所以小程序端请求开发者服务器的的请求都需要二次验证才是可信的。因为我们采取了小程序端只给 [代码]code[代码] ,由服务器端拿着 [代码]code[代码] 和 [代码]AppSecrect[代码] 去微信服务器请求的方式,才会给到开发者对应的[代码]openId[代码] 和用于加解密的 [代码]session_key。[代码] Q2: 既然用户的[代码]openId[代码] 是永远不变的,那么开发者可以使用[代码]openId[代码] 作为用户的登录态么? A: 不行,这是非常危险的行为。因为 [代码]openId[代码] 是不变的,如果有坏人拿着别人的 [代码]openId[代码] 来进行请求,那么就会出现冒充的情况。所以我们建议开发者可以自己在后台生成一个拥有有效期的 [代码]第三方session[代码] 来做登录态,用户每隔一段时间都需要进行更新以保障数据的安全性。 Q3: 是不是用户每次打开小程序都需要重新[代码]login[代码]? A: 不必,可以将登录态存入[代码]storage[代码]中,用户再次登录就可以拿[代码]storage[代码] 里的登录态做正常的业务请求,只有当登录态过期了之后才需要重新[代码]login[代码] 。这样子做一则可以减少用户等待时间,二则可以减少网络带宽。 目前微信的[代码]session_key[代码] 有效期是三天,所以建议开发者设置的登录态有效期要小于这个值。
2018-08-17 - 小程序里使用async和await变异步为同步,解决回调地狱问题
最近好多同学,学习完石头哥的云开发基础以后,自己实际项目中,总会遇到各种各样的异步问题。 一,异步问题 所谓异步:就是我们请求数据库的数据时,由于网速等各方面原因,数据返回的时间不确定,而我们要使用这些数据,就要等数据返回成功后才可以使用,否则就会报错。 1-1,问题描述 如下: [图片] 好多同学都会认为代码从上往下执行,会先执行请求成功,然后才会执行第11行的代码,商品个数也应该是2. 但是我们的第11行打印却是0.这是为什么呢。 这个错误的原因就是我们使用数据没有写在请求成功里面。正确数据请求返回是异步的,什么时候请求成功不知道,但是我们的第11行代码不会等我们数据请求成功才会执行,所以第11行的打印是0而不是2. 1-2,解决方案 要想解决上面的问题,把你使用数据的地方写到数据请求成功里。 [图片] 这样就能解决异步的问题,但是如果我们有很多地方要使用请求成功的数据,该怎么办呢,总不能把所有的代码都写在数据请求成功里吧。这个时候就要借助async和await来解决这个问题了。 二,使用async和await变异步为同步 所谓的同步,就是我们保持代码正常的从上往下执行。但是呢只要有数据请求,就会有异步问题。所以我们这里要想办法变异步为同步。这就要用到async和await了。 代码如下: [图片] 可以看出,我们不用把使用到数据的代码写到请求成功里就可以了,这样代码读起来是不是常规的从上往下执行的了。 await翻译过来就是等待的意思,其实这里的意思就是,我们等待数据请求完成后,把数据的返回结果赋值给res,然后等数据请求成功以后,就可以正常使用数据请求返回的结果啦。 注意事项 我们在小程序里使用async和await时,一定是成对的。 async放在函数名前面,await放在数据请求前面。 [图片] 并且也要勾选一下:增强编译 [图片] 现在最新版本的小程序开发者工具好像已经支持async和await方法了,好像不勾选增强编译也没事。但是安全起见,还是勾选下增强编译比较好。 三,回调地狱 比如我们有这么一个需求: 用户注册的时候,要先查询是否注册过,没有注册过,才可以新注册。而注册成功后,才可以查看商品列表。 3-1,问题描述 这里给大家分析下需求 [图片] 如果只看流程图,肯定会觉得很简单;但是里面的链路你要认清一个现实。 就是我们如果想最终把商品显示到页面上,必须依赖每个流程都要请求成功。现在是只有3个请求,如果有100个呢,一层套一层的,最后会把你绕晕。这就是回调地狱。 3-2,回调地狱代码 单纯的给你讲,你可能体会不到回调地狱的坏处。那么我用代码实现下我们上面的需求。 假设我们有 用户表:user 商品表:goods 比如我们要注册一个名为”小石头“的用户 第一步:先查询是否注册过 [图片] 可以看出返回的个数为0,代表没有注册过 第二步:注册用户 [图片] 可以看到我们已经可以注册成功了,但是这个时候代码已经嵌套了。 [图片] 第三步:查询商品 由于我们第二步,已经注册’小石头‘成功,所以我们这一步注册一个’大石头‘,注册成功后查询商品。 首先看下代码,这个时候已经嵌套3层了。代码已经变得有点乱了 [图片] 看下结果 [图片] 可以看出我们已经能够成功的查询到商品数据了。 这里只嵌套了三层,看起来还可以接受,如果再继续一层层的嵌套呢。后面代码会变得越来越乱,为了避免回调地狱,我们也可以使用async和await来改造代码。 四,async结合await解决回调地狱 首先看下改造后的代码 [图片] 可以看到代码简洁了很多,逻辑也就是正常的从上往下执行代码 为了更明显的比较。 [图片] 到这里我们就讲完了,是不是感觉使用async和await让你的代码简洁了很多。赶紧跟着石头哥的这篇文章去体验下吧。
2021-05-29 - 小程序能做那种任务计划提醒吗?用户可能不会一直留在小程序上面
我们的一个小程序,有一个做任务计划提醒的功能,定好时间后到点提醒你该做什么,这个可以做吗,因为小程序好像不算常驻应用吧
2018-11-27 - 小程序登录、用户信息相关接口调整说明
公告更新时间: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 - 社区每周 | PC小程序公测、云开发支持代开发、每周反馈(0729-0802)
各位微信开发者: 以下是新能力公测与更新公告、上周我们在社区收到的问题反馈与需求的处理进度,希望同大家一同打造小程序生态。 微信 PC 版小程序开发者公测 微信 PC 版新版本中,支持打开聊天中分享的小程序,开发者可下载安装微信 PC 版内测版本进行体验和适配。最新版微信开发者工具新增支持在微信 PC 版中预览小程序 查看详情 微信 PC 版内测版:点击下载 微信开发者工具:点击下载 开发者遇到问题,请在社区中发表标题包含「PC 小程序」的问答帖子反馈。 微信 Mac 版小程序也将在近期开启测试。 小程序·云开发支持第三方平台代开发 小程序·云开发目前已支持第三方平台代开发。第三方平台可在小程序权限集中勾选云开发管理权限,并通知小程序进行云开发权限集授权。授权完成后即可进行代开发,具体开发过程可参考第三方平台代开发 同时,为方便第三方平台进行代开发,云开发还提供了多种 API 接口,包括开通云开发、创建云环境以及创建云函数等。详见云开发接入指南 上周问题反馈和处理进度(07.29-08.02) 已修复的问题基础库2.8.0,iOS 下 video 组件同层渲染失效的问题 查看详情 修复中的问题自定义组件使用startBeaconDiscovery,uuids 五个以上,闪退的问题 查看详情 map 组件 bindregionchange 事件的问题 查看详情 工具目录树滑动时表现异常的问题 查看详情 需求反馈需求评估中 用 wx.request 发送带 PFX 证书和密码的请求 查看详情 浮窗关闭音频播放能监听的需求 查看详情 希望提供获取小程序内存使用情况的 API,以简化内存相关异常的排查流程 查看详情 小程序录音支持 PCM 格式的需求 查看详情 希望微信官方开放一个API:提供子组件获取父组件的实例对象 查看详情 camera 组件变焦的需求 查看详情 onCameraFrame 可以调整获取图片的频率吗的需求 查看详情 关于 swiper 组件动态添加 item 的需求 查看详情 强烈建议官方为小程序增加 table 组件 查看详情 官方版本的 jweixin SDK 的需求 查看详情 wx.previewImage 查看大图限制保存或获取用户点击保存事件回调的需求 查看详情 第三方平台,绑定小程序客服接口的需求 查看详情 地图缩放操作时 data 里面的 scale 可否同步更新的需求 查看详情 用 wx.request 发送带 PFX 证书和密码的请求 查看详情 浮窗关闭音频播放能监听的需求 查看详情 希望提供获取小程序内存使用情况的 API,以简化内存相关异常的排查流程 查看详情 小程序录音支持 PCM 格式的需求 查看详情 希望微信官方开放一个API:提供子组件获取父组件的实例对象 查看详情 camera 组件变焦的需求 查看详情 onCameraFrame 可以调整获取图片的频率吗的需求 查看详情 关于 swiper 组件动态添加 item 的需求 查看详情 强烈建议官方为小程序增加 table 组件 查看详情 官方版本的 jweixin SDK 的需求 查看详情 wx.previewImage 查看大图限制保存或获取用户点击保存事件回调的需求 查看详情 第三方平台,绑定小程序客服接口的需求 查看详情 地图缩放操作时 data 里面的 scale 可否同步更新的需求 查看详情 微信团队 2019.08.09
2019-08-09 - 小程序地图学习之获取位置 获取经纬度 获取地名 获取地址
我们在做小程序开发时,难免会遇到地图相关的开发,而小程序已经为我们提供的比较完善的地图组件。我们只需要调用相关的api就可以实现大致的功能。如:获取经纬度,获取位置,获取地址,获取地名。结下来就具体给大家讲解。 老规矩先看效果图 [图片] 接下来我们就来看看具体实现步骤 一,定义一个按钮来调用位置获取的api [代码]<!--index.wxml--> <button bindtap='getLocation'>获取位置信息</button> <text>{{jingwei}}</text> <text>{{address}}</text> <text>{{name}}</text> [代码] 二,调用获取地理位置的方法 [代码]//index.js Page({ getLocation() { let that = this; wx.chooseLocation({ success: function(res) { console.log(res) var latitude = res.latitude var longitude = res.longitude; that.setData({ jingwei: "经纬度:" + longitude + ", " + latitude, address: " 地址:" + res.address, name: " 地名:" + res.name }) } }); } }) [代码] 其实到这里我们就可以实现获取经纬度,获取位置信息的功能了。 但是呢??现在小程序调用用户位置信息时,需要用户授权,如下图,如果用户点击了拒绝,我们就没有办法调用地图获取位置信息了。 [图片] 所以呢,我们要想实现一个完整的获取用户位置信息的功能,就要在监测到用户拒绝的位置权限时,引导用户去重新授权。这样才是一个友好的健壮的程序。下面就来教大家如何引导用户去打开授权。 三,在app.json里注册位置权限 [图片] 上图红色框里就是我们的位置权限的注册代码,app.json的完整代码如下。 [代码]{ "pages": [ "pages/index/index", "pages/setting/setting" ], "window": { "backgroundTextStyle": "light", "navigationBarBackgroundColor": "#fff", "navigationBarTitleText": "WeChat", "navigationBarTextStyle": "black" }, "permission": { "scope.userLocation": { "desc": "你的位置信息将用于小程序位置接口的效果展示" } }, "sitemapLocation": "sitemap.json" } [代码] 四,定义检查位置权限是否打开的方法 [代码] //校验位置权限是否打开 checkLocation() { let that = this; //选择位置,需要用户授权 wx.getSetting({ success(res) { if (!res.authSetting['scope.userLocation']) { wx.authorize({ scope: 'scope.userLocation', success() { wx.showToast({ //这里提示失败原因 title: '授权成功!', duration: 1500 }) }, fail() { that.showSettingToast('需要授权位置信息'); } }) } } }) }, [代码] 这个方法就是来检查用户的位置权限是否授权,如果没有授权,就弹窗提示用户去授权页授权。弹窗代码如下: [代码] // 打开权限设置页提示框 showSettingToast: function(e) { wx.showModal({ title: '提示!', confirmText: '去设置', showCancel: false, content: e, success: function(res) { if (res.confirm) { wx.navigateTo({ url: '../setting/setting', }) } } }) }, [代码] 至此就可以实现一个完整的获取用户位置信息的小程序了,index.js完整代码如下 [代码]//index.js Page({ getLocation() { this.checkLocation(); let that = this; wx.chooseLocation({ success: function(res) { console.log(res) var latitude = res.latitude var longitude = res.longitude; that.setData({ jingwei: "经纬度:" + longitude + ", " + latitude, address: " 地址:" + res.address, name: " 地名:" + res.name }) } }); }, //校验位置权限是否打开 checkLocation() { let that = this; //选择位置,需要用户授权 wx.getSetting({ success(res) { if (!res.authSetting['scope.userLocation']) { wx.authorize({ scope: 'scope.userLocation', success() { wx.showToast({ //这里提示失败原因 title: '授权成功!', duration: 1500 }) }, fail() { that.showSettingToast('需要授权位置信息'); } }) } } }) }, // 打开权限设置页提示框 showSettingToast: function(e) { wx.showModal({ title: '提示!', confirmText: '去设置', showCancel: false, content: e, success: function(res) { if (res.confirm) { wx.navigateTo({ url: '../setting/setting', }) } } }) }, }) [代码] 从代码中可以看到,我们在用户拒绝授权时的提示框,点击会跳转到setting页,setting也是我们自己的页面,但是这个页面特别简单。就定义一个button。 [代码]<!--pages/setting/setting.wxml--> <button class="button" open-type="openSetting" type='primary'> 打开授权设置页 </button> [代码] 为什么要这么做呢,因为微信不允许我们直接打开权限设置页,必须通过button组件提供的开发能力去到设置页,这里的开放能力就是open-type=“openSetting” 中的openSetting。我们点击按钮后就到了权限设置页。 [图片] 这样就可以引导用户再次授权了。 有任何关于编程的问题都可以加我微信2501902696(备注编程开发) 编程小石头,码农一枚,非著名全栈开发人员。分享自己的一些经验,学习心得,希望后来人少走弯路,少填坑。 完整的源码可以加老师微信获取,也可以关注下面老师公号,回复“地图源码” 获取。 [图片]
2019-05-08 - 扫码拍照,留存底单
- 需求的场景描述(希望解决的问题) 由于快递行业需要扫码识别运单号,然后也需要客户签收留存底单,so... 弄了官方的东西好像不支持既扫描又拍照的方式,希望官方尽早开放此需求~~ - 希望提供的能力 扫码的同时拍下扫码界面
2018-07-14