hookehuyr

feat(auth): 重构授权逻辑并添加静默授权功能

重构应用授权流程,将授权逻辑抽离到单独的工具模块
添加静默授权功能,优化用户体验
完善请求拦截器中的授权检查和跳转逻辑
新增页面路径保存和返回功能
1 // ESLint 检查 .vue 文件需要单独配置编辑器: 1 // ESLint 检查 .vue 文件需要单独配置编辑器:
2 // https://eslint.vuejs.org/user-guide/#editor-integrations 2 // https://eslint.vuejs.org/user-guide/#editor-integrations
3 { 3 {
4 - "extends": ["taro/vue3"] 4 + "extends": ["taro/vue3"],
5 + "parserOptions": {
6 + "requireConfigFile": false
7 + }
5 } 8 }
6 -
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
7 */ 7 */
8 import axios from '@/utils/request'; 8 import axios from '@/utils/request';
9 import Taro from '@tarojs/taro' 9 import Taro from '@tarojs/taro'
10 -// import qs from 'qs' 10 +import qs from 'qs'
11 11
12 /** 12 /**
13 * 网络请求功能函数 13 * 网络请求功能函数
...@@ -80,9 +80,13 @@ export const fetch = { ...@@ -80,9 +80,13 @@ export const fetch = {
80 post: function (api, params) { 80 post: function (api, params) {
81 return axios.post(api, params) 81 return axios.post(api, params)
82 }, 82 },
83 - // stringifyPost: function (api, params) { 83 + stringifyPost: function (api, params) {
84 - // return axios.post(api, qs.stringify(params)) 84 + return axios.post(api, qs.stringify(params), {
85 - // }, 85 + headers: {
86 + 'content-type': 'application/x-www-form-urlencoded'
87 + }
88 + })
89 + },
86 basePost: function (url, data, config) { 90 basePost: function (url, data, config) {
87 return axios.post(url, data, config) 91 return axios.post(url, data, config)
88 } 92 }
......
...@@ -8,33 +8,34 @@ ...@@ -8,33 +8,34 @@
8 import { createApp } from 'vue' 8 import { createApp } from 'vue'
9 import { createPinia } from 'pinia' 9 import { createPinia } from 'pinia'
10 import './app.less' 10 import './app.less'
11 -import { routerStore } from '@/stores/router' 11 +import { saveCurrentPagePath, needAuth, silentAuth, navigateToAuth } from '@/utils/authRedirect'
12 -import Taro from '@tarojs/taro'
13 12
14 const App = createApp({ 13 const App = createApp({
15 // 对应 onLaunch 14 // 对应 onLaunch
16 - onLaunch(options) { 15 + async onLaunch(options) {
17 - // 未授权状态跳转授权页面,首页不需要权限 16 + const path = options?.path || ''
18 - const path = options.path; 17 + const query = options?.query || {}
19 - const query = options.query; 18 +
20 - // 缓存没有权限的地址 19 + const query_string = Object.keys(query)
21 - const router = routerStore(); 20 + .map((key) => `${key}=${encodeURIComponent(query[key])}`)
22 - router.add(path); 21 + .join('&')
23 - // if (path !== 'pages/index/index' && !wx.getStorageSync("sessionid")) { 22 + const full_path = query_string ? `${path}?${query_string}` : path
24 - if (!wx.getStorageSync("sessionid")) { 23 +
25 - console.warn("没有权限"); 24 + if (full_path) {
26 - // if (path === 'pages/detail/index') { 25 + saveCurrentPagePath(full_path)
27 - // Taro.navigateTo({ 26 + }
28 - // url: `./pages/auth/index?url=${path}&id=${query.id}&start_date=${query.start_date}&end_date=${query.end_date}`, 27 +
29 - // }) 28 + if (!needAuth()) return
30 - // } else { 29 +
31 - // Taro.navigateTo({ 30 + if (path === 'pages/auth/index') return
32 - // url: './pages/auth/index?url=' + path, 31 +
33 - // }) 32 + try {
34 - // } 33 + await silentAuth()
34 + } catch (error) {
35 + navigateToAuth(full_path || undefined)
35 } 36 }
36 }, 37 },
37 - onShow(options) { 38 + onShow() {
38 }, 39 },
39 // 入口组件不需要实现 render 方法,即使实现了也会被 taro 所覆盖 40 // 入口组件不需要实现 render 方法,即使实现了也会被 taro 所覆盖
40 }); 41 });
......
...@@ -15,62 +15,14 @@ ...@@ -15,62 +15,14 @@
15 15
16 <script setup> 16 <script setup>
17 import Taro, { useDidShow } from '@tarojs/taro' 17 import Taro, { useDidShow } from '@tarojs/taro'
18 -import request from '@/utils/request'; 18 +import { silentAuth, returnToOriginalPage } from '@/utils/authRedirect'
19 19
20 useDidShow(() => { 20 useDidShow(() => {
21 - // 授权登陆 21 + silentAuth()
22 - Taro.login({ 22 + .then(() => returnToOriginalPage('/pages/index/index'))
23 - success: function (res) { 23 + .catch((error) => {
24 - if (res.code) { 24 + Taro.showToast({ title: error?.message || '授权失败', icon: 'none' })
25 - //发起网络请求 25 + })
26 - Taro.showLoading({
27 - title: '授权中',
28 - })
29 - request.post('/srv/?a=openid', {
30 - code: res.code
31 - })
32 - .then(res => {
33 - if (res.data.code === 1) { // 成功
34 - var cookie = res.cookies ? res.cookies[0] : (res.header['Set-Cookie'] || res.header['set-cookie']);
35 - // 有些时候 cookie 可能是一个数组,或者分号分隔的字符串
36 - // axios-miniprogram 返回的 cookies 应该是一个数组
37 -
38 - if (cookie) {
39 - // 处理 cookie 格式,如果包含分号,取第一个
40 - if (Array.isArray(cookie)) cookie = cookie[0];
41 - // 简单处理,通常服务器返回 PHPSESSID=xxx; path=/
42 - // 我们存入 storage
43 - Taro.setStorageSync("sessionid", cookie);
44 -
45 - // 返回上一页
46 - Taro.hideLoading();
47 - Taro.navigateBack({
48 - fail: () => {
49 - Taro.reLaunch({ url: '/pages/index/index' })
50 - }
51 - });
52 - } else {
53 - console.warn('No cookie received');
54 - Taro.hideLoading();
55 - Taro.showToast({ title: '授权失败: 无Cookie', icon: 'none' });
56 - }
57 - } else {
58 - console.warn(res.data.msg);
59 - Taro.hideLoading();
60 - Taro.showToast({ title: res.data.msg || '授权失败', icon: 'none' });
61 - }
62 - })
63 - .catch(err => {
64 - console.error(err);
65 - Taro.hideLoading();
66 - Taro.showToast({ title: '网络错误', icon: 'none' });
67 - });
68 - } else {
69 - console.log('登录失败!' + res.errMsg)
70 - Taro.showToast({ title: '微信登录失败', icon: 'none' });
71 - }
72 - }
73 - })
74 }) 26 })
75 </script> 27 </script>
76 28
......
1 +import Taro from '@tarojs/taro'
2 +import { routerStore } from '@/stores/router'
3 +import request from '@/utils/request'
4 +
5 +export const getCurrentPageFullPath = () => {
6 + const pages = getCurrentPages()
7 + if (!pages || pages.length === 0) return ''
8 +
9 + const current_page = pages[pages.length - 1]
10 + const route = current_page.route
11 + const options = current_page.options || {}
12 +
13 + const query_params = Object.keys(options)
14 + .map((key) => `${key}=${encodeURIComponent(options[key])}`)
15 + .join('&')
16 +
17 + return query_params ? `${route}?${query_params}` : route
18 +}
19 +
20 +export const saveCurrentPagePath = (custom_path) => {
21 + const router = routerStore()
22 + const path = custom_path || getCurrentPageFullPath()
23 + router.add(path)
24 +}
25 +
26 +export const needAuth = () => {
27 + try {
28 + const sessionid = Taro.getStorageSync('sessionid')
29 + return !sessionid || sessionid === ''
30 + } catch (error) {
31 + console.error('检查授权状态失败:', error)
32 + return true
33 + }
34 +}
35 +
36 +export const silentAuth = async (on_success, on_error) => {
37 + try {
38 + if (!needAuth()) {
39 + const result = { code: 1, msg: '已授权' }
40 + if (on_success) on_success(result)
41 + return result
42 + }
43 +
44 + Taro.showLoading({
45 + title: '加载中...',
46 + mask: true,
47 + })
48 +
49 + const login_result = await new Promise((resolve, reject) => {
50 + Taro.login({
51 + success: resolve,
52 + fail: reject,
53 + })
54 + })
55 +
56 + if (!login_result || !login_result.code) {
57 + throw new Error('获取微信登录code失败')
58 + }
59 +
60 + const response = await request.post('/srv/?a=openid', {
61 + code: login_result.code,
62 + })
63 +
64 + Taro.hideLoading()
65 +
66 + if (!response?.data || response.data.code !== 1) {
67 + const error_msg = response?.data?.msg || '授权失败'
68 + if (on_error) on_error(error_msg)
69 + throw new Error(error_msg)
70 + }
71 +
72 + let cookie =
73 + (response.cookies && response.cookies[0]) ||
74 + response.header?.['Set-Cookie'] ||
75 + response.header?.['set-cookie']
76 +
77 + if (Array.isArray(cookie)) cookie = cookie[0]
78 +
79 + if (!cookie) {
80 + const error_msg = '授权失败:没有获取到有效的会话信息'
81 + if (on_error) on_error(error_msg)
82 + throw new Error(error_msg)
83 + }
84 +
85 + Taro.setStorageSync('sessionid', cookie)
86 +
87 + if (request?.defaults?.headers) {
88 + request.defaults.headers.cookie = cookie
89 + }
90 +
91 + if (on_success) on_success(response.data)
92 + return response.data
93 + } catch (error) {
94 + Taro.hideLoading()
95 + const error_msg = error?.message || '授权失败,请稍后重试'
96 + if (on_error) on_error(error_msg)
97 + throw error
98 + }
99 +}
100 +
101 +export const navigateToAuth = (return_path) => {
102 + if (return_path) {
103 + saveCurrentPagePath(return_path)
104 + } else {
105 + saveCurrentPagePath()
106 + }
107 +
108 + Taro.navigateTo({
109 + url: '/pages/auth/index',
110 + })
111 +}
112 +
113 +export const returnToOriginalPage = async (default_path = '/pages/index/index') => {
114 + const router = routerStore()
115 + const saved_path = router.url
116 +
117 + try {
118 + router.remove()
119 +
120 + const pages = Taro.getCurrentPages()
121 + const current_page = pages[pages.length - 1]
122 + const current_route = current_page?.route
123 +
124 + let target_path = default_path
125 + if (saved_path && saved_path !== '') {
126 + target_path = saved_path.startsWith('/') ? saved_path : `/${saved_path}`
127 + }
128 +
129 + const target_route = target_path.split('?')[0].replace(/^\//, '')
130 +
131 + if (current_route === target_route) {
132 + return
133 + }
134 +
135 + try {
136 + await Taro.redirectTo({ url: target_path })
137 + } catch (error) {
138 + await Taro.reLaunch({ url: target_path })
139 + }
140 + } catch (error) {
141 + console.error('returnToOriginalPage 执行出错:', error)
142 + try {
143 + await Taro.reLaunch({ url: default_path })
144 + } catch (final_error) {
145 + console.error('最终降级方案也失败了:', final_error)
146 + }
147 + }
148 +}
149 +
150 +export const isFromShare = (options) => {
151 + return options && (options.from_share === '1' || options.scene)
152 +}
153 +
154 +export const handleSharePageAuth = async (options, callback) => {
155 + if (!needAuth()) {
156 + if (typeof callback === 'function') callback()
157 + return true
158 + }
159 +
160 + if (isFromShare(options)) {
161 + saveCurrentPagePath()
162 + }
163 +
164 + try {
165 + await silentAuth(
166 + () => {
167 + if (typeof callback === 'function') callback()
168 + },
169 + () => {
170 + Taro.navigateTo({ url: '/pages/auth/index' })
171 + }
172 + )
173 + return true
174 + } catch (error) {
175 + Taro.navigateTo({ url: '/pages/auth/index' })
176 + return false
177 + }
178 +}
179 +
180 +export const addShareFlag = (path) => {
181 + const separator = path.includes('?') ? '&' : '?'
182 + return `${path}${separator}from_share=1`
183 +}
1 /* 1 /*
2 * @Date: 2022-09-19 14:11:06 2 * @Date: 2022-09-19 14:11:06
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2025-07-01 11:17:49 4 + * @LastEditTime: 2026-01-06 21:41:47
5 - * @FilePath: /xyxBooking-weapp/src/utils/request.js 5 + * @FilePath: /itomix/git/xyxBooking-weapp/src/utils/request.js
6 * @Description: 简单axios封装,后续按实际处理 6 * @Description: 简单axios封装,后续按实际处理
7 */ 7 */
8 // import axios from 'axios' 8 // import axios from 'axios'
9 import axios from 'axios-miniprogram'; 9 import axios from 'axios-miniprogram';
10 import Taro from '@tarojs/taro' 10 import Taro from '@tarojs/taro'
11 -// import { strExist } from './tools' 11 +import qs from 'qs'
12 -// import qs from 'Qs' 12 +import { strExist } from './tools'
13 -import { mainStore } from '@/stores/main' 13 +import { routerStore } from '@/stores/router'
14 14
15 // import { ProgressStart, ProgressEnd } from '@/components/axios-progress/progress'; 15 // import { ProgressStart, ProgressEnd } from '@/components/axios-progress/progress';
16 // import store from '@/store' 16 // import store from '@/store'
...@@ -30,6 +30,30 @@ const getSessionId = () => { ...@@ -30,6 +30,30 @@ const getSessionId = () => {
30 } 30 }
31 }; 31 };
32 32
33 +const getCurrentPageFullPath = () => {
34 + try {
35 + const pages = Taro.getCurrentPages()
36 + if (!pages || pages.length === 0) return ''
37 +
38 + const currentPage = pages[pages.length - 1]
39 + const route = currentPage.route
40 + const options = currentPage.options || {}
41 +
42 + const queryParams = Object.keys(options)
43 + .map(key => `${key}=${encodeURIComponent(options[key])}`)
44 + .join('&')
45 +
46 + return queryParams ? `${route}?${queryParams}` : route
47 + } catch (error) {
48 + return ''
49 + }
50 +}
51 +
52 +const isPlainObject = (value) => {
53 + if (value === null || typeof value !== 'object') return false
54 + return Object.prototype.toString.call(value) === '[object Object]'
55 +}
56 +
33 // create an axios instance 57 // create an axios instance
34 const service = axios.create({ 58 const service = axios.create({
35 baseURL: BASE_URL, // url = base url + request url 59 baseURL: BASE_URL, // url = base url + request url
...@@ -56,17 +80,28 @@ service.interceptors.request.use( ...@@ -56,17 +80,28 @@ service.interceptors.request.use(
56 if (sessionid) { 80 if (sessionid) {
57 config.headers.cookie = sessionid; 81 config.headers.cookie = sessionid;
58 } 82 }
59 - 83 +
60 // 增加时间戳 84 // 增加时间戳
61 if (config.method === 'get') { 85 if (config.method === 'get') {
62 config.params = { ...config.params, timestamp: (new Date()).valueOf() } 86 config.params = { ...config.params, timestamp: (new Date()).valueOf() }
63 } 87 }
64 88
65 - /** 89 + if ((config.method || '').toLowerCase() === 'post') {
66 - * POST PHP需要修改数据格式 90 + const url = config.url || ''
67 - * 序列化POST请求时需要屏蔽上传相关接口,上传相关接口序列化后报错 91 + const headers = config.headers || {}
68 - */ 92 + const contentType = headers['content-type'] || headers['Content-Type']
69 - // config.data = config.method === 'post' && !strExist(['a=upload', 'upload.qiniup.com'], config.url) ? qs.stringify(config.data) : config.data; 93 + const shouldUrlEncode =
94 + !contentType || String(contentType).includes('application/x-www-form-urlencoded')
95 +
96 + if (shouldUrlEncode && !strExist(['upload.qiniup.com'], url) && isPlainObject(config.data)) {
97 + config.headers = {
98 + ...headers,
99 + 'content-type': 'application/x-www-form-urlencoded'
100 + }
101 + config.data = qs.stringify(config.data)
102 + }
103 + }
104 +
70 return config 105 return config
71 }, 106 },
72 error => { 107 error => {
...@@ -90,7 +125,7 @@ service.interceptors.response.use( ...@@ -90,7 +125,7 @@ service.interceptors.response.use(
90 */ 125 */
91 response => { 126 response => {
92 const res = response.data 127 const res = response.data
93 - 128 +
94 // 401 未授权处理 129 // 401 未授权处理
95 if (res.code === 401) { 130 if (res.code === 401) {
96 // 跳转到授权页 131 // 跳转到授权页
...@@ -98,6 +133,11 @@ service.interceptors.response.use( ...@@ -98,6 +133,11 @@ service.interceptors.response.use(
98 const pages = Taro.getCurrentPages(); 133 const pages = Taro.getCurrentPages();
99 const currentPage = pages[pages.length - 1]; 134 const currentPage = pages[pages.length - 1];
100 if (currentPage && currentPage.route !== 'pages/auth/index') { 135 if (currentPage && currentPage.route !== 'pages/auth/index') {
136 + const router = routerStore()
137 + const currentPath = getCurrentPageFullPath()
138 + if (currentPath) {
139 + router.add(currentPath)
140 + }
101 Taro.navigateTo({ 141 Taro.navigateTo({
102 url: '/pages/auth/index' 142 url: '/pages/auth/index'
103 }); 143 });
...@@ -108,11 +148,10 @@ service.interceptors.response.use( ...@@ -108,11 +148,10 @@ service.interceptors.response.use(
108 if (['预约ID不存在'].includes(res.msg)) { 148 if (['预约ID不存在'].includes(res.msg)) {
109 res.show = false; 149 res.show = false;
110 } 150 }
111 - 151 +
112 return response 152 return response
113 }, 153 },
114 error => { 154 error => {
115 - console.log('err' + error) // for debug
116 // Taro.showToast({ 155 // Taro.showToast({
117 // title: error.message, 156 // title: error.message,
118 // icon: 'none', 157 // icon: 'none',
......