UFT 需求提报平台回顾

最初它的名字为 BugHound,主要功能是 bug 提交,EC 同学已经造出了它最初的样子,它最初的目标用户是移动用户,能在提 bug 的同时收集设备的相关信息。主要用到的技术点:
- php
- mongodb
我开始熟悉原代码并尝试在此基础上进行迭代开发,但是由于功能上有较大的变更,而我没有用过 Mongodb 和 php,这时我更倾向于使用我熟悉的 MySql 和 Nodejs,在得到 EC 同学的允许后,我决定从零开始搭建系统。本文选择其中几点进行阐述。
数据设计
第一步是建表,下图是最初的表设计,基本上按照原 mongodb 中的数据关系进行定义,设计好数据表,再把它们写进数据字典,最后才开始建表。
(数据字典对开发和协作的作用非常大,它能让你和别人知道表中有哪些字段,某字段代表什么等信息,在接下来的编码工作中还会经常去查看它。)
随着开发的深入,数据不断拓展,有些字段被淘汰或被拆分到新表,有些字段新加进来,比如代表“文件”的数据,原来以字符串的形式写在 demand 表的 files 字段,作为经常被读写的数据,以字符串的形式保存显然不正确,我们需要把每个文件当作一个数据元,于是把 files 字段拆分成新表 demandfiles,并增加了上传人、上传时间和下载次数这些信息。
为了让数据库设计更加合理,使用了外键约束,确保在删除修改数据的同时能级联删除修改,外键统一命名为 fk_table_field
的形式。修改后的 ER 图如下所示:
技术实践
第一阶段
起初,项目后端使用了
ejs
模板引擎,拆分头尾在后端渲染模板后发回给前端,前端用 jQuery 进行逻辑的处理。第二阶段
前端开始使用 Angular 替代 jQuery,但是 ejs 跟 Angular 好像有点格格不入,前者意在后端渲染数据,后者通常在前端通过 $http 获取数据来填充,这时的 ejs 的存在主要是为了组装拆分出来的头尾,不好去掉它。但是,接着发现了
ng-include
,这让组装头尾的工作交给了前端,从而摒弃掉了 ejs。第三阶段
接着,切换页面的时候头部闪烁的问题引起了注意,原来是 Angular 使用方式不对,我的每个页面都是一个单独的 Angular app,每次切换页面时都引入一次头尾导致闪烁,于是引入 ui-router 把系统修改成一个单页面应用,更新
ui-view
即可切换页面。这又引出了一个新的问题,所有页面的入口都是 index.html,也就是说不能再简单地在后端通过路由来控制页面的访问权限了,这样另一个概念就登场了——本地身份验证。简单点说,就是在进入应用主入口 index.html 的时候,从服务器请求身份信息,身份信息将保存在
service
中(类似 session storage),然后在状态(页面)切换事件stateChangeStart
或stateChangeSuccess
中,对页面请求进行拦截或重定向等操作。然而,网络请求都有一定的延迟,在身份信息返回来之前,某些页面的内容不应该被呈现出来,所以要在获取到身份信息后通知到页面,于是就加进了事件广播和接收器
$broadcast
和$on
:获取到身份信息后通过$broadcast
广播一个事件,在子页面中通过$on
接收到该事件后执行相关逻辑。
排期表设计
v1 版
背景:需求排期时间不能冲突,排期由排期起始和结束时间决定。
从服务器获取的源数据形式如下,已按 startDate 排好序,每个用户数据中包含排期数据(data):
1234567891011121314[{erp:xx,name:xx,... ,data[{demandID:xx, startDate:xx, endDate:xx, ...},{demandID:xx, startDate:xx, endDate:xx, ...},{demandID:xx, startDate:xx, endDate:xx, ...},...]},...]在前端计算好周起始时间
ws
和周结束时间we
,对每条排期数据进行判断,如果排期时间跟本周的时间有交集,则计算该排期都出现在了哪几天,比如下图的情形,这里有两个排期:数据将会按下面的形式(blocks)附加到用户数据中,其中 [0,1,2] 和 [4,5] 的数字就分别表示了两个排期在周排期表中占据的位置。
1234567891011121314151617181920212223242526272829[{erp:xx,name:xx,... ,data[{demandID:xx, startDate:xx, endDate:xx, ...},{demandID:xx, startDate:xx, endDate:xx, ...},{demandID:xx, startDate:xx, endDate:xx, ...},...],blocks:[{demandID:xx,demandName:xx,dictator:xx,block: [0, 1, 2]},{demandID:xx,demandName:xx,dictator:xx,block: [4, 5]},...]},...]接着,处理排期表中的空闲的天,给用户数据附加以下数据:
123456schedule: [{demandID:xxx, ...}, // blocks[0]{date:xxx}, //周三,时间用于点进申请页自动填好时间{demandID:xxx, ...}, // blocks[1]{date:xxx} //周六,时间用于点进申请页自动填好时间]然后在页面上由
schedule
循环输出 td,blocks[0] 和 blocks[1] 的长度就是 td 横跨的 td 个数。v2 版
背景:v1 版的排期表设计中,由于排期的最小单位是“天”,而存在数据库中的 startDate 和 endDate 的最小单位却是毫秒,在计算上无疑增加了大量纷繁的计算,受移动组 task 系统的启发,把排期数据改为 startDate 和 days(所需天数),这样可以减少一半的计算,也更好理解。另外较大的改动,排期时间将允许有重叠(即同一天可以排多个需求)。
从服务器获取的源数据的形式不变,这次我在计算之前把时间都转化成了天(1970-01-01至今的天数)以方便计算,为了解决排期堆叠的问题,添加了两个关键的变量:
12var weekpile = [0,0,0,0,0,0,0]; //记录排期在该天已经堆到第几层了var wee = [[], [], [], [], [], [], []]; //数组中的每个数组代表星期里的一天,存储由该天开始的需求,下面有详解weekpile 原理如下图,每个排期排在第几层由它在排期表中的第一天所在层级决定。
层数记录附加到用户数据中,如下:
12345678blocks: [{id:xx, demandID:xx, startDate:xx, days:xx, ... ,weekdays: [0, 1, 2],pilefloor: n // 所在层级},...]另外,上文提到的
wee
变量存储的是指向blocks
索引的数字,把它也附加到用户数据中。如图,外层 div 的margin-top
由wee
所指向的第一个排期的pilefloor
决定,比如下图假设这是星期六,那么外层 div 的margin-top
由blocks[wee[6][0]].pilefloor
决定。因为已知排期的天数 n,给每个排期的宽度设置相应的 n*100% 即可。
然后在页面上由
wee
循环输出 td,blocks[n] 中的 weekdays 的 length 决定了排期的长度。v2 版排期表如图所示:
协作
最初 UFT 主要由我进行开发,后来另一个同事加入开发,变成我负责 UFT 的功能开发,她负责 UFT 的 UI 优化,项目源码放到 github 上了,我们要在 github 共同进行开发,我们两人琢磨了好久,面对冲突问题时开始还采取过删除后重新拉取暴力做法,几经尝试,我们最终探索出两种较好的协作方式:
- 我们各自拉取分支修改,push 上去后发起合并请求,我负责合并分支;
在本地修改后,先从服务器上拉取更新合并:
git fetch origin master
然后进行合并
git merge FETCH_HEAD
一般情况下,合并会顺利进行,但难免会有冲突,一旦有冲突,就调用 mergetool 进行合并,或者直接在原文件上手动合并。最后,就是正常的提交过程了。
关于 mergetool,自从同事分享过一次,回去后自己装了 p4merge,配置如下:
12345678910[diff]tool = p4merge[difftool "p4merge"]cmd = "p4merge $LOCAL $REMOTE"[merge]tool = p4merge[mergetool "p4merge"]cmd = "p4merge $BASE $LOCAL $REMOTE $MERGED"trustExitCode = truekeepBackup = false但是,没有冲突是调用不了 mergetool 的。
其他
临时文件的处理
临时文件指的是异步上传到 temp 文件夹的文件,用户上传文件后如果不打算进一步提交了,这些临时文件就成了“弃儿”,如果不对这些文件进行处理,temp 文件夹会越来越大。于是加进了定期任务,每隔一段时间对临时文件夹中的文件进行清理,为了防止把用户刚上传的文件清理掉了,被清理的文件的最后访问时间要在3天之前。
总结
UFT 从无到有,很多东西边学边用:第一次尝试用 Express,配设置、配路由等过程遇到不少坑;第一次用 Angular,用得并不规范,多亏涛哥从中相助,解决我不少困惑;Angular 各种各样插件的学习使用过程,就是不断试错,不断筛选满意插件的过程。
除了知识上吸收,最大的收获是意识到项目规划的重要性。开始接手后就马上开始写代码,需要什么功能就写什么,以至于最后需要经常较大地改动代码,比如,列表之前用 Bootstrap-table 写,然后改成 Angular,又比如将页面合并成单页面 Angular 应用时,需要把大量的插件都换成 Angular 插件,还有项目的结构也几经变换。折腾这几番足见项目前期的规划还是非常重要且必要的。
未来规划
UFT 原来的目标是供部门内部使用,用户访问系统需要先经过内部员工帐号的身份认证过程,然而,随着内部推出了新的需求管理平台并推广到部门,UFT 被闲置,为了让 UFT 重新焕发活力,在接下来,把源码开源,并将把 UFT 改造成通用的需求管理平台。目前平台仍有不少可以优化的地方,例如:
- MySql 使用上 View 和 Function 会简化很多工作
- Models 不是真正的 MVC 中的 M,可以考虑让这部分更远离业务逻辑更接近数据
- 需求状态的多样化,需求状态变更的系统通知(非邮件)
- ……
源码地址:https://github.com/o2team/UFT