You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Struts 6.3.0.2升级后PDF/Word文档下载损坏问题求助

文件下载损坏问题(Struts2升级至6.3.0.2后)

升级后PDF和docx文件下载出现损坏:

  • 下载的PDF无法打开
  • docx文件打开时显示自定义提示「系统无法处理您的请求」
  • 控制台日志显示文件已成功下载,但docx仍无法打开

升级前原有代码

import java.io.*;
import java.nio.file.Files;
import java.util.ResourceBundle;

public void downloadAttach(){
    try {
        String filename = DotsFormUtil.cleanStringFile(request.getParameter("filename"));
        String realname = DotsFormUtil.cleanStringFile(request.getParameter("name"));

        HttpSession session = (HttpSession) request.getSession();
        String strDotsIdToken = (String) session.getAttribute("strDotsIdToken");

        try{
            if(strDotsIdToken == null || !strDotsIdToken.equals(filename.split("_")[0]))
                logger.error("strDotsIdToken is null or strDotsIdToken is not equal and Exception is thrown");
        }catch (Exception e){
            logger.info(e);
        }
        try {
            if(filename!= null){
               ServletOutputStream out = null;
               FileInputStream fi = null;
                try {
                    ResourceBundle bundle = ResourceBundle.getBundle("resources.dotsDisplay");
                    String strDirectory=DotsFormUtil.cleanpString(bundle.getString("dots.attachments.path"));
                    File f= new File(FilenameUtils.normalize(DotsFormUtil.cleanString(strDirectory+File.separator + filename)));
                    String pattern = "[a-zA-Z0-9]{1,50}\\.[a-zA-Z0-9]{1,10}";
                    if(realname.matches(pattern)){

                        response.setHeader("Content-Disposition","attachment; fileName=" +realname);
                    }
                    response.addHeader("Content-Transfer-Encoding","base64");

                    out = response.getOutputStream();
                    fi= new FileInputStream(f);
                    byte[] buffer = new byte[(int)f.length()];

                    int readInt=fi.read(buffer);
                    try{
                        if(readInt != -1){
                            out.write(buffer);
                            out.flush();
                        }
                    }catch (Exception e){
                        logger.info(e);
                    }
                } catch (FileNotFoundException e){
                    e.printStackTrace();
                    logger.info("File not found error" + filename);
                    logger.error(e.getMessage());
                }catch (IOException e){
                    logger.info("error in reading file:" + filename);
                }finally {
                    if(out != null) safeClose(out);
                    if(fi != null) safeClose(fi);
                }
            }
        }catch (Exception e){
            e.printStackTrace();
            logger.error(e.getMessage(),e);
        }
        logger.info("successfully download attachment file");
    }catch (Exception e){
        e.printStackTrace();
    }
}

public static String cleanStringFile(String aString){
    if(aString==null) return null;
    String cleanString = "";
    char cleanChar = '\0';
    for(int i=0; i<aString.length(); i++){
        cleanChar = cleanCharFile(aString.charAt(i));
        if(cleanChar != '\0') cleanString+=cleanChar;
    }
    return cleanString;
}

private static char cleanCharFile(char aChar){
    for(int i = 48; i<58; ++i){
        if(aChar ==i) return (char) i;
    }
    for(int i = 65; i<91; ++i){
        if(aChar ==i) return (char) i;
    }
    for(int i = 97; i<123; ++i){
        if(aChar ==i) return (char) i;
    }

    switch (aChar){
        case '.':
            return '.';
        case '_':
            return '_';
        case '-':
            return '-';
        case '!':
            return '!';
    }
    return '\0';
}

调整后代码

import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;

public class getDotsMissionAction {

    public void downloadAttach() {
        // Your existing code...

        try {
            if (filename != null) {
                try {
                    ResourceBundle bundle = ResourceBundle.getBundle("resources.dotsDisplay");
                    String strDirectory =  DotsFormUtil.cleanString(bundle.getString("dots.attachments.path"));
                    File f = new File(strDirectory + File.separator + filename);
                    String pattern = "[a-zA-Z0-9]{1,50}\\.[a-zA-Z0-9]{1,10}";
                    if (realname.matches(pattern)) {
                        response.reset();
                        response.setCharacterEncoding("UTF-8");
                        response.setContentType("application/pdf");
                        response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document");
                        response.setHeader("Content-Disposition", "attachment; fileName=" + realname);

                        try (FileInputStream fis = new FileInputStream(f);
                             OutputStream out2 = response.getOutputStream()) {
                            byte[] buffer = new byte[1024];
                            int bytesRead;
                            while ((bytesRead = fis.read(buffer)) != -1) {
                                out2.write(buffer, 0, bytesRead);
                            }
                        }
                    }
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                    logger.error(e.getMessage());
                }
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            logger.error(e.getMessage());
        }
        logger.info("successfully download attachment file");
    }
}

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import javax.servlet.http.HttpServletResponse;

public class getDotsMissionAction {

