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

news2024/11/16 15:26:27

提示:学习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
parans:{
“userName”: “longlongago1”,
“password”: “long123456”
}
在这里插入图片描述

总结

踩坑路漫漫长@~@

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

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

相关文章

UI自动化-(web端下拉选择框弹出框滚动条操作-实操入门)

1、下拉选择框操作 在 UI 自动化中,操作下拉选择框可以通过以下步骤进行: 定位下拉选择框元素:通过适当的元素定位方法,找到下拉选择框的元素。打开下拉框:例如通过点击(.click)来操作下拉框元素打开下拉框。选择选项:可以通过以下几种方式选择下拉框中的选项:根据索…

【01】openEuler 源码安装 PostgreSQL

openEuler 源码安装 PostgreSQL 部署环境说明Shell 前端软件包管理器基础概念YUM 简介DNF 简介 源码安装 PostgreSQL环境变量&#xff08;env&#xff09;设置临时环境变量设置永久环境变量设置 初始化数据库&#xff08;initdb&#xff09; 数据库基本操作数据库基本配置&…

【漏洞复现】某厂商明御WEB应用防火墙任意用户登录漏洞

Nx01 产品简介 安恒明御WEB应用防火墙&#xff08;简称WAF&#xff09;是杭州安恒信息技术股份有限公司自主研发的一款专业应用安全防护产品&#xff0c;专注于为网站、APP等Web业务系统提供安全防护。 Nx02 漏洞描述 安恒明御WEB应用防火墙report.php文件存在硬编码设置的Con…

当代人吃瓜成习惯!后续来了!人生进阶5个方法——早读(逆天打工人爬取热门微信文章解读)

让瓜再熟一点 引言python代码第一篇 春秋航空 开年首场机票限时抢&#xff01;春日畅飞&#xff0c;多重好礼第二篇 人民日报 “我该给十年前的支教老师打电话吗&#xff1f;”后续来了&#xff01;第三篇 人民日报 【夜读】想要人生进阶&#xff0c;试试这五种方法第四篇&…

python自动化之项目架构搭建与思路讲解(第二天)

1.自动化测试的概念 自动化测试是指使用自动化工具和脚本来执行测试任务,以验证软件或系统的正确性和稳定性。它可以提高测试的效率和准确性,并节约时间和成本。 2.自动化脚本编写的思路 xmind文档如有需要,可在资源里自行下载 3.项目代码工程创建 lib :基本代码库包 …

【Leetcode】2369. 检查数组是否存在有效划分

文章目录 题目思路代码结果 题目 题目链接 给你一个下标从 0 开始的整数数组 nums &#xff0c;你必须将数组划分为一个或多个 连续 子数组。 如果获得的这些子数组中每个都能满足下述条件 之一 &#xff0c;则可以称其为数组的一种 有效 划分&#xff1a; 子数组 恰 由 2 个…

Thinkphp框架漏洞--->5.0.23 RCE

1.Thinkphp ThinkPHP是一个免费开源的&#xff0c;快速、简单的面向对象的轻量级PHP开发框架&#xff0c;是为了敏捷WEB应用开发和简化 企业应用开发而诞生的。 2.漏洞原理及成因 该漏洞出现的原因在于 ThinkPHP5框架底层对控制器名过滤不严 &#xff0c;从而让攻击者可以通过…

抖音小店无货源怎么做起来?正确玩法是什么?做店步骤都在这里了

大家好&#xff0c;我是电商花花。 新的一年肯定又会有不少创业者疑问2024年抖音小店还能继续做吗&#xff1f;怎么做才能赚到钱&#xff1f; 尤其是现在做无货源模式&#xff0c;还会被判罚吗&#xff1f; 我认为2024年的抖音小店不管是有货源还是无货源都是可以做的&#…

YOLOv9中加入SCConv模块!

专栏介绍&#xff1a;YOLOv9改进系列 | 包含深度学习最新创新&#xff0c;主力高效涨点&#xff01;&#xff01;&#xff01; 一、本文介绍 本文将一步步演示如何在YOLOv9中添加 / 替换新模块&#xff0c;寻找模型上的创新&#xff01; 适用检测目标&#xff1a; YOLOv9模块…

《梦幻西游》本人收集的34个单机版游戏,有详细的视频架设教程,值得收藏

梦幻西游这款游戏&#xff0c;很多人玩&#xff0c;喜欢研究的赶快下载吧。精心收集的34个版本。不容易啊。里面有详细的视频架设教程&#xff0c;可以外网呢。 《梦幻西游》本人收集的34个单机版游戏&#xff0c;有详细的视频架设教程&#xff0c;值得收藏 下载地址&#xff1…

