authRedirect.js
9.98 KB
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
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
import Taro from '@tarojs/taro'
import { routerStore } from '@/stores/router'
import BASE_URL, { REQUEST_DEFAULT_PARAMS } from './config'
/**
* 授权与回跳相关工具
* - 统一管理:保存来源页、静默授权、跳转授权页、授权后回跳
* - 约定:sessionid 存在于本地缓存 key 为 sessionid
* - 说明:refreshSession/silentAuth 使用单例 Promise,避免并发重复授权
*/
/**
* 获取当前页完整路径(含 query)
* @returns {string} 当前页路径,示例:pages/index/index?a=1
*/
export const getCurrentPageFullPath = () => {
const pages = Taro.getCurrentPages()
if (!pages || pages.length === 0) return ''
const current_page = pages[pages.length - 1]
const route = current_page.route
const options = current_page.options || {}
const query_params = Object.keys(options)
.map((key) => `${key}=${encodeURIComponent(options[key])}`)
.join('&')
return query_params ? `${route}?${query_params}` : route
}
/**
* 保存当前页路径(用于授权成功后回跳)
* @param {string} custom_path 自定义路径,不传则取当前页
* @returns {void}
*/
export const saveCurrentPagePath = (custom_path) => {
const router = routerStore()
const path = custom_path || getCurrentPageFullPath()
router.add(path)
}
/**
* 判断是否需要授权
* @returns {boolean} true=需要授权,false=已存在 sessionid
*/
export const needAuth = () => {
try {
const sessionid = Taro.getStorageSync('sessionid')
return !sessionid || sessionid === ''
} catch (error) {
console.error('检查授权状态失败:', error)
return true
}
}
let auth_promise = null
/**
* 刷新会话:通过 Taro.login 获取 code,换取后端会话 cookie 并写入缓存
* - 被 request.js 的 401 拦截器调用,用于自动“静默续期 + 原请求重放”
* - 复用 auth_promise,防止多个接口同时 401 时并发触发多次登录
* @param {object} options 可选项
* @param {boolean} options.show_loading 是否展示 loading,默认 true
* @returns {Promise<{code:number,msg?:string,data?:any,cookie?:string}>} 后端返回 + cookie
*/
export const refreshSession = async (options) => {
const show_loading = options?.show_loading !== false
// 已有授权进行中时,直接复用同一个 Promise
if (auth_promise) return auth_promise
auth_promise = (async () => {
try {
if (show_loading) {
Taro.showLoading({
title: '加载中...',
mask: true,
})
}
// 调用微信登录获取临时 code
const login_result = await new Promise((resolve, reject) => {
Taro.login({
success: resolve,
fail: reject,
})
})
if (!login_result || !login_result.code) {
throw new Error('获取微信登录code失败')
}
const request_data = {
code: login_result.code,
}
// 开发环境可按需手动传 openid(仅用于本地联调)
if (process.env.NODE_ENV === 'development') {
// request_data.openid = 'h-008';
// request_data.openid = 'h-009';
// request_data.openid = 'h-010';
// request_data.openid = 'h-011';
// request_data.openid = 'h-012';
// request_data.openid = 'h-013';
// request_data.openid = 'oWbdFvkD5VtloC50wSNR9IWiU2q8';
// request_data.openid = 'oex8h5QZnZJto3ttvO6swSvylAQo';
}
if (process.env.NODE_ENV === 'production') {
// request_data.openid = 'h-013';
}
// 换取后端会话(服务端通过 Set-Cookie 返回会话信息)
const response = await Taro.request({
url: `${BASE_URL}/srv/?a=openid&f=${encodeURIComponent(REQUEST_DEFAULT_PARAMS.f)}&client_name=${encodeURIComponent(REQUEST_DEFAULT_PARAMS.client_name)}`,
method: 'POST',
data: request_data,
})
if (!response?.data || response.data.code !== 1) {
throw new Error(response?.data?.msg || '授权失败')
}
// 兼容小程序环境下 cookie 的不同字段位置
let cookie =
(response.cookies && response.cookies[0]) ||
response.header?.['Set-Cookie'] ||
response.header?.['set-cookie']
if (Array.isArray(cookie)) cookie = cookie[0]
if (!cookie) {
throw new Error('授权失败:没有获取到有效的会话信息')
}
// 写入本地缓存:后续请求会从缓存取 sessionid 并带到请求头
Taro.setStorageSync('sessionid', cookie)
return {
...response.data,
cookie,
}
} finally {
if (show_loading) {
Taro.hideLoading()
}
}
})().finally(() => {
auth_promise = null
})
return auth_promise
}
const do_silent_auth = async (show_loading) => {
// 已有 sessionid 时直接视为已授权
if (!needAuth()) {
return { code: 1, msg: '已授权' }
}
// 需要授权时,走刷新会话逻辑
return await refreshSession({ show_loading })
}
/**
* 静默授权:用于启动阶段/分享页/授权页发起授权
* - 与 refreshSession 共用 auth_promise,避免并发重复调用
* @param {Function} on_success 成功回调
* @param {Function} on_error 失败回调(入参为错误文案)
* @param {object} options 可选项
* @param {boolean} options.show_loading 是否展示 loading,默认 true
* @returns {Promise<any>} 授权结果
*/
export const silentAuth = async (on_success, on_error, options) => {
const show_loading = options?.show_loading !== false
try {
// 未有授权进行中时才发起一次授权,并复用 Promise
if (!auth_promise) {
auth_promise = do_silent_auth(show_loading).finally(() => {
auth_promise = null
})
}
const result = await auth_promise
if (on_success) on_success(result)
return result
} catch (error) {
const error_msg = error?.message || '授权失败,请稍后重试'
if (on_error) on_error(error_msg)
throw error
}
}
/**
* 跳转到授权页(降级方案)
* - 会先保存回跳路径(默认当前页),授权成功后在 auth 页回跳
* @param {string} return_path 指定回跳路径
* @returns {void}
*/
export const navigateToAuth = (return_path) => {
if (return_path) {
saveCurrentPagePath(return_path)
} else {
saveCurrentPagePath()
}
const pages = Taro.getCurrentPages()
const current_page = pages[pages.length - 1]
const current_route = current_page?.route
if (current_route === 'pages/auth/index') {
return
}
// TAG: navigateTo 失败时(例如页面栈满),降级为 redirectTo
Taro.navigateTo({ url: '/pages/auth/index' }).catch(() => {
return Taro.redirectTo({ url: '/pages/auth/index' })
})
}
/**
* 授权成功后回跳到来源页
* - 优先使用 routerStore 里保存的路径
* - 失败降级:redirectTo -> reLaunch
* @param {string} default_path 未保存来源页时的默认回跳路径
* @returns {Promise<void>}
*/
export const returnToOriginalPage = async (default_path = '/pages/index/index') => {
const router = routerStore()
const saved_path = router.url
try {
router.remove()
const pages = Taro.getCurrentPages()
const current_page = pages[pages.length - 1]
const current_route = current_page?.route
let target_path = default_path
if (saved_path && saved_path !== '') {
target_path = saved_path.startsWith('/') ? saved_path : `/${saved_path}`
}
const target_route = target_path.split('?')[0].replace(/^\//, '')
if (current_route === target_route) {
return
}
try {
await Taro.redirectTo({ url: target_path })
} catch (error) {
await Taro.reLaunch({ url: target_path })
}
} catch (error) {
console.error('returnToOriginalPage 执行出错:', error)
try {
await Taro.reLaunch({ url: default_path })
} catch (final_error) {
console.error('最终降级方案也失败了:', final_error)
}
}
}
/**
* 判断是否来自分享场景
* @param {object} options 页面 options
* @returns {boolean}
*/
export const isFromShare = (options) => {
return options && (options.from_share === '1' || options.scene)
}
/**
* 分享页进入时的授权处理
* - 来自分享且未授权:保存当前页路径,授权成功后回跳
* - 授权失败:返回 false,由调用方决定是否继续降级处理
* @param {object} options 页面 options
* @param {Function} callback 授权成功后的继续逻辑
* @returns {Promise<boolean>} true=已处理且可继续,false=授权失败
*/
export const handleSharePageAuth = async (options, callback) => {
if (!needAuth()) {
if (typeof callback === 'function') callback()
return true
}
if (isFromShare(options)) {
saveCurrentPagePath()
}
try {
await silentAuth(
() => {
if (typeof callback === 'function') callback()
},
() => {
Taro.navigateTo({ url: '/pages/auth/index' })
}
)
return true
} catch (error) {
Taro.navigateTo({ url: '/pages/auth/index' })
return false
}
}
/**
* 为路径追加分享标记
* @param {string} path 原路径
* @returns {string} 追加后的路径
*/
export const addShareFlag = (path) => {
const separator = path.includes('?') ? '&' : '?'
return `${path}${separator}from_share=1`
}