文件分片上传设计

news2024/11/24 19:21:13

shigen日更文章的博客写手,擅长Java、python、vue、shell等编程语言和各种应用程序、脚本的开发。记录成长,分享认知,留住感动。

现在是接近凌晨了,突然有伙伴给我提到了文件分片上传的事情,我一想,这个我熟悉呀。因为在若干月前,我想亲手写了这部分的代码,还给自己整理出了飞书文档。对,一看文件,原来是遥远的2023年6月20日

shigen的飞书文档截图

其实说分片上传,原理很简单,就是前端分片、上传,后端的解析合并。其实半句话就可以讲清楚,但是代码实现起来要花很大的功夫。

今天的代码案例shigen选取的是node.js作为后端服务写的文件上传。

我们先来看一下实现的效果:

选择文件之后出现一批调用上传的接口

后端产生的文件夹

查看到的小姐姐视频

控制台输出的部分日志

整体的传输效果很快,会在文件夹里存储分片,在所有的分片上传完毕之后,整合成一个文件。我可以直接的打开和预览。

那代码怎么设计的呢?这是个核心的问题。一起来和shigen看看吧。

代码设计

前端

文件名为index.html

<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>file-upload</title>
</head>

<body>
    <input type="file" onchange="selFile(event)" />
    <script src="https://cdn.bootcdn.net/ajax/libs/axios/1.3.4/axios.min.js"></script>

    <script>
        // default size: 0.5MB
        function createThunk(file, size = 1024 * 1024 * 0.5) {
            const res = [];
            let cur = 0
            while (cur < file.size) {
                res.push({
                    tempFile: file.slice(cur, cur + size),
                });
                cur += size;
            }
            return res;
        }

        function selFile(event) {
            const file = event.currentTarget.files[0];
            const fileList = createThunk(file);
            console.log(file);
            // console.log(fileList);
            // 发送请求, uuid作为文件名
            // const uuid = crypto.randomUUID(); // Uncaught TypeError: crypto.randomUUID is not a function
            const uuid = file.name;
            const uploadList = fileList.map((item, index) => {
                const formData = new FormData();
                // formData includes chunk,name, filename
                formData.append('chunk', item.tempFile);
                formData.append('name', uuid + "_" + index);
                formData.append('filename', uuid);
                return axios.post('/upload_file_thunk', formData);
            });
            // after all files are uploaded
            Promise.all(uploadList).then((res) => {
                console.log('upload success');
                axios.post('/upload_thunk_end', {
                    filename: uuid,
                    extname: file.name.split('.').slice(-1)[0],
                }).then((res) => {
                    console.log(res.data);
                });
            });
        }    
    </script>
</body>

</html>

前端部分的代码分析如下:

  1. 异步的网络请求-上传文件选取的是axios作为工具,很符合promise风格,写起来也丝滑友好;
  2. 采用了输入框的失去焦点事件,失去焦点即上传文件。文件根据规定的大小0.5MB分块,用UUID+文件分片序号作为新的文件标识,异步的调用分片上传文件的接口
  3. 当所有的分片上传完毕之后,调用合并文件的接口,实现文件的合并。

是不是顿时感觉so easy了。我们再来看看后端的代码。

后端

文件名为:app.js

const express = require('express');
const multiparty = require('multiparty');
const fs = require('fs');
const path = require('path');

const app = express();

app.use(express.json());
app.use('/', express.static('./public'));

app.post('/upload_file_thunk', (req, res) => {
    const form = new multiparty.Form();
    form.parse(req, (err, fields, files) => {
        if (err) {
            res.json({
                code: 0,
                data: {},
            });
        } else {
            // save chunk files
            console.log(fields);
            fs.mkdirSync('./public/uploads/thunk/' + fields['filename'][0], {
                recursive: true
            });
            // move
            console.log('files', files);
            fs.renameSync(files['chunk'][0].path, './public/uploads/thunk/' + fields['filename'][0] + '/' + fields['name'][0]);
            res.json({
                code: 1,
                data: '分片上传成功',
            });
        }
    });
});

/**
 * 文件合并
 * @param {*} sourceFiles 源文件
 * @param {*} targetFile  目标文件
 */
