2022-05-15_luckysheet导出xls文件(第三方版)

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中 &#xA 的换行
        String exceldata = JSON.toJSONString(lists).replace("&#xA;", "\\r\\n");
        ExcelUtils.exportLuckySheetXlsx(exceldata,request,response,fileName);
    }
}

7.前端使用

  • 注意要用浏览器来访问这个地址。用jquery.get()方法没办法直接弹出下载,

  • function saveToXlsFromService() {
        let downUrl = "luckysheet/download?projectID=" + project.id;
        window.location.href=downUrl;
    }
    

附录

1.Java poi导出LuckySheet在线表格

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,607评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,239评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,960评论 0 355
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,750评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,764评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,604评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,347评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,253评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,702评论 1 315
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,893评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,015评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,734评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,352评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,934评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,052评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,216评论 3 371
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,969评论 2 355

推荐阅读更多精彩内容