express+mysql+vue,从零搭建一个商城管理系统8--文件上传,大文件分片上传

news2024/11/15 9:45:48

提示:学习express,搭建管理系统

文章目录

  • 前言
  • 一、安装multer,fs-extra
  • 二、新建config/upload.js
  • 三、新建routes/upload.js
  • 四、修改routes下的index.js
  • 五、修改index.js
  • 六、新建上传文件test.html
  • 七、开启jwt验证token,通过login接口生成token
  • 总结


前言

需求:主要学习express,所以先写service部分

一、安装multer,fs-extra

npm install multer --save
npm install fs-extra --save

在这里插入图片描述

二、新建config/upload.js

upload.js

const fs = require('fs');
const fsExtra = require('fs-extra');
const path = require('path');
const rootDir = path.resolve(__dirname,'../upload/');
const temporaryDir = path.resolve(__dirname,'../upload/temporary/');
const errFun = (msg,code)=>{
    return {
        code:code||500,
        success:false,
        msg:msg||'操作失败'
    }
}
const sucFun = (data,msg)=>{
    return {
        code:200,
        success:true,
        msg:msg||'操作成功',
        data,
    }
}
const uploadUtil = {
    upload:(req)=>{
        const { fileMD5,chunkMD5 } = req.body;
        return new Promise ((resolve,reject)=>{
            const folderPath = temporaryDir+'/'+fileMD5;
            const filePath = temporaryDir+'/'+fileMD5+'/'+chunkMD5
            if(!fs.existsSync(folderPath))fs.mkdirSync(folderPath);
            fs.writeFile(filePath,req.file.buffer,(err)=>{
                if(err)reject(errFun('切片上传失败'));
                resolve(sucFun({},'上传成功'));
            })
        });
    },
    merge:async(req)=>{
        const {fileMD5,chunkMD5Arr,type} = req.body;
        const folderPath = temporaryDir+'/'+fileMD5;
        let chunkBufferArr = [];
        for(let i=0;i<chunkMD5Arr.length;i++){
            let chunkBuffer = await fs.readFileSync(folderPath+'/'+chunkMD5Arr[i]);
            chunkBufferArr.push(chunkBuffer);
        }
        console.log(chunkBufferArr)
        let fileurl = rootDir+'/images/'+fileMD5+'-'+(new Date().getTime())+'.'+type;
        fs.writeFile(fileurl,Buffer.concat(chunkBufferArr),(err)=>{
            if(err)errFun(fileMD5+'文件切片merge失败');
            sucFun({url:fileurl},'文件切片merge成功');
            //删除临时文件夹以及文件夹下的所有文件
            fsExtra.removeSync(folderPath);
        });
    }
}
module.exports = uploadUtil;

在这里插入图片描述

三、新建routes/upload.js

const uploadUtil = require('../config/upload');
//文件流的key与multer的single方法的参数param,必须保持一致,不一致接收不到文件流
 //fd.append('xxx',chunk);
 //multer().single('xxxx');

const multer = require('multer');
const chunk = multer().single('chunk');

const uploadRoutes = (router)=>{
    router.post('/upload/upload',chunk,async (req,res)=>{
        const result = await uploadUtil.upload(req);
        res.json(result);
    });
    router.post('/upload/merge',async (req,res)=>{
        const result = await uploadUtil.merge(req);
        res.json(result);
    });
}
module.exports = uploadRoutes;

在这里插入图片描述

四、修改routes下的index.js

const userRoutes = require('./user');
const uploadRoutes = require('./upload');
const routes = (router)=>{
    //user路由
    userRoutes(router);
    //upload路由
    uploadRoutes(router);
}

module.exports = routes;

在这里插入图片描述

五、修改index.js

注释jwt的token验证,方便测试。如果打开验证,需要通过login接口生成token,上传文件request请求头部携带

const express = require('express');
const app = express();
const router = express.Router();
const jwt = require('./config/jwt');

