当前位置: 首页 > news >正文

秦皇岛网站建公司百度首页

秦皇岛网站建公司,百度首页,北京不再公布疫情了,政府网站维护方案springbootvue大文件断点续传 后端代码代码说明1.上传分片接口2. 查询已上传分片接口3. 合并分片接口4.后端流程 前端组件前端流程 后端代码 RestController RequestMapping("/api/commonFileUtil") public class CommonFileController {Autowiredprivate FilePathC…

springboot+vue大文件断点续传

    • 后端代码
    • 代码说明
      • 1.上传分片接口
      • 2. 查询已上传分片接口
      • 3. 合并分片接口
      • 4.后端流程
    • 前端组件
      • 前端流程

后端代码

@RestController
@RequestMapping("/api/commonFileUtil")
public class CommonFileController {@Autowiredprivate FilePathConfig filePathConfig;/*** 上传分片**/@PostMapping("commonUploadByModule/{module}")public ActionResult<?> commonUploadByModule(@PathVariable("module") String folder,@RequestPart("file") MultipartFile multipartFile,@RequestParam("fileHash") String fileHash,@RequestParam("chunkIndex") int chunkIndex,@RequestParam("totalChunks") int totalChunks,@RequestParam("fileName") String originalFileName) throws IOException {String baseFolder = filePathConfig.getUploadByModule() + (StrUtil.isEmpty(folder) ? "default" : folder);String tempFolder = baseFolder + "/temp/" + fileHash;FileUtil.checkAndCreateFolder(tempFolder);File chunkFile = new File(tempFolder, chunkIndex + ".part");IoUtil.write(new FileOutputStream(chunkFile), true, multipartFile.getBytes());return ActionResult.success("分片上传成功");}/*** 查询自己的分片**/@GetMapping("commonUploadByModule/{module}/status")public ActionResult<?> uploadedChunks(@PathVariable("module") String folder,@RequestParam("fileHash") String fileHash) {String baseFolder = filePathConfig.getUploadByModule() + (StrUtil.isEmpty(folder) ? "default" : folder);String tempFolder = baseFolder + "/temp/" + fileHash;List<Integer> uploaded = new ArrayList<>();File folderFile = new File(tempFolder);if (folderFile.exists()) {File[] files = folderFile.listFiles((dir, name) -> name.endsWith(".part"));if (files != null) {for (File file : files) {String name = file.getName().replace(".part", "");uploaded.add(Integer.parseInt(name));}}}Map<Object, Object> map = new HashMap<>();map.put("uploaded", uploaded);return ActionResult.success(map);}/*** 分片合并**/@PostMapping("commonUploadByModule/{module}/merge")public ActionResult<?> mergeChunks(@PathVariable("module") String folder,@RequestBody Map<String, String> params) throws IOException {String fileHash = params.get("fileHash");String originalFileName = params.get("fileName");String baseFolder = filePathConfig.getUploadByModule() + (StrUtil.isEmpty(folder) ? "default" : folder);String tempFolder = baseFolder + "/temp/" + fileHash;File[] chunks = new File(tempFolder).listFiles((dir, name) -> name.endsWith(".part"));if (chunks == null || chunks.length == 0) {return ActionResult.fail("未找到分片文件");}Arrays.sort(chunks, Comparator.comparingInt(f -> Integer.parseInt(f.getName().replace(".part", ""))));String mergedFileName = FileUtil.addPrefix(Paths.get(originalFileName).getFileName().toString());String mergedFilePath = baseFolder + "/" + mergedFileName;FileUtil.checkAndCreateFolder(baseFolder);try (FileOutputStream fos = new FileOutputStream(mergedFilePath)) {for (File chunk : chunks) {Files.copy(chunk.toPath(), fos);}}// 清理临时文件夹for (File chunk : chunks) chunk.delete();new File(tempFolder).delete();Map<String, String> map = new HashMap<>();map.put("fileName", mergedFileName);map.put("fileOriginName", originalFileName);map.put("fileSize", String.format("%.2fMB", new File(mergedFilePath).length() / 1024 / 1024f));map.put("filePath", mergedFilePath);return ActionResult.success(map);}
}

代码说明

1.上传分片接口

@PostMapping("{module}")
public ActionResult<?> uploadChunk(@PathVariable("module") String folder,@RequestPart("file") MultipartFile multipartFile,@RequestParam("fileHash") String fileHash,@RequestParam("chunkIndex") int chunkIndex,@RequestParam("totalChunks") int totalChunks,@RequestParam("fileName") String originalFileName
) throws IOException {
  • @PostMapping(“{module}”): 表示 POST 请求路径为 /uploadByModule/{module},例如:/uploadByModule/video。

  • @PathVariable(“module”) String folder: 获取路径变量 module,如 “video”,表示上传分类。

  • @RequestPart(“file”) MultipartFile multipartFile: 接收上传的文件分片。

  • 其余参数为前端额外上传的信息:

    • fileHash: 前端根据文件内容生成的唯一标识;

    • chunkIndex: 当前是第几个分片;

    • totalChunks: 总共有多少个分片;

    • fileName: 文件原始名。

String baseFolder = filePathConfig.getUploadByModule() + (StrUtil.isEmpty(folder) ? "default" : folder);
  • 构造出完整的基础目录路径,比如:/mnt/upload/video。

  • 如果路径为空,则用默认目录 /mnt/upload/default。

String tempFolder = baseFolder + "/temp/" + fileHash;
FileUtil.checkAndCreateFolder(tempFolder);
  • 构造当前文件的临时目录,如 /mnt/upload/video/temp/abc123。

  • checkAndCreateFolder() 是你自己定义的工具方法,用于确保目录存在(不存在则创建)。

File chunkFile = new File(tempFolder, chunkIndex + ".part");
IoUtil.write(new FileOutputStream(chunkFile), true, multipartFile.getBytes());
  • 创建当前分片的目标文件对象,例如 /mnt/upload/video/temp/abc123/0.part。

  • 使用 IoUtil.write() 将上传的分片写入本地文件。

2. 查询已上传分片接口

@GetMapping("{module}/status")
public ActionResult<?> uploadedChunks(@PathVariable("module") String folder,@RequestParam("fileHash") String fileHash
) {
  • GET 请求路径为:/uploadByModule/video/status?fileHash=abc123

  • 返回指定文件(通过 fileHash 唯一标识)的 已上传分片编号。

String baseFolder = filePathConfig.getUploadByModule() + (StrUtil.isEmpty(folder) ? "default" : folder);
String tempFolder = baseFolder + "/temp/" + fileHash;
  • 计算临时分片文件夹路径。
List<Integer> uploaded = new ArrayList<>();
File folderFile = new File(tempFolder);
  • 准备存储已上传的分片编号。
if (folderFile.exists()) {File[] files = folderFile.listFiles((dir, name) -> name.endsWith(".part"));
  • 判断该文件夹是否存在,如果存在则读取里面所有以 .part 结尾的文件(即分片)。
    for (File f : files) {String name = f.getName().replace(".part", "");uploaded.add(Integer.parseInt(name));}
}
  • 去除每个文件名的 .part 后缀,并转为数字(分片编号)存入列表中。

3. 合并分片接口

@PostMapping("{module}/merge")
public ActionResult<?> mergeChunks(@PathVariable("module") String folder,@RequestBody Map<String, String> params
) throws IOException {
  • POST /uploadByModule/video/merge

  • @RequestBody 参数为 JSON 对象,包含两个 key:

  • fileHash: 文件唯一标识;

  • fileName: 原始文件名。

String fileHash = params.get("fileHash");
String originalFileName = params.get("fileName");
  • 从请求体中提取参数。
String baseFolder = filePathConfig.getUploadByModule() + (StrUtil.isEmpty(folder) ? "default" : folder);
String tempFolder = baseFolder + "/temp/" + fileHash;
  • 组装临时目录。
File[] chunks = new File(tempFolder).listFiles((dir, name) -> name.endsWith(".part"));
if (chunks == null || chunks.length == 0) {return ActionResult.error("未找到分片文件");
}
Arrays.sort(chunks, Comparator.comparingInt(f -> Integer.parseInt(f.getName().replace(".part", ""))));
  • 对所有分片文件按编号升序排序,确保按顺序合并。
String mergedFileName = FileUtil.addPrefix(Paths.get(originalFileName).getFileName().toString());
String mergedFilePath = baseFolder + "/" + mergedFileName;
FileUtil.checkAndCreateFolder(baseFolder);
  • 使用你的 addPrefix() 工具方法给原始文件名加上唯一前缀;

  • 最终合并后文件路径:如 /mnt/upload/video/1651283761234_myfile.mp4

try (FileOutputStream fos = new FileOutputStream(mergedFilePath)) {for (File chunk : chunks) {Files.copy(chunk.toPath(), fos);}
}
  • 创建输出流 fos,逐个读取 .part 文件并写入合并目标文件。
for (File chunk : chunks) chunk.delete();
new File(tempFolder).delete();
  • 合并完成后删除所有分片及临时文件夹。
Map<String, String> map = new HashMap<>();
map.put("fileName", mergedFileName);
map.put("fileOriginName", originalFileName);
map.put("fileSize", String.format("%.2fMB", new File(mergedFilePath).length() / 1024f / 1024f));
map.put("filePath", mergedFilePath);
return ActionResult.success(map);
  • 返回完整信息给前端,包括:

    • 实际文件名(带前缀);

    • 原始名称;

    • 文件大小;

    • 路径。

4.后端流程

[后端]

  • 每个分片保存在 temp/{fileHash}/chunkIndex.part
  • 合并时按照顺序将所有 part 合成文件

前端组件


<template><div class="upload-wrapper"><input type="file" @change="handleFileChange" /><button :disabled="!file || uploading" @click="uploadFile">{{ uploading ? '上传中...' : '上传文件' }}</button><div v-if="uploading">上传进度:{{ progress.toFixed(2) }}%</div></div>
</template><script>
import Vue from "vue"
import SparkMD5 from 'spark-md5'
import request from "@/utils/request";export default {data () {return {file: null,chunkSize: 5 * 1024 * 1024, // 每片 5MBuploading: false,progress: 0,module: 'video' // 上传模块路径,如 default、video、image...}},methods: {handleFileChange (e) {this.file = e.target.files[0]},async uploadFile () {if (!this.file) returnthis.uploading = trueconst fileHash = await this.calculateFileHash(this.file)const totalChunks = Math.ceil(this.file.size / this.chunkSize)const uploadedChunkIndexes = await this.getUploadedChunkIndexes(fileHash)for (let index = 0; index < totalChunks; index++) {if (uploadedChunkIndexes.includes(index)) {this.updateProgress(index + 1, totalChunks)continue}const start = index * this.chunkSizeconst end = Math.min(start + this.chunkSize, this.file.size)const chunk = this.file.slice(start, end)await this.uploadChunk({chunk,fileHash,chunkIndex: index,totalChunks,fileName: this.file.name})this.updateProgress(index + 1, totalChunks)}await this.mergeChunks(fileHash, this.file.name)this.uploading = falsealert('上传成功')},updateProgress (uploadedChunks, totalChunks) {this.progress = (uploadedChunks / totalChunks) * 100},async calculateFileHash (file) {return new Promise(resolve => {const spark = new SparkMD5.ArrayBuffer()const reader = new FileReader()const chunkSize = this.chunkSizeconst chunks = Math.ceil(file.size / chunkSize)let currentChunk = 0const loadNext = () => {const start = currentChunk * chunkSizeconst end = Math.min(start + chunkSize, file.size)reader.readAsArrayBuffer(file.slice(start, end))}reader.onload = e => {spark.append(e.target.result)currentChunk++if (currentChunk < chunks) {loadNext()} else {resolve(spark.end())}}loadNext()})},async getUploadedChunkIndexes (fileHash) {// try {//   const res = await axios.get(`/api/commonFileUtil/commonUploadByModule/${this.module}/status`, {//     params: { fileHash }//   })//   return res.data.data.uploaded || []// } catch (e) {//   return []// }return new Promise((resolve, reject) => {request({url: `/api/commonFileUtil/commonUploadByModule/${this.module}/status?fileHash=${fileHash}`,method: "GET"}).then((res) => {if (res && res.data && Array.isArray(res.data.uploaded)) {resolve(res.data.uploaded)} else {// 兜底,避免不是数组时报错resolve([])}}).catch((err) => {reject([])})})},async uploadChunk ({ chunk, fileHash, chunkIndex, totalChunks, fileName }) {const formData = new FormData()formData.append('file', chunk)formData.append('fileHash', fileHash)formData.append('chunkIndex', chunkIndex)formData.append('totalChunks', totalChunks)formData.append('fileName', fileName)return new Promise((resolve, reject) => {request({url: `/api/commonFileUtil/commonUploadByModule/${this.module}`,method: "post",data: formData,}).then((res) => {resolve(res.data)}).catch((err) => {reject(err)})})},async mergeChunks (fileHash, fileName) {return new Promise((resolve, reject) => {request({url: `/api/commonFileUtil/commonUploadByModule/${this.module}/merge`,method: "post",data: {fileHash,fileName},}).then((res) => {resolve(res.data)}).catch((err) => {reject(err)})})}}
}
</script><style scoped>
.upload-wrapper {padding: 20px;border: 1px dashed #999;border-radius: 10px;width: 400px;
}
</style>

前端流程

[前端]
→ 1. 计算 fileHash
→ 2. 查询 status 接口,获取已上传分片
→ 3. 上传分片(附带 index/hash/total)
→ 4. 上传完成后,调用 merge 接口

http://www.dinnco.com/news/27457.html

相关文章:

  • 网站服务器管理 硬件网站排行查询
  • 上海网站建设网页制作网站品牌推广策略
  • 网络推广服务营销北京seo全网营销
  • 廊坊seo软件昆明seo网站管理
  • 想注册一个做网站的公司网站服务器速度对seo有什么影响
  • 免费做h5的网站网站排名优化软件联系方式
  • hbuilder 做网站国际新闻今日头条
  • 郑州网站建设电话网站建设哪家好公司
  • 阿里云官网入口seo外贸网站制作
  • 网站建设维护 天博网络下载百度语音导航地图
  • b2c电商网站开发百度自动搜索关键词软件
  • 动漫设计与制作学费北京优化互联网公司
  • 建设网站的意义收录优美图片崩了
  • 网站目录 index营销型网站建设哪家好
  • 长沙开福区专业制作网站汕头seo不错
  • 游戏平台网站制作成都关键词优化报价
  • 专门做网站开发的公司百度信息流推广教程
  • 大石桥做网站软文编辑器
  • 自己做电影网站有没有钱赚seo怎么弄
  • 移动端模板网站建设怎么创建网站?
  • 西安免费做网站哪家好网络营销的主要推广方式
  • 企业的网站特点发帖百度秒收录网站分享
  • 男女做污视频网站seo是什么职位简称
  • 网站开发论文说明网络营销策略包括
  • 肥西网站建设成都本地推广平台
  • 网站服务器是指什么如何制作网页广告
  • 做网站登录的需求分析刚刚济南发通知
  • 符合网络营销的网站关键词搜索查询
  • 学网站建设前途企业网站设计与实现论文
  • 专业网站建设搭建网络营销有什么特点