feat(client): 实现稳定的设备ID生成与认证优化
- 重构设备ID生成逻辑,采用多重降级策略确保唯一性与稳定性- 移除客户端SQLite缓存依赖,改用localStorage存储token与设备ID - 优化认证流程,简化token管理与会话恢复逻辑- 增加设备数量限制检查,防止超出配额 - 更新SSE连接逻辑,适配新的认证机制 - 调整Redis连接池配置,提升并发性能与稳定性 - 移除冗余的缓存接口与本地退出逻辑 - 修复设备移除时的状态处理问题,避免重复调用offline接口 - 引入OSHI库用于硬件信息采集(备用方案)- 更新开发环境API地址配置
This commit is contained in:
@@ -3,7 +3,7 @@ import {onMounted, ref, computed, defineAsyncComponent, type Component, onUnmoun
|
||||
import {ElMessage, ElMessageBox} from 'element-plus'
|
||||
import zhCn from 'element-plus/es/locale/lang/zh-cn'
|
||||
import 'element-plus/dist/index.css'
|
||||
import {authApi} from './api/auth'
|
||||
import {authApi, TOKEN_KEY} from './api/auth'
|
||||
import {deviceApi, type DeviceItem, type DeviceQuota} from './api/device'
|
||||
import {getOrCreateDeviceId} from './utils/deviceId'
|
||||
const LoginDialog = defineAsyncComponent(() => import('./components/auth/LoginDialog.vue'))
|
||||
@@ -146,21 +146,19 @@ function handleMenuSelect(key: string) {
|
||||
}
|
||||
|
||||
async function handleLoginSuccess(data: { token: string; permissions?: string; expireTime?: string }) {
|
||||
isAuthenticated.value = true
|
||||
showAuthDialog.value = false
|
||||
showRegDialog.value = false
|
||||
|
||||
try {
|
||||
await authApi.saveToken(data.token)
|
||||
const username = getUsernameFromToken(data.token)
|
||||
currentUsername.value = username
|
||||
userPermissions.value = data?.permissions || ''
|
||||
vipExpireTime.value = data?.expireTime ? new Date(data.expireTime) : null
|
||||
localStorage.setItem(TOKEN_KEY, data.token)
|
||||
isAuthenticated.value = true
|
||||
showAuthDialog.value = false
|
||||
showRegDialog.value = false
|
||||
|
||||
currentUsername.value = getUsernameFromToken(data.token)
|
||||
userPermissions.value = data.permissions || ''
|
||||
vipExpireTime.value = data.expireTime ? new Date(data.expireTime) : null
|
||||
|
||||
// 获取设备ID并注册设备
|
||||
const deviceId = await getOrCreateDeviceId()
|
||||
await deviceApi.register({
|
||||
username,
|
||||
username: currentUsername.value,
|
||||
deviceId,
|
||||
os: navigator.platform
|
||||
})
|
||||
@@ -168,26 +166,13 @@ async function handleLoginSuccess(data: { token: string; permissions?: string; e
|
||||
} catch (e: any) {
|
||||
isAuthenticated.value = false
|
||||
showAuthDialog.value = true
|
||||
await authApi.deleteTokenCache()
|
||||
localStorage.removeItem(TOKEN_KEY)
|
||||
ElMessage.error(e?.message || '设备注册失败')
|
||||
}
|
||||
}
|
||||
|
||||
async function logout() {
|
||||
try {
|
||||
const deviceId = await getClientIdFromToken()
|
||||
if (deviceId) await deviceApi.offline({ deviceId })
|
||||
} catch (error) {
|
||||
console.warn('离线通知失败:', error)
|
||||
}
|
||||
|
||||
try {
|
||||
const tokenRes: any = await authApi.getToken()
|
||||
const token = typeof tokenRes === 'string' ? tokenRes : tokenRes?.data
|
||||
if (token) await authApi.logout(token)
|
||||
} catch {}
|
||||
|
||||
await authApi.deleteTokenCache()
|
||||
function clearLocalAuth() {
|
||||
localStorage.removeItem(TOKEN_KEY)
|
||||
isAuthenticated.value = false
|
||||
currentUsername.value = ''
|
||||
userPermissions.value = ''
|
||||
@@ -197,6 +182,16 @@ async function logout() {
|
||||
SSEManager.disconnect()
|
||||
}
|
||||
|
||||
async function logout() {
|
||||
try {
|
||||
const deviceId = getClientIdFromToken()
|
||||
if (deviceId) await deviceApi.offline({ deviceId })
|
||||
} catch (error) {
|
||||
console.warn('离线通知失败:', error)
|
||||
}
|
||||
clearLocalAuth()
|
||||
}
|
||||
|
||||
async function handleUserClick() {
|
||||
if (!isAuthenticated.value) {
|
||||
showAuthDialog.value = true
|
||||
@@ -220,47 +215,42 @@ function showRegisterDialog() {
|
||||
|
||||
function backToLogin() {
|
||||
showRegDialog.value = false
|
||||
|
||||
showAuthDialog.value = true
|
||||
}
|
||||
|
||||
async function checkAuth() {
|
||||
try {
|
||||
await authApi.sessionBootstrap().catch(() => undefined)
|
||||
const tokenRes: any = await authApi.getToken()
|
||||
const token = typeof tokenRes === 'string' ? tokenRes : tokenRes?.data
|
||||
|
||||
if (token) {
|
||||
const verifyRes: any = await authApi.verifyToken(token)
|
||||
isAuthenticated.value = true
|
||||
currentUsername.value = getUsernameFromToken(token) || ''
|
||||
|
||||
userPermissions.value = verifyRes?.data?.permissions || verifyRes?.permissions || ''
|
||||
if (verifyRes?.data?.expireTime || verifyRes?.expireTime) {
|
||||
vipExpireTime.value = new Date(verifyRes?.data?.expireTime || verifyRes?.expireTime)
|
||||
const token = localStorage.getItem(TOKEN_KEY)
|
||||
if (!token) {
|
||||
if (['rakuten', 'amazon', 'zebra', 'shopee'].includes(activeMenu.value)) {
|
||||
showAuthDialog.value = true
|
||||
}
|
||||
|
||||
SSEManager.connect()
|
||||
return
|
||||
}
|
||||
} catch {
|
||||
await authApi.deleteTokenCache()
|
||||
}
|
||||
|
||||
if (['rakuten', 'amazon', 'zebra', 'shopee'].includes(activeMenu.value)) {
|
||||
showAuthDialog.value = true
|
||||
const res: any = await authApi.verifyToken(token)
|
||||
isAuthenticated.value = true
|
||||
currentUsername.value = getUsernameFromToken(token) || ''
|
||||
userPermissions.value = res?.data?.permissions || res?.permissions || ''
|
||||
|
||||
const expireTime = res?.data?.expireTime || res?.expireTime
|
||||
if (expireTime) vipExpireTime.value = new Date(expireTime)
|
||||
|
||||
SSEManager.connect()
|
||||
} catch {
|
||||
localStorage.removeItem(TOKEN_KEY)
|
||||
if (['rakuten', 'amazon', 'zebra', 'shopee'].includes(activeMenu.value)) {
|
||||
showAuthDialog.value = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function getClientIdFromToken(token?: string) {
|
||||
function getClientIdFromToken(token?: string) {
|
||||
try {
|
||||
let t = token
|
||||
if (!t) {
|
||||
const tokenRes: any = await authApi.getToken()
|
||||
t = typeof tokenRes === 'string' ? tokenRes : tokenRes?.data
|
||||
}
|
||||
const t = token || localStorage.getItem(TOKEN_KEY)
|
||||
if (!t) return ''
|
||||
const payload = JSON.parse(atob(t.split('.')[1]))
|
||||
return payload.clientId || ''
|
||||
return JSON.parse(atob(t.split('.')[1])).clientId || ''
|
||||
} catch {
|
||||
return ''
|
||||
}
|
||||
@@ -268,32 +258,23 @@ async function getClientIdFromToken(token?: string) {
|
||||
|
||||
function getUsernameFromToken(token: string) {
|
||||
try {
|
||||
const payload = JSON.parse(atob(token.split('.')[1]))
|
||||
return payload.username || ''
|
||||
return JSON.parse(atob(token.split('.')[1])).username || ''
|
||||
} catch {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
// SSE管理器
|
||||
const SSEManager = {
|
||||
connection: null as EventSource | null,
|
||||
async connect() {
|
||||
if (this.connection) return
|
||||
|
||||
try {
|
||||
const tokenRes: any = await authApi.getToken()
|
||||
const token = typeof tokenRes === 'string' ? tokenRes : tokenRes?.data
|
||||
if (!token) {
|
||||
console.warn('SSE连接失败: 没有有效的 token')
|
||||
return
|
||||
}
|
||||
const token = localStorage.getItem(TOKEN_KEY)
|
||||
if (!token) return console.warn('SSE连接失败: 没有token')
|
||||
|
||||
const clientId = await getClientIdFromToken(token)
|
||||
if (!clientId) {
|
||||
console.warn('SSE连接失败: 无法从 token 获取 clientId')
|
||||
return
|
||||
}
|
||||
const clientId = getClientIdFromToken(token)
|
||||
if (!clientId) return console.warn('SSE连接失败: 无法获取clientId')
|
||||
|
||||
let sseUrl = 'http://192.168.1.89:8085/monitor/account/events'
|
||||
try {
|
||||
@@ -348,16 +329,15 @@ const SSEManager = {
|
||||
},
|
||||
|
||||
handleError() {
|
||||
if (!this.connection) return
|
||||
try { this.connection.close() } catch {}
|
||||
this.connection = null
|
||||
console.warn('SSE连接失败,已断开')
|
||||
this.disconnect()
|
||||
},
|
||||
|
||||
disconnect() {
|
||||
if (!this.connection) return
|
||||
try { this.connection.close() } catch {}
|
||||
this.connection = null
|
||||
if (this.connection) {
|
||||
try { this.connection.close() } catch {}
|
||||
this.connection = null
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
@@ -409,8 +389,9 @@ async function confirmRemoveDevice(row: DeviceItem & { isCurrent?: boolean }) {
|
||||
devices.value = devices.value.filter(d => d.deviceId !== row.deviceId)
|
||||
deviceQuota.value.used = Math.max(0, (deviceQuota.value.used || 0) - 1)
|
||||
|
||||
const clientId = await getClientIdFromToken()
|
||||
if (row.deviceId === clientId) await logout()
|
||||
if (row.deviceId === getClientIdFromToken()) {
|
||||
clearLocalAuth() // 移除当前设备:只清理本地状态,不调用offline(避免覆盖removed状态)
|
||||
}
|
||||
|
||||
ElMessage.success('已移除设备')
|
||||
} catch (e: any) {
|
||||
|
||||
Reference in New Issue
Block a user