axios 封装 http 请求详解


前言

Axios 是一个基于 Promise 的 HTTP 库,它的概念及使用方法本文不过多赘述,请参考:axios传送门
本文重点讲述下在项目中是如何利用 axios 封装 http 请求。


一、预设全局变量

在 /const/preset.js 中配置预先设置一些全局变量

window.$env = process.env.NODE_ENV === 'development' ? 'DEV' : 'PROD'// 默认开发环境
let config = {baseURL: location.origin,httpBaseURL: location.origin + '/api',webBaseURL: location.origin + location.pathname,vipAddress: '/necp/mapp/sc', // 后端微服务的统一入口
}// 生产环境
if (window.$env !== 'DEV') {if (location.href.indexOf('/ecs/') > -1) {config.baseURL = location.href.replace(/\/ecs.+/, '')config.httpBaseURL = config.baseURL}
}// 文件资源请求路径
config.fileUrl = config.httpBaseURL + config.vipAddress + 'file/download'window.$globals = config

在 main.js 中引入

import Vue from 'vue'
import './const/preset'
// ...
// 把 vue 示例挂载到 window 下
window.$vm = new Vue({render: h => h(App),router
}).$mount('#app')

因为生产环境部署的差异,http 请求的 baseURL 并非都是统一的,所以不单独配置默认的 axios.defaults.baseURL,而是通过此文件预设的变量进行设置。

全局预设变量中的 config.httpBaseURL 将添加到请求的 URL 中,对于代码中的 location.href.indexOf(‘/ecs/’) > -1 判断只是举例,可根据实际需求决定是否需要。

二、http 请求封装

1.配置全局 axios 默认值

axios.defaults.headers.post['Content-Type'] = 'application/json;charset=UTF-8'
axios.defaults.timeout = 60000
axios.defaults.crossDomain = true

此三条配置分别对应以下作用:

  • 发送POST请求时,设置请求头的 Content-Type 字段为 ‘application/json;charset=UTF-8’ ,以便服务器正确解析请求的数据。
  • 发送请求默认的超时时间为 60s。
  • 允许跨域请求。

提示:覆盖默认超时时间,可在 axios 发送请求的参数 config 对象中设置 timeout 属性即可

2.配置请求拦截器

请求拦截器是在发送请求前执行的函数,它可以用于修改请求的配置或者在请求发送前进行一些操作。最常用的功能就是使用请求拦截器实现身份验证

一个常见的实现是用户登录之后,服务端会响应用户的登录信息,并且把用户的身份认证 token 存储到 cookie 中,然后在请求拦截器中将 cookie 中获取到的 token 设置到请求头中,每次发送请求都会携带上此 token 发送到服务端,服务端再获取请求头的 token 来判断用户是否登录状态或者登录已过期,作出不同的响应。

axios.interceptors.request.use(config => {const token = cookie.get(TOKEN_COOKIE_KEY)if (token) {config.headers[TOKEN_REQ_KEY] = token}return config},error => {return Promise.reject(error)}
)

3.配置响应拦截器

响应拦截器是在接收到响应后执行的函数,它可以用于修改响应的数据或者在接收到响应后进行一些操作。
响应拦截器主要作用包括修改响应数据、错误处理、统一处理响应等功能,因把响应数据及错误的处理都放在了发送请求的回调中,所以只定义了最简单的响应拦截器。

axios.interceptors.response.use(response => {return response
}, error => {return Promise.reject(error)
})

4.发送请求的 request 函数

此函数接收四个参数:请求方法,请求的 api 接口,请求参数,请求的 config 配置项,返回一个 Promise 的实例。此函数完成了正常响应处理、异常处理、重复请求取消等功能。

4.1 拼接完整的请求 url

