O2OA是一个基于J2EE分布式架构,集成移动办公、智能办公,支持私有化部署,自适应负载能力的,能够很大程度上节约企业软件开发成本的基于AGPL协议开放源代码的企业信息化系统需求定制开发平台解决方案。O2OA对外提供专业的开发运维等技术服务。
开源O2OA官网:http://www.o2oa.net/
下文是O2OA前端性能优化的教程干货:
O2OA是一个SPA应用,相对与传统的多页应用,前端的代码量比较大,用户进行二次开发也主要是前端的脚本,本文中,我们来讨论一下与页面展现性能相关的一些内容。(数据库和服务器的优化不在本文讨论范围内)。本文中的内容需要O2OA 5.3 及以上版本支持。
缓存
客户端的缓存设置
一般情况下,现代浏览器都默认支持http协议的缓存策略,我们需要注意IE10和IE11,在“网站数据设置”中推荐使用“自动”。
如果您将O2OA通过webview嵌入到自己的移动端APP中时,注意使用webview的默认缓存策略。(IOS:NSURLRequestUseProtocolCachePolicy;Android:LOAD_DEFAULT)
Max-Age
为了浏览器可以直接从缓存中获取静态文件,可以设置静态资源响应头的Cache-Control的Max-Age值。在服务器config/node_xxx.xxx.xxx.xxx.json文件中,配置web中的cacheControlMaxAge字段,以秒为单位,配置静态资源本地缓存的时间。如:
{ "web": { "cacheControlMaxAge": 86400, }, }
配置后需要重启服务器生效。
Etag
一些服务请求,在响应头中会有Etag,客户端收到带有Etag响应头的响应后,会把内容和Etag值缓存下来,下次发起同一个请求时,会带上请求头If-None-Match,值为上一次收到的Etag值,服务器收到请求后,会计算出Etag值,并与请求的If-None-Match值比较,如果相同,则不需要返回内容,返回304,告诉浏览器从本地缓存获取内容即可。如果两值不匹配,说明要请求的内容已经修改过,需要重新下载。
所以,我们系统在经过一系列网络设备,如负载设备和Web应用防火墙(WAF)等,请确保这些设备不对请求的Etag响应头进行修改。
静态资源缓存服务器
我们可以把web服务器部署到其他静态资源的web服务器,也可以通过nginx等配置静态资源缓存。
服务器目录下servers/webServer/下的所有文件,拷贝到其他web服务器的web目录,修改x_desktop/res/config/config.json文件中的配置即可:
{ "center": [ { "port": "20030", //O2服务器中心服务器端口 "host": "develop.o2oa.net" //O2服务器中心服务器Host } ], "applicationServer": { "host": "develop.o2oa.net" //O2应用服务器Host }, "initManagerChanged": true, "initManagerName": "", }
此文件主要修改两部分内容:
1、center部分,修改为要访问的O2中心服务器地址和端口;
2、applicationServer部分,修改为要访问的O2应用服务器地址,如果没有applicationServer,就添加一个。集群环境下,可配置应用服务器负载地址;
不发起option请求
默认情况下O2OA服务器启用三个端口80、20020和20030,分别对应web服务、应用服务和center服务。由于使用不同端口,在浏览器请求时,会先发送option请求来判断跨域访问策略。见下图:
方法一
我们可以按以下配置,使请求同源,就不再需要发送option请求。
在服务器config/node_xxx.xxx.xxx.xxx.json文件中,配置web中的proxyApplicationEnable字段为true,将application和center中的proxyPort配置为和web端口一致(默认为80),将proxyHost配置为用户访问的域名。
{ "center": { "proxyHost": "develop.o2oa.net", "proxyPort": 80, }, "application": { "proxyHost": "develop.o2oa.net", "proxyPort": 80,", }, "web": { proxyApplicationEnable: true, }, }
配置后重启服务器后,就不会有option请求发起。
方法二
我们也可以通过负载设备的http代理配置来避免发送option请求,以nginx为例,我们可以进行如下配置:
在nginx配置中,通过配置不同路径来映射到O2OA服务器的web、application和center。
listen 80; server_name develop.o2oa.net; location /web/ { proxy_pass http://xxx.xxx.xxx.xxx:80/; } location /app/ { proxy_pass http://xxx.xxx.xxx.xxx:20020/; } location /center/ { proxy_pass http://xxx.xxx.xxx.xxx:20030/; }
然后在O2OA服务器config/portal.json文件中配置urlMapping,如下:(如果在config目录下没有portal.json文件,可以从configSample目录下拷贝一个过来)
{ "urlMapping": { "develop.o2oa.net:20020": "develop.o2oa.net/app", "develop.o2oa.net:20030": "develop.o2oa.net/center" }, }
重启nginx和O2OA服务器后,我们可以用 http://develop.o2oa.net/web 访问系统。此方法通过nginx将不同路径的请求代理到O2OA服务器的不同端口,浏览器只使用80端口访问,避免的跨域请求,也不会发起option请求。
获取服务地址请求
默认情况下,访问系统页面时,会发起若干个获取服务器地址的请求,地址如:http://develop.o2oa.net:20030/x_program_center/jaxrs/distribute/assemble/source/develop.o2oa.net。
此服务会返回一组服务列表,后续的请求都会使用此列表中的host和port。这样我们就可以在集群环境下,根据服务器策略,给不同的用户分配不同的服务地址。
假设我们有两台服务器:server1和server2,端口配置都为默认,组成了O2OA的集群,我们可以查看/x_desktop/res/config/config.json文件,服务器自动生成了center服务器地址配置:
{ "center": [ { "port": "20030", "host": "" }, { "port": "20030", "host": "server1" }, { "port": "20030", "host": "server2" } ], }
此时,浏览器会同时发起三个获取服务地址的请求:
1、http://(用户访问系统的域名):20030/x_program_center/jaxrs/distribute/assemble/source/(用户访问系统的域名);
2、http://server1:20030/x_program_center/jaxrs/distribute/assemble/source/server1
3、http://server2:20030/x_program_center/jaxrs/distribute/assemble/source/server2
其中只要有一个服务成功返回数据,其他服务就会取消,系统就会使用返回的服务地址列表进行后续访问,返回的数据格式如下:
{ "type": "success", "data": { "x_portal_assemble_designer": { "name": "门户设计", "host": "server1", "port": 20020, "context": "/x_portal_assemble_designer" }, "x_portal_assemble_surface": { "name": "门户", "host": "server1", "port": 20020, "context": "/x_portal_assemble_surface" }, "x_query_assemble_surface": { "name": "数据查询", "host": "server1", "port": 20020, "context": "/x_query_assemble_surface" }, "x_file_assemble_control": { "name": "云文件", "host": "server1", "port": 80, "context": "/x_file_assemble_control" }, }, "message": "", "date": "2020-11-25 16:14:58", "spent": 0, "size": -1, "count": 0, "position": 0 }
其中每一个服务的host是服务器根据策略分配的。所以不同用户或这个每次访问会分配到不同的服务地址,以此平衡服务器压力。
但大多数情况下,我们通过都会nginx配置来搭建负载均衡集群,那此时,我们的获取到的服务地址列表始终是指向nginx服务器的。此时再去动态获取服务地址列表,就显得没有必要了。
所以我们可以手工配置所有服务地址,这样浏览器就不需要发起获取服务地址的请求了。在服务器config/web.json文件中,添加以下配置(如果在config目录下没有web.json文件,可以从configSample目录下拷贝一个过来):
{ "configMapping": { //此处的server1是用户访问系统时所用的域名。 "server1": { "center": { "host": "center服务器域名", "port": "20030" }, //servers下配置所有系统服务的地址, //可以通过http://center服务器:20030/x_program_center/jaxrs/distribute/assemble/source/server1 //这个请求来获取,返回的data中的内容 "servers": { "x_portal_assemble_designer": { "name": "门户设计", "host": "app.cqmc.com", "port": 8085, "context": "/x_portal_assemble_designer" }, } }, //如果有多个域名可以访问本系统,可以配置多组。 "server2": { } } }
配置完成后重启服务器,再次访问系统,就不会发起获取服务地址的请求了。
脚本预加载
在设计表单和页面时,我们经常会引用脚本库中的设计,在以前的版本中,我们会使用this.include方法引用脚本,这样会向服务器发起请求,在请求完成后再执行其他脚本。如果在页面载入的时候,需要引入的脚本过多时,必然会拖慢页面加载速度。
举例说明:在以前的代码中,我们很多时候都是include一个脚本,运行一部分代码,再include一部分脚本,再运行一部分代码,这样就要求include的脚本同步加载,阻塞页面载入,如以下代码:
//在表单的queryLoad事件中的代码 this.include("script1"); this.script1Function(); this.include("script2"); this.script2Function(); this.include("script3"); this.script3Function();
在表单的queryLoad事件中有以上代码,并有三个脚本库script1、script2和script3,分别定义三个方法:
//script1 this.define("script1Function", function(txt){ console.log("script1 function is run"); });
//script2 this.define("script2Function", function(txt){ console.log("script2 function is run"); });
//script3 this.define("script3Function", function(txt){ console.log("script3 function is run"); });
当我们展现这个表单时,通过浏览器开发工具的网络监控可以看到:
三个引入脚本是顺序加载的,阻塞了页面载入,导致页面展现速度变慢,我们可以通过以下两种方法来解决此问题。
页面配置预加载脚本
在5.3版本开始,我们在设计表单和页面时,可以配置需要预加载的脚本,只要选择在页面载入时需要加载的脚本即可,不需要再使用this.include方法加载了。但要注意的是,如果脚本有依赖关系,必须按照一定的顺序选择脚本。
在上面的例子中,我们只需要将要引入的脚本,选择到表单属性的“预加载脚本”中。
然后修改queryLoad代码:
//在表单的queryLoad事件中的代码 //脚本已经预加载,不需要通过this.include载入了 this.script1Function(); this.script2Function(); this.script3Function();
再次展显此表单时,方法正常运行,但没有了引入脚本的http请求了。
异步加载
我们也可以通过异步加载脚本,来加快页面展现速度,我们将上例中的queryLoad事件代码做如下改动:
//在表单的queryLoad事件中的代码 this.include(["script1","script2","script3"], function(){ this.script1Function(); this.script2Function(); this.script3Function(); }.bind(this), true);
通过设置this.include方法的第二个个参数为回调函数,这样第一个参数中的数组所指向的脚本会进行异步加载,加载完成后,运行回调函数。
再次展显此表单时,我们会看到方法正常运行,通过浏览器开发工具的网络监控可以看到:
三个请求同时运行,完成后运行脚本中的方法,不阻塞页面展现。
但上面的方法存在一个问题,就是在queryLoad事件中异步加载脚本,不会使页面展现阻塞,此时页面加载继续执行,如果我们在页面的其他事件或组件默认值等脚本中,使用了要加载的脚本中定义的方法时,就会出现错误,所以如果有这样的情况,我们需要在queryLoad事件中异步加载脚本时,暂停页面载入,等脚本加载完成后,再继续载入页面。我们修改上面的queryLoad事件代码如下:
//在表单的queryLoad事件中的代码 //暂停页面加载,并获取要恢复页面加载的回调方法 var resolve = this.wait(); this.include(["script1","script2","script3"], function(){ this.script1Function(); this.script2Function(); this.script3Function(); //异步加载完成,获取回调方法 resolve.cb(); //回调方法继续展现页面 }.bind(this), true);
异步加载的方法不但适用脚本加载,也同样适用数据字典或其他异步请求的数据获取,可以通过异步方式获取所有需要的数据后,再加载表单,这样可以有效避免同步请求阻塞页面。请看下面的例子:
//假设在表单加载时,我们会使用到若干脚本、几个数据字典、以及通过服务获取的数据 //可以让这些请求异步执行,所有数据准备好以后,再继续展现表单 //在表单的queryLoad事件中的代码 //暂停页面加载,并获取要恢复页面加载的回调方法 var resolve = this.wait(); //加载脚本的Promise var scriptPromise = new Promise(function(resolve, reject){ this.include(["script1","script2","script3"], function(){ resolve(); }.bind(this), true); }.bind(this)); //加载通过服务器请求的数据 //此处以获取当前用户最新10条待办为例 //Actions的方法返回Promise对象 var dataPromise = this.Actions.load("x_processplatform_assemble_surface").TaskAction.V2ListPaging(1,10); //加载第一个数据字典 //get方法第二个参数表示异步,也可以是一个回调函数,返回Promise对象 var dict1Promise = (new this.Dict("dict1")).get("path", true); //加载第二个数据字典 var dict2Promise = (new this.Dict("dict2")).get("path",true); //通过Promise.all方法确保所有资源加载 Promise.all([scriptPromise, dict1Promise, dict2Promise, dataPromise]).then(function(values){ //获取到的数据赋值,以备后续使用 this.dict1Data = values[1]; this.dict2Data = values[2]; this.taskData = values[3].data; resolve.cb(); //继续加载表单 }.bind(this)).catch(function(){ //即使资源加载有错,也继续加载表单 resolve.cb(); });
再次展显此表单时,通过浏览器开发工具的网络监控可以看到:
所有请求同时发起,减少了页面阻塞时间。
组件延时加载
表单和页面的Tab页组件和子表单组件,具有“延时加载”选项。
Tab页组件建议“延时加载”选项始终选“是”。
对于子表单,首先建议尽可能不用计算子表单。然后“延时加载”选项选:“是”,然后在需要子表单展现时,使用以下代码激活:
this.form.get("subform").active(function(){ //子表单加载完成后的回调方法 });
组件默认值异步处理
5.3版本开始支持表单组件默认值通过Promise进行异步处理,有关详细内容可查看文档:
https://www.yuque.com/o2oa/rf2zrv/ws07m0
想要了解更多开源O2OA办公开发平台,欢迎随时访问官网。