Vue3 + Ts + Vite 封装一套企业级axiso全流程

news2024/11/26 10:15:16

9a69fede8b2044a79dd834e3e48f20b4.png前期回顾f8e3cc1a0f694ac2b665ca2ad14c49d7.png 

从零搭建 Vue3 + VIte + Ts 项目 —— 并集成eslint 、prettier、stylelint、husky、lint-staged、pinia、axios、loding、动态路由…_彩色之外的博客-CSDN博客

   实现功能:

  1. 取消重复请求:完全相同的接口在上一个pending状态时,自动取消下一个请求

  2. 请求失败自动重试: 接口请求后台异常时候, 自动重新发起多次请求, 直到达到所设次数

  3. 请求接口数据缓存: 接口在设定时间内不会向后台获取数据, 而是直接拿本地缓存

  4. 父页面单独取消当前请求、并发取消指定请求

  5. 父页面取消所有请求

  6. 更多功能根据你的需求自定制

目录

  🌍 第一 配置 vite.config.ts 基地址: 

  🤖 第二 配置环境变量:

 🛹 第三  配置ts类型

 🪂 第四 封装本地存储

 🎋 第五 封装axios: 

 👀 第六 页面使用:


🌍 第一 配置 vite.config.ts 基地址: 

  🤖 第二 配置环境变量:

🛹 第三  配置ts类型

src/type/axiso.d.ts

/* eslint-disable */
import * as axios from 'axios';

// 扩展 axios 数据返回类型,可自行扩展
declare module 'axios' {
	export interface AxiosResponse<T = any> {
		code: number;
		data: T;
		message: string;
		type?: string;
		/* 
		[key: string]: T; 这段代码是定义了一个索引签名,它表示可以使用任意字符串作为key,并且对应的值的类型是T。
		索引签名允许在对象中使用动态的属性,也就是说,在定义AxiosResponse接口时,除了预定义的code、data、message属性,还可以添
		加其他任意属性,且属性的值的类型是T。
	*/
		[key: string]: T;
	}
	export interface AxiosRequestConfig<T = any> {
		retry?: number;
		retryDelay?: number;
		cache?: boolean;
		cacheTimestamp?: number;
		[key: string]: T;
	}
	export interface AxiosError<T = any> {
		config: AxiosRequestConfig<T>;
		code?: string;
		request?: any;
		response?: AxiosResponse<T>;
		isAxiosError: boolean;
		retry?: number;
		retryDelay?: number;
		retryCount: number;
		cache?: boolean;
		cacheTimestamp?: number;
		[key: string]: T;
	}

	export interface CancelTokenSource<T = any> {
		token: CancelToken;
		cancel: Canceler;
		isFinished?: boolean;
		[key: string]: T;
	}
}

 🪂 第四 封装本地存储

/**
 * window.localStorage 浏览器永久缓存
 * @method set 设置永久缓存
 * @method get 获取永久缓存
 * @method remove 移除永久缓存
 * @method clear 移除全部永久缓存
 */
export const Local = {
	// 设置永久缓存
	set(key: string, val: any) {
		window.localStorage.setItem(key, JSON.stringify(val));
	},
	// 获取永久缓存
	get(key: string) {
		let json = <string>window.localStorage.getItem(key);
        // !null为true
		if (!json) return null;
		return JSON.parse(json);
	},
	// 移除永久缓存
	remove(key: string) {
		window.localStorage.removeItem(key);
	},
	// 移除全部永久缓存
	clear() {
		window.localStorage.clear();
	},
};

/**
 * window.sessionStorage 浏览器临时缓存
 * @method set 设置临时缓存
 * @method get 获取临时缓存
 * @method remove 移除临时缓存
 * @method clear 移除全部临时缓存
 */
