Copilot新模型GPT-5.1太强了!自动生成完美Axios封装,同事都看傻了

November 13, 2025
OpenAI's GPT-5.1, GPT-5.1-Codex and GPT-5.1-Codex-Mini are now in public preview for GitHub Copilot

打造极致优雅的Axios封装,让接口请求回归简单...

GPT-5.1赋能:让Axios封装进入AI驱动的全新时代...

走起,直接上代码....

js 复制代码
import axios from "axios";
import qs from "qs";
import { Message } from "element-gui";

// ==================== 配置常量 ====================
const REQUEST_CONFIG = {
  BASE_URL: process.env.VUE_APP_BASE_API,
  TIMEOUT: 60000,
  LOGIN_PATH: "/login",
  DEFAULT_ERROR_MESSAGE: "请求失败",
  ENABLE_LOG: process.env.NODE_ENV === "development",
};

// HTTP 状态码错误消息映射
const HTTP_ERROR_MESSAGES = {
  401: "未授权,请重新登录",
  403: "没有权限访问",
  404: "请求资源不存在",
  500: "服务器错误",
  502: "网关错误",
  503: "服务不可用",
  504: "网关超时",
};

// ==================== 请求管理 ====================
class RequestManager {
  constructor() {
    this.pendingRequests = new Map();
  }

  // 生成请求唯一标识
  generateKey(config) {
    const { method, url, params, data } = config;
    const serializedParams = params
      ? qs.stringify(params, { sort: (a, b) => a.localeCompare(b) })
      : "";
    const serializedData =
      data && !(data instanceof FormData)
        ? qs.stringify(data, { sort: (a, b) => a.localeCompare(b) })
        : "";

    return `${method}:${url}:${serializedParams}:${serializedData}`;
  }

  // 添加请求
  addRequest(config) {
    const requestKey = this.generateKey(config);

    // 取消相同的进行中请求
    if (this.pendingRequests.has(requestKey)) {
      const cancel = this.pendingRequests.get(requestKey);
      cancel("取消重复请求");
    }

    // 为新请求创建取消令牌
    config.cancelToken = new axios.CancelToken((cancel) => {
      this.pendingRequests.set(requestKey, cancel);
    });

    config.requestKey = requestKey;
    return config;
  }

  // 移除请求
  removeRequest(config) {
    if (config?.requestKey) {
      this.pendingRequests.delete(config.requestKey);
    }
  }

  // 清空所有请求
  clearAll() {
    this.pendingRequests.forEach((cancel) => cancel("清空所有请求"));
    this.pendingRequests.clear();
  }
}

// ==================== 工具函数 ====================
class RequestUtils {
  // 日志打印
  static log(message, data) {
    if (REQUEST_CONFIG.ENABLE_LOG) {
      console.log(`[Request] ${message}`, data);
    }
  }

  static error(message, data) {
    if (REQUEST_CONFIG.ENABLE_LOG) {
      console.error(`[Request] ${message}`, data);
    }
  }

  // Token 管理
  static getToken() {
    return localStorage.getItem("token");
  }

  static removeToken() {
    localStorage.removeItem("token");
  }

  static redirectToLogin() {
    window.location.href = REQUEST_CONFIG.LOGIN_PATH;
  }

  // Content-Type 处理
  static isFormData(data) {
    return data instanceof FormData;
  }

  static isFormUrlEncoded(headers) {
    return headers?.["Content-Type"] === "application/x-www-form-urlencoded";
  }

  static isBlobResponse(config) {
    return config.responseType === "blob";
  }
}

// ==================== Axios 实例 ====================
const requestManager = new RequestManager();

const service = axios.create({
  baseURL: REQUEST_CONFIG.BASE_URL,
  timeout: REQUEST_CONFIG.TIMEOUT,
  withCredentials: true,
  headers: {
    "Content-Type": "application/json",
  },
});

