2018.11.01
6159
男神苟毅
彻底搞懂小程序登录流程
用户登录是大部分完整 App 必备的流程
一个简单的用户系统需要关注至少这些层面
很多的业务需求都可以抽象成 Restful 接口配合 CRUD 操作
但登录流程却是错综复杂, 各个平台有各自的流程, 反倒成了项目中费时间的部分, 比如小程序的登录流程
对于一个从零开始的项目来说, 搞定登录流程, 就是一个好的开始, 一个好的开始, 就是成功的一半
本文就以微信小程序这个平台, 讲述一个完整的自定义用户登录流程, 一起来啃这块难啃的骨头
先给登录流程时序图中出现的名词简单做一个解释
哪些信息是敏感信息呢? 手机号, openId, unionId, 可以看出这些值都可以唯一定位一个用户, 而昵称, 头像这些不能定位用户的都不是敏感信息
我们发现小程序的异步接口都是 success 和 fail 的回调, 很容易写出回调地狱
因此可以先简单实现一个 wx 异步函数转成 promise 的工具函数
const promisify = original => { return function(opt) { return new Promise((resolve, reject) => {
opt = Object.assign({ success: resolve, fail: reject
}, opt)
original(opt)
})
}
} 复制代码
这样我们就可以这样调用函数了
promisify(wx.getStorage)({key: 'key'}).then(value => { // success }).catch(reason => { // fail }) 复制代码
本 demo 的服务端实现基于 express.js
注意, 为了 demo 的简洁性, 服务端使用 js 变量来保存用户数据, 也就是说如果重启服务端, 用户数据就清空了
如需持久化存储用户数据, 可以自行实现数据库相关逻辑
// 存储所有用户信息 const users = { // openId 作为索引 openId: { // 数据结构如下 openId: '', // 理论上不应该返回给前端 sessionKey: '', nickName: '', avatarUrl: '', unionId: '', phoneNumber: '' }
}
app
.use(bodyParser.json())
.use(session({ secret: 'alittlegirl', resave: false, saveUninitialized: true })) 复制代码
我们先实现一个基本的 oauth 授权登录
oauth 授权登录主要是 code 换取 openId 和 sessionKey 的过程
写在 app.js 中
login () { console.log('登录') return util.promisify(wx.login)().then(({code}) => { console.log(`code: ${code}`) return http.post('/oauth/login', {
code, type: 'wxapp' })
})
} 复制代码
服务端实现上述/oauth/login这个接口
app
.post('/oauth/login', (req, res) => { var params = req.body var {code, type} = params if (type === 'wxapp') { // code 换取 openId 和 sessionKey 的主要逻辑 axios.get('https://api.weixin.qq.com/sns/jscode2session', { params: { appid: config.appId, secret: config.appSecret, js_code: code, grant_type: 'authorization_code' }
}).then(({data}) => { var openId = data.openid var user = users[openId] if (!user) {
user = {
openId, sessionKey: data.session_key
}
users[openId] = user console.log('新用户', user)
} else { console.log('老用户', user)
}
req.session.openId = user.openId
req.user = user
}).then(() => {
res.send({ code: 0 })
})
} else { throw new Error('未知的授权类型')
}
}) 复制代码
登录系统中都会有一个重要的功能: 获取用户信息, 我们称之为getUserInfo
如果已登录用户调用 getUserInfo 则返回用户信息, 比如昵称, 头像等, 如果未登录则返回"用户未登录"
也就是说此接口还有判断用户是否登录的功效...
小程序的用户信息一般存储在app.globalData.userInfo中(模板如此)
我们在服务端加上前置中间件, 通过 session 来获取对应的用户信息, 并放在 req 对象中
app
.use((req, res, next) => {
req.user = users[req.session.openId]
next()
}) 复制代码
然后实现/user/info接口, 用来返回用户信息
app
.get('/user/info', (req, res) => { if (req.user) { return res.send({ code: 0, data: req.user
})
} throw new Error('用户未登录')
}) 复制代码
小程序调用用户信息接口
getUserInfo () { return http.get('/user/info').then(response => { let data = response.data if (data && typeof data === 'object') { // 获取用户信息成功则保存到全局 this.globalData.userInfo = data return data
} return Promise.reject(response)
})
} 复制代码
小程序代码通过http.get,http.post这样的 api 来发请求, 背后使用了一个请求库
@chunpu/http 是一个专门为小程序设计的 http 请求库, 可以在小程序上像 axios 一样发请求, 支持拦截器等强大功能, 甚至比 axios 更顺手
初始化方法如下
import http from '@chunpu/http' http.init({ baseURL: 'http://localhost:9999', // 定义 baseURL, 用于本地测试 wx // 标记是微信小程序用 }) 复制代码
具体使用方法可参照文档 github.com/chunpu/http…
浏览器有 cookie, 然而小程序没有 cookie, 那怎么模仿出像网页这样的登录态呢?
这里要用到小程序自己的持久化接口, 也就是 setStorage 和 getStorage
为了方便各端共用接口, 或者直接复用 web 接口, 我们自行实现一个简单的读 cookie 和种 cookie 的逻辑
先是要根依据返回的 http response headers 来种上 cookie, 此处我们用到了@chunpu/http中的 response 拦截器, 和 axios 用法一样
http.interceptors.response.use(response => { // 种 cookie var {headers} = response var cookies = headers['set-cookie'] || '' cookies = cookies.split(/, */).reduce((prev, item) => {
item = item.split(/; */)[0] var obj = http.qs.parse(item) return Object.assign(prev, obj)
}, {}) if (cookies) { return util.promisify(wx.getStorage)({ key: 'cookie' }).catch(() => {}).then(res => {
res = res || {} var allCookies = res.data || {} Object.assign(allCookies, cookies) return util.promisify(wx.setStorage)({ key: 'cookie', data: allCookies
})
}).then(() => { return response
})
} return response
}) 复制代码
当然我们还需要在发请求的时候带上所有 cookie, 此处用的是 request 拦截器
http.interceptors.request.use(config => { // 给请求带上 cookie return util.promisify(wx.getStorage)({ key: 'cookie' }).catch(() => {}).then(res => { if (res && res.data) { Object.assign(config.headers, { Cookie: http.qs.stringify(res.data, ';', '=')
})
} return config
})
}) 复制代码
我们知道, 浏览器里面的登录态 cookie 是有失效时间的, 比如一天, 七天, 或者一个月
也许有朋友会提出疑问, 直接用 storage 的话, 小程序的登录态有效期怎么办?
问到点上了! 小程序已经帮我们实现好了 session 有效期的判断wx.checkSession
它比 cookie 更智能, 官方文档描述如下
通过 wx.login 接口获得的用户登录态拥有一定的时效性。用户越久未使用小程序,用户登录态越有可能失效。反之如果用户一直在使用小程序,则用户登录态一直保持有效
也就是说小程序还会帮我们自动 renew 咱们的登录态, 简直是人工智能 cookie, 点个赞
2019.01.30
5621
俗话说:酒好也怕巷子深。无论公司的产品或服务有多好,不去做营销推广、广告宣传,就没有人知道它;当潜在消费者需要时,也就难以搜寻到你的品牌产品,更谈不上购买了。所以,品牌竞争中需要运用一些方法,目的是使你的品牌崛起成为知名品牌。