Files
doodle-mini/miniprogram/services/api-transport.ts
T

101 lines
3.5 KiB
TypeScript

import { getApiConfig } from '../config/api';
export class ApiRequestError extends Error {
constructor(
message: string,
readonly statusCode = 0,
readonly reason = '',
) {
super(message);
this.name = 'ApiRequestError';
}
}
export interface RequestOptions {
method?: 'GET' | 'POST' | 'PUT' | 'DELETE';
data?: Record<string, unknown>;
header?: Record<string, string>;
timeout?: number;
upload?: { filePath: string; uploadId: string };
}
/** 底层只发请求,不触发登录;登录接口与鉴权包装共用,避免循环依赖。 */
export function requestApi<T>(
path: string,
options: RequestOptions = {},
config = getApiConfig(),
): Promise<T> {
if (!path.startsWith('/') || path.startsWith('//'))
throw new Error('接口路径不正确');
return new Promise((resolve, reject) => {
const success = (res: { statusCode: number; data: unknown }) => {
const body = res.data as {
code?: number;
data?: T;
reason?: string;
} | null;
if (res.statusCode < 200 || res.statusCode >= 300) {
reject(
new ApiRequestError(
res.statusCode === 404
? '内容暂不可用'
: '接口请求失败,请稍后重试',
res.statusCode,
typeof body?.reason === 'string' &&
/^[A-Z_]{1,64}$/.test(body.reason)
? body.reason
: '',
),
);
} else if (
!body ||
body.code !== 0 ||
!Object.prototype.hasOwnProperty.call(body, 'data')
) {
reject(
new ApiRequestError(
'接口返回异常,请稍后重试',
res.statusCode,
),
);
} else resolve(body.data as T);
};
const fail = () =>
reject(new ApiRequestError('连接失败,请确认后端已启动及网络配置'));
if (options.upload) {
// wx.uploadFile 由运行时生成 multipart boundary,不能自行设置 JSON Content-Type。
wx.uploadFile({
url: `${config.baseUrl}${path}`,
filePath: options.upload.filePath,
name: 'file',
formData: { uploadId: options.upload.uploadId },
header: options.header,
timeout: 60000,
success(res) {
let data: unknown;
try {
data = JSON.parse(res.data);
} catch {
reject(new ApiRequestError('上传响应格式异常'));
return;
}
success({ statusCode: res.statusCode, data });
},
fail,
});
} else
wx.request({
url: `${config.baseUrl}${path}`,
method: options.method || 'GET',
data: options.data,
header: {
'Content-Type': 'application/json',
...options.header,
},
timeout: options.timeout ?? config.timeout,
success,
fail,
});
});
}