// ==================== 请求拦截器 ====================
service.interceptors.request.use(
  (config) => {
    // 处理重复请求取消
    if (config.cancelDuplicate !== false) {
      requestManager.addRequest(config);
    }

    // 处理 Authorization
    if (!config.headers?.Authorization) {
      const needToken = config.needToken !== false;
      if (needToken) {
        const token = RequestUtils.getToken();
        if (token) {
          config.headers = config.headers || {};
          config.headers["Authorization"] = `Bearer ${token}`;
          RequestUtils.log("添加 Token", { url: config.url });
        }
      }
    } else {
      RequestUtils.log("使用自定义 Authorization", { url: config.url });
    }

    // 处理 FormData
    if (RequestUtils.isFormData(config.data)) {
      delete config.headers["Content-Type"];
      RequestUtils.log("检测到 FormData", { url: config.url });
    }
    // 处理 application/x-www-form-urlencoded
    else if (RequestUtils.isFormUrlEncoded(config.headers)) {
      if (config.data && typeof config.data === "object") {
        config.data = qs.stringify(config.data);
        RequestUtils.log("序列化 Form 数据", { url: config.url });
      }
    }

    RequestUtils.log("发送请求", {
      method: config.method,
      url: config.url,
      params: config.params,
      data: config.data,
    });

    return config;
  },
  (error) => {
    RequestUtils.error("请求配置错误", error);
    if (error.config?.showError !== false) {
      Message.error("请求发送失败: " + (error.message || "未知错误"));
    }
    return Promise.reject(error);
  }
);

// ==================== 响应拦截器 ====================
service.interceptors.response.use(
  (response) => {
    // 清理请求记录
    requestManager.removeRequest(response.config);

    RequestUtils.log("收到响应", {
      url: response.config.url,
      status: response.status,
      data: response.data,
    });

    // 处理文件下载响应
    if (RequestUtils.isBlobResponse(response.config)) {
      return response;
    }

    const res = response.data;

    // 业务成功
    if (res.code === 0 && response.status === 200) {
      return res;
    }

    // 业务失败
    if (res.code !== 0) {
      // 未授权处理
      if (res.code === 401) {
        RequestUtils.removeToken();
        RequestUtils.redirectToLogin();
      }

      const errorMessage = res.message || REQUEST_CONFIG.DEFAULT_ERROR_MESSAGE;
      if (response.config?.showError !== false) {
        Message.error(errorMessage);
      }

      return Promise.reject(new Error(errorMessage));
    }

    return res;
  },
  (error) => {
    // 清理请求记录
    requestManager.removeRequest(error.config);

    // 处理取消请求
    if (axios.isCancel(error)) {
      RequestUtils.log("请求被取消", error.message);
      return Promise.reject(error);
    }

    RequestUtils.error("响应错误", error);

    let errorMessage = REQUEST_CONFIG.DEFAULT_ERROR_MESSAGE;

    // HTTP 错误
    if (error.response) {
      const status = error.response.status;
      errorMessage =
        HTTP_ERROR_MESSAGES[status] || `未知错误: ${status}`;

      // 401 处理
      if (status === 401) {
        RequestUtils.removeToken();
        RequestUtils.redirectToLogin();
      }
    }
    // 网络错误
    else if (error.request) {
      errorMessage =
        error.code === "ECONNABORTED" ? "请求超时" : "网络连接异常";
    }
    // 其他错误
    else {
      errorMessage = error.message || "请求配置错误";
    }

    if (error.config?.showError !== false) {
      Message.error(errorMessage);
    }

    return Promise.reject(error);
  }
);

