| New file |
| | |
| | | /* |
| | | * Copyright (c) 2018-2028, Chill Zhuang All rights reserved. |
| | | * |
| | | * Redistribution and use in source and binary forms, with or without |
| | | * modification, are permitted provided that the following conditions are met: |
| | | * |
| | | * Redistributions of source code must retain the above copyright notice, |
| | | * this list of conditions and the following disclaimer. |
| | | * Redistributions in binary form must reproduce the above copyright |
| | | * notice, this list of conditions and the following disclaimer in the |
| | | * documentation and/or other materials provided with the distribution. |
| | | * Neither the name of the dreamlu.net developer nor the names of its |
| | | * contributors may be used to endorse or promote products derived from |
| | | * this software without specific prior written permission. |
| | | * Author: Chill 庄骞 (smallchill@163.com) |
| | | */ |
| | | package org.sxkj.gd.workorder.utils; |
| | | |
| | | import org.apache.poi.openxml4j.exceptions.InvalidFormatException; |
| | | import org.apache.poi.util.Units; |
| | | import org.apache.poi.xwpf.usermodel.ParagraphAlignment; |
| | | import org.apache.poi.xwpf.usermodel.XWPFDocument; |
| | | import org.apache.poi.xwpf.usermodel.XWPFParagraph; |
| | | import org.apache.poi.xwpf.usermodel.XWPFRun; |
| | | import org.apache.poi.xwpf.usermodel.XWPFTable; |
| | | import org.apache.poi.xwpf.usermodel.XWPFTableCell; |
| | | import org.apache.poi.xwpf.usermodel.XWPFTableRow; |
| | | import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTbl; |
| | | import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTblGrid; |
| | | import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTblPr; |
| | | import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTblWidth; |
| | | import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTcBorders; |
| | | import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTcPr; |
| | | import org.openxmlformats.schemas.wordprocessingml.x2006.main.STBorder; |
| | | import org.openxmlformats.schemas.wordprocessingml.x2006.main.STMerge; |
| | | import org.openxmlformats.schemas.wordprocessingml.x2006.main.STTblWidth; |
| | | import org.openxmlformats.schemas.wordprocessingml.x2006.main.STTextDirection; |
| | | import org.slf4j.Logger; |
| | | import org.slf4j.LoggerFactory; |
| | | import org.springblade.core.tool.utils.StringUtil; |
| | | import org.sxkj.gd.workorder.entity.GdPatrolTaskEntity; |
| | | import org.sxkj.gd.workorder.entity.GdTaskResultEntity; |
| | | |
| | | import java.io.ByteArrayInputStream; |
| | | import java.io.ByteArrayOutputStream; |
| | | import java.io.File; |
| | | import java.io.FileOutputStream; |
| | | import java.io.IOException; |
| | | import java.io.InputStream; |
| | | import java.math.BigInteger; |
| | | import java.net.HttpURLConnection; |
| | | import java.net.URL; |
| | | import java.time.ZoneId; |
| | | import java.time.format.DateTimeFormatter; |
| | | import java.util.Collections; |
| | | import java.util.Date; |
| | | import java.util.HashSet; |
| | | import java.util.List; |
| | | import java.util.Locale; |
| | | import java.util.Map; |
| | | import java.util.Set; |
| | | import java.util.concurrent.CompletableFuture; |
| | | import java.util.concurrent.ConcurrentHashMap; |
| | | import java.util.concurrent.ExecutionException; |
| | | import java.util.concurrent.ExecutorService; |
| | | import java.util.concurrent.LinkedBlockingQueue; |
| | | import java.util.concurrent.ThreadPoolExecutor; |
| | | import java.util.concurrent.TimeUnit; |
| | | import java.util.stream.Collectors; |
| | | |
| | | public final class GdPatrolReportWordUtil { |
| | | |
| | | private static final Logger logger = LoggerFactory.getLogger(GdPatrolReportWordUtil.class); |
| | | private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm"); |
| | | private static final int CONNECT_TIMEOUT = 3000; |
| | | private static final int READ_TIMEOUT = 8000; |
| | | |
| | | private static final ExecutorService RESOURCE_EXECUTOR = new ThreadPoolExecutor( |
| | | 8, 16, 60L, TimeUnit.SECONDS, |
| | | new LinkedBlockingQueue<>(200), |
| | | new ThreadPoolExecutor.CallerRunsPolicy() |
| | | ); |
| | | |
| | | private GdPatrolReportWordUtil() { |
| | | } |
| | | |
| | | public static File generateReportFile(GdPatrolTaskEntity taskEntity, |
| | | List<GdTaskResultEntity> resultList, |
| | | String creatorName, |
| | | String deptName) |
| | | throws IOException, InvalidFormatException, InterruptedException, ExecutionException { |
| | | String title = StringUtil.isBlank(taskEntity.getPatrolTaskName()) ? "巡查任务" : taskEntity.getPatrolTaskName(); |
| | | List<GdTaskResultEntity> results = resultList == null ? Collections.emptyList() : resultList; |
| | | File reportFile = createReportTempFile(taskEntity); |
| | | |
| | | try (XWPFDocument document = new XWPFDocument(); |
| | | FileOutputStream fos = new FileOutputStream(reportFile)) { |
| | | CompletableFuture<Map<String, byte[]>> imageFuture = CompletableFuture.supplyAsync( |
| | | () -> preloadImages(results), RESOURCE_EXECUTOR |
| | | ); |
| | | CompletableFuture<Map<String, byte[]>> qrCodeFuture = CompletableFuture.supplyAsync( |
| | | () -> preloadQrCodes(results), RESOURCE_EXECUTOR |
| | | ); |
| | | CompletableFuture.allOf(imageFuture, qrCodeFuture).join(); |
| | | Map<String, byte[]> imageCache = imageFuture.get(); |
| | | Map<String, byte[]> qrCodeCache = qrCodeFuture.get(); |
| | | |
| | | createTitle(document, title); |
| | | createSectionHeader(document, "一、任务详情"); |
| | | addTaskDetails(document, taskEntity, creatorName, deptName); |
| | | |
| | | int resultCount = results.size(); |
| | | createSectionHeader(document, "二、巡查结果(" + resultCount + "件)"); |
| | | addResultDetails(document, results, imageCache, qrCodeCache); |
| | | |
| | | document.write(fos); |
| | | return reportFile; |
| | | } |
| | | } |
| | | |
| | | private static File createReportTempFile(GdPatrolTaskEntity taskEntity) throws IOException { |
| | | String taskNo = taskEntity != null ? taskEntity.getTaskNo() : null; |
| | | String safePrefix = buildSafeFilePrefix(taskNo); |
| | | return File.createTempFile(safePrefix, ".docx"); |
| | | } |
| | | |
| | | private static String buildSafeFilePrefix(String text) { |
| | | String baseName = StringUtil.isBlank(text) ? "gd_patrol_report" : "gd_patrol_report_" + text; |
| | | String safeName = baseName.replaceAll("[^a-zA-Z0-9_-]", "_"); |
| | | if (safeName.length() < 3) { |
| | | safeName = "gd_patrol_report"; |
| | | } |
| | | if (safeName.length() > 50) { |
| | | safeName = safeName.substring(0, 50); |
| | | } |
| | | return safeName + "_"; |
| | | } |
| | | |
| | | private static void addTaskDetails(XWPFDocument document, |
| | | GdPatrolTaskEntity taskEntity, |
| | | String creatorName, |
| | | String deptName) { |
| | | XWPFTable table = document.createTable(4, 2); |
| | | table.setWidth("100%"); |
| | | table.getCTTbl().addNewTblGrid().addNewGridCol().setW(BigInteger.valueOf(3000)); |
| | | table.getCTTbl().getTblGrid().addNewGridCol().setW(BigInteger.valueOf(6000)); |
| | | table.getCTTbl().addNewTblPr().addNewTblBorders(); |
| | | |
| | | setTableCellWithGrayBackground(table, 0, 0, "任务编号"); |
| | | setTableCellWithSpacing(table, 0, 1, formatString(taskEntity.getTaskNo())); |
| | | |
| | | setTableCellWithGrayBackground(table, 1, 0, "创建人"); |
| | | setTableCellWithSpacing(table, 1, 1, formatString(creatorName)); |
| | | |
| | | setTableCellWithGrayBackground(table, 2, 0, "所属部门"); |
| | | setTableCellWithSpacing(table, 2, 1, formatString(deptName)); |
| | | |
| | | setTableCellWithGrayBackground(table, 3, 0, "任务时间"); |
| | | Date taskTime = taskEntity.getExecuteTime() != null ? taskEntity.getExecuteTime() : taskEntity.getCreateTime(); |
| | | setTableCellWithSpacing(table, 3, 1, formatDate(taskTime)); |
| | | } |
| | | |
| | | private static void addResultDetails(XWPFDocument document, |
| | | List<GdTaskResultEntity> results, |
| | | Map<String, byte[]> imageCache, |
| | | Map<String, byte[]> qrCodeCache) |
| | | throws IOException, InvalidFormatException { |
| | | if (results == null || results.isEmpty()) { |
| | | addDetailItem(document, "/"); |
| | | return; |
| | | } |
| | | |
| | | int totalRows = results.size() * 4 + (results.size() - 1); |
| | | XWPFTable resultTable = document.createTable(totalRows, 3); |
| | | |
| | | CTTbl ctTbl = resultTable.getCTTbl(); |
| | | CTTblGrid tblGrid = ctTbl.getTblGrid(); |
| | | if (tblGrid == null) { |
| | | tblGrid = ctTbl.addNewTblGrid(); |
| | | } |
| | | while (tblGrid.sizeOfGridColArray() < 3) { |
| | | tblGrid.addNewGridCol(); |
| | | } |
| | | tblGrid.getGridColArray(0).setW(BigInteger.valueOf(600)); |
| | | tblGrid.getGridColArray(1).setW(BigInteger.valueOf(1500)); |
| | | tblGrid.getGridColArray(2).setW(BigInteger.valueOf(3900)); |
| | | |
| | | XWPFTableRow firstRow = resultTable.getRow(0); |
| | | firstRow.getCell(0).setWidth("600"); |
| | | firstRow.getCell(1).setWidth("1500"); |
| | | firstRow.getCell(2).setWidth("3900"); |
| | | |
| | | CTTblPr tblPr = ctTbl.getTblPr(); |
| | | if (tblPr == null) { |
| | | tblPr = ctTbl.addNewTblPr(); |
| | | } |
| | | CTTblWidth tblWidth = tblPr.isSetTblW() ? tblPr.getTblW() : tblPr.addNewTblW(); |
| | | tblWidth.setW(BigInteger.valueOf(6000)); |
| | | tblWidth.setType(STTblWidth.DXA); |
| | | |
| | | int currentRow = 0; |
| | | for (int i = 0; i < results.size(); i++) { |
| | | GdTaskResultEntity result = results.get(i); |
| | | String resultPrefix = String.format("%02d", i + 1); |
| | | |
| | | setTableCellWithSequence(resultTable, currentRow, 0, resultPrefix, 4); |
| | | setTableCellWithBoldAndCenter(resultTable, currentRow, 1, "成果编号"); |
| | | setTableCellWithSpacing(resultTable, currentRow, 2, formatString(result.getResultCode())); |
| | | currentRow++; |
| | | |
| | | setTableCellWithBoldAndCenter(resultTable, currentRow, 1, "成果地址"); |
| | | setTableCellWithSpacing(resultTable, currentRow, 2, "/"); |
| | | currentRow++; |
| | | |
| | | setTableCellWithBoldAndCenter(resultTable, currentRow, 1, "成果定位"); |
| | | XWPFTableCell navCell = resultTable.getRow(currentRow).getCell(2); |
| | | addLocationInfoToCell(navCell, result, qrCodeCache); |
| | | currentRow++; |
| | | |
| | | setTableCellWithBoldAndCenter(resultTable, currentRow, 1, "成果图片"); |
| | | XWPFTableCell imageCell = resultTable.getRow(currentRow).getCell(2); |
| | | addImageToCell(imageCell, result.getResultUrl(), imageCache); |
| | | currentRow++; |
| | | |
| | | if (i < results.size() - 1) { |
| | | createUnifiedSeparatorRow(resultTable, currentRow); |
| | | currentRow++; |
| | | } |
| | | } |
| | | } |
| | | |
| | | private static void createUnifiedSeparatorRow(XWPFTable table, int row) { |
| | | XWPFTableCell cell0 = table.getRow(row).getCell(0); |
| | | XWPFTableCell cell1 = table.getRow(row).getCell(1); |
| | | XWPFTableCell cell2 = table.getRow(row).getCell(2); |
| | | |
| | | clearCellParagraphs(cell0); |
| | | clearCellParagraphs(cell1); |
| | | clearCellParagraphs(cell2); |
| | | |
| | | XWPFParagraph para0 = cell0.addParagraph(); |
| | | XWPFParagraph para1 = cell1.addParagraph(); |
| | | XWPFParagraph para2 = cell2.addParagraph(); |
| | | |
| | | para0.setSpacingBefore(80); |
| | | para0.setSpacingAfter(80); |
| | | para1.setSpacingBefore(80); |
| | | para1.setSpacingAfter(80); |
| | | para2.setSpacingBefore(80); |
| | | para2.setSpacingAfter(80); |
| | | |
| | | cell0.setColor("C0C0C0"); |
| | | cell1.setColor("C0C0C0"); |
| | | cell2.setColor("C0C0C0"); |
| | | |
| | | setCellBorders(cell0, true, false, true, true); |
| | | setCellBorders(cell1, false, false, true, true); |
| | | setCellBorders(cell2, false, true, true, true); |
| | | |
| | | para0.createRun().setText(""); |
| | | para1.createRun().setText(""); |
| | | para2.createRun().setText(""); |
| | | } |
| | | |
| | | private static void setCellBorders(XWPFTableCell cell, boolean left, boolean right, boolean top, boolean bottom) { |
| | | CTTcPr tcPr = cell.getCTTc().getTcPr(); |
| | | if (tcPr == null) { |
| | | tcPr = cell.getCTTc().addNewTcPr(); |
| | | } |
| | | CTTcBorders borders = tcPr.isSetTcBorders() ? tcPr.getTcBorders() : tcPr.addNewTcBorders(); |
| | | |
| | | if (borders.getLeft() == null) { |
| | | borders.addNewLeft(); |
| | | } |
| | | borders.getLeft().setVal(left ? STBorder.SINGLE : STBorder.NIL); |
| | | |
| | | if (borders.getRight() == null) { |
| | | borders.addNewRight(); |
| | | } |
| | | borders.getRight().setVal(right ? STBorder.SINGLE : STBorder.NIL); |
| | | |
| | | if (borders.getTop() == null) { |
| | | borders.addNewTop(); |
| | | } |
| | | borders.getTop().setVal(top ? STBorder.SINGLE : STBorder.NIL); |
| | | |
| | | if (borders.getBottom() == null) { |
| | | borders.addNewBottom(); |
| | | } |
| | | borders.getBottom().setVal(bottom ? STBorder.SINGLE : STBorder.NIL); |
| | | } |
| | | |
| | | private static void setTableCellWithSequence(XWPFTable table, int row, int col, String text, int rowSpan) { |
| | | XWPFTableCell cell = table.getRow(row).getCell(col); |
| | | clearCellParagraphs(cell); |
| | | XWPFParagraph para = cell.addParagraph(); |
| | | para.setAlignment(ParagraphAlignment.CENTER); |
| | | cell.setVerticalAlignment(XWPFTableCell.XWPFVertAlign.CENTER); |
| | | para.setWordWrapped(false); |
| | | |
| | | if (cell.getCTTc().getTcPr() == null) { |
| | | cell.getCTTc().addNewTcPr(); |
| | | } |
| | | cell.getCTTc().getTcPr().addNewNoWrap(); |
| | | CTTcPr tcPr = cell.getCTTc().getTcPr(); |
| | | if (tcPr.getTextDirection() == null) { |
| | | tcPr.addNewTextDirection(); |
| | | } |
| | | tcPr.getTextDirection().setVal(STTextDirection.LR_TB); |
| | | |
| | | XWPFRun run = para.createRun(); |
| | | run.setText(text); |
| | | run.setBold(true); |
| | | |
| | | if (rowSpan > 1) { |
| | | CTTcPr mergeTcPr = cell.getCTTc().getTcPr(); |
| | | if (mergeTcPr == null) { |
| | | mergeTcPr = cell.getCTTc().addNewTcPr(); |
| | | } |
| | | mergeTcPr.addNewVMerge().setVal(STMerge.RESTART); |
| | | for (int i = 1; i < rowSpan; i++) { |
| | | XWPFTableCell mergeCell = table.getRow(row + i).getCell(col); |
| | | CTTcPr mergeCellPr = mergeCell.getCTTc().getTcPr(); |
| | | if (mergeCellPr == null) { |
| | | mergeCellPr = mergeCell.getCTTc().addNewTcPr(); |
| | | } |
| | | mergeCellPr.addNewVMerge().setVal(STMerge.CONTINUE); |
| | | mergeCell.setVerticalAlignment(XWPFTableCell.XWPFVertAlign.CENTER); |
| | | if (!mergeCell.getParagraphs().isEmpty()) { |
| | | mergeCell.getParagraphs().get(0).setWordWrapped(false); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | private static void setTableCellWithBoldAndCenter(XWPFTable table, int row, int col, String text) { |
| | | XWPFTableCell cell = table.getRow(row).getCell(col); |
| | | clearCellParagraphs(cell); |
| | | XWPFParagraph para = cell.addParagraph(); |
| | | para.setAlignment(ParagraphAlignment.CENTER); |
| | | cell.setVerticalAlignment(XWPFTableCell.XWPFVertAlign.CENTER); |
| | | para.setWordWrapped(false); |
| | | |
| | | if (cell.getCTTc().getTcPr() == null) { |
| | | cell.getCTTc().addNewTcPr(); |
| | | } |
| | | cell.getCTTc().getTcPr().addNewNoWrap(); |
| | | |
| | | XWPFRun run = para.createRun(); |
| | | run.setText(formatString(text)); |
| | | run.setBold(true); |
| | | } |
| | | |
| | | private static void setTableCellWithGrayBackground(XWPFTable table, int row, int col, String text) { |
| | | XWPFTableCell cell = table.getRow(row).getCell(col); |
| | | clearCellParagraphs(cell); |
| | | XWPFParagraph para = cell.addParagraph(); |
| | | para.setAlignment(ParagraphAlignment.CENTER); |
| | | cell.setVerticalAlignment(XWPFTableCell.XWPFVertAlign.CENTER); |
| | | para.setWordWrapped(false); |
| | | |
| | | if (cell.getCTTc().getTcPr() == null) { |
| | | cell.getCTTc().addNewTcPr(); |
| | | } |
| | | cell.getCTTc().getTcPr().addNewNoWrap(); |
| | | |
| | | XWPFRun run = para.createRun(); |
| | | run.setText(formatString(text)); |
| | | cell.setColor("D0D0D0"); |
| | | } |
| | | |
| | | private static void setTableCellWithSpacing(XWPFTable table, int row, int col, String text) { |
| | | XWPFTableCell cell = table.getRow(row).getCell(col); |
| | | clearCellParagraphs(cell); |
| | | XWPFParagraph para = cell.addParagraph(); |
| | | cell.setVerticalAlignment(XWPFTableCell.XWPFVertAlign.CENTER); |
| | | para.setWordWrapped(false); |
| | | |
| | | if (cell.getCTTc().getTcPr() == null) { |
| | | cell.getCTTc().addNewTcPr(); |
| | | } |
| | | cell.getCTTc().getTcPr().addNewNoWrap(); |
| | | |
| | | XWPFRun run = para.createRun(); |
| | | String formattedText = (text != null && !"/".equals(text)) ? " " + text : formatString(text); |
| | | run.setText(formattedText); |
| | | } |
| | | |
| | | private static void addLocationInfoToCell(XWPFTableCell cell, |
| | | GdTaskResultEntity result, |
| | | Map<String, byte[]> qrCodeCache) |
| | | throws IOException, InvalidFormatException { |
| | | clearCellParagraphs(cell); |
| | | XWPFParagraph para = cell.addParagraph(); |
| | | cell.setVerticalAlignment(XWPFTableCell.XWPFVertAlign.CENTER); |
| | | para.setWordWrapped(false); |
| | | para.setAlignment(ParagraphAlignment.LEFT); |
| | | |
| | | if (result.getLongitude() != null && result.getLatitude() != null) { |
| | | String longitude = formatCoordinateValue(result.getLongitude()); |
| | | String latitude = formatCoordinateValue(result.getLatitude()); |
| | | if (StringUtil.isBlank(longitude) || StringUtil.isBlank(latitude)) { |
| | | para.setAlignment(ParagraphAlignment.CENTER); |
| | | para.createRun().setText("/"); |
| | | return; |
| | | } |
| | | |
| | | XWPFRun coordinateRun = para.createRun(); |
| | | coordinateRun.setText(longitude + "," + latitude + " "); |
| | | coordinateRun.setFontSize(10); |
| | | |
| | | String qrKey = longitude + "," + latitude; |
| | | byte[] qrCodeData = qrCodeCache.get(qrKey); |
| | | if (qrCodeData != null && qrCodeData.length > 0) { |
| | | try { |
| | | para.setIndentationLeft(100); |
| | | para.setIndentationRight(100); |
| | | para.setSpacingBefore(50); |
| | | para.setSpacingAfter(50); |
| | | |
| | | XWPFRun qrRun = para.createRun(); |
| | | qrRun.addPicture( |
| | | new ByteArrayInputStream(qrCodeData), |
| | | XWPFDocument.PICTURE_TYPE_PNG, |
| | | "qrcode.png", |
| | | Units.toEMU(80), |
| | | Units.toEMU(80) |
| | | ); |
| | | } catch (Exception e) { |
| | | logger.error("二维码插入失败,成果编号:{}", result.getResultCode(), e); |
| | | XWPFRun errorRun = para.createRun(); |
| | | errorRun.setText("(二维码生成失败)"); |
| | | } |
| | | } else { |
| | | XWPFRun errorRun = para.createRun(); |
| | | errorRun.setText("(二维码生成失败)"); |
| | | } |
| | | } else { |
| | | para.setAlignment(ParagraphAlignment.CENTER); |
| | | para.createRun().setText("/"); |
| | | } |
| | | } |
| | | |
| | | private static void addImageToCell(XWPFTableCell cell, String imageUrl, Map<String, byte[]> imageCache) |
| | | throws IOException, InvalidFormatException { |
| | | clearCellParagraphs(cell); |
| | | XWPFParagraph para = cell.addParagraph(); |
| | | para.setAlignment(ParagraphAlignment.CENTER); |
| | | cell.setVerticalAlignment(XWPFTableCell.XWPFVertAlign.CENTER); |
| | | para.setWordWrapped(false); |
| | | |
| | | XWPFRun run = para.createRun(); |
| | | if (StringUtil.isBlank(imageUrl)) { |
| | | run.setText("(无图片)"); |
| | | return; |
| | | } |
| | | |
| | | byte[] imageData = imageCache.get(imageUrl); |
| | | if (imageData == null || imageData.length == 0) { |
| | | run.setText("(图片加载失败)"); |
| | | return; |
| | | } |
| | | |
| | | try { |
| | | int format = findImageFormat(imageUrl); |
| | | para.setSpacingBefore(150); |
| | | para.setSpacingAfter(100); |
| | | para.setIndentationLeft(50); |
| | | para.setIndentationRight(50); |
| | | |
| | | run.addPicture(new ByteArrayInputStream(imageData), format, "image.tmp", |
| | | Units.toEMU(320), Units.toEMU(180)); |
| | | } catch (Exception e) { |
| | | logger.error("图片插入失败,地址:{}", imageUrl, e); |
| | | run.setText("(图片插入失败)"); |
| | | } |
| | | } |
| | | |
| | | private static Map<String, byte[]> preloadImages(List<GdTaskResultEntity> results) { |
| | | Set<String> uniqueUrls = new HashSet<>(); |
| | | if (results != null) { |
| | | results.stream() |
| | | .map(GdTaskResultEntity::getResultUrl) |
| | | .filter(url -> !StringUtil.isBlank(url)) |
| | | .forEach(uniqueUrls::add); |
| | | } |
| | | if (uniqueUrls.isEmpty()) { |
| | | return Collections.emptyMap(); |
| | | } |
| | | |
| | | Map<String, byte[]> resultMap = new ConcurrentHashMap<>(); |
| | | List<CompletableFuture<Void>> futures = uniqueUrls.stream() |
| | | .map(url -> CompletableFuture.runAsync(() -> { |
| | | try { |
| | | byte[] data = downloadImage(url); |
| | | if (data != null && data.length > 0) { |
| | | resultMap.put(url, data); |
| | | } |
| | | } catch (Exception e) { |
| | | logger.error("图片下载失败,地址:{}", url, e); |
| | | } |
| | | }, RESOURCE_EXECUTOR)) |
| | | .collect(Collectors.toList()); |
| | | |
| | | try { |
| | | CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join(); |
| | | } catch (Exception e) { |
| | | logger.error("等待图片下载完成时发生异常", e); |
| | | } |
| | | return resultMap; |
| | | } |
| | | |
| | | private static Map<String, byte[]> preloadQrCodes(List<GdTaskResultEntity> results) { |
| | | if (results == null || results.isEmpty()) { |
| | | return Collections.emptyMap(); |
| | | } |
| | | |
| | | List<GdTaskResultEntity> needQrCodeResults = results.stream() |
| | | .filter(result -> result.getLongitude() != null && result.getLatitude() != null) |
| | | .collect(Collectors.toList()); |
| | | if (needQrCodeResults.isEmpty()) { |
| | | return Collections.emptyMap(); |
| | | } |
| | | |
| | | Map<String, byte[]> qrCodeCache = new ConcurrentHashMap<>(); |
| | | List<CompletableFuture<Void>> futures = needQrCodeResults.stream() |
| | | .map(result -> CompletableFuture.runAsync(() -> { |
| | | String longitude = formatCoordinateValue(result.getLongitude()); |
| | | String latitude = formatCoordinateValue(result.getLatitude()); |
| | | if (StringUtil.isBlank(longitude) || StringUtil.isBlank(latitude)) { |
| | | return; |
| | | } |
| | | String destination = StringUtil.isBlank(result.getResultCode()) ? "巡查结果" : result.getResultCode(); |
| | | String navUrl = GdQrCodeUtil.generateAmapNavigationUrl(longitude, latitude, destination); |
| | | if (StringUtil.isBlank(navUrl)) { |
| | | return; |
| | | } |
| | | try { |
| | | byte[] qrCode = GdQrCodeUtil.generateQrCodeImageBytes(navUrl, 100, 100); |
| | | if (qrCode != null && qrCode.length > 0) { |
| | | qrCodeCache.put(longitude + "," + latitude, qrCode); |
| | | } |
| | | } catch (Exception e) { |
| | | logger.error("二维码生成失败,成果编号:{}", result.getResultCode(), e); |
| | | } |
| | | }, RESOURCE_EXECUTOR)) |
| | | .collect(Collectors.toList()); |
| | | |
| | | try { |
| | | CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join(); |
| | | } catch (Exception e) { |
| | | logger.error("等待二维码生成完成时发生异常", e); |
| | | } |
| | | return qrCodeCache; |
| | | } |
| | | |
| | | private static byte[] downloadImage(String urlString) throws IOException { |
| | | URL url = new URL(urlString); |
| | | HttpURLConnection conn = (HttpURLConnection) url.openConnection(); |
| | | try { |
| | | conn.setConnectTimeout(CONNECT_TIMEOUT); |
| | | conn.setReadTimeout(READ_TIMEOUT); |
| | | conn.setRequestMethod("GET"); |
| | | conn.setRequestProperty("User-Agent", "Mozilla/5.0"); |
| | | conn.setInstanceFollowRedirects(false); |
| | | |
| | | int responseCode = conn.getResponseCode(); |
| | | if (responseCode >= 300 && responseCode < 400) { |
| | | String newUrl = conn.getHeaderField("Location"); |
| | | if (!StringUtil.isBlank(newUrl)) { |
| | | conn.disconnect(); |
| | | return downloadImage(newUrl); |
| | | } |
| | | } |
| | | if (responseCode != HttpURLConnection.HTTP_OK) { |
| | | throw new IOException("图片请求失败,状态码:" + responseCode); |
| | | } |
| | | |
| | | try (InputStream is = conn.getInputStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream()) { |
| | | byte[] buffer = new byte[16384]; |
| | | int bytesRead; |
| | | while ((bytesRead = is.read(buffer)) != -1) { |
| | | baos.write(buffer, 0, bytesRead); |
| | | } |
| | | return baos.toByteArray(); |
| | | } |
| | | } finally { |
| | | conn.disconnect(); |
| | | } |
| | | } |
| | | |
| | | private static void createTitle(XWPFDocument document, String text) { |
| | | XWPFParagraph p = document.createParagraph(); |
| | | p.setAlignment(ParagraphAlignment.CENTER); |
| | | XWPFRun run = p.createRun(); |
| | | run.setText(text); |
| | | run.setBold(true); |
| | | run.setFontSize(20); |
| | | } |
| | | |
| | | private static void createSectionHeader(XWPFDocument document, String text) { |
| | | XWPFParagraph p = document.createParagraph(); |
| | | XWPFRun run = p.createRun(); |
| | | run.setText(text); |
| | | run.setBold(true); |
| | | run.setFontSize(16); |
| | | } |
| | | |
| | | private static void addDetailItem(XWPFDocument document, String text) { |
| | | document.createParagraph().createRun().setText(text); |
| | | } |
| | | |
| | | private static String formatString(String text) { |
| | | return !StringUtil.isBlank(text) ? text : "/"; |
| | | } |
| | | |
| | | private static String formatDate(Date date) { |
| | | if (date == null) { |
| | | return "/"; |
| | | } |
| | | return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime().format(DATE_TIME_FORMATTER); |
| | | } |
| | | |
| | | private static String formatCoordinateValue(Double coordinate) { |
| | | if (coordinate == null) { |
| | | return ""; |
| | | } |
| | | return String.format(Locale.CHINA, "%.6f", coordinate); |
| | | } |
| | | |
| | | private static int findImageFormat(String url) { |
| | | String cleanUrl = url; |
| | | int queryIndex = url.indexOf("?"); |
| | | if (queryIndex > -1) { |
| | | cleanUrl = url.substring(0, queryIndex); |
| | | } |
| | | String lowerUrl = cleanUrl.toLowerCase(Locale.ROOT); |
| | | if (lowerUrl.endsWith(".jpeg") || lowerUrl.endsWith(".jpg")) { |
| | | return XWPFDocument.PICTURE_TYPE_JPEG; |
| | | } |
| | | if (lowerUrl.endsWith(".png")) { |
| | | return XWPFDocument.PICTURE_TYPE_PNG; |
| | | } |
| | | if (lowerUrl.endsWith(".gif")) { |
| | | return XWPFDocument.PICTURE_TYPE_GIF; |
| | | } |
| | | return XWPFDocument.PICTURE_TYPE_JPEG; |
| | | } |
| | | |
| | | private static void clearCellParagraphs(XWPFTableCell cell) { |
| | | int paragraphCount = cell.getParagraphs().size(); |
| | | for (int i = paragraphCount - 1; i >= 0; i--) { |
| | | cell.removeParagraph(i); |
| | | } |
| | | } |
| | | } |