axios是如何实现的(源码解析)

1 axios的实例与请求流程

在阅读源码之前,先大概了解一下axios实例的属性和请求整体流程,带着这些概念,阅读源码可以轻松不少!

下图是axios实例属性的简图。

可以看到axios的实例上,其实主要就这三个东西:

  1. config:配置,比如url、method、params、headers等等
  2. interceptors :拦截器,分为请求拦截器和返回拦截器。
  3. request:调用xhr或者http请求的方法,参数就是config

由于调用request方法的时候可以再次传入config,但是不能传入interceptors,所以拦截器一定是要在请求之前就在axios上添加好,不能临时加。

下图是axios的请求流程,其实相当简单,先了解这个流程,看源码的时候就会有方向。

2 源码文件结构解析

axios的源码都在lib文件夹下,最核心的内容在core文件夹里。

lib│  axios.js    // 最终导出的文件│  utils.js    // 工具类├─adapters     // 适配器相关│      adapters.js //适配器类│      http.js     // node请求│      xhr.js      // 浏览器请求├─cancel      // 取消功能相关│      CanceledError.js //取消异常类│      CancelToken.js   //取消token类│      isCancel.js      //判断是否取消├─core       // 核心功能相关 │      Axios.js                 // axios类│      AxiosError.js            // axios异常类│      AxiosHeaders.js          // 请求头│      buildFullPath.js         // 构造请求地址│      dispatchRequest.js       // 发送请求方法│      InterceptorManager.js    // 拦截器的类│      mergeConfig.js           // 合并配置方法│      settle.js                // 处理请求结果方法│      transformData.js         // 数据转换执行方法├─defaults    // 默认配置│      index.js                 // 默认请求参数配置│      transitional.js          // 默认transitional配置├─env        //  node环境没有FormData,│  │  data.js│  └─classes│          FormData.js├─helpers  // 各种工具类方法,看名字就可以大概猜到作用│      AxiosTransformStream.js│      AxiosURLSearchParams.js│      bind.js│      buildURL.js│      callbackify.js│      combineURLs.js│      cookies.js│      deprecatedMethod.js│      formDataToJSON.js│      formDataToStream.js│      fromDataURI.js│      HttpStatusCode.js│      isAbsoluteURL.js│      isAxiosError.js│      isURLSameOrigin.js│      null.js│      parseHeaders.js│      parseProtocol.js│      readBlob.js│      README.md│      speedometer.js│      spread.js│      throttle.js│      toFormData.js│      toURLEncodedForm.js│      validator.js│      ZlibHeaderTransformStream.js└─platform  // 为不同环境下准备的方法    │  index.js    ├─browser    │  │  index.js    │  └─classes    │          Blob.js    │          FormData.js    │          URLSearchParams.js    └─node        │  index.js        └─classes                FormData.js                URLSearchParams.js

3 源码文件阅读

3.1 入口文件 axios.js

该文件创建了一个axios实例,并且导出,所以我们import axios from 'axios'引入的就是该实例,可以直接使用,不需要再new Axios({...})这样写。

下面看一下axios实例是如何创建的吧~


