记录小程序开发中的一些坑和技巧

URI编码问题 在进行GET提交参数的时候,如果参数中有中文,记得要进行URI编码,encodeURI(参数) 如果不进行编码,ios设备正常。安卓设备会可能出现获取不到参数的情况。 新特性支持情况 有些新特性可能会只支持高版本,对于低版本不支持,开发的时候要看清楚官方文档。例如:icon组件的size属性从2.4.0基础库开始支持rpx属性。如果用户客户端基础库小于这个版本,就会出现无法显示的问题,从而影响业务。 封装请求 开发开始之前最好根据业务封装请求函数,这样会事半功倍。 建立工具函数库 把一些常用的函数,例如格式化时间、封装的请求、去空格等等。放到util.js里面做为工具类库。 使用WXS WXS是小程序脚本语言,功能类似于Vue的过滤器。但是WXS的语法不全和js一样,具体看官方文档。 静态资源问题 把大部分静态资源放到云上(除非你有特殊需求),把tab图标放到本地项目。这样会大大节省小程序资源。但是网络不好的情况,会出现静态资源加载慢的情况。 RESTful 接口独立 把请求的接口路径进行独立,单独放到一个类似serve.js的文件里面,便于以后维护。把域名也独立出来。 分包加载 在一些大项目的时候,根据业务可以使用分包加载技术。即把一些公用的页面放到主包,把其他的业务页面放到各个分包中,然后在app.js里面设置分包预加载。这样做可以节省网络开销,更有利于用户体验。但是,目前分包预加载只适用在app.js中配置,没有API接口。 rpx转px 在某些场景中不能使用rpx,比如在使用小程序动画功能的时候,并不支持rpx,只能输入固定的px值,这就对屏幕适配很不友好了。下面我贴出我自用的rpx转px函数: const rpxToPx = rpx => { let rpx1 = parseInt(rpx); return Math.floor(rpx1 / 750 * wx.getSystemInfoSync().windowWidth) }

March 19, 2019 · 1 min · 37 words · Norman Wang

chart.js更改LineChart的动画方向为从左到右

