前端 大文件分片下载上传

前端 大文件分片下载上传

背景介绍:

当前项目是给投行部门做系统,业务方需要有专门的文档中心去管理文件,包括但是不限于文件的上传和下载等等。笔者本来就是采用的浏览器表单上传的方式进行文件上传,但是谁曾想在进行稍微大一点的文件上传时,因为正式环境nginx 限制了请求体大小导致文件传不上去,所以不得不重新调整文件上传的策略。

解决方案:

采用分片上传

设计思路:

1. 文件选择与校验

  1. 用户通过前端界面选择文件,触发 fileChange方法
  2. 校验文件合法性:

​ 检查文件大小是否超过配置阈值(例如默认限制 500MB),超限则提示警告

​ 验证文件扩展名是否在黑名单(如 exesh等),禁止上传危险类型文件

  1. 若校验失败,终止流程并提示用户。
this.fileChange = (options: any) => {const file = options.file;// 1.1 校验文件大小if (file.size > Number(this.constants.UPLOAD_SIZE) * 1024 * 1024) {ElMessage.warning(`${`文件大小限制${this.constants.UPLOAD_SIZE}`}m`)return}// 1.2 校验文件类型const fileName = file.name.split('.')const fileSuffix = fileName[fileName.length - 1]if (this.prohibitArr.indexOf(fileSuffix.toLowerCase()) !== -1) {ElMessage.warning('文件类型限制')return}
}

2. 任务初始化与切片生成

创建唯一任务标识

通过文件名称、大小生成 MD5 哈希值作为 `taskId`,确保任务唯一性

文件切片

调用 `sliceFile`方法,按配置的切片大小(如默认 1MB)将文件分割为多个 `Blob`对象记录切片总数、文件总大小等元数据。

初始化任务对象

