当前位置: 首页 > news >正文

TypeScript封装Axios

TypeScript封装Axios

Axios的基本使用

因axios基础使用十分简单,可参考axios官方文档,这里不在介绍他基本用法,主要讲解拦截器。
拦截器主要分为两种,请求拦截器响应拦截器
请求拦截器:请求发送之前进行拦截,应用于我们在请求发送前需要对请求数据做一些处理。例如:

  • 携带token
  • 当请求时间过长时,设置loading

响应拦截器:在响应到达时进行拦截,应用于在我们业务代码中拿到数据之前,需要对数据做一定处理。例如:

  • 转换数据格式
  • 移除loading

为什么要封装Axios

在项目中会有很多的模块都需要发送网络请求,常见的比如登录模块,首页模块等,如果我们项目中直接使用诸如axios.get(), axios.post(),会存在很多弊端,哪些弊端呢?

  1. 首先这样做会导致我们每个模块对axios依赖性太强,意味着我们项目中的每个模块都和一个第三方库耦合度较高,这样的话,如果axios不在维护,我们要更换库的时候将非常麻烦,我们可以假设一下,随着时间的推移,axios可能因为浏览器的升级,Webpack的改变而出现一些bug, 然而axios已不再维护,这时我们往往需要切换库,这就意味着我们需要去修改每个模块中的请求相关的代码,显而易见,非常繁琐。
  2. 还有一点,在我们发送网络请求的时候,往往会有很多共同的特性,比如说,在我们成功登录之后的其他请求中,我们往往需要在请求头中添加token,然后发送请求;在每次请求中,我们想展示一个loading… 这些功能如果在每次请求的逻辑中都写一遍,很明显,我们的代码重复度太高了。

而axios封装之后,则会带来很多好处:

解决以上弊端,降低与第三方库的耦合度,这样我们将来需要更换库时,只需要修改我们封装后的request即可,这样我们往往只是修改封装后一两个文件,而不再需要每个模块每个模块的修改。

在我们开发中,我认为class的相关语法封装性会更好,因此这里我选择尝试用类相关的概念来封装axios。我想要的封装后达到的效果:可以直接在其他项目使用。

利用面向对象的思想对Axios进行封装

基础封装

封装一个Request的类,使得在外部可以调用此类的构造函数创建实例,创建的实例就对应axios实例,http/request.ts中代码如下:

import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from "axios";// 创建这个类的目的:每个创建出的HDRequest的实例都对应一个axios实例
class Request {// 创建实例的方法:constructor()构造实例instance: AxiosInstance;constructor(config: AxiosRequestConfig) {this.instance = axios.create(config);}// 二次封装网络请求的方法request(config: AxiosRequestConfig) {return this.instance.request(config);}
}
// 暴露Request类
export default Request;

基本配置信息单独写在一个文件中,config/index.ts中代码如下:

const CONFIG = {// 服务器地址serverAddress: 'https://91huajian.cn',// 其他基础配置项,入最长响应时间等
};
export default CONFIG;

http/index.ts中创建一个Request类的一个实例http,并配置这个实例:

import Request from "./index";
import CONFIG from "@/config";// 创建一个axios实例
const http = new Request({baseURL: CONFIG.serverAddress,timeout: CONFIG.maxTimeout,
})export default http;

在接口中使用该实例发送请求:

// 在http/api/sponsor.ts文件中封装发送请求的方法,在页面组件任意位置随意调用
import http from '../request';
// 查询赞助
export const getSponsorListAsync: any = (params: any) => { return http.request({url: '/huajian/common/getSponsorList',method: 'get',params: params});
}

拦截器的类型

拦截器分为三种:

  • 类拦截器(在封装的axios类(文中类为Request类)上定义的拦截器)
  • 实例拦截器(在利用Request类实例化对象时传递的参数中定义的拦截器)
  • 接口拦截器(在调用实例时传入的参数中定义的参数)

配置全局拦截器(类拦截)

