| | |
| | | import com.estate.expert.admin.service.CheckTaskService; |
| | | import com.estate.expert.admin.service.ReportService; |
| | | import com.estate.expert.admin.service.ReportTemplateService; |
| | | import com.estate.expert.admin.utils.wordtopdf.PdfUtils; |
| | | import com.estate.expert.admin.utils.wordtopdf.WordToPdfTest; |
| | | import com.estate.expert.common.auth.IgnoreAuth; |
| | | import com.estate.expert.common.rpc.Result; |
| | | import fr.opensagres.poi.xwpf.converter.pdf.PdfConverter; |
| | | import fr.opensagres.poi.xwpf.converter.pdf.PdfOptions; |
| | | import io.swagger.annotations.Api; |
| | | import io.swagger.annotations.ApiOperation; |
| | | import io.swagger.annotations.ApiParam; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.apache.pdfbox.pdmodel.PDDocument; |
| | | import org.apache.pdfbox.rendering.ImageType; |
| | | import org.apache.pdfbox.rendering.PDFRenderer; |
| | | import org.apache.poi.xwpf.usermodel.XWPFDocument; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.beans.factory.annotation.Value; |
| | | import org.springframework.stereotype.Controller; |
| | | import org.springframework.validation.annotation.Validated; |
| | | import org.springframework.web.bind.annotation.CrossOrigin; |
| | | import org.springframework.web.bind.annotation.GetMapping; |
| | | import org.springframework.web.bind.annotation.RequestMapping; |
| | | import org.springframework.web.bind.annotation.RequestParam; |
| | | import org.springframework.web.bind.annotation.*; |
| | | |
| | | import javax.annotation.Resource; |
| | | import javax.imageio.ImageIO; |
| | | import javax.servlet.http.HttpServletResponse; |
| | | import java.io.BufferedOutputStream; |
| | | import java.io.IOException; |
| | | import java.io.OutputStream; |
| | | import java.awt.image.BufferedImage; |
| | | import java.io.*; |
| | | import java.net.HttpURLConnection; |
| | | import java.net.URL; |
| | | import java.net.URLEncoder; |
| | | import java.nio.file.Files; |
| | | import java.nio.file.Path; |
| | | import java.nio.file.StandardOpenOption; |
| | | |
| | | @Api(tags = "管理后台/统计/报告下载") |
| | | @Slf4j |
| | |
| | | @Resource |
| | | private ReportTemplateService reportTemplateService; |
| | | |
| | | @Value("${file.upload.dir}/reports") |
| | | private String REPORT_UPLOAD_DIR; |
| | | |
| | | @ApiOperation("模糊评价报告下载") |
| | | @GetMapping("/fuzzyReport") |
| | | @IgnoreAuth |
| | | public void fuzzyReport(@ApiParam("检查任务ID") @RequestParam(value = "checkTaskId") Integer checkTaskId, |
| | | @ApiParam("报告模版ID") @RequestParam(value = "templateId") Integer templateId, |
| | | HttpServletResponse response) throws IOException { |
| | | @ApiParam("报告模版ID") @RequestParam(value = "templateId") Integer templateId, |
| | | @ApiParam("uuid") @RequestParam(value = "uuid",required = false) String uuid, |
| | | HttpServletResponse response) throws IOException { |
| | | try (var doc = reportService.fuzzyReport(checkTaskId, templateId)) { |
| | | download(doc, response, "模糊评价", checkTaskId, templateId); |
| | | download(doc, response, "模糊评价", checkTaskId, templateId,uuid); |
| | | } |
| | | } |
| | | |
| | |
| | | @IgnoreAuth |
| | | public void scoreReport(@ApiParam("检查任务ID") @RequestParam(value = "checkTaskId") Integer checkTaskId, |
| | | @ApiParam("报告模版ID") @RequestParam(value = "templateId") Integer templateId, |
| | | @ApiParam("uuid") @RequestParam(value = "uuid",required = false) String uuid, |
| | | HttpServletResponse response) throws IOException { |
| | | try (var doc = reportService.scoreReport(checkTaskId, templateId)) { |
| | | download(doc, response, "打分评价", checkTaskId, templateId); |
| | | download(doc, response, "打分评价", checkTaskId, templateId,uuid); |
| | | } |
| | | } |
| | | |
| | |
| | | public void example(@ApiParam("报告模版ID") @RequestParam(value = "templateId") Integer templateId, |
| | | HttpServletResponse response) throws IOException { |
| | | try (var doc = reportService.fuzzyReport(1, templateId)) { |
| | | download(doc, response, "示例",1, templateId); |
| | | download(doc, response, "示例",1, templateId,null); |
| | | } |
| | | } |
| | | |
| | | @Autowired |
| | | private PdfUtils pdfUtils; |
| | | |
| | | |
| | | @ApiOperation("模糊评价报告PDF下载(新接口)- 通过Word链接转换") |
| | | @GetMapping("/fuzzyReportPdfNew") |
| | | @IgnoreAuth |
| | | @ResponseBody |
| | | public Result fuzzyReportPdfNew( @RequestParam(value = "uuid", required = false) String uuid, |
| | | HttpServletResponse response) throws Exception { |
| | | |
| | | try { |
| | | // String test = pdfUtils.test("F:\\file\\estate\\reports\\1769239688785.docx"); |
| | | String s = WordToPdfTest.testSingleFileConversion(uuid); |
| | | return Result.success(s); |
| | | } catch (Exception e) { |
| | | throw new RuntimeException(e); |
| | | } |
| | | |
| | | } |
| | | |
| | | |
| | | @ApiOperation("模糊评价报告PDF下载") |
| | | @GetMapping("/fuzzyReportPdf") |
| | |
| | | downloadPdf(checkTaskId, templateId, response, "打分评价", false); |
| | | } |
| | | |
| | | @ApiOperation("检查报告转换状态") |
| | | @GetMapping("/checkConversionStatus") |
| | | @ResponseBody |
| | | @IgnoreAuth |
| | | public Result<ConversionStatus> checkConversionStatus(@ApiParam("文件UUID") @RequestParam(value = "uuid") String uuid) { |
| | | try { |
| | | ConversionStatus status = new ConversionStatus(); |
| | | status.setUuid(uuid); |
| | | |
| | | // 检查Word文件是否存在 |
| | | Path wordPath = Path.of(REPORT_UPLOAD_DIR).resolve(uuid + ".docx"); |
| | | status.setWordFileExists(Files.exists(wordPath)); |
| | | |
| | | // 检查图片文件 |
| | | Path imageDir = Path.of(REPORT_UPLOAD_DIR, "images"); |
| | | if (Files.exists(imageDir)) { |
| | | try (var stream = Files.list(imageDir)) { |
| | | long imageCount = stream.filter(path -> { |
| | | String fileName = path.getFileName().toString(); |
| | | return fileName.startsWith(uuid + "_page_") && fileName.endsWith(".png"); |
| | | }).count(); |
| | | status.setImageCount((int) imageCount); |
| | | status.setConversionCompleted(imageCount > 0); |
| | | } |
| | | } else { |
| | | status.setImageCount(0); |
| | | status.setConversionCompleted(false); |
| | | } |
| | | |
| | | return Result.success(status); |
| | | |
| | | } catch (Exception e) { |
| | | log.error("检查转换状态失败: uuid={}", uuid, e); |
| | | return Result.error("检查转换状态失败: " + e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | private void download(XWPFTemplate doc, HttpServletResponse response, String typeName, |
| | | Integer checkTaskId, Integer templateId) throws IOException { |
| | | OutputStream out; |
| | | BufferedOutputStream bos; |
| | | Integer checkTaskId, Integer templateId,String uuid) throws IOException { |
| | | ByteArrayOutputStream docOutputStream = null; |
| | | OutputStream out = null; |
| | | BufferedOutputStream bos = null; |
| | | |
| | | var checkTask = checkTaskService.get(checkTaskId, true); |
| | | var reportTemplate = reportTemplateService.get(templateId); |
| | | BizResultCode.TASK_NOT_FOUND.check(checkTask == null); |
| | | try { |
| | | var checkTask = checkTaskService.get(checkTaskId, true); |
| | | var reportTemplate = reportTemplateService.get(templateId); |
| | | BizResultCode.TASK_NOT_FOUND.check(checkTask == null); |
| | | |
| | | var docName = checkTask.getProject().getName()+ "_" + reportTemplate.getName() + "_" + typeName ; |
| | | String encodedFileName = URLEncoder.encode(docName, "UTF-8"); |
| | | response.setContentType("application/octet-stream"); |
| | | response.setHeader("Content-disposition", "attachment;filename=\"" + encodedFileName + ".docx\""); |
| | | var docName = checkTask.getProject().getName() + "_" + reportTemplate.getName() + "_" + typeName; |
| | | String encodedFileName = URLEncoder.encode(docName, "UTF-8"); |
| | | response.setContentType("application/octet-stream"); |
| | | response.setHeader("Content-disposition", "attachment;filename=\"" + encodedFileName + ".docx\""); |
| | | |
| | | // HttpServletResponse response |
| | | out = response.getOutputStream(); |
| | | bos = new BufferedOutputStream(out); |
| | | doc.write(bos); |
| | | bos.flush(); |
| | | out.flush(); |
| | | // 先将文档写入字节数组 |
| | | docOutputStream = new ByteArrayOutputStream(); |
| | | doc.write(docOutputStream); |
| | | byte[] docBytes = docOutputStream.toByteArray(); |
| | | |
| | | // 写入响应流供用户下载 |
| | | out = response.getOutputStream(); |
| | | bos = new BufferedOutputStream(out); |
| | | bos.write(docBytes); |
| | | bos.flush(); |
| | | out.flush(); |
| | | |
| | | // 保存文件到服务器 |
| | | if(uuid!=null && !uuid.isEmpty()){ |
| | | saveFileToServer(docBytes, docName + ".docx",uuid); |
| | | } |
| | | } catch (Exception e) { |
| | | log.error("文档下载失败", e); |
| | | throw new IOException("文档下载失败: " + e.getMessage(), e); |
| | | } finally { |
| | | if (docOutputStream != null) { |
| | | try { |
| | | docOutputStream.close(); |
| | | } catch (Exception e) { |
| | | log.warn("关闭输出流失败", e); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | private void downloadPdf(Integer checkTaskId, Integer templateId, HttpServletResponse response, |
| | | String typeName, boolean isFuzzy) throws IOException { |
| | | OutputStream out; |
| | | BufferedOutputStream bos; |
| | | ByteArrayOutputStream pdfOutputStream = null; |
| | | OutputStream out = null; |
| | | BufferedOutputStream bos = null; |
| | | |
| | | var checkTask = checkTaskService.get(checkTaskId, true); |
| | | var reportTemplate = reportTemplateService.get(templateId); |
| | | BizResultCode.TASK_NOT_FOUND.check(checkTask == null); |
| | | try { |
| | | var checkTask = checkTaskService.get(checkTaskId, true); |
| | | var reportTemplate = reportTemplateService.get(templateId); |
| | | BizResultCode.TASK_NOT_FOUND.check(checkTask == null); |
| | | |
| | | var docName = checkTask.getProject().getName() + "_" + reportTemplate.getName() + "_" + typeName; |
| | | String encodedFileName = URLEncoder.encode(docName, "UTF-8"); |
| | | response.setContentType("application/pdf"); |
| | | response.setHeader("Content-disposition", "attachment;filename=\"" + encodedFileName + ".pdf\""); |
| | | var docName = checkTask.getProject().getName() + "_" + reportTemplate.getName() + "_" + typeName; |
| | | String encodedFileName = URLEncoder.encode(docName, "UTF-8"); |
| | | response.setContentType("application/pdf"); |
| | | response.setHeader("Content-disposition", "attachment;filename=\"" + encodedFileName + ".pdf\""); |
| | | |
| | | out = response.getOutputStream(); |
| | | bos = new BufferedOutputStream(out); |
| | | // 先将PDF写入字节数组 |
| | | pdfOutputStream = new ByteArrayOutputStream(); |
| | | if (isFuzzy) { |
| | | reportService.fuzzyReportPdf(checkTaskId, templateId, pdfOutputStream); |
| | | } else { |
| | | reportService.scoreReportPdf(checkTaskId, templateId, pdfOutputStream); |
| | | } |
| | | byte[] pdfBytes = pdfOutputStream.toByteArray(); |
| | | |
| | | if (isFuzzy) { |
| | | reportService.fuzzyReportPdf(checkTaskId, templateId, bos); |
| | | } else { |
| | | reportService.scoreReportPdf(checkTaskId, templateId, bos); |
| | | // 写入响应流供用户下载 |
| | | out = response.getOutputStream(); |
| | | bos = new BufferedOutputStream(out); |
| | | bos.write(pdfBytes); |
| | | bos.flush(); |
| | | out.flush(); |
| | | |
| | | // 保存文件到服务器 |
| | | saveFileToServer(pdfBytes, docName + ".pdf",null); |
| | | |
| | | } catch (Exception e) { |
| | | log.error("PDF下载失败", e); |
| | | throw new IOException("PDF下载失败: " + e.getMessage(), e); |
| | | } finally { |
| | | if (pdfOutputStream != null) { |
| | | try { |
| | | pdfOutputStream.close(); |
| | | } catch (Exception e) { |
| | | log.warn("关闭输出流失败", e); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 将Word文档转换为PDF并下载 |
| | | * @param doc Word文档模板 |
| | | * @param response HTTP响应 |
| | | * @param typeName 报告类型名称 |
| | | * @param checkTaskId 检查任务ID |
| | | * @param templateId 模板ID |
| | | * @throws IOException IO异常 |
| | | */ |
| | | private void downloadAsPdf(XWPFTemplate doc, HttpServletResponse response, String typeName, |
| | | Integer checkTaskId, Integer templateId) throws IOException { |
| | | ByteArrayOutputStream docOutputStream = null; |
| | | ByteArrayInputStream docInputStream = null; |
| | | ByteArrayOutputStream pdfOutputStream = null; |
| | | XWPFDocument document = null; |
| | | OutputStream out = null; |
| | | BufferedOutputStream bos = null; |
| | | |
| | | try { |
| | | var checkTask = checkTaskService.get(checkTaskId, true); |
| | | var reportTemplate = reportTemplateService.get(templateId); |
| | | BizResultCode.TASK_NOT_FOUND.check(checkTask == null); |
| | | |
| | | var docName = checkTask.getProject().getName() + "_" + reportTemplate.getName() + "_" + typeName; |
| | | String encodedFileName = URLEncoder.encode(docName, "UTF-8"); |
| | | response.setContentType("application/pdf"); |
| | | response.setHeader("Content-disposition", "attachment;filename=\"" + encodedFileName + ".pdf\""); |
| | | |
| | | // 将Word模板写入字节数组 |
| | | docOutputStream = new ByteArrayOutputStream(); |
| | | doc.write(docOutputStream); |
| | | |
| | | // 从字节数组读取Word文档 |
| | | docInputStream = new ByteArrayInputStream(docOutputStream.toByteArray()); |
| | | document = new XWPFDocument(docInputStream); |
| | | |
| | | // 配置PDF选项 |
| | | PdfOptions options = PdfOptions.create(); |
| | | options.fontEncoding("UTF-8"); |
| | | |
| | | // 先将PDF转换到字节数组 |
| | | pdfOutputStream = new ByteArrayOutputStream(); |
| | | PdfConverter.getInstance().convert(document, pdfOutputStream, options); |
| | | byte[] pdfBytes = pdfOutputStream.toByteArray(); |
| | | |
| | | // 写入响应流供用户下载 |
| | | out = response.getOutputStream(); |
| | | bos = new BufferedOutputStream(out); |
| | | bos.write(pdfBytes); |
| | | bos.flush(); |
| | | out.flush(); |
| | | |
| | | // 保存文件到服务器 |
| | | saveFileToServer(pdfBytes, docName + ".pdf",null); |
| | | |
| | | } catch (Exception e) { |
| | | log.error("Word转PDF失败", e); |
| | | throw new IOException("Word转PDF失败: " + e.getMessage(), e); |
| | | } finally { |
| | | // 关闭资源 |
| | | if (document != null) { |
| | | try { |
| | | document.close(); |
| | | } catch (Exception e) { |
| | | log.warn("关闭Word文档失败", e); |
| | | } |
| | | } |
| | | if (pdfOutputStream != null) { |
| | | try { |
| | | pdfOutputStream.close(); |
| | | } catch (Exception e) { |
| | | log.warn("关闭PDF输出流失败", e); |
| | | } |
| | | } |
| | | if (docInputStream != null) { |
| | | try { |
| | | docInputStream.close(); |
| | | } catch (Exception e) { |
| | | log.warn("关闭输入流失败", e); |
| | | } |
| | | } |
| | | if (docOutputStream != null) { |
| | | try { |
| | | docOutputStream.close(); |
| | | } catch (Exception e) { |
| | | log.warn("关闭输出流失败", e); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 保存文件到服务器 |
| | | * @param fileBytes 文件字节数组 |
| | | * @param fileName 文件名 |
| | | */ |
| | | private String saveFileToServer(byte[] fileBytes, String fileName,String uuid) { |
| | | try { |
| | | // 确保目录存在 |
| | | Path reportDir = Path.of(REPORT_UPLOAD_DIR).toAbsolutePath().normalize(); |
| | | if (!Files.exists(reportDir)) { |
| | | Files.createDirectories(reportDir); |
| | | } |
| | | //文件名随机 |
| | | fileName =uuid+".docx"; |
| | | // 保存文件 |
| | | Path filePath = reportDir.resolve(fileName); |
| | | Files.write(filePath, fileBytes, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); |
| | | |
| | | log.info("文件已保存到服务器: {}", filePath.toString()); |
| | | return fileName; |
| | | } catch (Exception e) { |
| | | log.error("保存文件到服务器失败: {}", fileName, e); |
| | | // 不抛出异常,避免影响用户下载 |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | /** |
| | | * 将Word文档转换为图片并保存到服务器 |
| | | * @param docBytes Word文档字节数组 |
| | | * @param uuid 文件唯一标识 |
| | | */ |
| | | private void convertWordToImagesAndSave(byte[] docBytes, String uuid) { |
| | | ByteArrayInputStream docInputStream = null; |
| | | XWPFDocument document = null; |
| | | ByteArrayOutputStream pdfOutputStream = null; |
| | | PDDocument pdfDocument = null; |
| | | |
| | | try { |
| | | // 1. 将Word文档转换为PDF |
| | | docInputStream = new ByteArrayInputStream(docBytes); |
| | | document = new XWPFDocument(docInputStream); |
| | | |
| | | pdfOutputStream = new ByteArrayOutputStream(); |
| | | PdfOptions options = PdfOptions.create(); |
| | | options.fontEncoding("UTF-8"); |
| | | PdfConverter.getInstance().convert(document, pdfOutputStream, options); |
| | | |
| | | byte[] pdfBytes = pdfOutputStream.toByteArray(); |
| | | |
| | | // 2. 将PDF转换为图片 |
| | | ByteArrayInputStream pdfInputStream = new ByteArrayInputStream(pdfBytes); |
| | | pdfDocument = PDDocument.load(pdfInputStream); |
| | | PDFRenderer pdfRenderer = new PDFRenderer(pdfDocument); |
| | | |
| | | // 确保图片目录存在 |
| | | Path imageDir = Path.of(REPORT_UPLOAD_DIR, "images").toAbsolutePath().normalize(); |
| | | if (!Files.exists(imageDir)) { |
| | | Files.createDirectories(imageDir); |
| | | } |
| | | |
| | | // 转换每一页为图片 |
| | | int pageCount = pdfDocument.getNumberOfPages(); |
| | | log.info("开始转换Word为图片,共{}页", pageCount); |
| | | |
| | | for (int i = 0; i < pageCount; i++) { |
| | | // 渲染PDF页面为图片,DPI设置为300以获得高质量图片 |
| | | BufferedImage image = pdfRenderer.renderImageWithDPI(i, 300, ImageType.RGB); |
| | | |
| | | // 保存图片 |
| | | String imageFileName = uuid + "_page_" + (i + 1) + ".png"; |
| | | Path imagePath = imageDir.resolve(imageFileName); |
| | | ImageIO.write(image, "PNG", imagePath.toFile()); |
| | | |
| | | log.info("页面{}已转换为图片: {}", i + 1, imagePath.toString()); |
| | | } |
| | | |
| | | log.info("Word文档已成功转换为{}张图片", pageCount); |
| | | |
| | | } catch (Exception e) { |
| | | log.error("Word转图片失败: uuid={}", uuid, e); |
| | | // 不抛出异常,避免影响用户下载 |
| | | } finally { |
| | | // 关闭资源 |
| | | if (pdfDocument != null) { |
| | | try { |
| | | pdfDocument.close(); |
| | | } catch (Exception e) { |
| | | log.warn("关闭PDF文档失败", e); |
| | | } |
| | | } |
| | | if (document != null) { |
| | | try { |
| | | document.close(); |
| | | } catch (Exception e) { |
| | | log.warn("关闭Word文档失败", e); |
| | | } |
| | | } |
| | | if (pdfOutputStream != null) { |
| | | try { |
| | | pdfOutputStream.close(); |
| | | } catch (Exception e) { |
| | | log.warn("关闭PDF输出流失败", e); |
| | | } |
| | | } |
| | | if (docInputStream != null) { |
| | | try { |
| | | docInputStream.close(); |
| | | } catch (Exception e) { |
| | | log.warn("关闭文档输入流失败", e); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 从文件名中提取页码 |
| | | * @param fileName 文件名,格式:uuid_page_N.png |
| | | * @return 页码 |
| | | */ |
| | | private int extractPageNumber(String fileName) { |
| | | try { |
| | | // 文件名格式:uuid_page_N.png |
| | | String[] parts = fileName.replace(".png", "").split("_page_"); |
| | | if (parts.length == 2) { |
| | | return Integer.parseInt(parts[1]); |
| | | } |
| | | } catch (Exception e) { |
| | | log.warn("解析页码失败: {}", fileName, e); |
| | | } |
| | | return 0; |
| | | } |
| | | |
| | | /** |
| | | * 通过Word文档URL转换为PDF并下载 |
| | | * @param wordUrl Word文档的URL地址 |
| | | * @param fileName 文件名(可选) |
| | | * @param response HTTP响应 |
| | | * @throws IOException IO异常 |
| | | */ |
| | | private void downloadPdfFromWordUrl(String wordUrl, String fileName, HttpServletResponse response) throws IOException { |
| | | InputStream wordInputStream = null; |
| | | XWPFDocument document = null; |
| | | ByteArrayOutputStream pdfOutputStream = null; |
| | | OutputStream out = null; |
| | | BufferedOutputStream bos = null; |
| | | HttpURLConnection connection = null; |
| | | |
| | | try { |
| | | log.info("开始从URL下载Word文档: {}", wordUrl); |
| | | |
| | | // 1. 从URL下载Word文档 |
| | | URL url = new URL(wordUrl); |
| | | connection = (HttpURLConnection) url.openConnection(); |
| | | connection.setRequestMethod("GET"); |
| | | connection.setConnectTimeout(30000); // 30秒连接超时 |
| | | connection.setReadTimeout(60000); // 60秒读取超时 |
| | | connection.setRequestProperty("User-Agent", "Mozilla/5.0"); |
| | | |
| | | int responseCode = connection.getResponseCode(); |
| | | if (responseCode != HttpURLConnection.HTTP_OK) { |
| | | throw new IOException("下载Word文档失败,HTTP状态码: " + responseCode); |
| | | } |
| | | |
| | | wordInputStream = connection.getInputStream(); |
| | | log.info("Word文档下载成功,开始转换为PDF"); |
| | | |
| | | // 2. 读取Word文档 |
| | | document = new XWPFDocument(wordInputStream); |
| | | |
| | | // 3. 转换为PDF |
| | | pdfOutputStream = new ByteArrayOutputStream(); |
| | | PdfOptions options = PdfOptions.create(); |
| | | options.fontEncoding("UTF-8"); |
| | | |
| | | // 确保图表不丢失的关键配置 |
| | | PdfConverter.getInstance().convert(document, pdfOutputStream, options); |
| | | |
| | | byte[] pdfBytes = pdfOutputStream.toByteArray(); |
| | | log.info("PDF转换成功,大小: {} bytes", pdfBytes.length); |
| | | |
| | | // 4. 设置响应头(修复中文乱码) |
| | | String downloadFileName = fileName != null && !fileName.isEmpty() |
| | | ? fileName |
| | | : "report_" + System.currentTimeMillis(); |
| | | |
| | | if (!downloadFileName.toLowerCase().endsWith(".pdf")) { |
| | | downloadFileName += ".pdf"; |
| | | } |
| | | |
| | | // 使用RFC 5987规范处理中文文件名 |
| | | String encodedFileName = URLEncoder.encode(downloadFileName, "UTF-8").replaceAll("\\+", "%20"); |
| | | |
| | | response.setContentType("application/pdf"); |
| | | response.setCharacterEncoding("UTF-8"); |
| | | response.setHeader("Content-Disposition", |
| | | "attachment; filename=\"" + encodedFileName + "\"; filename*=UTF-8''" + encodedFileName); |
| | | response.setContentLength(pdfBytes.length); |
| | | |
| | | // 5. 写入响应流 |
| | | out = response.getOutputStream(); |
| | | bos = new BufferedOutputStream(out); |
| | | bos.write(pdfBytes); |
| | | bos.flush(); |
| | | out.flush(); |
| | | |
| | | log.info("PDF文档下载成功: {}", downloadFileName); |
| | | |
| | | } catch (Exception e) { |
| | | log.error("从Word URL转换PDF失败: url={}", wordUrl, e); |
| | | throw new IOException("从Word URL转换PDF失败: " + e.getMessage(), e); |
| | | } finally { |
| | | // 关闭资源 |
| | | if (document != null) { |
| | | try { |
| | | document.close(); |
| | | } catch (Exception e) { |
| | | log.warn("关闭Word文档失败", e); |
| | | } |
| | | } |
| | | if (pdfOutputStream != null) { |
| | | try { |
| | | pdfOutputStream.close(); |
| | | } catch (Exception e) { |
| | | log.warn("关闭PDF输出流失败", e); |
| | | } |
| | | } |
| | | if (wordInputStream != null) { |
| | | try { |
| | | wordInputStream.close(); |
| | | } catch (Exception e) { |
| | | log.warn("关闭Word输入流失败", e); |
| | | } |
| | | } |
| | | if (connection != null) { |
| | | try { |
| | | connection.disconnect(); |
| | | } catch (Exception e) { |
| | | log.warn("断开HTTP连接失败", e); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 转换状态内部类 |
| | | */ |
| | | public static class ConversionStatus { |
| | | private String uuid; |
| | | private boolean wordFileExists; |
| | | private int imageCount; |
| | | private boolean conversionCompleted; |
| | | |
| | | public String getUuid() { |
| | | return uuid; |
| | | } |
| | | |
| | | bos.flush(); |
| | | out.flush(); |
| | | public void setUuid(String uuid) { |
| | | this.uuid = uuid; |
| | | } |
| | | |
| | | public boolean isWordFileExists() { |
| | | return wordFileExists; |
| | | } |
| | | |
| | | public void setWordFileExists(boolean wordFileExists) { |
| | | this.wordFileExists = wordFileExists; |
| | | } |
| | | |
| | | public int getImageCount() { |
| | | return imageCount; |
| | | } |
| | | |
| | | public void setImageCount(int imageCount) { |
| | | this.imageCount = imageCount; |
| | | } |
| | | |
| | | public boolean isConversionCompleted() { |
| | | return conversionCompleted; |
| | | } |
| | | |
| | | public void setConversionCompleted(boolean conversionCompleted) { |
| | | this.conversionCompleted = conversionCompleted; |
| | | } |
| | | } |
| | | |
| | | } |