// ==================== 文件下载处理 ====================
function handleDownloadResponse(response, fileName, showError = true) {
  const contentType = response.headers["content-type"] || "";

  // 检查是否为 JSON 错误响应
  if (contentType.includes("application/json")) {
    if (showError) {
      Message.error("暂无数据,无法导出!");
    }
    return;
  }

  // 从响应头获取文件名
  const disposition = response.headers["content-disposition"] || "";
  const headerFileName = disposition
    ? decodeURIComponent(
        disposition.split("filename*=UTF-8''")[1] ||
          disposition.split("filename=")[1] ||
          ""
      ).replace(/['"]/g, "")
    : "";

  // 创建下载链接
  const blob = new Blob([response.data]);
  const link = document.createElement("a");
  link.href = window.URL.createObjectURL(blob);
  link.download =
    fileName ||
    headerFileName ||
    `download_${Date.now()}.xlsx`;
  link.click();

  // 清理
  setTimeout(() => {
    window.URL.revokeObjectURL(link.href);
  }, 100);

  return response;
}

// ==================== 封装请求方法 ====================
const request = {
  /**
   * GET 请求
   * @param {string} url - 请求地址
   * @param {object} params - URL 参数
   * @param {object} config - Axios 配置
   */
  get(url, params = {}, config = {}) {
    return service({
      method: "get",
      url,
      params,
      ...config,
    });
  },

  /**
   * POST 请求
   * @param {string} url - 请求地址
   * @param {object} data - 请求体数据
   * @param {object} config - Axios 配置
   */
  post(url, data = {}, config = {}) {
    return service({
      method: "post",
      url,
      data,
      ...config,
    });
  },

  /**
   * PUT 请求
   */
  put(url, data = {}, config = {}) {
    return service({
      method: "put",
      url,
      data,
      ...config,
    });
  },

  /**
   * DELETE 请求
   */
  delete(url, params = {}, config = {}) {
    return service({
      method: "delete",
      url,
      params,
      ...config,
    });
  },

  /**
   * PATCH 请求
   */
  patch(url, data = {}, config = {}) {
    return service({
      method: "patch",
      url,
      data,
      ...config,
    });
  },

  /**
   * 文件上传
   * @param {string} url - 上传地址
   * @param {FormData} formData - 表单数据
   * @param {object} config - 配置
   */
  upload(url, formData, config = {}) {
    return service({
      method: "post",
      url,
      data: formData,
      headers: {
        "Content-Type": "multipart/form-data",
        ...(config.headers || {}),
      },
      cancelDuplicate: false, // 上传不取消重复请求
      ...config,
    });
  },

  /**
   * 文件下载
   * @param {string} url - 下载地址
   * @param {object} data - 请求数据
   * @param {string} fileName - 文件名
   * @param {object} config - 配置
   */
  download(url, data = {}, fileName = "", config = {}) {
    return service({
      method: "post",
      url,
      data,
      responseType: "blob",
      cancelDuplicate: false, // 下载不取消重复请求
      ...config,
    })
      .then((response) =>
        handleDownloadResponse(response, fileName, config.showError !== false)
      )
      .catch((error) => {
        if (config.showError !== false && !axios.isCancel(error)) {
          Message.error("下载失败!");
        }
        return Promise.reject(error);
      });
  },

  /**
   * 表单提交 (application/x-www-form-urlencoded)
   */
  form(url, data = {}, config = {}) {
    return service({
      method: "post",
      url,
      data,
      headers: {
        "Content-Type": "application/x-www-form-urlencoded",
        ...(config.headers || {}),
      },
      ...config,
    });
  },

  /**
   * 自定义请求
   * @param {string} method - 请求方法
   * @param {string} url - 请求地址
   * @param {object} data - 请求体数据
   * @param {object} params - URL 参数
   * @param {object} config - 配置
   */
  custom(method, url, data = {}, params = {}, config = {}) {
    return service({
      method,
      url,
      data,
      params,
      ...config,
    });
  },

  /**
   * 取消所有进行中的请求
   */
  cancelAll() {
    requestManager.clearAll();
  },
};

export default request;
export { service, requestManager };

主要改进点

1. 代码结构更清晰

  • 使用类封装相关功能,职责划分明确
  • 配置集中管理,便于维护
  • 代码分层更合理

2. 安全性提升

  • 生产环境不输出敏感日志
  • Token 管理更规范
  • 错误信息标准化

3. 可维护性增强

  • JSDoc 注释完善
  • 错误消息统一管理
  • 配置可扩展性强

4. 功能更完善

  • 支持手动取消所有请求
  • 文件下载处理更健壮
  • 默认 Content-Type 设置

5. 调试更友好

  • 开发环境详细日志
  • 请求/响应完整追踪
  • 错误定位更精准

使用示例

基础使用

js 复制代码
import request from '@/utils/request'

// ==================== GET 请求 ====================
export function getUserList(params) {
  return request.get('/api/users', params)
}

// 自定义配置
export function getUserListNoAuth(params) {
  return request.get('/api/users', params, {
    needToken: false,  // 不需要 token
    showError: false,  // 不显示错误提示
  })
}

// ==================== POST 请求 ====================
export function createUser(data) {
  return request.post('/api/users', data)
}

// 禁用重复请求取消
export function sendOtp(data) {
  return request.post('/api/send-otp', data, {
    cancelDuplicate: false,  // 允许重复发送
  })
}

// ==================== PUT/DELETE 请求 ====================
export function updateUser(id, data) {
  return request.put(`/api/users/${id}`, data)
}

export function deleteUser(id) {
  return request.delete(`/api/users/${id}`)
}

// ==================== 表单提交 ====================
export function login(data) {
  return request.form('/api/login', data)
}

// ==================== 文件上传 ====================
export function uploadAvatar(file) {
  const formData = new FormData()
  formData.append('file', file)
  formData.append('type', 'avatar')
  
  return request.upload('/api/upload', formData, {
    onUploadProgress: (progressEvent) => {
      const percent = Math.round((progressEvent.loaded * 100) / progressEvent.total)
      console.log('上传进度:', percent + '%')
    }
  })
}

// ==================== 文件下载 ====================
export function exportUsers(params) {
  return request.download('/api/users/export', params, '用户列表')
}

// 自定义文件名
export function exportReport(params) {
  const fileName = `报表_${new Date().toLocaleDateString()}`
  return request.download('/api/report/export', params, fileName)
}

// ==================== 自定义 Authorization ====================
export function callThirdPartyApi(data) {
  return request.post('/api/third-party', data, {
    headers: {
      'Authorization': 'Bearer your-custom-token',
      'X-Api-Key': 'your-api-key'
    }
  })
}

在 Vue 组件中使用

js 复制代码
<template>
  <div>
    <!-- 用户列表 -->
    <el-table :data="userList" v-loading="loading">
      <el-table-column prop="name" label="姓名" />
      <el-table-column prop="email" label="邮箱" />
      <el-table-column label="操作">
        <template slot-scope="{ row }">
          <el-button @click="handleEdit(row)">编辑</el-button>
          <el-button @click="handleDelete(row.id)">删除</el-button>
        </template>
      </el-table-column>
    </el-table>

    <!-- 文件上传 -->
    <el-upload
      action="#"
      :http-request="handleUpload"
      :before-upload="beforeUpload"
    >
      <el-button>上传头像</el-button>
    </el-upload>

    <!-- 导出按钮 -->
    <el-button @click="handleExport">导出数据</el-button>
  </div>
</template>

<script>
import { getUserList, deleteUser, uploadAvatar, exportUsers } from '@/api/user'

export default {
  data() {
    return {
      userList: [],
      loading: false,
      queryParams: {
        page: 1,
        size: 10
      }
    }
  },

  created() {
    this.fetchUsers()
  },

  methods: {
    // 获取用户列表
    async fetchUsers() {
      try {
        this.loading = true
        const res = await getUserList(this.queryParams)
        this.userList = res.data
      } catch (error) {
        // 错误已在拦截器中处理
        console.error('获取用户列表失败', error)
      } finally {
        this.loading = false
      }
    },

    // 删除用户
    async handleDelete(id) {
      try {
        await this.$confirm('确认删除该用户?', '提示', {
          type: 'warning'
        })

        await deleteUser(id)
        this.$message.success('删除成功')
        this.fetchUsers()
      } catch (error) {
        if (error !== 'cancel') {
          console.error('删除失败', error)
        }
      }
    },

    // 上传前验证
    beforeUpload(file) {
      const isImage = file.type.startsWith('image/')
      const isLt2M = file.size / 1024 / 1024 < 2

      if (!isImage) {
        this.$message.error('只能上传图片文件!')
        return false
      }
      if (!isLt2M) {
        this.$message.error('图片大小不能超过 2MB!')
        return false
      }
      return true
    },

    // 自定义上传
    async handleUpload({ file }) {
      try {
        const res = await uploadAvatar(file)
        this.$message.success('上传成功')
        console.log('文件地址:', res.data.url)
      } catch (error) {
        console.error('上传失败', error)
      }
    },

    // 导出数据
    async handleExport() {
      try {
        await exportUsers(this.queryParams)
        // 下载成功后无需额外处理
      } catch (error) {
        console.error('导出失败', error)
      }
    }
  },

  // 组件销毁时取消所有请求
  beforeDestroy() {
    // 如果需要取消所有请求
    // request.cancelAll()
  }
}
</script>

高级用法

js 复制代码
import request from '@/utils/request'

// ==================== 并发请求 ====================
export async function fetchDashboardData() {
  try {
    const [users, orders, statistics] = await Promise.all([
      request.get('/api/users'),
      request.get('/api/orders'),
      request.get('/api/statistics')
    ])

    return {
      users: users.data,
      orders: orders.data,
      statistics: statistics.data
    }
  } catch (error) {
    console.error('获取数据失败', error)
    throw error
  }
}

// ==================== 串行请求(有依赖关系)====================
export async function createOrderWithDetails(orderData, itemsData) {
  try {
    // 先创建订单
    const orderRes = await request.post('/api/orders', orderData)
    const orderId = orderRes.data.id

    // 再添加订单明细
    const itemsRes = await request.post('/api/order-items', {
      orderId,
      items: itemsData
    })

    return {
      order: orderRes.data,
      items: itemsRes.data
    }
  } catch (error) {
    console.error('创建订单失败', error)
    throw error
  }
}

// ==================== 轮询请求 ====================
export function pollTaskStatus(taskId, interval = 2000, maxAttempts = 30) {
  let attempts = 0

  return new Promise((resolve, reject) => {
    const timer = setInterval(async () => {
      attempts++

      try {
        const res = await request.get(`/api/tasks/${taskId}`, {}, {
          showError: false  // 轮询不显示错误
        })

        if (res.data.status === 'completed') {
          clearInterval(timer)
          resolve(res.data)
        } else if (res.data.status === 'failed') {
          clearInterval(timer)
          reject(new Error('任务执行失败'))
        } else if (attempts >= maxAttempts) {
          clearInterval(timer)
          reject(new Error('轮询超时'))
        }
      } catch (error) {
        clearInterval(timer)
        reject(error)
      }
    }, interval)
  })
}

// ==================== 重试机制 ====================
export async function requestWithRetry(fn, maxRetries = 3) {
  let lastError

  for (let i = 0; i < maxRetries; i++) {
    try {
      return await fn()
    } catch (error) {
      lastError = error
      console.log(`请求失败,第 ${i + 1} 次重试...`)
      
      // 等待一段时间再重试
      await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)))
    }
  }

  throw lastError
}

