vue java 实现大地图切片上传

文章目录

一、项目背景

技术:vue+Arco Design+java

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

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

二、页面

三、代码

1.前端

java 复制代码
<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文件

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

3.xx.js文件定义方法

java 复制代码
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

java 复制代码
spring.servlet.multipart.max-file-size=600MB
spring.servlet.multipart.max-request-size=2GB

5.后端方法

java 复制代码
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分片文件时会报错,无法调通接口,执行流程就卡在前端了,压根调不通后端上传接口。

java 复制代码
spring.servlet.multipart.max-file-size=600MB
spring.servlet.multipart.max-request-size=2GB

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

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

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

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

五、补充点

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

相关推荐
醉城夜风~1 小时前
Java详解经典算法题:接雨水(三种实现方案+原理剖析)
java·开发语言·算法
数智化转型推荐官2 小时前
2026统一身份管理系统五大发展趋势解读
java·运维·微服务
看菜鸡互2 小时前
探索用 SlideML 让大模型生成 PPT 的实验方法
java·运维
你挚爱的强哥4 小时前
Vue2 实现 1.5s 十连击监听(连续点击若干次),封装通用可复用点击检测工具,不用 data!Vue 封装通用连击监听方法,支持自定义时长与点击次数
前端·javascript·vue.js
程序员张34 小时前
SpringBoot集成BCrypt密码加密库
java·spring boot·后端
闲猫4 小时前
Spring AI Agentic 模式(第1部分):Agent Skills——模块化、可复用的能力
java·人工智能·spring
源图客4 小时前
云途物流API开发-鉴权认证(Java)
java·网络·python
liguojun20254 小时前
篮球馆自动计时收费系统:从规则配置到自动结算的全流程拆解
java·大数据·运维·人工智能·物联网·1024程序员节
952365 小时前
RabbitMQ-基础操作
java·spring boot·分布式·后端·spring·rabbitmq
卓怡学长5 小时前
w266基于spring boot + vue 圣地延安美食乐享系统
java·数据库·vue.js·spring boot·spring·intellij-idea