const bodyParser = require('body-parser');
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());

const port = 1990;

app.all('*',  (req, res, next) => {
    res.header('Access-Control-Allow-Origin', '*');
    res.header("Access-Control-Allow-Headers", "Authorization,token,content-type");
    res.header('Access-Control-Allow-Methods', '*');
    res.header('Content-Type', 'application/json;charset=utf-8');
    next();
});

//全局验证token
// app.use('/*',(req,res,next)=>{
//     let notValidateData = ['/user/login','/user/register'];
//     if(notValidateData.indexOf(req.baseUrl)>-1)return next();
//     if((jwt.verify(req.headers.authorization||'')||{}).success)return next();
//     return res.json({success:false,code:500,msg:'token验证失败'});
// })

//初始化路由
require('./routes/index')(router);

app.use('/', router);
app.listen(port,()=>{
    console.log('http://localhost:'+port);
})

在这里插入图片描述

六、新建上传文件test.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>test upload</title>
</head>
<body>
    <input type="file" onchange="goUpload(this.files)">
    <script type="text/javascript" src="https://cdn.bootcss.com/axios/0.19.0-beta.1/axios.js"></script>
    <script type="text/javascript" src="https://cdn.bootcdn.net/ajax/libs/blueimp-md5/2.16.0/js/md5.js"></script>
    <script>
        const reader = new FileReader();
        const instance = axios.create({
            baseURL: 'http://localhost:1990/', //后台接口url+port
            timeout: 5000,
            headers: {
                'Content-Type': 'multipart/form-data',
                "authorization":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyTmFtZSI6Imxvbmdsb25nYWdvMSIsInBhc3N3b3JkIjoibG9uZzEyMzQ1NiIsImlhdCI6MTcwOTIwMjQ1NiwiZXhwIjoxNzA5MjA2MDU2fQ.XztBbjm2BbeQB7-OIXX040xuNxR5gnioCCgNV2c5NGI",
            },
            withCredentials: false, // default
            responseType: 'json', // default
            maxContentLength: 2000,
        });
        //上传文件计数对象
        let postFileObj = {};
        //上传切片计数对象
        let postChunkObj = {};
        //组件上传文件
        const goUpload = (files)=>{
            postFileObj = {};
            postChunkObj = {};
            upload(files[0])
        }
        //上传文件
        const upload = async (file)=>{
            //文件blob
            const fileBlob = file.slice(0,file.size);
            //文件hash,用作后端新建临时文件夹名和上传完成的文件命名参数  并且,相同文件内容(文件名可不同)进行上传,生成的fileMD5是相同的,如有需要,可根据存储的文件名判断,不需要重新上传
            const fileMD5 = await blodToString(fileBlob);
            //切片数组
            const chunks = fileToChunks(file);
            //切片hash 用来命名生成的临时文件,并在所有切片上传完成,按顺序,读取切片,生成文件,如果不按照顺序,生成文件会因为写入顺序不对而乱码
            const chunkMD5Arr = [];
            //初始化当前文件上传计数
            if(!postFileObj[fileMD5])postFileObj[fileMD5] = {count:1}
            for(let i=0;i<chunks.length;i++){
                const chunkMD5 = await blodToString(chunks[i]);
                chunkMD5Arr.push(chunkMD5);
                //初始化key为fileMD5的切片组
                postChunkObj[fileMD5] = {};
                //初始化key为fileMD5的切片组中key为chunkMD5的切片计数对象
                postChunkObj[fileMD5][chunkMD5] = {success:false,count:1}
                await postChunk(chunks[i],fileMD5,chunkMD5);
            }
            let postAllChunk = true;
            //判断切片是否都已上传完成
            for(let key in postChunkObj[fileMD5]){
                if(!postChunkObj[fileMD5][key].success){
                        postAllChunk = false;
                        break
                }
            }
            // 所有切片都已上传成功
            if(postAllChunk){
                //调用merge方法
                const typeArr = file.name.split('.');
                mergeChunks(fileMD5,chunkMD5Arr,typeArr[typeArr.length-1]);
            }else{
                //有切片上传失败,重新执行上传
                postFileObj[fileMD5].count++;
                if(postFileObj[fileMD5].count>10) return console.log('文件上传失败');
                //重新上传
                await upload(file);
            }
        }
        //blob转换成string
        const blodToString = (blob)=>{
            return new Promise((resolve,reject)=>{
                reader.onloadend = (e)=>{
                    resolve(md5(e.target.result));
                }
                reader.readAsText(blob);
            });
        }
        //file转换成chunks
        const fileToChunks = (file)=>{
            //切片数组
            let chunks = [];
            //每个chunk大小
            const chunkSize = 1024*128;
            //转换成多少个chunk
            const chunkCount = Math.ceil(file.size/chunkSize);
            for(let i=0;i<chunkCount;i++){
                if(i==chunkCount-1){
                    chunks.push(file.slice(i*chunkSize,file.size));
                }else{
                    chunks.push(file.slice(i*chunkSize,(i+1)*chunkSize));
                }
            }
            return chunks;
        }
        //上传切片
        const postChunk = (chunk,fileMD5,chunkMD5)=>{
            let fd = new FormData();
            //文件流的key与multer的single方法的参数param,必须保持一致,不一致接收不到文件流
            /**
             * fd.append('xxx',chunk);
             * multer().single('xxxx');
             * **/
            fd.append('chunk',chunk);   
            fd.append('fileMD5',fileMD5);
            fd.append('chunkMD5',chunkMD5);
            let postCount = 0;
            return new Promise((resolve,reject)=>{
                instance.post('upload/upload',fd).then((res)=>{
                    if(res.data&&res.data.success){
                        postChunkObj[fileMD5][chunkMD5].success = true;
                        resolve(console.log(`上传切片${chunkMD5}成功`));
                    }else{
                        //记录当前切片上传次数
                        postChunkObj[fileMD5][chunkMD5].count++;
                        //当前切片上传超过10次,终止上传
                        if(postChunkObj[fileMD5][chunkMD5].count<11) { 
                            //上传失败,重新上传
                            postChunk(chunk,fileMD5,chunkMD5);
                        }
                    }
                    resolve(console.log(`上传切片${chunkMD5}失败`))
                })
            });
        }
        //合并多个chunk
        mergeChunks = (fileMD5,chunkMD5Arr,type)=>{
            // let fd = new FormData();
            // fd.append('chunk',Buffer.form(''));   
            // fd.append('fileMD5',fileMD5);
            // fd.append('chunkMD5Arr',chunkMD5Arr);
            // fd.append('type',type);
            // instance.post('upload/merge',fd).then((res)=>{

            // });
            axios.post('http://localhost:1990/upload/merge',{fileMD5,chunkMD5Arr,type}).then((res)=>{

            });
        }
    </script>
    