export const Session = {
	// 设置临时缓存
	set(key: string, val: any) {
		window.sessionStorage.setItem(key, JSON.stringify(val));
	},
	// 获取临时缓存
	get(key: string) {
		let json = <string>window.sessionStorage.getItem(key);
		if (!json) return null;
		return JSON.parse(json);
	},
	// 移除临时缓存
	remove(key: string) {
		window.sessionStorage.removeItem(key);
	},
	// 移除全部临时缓存
	clear() {
		window.sessionStorage.clear();
	},
};

  🎋 第五 封装axios: 

新建 \src\api 文件夹,里面有三个ts文件,request.ts 封装axios统一请求,requestMethod.ts   封装的是请求方法,api.ts 封装的是api接口,方便统一管理不至于api接口分散项目各处造成不易维护。

src\api\request.ts

import axios, { AxiosError, AxiosInstance, AxiosRequestConfig, CancelTokenSource } from 'axios';
/* 
	1. 取消重复请求:完全相同的接口在上一个pending状态时,自动取消下一个请求 
	2. 请求失败自动重试: 接口请求后台异常时候, 自动重新发起多次请求, 直到达到所设次数 
	3. 请求接口数据缓存: 接口在设定时间内不会向后台获取数据, 而是直接拿本地缓存
	4. 父页面单独取消当前请求
	5. 父页面取消所有请求
*/

// 导入 element-plus 中的消息和弹框组件
import { ElMessage, ElMessageBox } from 'element-plus';
// 导入 storage.ts 中的 Session 对象
import { Session } from '/@/utils/storage';
// 导入 main.ts 中的 app 对象 用于Loading组件的显示和隐藏
import app from '/@/main';

// 创建 Axios 实例
const service: AxiosInstance = axios.create({
	baseURL: import.meta.env.VITE_API_URL, // 设置基础 URL
	timeout: 5000, // 设置超时时间
	headers: { 'Content-Type': 'application/json' }, // 设置请求头
});

// handlerRequest Start --------------------------------------------------------------------------

// cacheTimestamp用于判断是否存在相同缓存的请求
let cacheTimestampFlag = 0;
// requestKey用于缓存接口函数 判断是否存在相同的请求
let requestKey = '';
// 创建一个存储请求的Map对象
const pendingRequests: Map<string, CancelTokenSource> = new Map();
// 取消重复请求的方法
const cancelDuplicateRequest = (config: AxiosRequestConfig): void => {
	// 生成请求的唯一标识
	requestKey = `${config.method}-${config.url}`;

	// 如果已经存在该请求,则取消该请求
	if (pendingRequests.has(requestKey)) {
		const cancelToken = pendingRequests.get(requestKey);
		cancelToken?.cancel('进行中的重复请求被拦截,请您等待当前请求完成后再发起请求');
	}
	// 生成一个取消请求的标识
	const cancelToken = axios.CancelToken.source();
	// 将该请求保存到 pendingRequests 中
	pendingRequests.set(requestKey, cancelToken);
	// 设置取消请求的标识
	config.cancelToken = cancelToken.token;
	// 设置缓存时间
	if (config.cacheTimestamp) {
		cacheTimestampFlag = eval(`1000 * 60 * ${config.cacheTimestamp}`);
	}
	// 如果本地有缓存数据,直接返回缓存数据,不经过请求拦截
	if (config.cache) requestIsCache();
};
// 缓存接口函数 - 注意发起请求判断是否存在相同url
async function requestIsCache(): Promise<any> {
	// 获取本地存储的所有键
	const keys = Object.keys(sessionStorage);
	if (requestKey) {
		// 停留时间 > 缓存时间阈值
		const isCache = Date.now() - Session.get(requestKey)?.cacheTimestamp > cacheTimestampFlag;
		// console.log('是否有key', keys.includes(requestKey));
		// console.log('停留时间', Date.now() - Session.get(requestKey)?.cacheTimestamp);
		// console.log('判断阈值', cacheTimestampFlag);

		// 如果包含 urlKey 并且 缓存未过期
		if (keys.includes(requestKey) && !isCache) {
			// 直接返回本地缓存数据
			const cacheData = Session.get(requestKey);
			return Promise.resolve(cacheData);
		} else {
			// 清除缓存
			Session.remove(requestKey);
			// 不存在缓存 或 缓存已过期
			return Promise.reject('不存在缓存 或 缓存已过期');
		}
	}
}