function thunkStreamMerge(sourceFiles, targetFile) {
    const thunkFilesDir = sourceFiles;
    const list = fs.readdirSync(thunkFilesDir); // 读取目录中的文件

    const fileList = list
        .sort((a, b) => a.split('_')[1] * 1 - b.split('_')[1] * 1)
        .map((name) => ({
            name,
            filePath: path.resolve(thunkFilesDir, name),
        }));
    const fileWriteStream = fs.createWriteStream(targetFile);
    thunkStreamMergeProgress(fileList, fileWriteStream, sourceFiles);
}

/**
 * 合并每一个切片
 * @param {*} fileList        文件数据
 * @param {*} fileWriteStream 最终的写入结果
 * @param {*} sourceFiles     文件路径
 */
function thunkStreamMergeProgress(fileList, fileWriteStream, sourceFiles) {
    if (!fileList.length) {
        // thunkStreamMergeProgress(fileList)
        fileWriteStream.end('完成了');
        // 删除临时目录
        // if (sourceFiles)
        //     fs.rmdirSync(sourceFiles, { recursive: true, force: true });
        return;
    }
    const data = fileList.shift(); // 取第一个数据
    const { filePath: chunkFilePath } = data;
    const currentReadStream = fs.createReadStream(chunkFilePath); // 读取文件
    // 把结果往最终的生成文件上进行拼接
    currentReadStream.pipe(fileWriteStream, { end: false });
    currentReadStream.on('end', () => {
        // console.log(chunkFilePath);
        // 拼接完之后进入下一次循环
        thunkStreamMergeProgress(fileList, fileWriteStream, sourceFiles);
    });
}

// 合并切片
app.post('/upload_thunk_end', (req, res) => {
    const fileName = req.body.filename;
    const extName = req.body.extname;
    const targetFile = './public/uploads/' + fileName + '.' + extName;
    thunkStreamMerge('./public/uploads/thunk/' + fileName, targetFile);
    res.json({
        code: 1,
        data: targetFile,
    });
});


function getLocalIP() {
    const os = require('os');
    //获取本机ip
    var interfaces = os.networkInterfaces();
    for (var devName in interfaces) {
        var iface = interfaces[devName];
        for (var i = 0; i < iface.length; i++) {
            var alias = iface[i];
            if (alias.family === 'IPv4' && alias.address !== '127.0.0.1' && !alias.internal) {
                return alias.address;
            }
        }
    }
}


port = 9000;

const ip = getLocalIP();
console.log('ip', ip);
app.listen(port, () => console.log(`server running on ${getLocalIP()}:${port}......`));

这个代码就有点多了,115行,但是都是对应着后端的操作,并提供http服务。

shigen从分析每一个接口开始:

  1. /:主要是代理到public文件夹下,展示index.html,即我们上边的代码;
  2. upload_file_thunk:主要就是上传分片,并把分片从系统的某个空间转移到我们约定的目录之下
  3. upload_thunk_end: 主要就是合并我所有的分片了。它会调用我上边定义的方法,递归的拼接文件
  4. 最后的getLocalIP是我调用锡荣的工具类实现获得局域网下我的电脑IP地址,实现内网的相互访问和文件共享。岂不是很nice、smart!

那我启动起来就是一个命令即可:

node app.js

浏览器访问输出的IP+端口即可。

后记

最近突然有了一种偏见,这些设计完全都是没用的。因为仙子云服务这么成熟的了,对象存储这么成熟了,谁还成天研究这些东西。我们以腾讯云的对象存储COS为例子,我们看看腾讯云COS操作文档:

COS分片上传

作为云服务提供厂商,它已经帮我们想好了遇到的各种情况,甚至把相应的API设计好了。我们再去想破头实现,显得是那么的无意义。因为在云时代,我们更关注的是效率的提升和业务的增长。作为云服务厂商,它给我们提供了广大的平台,我们只需要拿来即用即可。

也希望每个企业,无论是国企、还是小公司、外包,拥抱云时代,别再花心思自研一些虚无的东西。业务的增长才是硬实力。


