openid.js
5.84 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
/**
* 微信授权(openid)管理
*
* @description 处理小程序授权逻辑,包括 wx.login 和 miniProgramAuthAPI 调用
* @module utils/openid
*/
import Taro from '@tarojs/taro'
import axios from '@/utils/request'
import { setSessionId } from '@/utils/request'
import { loginStatusAPI } from '@/api/user'
/**
* 小程序授权
* @description 调用 wx.login 获取 code,由后端授权获取 openid
* @description 授权成功后会自动将 sessionid 写入本地存储
* @returns {Promise<{user: Object|null}>} 返回用户信息(如果已自动登录)
*
* @example
* const user = await miniProgramAuth()
* if (user) {
* console.log('已自动登录', user)
* } else {
* console.log('需要手动登录')
* }
*/
export async function miniProgramAuth() {
try {
// 1. 调用 wx.login 获取 code
const { code } = await Taro.login()
if (!code) {
throw new Error('获取微信 code 失败')
}
// 2. 调用后端授权接口(直接使用 axios 以访问响应头)
const response = await axios.post('/srv/?a=openid', { code })
// 检查响应状态
if (!response?.data || response.data.code !== 1) {
throw new Error(response?.data?.msg || '小程序授权失败')
}
// 3. 从响应中提取 cookie 并写入本地存储
const cookie = extractCookieFromResponse(response)
if (cookie) {
setSessionId(cookie)
console.log('授权成功,sessionid 已写入本地存储')
}
// 4. 返回用户信息(如果已自动登录)
return response.data.data?.user || null
} catch (err) {
console.error('小程序授权失败:', err)
throw err
}
}
/**
* 从 axios 响应中提取 cookie
* @description 尝试从多个可能的位置提取 cookie,并去重
* @param {Object} response axios 响应对象
* @returns {string|null} cookie 字符串或 null
*
* @example
* const cookie = extractCookieFromResponse(response)
* if (cookie) {
* setSessionId(cookie)
* }
*/
function extractCookieFromResponse(response) {
// 尝试从 response.headers 中提取
if (response?.headers) {
// 标准的 set-cookie 头
if (response.headers['set-cookie']) {
const cookies = response.headers['set-cookie']
return normalizeCookies(cookies)
}
// 小写版本(某些环境)
if (response.headers['Set-Cookie']) {
const cookies = response.headers['Set-Cookie']
return normalizeCookies(cookies)
}
// 小程序环境中可能在 cookies 字段
if (response.headers.cookies) {
const cookies = response.headers.cookies
return normalizeCookies(cookies)
}
}
// 尝试从 response.cookies 中提取(axios-miniprogram)
if (response?.cookies) {
const cookies = response.cookies
// 如果是对象数组格式 [{name, value}, ...]
if (Array.isArray(cookies) && cookies.length > 0 && cookies[0].name) {
return cookies.map(c => `${c.name}=${c.value}`).join('; ')
}
// 如果是字符串数组
return normalizeCookies(cookies)
}
console.warn('未能从响应中提取 cookie')
return null
}
/**
* 标准化 cookie 数组并提取会话 cookie
* @description 将 cookie 数组转换为字符串,优先提取包含会话标识的 cookie
* @param {string|string[]} cookies cookie 字符串或数组
* @returns {string|null} 标准化后的 cookie 字符串
*
* @example
* // 多个 Set-Cookie,优先返回包含 sessionid/PHPSESSID 的
* normalizeCookies(['sessionid=xxx; path=/', 'csrftoken=yyy; path=/'])
* // 返回: 'sessionid=xxx; path=/'
*
* @example
* // 单个 cookie 字符串
* normalizeCookies('PHPSESSID=xxx; path=/')
* // 返回: 'PHPSESSID=xxx; path=/'
*/
function normalizeCookies(cookies) {
if (!cookies) return null
// 如果是单个 cookie 字符串,直接返回
if (typeof cookies === 'string') {
return cookies
}
// 如果是数组,智能提取会话 cookie
if (Array.isArray(cookies)) {
// 去重
const uniqueCookies = [...new Set(cookies)]
// 常见的会话 cookie 名称
const sessionCookieNames = ['sessionid', 'PHPSESSID', 'jsessionid', 'JSESSIONID', 'sid', 'SID']
// 优先查找包含会话标识的 cookie
for (const cookie of uniqueCookies) {
const cookieLower = cookie.toLowerCase()
for (const name of sessionCookieNames) {
if (cookieLower.includes(name.toLowerCase() + '=')) {
console.log(`[openid] 找到会话 cookie: ${name}`)
return cookie
}
}
}
// 如果没有找到会话 cookie,返回第一个(兼容旧逻辑)
console.warn('[openid] 未找到会话 cookie,使用第一个 cookie')
return uniqueCookies[0] || null
}
return null
}
/**
* 检查 openid 状态
* @description 调用 loginStatusAPI 检查 is_openid
* @returns {Promise<boolean>} 是否已授权
*
* @example
* const isOpenid = await checkOpenidStatus()
* if (!isOpenid) {
* await miniProgramAuth()
* }
*/
export async function checkOpenidStatus() {
try {
const res = await loginStatusAPI()
if (res.code === 1) {
return res.data.is_openid
} else {
return false
}
} catch (err) {
console.error('检查 openid 状态失败:', err)
return false
}
}
/**
* 确保 openid 已授权并尝试自动登录
* @description 如果未授权,则调用 wx.login 授权
* @returns {Promise<{user: Object|null}>} 返回用户信息(如果已自动登录)
*
* @example
* const user = await ensureOpenidAuthorized()
* if (user) {
* console.log('已自动登录', user)
* } else {
* console.log('已授权但未登录,需要检查登录状态')
* }
*/
export async function ensureOpenidAuthorized() {
const isOpenid = await checkOpenidStatus()
if (!isOpenid) {
// 未授权,调用 wx.login 授权
return await miniProgramAuth()
}
// 已授权,返回 null(需要检查登录状态)
return null
}