1.原生的文件上传
<input id="uploadValue" type="file" multiple="multiple" onchange="fileChange()">
<script>
function fileChange() {
const value = document.getElementById('uploadValue').value
const files = document.getElementById('uploadValue').files
console.log(value, 'value') // 表示选择文件的路径
console.log(files, 'files') // 表示已选择文件的数组集合
const formData = new FormData()
formData.append("userfile", files[0]) //拿到第一个文件放到formData里面
console.log(formData, 'formData has file')
const res = formData.get('userfile') // 也能通过get去获取对应的文件,也能看到file里面有什么信息
console.log(res, 'res')
}
</script>
多文件上传,先选择,然后按住ctrl,可以选择多个,那么显示的就是已经选中n个文件
2.form表单以及formData的作用
其实我们在做表单提交的时候,会用到FormData这个构造函数,那么为什么要用这个呢?
2.1 form表单
参考:
表单,FormData 对象
form标签
<form action="form_action.asp" method="get">
<p>First name: <input type="text" name="fname" /></p>
<p>Last name: <input type="text" name="lname" /></p>
<input type="submit" value="Submit" />
</form>
在点击提交的时候(type="submit")的时候,浏览器会自动将form表单里面的数据以键值对的方式,提交给服务器,但是我们平时提交,肯定不希望是浏览器自动去完成这个提交到服务器的操作,我们只需要获取到form表单里面的数据,就行了,什么时候提交到服务器,由我们自己决定,所以浏览器原生提供了 FormData 对象来完成这项工作,FormData()构造函数的参数是一个 DOM 的表单元素,构造函数会自动处理表单的键值对
2.2 FormData 构造函数
现在来实现,只获取表单中的数据,而不需要进行自动提交到服务器
<form id="form">
<p>First name: <input type="text" name="fname" /></p>
<p>Last name: <input type="text" name="lname" /></p>
<input type="button" value="Submit" onclick="getFormData()" />
</form>
function getFormData() {
const data = document.getElementById('form')
const formData = new FormData(data);
console.log(formData.get('fname'), 'First name')
console.log(formData.get('lname'), 'Last name')
}
FormData是一个构造函数,能够通过set去添加属性,能够通过get去获取属性
3.实现使用element ui两种上传方式
3.1 利用action自动上传
<el-upload
ref="upload"
class="upload-demo"
:action="getUrl()"
:headers="uploadHeaders()"
:on-success="uploadFile"
:on-remove="handleRemove"
:before-remove="beforeRemove"
:file-list="fileList"
:on-exceed="handleExceed"
:limit="1"
accept=".so, .zip"
>
<el-button size="small" type="primary">点击上传</el-button>
</el-upload>
getUrl() {
return `${process.env.API_URL}接口名`
},
uploadHeaders() {
return {
'Authorization': `Bearer ${this.token}`,
}
},
uploadFile(res, file, fileList) {
console.log(res, 'res')
//上传结果res,可以根据code判断
},
beforeRemove(file, fileList) {
return this.$confirm(`确定移除 ${file.name}?`)
},
handleExceed() {
this.$message.warning(`当前限制选择1个文件,已经存在了1个文件`)
},
要注意的是:
前端用xhr.send(formData)上传,服务端接收body,然后后端去解析formData,所以使用post请求
服务端可以通过解析拿到对应上传的文件 我在项目中(node+sequelize)可以通过ctx.request.files拿到对应的数据 深入浅出 multipart/form-data
阿里云文件上传参考这篇: vue文件上传至阿里云 ali-oss
3.2 自定义上传
<el-upload
ref="upload"
class="upload-demo"
action=""
:http-request="customerHttp"
:on-remove="handleRemove"
:before-remove="beforeRemove"
:file-list="fileList"
:on-exceed="handleExceed"
:limit="1"
accept=".so, .zip"
>
<el-button size="small" type="primary">点击上传</el-button>
</el-upload>
data() {
return {
packageFile: '',
}
}
methods: {
customerHttp(item) {
const formData = new FormData()
formData.append('file', item.file)
// 如果直接上传就不用存储,如果等按确定按钮,就需要存储
this.packageFile = formData
const res = await uploadFile(this.packageFile).catch(() => false)
console.log(res)
},
// 删除了之后 应该把this.packageFile进行清空
handleRemove(file, fileList) {
if (fileList.length === 0) {
this.packageFile = ''
}
},
}
4.大文件分片上传(element ui 纯前端实现)
知识点汇总
VUE前端分片直传大文件到OSS方法
阿里云OSS文件上传(分片上传、断点续传)前后端实现
一定要看api 分片上传官方案例(node.js)
模拟两个文件上传,都是205兆,为什么要分片上传?
之前我们上传的文件,是大概几十兆,所以上传虽然慢了点,也不是不能忍,但是后面兆数增加到400多兆,因为之前文件上传到oss,是在node端处理的,但是node.js有2分钟超时限制,所以为了减少node层转换,直接采用在前端进行oss上传
1.template
<el-upload
ref="upload"
class="upload-demo"
action=""
:http-request="customerHttp"
:on-remove="handleRemove"
:before-remove="beforeRemove"
:file-list="fileList"
:on-exceed="handleExceed"
:limit="1"
accept=".so, .zip"
>
<el-button size="small" type="primary">点击上传</el-button>
</el-upload>
<div style="padding: 10px 10px 10px 0px; width: 320px">
<el-progress v-if="percentage" :text-inside="true"
:stroke-width="24" :percentage="percentage" status="success" />
</div>
2. 数据定义
import moment from 'moment'
import OSS from 'ali-oss'
const client = new OSS({
region: 'xxxxx',
accessKeyId: 'xxxxx',
accessKeySecret: 'xxxxx',
endpoint: 'xxxxx',
bucket: 'xxxxxx',
})
data() {
return {
percentage: 0,
savaData: {},
}
}
如果在node端去拿配置项,那么可以直接在config.local.js文件里面去定义ossConfig配置项,如果是纯前端实现的话,肯定不能直接定义(很不安全),要从接口拿,我是模拟,所以直接在页面写了
const res = await this.sts.assumeRole(
acs:ram::${accountId}:role/${roleName}, 'SessionTest')
3.分片上传参数获取
async customerHttp(item) {
// 拿到上传到的file
const uploadFile = item.file
// 拿到上传的size
const uploadFileSize = uploadFile.size // 这里拿到的单位是字节(uploadFileSize/ 1024 / 1024
// = 多少兆)
// 设置每一片的大小,partSize 指定上传的每个分片的大小,范围为100 KB~5 GB。
const partSize = Math.ceil(uploadFileSize / 1024 / 1024 / 1000) * 1024 * 1024
// 设置所有的文件上传所有的唯一的saveFileId
const { name, size, lastModified, type } = uploadFile
const saveFileId = `${lastModified}_${size}_${name}_${type}`
this.multipartUpload(partSize, saveFileId, uploadFile)
},
partSize可以根据业务需求设定,最好就是设定一个最大的片数,看node的官方的例子是1000片,我这里设置最大的片数也是1000片,所以我的逻辑是如果超过1000片,我就将兆数放大,如果小于1000片,我就还是使用1兆,Math.ceil(uploadFileSize / 1024 / 1024 / 1000)是为了知道是不是大于1000片
为什么我要去创建一个saveFileId,可以叫做他文件的唯一的id,其实我看过很多例子,很多人的断点续传只做到了我传a文件,网页崩了,我再续传a文件,而我这里去创建saveFileId是为了,我传a文件,网页崩了,我又传b文件,网页崩了,过一会,我再传a文件或者b文件都要有续存的效果,所以我会将文件的信息形成唯一的id进行存储,然后放到内存里面,将所有的中断信息变成一个对象存储,对象的key是saveFileId(文件唯一的信息)
4.分片上传
async multipartUpload(partSize, saveFileId, uploadFile) {
try {
// object-name目前我是用的uploadFile.name,其实也是要根据你们的项目而定,
有没有具体的规定,要不要加项目名,要不要加对应的环境
// 上传的参数
const uploadParams = {
partSize,
progress: (percentage, checkpoint) => {
this.savaData[saveFileId] = checkpoint
console.log(checkpoint)
this.savaData['lastSaveTime'] = new Date()
this.percentage = parseInt(percentage * 100)
// 在上传过程中,把已经上传的数据存储下来
this.saveFinishedData(this.savaData)
},
}
// 断点续传
await this.resumeUpload(uploadParams, saveFileId)
const { res: { status }} = await client.multipartUpload(uploadFile.name,
uploadFile, uploadParams)
if (status === 200) {
// 重新去掉某个缓存进行设置
delete this.savaData[saveFileId]
this.saveFinishedData(this.savaData)
}
} catch (e) {
// 捕获超时异常。
if (e.code === 'ConnectionTimeoutError') {
console.log('TimeoutError')
// do ConnectionTimeoutError operation
}
}
},
很多人说进度条不能实时更新,其实是因为别人的例子,直接用的function,无法改变父级的this.percentage,所以这里应该用箭头函数(percentage, checkpoint) =>
5.断点续传
async resumeUpload(uploadParams, saveFileId) {
if (localStorage.getItem('upload-function-name')) {
const obj = JSON.parse(localStorage.getItem('upload-function-name'))
if (Object.keys(obj).includes(saveFileId)) {
uploadParams.checkpoint = obj[saveFileId]
}
}
},
// 存储到内存
saveFinishedData(finishedData) {
localStorage.setItem(
'upload-function-name',
JSON.stringify(finishedData)
)
},
主要是判断内存里面的文件和我现在上传文件是不是一致.是的话把对应的checkpoint传过去
upload-function-name这个名字,我觉得按照功能随便取个名字就好
6.初始化拿到内存里面的数据
mounted() {
this.initPage()
},
methods: {
initPage() {
// 判断是不是有缓存
const localData = localStorage.getItem('upload-function-name')
if (!localData) return
this.savaData = JSON.parse(localData)
// 当前时间 > 存储时间(1000 * 60 * 60表示1h,意思就是这些数据你要存多久,
// 可以是1h也可以是多少天,随意)
if (moment(new Date()).diff(moment(this.savaData.lastSaveTime)) > 1000 * 60 * 60) {
localStorage.removeItem('upload-function-name')
}
},
}
7.知识点梳理
1. 分片上传,主要是把一个大文件,分成一小片上传,而按道理,后端应该把前端传的一片片合起来,这一步阿里云帮我们做了
2. oss上传中,同一个文件,用saveFileId做文件的唯一标识
3. 因为想要做到多个文件中断,都能够进行分别续存,所以将这些信息存到内存里面
4. 只要上传成功了,应该将对应的内存中的信息删除
5. 为了不让上传信息一直停留在内存,做了一个定期的删除
6. doneParts 是已经上传的片数的集合
8.开发过程中一些思考(可不看)
在做分片续存这里,首先我在想checkpoint是什么?我靠什么信息知道哪些已经上传了,哪些没上传,是只用把中断的最后一片知道了就行了嘛,checkpoint是上传每一片的信息嘛?我以为checkpoint每次打印出来,应该uploadId是不一样的,每个checkpoint表示的是已上传的那一片,结果我错了,或者我觉得至少每个checkpoint有个东西完全不一样,结果每次上传完了,我去看checkpoint,好像每个都一样(我查了后面几十个都一样)
于是我很困惑,这没有一个标志值怎么搞?看了看源码: checkpoint 返回的数据:
file 就是我们传的file
fileSize 也就是文件总大小
name文件名
partSize也就是我们传的portSize,没有就是1兆
那么uploadId是不是唯一的呢?是代表了每一片的上传了的id还是说只要是这个文件上传,所有的uploadId是一样的呢?答案就是只要是同一个文件上传,uploadId是一致的,不是代表每一片
源码:
第一次创建uploadId还是根据前端传的参数name和options,在上传调用时,这些参数都是不变的,所以不管request里面怎么写,uploadId应该是根据name和option出来的一个唯一值,所以我存储的断点以上提到的几个数都是固定的,那么唯一有可能变化的就是doneParts
源码证明,变化的应该是doneParts,而为什么我所有的数据doneParts都是206片呢???其实是因为我每次去分析数据都是全部上传完看最后的那些数据,只有在上传的过程中,整个doneParts的数量才是按照0->206从小到大数组在变化,而结束后的打印是没有规律的,大部分变成了206片,这里的原因还没有搞懂。。。。
因为doneParts上传中规律增加,中断时,我们把对应的checkpoint存储,oss解析到传过来的checkpoint.uploadId与当前传的一致就会进行续存.
开心每一天-_-