vue java 实现大地图切片上传

发布于:2025-03-25 ⋅ 阅读:(26) ⋅ 点赞:(0)

在这里插入图片描述

一、项目背景

技术:vue+Arco Design+java

介绍:页面上传mapShow.zip压缩包,只允许上传压缩包,且上传有格式校验,然后文件大小在600M或者上G的压缩包,像这种上传是不可能直接一整个包上传的,浏览器也不支持,同时这样做也不友好。

解决方案:采用分片技术,即把一个打压缩包切割成每个10M大小的分片,然后上传到指定目录下,最后再把所有分片文件进行合并成mapShow.zip压缩包,最后再解压mapShow.zip文件到指定目录下即可。

二、页面

在这里插入图片描述

三、代码

1.前端

<div class="param">
  <a-spin style="width: 70%">
    <div class="param-title">
      <div class="param-title-text">
        {{ $t("OfflineMapPathSetting") }}:
      </div>
    </div>
    <div class="param-content">
      <div
        class="parent"
        style="display: flex; align-items: center; gap: 20px"
      >
        <div>
          <span class="label">{{ $t("Import") }}:</span>
        </div>
        <a-input class="div" v-model="fileName" disabled />
        <a-upload
          @change="onChange"
          :auto-upload="false"
          :show-file-list="false"
        >
          <template #upload-button>
            <svg-loader
              class="frameIcon"
              name="import-file"
            ></svg-loader>
          </template>
        </a-upload>
      </div>
    </div>
  </a-spin>
</div>

const onChange = async (fileList) => {
  files.value = [];
  const lastUploadedFile = fileList[fileList.length - 1];
  fileName.value = lastUploadedFile.name;
  const allowedExtensions = /(.zip)$/i;
  if (!allowedExtensions.exec(lastUploadedFile.name)) {
    Message.error(t("invalidCerFileType2"));
    return;
  }
  uploadeLoading.value = true;
  if (fileList.length > 0) {
    const lastUploadedFile = fileList[fileList.length - 1];
    const chunkSize = 10 * 1024 * 1024;
    const totalChunks = Math.ceil(lastUploadedFile.file.size / chunkSize);

    for (let chunkIndex = 0; chunkIndex < totalChunks; chunkIndex++) {
      const start = chunkIndex * chunkSize;
      const end = Math.min(start + chunkSize, lastUploadedFile.file.size);
      const chunk = lastUploadedFile.file.slice(start, end);

      const formData = new FormData();
      formData.append("file", chunk);
      formData.append("fileId", "mapShow");
      formData.append("chunkIndex", chunkIndex);
      formData.append("totalChunks", totalChunks);
      await importOfflineMapFunction(formData);
    }
    progress.value = 100;
    uploadeLoading.value = false;
    Message.success(t("LoadMap"));
  }
};

const importOfflineMapFunction = async (formData) => {
  await importOfflineMap(formData).then((response) => {
    commonResponse({
      response,
      onSuccess: () => {},
    });
  });
};

2.mock-i18n.js文件

"invalidCerFileType2": "无效的文件类型,文件后缀必须是.zip等格式"

3.xx.js文件定义方法

export const importOfflineMap = (data) => {
  return $http({
    url: `/api/systemParam/importOfflineMap`,
    method: Method.POST,
    headers: {
      'Content-Type': 'multipart/form-data',
      'X-Requested-With': 'XMLHttpRequest'
    },
    data,
    timeout: 300000
  })
}

4.配置文件 application.properties

spring.servlet.multipart.max-file-size=600MB
spring.servlet.multipart.max-request-size=2GB

5.后端方法

private static String OFFLINE_MAP_TEMPORARY_PATH = "\\..\\temporary\\";
private static String OFFLINE_MAP_TARGET_PATH = "\\..\\nginx\\html\\mapShow\\";