// 使用示例
export function fetchImportantData() {
  return requestWithRetry(() => request.get('/api/important-data'))
}

// ==================== 请求队列 ====================
class RequestQueue {
  constructor(concurrency = 3) {
    this.concurrency = concurrency
    this.running = 0
    this.queue = []
  }

  async add(fn) {
    if (this.running >= this.concurrency) {
      await new Promise(resolve => this.queue.push(resolve))
    }

    this.running++
    try {
      return await fn()
    } finally {
      this.running--
      const resolve = this.queue.shift()
      if (resolve) resolve()
    }
  }
}

// 批量上传文件(限制并发数)
export async function batchUpload(files) {
  const queue = new RequestQueue(3) // 最多同时上传 3 个
  const results = []

  for (const file of files) {
    const result = await queue.add(() => uploadAvatar(file))
    results.push(result)
  }

  return results
}

全局配置和扩展

js 复制代码
import request, { service, requestManager } from '@/utils/request'

// ==================== 全局请求前置处理 ====================
export function setupRequestInterceptor() {
  // 添加请求追踪
  service.interceptors.request.use(config => {
    config.metadata = { startTime: Date.now() }
    return config
  })

  // 添加响应时间统计
  service.interceptors.response.use(
    response => {
      const duration = Date.now() - response.config.metadata.startTime
      console.log(`请求耗时: ${duration}ms`)
      return response
    },
    error => {
      if (error.config?.metadata) {
        const duration = Date.now() - error.config.metadata.startTime
        console.log(`请求失败耗时: ${duration}ms`)
      }
      return Promise.reject(error)
    }
  )
}

