hookehuyr

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

重构应用授权流程,将授权逻辑抽离到单独的工具模块
添加静默授权功能,优化用户体验
完善请求拦截器中的授权检查和跳转逻辑
新增页面路径保存和返回功能
// ESLint 检查 .vue 文件需要单独配置编辑器:
// https://eslint.vuejs.org/user-guide/#editor-integrations
{
"extends": ["taro/vue3"]
"extends": ["taro/vue3"],
"parserOptions": {
"requireConfigFile": false
}
}
......
......@@ -7,7 +7,7 @@
*/
import axios from '@/utils/request';
import Taro from '@tarojs/taro'
// import qs from 'qs'
import qs from 'qs'
/**
* 网络请求功能函数
......@@ -80,9 +80,13 @@ export const fetch = {
post: function (api, params) {
return axios.post(api, params)
},
// stringifyPost: function (api, params) {
// return axios.post(api, qs.stringify(params))
// },
stringifyPost: function (api, params) {
return axios.post(api, qs.stringify(params), {
headers: {
'content-type': 'application/x-www-form-urlencoded'
}
})
},
basePost: function (url, data, config) {
return axios.post(url, data, config)
}
......
......@@ -8,33 +8,34 @@
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import './app.less'
import { routerStore } from '@/stores/router'
import Taro from '@tarojs/taro'
import { saveCurrentPagePath, needAuth, silentAuth, navigateToAuth } from '@/utils/authRedirect'
const App = createApp({
// 对应 onLaunch
onLaunch(options) {
// 未授权状态跳转授权页面,首页不需要权限
const path = options.path;
const query = options.query;
// 缓存没有权限的地址
const router = routerStore();
router.add(path);
// if (path !== 'pages/index/index' && !wx.getStorageSync("sessionid")) {
if (!wx.getStorageSync("sessionid")) {
console.warn("没有权限");
// if (path === 'pages/detail/index') {
// Taro.navigateTo({
// url: `./pages/auth/index?url=${path}&id=${query.id}&start_date=${query.start_date}&end_date=${query.end_date}`,
// })
// } else {
// Taro.navigateTo({
// url: './pages/auth/index?url=' + path,
// })
// }
async onLaunch(options) {
const path = options?.path || ''
const query = options?.query || {}
const query_string = Object.keys(query)
.map((key) => `${key}=${encodeURIComponent(query[key])}`)
.join('&')
const full_path = query_string ? `${path}?${query_string}` : path
if (full_path) {
saveCurrentPagePath(full_path)
}
if (!needAuth()) return
if (path === 'pages/auth/index') return
try {
await silentAuth()
} catch (error) {
navigateToAuth(full_path || undefined)
}
},
onShow(options) {
onShow() {
},
// 入口组件不需要实现 render 方法,即使实现了也会被 taro 所覆盖
});
......
......@@ -15,62 +15,14 @@
<script setup>
import Taro, { useDidShow } from '@tarojs/taro'
import request from '@/utils/request';
import { silentAuth, returnToOriginalPage } from '@/utils/authRedirect'
useDidShow(() => {
// 授权登陆
Taro.login({
success: function (res) {
if (res.code) {
//发起网络请求
Taro.showLoading({
title: '授权中',
})
request.post('/srv/?a=openid', {
code: res.code
})
.then(res => {
if (res.data.code === 1) { // 成功
var cookie = res.cookies ? res.cookies[0] : (res.header['Set-Cookie'] || res.header['set-cookie']);
// 有些时候 cookie 可能是一个数组,或者分号分隔的字符串
// axios-miniprogram 返回的 cookies 应该是一个数组
if (cookie) {
// 处理 cookie 格式,如果包含分号,取第一个
if (Array.isArray(cookie)) cookie = cookie[0];
// 简单处理,通常服务器返回 PHPSESSID=xxx; path=/
// 我们存入 storage
Taro.setStorageSync("sessionid", cookie);
// 返回上一页
Taro.hideLoading();
Taro.navigateBack({
fail: () => {
Taro.reLaunch({ url: '/pages/index/index' })
}
});
} else {
console.warn('No cookie received');
Taro.hideLoading();
Taro.showToast({ title: '授权失败: 无Cookie', icon: 'none' });
}
} else {
console.warn(res.data.msg);
Taro.hideLoading();
Taro.showToast({ title: res.data.msg || '授权失败', icon: 'none' });
}
})
.catch(err => {
console.error(err);
Taro.hideLoading();
Taro.showToast({ title: '网络错误', icon: 'none' });
});
} else {
console.log('登录失败!' + res.errMsg)
Taro.showToast({ title: '微信登录失败', icon: 'none' });
}
}
})
silentAuth()
.then(() => returnToOriginalPage('/pages/index/index'))
.catch((error) => {
Taro.showToast({ title: error?.message || '授权失败', icon: 'none' })
})
})
</script>
......
import Taro from '@tarojs/taro'
import { routerStore } from '@/stores/router'
import request from '@/utils/request'
export const getCurrentPageFullPath = () => {
const pages = 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
}
export const saveCurrentPagePath = (custom_path) => {
const router = routerStore()
const path = custom_path || getCurrentPageFullPath()
router.add(path)
}
export const needAuth = () => {
try {
const sessionid = Taro.getStorageSync('sessionid')
return !sessionid || sessionid === ''
} catch (error) {
console.error('检查授权状态失败:', error)
return true
}
}
export const silentAuth = async (on_success, on_error) => {
try {
if (!needAuth()) {
const result = { code: 1, msg: '已授权' }
if (on_success) on_success(result)
return result
}
Taro.showLoading({
title: '加载中...',
mask: true,
})
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 response = await request.post('/srv/?a=openid', {
code: login_result.code,
})
Taro.hideLoading()
if (!response?.data || response.data.code !== 1) {
const error_msg = response?.data?.msg || '授权失败'
if (on_error) on_error(error_msg)
throw new Error(error_msg)
}
let cookie =
(response.cookies && response.cookies[0]) ||
response.header?.['Set-Cookie'] ||
response.header?.['set-cookie']
if (Array.isArray(cookie)) cookie = cookie[0]
if (!cookie) {
const error_msg = '授权失败:没有获取到有效的会话信息'
if (on_error) on_error(error_msg)
throw new Error(error_msg)
}
Taro.setStorageSync('sessionid', cookie)
if (request?.defaults?.headers) {
request.defaults.headers.cookie = cookie
}
if (on_success) on_success(response.data)
return response.data
} catch (error) {
Taro.hideLoading()
const error_msg = error?.message || '授权失败,请稍后重试'
if (on_error) on_error(error_msg)
throw error
}
}
export const navigateToAuth = (return_path) => {
if (return_path) {
saveCurrentPagePath(return_path)
} else {
saveCurrentPagePath()
}
Taro.navigateTo({
url: '/pages/auth/index',
})
}
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)
}
}
}
export const isFromShare = (options) => {
return options && (options.from_share === '1' || options.scene)
}
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
}
}
export const addShareFlag = (path) => {
const separator = path.includes('?') ? '&' : '?'
return `${path}${separator}from_share=1`
}
/*
* @Date: 2022-09-19 14:11:06
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-07-01 11:17:49
* @FilePath: /xyxBooking-weapp/src/utils/request.js
* @LastEditTime: 2026-01-06 21:41:47
* @FilePath: /itomix/git/xyxBooking-weapp/src/utils/request.js
* @Description: 简单axios封装,后续按实际处理
*/
// import axios from 'axios'
import axios from 'axios-miniprogram';
import Taro from '@tarojs/taro'
// import { strExist } from './tools'
// import qs from 'Qs'
import { mainStore } from '@/stores/main'
import qs from 'qs'
import { strExist } from './tools'
import { routerStore } from '@/stores/router'
// import { ProgressStart, ProgressEnd } from '@/components/axios-progress/progress';
// import store from '@/store'
......@@ -30,6 +30,30 @@ const getSessionId = () => {
}
};
const getCurrentPageFullPath = () => {
try {
const pages = Taro.getCurrentPages()
if (!pages || pages.length === 0) return ''
const currentPage = pages[pages.length - 1]
const route = currentPage.route
const options = currentPage.options || {}
const queryParams = Object.keys(options)
.map(key => `${key}=${encodeURIComponent(options[key])}`)
.join('&')
return queryParams ? `${route}?${queryParams}` : route
} catch (error) {
return ''
}
}
const isPlainObject = (value) => {
if (value === null || typeof value !== 'object') return false
return Object.prototype.toString.call(value) === '[object Object]'
}
// create an axios instance
const service = axios.create({
baseURL: BASE_URL, // url = base url + request url
......@@ -56,17 +80,28 @@ service.interceptors.request.use(
if (sessionid) {
config.headers.cookie = sessionid;
}
// 增加时间戳
if (config.method === 'get') {
config.params = { ...config.params, timestamp: (new Date()).valueOf() }
}
/**
* POST PHP需要修改数据格式
* 序列化POST请求时需要屏蔽上传相关接口,上传相关接口序列化后报错
*/
// config.data = config.method === 'post' && !strExist(['a=upload', 'upload.qiniup.com'], config.url) ? qs.stringify(config.data) : config.data;
if ((config.method || '').toLowerCase() === 'post') {
const url = config.url || ''
const headers = config.headers || {}
const contentType = headers['content-type'] || headers['Content-Type']
const shouldUrlEncode =
!contentType || String(contentType).includes('application/x-www-form-urlencoded')
if (shouldUrlEncode && !strExist(['upload.qiniup.com'], url) && isPlainObject(config.data)) {
config.headers = {
...headers,
'content-type': 'application/x-www-form-urlencoded'
}
config.data = qs.stringify(config.data)
}
}
return config
},
error => {
......@@ -90,7 +125,7 @@ service.interceptors.response.use(
*/
response => {
const res = response.data
// 401 未授权处理
if (res.code === 401) {
// 跳转到授权页
......@@ -98,6 +133,11 @@ service.interceptors.response.use(
const pages = Taro.getCurrentPages();
const currentPage = pages[pages.length - 1];
if (currentPage && currentPage.route !== 'pages/auth/index') {
const router = routerStore()
const currentPath = getCurrentPageFullPath()
if (currentPath) {
router.add(currentPath)
}
Taro.navigateTo({
url: '/pages/auth/index'
});
......@@ -108,11 +148,10 @@ service.interceptors.response.use(
if (['预约ID不存在'].includes(res.msg)) {
res.show = false;
}
return response
},
error => {
console.log('err' + error) // for debug
// Taro.showToast({
// title: error.message,
// icon: 'none',
......