从架构师的角度对blog管理系统项目的研发报告

关键字:软件工程,校内网,博客,blog,ASP.NET 3.5,LINQ,XML,ATOM

摘要:以下是本人2007年12月在《软件工程》课程中开发“blog管理系统"项目的研发报告,主要阐述了这个软件的设计过程以及用ASP.NET 3.5实现的部分细节。

小组成员——项目经理:蒋彦;架构设计师:石君霄;业务分析员:熊帆,严栋孜;程序员:张篪,张臣,孙俊卿,王成超,孙斌,陆黎青;测试员:罗慧骏,袁文俊。 本人任务:架构师

由于本人长期从事信息系统开发,经验丰富,因此全组一致推举我担当架构师一职,且完成了不少编程工作。由于工期紧张,这个程序并不符合yoursunny的质量标准,因此我放弃了署名权,且程序代码进入Public Domain、不受版权保护。

一、 需求分析

设计实现一个功能和界面与校内网xiaonei.com日志模块相似的blog管理系统,主要功能:

  1. 在首页上显示系统广告、帮助等。
  2. 实施完善的实名认证功能。为方便起见,实名认证将采取上海交通大学网络中心提供的jAccount认证服务,用真实的学号或工号作为认证依据。为了保护隐私,必须先用自己的实名帐号登录,否则不能访问本系统的大部分功能。
  3. 每位用户拥有自己独立的博客首页。页面上显示最近的10篇文章,并显示翻页控件可以看到更多的文章。提供博客文章的按月存档,可以点击查看任何一个月的文章,且同样提供每页10篇的翻页功能。
  4. 支持使用富文本编辑器发布和修改日志。不支持直接上传图片,但是可以将图片上传到外部的图片站点(如大脚社区footbig.com)并插入文章中。
  5. 每篇日志具有独立页面,自动统计这个页面的访问次数并显示在文章后面。允许用户对日志发表评论、一经发表不可修改或删除,日志作者可删除评论、但不可修改。
  6. 每位用户要上传自己的头像,这个头像显示在其博客首页、日志页面、在别人日志后的评论内。点击评论内的头像,可进入评论者的博客首页。
  7. 为每位用户的博客提供ATOM聚合源输出功能,且允许未登录用户访问聚合源,以便关注者通过Google Reader、鲜果阅读器、Foxmail等工具订阅。在博客首页添加用于自动发现的link标签。——这是校内网不具备的、本程序新增的功能

二、 软件设计过程概述

这个软件的设计采用yoursunny自有的小型B/S信息系统制作方法论,而不是标准的软件项目管理方法。这种方法概述如下:

  1. 在需求分析的基础上,根据数据量、性能要求、导入导出需要,决定应该用什么语言和框架开发、所有的数据应该以什么样的格式存储。例如是存储在XML文档中、还是关系型数据库、还是文本文件中?XML文档的文件名有什么规律?关系型数据库选用哪一种DBMS,是ACCESS、SQLite,还是MySQL?
  2. 决定信息系统需要哪些主要的页面?哪些是大多数访问者能看到的、哪些是少量的管理员使用的?对大多数访问者能看到的页面,交由专业美工进行设计,或者先设计好初步排版请专业美工调整、配色、美化。确定哪些页面中的哪些操作需要快速响应——考虑使用AJAX,哪些操作最为常用——考虑给出更方便的操作链接,哪些操作耗时较长——需要显示动画效果。
  3. 如果采取以AJAX为主的客户端、或者需要支持开放的API,则决定客户端与服务端的通信采取何种现有第三方API、或者自己定义一个通信调用的方式。如果是以Web Form或HTML Form为主的客户端,也需要作类似的定义。这一步有时与下一步同时进行。
  4. 着手实现客户端和服务端。通常,要将客户端和服务端同时编码实现。这样做的好处是,随时发现接口定义的遗漏之处并加以补充,当然如果使用现有第三方API就不存在这个问题。如果接口较为复杂,必须随时在代码注释中写清接口功能和调用参数;对于AJAX调用,还要写上每个接口功能分别被哪几个页面上的哪些函数调用,以及服务端应该返回的内容。这些注释通常写在服务端代码中,客户端代码只需简单注释,以节省产品占用的网络带宽。客户端和服务端同时编码实现的另一个好处是方便进行测试,可以直接使用的半成品的各项功能,通过各类调试工具(例如Firefox的两个插件FireBug和WebDeveloper)进行简单的测试。在AJAX应用中,客户端稍稍提前,服务端要配合客户端的操作。编码过程中随时注意安全性,而不是等到引入了安全问题之后才想到去修补;例如,“通用防注入脚本"之类是没有用的,只有注意每一处的SQL语句构造才行。
  5. 请一位非开发人员、最好对Web不很熟悉的人员,进行可用性测试。可用性测试过程中只告诉他“做什么"而不告诉他“怎么做",让他独立操作,开发人员在旁边观察他的操作。如果他没有完成任务,就需要调整程序的设计。

