Lecture16-axios 进阶(拦截器、错误处理、参数)

文章目录

    [toc]

    单词词性音标含义
    interceptorn./ˌɪntəˈseptə(r)/拦截器,在请求发出前 / 响应返回后统一插入处理
    requestn./rɪˈkwest/请求,请求拦截器用于统一携带 token
    responsen./rɪˈspɒns/响应,响应拦截器统一处理成功与错误
    localStoragen./ˈləʊkl ˈstɔːrɪdʒ/浏览器本地存储,刷新、关闭浏览器都不丢
    tokenn./ˈtəʊkən/登录凭证,存 localStorage 并随请求头带上
    rejectv./rɪˈdʒekt/拒绝,业务失败时 reject 让调用方进入 catch
    Authorizationn./ˌɔːθəraɪˈzeɪʃn/请求头字段,用于携带 Bearer token

    Lecture16-axios 进阶(拦截器、错误处理、参数)

    1、本节讲什么

    把 axios 再"武装"一下:用拦截器统一处理"每个请求都带 token、每个错误都弹提示",并搞清响应拦截器里"逻辑成功和逻辑失败"是两回事;最后把所有接口函数收进 src/api/,让页面不出现裸 http.get。

    2、概念速览

    什么是拦截器(interceptor):在请求发出前、响应返回后,统一"插一脚"做点事——给每个请求自动带 token、每个错误自动弹提示。不用每个页面写一遍。

    类比:像 SpringMVC 的拦截器 / AOP——在方法执行前后统一处理通用逻辑。

    什么是 localStorage:浏览器提供的一个"本地存储"对象,用 key-value 存数据,刷新页面、关掉浏览器都不会丢。

    • 类比:像 Java 后端的"持久化存储"——服务器重启数据还在;localStorage 就是浏览器里的"小仓库"。
    • 三个最常用方法:
    localStorage.setItem('token', 'abc')   // 存:setItem(键, 值)
    localStorage.getItem('token')          // 取:返回 'abc',没有则返回 null
    localStorage.removeItem('token')       // 删:按键删掉
    
    • 注意:存的是字符串。存对象要 JSON.stringify(obj) 转成字符串,读回来再 JSON.parse(str) 转回对象。
    • 正确 vs 误区:localStorage 是"浏览器本地、这个浏览器这台机器"的,换浏览器/换设备没有;它主要用来存"刷新不能丢"的东西(如登录 token、记住的用户名),别当普通变量随手用。

    关键认知:HTTP 成功 ≠ 业务成功。 后端通常返回统一响应体,形如:

    { "code": 200, "msg": "ok", "data": { ... } }
    
    • HTTP 200:请求到达了后端、后端没崩——网络层成功,会进响应拦截器的"成功回调"。
    • code === 200:业务逻辑成功,才真正拿到想要的数据。
    • code !== 200(如 { code: 400, msg: "库存不足" }):业务失败了,但 HTTP 还是 200。

    所以成功回调里还要再判断一次 code——这正是本节第一件事。

    正确 vs 误区:错误处理集中在拦截器,而不是每个页面都 try/catch。页面只管"成功后的业务"。

    3、先看效果:一段"标准封装"

    src/api/http.ts

    import axios from 'axios'
    import { ElMessage } from 'element-plus'
    
    const http = axios.create({ baseURL: '/api', timeout: 5000 })
    
    // 请求拦截器:每个请求发出前,自动带上 token
    http.interceptors.request.use((config) => {
      const token = localStorage.getItem('token')
      if (token) config.headers.Authorization = `Bearer ${token}`
      return config
    })
    
    // 响应拦截器:HTTP 成功 / 失败 都要处理,HTTP 成功里还要区分"逻辑成功 / 逻辑失败"
    http.interceptors.response.use(
      // —— 成功回调:HTTP 200,但不代表业务成功 ——
      (res) => {
        const body = res.data                          // 统一响应体 { code, msg, data }
        if (body.code === 200) {
          return body.data                             // 情况A:逻辑成功 → 返回业务数据,调用处直接拿
        }
        // 情况B:HTTP 成功但业务失败(如库存不足、没权限)
        ElMessage.error(body.msg)
        return Promise.reject(body.msg)                // 抛给调用方,走 catch
      },
      // —— 失败回调:HTTP 层面失败(404 / 500 / 网络断开…)——
      (err) => {
        const status = err.response?.status
        if (status === 401) { ElMessage.error('登录已过期,请重新登录') }
        else if (status === 500) { ElMessage.error('服务器出错了') }
        else { ElMessage.error(err.message) }
        return Promise.reject(err)                     // 仍要 reject,让调用方能感知失败
      },
    )
    
    export default http
    

    4、照着做

    ① 先动手认识 localStorage:打开浏览器 DevTools 的 Console,逐行试:

    localStorage.setItem('token', 'abc')      // 存
    localStorage.getItem('token')             // 取 → 'abc'
    localStorage.removeItem('token')          // 删
    localStorage.getItem('token')             // → null
    

    再到 Application → Local Storage 面板里看刚才存的键值——这就是请求拦截器里 token 的存放位置。

    ② 用上面的代码替换 src/api/http.ts。

    ③ 试一个"业务失败"的请求(后端返回 code !== 200),看是否弹 msg 里的提示、且调用处能 catch 到——因为成功回调里 Promise.reject 了。

    ④ 把接口函数收进 src/api/,页面不出现裸 http.get。

    新建 src/api/user.ts:

    import http from './http'
    
    export interface User {
      id: number
      name: string
      age: number
    }
    
    // 所有接口函数都收在这里:页面只 import 函数,不碰 http
    export function fetchUsers(keyword = '') {
      return http.get('/users', { params: { keyword } })
    }
    export function createUser(data: Partial<User>) {
      return http.post('/users', data)
    }
    export function deleteUser(id: number) {
      return http.delete(`/users/${id}`)
    }
    

    页面里只 import 函数:

    <script setup lang="ts">
    import { ref, onMounted } from 'vue'
    import { fetchUsers } from '../api/user'   // 只 import 函数,不出现 http
    
    const list = ref<User[]>([])
    onMounted(async () => {
      list.value = await fetchUsers('红')       // 直接拿数据(拦截器已返回业务数据)
    })
    </script>
    

    ⑤ 学传参数的几种写法:

    // GET 带 query 参数:?keyword=xx&page=1
    await http.get('/users', { params: { keyword: '红', page: 1 } })
    
    // POST 带 body(表单数据)
    await http.post('/users', { name: '小明' })
    
    // 路径参数
    await http.delete('/users/5')           // 删除 id=5
    await http.put('/users/5', { age: 23 }) // 编辑 id=5
    

    5、常见疑问

    • Q:成功回调里,情况A和情况B怎么区分? A:看后端统一响应体里的 code。code === 200(或团队约定的成功码)→ 情况A,return body.data;否则 → 情况B,Promise.reject。先和后端对齐"成功码是几"。

    • Q:为什么情况B要 Promise.reject? A:因为调用处用 await 等结果。不 reject 的话,调用处会以为"成功了"继续走成功分支,拿到的却是失败数据。reject 才能让它进 catch,正确感知"这次失败了"。

    • Q:return res.data.data 和 return body.data 有什么区别? A:一回事。res.data 是 HTTP 响应体(即统一响应体),body.data(或 res.data.data)是响应体里真正要的业务数据。取决于后端包了几层,先打印 res.data 看结构。

    • Q:接口函数收进 src/api/,页面不出现裸 http.get,好处是什么? A:① 接口路径、参数只在 src/api/user.ts 出现一次,后端改了只改一处;② 页面代码更干净,只看到"业务函数"(fetchUsers),看不到"HTTP 细节"(/users?keyword=)。规范"数据收口"。

    • Q:token 从哪来、存哪? A:登录成功后后端在响应体中返回 token 数据,前端接收到 token 就存 localStorage(刷新不丢)或 Pinia。请求拦截器每次带上。

    • Q:为什么 token 放 localStorage,不放普通变量? A:普通变量(内存)刷新页面就没了,token 丢了就等于登录态丢了、要重新登录。localStorage 刷新不丢,所以登录凭证放这里。存对象记得 JSON.stringify / JSON.parse。总之localStorage在浏览器端,可以长期存储数据。

    • Q:localStorage 和 sessionStorage 有什么区别? A:都能在浏览器本地存 key-value。区别在存活期:localStorage 关掉浏览器还在(下次打开仍在);sessionStorage 只在这个"标签页会话"内有效,关标签页就清空。token 用 localStorage。

    • Q:拦截器会不会影响调试? A:会多一点"黑盒",但更好排查——你只需看"请求有没有发出、响应有没有回来",通用的都集中了。

    • Q:如何从响应 error 对象中解析到我们关心的错误信息?

      A:参考下面的结构图

      image-20261009161557239

    6、规范要点

    • 请求/响应拦截器集中放 src/api/http.ts,token、超时、错误提示这类"横切逻辑"写一次。
    • 响应拦截器必须区分逻辑成功/失败:成功 return body.data,业务失败 Promise.reject(msg),别让调用方误判。
    • 所有接口函数收进 src/api/(如 src/api/user.ts),页面只 import 函数,不出现裸 http.get。

    7、本节验证

    • 会用请求拦截器给每个请求自动带 token ✅
    • 会在响应拦截器区分“逻辑成功 / 逻辑失败”:成功 return body.data、失败 Promise.reject ✅
    • 会把接口函数收进 src/api/,页面不出现裸 http.get ✅
    • 会用 localStorage 存 token(刷新不丢) ✅
    • 认识“HTTP 成功 ≠ 业务成功” ✅
    end