提示:学习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 --savenpm 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+'/'+chunkMD5if(!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+porttimeout: 5000,headers: {'Content-Type': 'multipart/form-data',"authorization":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyTmFtZSI6Imxvbmdsb25nYWdvMSIsInBhc3N3b3JkIjoibG9uZzEyMzQ1NiIsImlhdCI6MTcwOTIwMjQ1NiwiZXhwIjoxNzA5MjA2MDU2fQ.XztBbjm2BbeQB7-OIXX040xuNxR5gnioCCgNV2c5NGI",},withCredentials: false, // defaultresponseType: 'json', // defaultmaxContentLength: 2000,});//上传文件计数对象let postFileObj = {};//上传切片计数对象let postChunkObj = {};//组件上传文件const goUpload = (files)=>{postFileObj = {};postChunkObj = {};upload(files[0])}//上传文件const upload = async (file)=>{//文件blobconst 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转换成stringconst blodToString = (blob)=>{return new Promise((resolve,reject)=>{reader.onloadend = (e)=>{resolve(md5(e.target.result));}reader.readAsText(blob);});}//file转换成chunksconst fileToChunks = (file)=>{//切片数组let chunks = [];//每个chunk大小const chunkSize = 1024*128;//转换成多少个chunkconst 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}失败`))})});}//合并多个chunkmergeChunks = (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”
}
总结
踩坑路漫漫长@~@