// 核心方法,根据config创建axios实例
function createInstance (defaultConfig) {// 创建axios实例const context = new Axios(defaultConfig);// 给Axios原型上的request方法绑定context为它的this// 这个instance就是我们最终使用的axios// 没想到吧,最开始的instance其实是个函数,// 所以我们才可以使用这个用法axios('/api/url')// 只不过后面给它扩展了很多东西const instance = bind(Axios.prototype.request, context);// 将Axios.prototype上的属性都绑定到instance上,// 这样它就拥有了简写的请求方法,比如axios.get(),axios.post()// 如果是函数,this绑定为contextutils.extend(instance, Axios.prototype, context, { allOwnKeys: true });// 将context上的属性都绑定到instance上,// 这样它就拥有了拦截器属性,可以使用axios.interceptors.request.use()// 因为context上的函数的this本就指向context,所以第三个参数不需要再指定utils.extend(instance, context, null, { allOwnKeys: true });// 给instance增加create方法,可以通过create创建一个实例instance.create = function create (instanceConfig) {// 入参为拼接配置项,以instanceConfig为优先return createInstance(mergeConfig(defaultConfig, instanceConfig));};return instance;
}// 调用上面的方法,最终导出的是axios,
// 其实是Axios.prototype.request,并扩展了很多属性
const axios = createInstance(defaults);// 继续给axios增加属性
// 这就说明如果自己通过const myAxios=axios.create({});
// 创建出来的实例就没有下面这些属性了
// 所以下面这些属性只能通过import axios from 'axios';
// axios.all()这样的方式来使用axios.Axios = Axios;// Cancel相关
axios.CanceledError = CanceledError;
axios.CancelToken = CancelToken;
axios.isCancel = isCancel;
axios.VERSION = VERSION;
// 工具函数,将对象转为FormData
axios.toFormData = toFormData;// Axios通用异常类
axios.AxiosError = AxiosError;// Cancel异常类
axios.Cancel = axios.CanceledError;// Expose all/spread
// 工具函数
axios.all = function all (promises) {return Promise.all(promises);
};// 工具函数,利用apply将数组参数改为单个传入的参数
axios.spread = spread;// 判断异常是否是AxiosError
axios.isAxiosError = isAxiosError;// 合并config对象方法
axios.mergeConfig = mergeConfig;axios.AxiosHeaders = AxiosHeaders;// 工具方法
axios.formToJSON = thing => formDataToJSON(utils.isHTMLForm(thing) ? new FormData(thing) : thing);// 获取适配器:xhr 、http
axios.getAdapter = adapters.getAdapter;// 请求状态
axios.HttpStatusCode = HttpStatusCode;axios.default = axios;// 最终导出
export default axios

3.2 Axios类

从上面的文件可以看出,axios扩展了Axios的原型方法和Axios实例的属性,所以接下来要重点看Axios的类里有什么内容。


