登录流程
首先看下小程序官方的登录流程图
大致的登录流程是这样的:
- 前端:小程序请求是先使用
wx.checkSession
验证小程序服务器登录态是否失效,如果失效,则使用wx.login
进行登录,然后获取到code发送给业务服务器。- 后端:业务服务器拿到code后请求小程序服务器获取用户的openid等信息。
- 后端:业务服务器使用openid去匹配服务器中用户的数据,如果openid存在,说明用户已注册,如果不存在,则直接注册一个用户,并将openid保存在数据库以便下次验证。
- 后端:如果用户已注册,后端直接执行登录操作,即返回用户的业务登录态(token),也可以加上需要的用户信息等。
- 前端:拿到业务端的登录态后,再次携带登录态访问业务端接口。
- 后端:验证用户登录态是否有效,有效则返回业务接口数据。
- 后端:无效则返回登录态失效信息。
- 前端:如果请求返回的是登录态失效,则再次使用
wx.login
拿到code请求业务端登录接口,获取业务的登录态。
- 前端:如果验证小程序登录态没有失效,则前端直接请求业务端接口(请求中携带业务端登录态,即业务的token)。
- 后端:验证用户登录态是否有效,有效则返回业务接口数据。
- 后端:无效则返回登录态失效信息。
- 前端:如果请求返回的是登录态失效,则再次使用
wx.login
拿到code请求业务端登录接口,获取业务的登录态。
大致的前后端流程就是这样。
然后需要将上面的流程中的前端请求封装一下。
调用的时候直接使用:1
http(route, data, config).then(res={}).catch=(err=>{})
config暂时没有写配置,可以自定义一下,如请求方式(get,post…),还有是否弹出错误提示和正确提示,是否显示加载等。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
import config from './config'
import {RootObject} from '../model/rootObject'
interface option {
method?: 'OPTIONS' | 'GET' | 'HEAD' | 'POST' | 'PUT' | 'DELETE' | 'TRACE' | 'CONNECT' | undefined
errAlert?: boolean | 'toast'
resAlert?: boolean | 'toast'
loading?: boolean
maxRefresh?:number
}
class Http {
route:string
options:option
data:unknown
refresh:number // 当前熔断次数
maxRefresh:number // 最大熔断次数
constructor(){
this.route = ''
this.data = {}
this.refresh = 0
this.options = {
'method': 'GET',
'errAlert': true,
'resAlert': false,
'loading': true,
'maxRefresh': 2
}
this.maxRefresh = <number> this.options.maxRefresh
}
async request(route:string, data:unknown = {}, options?:option):Promise<RootObject<unknown>>{
this.route = route
this.data = data
this.options = {
'method': 'GET',
'errAlert': true,
'resAlert': false,
'loading': true,
'maxRefresh': 2,
...options
}
this.maxRefresh = <number> this.options.maxRefresh
this.showLoading()
if (await this.checkSession()) {
const loginStatus = await this.login()
if (!loginStatus){
if (this.refresh >= this.maxRefresh){
this.hideLoading()
return Promise.reject('登录态获取失败')
}
await this.sleep(500)
this.refresh++
await this.request(this.route, this.data, this.options)
}
}
let res = await this.HttpClient()
if (res?.code === 500) {
await this.login()
res = await this.HttpClient()
}
this.hideLoading()
if (res?.code === 200){
this.successToast('resAlert', res.msg)
} else {
this.successToast('errAlert', res.msg || '服务器故障')
}
return Promise.resolve(res)
}
async HttpClient():Promise<RootObject<unknown>>{
return new Promise((resolve, reject)=>{
wx.request({
'url': config.api + this.route,
'data': {
...<object> this.data
},
'method': this.options && this.options.method || 'POST',
'header': {
'Content-Type': 'application/json',
'token': wx.getStorageSync('token') || ''
},
'success': (res)=> {
resolve(<RootObject<any>>res.data)
},
'fail': (err)=> {
reject(err)
}
})
})
}
// 检测登录态 返回true不需要重新登录 返回false需要重新登录
checkSession():Promise<boolean> {
return new Promise((resolve) => {
wx.checkSession({
'success': () => {
const token = wx.getStorageSync('token')
if (token) {
resolve(false)
} else {
resolve(true)
}
},
'fail': () => {
resolve(true)
}
})
})
}
// 登录
async login():Promise<boolean>{
return new Promise((resolve) => {
wx.login({
'success': res => {
wx.request({
'url': config.api + 'wxlogin',
'data': {
'code': res.code
},
'method': 'POST',
'header': {
'Content-Type': 'application/json'
},
'success': (loginRes)=> {
if (loginRes.statusCode === 200){
resolve(true)
wx.setStorageSync('token', (loginRes.data as RootObject<any>).data.token)
} else {
resolve(false)
}
},
'fail': ()=> {
resolve(false)
}
})
}
})
})
}
sleep(time:number):Promise<void>{
return new Promise(resolve=>{
setTimeout(() => {
resolve()
}, time)
})
}
showLoading(){
if (this.options.loading){
wx.showLoading({
'title': ''
})
}
}
hideLoading(){
if (this.options.loading){
wx.hideLoading()
}
}
successToast(type:'resAlert'|'errAlert', content:string, title = '提示', confirm?:Function, cancel?:Function){
if (typeof this.options[type] === 'boolean'){
if (this.options[type]){
wx.showModal({
'title': title,
'content': content,
success(res) {
if (res.confirm) {
confirm && confirm()
} else if (res.cancel) {
cancel && cancel()
}
}
})
}
} else if (this.options[type] === 'toast'){
wx.showToast({
'title': content,
'icon': 'none',
'duration': 2000
})
}
}
}
export default new Http()