最近在写视频课程的上传,需要上传的视频几百MB到几个G不等,普通的上传都限制了文件的大小,况且上传的文件太大的话会超时、异常等。所以这时候需要考虑分片上传了,把需要上传的视频分成多个小块上传到,最后再合并成一个视频进行存储。我这里上传到自有的Minio,其它存储应该也是大同小异。

前端:vue3+vue-simple-uploader
后端:springboot+Minio

先看前端效果

vue-simple-uploader原生组件的样式效果很不错了,vue-simple-uploader文档
在这里插入图片描述
下面这个是我上传成功后自定义的样式
在这里插入图片描述

再来看看流程图思路

在这里插入图片描述

有了流程我们就直接上代码了

1、前端安装vue-simple-uploader

npm install vue-simple-uploader@next --save

2、在main.ts引入

import uploader from 'vue-simple-uploader'
import 'vue-simple-uploader/dist/style.css';
// ...
const app = createApp(App)
// ...
// 引入上传组件
app.use(uploader)

3、前端组件全部代码

<template><uploaderref="uploaderRef":options="options":auto-start="false":fileStatusText="fileStatusText"class="uploader-container"@file-added="onFileAdded"@file-progress="onFileProgress"@file-success="onFileSuccess"@file-error="onFileError"@file-removed="onDelete"><uploader-unsupport>您的浏览器不支持上传组件</uploader-unsupport><uploader-drop><uploader-btn :attrs="attrs">{{deProps.btnText}}</uploader-btn></uploader-drop><uploader-list><template #default="props"><!-- 已上传的文件列表 --><div v-for="file in uploadFileList" :key="file.fileId" :file="file" class="raw-list"><div class="file-title">{{ file.name }}</div><div class="file-size">{{ parseFloat(file.size / 1024 / 1024).toFixed(2) }} MB</div><div class="file-status">已上传</div><el-button size="mini" type="danger" @click="onDelete(uploadFileList, file)">删除文件</el-button></div><!-- 正在上传的文件列表 --><uploader-file v-for="file in props.fileList" :key="file.fileId" :file="file" :list="true" v-show="!file.completed" /></template></uploader-list></uploader>
</template><script setup>
import axios from 'axios';
import { getAccessToken,getTenantId } from '@/utils/auth';
import { propTypes } from '@/utils/propTypes'
const token = getAccessToken();
import {config} from '@/config/axios/config';
const emit = defineEmits(['success','delete'])const fileStatusText = {success: '上传成功',error: '上传失败',uploading: '正在上传',paused: '暂停上传',waiting: '等待上传'
};
// 原来已上传过的文件列表
const uploadFileList = ref([]);
// const uploader = ref(null);
const currentFile = ref(null);
const isPaused = ref(false);const deProps = defineProps({btnText: propTypes.string.def('选择文件上传') ,fileList: propTypes.array.def([]), // 原来已上传的文件列表singleFile: propTypes.bool.def(true), // 是否单文件上传
})uploadFileList.value = deProps.fileList;/*** 上传组件配置*/
const options = {target: config.base_url + '/infra/minio/upload', // 目标上传 URLheaders: {'tenant-id': getTenantId(),'Authorization': `Bearer ${token}`}, // 接口的定义, 根据实际情况而定chunkSize: 5 * 1024 * 1024, // 分块大小singleFile:  deProps.singleFile, // 是否单文件上传simultaneousUploads: 3, // 同时上传3个分片forceChunkSize: true, // 是否强制所有的块都是小于等于 chunkSize 的值。默认是 false。// fileParameterName: 'file', // 上传文件时文件的参数名,默认filemaxChunkRetries: 3, // 最大自动失败重试上传次数testChunks: false, // 是否开启服务器分片校验// 额外的请求参数query: (file) => {return {uploadId: file.uploadId,fileName: file.name,totalChunks: file.chunks.length};},// 处理请求参数, 将参数名字修改成接口需要的processParams: (params, file, chunk) => {params.chunkIndex = chunk.offset; // 分片索引return params;}
};
// 限制上传的文件类型
const attrs = {accept: '.mp4,.png,.jpg,.txt,.pdf,.ppt,.pptx,.doc,docx,.xls,xlsx,.ofd,.zip,.rar',
};const uploaderRef = ref(null);/*** 模版中禁止了自动上传(:auto-start="false")*/
const onFileAdded = async (file) => {currentFile.value = file;isPaused.value = false;try {// 1. 初始化上传会话const initResponse = await axios.post(`${config.base_url}/infra/minio/init`,{ fileName: file.name, fileSize: file.size },{headers: {'tenant-id': getTenantId(),'Authorization': `Bearer ${token}`},});if (initResponse.data.code === 0) {file.uploadId = initResponse.data.data;} else {throw new Error(initResponse.data.msg);}// 2. 获取已上传分片列表const chunksResponse = await axios.get(`${config.base_url}/infra/minio/uploaded-parts/${file.uploadId}`,{params: { totalChunks: file.chunks.length },headers: {'tenant-id': getTenantId(),'Authorization': `Bearer ${token}`},},);if (chunksResponse.data.code === 0) {// 设置已上传分片file.uploadedChunks = chunksResponse.data.data || [];}// 开始上传file.resume();} catch (error) {file.cancel();console.error('初始化上传出错:', error);}
};// 上传进度事件
const onFileProgress = (rootFile, file, chunk) => {// 使用 progress() 方法获取上传进度const progress = Math.round(file.progress() * 100);console.log(`上传进度: ${progress}%`);
};// 文件上传成功
const onFileSuccess = async (rootFile, file, response) => {try {// 调用合并接口const mergeResponse = await axios.post(config.base_url + '/infra/minio/merge', {uploadId: file.uploadId,fileName: file.name},{headers: {'tenant-id': getTenantId(),'Authorization': `Bearer ${token}`},});if (mergeResponse.data.code === 0) {// 添加到已上传文件列表uploadFileList.value.push({fileId: file.uploadId,name: file.name,size: file.size,url: mergeResponse.data.data});} else {console.error('文件合并失败:', mergeResponse.data.msg);}console.log(uploadFileList.value)emit('success', uploadFileList.value);} catch (error) {console.error('文件合并请求失败:', error);} finally {currentFile.value = null;}
};// 文件上传失败
const onFileError = (rootFile, file, message) => {console.error('文件上传失败:', message);currentFile.value = null;
};// 暂停/继续上传
const togglePause = () => {if (!currentFile.value) return;if (isPaused.value) {currentFile.value.resume();} else {currentFile.value.pause();}isPaused.value = !isPaused.value;
};// 取消上传
const onCancel = async (file) => {if (file.status === 'uploading') {try {// 调用后端取消接口await axios.post(`${config.base_url}/infra/minio/cancel/${file.uploadId}`,{headers: {'tenant-id': getTenantId(),'Authorization': `Bearer ${token}`},});file.cancel();} catch (error) {console.error('取消上传失败:', error);}} else {file.cancel();}currentFile.value = null;
};// 删除文件
const onDelete = async (list,file) => {try {// 调用后端删除接口await axios.delete(`http://localhost:48080/admin-api/infra/minio/delete?path=${file.url}`,{headers: {'tenant-id': getTenantId(),'Authorization': `Bearer ${token}`},});// 从列表中移除const index = list.findIndex(f => f.fileId === file.fileId);if (index !== -1) {list.splice(index, 1);}emit('delete', list);} catch (error) {console.error('删除文件失败:', error);}
};
</script>
<style lang="scss" scoped>
.uploader-container {border: 1px solid #eee;border-radius: 4px;padding: 15px;
}.raw-list{display: flex;align-items: center;justify-content: space-between;padding: 10px 20px;.file-title{width: 30%;overflow: hidden;text-overflow: ellipsis;white-space: nowrap;}
}</style> 

4、使用组件

<upload-chunk 
ref="uploadChunkRef" 
btnText="上传视频" 
:fileList="fileList" 
@success="uploadChunkSuccess" 
@delete="uploadChunkDelete" />

前端的代码就这么多,接下来看看后端。

1、添加minio依赖

<dependency><groupId>io.minio</groupId><artifactId>minio</artifactId><version>8.5.7</version>
</dependency>

2、创建3个请求实体类

public class FileChunkInitReqVO {private String fileId;private String fileName;private Long fileSize;
}public class FileChunkMergeReqVO {private String uploadId;private String fileName;
}public class FileChunkUploadReqVO {private String uploadId;private String fileName;private Integer chunkIndex;private Integer totalChunks;private MultipartFile file;
}

3、Controller类

/*** 删除指定文件** @param path 文件ID* @return 响应状态*/
@DeleteMapping("/delete")
@PermitAll
public CommonResult<String> deleteFile(@RequestParam String path) {try {fileService.deleteFile(path);return CommonResult.success("File deleted successfully.");} catch (IOException | NoSuchAlgorithmException | InvalidKeyException e) {return CommonResult.error(HttpStatus.INTERNAL_SERVER_ERROR.value(), "Error deleting file: " + e.getMessage());}
}///初始化分片上传
@PostMapping("/init")
@PermitAll
public CommonResult<String> initUploadSession(@RequestBody FileChunkInitReqVO reqVO) {try {String uploadId = fileService.initUploadSession(reqVO.getFileName(), reqVO.getFileSize());return CommonResult.success(uploadId);} catch (Exception e) {log.error("初始化上传会话失败", e);return CommonResult.error(HttpStatus.INTERNAL_SERVER_ERROR.value(), "初始化上传会话失败");}
}//上传文件分片
@PostMapping("/upload")
@PermitAll
public CommonResult<Boolean> uploadFilePart(@Validated FileChunkUploadReqVO reqVO) {try {boolean result = fileService.uploadFilePart(reqVO.getUploadId(),reqVO.getChunkIndex(),reqVO.getTotalChunks(),reqVO.getFile());return CommonResult.success(result);} catch (Exception e) {log.error("上传分片失败", e);return CommonResult.error(HttpStatus.INTERNAL_SERVER_ERROR.value(), "上传分片失败");}
}//获取已上传分片列表
@GetMapping("/uploaded-parts/{uploadId}")
@PermitAll
public CommonResult<List<Integer>> getUploadedParts(@PathVariable String uploadId,@RequestParam int totalChunks) {try {List<Integer> uploadedParts = fileService.getUploadedParts(uploadId, totalChunks);return CommonResult.success(uploadedParts);} catch (Exception e) {log.error("获取已上传分片失败", e);return CommonResult.error(HttpStatus.INTERNAL_SERVER_ERROR.value(), "获取已上传分片失败");}
}//合并文件分片
@PostMapping("/merge")
@PermitAll
public CommonResult<String> mergeFileParts(@RequestBody FileChunkMergeReqVO reqVO) {try {String fileUrl = fileService.mergeFileParts(reqVO.getUploadId(),reqVO.getFileName());return CommonResult.success(fileUrl);} catch (Exception e) {log.error("合并文件失败", e);return CommonResult.error(HttpStatus.INTERNAL_SERVER_ERROR.value(), "合并文件失败");}
}//取消上传
@PostMapping("/cancel/{uploadId}")
@PermitAll
public CommonResult<Boolean> cancelUpload(@PathVariable String uploadId) {try {fileService.cancelUpload(uploadId);return CommonResult.success(true);} catch (Exception e) {log.error("取消上传失败", e);return CommonResult.error(HttpStatus.INTERNAL_SERVER_ERROR.value(), "取消上传失败");}
}

5、service类


@Service
public class MinioChunkUploadService {@Resourceprivate FileMapper fileMapper;// 改成自己的private static final String endpoint = "http://xxxx";private static final String accessKey = "BDnZ1SS3Kq0pxxxxxx";private static final String accessSecret = "MAdjW4rd0hXoZNrxxxxxxxx";private static final String bucketName = "xxtigrixxx";private static final String CHUNK_PREFIX = "chunks/";private static final String MERGED_PREFIX = "merged/";/*** 创建 Minio 客户端* @return*/private MinioClient createMinioClient() {return MinioClient.builder().endpoint(endpoint).credentials(accessKey, accessSecret).build();}/*** 删除指定文件** @param path 文件路径*/public void deleteFile(String path) throws IOException, NoSuchAlgorithmException, InvalidKeyException {MinioClient minioClient = createMinioClient();try {String fileNames = MERGED_PREFIX + path;// 删除文件minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(fileNames).build());} catch (MinioException e) {throw new IOException("Error deleting file: " + e.getMessage(), e);}}// 新增方法:检查分片是否已存在public boolean checkChunkExists(String fileId, int chunkIndex)throws IOException, NoSuchAlgorithmException, InvalidKeyException {MinioClient minioClient = createMinioClient();try {String objectName = fileId + "/chunk-" + chunkIndex;minioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(objectName).build());return true;} catch (Exception e) {throw new IOException("Error checking chunk existence: " + e.getMessage(), e);}}// 新增方法:获取已上传分片列表public List<Integer> getUploadedChunks(String fileId)throws IOException, NoSuchAlgorithmException, InvalidKeyException {MinioClient minioClient = createMinioClient();List<Integer> uploadedChunks = new ArrayList<>();// 列出所有分片Iterable<Result<Item>> results = minioClient.listObjects(ListObjectsArgs.builder().bucket(bucketName).prefix(fileId + "/chunk-").build());for (Result<Item> result : results) {Item item = null;try {item = result.get();} catch (Exception e) {throw new RuntimeException(e);}String objectName = item.objectName();// 提取分片索引: fileId/chunk-123String chunkStr = objectName.substring(objectName.lastIndexOf("-") + 1);try {int chunkIndex = Integer.parseInt(chunkStr);uploadedChunks.add(chunkIndex);} catch (NumberFormatException ignored) {// 忽略无效分片名}}return uploadedChunks;}/*** 初始化上传会话*/public String initUploadSession(String fileName, long fileSize) {// 生成唯一上传IDreturn UUID.randomUUID().toString();}/*** 上传文件分片*/public boolean uploadFilePart(String uploadId, int chunkIndex, int totalChunks, MultipartFile filePart)throws IOException, NoSuchAlgorithmException, InvalidKeyException, MinioException {// 构建分片对象名称String objectName = CHUNK_PREFIX + uploadId + "/" + chunkIndex;MinioClient minioClient = createMinioClient();// 上传文件分片minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object(objectName).stream(filePart.getInputStream(), filePart.getSize(), -1).contentType(filePart.getContentType()).build());return true;}/*** 获取已上传分片列表*/public List<Integer> getUploadedParts(String uploadId, int totalChunks)throws IOException, NoSuchAlgorithmException, InvalidKeyException, MinioException {List<Integer> uploadedParts = new ArrayList<>();MinioClient minioClient = createMinioClient();// 列出所有分片Iterable<Result<Item>> results = minioClient.listObjects(ListObjectsArgs.builder().bucket(bucketName).prefix(CHUNK_PREFIX + uploadId + "/").build());for (Result<Item> result : results) {try {Item item = result.get();String objectName = item.objectName();// 提取分片索引: chunks/uploadId/123String chunkIndexStr = objectName.substring(objectName.lastIndexOf("/") + 1);uploadedParts.add(Integer.parseInt(chunkIndexStr));} catch (Exception e) {System.out.println(e.getMessage());}}return uploadedParts;}/*** 合并文件分片*/public String mergeFileParts(String uploadId, String fileName)throws IOException, NoSuchAlgorithmException, InvalidKeyException, MinioException {// 获取所有分片List<String> partNames = new ArrayList<>();List<ComposeSource> sources = new ArrayList<>();MinioClient minioClient = createMinioClient();Iterable<Result<Item>> results = minioClient.listObjects(ListObjectsArgs.builder().bucket(bucketName).prefix(CHUNK_PREFIX + uploadId + "/").build());for (Result<Item> result : results) {Item item = result.get();partNames.add(item.objectName());sources.add(ComposeSource.builder().bucket(bucketName).object(item.objectName()).build());}// 按分片索引排序sources.sort((a, b) -> {int indexA = Integer.parseInt(a.object().substring(a.object().lastIndexOf("/") + 1));int indexB = Integer.parseInt(b.object().substring(b.object().lastIndexOf("/") + 1));return Integer.compare(indexA, indexB);});// 构建最终文件对象名称String finalObjectName = MERGED_PREFIX + DateUtil.format(LocalDateTime.now(), "yyyyMMdd") + "/" + uploadId + "/" + fileName;// 合并文件minioClient.composeObject(ComposeObjectArgs.builder().bucket(bucketName).object(finalObjectName).sources(sources).build());// 删除分片文件for (String partName : partNames) {minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(partName).build());}// 返回文件访问URLreturn finalObjectName;}/*** 取消上传*/public void cancelUpload(String uploadId)throws IOException, NoSuchAlgorithmException, InvalidKeyException, MinioException {MinioClient minioClient = createMinioClient();// 删除所有分片Iterable<Result<Item>> results = minioClient.listObjects(ListObjectsArgs.builder().bucket(bucketName).prefix(CHUNK_PREFIX + uploadId + "/").build());for (Result<Item> result : results) {Item item = result.get();minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(item.objectName()).build());}}
}

OK,全部代码完成,有问题或哪里不对的地方欢迎指正

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

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

相关文章

AI 重构代码实战:如何用飞算 JavaAI 快速升级遗留系统?

在企业数字化进程中&#xff0c;遗留系统如同陈旧的基础设施&#xff0c;虽承载着重要业务逻辑&#xff0c;但因技术落后、架构复杂&#xff0c;升级维护困难重重。飞算 JavaAI 的出现&#xff0c;为遗留系统的二次开发带来了新的转机&#xff0c;其基于智能分析与关联项目的技…

鸿蒙运动开发实战:打造专属运动视频播放器

##鸿蒙核心技术##运动开发##Media Kit&#xff08;媒体服务&#xff09;# 在当今数字化时代&#xff0c;运动健身已经成为许多人生活的一部分。今天我将在应用中添加视频播放器&#xff0c;帮助用户在运动前、运动后更好地进行热身和拉伸。这篇文章将从代码核心点入手&#xf…

一个包含15个界面高质量的电商APP客户端UI解决方案

一个包含15个界面高质量的电商APP客户端UI解决方案 您可以将其用于电商APP应用项目。包含一系列完整的界面设计元素&#xff0c;包括欢迎页、登录、注册、首页、产品分类、产品详情、尺码选择、购物车、订单、支付&#xff0c;覆盖电商APP的大部分界面。每个部分都精心设计&…

执行 PGPT_PROFILES=ollama make run下面报错,

执行 PGPT_PROFILESollama make run 下面报错&#xff0c; File "/home/powersys/.cache/pypoetry/virtualenvs/private-gpt-ZIwX6JeM-py3.11/lib/python3.11/site-packages/qdrant_client/http/api_client.py", line 108, in send_inner raise ResponseHandling…

【Docker基础】Docker核心概念:命名空间(Namespace)之User详解

目录 引言 1 基础概念回顾 1.1 命名空间概述 1.2 命名空间的类型 2 User命名空间详解 2.1 基本概念 2.2 工作原理 User命名空间的工作流程 User命名空间架构 3 应用场景 4 配置与使用 5 总结 引言 随着容器化技术的广泛应用&#xff0c;Docker已成为现代软件开发、…

DIDCTF-应急响应

前言 最近在学长分享应急响应与电子取证的知识&#xff0c;又恰逢期末周没有课&#xff0c;记录自己在取证道路的成长。 linux-basic-command 下载附件&#xff0c;得到Apache 服务器访问日志文件&#xff0c;根据题目要求找出排名前五的ip&#xff0c;题目提示写脚本&#…

MybatisPlus深入学习

今天深入的学习了一下mp&#xff0c;从头开始学习&#xff01;哈哈哈哈哈 本节只讲干的&#xff01; 我们上来先看一段代码&#xff0c;不知道你能不能看明白&#xff01; package com.itheima.mp.mapper;import com.baomidou.mybatisplus.core.conditions.query.QueryWrapp…

安卓9.0系统修改定制化____安卓9.0系统精简 了解系统app组件构成 系统app释义 常识篇 一

在安卓 9.0 系统的使用过程中,许多用户都希望能够对系统进行深度定制,让手机系统更加贴合个人需求。而系统精简作为定制化的重要一环,不仅能够释放手机存储空间,还能提升系统运行速度,优化资源分配。想要安全有效地对安卓 9.0 系统进行精简,深入了解系统 app 组件的构成是…

2.4 Windows Conan编译FFmpeg 4.4.1

Conan的安装与使用参考之前的文章&#xff1a;Conan简单使用 一、Conan编译ffmpeg 1.1 Conan的配置文件 创建配置文件&#xff1a;C:\Users\wujh\.conan2\profiles\vs2019 [settings] archx86_64 build_typeRelease compilermsvc compiler.cppstd14 compiler.runtimedynami…

社群经济视域下开源链动2+1模式与AI智能名片赋能S2B2C商城小程序的创新发展研究

摘要&#xff1a;在数字经济蓬勃发展的背景下&#xff0c;社群经济作为连接用户情感与价值反哺的新型经济形态&#xff0c;正通过技术创新与模式重构实现深度演化。本文基于社群经济“创造有价值连接”的本质特征&#xff0c;系统探讨“开源链动21模式”“AI智能名片”与“S2B2…

【计算机网络】——reactor模式高并发网络服务器设计

&#x1f525;个人主页&#x1f525;&#xff1a;孤寂大仙V &#x1f308;收录专栏&#x1f308;&#xff1a;计算机网络 &#x1f339;往期回顾&#x1f339;&#xff1a;【计算机网络】非阻塞IO——epoll 编程与ET模式详解——(easy)高并发网络服务器设计 &#x1f516;流水不…

Uniapp跨端兼容性全方位解决方案

在当今多端融合的移动互联网时代&#xff0c;Uniapp作为一款优秀的跨平台开发框架&#xff0c;已成为许多开发者的首选。然而&#xff0c;真正的挑战在于如何优雅地处理不同平台之间的差异。本文将全面剖析Uniapp跨端开发的兼容性处理方案&#xff0c;提供从基础到高级的完整解…

迅为RK3576开发板NPUrknn-toolkit2环境搭建和使用docker环境安装

开发板采用核心板底板结构&#xff0c;在我们的资料里提供了底板的原理图工程以及PCB工程&#xff0c;可以实现真正意义上的裁剪、定制属于自己的产品&#xff0c;满足更多应用场合。 迅为针对RK3576开发板整理出了相应的开发流程以及开发中需要用到的资料&#xff0c;并进行详…

什么是 OpenFeigin ?微服务中的具体使用方式

什么是Feign&#xff1f; Feign 是一种声明式的 HTTP 客户端框架&#xff0c;主要用于简化微服务架构中服务之间的远程调用&#xff0c;也可以通过定义接口和注解的方式调用远程服务&#xff0c;无需手动构建 HTTP 请求或解析响应数据。Spring Cloud 对 Feign 进行了增强&…

对抗性提示:进阶守护大语言模型

人工智能模型正快速进化 —— 变得更具帮助性、更流畅&#xff0c;并且更深入地融入我们的日常生活和商业运营中。但随着其能力的提升&#xff0c;风险也在增加。在维护安全可信的人工智能方面&#xff0c;最紧迫的挑战之一是对抗性提示&#xff1a;这是一种微妙且通常富有创意…

运营商频段

以下是三大运营商&#xff08;中国移动、中国电信、中国联通&#xff09;及中国广电的 5G 主要频段 及其所属运营商的整理表格&#xff1a; 运营商频段上行频率 (MHz)下行频率 (MHz)带宽备注广电n28703-733758-788230MHz移动共享n794900-4960-60MHz-移动n412515-2675-160MHz-n7…

项目拓展-Apache对象池,对象池思想结合ThreadLocal复用日志对象

优化日志对象创建以及日志对象复用 日志对象上下文实体类 traceId 请求到达时间戳 请求完成时间戳 请求总共耗费时长 get/post/put/delete请求方式 Http状态码 原始请求头中的所有键值对 请求体内容 响应体内容 失败Exception信息详细记录 是否命中缓存 package c…

Javaweb - Vue入门

Vue是一款用于构建用户界面的渐进式的JavaScript框架。 使用步骤 引入Vue模块&#xff0c;创建Vue的应用实例&#xff0c;定义元素&#xff0c;交给Vue控制。 一、引入Vue模块 因为使用的是模块化的JavaScript&#xff0c;因此在script标签内要声明一个属性&#xff1a;typ…

C++ 标准模板库各个容器的应用场景分析

C 标准模板库&#xff08;STL&#xff09;中的容器分为序列式容器、关联式容器和无序容器&#xff0c;各自适用于不同场景。以下是主要容器的应用场景及案例&#xff1a; 一、序列式容器 元素按插入顺序存储&#xff0c;支持线性访问。 1. vector 场景&#xff1a;动态数组…

安装前端vite框架,后端安装fastapi框架

前期准备 首先新建一个文件夹&#xff0c;文件夹里面新建一个文件夹&#xff0c;用于安装依赖 安装vite框架 npm init -y 目的是安装package.json配置文件 npm install vite --save-dev 安装vite框架 安装完是这个样子 新建了一个文件夹和js文件 后端内容 main.js document.…