// ==================== 路由守卫中取消请求 ====================
export function setupRouterCancelRequests(router) {
  router.beforeEach((to, from, next) => {
    // 路由切换时取消所有进行中的请求
    requestManager.clearAll()
    next()
  })
}

// ==================== 在 main.js 中使用 ====================
// import { setupRequestInterceptor, setupRouterCancelRequests } from '@/utils/request-extension'
// setupRequestInterceptor()
// setupRouterCancelRequests(router)
相关推荐
米欧1 小时前
取消当前正在进行的所有接口请求
前端·javascript·axios
浪里行舟1 小时前
告别“拼接”,迈入“原生”:文心5.0如何用「原生全模态」重塑AI天花板?
前端·javascript·后端
OpenTiny社区1 小时前
救命!这个低代码工具太香了 ——TinyEngine 物料自动导入上手
前端·低代码·github
努力学前端Hang1 小时前
移动端跨平台开发深度解析:UniApp、Taro、Flutter 与 React Native 对比
前端·javascript·react native·react.js
前端九哥2 小时前
🚫循环里写return,浏览器当场沉默!
前端·javascript
亲爱的马哥2 小时前
填鸭表单!开箱即用的开源问卷调查系统!
java·前端·低代码·产品经理
米诺zuo2 小时前
nextjs文件路由、路由组
前端·next.js
加个鸡腿儿2 小时前
锚点跳转-附带CSS样式 & 阻止页面刷新技术方案
前端·javascript·css
dragon7252 小时前
FutureProvider会刷新两次的问题研究
前端·flutter