当前位置: 首页 > news >正文

蓝凌EKP产品:JSP 项目性能基于业务维度的 JS 压缩合并方案优化实战

一、业务背景

蓝凌的OA系统是一个典型的功能强大,业务使用广泛的系统,蓝凌的OA可以随便拖拉拽配置复杂的表单,比如在流程管理模块配置一个完整的合同审批流,整个合同流程可以通过第三方系统发起合同,有完整的接口提供。在合同审批中,可以通过机器人节点完成和第三方系统交互,推送数据,从第三方抓取数据审查合同数据。还能通过EKP的最新AI功能,在合同审批节点完成对合同的数据比对和分析,大量节省人力。但是为了满足复杂的功能,开发一个页面时也需要引用第三方库完成复杂的页面交互。有时经常需要加载几十甚至上百个 JavaScript 文件。此工具还提供扩展接口,方便项目做强大定制,只需要按照扩展点实现,引入扩展点即可,在自己的扩展业务里面做好替代的引入压缩js,对业务定制功能性能也有保障。

比如:

  • 流程审批页面:需要引入公用的工具库、表单校验、流程图组件、权限控制脚本;

  • 报表统计页面:需要引入图表库、数据处理脚本、导出功能;

  • 门户首页:需要引入各种小部件 JS、消息中心脚本、公告模块脚本。

结果就是:
👉 一个页面加载时可能触发 几百个 JS 请求

这带来了几个问题:

  • 请求数过多,浏览器并发限制导致脚本队列排队;

  • TCP 连接和 HTTP 头开销严重;

  • 页面白屏时间长,用户体验差。

于是我们决定:按业务功能维度合并压缩 JS,用一个请求替代几十个请求。


二、解决思路

1. 按业务功能分类

我们不是简单地把所有 JS 合并成一个“巨无霸”文件,而是按照业务场景来划分:

  • 公共库 JS(所有页面必用,比如 jQuery、工具函数)

  • 流程审批 JS(审批页面特有脚本)

  • 自定义表单JS(拖拉拽的表单)

  • 报表统计 JS(图表处理、导出相关脚本)

  • 门户首页 JS(公告、消息、模块拼装脚本)

这样做的好处:

  • 保证通用库只需加载一次;

  • 业务模块按需加载,避免无关脚本浪费。

2. 实现原理

定义一个扩展点,EKP的扩展点功能强大,底层实现完全基于解耦合,方便各个业务模块扩展,可以做到代码的零耦合,业务要扩展直接在业务端增加配置即可实现业务的功能增强。

声明的扩展点

<extension-point id="com.landray.kmss.sys.ui.compressExecutor"><item name="executor"><param name="unid"required="true"kind="unid"><description>唯一标识</description></param><param name="bean"basedOn="com.landray.kmss.sys.ui.service.ISysUiCompressExecutor"required="true"kind="spring"><description>压缩合并js执行接口的具体实现类</description></param><description>js合并压缩扩展点,在门户维护参数>参数配置中开启压缩合并js时执行</description></item><description>js合并压缩执行配置</description></extension-point>

实现的扩展点

    <extension point="com.landray.kmss.sys.ui.compressExecutor"model="com.landray.kmss.sys.ui.service.ISysUiCompressExecutor"><item name="executor"><param name="unid" value="sysUiCompressExecutor" /><param name="bean" value="sysUiCompressExecutor"/></item></extension>

        

sysUiCompressExecutor  这个就是业务实现的功能,合并业务的js,此处就是按业务功能维度合并整理的合并JS文件。
合并的js 分类整理和命名

这些工具可以:

  • 自动合并多个 JS;

  • 去掉空格、注释;

  • 变量名缩短,减少体积;

  • 输出一个新的 .min.js 文件。

3. 页面引用替换

  • 原来:页面引用几十个 JS 文件;

  • 优化后:页面只引用一个合并压缩后的 JS 文件。


三、实现步骤

1. 业务模块归类

