SkyForm CMP产品UI模块化实践

■ 文/ 天云软件 前端工程师  王培培

一、概述

SkyForm CMP是一个开放、中立的企业级云管理平台sf-ui是针对它的UI框架。早期的UI框架中使用jquery+bootstrap进行开发,以每个菜单为编码单位进行代码上的隔离,方便了任务的分配,但同时也造成框架配置项激增,页面风格不够统一。由于每个人只负责自己的菜单项,导致每个人都不想接手其它人的代码,框架的后期维护越发困难。

为了解决这些问题、优化项目,在后台采用了微服务模式进行开发后,通过查阅资料,决定采用模块化的思想对sf-ui框架进行重构,减少配置项、统一风格,减轻工程师重复编码的工作和项目的维护成本。

为了解决以上提到的问题,这里对新的UI框架sf-ui进行了以下思考:

1、选用angularjs+bootstrap进行编码开发。选用angularjs是因为它是纯客户端技术,本身封装了一些低级操作,包括:DOM操作、事件监听、输入验证等,能让开发者更多地关注业务逻辑,同时angularjs还提供了数据与视图的双向绑定、依赖注入等技术。这些都极大地方便了前端实现模块化开发,前端的模块划分从菜单转变成业务。

2、SkyForm CMP会与多个服务进行交互,这就要求sf-ui框架能统一与各服务间的交互方式,同时注意控制配置项的数量。

3、SkyForm CMP平台中有很多相似页面,页面里也有一些十分类似的功能,可以将这些共性提取出来进行封装,减少开发者的人为失误,同时提高页面风格的统一性。此外,还可以考虑自动代码生成手段,减轻开发者重复工作量。前端自动代码生成将在后续的文章中讲到,这里只关注将共性写成模块方便调用和维护。

二、解决思路及实现

2.1  与不同服务交互时请求响应不统一

云管理平台的后台服务采用了微服务模式进行开发,将每项业务做成一个微服务, sf-ui与这些服务的交互如下图所示:

UI

核心交互过程:

(1)sf-ui通过cas进行单点登录。

(2)sf-ui通过nginx进行负载均衡的配置,进而同各个服务进行数据交互。

(3)sf-ui通过浏览器调用与granafa服务进行交互。

从图中可以看出,sf-ui同时与多个服务进行交互,且与每个服务的交互方式不尽相同。随着业务的变更,服务个数和内容也会随之改变,每个服务各司其职,除了granafa服务外,sf-ui通过Restful API与这些服务进行交互。因此这里将每个服务的ajax请求分别进行封装,每个服务只有一个请求入口,所有服务的ajax响应做统一处理,提供统一的请求结果提示。下面以sf-ui的自定义模块虚拟机调用后台zvm服务为例,讲述sf-ui与后台服务的交互。

1、封装ajax调用过程,对请求的发送和接收做统一处理。

编写apiZstack service,该service中对请求发送时所需的参数和header设置做统一处理,封装get和post两种类型的请求方法,具体请参见js/api.js文件。

2、在虚拟机模块的页面中将需要调用的api接口进行封装,方便反复调用。

app.service(‘vmService’, vmService);
function vmService(apiZstack, $cookieStore, SERVICE_CONFIG, commonService) {
    var self = this;

                 //获取模块的名称
    self.ModuleName = SERVICE_CONFIG.module_vm.name

                 //获取模块中所需后台api服务的访问地址
    var VMServiceURL = SERVICE_CONFIG.vmServiceURL;    
    //配置模块所需接口的名称
    self.ResourceCMD = {
        ‘vm_delete’     : VMServiceURL + ‘/DestroyVmInstance’

                     //省略N
};

    //模块实际需要调用的function
 self.vm_delete = function(params) {

        return apiZstack.send(self.ResourceCMD.vm_delete, params);
    };

                 //省略N
    }

3、调用。

在实际使用中,只需要引入vmService服务,然后调用服务中对应的function即可,如下所示,调用删除虚拟机的接口:

vmService.vm_delete({“uuid”: deletingItemUuid}).then(function(response){
        if(response.status || response.errorCode==0){
            //返回结果显示为成功后的操作
        } else {

            //返回结果显示为失败后的操作

        }
    });
}, function () {
    $log.info(‘Modal dismissed at: ‘ + new Date());
});

2.2  配置项过多,增加维护难度

sf-ui是一个静态web项目,它依赖cas进行用户信息管理、依赖后台服务提供数据,因此在启动sf-ui之前需要保证cas和后台服务能正常使用。cas和后台服务的配置及启动不在本文档的说明范围内,这里不再赘述。

为了方便管理和调用后台服务的配置,这里将所有配置集中到少数文件中:使用了config.js文件对sf-ui整个框架中所需的配置项进行了统一处理,同时使用nginx进行转发。配置过程如下:

1、在config.js文件中配置后台服务路径,如下所示:

var app =
angular.module(‘app’)
.constant(‘SERVICE_CONFIG’, {
queryFrequency: 2000,         //
毫秒
zconfServiceURL : ‘/services/zconf’,
systemServiceURL : ‘/services/sys’,
cmdbServiceURL : ‘/services/cmdb’,
vmServiceURL : ‘/services/zvm’,
vmConsoleServiceURL : ‘/vm’,
volumeServiceURL : ‘/services/zvol’,
networkServiceURL : ‘/services/znet’,
aggregateServiceURL : ‘/services/agrg’

  //省略n
})

这里将路径统一在了常量SERVICECONFIG中,是因为angularjs中constant可以注入到config中并在任何地方调用,非常适合保存一些经常使用的数据。

2、修改nginx.conf文件实现nginx转发。注意,转发路径要和config.js中的路径名称一致,同时分配好nginx和tomcat的监听端口(这里nginx的监听端口为8080,tomat的监听端口为8088)。