// 封装提示函数 (token过期、重复登录)
const tipError = (value: string, title: string) => {
	ElMessageBox.alert(value, title, {
		confirmButtonText: title,
		type: 'warning',
	}).then(() => {
		Session.clear(); // 清除临时缓存
		// 清除cookie
		document.cookie.split(';').forEach(function (c) {
			document.cookie = c
				.replace(/^ +/, '')
				.replace(/=.*/, '=;expires=' + new Date().toUTCString() + ';path=/');
		});
		window.location.reload(); // 刷新页面
	});
};

// 请求失败自动重试的方法
const retryFailedRequest = async (error: AxiosError): Promise<any> => {
	const config = error;
	// 如果没有设置重试次数 或 已经达到最大重试次数,则直接返回错误
	if (!config || !config.retry || config.retryCount >= config.retry) {
		return Promise.reject(config);
	}
	// 设置重试次数关闭阈值
	config.retryCount = config.retryCount || 0;
	// 重试次数自增
	config.retryCount += 1;
	// 设置重试延时
	const delay = config.retryDelay || 1000;
	// 延时处理
	await new Promise<void>((resolve) => {
		setTimeout(() => resolve(), delay);
	});
	// console.log('🤺🤺  🚀 ==>:', service(config));
	return service(config);
};
// handlerRequest End --------------------------------------------------------------------------

// Axios 的请求拦截器期望返回一个配置对象,而不是响应对象。如果你试图返回一个响应对象,Axios 将会抛出一个错误。
service.interceptors.request.use(
	async (config: AxiosRequestConfig) => {
		// 在发送请求之前做些什么?
		const token = Session.get('token');
		if (token) config.headers!['token'] = token; // 在请求头中添加 token
		// 取消重复请求
		cancelDuplicateRequest(config);

		app.config.globalProperties.$smallLoading.showLoading();
		return config;
	},
	async (error) => {
		// 对请求错误做些什么?
		app.config.globalProperties.$smallLoading.hideLoading();
		// 请求失败重试
		await retryFailedRequest(error);
		return Promise.reject(error);
	}
);