class Axios {// 可以看到Axios的构造函数相当简单// 仅仅是保存了我们传入的config,// 然后初始化空的拦截器对象constructor(instanceConfig) {// 所有的配置都设置再defaults上this.defaults = instanceConfig;// 初始化空的拦截器对象,包含请求拦截器request和返回拦截器responsethis.interceptors = {request: new InterceptorManager(),response: new InterceptorManager()};}// request是Axios的核心方法// 所有的核心都在request方法里,// request方法接收两种参数,【直接传config对象】或者【传url和config对象】request (configOrUrl, config) {// 允许axios('example/url'[, config]) 这样使用if (typeof configOrUrl === 'string') {config = config || {};config.url = configOrUrl;} else {config = configOrUrl || {};}// request会使用传入的配置merge默认配置// 所以即使只传了一个url,也会使用默认的Get方法config = mergeConfig(this.defaults, config);const { headers } = config;// 默认get请求config.method = (config.method || this.defaults.method || 'get').toLowerCase();// 说明header可以直接设置// 也可以在common设置通用header,也可以为每种请求设置特定的headerlet contextHeaders = headers && utils.merge(headers.common,headers[config.method]);headers && utils.forEach(['delete', 'get', 'head', 'post', 'put', 'patch', 'common'],(method) => {delete headers[method];});// 优先使用headers下配置,再使用headers.common和headers[get,post]的配置config.headers = AxiosHeaders.concat(contextHeaders, headers);// 请求拦截器链const requestInterceptorChain = [];// 记录是否使用同步的方式调用,我们配置拦截器的时候,默认是false,也就是异步let synchronousRequestInterceptors = true;this.interceptors.request.forEach(function unshiftRequestInterceptors (interceptor) {// 如果配置了runWhen函数,那么会先执行runWhen,如果为true,才会添加该拦截器if (typeof interceptor.runWhen === 'function' && interceptor.runWhen(config) === false) {return;}synchronousRequestInterceptors = synchronousRequestInterceptors && interceptor.synchronous;// unshift说明后传入的请求拦截器先执行,一次放入两个,分别是fulfilled和rejectedrequestInterceptorChain.unshift(interceptor.fulfilled, interceptor.rejected);});// 响应拦截器链const responseInterceptorChain = [];this.interceptors.response.forEach(function pushResponseInterceptors (interceptor) {// push说明先传入的响应拦截器先执行responseInterceptorChain.push(interceptor.fulfilled, interceptor.rejected);});let promise;let i = 0;let len;// 默认是异步执行,也就是一个执行完再执行下一个if (!synchronousRequestInterceptors) {//dispatchRequest是真正的发送请求const chain = [dispatchRequest.bind(this), undefined];// 前面插入请求拦截器chain.unshift.apply(chain, requestInterceptorChain);// 后面插入响应拦截器chain.push.apply(chain, responseInterceptorChain);len = chain.length;promise = Promise.resolve(config);// 依次执行while (i < len) {promise = promise.then(chain[i++], chain[i++]);}return promise;}len = requestInterceptorChain.length;let newConfig = config;i = 0;// 同步执行,请求拦截器while (i < len) {const onFulfilled = requestInterceptorChain[i++];const onRejected = requestInterceptorChain[i++];try {newConfig = onFulfilled(newConfig);} catch (error) {onRejected.call(this, error);break;}}// 发起请求try {promise = dispatchRequest.call(this, newConfig);} catch (error) {return Promise.reject(error);}i = 0;len = responseInterceptorChain.length;// 返回有异常可以继续走下去while (i < len) {promise = promise.then(responseInterceptorChain[i++], responseInterceptorChain[i++]);}return promise;}// 获取请求地址getUri (config) {config = mergeConfig(this.defaults, config);const fullPath = buildFullPath(config.baseURL, config.url);return buildURL(fullPath, config.params, config.paramsSerializer);}
}// Provide aliases for supported request methods
// 给Axios原型注入四个请求方法,请求方法本质都是调用request方法
// 这四个都是不带请求体的
utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData (method) {Axios.prototype[method] = function (url, config) {return this.request(mergeConfig(config || {}, {method,url,data: (config || {}).data}));};
});// 给Axios注入post,put,patch,postForm,putForm,patchForm方法
// 这几个方法都是带请求体的
utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData (method) {function generateHTTPMethod (isForm) {return function httpMethod (url, data, config) {return this.request(mergeConfig(config || {}, {method,headers: isForm ? {'Content-Type': 'multipart/form-data'} : {},url,data}));};}Axios.prototype[method] = generateHTTPMethod();Axios.prototype[method + 'Form'] = generateHTTPMethod(true);
});export default Axios;

3.3 InterceptorManager类

接下来看看拦截器是如何实现的。

先回顾一下我们平时是怎么使用拦截器的?


axios.interceptors.request.use({fulfilled:()=>{},rejected:()=>{}
})

可以看到我们给use传递的是一个对象,对象包含fulfilled函数和rejected函数。

接下来看源码:

class InterceptorManager {// 构造函数只初始化了一个空的handlers数组// 拦截器就是放在这个数组里的constructor() {this.handlers = [];}// 添加拦截器,返回索引,可以用索引来移除拦截器// 可以发现除了fulfilled和rejected,// 我们还可以设置synchronous和runWhen// runWhen函数用来动态控制是否使用该拦截器use (fulfilled, rejected, options) {this.handlers.push({fulfilled,rejected,synchronous: options ? options.synchronous : false,runWhen: options ? options.runWhen : null});return this.handlers.length - 1;}// 根据添加时返回的索引去删除拦截器eject (id) {if (this.handlers[id]) {this.handlers[id] = null;}}// 清空拦截器clear () {if (this.handlers) {this.handlers = [];}}// 提供遍历拦截器快捷操作forEach (fn) {utils.forEach(this.handlers, function forEachHandler (h) {if (h !== null) {fn(h);}});}
}export default InterceptorManager;

3.4 dispatchRequest发送请求

看完上面的代码,我们已经基本搞清楚了axios的整体流程:

组装config->组装header->调用请求拦截器->发送实际请求->调用返回拦截器。

但是我们还不知道axios具体是如何调用请求的,那么接下来就要看dispatchRequest代码咯!


