实现分片上传、断点续传、秒传 (JS+NodeJS)(TypeScript)
一、引入及效果
上传文件是一个很常见的操作,但是当文件很大时,上传花费的时间会非常长,上传的操作就会具有不确定性,如果不小心连接断开,那么文件就需要重新上传,导致浪费时间和网络资源。
所以,当涉及到大文件上传时,分片上传和断点续传就显得很有必要。把文件分成多个分片,一片片上传,最终服务端再进行合并操作。如果遇到网络中断的问题,已上传的分片就无需上传了,实现断点续传。
使用效果: photo.lhbbb.top/video/slice.mp4 (将分片大小设置为了1*1024B,便于观察)
二、思路
1.文件唯一标识
因为需要进行多个分片的上传、续传等操作,我们需要知道这个文件的唯一标识,这样后端才能知道这些分片属于哪个文件,断点续传的时候也能判断这个文件是否曾经上传过。
方法一:可以通过计算文件的md5,来得到文件的唯一标识。(文件被修改过 或 不同文件的唯一标识都是不同的)
优点:文件标识的唯一性可以保证,且有现成的计算md5的库可以调用
缺点:计算文件的md5是一个非常耗时的操作,文件越大,花费的时间越多。JS又是单线程的,计算md5的过程中无法执行其它操作。
方法二:大文件计算md5花费的时间很长,就退而求其次, 使用 文件名+文件最后修改时间+文件大小 作为唯一标识。
优点:计算速度快,因为这些数据在文件的File对象上都有。
缺点:有极小概率可能导致标识重复。
综合方法:可以这样:文件大小低于某个阈值,使用方法一,文件过大时,使用方法二
2.前端
1. 计算唯一标识:通过input标签,拿到File文件之后,我们就可以计算唯一标识了。
2. 秒传:计算好唯一标识后,拿着这个唯一标识去请求后端接口,看看该文件是否曾经上传过,如果上传过,就直接显示“上传成功”,也就是实现秒传。
3. 断点续传:如果曾经没有完整上传成功过,就开始对文件进行分片。分好片后,拿着分片的信息和唯一标识,请求后端接口,看看这个文件及其分片是否 “上传了,但没完全上传”,如果符合,就进行断点续传。 (这里我实现的思路是,传给后端分片的总长度,后端去看后端文件夹中有几个分片,返回缺少的分片序号。也可以通过对每个分片进行计算唯一标识,更加稳妥,但是比较费时)
4. 分片上传:如果这个文件完全没有上传过,就开始走正常的分片上传流程。遍历所有分片,把分片通过请求的方式发送到后端。 (注意:浏览器的请求并发数量一般是6个,如果一次性把所有分片请求都发送了,会导致后续的请求需要等待。对此,我们可以使用Promise并发池进行控制,减缓HTTP压力)
5. 完整性校验:全部分片上传完毕后,需要进行完整性校验,避免某个分片遇到故障没有成功上传。发送请求给后端,后端来判断分片数量是否正常,若不正常就上传缺少的分片。
6. 发送合并请求:完整性校验通过后,发送合并请求,后端进行文件合并,返回文件访问路径。
总结: 计算唯一标识 --> 若上传过,则秒传 --> 分片 --> 若上传过部分,则断点续传 --> 正常分片上传 --> 完整性校验 --> 合并请求
3.后端
1. 判断文件是否曾经完整上传过:根据文件md5,查看对应目录下的文件是否存在
2. 判断文件是否上传过切片:根据文件md5,查看对应目录中缺少哪些切片
3. 分片上传的接收:把分配存储到一个临时文件夹中,文件夹名字用 唯一标识 命名,方便以后查找。每个分片的命名用分片的序号,便于校验分片是否缺失。
4. 完整性校验:根据md5和分片总数,去检查对应的临时文件夹下缺少哪些文件序号,返回缺少的序号数组。
5. 合并文件:按照分片序号顺序,把分片写入一个新文件中,然后返回这个新文件的访问路径
三、前端实现
秒传和断点续传放最后讲,就是一个函数执行顺序的问题。
一些用到的ts类型如下:
/**进度条函数的类型,参数是进度,是一个小数,需要 *100 才是正常进度条 */
type onProgress = (progress: number) => any/**切片的类型 */
interface sliceType {/**分片的序号 */ flag: number;/**分片的二进制数据 */blob: Blob;
}
1. 计算文件唯一标识
这里计算md5使用了一个库,需要npm安装: npm i spark-md5
/**获得文件的md5,用来作为唯一索引 - 文件过大时,获取md5会很长很长时间,可以考虑用 文件名+文件最后修改时间+文件大小 做“唯一”标识*/
const getMd5 = (file: File) => {return new Promise<string>(async (resolve, reject) => {try {const reader = new FileReader();reader.readAsArrayBuffer(file);// 当文件读取完成时,计算文件MD5值reader.onloadend = function (e) {if (!e.target?.result) {return reject('文件读取失败')}const spark = new SparkMD5.ArrayBuffer()spark.append(e.target.result as ArrayBuffer)const md5 = spark.end()resolve(md5)}} catch (error) {reject(error)}})
}
2. 对文件分片
File对象的原型对象(Blob)上有一个slice方法,我们可以通过这个来进行分片
传入文件对象和需要的单个分片的大小,单位字节 (比如传入 5*1024*1024 就是5MB)
得到一个分片数组,每个分片包含分片序号和对应的Blob
/**文件切片 */
const fileSlice = (file: File, chunkSize: number) => {const result: sliceType[] = []let index = 0for (let nowSize = 0; nowSize < file.size; nowSize += chunkSize) {result.push({flag: index,blob: file.slice(nowSize, nowSize + chunkSize),})index++}return result
}
3. 把分片上传
/**生成上传切片的promise函数 */
const getSliceUploadPromise = (slice: sliceType, md5: string) => {const formData = new FormData();formData.append(`file`, slice.blob);formData.append(`index`, String(slice.flag));formData.append(`md5`, md5);return () => request.postByForm('/sliceUpload/upload', formData) //这里填写自己封装的请求函数
}/**把所有切片上传 */
const sliceUpload = async (sliceList: sliceType[], md5: string, onProgress: onProgress) => {const taskList: (() => Promise<string>)[] = []const length = sliceList.lengthfor (let i = 0; i < length; i++) {taskList.push(getSliceUploadPromise(sliceList[i], md5))}//使用并发池优化,避免堵塞const res = await promisePool<string, string>(taskList, 5, (count) => onProgress(count / length))return res
}
为了避免请求拥塞,这里使用promise并发池进行优化,最大并发数量5。(而不是一次性把所有请求都发出去)
/**Promise并发池,当有大量promise并发时,可以通过这个来限制并发数量* @param taskList 任务列表* @param max 最大并发数量* @param oneFinishCallback 每个完成的回调,参数是当前完成的个数和执行结果,可以用来制作进度条* @retrun 返回每个promise的结果,顺序和任务列表相同。 目前是成功和失败都会放入该结果* @template T 泛型T会自动填写,是promise成功的结果* @template Err 此泛型是promise错误的结果 (因为 成功和失败都会放入res,所以加个泛型可以便于ts判断)*/
const promisePool = async <T, Err>(taskList: (() => Promise<T>)[], max: number, oneFinishCallback?: (count: number, res: T | Err) => any) => {return new Promise<Array<T | Err>>(async (resolve, reject) => {type resType = T | Errtry {const length = taskList.lengthconst pool: Promise<resType>[] = []//并发池 let count = 0//当前结束了几个const res = new Array<resType>(length)for (let i = 0; i < length; i++) {let task = taskList[i]();//成功和失败都要执行的函数const handler = (_res: resType) => {pool.splice(pool.indexOf(task), 1) //每当并发池跑完一个任务,从并发池删除个任务res[i] = _res //放入结果数组count++oneFinishCallback && oneFinishCallback(count, _res)if (count === length) {return resolve(res)}}task.then((data) => {handler(data)console.log(`第${i}个任务完成,结果为`, data);}, (err) => {handler(err)console.log(`第${i}个任务失败,原因为`, err);})pool.push(task);if (pool.length === max) {//Promise.race:返回最快执行的promise//利用Promise.race来看获得哪个任务完成的信号//搭配await,一旦发现有任务完成了,就继续for循环,把并发池塞满await Promise.race(pool)}}} catch (error) {console.error('promise并发池出错', error);reject(error)}})}
4. 完整性校验 (同时适配断点续传)
分片上传完毕后,就要进行完整性校验,判断分片是否完整。
getArr函数:发送请求给后端,后端返回缺少的分片序号数组 (返回空数组代表分片完整)
完整性校验:根据缺少的分片序号,把缺少的分片继续上传 (这里兼容了断点续传)。同时限制了重试次数,当超过5次仍然没有完整上传,就判断此次上传失败。
/**请求后端,获得缺少的分片序号数组*/
const getArr = async () => (await request.post('/sliceUpload/integrityCheck', { count: sliceList.length, md5 })).missingArr/**完整性校验,缺少的继续上传 (断点续传) */
const integrityCheck = async (sliceList: sliceType[], md5: string, onProgress: onProgress) => {let maxTest = 5 //最大尝试次数,避免无限尝试/**缺少的序号数组 */let missingArr: number[] = await getArr()/**总分片数量 */const sliceListLength = sliceList.lengthonProgress((sliceListLength - missingArr.length) / sliceListLength)//更新进度条while (missingArr.length) {const tasks: (() => Promise<string>)[] = []for (let i = 0; i < missingArr.length; i++) {tasks.push(getSliceUploadPromise(sliceList[missingArr[i]], md5))}//使用并发池优化请求await promisePool<string, string>(tasks, 5, (count) => onProgress((sliceListLength - (missingArr.length - count)) / sliceListLength))//这里的count是缺少的部分中,完成的数量,作为进度条。missingArr = await getArr() //上传完成后,再次进行完整性校验。maxTest--if (maxTest === 0) {return Promise.reject('尝试五次仍未上传成功')}}
}
5. 发送合并请求
通过完整性校验后,就来到了最后一步,合并切片 (这里主要是后端干活,前端不做过多解释)
/**合并切片,拿到路径 */
const merge = async (file: File, md5: string) => {const suffix = getSuffix(file.name) //拿到后缀const path = await request.post('/sliceUpload/merge', { md5, suffix })return path
}
6.秒传
对秒传的判断,在第一步和第二步之间 (计算好唯一标识后就能进行秒传判断了)(这里主要是后端干活,前端不做过多解释)
/**秒传 - 判断文件是否上传过,如果上传过了就直接返回文件路径,不用分片即上传 */
const isUploaded = async (file: File, md5: string) => {const res: isUploadedRes = await request.post('/sliceUpload/isUploaded', { md5, suffix })return res
}// 注:关于 /sliceUpload/isUploaded 接口的返回值:type isUploadedRes = {/**是否上传完整了 */flag: boolean/**如果上传过,路径是什么 (没上传的话为空) */path: string/**(仅在flag为false有效)是否上传过,但没上传完整? 为true就走断点续传 (请调用完整性校验接口) */noComplete: boolean
}
7.完整流程
搭配上面的函数观看。整体流程需要根据后端给的接口返回值来进行修改,但整体思路不变
/**分片上传 - 只支持单文件* @param file 文件* @param chunkSize 一个分片的大小 * @param setTip 可以用于文字提示 * @param onProgress 上传进度的回调,参数是进度 * @returns 上传文件的url*/
export async function uploadBySlice(file: File, chunkSize: number, setTip: (tip: string) => any , onProgress: onProgress) {setTip("正在计算md5");const md5 = await getMd5(file)const isUploadedFlag = await isUploaded(file, md5)if (isUploadedFlag.flag) {//已经上传过,就直接返回路径,实现秒传onProgress(1)setTip('文件上传成功')return isUploadedFlag.path} else {setTip('正在进行切片')const sliceList = fileSlice(file, chunkSize)console.log('切片', sliceList);if (!isUploadedFlag.noComplete) { //没传递过的文件,才走完整的上传流程 (断点续传)setTip('正在进行文件上传')await sliceUpload(sliceList, md5, onProgress)} else {setTip('正在进行断点续传')}await integrityCheck(sliceList, md5, onProgress)//不管是断点续传还是正常上传,都走这个函数,进行复用 (正常上传的也要校验完整性,断点续传的也要根据校验的完整性来继续上传)setTip("正在合并文件")const path = await merge(file, md5)setTip("文件上传成功!")return path}
}
四、后端实现
这里使用 NextJS (NodeJS) 做后端,由于各个框架使用不同,仅写出关键步骤
下面代码中使用的一些,在nodejs中关于文件操作的函数:
//本文件进行一些I/O操作
import fs from 'fs'
import path from 'path'
import 'server-only'//代表仅服务端可使用 /**写入Buffer文件在指定路径下。路径不存在时将会创建路径 */
export const writeFile = (filePath: string, buffer: Buffer) => {return new Promise<string>(async (resolve, reject) => {try {const directory = path.dirname(filePath);fs.mkdir(directory, { recursive: true }, (err) => {if (err) {reject(err);} else {fs.writeFile(filePath, buffer, (err) => {if (err) {reject(err);} else {resolve(filePath);}});}});} catch (error) {reject(error)}})
}
/**删除指定路径的文件 */
export const deleteFile = (path: string) => {return new Promise<void>(async (resolve, reject) => {try {fs.unlink(path, (err) => {if (err) reject(err)else resolve()})} catch (error) {reject(error)}})}
/**删除指定文件夹及其所有文件。 */
export const deleteFolderRecursive = (folderPath: string) => {if (fs.existsSync(folderPath)) {fs.readdirSync(folderPath).forEach((file) => {const currentPath = `${folderPath}/${file}`;if (fs.lstatSync(currentPath).isDirectory()) {// 递归删除子文件夹deleteFolderRecursive(currentPath);} else {// 删除文件fs.unlinkSync(currentPath);}});// 删除空文件夹fs.rmdirSync(folderPath);}
};
/**在文件末尾追加,不存在的话会新增目录 */
export const appendToFile = (text: string | Buffer, filePath: string, errFn?: (err: NodeJS.ErrnoException | null) => void) => {return new Promise<void>(async (resolve, reject) => {try {const directory = path.dirname(filePath);fs.mkdir(directory, { recursive: true }, (err) => {if (err) {reject(err);return;}fs.appendFile(filePath, text, (err) => {if (err) {errFn?.(err);reject(err);} else {resolve();}});});} catch (error) {reject(error)}})}
/**获得路径文件夹下的所有文件 */
export const getDir = (directoryPath: string) => {return new Promise<fs.Dirent[]>(async (resolve, reject) => {try {fs.readdir(directoryPath, { withFileTypes: true }, (err, files) => {if (err) {reject(err);return;}resolve(files)})} catch (error) {reject(error)}})
}
/**判断文件(文件夹)是否存在。 */
export const fileExists = (filePath: string): boolean => {return fs.existsSync(filePath);
};
1.判断文件是否曾经上传过
/sliceUpload/isUploaded 接口
const { md5, suffix } = await xxxxx() //获取body或query的参数,进行参数校验const realPath = `https://xxx.com/xxxxxx/${md5}${suffix}` //外界访问的路径
const targetFilePath = `/aaaa/${md5}${suffix}` ; //这里是文件存放的路径 (如果曾经完整上传过)
/**是否完整的上传过 */
const flag = fileExists(targetFilePath)
/**临时文件夹下是否有这个md5的临时文件夹,有就代表可以进行断点续传 */
const noComplete = fileExists(getAbsPath(`/temp/${md5}`))
return resFn.success<isUploadedRes>({flag: flag,path: flag ? realPath : "",noComplete
});
2.接收分片上传的文件
/sliceUpload/upload 接口
const [files, otherData] = await getFormData(request) //从formdata中取出文件和其它数据 (自行根据框架封装)
if (!files[0]) throw '文件不存在'
await writeFile(`/temp/${otherData.md5}/${otherData.index}`, files[0]) //存放在 /temp/{md5}/ 目录下
return resFn.success('操作成功');
3.完整性校验接口
/sliceUpload/integrityCheck
const { count, md5 } = await xxxx(request) //获取前端传递来的数据
const files = await getDir(`/temp/${md5}/`) //拿到这个临时文件夹下的所有文件
const judgeSet = new Set(Array.from({ length: count }, (k, i) => i)) //根据分片总数,生成一个set (假设分片总数为10,那么这里就是 0,1,2...,10 的一个set)//把文件夹中有的文件序号,从set中删除
files.forEach((file) => {judgeSet.delete(parseInt(file.name))
})//返回缺少的序号,空数组代表通过完整性校验
return resFn.success({missingArr: [...judgeSet]
});
4.合并文件接口
/sliceUpload/merge
const { md5, suffix } = await xxx() //拿到前端传来的参数const tempDirName = `/temp/${md5}/` //临时文件夹的路径 const files = await getDir(tempDirName)//拿到临时文件夹下的全部文件files.sort((a, b) => parseInt(a.name) - parseInt(b.name));// 按照数字顺序排序文件名数组//按照切片顺序,把切片的文件写入新文件
for (let i = 0; i < files.length; i++) {const file = files[i]; const content = fs.readFileSync(`/temp/${md5}/${file.name}`);//切片的位置await appendToFile(content, `/xxxx/${md5}${suffix}`) // 写入在服务器上存放文件的路径
}deleteFolderRecursive(tempDirName)//删除temp文件夹下的切片文件const realPath = `http://xxxx.com/xxxxx/${md5}${suffix}`//外界访问的路径return resFn.success(realPath);
五、总结
主要是需要有思路,思路有了就很好写了。上面的思路是自己想的,所以可能有些地方不完善,如果有不对的地方欢迎指出
相关文章:

实现分片上传、断点续传、秒传 (JS+NodeJS)(TypeScript)
一、引入及效果 上传文件是一个很常见的操作,但是当文件很大时,上传花费的时间会非常长,上传的操作就会具有不确定性,如果不小心连接断开,那么文件就需要重新上传,导致浪费时间和网络资源。 所以࿰…...

浅谈安科瑞EMS能源管控平台建设的意义-安科瑞 蒋静
摘 要:能源消耗量大、能源运输供给不足、环境压力日趋增加、能耗双控等一系列问题一直困扰着钢铁冶金行业,制约着企业快速稳定健康发展。本文介绍的安科瑞EMS能源管控平台,采用自动化、信息化技术,实现从能源数据采集、过程监控、…...

【原创】指针变量作为函数参数要点注意+main函数中值是否改变
指针变量作为函数参数要点注意(已写至笔记) 1传参指针不加*(main中函数) 2收参指针要加*(被main调用的函数) 3传参指针名可与收参指针名不同,不影响 4【问】如何看主函数中指针所指内容是否改变…...

售后处置跟踪系统设想
售后处置跟踪系统设想 前言 随着汽车工业的发展,软件定义车的模式已成为主流汽车设计及智能化功能架构模式,通过引入SOA的软件架构设计,使得现有的座舱软件、云端服务软件、App软件等众多功能模块的版本迭代频次日新月异,发版更…...

python实现ModBusTCP协议的server
python实现ModBusTCP协议的server是一件简单的事情,只要通过pymodbus、pyModbusTCP等模块都可以实现,本文采用pymodbus。 相关文章见: python实现ModBusTCP协议的client-CSDN博客 一、了解pymodbus的Server 1、pymodbus.server的模块 pym…...

AndroidStudio编译错误‘android.injected.build.density‘ is deprecated
问题 AndroidStudio编译错误 The option ‘android.injected.build.density’ is deprecated. It was removed in version 8.0 of the Android Gradle plugin. Density property injection from Android Studio has been removed. 解决 app/build.gradle 中这行 apply plugi…...

计网小题题库整理第一轮(面向期末基础)(3)
基础选择题的最后一章更新,看完期末75至少没问题~ 前情提要: 计网小题题库整理第一轮(12期) 一.选择题 1、 目前,最流行的以太网组网的拓扑结构是( C )。 A) 总线结构 B) 环…...

进程控制(一):进程终止
文章目录 进程控制(一)进程终止运行正常退出码 运行异常进程正常/异常总结 进程控制(一) 在前文中,我们初步了解了进程的概念,以及通过fork函数来创建子进程,并对于为什么运行一个程序…...

特殊类设计[下] --- 单例模式
文章目录 5.只能创建一个对象的类5.1设计模式[2.5 万字详解:23 种设计模式](https://zhuanlan.zhihu.com/p/433152245)5.2单例模式1.饿汉模式1.懒汉模式 6.饿汉模式7.懒汉模式7.1饿汉模式优缺点:7.2懒汉模式1.线程安全问题2.单例对象的析构问题 8.整体代码9.C11后可…...

计算机网络-应用层(1)
一、DNS 域名系统 (DNS) 是把主机域名解析为IP地址的系统。该系统是由解析器和域名服务器组成的。采用UDP 协议,较少情况下使用TCP 协议,端口号均为53。 域名系统由三部分构成: DNS 名字空间、域名服务器、 DNS客户机。 (1)根域:…...

Kotlin基础——枚举、When、in、for
枚举 声明只有值的枚举 enum class Color {RED, GREEN, BLUE }此外还可以增加属性和方法,如果需要在枚举类中定义方法,要使用分号把枚举常量列表和方法定义分开,这也是Kotlin唯一必须使用分号的地方 enum class Color(val r: Int, val g: …...

C++编程题目------平面上的最接近点对(分治算法)
题目描述 给定平面上n个点,找出其中的一对点的距离,使得在这n个点的所有点对中,该距离为所有点对中最小的。 输入格式 第一行一个整数 n,表示点的个数。 接下来 n 行,每行两个实数 x,y ,表示一个点的行…...

Linux下的文件操作和文件管理
文章目录 应用编程文件操作文件描述符open函数write函数read函数close函数lseek函数文件操作例子 文件管理文件基本知识文件类型文件共享空洞文件错误处理退出程序原子操作fcntl和ioctl截断文件stat函数软链接和硬链接 应用编程 系统调用(system call)是Linux内核提供给应用层…...

设计模式之桥梁模式
什么是桥梁模式 桥梁模式(Bridge Pattern)也称为桥接模式,属于结构型模式,它主要目的是通过组合的方式建立两个类之间的联系,而不是继承。桥梁模式将抽象部分与它的具体实现部分分离,使它们都可以独立地变…...

“从部署到优化,打造高效会议管理系统“
目录 引言一、部署单机项目 - 会议OA1.1 硬件和软件环境准备1.2 检查项目1.3 系统部署1.后端部署 二、部署前后端分离项目 - SPA项目后端部署2.前端部署 总结 引言 在现代化办公环境中,会议是组织沟通、决策和合作的重要方式之一。为了提高会议的效率和质量&#x…...

Facebook广告效果数据获取
一、背景 公司每年在Facebook和Google上投放了大量的广告,我总不能让老板登录Facebook广告投放平台上去看广告效果,其实老板只关注每天花了多少钱引来了多少客户,每个客户平均花费多少钱,其它的他才不关心,有Facebook…...

nlp之文本转向量
文章目录 代码代码解读 代码 from tensorflow.keras.preprocessing.text import Tokenizer # 标记器(每一个词,以我们的数值做映射,)words [LaoWang has a Wechat account., He is not a nice person., Be careful.] # 把这句话中每一个单词…...

【luckfox】添加压力传感器hx711
文章目录 前言一、参考资料二、电路图三、驱动四、makefile——添加驱动五、dts——使能gpio5.1 参考5.2 改动1—— hx117节点5.3 改动2——引脚节点5.4 已经被定义的引脚5.5 gpio源码 六、改动总结——使能hx711七、验证驱动添加八、编写测试文件8.1 测试代码8.2 配置编译环境…...

C++11的lambda表达式
lambda来源于函数式编程的概念。C11这次终于把lambda加进来了。 lambda表达式有如下优点: 1、声明式编程风格:就地匿名定义目标函数或函数对象,不需要额外写一个命名函数或者函数对象。以更直接的方式去写程序,好的可读性和可维护…...

矩阵特征值与特征向量的理解
各位朋友大家好,我是小C哈哈哈,很高兴认识大家,在这里,我会将一些枯燥难懂的数学和算法知识以图片或动画的形式通俗易懂的展现给大家,希望大家喜欢。 线性代数中的矩阵特征值与特征向量这两个基本概念总是让很多人摸不…...

云原生安全:如何保护云上应用不受攻击
文章目录 云原生安全的概念1. 多层次的安全性2. 自动化安全3. 容器安全4. 持续监控5. 合规性 云原生安全的关键挑战1. 无边界的环境2. 动态性3. 多云环境4. 容器化应用程序5. API和微服务 如何保护云上应用不受攻击1. 身份验证和访问控制示例代码: 2. 数据加密示例代…...

如何在用pip配置文件设置HTTP爬虫IP
首先,定义问题:在 Pip 中设置HTTP爬虫IP服务器,以便在网络上进行访问和下载。 亲身经验:我曾经遇到过类似问题,通过设置HTTP爬虫IP服务器成功解决了网络访问问题。 数据和引证:根据 pip 官方文档ÿ…...

2023MathorCup高校数模挑战赛B题完整解题代码教程
赛道 B: 电商零售商家需求预测及库存优化问题 问题背景: 电商平台存在着上千个商家,他们会将商品货物放在电商配套的仓库, 电商平台会对这些货物进行统一管理。通过科学的管理手段和智能决策, 大数据智能驱动的供应链…...

《动手学深度学习 Pytorch版》 10.7 Transformer
自注意力同时具有并行计算和最短的最大路径长度这两个优势。Transformer 模型完全基于注意力机制,没有任何卷积层或循环神经网络层。尽管 Transformer 最初是应用于在文本数据上的序列到序列学习,但现在已经推广到各种现代的深度学习中,例如语…...

ORACLE-递归查询、树操作
1. 数据准备 -- 测试数据准备 DROP TABLE untifa_test;CREATE TABLE untifa_test(child_id NUMBER(10) NOT NULL, --子idtitle VARCHAR2(50), --标题relation_type VARCHAR(10) --关系,parent_id NUMBER(10) --父id );insert into untifa_test (CHILD_ID, TITLE, RELATION_TYP…...

MySQL篇---第四篇
系列文章目录 文章目录 系列文章目录一、并发事务带来哪些问题?二、事务隔离级别有哪些?MySQL的默认隔离级别是?三、大表如何优化?一、并发事务带来哪些问题? 在典型的应用程序中,多个事务并发运行,经常会操作相同的数据来完成各自的任务(多个用户对 同一数据进行操作…...

em/px/rem/vh/vw单位的区别
一、绝对长度单位 1.px 表示像素,显示器上每个像素点大小都是相同的 二、相对长度单位 2.em 相对于当前对象内文本的字体尺寸,如未设置对行内文本字体的尺寸,则相对于浏览器的默认字体(1em16px) em值不是固定的&…...

【C++】多态 ③ ( “ 多态 “ 实现需要满足的三个条件 | “ 多态 “ 的应用场景 | “ 多态 “ 的思想 | “ 多态 “ 代码示例 )
文章目录 一、" 多态 " 实现条件1、" 多态 " 实现需要满足的三个条件2、" 多态 " 的应用场景3、" 多态 " 的思想 二、" 多态 " 代码示例 一、" 多态 " 实现条件 1、" 多态 " 实现需要满足的三个条件 &q…...

创建一个Keil项目
1、创建项目 2、选择存放的文件夹,还有设置项目名 3、选择型号(因为没有STC,用下面这个替代,功能差不多) 4、选择不用启动文件 5、就会得到下面这个,可以在Source Group 1下面编写代码了 6、右键source Group 1,添加c语…...

Xray的简单使用
xray 简介 xray 是一款功能强大的安全评估工具,由多名经验丰富的一线安全从业者呕心打造而成,主要特性有: 检测速度快。发包速度快; 漏洞检测算法效率高。支持范围广。大至 OWASP Top 10 通用漏洞检测,小至各种 CMS 框架 POC,均…...