package com.estate.expert.admin.controller.stat;
|
|
import com.deepoove.poi.XWPFTemplate;
|
import com.estate.expert.admin.enums.BizResultCode;
|
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.*;
|
|
import javax.annotation.Resource;
|
import javax.imageio.ImageIO;
|
import javax.servlet.http.HttpServletResponse;
|
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
|
@CrossOrigin
|
@Controller
|
@RequestMapping("/admin/stat/report")
|
@Validated
|
public class ReportController {
|
|
@Resource
|
private ReportService reportService;
|
|
@Resource
|
private CheckTaskService checkTaskService;
|
|
@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,
|
@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,uuid);
|
}
|
}
|
|
@ApiOperation("打分评价报告下载")
|
@GetMapping("/scoreReport")
|
@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,uuid);
|
}
|
}
|
|
@ApiOperation("模版示例下载")
|
@GetMapping("/example")
|
@IgnoreAuth
|
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,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")
|
@IgnoreAuth
|
public void fuzzyReportPdf(@ApiParam("检查任务ID") @RequestParam(value = "checkTaskId") Integer checkTaskId,
|
@ApiParam("报告模版ID") @RequestParam(value = "templateId") Integer templateId,
|
HttpServletResponse response) throws IOException {
|
downloadPdf(checkTaskId, templateId, response, "模糊评价", true);
|
}
|
|
@ApiOperation("打分评价报告PDF下载")
|
@GetMapping("/scoreReportPdf")
|
@IgnoreAuth
|
public void scoreReportPdf(@ApiParam("检查任务ID") @RequestParam(value = "checkTaskId") Integer checkTaskId,
|
@ApiParam("报告模版ID") @RequestParam(value = "templateId") Integer templateId,
|
HttpServletResponse response) throws IOException {
|
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,String uuid) throws IOException {
|
ByteArrayOutputStream docOutputStream = 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/octet-stream");
|
response.setHeader("Content-disposition", "attachment;filename=\"" + encodedFileName + ".docx\"");
|
|
// 先将文档写入字节数组
|
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 {
|
ByteArrayOutputStream pdfOutputStream = 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\"");
|
|
// 先将PDF写入字节数组
|
pdfOutputStream = new ByteArrayOutputStream();
|
if (isFuzzy) {
|
reportService.fuzzyReportPdf(checkTaskId, templateId, pdfOutputStream);
|
} else {
|
reportService.scoreReportPdf(checkTaskId, templateId, pdfOutputStream);
|
}
|
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("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;
|
}
|
|
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;
|
}
|
}
|
|
}
|