// 暂且先记住,这个函数的作用就是用来判断请求是否被取消,
// 如果要的话,则直接抛出异常,
function throwIfCancellationRequested (config) {if (config.cancelToken) {config.cancelToken.throwIfRequested();}if (config.signal && config.signal.aborted) {throw new CanceledError(null, config);}
}// 发送请求核心函数
export default function dispatchRequest (config) {// 刚开始请求前判断一次是否取消throwIfCancellationRequested(config);config.headers = AxiosHeaders.from(config.headers);// 执行数据转换操作config.data = transformData.call(config,config.transformRequest);// 默认设置请求头的contentType为application/x-www-form-urlencodedif (['post', 'put', 'patch'].indexOf(config.method) !== -1) {config.headers.setContentType('application/x-www-form-urlencoded', false);}// 获取适配器,如果是浏览器环境获取xhr,// 如果是Node环境,获取http // 适配器就是最终用来发送请求的东西const adapter = adapters.getAdapter(config.adapter || defaults.adapter);// 请求是使用适配器执行configreturn adapter(config).then(function onAdapterResolution (response) {// 请求完之后判断是否要取消throwIfCancellationRequested(config);// 对返回结果进行转换response.data = transformData.call(config,config.transformResponse,response);// 设置返回头response.headers = AxiosHeaders.from(response.headers);return response;}, function onAdapterRejection (reason) {// 如果不是因为取消而报错if (!isCancel(reason)) {// 再次判断是否要取消,如果是会抛出异常throwIfCancellationRequested(config);// 处理正常错误的返回值if (reason && reason.response) {reason.response.data = transformData.call(config,config.transformResponse,reason.response);reason.response.headers = AxiosHeaders.from(reason.response.headers);}}return Promise.reject(reason);});
}

3.5 adapter 请求适配器,此处以xhr请求适配器为例

dispatchRequest的流程还是相对简单的,剩下的疑惑就是adapter干了些什么,让我们接着往下看吧!


