58招聘APP详情页系统架构
LucRoyston
8年前
<p><strong>58APP招聘详情页是listing列表页职位的落地页 </strong> 。详情页包含职位基本信息、工作地址、职位描述、公司信息、附近职位、推荐等模块。本文主要讲为 native提供数展示协议的服务端站点整体结构,以及如何使用velocity自定义标签管理app端多版本模版。</p> <h3><strong>1.项目总体架构</strong></h3> <p>58的web站点通常使用wf框架进行搭建,整个站点结构如下所示</p> <p style="text-align: center;"><img src="https://simg.open-open.com/show/9911159478155e3d279d02a492ada743.jpg"></p> <p>下图是web端框架,后面我们将对web站点流程进行介绍</p> <p style="text-align: center;"><img src="https://simg.open-open.com/show/1f25a13d05720972c232cd09ca406472.jpg"></p> <h3><strong>2.web端流程概述</strong></h3> <p><strong> 2.1初始化过程 </strong></p> <p>整个站点的初始化流程图如下所示,下面将做主要流程介绍:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/260457243fef63afc94ffe3832c8d9fa.jpg"></p> <p><strong>1)APIInfodetailController初始化</strong></p> <p>APIInfodetailController作为整个详情页入口的控制器,在服务器启动时,会在其构造器中初始化相关配置类,读取wf文件中相关的配置文件。其中初始化定位各业务处理类(service层)的apiconfig.xml配置文件是关键点,apiconfig.xml配置文件内容如下:</p> <p style="text-align: center;"><img src="https://simg.open-open.com/show/5613cd8c456c264b6753e11fda6a3080.jpg"></p> <p>之后站点会调用Configuration类的getInstance()方法获取到保存的appManagerClassMap的map时,会遍历此map集合,并将对应的key和value的值保存到appManagerMap中。随后,实例化一个IURLHandlerService的实例。最后,调用ResultConfig的init()方法,读取resultconfig文件下的job.xml配置文件(配置各类别模版路径),配置文件如下:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/8140f82f175be457a7b469d2e502fff3.png"></p> <p><strong>2)InitController</strong></p> <p>InitController控制器实现了Spirng框架的ServletContextAware类,所以会在服务器启动的时候去调用setServletContext,在此方法中调用ScnnerTemplates的scan方法,扫描所有的模版,并将模版的文件路径存放在map集合中。</p> <h3><strong>3 业务逻辑层</strong></h3> <p>这一节将主要介绍本系统的业务逻辑层所涉及的业务逻辑、自定义异常、日志写入、以及如何去调用服务。</p> <p><strong>3.1 主要业务逻辑</strong></p> <p>详情页的主要业务逻辑是根据帖子的一级类别,传入不同的参数查询获取一个详情页的实体结果,然后去构建详情页显示的各个模块:如帖子基本信息、工作地址、职位描述、公司信息等,之后利用velocity渲染协议,返回如安卓的xml格式协议,和ios的json格式协议,当然不同app版本的协议也是不同的</p> <p style="text-align: center;"><img src="https://simg.open-open.com/show/b18f2717db069c67c4b50fa5f27c25cc.jpg"> <img src="https://simg.open-open.com/show/5664ac0088e476b973d7a42a957e580d.jpg"></p> <p><strong>3.2自定义异常</strong></p> <p>在java中所有异常的跟类为Throwable,在本系统中,为了一些特殊的处理,自定义了许多异常,下图系统汇总要到的自定义异常类图:</p> <p style="text-align: center;"><img src="https://simg.open-open.com/show/a3e6bafaa708f1f263d74a6006cbb0fa.jpg"></p> <p style="text-align: center;">从类图中可以观察到,自定义的异常类的根类皆属于运行时异常(RuntimeException)。为保证系统不被异常中断,都会在业务逻辑中进行catch处理。</p> <p><strong>3.3 日志管理</strong></p> <p>本系统日志分为一般日志信息和ROI日志信息。普通日志的级别分为info级别、error级别、debug级别、warn级别。</p> <p>除了使用WF自带的日志类 LogFactory,并自定义了BllLog类,便于操作日志。BllLog类如下所示:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/abb323da30c6d2542da592776d7f3c22.png"></p> <p><strong>3.4 枚举</strong></p> <p>枚举是一种规范,规范了参数的形式,这样就可以不用考虑类型的不匹配并且显式的替代了int型参数可能带来的模糊概念 枚举像一个类,又像一个数组。本系统多处使用了枚举类,使代码阅读性更好,这里就不再赘述了。</p> <h3><strong>4 视图层</strong></h3> <p>视图层主要是负责提供不同格式的协议,由于app的特性,会有版本的升级,每次版本的升级,几乎都涉及到视图层页面的增加或者修改。就无疑就给页面路由带来了一定的难度,为解决路由这一问题,采用了自定义了velocity标签-- rewparse来实现。</p> <p><strong>4.1 设计方案原因</strong></p> <p>由于App端的发版现状需要兼容各个版本,每一版本的代码都要保留,所以为了让每个版本都能正常运行,在代码中和模板上做了很多的版本控制。目前app每月发版一次,如果每次发版都有代码层面的变动,我们的代码就会十分混乱,难以维护。为了让代码看起来更清晰,更干净,管理不同的版本代码更容易,我们设计了这套模板版本控制方案。</p> <p><strong>4.2 系统模板说明</strong></p> <p>Web端通过velocity渲染,并区分ios(json)和android(xml)两套协议模版。前端以主模板+碎片的方式存在,按照几个大的功能划分,将大部分功能分在了碎片中。模板之间通过parse标签引入。</p> <p>改版前目录结构如下:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/fa366d4018821614d0f71f3f7834f2af.png"><br> <img src="https://simg.open-open.com/show/c4470bb9ab2cc42a6bb3c429ff59400b.png"></p> <p><strong>4.3 优化方案</strong></p> <p>Velocity提供了自定义标签的功能,所以本次优化从源头重写parse标签开始。既然是动态选择模板,那我们就需要知道当前系统运行中有几个模板,每个模板分别支持哪个版本,所以我们需要规范模板的目录,命名。这样当一个请求到来时,我们可以根据版本号去选择正确的模板。</p> <p>1、自定义新标签,取名为rewparse,实现功能包括原parse标签的功能,加入根据版本号动态选择不同版本模板的功能。</p> <p>2、规范模板设计,按照一定规范创建新的版本,同一碎片不同版本之间按照规范存储。</p> <p>3、规范版本号,同一碎片不同版本之间通过规范的版本号做区分 。</p> <p><strong>4.4 具体设计方案</strong></p> <p>1、目前android使用3位版本号,ios使用4位版本号,后续native可能会统一为4位,并且每位取值范围在0-99之间。所以,版本号规范为8位,接收到的版本号不足4位的末位补0,每位不足2位数的前边补0,如:6.4.5会被转化为06040500</p> <p>2、按照版本号建立文件夹,版本迭代中比原来版本改动较大的碎片选择新创建一个同名碎片,并将同名碎片放在与旧版本同级目录的以当前版本号为名的文件夹下。如:</p> <p style="text-align:center"><strong><strong><strong><img src="https://simg.open-open.com/show/000e6cfb4f99c544c322821289939a71.jpg"> </strong> </strong> </strong></p> <p>上图中,nearjob.vm碎片中包含的功能是附近职位模块,这个模块在7.2.0版本时做了一次优化。所以将7.2.0版本的nearjob.vm放在07020000文件夹下,表示两个nearjob.vm是有实现同一功能不同版本的关系。</p> <p>1、Rewparse标签依赖需要知道当前工程中有哪些版本的碎片,每个碎片有几个版本。所以我们需要去扫描模板所在目录。因为在系统运行中模板是不会变动的,所以我们不必每次请求时都去做一次扫描,只需要在工程启动时做一个扫描模板的初始化,将结果保存在内存中即可。每个非数字构成的文件夹下的同名碎片为实现相同功能的不同版本的碎片。如:</p> <p>templates/json/job/fragment/nearjob.vm</p> <p>templates/json/job/fragment/07020000/nearjob.vm</p> <p>按照此种格式存储的碎片我们会认为是实现同一功能的不同版本的碎片。</p> <p>上述碎片会按照(templates/json/job/fragment/nearjob.vm,[t emplates/json/job/fragment/nearjob.vm,templates/json/job/fragment/07020000/nearjob.vm])</p> <p>这种key、value格式存储,rewparse标签会根据传入的templates/json/job/fragment/nearjob.vm模版碎片名和当前版本号去value中寻找小于等于当前请求版本号的模版碎片(未做版本区分时,默认使用通用碎片)。</p> <p>备注:目前系统中模板数量较少,后期当模板数量变多之后,可以考虑建立缓存机制,将每一个版本使用哪一个模板缓存,可以减少不必要的开销。</p> <p>2、Parse和rewparse标签共存。</p> <p>新的标签实现了一些较为复杂的功能,为了避免不必要的开销带了未知的问题,我们采取了两个标签共存的方式,没有多个版本的模板依然使用velocity原有标签,只有多个模板存在的形式下才引用新的标签。</p> <p style="text-align: center;"><img src="https://simg.open-open.com/show/03011549a8a047c4a2d7badf56150540.jpg"></p> <p><strong>4.4 方案小结</strong></p> <p>动态选择模板方案,保证了各个版本模板间的独立性,每个版本修改均不会影响其他版本。在一定程度上也减少了老版本测试工作量,有效避免了兼容问题;动态路由是在基于模板存在的情况下进行的,某一版本模板缺失,系统可以自动路由至其他版本模板,避免出现异常;如果N个版本后要放弃维护某些低版本,可以直接物理删除这些版本即可;去掉版本判断后的代码也变的更加清晰简单,维护成本大大降低。</p> <h3><strong>5 总结</strong></h3> <p>APP详情页作为服务端与native端的桥梁,既要关注业务逻辑又要关注如何去给native提供数据协议。在工作中要区分数据的是服务端的还是要给native端的,要更加细化业务层,了解从视图到业务逻辑层是如何实现的。</p> <p> </p> <p> </p> <p> </p> <p>来自:http://mp.weixin.qq.com/s?__biz=MzIzNzQzNjEwMw==&mid=2247484142&idx=1&sn=7c0659b0a2510ad26e2b116c41b69538&chksm=e8c9ef46dfbe6650663911bc315ddd2cee02368db3c9cca88b185a7c21d0f4f0acc8d621142e&scene=0#wechat_redirect</p> <p> </p>