- ipad使用chooseMedia,拍摄视频直接卡死
wx.chooseMedia 使用真机调试,wx.chooseMedia 函数里的success没有任何内容 提示已经上传成功,[图片]但是ipad直接卡在拍摄视频的画面。[图片]只能点击home键 其他没有反应直接卡死
2020-12-07 - 小程序app.onLaunch与page.onLoad异步问题的最佳实践
场景: 在小程序中大家应该都有这样的场景,在onLaunch里用wx.login静默登录拿到code,再用code去发送请求获取token、用户信息等,整个过程都是异步的,然后我们在业务页面里onLoad去用的时候异步请求还没回来,导致没拿到想要的数据,以往要么监听是否拿到,要么自己封装一套回调,总之都挺麻烦,每个页面都要写一堆无关当前页面的逻辑。 直接上终极解决方案,公司内部已接入两年很稳定: 1.可完美解决异步问题 2.不污染原生生命周期,与onLoad等钩子共存 3.使用方便 4.可灵活定制异步钩子 5.采用监听模式实现,接入无需修改以前相关逻辑 6.支持各种小程序和vue架构 。。。 //为了简洁明了的展示使用场景,以下有部分是伪代码,请勿直接粘贴使用,具体使用代码看Github文档 //app.js //globalData提出来声明 let globalData = { // 是否已拿到token token: '', // 用户信息 userInfo: { userId: '', head: '' } } //注册自定义钩子 import CustomHook from 'spa-custom-hooks'; CustomHook.install({ 'Login':{ name:'Login', watchKey: 'token', onUpdate(token){ //有token则触发此钩子 return !!token; } }, 'User':{ name:'User', watchKey: 'userInfo', onUpdate(user){ //获取到userinfo里的userId则触发此钩子 return !!user.userId; } } }, globalData) // 正常走初始化逻辑 App({ globalData, onLaunch() { //发起异步登录拿token login((token)=>{ this.globalData.token = token //使用token拿用户信息 getUser((user)=>{ this.globalData.user = user }) }) } }) //关键点来了 //Page.js,业务页面使用 Page({ onLoadLogin() { //拿到token啦,可以使用token发起请求了 const token = getApp().globalData.token }, onLoadUser() { //拿到用户信息啦 const userInfo = getApp().globalData.userInfo }, onReadyUser() { //页面初次渲染完毕 && 拿到用户信息,可以把头像渲染在canvas上面啦 const userInfo = getApp().globalData.userInfo // 获取canvas上下文 const ctx = getCanvasContext2d() ctx.drawImage(userInfo.head,0,0,100,100) }, onShowUser() { //页面每次显示 && 拿到用户信息,我要在页面每次显示的时候根据userInfo走不同的逻辑 const userInfo = getApp().globalData.userInfo switch(userInfo.sex){ case 0: // 走女生逻辑 break case 1: // 走男生逻辑 break } } }) 具体文档和Demo见↓ Github:https://github.com/1977474741/spa-custom-hooks 祝大家用的愉快,记得star哦
2023-04-23 - 微信小程序获取openid的两种方法
第一种:使用云开发 这种比较简单,只需要开通云开发,创建云函数,调用云函数就可获得。 调用云函数 Promise Cloud.callFunction(Object object) 返回一个Promise对象,所以不用考虑异步问题。 callFunction说明 https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-sdk-api/functions/Cloud.callFunction.html具体代码如下: 我这里云函数名为helloCloud // helloCloud-index.js 云函数入口函数 exports.main = async (event, context) => { let{ APPID,OPENID}=cloud.getWXContext() return { APPID, OPENID } //------------------------------------------------------ //云函数调用 wx.cloud.callFunction({ name:'helloCloud', data:{ message:'helloCloud', } }).then(res=>{ console.log(res)//res就将appid和openid返回了 //做一些后续操作,不用考虑代码的异步执行问题。 }) 第二种:不使用云开发 这种方式就需要开发者有自己的后台了。 首先需要在微信小程序调用登录开放接口 wx.login() 获取用户登陆凭证code。 wx.login()接口说明 https://developers.weixin.qq.com/miniprogram/dev/api/open-api/login/wx.login.html 然后,向自己的服务器发送请求,并将code一起发送过去。 wx.login({ success (res) { if (res.code) { //发起网络请求 wx.request({ url: '自己的服务器请求接口', data: { code: res.code } }) } else { console.log('登录失败!' + res.errMsg) } } }) 接下来,在自己的服务端调用auth.code2Session接口,我这里是用Java后台。 auth.code2Session接口说明 https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/login/auth.code2Session.html @RequestMapping("/testopenid") public String getUserInfo(@RequestParam(name = "code") String code) throws Exception { System.out.println("code" + code); String url = "https://api.weixin.qq.com/sns/jscode2session"; url += "?appid=xxxxxxxxxxxxx";//自己的appid url += "&secret=xxxxxxxxxxxxxxxxxxx";//自己的appSecret url += "&js_code=" + code; url += "&grant_type=authorization_code"; url += "&connect_redirect=1"; String res = null; CloseableHttpClient httpClient = HttpClientBuilder.create().build(); // DefaultHttpClient(); HttpGet httpget = new HttpGet(url); //GET方式 CloseableHttpResponse response = null; // 配置信息 RequestConfig requestConfig = RequestConfig.custom() // 设置连接超时时间(单位毫秒) .setConnectTimeout(5000) // 设置请求超时时间(单位毫秒) .setConnectionRequestTimeout(5000) // socket读写超时时间(单位毫秒) .setSocketTimeout(5000) // 设置是否允许重定向(默认为true) .setRedirectsEnabled(false).build(); // 将上面的配置信息 运用到这个Get请求里 httpget.setConfig(requestConfig); // 由客户端执行(发送)Get请求 response = httpClient.execute(httpget); // 从响应模型中获取响应实体 HttpEntity responseEntity = response.getEntity(); System.out.println("响应状态为:" + response.getStatusLine()); if (responseEntity != null) { res = EntityUtils.toString(responseEntity); System.out.println("响应内容长度为:" + responseEntity.getContentLength()); System.out.println("响应内容为:" + res); } // 释放资源 if (httpClient != null) { httpClient.close(); } if (response != null) { response.close(); } JSONObject jo = JSON.parseObject(res); String openid = jo.getString("openid"); System.out.println("openid" + openid); return openid; } 部分参考 https://blog.csdn.net/qq_42940875/article/details/82706638?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task 这样就获得openid了。 但是在实际应用场景中,往往需要在界面展示之前获得openid来做一些操作或者什么。 用以上代码会发现,openid后台虽然获取到了,但是小程序端页面刚展示时好像并没有获取到openid,但是之后查看数据能看到openid。 这是因为wx.request()是异步请求。也就是在请求的过程中,小程序的其他工作没有因为请求而停止。 所以,我们需要将请求封装成一个返回Promise对象的函数。 廖雪峰老师讲的Promise使用 https://www.liaoxuefeng.com/wiki/1022910821149312/1023024413276544 这样就能在请求完做一些后续操作。 代码如下: //封装wx.request() function request(requestMapping, data, requestWay, contentType) { wx.showLoading({ title: '请稍后', }) return new Promise(function(resolve, reject) { console.log('请求中。。。。。') wx.request({ url: '自己的服务器地址' + requestMapping, data: data, header: { 'content-type': contentType // 默认值 }, timeout: 3000, method: requestWay, success(res) { //console.log(res) if (res.data.success == false || res.data.statusCode == 404) { reject(res) } else { resolve(res) } }, fail: (e) => { wx.showToast({ title: '连接失败', icon: 'none' })}, complete: () => { wx.hideLoading() } }) }) } //获取openid function getOpenId(app, that){ return new Promise(function (resolve, reject) { wx.login({ success: function (yes) { // 发送 res.code 到后台换取 openId, sessionKey, unionId var requestMapping = '/testopenid' var data = { code: yes.code } var requestWay = 'GET' var contentType = 'application/json' var p =request(requestMapping, data, requestWay, contentType) p.then(res => { //console.log(res) 做一些后续操作 app.globalData.openId = res.data; resolve(res) }).catch(e => { reject(e) }) }, fail(e) { console.log(e) } }) }) } 这样就解决了因为异步获取不到数据的问题。 技术有限,欢迎交流。 觉得有用请点个赞。
2020-12-05 - 微信小程序使用自定义目录(文件路径)进行下载/保存 案例(fail permission denied 解决方案)
场景描述 最近项目中有一个需要把网络文件下载下来保存到本地,然后对下载的文件进行读取,待文件不再使用后把文件进行删除的需求。当然也类似的需求还有很多,比如把小程序中的临时图片/文件永久保存下来等等,都是对文件操作的典型场景。 常见问题 在以上场景的实现过程中可能会遇到各式各样的问题,以下是比较常见的几个: 不清楚文件应该保存到哪个目录下。 fail permission denied 文件权限问题。 使用同步函数不清楚怎么获取执行结果。 API提炼 提到文件操作我们会自然而然地想到了API中的FileSystemManager相关的API,我这里用到的函数有以下几个: 下载函数 wx.downloadFile(Object object) 异步函数: FileSystemManager.access(Object object) FileSystemManager.mkdir(Object object) 同步函数: FileSystemManager.accessSync(string path) FileSystemManager.mkdirSync(string dirPath, boolean recursive) 我对同样的业务逻辑分别分别尝试了异步和同步的两种不同的方案,下面我们用一个最简单的下载保存到本地的案例来切入正题。 案例实践 一:获取正确的文件目录路径 当然在保存文件之前我们先要解决一个小问题,那就是我们要保存到哪里?也就是我们自定义的目录。这里我们简单命名其为 [代码]//自定义缓存文件根路径 var rootPath = "......"; [代码] (当然你也可以命名成其他名字) 变量名字可以随便写,不过自定义路径可不能随便写,也不可以在下载的时候直接给一个path路径,否者就会抛出没有权限或找不到文件的异常。所以开发者并不是可以随意的决定自定义文件的路径,这里就不卖关子了,小程序API中有一个很容易被忽略的API, wx.env.USER_DATA_PATH是专门获取文件系统中的用户目录路径的常量值。 这就是我们在小程序中合法的可操作文件的根目录路径: [代码]rootPath = wx.env.USER_DATA_PATH; [代码] 好了到目前为止我们已经知道了我们该往里存储文件了。 定义一下我们下载文件的缓存目录 [代码]var cachePath = rootPath+"/cache"; [代码] 也就是说之后我们下载的文件都会保存到 /cache 目录下,我们在之后的代码中用到的目录路径均为此路径。 二:异步函数实现方案 我们先来用异步函数来实现一下下载保存的流程。 1 下载文件之前我们首先要判断当前目录是否存在,如果目录不存在我们就直接下载文件到该目录下就会抛出 fail permission denied [图片] 这是判断目录存在的代码 [代码] access() { return new Promise(function(resolve, reject) { let fm = wx.getFileSystemManager(); fm.access({ path: cachePath, success: function(res) { resolve(); }, fail: function(err) { resolve(err); } }); }); }, [代码] 2 如果目录真实存在那我们当然可以直接使用,如果目录不存在则需要开发者自己创建目录。 [代码] mkdir(){ return new Promise(function(resolve, reject) { let fm = wx.getFileSystemManager(); fm.mkdir({ dirPath: cachePath, recursive: true, success: function(res) { resolve(); }, fail: function(err) { resolve(err); } }); }); }, [代码] 代码执行完之后我可以验证一下目录是否如我们所愿被创建出来。 开发工具右上角的详情–>基本信息–>文件系统–>当前小程序文件系统根目录 [图片] 点击usr文件夹进入根目录 [图片] 执行完上面代码之后我们可以看一下 cache 文件夹确实已经存在了 [图片] 3 目录也存在了,万事具备只欠下载了 [代码] downloadFile() { let fileUrl = 'https://res.wx.qq.com/wxdoc/dist/assets/img/0.4cb08bb4.jpg'; wx.downloadFile({ url: fileUrl, filePath: cachePath + '/temp.png', success: function(res) { console.log('downloadFile success', res); }, fail: function(err) { console.log('downloadFile fail', err); } }); }, [代码] 那执行完下载的代码之后我们再来看看cache目录 [图片] 这就是我们刚刚下载的图片。 ok,异步的整个下载和文件创建流程就走完了。接下来我们来瞅瞅同步流程中有哪些需要我们注意的。 三: 同步方案 同步方案和异步方案的流程大体一致,都是先判断文件目录是否存在,若不存在则创建目录,存在则执行下载逻辑。 使用同步函数需要特别注意的是怎么去判断函数的执行结果,由于 FileSystemManager.accessSync(string path) FileSystemManager.mkdirSync(string dirPath, boolean recursive) 这两个同步函数没有直接给出任何的返回结果。那我们怎么知道目录是否存在、目录是否被创建成功了呢? 这里我们需要剑走偏锋一下,即利用同步函数抛出的异常来判断结果。 我们直接来看代码 [代码] accessSync() { return new Promise(function(resolve, reject) { let fm = wx.getFileSystemManager(); try { fm.accessSync(cachePath); resolve(); } catch (err) { resolve(err); } }); }, mkdirSync() { return new Promise(function(resolve, reject) { let fm = wx.getFileSystemManager(); try { fm.mkdirSync(cachePath, true); resolve(); } catch (err) { resolve(err); } }); }, [代码] 可以看到我们的代码中多了 try catch 的代码结构,因为同步方法中没有直接返回给我们可用的信息,那我们可以认为同步函数正常执行完的结果为true或success,而进入 catch 后则结果为false或fail亦或根据具体异常具体处理。我们利用同步函数来走一遍下载保存的流程。 [代码] // 同步函数流程 this.accessSync().then(function (err) { if (err) { return that.mkdirSync(); } }).then(function (err) { if (!err) { that.downloadFile() } }); [代码] 可以看到我们利用同步函数下载的图片。 [图片] 总结时刻 我们分别使用异步和同步函数完成了目录的创建和文件的下载等流程。在这个过程中我们特别需要注意几点: 操作文件的根目录是以 wx.env.USER_DATA_PATH 开头的。 使用自定义目录时一定主要不可直接使用,需要增加 判断目录存在、创建目录 两个步骤。 使用同步函数时的执行结果是通过抓取同步函数抛出的异常来进行判断的。在没有给出直接结果的时候要学会利用异常信息来达到目的。 在创建目录时如果该目录存在同样会抛出异常(fail file already exists),这时按照success逻辑继续往下执行即可。 异步方案中介绍了目录查看的步骤和方法,可自行验证。其中usr目录是开发者自定义目录根节点,tmp目录是小程序默认的缓存根节点。 结尾 叙述若有不对或不严谨之处还请不吝指正。
2019-10-31