功能扩展&性能篇

葡萄城推出的类 Excel全栈解决方案主要用于应对复杂公式的计算场景,可帮助开发者搭建前后端负载均衡的Web应用程序。该方案以纯前端表格控件 SpreadJS 为视图层,可在浏览器中直接编辑表格数据;以GcExcel为后端逻辑层,在服务端解析Excel 文档,无需依赖 Office 软件和 POI组件,即可实现更为高效的电子表格功能。

在前两篇文章中,我们介绍了全栈表格技术在应对复杂公式计算场景时的设计思路和示例代码,本期我们将对该代码进行调优,并借助 SpreadJS 和 GcExcel 的API实现功能扩展。

功能调优 & 扩展

待优化项目:由于SpreadJS会默认开启公式自动计算的功能,因此该示例会在每次切换sheet的时候自动更新公式的计算结果。为了进一步提升性能,我们需要关闭该功能,即当我们输入一个新的公式,或者去修改已有公式引用导致公式发生变化时,公式不会进行计算或重算。除此之外,我们还需自行设置一个强制计算的按钮,当点击该按钮时,GcExcel会在服务端执行公式计算,并将计算后的结果返回前端页面渲染,效果类似于Excel的“开始计算功能”。

优化思路:通过强制计算按钮来避免前端无意义的公式计算过程,然后利用GcExcel在后端执行更高性能的公式计算,从而达到最合理的资源分配。

示例代码:

document.getElementById("calc").onclick = function(){
                    var currentSheet = spread.getActiveSheet();
                    var spreadDirtyCells = {};
                    for(var i=0;i<spread.getSheetCount();i++){
                            var sheet = spread.getSheet(i);
                            var sheetName = sheet.name();
                            spreadDirtyCells[sheetName] = sheet.getDirtyCells();

                    }
                    var calc = {
                                    currentSheet:currentSheet.name(),
                                dirtyCells:spreadDirtyCells
                }

                    $.ajax({
                            url: "getCalcResult",
                            type:"POST",
                            data:JSON.stringify(calc),
                            contentType: 'application/json',
                            async:false,
                            success: function (data) {
                                    if (data != null) {
                            var json = ungzipString(data);
                            json = JSON.parse(json);
                            currentSheet.fromJSON(json);
                        }
                            }
                    });

            }

当用户在前端页面点击计算按钮后,可以获取当前sheet中所有的dirtyCells并传给后端,后端拿到dirtyCells之后在GcExcel解析并填入:

@RequestMapping(value = "/getCalcResult", method = RequestMethod.POST)
        @ResponseBody
        public String getCalcResult(@RequestBody Calc calc) {
                IWorksheet currentSheet = workbook.getWorksheets().get(calc.getCurrentSheet());
                Map<String,Object> map = calc.getDirtyCells();
                for (Map.Entry<String, Object> entry : map.entrySet()) {
                        String key = entry.getKey();
                        IWorksheet dirtySheet = workbook.getWorksheets().get(key);
                        List<Map<String,Object>> sheetDirtys = (List<Map<String, Object>>) entry.getValue();
                        for(int i=0;i<sheetDirtys.size();i++) {
                                Map<String,Object> dirtyCell = sheetDirtys.get(i);
                                int row = (int) dirtyCell.get("row");
                                int col = (int) dirtyCell.get("col");
                                dirtySheet.getRange(row, col).setValue(dirtyCell.get("newValue"));
                        }
                }
                workbook.setEnableCalculation(true);
                String result = null;
                if(currentSheet!=null) {
                        result = currentSheet.toJson();
                        result = GZip.compress(result);        
                }
                return result;
        }

填入计算结果后,GcExcel还会通过设置workbook.setEnableCalculation(true) ,让整个workbook重新计算一遍以获取正确的结果,最后通过sheet.toJson() 将结果返回给前端。

性能测试:下面我们来测试一下优化后的程序性能。这里随机选取了一份测试文件,该文件包含了很多复杂公式 (测试文件会在下方提供)。

1、用SpreadJS直接导入该文件,测试一下花费的时间(作者的测试机器大约需要花费12s左右)。

2、我们使用前后端结合的方式来测试一下,结果花费不超过1s。

3、当我们修改"INPUT"这个sheet中的填报内容,然后切换到"OUTPUT"sheet中查看结果时,SpreadJS在每一步操作后都会自动执行公式计算,总共花费大约10s左右。

4、使用上文提到的优化方案,在每一步操作后不自动执行公式计算,结果总共花费不到1s。

PS:由此可见,这一优化措施将该示例的处理性能提升了大约10倍左右。

本方案的相关代码及测试文件:https://gitee.com/GrapeCity/spread-jsgcexcel-formula2.git

以上便是全栈表格技术(SpreadJS + GcExcel)应对复杂公式计算场景的性能调优和功能扩展部分,除了 SpreadJS 和 GcExcel 自身的组件功能,这里还用到了缓式加载、异步函数、序列化和反序列化等开发技巧,经过性能调优后的示例代码已经完全可以满足各类复杂数据的计算场景需要。

如果您希望进一步了解类 Excel 全栈解决方案的应用场景和案例,欢迎点击此处访问。