后台服务调用路径和nginx转发配置完成后,将sf-ui中静态文件和对应的lib包放到tomcat中,启动tomcat后首先看到的是cas登录页面,登录后即可看到SkyForm CMP的UI界面,如下图所示:

UI02

2.3  页面相似或重复部分不统一

这里的统一包含了展现样式操作方式两部分。为了减少开发人员工作量,统一页面,将相似或重复部分做成模块进行开发。

1、展现样式

文件夹css中存放了框架中的所有样式文件,包括bootstrap.min.css、animate.css、font-awesome.min.css等第三方文件和自定义样式文件app.min.css。样式的资源文件分别位于fonts和img文件夹中。

开源文件不必提,需要统一的是自定义样式。自定义样式文件app.min.css的源码文件位于sources/style文件夹中,以less进行编写,通过gulp编译压缩后生成。源码中将页面元素分成一个个小的模块进行样式的编写,如下图所示:

UI03

模块包含按钮app.buttons.less、菜单app.nav.less、装饰框app.widgets.less等等,同时将页面颜色(app.colors.less)和变量(app.variable.less)单独写成一个文件,调整样式的时候只需修改对应的less文件然后编译即可看到所有样式跟着变化,无需修改对应的样式文件和html文件。这样做极大地方便了开发者进行样式的统一调整,减轻了美工人员调整页面的工作量。

2、通用指令服务等

整个项目中大量使用了grid、breadcrumb等组件,且查询、资源状态等拥有统一的展现形式,因此将这些通用部分也抽象成一个个小的模块,编写成合适的指令或服务,方便页面调用和代码管理。目前编写的指令包括表格指令sfUiGrid、状态指令sfState、面包屑指令sfBreadcrumb、验证ip地址格式指令checkIpFormat、资源类型过滤器resourceTypeFilter等等。指令编写完成后需要在index.html中引用以生效。

3、补充说明

为保证多系统间用户认证的统一,引入了cas实现单点登录。cas服务配置在web.xml中,如下(请注意参看注释):

<!– ======================== 单点登录开始 ======================== –>
<!–
用于单点退出,该过滤器用于实现单点登出功能,可选配置–>
<listener>

    <listener-class>org.jasig.cas.client.session.SingleSignOutHttpSessionListener</listener-class>
</listener>

<filter>
    <filter-name>singleSignOutFilter</filter-name>
    <filter-class>org.jasig.cas.client.session.SingleSignOutFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>singleSignOutFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

<filter>
    <filter-name>CAS Filter</filter-name>
    <filter-class>org.jasig.cas.client.authentication.AuthenticationFilter</filter-class>
    <init-param>
        <param-name>casServerLoginUrl</param-name>

<!– 根据真实地址填写,cas服务的登录地址 –>
        <param-value>http:// cas服务的登录地址/cas/login</param-value>
    </init-param>
    <init-param>
        <param-name>serverName</param-name>

<!–根据真实地址填写,sf-ui的访问地址–>
        <param-value>http:// sf-ui的访问地址</param-value>

    </init-param>
</filter>
<filter-mapping>
    <filter-name>CAS Filter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
<!– 该过滤器负责对Ticket的校验工作,必须启用它 –>
<filter>

    <filter-name>CAS Validation Filter</filter-name>
    <filter-class>
        org.jasig.cas.client.validation.Cas20ProxyReceivingTicketValidationFilter</filter-class>
    <init-param>
        <param-name>casServerUrlPrefix</param-name>

<!– 根据真实地址填写,cas服务地址 –>
        <param-value>http:// cas服务地址/cas</param-value>
    </init-param>
    <init-param>
        <param-name>serverName</param-name>

<!– 根据真实地址填写,sf-ui服务地址 –>
        <param-value>http:// sf-ui服务地址</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>CAS Validation Filter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

<!–
该过滤器负责实现HttpServletRequest请求的包裹,比如允许开发者通过HttpServletRequestgetRemoteUser()方法获得SSO登录用户的登录名,可选配置。
–>
<filter>

    <filter-name>CAS HttpServletRequest Wrapper Filter</filter-name>
    <filter-class>
        org.jasig.cas.client.util.HttpServletRequestWrapperFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>CAS HttpServletRequest Wrapper Filter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

<!–
该过滤器使得开发者可以通过org.jasig.cas.client.util.AssertionHolder来获取用户的登录名。比如AssertionHolder.getAssertion().getPrincipal().getName()
–>
<filter>

    <filter-name>CAS Assertion Thread Local Filter</filter-name>
    <filter-class>org.jasig.cas.client.util.AssertionThreadLocalFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>CAS Assertion Thread Local Filter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
<!– ======================== 单点登录结束 ======================== –>

<!– 获取登录用户servlet begin –>
<servlet>
    <servlet-name>getUserInfo</servlet-name>
    <servlet-class>com.skycloud.cas.SkyCasClient</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>getUserInfo</servlet-name>
    <url-pattern>/getUserInfo</url-pattern>
</servlet-mapping>
<!– 获取登录用户servlet end–>

三、结束语

至此,SkyForm CMP产品的ui框架模块化实践说明已完毕。经实践,由于进行了模块化,开发任务按服务进行分配,开发者能快速投入业务逻辑中,不必再投入大量时间精力在页面的统一和重复开发上,开发中新的优化点也能迅速被团队共享,人员交接过程中节省了大量时间,维护成本降低。如果对以上分享有任何问题欢迎联系公司的技术邮箱skyform_support@chinaskycloud.com,以后有机会继续和大家进行分享。

参考资料

highcharts: https://www.hcharts.cn/

echarts: http://echarts.baidu.com/

nginx:《CentOS+Nginx一步一步开始配置负载均衡》

—End—