mirror of
https://github.com/Cccc-owo/CheckInApp.git
synced 2026-06-17 05:56:29 +00:00
192 lines
5.4 KiB
JavaScript
192 lines
5.4 KiB
JavaScript
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
|
import { message } from 'ant-design-vue'
|
|
import { useAuthStore } from '@/stores/auth'
|
|
import { useUserStore } from '@/stores/user'
|
|
import { useRouter } from 'vue-router'
|
|
|
|
/**
|
|
* Token 过期监控 Composable
|
|
*
|
|
* 功能:
|
|
* 1. 定时检查 Token 状态
|
|
* 2. Token 过期后 5 分钟内提醒用户
|
|
* 3. 为有密码的用户提供友好的过期处理
|
|
*
|
|
* 注意:使用单例模式,确保全局只有一个监控实例
|
|
*/
|
|
|
|
// 全局单例:确保整个应用只有一个监控实例
|
|
let monitorTimer = null
|
|
let warningShown = false
|
|
let isMonitoring = false // 新增:防止重复启动
|
|
|
|
// 检查间隔(毫秒)
|
|
const NORMAL_CHECK_INTERVAL = 15 * 60 * 1000 // 正常情况:15 分钟
|
|
const URGENT_CHECK_INTERVAL = 5 * 60 * 1000 // Token 即将过期:5 分钟
|
|
|
|
export function useTokenMonitor() {
|
|
const authStore = useAuthStore()
|
|
const userStore = useUserStore()
|
|
const router = useRouter()
|
|
|
|
const tokenStatus = computed(() => userStore.tokenStatus)
|
|
const hasPassword = computed(() => authStore.user?.has_password || false)
|
|
|
|
// 计算 Token 剩余分钟数
|
|
const getRemainingMinutes = () => {
|
|
if (!tokenStatus.value?.expires_at) return null
|
|
|
|
const now = Math.floor(Date.now() / 1000)
|
|
const expiresAt = tokenStatus.value.expires_at
|
|
const diffSeconds = expiresAt - now
|
|
|
|
return Math.floor(diffSeconds / 60)
|
|
}
|
|
|
|
// 检查 Token 状态并显示提醒
|
|
const checkTokenStatus = async () => {
|
|
// 如果未登录,不检查
|
|
if (!authStore.isAuthenticated) {
|
|
return
|
|
}
|
|
|
|
try {
|
|
// 获取最新的 Token 状态
|
|
await userStore.fetchTokenStatus()
|
|
|
|
const remainingMinutes = getRemainingMinutes()
|
|
|
|
// Token 已过期(负数分钟)
|
|
if (remainingMinutes !== null && remainingMinutes < 0) {
|
|
const expiredMinutes = Math.abs(remainingMinutes)
|
|
|
|
// Token 过期后 5 分钟内提醒
|
|
if (expiredMinutes <= 5) {
|
|
if (hasPassword.value) {
|
|
// 有密码的用户:友好提示
|
|
if (!warningShown) {
|
|
message.warning({
|
|
content: `您的登录凭证已过期 ${expiredMinutes} 分钟,部分功能可能受限。建议您扫码刷新凭证。`,
|
|
duration: 8,
|
|
key: 'token-expired-warning',
|
|
})
|
|
warningShown = true
|
|
}
|
|
} else {
|
|
// 没有密码的用户:必须重新登录
|
|
message.error({
|
|
content: '您的登录凭证已过期,请重新扫码登录',
|
|
duration: 5,
|
|
key: 'token-expired-error',
|
|
})
|
|
|
|
// 清除登录状态并跳转
|
|
authStore.logout()
|
|
router.push('/login')
|
|
}
|
|
} else if (expiredMinutes > 5) {
|
|
// 过期超过 5 分钟
|
|
if (!hasPassword.value) {
|
|
// 没有密码的用户:强制退出
|
|
authStore.logout()
|
|
router.push('/login')
|
|
}
|
|
}
|
|
}
|
|
// Token 即将过期(1小时内)
|
|
else if (remainingMinutes !== null && remainingMinutes > 0 && remainingMinutes <= 60) {
|
|
if (!warningShown) {
|
|
message.warning({
|
|
content: `您的登录凭证将在 ${remainingMinutes} 分钟后过期,建议您提前刷新`,
|
|
duration: 6,
|
|
key: 'token-expiring-warning',
|
|
})
|
|
warningShown = true
|
|
}
|
|
|
|
// Token 即将过期时,切换到更频繁的检查(5 分钟)
|
|
adjustCheckInterval(URGENT_CHECK_INTERVAL)
|
|
}
|
|
// Token 状态正常
|
|
else if (remainingMinutes !== null && remainingMinutes > 60) {
|
|
// 重置警告标志
|
|
warningShown = false
|
|
|
|
// 恢复正常检查频率(15 分钟)
|
|
adjustCheckInterval(NORMAL_CHECK_INTERVAL)
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('检查 Token 状态失败:', error)
|
|
}
|
|
}
|
|
|
|
// 调整检查间隔
|
|
const adjustCheckInterval = (newInterval) => {
|
|
if (monitorTimer) {
|
|
const currentInterval = monitorTimer._idleTimeout || 0
|
|
|
|
// 只有当新间隔与当前间隔不同时才重启定时器
|
|
if (currentInterval !== newInterval) {
|
|
clearInterval(monitorTimer)
|
|
monitorTimer = setInterval(() => {
|
|
checkTokenStatus()
|
|
}, newInterval)
|
|
}
|
|
}
|
|
}
|
|
|
|
// 启动监控
|
|
const startMonitoring = () => {
|
|
// 避免重复启动(单例模式)
|
|
if (isMonitoring || monitorTimer) {
|
|
return
|
|
}
|
|
|
|
isMonitoring = true
|
|
|
|
// 立即检查一次
|
|
checkTokenStatus()
|
|
|
|
// 默认使用正常检查频率(15 分钟)
|
|
monitorTimer = setInterval(() => {
|
|
checkTokenStatus()
|
|
}, NORMAL_CHECK_INTERVAL)
|
|
}
|
|
|
|
// 停止监控
|
|
const stopMonitoring = () => {
|
|
if (monitorTimer) {
|
|
clearInterval(monitorTimer)
|
|
monitorTimer = null
|
|
}
|
|
isMonitoring = false
|
|
warningShown = false
|
|
}
|
|
|
|
// 手动触发检查
|
|
const checkNow = () => {
|
|
warningShown = false // 重置警告标志,允许再次显示
|
|
checkTokenStatus()
|
|
}
|
|
|
|
// 组件挂载时启动监控
|
|
onMounted(() => {
|
|
if (authStore.isAuthenticated) {
|
|
startMonitoring()
|
|
}
|
|
})
|
|
|
|
// 组件卸载时不停止监控(因为是全局单例)
|
|
// onUnmounted 中不调用 stopMonitoring(),让监控持续运行
|
|
|
|
return {
|
|
tokenStatus,
|
|
hasPassword,
|
|
startMonitoring,
|
|
stopMonitoring,
|
|
checkNow,
|
|
getRemainingMinutes,
|
|
}
|
|
}
|