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)
}
if (!$mod.$auth) {
// Initialize VBase Service
$mod.users = {}
const vbase = new VBase('vb', $mod.scoped, null, $mod.users); // Relative path
$mod.$auth = vbase;
const vbase = new VBase($mod.scoped, null, $mod.users)
$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 管理员 (完全控制)
};
const REFRESH_INTERVAL = 12 * 60 * 1000; // 12 分钟access_token 15min 过期)
class VBase {
constructor(scope, baseURL, login_page, users = {}) {
if (!scope) throw new Error('VBase: scope is required');
constructor(baseURL, login_page, users = {}) {
if (!baseURL) baseURL = window.location.origin;
if (baseURL === '' || baseURL === '/') baseURL = window.location.origin;
this.baseURL = baseURL;
this.scope = scope;
this.login_page = login_page || (baseURL + '/login');
this.userKey = 'vbase_user';
this.refreshTimer = null;
this.users = users;
this._user = null;
try {
@ -36,15 +36,58 @@ class VBase {
this._user = cached;
this._cachePublicUser(cached);
}
} catch (e) {}
} catch (e) { }
this._pendingUserIDs = new Set();
this._loadingUserIDs = new Set();
this._resolvedUserIDs = new Set();
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 ==========
@ -57,10 +100,15 @@ class VBase {
localStorage.setItem(this.userKey, JSON.stringify(val));
} else {
localStorage.removeItem(this.userKey);
localStorage.removeItem('vbase_last_refresh');
}
this._cachePublicUser(val);
}
_touchRefresh() {
localStorage.setItem('vbase_last_refresh', Date.now().toString());
}
// ========== API 请求 ==========
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) {
const data = await this.request('POST', '/api/auth/login', { username, password });
if (data.user) {
this.user = data.user;
await this.fetchUser();
await this.onAuthSuccess(data.user);
return true;
}
return false;
@ -108,38 +163,25 @@ class VBase {
/** OAuth 回调 */
async oauthCallback(provider, code, state) {
const data = await this.request('GET', `/api/auth/callback/${provider}?code=${code}&state=${state}`);
if (data.user) {
this.user = data.user;
await this.fetchUser();
}
if (data.user) await this.onAuthSuccess(data.user);
return data;
}
/** 绑定已有账号 */
async bindAccount(tempToken, username, password) {
const data = await this.request('POST', '/api/auth/bind', {
temp_token: tempToken,
username,
password,
temp_token: tempToken, username, password,
});
if (data.user) {
this.user = data.user;
await this.fetchUser();
}
if (data.user) await this.onAuthSuccess(data.user);
return data;
}
/** 绑定并注册 */
async bindRegister(tempToken, username, email) {
const data = await this.request('POST', '/api/auth/bind-register', {
temp_token: tempToken,
username,
email,
temp_token: tempToken, username, email,
});
if (data.user) {
this.user = data.user;
await this.fetchUser();
}
if (data.user) await this.onAuthSuccess(data.user);
return data;
}
@ -156,10 +198,11 @@ class VBase {
}
}
/** Token 刷新(后端自动处理,前端可主动调用) */
/** Token 刷新 */
async refresh() {
try {
await this.request('POST', '/api/auth/refresh', {});
this._touchRefresh();
return true;
} catch (e) {
return false;
@ -180,6 +223,7 @@ class VBase {
/** 清除登录状态 */
clear() {
this._stopRefreshTimer();
this.user = null;
for (const id of Object.keys(this.users)) {
delete this.users[id];

Loading…
Cancel
Save