由于uniapp官方教程并没有找到适用于h5端的文件切分上传的例子,于是便自己查资料封装了一个上传的js代码,如有问题请各位大佬指正(本人只是兼职玩前端)
1. 通过uni.chooseFile选择本地要上传的文件
// 选择文件并开始上传
function chooseFileUpload(urll, percentCallback, data) {
uni.chooseFile({
success(res) {
const file = res.tempFiles[0];
const filePath = res.tempFilePaths[0]
// 计算切片数量
totalChunks = Math.ceil(file.size / chunkSize);
// 生成文件的唯一标识
fileKey = generateFileKey();
// 将文件切割为多个切片
splitFileIntoChunks(file);
// 开始上传第一个切片
uploadFile(fileChunks[fileIndex], file.name, filePath, urll, percentCallback, data);
},
fail(error) {
// 选择文件失败,进行错误处理
}
});
}
2. 将选择的文件切片
// 将文件切割为多个切片
function splitFileIntoChunks(file) {
const fileSize = file.size
console.log("filesize:", fileSize)
fileChunks = [];
for (let i = 0; i < totalChunks; i++) {
const start = i * chunkSize;
const end = Math.min(start + chunkSize, fileSize);
const chunk = file.slice(start, end);
fileChunks.push(chunk);
}
}
3. 通过uni.uploadFile上传文件到服务端
// 上传切片
function uploadFile(chunk, filename, filePath, urll, percentCallback, data) {
if (retryTimes > retryLimit) {
// 重试次数过多返回
return
}
console.log("filename:", filename, " filepath:", filePath)
console.log("data:", JSON.stringify(data))
let tdata = data
let formData = tdata
// formData.append("unique", fileKey)
// formData.append('chunk_index', fileIndex)
// formData.append('chunk_total', totalChunks)
formData['unique'] = fileKey
formData['chunk_index'] = fileIndex
formData['chunk_total'] = totalChunks
formData['filename'] = filename
console.log('uploadFile formData:', JSON.stringify(formData))
uni.uploadFile({
url: urll, // 替换为你的上传接口地址
filePath: filePath,
name: 'chunk_file',
file: chunk,
// files:[{name:'chunk_file',file:chunk,uri:filePath}],
formData: formData,
success(res) {
// 上传成功处理逻辑
if (res.statusCode === 200) {
// 重试次数清零
retryTimes = 0
const data = JSON.parse(res.data);
if (data.code === 0) {
// 切片上传成功,继续上传下一片
if (percentCallback) {
percentCallback((fileIndex+1)*100/totalChunks)
}
fileIndex++;
if (fileIndex < totalChunks) {
uploadFile(fileChunks[fileIndex], filename,filePath, urll, percentCallback, tdata)
} else {
// 所有切片上传完成,进行合并或其他处理
mergeChunks()
}
} else {
// 切片上传失败,进行错误处理
// mergeChunks()
retryTimes++;
uploadFile(fileChunks[fileIndex], filename,filePath, urll, percentCallback, tdata)
}
} else {
// 上传失败,进行错误处理
retryTimes++;
uploadFile(fileChunks[fileIndex], filename,filePath, urll, percentCallback, tdata)
}
},
fail(error) {
// 上传失败,进行错误处理
retryTimes++;
uploadFile(fileChunks[fileIndex], filename,filePath, urll, percentCallback, tdata)
}
});
}
目前实现的是按分片顺序上传,如果需要也可以改成多线程并行上传速度更快
完整代码如下:
// 切片上传相关配置
const chunkSize = 5*1024 * 1024; // 每个切片的大小(这里设置为5MB)
let fileIndex = 0; // 当前切片索引
let totalChunks = 0; // 总切片数
let fileKey = ''; // 文件的唯一标识
let fileChunks = []; // 存储切片的数组
let retryTimes = 0; // 失败重试次数
const retryLimit = 5; // 失败重试次数限制
// 选择文件并开始上传
function chooseFileUpload(urll, percentCallback, data) {
uni.chooseFile({
success(res) {
const file = res.tempFiles[0];
const filePath = res.tempFilePaths[0]
// 计算切片数量
totalChunks = Math.ceil(file.size / chunkSize);
// 生成文件的唯一标识
fileKey = generateFileKey();
// 将文件切割为多个切片
splitFileIntoChunks(file);
// 开始上传第一个切片
uploadFile(fileChunks[fileIndex], file.name, filePath, urll, percentCallback, data);
},
fail(error) {
// 选择文件失败,进行错误处理
}
});
}
// 将文件切割为多个切片
function splitFileIntoChunks(file) {
const fileSize = file.size
console.log("filesize:", fileSize)
fileChunks = [];
for (let i = 0; i < totalChunks; i++) {
const start = i * chunkSize;
const end = Math.min(start + chunkSize, fileSize);
const chunk = file.slice(start, end);
fileChunks.push(chunk);
}
}
// 上传切片
function uploadFile(chunk, filename, filePath, urll, percentCallback, data) {
if retryTimes > retryLimit {
// 重试次数过多返回
return
}
console.log("filename:", filename, " filepath:", filePath)
console.log("data:", JSON.stringify(data))
let tdata = data
let formData = tdata
// formData.append("unique", fileKey)
// formData.append('chunk_index', fileIndex)
// formData.append('chunk_total', totalChunks)
formData['unique'] = fileKey
formData['chunk_index'] = fileIndex
formData['chunk_total'] = totalChunks
formData['filename'] = filename
console.log('uploadFile formData:', JSON.stringify(formData))
uni.uploadFile({
url: urll, // 替换为你的上传接口地址
filePath: filePath,
name: 'chunk_file',
file: chunk,
// files:[{name:'chunk_file',file:chunk,uri:filePath}],
formData: formData,
success(res) {
// 上传成功处理逻辑
if (res.statusCode === 200) {
// 重试次数清零
retryTimes = 0
const data = JSON.parse(res.data);
if (data.code === 0) {
// 切片上传成功,继续上传下一片
if (percentCallback) {
percentCallback((fileIndex+1)*100/totalChunks)
}
fileIndex++;
if (fileIndex < totalChunks) {
uploadFile(fileChunks[fileIndex], filename,filePath, urll, percentCallback, tdata)
} else {
// 所有切片上传完成,进行合并或其他处理
mergeChunks()
}
} else {
// 切片上传失败,进行错误处理
// mergeChunks()
retryTimes++;
uploadFile(fileChunks[fileIndex], filename,filePath, urll, percentCallback, tdata)
}
} else {
// 上传失败,进行错误处理
retryTimes++;
uploadFile(fileChunks[fileIndex], filename,filePath, urll, percentCallback, tdata)
}
},
fail(error) {
// 上传失败,进行错误处理
retryTimes++;
uploadFile(fileChunks[fileIndex], filename,filePath, urll, percentCallback, tdata)
}
});
}
// 切片合并或其他处理
function mergeChunks() {
// 所有切片上传完成后,进行切片合
// 其他处理逻辑
// 所有切片上传完成,清理数据
fileIndex = 0
totalChunks = 0
fileKey = ''
fileChunks = []
}
// 生成文件的唯一标识
function generateFileKey() {
// 根据需要生成文件的唯一标识,例如使用时间戳+随机字符串等方式
// 返回文件的唯一标识
return Date.now()
}
export default {
chooseFileUpload
}
uniapp vue文件中导入该js 调用choseFileUpload即可实现选择文件并实现分片上传的逻辑.
后端代码逻辑如下:
1. 接收每一个分片的请求,并将分片的数据保存到临时文件中
2. 当接收完最后一个分片时(chunk_index+1 == chunk_total)将所有分片的临时文件合并为一个文件即可
后端主要逻辑如下,golang写的:
// 分片上传
func chunkUpload(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodPost {
if !strings.Contains(r.Header.Get("Content-Type"), "multipart/form-data") {
// 不支持的 Content-Type 类型
blog.E.Println("Invalid Content-Type:", r.Header.Get("Content-Type"))
httpcommon.WriteError(w, r, httpcommon.ResponeBase{Code: httpcommon.RESPONSE_ERROR, Msg: "参数错误"})
return
}
chunkIndexStr := r.PostFormValue("chunk_index")
chunkTotalStr := r.PostFormValue("chunk_total")
unique := r.PostFormValue("unique")
chunkIndex, err := strconv.Atoi(chunkIndexStr)
if err != nil {
blog.E.Println("chunk_index:", chunkIndexStr)
httpcommon.WriteError(w, r, httpcommon.ResponeBase{Code: httpcommon.RESPONSE_ERROR, Msg: "参数错误"})
return
}
chunkTotal, err := strconv.Atoi(chunkTotalStr)
if err != nil {
blog.E.Println("chunk_total:", chunkTotalStr)
httpcommon.WriteError(w, r, httpcommon.ResponeBase{Code: httpcommon.RESPONSE_ERROR, Msg: "参数错误"})
return
}
if chunkIndex >= chunkTotal {
blog.E.Println("chunkIndex:", chunkIndex, "chunkTotal:", chunkTotal)
httpcommon.WriteError(w, r, httpcommon.ResponeBase{Code: httpcommon.RESPONSE_ERROR, Msg: "参数错误"})
return
}
err = chunkParse(chunkIndex, chunkTotal, unique, r)
if err != nil {
chunkFileClear(unique, chunkTotal)
httpcommon.WriteRespone(w, httpcommon.ResponeBase{Code: httpcommon.RESPONSE_ERROR, Msg: err.Error()})
return
}
if chunkIndex == chunkTotal-1 {
chunkFileClear(unique, chunkTotal)
}
httpcommon.WriteRespone(w, httpcommon.ResponeBase{Code: httpcommon.RESPONSE_OK})
} else {
httpcommon.WriteError(w, r, httpcommon.ResponeBase{Code: httpcommon.RESPONSE_ERROR, Msg: "错误"})
}
}