三、 语言和框架选择、存储格式设计

在长期开发中,我积累了需要已经写好的组件,这是我决定信息系统使用的开发语言和框架的重要因素。为了学习更多的新技术,我也会选择自己不很熟悉的方法进行开发。因此,这个程序我选择了服务端使用C#语言在ASP.NET 3.5的框架下进行开发,客户端则只能有JavaScript这个惟一的选择。

为了方便部署和安装这个程序,我决定使用XML作为主要的数据存储格式,这样就不需要安装任何DBMS。Blog内容常用的XML格式有ATOM和RSS两种,它们是等价的,我选择了ATOM,且每个用户存储一个文件。ATOM是可以扩展的,jAccount的学工号和姓名、评论等,都使用http://infosec.sjtu.edu.cn/5050369040/Fun2007/blog/feed-extension这个命名空间直接引入到ATOM文件中。具体新增定义的元素如下:

<atom:feed xmlns:atom="http://www.w3.org/2005/Atom" xmlns:ext="http://infosec.sjtu.edu.cn/5050369040/Fun2007/blog/feed-extension">
  <ext:jaccount>
    <ext:sid>学工号</ext:sid>
    <ext:uid>用户名</ext:uid>
    <ext:person>姓名</ext:person>
  </ext:jaccount>
  <atom:entry>
    <ext:hits>点击次数</ext:hits>
    <ext:comment>
      <ext:id>评论的惟一编号</ext:id>
      <ext:author>评论者姓名</ext:author>
      <ext:sid>评论者学工号</ext:sid>
      <ext:timestamp>评论时间,RFC3339格式</ext:timestamp>
      <ext:content>评论内容,纯文本</ext:content>
    </ext:comment>
  </atom:entry>
</atom:feed>

在C#代码中,将使用System.Xml.Linq操作XML数据。据说LINQ的执行效率不高(比XPath慢),但是使用方便、可以缩短开发周期,因此小规模的非商用程序,使用LINQ进行开发是合适的,有助于节省人力成本。

以上设计体现了MVC设计模式——XML作为Model(模型),C#代码作为Controller(控制器),HTML作为View(视图)。在MVC设计模式中,Model与View不能直接通信、要通信必须经过Controller,本程序符合这一特点。

四、 页面结构及服务端接口设计、编码实现

本程序需要以下这些用户可见的页面:

  1. 首页,显示系统公告、广告等
  2. “我的日志"页面,显示当前用户的所有日志及修改、删除链接,显示头像上传框
  3. “写新日志"、“编辑日志"页面,发表新的日志、或编辑现有的日志,这两种页面基本上是相同的
  4. “×××的日志"页面,即用户的博客首页,显示用户最近的文章、支持翻页,提供按月的“日志存档"功能
  5. 阅读日志页面,显示一篇日志,显示和创建评论,对日志作者提供删除评论功能

首页为纯静态HTML制作。其他页面均采用HTML格式的模板文件通过替换类似“%blog-title%"的特殊代码为实际内容的方式,用ASP.NET的IHttpHandler动态生成,不带缓存处理。客户端只含有少量的JavaScript,几乎不使用AJAX。

服务端定义了以下这些类:

yoursunny.Fun2007.jAccountClient jAccount认证服务组件(2007年11月作品)。 在某服务器安装好jAccount认证服务所需密钥和COM组件,然后安装jaccount.asp“转发脚本"(2007年7月作品),即可使用;原理是将数据传送到“转发脚本"并用MD5保证完整性,然后由“转发脚本"调用COM组件到网络中心的jAccount认证服务器完成认证,最后回送数据并同样用MD5保证完整性。