</body>
</html>

七、开启jwt验证token,通过login接口生成token

添加用户
url:http://localhost:1990/user/login
name:/user/login
params:{
“userName”: “longlongago1”,
“password”: “long123456”
}
在这里插入图片描述

总结

踩坑路漫漫长@~@

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

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

相关文章

渗透测试专用firefox浏览器 v2024.2最新版!!!

前言 之前做了一个firefox渗透测试专用浏览器v1&#xff0c;并且经过广大师傅的意见以及建议部分功能进行了调整以及新增。 如有相关意见&#xff0c;可以加入我们交流群进行反馈。 更新内容 去除DarkReader&#xff08;夜间模式&#xff09; 修改firefox主题&#xff0c;使…

【促销定价】背后的算法技术3-数据挖掘分析

【促销定价】背后的算法技术3-数据挖掘分析 01 整体分析1&#xff09;整体概览2&#xff09;类别型特征概览3&#xff09;数值型特征概览 02 聚合分析1&#xff09;天维度2&#xff09;品维度3&#xff09;价格维度4&#xff09;数量维度 03 相关分析1&#xff09;1级品类2&…

如何准备2024年汉字小达人:历年考题练一练-18道选择题解析

距离2024年第11届汉字小达人比赛还有八个多月的时间&#xff0c;建议如果有可能都让孩子参加一下&#xff0c;无需报名费&#xff0c;如果没时间准备也可以直接上阵参赛&#xff0c;检验一下孩子语文字、词、成语和古诗文方面的掌握情况。一方面可以激发孩子学习语文的兴趣&…