// 响应拦截器
service.interceptors.response.use(
	(response) => {
		// 对响应数据做点什么? 这里只返回成功响应数据!
		const { config, data } = response;
		// 给 pendingRequests 标记一个isFinished为true 请求完成的标识
		const responseKey = `${config.method}-${config.url}`;
		const request = pendingRequests.get(responseKey);
		if (request && request.token) {
			pendingRequests.set(responseKey, { ...request, isFinished: true });
		}

		const cacheKey = `${config.method}-${config.url}`;
		app.config.globalProperties.$smallLoading.hideLoading();

		// 判断是否有缓存
		if (config.cache) {
			const cachedResponse = Session.get(cacheKey);
			if (cachedResponse) {
				return cachedResponse;
			} else {
				// 接口有 cache 参数,且缓存不存在,则缓存接口数据,并插入当前时间戳
				data.cacheTimestamp = new Date().getTime();
				Session.set(cacheKey, data);
				return data;
			}
		} else {
			return data;
		}
	},
	(error) => {
		// 对响应错误数据做点什么?这里只显示错误消息!
		app.config.globalProperties.$smallLoading.hideLoading();
		/* 
			axios.isCancel(error) 是 Axios 库中的一个方法,用于判断一个错误对象是否是由于请求取消导致的。
			当使用 axios.CancelToken 取消请求时,会抛出一个带有一个 message 属性的错误对象。
			axios.isCancel(error) 的作用就是判断这个错误对象的类型,如果是由请求取消导致的错误,则返回 true,否则返回 false。
		    console.log('打印cancelToken.cancel('xxx')传入来的值', error.message);
	    */
		if (axios.isCancel(error)) {
			// 只提示请求取消有主动填写的消息 如:cancelToken.cancel('xxx')
			if (error.message !== 'canceled') ElMessage.error(requestKey + ' 🤖 ' + error.message);
		} else {
			// 响应失败重试
			retryFailedRequest(error);
			// 不是由请求取消导致的错误
			let errorMessage; // 错误提示变量
			let statusData = error.response?.data; // 错误data数据
			const describeForNameMap = [
				[
					() => error.message.indexOf('timeout') !== -1,
					() => (errorMessage = '网络超时 🤖'),
				],
				[() => error.message === 'Network Error', () => (errorMessage = '网络连接错误 🤪')],
				[
					() => statusData?.code === 100010021,
					() => {
						// 100010021 重复登录
						errorMessage = statusData.message;
						tipError(errorMessage, '重复登录');
					},
				],
				[
					() => statusData?.code === 100010011,
					() => {
						// 100010011 token过期
						errorMessage = statusData.message;
						tipError(errorMessage, '登录过期');
					},
				],
				// 否则 显示错误消息,这里要根据后台返回的数据结构来定
				[() => statusData, () => (errorMessage = statusData.message)],
			];
			// 获取符合条件的子数组
			const getDescribe = describeForNameMap.find((item) => item[0]());
			// 执行子数组中的函数
			getDescribe && getDescribe[1]();
			ElMessage.error(errorMessage); // 显示错误消息
		}
	}
);
// 取消全部请求的方法
export const cancelAllRequest = (): void => {
	// 创建一个标记 是否取消成功,初始值为false
	let hasCancelled = false;

	// 遍历所有待处理的请求
	pendingRequests.forEach((value) => {
		// 如果请求还没有完成
		if (!value.isFinished) {
			// 取消请求
			value.cancel();
			// 将标记设为true
			hasCancelled = true;
		}
	});

	// 清空待处理请求的集合
	pendingRequests.clear();

	// 至少取消了一个请求,显示提示,防止都是成功请求点击取消按钮时也提示
	if (hasCancelled) {
		ElMessage.success('成功取消全部请求');
	}
};

// 取消当前请求的方法
export const cancelCurrentRequest = (payloadCurrentKey: string = requestKey): void => {
	// 遍历所有待处理的请求
	pendingRequests.forEach((value, key) => {
		if (key === payloadCurrentKey) {
			value.cancel();
			pendingRequests.delete(key);
			ElMessage.success('成功取消当前请求');
		}
	});
};

// 导出 service将其命名为axios , requestIsCache 用于判断是否有缓存
export { service as axios, requestIsCache };

src\api\requestMethod.ts

import { axios } from './request';
// post使用data接受参数,get使用params接受参数
// 如果是post请求,但是参数是在url上的,那么就要使用params接受参数,否则使用data接受参数
// put 也相当与post请求,如果报参数错误,就是接受参数的请求体错了post/put用data,get请求用params
type method = 'GET' | 'POST' | 'PUT' | 'DELETE';
// 规定缓存时间戳的类型只能 1 - 5 分钟
type cacheTimestamp = 1 | 2 | 3 | 4 | 5;

/**
 * @name request 配置
 * @param { string } - method 请求方法
 * @param { string } - url   请求地址
 * @param { object } - data/params 请求参数
 * @param { number } - retry  重试次数
 * @param { number } - retryDelay 重试延迟
 * @param { boolean } -  cache 是否缓存
 * @param { number }  - cacheTimestamp 缓存过期 1-5分钟
 * @createDate 2023/08/09 13:12:08
 * @lastFixDate 2023/08/09 13:12:08
 */
