refactor(ui): Refactor VBase auth client with auto-refresh and onAuthSuccess

- Remove scope parameter from VBase constructor
    - Add _ensureAuth with smart refresh logic (refresh if stale >12min)
    - Add background token refresh timer (every 12 minutes)
    - Add centralized onAuthSuccess handler for all login flows
    - Remove axios response interceptor from env.js
    - Clean up timer and state on clear()
master
veypi 3 weeks ago
parent 4bb1283a0a
commit 5c542daba0

@ -10,16 +10,8 @@ export default async ($mod) => {
console.error('Failed to load langs.json', e) console.error('Failed to load langs.json', e)
} }
if (!$mod.$auth) { if (!$mod.$auth) {
// Initialize VBase Service
$mod.users = {} $mod.users = {}
const vbase = new VBase('vb', $mod.scoped, null, $mod.users); // Relative path const vbase = new VBase($mod.scoped, null, $mod.users)
$mod.$auth = vbase; $mod.$auth = vbase
} }
$mod.$axios.interceptors.response.use(function(response) {
return response?.data
}, function(error) {
let data = error.response ? error.response.data : error.response
return Promise.reject(data?.message || data);
});
} }

@ -16,18 +16,18 @@ export const Level = {
Admin: 7, // 111 管理员 (完全控制) Admin: 7, // 111 管理员 (完全控制)
}; };
const REFRESH_INTERVAL = 12 * 60 * 1000; // 12 分钟access_token 15min 过期)
class VBase { class VBase {
constructor(scope, baseURL, login_page, users = {}) { constructor(baseURL, login_page, users = {}) {
if (!scope) throw new Error('VBase: scope is required');
if (!baseURL) baseURL = window.location.origin; if (!baseURL) baseURL = window.location.origin;
if (baseURL === '' || baseURL === '/') baseURL = window.location.origin; if (baseURL === '' || baseURL === '/') baseURL = window.location.origin;
this.baseURL = baseURL; this.baseURL = baseURL;
this.scope = scope;
this.login_page = login_page || (baseURL + '/login'); this.login_page = login_page || (baseURL + '/login');
this.userKey = 'vbase_user'; this.userKey = 'vbase_user';
this.refreshTimer = null;
this.users = users; this.users = users;
this._user = null; this._user = null;
try { try {
@ -43,8 +43,51 @@ class VBase {
this._resolvedUserIDs = new Set(); this._resolvedUserIDs = new Set();
this._pendingUserFlush = null; this._pendingUserFlush = null;
// 验证登录状态 // 初始化鉴权:有用户缓存就验证 + 定时刷新
this.fetchUser().catch(() => {}); if (this._user) {
this._ensureAuth();
}
}
async _ensureAuth() {
const now = Date.now();
const lastRefresh = parseInt(localStorage.getItem('vbase_last_refresh'), 10) || 0;
// 超过 12 分钟没刷新,先刷新再拉用户
if (now - lastRefresh > REFRESH_INTERVAL) {
const ok = await this.refresh();
if (!ok) {
this.clear();
return;
}
}
// 无用户信息或刷新后重新拉
try {
await this.fetchUser();
} catch (e) {
this.clear();
return;
}
// 启动定时刷新
this._startRefreshTimer();
}
_startRefreshTimer() {
this._stopRefreshTimer();
this.refreshTimer = setInterval(() => {
this.refresh().then(ok => {
if (!ok) this.clear();
});
}, REFRESH_INTERVAL);
}
_stopRefreshTimer() {
if (this.refreshTimer) {
clearInterval(this.refreshTimer);
this.refreshTimer = null;
}
} }
// ========== Getters / Setters ========== // ========== Getters / Setters ==========
@ -57,10 +100,15 @@ class VBase {
localStorage.setItem(this.userKey, JSON.stringify(val)); localStorage.setItem(this.userKey, JSON.stringify(val));
} else { } else {
localStorage.removeItem(this.userKey); localStorage.removeItem(this.userKey);
localStorage.removeItem('vbase_last_refresh');
} }
this._cachePublicUser(val); this._cachePublicUser(val);
} }
_touchRefresh() {
localStorage.setItem('vbase_last_refresh', Date.now().toString());
}
// ========== API 请求 ========== // ========== API 请求 ==========
async request(method, path, data = null, headers = {}) { async request(method, path, data = null, headers = {}) {
@ -94,12 +142,19 @@ class VBase {
// ========== 认证 ========== // ========== 认证 ==========
/** 登录成功后初始化鉴权状态密码登录之外的方式验证码、OAuth、注册后自动登录等 */
async onAuthSuccess(user) {
this._touchRefresh();
this.user = user;
await this.fetchUser();
this._startRefreshTimer();
}
/** 用户名密码登录 */ /** 用户名密码登录 */
async login(username, password) { async login(username, password) {
const data = await this.request('POST', '/api/auth/login', { username, password }); const data = await this.request('POST', '/api/auth/login', { username, password });
if (data.user) { if (data.user) {
this.user = data.user; await this.onAuthSuccess(data.user);
await this.fetchUser();
return true; return true;
} }
return false; return false;
@ -108,38 +163,25 @@ class VBase {
/** OAuth 回调 */ /** OAuth 回调 */
async oauthCallback(provider, code, state) { async oauthCallback(provider, code, state) {
const data = await this.request('GET', `/api/auth/callback/${provider}?code=${code}&state=${state}`); const data = await this.request('GET', `/api/auth/callback/${provider}?code=${code}&state=${state}`);
if (data.user) { if (data.user) await this.onAuthSuccess(data.user);
this.user = data.user;
await this.fetchUser();
}
return data; return data;
} }
/** 绑定已有账号 */ /** 绑定已有账号 */
async bindAccount(tempToken, username, password) { async bindAccount(tempToken, username, password) {
const data = await this.request('POST', '/api/auth/bind', { const data = await this.request('POST', '/api/auth/bind', {
temp_token: tempToken, temp_token: tempToken, username, password,
username,
password,
}); });
if (data.user) { if (data.user) await this.onAuthSuccess(data.user);
this.user = data.user;
await this.fetchUser();
}
return data; return data;
} }
/** 绑定并注册 */ /** 绑定并注册 */
async bindRegister(tempToken, username, email) { async bindRegister(tempToken, username, email) {
const data = await this.request('POST', '/api/auth/bind-register', { const data = await this.request('POST', '/api/auth/bind-register', {
temp_token: tempToken, temp_token: tempToken, username, email,
username,
email,
}); });
if (data.user) { if (data.user) await this.onAuthSuccess(data.user);
this.user = data.user;
await this.fetchUser();
}
return data; return data;
} }
@ -156,10 +198,11 @@ class VBase {
} }
} }
/** Token 刷新(后端自动处理,前端可主动调用) */ /** Token 刷新 */
async refresh() { async refresh() {
try { try {
await this.request('POST', '/api/auth/refresh', {}); await this.request('POST', '/api/auth/refresh', {});
this._touchRefresh();
return true; return true;
} catch (e) { } catch (e) {
return false; return false;
@ -180,6 +223,7 @@ class VBase {
/** 清除登录状态 */ /** 清除登录状态 */
clear() { clear() {
this._stopRefreshTimer();
this.user = null; this.user = null;
for (const id of Object.keys(this.users)) { for (const id of Object.keys(this.users)) {
delete this.users[id]; delete this.users[id];

Loading…
Cancel
Save