以上就是今天分享的全部内容了,觉得不错的话,记得点赞 在看 关注支持一下哈,您的鼓励和支持将是shigen坚持日更的动力。同时,shigen在多个平台都有文章的同步,也可以同步的浏览和订阅:

平台账号链接
CSDNshigen01shigen的CSDN主页
知乎gen-2019shigen的知乎主页
掘金shigen01shigen的掘金主页
腾讯云开发者社区shigenshigen的腾讯云开发者社区主页
微信公众平台shigen公众号名:shigen

shigen一起,每天不一样!

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

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

相关文章

8086读取键盘输入

文章目录 前言1.从键盘读数据 前言 想过一个问题没有&#xff0c; 8086是如何从键盘中接受输入的&#xff1f; 8086如何将字符在显示器上显示的&#xff1f; 8086如何从磁盘中读取数据的&#xff1f; 上面的问题都是没有操作系统的时候&#xff0c;比如bios的那段代码。 微型…

网络流量分类概述

1. 什么是网络流量&#xff1f; 一条网络流量是指在一段特定的时间间隔之内&#xff0c;通过网络中某一个观测点的所有具有相同五元组(源IP地址、目的IP地址、传输层协议、源端口和目的端口)的分组的集合。 比如(10.134.113.77&#xff0c;47.98.43.47&#xff0c;TLSv1.2&…

Seata分布式事务实现原理

Seata可以解决分布式事务问题&#xff0c;利用GlobalTransacational(name "fsp-create-order",rollbackFor Exception.class)注解就可以实现全局的事务管理&#xff0c;但是我们需要明白原理的实现。 我们举例创建订单——>调减库存——>调扣余额——>改订…

Linux--vim

文章目录 Vim的介绍Vim的几种模式命令模式下的基本操作批量化注释Vim的简单配置使用插件 Vim的介绍 Vim是一个强大的文本编辑器&#xff0c;是从vi编辑器发展而来的&#xff0c;在vi编辑器的基础上进行了改进和拓展&#xff0c;具有强大的特性和功能。 Vim是一个自由开源软件&…

CSS特效002:花样的鼠标悬停效果

GPT能够很好的应用到我们的代码开发中&#xff0c;能够提高开发速度。你可以利用其代码&#xff0c;做出一定的更改&#xff0c;然后实现效能。 css实战中&#xff0c;鼠标的悬停有各种各样的效果&#xff0c;下面的这个示例集中了填充、脉搏抖动&#xff0c;关闭&#xff0c;…

Qt插件开发_入门教程