@Operation(summary = "导入离线地图")
@PostMapping(value = "/importOfflineMap")
public ResponseModel importOfflineMap(@RequestParam("file") MultipartFile file,
                                      @RequestParam("fileId") String fileId,
                                      @RequestParam("chunkIndex") int chunkIndex,
                                      @RequestParam("totalChunks") int totalChunks) {
    return systemParamUserControl.importOfflineMap(file, fileId, chunkIndex, totalChunks);
}

public ResponseModel importOfflineMap(MultipartFile file, String fileId, int chunkIndex, int totalChunks) {
        try {
            String userDir = System.getProperty("user.dir");
            logger.info("importOfflineMap-user.dir:{}", userDir);
            String offlineMapTemporaryPath = userDir + OFFLINE_MAP_TEMPORARY_PATH;
            File uploadFile = new File(offlineMapTemporaryPath);
            if (!uploadFile.exists()) {
                uploadFile.mkdirs();
            }
            String chunkFileName = offlineMapTemporaryPath + fileId + ".part" + chunkIndex;
            file.transferTo(new File(chunkFileName));

            if (chunkIndex == totalChunks - 1) {
                String offlineMapTargetPath = userDir + OFFLINE_MAP_TARGET_PATH;
                mergeFile(fileId, totalChunks, offlineMapTemporaryPath, offlineMapTargetPath);
            }
        } catch (IOException e) {
            logger.error("importOfflineMap-IOException:{}", e);
        }
        return ResponseModel.ofSuccess();
    }

    private void mergeFile(String fileId, int totalChunks, String offlineMapTemporaryPath, String offlineMapTargetPath) {
        // 创建最终文件
        File outputFile = new File(offlineMapTemporaryPath + fileId + ".zip");
        try {
            FileOutputStream fos = new FileOutputStream(outputFile);
            for (int i = 0; i < totalChunks; i++) {
                Path chunkPath = Paths.get(offlineMapTemporaryPath + fileId + ".part" + i);
                Files.copy(chunkPath, fos);
                Files.delete(chunkPath);
            }
            unzip(outputFile, offlineMapTargetPath);
        } catch (Exception e) {
            logger.error("mergeFile-Exception:{}", e);
        }
    }

    private void unzip(File zipFile, String destDir) throws IOException {
        byte[] buffer = new byte[1024];
        try (ZipInputStream zis = new ZipInputStream(new FileInputStream(zipFile))) {
            ZipEntry zipEntry = zis.getNextEntry();
            while (zipEntry != null) {
                File newFile = new File(destDir, zipEntry.getName());
                if (zipEntry.isDirectory()) {
                    newFile.mkdirs();
                } else {
                    // Create directories for nested files
                    new File(newFile.getParent()).mkdirs();
                    try (FileOutputStream fos = new FileOutputStream(newFile)) {
                        int len;
                        while ((len = zis.read(buffer)) > 0) {
                            fos.write(buffer, 0, len);
                        }
                    }
                }
                zipEntry = zis.getNextEntry();
            }
            zis.closeEntry();
        }
        zipFile.delete();
    }

四、易错点

易错点1:前端要进行分片切割,然后再分片上传。

易错点2:后端配置文件要配置。

后端配置文件要配置,不然默认浏览器只支持5M或者1M,当上传10M分片文件时会报错,无法调通接口,执行流程就卡在前端了,压根调不通后端上传接口。

spring.servlet.multipart.max-file-size=600MB
spring.servlet.multipart.max-request-size=2GB

易错点3:个人感觉最好分片大小别太大。

易错点4:上传途中最好加个“遮罩”或者进度条,比如显示“正在上传中…”会更友好。

易错点5:如果项目采用nginx,记得修改nginx.conf。

如果项目采用nginx,记得修改nginx.conf,配置上传切片大小、超时时间等等参数设置,否则也会上传失败。

五、补充点

网上还有网友说道使用什么“断点续传”的功能,这个我目前未验证,不清楚如何,其他博主可自行验证然后分享结论看是否可行。


网站公告

今日签到

点亮在社区的每一天
去签到