feat: 完成全部接口替换为新后端接口
This commit is contained in:
@@ -1,74 +1,102 @@
|
||||
import { getApiConfig } from '../config/api';
|
||||
import {
|
||||
ApiRequestError,
|
||||
requestApi,
|
||||
type RequestOptions,
|
||||
} from './api-transport';
|
||||
import {
|
||||
cachedServerUser,
|
||||
clearSession,
|
||||
ensureSession,
|
||||
getAuthContext,
|
||||
recoverSession,
|
||||
rejectSession,
|
||||
} from './auth-session';
|
||||
export { ApiRequestError } from './api-transport';
|
||||
|
||||
interface ApiEnvelope<T> {
|
||||
code: number;
|
||||
message: string;
|
||||
data: T;
|
||||
requestId?: string;
|
||||
}
|
||||
const recoverable = new Set([
|
||||
'AUTH_REQUIRED',
|
||||
'AUTH_INVALID',
|
||||
'AUTH_EXPIRED',
|
||||
'AUTH_REVOKED',
|
||||
]);
|
||||
const blocked = new Set([
|
||||
'ACCOUNT_NOT_FOUND',
|
||||
'ACCOUNT_DISABLED',
|
||||
'WECHAT_LOGIN_BLOCKED',
|
||||
]);
|
||||
|
||||
/** 保留有限的状态用于页面反馈,不把服务器原始响应或凭证输出到界面。 */
|
||||
export class ApiRequestError extends Error {
|
||||
constructor(
|
||||
message: string,
|
||||
readonly statusCode: number = 0,
|
||||
) {
|
||||
super(message);
|
||||
this.name = 'ApiRequestError';
|
||||
}
|
||||
}
|
||||
|
||||
/** 新后端统一请求层:校验 HTTP 与业务状态,旧云函数/CDN 请求保持独立。 */
|
||||
/** 公开接口显式 auth=none;其余接口合并登录,最多恢复并重发一次,不跨账户补发。 */
|
||||
export async function apiRequest<T>(
|
||||
path: string,
|
||||
options: {
|
||||
method?: 'GET' | 'POST' | 'PUT' | 'DELETE';
|
||||
data?: Record<string, unknown>;
|
||||
header?: Record<string, string>;
|
||||
} = {},
|
||||
options: RequestOptions & { auth?: 'required' | 'none' } = {},
|
||||
): Promise<T> {
|
||||
const config = getApiConfig();
|
||||
if (!path.startsWith('/') || path.startsWith('//'))
|
||||
throw new Error('接口路径不正确');
|
||||
return new Promise((resolve, reject) => {
|
||||
wx.request<ApiEnvelope<T>>({
|
||||
url: `${config.baseUrl}${path}`,
|
||||
method: options.method || 'GET',
|
||||
data: options.data,
|
||||
header: { 'Content-Type': 'application/json', ...options.header },
|
||||
timeout: config.timeout,
|
||||
success(res) {
|
||||
if (res.statusCode < 200 || res.statusCode >= 300) {
|
||||
reject(
|
||||
new ApiRequestError(
|
||||
res.statusCode === 404
|
||||
? '内容暂不可用'
|
||||
: '接口请求失败,请稍后重试',
|
||||
res.statusCode,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (
|
||||
!res.data ||
|
||||
res.data.code !== 0 ||
|
||||
!Object.prototype.hasOwnProperty.call(res.data, 'data')
|
||||
) {
|
||||
reject(
|
||||
new ApiRequestError(
|
||||
'接口返回异常,请稍后重试',
|
||||
res.statusCode,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
resolve(res.data.data);
|
||||
// Authorization 由会话层统一提供;公开请求不附带调用方误传的凭证。
|
||||
const header = Object.fromEntries(
|
||||
Object.entries(options.header || {}).filter(
|
||||
([key]) => key.toLowerCase() !== 'authorization',
|
||||
),
|
||||
);
|
||||
if (options.auth === 'none')
|
||||
return requestApi(path, { ...options, header }, getApiConfig());
|
||||
const context = getAuthContext();
|
||||
const previousUserId = cachedServerUser()?.id;
|
||||
const initial = await ensureSession(context);
|
||||
if (previousUserId && initial.user.id !== previousUserId) {
|
||||
clearSession(false, context);
|
||||
throw new ApiRequestError(
|
||||
'账户已变化,请重新操作',
|
||||
401,
|
||||
'ACCOUNT_CHANGED',
|
||||
);
|
||||
}
|
||||
const send = async (accessToken: string) => {
|
||||
if (getAuthContext().key !== context.key)
|
||||
throw new ApiRequestError('接口环境已变化,请重新操作');
|
||||
const result = await requestApi<T>(
|
||||
path,
|
||||
{
|
||||
...options,
|
||||
header: { ...header, Authorization: `Bearer ${accessToken}` },
|
||||
},
|
||||
fail() {
|
||||
reject(
|
||||
new ApiRequestError('连接失败,请确认后端已启动及网络配置'),
|
||||
context,
|
||||
);
|
||||
if (
|
||||
getAuthContext().key !== context.key ||
|
||||
cachedServerUser()?.id !== initial.user.id
|
||||
)
|
||||
throw new ApiRequestError('账户状态已变化,请重新操作');
|
||||
return result;
|
||||
};
|
||||
try {
|
||||
return await send(initial.accessToken);
|
||||
} catch (error) {
|
||||
if (!(error instanceof ApiRequestError) || error.statusCode !== 401)
|
||||
throw error;
|
||||
if (!recoverable.has(error.reason)) {
|
||||
if (blocked.has(error.reason))
|
||||
rejectSession(context, initial.accessToken, true);
|
||||
throw error;
|
||||
}
|
||||
const renewed = await recoverSession(context, initial);
|
||||
if (renewed.user.id !== initial.user.id) {
|
||||
clearSession(false, context);
|
||||
throw new ApiRequestError(
|
||||
'账户已变化,请重新操作',
|
||||
401,
|
||||
'ACCOUNT_CHANGED',
|
||||
);
|
||||
}
|
||||
try {
|
||||
return await send(renewed.accessToken);
|
||||
} catch (second) {
|
||||
if (second instanceof ApiRequestError && second.statusCode === 401)
|
||||
rejectSession(
|
||||
context,
|
||||
renewed.accessToken,
|
||||
blocked.has(second.reason),
|
||||
);
|
||||
},
|
||||
});
|
||||
});
|
||||
throw second;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user