前言

       最近做了个Excel导出功能,流程是先生成文件在本地,然后通过Nginx代理出本地路径,返回代理的访问地址,前端通过fetch 请求下载文件,导出文件报错:Failed to parse multipart servlet request; nested exception is java.io.IOException:
The temporary upload location [C:\Windows\Temp\tomcat.8567183481807092481.9901\work\Tomcat\localhost\ROOT] is not valid

 Failed to parse multipart servlet request; nested exception is java.io.IOException: 
 The temporary upload location [C:\Windows\Temp\tomcat.8567183481807092481.9901\work\Tomcat\localhost\ROOT] is not valid

一 原请求方法

1.1 前端请求
function initUpload() {
    uploadInstance = upload.render({
        elem: '#uploadHomeVisitsBtn', // 上传按钮的选择器
        auto: false, // 设置为自动上传
        multiple: false, // 多文件上传
        accept: 'images', // 只允许图片
        exts: 'jpg|png|gif|bmp|jpeg|webp|svg', // 允许的文件后缀
        url: '/springApi/inducts/importVisit', // 后台接口 URL (请替换成实际接口地址)
        choose: function (obj) {
            // 检查当前文件列表是否超过最大数量
            if (fileList.length === maxFiles || fileList.length > maxFiles) {
                PromptModal("警告", "最多只能上传9张图片");
                return; // 阻止上传
            }

            // 点击上传就禁用保存按钮
            $("#idSave").attr("disabled", "disabled");

            // 创建 FormData 对象
            var formData = new FormData();
            obj.preview(function (index, file, result) {
                formData.append("file", file);  // 添加文件到 FormData
                formData.append('urlPrefix', prefix);  // 添加额外的参数

                // 使用 fetch 上传文件
                fetch('/springApi/inducts/importVisit', {
                    method: 'POST',
                    body: formData,
                    credentials: 'include' // 显式携带 cookie
                }).then(resp => resp.json())
                    .then(res => {
                        if (res.code == 0) {
                            // 上传成功,更新文件列表
                            fileList.push({
                                fileName: res.data.name,
                                previewUrl: res.data.url
                            });
                            updateFileList();
                        } else {
                            // 上传失败,弹出提示并重置按钮状态
                            uploadInstance.config.elem.val('');
                            hyd.alert.showAlert(`上传失败:【 ${res.message}`);
                        }
                        // 最后处理恢复按钮
                        $("#idSave").removeAttr("disabled");
                    }).catch(error => {
                        // 上传过程中发生错误,处理异常
                        uploadInstance.config.elem.val('');
                        hyd.alert.showAlert(`上传失败:【 ${error}`);
                        // 上传完成后,恢复保存按钮状态
                        $("#idSave").removeAttr("disabled");
                })
            });
        },
        error: function (index, upload) {
            // 上传失败的回调
            hyd.alert.showAlert("上传失败,请重试或者联系管理员");
            // 失败后解禁保存按钮
            $("#idSave").removeAttr("disabled");
        },
    });
}

1.2 后端处理
public ResultVO importVisit(MultipartFile file,String urlPrefix) {

        if (file.isEmpty()) {
            return ResultFactory.error("现场照片不能为空");
        }

        //写入本地 D:\\huabin\\bizfile\\wechatfile\\
        String format = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
        String basePath = importVisitPath + format + File.separator;
        File dir = new File(basePath);
        if (!dir.exists()) {
            dir.mkdirs(); // 创建目录
        }
        //文件名
        String fileName = UUID.randomUUID() + "_" + file.getOriginalFilename();
        File file1 = new File(dir, fileName);
        try {
            file.transferTo(file1);
        } catch (IOException e) {
            return ResultFactory.error("上传现场照片失败");
        }
        FollowupPicture followupPicture = new FollowupPicture();
        //代理路径 /wechatfile/2025-02-07/e0dccef4-8a05-4a9d-bc41-dd6a397d7f84_photo2.png
        String fullPath = urlPrefix + sharePath + format + BaseConstants.SLASH + fileName;
        followupPicture.setUrl(fullPath);
        followupPicture.setName(fileName);
        return ResultFactory.success(followupPicture);
    }

        在上述的后端处理中,file.transferTo() 方法会把前端传递的文件对象生成到一个临时文件,然后在把临时文件转移到目标路径,这就很容易造成一个问题,那就是这个临时文件路径一般都是没有给外部权限的,就容易报错:Failed to parse multipart servlet request; nested exception is java.io.IOException:
The temporary upload location [C:\Windows\Temp\tomcat.8567183481807092481.9901\work\Tomcat\localhost\ROOT] is not valid

二 处理 办法

2.1 更改临时文件路径
1.yml配置目标路径
 server:
  port: 8770
  tomcat:
    basedir: C:/users/song/tomcatTmp
 
 2.java中指定Tomcat的临时文件路径   
