# API 请求封装使用指南 本项目参考 JeecgBoot 前端的请求封装方案,提供了完整的 HTTP 请求工具和服务层封装。 ## 目录结构 ``` src/ ├── utils/ │ └── request.ts # 核心请求工具 ├── services/ │ ├── api.ts # 系统 API │ ├── BaseService.ts # 基础服务类 │ ├── index.ts # 统一导出 │ └── modules/ # 业务模块服务 │ ├── user.ts │ ├── role.ts │ └── content.ts └── examples/ └── ApiUsage.tsx # 使用示例 ``` ## 核心特性 ### 1. 请求拦截器 - 自动添加 Token - GET 请求自动添加时间戳防缓存 - 统一请求头配置 ### 2. 响应拦截器 - 自动处理 JeecgBoot 标准响应格式 - 支持文件下载 - 统一错误处理 ### 3. 错误处理 - Token 过期自动跳转登录 - 权限不足提示 - 服务器错误提示 - 支持跳过全局错误处理 ### 4. 超时控制 - 默认 30 秒超时 - 可自定义超时时间 ## 快速开始 ### 方式一:直接使用 http 工具 ```typescript import http from "@/utils/request"; // GET 请求 const users = await http.get("/sys/user/list", { pageNo: 1, pageSize: 10 }); // POST 请求 const result = await http.post("/sys/user/add", { username: "test" }); // PUT 请求 await http.put("/sys/user/edit", { id: "1", username: "updated" }); // DELETE 请求 await http.delete("/sys/user/delete", { id: "1" }); // 文件上传 const formData = new FormData(); formData.append("file", file); const uploadResult = await http.upload("/sys/common/upload", formData); // 文件下载 await http.download("/sys/user/exportXls", "用户列表.xls", { status: 1 }); ``` ### 方式二:使用预定义的 API 服务 ```typescript import { sysApi, dictApi, uploadApi } from "@/services"; // 系统 API const menus = await sysApi.getUserMenus(); const permissions = await sysApi.getUserPermissions(); await sysApi.login({ username: "admin", password: "123456" }); // 字典 API const dictItems = await dictApi.getDictItems("sex"); // 上传 API const result = await uploadApi.upload(file); ``` ### 方式三:使用 BaseService 封装的服务 ```typescript import { userService, roleService, contentService } from "@/services"; // 分页查询 const userList = await userService.list({ pageNo: 1, pageSize: 10, column: "createTime", order: "desc", username: "admin", // 查询条件 }); // CRUD 操作 const user = await userService.getById("1"); await userService.add({ username: "newuser" }); await userService.edit({ id: "1", username: "updated" }); await userService.delete("1"); await userService.deleteBatch(["1", "2", "3"]); // 导入导出 await userService.exportXls({ status: 1 }, "用户列表.xls"); await userService.importExcel(file); ``` ### 方式四:动态创建服务 ```typescript import { createService } from "@/services"; // 为任意模块创建服务 const customService = createService("/api/custom"); const list = await customService.list({ pageNo: 1, pageSize: 10 }); const item = await customService.getById("1"); await customService.add({ name: "test" }); ``` ## 创建自定义服务 ### 1. 继承 BaseService ```typescript // src/services/modules/department.ts import { BaseService } from "../BaseService"; import http from "../../utils/request"; export interface Department { id: string; departName: string; parentId?: string; orgCode?: string; } class DepartmentService extends BaseService { constructor() { super("/sys/sysDepart"); // 基础路径 } // 自定义方法 queryTreeList() { return http.get("/sys/sysDepart/queryTreeList"); } searchBy(keyword: string) { return http.get("/sys/sysDepart/searchBy", { keyword }); } } export const departmentService = new DepartmentService(); export default departmentService; ``` ### 2. 在 services/index.ts 中导出 ```typescript export { default as departmentService } from "./modules/department"; export type { Department } from "./modules/department"; ``` ## 高级用法 ### 1. 自定义请求配置 ```typescript // 跳过全局错误处理 const data = await http.get( "/api/check", { username: "test" }, { skipErrorHandler: true } ); // 自定义超时时间 const data = await http.post( "/api/long-task", { data: "..." }, { timeout: 60000 } // 60秒 ); // 自定义请求头 const data = await http.get( "/api/data", {}, { headers: { "Custom-Header": "value", }, } ); ``` ### 2. Token 管理 ```typescript import { TokenManager } from "@/services"; // 获取 Token const token = TokenManager.getToken(); // 设置 Token TokenManager.setToken("your-token"); // 删除 Token TokenManager.removeToken(); ``` ### 3. 文件操作 ```typescript // 单文件上传 import { uploadApi } from "@/services"; const handleUpload = async (file: File) => { const result = await uploadApi.upload(file); console.log("文件地址:", result.url); }; // 批量上传 const handleBatchUpload = async (files: File[]) => { const result = await uploadApi.uploadBatch(files); console.log("文件地址列表:", result.urls); }; // 导出 Excel await userService.exportXls( { status: 1, createTime_begin: "2024-01-01", createTime_end: "2024-12-31", }, "用户列表.xls" ); // 导入 Excel const handleImport = async (file: File) => { const result = await userService.importExcel(file); console.log("导入结果:", result); }; ``` ### 4. 分页查询 ```typescript import type { PageParams, PageResult } from "@/services"; const queryUsers = async () => { const params: Partial = { pageNo: 1, pageSize: 10, column: "createTime", // 排序字段 order: "desc", // 排序方式 // 查询条件 username: "admin", status: 1, createTime_begin: "2024-01-01", createTime_end: "2024-12-31", }; const result: PageResult = await userService.list(params); console.log("总记录数:", result.total); console.log("总页数:", result.pages); console.log("当前页:", result.current); console.log("每页大小:", result.size); console.log("数据列表:", result.records); }; ``` ## 响应格式 ### JeecgBoot 标准响应格式 ```json { "success": true, "message": "操作成功", "code": 200, "result": { // 实际数据 }, "timestamp": 1638888888888 } ``` ### 分页响应格式 ```json { "success": true, "message": "查询成功", "code": 200, "result": { "records": [...], "total": 100, "size": 10, "current": 1, "pages": 10 } } ``` ## 错误处理 ### 全局错误处理 系统会自动处理以下错误: - **401 Token 过期**:弹窗提示并跳转登录页 - **403 权限不足**:弹窗提示 - **500 服务器错误**:弹窗提示 - **其他错误**:弹窗显示错误信息 ### 自定义错误处理 ```typescript try { const data = await http.get("/api/data", {}, { skipErrorHandler: true }); // 处理成功 } catch (error) { // 自定义错误处理 console.error("请求失败:", error.message); // 显示自定义提示 alert("操作失败,请重试"); } ``` ## 最佳实践 ### 1. 服务层组织 ``` services/ ├── api.ts # 系统级 API(登录、权限等) ├── BaseService.ts # 基础服务类 ├── index.ts # 统一导出 └── modules/ # 按业务模块组织 ├── user.ts # 用户模块 ├── role.ts # 角色模块 ├── department.ts # 部门模块 └── content.ts # 内容模块 ``` ### 2. 类型定义 ```typescript // 定义接口类型 export interface User { id: string; username: string; realname: string; // ... } // 使用泛型 class UserService extends BaseService { // TypeScript 会自动推断返回类型 } ``` ### 3. 错误处理策略 ```typescript // 需要用户感知的错误:使用全局错误处理 await userService.delete(id); // 不需要用户感知的错误:跳过全局错误处理 try { await http.get("/api/check", { username }, { skipErrorHandler: true }); // 用户名可用 } catch { // 用户名已存在 } ``` ### 4. Loading 状态管理 ```typescript const [loading, setLoading] = useState(false); const handleSubmit = async () => { try { setLoading(true); await userService.add(formData); // 成功提示 } catch (error) { // 错误已被全局处理 } finally { setLoading(false); } }; ``` ## 环境配置 在 `.env` 文件中配置 API 基础地址: ```bash # 开发环境 VITE_API_BASE_URL=http://localhost:8080 # 生产环境 VITE_API_BASE_URL=https://api.example.com ``` ## 常见问题 ### 1. Token 如何存储? Token 存储在 localStorage 中,key 为 `ACCESS_TOKEN`。 ### 2. 如何修改 Token Header 名称? 在 `src/utils/request.ts` 中修改 `TokenManager.TOKEN_HEADER`。 ### 3. 如何自定义超时时间? ```typescript const data = await http.get("/api/data", {}, { timeout: 60000 }); ``` ### 4. 如何处理文件流响应? 系统会自动检测 `content-type`,如果是 `application/octet-stream` 会返回 Blob。 ### 5. 如何添加请求日志? 在 `src/utils/request.ts` 的 `requestInterceptor` 中添加: ```typescript console.log("请求:", config.url, config); ``` ## 迁移指南 ### 从 Axios 迁移 ```typescript // Axios axios.get("/api/users", { params: { id: 1 } }); axios.post("/api/users", { username: "test" }); // 本项目 http.get("/api/users", { id: 1 }); http.post("/api/users", { username: "test" }); ``` ### 从 JeecgBoot Vue 版本迁移 大部分 API 保持一致,只需要: 1. 将 `this.$http` 改为 `http` 2. 将 `this.url.xxx` 改为服务方法调用 3. 使用 async/await 替代 Promise.then ```typescript // Vue 版本 this.$http.get(this.url.list, { params: query }).then((res) => { this.dataSource = res.result.records; }); // React 版本 const result = await userService.list(query); setDataSource(result.records); ```