文章目录 前言插件的好处具体流程1. 第一,我们先创建一个主框架应用(**第一个工程**)2. GUI 设计 ![在这里插入图片描述](https://img-blog.csdnimg.cn/f215270ccfac4e038e7261c4b4891ec1.png)3. 创建动态库项目(**第2个工程**)4. 给插件项目添加qt界面类5.在插件工程添加一个头…

矩阵乘积的迹对矩阵求导

矩阵A、B乘积的迹对矩阵A求导 下面来证明。

从研发域到量产域的自动驾驶工具链探索与实践

导读 本文整理自 2023 年 9 月 5 日百度云智大会 - 智能汽车分论坛&#xff0c;百度智能云自动驾驶云研发高级经理徐鹏的主题演讲《从研发域到量产域的自动驾驶工具链探索与实践》。 全文中部段落附有演讲中 2 个产品演示视频的完整版&#xff0c;精彩不容错过。 (视频观看&…

原文远知行COO张力加盟逐际动力 自动驾驶进入视觉时代?

11月7日&#xff0c;通用足式机器人公司逐际动力LimX Dynamics官宣了两位核心成员的加入。原文远知行COO张力出任逐际动力联合创始人兼COO&#xff0c;香港大学长聘副教授潘佳博士为逐际动力首席科学家。 根据介绍&#xff0c;两位核心成员的加入&#xff0c;证明一家以技术驱…

SAP-MM-查找采购订单的创建和修改日期

在采购订单页面可以查看采购订单的修改和创建&#xff0c;但是有些内容不能完成看到 例如这个订单显示是用户唐创建&#xff0c;但是他不记得是什么时候创建的&#xff0c;怎么创建的&#xff1f; 点击菜单-环境-表头更改、项目更改&#xff0c;可以查看更改内容 通过这个表可…

【解密ChatGPT】:从过去到未来,揭示其发展与变革

&#x1f38a;专栏【ChatGPT】 &#x1f33a;每日一句&#xff1a;天行健,君子以自强不息,地势坤,君子以厚德载物 ⭐欢迎并且感谢大家指出我的问题 文章目录 一、ChatGPT的发展历程 二、ChatGPT的技术原理 三、ChatGPT的应用场景 四、ChatGPT的未来趋势 五、总结 引言:随着…

kubernetes集群编排——k8s存储(configmap,secrets)

configmap 字面值创建 kubectl create configmap my-config --from-literalkey1config1 --from-literalkey2config2kubectl get cmkubectl describe cm my-config 通过文件创建 kubectl create configmap my-config-2 --from-file/etc/resolv.confkubectl describe cm my-confi…

ROS学习笔记(5):ros_control

1.ros_control简介 ros_control - ROS Wiki ros_control是为ROS提供的机器人控制包&#xff0c;包含一系列控制器接口、传动装置接口、控制器工具箱等,有效帮助机器人应用功能包快速落地&#xff0c;提高开发效率。 2.ros_control框架 ros_control总体框架&#xff1a; 针对…

vue使用websocket与springboot通信

WebSocket是HTML5下一种新的协议&#xff0c;它实现了浏览器与服务器全双工通信&#xff0c;能更好的节省服务器资源和带宽并达到实时通讯的目的 在很多项目中&#xff0c;都要用到websocket&#xff0c;使得前端页面与后端页进行实时通信&#xff0c;例如&#xff0c;实时查询…

SCI论文公式快捷获取

文章目录 前言IEEE论文公式获取Elsevier论文公式获取Springer的论文公式获取Wiley出版社公式获取SAGE出版社论文公式获取Taylor & Francis Group出版社论文公式获取latex源码转矢量公式 前言 相信许多做科研的同学对于文献公式如何提取很是抓狂&#xff0c;尤其是做PPT时&…

HCIP---企业网的三层架构

文章目录 前言一、pandas是什么&#xff1f;二、使用步骤 1.引入库2.读入数据总结 前言 一.园区网 概念&#xff1a; 园区网是指在园区内建立起来的互联网基础设施&#xff0c;包括网络设备、传输网络、网络管理系统等&#xff0c;其目的是为了方便企业和园区管理方在园区内进…

论文浅尝 | 基于对多条思维链的元推理实现智能问答

笔记整理&#xff1a;屠铭尘&#xff0c;浙江大学硕士&#xff0c;研究方向为知识图谱 链接&#xff1a;https://arxiv.org/abs/2304.13007 1. 动机 1.1 Chain of Thought的诞生 尽管大语言模型在许多自然语言处理任务上表现出色&#xff0c;但由于其本质是token by token的类似…

深度学习环境搭建入门环境搭建(pytorch版本)

从Python安装到深度学习环境搭建 1. Anaconda安装 python可以通过官网下载exe&#xff0c;这里提供的是使用anaconda创建多个虚拟 的python环境&#xff0c;使用Anaconda Prompt管理虚拟环境更方便。 官网地址&#xff1a;Free Download|Anaconda 下载到本地后双击此文…

Mall4cloud 微服务商城系统 2.0 发布

导读现在 jdk17 和 spring boot 以及 spring cloud alibaba 2022 的第三方依赖已经趋于成熟&#xff0c;所以 mall4cloud 也一把梭哈做了升级嗷。 本次更新重点&#xff1a; 系统由 jdk8 最低要求升级到 jdk17spring boot 由 2.7.x 升级到 3.1.xjavax 升级到 jakartaspring-cl…

Flink--简介

1、Apache Flink 是一个实时计算的框架和分布式处理引擎&#xff0c;用于在无边界喝有边界数据流上进行有状态的计算&#xff0c;并且能够在常见的集群上运行&#xff0c;并能以内存速度和任意规模进行计算。 有边界数据流&#xff1a;指的是有开始&#xff0c;也有结束&…