interface requestConfig {
	method: method;
	url: string;
	data?: object;
	params?: object;
	retry?: number;
	retryDelay?: number;
	cache?: boolean;
	cacheTimestamp?: cacheTimestamp;
}

function request({
	method = 'GET',
	url,
	data = {},
	params = {},
	retry,
	retryDelay,
	cache,
	cacheTimestamp = 1,
}: requestConfig) {
	return axios({
		method,
		url,
		data,
		params,
		retry,
		retryDelay,
		cache,
		cacheTimestamp,
	});
}

export default request;

src\api/auth-manage/menu.ts

// 导入axios中的AxiosResponse的泛型接口
import { AxiosResponse } from 'axios';
// 导入封装的axios请求方法
import request from '/@/utils/requestMethod';

// 获取菜单树接口: 不包含菜单中的按钮
export const getMenuTree = (): Promise<AxiosResponse<any>> =>
	request({
		method: 'POST',
		url: '/menu/queryMenuTree',
		cache: true,
		cacheTimestamp: 1,
	});

👀 第六 页面使用:

<script setup lang="ts" name="importGeneration">
import { getMenuTree } from '/@/api/auth-manage/menu';
import { getUserInfo } from '/@/api/auth-manage/user';
import { ElMessage } from 'element-plus';

/* 
 requestIsCache - 判断请求是否开启了缓存
 cancelAllRequest - 取消所有请求
 cancelCurrentRequest - 取消当前请求
*/
import { requestIsCache, cancelAllRequest, cancelCurrentRequest } from '/@/utils/request';

// 封装请求错误提示 http状态是200 但是code不是200 返回数据是错误的需要处理
function tipError<T extends { code: number; message: string }>(res: T) {
	if (res.code !== 200) {
		ElMessage.error(res.message);
		return;
	}
}
// 发起 getMenuTree
const handleClick = async () => {
	// 缓存函数,如果在接口开启了cache: true,需要在请求前调用此函数
	await requestIsCache()
		.then((res) => {
			if (!res) return;
			tipError(res);
			ElMessage.success('本地数据请求成功');
		})
		.catch(() => {
			getMenuTree().then((res) => {
				if (!res) return;
				tipError(res);
				ElMessage.success(res.message);
			});
		});
};

// 取消 getMenuTree
const handleCance = () => {
	// 在适当的时机调用取消请求(例如点击取消按钮),不传参数默认取消最后一条请求
	cancelCurrentRequest('post-/menu/queryMenuTree');
};

// 取消 getUserInf
const handleCance1 = () => {
	cancelCurrentRequest('get-/user/info');
};

// 发起 getUserInf
const handleClick1 = async () => {
	await getUserInfo().then((res) => {
		if (!res) return;
		tipError(res);
		ElMessage.success(res.message);
	});
};

// 取消所有请求
function handleCancelAll() {
	cancelAllRequest();
}
</script>

<template>
	<div>
		<!-- 发起 -->
		<el-button type="primary" @click="handleClick">发起 getMenuTree axios</el-button>
		<!-- 取消 -->
		<el-button type="danger" @click="handleCance">取消 getMenuTree</el-button>
		<!-- 发起 -->
		<el-button type="primary" @click="handleClick1">发起 getUserInf axios</el-button>
		<!-- 取消 -->
		<el-button type="danger" @click="handleCance1">取消 getUserInf</el-button>
		<el-button type="danger" @click="handleCancelAll">取消所有请求</el-button>
	</div>
</template>

全文结束,所有代码都在文中,最上面的链接中也有原项目

7730e2bd39d64179909767e1967da702.jpeg

 _______________________________  期待再见  _______________________________ 

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

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

相关文章

Flink多流处理之coGroup(协同分组)

这篇文章主要介绍协同分组coGroup的使用,先讲解API代码模板,后面会结图解介绍coGroup是如何将流中数据进行分组的. 1 API介绍 数据源# 左流数据 ➜ ~ nc -lk 6666 101,Tom 102,小明 103,小黑 104,张强 105,Ken 106,GG小日子 107,小花 108,赵宣艺 109,明亮# 右流数据 ➜ ~ n…