【Web安全靶场】sqli-labs-master 54-65 Challenges 与62关二分法和like模糊搜索

sqli-labs-master 54-65 Challenges 其他关卡和靶场见专栏… 文章目录 sqli-labs-master 54-65 Challenges第五十四关-联合注入第五十五关-联合注入第五十六关-联合注入第五十七关-联合注入第五十八关-报错注入第五十九关-报错注入第六十关-报错注入第六十一关-报错注入第六十…

基于zemax的激光合束过程分析

系统里的透镜包括FAC/SAC及球面聚焦镜都是采用市面上标准的透镜&#xff0c;在典型的光纤耦合14针蝶形封装中&#xff0c;最多需要三个独立的透镜才能提供有效且稳定的耦合。大多数高端激光二极管使用两个交叉的柱面方形微透镜来补偿激光二极管快轴和慢轴的发散角之间的差异。第…

Python 编程中的迭代器、生成器和装饰器探究【第110篇—迭代器】

Python 编程中的迭代器、生成器和装饰器探究 在Python编程中&#xff0c;迭代器&#xff08;Iterators&#xff09;、生成器&#xff08;Generators&#xff09;和装饰器&#xff08;Decorators&#xff09;是三个强大的概念&#xff0c;它们为代码的可读性、效率和灵活性提供…

准备车载测试面试的小伙伴,赶紧看起来!

随着现代汽车的电子化程度越来越高&#xff0c;汽车总线系统也变得越来越复杂。汽车总线测试是一项重要的任务&#xff0c;它有助于确定车辆电子系统中的问题&#xff0c;并保障车辆的安全和可靠性。 CAN总线…… 控制器区域网&#xff08;Controller Area Network&#xff0c…

from tensorflow.keras.layers import Dense,Flatten,Input报错无法引用

from tensorflow.keras.layers import Dense,Flatten,Input 打印一下路径&#xff1a; import tensorflow as tf import keras print(tf.__path__) print(keras.__path__) [E:\\开发工具\\pythonProject\\studyLL\\venv\\lib\\site-packages\\keras\\api\\_v2, E:\\开发工具\\…

什么是人才储备?如何做人才储备?

很多小伙伴都会有企业面试被拒的情况&#xff0c;然后HR会告诉你&#xff0c;虽然没有录用你&#xff0c;但是你进入了他们的人才储备库&#xff0c;那么这个储备库有什么作用和特点呢&#xff1f;我们如何应用人才测评系统完善人才储备库呢&#xff1f; 人才储备一般有以下三…

备考2025年AMC8数学竞赛:2000-2024年AMC8真题练一练

想了解如何提高小学和初中数学成绩&#xff1f;小学和初中可以参加的数学竞赛有哪些&#xff1f;不妨看看AMC8美国数学竞赛&#xff0c;现在许多小学生和初中生都在参加这个比赛。如果孩子有兴趣&#xff0c;有余力的话可以系统研究AMC8的历年真题&#xff0c;即使不参加AMC8竞…

链式插补 (MICE):弥合不完整数据分析的差距

导 读 数据缺失可能会扭曲结果&#xff0c;降低统计功效&#xff0c;并且在某些情况下&#xff0c;导致估计有偏差&#xff0c;从而破坏从数据中得出的结论的可靠性。 处理缺失数据的传统方法&#xff08;例如剔除或均值插补&#xff09;通常会引入自己的偏差或无法充分利用数…

鸿蒙Harmony应用开发—ArkTS声明式开发(自定义事件分发)

ArkUI在处理触屏事件时&#xff0c;会在触屏事件触发前进行按压点和组件区域的触摸测试&#xff0c;来收集需要响应触屏事件的组件&#xff0c;再基于触摸测试结果分发相应的触屏事件。在父节点&#xff0c;开发者可以通过onChildTouchTest决定如何让子节点去做触摸测试&#x…

《Spring Security 简易速速上手小册》第6章 Web 安全性(2024 最新版)

文章目录 6.1 CSRF 防护6.1.1 基础知识详解CSRF 攻击原理CSRF 防护机制最佳实践 6.1.2 重点案例&#xff1a;Spring Security 中的 CSRF 防护案例 Demo测试 CSRF 防护 6.1.3 拓展案例 1&#xff1a;自定义 CSRF 令牌仓库案例 Demo测试自定义 CSRF 令牌仓库 6.1.4 拓展案例 2&am…