// 用于给上传和下载进度增加监听函数
function progressEventReducer (listener, isDownloadStream) {let bytesNotified = 0;const _speedometer = speedometer(50, 250);return e => {const loaded = e.loaded;const total = e.lengthComputable ? e.total : undefined;const progressBytes = loaded - bytesNotified;const rate = _speedometer(progressBytes);const inRange = loaded <= total;bytesNotified = loaded;const data = {loaded,total,progress: total ? (loaded / total) : undefined,bytes: progressBytes,rate: rate ? rate : undefined,estimated: rate && total && inRange ? (total - loaded) / rate : undefined,event: e};data[isDownloadStream ? 'download' : 'upload'] = true;listener(data);};
}// 判断是否支持XMLHttpRequest
const isXHRAdapterSupported = typeof XMLHttpRequest !== 'undefined';// 适配器的请求参数是config
export default isXHRAdapterSupported && function (config) {// 返回Promisereturn new Promise(function dispatchXhrRequest (resolve, reject) {// 请求体let requestData = config.data;// 请求头const requestHeaders = AxiosHeaders.from(config.headers).normalize();// 返回数据类型const responseType = config.responseType;let onCanceled;// function done () {if (config.cancelToken) {config.cancelToken.unsubscribe(onCanceled);}if (config.signal) {config.signal.removeEventListener('abort', onCanceled);}}// 自动帮我们设置contentType,// 这就是为什么我们使用的时候都不需要// 特别设置contentType的原因了if (utils.isFormData(requestData)) {if (platform.isStandardBrowserEnv || platform.isStandardBrowserWebWorkerEnv) {// 浏览器环境让浏览器设置requestHeaders.setContentType(false); } else {requestHeaders.setContentType('multipart/form-data;', false); }}// 请求let request = new XMLHttpRequest();// 设置auth,帮我们转码好了if (config.auth) {const username = config.auth.username || '';const password = config.auth.password ? unescape(encodeURIComponent(config.auth.password)) : '';requestHeaders.set('Authorization', 'Basic ' + btoa(username + ':' + password));}// 拼接完整URL路径const fullPath = buildFullPath(config.baseURL, config.url);// 开启请求request.open(config.method.toUpperCase(), buildURL(fullPath, config.params, config.paramsSerializer), true);// 设置超时时间request.timeout = config.timeout;//function onloadend () {if (!request) {return;}// 预准备返回体的内容const responseHeaders = AxiosHeaders.from('getAllResponseHeaders' in request && request.getAllResponseHeaders());const responseData = !responseType || responseType === 'text' || responseType === 'json' ?request.responseText : request.response;const response = {data: responseData,status: request.status,statusText: request.statusText,headers: responseHeaders,config,request};// 请求完之后判断请求是成功还是失败// 执行resolve和reject的操作settle(function _resolve (value) {resolve(value);done();}, function _reject (err) {reject(err);done();}, response);// 清除requestrequest = null;}if ('onloadend' in request) {// 设置onloadendrequest.onloadend = onloadend;} else {// Listen for ready state to emulate onloadendrequest.onreadystatechange = function handleLoad () {if (!request || request.readyState !== 4) {return;}// The request errored out and we didn't get a response, this will be// handled by onerror instead// With one exception: request that using file: protocol, most browsers// will return status as 0 even though it's a successful requestif (request.status === 0 && !(request.responseURL && request.responseURL.indexOf('file:') === 0)) {return;}// readystate handler is calling before onerror or ontimeout handlers,// so we should call onloadend on the next 'tick'// readystate之后再执行onloadendsetTimeout(onloadend);};}// 处理浏览器请求取消事件request.onabort = function handleAbort () {if (!request) {return;}reject(new AxiosError('Request aborted', AxiosError.ECONNABORTED, config, request));request = null;};// 处理低级的网络错误request.onerror = function handleError () {reject(new AxiosError('Network Error', AxiosError.ERR_NETWORK, config, request));request = null;};// 处理超时request.ontimeout = function handleTimeout () {let timeoutErrorMessage = config.timeout ? 'timeout of ' + config.timeout + 'ms exceeded' : 'timeout exceeded';const transitional = config.transitional || transitionalDefaults;if (config.timeoutErrorMessage) {timeoutErrorMessage = config.timeoutErrorMessage;}reject(new AxiosError(timeoutErrorMessage,transitional.clarifyTimeoutError ? AxiosError.ETIMEDOUT : AxiosError.ECONNABORTED,config,request));request = null;};// 添加 xsrfif (platform.isStandardBrowserEnv) {const xsrfValue = (config.withCredentials || isURLSameOrigin(fullPath))&& config.xsrfCookieName && cookies.read(config.xsrfCookieName);if (xsrfValue) {requestHeaders.set(config.xsrfHeaderName, xsrfValue);}}// 无请求体的话就移除contentTyperequestData === undefined && requestHeaders.setContentType(null);// 添加headers if ('setRequestHeader' in request) {utils.forEach(requestHeaders.toJSON(), function setRequestHeader (val, key) {request.setRequestHeader(key, val);});}// 添加withCredentials if (!utils.isUndefined(config.withCredentials)) {request.withCredentials = !!config.withCredentials;}// 添加responseTypeif (responseType && responseType !== 'json') {request.responseType = config.responseType;}// 增加下载过程的监听函数if (typeof config.onDownloadProgress === 'function') {request.addEventListener('progress', progressEventReducer(config.onDownloadProgress, true));}// 增加上传过程的监听函数if (typeof config.onUploadProgress === 'function' && request.upload) {request.upload.addEventListener('progress', progressEventReducer(config.onUploadProgress));}// 请求过程中取消if (config.cancelToken || config.signal) {onCanceled = cancel => {if (!request) {return;}reject(!cancel || cancel.type ? new CanceledError(null, config, request) : cancel);request.abort();request = null;};config.cancelToken && config.cancelToken.subscribe(onCanceled);if (config.signal) {config.signal.aborted ? onCanceled() : config.signal.addEventListener('abort', onCanceled);}}// 获取请求协议,比如https这样的const protocol = parseProtocol(fullPath);// 判断当前环境是否支持该协议if (protocol && platform.protocols.indexOf(protocol) === -1) {reject(new AxiosError('Unsupported protocol ' + protocol + ':', AxiosError.ERR_BAD_REQUEST, config));return;}// 发送请求request.send(requestData || null);});
}

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

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