问题探讨 chart.js到LineChart图表默认动画方向是从下到上,没有像echart那样从左到右到效果。动画方向上写死到代码里面到,不可更改。我通过翻阅GitHub issues发现了有人提出过类似到问题,作者也简答了,可惜我能力有限没有理解作者到意思,那就从另一种思路开始解决吧。 问题解决 我的思路是,把动画关闭也就是duration = 0,然后x轴数据固定,y轴数据通过update方法没次更新一个值,使用定时执行每50ms update一次,人眼就产生到从左到右到到感觉。 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> </head> <body> <div> <img src="https://wong-1251253615.cos.ap-shanghai.myqcloud.com/blog/images/2019-03-13/chart.gif" alt=""> </div> <div style="height: 300px;width: 500px"> <canvas id="chart1"></canvas> </div> <script src="js/chart.js"></script> <script> var xData = ['x1','x2','x3','x4','x5','x6','x7','x8','x9','x10'], yData = [13,35,78,100,130,190,240,300,350,400], len = yData.length, count = 0; var myChart = new Chart('chart1',{ type: 'line', data: { labels: xData, datasets:[{ label: '测试', backgroundColor: 'rgba(54, 162, 235, .8)', // 背景填充色 borderColor: 'deepskyblue', // 路径颜色 pointBackgroundColor: 'rgba(54, 162, 235, .4)', // 数据点颜色 pointBorderColor: 'translate', // 数据点边框颜色 data: [] // 对应的值 }] }, options: { animation: { duration: 0 }, scales: { yAxes:[{ ticks:{ max: 400 } }] } } }); var timer = setInterval(() => { if(count > len){ clearInterval(timer) } myChart.data.datasets.forEach(function(dataset){ dataset.data.push(yData[count]) }) myChart.update(); count++; },100) </script> </body> </html>

March 13, 2019 · 1 min · 133 words · Norman Wang

chart.js图例与图表距离更改方法

chart.js提供给开发者的配置项较少,往外暴露的自定义接口也较少。我在开发中发现图例与图表的距离是个固定值10像素不可更改,然后在研究chart.js的源代码发现了更改方法。 下载chart.bundle.js 定位到16159行 如果你的图例的水平排列,则改高。如果是垂直排列则改宽 if (isHorizontal) { minSize.width = me.maxWidth; // fill all the width // 水平状态时图例到图表的距离,默认10px minSize.height = display ? 20 : 0; } else { // 垂直状态时图例到图表的距离,默认10px minSize.width = display ? 10 : 0; minSize.height = me.maxHeight; // fill all the height }

March 12, 2019 · 1 min · 45 words · Norman Wang

哔哩哔哩视频下载请求头的分析及伪造

B站视频下载需要用到2个接口,一个是通过Aid号获取视频基本信息和Cid号的接口,一个是通过视频Cid号获取下载地址的接口。 接口一 https://api.bilibili.com/x/web-interface/view?aid=29702317 接口二 https://api.bilibili.com/x/player/playurl?avid=29702317&cid=51686571&qn=74&otype=json 注意事项 接口二在获取视频下载地址的时候,如果没有cookie信息的话最大只能获取到480P清晰度的视频,需要用账户登录后获取其cookie里面的SESSDATA值(SESSDATA有效期是一个月)请求的同时还要在headers中添加User-Agent和Host,具体请看下面: headers: { 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:56.0) Gecko/20100101 Firefox/56.0', 'Host': 'api.bilibili.com', 'Cookie': 'SESSDATA=bd125cdd%2C1554519358%2C6d4df231' } 在下载视频的时候,headers也要进行伪造,具体看下面: headers: { 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:56.0) Gecko/20100101 Firefox/56.0', 'Accept': '*/*', 'Accept-Language': 'en-US,en;q=0.5', 'Accept-Encoding': 'gzip, deflate, br', 'Range': 'bytes=0-', 'Referer': 'http://www.bilibili.com/video/av46174956/', 'Origin': 'https://www.bilibili.com', 'Connection': 'keep-alive' } 把Referer里面的av号替换成下载的视频的aid号

March 1, 2019 · 1 min · 57 words · Norman Wang

Flutter开发环境的搭建(MacOS)

安装Homebrew /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" 安装/配置Git brew install git git config --global user.name "John Doe" git config --global user.email johndoe@example.com 下载/配置Flutter Flutter推荐上官网下载,不用使用Homebrew安装 下载地址:https://flutter.dev/docs/get-started/install/macos 下载好以后解压到一个位置,然后把路径添加大盘PATH里面,具体请看下面: vim ~/.bash_profile // 在这个文件里面新增一行,记得把路径替换成你的路径,然后保存退出 export PATH=/app/flutter/bin:$PATH // 重载一下 source ~/.bash_profile // 输入命令测试一下是否配置正确 flutter -h //这个命令会检查当前flutter的环境是否安装正常,前面有x的代表没有安装,需要安装。下面就会根据这些x进行环境搭建 flutter doctor 安装Android Studio 下载地址:https://developer.android.google.cn/studio/#downloads 配置Android Studio 使用Android Studio需要科学上网,打开AS后首先配置一下代理。然后根据引导进行下载SDK,SDK版本根据个人需求下载,不知道的下载最新的和一个老三代的就OK了。SDK下载好以后,再通过AVD下载一个模拟器。 然后打开Plugin下载Flutter插件。 安装安卓协议 一路输入Y就可以了,这个安装好以后,安卓环境算是搭建好了,可以运行一下flutter doctor看一下目前环境情况 flutter doctor --android-licenses ios环境搭建 此时运行flutter doctor出现以下结果,你的可能与我的不同,依你的为准。 安装图上的提示按顺序安装对应的软件。 brew install --HEAD libimobiledevice brew install ideviceinstaller brew install ios-deploy brew install cocoapods pod setup 安装完这些再运行flutter doctor,此时只有VScode环境没有配置好其他都应该正常了,如果你使用VCcode,可以再配置VScode环境,我不使用就不配置了。 ...

January 10, 2019 · 1 min · 88 words · Norman Wang

Win10搭建Git服务器

软件及环境 Win10专业版 Git 2.13.3-X64 Copssh 4.1.0 搭建过程 安装Git 所有的安装选项维持默认即可,建议不要安装到Program Files文件夹。 安装Copssh 建议安装路径不要安装到Program Files文件夹 安装的时候会创建一个windows账户,建议自定义。到时候在传代码的时候会用到 会安装一个openssh服务,点击确定 安装完成 配置Copssh 在开始菜单中找到 COPSSH Control Panel 打开,点击Users栏,按图中添加CopSSH用户(此用户就是安装时候添加的那个)。 将Git安装目录下C:\Git\mingw64\bin中的git.exe、git-receive-pack.exe、git-upload-archive.exe、git-upload-pack.exe、libiconv-2.dll五个文件拷贝到Copssh安装目录bin文件夹中 打开Copssh安装路径下etc/profile文件。在最后追加四行命令(注意:前三行命令中的/cygdrive后面换成你的路径) gitpath='/cygdrive/e/Git/mingw64/bin' gitcorepath='/cygdrive/e/Git/mingw64/libexec/git-core' gitmergetoolspath='/cygdrive/e/Git/mingw64/libexec/git-core/mergetools' export PATH="/bin:$syspath:$gitpath:$gitcorepath:$gitmergetoolspath:$winpath" 服务端配置Git 将下面三个路径添加到环境变量path中,注意替换成你的路径 C:\Git\mingw64\bin C:\Git\mingw64\libexec\git-core C:\Git\mingw64\libexec\git-core\mergetools 客户端配置Git 配置git名称和邮箱,注意邮箱和名字替换成你自己的 git config --global user.email "mrtantan@gmail.com" git config --global user.name "mrtantan" 此时你就可以用密码登录了,如果你还想实现免密登录(公钥登录),就往下看吧 通过下面命令生成公私钥,邮箱就是上一步设置的邮箱,连续按三次空格就行 ssh-keygen -t rsa -C "youremail@example.com" 公钥登录 在服务端的copssh安装目录下面的home\你创建的用户.ssh 文件夹下面新建authorized_keys文件,注意没有扩展名 把客户端生成的公钥文件复制到上一步的authorized_keys文件里面(公钥文件是 C:\Users\当前用户名.ssh 文件夹下的 .pub格式文件。直接用文本编辑器打开复制里面的内容到authorized_keys文件,如果添加多个请换行) 关于下面这个选项,如果未选中的话,必须要有公钥才可以登录,不可以用密码登录。如果选中的话,会首先验证公钥,没有公钥在验证密码。 现在你就可以用公钥登录了。 遇到的2个坑 执行 git clone 时候会出现下面情况 $ git clone git@1xx.2xx.3xx.2xx:/xxx/sample.git Cloning into 'sample'... @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY! Someone could be eavesdropping on you right now (man-in-the-middle attack)! It is also possible that a host key has just been changed. The fingerprint for the ECDSA key sent by the remote host is x:1x:ex:6x:ax:cx:6x:6x:3x:3x:ex:3x:0x:cx:1x:5x. Please contact your system administrator. Add correct host key in /c/Users/xxx/.ssh/known_hosts to get rid of th is message. Offending RSA key in /c/Users/xxx/.ssh/known_hosts:10 ECDSA host key for 1xx.2xx.3xx.2xx has changed and you have requested strict chec king. Host key verification failed. fatal: Could not read from remote repository. Please make sure you have the correct access rights and the repository exists. 问题原因: 服务端发生变化,加密的密钥改变,造成本地保存的密钥失败。 ...

November 4, 2018 · 1 min · 199 words · Norman Wang

小程序WebSocket实践(心跳检测、断线重连)

我把小程序WebSocket的一些功能封装成一个类,里面包括建立连接、监听消息、发送消息、心跳检测、断线重连等等常用的功能。 export default class websocket { constructor({ heartCheck, isReconnection }) { // 是否连接 this._isLogin = false; // 当前网络状态 this._netWork = true; // 是否人为退出 this._isClosed = false; // 心跳检测频率 this._timeout = 3000; this._timeoutObj = null; // 当前重连次数 this._connectNum = 0; // 心跳检测和断线重连开关,true为启用,false为关闭 this._heartCheck = heartCheck; this._isReconnection = isReconnection; this._onSocketOpened(); } // 心跳重置 _reset() { clearTimeout(this._timeoutObj); return this; } // 心跳开始 _start() { let _this = this; this._timeoutObj = setInterval(() => { wx.sendSocketMessage({ // 心跳发送的信息应由前后端商量后决定 data: JSON.stringify({ "key": 'value' }), success(res) { console.log(res) console.log("发送心跳成功"); }, fail(err) { console.log(err) _this._reset() } }); }, this._timeout); } // 监听websocket连接关闭 onSocketClosed(options) { wx.onSocketClose(err => { console.log('当前websocket连接已关闭,错误信息为:' + JSON.stringify(err)); // 停止心跳连接 if (this._heartCheck) { this._reset(); } // 关闭已登录开关 this._isLogin = false; // 检测是否是用户自己退出小程序 if (!this._isClosed) { // 进行重连 if (this._isReconnection) { this._reConnect(options) } } }) } // 检测网络变化 onNetworkChange(options) { wx.onNetworkStatusChange(res => { console.log('当前网络状态:' + res.isConnected); if (!this._netWork) { this._isLogin = false; // 进行重连 if (this._isReconnection) { this._reConnect(options) } } }) } _onSocketOpened() { wx.onSocketOpen(res => { console.log('websocket已打开'); // 打开已登录开关 this._isLogin = true; // 发送心跳 if (this._heartCheck) { this._reset()._start(); } // 发送登录信息 wx.sendSocketMessage({ // 这里是第一次建立连接所发送的信息,应由前后端商量后决定 data: JSON.stringify({ "key": 'value' }) }) // 打开网络开关 this._netWork = true; }) } // 接收服务器返回的消息 onReceivedMsg(callBack) { wx.onSocketMessage(msg => { if (typeof callBack == "function") { callBack(msg) } else { console.log('参数的类型必须为函数') } }) } // 建立websocket连接 initWebSocket(options) { let _this = this; if (this._isLogin) { console.log("您已经登录了"); } else { // 检查网络 wx.getNetworkType({ success(result) { if (result.networkType != 'none') { // 开始建立连接 wx.connectSocket({ url: options.url, success(res) { if (typeof options.success == "function") { options.success(res) } else { console.log('参数的类型必须为函数') } }, fail(err) { if (typeof options.fail == "function") { options.fail(err) } else { console.log('参数的类型必须为函数') } } }) } else { console.log('网络已断开'); _this._netWork = false; // 网络断开后显示model wx.showModal({ title: '网络错误', content: '请重新打开网络', showCancel: false, success: function (res) { if (res.confirm) { console.log('用户点击确定') } } }) } } }) } } // 发送websocket消息 sendWebSocketMsg(options) { wx.sendSocketMessage({ data: options.data, success(res) { if (typeof options.success == "function") { options.success(res) } else { console.log('参数的类型必须为函数') } }, fail(err) { if (typeof options.fail == "function") { options.fail(err) } else { console.log('参数的类型必须为函数') } } }) } // 重连方法,会根据时间频率越来越慢 _reConnect(options) { let timer, _this = this; if (this._connectNum < 20) { timer = setTimeout(() => { this.initWebSocket(options) }, 3000) this._connectNum += 1; } else if (this._connectNum < 50) { timer = setTimeout(() => { this.initWebSocket(options) }, 10000) this._connectNum += 1; } else { timer = setTimeout(() => { this.initWebSocket(options) }, 450000) this._connectNum += 1; } } // 关闭websocket连接 closeWebSocket(){ wx.closeSocket(); this._isClosed = true; } } 使用方法 在app.js里面引入,然后在onLaunch里面创建 ...

October 30, 2018 · 3 min · 522 words · Norman Wang

小程序跨页面通信实现

借助一个 weapp-event 的第三方插件可以轻松实现小程序跨页面通信 原作者的这个插件有时候会出现发送两次的问题,为此我改良了一下 改良版 在发射事件里面添加了一个判断来检测 var events = {}; function on(name, self, callback) { var tuple = [self, callback]; var callbacks = events[name]; if (Array.isArray(callbacks)) { callbacks.push(tuple); } else { events[name] = [tuple]; } } function remove(name, self) { var callbacks = events[name]; if (Array.isArray(callbacks)) { events[name] = callbacks.filter((tuple) => { return tuple[0] != self; }) } } function emit(name, data) { var cb = events[name],callbacks; if(cb){ callbacks = []; callbacks.push(cb[0]); } console.log(callbacks); if (Array.isArray(callbacks)) { callbacks.map((tuple) => { var self = tuple[0]; var callback = tuple[1]; callback.call(self, data); }) } } exports.on = on; exports.remove = remove; exports.emit = emit; 用法 在需要发送和监听事件的页面引入此js ...

August 27, 2018 · 1 min · 126 words · Norman Wang

小程序实现聊天室功能(WebSocket技术)

演示 功能 群聊,每个进入的用户都可以看到自己和他人的聊天记录 实时显示当前在线人数 自己发送的消息和他人有区分 服务端 const express = require('express'); const app = express(); const WebSocket = require('ws'); const serve = app.listen(3001,function () { console.log('server run 3001'); }); const ws = new WebSocket.Server({ server: serve }); let clientsUser = {}; // 获取连接个数 function allConnections(ws) { let i = 0; ws.clients.forEach(function each() { i+=1 }); console.log(`[SERVER] 共有${i}个连接`); return i; } // 广播 function broadcast(data) { let num = allConnections(ws); ws.clients.forEach(function each(client) { client.send(data); }); console.log(`[SERVER] 给${num}个用户发送了广播消息`); } // 判断id function checkId(id) { let resArray = id.split("_"); if (resArray[0] === 'user'){ return 'user'; } if (resArray[0] === 'manage'){ return 'manage'; } } // 判断id是否存在 function checkIdExist(clientsUser,id) { for (key in clientsUser){ if (key === id){ return true; } } return false; } // 发送目前总连接数 function sendCountNum(ws) { let broadcastMsg = JSON.stringify({ "rstCode": "0", "rstMsg": { "count": allConnections(ws) } }); broadcast(broadcastMsg); } // 监听连接 ws.on('connection',function (result) { console.log('客户端正在连接中...'); // 监听发送消息 result.on('message',function (message) { let res = JSON.parse(message), checkRes = checkId(res.websocketId); // 检测是否是用户 if (checkRes === 'user'){ // 检测如果是登录状态则保存用户信息 if (res.status === 'login'){ // 检测服务端是否存储用户信息,存储则删旧添新,没有则直接添加 if (checkIdExist(clientsUser,res.websocketId)){ delete clientsUser[res.websocketId]; clientsUser[res.websocketId] = result; // 广播目前总连接数 sendCountNum(ws); }else { clientsUser[res.websocketId] = result; // 广播目前总连接数 sendCountNum(ws); } } // 发送信息操作 if (res.status === 'send'){ let msg = JSON.stringify({ "rstCode": "0", "rstMsg": { "count": allConnections(ws), "msg": res.msg, "websocketId": res.websocketId, "nickname": res.nickname } }) broadcast(msg); } } }) // 监听下线 result.on('close', function (close) { for (key in clientsUser){ if (clientsUser[key] === result){ delete clientsUser[key]; console.log(`[SERVER] 客户端${key}连接已关闭`); } } // 广播目前的连接数 let msg = JSON.stringify({ "rstCode": "0", "rstMsg": { "count": allConnections(ws) } }) broadcast(msg); }) }) 客户端 wxml ...

August 7, 2018 · 4 min · 642 words · Norman Wang

WebSocket 简单入门(Node.js)

服务端 服务端是使用Node.js的ws模块,具体API可以点击这里 –> 点我查看 <– // 引入express和ws const express = require('express'); const app = express(); const WebSocket = require('ws'); // 使用express创建一个server const serve = app.listen(3000,function () { console.log('server run 3000'); }); // 把这个server绑定到websocket-server上 // 以后所有的请求都会经过websocket-server // 如果是websocket连接就会由websocket-server接管,如果是普通连接会由express-server接管 const ws = new WebSocket.Server({ server: serve }); // 这里创建的clientsInfo用来保存目前连接的所有用户发送的信息 // clientsUser用来保存连接的用户系统信息,采用自定义数据作为key,用来私发消息 let testData,clientsInfo = [],clientsUser = {}; app.get('/websocket/sendtoken', function(req, res){ console.log('data',req.query.token); testData = req.query.token; res.json({ msg: 'ok', token: req.query.token }) }); app.get('/websocket/clientsinfo', function(req, res){ res.json({ msg: 'ok', data: clientsInfo }) }); // 获取总连接个数 function allConnections(ws) { let i = 0; ws.clients.forEach(function each() { i+=1 }); console.log(`[SERVER] : 共有${i}个连接`); return i; } // 广播方法 ws.broadcast = function broadcast(data) { let num = allConnections(ws); ws.clients.forEach(function each(client) { client.send('广播'+data +'共有'+ num +'个用户'); }); console.log(`[SERVER] 收到客户端消息: ${data},共有${num}个用户`); } // 删除用户方法 ws.removeClientInfo = function removeClientInfo(clientsInfo,client) { let clientName = client.userName; clientsInfo.forEach(function (item,index) { if (item.userName === clientName){ clientsInfo.splice(index,1); } }); return clientsInfo; } ws.on('connection', function (result) { console.log(`[SERVER] 客户端已连接`); result.on('message', function (message) { let res = JSON.parse(message); if (res.used === 'login'){ // 用户登录后储存用户信息 clientsUser[res.userName] = result; clientsInfo.push(res); result.send('from server' + res.userName + '已储存'); console.log(res.userName + '已储存'); } if(res.used === 'logout'){ // 用户登出时删除用户信息 delete clientsUser[res.userName]; ws.removeClientInfo(clientsInfo,res); result.send('from server' + res.userName + '已删除'); console.log(res.userName + '已删除'); } // 管理端查看 发送当前连接数 if(res.used === 'manage'){ let currentClients = JSON.stringify(clientsInfo); result.send(currentClients); console.log(res.userName + '信息已发送'); } // 管理端给指定用户发送websocket信息 if (res.used === 'send'){ let sendUser = clientsUser[res.userName]; sendUser.send('hello,world!') } // 管理端 群发除了自己 if (res.used === 'sendall'){ console.log('接收到群发消息'); ws.clients.forEach(function each(client) { if (client !== result) { client.send('广播消息'); } }); } // 广播消息 // ws.broadcast(res.msg); // console.log(`[SERVER] 收到客户端消息: ${res.msg}`); // 单发消息 // result.send(`ECHO: ${res.msg}`, (err) => { // if (err) { // console.log(`[SERVER] error: ${err}`); // }else { // console.log(`[SERVER] 收到客户端消息: ${res.msg}`); // } // }); }) result.on('close', function (closeResult) { console.log(`[SERVER] 客户端${closeResult}连接已关闭`); }); }); 客户端 客户端其实都一样,应为html5已经全面支持WebSocket技术,可以直接new出来了!!! ...

July 31, 2018 · 3 min · 440 words · Norman Wang