0、杂记
0.1、在实际的开发中,图片资源不会存储在小程序的目录中,因为小程序的大小不能超过1MB(现在改为2M)。超过则无法真机运行和发布项目。我们应该将图片都存放在服务器上,让小程序通过网络来加载图片资源。
0.2、在wxss中,本地资源是无法使用的,比如:background-image
,如果使用本地的图片是无法显示的,可以使用网络图片来代替本地图片,同时要加上background-size
属性,属性值的单位为rpx。
0.3、小程序自适应尺寸单位rpx
,在微信小程序中,尺寸单位可以使用px,也可以使用rpx,使用rpx可以使组件自适应屏幕的高度和宽度,但是使用px则不会。(官网:规定屏幕宽为750rpx。如在 iPhone6 上,屏幕宽度为375px,共有750个物理像素,则750rpx = 375px = 750物理像素,1rpx = 0.5px = 1物理像素)。在开发过程中,到底使用rpx还是px?主要取决于业务需求,也就是需要元素随着移动设备尺寸的变化而变化,还是让元素始终保持不变,所以需要具体问题,具体分析。比如:一个元素的width、heigth、margin、 font-size,在很多的时候,需要随着设备的尺寸不同,而动态发生变化,从而保持页面元素之间的布局可以保持在一定的比例关系,在这种情况下就应该使用rpx,所以rpx作为小程序的自适应单位,它非常适合来控制图片的宽和高以及元素之间的间距。但是border相关属性不需要随着移动设备的尺寸变化而动态发生变化,因为,如果border动态变化,那么会在屏幕尺寸较大的手机上会变的很粗,这个并不是我们想要的结果,所以应当将border相关属性的单位设置为px。
0.4、关于分辨率和物理像素:在微信小程序的模拟器中,都给出了每种机型的分辨率,需要注意的是,这里的分辨率指的是逻辑分辨率pt,而非物理分辨率,以iphone6为例,模拟器的逻辑分辨率是375px×667px,设备像素比(Dpr:Device Pixel Ratio)为2,而iphone6的物理分辨率是1334px×750px,以上的意思是:iphone6的水平方向上有375个像素点,竖直方向上有667个像素点,而每个逻辑像素点包含2个物理像素点。所以开发人员一定要注意逻辑像素和物理像素的区别,1物理像素不等于1px,通常UI设计人员做出来的设计图的像素是物理像素。假设有一张图片的宽度是750像素(物理像素),我们想让这张图片充满整个页面,如果直接设置在页面里面将图片的宽度设置为750px,是不对的,正确的设置方法是为750rpx或者350px。
1、给container添加背景颜色显示问题
2、image组件9种图片裁剪与4种缩放的模式
如果一个元素的宽和高分别设置成340rpx和100%(在iphone6下就是750rpx),而雪糕图片的素材原始高度分别为600px和750px。
在现实的项目中,我们经常需要面对原始图片的尺寸和设计图里的尺寸不一样的情况(尤其是原始图片高度是未知和不固定的情况,比如动态从网络获取图片)。在这种情况下,我们必须要有所舍弃,或放弃等比例,或者裁剪掉图片的一部分。接受不完美,这也是编程中很重要的心态,如和选择,需要看业务上的需求。
小程序的image组件提供了4种缩放模式和9种裁剪模式,来支持我们的选择。
小程序页面设计的框架结构MINA(MVVM)
MINA(MINA IS NOT APP)就是微信小程序开发使用的框架
整个系统分为两块:视图层(View) 和 逻辑层(App Service)。
MINA可以让数据与视图保持同步非常简单。当做数据修改的时候,只需要在逻辑层修改数据,视图层就会做相应的更新。(通过this.setData进行同步数据)
3、page页面的生命周期
什么是页面的生命周期?如同人的成长需要分为出生、童年、青年、中年、老年一样,一个页面从创建到卸载,同样会经历以下5个周期:
- 加载
- 显示
- 渲染
- 隐藏
- 卸载
MINA框架分别提供了5个生命周期函数来监听这个5个特定的生命周期,以方便开发人员可以在这些特定的时刻执行一些自己的代码逻辑,它们分别是:
- onLoad 监听页面加载,一个页面只能会调用一次。
- onShow 监听页面显示,每次打开页面都会被调用
- onReady 监听页面初次渲染完成,一个页面只会调用一次,代表页面已经准备妥当,可以和视图层进行交互
- onHide 监听页面隐藏
- onUnload 监听页面卸载
可以在Page()方法中去演示这些页面的生命周期
Page({ data:{}, onLoad:function(options){ console.log('onLoad:页面被加载') }, onShow: function() { console.log('onShow函数被加载') }, onReady: function() { console.log('onReady函数被加载') }, onHide: function() { console.log('onHide函数被加载') }, onUnload: function() { console.log('onUnload函数被加载') } })
onHide和onUnload这两个函数需要执行一些API操作,比如页面执行tab栏切换页面、navigateTo方法、或者使用小程序切换后台的按钮时会执行onHide函数;当页面执行redirectTo或navigateBack的时候会执行onUnload函数。
当然我们还可以添加任意的数据和函数到这个Page方法的Object参数中,在页面的函数中用this就可以访问这些自定义的数据或者函数
以下内容你不需要立马完全弄明白,不过以后它会有帮助。
生命周期
通过以上图解可知
- onLoad 、onShow和onReady是按照先后顺序,依次执行
- onLoad、onReady在整个页面的生命周期中只会执行一次(除非这个页面被执行onUnload卸载掉了)
- onHide和onShow在一次生命周期内可能会执行多次。
- 除了页面的First Render第一次渲染,页面还有可能会Rerender 再次渲染多次,数据更新会造成页面的重新渲染,这里要注意的是,小程序仅在第一次 First Render完成后,提供了监听函数onReady,对于以后的Rerednder并没有提供相应的监听函数。所以,onReady仅用来监听“第一次渲染”完成。
4、数据绑定
小程序借鉴了比如像Angular、vue这些流行框架的思想,采用数据绑定的机制来做数据的初始化和更新,不同于像Angular和Vue的双向数据绑定,小程序仅实现了单向数据绑定,即支持从逻辑层传递到视图层的数据绑定,反之则不行。
小程序使用Page方法参数里的data变量作为数据绑定的桥梁,写在data里的数据,被称为数据绑定的初始化数据。
数据的绑定有以下两种:
- 一种是初始化数据的数据绑定,通常将这些数据直接写在Page方法参数的data对象下面。
- 另外一种是使用setData方法来做数据绑定,这中方式也可以理解为数据更新。这样的数据更新将引起页面的重新渲染(Rerender)
说明:小程序的脚本是运行在JSCore中,JSCore是一个没有DOM的环境,所以小程序只能使用数据绑定来做数据的相关操作。
4.1、初始化数据绑定
小程序使用Mustache语法双大括号{{}}在微信,wxml组件里进行数据的绑定,通过Page实例的生命周期,解释一下初始化数据绑定的过程
当页面执行了onShow函数后,逻辑层会收到一个通知(Notify),随后逻辑层会将data对象以json的形式发动到view视图层(Send Initial Data),视图层接收到初始化数据后,开始第一次渲染,显示初始化数据(First Render),最终将数据呈现在开发者的眼前。
说明:如果数据绑定是作用在组件的属性中,比如
<image src="{{pic}} />"
,一定要在{{}}外边加上双引号,否则小程序会报错,如果是内容型的数据,则不需要加双引号,比如<text>{{data}}</text>
4.2、查看数据绑定对象
通过AppData面板来查看和调试数据绑定变量,AppData下的数据以页面为组织单位,更改这里的某一项数据的值,都是实时进行更新的。
4.3、数据绑定更新
可以通过setData函数来做数据绑定,这种方法可以理解为“数据更新”。setData方法位于Page对象的原型链上:Page.prototype.setData。大多数情况下,我们使用this.setData的方式来调用这个方法。
setData的参数接受一个对象,以key和value的形式将this.data中的key对应的值设置成value。
上面的话要注意两点:
- setData 会改变this.data变量里相同key的值。
- setData执行后会通知逻辑层执行Rerenader,并立刻重新渲染视图层。
this.setData所绑定或者更新的数据,并不要求在this.data中已预先定义。
Page({
data:{
},
onLoad: function(){
var objData = {
obj: {
text:"hello"
},
name:"小明"
}
this.setData({
pageData: objData
})
}
})
<text> {{pageData.obj.text}} </text>
5、列表渲染 wx:for
<block></block>
标签没有实质意义,它并不是组件,所以我们称作“标签”,它仅仅是一个包装,不会在页面内被渲染,在这里可以理解为一个JavaScript编程语言中的括号,在block标签中被包裹的元素将被重复渲染。
wx:for-item="item"
指定数组当前子元素的变量名,我们将元素的变量名指定为item,当然这个变量名可以更改。
如果不定义item数组子元素的变量名,依然是可以正常显示的,原因是小程序默认子元素的变量名就是item。
wx:for-index="idx"
指定当前元素在数组中索引号的变量名,我们命名为idx。
wx:for
并不是一定要作用在block标签上的,也可以作用在view组件上,一样可以照常运行,但是并不推荐使用view等组件来做列表渲染,因为我们希望标签或者组件元素是语义明确的,view组件通常被用来当做视图容器或者是区域的分隔,它有它的使命,不应该滥用。
6、事件
要从一个页面跳转到另外一个页面,需要使用事件来响应点击某个动作。
什么是事件?
事件定义:事件是视图层(wxml)到逻辑层的通讯方式。简单理解:事件可以让我们在js里处理一些用户在界面上的一些操作,并对这些操作做出反馈。比如:点击一个按钮,从一个页面跳转到另外一个页面,在这个过程中,需要在js里调用MINA框架的API,使从一个页面跳转到另外一个页面。
想要实现页面跳转的这个机制,需要做两件事情
- 在组件上注册事件,告诉小程序要监听哪个组件的什么事件?
- 在js中编写事件处理函数响应事件,也就是说,监听到事件后,需要编写自己的业务。
6.1、冒泡事件和非冒泡事件
冒泡事件是指某个组件上的事件被触发后,事件还会向父级元素传递,一直到页面的顶级元素。
非冒泡事件则不会向父级元素传递事件。
bind和catch的区别
bind不会阻止事件的传播,而catch会阻止事件继续向父节点传播。
7、导航
小程序提供了5个导航API,从而帮助开发者实现页面的跳转
-
wx.navigateTo
保留当前页面,跳转到应用内的某个页面,使用wx.navigateBack
可以返回到原页面 -
wx.redirectTo
关闭当前页面,跳转到应用内的某个页面 -
wx.switchTab
只能用于跳转到带有 tabBar 页面,并关闭其他所有非 tabBar 页面 -
wx.navigateBack
关闭当前页面,返回上一页面或多级页面。可通过getCurrentPages()
获取当前的页面栈,决定需要返回几层 -
wx.reLaunch
关闭所有页面,打开到应用内的某个页面
wx.navigateTo
和wx.redirectTo
的区别
tapHandle: function (){
wx.navigateTo({
url: '../main/main?name=小明',
})
// wx.redirectTo({
// url: '../footer/footer',
// })
},
onUnload: function(){
console.log("page is unload")
},
onHide: function (event) {
console.log("page is hide")
}
使用 wx.redirectTo
实现跳转,将打印输出 page is unload,但是不会输出page is hide,跳转到的页面没有返回按钮,无法返回到之前的页面了。
使用wx.navigateTo
实现跳转,将打印输出 page is hide,但是不输出 page is unload,页面左上角出现一个可以返回到之前页面的按钮。
所以总结出wx.redirectTo
是将关闭当前页面并将页面卸载,无法返回到之前的页面,而wx.navigateTo
仅仅是隐藏当前页面,还可以再次返回到之前的被隐藏的页面。
再来考虑一个问题,当wx.navigateTo
从一个页面跳转到另外一个页面后(从A页面跳转到B页面),再从B页面返回到A页面时,B页面会执行onHide还是onUnload呢?答案是执行B页面的onUnload函数,也就是说当从子页面返回到父页面时,子页面会被卸载。因为这样设计就不会造成大量的子页面残留在小程序中了。(早期的小程序是不会卸载的,后来在版本更新的时候更改的。)
7.1、小程序最多只能有5层页面
当我们使用wx.navigateTo
从父页面跳转到子页面后,就形成了2个页面层级,可以继续在子页面里使用wx.navigateTo
跳转到子页面。
但是小程序强制规定,只允许有最多5层父子页面,事实上,太多的页面将严重影响用户的产品体验,建议页面最多不要超过3层。
wx.redirectTo
不存在这个问题,因为当跳转到另外一个页面上后,上一个页面被强制卸载掉了。
8、小程序的模块化
如果所有的数据都写在js文件中,这样会污染了我们的业务层,我们应该把这些数据分离到一个单独的js文件中,
在项目的根目录下新建一个文件夹,命名为data,然后再data目录下新建一个js文件,命名为data.js。
// data.js
var postList = [
{
date:'1111',
title:'hahha'
}
]
然后将之前js文件中data里面的数据剪切到data.js中。所以我们提取出来的数据文件data.js可以看做是小程序的一个模块。同时我们需要在data.js中使用module.exports向外部暴露一个接口
// data.js
var postList = [
{
data:'1111',
title:'hahha'
}
]
module.exports = {
postList:postList
}
然后在其他js文件中引用这个data.js模块,
// 其它js文件
var dataObj = require('../data/data.js')
Page({
data:{
},
onLoad: funciton (){
this.setData({
postList:dataObj.postList
})
}
})
使用require 引用js模块时,要注意以下几点:
- 被引用的文件一定要带有拓展名.js,这一点是不同于页面路径的。
- path路径不可以使用绝对路径,否则会报错,应该使用相对路径。
- 在JavaScript文件中声明的变量和函数只在该文件中有效,不同的文件可以声明相同名字的变量和函数,不会相互影响。
9、小程序的模板化
我们通常可以将一些公共的、经常使用的业务逻辑提取成一个公共的函数,当在多个地方需要使用函数时,只需要要调用这个函数即可。
事实上,有一句话是这么描述软件开发的:编程世界里遇到的绝大多数问题都可以用封装的思想来解决。
// post-item-tpl.wxml
<template name="postItemTpl">
<view>
{{item.date}}
</view>
</template>
模板相关的内容必须包裹在<template></template>
标签中,使用name属性来指定模板的名称
// post.wxml
// 使用import来引用模板
<import src="./post-item-tpl.wxml">
<block wx:for="{{postList}}" wx:for-item="item" wx:for-idx="idx">
<template is="postItemTpl" data="{{item}}">
</block>
template的is属性指定要使用哪个模板,template的data属性,可以向template传递数据,这里是将wx:for得到的item传入到template里面,这样就可以在template内部使用这个item了。
模板的好处是它可以让多个调用方来调用,不可能要求每个调用方都使用同样的变量名来调用模板,这种由定义方要求调用方遵守变量名命名的做法是不合理的。
要解决这个问题,就必须消除template对于外部变量名的依赖,可以使用拓展运算符"…" 展开传入对象变量来消除这个问题。
<template name="postItemTpl" data="{{...item}}">
</template>
然后在 post-item-tpl.wxml 中就可以去掉{{}}中的item
// post-item-tpl.wxml
<template name="postItemTpl">
<view>
{{date}}
</view>
</template>
9.1 include与import引用模板的区别
- import 需要先引入template,然后再使用template,但是include不需要预先引入template,直接在需要的地方引入模板即可。
- include 模式非常简单,就是简单的代码替换,不存在作用域,也不能像import一样使用data传递变量。
// post.wxml
<block wx:for="{{postList}}" wx:for-item="item" wx:for-idx="idx">
// 使用include来引用模板
<include src="./post-item-tpl.wxml"/>
</block>
9.2 css模块化
使用@import "post-item-tpl.wxss"
引入样式文件
10、不要在template上注册事件
template标签仅仅只是一个占位符,在编译后,会被template的模板内容替换,所以在template标签上注册事件是无效的。同理,也不能在block上注册事件,因为block也会在编译后消失。
解决的办法就是在template标签的外部增加一个view组件,将template包裹起来,并将事件注册在view组件上。
11、页面间的传递参数
从文章列表页面跳转到文章详情页页面,需要正确展示被点击所对应的文章内容,首先就需要将文章的id号由index页面传递到index-detail页面,这样index-detai页面才能知道,到底要显示哪篇文章。
所以在这里就涉及到页面之前的参数传递与通信了,通过使用页面导航url的query参数传递,就能够实现页面之间的参数传递。
12、组件自定义属性名的规则
- 必须以data-开头。
- 多个单词由连接符 - 链接。
- 单词中最好不要有大写字母,如果有大写字母,除第一字母外,其余大写字母都转化成小写。
- 在js中获取自定义属性时,多个单词将被转化为驼峰命名。
13、文章id号流向图
14、动态设置导航栏标题
前提:在某些情况下,我们希望导航栏的文字可以根据页面内容的不同而有所变化,比如在文章详情页中,我们希望导航栏可以实时显示当前文章的标题,不同文章显示不同的标题。
小程序提供了wx.setNvigationBarTitle()
来动态设置导航栏标题,这个方法无论是在onLoad或者onShow函数中调用,都可以成功的设置导航栏标题,但是建议在onReady函数中设置此方法,因为onReady在onShow发生之后才触发,onShow将标题设置完毕后,onReady会重新渲染页面,并覆盖导航栏的标题。
15、实现图片预览功能
wx.previewImage
只能预览位于网络中的图片,无法预览本地的图片。
16、wx:if和hidden控制元素显示和隐藏
<view hidden="{{falg}}"</view>
<view hidden="{{!falg}}"</view>
<view wx:if="{{falg}}"></view>
wx:if
有较高的切换消耗,而hidden有更高的初始消耗(使用hidden
话,组件始终会被渲染,只是简单的控制显示和隐藏),因此,在需要频繁切换的情景的话,使用hidden
更好,更合适,在运行条件不大可能改变的时候,使用wx:if
比较好。
17、实现回车发送评论消息的功能
- bindinput
- bindfocus
- bindblur
- bindconfirm
以上事件都属于非冒泡事件
- bindinput具有以下特定
- 当用户输入字符时触发
- 每当用户输入或者删除一个字符时,bindinput事件就会触发一次
- 可以在事件响应函数中使用return 返回一个字符或者字符串,该字符串将替换input输入框中的显示文本
- 非常适合做“即时搜索”功能
- bindfocus 当input组件获取焦点时触发
- bindblur 当input组件失去焦点时触发
- bindconfirm 响应真机上点击键盘“完成”按钮事件。或者键盘上的回车事件,
input输入值都是在事件对应的响应函数中使用event.detail.value
20、解决真机运行时页面卡顿问题
给页面的容器加上-webkit-overflow-scrolling:touch;
css属性
21、杂项知识
1、小程序提供了一个全局方法,getAppp()
,用于获取小程序的APP对象,我们可以通过app.globalData
来访问全局变量
2、分享按钮是页面行为,而不是应用程序的行为,每个页面都可以调用分享API,并设置分享的参数。onShareAPPMessage
,这个方法是一个页面方法,不是wx.onShareAPPMessage
。
3、微信小程序动画
animation.scale(2,2).rotate(45).step().translate.step()
以上是两个动画组,它们之间使用step()作为分隔,每个动画组中的动画方法执行顺序是:同一组中的动画方法会同时执行,但动画组必须是先后执行。也就是说一组动画先执行完成后,后面的动画组中的动画才能执行。
可不可以在不同的动画组中设置不同的动画效果参数?答案是可以的。每个step方法都可以接受一个obiect对象:可以传入一个跟wx.createAnimation()一样的配管.参数,用于指定当前组动画的配置。
step接受动画配置
//创建一个动画实例,
var animation = wx.createAnimation(!timingFunction: 'ease-in-out
// 设置动画组(以step()分隔),每个队列,
animation.scale(2, 2).rotate(45).step().translate(30).step(( duration: 1000 )
小程序提供了6类动画方法:
- 样式opacity, backgroundColor, width. height. top. left. bottom. right.
- 旋转rotate, rotateX. rotateY, rotatez. rotate3d,
- 缩放scale. scaleX. scaleY, scalez. scale3d.
- 偏移 translate, translateX. translateY. translateZ. translate3d.
- 倾斜 skew, skewX. skewY.
- 矩阵变形matrix, matrix3d.
4、电影
5、实现页面下拉刷新“ 三部曲”
实现一个页面的刷新需要三步
- 在当前页面json文件中配置enablePullDownRefresh选项为true,打开开关
- 在当前页面的js中编写onPullDownRefresh函数,在函数中完成下拉刷新逻辑
- 编写完下拉刷新逻辑代码后,主动调用wx.stopPullDownRefresh函数停止当前页面的下拉刷新
6、scroll-view组件的注意要点
- 如果scroll-view下排列的多个子元素是块级元素(比如view) ,就直接对sctoll-view设置display:fex和flex-direction:row,不会使子素首动成为水平列,如果不使用y而将容器元素换成view,那么设置display:flex和flex-directionrow是可以使子元素自动成水平排列的.
- 如果想让scroll-view下的view元素水平排列,一种可行的方法是将子元素view设置为有inline-block或者inline-flex。
- 子元素有可能出现换行的情况,需要在容器上设置white-space:nowrap;
- 请勿在scroll-view中使用textarea、map、Canvas、video组件
- scoll-into-view的优先级高于scroll-top。
- 在滚动scroll-view时会阻止页面回弹,所以在scroll-view中滚动是无法触发onPullDownRefresh的。
- 若要使用下拉刷新,请使用页面的滚动(wx.pageScrollTo),而不是scroll-view,这样也能通过点击顶部状态栏回到页面顶部。
22、获取用户基本信息
小程序提供一个wx.getUserInfo()方法来获取用户信息,用户信息分为用户基本信息和用户 openId UnionId。基本信息是明文的,而openId和unionid是加密数据。这两种类型的数据都由wx.getUserInfo()方法返回。
在小程序汇中,用户的基本信息可以轻易获得,他们是明文的、不加密的。但是openid和unionid是加密的,什么是openid和unionid?可以将openid和unionid理解为用户在微信应用中的id号,他们的区别是:openid只代表用户的在某个微信应用下的id号,而unionid是跨应用的。同一个用户在同一个开发者的多个应用里面,unionod是唯一的。
如果开发者拥有多个移动应用、网站应用和公众账号(包括小程序),可以通过unionid区分用户的唯一性,因为只要是同一个微信开放平台账号下的移动应用、网站应用和公众账号(包括微信小程序),用户的unionid就是唯一的。也就是同一个用户在同一个微信开放平台下的不同应用中,unionid是相同的。
所以openid不能跨应用,如果要在多应用间统一用户身份,请使用unionid。这里要注意的是,在微信小程序中使用unionid首先需要前往微信开放平台绑定小程序。
如果要开发真实的项目,一定要考虑各种调用失败的情况,并加强安全性。
23、用户登录
微信小程序登录是为了让开发者的服务器获取用户的openid以及session_key的令牌。微信小程序提供了wx .login()方法用于用户登录。wx.login的方法的主要目的是拿到用户的openid和用户本次登录的session_key。
openid是用户对于当前小程序的身份标识。
session_key是本次用户登录的会话密钥,通常用来对用户的通信数据进行加密。通常用来对用户的通信数据进行加密。
要获取session_key和openId,首先需要在小程序中调用wx.login,并获取code;随后将code发送到开发者服务器并同appid和appsecret一起发送到微信服务器中,微信服务器会返回我们需要的session_key和openId。
code是一把钥匙,是得到openid和session_key的关键。code的有效期只有5分钟,如果在5分钟之内还没有用code换取openid和session_key,那么就不能在使用了。
code是一把钥匙,是得到openid和session_key的关键,code有效期只有5分钟,如果在5分钟之内还没有用code换取openid和session_key,那么就不能再使用了。
session_key 肯定是有失效期的,在session_key有效期内,开发者最好不要重复调用wx.login接口,不断用code换取session_key,而应该将session_key保存在服务器中,等到session_key失效后,再重新获取新的session_key。
怎么检查session_key是否失效了呢?小程序提供了wx.checkSession();来校验session_key是否失效,只有在session_key确认失效后,才能再次调用wx.login。
wx.login得到的code只能使用一次,一旦你使用code换取了openid和session_key,这个code就马上失效,不能再次使用。当然如果5分钟内这个code还没有使用,那么也会失效。
在实际的项目中,将openid和session_key返回到客户端是非常危险的,也完全没有必要。因为需要使用openid和session_key的场景都会被放到服务器进行的,所以返回到小程序中没有任何意义,反而会增加数据的泄露风险。
24、用户信息校验
调用wx.getUserInfo接口拿到了用户的明文基本信息数据和用户加条新据,并使用了明文数据。下面回顾一下wx.getUserInfo返回的数据:
- userlnfo 用户信息对象。
- awData 不包括敏感信息的原始数据字符串。
- signature 使用shal( rawData + sessionkey )得到字符串,用于校验用户信息。
- encryptedData 包括敏感数据在内的完整用户信息的加密数据.
- iv加密算法的初始向量。
,我们使用了userlnfo对象,包括userInfo和rawData在内的明文数据。都可能存在被篡改的风险。如何知道明文数据是否被篡改了呢?
这个时候rawData和singature就可以发挥作用了.rawData和signature用于校验用户数据到底有被篡改过(没有绝对安全的网络,据极有可能被抓包或者通过其他万式 .通常来说,想要实现这个校验必在服务器编码才能进行。这需要小程序将获取的rawData和signature一并提交到服务器,由服务器完成校验工作。
校验的基本原理是: rawData是用户原始明文数据, signature是使用shal (rawData +sessionkey)得到的字符串。理论上讲,如果数据没有被篡改,那么signature等于shal(rawData+ sessionkey);如果rawData或者signature被修改了,那么signature必然不再等于shal (rawData.+ sessionkey).
是否存在signature和rawData同时被修改的情况呢?理论上是不可能的,因为session key并不在网络上传输,篡改者不知道这个变量,被篡改且校验通过的概率很小。
有可能从signature中 session key论E,这是不可能的。因为shal算法是不可逆的,无法在已知rawDatasignature的情况下推算出session kev,不知道session key就无法通过同时修改rawData和signature达到“欺骗校验的目的”。如果知道了session key,只需要修改rawData并重新用session key计算一下新的shal (rawData+session kev)就又可以让新的rawData等于新的shal (rawData+session key)了。这样,开发者就无法知道rawData是被修改过的。
这也是为什么官方文档一再强调,不要在网络上传输session kev,而应该将其保存在服务器上使用,以降低session key被泄露的风险。
session key有点类似于我们在数据库中保存用户密码时所使用的“盐(salt) "。在数据库保存用户密码时,并不是直接将用户的密码以明文的方式存放在数据库表中,通常都会使用SHA-1或者MD5算将用户密码和salt随机字符串拼接在一起,重新计算一下再存入数据库中。被重新使用SHA-1或MD5算法计算的用户密码谁都不知道是什么,开发者也只能E对每次登录时输入的密码和数据库保存的密码是否一致,判断是否为合法用户,却无法知道密码到底是什么。
上图是在服务器中没有保存session_key的,因为我们需要拿到session_key才能进行用户数据校验,所以上图的流程图再一次重复了用户的登录流程。
在实际的开发中,用户登录在session_key的有效时间内只应该执行一次,session_key也应该保存在服务器中,其实小程序只需要使用wx.request将raw.Data和signature发送到服务器即可,服务器无须使用code换取session_key。直接和SHA-1或者MD5算法的签名对比即可。如下图所示
以上两个图对比可以得出,服务器保存session_key后整个流程变得更加简单,完全不需要在于微信服务器进行交互。
建议开发者在客户端使用用户明文数据时(通过wx.getUserInfo获得),使用rawData,而不要使用userInfo。因为数据验证的是rawData有没有被篡改,而不是验证的是userInfo是否被篡改。所以建议开发者使用rawData作为用户的基本信息。
25、解析用户加密数据获取openId及UnionId
调用wx.getUserInfor后将返回encryptedData和iv等数据,encryptedData是包含敏感数据在内的完整用户信息的加密数据,iv是用于解密这两个数据,整个解密的过程和用户信息校验的流程基本相同,不同的是,我们提交到服务器的数据是encryptedData和iv而不是rawData和signature。
26、微信小程序模板消息
要成功发送模板消息,必须要有一个formld的id号,但经过测试发现开发工具中法获取formld,也就是说在开发工具中不能产生模板逍息,只有在真机中方能拿到formld,formld是发送模板消息的关键。模板消息是被动的,只有用户本人在小程序中有一定交互行为后,服务器才能够向用户推送模板消息。下面来解释一下用户的什么行为,才能让服务器可以向小程序推一条模板消息。
(1)用户在小程序内完成过支付行为, 7天内可允许开发者向用户推送有限制的模板消息(一次支付可下发一条,多次支付可发条数独立,互相不影响) 。
-(2)当用户在小程序内发生过提交表单行为且该表单声明为要发模板消息时, 7天内可许开发者向用户推送有限条数的模板消息(一次提交表单可发一条,多次提交可发条数独立,互相不影响)
以上流程是建立在已经在小程序中拿到了formid且formid没有被使用的前提下。发送模板消息需要知道用户的openid,获取openid的流程不是必须的。如果用户已经在小程序中登录,服务器也保存了openid和session_key,就没有必要再使用code换取openid。
发送模板消息的流程还需要一个access_token。access_token在微信服务号或者订阅号里经常被作为令牌使用,access_token也是有失效期的,开发者应该在真实的项目中像管理session_key一样管理access_token。获取access_token同样需要携带微信小程序的appid和appsecret调用微信服务器。
流程图中没有标示出formid的获取过程,那么formid是怎么来的?
当用户提交表单时,表单提交函数的event事件对象中将包含一个formid,这个formid只能使用一次,有效期为7天,一旦使用formid推送一条模板消息,这个formid就不可以再次使用。如果还想推送模板消息,就只能等用户再一次提交表单并产生新的formid。
27、form表单组件
<form report-submit="true" bindsubmit="formSubmit" bindreset="formReset">
<button formType="submit">Submit</button>
<button formType="reset">Reset</button>
</form>
report-submit Boolean 是否返回 formId 用于发送模板消息
用户点击formType类型为submit的button之后,将执行bindsubmit属性所指定的响应函数,在响应函数的event事件对象中将可以获取form下所有表单元素的用户输入值。report-submit属性为true,那么触发bindsubmit后,event事件对象中将包含一个formid,在开发工具中获取到的formid的值为 the formid is a mock one[图片上传失败...(image-c6aada-1541424452744)]
这个并不是一个可以使用的formid,只有在真机上才能获取真实的formid。
28、微信支付
支付流程看起来比较复杂,但微信提供了一个SDK,使用SDK基本上不需要编写太多支付相关的代码。事实上,在真实的项目中,支付相关的代码复杂的不是支付本身,而是我们自己的业务逻辑。
以下是微信支付的最简单流程,该流程不包括开发者自己的业务逻辑。
- 首先,小程序客户端调用开发者自己的服务器,将一系列订单信息发送到服务器,比如商品的id等信息。
- 开发者服务器接受被购买商品的信息后,调用微信服务器的统一下单API,生成一个预付单,并将预付单信息返回开发者服务器。统一下单API需要用户的openid,如果已经在服务器保存了openid,就不需要获取用户的openid了,如果没有当前支付用户的openid,那么需要在第一步中携带code,以方便在这一步中使用code换取用户的openid。
- 开发者服务器需要对预付单的信息签名,并将预付单信息和签名一起返回小程序客户端。
- 小程序客户端在收到预付单信息及签名后,再调用wx.requestPayment,将预付单信息和签名一起提交到微信服务器。
- 微信服务器会验证这些预付单信息,如果验证通过,那么小程序将拉起支付界面(在开发工具中,这一步首先会弹出一个二维码,开发者扫描这个二维码将在开发者的微信中拉起支付界面)。
- 支付完成后,微信会主动调用开发者服务器将支付结果推送到开发者服务器中,开发者可根据支付结果处理自己的业务逻辑。这一步需要开发者有自己的外网服务器,否则微信无法推送通知。
29、真实的微信小程序登录状态维护
在session_key的有效时间内(未过期),开发者应该自行维护session_key而不是重复获取。
同时,openid最好不要作为用户的id被传递到小程序客户端。开发者应该有自己生成令牌作为自己业务的用户id,而不应该使用微信的openid。
以下是小程序登录及登录状态维护流程图
在小程序登录及登录状态维护流程图中,获取到session_key和openid后不要将这两个变量发送回客户端。此时应该生成一个令牌(随机字符串,也就是图中描述的3rd_session)作为用户的session_key和openid的键,将令牌(键)和session_key、openid(值)保存到服务器的Redis或者Memcache中。同时,这个自己生成的令牌应当具有一定的时效性,时效性一定要小于30天,开发者可根据自己的小程序的安全性要求自行调整这个令牌的有效期。开发者应当将自己生成的令牌发动到微信小程序并存入小程序的缓存中,每次请求服务器时都携带这个令牌,服务器接收令牌后在Redis中查找用户真实的openid和session_key。
以上流程只是一个指导性的流程,在真实的项目中还有很多变化和细节,但在总体思路上应当遵守上述流程。