feat: 功能
This commit is contained in:
108
frontend/utils/README.md
Normal file
108
frontend/utils/README.md
Normal file
@@ -0,0 +1,108 @@
|
||||
# 全局工具模块 (Monorepo Utils)
|
||||
|
||||
此目录包含可在 monorepo 各个应用中复用的全局工具函数。
|
||||
|
||||
## 📁 目录结构
|
||||
|
||||
```
|
||||
utils/
|
||||
├── token-manager.js # Token 统一管理工具
|
||||
└── README.md # 本文件
|
||||
```
|
||||
|
||||
## 🚀 使用方式
|
||||
|
||||
### Token 管理 (`token-manager.js`)
|
||||
|
||||
在应用中使用全局 token 管理工具:
|
||||
|
||||
```javascript
|
||||
// 导入 token 管理工具
|
||||
import {
|
||||
getToken,
|
||||
setToken,
|
||||
clearAllTokens,
|
||||
getAuthHeader
|
||||
} from '@gold/utils/token-manager'
|
||||
|
||||
// 获取 token
|
||||
const token = getToken()
|
||||
|
||||
// 设置 token
|
||||
setToken({
|
||||
accessToken: 'xxx',
|
||||
refreshToken: 'yyy'
|
||||
})
|
||||
|
||||
// 获取 Authorization Header
|
||||
const header = getAuthHeader() // Bearer xxx
|
||||
|
||||
// 清空所有 token
|
||||
clearAllTokens()
|
||||
```
|
||||
|
||||
## 📝 API 说明
|
||||
|
||||
### Token 管理函数
|
||||
|
||||
#### `getToken()`
|
||||
获取访问令牌(按优先级读取)
|
||||
|
||||
**返回值:** `string` - token 字符串或空字符串
|
||||
|
||||
#### `getAccessToken()`
|
||||
获取访问令牌(别名,兼容旧代码)
|
||||
|
||||
**返回值:** `string` - token 字符串或空字符串
|
||||
|
||||
#### `getRefreshToken()`
|
||||
获取刷新令牌
|
||||
|
||||
**返回值:** `string | null` - 刷新令牌或 null
|
||||
|
||||
#### `setToken(tokens)`
|
||||
设置访问令牌和刷新令牌
|
||||
|
||||
**参数:**
|
||||
- `tokens.accessToken` (string, 可选): 访问令牌
|
||||
- `tokens.refreshToken` (string, 可选): 刷新令牌
|
||||
|
||||
#### `clearAllTokens()`
|
||||
清除所有 token(包括 dev token、access token、refresh token)
|
||||
|
||||
#### `removeToken()`
|
||||
删除 token(别名,调用 `clearAllTokens()`)
|
||||
|
||||
#### `getAuthHeader()`
|
||||
获取完整的 Authorization Header 值
|
||||
|
||||
**返回值:** `string` - `Bearer token` 或空字符串
|
||||
|
||||
#### `setDevToken(token)`
|
||||
设置手动输入的 dev token(用于开发测试)
|
||||
|
||||
**参数:** `token` (string) - dev token
|
||||
|
||||
#### `getDevToken()`
|
||||
获取手动输入的 dev token
|
||||
|
||||
**返回值:** `string` - dev token 或空字符串
|
||||
|
||||
## 🔄 Token 存储优先级
|
||||
|
||||
1. **手动输入的 dev token** (sessionStorage) - 最高优先级
|
||||
2. **正式登录的 token** (wsCache/localStorage)
|
||||
3. **环境变量 VITE_DEV_TOKEN** - 兜底
|
||||
|
||||
## 📦 在 Vite 配置中使用
|
||||
|
||||
确保在 `vite.config.js` 中配置了 `@gold` 别名:
|
||||
|
||||
```javascript
|
||||
resolve: {
|
||||
alias: {
|
||||
'@gold': fileURLToPath(new URL('../../', import.meta.url))
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
160
frontend/utils/TOKEN_STORAGE.md
Normal file
160
frontend/utils/TOKEN_STORAGE.md
Normal file
@@ -0,0 +1,160 @@
|
||||
# Token 存储位置说明
|
||||
|
||||
## 📍 Token 存储位置总览
|
||||
|
||||
Token 在项目中有 **3 个存储位置**,按读取优先级排序:
|
||||
|
||||
### 1. **Dev Token(开发手动输入的 token)**
|
||||
- **存储位置**:`sessionStorage`
|
||||
- **键名**:`DEV_MANUAL_TOKEN`
|
||||
- **设置方式**:`setDevToken(token)`
|
||||
- **特点**:
|
||||
- 优先级最高(读取时优先使用)
|
||||
- 关闭浏览器标签页后自动清除
|
||||
- 用于开发测试
|
||||
|
||||
**浏览器查看方式:**
|
||||
```javascript
|
||||
// 在浏览器控制台执行
|
||||
sessionStorage.getItem('DEV_MANUAL_TOKEN')
|
||||
```
|
||||
|
||||
### 2. **正式登录的 Token(主要存储)**
|
||||
- **存储位置**:`localStorage`(通过 WebStorageCache 封装)
|
||||
- **键名**:
|
||||
- `ACCESS_TOKEN` 或 `access_token`(访问令牌)
|
||||
- `REFRESH_TOKEN` 或 `refresh_token`(刷新令牌)
|
||||
- **设置方式**:`setToken({ accessToken, refreshToken })`
|
||||
- **特点**:
|
||||
- 持久化存储(关闭浏览器后仍然存在)
|
||||
- 使用 `WebStorageCache` 库管理
|
||||
- 支持大小写不同的键名变体(兼容性)
|
||||
|
||||
**浏览器查看方式:**
|
||||
```javascript
|
||||
// 在浏览器控制台执行
|
||||
localStorage.getItem('ACCESS_TOKEN')
|
||||
localStorage.getItem('REFRESH_TOKEN')
|
||||
// 或者
|
||||
localStorage.getItem('access_token')
|
||||
localStorage.getItem('refresh_token')
|
||||
```
|
||||
|
||||
**实际存储结构:**
|
||||
```
|
||||
localStorage:
|
||||
├── ACCESS_TOKEN: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
|
||||
└── REFRESH_TOKEN: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
|
||||
```
|
||||
|
||||
### 3. **环境变量 Token(兜底)**
|
||||
- **存储位置**:代码中(不存储在浏览器)
|
||||
- **变量名**:`VITE_DEV_TOKEN`
|
||||
- **设置方式**:`.env` 文件或环境变量
|
||||
- **特点**:
|
||||
- 只在代码中读取,不写入浏览器存储
|
||||
- 优先级最低(前两者都没有时才使用)
|
||||
- 用于开发环境默认配置
|
||||
|
||||
## 🔄 Token 读取优先级
|
||||
|
||||
当调用 `getToken()` 时,按以下顺序查找:
|
||||
|
||||
```
|
||||
1. sessionStorage['DEV_MANUAL_TOKEN'] ← 最高优先级
|
||||
↓ (如果没有)
|
||||
2. localStorage['ACCESS_TOKEN'] 或 localStorage['access_token']
|
||||
↓ (如果没有)
|
||||
3. import.meta.env.VITE_DEV_TOKEN ← 最低优先级
|
||||
```
|
||||
|
||||
## 📝 代码示例
|
||||
|
||||
### 设置 Token
|
||||
|
||||
```javascript
|
||||
import { setToken, setDevToken } from '@gold/utils/token-manager'
|
||||
|
||||
// 设置正式登录的 token(存储到 localStorage)
|
||||
setToken({
|
||||
accessToken: 'xxx',
|
||||
refreshToken: 'yyy'
|
||||
})
|
||||
|
||||
// 设置开发 token(存储到 sessionStorage)
|
||||
setDevToken('dev-token-123')
|
||||
```
|
||||
|
||||
### 读取 Token
|
||||
|
||||
```javascript
|
||||
import { getToken } from '@gold/utils/token-manager'
|
||||
|
||||
// 自动按优先级读取
|
||||
const token = getToken()
|
||||
```
|
||||
|
||||
### 清除 Token
|
||||
|
||||
```javascript
|
||||
import { clearAllTokens } from '@gold/utils/token-manager'
|
||||
|
||||
// 清除所有位置的 token
|
||||
clearAllTokens()
|
||||
// 会清除:
|
||||
// 1. sessionStorage['DEV_MANUAL_TOKEN']
|
||||
// 2. localStorage['ACCESS_TOKEN'] 和 'access_token'
|
||||
// 3. localStorage['REFRESH_TOKEN'] 和 'refresh_token'
|
||||
```
|
||||
|
||||
## 🔍 在浏览器中查看
|
||||
|
||||
### Chrome DevTools
|
||||
|
||||
1. **打开 DevTools** (F12)
|
||||
2. **Application 标签页**
|
||||
3. **Storage 部分**:
|
||||
- **Local Storage** → 查看 `ACCESS_TOKEN`、`REFRESH_TOKEN`
|
||||
- **Session Storage** → 查看 `DEV_MANUAL_TOKEN`
|
||||
|
||||
### 控制台命令
|
||||
|
||||
```javascript
|
||||
// 查看所有 token
|
||||
console.log('Dev Token:', sessionStorage.getItem('DEV_MANUAL_TOKEN'))
|
||||
console.log('Access Token:', localStorage.getItem('ACCESS_TOKEN'))
|
||||
console.log('Refresh Token:', localStorage.getItem('REFRESH_TOKEN'))
|
||||
|
||||
// 查看所有 localStorage
|
||||
console.table(localStorage)
|
||||
|
||||
// 查看所有 sessionStorage
|
||||
console.table(sessionStorage)
|
||||
```
|
||||
|
||||
## ⚠️ 注意事项
|
||||
|
||||
1. **WebStorageCache 封装**:
|
||||
- `useCache()` 默认使用 `localStorage`
|
||||
- 通过 `WebStorageCache` 库管理,支持过期时间等功能
|
||||
- 实际存储位置仍然是 `localStorage`
|
||||
|
||||
2. **键名大小写**:
|
||||
- 代码中统一使用 `ACCESS_TOKEN` 和 `REFRESH_TOKEN`(大写)
|
||||
- 但为了兼容,也支持 `access_token` 和 `refresh_token`(小写)
|
||||
- 读取时会尝试所有变体
|
||||
|
||||
3. **清除逻辑**:
|
||||
- `clearAllTokens()` 会清除所有位置的 token
|
||||
- 包括 sessionStorage、localStorage 的所有变体键名
|
||||
- 确保完全清除,避免残留
|
||||
|
||||
## 📊 存储位置总结表
|
||||
|
||||
| Token 类型 | 存储位置 | 键名 | 持久化 | 优先级 |
|
||||
|-----------|---------|------|--------|--------|
|
||||
| Dev Token | sessionStorage | `DEV_MANUAL_TOKEN` | ❌ 关闭标签页清除 | 1 (最高) |
|
||||
| Access Token | localStorage | `ACCESS_TOKEN` / `access_token` | ✅ 持久化 | 2 |
|
||||
| Refresh Token | localStorage | `REFRESH_TOKEN` / `refresh_token` | ✅ 持久化 | 2 |
|
||||
| Env Token | 代码中 | `VITE_DEV_TOKEN` | N/A | 3 (最低) |
|
||||
|
||||
215
frontend/utils/token-manager.js
Normal file
215
frontend/utils/token-manager.js
Normal file
@@ -0,0 +1,215 @@
|
||||
import { useCache, CACHE_KEY, deleteTokenCache } from '../hooks/web/useCache'
|
||||
|
||||
/**
|
||||
* Token 统一管理模块(Monorepo 全局工具)
|
||||
*
|
||||
* 这是项目中唯一的 token 管理入口,所有 token 操作都应该通过此模块进行。
|
||||
*
|
||||
* Token 存储优先级(读取顺序):
|
||||
* 1. 手动输入的 dev token (sessionStorage)
|
||||
* 2. 正式登录的 token (wsCache/localStorage)
|
||||
* 3. 环境变量 VITE_DEV_TOKEN
|
||||
*
|
||||
* Token 存储位置:
|
||||
* - ACCESS_TOKEN/access_token: 访问令牌(wsCache/localStorage)
|
||||
* - REFRESH_TOKEN/refresh_token: 刷新令牌(wsCache/localStorage)
|
||||
* - DEV_MANUAL_TOKEN: 开发手动输入的token(sessionStorage)
|
||||
*/
|
||||
|
||||
// ==================== 常量定义 ====================
|
||||
const DEV_MANUAL_TOKEN_KEY = 'DEV_MANUAL_TOKEN'
|
||||
|
||||
// Token 键名变体(支持大小写不同)
|
||||
const TOKEN_KEYS = {
|
||||
ACCESS: [CACHE_KEY.ACCESS_TOKEN, 'access_token'],
|
||||
REFRESH: [CACHE_KEY.REFRESH_TOKEN, 'refresh_token']
|
||||
}
|
||||
|
||||
// ==================== 缓存实例管理 ====================
|
||||
let wsCache = null
|
||||
|
||||
/**
|
||||
* 获取 wsCache 实例(延迟初始化,避免模块加载时出错)
|
||||
* @returns {Object} wsCache 实例
|
||||
*/
|
||||
function getCache() {
|
||||
if (!wsCache) {
|
||||
try {
|
||||
wsCache = useCache().wsCache
|
||||
} catch (e) {
|
||||
console.warn('初始化 wsCache 失败:', e)
|
||||
// 返回一个安全的空对象,避免后续调用出错
|
||||
wsCache = {
|
||||
get: () => null,
|
||||
set: () => {},
|
||||
delete: () => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
return wsCache
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取完整的 Authorization Header 值
|
||||
* @returns {string} Bearer token 或空字符串
|
||||
*/
|
||||
export function getAuthHeader() {
|
||||
const token = getToken()
|
||||
return token ? `Bearer ${token}` : ''
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 token
|
||||
* @returns {string} token 字符串
|
||||
*/
|
||||
export function getToken() {
|
||||
// 1. 优先使用手动输入的 dev token
|
||||
const manualToken = sessionStorage.getItem(DEV_MANUAL_TOKEN_KEY)
|
||||
if (manualToken) {
|
||||
return manualToken
|
||||
}
|
||||
|
||||
// 2. 使用正式登录的 token(从 wsCache 读取)
|
||||
try {
|
||||
const cache = getCache()
|
||||
// 尝试所有可能的键名变体
|
||||
for (const key of TOKEN_KEYS.ACCESS) {
|
||||
const accessToken = cache.get(key)
|
||||
if (accessToken) {
|
||||
return accessToken
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('获取 wsCache 失败:', e)
|
||||
}
|
||||
|
||||
// 3. 兜底:环境变量中的 token
|
||||
const envToken = import.meta?.env?.VITE_DEV_TOKEN
|
||||
if (envToken) {
|
||||
return envToken
|
||||
}
|
||||
|
||||
return ''
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置手动输入的 dev token
|
||||
* @param {string} token
|
||||
*/
|
||||
export function setDevToken(token) {
|
||||
if (token) {
|
||||
sessionStorage.setItem(DEV_MANUAL_TOKEN_KEY, token)
|
||||
} else {
|
||||
sessionStorage.removeItem(DEV_MANUAL_TOKEN_KEY)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取手动输入的 dev token(用于显示)
|
||||
* @returns {string}
|
||||
*/
|
||||
export function getDevToken() {
|
||||
return sessionStorage.getItem(DEV_MANUAL_TOKEN_KEY) || ''
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置访问令牌和刷新令牌
|
||||
* @param {Object} tokens - token 对象
|
||||
* @param {string} tokens.accessToken - 访问令牌
|
||||
* @param {string} tokens.refreshToken - 刷新令牌
|
||||
*/
|
||||
export function setToken(tokens) {
|
||||
if (!tokens || (!tokens.accessToken && !tokens.refreshToken)) {
|
||||
console.warn('setToken: token 参数无效', tokens)
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const cache = getCache()
|
||||
if (tokens.accessToken) {
|
||||
cache.set(CACHE_KEY.ACCESS_TOKEN, tokens.accessToken)
|
||||
}
|
||||
if (tokens.refreshToken) {
|
||||
cache.set(CACHE_KEY.REFRESH_TOKEN, tokens.refreshToken)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('设置 token 失败:', e)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取刷新令牌
|
||||
* @returns {string|null} 刷新令牌或 null
|
||||
*/
|
||||
export function getRefreshToken() {
|
||||
try {
|
||||
const cache = getCache()
|
||||
// 尝试所有可能的键名变体
|
||||
for (const key of TOKEN_KEYS.REFRESH) {
|
||||
const token = cache.get(key)
|
||||
if (token) {
|
||||
return token
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('获取 refresh token 失败:', e)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除所有 token
|
||||
* 清空所有可能的 token 存储位置,确保完全清除
|
||||
*/
|
||||
export function clearAllTokens() {
|
||||
try {
|
||||
// 1. 清空 sessionStorage 中的 dev token
|
||||
sessionStorage.removeItem(DEV_MANUAL_TOKEN_KEY)
|
||||
} catch (e) {
|
||||
console.warn('清除 sessionStorage token 失败:', e)
|
||||
}
|
||||
|
||||
try {
|
||||
// 2. 清空 wsCache (localStorage) 中的所有可能的 token 键名
|
||||
// 使用统一的 deleteTokenCache 函数,保持与 useCache 的一致性
|
||||
deleteTokenCache()
|
||||
} catch (e) {
|
||||
console.warn('清除 wsCache token 失败:', e)
|
||||
}
|
||||
|
||||
try {
|
||||
// 3. 清空 localStorage 中可能的 token(兜底处理)
|
||||
// 注意:wsCache 默认使用 localStorage,但为了确保清除,也直接操作 localStorage
|
||||
const localStorageKeys = [
|
||||
...TOKEN_KEYS.ACCESS,
|
||||
...TOKEN_KEYS.REFRESH,
|
||||
DEV_MANUAL_TOKEN_KEY
|
||||
]
|
||||
localStorageKeys.forEach(key => {
|
||||
try {
|
||||
localStorage.removeItem(key)
|
||||
} catch (e) {
|
||||
// 忽略单个键删除失败
|
||||
}
|
||||
})
|
||||
} catch (e) {
|
||||
console.warn('清除 localStorage token 失败:', e)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除 token(别名,兼容旧代码)
|
||||
* @deprecated 使用 clearAllTokens() 代替
|
||||
*/
|
||||
export function removeToken() {
|
||||
clearAllTokens()
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取访问令牌(别名,兼容旧代码)
|
||||
* @returns {string} token 字符串
|
||||
*/
|
||||
export function getAccessToken() {
|
||||
return getToken()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user