保证每一个axios实例对象都有拦截器,即本使用Request实例化的对象发送的请求都会被配置的拦截器所拦截并执行拦截器中的程序。

类拦截器比较容易实现,只需要在类中对axios.create()创建的实例调用interceptors下的两个拦截器即可,实例代码如下:

import axios, { AxiosInstance,AxiosRequestConfig,AxiosResponse } from 'axios';
import { RequestConfig } from './types/types';
class Request {instance: AxiosInstance;constructor(config: RequestConfig) { this.instance = axios.create(config);// 添加全局请求拦截器,每个实例都有this.instance.interceptors.request.use(// 拦截到请求中携带的所有配置项config(config: AxiosRequestConfig) => {console.log('全局请求拦截器', config);return config;},(err: any) => err)// 添加全局响应拦截器,每个实例都有this.instance.interceptors.response.use(// 拦截到服务器返回的响应体res(res: AxiosResponse) => {console.log('全局响应拦截器', res);return res.data;},(err: any) => err;}request(config: AxiosRequestConfig) { return this.instance.request(config);}
}
export default Request;

我们在这里对响应拦截器做了一个简单的处理,就是将请求结果中的.data进行返回,因为我们对接口请求的数据主要是存在在.data中,跟data同级的属性我们基本是不需要的。

为某一Request实例单独配置拦截器(实例拦截)

实例拦截器是为了保证封装的灵活性,因为每一个实例中的拦截后处理的操作可能是不一样的,所以在定义实例时,允许我们传入拦截器。

新创建一个实例http2在它的config中传入拦截器属性,但是axiosAxiosRequestConfig类型中并没有拦截器属性类型。

因此需要对types/index.ts中的构造函数中的config类型进行扩展(extends)。首先我们定义一下interface,方便类型提示,代码如下:

import { AxiosRequestConfig, AxiosResponse } from "axios";
// 拦截器的类型
export interface RequestInterceptors<T> {// 请求拦截器requestInterceptor?: (config: AxiosRequestConfig) => AxiosRequestConfig;// 在发送请求之前做些什么requestInterceptorCatch?: (error: any) => any;// 对请求错误做些什么// 响应拦截器responseInterceptor?: (res: T) => T;// 对响应数据做点什么responseInterceptorsCatch?: (err: any) => any;// 对响应错误做点什么
}
// 自定义传入的参数
export interface RequestConfig<T =  AxiosResponse> extends AxiosRequestConfig{interceptors?: RequestInterceptors<T>;
}

然后再创建新的实例,并在实例中引入定义的拦截器:

import Request from "./index";
import CONFIG from "@/config";
import {RequestConfig} from "./types/types";
import { AxiosResponse } from "axios";// 创建一个axios实例
const http = new Request({baseURL: CONFIG.serverAddress,timeout: CONFIG.maxTimeout
})
const http2 = new Request({baseURL: CONFIG.serverAddress,timeout: CONFIG.maxTimeout,interceptors: {// 配置请求拦截器requestInterceptor: (config: RequestConfig) => { console.log('通过请求拦截器,拿到http2的请求配置参数',config);return config;},// 响应拦截器responseInterceptor: (result: AxiosResponse) => {console.log('通过响应拦截器,拿到http2的响应返回的结果',result);return result;}}
})export default {http,http2};

注意:这里的拦截器只能由使用http2实例发送的请求才会执行。

我们的拦截器的执行顺序为实例请求→类请求→实例响应→类响应;这样我们就可以在实例拦截上做出一些不同的拦截,

此时在使用Request实例化对象http2时,我们传入的配置项中多了interceptors配置项,那么在Request类中我们就得接收并在实例化时执行:

import axios, { AxiosInstance,AxiosRequestConfig,AxiosResponse } from 'axios';
import { RequestConfig,RequestInterceptors } from './types/types';
class Request {instance: AxiosInstance;// 拦截器对象interceptorsObj?: RequestInterceptors<AxiosResponse>;constructor(config: RequestConfig) { this.instance = axios.create(config);this.interceptorsObj = config.interceptors;//接收实例对象传入的该实例的定制拦截器// 全局请求拦截器this.instance.interceptors.request.use((config: AxiosRequestConfig) => {console.log('全局请求成功拦截器', config);return config;},(err: any) => err)  // 使用实例对象的自定义拦截器 针对特定的http2实例添加拦截器this.instance.interceptors.request.use(this.interceptorsObj?.requestInterceptor, // 请求前的拦截器this.interceptorsObj?.requestInterceptorCatch // 发送请求失败的拦截器)// 使用实例对象的自定义拦截器 针对特定的http2实例添加拦截器this.instance.interceptors.response.use(config.interceptors?.responseInterceptor,config.interceptors?.responseInterceptorsCatch);// 全局响应拦截器this.instance.interceptors.response.use((res: AxiosResponse) => {console.log('全局响应成功拦截器', res);return res.data;},(err: any) => {return err;});}request(config: AxiosRequestConfig) { return this.instance.request(config);}
}
export default Request;

同一个request实例的不同网络请求设置不同的拦截器(接口拦截)

现在我们对单一接口进行拦截操作,首先我们将AxiosRequestConfig类型修改为RequestConfig允许传递拦截器;然后我们在类拦截器中将接口请求的数据进行了返回,也就是说在request()方法中得到的类型就不是AxiosResponse类型了。

接口中同一个实例在发送不同的request请求时一个配置了拦截器一个没配拦截器

import { http2 } from "..";http2.request({url: "/entire/list",params: {offset: 0,size: 20,},}).then((res) => {console.log(res);});http2.request({url: "/home/highscore",interceptors: {responseInterceptor: (config) => {console.log("来自接口定制的请求前的拦截");return config;},responseInterceptor: (res) => {console.log("来自接口的响应成功的拦截");return res;},},}).then((res) => {console.log(res);});

request/index.tsrequest方法进行进一步封装,使之能够立即执行传进来的拦截器:

// Request类的request方法:
// 二次封装网络请求的方法
request<T>(config: RequestConfig<T>): Promise<T> { return new Promise((resolve, reject) => {// 为同一个request实例的不同网络请求设置不同的拦截器// 不能将拦截器放在实例上,这样的话同一个实例的拦截器都是一样的了// 只能判断传进来的config中是否设置了拦截器,若设置了就直接执行// 执行this.instance.request(config)之前先执行requestInterceptor,并更新configif (config.interceptors?.requestInterceptor) {//立即调用拦截器函数执行config = config.interceptors.requestInterceptor(config);}// 由于执行完this.instance.request(config)之后才能对response结果进行拦截,是个异步的过程// 在Promise内部调用instance实例先执行this.instance.request(config),然后等待结果,之后以结果作为拦截器函数的参数进行调用this.instance.request<any, T>(config).then((res) => {// 如果给单个响应设置拦截器,这里使用单个响应的拦截器if (config.interceptors?.responseInterceptor) {res = config.interceptors.responseInterceptor(res);}resolve(res);}).catch((err: any) => {reject(err);})})
}

各种请求拦截的执行顺序:

拦截器执行顺序:接口请求 -> 实例请求 -> 全局请求 -> 实例响应 -> 全局响应 -> 接口响应

实例请求和全局请求的先后顺序取决于在Requestconstructor()构造函数中两种请求的执行顺序。

取消请求

思路步骤:

  1. 创建一个数组用于存储控制器资源;
  2. 在请求拦截器中将控制器存入数组;
  3. 在响应拦截器中将控制器从数组中移除;
  4. 封装一个取消全部请求的方法;
  5. 封住一个可以取消指定请求的方法;

准备

我们需要将所有请求的取消方法保存到一个集合(这里我用的数组,也可以使用Map)中,然后根据具体需要去调用这个集合中的某个取消请求方法。

因此,我们首先进行类型定义:

// 一个取消请求对象,键位url,值为取消控制器
export interface CancelRequestSource { // 取消请求的标识[index: string]: AbortController;
}

然后我们在Request类中定义储存取消请求对象的数组,和存放请求url的数组

/*
存放取消控制对象的集合
* 在创建请求后将取消控制对象 push 到该集合中
* 封装一个方法,可以取消请求,传入 url: string|string[]  
* 在请求之前判断同一URL是否存在,如果存在就取消请求
*/
cancelRequestSourceList ?: CancelRequestSource[];
/*
存放所有请求URL的集合
* 请求之前需要将url push到该集合中
* 请求完毕后将url从集合中删除
* 添加在发送请求之前完成,删除在响应之后删除
*/
requestUrlList ?: string[];

接着我们要准备两个方法,一个时根据url在取消控制对象数组中找到对应请求的方法,另一个时完成取消请求后删除存放url数组和存放取下请求对象数组中对象请求的方法。

//根据url找到取消请求对象数组中此次请求取消对象存放的地址
private getSourceIndex(url: string): number {return this.cancelRequestSourceList?.findIndex((item: CancelRequestSource) => {return Object.keys(item)[0] === url;}) as number;
}
//请求取消完成后,我们要删除对应请求和取消请求对象
private delUrl(url: string) {const urlIndex = this.requestUrlList?.findIndex((u) => u === url);const sourceIndex = this.getSourceIndex(url);// 删除url和AbortController对象urlIndex !== -1 && this.requestUrlList?.splice(urlIndex as number, 1);sourceIndex !== -1 && this.cancelRequestSourceList?.splice(sourceIndex as number, 1);
}

在发送请求前存入AbortController对象

const url = config.url;
// url存在 保存当前请求url 和 取消请求方法
if (url) {this.requestUrlList?.push(url);//将url存入url数组const controller = new AbortController();//构造实例化一个AbortController对象控制器config.signal = controller.signal//绑定请求this.cancelRequestSourceList?.push({[url]: controller//将该控制器添加入cancelRequestSourceList数组})
}

请求已经完成了删除保存的url和AbortController对象

this.instance.request<any, T>(config).then(res => {// 如果我们为单个响应设置拦截器,这里使用单个响应的拦截器if (config.interceptors?.responseInterceptor) {res = config.interceptors.responseInterceptor<T>(res)}resolve(res)}).catch((err: any) => {reject(err)}).finally(() => {url && this.delUrl(url);// 请求执行完毕,删除保存在数组中的url和该请求的取消方法});

封装取消请求方法

  • 封装取消全部请求
// 取消全部请求
cancelAllRequest() {this.cancelRequestSourceList?.forEach((source) => {const key = Object.keys(source)[0];source[key].abort();})
}
  • 封装取消部分请求
// 取消请求
cancelRequest(url: string | string[]) {if (typeof url === 'string') {//  取消单个请求const sourceIndex = this.getSourceIndex(url);sourceIndex >= 0 && this.cancelRequestSourceList?.[sourceIndex][url].abort();} else {// 存在多个需要取消请求的地址url.forEach((u) => {const sourceIndex = this.getSourceIndex(u);sourceIndex >= 0 && this.cancelRequestSourceList?.[sourceIndex][u].abort();});}
}

相关文章:

TypeScript封装Axios

TypeScript封装Axios Axios的基本使用 因axios基础使用十分简单&#xff0c;可参考axios官方文档&#xff0c;这里不在介绍他基本用法&#xff0c;主要讲解拦截器。 拦截器主要分为两种&#xff0c;请求拦截器和响应拦截器。 请求拦截器&#xff1a;请求发送之前进行拦截&…...

指针(一)【C语言进阶版】

大家好&#xff0c;我是深鱼~ 【前言】&#xff1a; 指针的主题&#xff0c;在初阶指针章节已经接触过了&#xff0c;我们知道了指针的概念&#xff1a; 1.指针就是个变量&#xff0c;用来存放地址&#xff0c;地址的唯一标识一块内存空间&#xff08;指针变量&#xff09;&a…...

回归预测 | MATLAB实现SA-BP模拟退火算法优化BP神经网络多输入单输出回归预测(多指标,多图)

回归预测 | MATLAB实现SA-BP模拟退火算法优化BP神经网络多输入单输出回归预测&#xff08;多指标&#xff0c;多图&#xff09; 目录 回归预测 | MATLAB实现SA-BP模拟退火算法优化BP神经网络多输入单输出回归预测&#xff08;多指标&#xff0c;多图&#xff09;效果一览基本介…...

springMVC 已解密的登录请求

问题描述&#xff1a; 解决方案&#xff1a; 1.对用户所输入的密码在页面进行MD5加密并反馈至密码输入框。 2. 手动生成SSL安全访问证书&#xff1b;在此不做介绍&#xff0c;相关方法可通过网上查找&#xff1b; 3. 将产品HTTP访问方式改为SSL安全访问方式&#xff1b;在Ap…...

机器学习赋能乳腺癌预测:如何使用贝叶斯分级进行精确诊断?

一、引言 乳腺癌是女性最常见的恶性肿瘤之一&#xff0c;也会发生在男性身上。每年全球有数百万人被诊断出乳腺癌&#xff0c;对患者的生活和健康造成了巨大的影响。早期的乳腺癌检测和准确的诊断对于提高治疗的成功率至关重要。然而&#xff0c;乳腺癌的早期诊断面临着许多挑战…...

Java后端开发面试题——框架篇

Spring框架中的bean是单例的吗&#xff1f;Spring框架中的单例bean是线程安全的吗&#xff1f; singleton : bean在每个Spring IOC容器中只有一个实例。 prototype&#xff1a;一个bean的定义可以有多个实例。 Spring bean并没有可变的状态(比如Service类和DAO类)&#xff0c…...

【新版】系统架构设计师 - 系统测试与维护

个人总结&#xff0c;仅供参考&#xff0c;欢迎加好友一起讨论 文章目录 架构 - 系统测试与维护考点摘要软件测试软件测试 - 测试类型软件测试 - 静态测试软件测试 - 动态测试软件测试 - 测试阶段软件测试 - 测试阶段 - 单元测试软件测试 - 测试阶段 - 集成测试软件测试 - 测试…...

使用 useEffect 和 reactStrictMode:优化 React 组件的性能和可靠性

使用useEffect和React.StrictMode是一种优化React组件性能和可靠性的常见做法。下面是关于如何使用这两个特性的示例&#xff1a; import React, { useEffect } from react;function MyComponent() {useEffect(() > {// 在组件挂载/更新时执行的副作用代码// 可以进行数据获…...

商业智能BI是什么都不明白,如何实现数字化?

2021年下半年中国商业智能软件市场规模为4.8亿美元&#xff0c;2021年度市场规模达到7.8亿美元&#xff0c;同比增长34.9%&#xff0c;呈现飞速增长的趋势。数字化时代&#xff0c;商业智能BI对于企业的落地应用有着巨大价值&#xff0c;逐渐成为了现代企业信息化、数字化转型中…...

【五子棋】

五子棋 文章目录 五子棋前言一、登录功能二.哈希表管理用户的会话和房间三.基于Websocket连接开发的功能1.匹配功能2.游戏房间3.挑战功能4.人机对战5.聊天功能 前言 这篇博客主要详细介绍我的五子棋项目的核心功能的实现细节&#xff0c;也就是详细介绍五子棋各个功能是如何实…...

docker 01(初识docker)

一、docker概念 Docker是一个开源的应用容器引擎&#xff1b;诞生于2013年初&#xff0c;基于Go 语言实现&#xff0c;dotCloud公司出品(后改名为Dockerlnc);Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中&#xff0c;然后发布到任何流行的Linux …...

爬虫逆向实战(十九)--某号站登录

一、数据接口分析 主页地址&#xff1a;某号站 1、抓包 通过抓包可以发现登录接口 2、判断是否有加密参数 请求参数是否加密&#xff1f; 通过查看“载荷”模块可以发现有一个jsondata_rsa的加密参数 请求头是否加密&#xff1f; 无响应是否加密&#xff1f; 无cookie是否…...

linux环境docker安装mysql

1&#xff1a;docker拉取mysql镜像&#xff08;可有自己选择mysql版本&#xff09; [rootlocalhost ~]# docker pull mysql:8.02&#xff1a; Docker 中启动 MySQL 容器&#xff0c;可以使用以下命令&#xff1a; docker run -d --name mysql_container -v ./sql:/docker-…...

taro h5 formData上传图片的坑-Required request part ‘file‘ is not present

描述&#xff1a;用formData上传图片 1、生成formData const formData new FormData() formData.append(file, data) // data是file formData.append(xxx, xxx) // 添加其他参数2、用taro.request请求 Taro.request({url: xxxx,data: formData,header: {Content-Type: mult…...

GNU GRUB version 2.06 Minimal Bash-lke line editing is supported 问题修复

一、问题背景 博主喜欢折腾系统&#xff0c;电脑原来有一个windows系统&#xff0c;想整一个Linux双系统&#xff0c;结果开机时出现以下画面&#xff1a; GNU GRUB version 2.06 Minimal Bash-lke line editing is supported. TAB lists possible comand completions, Anywh…...

Embedding 向量生成GPT数据使用相关

如果使用python3.6的版本&#xff0c;使用pycharm创建工程&#xff0c;那么默认会使用 docx包&#xff0c;这样运行程序会爆异常&#xff0c;突然想起以前请教的一个大神&#xff0c;想当 初&#xff0c;这个问题困扰了我 两天时间&#xff0c;在此记录一下&#xff1a; pytho…...

Jenkins工具系列 —— 配置邮箱 每个job下动态设置临时发送人

文章目录 安装插件添加邮箱认证邮箱申请&#xff08;以QQ邮箱网页为例&#xff09;jenkins添加邮箱认证 jenkins设置邮箱相关信息配置全局邮件单个JOB邮箱配置 安装插件 点击 左侧的 Manage Jenkins —> Plugins ——> 左侧的 Available plugins 添加邮箱认证 邮箱申请…...

华纳云:ubuntu中怎么查看进程占用的端口

在Ubuntu中&#xff0c;你可以使用以下命令来查看进程占用的端口&#xff1a; 打开终端&#xff08;Terminal&#xff09;。 使用 netstat 命令来查看进程占用的端口。以下是几个常用的命令示例&#xff1a; 查看所有进程占用的端口和地址&#xff1a; netstat -tuln 查看TCP端…...

【学会动态规划】 最长递增子序列(26)

目录 动态规划怎么学&#xff1f; 1. 题目解析 2. 算法原理 1. 状态表示 2. 状态转移方程 3. 初始化 4. 填表顺序 5. 返回值 3. 代码编写 写在最后&#xff1a; 动态规划怎么学&#xff1f; 学习一个算法没有捷径&#xff0c;更何况是学习动态规划&#xff0c; 跟我…...

Azure虚拟网络对等互连

什么是Azure虚拟网络对等互联 Azure虚拟网络对等互联&#xff08;Azure Virtual Network peering&#xff09;是一种连接两个虚拟网络的方法&#xff0c;使得这两个虚拟网络能够在同一地理区域内进行通信。它通过私有IP地址在虚拟网络之间建立网络连接&#xff0c;不论是在同一…...

【力扣数据库知识手册笔记】索引

索引 索引的优缺点 优点1. 通过创建唯一性索引&#xff0c;可以保证数据库表中每一行数据的唯一性。2. 可以加快数据的检索速度&#xff08;创建索引的主要原因&#xff09;。3. 可以加速表和表之间的连接&#xff0c;实现数据的参考完整性。4. 可以在查询过程中&#xff0c;…...

遍历 Map 类型集合的方法汇总

1 方法一 先用方法 keySet() 获取集合中的所有键。再通过 gey(key) 方法用对应键获取值 import java.util.HashMap; import java.util.Set;public class Test {public static void main(String[] args) {HashMap hashMap new HashMap();hashMap.put("语文",99);has…...

select、poll、epoll 与 Reactor 模式

在高并发网络编程领域&#xff0c;高效处理大量连接和 I/O 事件是系统性能的关键。select、poll、epoll 作为 I/O 多路复用技术的代表&#xff0c;以及基于它们实现的 Reactor 模式&#xff0c;为开发者提供了强大的工具。本文将深入探讨这些技术的底层原理、优缺点。​ 一、I…...

CMake控制VS2022项目文件分组

我们可以通过 CMake 控制源文件的组织结构,使它们在 VS 解决方案资源管理器中以“组”(Filter)的形式进行分类展示。 🎯 目标 通过 CMake 脚本将 .cpp、.h 等源文件分组显示在 Visual Studio 2022 的解决方案资源管理器中。 ✅ 支持的方法汇总(共4种) 方法描述是否推荐…...

SAP学习笔记 - 开发26 - 前端Fiori开发 OData V2 和 V4 的差异 (Deepseek整理)

上一章用到了V2 的概念&#xff0c;其实 Fiori当中还有 V4&#xff0c;咱们这一章来总结一下 V2 和 V4。 SAP学习笔记 - 开发25 - 前端Fiori开发 Remote OData Service(使用远端Odata服务)&#xff0c;代理中间件&#xff08;ui5-middleware-simpleproxy&#xff09;-CSDN博客…...

Linux离线(zip方式)安装docker

目录 基础信息操作系统信息docker信息 安装实例安装步骤示例 遇到的问题问题1&#xff1a;修改默认工作路径启动失败问题2 找不到对应组 基础信息 操作系统信息 OS版本&#xff1a;CentOS 7 64位 内核版本&#xff1a;3.10.0 相关命令&#xff1a; uname -rcat /etc/os-rele…...

安宝特案例丨Vuzix AR智能眼镜集成专业软件,助力卢森堡医院药房转型,赢得辉瑞创新奖

在Vuzix M400 AR智能眼镜的助力下&#xff0c;卢森堡罗伯特舒曼医院&#xff08;the Robert Schuman Hospitals, HRS&#xff09;凭借在无菌制剂生产流程中引入增强现实技术&#xff08;AR&#xff09;创新项目&#xff0c;荣获了2024年6月7日由卢森堡医院药剂师协会&#xff0…...

【分享】推荐一些办公小工具

1、PDF 在线转换 https://smallpdf.com/cn/pdf-tools 推荐理由&#xff1a;大部分的转换软件需要收费&#xff0c;要么功能不齐全&#xff0c;而开会员又用不了几次浪费钱&#xff0c;借用别人的又不安全。 这个网站它不需要登录或下载安装。而且提供的免费功能就能满足日常…...

Go 并发编程基础:通道(Channel)的使用

在 Go 中&#xff0c;Channel 是 Goroutine 之间通信的核心机制。它提供了一个线程安全的通信方式&#xff0c;用于在多个 Goroutine 之间传递数据&#xff0c;从而实现高效的并发编程。 本章将介绍 Channel 的基本概念、用法、缓冲、关闭机制以及 select 的使用。 一、Channel…...

力扣热题100 k个一组反转链表题解

题目: 代码: func reverseKGroup(head *ListNode, k int) *ListNode {cur : headfor i : 0; i < k; i {if cur nil {return head}cur cur.Next}newHead : reverse(head, cur)head.Next reverseKGroup(cur, k)return newHead }func reverse(start, end *ListNode) *ListN…...