注:本文更适合于动手去敲(针对于新手而言),这样更能理解每一行代码所存在的意义,同时也更能理解API
刚开始写一些全栈小项目的人都有一些苦恼,就是如何将前端传图片传给后端,前端该怎么和后端强强联手将这个图片妥妥的放好?下面就为大家展示一个demo,去实现如何前端传图片给后端。
前端部分:(用的是vite去构建的React项目,然后代码是截取的其中一个片段)
import React from 'react';
import axios from 'axios';
export const PublicPicture = () => {
const submitUpload = () => {
// 切割文件
let chunkSize = 2 * 1024 * 1024 // 限制单个最大为2M
// 拿到文件
let file = document.getElementById('f1').files[0] // 这里展示的是一个图片的上传,如果需要多个图片只需要将这个做一个forEach循环就OK了
// 打标记, 时间戳
let token = (+ new Date())// 用时间戳来作为传数的唯一标识号
let name = file.name// 获取文件的名字
let chunkCount = 0// 传文件时,作为一个累加的数
let sendChunkCount = 0// 对于传出的文件,作为一个计数
let chunks = []// 将大于2M的文件分割成为一个数组
// 拆文件
if (file.size > chunkSize) {
let start = 0, end = 0;
while (true) {
end += chunkSize
let blob = file.slice(start, end)// 将file切割成单个为2M的数据流
start += chunkSize
if (!blob.size) { // 截不到东西
break;
}
chunks.push(blob) // 保存片段
}
} else {
chunks.push(file.slice(0))// 文件小于2M直接一次性传给后端
};
chunkCount = chunks.length; // 获取数组的长度,依次向后端发起请求
for (let i = 0; i < chunkCount; i++) {
let fd = new FormData();//new一个formData [不懂的可以戳这个链接](https://developer.mozilla.org/zh-CN/docs/Web/API/FormData)
fd.append('token', token)//时间戳 作为唯一标记
fd.append('f1', chunks[i])//片段
fd.append('index', i)// 数量
// 拿到了每次需要的,此时只需要朝后端发起请求就OK了
AxiosPost(fd, () => {
sendChunkCount += 1
if (sendChunkCount === chunkCount) {
let formD = new FormData()
formD.append('type', 'merge')
formD.append('token', token)
formD.append('chunkCount', chunkCount)
formD.append('filename', name)
AxiosPost(formD, undefined)
}
})
}
}
// 每次传完之后都会进行一次判断, 如果没有传完则会继续调用AxiosPost() 如果传完了则会发送合并请求
const AxiosPost = (fd, callback) => {
post('/', fd).then(res => {
console.log(res);
if (res.data.code === 202) {
callback && callback()
}
})
}
return <>
<br />
<input type="file" name="" id="f1" />
<br />
<button id="btn-submit" onClick={submitUpload}>上传</button>
</>
}
后端部分:(后端我采用的是koa作为框架)
const Koa = require('koa2')
const koaBody = require('koa-body')//koa-body 是一个可以帮助解析 http 中 body 的部分的中间件,包括 json、表单、文本、文件等。
const cors = require('koa2-cors')// 解决跨域
const path = require('path')
const fs = require('fs') // 文件读写
const port = process.env.port || '3001' // 启动默认端口,如果被占用则用第二个选择
const uploadHost = `http://loaclhost:${port}`
const app = new Koa();
app.use(cors());//解决跨域问题
app.use(koaBody({
formidable: {
uploadDir: path.resolve(__dirname, './static'),// 可以填一个路径
maxFileSize: 10 * 1024 * 1024 // 默认2M
},
multipart: true ////解析多个文件
}))
app.use((ctx) => {
// 处理接受文件的片段 为ctx.request.files
let files = ctx.request.files ? ctx.request.files.f1 : [] // 找前端传过来的f1
let body = ctx.request.body // 简写
let result = []
let fileToken = body.token // 前端传过来的时间戳
let fileIndex = body.index // 前端传过来的下标
if (files && !Array.isArray(files)) {
files = [files] //如果不是数组,将files变成数组
}
files && files.forEach(item => {
let path = item.path.replace(/\\/g, '/') // 正则,将例如C:\workspace 转化为 C:/workspace
let fname = item.name // 拿到每一项的名字 blob
let nextPath = path.slice(0, path.lastIndexOf('/') + 1) + fileIndex + '-' + fileToken; // 下一个路径
if (item.size > 0 && path) {
fs.renameSync(path, nextPath) // 改路径的名字。改成自己取的名字 例如 C:/workspace/js/bigFile/server/static/upload_1e387a3f62851216089e83f4c1a5181b' 改成 C:/workspace/js/bigFile/server/static/Index-fileToken;
console.log(nextPath);
result.push(uploadHost + nextPath.slice(nextPath.lastIndexOf('/') + 1))// 截取文件片段名字
}
})
// 合并文件片段
if (body.type === 'merge') {
let filename = body.filename //拿到最后一次传过来的参数,包含文件名字
let chunkCount = body.chunkCount // 拿到总数
let folder = path.resolve(__dirname, './static') + '/'; // 拿到路径
let writeStram = fs.createWriteStream(`${folder}${filename}`) //创建出写文件的流,接受的参数是文件的路径
let cindex = 0
// 合并
const fnMergeFile = () => {
let fname = `${folder}${cindex}-${fileToken}`;
let readStream = fs.createReadStream(fname); // 读文件流
readStream.pipe(writeStram, { end: false }) // 会自己去找相关的文件,然后将其合并到第一个参数里边去,如果文件不完整,则会抛出错误
readStream.on('end', () => {
fs.unlink(fname, (err) => {//错误捕获
if (err) {
throw err
}
})
if (cindex + 1 < chunkCount) {
cindex += 1
fnMergeFile()
}
})
}
fnMergeFile()
ctx.body = {
code: 200,
msg: `上传成功!`
}
} else {
ctx.body = {
code: 202,
fileUrl: `${JSON.stringify(result)}`
}
}
})
app.listen(port, () => {
console.log(`服务启动在${port}`);
})
上边前后端代码可以直接拿去使用,没有任何副作用(除了一些基本的包的安装和react的构建)。 然后最后就是,本人是一枚前端小白,如果上述有不对或者不理解的东西欢迎大家留言或者私聊讨论指出,最后,码字不易,如果这篇文章对你有所帮助请帮忙点个赞吧~