2022-05-15_luckysheet导出xls文件(第三方版)
0.前言
- 目前通过服务器端poi框架(第三方版)的方式进行导出xls文件。
- 核心思想就是读取数据库存取的luckysheet格式的表格数据,通过poi转换为xls文件。然后给浏览器下载。
- 该框架实现了字体、边框、图片、行列冻结、样式
- 因为新版本的luckysheet更改了这个接口。作者的地址见附录。
1.导入依赖
- 注意版本。。poiV4不能使用
<!--poi-->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.12</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-scratchpad</artifactId>
<version>3.12</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.12</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml-schemas</artifactId>
<version>3.12</version>
</dependency>
2.复制class文件
-
复制如下文件。
-
复制过来后,idea会有报错。都是import错误。删掉这些报错的import错误。然后交给idea自动import即可。
文件清单 ExcelUtils.java
3.增加自定义导出文件名功能
-
原来程序定死了文件名为XXXX20201124.xlsx。
-
这里修改了调用方法,增加了文件名传入参数
定位ExcelUtils.java 178行。增加文件名传入参数。 //public static void exportLuckySheetXlsx(String excelData,HttpServletRequest request, HttpServletResponse response) public static void exportLuckySheetXlsx(String excelData,HttpServletRequest request, HttpServletResponse response, String fileName)
定位ExcelUtils.java 226行。使用传入的文件名。 //disposition += new String(("XXXX20201124.xlsx").getBytes(), "ISO8859-1"); disposition += new String((fileName+".xlsx").getBytes(), "ISO8859-1"); 定位ExcelUtils.java 229行。使用传入的文件名。 //disposition += URLEncoder.encode("XXXX20201124.xlsx", "UTF-8"); disposition += URLEncoder.encode(fileName+".xlsx", "UTF-8");
4.bug处理
4.1字符串无法转换为Interger错误
-
可能是fastjson版本缘故,直接使用这个库,会提示字符串无法转换为Interger错误。
定位ExcelUtils.java 676行,其他类似处理即可。 //xssfCellStyle.setBorderLeft(bordMap.get((int) l.get("style"))); //左边框 xssfCellStyle.setBorderLeft(bordMap.get(l.getInteger("style")));
4.2 JSONArray无法转换为JSONObject错误
-
错误原因是luckysheet更改了接口。。。dataVerification现在是一个JSONArray。。
-
暂时注释掉。。待后面慢慢处理。
定位ExcelUtils.java 188行。注释掉如下语句 //JSONObject dataVerification = jsonObject.getJSONObject("dataVerification");
4.3 一个见鬼的bug
-
这个bug简直见鬼了。虽说后面解决了。
-
bug现象:有值的单元格导出的表格文件包含边框,没有值的单元格就没有边框。
-
bug原因:这个框架,处理有值的单元格之前有一个
createCellStyle方法。
没有值的单元格就不会调用这个方法。这个方法有一个新增单元格格式的方法:XSSFCellStyle cellStyle = wb.createCellStyle();
-
没有值的单元格在设置边框的时候用如下方法获取单元格格式对象:
xssfCellStyle = cell.getCellStyle()
。猜测这个方法不生效。。因为都还没对这个单元格set单元格格式对象。 定位ExcelUtils.java 708行。修改成如下: XSSFCellStyle xssfCellStyle; if (sheet.getRow(row_) == null){ sheet.createRow(row_); } if (sheet.getRow(row_).getCell(col_) == null){ sheet.getRow(row_).createCell(col_); xssfCellStyle = wb.createCellStyle(); }else{ xssfCellStyle = sheet.getRow(row_).getCell(col_).getCellStyle(); }
4.4 NPE异常
-
错误提示:
java.lang.NullPointerException at com.glg.collsheet.exportXls.ExcelUtils.setCellValue(ExcelUtils.java:539) at com.glg.collsheet.exportXls.ExcelUtils.exportLuckySheetXlsx(ExcelUtils.java:215)
-
这个主要是因为有一些空sheet,是没有celldata对象的。celldata对象用来放单元格的值
定位ExcelUtils.java 186行。如下语句返回的是null JSONArray jsonObjectList = jsonObject.getJSONArray("celldata"); 定位ExcelUtils.java 2156行。如下语句就会触发NPE setCellValue(wb,sheet,jsonObjectList,columnlenObject,rowlenObject,defaultRowHeight,defaultColWidth);
增加非空判断即可。 定位ExcelUtils.java 215行。 if (jsonObjectList != null){ setCellValue(wb,sheet,jsonObjectList,columnlenObject,rowlenObject,defaultRowHeight,defaultColWidth); }
4.5 部分特殊单元格数值导出为空
-
特殊单元格是指有换行值的单元格、部分合并的单元格、数值格式不一样的单元格等。
-
之所以原本的代码不管用,主要原因是这部分单元格的数值的保存方法不一样
-
celldata[0].v.ct.s是一个数组。。里面的每个s[0].v是保存的数值,需要把这部分的数值合并起来。
-
[图片上传失败...(image-f43282-1654854873451)]
//定位ExcelUtils.java第559行,添加如下特殊处理 Object ct_ = jsonObjectValue.get("ct"); if (ct_ != null && ct_ instanceof JSONObject){ Object s_ = ((JSONObject) ct_).get("s"); if (s_ != null && s_ instanceof JSONArray){ for (int i = 0; i < ((JSONArray) s_).size(); i++) { m += ((JSONObject) ((JSONArray) s_).get(i)).getString("v"); } } }
5.新增不同边框处理功能
-
luckysheet修改了边框的接口了。新luckysheet的边框和原本很大不一样了。。
-
有个边框类型的属性。如全边框。半边框。中间边框等。这个框架没有区分这些属性。
-
感觉我的方法不是很简洁。有一些重复的代码。期待有更简洁的方法。但是不管怎么样。测试通过了。
//修改ExcelUtils.java 706行。修改成如下: int rowBegin = rowList.getInteger(0); int rowEnd = rowList.getInteger(rowList.size() - 1); int colBegin = columnList.getInteger(0); int colEnd = columnList.getInteger(columnList.size() - 1); for (int row_ = rowBegin; row_ < rowEnd + 1; row_++) { for (int col_ = colBegin; col_ < colEnd + 1; col_++) { setCellBorder(rowBegin,rowEnd,colBegin,colEnd,row_,col_,sheet,style_,color,borderType,wb); } }
//新增setCellBorder方法如下: private static void setCellBorder(int rowBegin, int rowEnd, int colBegin, int colEnd,int row_,int col_, XSSFSheet sheet, int style_,XSSFColor color, String borderType,XSSFWorkbook wb){ XSSFCellStyle xssfCellStyle; if (sheet.getRow(row_) == null){ sheet.createRow(row_); } if (sheet.getRow(row_).getCell(col_) == null){ sheet.getRow(row_).createCell(col_); xssfCellStyle = wb.createCellStyle(); }else{ xssfCellStyle = sheet.getRow(row_).getCell(col_).getCellStyle(); } switch (borderType){ case "border-all"://全边框 xssfCellStyle.setBorderLeft(bordMap.get(style_)); //左边框 xssfCellStyle.setLeftBorderColor(color);//左边框颜色 xssfCellStyle.setBorderRight(bordMap.get(style_)); //右边框 xssfCellStyle.setRightBorderColor(color);//右边框颜色 xssfCellStyle.setBorderTop(bordMap.get(style_)); //顶部边框 xssfCellStyle.setTopBorderColor(color);//顶部边框颜色 xssfCellStyle.setBorderBottom(bordMap.get(style_)); //底部边框 xssfCellStyle.setBottomBorderColor(color);//底部边框颜色 break; case "border-left"://左边框 if (col_ != colBegin){break;} xssfCellStyle.setBorderLeft(bordMap.get(style_)); //左边框 xssfCellStyle.setLeftBorderColor(color);//左边框颜色 break; case "border-right"://右边框 if (col_ != colEnd){break;} xssfCellStyle.setBorderRight(bordMap.get(style_)); //右边框 xssfCellStyle.setRightBorderColor(color);//右边框颜色 break; case "border-top"://上边框 if (row_ != rowBegin){break;} xssfCellStyle.setBorderTop(bordMap.get(style_)); //顶部边框 xssfCellStyle.setTopBorderColor(color);//顶部边框颜色 break; case "border-bottom"://下边框 if (row_ != rowEnd){break;} xssfCellStyle.setBorderBottom(bordMap.get(style_)); //底部边框 xssfCellStyle.setBottomBorderColor(color);//底部边框颜色 break; case "border-outside"://外部边框 if (col_ == colBegin){ xssfCellStyle.setBorderLeft(bordMap.get(style_)); //左边框 xssfCellStyle.setLeftBorderColor(color);//左边框颜色 } if (col_ == colEnd){ xssfCellStyle.setBorderRight(bordMap.get(style_)); //左边框 xssfCellStyle.setRightBorderColor(color);//左边框颜色 } if (row_ == rowBegin){ xssfCellStyle.setBorderTop(bordMap.get(style_)); //左边框 xssfCellStyle.setTopBorderColor(color);//左边框颜色 } if (row_ == rowEnd){ xssfCellStyle.setBorderBottom(bordMap.get(style_)); //左边框 xssfCellStyle.setBottomBorderColor(color);//左边框颜色 } break; case "border-inside"://内侧边框 if (col_ != colBegin){ xssfCellStyle.setBorderLeft(bordMap.get(style_)); //左边框 xssfCellStyle.setLeftBorderColor(color);//左边框颜色 } if (col_ != colEnd){ xssfCellStyle.setBorderRight(bordMap.get(style_)); //左边框 xssfCellStyle.setRightBorderColor(color);//左边框颜色 } if (row_ != rowBegin){ xssfCellStyle.setBorderTop(bordMap.get(style_)); //左边框 xssfCellStyle.setTopBorderColor(color);//左边框颜色 } if (row_ != rowEnd){ xssfCellStyle.setBorderBottom(bordMap.get(style_)); //左边框 xssfCellStyle.setBottomBorderColor(color);//左边框颜色 } break; case "border-horizontal"://内侧水平边框 if (row_ == rowEnd || row_ == rowBegin){break;} xssfCellStyle.setBorderTop(bordMap.get(style_)); //顶部边框 xssfCellStyle.setTopBorderColor(color);//顶部边框颜色 xssfCellStyle.setBorderBottom(bordMap.get(style_)); //底部边框 xssfCellStyle.setBottomBorderColor(color);//底部边框颜色 break; case "border-vertical": //内侧垂直边框 if (col_ == colEnd || col_ == colBegin){break;} xssfCellStyle.setBorderLeft(bordMap.get(style_)); //左边框 xssfCellStyle.setLeftBorderColor(color);//左边框颜色 xssfCellStyle.setBorderRight(bordMap.get(style_)); //右边框 xssfCellStyle.setRightBorderColor(color);//右边框颜色 break; } XSSFCell cell = sheet.getRow(row_).getCell(col_); cell.setCellStyle(xssfCellStyle); }
6.LuckysheetController
- 注意如下的
List<JSONObject> lists = jfGridFileGetService.getAllSheetByGridKey(gridKey);
该方法是官方提供,用来下载文档的所有数据。 - 注意如下的
ExcelUtils.exportLuckySheetXlsx(exceldata,request,response,fileName);
@Slf4j
@RestController
@RequestMapping("luckysheet")
@Tag(name = "表格文档相关接口")
public class LuckysheetController {
@Autowired
private JfGridFileGetService jfGridFileGetService;
@Autowired
private ProjectService projectService;
/*
* 以下是使用第三方框架导出。
* https://blog.csdn.net/zzq10066/article/details/110424977。
* 实现了字体、边框、图片、数据验证(部分)、行列冻结、样式
*/
@Operation(summary = "下载项目对象的文档")
@Parameters({@Parameter(name = "项目ID")})
@GetMapping("download")
public void download(HttpServletResponse response, HttpServletRequest request, long projectID) {
Project project = projectService.getProjectByID(projectID);
String gridKey = project.getGridKey();
String fileName = DateUtil.today() + project.getName();
List<JSONObject> lists = jfGridFileGetService.getAllSheetByGridKey(gridKey);
//去除luckysheet中 
 的换行
String exceldata = JSON.toJSONString(lists).replace("
", "\\r\\n");
ExcelUtils.exportLuckySheetXlsx(exceldata,request,response,fileName);
}
}
7.前端使用
-
注意要用浏览器来访问这个地址。用jquery.get()方法没办法直接弹出下载,
function saveToXlsFromService() { let downUrl = "luckysheet/download?projectID=" + project.id; window.location.href=downUrl; }
网友评论