在实际工作中,如果业务线是管理类项目或者存在大量报表需要导出的业务时,可以借助第三方插件实现其对应功能。
尤其是需要对word文档的动态操作或者模板数据的定向合并,使用Aspose会相对来说容易一些,而且相关文档比较完整,具体如何使用,请参看如下案例:
下图展示的是数据模板图1:
一、什么是Aspose?
官网地址:Aspose.Words 产品系列 | Aspose API 参考
文档处理是个人和专业人士必备的技能。然而,手动文档处理可能非常耗时且容易。Aspose.Words Java 文档处理教程提供了使用代码自动生成和管理文档的全面指南。
这些教程涵盖了广泛的主题,从基本的表格处理到高级文档合并和水印。Aspose.Words 教程注重实际实施,使开发人员能够高效地简化文档处理任务并提供定制的解决方案。
无论您是初学者还是经验丰富的开发人员,Aspose.Words Java 文档处理教程都可以帮助您将文档处理技能提升到更高的水平。
二、springboot项目如何引入?
1.获得许可
2.引入pom
<dependency>
<groupId>aspose</groupId>
<artifactId>words</artifactId>
<version>21.4.0</version>
<classifier>jdk17</classifier>
</dependency>
三、具体使用案例
1.集成授权
public static void setAuthLicense() {
try {
//根目录下的授权文件
ClassPathResource resource = new ClassPathResource("授权文件路径");
URL licenseFileNameURL = resource.getUrl();
String savePath = java.net.URLDecoder.decode(licenseFileNameURL.toString(), "utf-8");
String licenseFileName = savePath.toString().substring(6);
if (new File(licenseFileName).exists()) {
License license = new License();
license.setLicense(licenseFileName);
}
} catch (Exception e) {
e.printStackTrace();
log.error("aspose 授权异常");
}
}
2.设置模板绑定字段
/**
* 设置模板绑定字段
*
* @param document 默认的文档对象
* @param fieldname 模板key
* @param fieldvalue 模板value
* @throws Exception
*/
public static void mergeField(Document document, String fieldname,
Object fieldvalue) throws Exception {
document.getMailMerge().execute(new String[]{fieldname},
new Object[]{fieldvalue});
}
3.配置动态表格,主要用于集合遍历(数据列表展示)
public class ResultSetDataSource {
public ResultSetDataSource(){
}
/**
* 处理自定义的数据源
* @param document 文档
* @param tableStartFieldName tableStart的域名
* @param fieldNames 列的域名数组
* @param fieldValueArray 域值的数组
* @throws Exception
*/
public void executeWithRegions(Document document, String tableStartFieldName, String[] fieldNames, String[][] fieldValueArray) throws Exception {
java.sql.ResultSet resultSet = createCachedRowSet(fieldNames);
int length = fieldValueArray.length;
for(int i = 0; i < length;i ++){
addRow(resultSet,fieldValueArray[i]);
}
com.aspose.words.net.System.Data.DataTable table = new com.aspose.words.net.System.Data.DataTable(resultSet, tableStartFieldName);
document.getMailMerge().executeWithRegions(table);
}
public java.sql.ResultSet createCachedRowSet(String[] columnNames) throws Exception
{
RowSetMetaDataImpl metaData = new RowSetMetaDataImpl();
metaData.setColumnCount(columnNames.length);
for (int i = 0; i < columnNames.length; i++)
{
metaData.setColumnName(i + 1, columnNames[i]);
metaData.setColumnType(i + 1, java.sql.Types.VARCHAR);
}
CachedRowSet rowSet = RowSetProvider.newFactory().createCachedRowSet();
rowSet.setMetaData(metaData);
return rowSet;
}
public void addRow(java.sql.ResultSet resultSet, String[] values) throws Exception
{
resultSet.moveToInsertRow();
for (int i = 0; i < values.length; i++) {
resultSet.updateString(i + 1, values[i]);
}
resultSet.insertRow();
resultSet.moveToCurrentRow();
resultSet.last();
}
4.业务实现
public void testExportDoc(@RequestParam("id") String testId, HttpServletResponse response){
//aspose 授权
AsposeWordLib.setAuthLicense();
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
//插入动态表格
ResultSetDataSource dataSource = new ResultSetDataSource();
OutputStream responseOutputStream = null;
try{
//基础数据赋值
String uuid = testId;
RequestLogUtils.setAttribute("code",uuid);
ClassPathResource pathResource = new ClassPathResource("test01/test01.docx");
InputStream in = pathResource.getInputStream();
Document doc = new Document(in);
WordLib.mergeField(doc, "code",uuid);
WordLib.mergeField(doc, "productmodel", "2024-M11-");
WordLib.mergeField(doc, "productname", "产品名称");
WordLib.mergeField(doc, "username", "用户名");
Date date = new Date();
WordLib.mergeField(doc, "applytimestr",formatter.format(date));
WordLib.mergeField(doc, "num",556251);
//集合数据动态赋值
String [] arrDefault = {"seq","name","sign"};
String [][] arrayTab = new String[2][3];
for (int i = 0; i < 2; i++) {
arrayTab[i][0] = i + "";
arrayTab[i][1] = "物资" + i;
arrayTab[i][2] = "sign" + i;
}
dataSource.executeWithRegions(doc,"t1",arrDefault, arrayTab);
String [] arrMerge = {"content","status","userStr"};
String [][] arrayTabMerge = new String[3][3];
for (int i = 0; i < 3; i++) {
arrayTabMerge[i][0] = i + "、内容" + i;
if(i%2 == 0){
arrayTabMerge[i][1] = "☑ 正常 ☐ 停用";
}else {
arrayTabMerge[i][1] = "☐ 正常 ☑ 停用";
}
arrayTabMerge[i][2] = "用户";
}
dataSource.executeWithRegions(doc,"t2",arrMerge, arrayTabMerge);
//默认的行数 = 固定表格数(从1开始计算)+动态计算的arrayTab长度
int defaultRow = 7 + arrayTab.length;
//需要合并的行数
int dynamicMergeRowsCM = arrayTabMerge.length;
//内容列表-详细内容单元格合并
DocumentBuilder documentBuilder = new DocumentBuilder(doc);
//移动到第一个表格的第defaultStaIndex行的第columnIndex列(第一个格子)的第一个字符(doc表格外的标题不做计算)
documentBuilder.moveToCell(0, defaultRow, 0, 0);
documentBuilder.getCellFormat().setVerticalMerge(CellMerge.FIRST);
for(int i = 0; i < dynamicMergeRowsCM; i++ ) {
documentBuilder.moveToCell(0, defaultRow + i, 0, 0);
documentBuilder.getCellFormat().setVerticalMerge(CellMerge.PREVIOUS);
}
//内容列表-操作人单元格合并
//移动到第一个表格的第defaultStaIndex行的第columnIndex列(第一个格子)的第一个字符(doc表格外的标题不做计算)
documentBuilder.moveToCell(0, defaultRow, 3, 0);
documentBuilder.getCellFormat().setVerticalMerge(CellMerge.FIRST);
for(int i = 0; i < dynamicMergeRowsCM; i++ ) {
documentBuilder.moveToCell(0, defaultRow + i, 3, 0);
documentBuilder.getCellFormat().setVerticalMerge(CellMerge.PREVIOUS);
}
//返回前台
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try {
doc.save(outputStream, SaveFormat.DOCX);
} catch (Exception e) {
e.printStackTrace();
}
// 建立一个文件的输出的输出流
ByteArrayOutputStream byteArrayOutputStream = (ByteArrayOutputStream) outputStream;
byte[] aByte = byteArrayOutputStream.toByteArray();
response.setCharacterEncoding("UTF-8");
response.setHeader("Content-Disposition", "attachment; filename=" + uuid +".docx");
String returnType = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
response.setContentType(returnType);
responseOutputStream = response.getOutputStream();
responseOutputStream.write(aByte);
responseOutputStream.flush();
}catch (Exception e){
e.printStackTrace();
}
}
5.单元格合并问题(仅展示垂直合并)
- 解决不连续合并的问题(包括垂直和水平合并两种)
//默认的行数 = 固定表格数(从1开始计算)+动态计算的arrayTab长度
int defaultRow = 7 + arrayTab.length;
//需要合并的行数
int dynamicMergeRowsCM = arrayTabMerge.length;
//内容列表-详细内容单元格合并
DocumentBuilder documentBuilder = new DocumentBuilder(doc);
//移动到第一个表格的第defaultStaIndex行的第columnIndex列(第一个格子)的第一个字符(doc表格外的标题不做计算)
documentBuilder.moveToCell(0, defaultRow, 0, 0);
documentBuilder.getCellFormat().setVerticalMerge(CellMerge.FIRST);
for(int i = 0; i < dynamicMergeRowsCM; i++ ) {
documentBuilder.moveToCell(0, defaultRow + i, 0, 0);
documentBuilder.getCellFormat().setVerticalMerge(CellMerge.PREVIOUS);
}
- 对于连续合并的模板(此方案必须基于保存的文件)
public static void mergeCells(Cell startCell, Cell endCell) {
Table parentTable = startCell.getParentRow().getParentTable();
Point startCellPos = new Point(startCell.getParentRow().indexOf(startCell), parentTable.indexOf(startCell.getParentRow()));
Point endCellPos = new Point(endCell.getParentRow().indexOf(endCell), parentTable.indexOf(endCell.getParentRow()));
Rectangle mergeRange = new Rectangle(Math.min(startCellPos.x, endCellPos.x), Math.min(startCellPos.y, endCellPos.y), Math.abs(endCellPos.x - startCellPos.x) + 1,
Math.abs(endCellPos.y - startCellPos.y) + 1);
for (Row row : parentTable.getRows()) {
for (Cell cell : row.getCells()) {
Point currentPos = new Point(row.indexOf(cell), parentTable.indexOf(row));
if (mergeRange.contains(currentPos)) {
if (currentPos.x == mergeRange.x)
cell.getCellFormat().setHorizontalMerge(CellMerge.FIRST);
else
cell.getCellFormat().setHorizontalMerge(CellMerge.PREVIOUS);
if (currentPos.y == mergeRange.y)
cell.getCellFormat().setVerticalMerge(CellMerge.FIRST);
else
cell.getCellFormat().setVerticalMerge(CellMerge.PREVIOUS);
}
}
}
}
//具体实现方案
public static void main(String[] args) {
ClassPathResource re = new ClassPathResource("exportDocTemplate/new241119/test02.docx");
InputStream in = re.getInputStream();
Document targetDoc = new Document(in);
Table table = targetDoc.getFirstSection().getBody().getTables().get(0);
Cell cellStartRange = table.getRows().get(12).getCells().get(0); //开始的行和列
Cell cellEndRange = table.getRows().get(13).getCells().get(0); //结束的行列
ReportExportCommonUtils.mergeCells(cellStartRange, cellEndRange);
Cell s1 = table.getRows().get(12).getCells().get(3); //开始的行和列
Cell e1 = table.getRows().get(13).getCells().get(3); //结束的行列
ReportExportCommonUtils.mergeCells(s1, e1);
Cell s2 = table.getRows().get(10).getCells().get(0); //开始的行和列
Cell e2 = table.getRows().get(11).getCells().get(0); //结束的行列
ReportExportCommonUtils.mergeCells(s2, e2);
Cell s3 = table.getRows().get(10).getCells().get(3); //开始的行和列
Cell e3 = table.getRows().get(11).getCells().get(3); //结束的行列
mergeCells(s3, e3);
}
6.最终效果展示
7.需要主要的问题点
- 水平合并关键代码
documentBuilder.getCellFormat().setHorizontalMerge(CellMerge.FIRST);
- 在编辑数据模板时需要切换域代码,也就是文中指定的数据模板图1