uniapp-request-sdk
v1.6.0
Published
用于uniapp小程序的请求库的sdk
Readme
uniapp-request-sdk
一个为 uni-app 小程序框架设计的高效、易用的 HTTP 请求库。支持自动重试、动态 token 管理、请求头预处理等企业级特性。
特性
- ✅ 自动重试机制 - 请求失败自动重试,可配置重试次数和延迟
- ✅ 动态 Token 管理 - 支持自动获取和更新 token,兼容 APP 原生交互
- ✅ 请求头预处理 - 支持同步/异步预处理器,动态生成或修改请求头
- ✅ 完整的异常处理 - 统一的错误处理、HTTP 状态码处理、权限管理
- ✅ 文件上传支持 - 专门优化的文件上传接口,独立超时控制
- ✅ 平台兼容 - 支持 iOS App、Android App、H5 等多平台
- ✅ TypeScript 支持 - 完整的类型定义,更好的开发体验
- ✅ 完全向后兼容 - 不破坏现有代码,渐进式增强
安装
npm 安装
npm install uniapp-request-sdkyarn 安装
yarn add uniapp-request-sdk快速开始
基础使用
import UniRequest from 'uniapp-request-sdk';
// 1. 创建请求实例
const request = new UniRequest({
baseUrl: 'https://api.example.com',
timeout: 10000,
});
// 2. 发送 GET 请求
const data = await request.get('/users/list');
// 3. 发送 POST 请求
const result = await request.post('/users', { name: 'John' });
// 4. 发送 DELETE 请求
await request.delete('/users/1');
// 5. 发送 PUT 请求
await request.put('/users/1', { name: 'Jane' });
// 6. 上传文件
const uploadResult = await request.uploadFile(
'/upload',
'/path/to/file.jpg'
);详细配置
初始化配置
const request = new UniRequest({
// 基础配置
baseUrl: 'https://api.example.com', // API 基础地址(必须)
username: 'john_doe', // 用户名(用于日志)
// 超时设置
timeout: 10000, // 普通请求超时(毫秒,默认 10s)
uploadTimeout: 5000, // 文件上传超时(毫秒,默认 5s)
// 重试配置
maxRetryCount: 3, // 最大重试次数(默认 3)
retryDelay: 3000, // 重试延迟时间(毫秒,默认 3s)
// 请求头配置
header: { // 全局请求头
'Content-Type': 'application/json',
'X-Custom-Header': 'value',
},
// Token 配置
token: 'initial-token', // 初始 token
tokenHeader: 'Authorization', // token 所在的 header 字段名(默认)
tokenPrefix: 'Bearer ', // token 前缀(默认带空格)
tokenEventName: 'getToken', // 向 APP 获取 token 的事件名
getTokenFun: async () => { // 自定义获取 token 函数
return await fetchTokenFromStorage();
},
// 错误处理
onErrorHandler: (error) => { // 统一错误处理函数
console.error('请求错误:', error);
// 可以在这里上报错误日志
},
// 新增:请求头预处理
headerProcessor: async (header) => { // 动态处理请求头(异步)
const timestamp = Date.now();
const signature = await generateSignature(timestamp);
return {
'X-Timestamp': timestamp.toString(),
'X-Signature': signature,
};
},
});运行时修改配置
// 使用 setParams 方法动态更新配置
request.setParams({
token: 'new-token', // 更新 token
baseUrl: 'https://new-api.example.com', // 更新 API 地址
headerProcessor: (header) => { // 更新头处理器
return {
'X-New-Header': 'new-value',
};
},
});API 文档
GET 请求
// 基础 GET 请求
const data = await request.get<ResponseType>('/users/list');
// 带查询参数
const data = await request.get<ResponseType>('/users/list', {
page: 1,
limit: 10,
});
// 自定义请求头
const data = await request.get<ResponseType>(
'/users/list',
{},
{
'X-Custom-Header': 'custom-value',
}
);POST 请求
// 基础 POST 请求
const result = await request.post<ResponseType>('/users', {
name: 'John',
email: '[email protected]',
});
// 自定义请求头
const result = await request.post<ResponseType>(
'/users',
{ name: 'John' },
{
'X-Custom-Header': 'custom-value',
}
);DELETE 请求
const result = await request.delete<ResponseType>('/users/1');PUT 请求
const result = await request.put<ResponseType>('/users/1', {
name: 'Updated Name',
});文件上传
// 基础文件上传
const result = await request.uploadFile<ResponseType>(
'/upload', // 上传 URL
'/path/to/file.jpg' // 文件路径
);
// 带 FormData 的文件上传
const result = await request.uploadFile<ResponseType>(
'/upload',
'/path/to/file.jpg',
{ // FormData 字段
description: 'My upload',
category: 'profile',
}
);
// 自定义文件字段名和请求头
const result = await request.uploadFile<ResponseType>(
'/upload',
'/path/to/file.jpg',
{ description: 'My upload' },
'avatar', // 文件字段名(默认 'file')
{ // 自定义请求头
'X-Upload-Token': 'upload-token',
}
);使用示例
示例 1:基础请求
import UniRequest from 'uniapp-request-sdk';
// 创建实例
const request = new UniRequest({
baseUrl: 'https://api.example.com',
});
// 使用
export async function getUsers() {
try {
const response = await request.get('/users');
console.log('用户列表:', response);
return response;
} catch (error) {
console.error('获取用户列表失败:', error);
}
}示例 2:Token 管理
import UniRequest from 'uniapp-request-sdk';
const request = new UniRequest({
baseUrl: 'https://api.example.com',
tokenEventName: 'getToken', // APP 端事件名
onErrorHandler: (error) => {
if (error.statusCode === 401) {
console.log('用户已登出');
// 跳转到登录页
uni.redirectTo({ url: '/pages/login/index' });
}
},
});
// 登录后更新 token
async function login(username: string, password: string) {
const { token } = await request.post('/auth/login', {
username,
password,
});
// 更新 token
request.setParams({ token });
}示例 3:动态请求头预处理
import UniRequest from 'uniapp-request-sdk';
import { generateSignature } from './crypto';
const request = new UniRequest({
baseUrl: 'https://api.example.com',
headerProcessor: async (header) => {
// 生成签名
const timestamp = Date.now();
const sign = await generateSignature({
timestamp,
token: header['Authorization'],
});
return {
'X-Timestamp': timestamp.toString(),
'X-Sign': sign,
};
},
});示例 4:文件上传
import UniRequest from 'uniapp-request-sdk';
const request = new UniRequest({
baseUrl: 'https://api.example.com',
uploadTimeout: 30000, // 文件上传超时 30 秒
});
// 选择文件并上传
async function selectAndUploadFile() {
uni.chooseImage({
count: 1,
success: async (res) => {
try {
const result = await request.uploadFile(
'/upload',
res.tempFilePaths[0],
{
// 附加信息
description: '头像',
category: 'avatar',
}
);
console.log('上传成功:', result);
} catch (error) {
console.error('上传失败:', error);
}
},
});
}示例 5:实时生成签名
import UniRequest from 'uniapp-request-sdk';
import CryptoJS from 'crypto-js';
// 创建带有实时签名的请求实例
const request = new UniRequest({
baseUrl: 'https://api.example.com',
headerProcessor: async (header) => {
// 每次请求都生成新签名
const timestamp = Date.now().toString();
const appKey = 'your-app-key';
// 根据 timestamp + appKey 生成签名
const sign = CryptoJS.SHA256(timestamp + appKey).toString();
return {
'X-Timestamp': timestamp,
'X-Sign': sign,
};
},
});响应格式
库默认假定服务器返回的响应格式如下:
{
errno: 0, // 错误码(0 表示成功)
data: { // 实际数据
// ... 业务数据
}
}其中:
errno === 0表示请求成功errno !== 0表示业务错误,会触发 reject
错误处理
自动处理的错误
| 错误类型 | 处理方式 | |---------|---------| | HTTP 403 | 自动获取新 token 并重试 | | HTTP 401 | 触发 logout 事件,通知 APP 返回登录页 | | 网络错误 | 自动重试(最多 maxRetryCount 次) | | 超时错误 | 自动重试(最多 maxRetryCount 次) | | 业务错误 | 直接 reject,业务处理 |
全局错误处理
const request = new UniRequest({
baseUrl: 'https://api.example.com',
onErrorHandler: (error) => {
// 这里处理所有请求错误
if (error.statusCode === 401) {
// 处理权限错误
console.log('权限不足');
} else if (error.errno !== undefined) {
// 处理业务错误
console.log('业务错误:', error.errno);
} else {
// 处理网络错误
console.log('网络错误:', error.errMsg);
}
},
});局部错误处理
try {
const data = await request.get('/users');
} catch (error) {
// 处理该请求的错误
console.error('请求失败:', error);
}平台差异处理
H5 平台
在 H5 平台上,请勿在请求头中设置 cookie,因为出于安全考虑,H5 中自动删除了手动设置的 cookie。
// ❌ 错误:H5 中不生效
const request = new UniRequest({
tokenHeader: 'cookie',
});
// ✅ 正确:使用 Authorization header
const request = new UniRequest({
tokenHeader: 'Authorization',
});小程序平台
在小程序中,token 需要通过 sendNativeEvent 从 APP 中获取。
const request = new UniRequest({
tokenEventName: 'getToken', // APP 端对应事件名
});配置参数详解
| 参数 | 类型 | 默认值 | 必填 | 说明 | |-----|------|-------|------|------| | baseUrl | string | - | ✅ | API 基础地址,支持代理路径 | | timeout | number | 10000 | - | 普通请求超时(毫秒) | | uploadTimeout | number | 5000 | - | 文件上传超时(毫秒) | | maxRetryCount | number | 3 | - | 失败重试次数 | | retryDelay | number | 3000 | - | 重试延迟时间(毫秒) | | header | object | - | - | 全局请求头 | | headerProcessor | function | - | - | 请求头预处理函数(新增) | | token | string | - | - | 初始 token | | tokenHeader | string | Authorization | - | token 所在 header 字段 | | tokenPrefix | string | 'Bearer ' | - | token 前缀 | | tokenEventName | string | getToken | - | APP 获取 token 的事件名 | | getTokenFun | function | - | - | 自定义 token 获取函数 | | username | string | - | - | 用户名(用于日志) | | onErrorHandler | function | console.error | - | 全局错误处理函数 |
请求头预处理器(新增功能)
请求头预处理器允许你在每次请求前动态生成或修改请求头,特别适合需要:
- 动态生成签名 - 基于时间戳生成请求签名
- 权限认证 - 根据用户权限添加特殊 header
- 版本标记 - 添加 API 版本或协议版本
- 业务数据 - 从业务接口获取数据并添加到 header
使用场景
场景 1:生成请求签名
const request = new UniRequest({
baseUrl: 'https://api.example.com',
headerProcessor: async (header) => {
const timestamp = Date.now();
const appSecret = 'your-app-secret';
// 生成签名
const sign = generateHMAC(timestamp + appSecret);
return {
'X-Timestamp': timestamp.toString(),
'X-Sign': sign,
};
},
});场景 2:从业务接口获取 header 数据
const request = new UniRequest({
baseUrl: 'https://api.example.com',
headerProcessor: async (header) => {
// 从缓存或接口获取业务数据
const businessData = await getBusinessDataFromStorage();
return {
'X-Business-Id': businessData.id,
'X-Business-Version': businessData.version,
};
},
});场景 3:同步生成请求 ID
import { v4 as uuidv4 } from 'uuid';
const request = new UniRequest({
baseUrl: 'https://api.example.com',
headerProcessor: (header) => {
// 同步生成请求 ID
return {
'X-Request-Id': uuidv4(),
};
},
});预处理器的执行时机
- 执行点:在设置 token 后、发送请求前
- 执行频率:每次请求都执行(包括重试)
- 异常处理:如果预处理器异常,请求会直接 reject,不会发送
预处理器的签名
// 同步预处理器
type HeaderProcessor = (header: Record<string, string>) => Record<string, string>;
// 异步预处理器
type HeaderProcessor = (header: Record<string, string>) => Promise<Record<string, string>>;最佳实践
✅ 推荐做法
- 在应用初始化时创建单一实例
// app.ts 或 main.ts
import UniRequest from 'uniapp-request-sdk';
export const request = new UniRequest({
baseUrl: process.env.VUE_APP_API_URL,
token: initialToken,
username: getUserName(),
});- 为不同的业务模块创建单独的方法
// api/user.ts
export function getUserList(page: number) {
return request.get('/users', { page });
}
export function createUser(data: User) {
return request.post('/users', data);
}- 统一处理所有错误
const request = new UniRequest({
onErrorHandler: (error) => {
// 上报到日志系统
logService.error(error);
// 根据错误类型显示提示
if (error.errno) {
showErrorToast(error.errno);
}
},
});❌ 避免做法
- 不要在每个请求时创建新实例
// ❌ 错误
async function getUsers() {
const request = new UniRequest({ baseUrl: '...' });
return request.get('/users');
}
// ✅ 正确
async function getUsers() {
return request.get('/users');
}- 不要在预处理器中执行耗时操作
// ❌ 错误:每次请求都会等待 5 秒
headerProcessor: async () => {
await sleep(5000); // 不必要的延迟
return {};
}
// ✅ 正确:提前准备数据
headerProcessor: async () => {
const cachedData = await cache.get('businessData');
return { 'X-Business-Id': cachedData.id };
}- 不要在头预处理器中设置 cookie(H5 平台)
// ❌ 错误:H5 中不生效
headerProcessor: async () => {
return { 'cookie': 'sessionid=xxx' };
}
// ✅ 正确:使用其他方式
headerProcessor: async () => {
return { 'X-Session-Id': 'xxx' };
}企业级实战案例
真实案例:OA 流程审批系统
以下是基于 getui-oa-process-uniapp 项目的实战案例,展示如何在真实企业系统中使用本库。
项目背景
一个 uni-app 小程序 OA 系统,用于流程审批、预算查询等企业应用。该项目使用本库作为核心请求库。
1. 请求实例初始化
// src/requests/http/index.ts
import UniRequest from 'uniapp-request-sdk';
// 显示 Toast 提示(处理 iOS 冷启动吞掉 toast 的问题)
function showToast(title: string) {
setTimeout(() => {
uni.showToast({
title,
icon: 'none',
});
}, 500);
}
// 全局错误处理函数
function onErrorHandler(resData: any) {
if (resData?.data?.errno === 2002) {
// 业务逻辑:用户无权限
showToast(resData.data.errmsg ?? '用户无权限');
setTimeout(() => {
// 关闭小程序
uni.sendNativeEvent('closeMiniProgram', {}, () => {});
}, 1000);
} else if (resData?.statusCode === 403) {
// 业务逻辑:token 失效或权限不足
if (uni.getStorageSync('isDebug')) {
// 调试模式跳转到 mock 页面
uni.navigateTo({
url: '/pages/mock/index',
});
} else {
// 生产环境提示重新进入
showToast('用户登录信息失效,请退出后重试');
}
} else {
// 默认错误处理
showToast(resData?.data?.errmsg ?? '网络异常,请退出重新进入');
}
}
// 主请求实例(用于常规业务请求)
export const requestInstance = new UniRequest({
timeout: 4000, // 请求超时 4 秒
maxRetryCount: 2, // 重试 2 次
uploadTimeout: 1000 * 60 * 2, // 文件上传超时 2 分钟
onErrorHandler, // 使用统一错误处理
});
// AI 请求实例(用于 AI 接口请求)
export const aiRequestInstance = new UniRequest({
uploadTimeout: 1000 * 20, // 上传超时 20 秒
maxRetryCount: 0, // 不重试(AI 接口可能耗时较长)
onErrorHandler,
});2. 创建业务 API 层
// src/requests/severs/process.ts
import { requestInstance, aiRequestInstance } from '../http';
export const ProcessServes = {
// 获取流程详情
getProcessDetailes: (payload: { businessId: string }) =>
requestInstance.post<{ data: any }>('/process/application/business/detail', payload),
// 标记流程已读
readProcessMessage: (payload: { businessId: string }) =>
requestInstance.post<{ data: any }>('/process/application/business/readProcessMessage', payload),
// 查询申请人职位
getById: (payload: { id: string }) =>
requestInstance.post('/usercenter/oaUser/getById', payload),
// 撤回流程
processRecall: (payload: { businessId: string; processCategory: string; comment: string }) =>
requestInstance.post('/process/application/process/recall', payload),
// 获取流程事项列表
getProcessMatterList: (payload: any) =>
requestInstance.post<{ data: any[] }>('/process/application/matter/list', payload),
// 获取事项信息
getMatterInfo: (payload: any) =>
requestInstance.post('/process/application/matter/matterInfo', payload),
// 流程审批
processApprove: (payload: {
businessId: string;
processCategory: string;
ccUser: string[];
comment: string;
pass: boolean;
attachment: Array<{ name: string; url: string }>;
}) =>
requestInstance.post('/process/application/process/approve', payload),
// 保存并审批
processSaveAndApprove: (payload: any) =>
requestInstance.post('/process/application/process/saveAndApprove', payload),
// 保存流程
processSave: (payload: {
applicantDepartmentId: string;
processDepartmentId: string;
applicantId: string;
businessData: Record<string, any>;
processId: string;
sponsorId: string;
sponsorDepartmentId: string;
processCategory: string;
businessId?: string;
}) =>
requestInstance.post('/process/application/business/save', payload),
// 申请流程
processApply: (payload: { businessId: string; ccUser: any[]; processCategory: string }) =>
requestInstance.post('/process/application/process/apply', payload),
// 获取流程评论
getProcessComments: (payload: { businessId: string }) =>
requestInstance.post('/process/application/business/commentList', payload),
// 获取流程表单信息
getProcessFormInfo: (processCategory: string) =>
requestInstance.post('/process/application/business/formInfo', { processCategory }),
// 获取流程申请人列表
getApplyUsers: (payload: { processCategory: string; userId: string }) =>
requestInstance.post('/flow/assistant/getApplyUsers', {
business: payload.processCategory,
id: payload.userId,
}),
// 检查流程版本号
checkVersion: (payload: { processCategory: string; processId: string }) =>
requestInstance.post('/process/application/process/checkVersion', payload),
// 获取预算包使用进度
getBudgetInventoryProgress: (payload: {
processCategory: string;
departmentId: string;
subjects: string[];
businessLineUuids: string[];
}) =>
requestInstance.post('/financial/budgetInventory/budgetInventoryProgress', payload),
// 获取业务线列表
getBusinessLineList: () =>
requestInstance.post('financial/businessLine/businessLineList'),
// 获取合同详情
getContractDetail: (payload: { contractNumber: string; businessId: string }) =>
requestInstance.post('/financial/contract/processContractDetail', payload),
// 获取流程文件列表
getBusinessProcessFileList: (businessId: string) =>
requestInstance.post<any[]>('/process/application/business/businessProcessFileList', {
businessId,
fileTypes: ['pdf', 'docx', 'doc'],
}),
// 上传音频文件并转换为流程数据(使用 AI 实例,支持长超时)
uploadAudioToProcessData: ({ filePath, data }: any) => {
return aiRequestInstance.uploadFile('/ds/trans/process/', filePath, data);
},
};3. 公共 API 服务
// src/requests/severs/common.ts
import { requestInstance } from '../http';
export const CommmonServes = {
// 获取用户信息
getUserInfo: async (payload: { userIds: string[] }) => {
const data = await requestInstance.post<any>('/usercenter/oaUser/getUserInfoByIds', payload);
return data?.data;
},
// 获取部门信息
getDepartmentIdInfo: async (payload: { departmentId: string; type?: string }) => {
const data = await requestInstance.post<any>('/usercenter/oaDepartment/getParentById', payload);
return data?.data ?? data;
},
// GHR 认证
ghrAuthentication: async (payload: {}) => {
const data = await requestInstance.post<any>('/usercenter/ghr/authentication', payload);
return data?.data ?? data;
},
// 查询关联客户
getAssociatedCustomers: async (payload: {}) => {
const data = await requestInstance.post<any>('/gcrm/third/relevantCust/checkContains', payload);
return data?.data ?? data;
},
};4. 在 Vue 组件中使用
// 在组件中使用 API 服务
import { ProcessServes } from '@/requests/severs/process';
export default {
data() {
return {
processData: null,
loading: false,
};
},
async mounted() {
await this.loadProcessDetail();
},
methods: {
async loadProcessDetail() {
this.loading = true;
try {
// 使用 API 服务获取数据
const response = await ProcessServes.getProcessDetailes({
businessId: this.businessId,
});
this.processData = response;
} catch (error) {
// 错误处理已由全局 onErrorHandler 处理
console.error('加载流程详情失败:', error);
} finally {
this.loading = false;
}
},
async submitApproval() {
try {
// 提交审批
await ProcessServes.processApprove({
businessId: this.businessId,
processCategory: this.processCategory,
ccUser: this.ccUsers,
comment: this.comment,
pass: true,
attachment: this.attachments,
});
uni.showToast({
title: '审批成功',
icon: 'success',
});
} catch (error) {
console.error('审批失败:', error);
}
},
async uploadAudioFile() {
try {
// 选择音频文件
const res = await uni.chooseFile({
type: 'file',
accept: 'audio/*',
});
// 上传音频并转换
const result = await ProcessServes.uploadAudioToProcessData({
filePath: res.tempFilePaths[0],
data: {
processCategory: this.processCategory,
},
});
// 使用 AI 转换后的数据
this.processFormData = result;
} catch (error) {
console.error('上传音频失败:', error);
}
},
},
};5. 关键设计要点
多实例策略
项目使用两个不同的请求实例:
requestInstance: 用于常规业务请求
- 超时时间:4 秒
- 重试次数:2 次
- 适合快速响应的 API
aiRequestInstance: 用于 AI 接口请求
- 超时时间:20 秒(足够长时间处理 AI 计算)
- 重试次数:0 次(避免重复处理)
- 适合长时间处理的 API
统一错误处理
所有错误都通过 onErrorHandler 函数处理:
- 业务错误(errno = 2002):显示错误信息,关闭小程序
- 权限错误(statusCode = 403):引导用户重新登录
- 其他错误:显示通用错误信息
API 层封装
将所有 API 调用封装在专门的服务文件中:
- 统一的错误处理
- 类型安全的请求和响应
- 易于测试和维护
- 便于在多个组件间复用
6. 实战总结
这个案例展示了如何在生产环境中使用本库:
✅ 多实例管理 - 根据需求创建不同配置的实例 ✅ 统一错误处理 - 全局处理所有错误,避免重复代码 ✅ API 层封装 - 将请求逻辑与业务逻辑分离 ✅ 类型安全 - 充分利用 TypeScript 的泛型机制 ✅ 易于扩展 - 新 API 添加无需修改现有代码
Q: 如何处理跨域问题?
A: 在 baseUrl 中包含代理路径,让后端或开发服务器代理请求。
const request = new UniRequest({
baseUrl: 'https://localhost:8080/api/proxy/', // 包含代理路径
});Q: 如何上传多个文件?
A: 多个文件需要多次调用 uploadFile,或者一个文件一个请求。
// 方式 1:循环上传
for (const filePath of filePaths) {
await request.uploadFile('/upload', filePath);
}
// 方式 2:并行上传(不推荐,可能导致超时)
await Promise.all(
filePaths.map(filePath => request.uploadFile('/upload', filePath))
);Q: 如何处理 token 过期?
A: 库会自动在收到 403 状态码时获取新 token 并重试。如需自定义逻辑:
const request = new UniRequest({
getTokenFun: async () => {
// 自定义获取 token 的逻辑
const token = await refreshTokenFromServer();
return token;
},
});Q: 为什么第一次请求返回 403?
A: 可能是因为初始化时没有设置 token,或 token 已过期。库会在收到 403 时自动获取新 token 并重试。
Q: 如何禁用自动重试?
A: 设置 maxRetryCount 为 0。
const request = new UniRequest({
maxRetryCount: 0, // 禁用重试
});Q: headerProcessor 每次都会执行吗?
A: 是的,包括重试时都会执行。这是为了确保每次请求都获得最新的 header 值(如动态生成的签名)。
版本历史
v1.4.11
- ✨ 新增 请求头预处理器功能 (
headerProcessor) - 🐛 修复 原有逻辑兼容性
- 📚 文档 完整的 README 文档
v1.4.10 及之前
- 基础请求功能
- Token 管理
- 自动重试
- 错误处理
许可证
ISC License
作者
yaojun
贡献
欢迎提交 Issue 和 Pull Request!
获取帮助
如有问题,请:
- 查看 常见问题
- 提交 GitHub Issue
- 联系 [email protected]