高级货,极大提高效率,个人非常喜欢

软件简介&#xff1a; 软件【下载地址】获取方式见文末。注&#xff1a;推荐使用&#xff0c;更贴合此安装方法&#xff01; FileConverter中文版是一款免费软件&#xff0c;具有强大的功能。它支持多种文件格式的转换&#xff0c;包括视频、音频、文档等。您可以批量转换文件…

Unity 动态加载音频和音效

想要加载音效和音频需要两个组件&#xff1a; 听&#xff1a; 播&#xff1a; 一收一发 在层级中&#xff0c;右键创建 音频源 &#xff0c;放入物体的子物体中。 播放 方式一 拖动需要播放的音频文件到&#xff0c;音频源组件中。 using System.Collections; using Syst…

如何在Linux系统使用docker部署Apache Superset并结合内网穿透实现公网远程访问

文章目录 前言1. 使用Docker部署Apache Superset1.1 第一步安装docker 、docker compose1.2 克隆superset代码到本地并使用docker compose启动 2. 安装cpolar内网穿透&#xff0c;实现公网访问3. 设置固定连接公网地址 前言 Superset是一款由中国知名科技公司开源的“现代化的…

C# WinForm AndtUI第三方库 Tree控件使用记录

环境搭建 1.在NuGet中搜索AndtUI并下载至C# .NetFramework WinForm项目。 2.添加Tree控件至窗体。 使用方法集合 1.添加节点、子节点 using AntdUI; private void UpdateTreeView() {Tree tvwTestnew Tree();TreeItem rootTreeItem;TreeItem subTreeItem;Dictionary<str…

微信小程序用户隐私保护指引设置

场景&#xff1a;开发小程序时&#xff0c;有时候需要获取用户隐私信息&#xff0c;在提交小程序审核时&#xff0c;需要填写一份隐私保护协议&#xff0c;经常由于填写不规范导致审核不通过&#xff0c;在网上找到了一份模块可供参考 步骤&#xff1a;小程序后台-》设置-》服…

精品网站分享,建议关注收藏!~

01 练习打字 一个简约风格的可自定义主题、可切换字体的打字记录和键盘测试网站。 Typing | 一个简约风格的可自定义主题、可切换字体的打字记录和键盘测试网站。 typing.yasinchan.com 是一个可以测试键盘按键与打字速度并可以记录到排行榜的具有多主题简约风格的网站&#x…

llc半桥开关电源基础知识2(电路图简化)

llc半桥开关电源拓扑图如下 稳态:LLC电源已经正常工作,已经输出电压稳定稳态:LLC电源已经正常工作,已经输出电压稳定。 我们在分析拓扑结构的时候,都是基于他已经正常稳定输出的时候来分析的,毕竟LC电源只要以工作啊,绝大多数时间都是工作在稳态。 具体电路图化简分析如…

教育知识与能力保分卷一(中学)

2.在教育学的发展过程中&#xff0c;代表马克思主义的教育学著作是&#xff08;A &#xff09;。 A.凯洛夫的《教育学》 B.赞可夫的《教学与发展》 C.杜威的《民主主义与教育》 D.昆体良的《论演说家的教育》 8.小贺在一次期…

mysql学习笔记7——数据库查询深入