sjtu.infosec.ellendy.Fun2007.blog.BaseHandler 实现IHttpHandler接口,提供各页面的公用函数。 其他页面的Handler只需继承于BaseHandler,并在Run函数中写上所需功能即可。 BaseHandler提供的主要公用函数有:

  • MyBlog属性,返回当前登录用户的博客XML文档
  • GenGuid(),返回一个新的32位随机十六进制数字字符串
  • LocalTimestamp(string),接受RFC3399日期时间字符串,返回本地日期时间字符串
  • ATOMns属性,返回ATOM的XNamespace
  • SYSns属性,返回本程序扩展的XNamespace
  • GetBlogUrl(string),返回指定学工号的博客首页完整地址
  • GetFeedUrl(string),返回指定学工号的ATOM feed完整地址
  • GetEntryUrl(string,string) ,返回指定学工号、指定日志ID的日志页面完整地址
  • GetBlog(string),返回指定学工号的博客XML文档
  • SaveBlog(XDocument),保存博客XML文档到文件系统
  • GetEntry(string,XDocument),在给定XML文档中找到指定日志ID的日志XElement
  • GetEntry(string,string),在给定学工号的XML文档中找到指定日志ID的日志XElement
  • GetComment(string,XElement),在给定日志XElement中找到指定评论ID的评论XElement

sjtu.infosec.ellendy.Fun2007.blog.LoginHandler 对应login.ashx的GET方式请求。 调用jAccountClient类的相关功能,执行登录操作,登录完成后跳转到“我的日志"页面。

sjtu.infosec.ellendy.Fun2007.blog.MyBlogHandler 对应MyBlog.ashx的GET方式请求。 从MyBlog.htm创建并显示“我的日志"页面。

sjtu.infosec.ellendy.Fun2007.blog.UploadHeadHandler 对应UploadHead.ashx的POST方式请求。在“我的日志"页面表单提交调用。 参数:theFile,头像图片文件上传。 接受头像上传,并调用ResizeImage(Stream input,Stream output,int width,int height,out int w,out int h)(2007年8月作品)将图片缩小到50x75像素以内,然后跳转到“我的日志"页面。

sjtu.infosec.ellendy.Fun2007.blog.GetBlogHandler 对应GetBlog.ashx的GET方式请求。 参数:id,学工号;curpage,页码,0表示第一页,可选;year,存档年份,可选;month,存档月份,与year配合使用。 从GetBlog.htm创建并显示“×××的日志"页面。

sjtu.infosec.ellendy.Fun2007.blog.GetFeedHandler 对应GetFeed.ashx的GET方式请求,允许不登录访问。 参数:id,学工号。 读取博客XML文档,移除扩展命名空间的元素,输出ATOM feed。

sjtu.infosec.ellendy.Fun2007.blog.GetEntryHandler 对应GetEntry.ashx的GET方式请求。 参数:id,日志ID;owner,日志作者学工号。 从GetEntry.htm创建并显示阅读日志页面。

sjtu.infosec.ellendy.Fun2007.blog.NewEntryHandler 对应NewEntry.ashx的GET方式请求。 直接输出NewEntry.htm的内容。

sjtu.infosec.ellendy.Fun2007.blog.NewEntrySaveHandler 对应NewEntry.ashx的POST方式请求。 参数:title,日志标题;body,日志内容,HTML代码。 当前用户创建一篇新的日志,且更新feed中atom:feed/atom:updated元素为当前时间,然后跳转到阅读日志页面。

sjtu.infosec.ellendy.Fun2007.blog.EditEntryHandler 对应EditEntry.ashx的GET方式请求。 参数:id,日志ID。 从EditEntry.htm创建并显示“修改日志"页面。

sjtu.infosec.ellendy.Fun2007.blog.EditEntrySaveHandler 对应EditEntry.ashx的POST方式请求。 参数:id,日志ID,QueryString;title,日志标题;body,日志内容,HTML代码。 修改当前用户指定ID的日志,且更新feed中atom:feed/atom:updated元素为当前时间,然后跳转到阅读日志页面。

sjtu.infosec.ellendy.Fun2007.blog.DelEntryHandler 对应DelEntry.ashx的POST方式请求。在“我的日志"页面用AJAX调用。 参数:id,日志ID。 删除当前用户指定ID的日志,然后输出JavaScript代码使“我的日志"页面刷新。 本页面要求使用POST调用,可以避免跨站漏洞。

sjtu.infosec.ellendy.Fun2007.blog.PostCommentHandler 对应PostComment.ashx的POST方式请求。 参数:entry,日志ID;owner,日志作者学工号;body,评论内容,纯文本。 在日志上以当前用户名义创建评论,然后跳转到这篇日志的阅读页面。

sjtu.infosec.ellendy.Fun2007.blog.DelCommentHandler 对应DelComment.ashx的POST方式请求。由日志作者在阅读日志页面用AJAX调用。 参数:entry,日志ID;id,评论ID。 删除指定ID的评论,然后输出JavaScript代码使阅读日志页面刷新。 本页面要求使用POST调用,可以避免跨站漏洞。