const apiInterceptor = api => {if (api.startsWith('http')) { // 自定义请求路径return api.slice(4)}if (api.startsWith('_SC_')) { // 项目统一的api前缀api = $globals.vipAddress + api.slice(4)}return $globals.httpBaseURL + api
}const request = async (method = 'post', api, params = {}, config = {}) => {// 省略...let url = apiInterceptor(api)let opts = {method,url,headers: config.headers || {},withCredentials: config.withCredentials || true // 跨域请求时是否需要使用凭证}// 省略...
}

调用 apiInterceptor 函数来拼接完整的请求 url,如果 api 是以 http 开头,则表示自定义 api 的请求路径,否则请求路径使用 preset.js 中预设的全局变量来拼接完整的 url。

4.2 参数处理

const jsonObj2FormData = jsonObj => {let formData = new FormData()Object.keys(jsonObj).forEach(key => {if (jsonObj[key] instanceof Array) {formData.append(key, JSON.stringify(jsonObj[key]))} else {formData.append(key, jsonObj[key])}})return formData
}
// 省略...if (config.formDataFormat) {opts.headers['Content-Type'] = 'application/x-www-form-urlencoded;charset=UTF-8'params = jsonObj2FormData(params)
}
if (method == 'post') {opts.data = params
} else {opts.params = params
}
  • 服务端有部分接口接收的参数要求 FormData 格式,这时候需要将参数序列化,并且修改请求头的 Content-Type。
  • 发送 get/post 请求时,接收参数的对象的 key 不一样。

4.3 正常响应处理

使用 axios(opts) 发起请求,得到的是一个 Promise,在 then 的第一个参数中传入一个正常的响应处理函数,这个函数接收响应拦截器中返回的 response 作为参数。