.sql文件 在实际使用数据库时&#xff0c;常常要对数据库文件进行备份&#xff0c;以便在数据库遭到入侵或者非人为因素导致损坏后&#xff0c;快速恢复数据 .sql文件便提供了这种功能&#xff0c;首先.sql文件是由一串串mysql指令组成的&#xff0c;我们插入.sql文件实际相当…

OpenDDS 在 Windows 上的编译环境部署指南

目录 1、OpenDDS2、编译OpenDDS2.1、准备工作2.2、配置环境变量2.3、编译-TAO_IDL_ACE2.4、编译-TAO_ACE2.5、编译-ACE2.7、生成OpenDDS的解决方案2.8、编译-DDS_no_test2.9、编译-DDS 1、OpenDDS OpenDDS是使用C语言针对OMG数据分发服务(DDS)的一种开源实现。由OCI公司设计和…

K8s集群调度,亲和性,污点,容忍,排障

目录 1.调度约束 调度过程 指定调度节点 查看详细事件 获取标签帮助 修改成 nodeSelector 调度方式 2.亲和性 节点亲和性 Pod 亲和性 键值运算关系 硬策略 软策略 Pod亲和性与反亲和性 创建一个标签为 appmyapp01 的 Pod 使用 Pod 亲和性调度&#xff0c;创建多…

CUDA学习笔记03:封装CUDA函数给Qt使用

参考资料 QT 调用 CUDA实现GPU加速图像处理&#xff08;chapter1&#xff09;_qt 如何使用cuda加速-CSDN博客 平时工作用Qt多一些&#xff0c;需要将封装的CUDA函数在Qt里调用&#xff0c;这里举一个简单的例子。 1. 建立vs dll工程 在vs2019中建立一个dll工程&#xff0c;…

LLM春招准备(1)

llm排序 GPT4V GPT-4V可以很好地理解直接绘制在图像上的视觉指示。它可以直接识别叠加在图像上的不同类型的视觉标记作为指针&#xff0c;例如圆形、方框和手绘&#xff08;见下图&#xff09;。虽然GPT-4V能够直接理解坐标&#xff0c;但相比于仅文本坐标&#xff0c;GPT-4V在…

06. Nginx进阶-Nginx代理服务

proxy代理功能 正向代理 什么是正向代理&#xff1f; 正向代理&#xff08;forward proxy&#xff09;&#xff0c;一个位于客户端和原始服务器之间的服务器。 工作原理 为了从原始服务器获取内容&#xff0c;客户端向代理发送一个请求并指定目标&#xff08;即原始服务器…

14 数值稳定性 + 模型初始化和激活函数【李沐动手学深度学习v2笔记】

1. 数值稳定性 神经网络的梯度 向量对向量求导&#xff08;梯度&#xff09;得到矩阵&#xff0c;太多的矩阵进行乘法会导致常见的两个问题 梯度消失和梯度爆炸 MLP MLP使用ReLU作为激活函数 梯度爆炸的问题 输入很大的时候梯度接近为0 梯度消失 梯度消失的问题 只能训练比…

快速上手:在 Android 设备上运行 Pipy

Pipy 作为一个高性能、低资源消耗的可编程代理&#xff0c;通过支持多种计算架构和操作系统&#xff0c;Pipy 确保了它的通用性和灵活性&#xff0c;能够适应不同的部署环境&#xff0c;包括但不限于云环境、边缘计算以及物联网场景。它能够在 X86、ARM64、海光、龙芯、RISC-V …

(3)(3.2) MAVLink2数据包签名(安全)

文章目录 前言 1 配置 2 使用 3 MAVLink协议说明 前言 ArduPilot 和任务计划器能够通过使用加密密钥添加数据包签名&#xff0c;为空中 MAVLink 传输增加安全性。这并不加密数据&#xff0c;只是控制自动驾驶仪是否响应 MAVLink 命令。 当自动驾驶仪处于激活状态时&#x…