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
相关产品推荐
相关产品推荐

