Примечание редактора: автором этой статьи является Фэн Тонг, старший инженер-разработчик интерфейса Qi Dance Troupe.
Вход пользователя — необходимый процесс для большинства полных приложений.
Простая пользовательская система должна сосредоточиться как минимум на этих уровнях.
- Безопасность (шифрование)
- Постоянное состояние входа в систему (аналогично файлам cookie)
- Обработка истечения срока действия входа
- Обеспечьте уникальность пользователя и избегайте нескольких учетных записей
- Разрешить
- Привязать ник пользователя, аватар и другую информацию
- Привязать номер мобильного телефона (настоящее имя и секретный метод защиты)
Многие бизнес-требования могут быть абстрагированы в интерфейсы Restful для операций CRUD.
Однако процесс входа в систему сложен, у каждой платформы есть свой собственный процесс, но он стал трудоемкой частью проекта, например, процесс входа в апплет.
Для проекта, начинающегося с нуля, завершение процесса входа в систему — это хорошее начало, а хорошее начало — это полдела.
В этой статье апплет WeChat используется в качестве платформы для описания полного пользовательского процесса входа в систему, и давайте вместе сломаем эту сложную кость.
Глоссарий
Во-первых, дайте краткое объяснение существительных, которые появляются на диаграмме последовательности процесса входа в систему.
-
code
Временные учетные данные для входа, действительные в течение пяти минут, переданыwx.login()
Получать -
session_key
Сеансовый ключ, через серверcode2SessionПолучать -
openId
Уникальный идентификатор пользователя в апплете никогда не изменится, и сервер получит его через код -
unionId
Уникальный идентификатор пользователя под одной и той же учетной записью открытой платформы WeChat (паблик-аккаунт, апплет, веб-сайт, мобильное приложение) никогда не изменится. -
appId
Уникальный идентификатор мини-программы -
appSecret
Секрет приложения апплета, который можно обменять на session_key вместе с кодом и appId.
другие существительные
-
rawData
Строка необработанных данных, за исключением конфиденциальной информации, используемой для расчета подписи. -
encryptedData
Информация о пользователе, которая содержит конфиденциальную информацию, зашифрована -
signature
Используется для проверки того, не была ли изменена информация о пользователе. -
iv
Начальный вектор для алгоритма шифрования
Какая информация является конфиденциальной информацией Номер мобильного телефона, openId, unionId, видно, что эти значения могут однозначно определить местонахождение пользователя, а никнеймы и аватары, которые не могут определить местонахождение пользователей, не являются конфиденциальной информацией
Функции входа в мини-программу
wx.login
wx.getUserInfo
wx.checkSession
обещание апплета
Мы обнаружили, что асинхронный интерфейс апплета представляет собой обратный вызов успеха и неудачи, который легко написатьад обратного звонка
Поэтому вы можете просто реализовать инструментальную функцию, которая преобразует асинхронные функции wx в промисы.
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
})
Реализация сервера
Серверная реализация этой демонстрации основана на express.js.
Обратите внимание, что для простоты демонстрации сервер использует переменные 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 — это в основном процесс обмена кодом для 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
Сервер реализует вышеуказанное/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
Medium (шаблон вот такой)
Мы добавляем pre-middleware на сервер, получаем соответствующую информацию о пользователе через сеанс и помещаем ее в объект 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
Такой апи для отправки запроса, за ним используется библиотека запросов
@chunpu/httpЭто библиотека HTTP-запросов, специально разработанная для небольших программ. Она может отправлять запросы к небольшим программам, таким как axios, поддерживает мощные функции, такие как перехватчики, и даже более удобна, чем axios.
Далее следует инициализация
import http from '@chunpu/http'
http.init({
baseURL: 'http://localhost:9999', // 定义 baseURL, 用于本地测试
wx // 标记是微信小程序用
})
Для конкретного использования, пожалуйста, обратитесь к документацииGitHub.com/simple/HTTP…
Настраиваемое сохранение состояния входа
У браузера есть файл cookie, но у апплета нет файла cookie, так как же имитировать состояние входа в систему, как на веб-странице?
Здесь мы используем собственный апплетУпорствоИнтерфейсы, а именно setStorage и getStorage
Чтобы облегчить совместное использование интерфейсов на всех концах или повторно использовать веб-интерфейс напрямую, мы реализуем простую логику для самостоятельного чтения файлов cookie и их типов.
Во-первых, он основан на возвращенных заголовках ответа http.посадить печенье, здесь мы используем@chunpu/http
Перехватчик ответа в , такой же, как использование 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
})
Конечно, нам также нужно сделать запрос, когдапринести все печенье, Это перехватчик запросов
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 состояния входа в браузере имеет срок действия, например, один день, семь дней или один месяц.
Может кто-то из знакомых задаст вопросы: Если вы используете хранилище напрямую, что делать со сроком действия логин-статуса апплета?
Вопрос в точку!Апплет уже помог нам осознать срок действия сессииwx.checkSession
Он умнее, чем куки, официальная документация описывает его следующим образом.
Состояние входа пользователя, полученное интерфейсом wx.login, имеет определенную своевременность. Чем дольше пользователь не использовал апплет, тем больше вероятность того, что состояние входа пользователя будет недействительным. Насилие, если пользователь использовал небольшую программу, состояние входа пользователя сохраняется.
То есть апплет нам тоже поможетАвтоматически обновлять наш статус входа, Это просто печенье с искусственным интеллектом, вот так 👍
Так как же это работает на фронтенде?Код написан на app.js
onLaunch: function () {
util.promisify(wx.checkSession)().then(() => {
console.log('session 生效')
return this.getUserInfo()
}).then(userInfo => {
console.log('登录成功', userInfo)
}).catch(err => {
console.log('自动登录失败, 重新登录', err)
return this.login()
}).catch(err => {
console.log('手动登录失败', err)
})
}
Следует отметить, что сессия здесь — это не только статус входа в систему фронтенда, но и период действия session_key бэкэнда.
Теоретически апплет также может настраивать политику времени истечения срока действия входа, но в этом случае нам необходимо учитывать время истечения срока действия разработчика и время истечения срока действия службы интерфейса апплета, лучше сделать его простым
Убедитесь, что каждая страница может получить информацию о пользователе.
Если вы выберете в новом проекте апплетаСоздайте общий шаблон быстрого запуска
Мы получим шаблон, который можно запустить напрямую
Нажмите на код, чтобы увидеть, что большая часть кода имеет дело с userInfo....
в записке говорится
Поскольку getUserInfo является сетевым запросом, он может вернуться после Page.onLoad.
Так что добавьте обратный вызов здесь, чтобы предотвратить это
Но этот тип шаблона не является научным, он учитывает только ситуацию, когда домашней странице нужна информация о пользователе, что, если страница, введенная путем сканирования кода, также нуждается в информации о пользователе?Есть также неоплачиваемая страница активности страницы, которая напрямую переходит в.. .
Если каждая страница оценивает, загружена ли пользовательская информация таким образом, код слишком избыточен.
На этом этапе мы думаем о готовой функции jQuery.$(function)
До тех пор, пока документ готов, код в функции может быть выполнен напрямую. Если документ не готов, код будет выполнен после готовности
Вот и все!Мы рассматриваем приложение апплета как документ веб-страницы.
Наша цель - получить userInfo на странице без ошибок
Page({
data: {
userInfo: null
},
onLoad: function () {
app.ready(() => {
this.setData({
userInfo: app.globalData.userInfo
})
})
}
})
Здесь мы используемmin-readyдля достижения этой функции
Все еще напишите код для достижения app.js
import Ready from 'min-ready'
const ready = Ready()
App({
getUserInfo () {
// 获取用户信息作为全局方法
return http.get('/user/info').then(response => {
let data = response.data
if (data && typeof data === 'object') {
this.globalData.userInfo = data
// 获取 userInfo 成功的时机就是 app ready 的时机
ready.open()
return data
}
return Promise.reject(response)
})
},
ready (func) {
// 把函数放入队列中
ready.queue(func)
}
})
Свяжите информацию о пользователе и номера мобильного телефона
Недостаточно просто получить идентификатор openId пользователя.
Как получить эту информацию о пользователе и сохранить ее в базе данных?
Мы реализуем эти два интерфейса на стороне сервера,привязать информацию о пользователе, Привязать номер телефона пользователя
app
.post('/user/bindinfo', (req, res) => {
var user = req.user
if (user) {
var {encryptedData, iv} = req.body
var pc = new WXBizDataCrypt(config.appId, user.sessionKey)
var data = pc.decryptData(encryptedData, iv)
Object.assign(user, data)
return res.send({
code: 0
})
}
throw new Error('用户未登录')
})
.post('/user/bindphone', (req, res) => {
var user = req.user
if (user) {
var {encryptedData, iv} = req.body
var pc = new WXBizDataCrypt(config.appId, user.sessionKey)
var data = pc.decryptData(encryptedData, iv)
Object.assign(user, data)
return res.send({
code: 0
})
}
throw new Error('用户未登录')
})
Мини программа персональный центр wxml реализована следующим образом
<view wx:if="userInfo" class="userinfo">
<button
wx:if="{{!userInfo.nickName}}"
type="primary"
open-type="getUserInfo"
bindgetuserinfo="bindUserInfo"> 获取头像昵称 </button>
<block wx:else>
<image class="userinfo-avatar" src="{{userInfo.avatarUrl}}" mode="cover"></image>
<text class="userinfo-nickname">{{userInfo.nickName}}</text>
</block>
<button
wx:if="{{!userInfo.phoneNumber}}"
type="primary"
style="margin-top: 20px;"
open-type="getPhoneNumber"
bindgetphonenumber="bindPhoneNumber"> 绑定手机号 </button>
<text wx:else>{{userInfo.phoneNumber}}</text>
</view>
Функции BinduserInfo и BindphonEnumber в апплете, в соответствии с последней стратегией WeChat, эти две операции требуют, чтобы пользователи нажимали кнопку единообразной авторизации для запуска
bindUserInfo (e) {
var detail = e.detail
if (detail.iv) {
http.post('/user/bindinfo', {
encryptedData: detail.encryptedData,
iv: detail.iv,
signature: detail.signature
}).then(() => {
return app.getUserInfo().then(userInfo => {
this.setData({
userInfo: userInfo
})
})
})
}
},
bindPhoneNumber (e) {
var detail = e.detail
if (detail.iv) {
http.post('/user/bindphone', {
encryptedData: detail.encryptedData,
iv: detail.iv
}).then(() => {
return app.getUserInfo().then(userInfo => {
this.setData({
userInfo: userInfo
})
})
})
}
}
код
Код, упомянутый в этой статье, можно найти на моем github.
Код апплета находится вwxapp-login-demo
Серверный код Node.js находится вwxapp-login-server
О Еженедельнике Циву
«Qiwu Weekly» — это сообщество передовых технологий, управляемое профессиональной командой «Qiwu Troupe» компании 360. Подписавшись на официальный аккаунт, вы можете отправить нам статью, отправив ссылку прямо на фон.