与其说是popush,不如说cospider,毕竟从外部界面到内部逻辑,我们都做了巨大的改变,使其成为了一个全新的产品,一个团队项目管理平台。
这个大作业,对我自己来说,付出了不下于其余所有大作业总和的时间与精力。平时,每周两次开发,一次十小时。而其余诸如SmartDental、计网、操作系统每项每周也就一次四五小时的开发时间。推动着我们花费大量时间精力开发popush的,无疑是我们骨子里流露出的对代码的爱。
犹记当初,我们四人的第一次会议,对于需求、功能以及实现方式,我们赞同着,却更多地争吵着,不为别的,只为能做出一个让自己满意的产品。不得不说,那次会议成了我们前进路上的一面旗帜,标识着我们的道路,使得我们在遇到困难进行抉择时能够正确地取舍。还记得迭代一交付完,刘强老师与我们进行交流,提出了些许指导性的意见。在后来的一次集中开发上,我们就这些意见争论了很久。就对于注释功能而言,我们绞尽脑汁,想要实现在注释里添加代码,然后一键替换掉原来代码的功能。当时我第一想法就是难度太高,因为修改代码有增、删、改三种操作,而要进行智能的修改,则需要强大而正确的文本比对功能。而我们所用的svn,其在进行文本比对时,偶尔都会出现差错。而且还有一个重要的点,我们最开始的目标便是做一个项目管理平台,若真要花一两周的时间来完善注释功能,那我们当初准备做的项目、任务系统便很难完成。最终还是放弃了对注释功能的大规模修改,而是巧妙地将注释换为批注,加上些小功能,便结束了对编辑器的修改与实现。
剩下的时间,便是完成我们的项目系统。我是做后端的,准确的来说,是做服务器端的。一开始是卢凌铜和我一起写后端,然后另外两个写前端。做到这个阶段时,我们发现前端的需求量及代码量都很高,所以卢凌铜转去了前端,我还在后端孤军奋战。不过经过了几周的开发,对nodejs和mongo都非常熟悉了,所以在服务器端写监听接口和数据库接口都比较轻松。这段轻松的时间一直持续到迭代二前一周。
从当初我们展示的svn版本统计图就知道,我们最后一周都干了啥。在那之前,我们的版本号为80+,之后为250+。而且在周四前四天,也就是周日开始,我们便是整天整夜开发,准确地说是整夜。除了吃饭睡觉,就是写popush。很多人都问,为什么你们花了那么多时间去做呢?从客观原因来讲,popush架构复杂,特别是js的回调机制,再加上codemirror和backbone的机制,所以对一个功能的完成就需要花很长时间,更别说我们的功能还不少。从主观原因来说,就是喜欢,喜欢写代码,喜欢写popush。
我所遇到的困难,文件系统就是一个开头。以前的popush虽然有一定的文件架构,但是仔细阅读其代码就会发现,它的文件系统非常不完善,只有根目录的文件有重命名、共享、移交等操作,而二级目录下的文件除了能打开编辑外就是个花瓶。我为了完成完整的文件系统,删掉了DocDAO.js下的1300+的代码,亲自重写。有了对文件系统的理解,和对DAO的理解,写起来倒不麻烦,最终也还是顺利地写完了。不过文件系统的难点倒不在于其整体实现上,而是在权限上。
众所周知,我们有一个权限系统,每个人对每个文件都有一个权限,主人或共享者或游客。在一个文件树上,每个节点都要维护每个人的一个三进制的状态,而且最关键的是,在我们的机制里,如果A移交权限文件夹给B,那么这个文件夹下所有A的文件全部自动移交给B。如果A将这个文件夹共享给B,那么这个文件夹下所有A的文件全部自动共享给B。这个机制是我们经过深思熟虑,得出的一个相对来说逻辑最为正确的权限机制。最开始我采用顶层记录法,A将X文件夹移交给B,那么就将文件夹的owner改成B就行了。而每当要获取一个人在一个文件的权限时,就不断向上寻找,如果找到它的某个祖先文件的权限是这个人时,便将结果返回。在完成这个算法之后,在实际操作过程中,却出现了问题。因为一个人可以在不是他的文件夹下新建文件,这就意味着顶层记录法是错的,因为一个人的某种权限对于树的覆盖,不是以子树形式的,况且还是k-3的覆盖。在和组员讨论之后,决定采取暴力的方法,每次修改便遍历它的整个子树,相应地修改子树的所有节点的权限。而每次查找,只需访问该节点即可。这样以来,在时间复杂度上也有了较好的提高。以前的算法,每次修改是O(1),查找是O(lgN);现在的算法是修改O(N),查找O(1)。但别忘了,在正常工作下,修改权限的操作数比起查找的操作数,就如同九牛身上的一毛。关于算法的实现细节我也不赘述了,无非是实现了一个遍历,即递归。说起递归,因为mongo的数据库操作是以回调的形式传值,也就是说,它的数据库操作是异步的,你的所有代码要写在回调函数里面。所以递归操作数据库不是看起来那么简单的,因为你在递归时根本不知道什么时候停下来!后来,我采取的解决办法是在递归的每一层加一个counter标识,记录这一层有多少节点,每当遍历完一个节点的子树便counter减一,知道counter为0时,便返回到上一层的回调函数里。具体做法我也不细说了,这还是参考的网上的资料。我自己之前也写过,后来发现是错的,还得把近千行的代码删了重写。
在最后的四天里,我在不断完成及完善我的所有DAO以及不断的测试。卢凌铜则是在做界面,说实话,我们的界面确实是一流的,从配色到控件到整体的布局,再加上华丽的动态效果,夺人眼球。而聂中天和肖建楠还是一如既往地埋在前端及其机制两座大山下。我当时都比较轻松了,因为服务器端的接口写完就没事干了,但总不能在他们写代码面前玩吧。我便没事找事,检查所有DAO的所有操作的锁,没锁的加锁,该解锁的解锁,不该加锁的删掉。然后又检查一遍所有DAO,把注释全部加上。本来还真以为没啥事了,结果测试到上传和下载时出问题了。
我都不愿回忆起实现上传下载的那段时光,就是段黑暗的旅程。因为原来的popush自带上传下载功能,所以我修改了上传文件的类型要求,上传了一个pdf,上传成功,打开,毫无疑问的乱码,接着下载。下载的时候,我发现它的大小已经和原来的不一样了,心中一凉,打开一看,果然打开错误,这个pdf已经毁了。我用nodepad打开原来正确的pdf,发现和现在错误的pdf的文本形式,全是编码乱掉,仔细一看,倒数第三行最后一个字,正确的是“!”,错误的是“?”。就从这个感叹号入手,我不断地重复着上传下载的步骤,终于在上传完成的那个步骤上找到了错误的所在:把pdf文件的内容存进数据库时已经错了。编码问题我又能怎么做呢?那时只感到灰心,只想着我们的会议文件传个txt便得了。
老天开眼,让我无意间扫过头像修改的那段代码。我发现服务器存头像时,是直接在服务器端的本地文件系统里写入了一个图片文件。这就好比网盘,用户传文件时,直接将其文件“复制”一份到服务器。一拍大腿,对啊,我马上找到相关代码,并查阅了无数资料。在上传文件时,以dataURL读取,它的编码格式为base64。然后不按照以往代码文件存进数据库的格式,而是直接将其用FileWrite写入服务器的本地。果不其然,成功了!继而完成了下载的代码。然而塞翁得马,焉知非祸。我们的文件系统里出现了三种不同类型的文件,一是代码文件,二是非代码文件,三是会议文件。三种文件有两种读取方式,三种保存方式,三种下载方式。幸好我清楚其中的逻辑条理,不过DocDAO又要大改了。又经过几小时的奋战,终于完善了整个文件系统,也完成了文件,不,所有类型的文件的上传与下载。此时,已是十一点了,星期三上午十一点,已经连续写代码超过了15小时。
星期三下午醒来便继续干活。最后一晚,主要是做完善功能,测试功能以及做展示准备了。本想早点休息,结果还是熬到了深夜。最后一晚我自己的战果就是实现了打包下载功能,其余时间便是无尽的测试与修复了。那一晚我根本没睡,到四点过时,大家基本都完成任务了,回去睡觉了,而我不困,便想着能不能解决了实时刷新的问题,也就是socket.io中的broadcast。这是我们组一直没有解决的问题。当然在一些尝试后失败了,我估计是前端那边也需要做些修改,便放弃了。此时,天已蒙蒙亮了。
最后我粗略算了下我的工作量,纯代码五千行。我想,辛苦终究没有白费,在展示完成之后只觉得,这几个月的付出都值了。Popush是我入大学以来做得最酣畅淋漓的一个大作业,我这人又喜好熬夜,所以也喜欢和熬夜的人组队。所以我们老衲印象团队,又别名608午夜K歌团。因为我们喜欢半夜放歌,有天上午楼长还找上门说有同学投诉,让我们大大收敛。不得不说,那段一起写代码的日子真是难以忘怀的。
总之,一切的辛酸苦楚,一切的欢笑喜悦,一切的付出,一切的收获,尽在言与不言中。