最近项目用到了PDF导出,主要是后端完成,主要是个人不会设计PDF模板,效果太丑了,就想到了用Excel转PDF,且不用再设置样式,在模板中设置好了就可以直接套用了,记录下简单思路:
- 用excel设计好模板,因为涉及到多个表格需要动态插入数据,所以需要注意表格行列的计算。
- 将excel转为PDF文档。
- 若是存在多个PDF,则进行ZIP打包再提供下载,基于spring boot提供下载。
首先是excel模板样式预览
PDF预览
下载预览
首先引入依赖
<!-- https://mvnrepository.com/artifact/com.itextpdf/itextpdf -->
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.5.13.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.itextpdf/itext-asian -->
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itext-asian</artifactId>
<version>5.2.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.poi/poi -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>5.0.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.poi/poi-ooxml -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.0.0</version>
</dependency>
服务层代码
/**
* PDF导出
*
* @param form 表单
* @param response 响应
* @throws DocumentException 异常
* @throws IOException 异常
*/
public void pdfExport(BaseForm form, HttpServletResponse response) throws DocumentException, IOException {
//将zip以流的形式输出到前台
response.setContentType("application/octet-stream");
String keyword = form.getKeyword();
ApiRegisterVo vo = null;
if (StringUtils.isNotBlank(keyword)) {
vo = JSON.parseObject(keyword, ApiRegisterVo.class);
}
// 数据列表
List<ApiRegisterVo> vos = findList(vo);
if (ObjectUtils.isNotEmpty(vos)) { // 不为空才导出
if (vos.size() == 1) { // 单文件导出
ApiRegisterVo temp = vos.get(0);
ApiRegisterDetailVo detailVo = detail(temp.getId(), vo.getGatewayAddress());
response.setHeader("Content-Disposition", "attachment;filename="
+ URLEncoder.encode(detailVo.getName() + System.currentTimeMillis(), "UTF-8") + ".pdf");
Workbook workbook = excelService.openGatewayApiWorkbook(detailVo);
byte[] bytes = ExcelToPdfUtil.excelToPdf(workbook, false, 0.8f);
response.getOutputStream().write(bytes);
} else { // 批量压缩导出
// pdf数据包列表
List<Map<String, byte[]>> dataPackage = new ArrayList<>();
// 单个数据,key为文件名
Map<String, byte[]> map;
// 文件设置,附件形式打开
response.setHeader("content-disposition", "attachment; filename="
+ URLEncoder.code("GatewayInfo" + System.currentTimeMillis(), "UTF-8") + ".zip");
for (ApiRegisterVo registerVo : vos) {
ApiRegisterDetailVo detailVo = detail(registerVo.getId(), vo.getGatewayAddress());
Workbook workbook = excelService.openGatewayApiWorkbook(detailVo);
byte[] bytes = ExcelToPdfUtil.excelToPdf(workbook, false, 0.8f);
map = new HashMap<>();
map.put(detailVo.getName() + System.currentTimeMillis() + ".pdf", bytes);
dataPackage.add(map);
}
byte[] zipByte = ZipUtil.batchZipByte(dataPackage);
response.getOutputStream().write(zipByte);
}
response.flushBuffer();
}
}
excel导出方法
/**
* 根据模板导出excel
*
* @param vo 数据列表
* @return 工作簿
*/
public Workbook openGatewayApiWorkbook(ApiRegisterDetailVo vo) throws IOException {
String templatePath = "template/openGatewayApi.xlsx";
Workbook workbook = ExcelUtils.getWorkBook(templatePath);
// 字体信息
assert workbook != null;
Font font = workbook.createFont();
font.setFontName("宋体");
font.setBold(true);
font.setFontHeightInPoints((short) 12);
CellStyle workbookCellStyle = workbook.createCellStyle();
workbookCellStyle.setWrapText(true);
workbookCellStyle.setFont(font);
workbookCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
// 写入第一页sheet中
Sheet sheet0 = workbook.getSheetAt(0);
// 名称
setMergedCellValue(sheet0, 0, 0, vo.getName());
// 基本信息,固定行信息优先填写好
setMergedCellValue(sheet0, 2, 1, vo.getName());
setMergedCellValue(sheet0, 3, 1, vo.getMethods());
setMergedCellValue(sheet0, 4, 1, vo.getPath());
setMergedCellValue(sheet0, 5, 1, vo.getRequestUrl() == null ? "" : vo.getRequestUrl());
setMergedCellValue(sheet0, 6, 1, vo.getVersion() == null ? "" : vo.getVersion());
setMergedCellValue(sheet0, 7, 1, vo.getNote() == null ? "" : vo.getNote());
setMergedCellValue(sheet0, 15, 0, vo.getRequestParamsExample() == null ? ""
: vo.getRequestParamsExample().getExample() == null ? "" : vo.getRequestParamsExample().getExample());
setMergedCellValue(sheet0, 22, 0, vo.getPathParamsExample() == null ? ""
: vo.getPathParamsExample().getExample() == null ? "" : vo.getPathParamsExample().getExample());
setMergedCellValue(sheet0, 30, 0, vo.getResponseParamsExample() == null ? ""
: vo.getResponseParamsExample().getExample() == null ? "" : vo.getResponseParamsExample().getExample());
setMergedCellValue(sheet0, 34, 2, vo.getMoreStateCodeUrl() == null ? "" : vo.getMoreStateCodeUrl());
// 请求头
List<ApiParams> requestHeaderParams = vo.getRequestHeaderParams();
// 记录请求头动态添加的行数
int requestHeaderParamsAddRow = 0;
// 请求参数
List<ApiParams> requestParams = vo.getRequestParams();
// 记录请求参数动态添加的行数
int requestParamsAddRow = 0;
// 路径参数
List<ApiParams> pathParams = vo.getPathParams();
// 记录路径参数动态添加的行数
int pathParamsAddRow = 0;
// 响应头
List<ApiParams> responseHeaderParams = vo.getResponseHeaderParams();
// 记录响应头动态添加的行数
int responseHeaderParamsAddRow = 0;
// 响应参数
List<ApiParams> responseParams = vo.getResponseParams();
// 记录响应参数动态添加的行数
int responseParamsAddRow = 0;
// 状态码
List<ApiErrcode> apiErrcodes = vo.getApiErrcodes();
// 记录状态码动态添加的行数
int apiErrcodesAddRow = 0;
// 表格数据填充
if (ObjectUtils.isNotEmpty(requestHeaderParams)) { // 请求头
// 计算起始行和获取样式行
int startRow = 11;
// 样式行
requestHeaderParamsAddRow = getParamsAddRow(sheet0, requestHeaderParams, requestHeaderParamsAddRow, startRow);
}
if (ObjectUtils.isNotEmpty(requestParams)) { // 请求参数
// 计算起始行,原14+请求头添加行
int startRow = 14 + (Math.max(requestHeaderParamsAddRow - 1, 0));
// 样式行
Row styleRow = sheet0.getRow(startRow - 1);
if (requestParams.size() > 1) { // 动态插入行
sheet0.shiftRows(startRow, sheet0.getLastRowNum(), requestParams.size() - 1, true, false);
}
requestParamsAddRow = getParamsAddRow(sheet0, requestParams, requestParamsAddRow, startRow, styleRow);
}
if (ObjectUtils.isNotEmpty(pathParams)) { // 路径参数
// 计算起始行,原21+请求头添加行+请求参数添加行
int startRow = 21 + (Math.max(requestHeaderParamsAddRow - 1, 0)) + (Math.max(requestParamsAddRow - 1, 0));
// 样式行
Row styleRow = sheet0.getRow(startRow - 1);
pathParamsAddRow = getParamsAddRow(sheet0, pathParams, pathParamsAddRow, startRow, styleRow);
}
if (ObjectUtils.isNotEmpty(responseHeaderParams)) { // 响应头
// 计算起始行,原26+请求头添加行+请求参数添加行+路径参数添加行
int startRow = 26 + (Math.max(requestHeaderParamsAddRow - 1, 0)) + (Math.max(requestParamsAddRow - 1, 0))
+ (Math.max(pathParamsAddRow - 1, 0));
// 样式行
Row styleRow = sheet0.getRow(startRow - 1);
if (responseHeaderParams.size() > 1) { // 动态插入行
sheet0.shiftRows(startRow, sheet0.getLastRowNum(), pathParams.size() - 1, true, false);
}
for (ApiParams apiParams : responseHeaderParams) {
// 插入的行号
int rowNum = responseHeaderParamsAddRow + (startRow - 1);
Row newRow = sheet0.getRow(rowNum) == null ? sheet0.createRow(rowNum) : sheet0.getRow(rowNum);
newRow.setRowStyle(styleRow.getRowStyle());
newRow.setHeight(styleRow.getHeight());
Cell cell0 = getCell(newRow, 0);
cell0.setCellStyle(getCell(styleRow, 0).getCellStyle());
cell0.setCellValue(apiParams.getName());
Cell cell1 = getCell(newRow, 1);
cell1.setCellStyle(getCell(styleRow, 1).getCellStyle());
cell1.setCellValue(apiParams.getExample() == null ? "" : apiParams.getExample());
Cell cell2 = getCell(newRow, 2);
cell2.setCellStyle(getCell(styleRow, 2).getCellStyle());
Cell cell3 = getCell(newRow, 3);
cell3.setCellStyle(getCell(styleRow, 3).getCellStyle());
cell3.setCellValue(apiParams.getNote() == null ? "" : apiParams.getNote());
Cell cell4 = getCell(newRow, 4);
cell4.setCellStyle(getCell(styleRow, 4).getCellStyle());
if (!ExcelUtils.isMergedRegion(sheet0, newRow.getRowNum(), cell1.getColumnIndex())) {
// 合并单元格
CellRangeAddress cellAddresses1 = new CellRangeAddress(newRow.getRowNum(), newRow.getRowNum(), cell1.getColumnIndex(), cell2.getColumnIndex());
sheet0.addMergedRegion(cellAddresses1);
}
if (!ExcelUtils.isMergedRegion(sheet0, newRow.getRowNum(), cell3.getColumnIndex())) {
// 合并单元格
CellRangeAddress cellAddresses2 = new CellRangeAddress(newRow.getRowNum(), newRow.getRowNum(), cell3.getColumnIndex(), cell4.getColumnIndex());
sheet0.addMergedRegion(cellAddresses2);
}
responseHeaderParamsAddRow++;
}
}
if (ObjectUtils.isNotEmpty(responseParams)) { // 响应参数
// 计算起始行,原29+请求头添加行+请求参数添加行+路径参数添加行+响应头参数添加行
int startRow = 29 + (Math.max(requestHeaderParamsAddRow - 1, 0)) + (Math.max(requestParamsAddRow - 1, 0))
+ (Math.max(pathParamsAddRow - 1, 0)) + (Math.max(responseHeaderParamsAddRow - 1, 0));
// 样式行
responseParamsAddRow = getParamsAddRow(sheet0, responseParams, responseParamsAddRow, startRow);
}
if (ObjectUtils.isNotEmpty(apiErrcodes)) { // API状态码
// 计算起始行,原37+请求头添加行+请求参数添加行+路径参数添加行+响应头参数添加行+响应参数添加行
int startRow = 37 + (Math.max(requestHeaderParamsAddRow - 1, 0)) + (Math.max(requestParamsAddRow - 1, 0))
+ (Math.max(pathParamsAddRow - 1, 0)) + (Math.max(responseHeaderParamsAddRow - 1, 0))
+ (Math.max(responseParamsAddRow - 1, 0));
Row styleRow = sheet0.getRow(startRow - 1);
if (apiErrcodes.size() > 1) { // 动态插入行,此表格最后行为空行,会无法识别到,所以从上一行开始下移插入
sheet0.shiftRows(startRow - 1, sheet0.getLastRowNum(), apiErrcodes.size() - 1, true, false);
}
for (ApiErrcode errcode : apiErrcodes) {
// 插入的行号
int rowNum = apiErrcodesAddRow + (startRow - 1);
Row newRow = sheet0.getRow(rowNum) == null ? sheet0.createRow(rowNum) : sheet0.getRow(rowNum);
newRow.setRowStyle(styleRow.getRowStyle());
newRow.setHeight(styleRow.getHeight());
Cell cell0 = getCell(newRow, 0);
cell0.setCellStyle(getCell(styleRow, 0).getCellStyle());
cell0.setCellValue(errcode.getCode());
Cell cell1 = getCell(newRow, 1);
cell1.setCellStyle(getCell(styleRow, 1).getCellStyle());
cell1.setCellValue(errcode.getNote() == null ? "" : errcode.getNote());
Cell cell2 = getCell(newRow, 2);
cell2.setCellStyle(getCell(styleRow, 2).getCellStyle());
Cell cell3 = getCell(newRow, 3);
cell3.setCellStyle(getCell(styleRow, 3).getCellStyle());
apiErrcodesAddRow = getCodeAddRow(sheet0, apiErrcodesAddRow, styleRow, newRow, cell1);
}
}
workbook.close();
return workbook;
}
/**
* 合并单元格
*
* @param sheet0 工作簿
* @param addRow 添加行
* @param styleRow 样式行
* @param newRow 插入行
* @param cell1 单元格
* @return 添加行
*/
private int getCodeAddRow(Sheet sheet0, int addRow, Row styleRow, Row newRow, Cell cell1) {
Cell cell4 = getCell(newRow, 4);
cell4.setCellStyle(getCell(styleRow, 4).getCellStyle());
if (!ExcelUtils.isMergedRegion(sheet0, newRow.getRowNum(), cell1.getColumnIndex())) {
// 合并单元格
CellRangeAddress cellAddresses = new CellRangeAddress(newRow.getRowNum(), newRow.getRowNum(), cell1.getColumnIndex(), cell4.getColumnIndex());
sheet0.addMergedRegion(cellAddresses);
}
addRow++;
return addRow;
}
/**
* 提取重复项
*
* @param sheet0 工作簿
* @param paramsList 参数列表
* @param addRow 添加行
* @param startRow 样式行
* @param styleRow 样式行
* @return 参数添加行
*/
private int getParamsAddRow(Sheet sheet0, List<ApiParams> paramsList, int addRow, int startRow, Row styleRow) {
for (ApiParams apiParams : paramsList) {
// 插入的行号
int rowNum = addRow + (startRow - 1);
Row newRow = sheet0.getRow(rowNum) == null ? sheet0.createRow(rowNum) : sheet0.getRow(rowNum);
newRow.setRowStyle(styleRow.getRowStyle());
newRow.setHeight(styleRow.getHeight());
// 插入数据
Cell cell0 = getCell(newRow, 0);
cell0.setCellStyle(getCell(styleRow, 0).getCellStyle());
cell0.setCellValue(apiParams.getName());
Cell cell1 = getCell(newRow, 1);
cell1.setCellStyle(getCell(styleRow, 1).getCellStyle());
cell1.setCellValue(apiParams.getRequired());
Cell cell2 = getCell(newRow, 2);
cell2.setCellStyle(getCell(styleRow, 2).getCellStyle());
cell2.setCellValue(apiParams.getDataType());
Cell cell3 = getCell(newRow, 3);
cell3.setCellStyle(getCell(styleRow, 3).getCellStyle());
cell3.setCellValue(apiParams.getExample() == null ? "" : apiParams.getExample());
Cell cell4 = getCell(newRow, 4);
cell4.setCellStyle(getCell(styleRow, 4).getCellStyle());
cell4.setCellValue(apiParams.getNote() == null ? "" : apiParams.getNote());
addRow++;
}
return addRow;
}
/**
* 提取重复项
*
* @param sheet0 工作簿
* @param paramsList 参数列表
* @param addRow 添加行
* @param startRow 开始行
* @return 参数添加行
*/
private int getParamsAddRow(Sheet sheet0, List<ApiParams> paramsList, int addRow, int startRow) {
Row styleRow = sheet0.getRow(startRow - 1);
if (paramsList.size() > 1) { // 动态插入行
sheet0.shiftRows(startRow, sheet0.getLastRowNum(), paramsList.size() - 1, true, false);
}
for (ApiParams apiParams : paramsList) {
// 插入的行号
int rowNum = addRow + (startRow - 1);
Row newRow = sheet0.getRow(rowNum) == null ? sheet0.createRow(rowNum) : sheet0.getRow(rowNum);
newRow.setRowStyle(styleRow.getRowStyle());
newRow.setHeight(styleRow.getHeight());
Cell cell0 = getCell(newRow, 0);
cell0.setCellStyle(getCell(styleRow, 0).getCellStyle());
cell0.setCellValue(apiParams.getName());
Cell cell1 = getCell(newRow, 1);
cell1.setCellStyle(getCell(styleRow, 1).getCellStyle());
cell1.setCellValue(apiParams.getRequired());
Cell cell2 = getCell(newRow, 2);
cell2.setCellStyle(getCell(styleRow, 2).getCellStyle());
cell2.setCellValue(apiParams.getExample() == null ? "" : apiParams.getExample());
Cell cell3 = getCell(newRow, 3);
cell3.setCellStyle(getCell(styleRow, 3).getCellStyle());
cell3.setCellValue(apiParams.getNote() == null ? "" : apiParams.getNote());
addRow = getCodeAddRow(sheet0, addRow, styleRow, newRow, cell3);
}
return addRow;
}
/**
* 设置合并单元格值
*
* @param sheet 工作表
* @param row 行
* @param column 列
* @param value 值
*/
private void setMergedCellValue(Sheet sheet, int row, int column, Object value) {
Cell cell = ExcelUtils.getMergedRegion(sheet, row, column);
if (cell != null && value != null) {
cell.setCellValue(ExcelUtils.getCellValue(cell) + value);
}
}
excel工具类
package com.edp.gateway.open.utils;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.hssf.usermodel.HSSFClientAnchor;
import org.apache.poi.hssf.usermodel.HSSFPicture;
import org.apache.poi.hssf.usermodel.HSSFShape;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ooxml.POIXMLDocumentPart;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.PictureData;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.usermodel.XSSFClientAnchor;
import org.apache.poi.xssf.usermodel.XSSFDrawing;
import org.apache.poi.xssf.usermodel.XSSFPicture;
import org.apache.poi.xssf.usermodel.XSSFShape;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.CTMarker;
import org.springframework.core.io.ClassPathResource;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class ExcelUtils {
/**
* 获取excel
*
* @param filePath 文件路径
* @return Workbook
*/
public static Workbook getWorkBook(String filePath) {
if (StringUtils.isBlank(filePath)) {
return null;
}
Workbook workbook = null;
try {
ClassPathResource cpr = new ClassPathResource(filePath);
try (InputStream is = cpr.getInputStream()) {
if (filePath.endsWith("xls")) {
// 2003
workbook = new HSSFWorkbook(is);
} else if (filePath.endsWith("xlsx")) {
// 2007
workbook = new XSSFWorkbook(is);
}
}
} catch (IOException e) {
e.printStackTrace();
}
return workbook;
}
/**
* 读取excel中的图片和位置
*
* @param workbook excel
* @param sheet 工作表
* @return Map<String, PictureData>
*/
public static Map<String, PictureData> getPictures(Workbook workbook, Sheet sheet) {
Map<String, PictureData> map = new HashMap<>();
if (workbook instanceof HSSFWorkbook) {
map = getPictures2003((HSSFSheet) sheet);
} else if (workbook instanceof XSSFWorkbook) {
map = getPictures2007((XSSFSheet) sheet);
}
return map;
}
/**
* 读取excel中的图片和位置(xls)
*
* @param sheet 工作表
* @return Map<String, PictureData>
*/
public static Map<String, PictureData> getPictures2003(HSSFSheet sheet) {
Map<String, PictureData> map = new HashMap<>();
List<HSSFShape> list = sheet.getDrawingPatriarch().getChildren();
for (HSSFShape shape : list) {
if (shape instanceof HSSFPicture) {
HSSFPicture picture = (HSSFPicture) shape;
HSSFClientAnchor cAnchor = (HSSFClientAnchor) picture.getAnchor();
// 行号-列号
String key = cAnchor.getRow1() + "-" + cAnchor.getCol1();
map.put(key, picture.getPictureData());
}
}
return map;
}
/**
* 读取excel中的图片和位置(xlsx)
*
* @param sheet 工作表
* @return Map<String, PictureData>
*/
public static Map<String, PictureData> getPictures2007(XSSFSheet sheet) {
Map<String, PictureData> map = new HashMap<>();
List<POIXMLDocumentPart> list = sheet.getRelations();
for (POIXMLDocumentPart part : list) {
if (part instanceof XSSFDrawing) {
XSSFDrawing drawing = (XSSFDrawing) part;
List<XSSFShape> shapes = drawing.getShapes();
for (XSSFShape shape : shapes) {
if (shape instanceof XSSFPicture) {
XSSFPicture picture = (XSSFPicture) shape;
XSSFClientAnchor anchor = picture.getPreferredSize();
CTMarker marker = anchor.getFrom();
String key = marker.getRow() + "-" + marker.getCol();
map.put(key, picture.getPictureData());
}
}
}
}
return map;
}
/**
* 获取excel中每列宽度的占比
*
* @param sheet 工作表
* @return float[]
*/
public static float[] getColWidth(Sheet sheet) {
int rowNum = getMaxColRowNum(sheet);
Row row = sheet.getRow(rowNum);
int cellCount = row.getPhysicalNumberOfCells();
int[] colWidths = new int[cellCount];
int sum = 0;
for (int i = row.getFirstCellNum(); i < cellCount; i++) {
Cell cell = row.getCell(i);
if (cell != null) {
colWidths[i] = sheet.getColumnWidth(i);
sum += sheet.getColumnWidth(i);
}
}
float[] colWidthPer = new float[cellCount];
for (int i = row.getFirstCellNum(); i < cellCount; i++) {
//对数字进行操作前先校验是否为0
if (sum != 0) {
colWidthPer[i] = (float) colWidths[i] / sum * 100;
}
}
return colWidthPer;
}
/**
* 获取合并单元格
*
* @param sheet 工作表
* @param row 行
* @param column 列
* @return Cell
*/
public static Cell getMergedRegion(Sheet sheet, int row, int column) {
int sheetMergeCount = sheet.getNumMergedRegions();
for (int i = 0; i < sheetMergeCount; i++) {
CellRangeAddress ca = sheet.getMergedRegion(i);
int firstColumn = ca.getFirstColumn();
int lastColumn = ca.getLastColumn();
int firstRow = ca.getFirstRow();
int lastRow = ca.getLastRow();
if (row >= firstRow && row <= lastRow) {
if (column >= firstColumn && column <= lastColumn) {
Row fRow = sheet.getRow(firstRow);
return fRow.getCell(firstColumn);
}
}
}
return null;
}
/**
* 获取单元格的值
*
* @param cell 单元格
* @return String
*/
public static String getCellValue(Cell cell) {
if (cell == null) {
return "";
}
if (cell.getCellType() == CellType.STRING) {
return cell.getStringCellValue();
} else if (cell.getCellType() == CellType.BOOLEAN) {
return String.valueOf(cell.getBooleanCellValue());
} else if (cell.getCellType() == CellType.FORMULA) {
return cell.getCellFormula();
} else if (cell.getCellType() == CellType.NUMERIC) {
double value = cell.getNumericCellValue();
return String.valueOf((long) value);
}
return "";
}
/**
* 判断指定的单元格是否是合并单元格
*
* @param sheet 工作表
* @param row 行下标
* @param column 列下标
* @return boolean
*/
public static boolean isMergedRegion(Sheet sheet, int row, int column) {
int sheetMergeCount = sheet.getNumMergedRegions();
for (int i = 0; i < sheetMergeCount; i++) {
CellRangeAddress range = sheet.getMergedRegion(i);
int firstColumn = range.getFirstColumn();
int lastColumn = range.getLastColumn();
int firstRow = range.getFirstRow();
int lastRow = range.getLastRow();
if (row >= firstRow && row <= lastRow) {
if (column >= firstColumn && column <= lastColumn) {
return true;
}
}
}
return false;
}
/**
* 获取合并单元格宽高
*
* @param sheet 工作表
* @param row 行
* @param column 列
* @return int[]
*/
public static float[] getMergedRegionSize(Sheet sheet, int row, int column) {
int[] matrix = getMergedRegionMatrix(sheet, row, column);
float[] size = new float[2];
// 合并单元格高
float height = 0;
// 合并单元格宽
float width = 0;
for (int y = matrix[0]; y <= matrix[2]; y++) {
Row rowY = sheet.getRow(y);
if (rowY != null) {
height += sheet.getRow(y).getHeightInPoints();
}
}
for (int x = matrix[1]; x <= matrix[3]; x++) {
width += sheet.getColumnWidthInPixels(x);
}
size[0] = height;
size[1] = width;
return size;
}
/**
* 获取合并单元格矩阵
*
* @param sheet 工作表
* @param row 行
* @param column 列
* @return int[]
*/
public static int[] getMergedRegionMatrix(Sheet sheet, int row, int column) {
int sheetMergeCount = sheet.getNumMergedRegions();
int[] matrix = new int[4];
for (int i = 0; i < sheetMergeCount; i++) {
CellRangeAddress ca = sheet.getMergedRegion(i);
int firstColumn = ca.getFirstColumn();
int lastColumn = ca.getLastColumn();
int firstRow = ca.getFirstRow();
int lastRow = ca.getLastRow();
if (row >= firstRow && row <= lastRow) {
if (column >= firstColumn && column <= lastColumn) {
matrix[0] = firstRow;
matrix[1] = firstColumn;
matrix[2] = lastRow;
matrix[3] = lastColumn;
break;
}
}
}
return matrix;
}
/**
* 计算合并单元格合并的跨行跨列数
*
* @param sheet 工作表
* @param row 行
* @param column 列
* @return int[]
*/
public static int[] getMergedSpan(Sheet sheet, int row, int column) {
int sheetMergeCount = sheet.getNumMergedRegions();
int[] span = {1, 1};
for (int i = 0; i < sheetMergeCount; i++) {
CellRangeAddress range = sheet.getMergedRegion(i);
int firstColumn = range.getFirstColumn();
int lastColumn = range.getLastColumn();
int firstRow = range.getFirstRow();
int lastRow = range.getLastRow();
if (firstColumn == column && firstRow == row) {
span[0] = lastRow - firstRow + 1;
span[1] = lastColumn - firstColumn + 1;
break;
}
}
return span;
}
/**
* 获取excel中列数最多的行号
*
* @param sheet 工作表
* @return int
*/
private static int getMaxColRowNum(Sheet sheet) {
int rowNum = 0;
int maxCol = 0;
for (int r = sheet.getFirstRowNum(); r < sheet.getPhysicalNumberOfRows(); r++) {
Row row = sheet.getRow(r);
if (row != null && maxCol < row.getPhysicalNumberOfCells()) {
maxCol = row.getPhysicalNumberOfCells();
rowNum = r;
}
}
return rowNum;
}
}
excel转pdf工具类
package com.edp.gateway.open.utils;
import com.itextpdf.text.BaseColor;
import com.itextpdf.text.Document;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Element;
import com.itextpdf.text.Font;
import com.itextpdf.text.Image;
import com.itextpdf.text.PageSize;
import com.itextpdf.text.Phrase;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.BaseFont;
import com.itextpdf.text.pdf.PdfPCell;
import com.itextpdf.text.pdf.PdfPTable;
import com.itextpdf.text.pdf.PdfWriter;
import org.apache.poi.ss.usermodel.BorderStyle;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.PictureData;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Map;
public class ExcelToPdfUtil {
/**
* 用excel生成PDF
*
* @param workbook excel工作表
* @param isRotate 横向或纵向显示
* @param zoom 缩放比例
* @return byte[]
*/
public static byte[] excelToPdf(Workbook workbook, boolean isRotate, float zoom)
throws DocumentException, IOException {
Sheet sheet = workbook.getSheetAt(0);
// 获取excel中的图片
Map<String, PictureData> pictureDataMap = ExcelUtils.getPictures(workbook, sheet);
ByteArrayOutputStream os = new ByteArrayOutputStream();
// 创建PDF
Document document = new Document();
Rectangle pageSize;
if (isRotate) {
// 横向显示
pageSize = new Rectangle(PageSize.A4.getHeight(), PageSize.A4.getWidth());
} else {
// 纵向显示
pageSize = new Rectangle(PageSize.A4.getWidth(), PageSize.A4.getHeight());
}
document.setPageSize(pageSize);
PdfWriter.getInstance(document, os);
//设置页边距
document.setMargins(0, 0, 15, 15);
document.open();
// Table获取每列所占百分比
float[] widths = ExcelUtils.getColWidth(sheet);
int colCount = widths.length;
// 创建Table
PdfPTable pTable = new PdfPTable(widths);
// 设置Table占页面的百分比
pTable.setWidthPercentage(92);
// 设置基本字体
BaseFont baseFont = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
// 遍历Excel的行和列读取数据
for (int r = sheet.getFirstRowNum(); r < sheet.getPhysicalNumberOfRows(); r++) {
Row row = sheet.getRow(r);
// 为空时创建默认单元格
if (row == null) {
PdfPCell pCell = new PdfPCell(new Phrase(""));
pCell.setBorder(0);
pCell.setMinimumHeight(13);
pTable.addCell(pCell);
} else {
for (int c = row.getFirstCellNum(); (c < row.getLastCellNum() || c < colCount); c++) {
if (c > row.getPhysicalNumberOfCells()) {
PdfPCell pCell = new PdfPCell(new Phrase(""));
pCell.setBorder(0);
pTable.addCell(pCell);
continue;
}
Cell cell = row.getCell(c);
if (cell == null) {
continue;
}
// 获取单元格的值
String value = ExcelUtils.getCellValue(cell);
// 获取Excel单元格的样式
CellStyle cellStyle = cell.getCellStyle();
// 获取Excel单元格的字体
org.apache.poi.ss.usermodel.Font excelFont = workbook.getFontAt(cellStyle.getFontIndex());
// 设置PDF单元格字体
Font pFont = new Font(baseFont, excelFont.getFontHeightInPoints() * zoom,
excelFont.getBold() ? Font.BOLD : Font.NORMAL, BaseColor.BLACK);
// 处理图片
// 拼接图片的key
String pictureKey = r + "-" + c;
PdfPCell pCell;
if (pictureDataMap.containsKey(pictureKey)) {
// 如果是图片则给PDF单元格设置图片
PictureData pictureData = pictureDataMap.get(pictureKey);
Image image = Image.getInstance(pictureData.getData());
float percent = 0.8f;
if (ExcelUtils.isMergedRegion(sheet, r, c)) {
float[] size = ExcelUtils.getMergedRegionSize(sheet, r, c);
image.scaleToFit(size[0] * percent, size[1] * percent);
} else {
image.scaleToFit(row.getHeightInPoints() * percent,
sheet.getColumnWidthInPixels(cell.getColumnIndex()) * percent);
}
pCell = new PdfPCell(image, true);
} else {
pCell = new PdfPCell(new Phrase(value, pFont));
}
// 设置对齐方式
pCell.setHorizontalAlignment(getHorAlignment(cellStyle.getAlignment().getCode()));
pCell.setVerticalAlignment(getVerAlignment(cellStyle.getVerticalAlignment().getCode()));
// 设置最小高度
pCell.setMinimumHeight(row.getHeightInPoints() * zoom);
// 判断是否合并单元格
if (ExcelUtils.isMergedRegion(sheet, r, c)) {
int[] span = ExcelUtils.getMergedSpan(sheet, r, c);
// 忽略合并过的单元格
if (span[0] == 1 && span[1] == 1) {
continue;
}
pCell.setRowspan(span[0]);
pCell.setColspan(span[1]);
// 跳过合并过的单元格
c = c + span[1] - 1;
}
// 设置边框
setBorder(pCell, sheet, r, c);
pTable.addCell(pCell);
}
}
}
document.add(pTable);
document.close();
byte[] pdfByte = os.toByteArray();
os.close();
return pdfByte;
}
/**
* 设置PDF边框
*
* @param pCell pdf单元格
* @param sheet 工作表
* @param row 行
* @param column 列
*/
private static void setBorder(PdfPCell pCell, Sheet sheet, int row, int column) {
Cell cell;
boolean isMergedRegion = ExcelUtils.isMergedRegion(sheet, row, column);
if (isMergedRegion) {
cell = ExcelUtils.getMergedRegion(sheet, row, column);
} else {
cell = sheet.getRow(row).getCell(column);
}
assert cell != null;
CellStyle cellStyle = cell.getCellStyle();
if (BorderStyle.NONE.equals(cellStyle.getBorderTop())) {
// 隐藏上边框
pCell.disableBorderSide(1);
}
if (BorderStyle.NONE.equals(cellStyle.getBorderBottom())) {
// 隐藏下边框
pCell.disableBorderSide(2);
}
if (BorderStyle.NONE.equals(cellStyle.getBorderLeft())) {
// 隐藏左边框
pCell.disableBorderSide(4);
}
if (isMergedRegion) {
// 如果是合并单元格获取最后一个单元格的右边框样式
int[] matrix = ExcelUtils.getMergedRegionMatrix(sheet, row, column);
Cell endCell = sheet.getRow(matrix[0]).getCell(matrix[3]);
if (BorderStyle.NONE.equals(endCell.getCellStyle().getBorderRight())) {
// 隐藏右边框
pCell.disableBorderSide(8);
}
} else {
if (BorderStyle.NONE.equals(cellStyle.getBorderRight())) {
// 隐藏右边框
pCell.disableBorderSide(8);
}
}
}
/**
* excel垂直对齐方式映射到pdf对齐方式
*
* @param alignment 对齐方式
* @return int
*/
private static int getVerAlignment(int alignment) {
switch (alignment) {
case 2:
return Element.ALIGN_BOTTOM;
case 3:
return Element.ALIGN_TOP;
case 1:
default:
return Element.ALIGN_MIDDLE;
}
}
/**
* excel水平对齐方式映射到pdf水平对齐方式
*
* @param alignment 对齐方式
* @return int
*/
private static int getHorAlignment(int alignment) {
switch (alignment) {
case 2:
return Element.ALIGN_CENTER;
case 3:
return Element.ALIGN_RIGHT;
case 1:
default:
return Element.ALIGN_LEFT;
}
}
}
zip打包工具类
package com.edp.gateway.open.utils;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Base64;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
public class ZipUtil {
/**
* 文件流压缩
*
* @param basisByte 需要压缩的字节输出流(ByteArrayOutputStream)的字节数组
* @param fileName 需要压缩的文件名
* @return 压缩后字节数组输出流转为的字符串
* @throws IOException 异常
*/
public static String zipByteToString(byte[] basisByte, String fileName) throws IOException {
// 将需要压缩的字节输出流,转为字节数组输入流,
ByteArrayOutputStream byteArrayOutputStream;
// 创建压缩输出流
ZipOutputStream zipOutputStream;
try (ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(basisByte)) {
// 创建字节数组输出流,用于返回压缩后的输出流字节数组
byteArrayOutputStream = new ByteArrayOutputStream();
zipOutputStream = new ZipOutputStream(byteArrayOutputStream);
// zipOutputStream.setMethod(ZipOutputStream.DEFLATED);// 进行压缩存储
// zipOutputStream.setLevel(Deflater.BEST_COMPRESSION);// 压缩级别值为0-9共10个级别(值越大,表示压缩越利害)
// 设置ZipEntry对象,并对需要压缩的文件命名
zipOutputStream.putNextEntry(new ZipEntry(fileName));
// 读取要压缩的字节输出流,进行压缩
int temp;
while ((temp = byteArrayInputStream.read()) != -1) {
zipOutputStream.write(temp); // 压缩输出
}
}
zipOutputStream.close();
byteArrayOutputStream.close();// 关闭流
return Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray());
}
/**
* 文件流压缩
*
* @param basisByte 需要压缩的字节输出流(ByteArrayOutputStream)的字节数组
* @param fileName 需要压缩的文件名
* @return 压缩后字节数组
* @throws IOException 异常
*/
public static byte[] zipByte(byte[] basisByte, String fileName) throws IOException {
// 将需要压缩的字节输出流,转为字节数组输入流,
ByteArrayOutputStream byteArrayOutputStream;
// 创建压缩输出流
ZipOutputStream zipOutputStream;
try (ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(basisByte)) {
// 创建字节数组输出流,用于返回压缩后的输出流字节数组
byteArrayOutputStream = new ByteArrayOutputStream();
zipOutputStream = new ZipOutputStream(byteArrayOutputStream);
// zipOutputStream.setMethod(ZipOutputStream.DEFLATED);// 进行压缩存储
// zipOutputStream.setLevel(Deflater.BEST_COMPRESSION);// 压缩级别值为0-9共10个级别(值越大,表示压缩越利害)
// 设置ZipEntry对象,并对需要压缩的文件命名
zipOutputStream.putNextEntry(new ZipEntry(fileName));
// 读取要压缩的字节输出流,进行压缩
int temp;
while ((temp = byteArrayInputStream.read()) != -1) {
zipOutputStream.write(temp); // 压缩输出
}
}
zipOutputStream.close();
byteArrayOutputStream.close();// 关闭流
return byteArrayOutputStream.toByteArray();
}
/**
* 批量文件流压缩
*
* @param basisByteList List<Map<被压缩压缩文件名, 被压缩的文件流>>
* @return 压缩后字节数组输出流转为的字符串
* @throws IOException 异常
*/
public static String batchZipByteToString(List<Map<String, byte[]>> basisByteList) throws IOException {
// 创建字节数组输出流,用于返回压缩后的输出流字节数组
try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {
// 创建压缩输出流
try (ZipOutputStream zipOutputStream = new ZipOutputStream(byteArrayOutputStream)) {
// 遍历要批量压缩的集合文件流
compress(basisByteList, zipOutputStream);
}
// 关闭流
byteArrayOutputStream.close();
return Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray());
}
}
/**
* 批量文件流压缩
*
* @param basisByteList List<Map<被压缩压缩文件名, 被压缩的文件流>>
* @return 压缩后字节数组
* @throws IOException 异常
*/
public static byte[] batchZipByte(List<Map<String, byte[]>> basisByteList) throws IOException {
// 创建字节数组输出流,用于返回压缩后的输出流字节数组
try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {
// 创建压缩输出流
try (ZipOutputStream zipOutputStream = new ZipOutputStream(byteArrayOutputStream)) {
// 遍历要批量压缩的集合文件流
compress(basisByteList, zipOutputStream);
}
// 关闭流
byteArrayOutputStream.close();
return byteArrayOutputStream.toByteArray();
}
}
/**
* 提取重复项
*
* @param basisByteList 字节数组列表
* @param zipOutputStream 输出流
* @throws IOException 异常
*/
private static void compress(List<Map<String, byte[]>> basisByteList, ZipOutputStream zipOutputStream) throws IOException {
ByteArrayInputStream byteArrayInputStream;
Map<String, byte[]> tempMap;
String fileName;
int temp;
for (Map<String, byte[]> stringMap : basisByteList) {
tempMap = stringMap;
fileName = tempMap.keySet().iterator().next();
// 将需要压缩的字节输出流,转为字节数组输入流,
byteArrayInputStream = new ByteArrayInputStream(tempMap.get(fileName));
// zipOut.setMethod(ZipOutputStream.DEFLATED);// 进行压缩存储
// zipOut.setLevel(Deflater.BEST_COMPRESSION);// 压缩级别值为0-9共10个级别(值越大,表示压缩越利害)
// 设置ZipEntry对象,并对需要压缩的文件命名
zipOutputStream.putNextEntry(new ZipEntry(fileName));
// 读取要压缩的字节输出流,进行压缩
while ((temp = byteArrayInputStream.read()) != -1) {
zipOutputStream.write(temp); // 压缩输出
}
// 关闭流
byteArrayInputStream.close();
}
}
}
到此基本问题解决,工具类有一些是网友提供的,但是我没找到连接,在此就不贴原文引用链接了,知道的朋友也可提醒我补上,代码中还有不少地方可以优化,若对大家有帮助,可自行根据需求调整。
PS:
1.excel的动态插入行在转pdf的时候,空值单元格需要用空串占位,否则在转pdf时会发生数据错位现象。
2.因为全程都是字节数组和流式处理,对内存要求比较高,数据量大的注意控制内存,不要溢出。
3.字节数组文件直接压缩可能会导致,各个文件位置不统一,如效果图所示,自行定义文件位置后再打包。
4.打包中文乱码需要更改编码格式。