Discuz论坛网站防复制代码分享

代码一 <script language"JavaScript">document.onselectstartnew Function("event.returnValuefalse;");</script>或是 代码二 <body onmousemove\HideMenu()\ oncontextmenu"return false" ondragstart"return false&q…

springboot整合JMH做优化实战

这段时间接手项目出现各种问题&#xff0c;令人不胜烦扰。吐槽下公司做项目完全靠人堆&#xff0c;大上快上风格注定留下一地鸡毛&#xff0c;修修补补不如想如何提升同事代码水准免得背锅。偶然看到关于JMH对于优化java代码的直观性&#xff0c;于是有了这篇文章&#xff0c;希…

浮动路由解决单点链路故障问题(第三十三课)

浮动路由解决单点链路故障问题(第三十三课) 理论来源于实践 1 路由的分类 2 直连路由: PC>ping 192.168.5.11Ping 192.168.5.11: 32 data bytes, Press Ctrl_C to break Request timeout! Request timeout! Request timeout! Request timeout! Request timeout!--- 192.16…

电脑ip地址怎么改 ip地址怎么改到别的城市

一、ip地址怎么改到别的城市 1.ip地址怎么改到别的城市&#xff0c;1、重启WIFI路由设备 一般手机或电脑在家或公司上网时都是接入到路由器的WIFI网络,再由路由器分配上网IP地址,如果要更换上网IP那么重启路由器设备后,路由器会向网络运营商进行宽带的重新拨号,此时手机或电脑设…

小程序自动化测试的示例代码

目录 背景 自动化 SDK 还原用户行为 总结 背景 上述描述看似简单&#xff0c;但是中间还是有些难点的&#xff0c;第一个难点就是如何在业务人员操作小程序的时候记录操作路径&#xff0c;第二个难点就是如何将记录的操作路径进行还原。 自动化 SDK 如何将操作路径还原这…

直接在html中引入Vue.js的cdn来实现一个简单的上传图片组件

摘要 当使用 Vue.js 的 CDN 来实现一个简单的上传图片组件时&#xff0c;你可以利用 Vue 的数据绑定和事件处理能力&#xff0c;结合 HTML 和 CSS&#xff0c;轻松地创建一个交互式的图片上传界面。以下是一个示例&#xff1a; 代码结构 index.html <!DOCTYPE html> &…

springcloud3 springcloud stream的学习以及案例

一 springcloud stream的作用 1.1 springcloud stream作用 stream屏蔽底层消息中间件的差异&#xff0c;降低切换成本&#xff0c;统一消息的编程模型。 stream中的消息通信模式遵循了“发布-订阅”模式。 1.2 Binder作用 通过定义绑定器Binder作为中间层&#xff0c;实现…

tomcat7.exe 启动闪退解决

标题tomcat7.exe 启动闪退解决 双击tomcat7.exe启动&#xff0c;但是出现闪退问题&#xff0c;无法启动tomcat 解决&#xff1a; 1.解决 tomcat7.exe 启动闪退解决 第一步&#xff1a;双击打开tomcat7w.exe 文件 如果出现 “指定的服务未安装。 Unable to open the service ‘…

编译iOS系统可用的FFmpeg

在进行编译之前&#xff0c;需要做一些准备工作安装必备文件&#xff1a; 1 安装 gas-preprocessor FFmpeg-iOS-build-script 自动编译脚本需要使用到 gas-preprocessor . 执行 sudo git clone https://github.com/bigsen/gas-preprocessor.git /usr/local/bin/gas sudo c…

工作每天都在用的 DNS 协议,你真的了解么?(文末有惊喜,别错过)