客户端没有定义任何类库结构,只是在部分页面引入了彼此无关的几个处理函数、实现简单的弹出确认框等功能;整个程序还是以服务端处理为主。在NewEntry.htmEditEntry.htm调用了富文本编辑器,选用的编辑器是一款开源JavaScript编辑器TinyMCE,它具有优秀的浏览器兼容性,且经过精简数据量只有280kb。在GetBlog.htm页面上有一个较为复杂和重要的客户端函数showpager,功能是在调用位置显示一组翻页链接。

程序美化方面,无需过多的处理,因为大部分程序界面和CSS、图片均是直接取自校内网。校内网的界面,与其他一些BSP或SNS站点相比,并不能算是最好的;然而,校内网的HTML结构较为清晰,基本上实现了内容和显示样式的分离,因此采取校内网的样式并无不妥。由于本程序是用于个人学习、研究,对校内网并不构成侵权;何况,校内网的界面,也就是仿制Facebook——美国最大的SNS站点。

五、 测试和评价

根据yoursunny自有的开发方法,单元测试是在程序编码的过程中同步完成的,每写好一个或几个函数、至多是一个类,只要可以展示效果,就尽可能进行及时的测试,以防错误扩散。

不得不承认,低级的语法错误我常常犯,比如分号写成冒号、单词拼错,Visual Studio 2008在我写出错误的当场就画出了红色波浪线,所以可以很快发现。还有一个我常犯的低级错误是弄错类成员的保护级别,如试图从子类中调用父类的private成员,这种错误也很容易发现。这个项目的服务端程序中,我选择了使用System.Xml.Linq这套API操作XML,这是前不久刚刚出现的新技术,对我是完全陌生的;由于定义的存储格式中需要使用命名空间,我在各种调用中漏写XNamespace十几次;写文件操作中漏写XNamespace,可以通过打开存储的XML文件看到——xmlns不见了;读文件操作中漏写XNamespace,通常表现为运行时抛出NullReferenceException异常。

还有一个错误,2007年12月时没有发现,2008年1月1日才发现——存档日期的月份显示为两位、当前选中的月份要求附加selected这个CSS类。存档日期显示样式的判断代码是(archive.year==year.ToString() && archive.month==month.ToString()) ,“12"是两位数字、没出问题,到了1月、“1"是一位数字、就出问题了。改成(archive.year==year.ToString(“D4") && archive.month==month.ToString(“D2")),解决。类似的过几个月出问题的现象,我在做心擎网时也遇到过(0开头被JavaScript当作八进制数字,到了8月份就出错了)。看来,做测试时还需要修改机器日期,在“千年虫"以后,防止“1月虫"或“8月虫"的出现。

还有些不完善之处是测试员同学帮忙发现的。例如,显示评论时,评论的换行变成了空格,这是HttpUtility.HTMLEncode只转义<>&’"五个字符、不处理换行造成的,只需增加额外处理、把换行符\n替换成<br/>即可。

最后,我认为,这套blog管理系统基本符合需求,可以正常使用、具有良好的浏览器兼容性(虽然我总是推荐使用Firefox进行浏览),但是没有商用价值。我在开发这个程序的过程中,熟悉了System.Xml.Linq的使用方法,这是我的收获。

由于工期紧、且同期执行的项目过多,没能继续对本项目进行升级。因为代码开源、有简单的类层次结构,虽然没有写注释、但函数和变量名字都能很好的反映其含义和功能,程序在日后还是可以维护和升级的。程序中重用了本人过去的代码,主要是jAccounClient类和ResizeImage函数,我认为代码的合理重用可以极大提高开发速度,相当于站在巨人的肩膀上。

最后总结一下本程序的特点:

  • 完善的实名认证,出现违法信息时可以直接追查到学院和个人;
  • 具备一定的开放性,支持ATOM输出,支持分享到del.icio.us。

我认为,如果有人在某方面做得更好,就直接拿来使用吧:

  • 使用jAccount作为登录的手段,由网络中心负责保护帐户的安全性,用户可以少记一组密码,网站管理员也可以省掉解决“忘记密码"等问题的工作;
  • 使用jAccount作为实名认证的方法,网络中心已经严格认证了,比耗费大量成本配备客服24小时盯着屏幕负责认证方便多了;
  • 支持聚合源输出,这是Web2.0站点的最基本功能,没有人会天天直接看着各位的博客,但是很多人会天天使用Google Reader“扫掉"各位的数百篇文章;
  • 分享到del.icio.us,因为它是世界上最具影响力的书签分享站点;
  • 图片上传到大脚社区,这是正在快速成长的图片社区,图片有机会被更多人看到,同时节省了本网站的带宽。