204 lines
5.7 KiB
TypeScript
204 lines
5.7 KiB
TypeScript
import { isDev, ignoreTokens, config } from "@/config";
|
||
import { locale, t } from "@/locale";
|
||
import { isNull, isObject, parse, storage } from "../utils";
|
||
import { useStore } from "../store";
|
||
|
||
// 请求参数类型定义
|
||
export type RequestOptions = {
|
||
url: string; // 请求地址
|
||
method?: RequestMethod; // 请求方法
|
||
data?: any; // 请求体数据
|
||
params?: any; // URL参数
|
||
header?: any; // 请求头
|
||
timeout?: number; // 超时时间
|
||
withCredentials?: boolean; // 是否携带凭证
|
||
firstIpv4?: boolean; // 是否优先使用IPv4
|
||
enableChunked?: boolean; // 是否启用分块传输
|
||
};
|
||
|
||
// 响应数据类型定义
|
||
export type Response = {
|
||
code?: number;
|
||
message?: string;
|
||
data?: any;
|
||
};
|
||
|
||
// 请求队列(用于等待token刷新后继续请求)
|
||
let requests: ((token: string) => void)[] = [];
|
||
|
||
// 标记token是否正在刷新
|
||
let isRefreshing = false;
|
||
|
||
// 判断当前url是否忽略token校验
|
||
const isIgnoreToken = (url: string) => {
|
||
return ignoreTokens.some((e) => {
|
||
const pattern = e.replace(/\*/g, ".*");
|
||
return new RegExp(pattern).test(url);
|
||
});
|
||
};
|
||
|
||
/**
|
||
* 通用请求方法
|
||
* @param options 请求参数
|
||
* @returns Promise<T>
|
||
*/
|
||
export function request(options: RequestOptions): Promise<any> {
|
||
let { url, method = "GET", data = {}, header = {}, timeout = 60000 } = options;
|
||
|
||
const { user } = useStore();
|
||
|
||
// 开发环境下打印请求信息
|
||
if (isDev) {
|
||
console.log(`[${method}] ${url}`);
|
||
}
|
||
|
||
// 拼接基础url
|
||
if (!url.startsWith("http")) {
|
||
url = config.baseUrl + url;
|
||
}
|
||
|
||
// 获取当前token
|
||
let Authorization: string | null = user.token;
|
||
|
||
// 如果是忽略token的接口,则不携带token
|
||
if (isIgnoreToken(url)) {
|
||
Authorization = null;
|
||
}
|
||
|
||
return new Promise((resolve, reject) => {
|
||
// 发起请求的实际函数
|
||
const next = () => {
|
||
uni.request({
|
||
url,
|
||
method,
|
||
data,
|
||
header: {
|
||
Authorization,
|
||
language: locale.value,
|
||
...(header as UTSJSONObject)
|
||
},
|
||
timeout,
|
||
|
||
success(res) {
|
||
if (isDev) console.log(`[Response] ${url} status: ${res.statusCode}`, JSON.stringify(res.data));
|
||
// 401 无权限 或 业务码 1001 (未登录/失效)
|
||
const resData = res.data;
|
||
if (resData != null && isObject(resData)) {
|
||
const dataObj = resData as UTSJSONObject;
|
||
const bizCode = dataObj["code"];
|
||
if (res.statusCode == 401 || bizCode == 1001) {
|
||
user.logout();
|
||
reject({ message: t("登录已失效"), code: 1001 } as Response);
|
||
return;
|
||
}
|
||
} else if (res.statusCode == 401) {
|
||
user.logout();
|
||
reject({ message: t("登录已失效"), code: 1001 } as Response);
|
||
return;
|
||
}
|
||
|
||
// 502 服务异常
|
||
else if (res.statusCode == 502) {
|
||
reject({ message: t("服务异常") } as Response);
|
||
}
|
||
|
||
// 404 未找到
|
||
else if (res.statusCode == 404) {
|
||
return reject({
|
||
message: `[404] ${url}`
|
||
} as Response);
|
||
}
|
||
|
||
// 200 正常响应
|
||
else if (res.statusCode == 200) {
|
||
if (res.data == null) {
|
||
resolve(null);
|
||
} else if (!isObject(res.data as any)) {
|
||
// 处理非对象格式的数据(如字符串)
|
||
console.warn(`[Service] Received non-object data from ${url}:`, res.data);
|
||
resolve(res.data);
|
||
} else {
|
||
// 解析响应数据
|
||
try {
|
||
const resDataObj = res.data as UTSJSONObject;
|
||
const { code, message, data } = parse<Response>(
|
||
resDataObj ?? { code: 0 }
|
||
)!;
|
||
|
||
switch (code) {
|
||
case 1000:
|
||
console.log(`[Service] Request success for ${url}, data:`, JSON.stringify(data));
|
||
resolve(data);
|
||
break;
|
||
default:
|
||
console.warn(`[Service] Business error from ${url}:`, code, message);
|
||
reject({ message: message || t("请求失败"), code } as Response);
|
||
break;
|
||
}
|
||
} catch (e : any) {
|
||
console.error(`[Service] Parse error for ${url}:`, e);
|
||
reject({ message: e.message || t("服务异常") } as Response);
|
||
}
|
||
}
|
||
} else {
|
||
reject({ message: t("服务异常") } as Response);
|
||
}
|
||
},
|
||
|
||
// 网络请求失败
|
||
fail(err) {
|
||
console.error(`[Request Failed] ${url}`, err);
|
||
reject({ message: err.errMsg } as Response);
|
||
}
|
||
});
|
||
if (isDev) console.log(`[Request Sent] ${url}`);
|
||
};
|
||
|
||
// 非刷新token接口才进行token有效性校验
|
||
if (!options.url.includes("/refreshToken")) {
|
||
if (!isNull(Authorization)) {
|
||
// 判断token是否过期
|
||
if (storage.isExpired("token")) {
|
||
// 判断refreshToken是否过期
|
||
if (storage.isExpired("refreshToken")) {
|
||
// 刷新token也过期,直接退出登录
|
||
user.logout();
|
||
return;
|
||
}
|
||
|
||
// 如果当前没有在刷新token,则发起刷新
|
||
if (!isRefreshing) {
|
||
isRefreshing = true;
|
||
user.refreshToken()
|
||
.then((token) => {
|
||
// 刷新成功后,执行队列中的请求
|
||
requests.forEach((cb) => cb(token));
|
||
requests = [];
|
||
isRefreshing = false;
|
||
})
|
||
.catch((err) => {
|
||
reject(err);
|
||
user.logout();
|
||
});
|
||
}
|
||
|
||
// 将当前请求加入队列,等待token刷新后再执行
|
||
new Promise((resolve) => {
|
||
requests.push((token: string) => {
|
||
// 重新设置token
|
||
Authorization = token;
|
||
next();
|
||
resolve(true);
|
||
});
|
||
});
|
||
// 此处return,等待token刷新
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
|
||
// token有效,直接发起请求
|
||
next();
|
||
});
|
||
}
|