相关文章

CLion 2023:专注于C和C++编程的智能IDE mac/win版

JetBrains CLion 2023是一款专为C和C开发者设计的集成开发环境&#xff08;IDE&#xff09;&#xff0c;它集成了许多先进的功能&#xff0c;旨在提高开发效率和生产力。 CLion 2023软件获取 CLion 2023的智能代码编辑器提供了丰富的代码补全和提示功能&#xff0c;使您能够更…

基于Java+小程序点餐系统设计与实现(源码+部署文档)

博主介绍&#xff1a; ✌至今服务客户已经1000、专注于Java技术领域、项目定制、技术答疑、开发工具、毕业项目实战 ✌ &#x1f345; 文末获取源码联系 &#x1f345; &#x1f447;&#x1f3fb; 精彩专栏 推荐订阅 &#x1f447;&#x1f3fb; 不然下次找不到 Java项目精品实…

FairyGUI × Cocos Creator 3.x 场景切换

前言 前文提要&#xff1a; FariyGUI Cocos Creator 入门 FairyGUI Cocos Creator 3.x 使用方式 个人demo&#xff1a;https://gitcode.net/qq_36286039/fgui_cocos_demo_dust 个人demo可能会更新其他代码&#xff0c;还请读者阅读本文内容&#xff0c;自行理解并实现。 官…

MySql-DQL-聚合函数

目录 聚合函数统计该企业员工数量count&#xff08;字段&#xff09;count&#xff08;常量&#xff09;count&#xff08;*&#xff09; 统计该企业最早入职的员工统计该企业最迟入职的员工统计该企业员工 ID 的平均值统计该企业员工的 ID 之和 聚合函数 之前我们做的查询都是…

uniapp_微信小程序自定义顶部导航栏和右侧胶囊对齐(不对齐来打我)

一、想要的效果 思路首先开启自定义导航栏&#xff0c;取消自带的导航栏&#xff0c;然后计算胶囊的高度和标题对齐 二、成品代码 1、首先再你需要居中的代码添加以下style <view class"header":style"{paddingTop:navBarTop px,height:navBarHeight px,…

Vue的个人笔记

Vue学习小tips ctrl s ----> 运行 alt b <scrip> 链接 <script src"https://cdn.jsdelivr.net/npm/vue2.7.16/dist/vue.js"></script> 插值表达式 指令

力扣hot100题解(python版7-9题)

7、接雨水 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图&#xff0c;计算按此排列的柱子&#xff0c;下雨之后能接多少雨水。 示例 1&#xff1a; 输入&#xff1a;height [0,1,0,2,1,0,1,3,2,1,2,1] 输出&#xff1a;6 解释&#xff1a;上面是由数组 [0,1,0,2,1,0,1,…

第2讲:C语言数据类型和变量

第2讲&#xff1a;C语言数据类型和变量 目录1.数据类型介绍1.1字符型1.2整型1.3浮点型1.4 布尔类型1.5 各种数据类型的长度1.5.1 sizeof 操作符1.5.2 数据类型长度1.5.3 sizeof 中表达式不计算 2.signed 和 unsigned3.数据类型的取值范围4. 变量4.1 变量的创建4.2 变量的分类 5…

vue2+element医院安全(不良)事件报告管理系统源代码

目录 安全不良事件类型 源码技术栈 医院安全&#xff08;不良&#xff09;事件报告管理系统采用无责的、自愿的填报不良事件方式&#xff0c;有效地减轻医护人员的思想压力&#xff0c;实现以事件为主要对象&#xff0c;可以自动、及时、实际地反应医院的安全、不良、近失事件…

使用Jemeter对HTTP接口压测

我们不应该仅仅局限于某一种工具&#xff0c;性能测试能使用的工具非常多&#xff0c;选择适合的就是最好的。笔者已经使用Loadrunner进行多年的项目性能测试实战经验&#xff0c;也算略有小成&#xff0c;任何性能测试&#xff08;如压力测试、负载测试、疲劳强度测试等&#…

Open CASCADE学习|绘制砂轮

