fix:无法设置token的问题

This commit is contained in:
2026-06-12 15:55:50 +08:00
parent 513b6ac116
commit 0660b90809
6 changed files with 51 additions and 16 deletions
+3 -3
View File
@@ -7,9 +7,9 @@
import { ofetch, type $Fetch, type FetchHook } from "ofetch";
import { ApiError, ErrorCode } from "./api_result";
import { getApiConfig } from "../../../core/net/config/api_config";
import { authRefreshInterceptor } from "./interceptor/auth_refresh_interceptor";
import { loggingInterceptor } from "./interceptor/logging_interceptor";
import { tokenInterceptor } from "./interceptor/token_interceptor";
import { authRefreshInterceptor } from "../../../core/net/interceptor/auth_refresh_interceptor";
import { loggingInterceptor } from "../../../core/net/interceptor/logging_interceptor";
import { tokenInterceptor } from "../../../core/net/interceptor/token_interceptor";
/**
* 合并多个同类型 hook 为单个 hook(按顺序执行)
+3 -3
View File
@@ -10,6 +10,6 @@ export * from "./http_client";
export * from "./metrics_api";
export * from "./response_helper";
export * from "./user_api";
export * from "./interceptor/auth_refresh_interceptor";
export * from "./interceptor/logging_interceptor";
export * from "./interceptor/token_interceptor";
export * from "../../../core/net/interceptor/auth_refresh_interceptor";
export * from "../../../core/net/interceptor/logging_interceptor";
export * from "../../../core/net/interceptor/token_interceptor";
@@ -1,186 +0,0 @@
/**
* 认证刷新拦截器
*
* 处理 401 错误,自动刷新 token 并重试原请求。
* 原始 Dart: lib/core/net/interceptor/auth_interceptor.dart
*/
import type { FetchHook } from "ofetch";
import { ApiPath } from "../api_path";
import { ApiError, ErrorCode } from "../api_result";
import { AuthStorage } from "@/data/storage/auth/auth_storage";
import { deviceIdentifier } from "@/utils/device_identifier";
import { Result } from "@/utils/result";
/**
* 不需要 auth token 刷新的路径
* (这些路径返回 401 时不触发刷新)
*/
const NO_AUTH_REFRESH_PATHS: readonly string[] = [
ApiPath.sendCode,
ApiPath.emailLogin,
ApiPath.register,
ApiPath.guestLogin,
ApiPath.refresh,
];
/**
* 是否正在刷新 token(防止并发刷新)
*/
let isRefreshing = false;
/**
* 等待中的刷新 Promise
*/
let refreshPromise: Promise<void> | null = null;
/**
* 获取当前可用的 auth token(登录 token 优先,其次游客 token
*/
async function getAuthTokenAsync(): Promise<string | null> {
const storage = AuthStorage.getInstance();
const loginResult = await storage.getLoginToken();
if (Result.isOk(loginResult) && loginResult.data && loginResult.data.length > 0) {
return loginResult.data;
}
const guestResult = await storage.getGuestToken();
if (Result.isOk(guestResult) && guestResult.data && guestResult.data.length > 0) {
return guestResult.data;
}
return null;
}
/**
* 实际刷新 token(被拦截器调用)
*/
async function refreshToken(
isGuestMode: boolean,
baseUrl: string
): Promise<void> {
if (isGuestMode) {
await refreshGuestToken(baseUrl);
} else {
await refreshLoginToken(baseUrl);
}
}
/**
* 刷新登录 token
*/
async function refreshLoginToken(baseUrl: string): Promise<void> {
const storage = AuthStorage.getInstance();
const refreshResult = await storage.getRefreshToken();
if (!Result.isOk(refreshResult) || !refreshResult.data) {
throw new Error("No refresh token available");
}
const response = await fetch(`${baseUrl}${ApiPath.refresh}`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ refreshToken: refreshResult.data }),
});
if (!response.ok) {
throw new Error(`Refresh failed: ${response.status}`);
}
const json = (await response.json()) as {
success?: boolean;
data?: { token?: string; refreshToken?: string };
};
if (json.success && json.data) {
if (json.data.token) await storage.setLoginToken(json.data.token);
if (json.data.refreshToken) await storage.setRefreshToken(json.data.refreshToken);
} else {
throw new Error("Refresh response invalid");
}
}
/**
* 刷新游客 token
*/
async function refreshGuestToken(baseUrl: string): Promise<void> {
const storage = AuthStorage.getInstance();
const deviceId = await deviceIdentifier.getDeviceId();
const response = await fetch(`${baseUrl}${ApiPath.guestLogin}`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ deviceId }),
});
if (!response.ok) {
throw new Error(`Guest refresh failed: ${response.status}`);
}
const json = (await response.json()) as {
success?: boolean;
data?: { token?: string; deviceId?: string };
};
if (json.success && json.data) {
if (json.data.token) await storage.setGuestToken(json.data.token);
if (json.data.deviceId) await storage.setDeviceId(json.data.deviceId);
} else {
throw new Error("Guest refresh response invalid");
}
}
/**
* 401 → 刷新 token 并重试
*/
export const onResponseErrorAuthRefresh: FetchHook = async (ctx) => {
const status = ctx.response?.status;
// 只处理 401
if (status !== ErrorCode.httpUnauthorized) return;
// 跳过不需要刷新的路径
const requestUrl =
ctx.request instanceof Request ? ctx.request.url : ctx.request;
const path = new URL(requestUrl).pathname;
if (NO_AUTH_REFRESH_PATHS.some((p) => path.includes(p))) return;
// 已登录则刷新登录 token,否则刷新游客 token
const isGuestMode = !AuthStorage.getInstance().hasLoginToken();
// 并发刷新:等待或启动
if (isRefreshing && refreshPromise) {
await refreshPromise;
} else {
isRefreshing = true;
const baseUrl = new URL(requestUrl).origin;
refreshPromise = (async () => {
try {
await refreshToken(isGuestMode, baseUrl);
} finally {
isRefreshing = false;
refreshPromise = null;
}
})();
await refreshPromise;
}
// 刷新失败 → 清除数据并抛出
const newToken = await getAuthTokenAsync();
if (!newToken) {
AuthStorage.getInstance().clearAuthData();
throw new ApiError(
ErrorCode.unknownError,
"Token refresh failed",
status
);
}
// 刷新成功:更新请求头的 token
if (ctx.request instanceof Request) {
ctx.request.headers.set("Authorization", `Bearer ${newToken}`);
}
};
/**
* 兼容旧接口的 FetchHooks 形式
*/
export const authRefreshInterceptor = {
onResponseError: onResponseErrorAuthRefresh,
};
@@ -1,85 +0,0 @@
/**
* 日志拦截器
*
* 在开发模式下记录请求/响应/错误日志。
* 原始 Dart: lib/core/net/interceptor/app_interceptor.dart 中的日志部分。
*/
import type { FetchHook } from "ofetch";
const TAG = "[API]";
function isDev(): boolean {
return process.env.NODE_ENV !== "production";
}
function getRequestUrl(request: Request | string): string {
return typeof request === "string" ? request : request.url;
}
function getRequestMethod(
request: Request | string,
options: { method?: string }
): string {
return request instanceof Request ? request.method : options.method ?? "GET";
}
function formatBody(body: unknown): string {
if (body === undefined || body === null) return "";
try {
if (body instanceof FormData) {
const entries: string[] = [];
body.forEach((value, key) => {
if (value instanceof File) {
entries.push(`${key}=File(${value.name})`);
} else {
entries.push(`${key}=${String(value)}`);
}
});
return `FormData{${entries.join(", ")}}`;
}
return JSON.stringify(body);
} catch {
return String(body);
}
}
export const onRequestLogging: FetchHook = async (ctx) => {
if (!isDev()) return;
const method = getRequestMethod(ctx.request, ctx.options);
const url = getRequestUrl(ctx.request);
console.log(
`${TAG}${method} ${url}`,
ctx.options.body !== undefined ? formatBody(ctx.options.body) : ""
);
};
export const onResponseLogging: FetchHook = async (ctx) => {
if (!isDev() || !ctx.response) return;
const method = getRequestMethod(ctx.request, ctx.options);
const url = getRequestUrl(ctx.request);
console.log(
`${TAG}${ctx.response.status} ${method} ${url}`,
formatBody(ctx.response._data)
);
};
export const onErrorLogging: FetchHook = async (ctx) => {
if (!isDev()) return;
const status = ctx.response?.status ?? "N/A";
const method = getRequestMethod(ctx.request, ctx.options);
const url = getRequestUrl(ctx.request);
console.error(
`${TAG}${status} ${method} ${url}`,
ctx.error?.message ?? "Unknown error"
);
};
/**
* 兼容旧接口的 FetchHooks 形式(保留以备兼容)
*/
export const loggingInterceptor = {
onRequest: onRequestLogging,
onResponse: onResponseLogging,
onResponseError: onErrorLogging,
onRequestError: onErrorLogging,
};
@@ -1,74 +0,0 @@
/**
* Token 拦截器
*
* 在请求头中注入 Authorization: Bearer <token>。
* 跳过不需要 token 的路径(登录、注册、发送验证码、刷新等)。
* 原始 Dart: lib/core/net/interceptor/token_interceptor.dart + token_paths.dart
*/
import type { FetchHook } from "ofetch";
import { ApiPath } from "../api_path";
import { AuthStorage } from "../../../storage/auth/auth_storage";
/**
* 不需要 token 的路径
*/
const NO_TOKEN_PATHS: readonly string[] = [
ApiPath.sendCode,
ApiPath.emailLogin,
ApiPath.register,
ApiPath.guestLogin,
];
/**
* 获取请求路径
*/
function getRequestPath(request: Request | string): string {
if (typeof request === "string") {
try {
return new URL(request).pathname;
} catch {
return request;
}
}
return new URL(request.url).pathname;
}
/**
* 路径是否需要 token
*/
function needsToken(path: string): boolean {
return !NO_TOKEN_PATHS.some((noTokenPath) => path.includes(noTokenPath));
}
/**
* 获取当前可用的 auth token(登录 token 优先,其次游客 token
*/
async function getAuthToken(): Promise<string | null> {
const storage = AuthStorage.getInstance();
const loginR = await storage.getLoginToken();
if (loginR.success && loginR.data && loginR.data.length > 0) {
return loginR.data;
}
const guestR = await storage.getGuestToken();
if (guestR.success && guestR.data && guestR.data.length > 0) {
return guestR.data;
}
return null;
}
export const onRequestToken: FetchHook = async (ctx) => {
const path = getRequestPath(ctx.request);
if (!needsToken(path)) return;
const token = await getAuthToken();
if (token && ctx.request instanceof Request) {
ctx.request.headers.set("Authorization", `Bearer ${token}`);
}
};
/**
* 兼容旧接口的 FetchHooks 形式
*/
export const tokenInterceptor = {
onRequest: onRequestToken,
};