return new Promise((resolve, reject) => {axios(opts).then(response => {let res = response.dataif (config.customHandler) { // 自定义响应处理if (config.responseAll) return resolve(response)return resolve(res)}if (res) {if (res.code === 000) { // 登录超时$vm.$toast.error(res.message)$vm.$store.dispatch('REMOVE_USER') // 移除 cookie、session、storage 存储的信息reject(res.message)if (window.self === window.top) {$vm.$router.push('/login') // 跳转登录页}} else if (res.code === 200) {resolve(res.data)} else {$vm.$toast.error(res.message || '接口异常, 请稍后重试')reject(res)}} else {$vm.$toast.error('接口无返回内容')}})
})

提示:$vm 指向全局的 Vue 实例,$toast 则是将 element 的 Message 组件实例挂载到了 Vue 的原型上

  • 如果调用 request 函数传入了 config.customHandler = true,表示自定义响应处理,并且 config.responseAll = true 时,会把响应拦截器中得到的 response 直接返回,这个参数主要用于调用服务端响应字节流的接口时使用。
  • 后端响应的数据结构如下图,并且登录过期接口的 http 响应状态码是 200,但是响应的数据格式中的 code 值为特定值,所以要特殊处理此类情况,清空存储在客户端的客户信息,跳转到登录页。
  • 当响应的数据中与服务端约定响应正常的 code 为 200,此时把 data 作为 Promise.resolve 的值
    response响应结构

4.4 异常处理

异常处理在 axios(opts).then() 的第二个参数中传入处理函数,这个函数接收响应拦截器中返回的 Promise.reject(error) 作为参数。

异常处理主要针对 http 响应状态码不等于 200 的情况,包括常见的请求超时,404请求资源不存在,50X 服务器异常等情况。

axios(opts).then(response => {// 省略...
}, error => {// 如果自定义处理if (config.customHandler) {reject(error)return}// 请求超时if (error.code == 'ECONNABORTED' && error.message.indexOf('timeout') > -1) {$vm.$toast.error(`请求超时,接口地址:${url}`)reject(error)return}if (error.response) {// 401未登录或登录失效if (error.response.status === 401) {reject(error)if (window.self === window.top) {$vm.$router.push('/login')}return}switch (error.response.status) {case 404:$vm.$toast.error(`请求的资源不存在,异常服务接口地址:${url}`)breakcase 408:$vm.$toast.error('请求超时')breakcase 500:$vm.$toast.error('服务异常')breakcase 502:$vm.$toast.error(error.message || '服务未响应')breakcase 503:$vm.$toast.error(error.message || '服务暂不可访问')breakdefault:$vm.$toast.error(error.response.statusText || '服务异常, 请稍后重试')}} else {$vm.$toast.error(error.response.statusText || '未知错误, 请稍后重试')}reject(error)})

4.5 取消请求

在一些特定情况下,比如用户快速点击提交表单,短时间内同时触发同一个请求多次,我们可以借助 axios.cancelToken 来取消前几次请求,只保留最后一次请求。

主要实现的原理如下:

  1. 每次调用 request 函数时,根据传入的 method + api + JSON.stringify(config) 作为当前请求的标识 key,如果配置了 config.cancelTokenWidthParams = true,时,在 key 后面拼接 JSON.stringify(params) 作为 key。
  2. HTTP_CANCEL_MAP 每一项的 key 为每个请求的 ‘唯一标识 + _ + 时间戳’,每一项 value 设置为 axios.CancelToken 构造函数传入的 executor 函数的参数,也就是 cancel 函数,调用 checkHttpCancel 函数传入 key 判断是否为重复请求,是重复请求则调用 cancel() 取消请求。
  3. 调用 request 函数时,配置 opts.cancelToken,使用 new 调用 CancelToken 的构造函数来创建 cancel token
  4. 请求响应成功和失败时都需要从 HTTP_CANCEL_MAP 中删除 reqUniqueKey 对应的 cancelToken
const CANCEL_TOKEN = axios.CancelToken
const HTTP_CANCEL_MAP = $globals.httpCancelMap = new Map()
const IS_CANCELED_MSG = 'canceled'const checkHttpCancel = reqKey => {HTTP_CANCEL_MAP.forEach((v, k) => {if (k.slice(0, -14) === reqKey) {v()HTTP_CANCEL_MAP.delete(k)}})
}const request = async (method = 'post', api, params = {}, config = {}) => {let reqKey = method + api + JSON.stringify(config)if (config.cancelTokenWidthParams) reqKey += JSON.stringify(params)let reqUniqueKey = reqKey + '_' + new Date().getTime()checkHttpCancel(reqKey)// 省略...opts.cancelToken = new CANCEL_TOKEN(c => HTTP_CANCEL_MAP.set(reqUniqueKey, c))// ...axios(opts).then(response => {HTTP_CANCEL_MAP.delete(reqUniqueKey)// ...}, error => {HTTP_CANCEL_MAP.delete(reqUniqueKey)if (axios.isCancel(error)) {reject(new Error(IS_CANCELED_MSG))return}// ...})
})

注意

  1. 此项目使用的 axios 版本为 0.21.1,从 v0.22.0 开始,Axios 支持以 fetch API 方式—— AbortController 取消请求,CancelToken API被弃用
  2. 可以使用同一个 cancel token 取消多个请求

三、完整的 http.js

import axios from 'axios'
import { TOKEN_REQ_KEY, TOKEN_COOKIE_KEY } from '@/const/common'
import { session, cookie, jsonObj2FormData } from '@/util/common'axios.defaults.headers.post['Content-Type'] = 'application/json;charset=UTF-8'
axios.defaults.timeout = 120000
axios.defaults.crossDomain = trueaxios.interceptors.request.use(config => {const token = cookie.get(TOKEN_COOKIE_KEY)if (token) {config.headers[TOKEN_REQ_KEY] = token}return config},error => {return Promise.reject(error)}
)axios.interceptors.response.use(response => {return response
}, error => {return Promise.reject(error)
})const CANCEL_TOKEN = axios.CancelToken
const HTTP_CANCEL_MAP = $globals.httpCancelMap = new Map()
const IS_CANCELED_MSG = 'canceled'const checkHttpCancel = reqKey => {HTTP_CANCEL_MAP.forEach((v, k) => {if (k.slice(0, -14) === reqKey) {v()HTTP_CANCEL_MAP.delete(k)}})
}const apiInterceptor = api => {if (api.startsWith('http')) { // 自定义请求路径return api.slice(4)}if (api.startsWith('_SC_')) { // 项目统一的api前缀api = $globals.vipAddress + api.slice(4)}return $globals.httpBaseURL + api
}const request = async (method = 'post', api, params = {}, config = {}) => {let reqKey = method + api + JSON.stringify(config)if (config.cancelTokenWidthParams) reqKey += JSON.stringify(params)let reqUniqueKey = reqKey + '_' + new Date().getTime()checkHttpCancel(reqKey)return new Promise((resolve, reject) => {if (config.loading) $vm.$loading.show()let url = apiInterceptor(api)let opts = {method,url,headers: config.headers || {},withCredentials: config.withCredentials || true // 跨域请求时是否需要使用凭证}if (config.formDataFormat) {opts.headers['Content-Type'] = 'application/x-www-form-urlencoded;charset=UTF-8'params = jsonObj2FormData(params)}if (config.timeout) opts.timeout = config.timeoutif (config.extends) opts = Object.assign(opts, config.extends) // 如果有并列层级的参数扩展if (method == 'post') {opts.data = params} else {opts.params = params}opts.cancelToken = new CANCEL_TOKEN(c => HTTP_CANCEL_MAP.set(reqUniqueKey, c))if (config.responseType) opts.responseType = config.responseType// 发起 axios 请求axios(opts).then(response => {HTTP_CANCEL_MAP.delete(reqUniqueKey)if (config.loading) $vm.$loading.close()let res = response.dataif (config.customHandler) { // 自定义响应处理if (config.responseAll) return resolve(response)return resolve(res)}if (res) {if (res.code === 000) { // 登录超时$vm.$toast.error(res.message)$vm.$store.dispatch('REMOVE_USER') // 移除 cookie、session、storage 存储的信息reject(res.message)if (window.self === window.top) {$vm.$router.push('/login') // 跳转登录页}} else if (res.code === 200) {resolve(res.data)} else {$vm.$toast.error(res.message || '接口异常, 请稍后重试')reject(res)}} else {$vm.$toast.error('接口无返回内容')}}, error => {HTTP_CANCEL_MAP.delete(reqUniqueKey)if (axios.isCancel(error)) {reject(new Error(IS_CANCELED_MSG))return}if (config.loading) $vm.$loading.close()// 如果自定义处理if (config.customHandler) {reject(error)return}// 请求超时if (error.code == 'ECONNABORTED' && error.message.indexOf('timeout') > -1) {$vm.$toast.error(`请求超时,接口地址:${url}`)reject(error)return}if (error.response) {// 401未登录或登录失效if (error.response.status === 401) {reject(error)if (window.self === window.top) {$vm.$router.push('/login')}return}switch (error.response.status) {case 404:$vm.$toast.error(`请求的资源不存在,异常服务接口地址:${url}`)breakcase 408:$vm.$toast.error('请求超时')breakcase 413:$vm.$toast.error('请求实体大小超过服务器最大限制')breakcase 500:$vm.$toast.error('服务异常')breakcase 502:$vm.$toast.error(error.message || '服务未响应')breakcase 503:$vm.$toast.error(error.message || '服务暂不可访问')breakdefault:$vm.$toast.error(error.response.statusText || '服务异常, 请稍后重试')}} else {$vm.$toast.error(error.response.statusText || '未知错误, 请稍后重试')}reject(error)})})
}export default {get: (api, params = {}, config = {}) => {return request('get', api, params, config)},post: (api, params = {}, config = {}) => {return request('post', api, params, config)},image: id => {return `${$globals.fileUrl}?fileId=${id}`},isCanceled: error => {if (error && error.message === IS_CANCELED_MSG) return truereturn false}
}
  1. http.image 方法仅用于返回文件的请求完整 url,使用场景为比如 <img> 标签中的 src 的值
  2. http.isCanceled 方法用于判断当前请求是否取消,如果有请求未取消并且出现全局 loading 加载未关闭的情况,可根据此标志来判断是否关闭

四、封装成插件并挂载到原型

/plugins/http/install.js

import httpService from '@/service/http'export default {install: Vue => {Vue.prototype.$http = httpService}
}

五、管理 api

例如,根据业务可划分为文档,评论等模块,在 service 目录下分别创建对应的模块存放 api 的 js 文件,对 api 进行统一管理。

强烈建议给每个 api 备注功能,提高可维护性

service模块划分
/service/comment.js

/*** @name 获取评论列表* @param {Object} params 请求参数对象*/
export const getCommentListPromise = params => {params = Object.assign({page: 0, // 页码pageSize: 5, // 每页数量}, params)return $vm.$http.get('_SC_/comment/findCommentList', params)
}

在 Comment.vue 页面中使用

import { getCommentListPromise } from '@/service/comment'
async findCommentList() {const data = await getCommentListPromise()console.log(data)
}

总结

本文主要讲述了如何使用 axios 进行 http 封装的详细过程,及在项目中如何使用封装的 http 请求,请求拦截器和响应拦截器都是比较简单,没有处理很多的逻辑,逻辑处理基本是集中在 request 函数中。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/787797.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

adobe stock会员开通付费付款订阅充值教程/adobe stock免费白嫖一个月

登录adobe stock的官网&#xff0c;点击你想要下载的视频&#xff0c;然后点击免费下载&#xff0c;我们点击免费试用按钮&#xff0c;可以看到非常贵&#xff0c;需要80美金一个月&#xff0c;用fomepay可以免费白嫖一个月 点击获取一张虚拟信用卡&#xff0c;就可以白嫖一个…

配mmdetection

1. 安装conda 链接后面补上 2. 装pytorch&#xff0c;版本别装错了 Start Locally | PyTorch用这里面的命令 nvcc --version看cuda版本&#xff08;实验室服务器是11.0&#xff0c;可以用这个11.8的 &#xff09; 检查torch和cuda版本的命令&#xff1a; import torch # …

C语言:二叉树的构建

目录 一、二叉树的存储 1.1 顺序存储 1.2 链式存储 二、二叉树的顺序结构及实现 2.1堆的概念及结构 2.2堆的构建 2.3堆的插入 2.4堆顶的删除 2.5堆的完整代码 三、二叉树的链式结构及实现 3.1链式二叉树的构建 3.2链式二叉树的遍历 3.2.1前序遍历 …

鸿蒙原OS开发实例:【ArkTS类库单次I/O任务开发】

Promise和async/await提供异步并发能力&#xff0c;适用于单次I/O任务的场景开发&#xff0c;本文以使用异步进行单次文件写入为例来提供指导。 实现单次I/O任务逻辑。 import fs from ohos.file.fs; import common from ohos.app.ability.common;async function write(data:…

深入解析大数据体系中的ETL工作原理及常见组件

** 引言 关联阅读博客文章&#xff1a;探讨在大数据体系中API的通信机制与工作原理 关联阅读博客文章&#xff1a;深入理解HDFS工作原理&#xff1a;大数据存储和容错性机制解析 ** 在当今数字化时代&#xff0c;大数据处理已经成为了企业成功的重要组成部分。而在大数据处…

OpenHarmony实战:小型系统 STM32MP1 芯片移植案例

本文章基于意法半导体 STM32MP157 芯片的小熊派 BearPi-HM Micro 开发板&#xff0c;进行小型带屏开发板的移植&#xff0c;实现了 ace_engine_lite、arkui_ui_lite、aafwk_lite、appexecfwk_lite、HDF 等部件基于 OpenHarmony LiteOS-A 内核的适配。移植架构上采用 Board 与 S…

shopee虾皮怎么分析竞品?这些技巧能帮你提升出单量90%+

“竞品分析”长久以来都是运营所纠结的核心问题&#xff0c;没有分析出对标竞品怎么增长销量&#xff0c;却被其他竞品给町上了&#xff0c;导致自身销量不增反减。这种情况我们是无法直观从自身的店铺及商品本身去找到原因的&#xff0c;本质的问题在竞品身上。我们无法控制有…

基于RDMA的云服务能力实践与探索

01 背景 随着基于大数据大模型构建的数据系统越来越有商业价值&#xff0c;机器学习的玩家也越来越多&#xff0c;数据量越来越大。为解决海量数据在服务器之间的同步效率问题&#xff0c;RDMA(Remote Direct Memory Access) 技术逐渐走进了网络技术人员的视野。RDMA为什么…

鸿鹄工程项目管理系统源码:Spring Boot带来的快速开发与部署体验

随着企业规模的不断扩大和业务的快速发展&#xff0c;传统的工程项目管理方式已经无法满足现代企业的需求。为了提高工程管理效率、减轻劳动强度、提高信息处理速度和准确性&#xff0c;企业需要借助先进的数字化技术进行转型。本文将介绍一款采用Spring CloudSpring BootMybat…

蓝桥杯单片机速成6-DS1302

目录 一、电路图 二、底层驱动 三、实际应用 四、时序 一、电路图 上图是寄存器地址定义&#xff0c;时分秒&#xff0c;年月日等等 DS1302有自己的时钟线SCLK&#xff0c;不会跟单总线一样因为没有自己的时钟线而导致温度读不出来 CH&#xff1a;时钟静止&#xff0c;置1时…

2024最新软件测试【测试理论+ 抓包与网络协议】面试题(内附答案)

一、测试理论 3.1 你们原来项目的测试流程是怎么样的? 我们的测试流程主要有三个阶段&#xff1a;需求了解分析、测试准备、测试执行。 1、需求了解分析阶段 我们的 SE 会把需求文档给我们自己先去了解一到两天这样&#xff0c;之后我们会有一个需求澄清会议&#xff0c; …

【Linux】防火墙iptables详解

目录 一、防护墙概述 二、防火墙 2.1名词 2.2使用规则 2.3表与链 2.3.1简介 2.3.2每个表说明 1&#xff09;filter表 2)nat表 2.4环境的配置 2.5iptables的命令参数 2.6 配置filter表规则 2.6.1备份与恢复 2.6.2案例1&#xff1a;禁止访问22端口 2.6.3案例2&…

【微信小程序】流量主-激励视频(激励广告)下发策略,每天三次免费体验,然后再次点击触发激励视频,当日不再触发。

如题&#xff1a; 允许用户有三次体验效果&#xff0c;然后弹出激励视频弹窗&#xff0c;之后当日不再弹出。 体验小程序&#xff1a; /*** 判断当前项目当天是否点击超过3次&#xff0c;触发广告效果。* 若&#xff0c;当天低于三次&#xff0c;则新增&#xff0c;若高于…

图片标注编辑平台搭建系列教程(4)——fabric几何定制渲染

背景 标注的几何&#xff0c;有时需要一些定制化的渲染样式&#xff0c;例如&#xff0c;线中间展示箭头&#xff0c;表示方向。本期教程教大家如何实现fabric几何定制化渲染。 带箭头的线 fabric提供了一些原生的几何&#xff0c;例如Point、Polyline、Polygon。同时提供了…

前端学习<三>CSS进阶——03-网页设计和开发中,那些困扰大神的关于字体的知识

前言 我周围的码农当中&#xff0c;有很多是技术大神&#xff0c;却常常被字体这种简单的东西所困扰。 这篇文章&#xff0c;我们来讲一讲关于字体的常识。这些常识所涉及到的问题&#xff0c;有很强的可操作性&#xff0c;都是在实际业务中真实遇到的&#xff0c;都是需要开…

软件资源分享六:EPLAN Electric P8 2024 | Eplan 2024 中文版软件介绍+保姆级安装教程

原文链接&#xff1a;安装激活教程 EPLAN Electric P8 2024 | Eplan 2024 中文版软件介绍安装教程 EPLAN 2024是一款电气设计软件&#xff0c;它可以用于自动化系统的设计、文档编制和维护。EPLAN可以对电气设计的各个方面进行完整的支持&#xff0c;包括电气控制系统、机械设…

Spring 整合 Log4j2日志框架

1. Log4j2日志概述 在项目开发中&#xff0c;日志十分的重要&#xff0c;不管是记录运行情况还是定位线上问题&#xff0c;都离不开对日志的分析。日志记录了系统行为的时间、地点、状态等相关信息&#xff0c;能够帮助我们了解并监控系统状态&#xff0c;在发生错误或者接近某…

【JavaSE】一维数组和二维数组详解

欢迎关注个人主页&#xff1a;逸狼 创造不易&#xff0c;可以点点赞吗~ 如有错误&#xff0c;欢迎指出~ 目录 一维数组 基本语法 初始化 遍历和打印 数组是引用型变量 基本类型变量与引用类型变量的区别 null 数组传参和返回 总结 二维数组 基本语法 初始化 遍历和打印 一维数组…

论文精读--GPT4

现有的所有模型都无法做到在线学习&#xff0c;能力有限&#xff0c;而让大模型拥有一个tools工具库&#xff0c;则可以使大模型变成一个交互式的工具去协调调用API完成任务&#xff0c;同时GPT4还联网了&#xff0c;可以不断地更新自己的知识库 多模态模型&#xff0c;接受文…

单V及多V感知在自动驾驶在恶劣环境条件下的感知提升方案

单V及多V感知在自动驾驶在恶劣环境条件下的感知提升方案 附赠自动驾驶学习资料和量产经验&#xff1a;链接 自动驾驶中的视觉感知是车辆在不同交通条件下安全、可持续地行驶的关键部分。然而&#xff0c;在大雨和雾霾等恶劣天气下&#xff0c;视觉感知性能受到多种降级效应的极…