示例项目目录:

 "sys/ui/js/parser.js", "sys/ui/js/dialog.js", "sys/ui/js/util/str.js","sys/ui/js/topic.js", "sys/ui/js/Evented.js", "sys/ui/js/view/layout.js","sys/ui/js/base.js", "sys/ui/js/util/env.js", "sys/ui/js/overlay.js","sys/ui/js/dialog/actor.js", "sys/ui/js/dialog/trigger.js", "sys/ui/js/dialog/content.js","sys/ui/js/dragdrop.js", "sys/ui/js/util/crypto.js", "sys/ui/js/Class.js","sys/ui/js/view/Template.js", "sys/ui/js/util/loader.js", "sys/ui/js/toolbar.js","sys/ui/js/element.js", "sys/ui/js/suspend.js", "sys/ui/js/spa/directions/ifDirection.js","sys/ui/js/spa/directions/authDirection.js", "sys/ui/js/spa/directions/mapDirection.js", "sys/ui/js/spa/const.js","sys/ui/js/spa/Spa.js", "sys/ui/js/spa/router.js", "sys/ui/js/spa/values.js","sys/ui/js/spa/direction.js", "sys/ui/js/spa/router/hash.js", "sys/ui/js/framework/router/router-utils.js","sys/ui/js/framework/router/router.js", "sys/ui/js/framework/router/const.js", "sys/ui/js/framework/router/utils/create-route-map.js",//增加一些新公共js的提取jsHead.jsp"sys/ui/js/imageP/preview.js", "sys/ui/js/imageP/ImageP.js", "sys/ui/js/imageP/Play.js", "sys/ui/js/imageP/Panel.js","sys/ui/js/imageP/Thumb.js", "sys/ui/js/imageP/Value.js", "sys/ui/js/imageP/Path.js", "sys/ui/js/imageP/Toolbar.js","sys/ui/js/menu.js", "sys/ui/js/view/render.js", "sys/ui/js/switch.js", "sys/ui/js/listview/columntable.js","sys/ui/js/listview/paging.js", "sys/ui/js/popup.js", "sys/ui/js/spa/adapters/menuSourceAdapter.js", "sys/ui/js/spa/adapters/menuItemAdapter.js","sys/ui/js/spa/adapters/menuAdapter.js", "sys/authentication/identity/js/auth.js", "sys/ui/js/qrcode/qrcode.js"

原理一个页面针对某一个业务要请求几十个js 文件。

规划合并后:

  • common.min.js → 公共库

  • flow.min.js → 流程审批模块

  • report.min.js → 报表模块

  • portal.min.js → 门户首页模块

2. 使用 Java 压缩工具

YUI Compressor 为例:

