/admin/stat/report/fuzzyReportPdfNewfuzzyReportPdfNew()downloadAsPdf()fr.opensagres.poi.xwpf.converter.pdf.PdfConverterfr.opensagres.poi.xwpf.converter.pdf.PdfOptionsorg.apache.poi.xwpf.usermodel.XWPFDocumentjava.io.ByteArrayInputStreamjava.io.ByteArrayOutputStream新接口使用说明.md - 详细使用文档新接口快速参考.md - 快速参考卡片测试新接口.bat - Windows测试脚本测试新接口.sh - Linux/Mac测试脚本文件: ReportController.java
新增代码: 92 行
修改类型: 新增接口和方法
影响范围: Controller层
@ApiOperation("模糊评价报告PDF下载(新接口)")
@GetMapping("/fuzzyReportPdfNew")
@IgnoreAuth
public void fuzzyReportPdfNew(
@ApiParam("检查任务ID") @RequestParam(value = "checkTaskId") Integer checkTaskId,
@ApiParam("报告模版ID") @RequestParam(value = "templateId") Integer templateId,
HttpServletResponse response) throws IOException {
try (var doc = reportService.fuzzyReport(checkTaskId, templateId)) {
downloadAsPdf(doc, response, "模糊评价", checkTaskId, templateId);
}
}
特点:
- ✅ 复用现有的 fuzzyReport() 方法获取 Word 文档
- ✅ 使用 try-with-resources 自动关闭 Word 文档
- ✅ 在 Controller 层完成 PDF 转换
- ✅ 无需认证(@IgnoreAuth)
private void downloadAsPdf(XWPFTemplate doc, HttpServletResponse response,
String typeName, Integer checkTaskId, Integer templateId)
throws IOException {
ByteArrayOutputStream docOutputStream = null;
ByteArrayInputStream docInputStream = null;
XWPFDocument document = null;
OutputStream out = null;
BufferedOutputStream bos = null;
try {
// 1. 获取任务和模板信息
var checkTask = checkTaskService.get(checkTaskId, true);
var reportTemplate = reportTemplateService.get(templateId);
BizResultCode.TASK_NOT_FOUND.check(checkTask == null);
// 2. 设置响应头
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\"");
// 3. Word → 字节数组
docOutputStream = new ByteArrayOutputStream();
doc.write(docOutputStream);
// 4. 字节数组 → XWPFDocument
docInputStream = new ByteArrayInputStream(docOutputStream.toByteArray());
document = new XWPFDocument(docInputStream);
// 5. 配置PDF选项
PdfOptions options = PdfOptions.create();
options.fontEncoding("UTF-8");
// 6. 转换为PDF
out = response.getOutputStream();
bos = new BufferedOutputStream(out);
PdfConverter.getInstance().convert(document, bos, options);
bos.flush();
out.flush();
} catch (Exception e) {
log.error("Word转PDF失败", e);
throw new IOException("Word转PDF失败: " + e.getMessage(), e);
} finally {
// 7. 完善的资源清理
if (document != null) {
try { document.close(); }
catch (Exception e) { log.warn("关闭Word文档失败", 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); }
}
}
}
特点:
- ✅ 完善的异常处理
- ✅ 详细的错误日志
- ✅ 完整的资源清理(finally块)
- ✅ UTF-8编码支持
- ✅ 中文文件名支持
| 对比项 | 旧接口 /fuzzyReportPdf |
新接口 /fuzzyReportPdfNew |
|---|---|---|
| 实现位置 | Service层 | Controller层 |
| Word生成 | Service内部生成 | 复用 fuzzyReport() |
| 代码复用 | 低 | 高 |
| 转换方式 | 内存流 | 内存流 |
| 资源管理 | 简单 | 完善(try-finally) |
| 错误处理 | 基础 | 详细日志 |
| 可维护性 | 中 | 高 |
| 扩展性 | 低 | 高 |
直接访问: http://localhost:8080/admin/stat/report/fuzzyReportPdfNew?checkTaskId=1262&templateId=1
# 下载PDF
curl -o "模糊评价报告_新接口.pdf" \
"http://localhost:8080/admin/stat/report/fuzzyReportPdfNew?checkTaskId=1262&templateId=1"
# 检查文件
ls -lh 模糊评价报告_新接口.pdf
# 打开PDF
start 模糊评价报告_新接口.pdf # Windows
open 模糊评价报告_新接口.pdf # Mac
xdg-open 模糊评价报告_新接口.pdf # Linux
Windows:cmd 测试新接口.bat
Linux/Mac:bash chmod +x 测试新接口.sh ./测试新接口.sh
测试脚本会自动:
1. 下载 Word 版本
2. 下载旧接口 PDF
3. 下载新接口 PDF
4. 显示文件大小对比
5. 打开文件夹供手动检查
下载PDF后,请逐项检查:
admin-server/src/main/java/com/estate/expert/admin/controller/stat/ReportController.java
新接口使用说明.md - 详细使用文档(约200行)
新接口快速参考.md - 快速参考卡片(约150行)
测试新接口.bat - Windows测试脚本
测试新接口.sh - Linux/Mac测试脚本
cd admin-server
mvn clean compile
注意: 需要 Java 17+ 环境
mvn spring-boot:run
或者如果已经打包:bash java -jar target/admin-server-1.0-SNAPSHOT.jar
# Windows
测试新接口.bat
# Linux/Mac
chmod +x 测试新接口.sh
./测试新接口.sh
打开生成的PDF文件,重点检查:
- ✅ 图片是否显示
- ✅ 内容是否完整
- ✅ 格式是否正确
原因: fr.opensagres.poi.xwpf.converter.pdf 库在处理内存流中的图片时存在限制
解决方案:
- 如果图片仍然不显示,可以尝试使用临时文件方案
- 参考之前的修改方案(使用 Files.createTempFile())
当前实现: 使用内存流,适合小文件(<10MB)
优化建议:
- 大文件使用临时文件
- 考虑异步处理
- 添加缓存机制
当前实现: 每个请求独立处理,支持并发
注意事项:
- 内存流方式在高并发下可能占用较多内存
- 建议监控内存使用情况
错误信息: 无效的目标发行版: 17
解决方案:
```bash
java -version
### 问题2: PDF无法打开
**检查步骤**:
ls -lh *.pdf
tail -f logs/application.log | grep -i "word转pdf"
grep -i "exception|error" logs/application.log | tail -20
```
排查步骤:
1. 先下载Word版本,确认Word中有图片
2. 检查图片格式(应为JPG/PNG)
3. 对比旧接口PDF是否有图片
4. 查看日志中是否有图片加载错误
如果仍无图片:
考虑使用临时文件方案(需要修改 downloadAsPdf 方法)
检查:
- 确认 options.fontEncoding("UTF-8") 已设置
- 检查服务器是否安装中文字体
- 查看响应头中的编码设置
新接口使用说明.md - 详细使用说明新接口快速参考.md - 快速参考卡片private void downloadAsPdf(...) throws IOException {
Path tempDocxFile = null;
try {
// 创建临时文件
tempDocxFile = Files.createTempFile("report_", ".docx");
// 写入临时文件
try (FileOutputStream out = new FileOutputStream(tempDocxFile.toFile())) {
doc.write(out);
out.flush();
}
// 从文件读取并转换
try (FileInputStream in = new FileInputStream(tempDocxFile.toFile())) {
document = new XWPFDocument(in);
// 转换为PDF
PdfConverter.getInstance().convert(document, bos, options);
}
} finally {
// 删除临时文件
if (tempDocxFile != null) {
Files.deleteIfExists(tempDocxFile);
}
}
}
@Cacheable(value = "pdfReports",
key = "#checkTaskId + '_' + #templateId",
unless = "#result == null")
public byte[] getCachedPdf(Integer checkTaskId, Integer templateId) {
// 生成PDF并缓存
}
@Async
public CompletableFuture<byte[]> generatePdfAsync(
Integer checkTaskId, Integer templateId) {
// 异步生成PDF
return CompletableFuture.completedFuture(pdfBytes);
}
| 指标 | 预期值 | 说明 |
|---|---|---|
| 响应时间 | 2-5秒 | 取决于报告大小和图片数量 |
| 内存占用 | 10-50MB | 每个请求的峰值内存 |
| 并发能力 | 10-20 QPS | 取决于服务器配置 |
| 文件大小 | 1-10MB | 取决于内容和图片 |
监控内存使用
bash # 使用 jstat 监控 jstat -gc <pid> 1000
调整JVM参数
bash java -Xmx2g -Xms512m -jar admin-server.jar
添加性能日志
java long startTime = System.currentTimeMillis(); // 转换逻辑 long endTime = System.currentTimeMillis(); log.info("PDF转换耗时: {}ms", endTime - startTime);
/fuzzyReportPdfNew 接口downloadAsPdf() 转换方法实现完成时间: 2026-01-24
实现人: Claude Code
版本: v1.0
状态: ✅ 代码已完成,等待测试验证