如何通过Ajax无表单上传PDF,在Servlet中处理字节流并写入文件?
可行性分析与优化方案
这种方案理论上能勉强走通,但存在诸多致命问题,完全不推荐在实际项目中使用,下面我来拆解原因,并给出更合理的替代方案:
为什么不建议这么做?
- 二进制转JSON字符串的编码失真:JSON是纯文本格式,当你把PDF的字节流直接转成字符串再用
JSON.stringify处理时,会因为编码(比如UTF-8)的特性丢失二进制信息。PDF包含大量非文本的二进制数据,转成文本后再解码还原,大概率会导致文件损坏,无法正常打开。 - 传输与性能损耗:二进制数据转成字符串会显著增大体积(比如用Base64编码会额外增加33%的大小,直接转字符串可能更糟),大文件上传时会占用更多带宽,容易触发请求超时,同时也会增加服务器端的解析压力。
- 解析复杂度高:Gson本身并不擅长处理二进制数据的序列化/反序列化,你需要手动处理字符串到字节数组的转换,这个过程很容易引入bug,最后写出的文件极大概率是损坏的。
正确的无表单Ajax文件上传方案
实际上,不需要表单也能轻松实现文件上传,推荐使用FormData对象来封装文件,发送multipart/form-data请求,这是浏览器和服务器端都支持的标准文件上传方式,既稳定又高效。
前端JavaScript代码示例
// 假设你通过表格内的文件选择框获取到PDF文件对象 const pdfFile = document.querySelector('.approval-row input[type="file"]').files[0]; // 创建FormData对象,添加文件和审批相关的其他数据(比如行ID) const formData = new FormData(); formData.append("approvalPdf", pdfFile); // 后端接收的文件参数名 formData.append("approvalRowId", "row-123"); // 关联审批行的ID // 发送Ajax请求(用fetch或者XMLHttpRequest都可以) fetch("/approval-upload-servlet", { method: "POST", body: formData }) .then(res => res.text()) .then(message => { console.log("文件上传完成:", message); // 更新表格行的状态,比如标记上传成功 document.querySelector('.approval-row').classList.add('upload-success'); }) .catch(err => { console.error("上传失败:", err); });
后端Servlet代码示例
首先给Servlet加上@MultipartConfig注解,开启文件上传支持:
@WebServlet("/approval-upload-servlet") @MultipartConfig( fileSizeThreshold = 1024 * 1024 * 2, // 超过2MB的文件直接写入磁盘 maxFileSize = 1024 * 1024 * 15, // 单个文件最大15MB maxRequestSize = 1024 * 1024 * 50 // 整个请求最大50MB ) public class ApprovalUploadServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 获取上传的文件Part Part pdfPart = request.getPart("approvalPdf"); String rowId = request.getParameter("approvalRowId"); if (pdfPart == null || rowId == null) { response.getWriter().write("参数缺失"); return; } // 获取原始文件名 String originalFileName = pdfPart.getSubmittedFileName(); // 定义文件保存路径(建议用服务器的安全目录,避免直接暴露在web根目录) String saveDir = getServletContext().getRealPath("/WEB-INF/approval-pdfs"); File dir = new File(saveDir); if (!dir.exists()) { dir.mkdirs(); // 目录不存在则创建 } // 用行ID重命名文件,避免重名覆盖 String savePath = saveDir + File.separator + rowId + "_" + originalFileName; // 写出文件到磁盘 try (InputStream inputStream = pdfPart.getInputStream(); FileOutputStream outputStream = new FileOutputStream(savePath)) { byte[] buffer = new byte[1024]; int bytesRead; while ((bytesRead = inputStream.read(buffer)) != -1) { outputStream.write(buffer, 0, bytesRead); } } // 将文件路径关联到你的审批POJO对象,存入数据库或更新流程状态 // ApprovalPOJO approval = approvalDao.getById(rowId); // approval.setPdfFilePath(savePath); // approvalDao.update(approval); response.getWriter().write("PDF上传成功,已关联到审批行:" + rowId); } }
方案优势
- 无编码失真:直接传输二进制文件流,完全保留原始文件的完整性
- 高效传输:没有额外的编码膨胀,大文件上传更稳定
- 标准兼容:Servlet的
MultipartConfig和浏览器的FormData都是标准API,兼容性好,维护成本低
内容的提问来源于stack exchange,提问作者Tousik Ghosh Dastidar
相关产品推荐
相关产品推荐