今天绘制一个砂轮&#xff0c;其轮廓由两条直线段和两段圆弧构成&#xff0c;圆弧分别与直线相切&#xff0c;两条圆弧之间相交而非相切。建模思路是&#xff1a;先给定两条直线段的起始点及长度&#xff0c;画出直线段&#xff0c;然后给定其中一圆弧的半径及圆心角&#xff0…

Linux CentOS stream 9 firewalld

随着互联网行业快速发展&#xff0c;服务器成为用户部署网络业务重要的网络工具&#xff0c;但随之而来的就是更密集的网络攻击&#xff0c;这给网站带来了很大的阻碍。防火墙作为保障网络安全的主要设备&#xff0c;可以很好的抵御网络攻击。 防火墙基本上使用硬件和软件两种…

Unity中URP实现水效果(水的深度)

文章目录 前言一、搭建预备场景1、新建一个面片&#xff0c;使其倾斜一个角度&#xff0c;来模拟水底和岸边的效果2、随便创建几个物体&#xff0c;作为与水面接触的物体3、再新建一个面片&#xff0c;作为水面 二、开始编写水体的Shader效果1、新建一个URP基础Shader2、把水体…

第七篇【传奇开心果系列】python的文本和语音相互转换库技术点案例示例:Sphinx自动电话系统(IVR)经典案例

传奇开心果博文系列 系列博文目录python的文本和语音相互转换库技术点案例示例系列 博文目录前言一、雏形示例代码二、扩展思路介绍三、Sphinx多语言支持示例代码四、Sphinx和语音合成库集成示例代码五、Sphinx语音识别前自然语言预处理示例代码六、Sphinx语音识别自动电话系统…

flink watermark 生成机制与总结

flink watermark 生成机制与总结 watermark 介绍watermark生成方式watermark 的生成值算法策略watermark策略设置代码 watermark源码分析watermark源码调用流程debug&#xff08;重要&#xff09;测试思路 迟到时间处理FlinkSql 中的watermark引出问题与源码分析 watermark 介绍…

SpringBoot实现缓存预热的几种常用方案

&#x1f3f7;️个人主页&#xff1a;牵着猫散步的鼠鼠 &#x1f3f7;️系列专栏&#xff1a;Java全栈-专栏 &#x1f3f7;️个人学习笔记&#xff0c;若有缺误&#xff0c;欢迎评论区指正 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&…

TRS 2024 论文阅读 | 基于点云处理和点Transformer网络的人体活动连续识别

无线感知/雷达成像部分最新工作<持续更新>: 链接地址 注1:本文系“无线感知论文速递”系列之一,致力于简洁清晰完整地介绍、解读无线感知领域最新的顶会/顶刊论文(包括但不限于 Nature/Science及其子刊; MobiCom, Sigcom, MobiSys, NSDI, SenSys, Ubicomp; JSAC, 雷达学…

ubuntu使用LLVM官方发布的tar.xz来安装Clang编译器

ubuntu系统上的软件相比CentOS更新还是比较快的&#xff0c;但是还是难免有一些软件更新得不那么快&#xff0c;比如LLVM Clang编译器&#xff0c;目前ubuntu 22.04版本最高还只能安装LLVM 15&#xff0c;而LLVM 18 rc版本都出来了。参见https://github.com/llvm/llvm-project/…

go使用trpc案例

1.go下载trpc go install trpc.group/trpc-go/trpc-cmdline/trpclatest 有报错的话尝试配置一些代理&#xff08;选一个&#xff09; go env -w GOPROXYhttps://goproxy.cn,direct go env -w GOPROXYhttps://goproxy.io,direct go env -w GOPROXYhttps://goproxy.baidu.com/…

NXP实战笔记(六):S32K3xx基于RTD-SDK在S32DS上配置PWM发波

目录 1、概述 2、SDK配置 2.1、Port配置 2.2、Emios_Mcl_Ip 2.3、Emios_Pwm 2.4、代码示例 1、概述 针对S32K3xx芯片&#xff0c;产生PWM的硬件支持单元仅有两个&#xff0c;分别是eMiosx与Flexio. 生成PWM的顺序&#xff0c;按照单片机所用资源进行初始化执行如下 初始化…