    public void downloadAttach(HttpServletResponse response) {
        // Your existing code...

        try {
            if (filename != null) {
                ResourceBundle bundle = ResourceBundle.getBundle("resources.dotsDisplay");
                String strDirectory = DotsFormUtil.cleanString(bundle.getString("dots.attachments.path"));
                File f = new File(strDirectory + File.separator + filename);
                String pattern = "[a-zA-Z0-9]{1,50}\\.[a-zA-Z0-9]{1,10}";
                if (realname.matches(pattern)) {
                    response.reset();
                    response.setCharacterEncoding("UTF-8");

                    // Determine content type based on file extension
                    String contentType;
                    if (realname.toLowerCase().endsWith(".pdf")) {
                        contentType = "application/pdf";
                    } else if (realname.toLowerCase().endsWith(".docx")) {
                        contentType = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
                    } else {
                        // Add more content types as needed
                        contentType = "application/octet-stream"; // Default to binary
                    }
                    response.setContentType(contentType);
                    response.setHeader("Content-Disposition", "attachment; filename=" + realname);

                    try (FileInputStream fis = new FileInputStream(f);
                         OutputStream out2 = response.getOutputStream()) {
                        byte[] buffer = new byte[1024];
                        int bytesRead;
                        while ((bytesRead = fis.read(buffer)) != -1) {
                            out2.write(buffer, 0, bytesRead);
                        }
                    }
                } else {
                    // Handle invalid filename pattern
                    // For example: Log an error, return a response indicating invalid file, etc.
                }
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            logger.error(e.getMessage());
        } catch (IOException e) {
            e.printStackTrace();
            logger.error(e.getMessage());
        }
        logger.info("successfully download attachment file");
    }

    // Your other methods...
}

解决方案

  • 移除二进制文件的字符编码设置:删除response.setCharacterEncoding("UTF-8"),二进制文件不需要字符编码,该设置会破坏文件流结构。
  • 修复文件名编码:Struts 6.x对响应头解析更严格,文件名需采用UTF-8编码的标准格式:
    import java.net.URLEncoder;
    // ...
    String encodedFilename = URLEncoder.encode(realname, "UTF-8");
    response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + encodedFilename);
    
  • 添加Content-Length头:明确告诉浏览器文件大小,避免接收不完整:
    response.setContentLengthLong(f.length());
    
  • 清理响应状态:确保response.reset()在所有响应头设置前调用,清除可能残留的响应数据。
  • 移除错误的Content-Transfer-Encoding头:升级前代码中添加的base64编码头会导致二进制文件被错误编码,必须删除。
  • 使用Struts官方推荐的@Stream注解:简化文件下载逻辑,避免手动处理流的常见错误:
    import org.apache.struts2.convention.annotation.Action;
    import org.apache.struts2.convention.annotation.Result;
    import org.apache.struts2.stream.annotations.Stream;
    import java.io.FileInputStream;
    import java.io.InputStream;
    
    @Action(value = "downloadAttach", results = @Result(type = "stream"))
    public class getDotsMissionAction {
        private String realname;
        private File f;
    
        @Stream(value = "attachment", 
                contentType = "${contentType}", 
                contentLength = "${fileSize}", 
                fileName = "${encodedFilename}")
        public InputStream getAttachmentStream() throws Exception {
            return new FileInputStream(f);
        }
    
        // 初始化file、realname等参数的逻辑
        public String execute() throws Exception {
            // 原有验证、文件路径获取逻辑
            return "success";
        }
    
        // getter方法供@Stream注解使用
        public String getContentType() {
            if (realname.toLowerCase().endsWith(".pdf")) {
                return "application/pdf";
            } else if (realname.toLowerCase().endsWith(".docx")) {
                return "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
            }
            return "application/octet-stream";
        }
    
        public long getFileSize() {
            return f.length();
        }
    
        public String getEncodedFilename() throws Exception {
            return URLEncoder.encode(realname, "UTF-8");
        }
    
        // 其他getter/setter
    }
    
  • 检查拦截器干扰:在struts.xml中为该action排除可能修改响应流的拦截器,比如encoding拦截器:
    <action name="downloadAttach" class="com.yourpackage.getDotsMissionAction">
        <interceptor-ref name="defaultStack">
            <param name="encoding.excludeMethods">downloadAttach</param>
        </interceptor-ref>
        <result type="stream">
            <param name="contentType">${contentType}</param>
            <param name="contentDisposition">attachment; filename*=UTF-8''${encodedFilename}</param>
            <param name="contentLength">${fileSize}</param>
            <param name="inputName">attachmentStream</param>
        </result>
    </action>
    

内容的提问来源于stack exchange,提问作者Mahabir Gupta

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 23:05:55