@Bean
MultipartConfigElement multipartConfigElement() {
  MultipartConfigFactory factory = new MultipartConfigFactory();
  String location = "C:/users/song/tomcatTmp";
  File tmpFile = new File(location);
  if (!tmpFile.exists()) {
    tmpFile.mkdirs();
  }
  factory.setLocation(location);
  return factory.createMultipartConfig();
}
3.配置Jvm 启动参数
-Djava.io.tmpdir=D:/TEST/temp

       说实话,我用了上述方法,在本地是实现了,文件生成时是在指定的临时路径,但是在生产环境,就是不行,有大佬知道吗,劳烦提点提点?故我采取了另一种方式,采用文件流的方式处理,这种方式就不需要先生成在零时文件路径在转移至目标路径;

2.2 采用文件流的方式处理
2.21 前端调整
function initUpload() {
    uploadInstance = upload.render({
        elem: '#uploadHomeVisitsBtn', // 上传按钮的选择器
        auto: false, // 设置为自动上传
        multiple: false, // 多文件上传
        accept: 'images', // 只允许图片
        exts: 'jpg|png|gif|bmp|jpeg|webp|svg', // 允许的文件后缀
        url: '/springApi/inducts/uploadVisit', // 后台接口 URL (请替换成实际接口地址)
        choose: function (obj) {
            // 检查当前文件列表是否超过最大数量
            if (fileList.length === maxFiles || fileList.length > maxFiles) {
                PromptModal("警告", "最多只能上传9张图片");
                return; // 阻止上传
            }

            // 点击上传就禁用保存按钮
            $("#idSave").attr("disabled", "disabled");

            obj.preview(function (index, file, result) {
                // 使用 fetch 上传文件
                fetch('/springApi/inducts/uploadVisit', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/octet-stream',
                        'X-Filename': file.name
                    },
                    body: file,
                    credentials: 'include' // 显式携带 cookie
                }).then(resp => resp.json())
                    .then(res => {
                        if (res.code == 0) {
                            // 上传成功,更新文件列表
                            fileList.push({
                                fileName: res.data.name,
                                previewUrl: res.data.url
                            });
                            updateFileList();
                        } else {
                            // 上传失败,弹出提示并重置按钮状态
                            uploadInstance.config.elem.val('');
                            hyd.alert.showAlert(`上传失败:【 ${res.message}`);
                        }
                        // 最后处理恢复按钮
                        $("#idSave").removeAttr("disabled");
                    }).catch(error => {
                        // 上传过程中发生错误,处理异常
                        uploadInstance.config.elem.val('');
                        hyd.alert.showAlert(`上传失败:【 ${error}`);
                        // 上传完成后,恢复保存按钮状态
                        $("#idSave").removeAttr("disabled");
                })
            });
        },
        error: function (index, upload) {
            // 上传失败的回调
            hyd.alert.showAlert("上传失败,请重试或者联系管理员");
            // 失败后解禁保存按钮
            $("#idSave").removeAttr("disabled");
        },
    });
}
2.2.2 后端调整
public ResultVO uploadVisit(HttpServletRequest request) {
        //获取原文件名
        String originalFileName = request.getHeader("X-Filename");
        if (originalFileName == null) {
            return ResultFactory.error("文件名缺失");
        }
        try {
            originalFileName = URLDecoder.decode(originalFileName, String.valueOf(StandardCharsets.UTF_8));
        } catch (UnsupportedEncodingException e) {
            return ResultFactory.error("上传失败,文件名解密错误: " + e.getMessage());
        }
        String suffix = "";
        int dotIndex = originalFileName.lastIndexOf('.');
        if (dotIndex != -1) {
            suffix = originalFileName.substring(dotIndex);
        }

        String uuid = UUID.randomUUID().toString();
        String fileName = uuid + suffix;

        //写入本地 D:\\huabin\\bizfile\\wechatfile\\
        String format = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
        String basePath = importVisitPath + format + BaseConstants.SLASH;
        File dir = new File(basePath);
        if (!dir.exists()) {
            dir.mkdirs();
        }

        File outFile = new File(dir, fileName);
        try (ServletInputStream inputStream = request.getInputStream();
             OutputStream outputStream = Files.newOutputStream(outFile.toPath())) {
            byte[] buffer = new byte[8192];
            int len;
            while ((len = inputStream.read(buffer)) != -1) {
                outputStream.write(buffer, 0, len);
            }
            FollowupPicture followupPicture = new FollowupPicture();
            //代理路径 /wechatfile/2025-02-07/e0dccef4-8a05-4a9d-bc41-dd6a397d7f84_photo2.png
            String fullPath = sharePath + format + BaseConstants.SLASH + fileName;
            followupPicture.setUrl(fullPath);
            followupPicture.setName(fileName);
            return ResultFactory.success(followupPicture);
        } catch (IOException e) {
            return ResultFactory.error("上传失败: " + e.getMessage());
        }
    }
Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