API_GUIDE.md 10.0 KB

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 工具

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 服务

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 封装的服务

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);

方式四:动态创建服务

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

// 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<Department> {
  constructor() {
    super("/sys/sysDepart"); // 基础路径
  }

  // 自定义方法
  queryTreeList() {
    return http.get<Department[]>("/sys/sysDepart/queryTreeList");
  }

  searchBy(keyword: string) {
    return http.get<Department[]>("/sys/sysDepart/searchBy", { keyword });
  }
}

export const departmentService = new DepartmentService();
export default departmentService;

2. 在 services/index.ts 中导出

export { default as departmentService } from "./modules/department";
export type { Department } from "./modules/department";

高级用法

1. 自定义请求配置

// 跳过全局错误处理
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 管理

import { TokenManager } from "@/services";

// 获取 Token
const token = TokenManager.getToken();

// 设置 Token
TokenManager.setToken("your-token");

// 删除 Token
TokenManager.removeToken();

3. 文件操作

// 单文件上传
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. 分页查询

import type { PageParams, PageResult } from "@/services";

const queryUsers = async () => {
  const params: Partial<PageParams> = {
    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<User> = 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 标准响应格式

{
  "success": true,
  "message": "操作成功",
  "code": 200,
  "result": {
    // 实际数据
  },
  "timestamp": 1638888888888
}

分页响应格式

{
  "success": true,
  "message": "查询成功",
  "code": 200,
  "result": {
    "records": [...],
    "total": 100,
    "size": 10,
    "current": 1,
    "pages": 10
  }
}

错误处理

全局错误处理

系统会自动处理以下错误:

  • 401 Token 过期:弹窗提示并跳转登录页
  • 403 权限不足:弹窗提示
  • 500 服务器错误:弹窗提示
  • 其他错误:弹窗显示错误信息

自定义错误处理

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. 类型定义

// 定义接口类型
export interface User {
  id: string;
  username: string;
  realname: string;
  // ...
}

// 使用泛型
class UserService extends BaseService<User> {
  // TypeScript 会自动推断返回类型
}

3. 错误处理策略

// 需要用户感知的错误:使用全局错误处理
await userService.delete(id);

// 不需要用户感知的错误:跳过全局错误处理
try {
  await http.get("/api/check", { username }, { skipErrorHandler: true });
  // 用户名可用
} catch {
  // 用户名已存在
}

4. Loading 状态管理

const [loading, setLoading] = useState(false);

const handleSubmit = async () => {
  try {
    setLoading(true);
    await userService.add(formData);
    // 成功提示
  } catch (error) {
    // 错误已被全局处理
  } finally {
    setLoading(false);
  }
};

环境配置

.env 文件中配置 API 基础地址:

# 开发环境
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. 如何自定义超时时间?

const data = await http.get("/api/data", {}, { timeout: 60000 });

4. 如何处理文件流响应?

系统会自动检测 content-type,如果是 application/octet-stream 会返回 Blob。

5. 如何添加请求日志?

src/utils/request.tsrequestInterceptor 中添加:

console.log("请求:", config.url, config);

迁移指南

从 Axios 迁移

// 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

    // 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);