/*
|
* 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.utils.GdGeoAddressUtil;
|
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<Map<String, String>> addressFuture = CompletableFuture.supplyAsync(
|
() -> preloadAddresses(results), RESOURCE_EXECUTOR
|
);
|
CompletableFuture.allOf(imageFuture, qrCodeFuture, addressFuture).join();
|
Map<String, byte[]> imageCache = imageFuture.get();
|
Map<String, byte[]> qrCodeCache = qrCodeFuture.get();
|
Map<String, String> addressCache = addressFuture.get();
|
|
createTitle(document, title);
|
createSectionHeader(document, "一、任务详情");
|
addTaskDetails(document, taskEntity, creatorName, deptName);
|
|
int resultCount = results.size();
|
createSectionHeader(document, "二、巡查结果(" + resultCount + "件)");
|
addResultDetails(document, results, imageCache, qrCodeCache, addressCache);
|
|
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,
|
Map<String, String> addressCache)
|
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, "成果地址");
|
String address = getAddressFromCache(result, addressCache);
|
setTableCellWithSpacing(resultTable, currentRow, 2, formatString(address));
|
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 Map<String, String> preloadAddresses(List<GdTaskResultEntity> results) {
|
if (results == null || results.isEmpty()) {
|
return Collections.emptyMap();
|
}
|
List<GdTaskResultEntity> needAddressResults = results.stream()
|
.filter(result -> result.getLongitude() != null && result.getLatitude() != null)
|
.collect(Collectors.toList());
|
if (needAddressResults.isEmpty()) {
|
return Collections.emptyMap();
|
}
|
|
Map<String, String> addressCache = new ConcurrentHashMap<>();
|
List<CompletableFuture<Void>> futures = needAddressResults.stream()
|
.map(result -> CompletableFuture.runAsync(() -> {
|
String key = buildCoordinateKey(result.getLongitude(), result.getLatitude());
|
if (StringUtil.isBlank(key) || addressCache.containsKey(key)) {
|
return;
|
}
|
String address = GdGeoAddressUtil.getFormattedAddress(result.getLongitude(), result.getLatitude());
|
if (!StringUtil.isBlank(address)) {
|
addressCache.put(key, address);
|
}
|
}, RESOURCE_EXECUTOR))
|
.collect(Collectors.toList());
|
|
try {
|
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
|
} catch (Exception e) {
|
logger.error("等待成果地址获取完成时发生异常", e);
|
}
|
return addressCache;
|
}
|
|
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 String getAddressFromCache(GdTaskResultEntity result, Map<String, String> addressCache) {
|
if (result == null || addressCache == null || addressCache.isEmpty()) {
|
return null;
|
}
|
String key = buildCoordinateKey(result.getLongitude(), result.getLatitude());
|
if (StringUtil.isBlank(key)) {
|
return null;
|
}
|
return addressCache.get(key);
|
}
|
|
private static String buildCoordinateKey(Double longitude, Double latitude) {
|
if (longitude == null || latitude == null) {
|
return "";
|
}
|
return formatCoordinateValue(longitude) + "," + formatCoordinateValue(latitude);
|
}
|
|
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);
|
}
|
}
|
}
|