public static void compressOneJS(final JSContext ctx) throws EvaluatorException, IOException {final Reader in = ctx.getIn();final Writer out = ctx.getOut();try {JavaScriptCompressor compressor = new JavaScriptCompressor(in, new ErrorReporter() {@Overridepublic void warning(String message, String sourceName, int line, String lineSource, int lineOffset) {if (line < 0) {logger.debug(message);} else {logger.debug("" + line + ':' + lineOffset + ':' + message);}}@Overridepublic void error(String message, String sourceName, int line, String lineSource, int lineOffset) {String file = "[ERROR: " + ctx.getFile() + "]";if (line < 0) {logger.error(file + ", " + message);throw new Error("\n[ERROR] " + message);} else {logger.error(file + ", " + "第" + line + "行出错:" + message + "\n" + lineSource);throw new Error("\n[ERROR] 第" + line + "行出错:" + message + "\n" + lineSource);}}@Overridepublic EvaluatorException runtimeError(String message, String sourceName, int line, String lineSource,int lineOffset) {error(message, sourceName, line, lineSource, lineOffset);return new EvaluatorException(message);}});boolean munge = ctx.isMunge();boolean preserveAllSemiColons = ctx.isPreserveAllSemiColons();boolean disableOptimizations = ctx.isDisableOptimizations();boolean verbose = ctx.isVerbose();// 关闭详细信息(因为可能有大量警告信息)int linebreakpos = ctx.getLinebreakpos();// 无断行compressor.compress(out, linebreakpos, munge, verbose, preserveAllSemiColons, disableOptimizations);} catch (Throwable t) {logger.error("JS资源压缩错误", t);throw new RuntimeException(t);} finally {IOUtils.closeQuietly(in);IOUtils.closeQuietly(out);}}

执行后,dist/flow.min.js 就是压缩合并好的文件。

然后在业务模块使用即可。

3. 页面替换引用

原始页面:

	<script type="text/javascript" src='${LUI_ContextPath}/resource/js/domain.js?s_cache=${ LUI_Cache }'></script><script type="text/javascript" src='${LUI_ContextPath}/sys/ui/js/LUI.js?s_cache=${ LUI_Cache }'></script><script type="text/javascript" src="${LUI_ContextPath}/resource/js/common.js?s_cache=${ LUI_Cache }"></script><script type="text/javascript" src="${LUI_ContextPath}/resource/js/sea.js?s_cache=${ LUI_Cache }"></script>

优化后:主要增加了压缩合并开关功能,提供用户多重选择。

<c:choose><c:when test="${compressSwitch eq 'true' && lfn:jsCompressEnabled('sysUiCompressExecutor', 'jsHead_com_combined')}"><script src="<%= PcJsOptimizeUtil.getScriptSrcByExtension("sysUiCompressExecutor","jsHead_com_combined") %>?s_cache=${ LUI_Cache }"></script></c:when><c:otherwise><script type="text/javascript" src='${LUI_ContextPath}/resource/js/domain.js?s_cache=${ LUI_Cache }'></script><script type="text/javascript" src='${LUI_ContextPath}/sys/ui/js/LUI.js?s_cache=${ LUI_Cache }'></script><script type="text/javascript" src="${LUI_ContextPath}/resource/js/common.js?s_cache=${ LUI_Cache }"></script><script type="text/javascript" src="${LUI_ContextPath}/resource/js/sea.js?s_cache=${ LUI_Cache }"></script></c:otherwise>
</c:choose>

四、优化效果

  • 请求数对比

    • 优化前:一个页面 100+ 请求

    • 优化后:一个页面 < 10 请求

  • 加载速度

    • 首屏渲染加快 40%+

    • 移动端用户体验提升明显

  • 维护性

    • 仍然保持模块化,开发时引用原始 JS,打包时生成压缩文件;

    • 不会出现一个大文件难以维护的情况。


五、总结

这次优化的核心思路是:

  • 业务场景 出发划分 JS 模块;

  • 使用 Java 后台压缩工具 合并压缩,避免引入前端复杂构建链路;

  • 页面只需引用少量合并后的 JS 文件,大幅减少请求数。

最终,我们把“一个页面几百个 JS 请求”优化成了“按业务划分的几个请求”,极大改善了用户体验。

这套方案特别适合 传统 OA项目,既能结合现有业务结构,又能保证性能提升,值得在类似项目里推广。

http://www.lryc.cn/news/625808.html

相关文章:

  • 供水设备智慧化管理物联网解决方案:远程监控与运维
  • 操作系统:多线程、进程管理、内存分配、任务调度等
  • IC验证 AHB-RAM 项目(二)——接口与事务代码的编写
  • 比赛准备之环境配置
  • Nginx前后端分离反代(VUE+FastAPI)
  • 卫生许可证识别技术:通过OCR与NLP实现高效合规管理,提升审核准确性与效率
  • Apache IoTDB 大版本升级记录(成熟的2.0.2版本)
  • 汇编语言学习2---GNU Debugger (GDB)
  • PiscCode迅速集成YOLO-Pose 实现姿态关键点轨迹跟踪应用
  • 疏老师-python训练营-Day50预训练模型+CBAM注意力
  • PHP如何使用JpGraph生成折线图?
  • NVIDIA 优化框架:Jetson 平台 PyTorch 安装指南
  • vue,H5车牌弹框定制键盘包括新能源车牌
  • 楼宇自控系统的应用,已然成为智能建筑行业发展方向
  • 【网络运维】Playbook部署文件:Files模块库&JINJA2模板
  • 18650锂电池自动化生产线:智能集成提升制造效能
  • Qt猜数字游戏项目开发教程 - 从零开始构建趣味小游戏
  • 厚板数控矫平机的“第三堂课”——把视角拉远,看看它如何重塑整条制造链
  • AUTOSAR进阶图解==>AUTOSAR_SWS_FlashEEPROMEmulation
  • 星链之供应链:SpaceX供应链韧性密码,70%内部制造+模块化设计,传统航天企业如何追赶?
  • 数字孪生 :提高制造生产力的智能方法
  • 写代码的方式部署glm-4-9b-chat模型:gradio和api两种模式
  • python学习DAY46打卡
  • Apache ECharts 6.0.0 版本-探究自定义动态注册机制(二)
  • npm下的scratch(少儿编程篇)
  • 使用segment-anything将目标检测label转换为语义分割label
  • 零售行业新店网络零接触部署场景下,如何选择SDWAN
  • 【Proteus仿真】【51单片机】基于51单片机自动售货机12864屏幕
  • ICCV 2025 | 首个3D动作游戏专用VLA模型,打黑神话只狼超越人类玩家
  • 如何免费给视频加字幕