存储切片列表(`todoList`)、任务状态(`isPause`)、进度值(`progressValue`)等
// 2.1 创建任务对象
this.addTask = (data) => {const taskId = `${hex_md5(`${data.fileName}_${data.fileSize}`)}`const task = {taskId, // 文件哈希IDfileName: data.fileName,fileSize: data.fileSize,todoList: [],   // 待上传分片errorList: [],  // 失败分片flag: 0,        // 当前分片指针progressValue: 0}this.tasksList.push(task)
}// 2.2 文件切片处理
this.sliceFile = (file) => {const piece = 1024 * 1000 * Number(this.constants.UPLOAD_CHUNK_SIZE)const chunks = []let start = 0while (start < file.size) {const blob = file.slice(start, start + piece)chunks.push(blob)start += piece}return chunks
}

3. 切片上传与进度管理

构建分片请求:

​ 为每个切片创建 FormData,包含切片内容、文件名、索引、任务 ID 等元数据

​ 设置请求头(如 Authorization携带用户令牌)。

并发控制与断点续传

​ 通过递归调用 handleUpload逐个上传切片(非并行),但支持从断点索引(task.flag)继续上传

​ 上传前检查 isPause状态,支持用户暂停/继续操作

实时进度计算

​ 基于切片索引和当前切片上传进度,综合计算整体进度(避免进度条回退)

​ 通过 $emit('changeProgressValue')通知前端更新进度条

this.handleChunksUpload = (chunks, taskId) => {const task = this.getTask(taskId)chunks.forEach((chunk, index) => {const fd = new FormData()fd.append('file', chunk)             // 分片二进制fd.append('fileName', task.fileName) // 原文件名fd.append('chunk', index.toString()) // 分片序号fd.append('taskId', taskId)          // 任务IDtask.todoList.push({index,fd,config: {headers: { Authorization: `Bearer ${token}` },onUploadProgress: (progress) => { /* 进度处理 */ }}})})
}

4. 错误处理与重试机制

失败切片处理:

​ 若某切片上传失败,将其加入 errorList并继续上传后续切片

自动重试:

​ 当所有切片上传完成但 errorList非空时,触发重试(最多 UPLOAD_TRY_TIME次)

​ 超过重试次数则触发 handleUploadError事件,通知上传失败

this.handleUpload = (taskId) => {const task = this.getTask(taskId)const item = task.todoList[task.flag]axios.post(this.chunkUploadUrl, item.fd, item.config).then(() => {// 4.1 上传成功处理task.flag++ // 移动分片指针task.progressValue = Math.round((task.flag / task.todoList.length) * 100)this.$emit('changeProgressValue', { task })// 4.2 递归上传下一分片if (task.flag < task.todoList.length) {this.handleUpload(taskId)}}).catch((err) => {// 4.3 失败分片处理task.errorList.push(item)  // 加入失败列表task.flag++ // 继续后续分片// 4.4 自动重试机制if (task.flag >= task.todoList.length) {if (task.errorList.length > 0) {task.todoList = [...task.errorList] // 准备重试task.errorList = []task.flag = 0this.handleUpload(taskId) // 重启失败分片}} else {this.handleUpload(taskId) // 继续后续分片}})
}

5.合并切片与完成回调

后端自动合并

​ 所有切片成功上传后,前端无需显式请求合并(与部分方案不同),由后端根据 taskId自动合并切片

前端完成逻辑

​ 设置进度为 100%,触发最终进度更新事件。

​ 重置任务状态(清空 flagerrorList)。

​ 执行用户定义的 onSuccess回调,传递文件及响应数据。

​ 触发 handleUploadSuccess事件,通知上传完成

onUploadProgress: (progress) => {// 5.1 当前分片进度比const currentChunkProgress = progress.loaded / progress.total// 5.2 已完成分片的比例const completedRatio = task.flag / totalChunks// 5.3 整体进度合成(避免进度回退)const overallProgress = (completedRatio + (1 / totalChunks) * currentChunkProgress) * 100// 5.4 进度更新(前端显示)task.progressValue = Math.min(Math.max(task.progressValue, Math.round(overallProgress)), 99)
}

6.任务清理

​ 用户可通过 removeTask主动移除任务,释放内存资源。

​ 任务队列(tasksList)管理所有进行中的任务,支持多文件并行上传

// 6.1 全部分片成功上传
if (task.flag > task.todoList.length - 1 && task.errorList.length === 0) {task.progressValue = 100 // 最终进度this.$emit('changeProgressValue', { task })// 6.2 执行成功回调if (this.onSuccess) {this.onSuccess(response, task.file) }// 6.3 触发完成事件this.$emit('handleUploadSuccess', { task })// 6.4 重置任务状态task.flag = 0task.lastFileLoaded = 0
}

完整的代码如下:

useChunkUpload.ts

import { ElMessage } from 'element-plus'
import { hex_md5 } from './md5'const useChunkUpload = function (props: any, emit: any, instance: any) {this.isSendErrorMessage = falsethis.tryTime = 0this.flag = 0this.fileLoaded = 0this.lastFileLoaded = 0 // 上一次加载大小this.progressValue = 0 // 进度值this.persistentFailureTime = 0 // 连续失败次数this.isPause = falsethis.taskType = 'upload'this.taskId = ''this.tasksList = []this.prohibitArr = ['exe', 'c', 'java', 'jar', 'py', 'php', 'sh', 'bat']this.uploadInputDisabled = falsethis.$emit = emitthis.maxPersistentFailureTime = 8 // 最大连续失败次数this.onUploadProgress = nullthis.chunkUploadUrl = 'file/resumeUpload'this.chunkUploadProgress = ''this.constants = {UPLOAD_SIZE: 500,UPLOAD_CHUNK_SIZE: 1,DOWNLOAD_CHUNK_SIZE: 1,UPLOAD_TRY_TIME: 3,DOWNLOAD_TRY_TIME: 3,TIMEOUT: 60000,}this.onSuccess = null // 添加成功回调属性if (props.chunkUploadProps) {this.chunkUploadUrl =props.chunkUploadProps.chunkUploadUrl ?? this.chunkUploadUrlthis.chunkUploadProgress =props.chunkUploadProps.chunkUploadProgress ?? this.chunkUploadProgressif (props.chunkUploadProps.constants) {this.constants = {...this.constants,...props.chunkUploadProps.constants,}}if (props.chunkUploadProps.prohibitArr) {this.prohibitArr = [...this.prohibitArr,...props.chunkUploadProps.prohibitArr,]}if (props.onSuccess) {this.onSuccess = props.onSuccess}}this.getTasksList = () => {return (this.tasksList = [])}this.initTasksList = () => {this.tasksList = []}this.clearTasksList = () => {this.tasksList = []}this.addTask = (data) => {const taskId = `${hex_md5(`${data.fileName}_${data.fileSize}`)}`const task = {taskId, // 任务idtaskType: this.taskType, // 任务类型md5: data.md5 || '',blobs: [],todoList: [], // 待办分片列表errorList: [], // 失败列表tryTime: 0, // 失败尝试次数flag: 0, // 指针isSendErrorMessage: false, // 是否已发送错误信息isPause: false, // 暂停状态handleUpload: null, // 下载方法fileName: data.fileName, // 文件名fileSize: data.fileSize, // 文件大小file: data.file,fileLoaded: 0, // 当前加载大小lastFileLoaded: 0, // 上一次加载大小progressValue: 0, // 进度值persistentFailureTime: 0, // 连续失败次数}// 任务去重const sameTaskIndex = this.tasksList.findIndex((item) => {return item.taskId === taskId})if (sameTaskIndex === -1) {this.tasksList.push(task)} else {this.tasksList[sameTaskIndex] = task}const taskIndex = this.tasksList.length - 1this.tasksList[taskIndex]['taskIndex'] = taskIndexthis.$emit('changeCurrentTaskIndex', taskIndex, this)this.$emit('changeCurrentTaskId', task.taskId, this)this.$emit('changeCurrentTask', task, this)return task}this.getTask = (taskId) => {return this.tasksList.find((item) => item.taskId === taskId)}this.getTaskIndex = (taskId) => {return this.tasksList.findIndex((task) => task.taskId === taskId)}this.pauseTask = (taskId) => {const task = this.getTask(taskId)const taskIndex = this.getTaskIndex(taskId)if (task) {this.tasksList[taskIndex]['isPause'] = true}}this.continueTask = (taskId) => {const task = this.getTask(taskId)const taskIndex = this.getTaskIndex(taskId)if (task) {this.tasksList[taskIndex]['isPause'] = falsethis.handleUpload(taskId)}}this.removeTask = (taskId) => {const task = this.getTask(taskId)const taskIndex = this.getTaskIndex(taskId)if (task) {this.tasksList[taskIndex]['isPause'] = truethis.tasksList.splice(taskIndex, 1)}}this.handleChunksUpload = (chunks, taskId) => {const task = this.getTask(taskId)if (chunks.length === 0) {ElMessage.warning('文件太小')return null}// 计算每个分片的大小const chunkSize = chunks[0].sizeconst totalChunks = chunks.lengthconsole.log(`文件分片信息: 总大小=${task.fileSize}, 分片数=${totalChunks}, 每片大小=${chunkSize}`)const config = {headers: {contentType: false,processData: false,'X-Chunk-Upload': 'true', // 标记这是分片上传请求},config: {timeout: this.constants.TIMEOUT,// 指示该请求的错误将由组件内部处理,不需要全局错误拦截器显示错误skipGlobalErrorHandler: true,onUploadProgress: (progress) => {if (this.onUploadProgress) {this.onUploadProgress(progress, taskId)} else {// 修复进度计算逻辑console.log(`当前分片进度: loaded=${progress.loaded}, total=${progress.total}, 当前分片=${task.flag}, 总分片数=${totalChunks}`)// 当前分片的进度百分比 (0-1之间)const currentChunkProgress = progress.loaded / progress.total// 计算已完成部分的比例 (当前分片之前的所有完成分片)// 注意: 分片索引是从0开始,所以已完成的分片是task.flagconst completedChunksProgress = task.flag / totalChunks// 当前分片贡献的总体进度比例const currentChunkContribution = (1 / totalChunks) * currentChunkProgress// 总体进度 = 已完成分片的进度 + 当前分片的贡献const overallProgress = (completedChunksProgress + currentChunkContribution) * 100// 确保进度只会增加不会减少const previousProgress = task.progressValue || 0const newProgress = Math.min(Math.max(previousProgress, Math.round(overallProgress)), 99) // 保留最后1%给最终合并操作// 只有当进度有实质性变化时才更新if (newProgress > previousProgress) {task.progressValue = newProgress}console.log(`计算进度: 已完成分片=${task.flag}, 总分片数=${totalChunks}, 当前分片进度=${Math.round(currentChunkProgress * 100)}%, 总体进度=${task.progressValue}%`)// 触发进度更新事件this.$emit('changeProgressValue',{task,progressValue: task.progressValue,},this)// 如果有外部的进度回调函数,也调用它if (task.onProgress && typeof task.onProgress === 'function') {task.onProgress({loaded: task.flag * chunkSize + progress.loaded,total: task.fileSize}, task.file)}}},},}if (localStorage.getItem('token') !== null &&localStorage.getItem('token') !== '') {const Authorization = `Bearer ${localStorage.getItem('token') || ''}`config.headers['Authorization'] = Authorization}chunks.forEach((chunk, index) => {const fd = new FormData()fd.append('file', chunk)fd.append('fileName', task.fileName)fd.append('chunk', index.toString())fd.append('taskId', taskId)fd.append('size', chunk.size)fd.append('totalSize', task.fileSize)fd.append('chunkTotal', chunks.length)const item = {index: index.toString(),fd,config,}task.todoList.push(item)})this.$emit('beforeHandleUpload',{task,},this)if (this.chunkUploadProgress) {instance.appContext.config.globalProperties?.$http.post(this.chunkUploadProgress, {taskId,}).then((res) => {if (res && res.data) {const startChunkIndex = res.data.chunkconst task = this.getTask(taskId)if (startChunkIndex) {task.flag = Number(startChunkIndex)// 更新已完成的进度task.progressValue = Math.round((task.flag / totalChunks) * 100)}this.handleUpload(taskId)}})} else {this.handleUpload(taskId)}}this.handleUpload = (taskId) => {const task = this.getTask(taskId)this.$emit('handleUpload', { task }, this)const item = task.todoList[task.flag]if (task.isPause) {return null}console.log(`开始上传分片 ${task.flag + 1}/${task.todoList.length}`)instance.appContext.config.globalProperties?.$http.post(this.chunkUploadUrl, item.fd, item.config).then((res) => {const token = res.response.headers['authorization']if (token) {localStorage.setItem('token', token)}task.persistentFailureTime = 0task.flag += 1console.log(`分片 ${task.flag}/${task.todoList.length} 上传完成`)// 更新进度:当前分片完成// 确保进度只会增加不会减少,避免UI跳动const newProgress = Math.round((task.flag / task.todoList.length) * 100)task.progressValue = Math.max(task.progressValue || 0, newProgress)// 立即更新UI进度this.$emit('changeProgressValue',{task,progressValue: task.progressValue,},this)// 任务暂停if (task.isPause) returnif (task.flag > task.todoList.length - 1) {// 所有分片上传完成if (task.errorList.length === 0) {// 错误列表为空,上传成功task.progressValue = 100 // 确保最终进度为100%// 最后一次更新进度this.$emit('changeProgressValue',{task,progressValue: task.progressValue,},this)console.log('所有分片上传完成,调用成功回调')// 重置任务状态task.flag = 0task.lastFileLoaded = 0task.fileLoaded = 0// 调用成功回调if (this.onSuccess && typeof this.onSuccess === 'function') {this.onSuccess(res, task.file)}// 发出成功事件this.$emit('handleUploadSuccess', { task, response: res }, this)} else {// 有错误,需要重试if (task.tryTime >= this.constants.UPLOAD_TRY_TIME) {// 超过重试次数,上传失败if (!task.isSendErrorMessage) {this.isSendErrorMessage = truethis.$emit('handleUploadError', { task }, this)}return null} else {// 重试task.tryTime = task.tryTime + 1task.todoList = task.errorListtask.errorList = []task.flag = 0this.$emit('handleBeforeUploadNextChunk', { task }, this)this.handleUpload(task.taskId)}}} else {// 继续上传下一个分片this.$emit('handleBeforeUploadNextChunk', { task }, this)this.handleUpload(task.taskId)}}).catch((err) => {console.error(`分片 ${task.flag + 1} 上传失败:`, err)// 将失败的分片添加到错误列表task.errorList.push(item)task.flag += 1// 继续处理下一个分片或重试if (task.flag > task.todoList.length - 1) {if (task.tryTime >= this.constants.UPLOAD_TRY_TIME) {this.$emit('handleUploadError', { task }, this)} else {task.tryTime += 1task.todoList = task.errorListtask.errorList = []task.flag = 0this.handleUpload(task.taskId)}} else {this.handleUpload(task.taskId)}})}this.fileChange = (options: any) => {this.$emit('beforeUploadFile', options, this)// 如果options中传入了onSuccess,则使用它if (options.onSuccess && typeof options.onSuccess === 'function') {this.onSuccess = options.onSuccess}// 保存进度回调函数if (options.onProgress && typeof options.onProgress === 'function') {this.onProgress = options.onProgress}const file = options.fileif (file) {if (file.size > Number(this.constants.UPLOAD_SIZE) * 1024 * 1024) {ElMessage.warning(`${`文件大小限制${this.constants.UPLOAD_SIZE}`}m`)return}const fileName = file.name.split('.')if (fileName.length) {const fileSuffix = fileName[fileName.length - 1]if (this.prohibitArr.indexOf(fileSuffix.toLowerCase()) !== -1) {ElMessage.warning('文件类型限制')return}}const data = {fileName: file.name,fileSize: file.size,file,}const task = this.addTask(data)// 将回调函数保存到任务中if (options.onProgress) {task.onProgress = options.onProgress}const chunks = this.sliceFile(file)this.handleChunksUpload(chunks, task.taskId)}}this.sliceFile = (file,piece = 1024 * 1000 * Number(this.constants.UPLOAD_CHUNK_SIZE)) => {const totalSize = file.size // 文件总大小let start = 0 // 每次上传的开始字节let end = start + piece // 每次上传的结尾字节const chunks = []while (start < totalSize) {// 根据长度截取每次需要上传的数据// File对象继承自Blob对象,因此包含slice方法const blob = file.slice(start, end)chunks.push(blob)start = endend = start + piece}return chunks}this.clearInputFile = (f) => {// f = f || this.$refs.uploadif (f.value) {try {f.value = '' // for IE11, latest Chrome/Firefox/Opera...// eslint-disable-next-line no-empty} catch (err) {}if (f.value) {// for IE5 ~ IE10const form = document.createElement('form')const ref = f.nextSiblingform.appendChild(f)form.reset()ref.parentNode.insertBefore(f, ref)}}}this.createTaskId = (file, time) => {return `${time}-${file.size}-${file.name.length > 100? file.name.substring(file.name.length - 100, file.name.length): file.name}`}this.randomString = (len) => {len = len || 32const $chars ='ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678' /** **默认去掉了容易混淆的字符oOLl,9gq,Vv,Uu,I1****/const maxPos = $chars.lengthlet pwd = ''for (let i = 0; i < len; i++) {pwd += $chars.charAt(Math.floor(Math.random() * maxPos))}return pwd}
}export { useChunkUpload }

使用方式如下:

import { useChunkUpload } from "@/utils/useChunkUpload";...
// 初始化分片上传
const chunkUpload = new useChunkUpload(
{headers: { Authorization: token },data: { blockName: "uploadTest" },
},
emit,
instance,
);
......
// 大文件分片上传处理
const handleChunkUpload = (options) => {
const file = options.file;// 调用分片上传
const uploadPromise = chunkUpload.fileChange(options);// 存储上传任务,用于暂停/恢复
uploadTasks.set(file.uid, uploadPromise);// 返回 abort 对象,符合 el-upload 的要求
return {abort() {console.log("分片上传已中止");// 中止上传逻辑const task = uploadTasks.get(file.uid);if (task && task.abort) {task.abort();}},
};
};...

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

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

相关文章

【Python练习】097. 编写一个函数,实现简单的版本控制工具

097. 编写一个函数,实现简单的版本控制工具 097. 编写一个函数,实现简单的版本控制工具 示例代码 功能说明 使用方法 注意事项 实现方法 基于文件快照的实现方法 基于差异存储的实现方法 基于命令模式的实现方法 基于Git-like的实现方法 097. 编写一个函数,实现简单的版本控…

嵌入式硬件篇---Tof

TOF 的原理与本质TOF&#xff08;Time of Flight&#xff0c;飞行时间&#xff09;是一种通过测量信号&#xff08;通常是光&#xff09;在空间中传播时间来计算距离的技术。其本质是利用 “距离 速度 时间” 的物理公式&#xff1a;通过发射信号&#xff08;如激光、红外光&…

Vue diff简介

Vue3 diff 最长递增子序列双端diff 理念 相同的前置和后置元素的预处理&#xff0c;考虑边界情况&#xff0c;减少移动&#xff1b;最长递增子序列&#xff08;动态规划、二分法&#xff09;&#xff0c;判断是否需要移动 操作 前置与后置预处理判断是否需要移动 递增法&#x…

罗技MX Anywhere 2S鼠标修复记录

【现象】单击时总是出现双击的现象 【问题原因】从网络收集&#xff1a; 说法1&#xff0c;欧姆龙微动损坏&#xff1b;说法2&#xff0c;按键磨损导致按键和微动开关接触不良&#xff1b; 【问题排查】 微动损坏&#xff1a;拆掉壳子后&#xff0c;用手按住左键的微动开关&…

常见IP模块的仲裁策略和实现

在一个 Message Unit 中包含两个 Core&#xff08;处理器核心&#xff09;&#xff0c;它们之间访问共享资源&#xff08;如寄存器、FIFO、buffer 等&#xff09;时&#xff0c;仲裁机制&#xff08;Arbitration&#xff09; 是确保系统稳定性和正确性的关键。以下是常见的仲裁…

上周60+TRO案件,波比的游戏时间/丹迪世界/飞盘/迪奥/多轮维权,手表/汽车品牌持续发力,垃圾桶专利等新增侵权风险!

赛贝整理上周&#xff08;2025年8月11日-8月15日&#xff09;的TRO诉讼案件发案情况&#xff0c;根据赛贝TRO案件查询系统了解到&#xff0c;上周合计发起了超60起诉讼案件&#xff0c;涵盖 IP /品牌、版权、专利等多个领域&#xff0c;涉及影视、奢侈品、汽车、游戏、日常用品…

用 Python 在 30 分钟内搭一个「可回放的实时日志」——把攻击流量变成可视化剧情

业务背景 我们运营一款 FPS 端游&#xff0c;外挂作者常把 DDoS 伪装成「玩家掉线」来骗客服。以前排查要捞 CDN 日志、对时间戳、人工比对&#xff0c;平均 2 小时才能定位。现在用一条 30 行的 Python 脚本把边缘节点日志实时打到 Kafka&#xff0c;再回放到 Grafana&#xf…

如何将 LM Studio 与 ONLYOFFICE 结合使用,实现安全的本地 AI 文档编辑

人工智能正在改变我们的工作方式——但如今大多数 AI 工具都存在弊端&#xff1a;速度和便利性虽有所提升&#xff0c;但也意味着数据需要发送到外部服务器。对于教育工作者、企业、非政府组织以及任何处理敏感信息的人来说&#xff0c;这都是不可接受的风险。 LM Studio 和 O…

超市电商销售分析项目:从数据分析到业务决策

国际超市电商销售数据分析实战&#xff1a;从数据清洗到业务决策的完整流程 在电商行业&#xff0c;数据是驱动业务增长的核心引擎。本文将以国际超市电商销售数据为研究对象&#xff0c;完整拆解从数据准备 → 深度分析 → 策略输出的实战流程&#xff0c;涵盖数据清洗、多维度…

GitHub 热榜项目 - 日榜(2025-08-17)

GitHub 热榜项目 - 日榜(2025-08-17) 生成于&#xff1a;2025-08-17 统计摘要 共发现热门项目&#xff1a;12 个 榜单类型&#xff1a;日榜 本期热点趋势总结 本期GitHub热榜呈现三大技术趋势&#xff1a;1) AI基础设施持续爆发&#xff0c;Archon OS和Parlant聚焦AI任务管…

记忆翻牌游戏 greenfoot 开发

记忆翻牌游戏是一种经典的益智游戏&#xff0c;玩家需要翻开卡片并记住它们的位置&#xff0c;然后找到所有匹配的卡片对。 核心玩法 游戏开始时&#xff0c;所有卡片都是背面朝上玩家每次可以翻开两张卡片如果两张卡片图案相同&#xff0c;则保持翻开状态&#xff08;匹配成功…

【lucene】SegmentInfos

SegmentInfos 类中文说明 ———————————— **一句话** SegmentInfos 是 segments_N 文件的**内存表示**。它把磁盘上的 segments_N 读进来&#xff0c;变成一堆 SegmentInfo 的集合&#xff1b;当你增删改索引、合并段、提交时&#xff0c;再把它写回磁盘&#x…

Read Frog:一款开源AI浏览器语言学习扩展

Read Frog&#xff1a;一款开源AI浏览器语言学习扩展 来源&#xff1a;Poixe AI Read Frog&#xff08;中文名&#xff1a;陪读蛙&#xff09;是一款开源的浏览器扩展&#xff0c;旨在通过人工智能技术&#xff0c;将常规网页浏览转化为一种沉浸式的语言学习体验。该工具通过…

天地图应用篇:增加全屏、图层选择功能

天地图应用篇&#xff1a;增加全屏、图层选择功能本节说明&#xff1a; 目的&#xff1a; 实现地图的图层切换全屏显示 / 退出全屏案例截图 示下&#xff1a;案例代码示例代码&#xff1a; <template><div class"tianditu-map-container"><!-- 顶部搜…

从零开始,系统学习AI与机器学习:一份真诚的学习路线图

人工智能&#xff08;AI&#xff09;和机器学习&#xff08;ML&#xff09;正在深刻改变众多行业的面貌&#xff0c;掌握这些技术已成为许多技术从业者提升竞争力的重要路径。无论你是希望进入这个充满潜力的领域&#xff0c;还是期望在现有技术基础上进行拓展&#xff0c;一份…

NVIDIA CWE 2025 上海直击:从 GPU 集群到 NeMo 2.0,企业 AI 智能化的加速引擎

前言 8 月 8 日&#xff0c;我受邀参加了在上海举办的 NVIDIA CWE 大会。作为一个正在企业内部推动 AI 落地的从业者&#xff0c;这场会议对我来说不仅是“充电”&#xff0c;更像是一场“解题会”。参会感受 在分享干货之前&#xff0c;我先谈谈这次参会的不同感受。给我感受特…

Web攻防-大模型应用LLM安全提示词注入不安全输出代码注入直接间接数据投毒

知识点&#xff1a; 1、WEB攻防-LLM安全-API接口安全&代码注入 2、WEB攻防-LLM安全-提示词注入&不安全输出 Web LLM&#xff08;Large Language Model&#xff09;攻击指针对部署在Web端的AI大语言模型的攻击行为。攻击者通过恶意提示词注入、训练数据窃取、模型逆向工…

docker compose再阿里云上无法使用的问题

最原始的Dokcerfile # 使用官方Python 3.6.8镜像 FROM python:3.6.8-slimWORKDIR /app# 复制依赖文件 COPY requirements.txt .RUN pip install --upgrade pip # 检查并安装依赖&#xff08;自动处理未安装的包&#xff09; RUN pip install --no-cache-dir -r requirements.tx…

C++STL容器List的模拟实现

一、引言list的实现&#xff0c;还是比较简单的&#xff0c;大家只要想着土家楼的形状&#xff0c;画出图来就好了&#xff0c;不需要过多担心。本次的博客会发出一个完整的实现List的List.hpp&#xff0c;以后也会这样&#xff0c;主要是分段发被说孩子分段生。二、模拟List由…

区块链 + 域名Web3时代域名投资的新风口(上)

关于Dynadot Dynadot是通过ICANN认证的域名注册商&#xff0c;自2002年成立以来&#xff0c;服务于全球108个国家和地区的客户&#xff0c;为数以万计的客户提供简洁&#xff0c;优惠&#xff0c;安全的域名注册以及管理服务。 Dynadot平台操作教程索引&#xff08;包括域名邮…