♥ 前 言 我们经常访问一些网址的时候&#xff0c;浏览器里输入类似于 www.baidu.com 这样的地址&#xff0c;那么在浏览器里输入这个地址---> 百度服务器给我们返回这个百度的页面&#xff0c;中间的过程是什么样的呢&#xff1f; 带着这个问题&#xff0c;我们一起来解…

HECI-Securtiy 防火墙路由技术

目录 一、防火墙路由基本原理 1.路由分类 2.路由优先级 3.路由查询先后顺序 4.静态路由基本原理 &#xff08;1&#xff09;指定出接口场景 &#xff08;2&#xff09;指定下一跳地址场景 5.静态路由与多出口 &#xff08;1&#xff09;主备备份 &#xff08;2&#…

FastSAM初体验,比SAM快50倍

一、FastSAM介绍 1.简介 由美国Meta公司提出的能够“分割一切”的视觉基础大模型SAM引起了较大影响&#xff0c;为探索通用视觉大模型提供了一个新的方向。 2023年6月22日&#xff0c;中科院自动化所的研究团队针对“分割一切”任务&#xff0c;提出了FastSAM方法。中科院自动…

Boost开发指南-4.3optional

optional 在实际的软件开发过程中我们经常会遇到“无效值”的情况&#xff0c;例如函数并不是总能返回有效值&#xff0c;很多时候函数正确执行了&#xff0c;但结果却不是合理的值。如果用数学语言来解释&#xff0c;就是返回值位于函数解空间之外。 求一个数的倒数&#xf…

Twitter霸屏:掌握社交流量密码

Twitter群推王发现&#xff0c;Twitter以其简短而有力的信息传递方式而著名&#xff0c;其中字符限制仅有280个。这意味着在Twitter的世界中&#xff0c;迅速高效的沟通至关重要。拥有约321亿月活跃用户的Twitter&#xff0c;成为塑造资源品牌知名度的强大平台。如今&#xff0…

PHP智能人才招聘网站mysql数据库web结构apache计算机软件工程网页wamp

一、源码特点 PHP智能人才招聘网站 是一套完善的web设计系统&#xff0c;对理解php编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。 下载地址 https://download.csdn.net/download/qq_41221322/88199392 视频演示 PH…

Alpine Ridge控制器使其具备多种使用模式 - 英特尔发布雷电3接口:竟和USB Type-C统一了

同时又因为这建立在Type-C的基础上&#xff0c;雷电3也将利用现有的标准Type-C线缆引入有源支持。当使用Type-C的线缆时&#xff0c;雷电的速度就降到了20Gbps全双工——这与普通的Type-C的带宽相同——这是为了成本牺牲了一些带宽。可以比较一下&#xff0c;Type-C线的成本只有…

基于Kubeadm部署k8s集群:下篇

继续上篇内容 目录 7、安装flannel 8、节点管理命令 三、安装Dashboard UI 1、部署Dashboard 2、开放端口设置 3、权限配置 7、安装flannel Master 节点NotReady 的原因就是因为没有使用任何的网络插件&#xff0c;此时Node 和Master的连接还不正常。目前最流行的Kuber…

【内网穿透】实现无公网IP远程连接Linux服务器并安装部署MongoDB数据库

【内网穿透】实现无公网IP远程连接Linux服务器并安装部署MongoDB数据库 ​ 憧憬Blog主页 在强者的眼中&#xff0c;没有最好&#xff0c;只有更好。 全栈开发领域优质创作者&#xff0c;阿里云专家博主 文章目录 【内网穿透】实现无公网IP远程连接Linux服务器并安装部署MongoDB…

智慧工地源码,Spring Cloud+ Vue+UniApp开发,微服务架构

智慧工地源码&#xff0c;智慧工地云平台源码 智慧工地APP源码 智慧工地的核心是数字化&#xff0c;它通过传感器、监控设备、智能终端等技术手段&#xff0c;实现对工地各个环节的实时数据采集和传输&#xff0c;如环境温度、湿度、噪音等数据信息&#xff0c;将数据汇集到云…