5.13 共享书签系统——追加新书签
要使本系统更实用化,必须提供让用户方便地追加书签的功能,这里有4种追加自己新书签的方式:第1种就是通过网址直接追加新书签;第2种本质上与第一种是一样的,只不过在浏览器的“热门链接”中追加一个按钮,当想将浏览过的网页加入到自己的书签时,只用点击“热门链接”中的按钮(本系统提供的帮助中介绍了如果追加此按钮,名为“加入到我的书签”);第3种是通过定制搜索引擎(如Google)来追加书签;第4种最简单,将网站上别人的书签加入到自己的收藏夹中。
本章系统中删除了第3种方式,因此本节只介绍第1、第2与第4种方式。其截图如下。
追加书签
根据输入的URL追加书签
要点
在共享书签系统中,与书签关联的评论、tag、关键字等信息都是各自作为1个记录,登录到表bm_comment/bm_tag/bm_key中的。当然,这些评论、tag、关键字信息中必须包含作为父信息的书签信息。如果每一条评论、tag信息中都包含完全的书签信息,那么对于相同的标题、URL的书签信息而言,必然造成重复记录。
在关系数据库(RDB:Relational DataBase)中提供了解决上述问题的方法。为了避免出现重复的数据,尽量将表分割开来,相互通过共同的关键字关联起来(所谓表的标准化)。用在这里,就是将书签信息独立保存在书签信息表bm_master中,而在表bm_comment/bm_tag/bm_key只是保存书签ID,使其与书签信息关联。这样就不会信息会重复保存,使数据库空间得到充分利用。
但是,这样将表分割成多个表时,出现一个不得不考虑的问题。例如,当向表bm_master成功登录数据,而向表bm_comment的数据登录处理失败(反之情况也可以)时,会出现什么情况呢?页面上检索书签信息时,将检索不出你登录的这条信息,因为检索时,表bm_master中那条信息找不到与之对应的评论信息。换句话说,数据库中出现了不整合的问题数据。
为解决上述问题,必须要到事务处理(transaction)的概念,即将表bm_master的登录处理,bm_comment的登录处理,以及bm_tag的登录处理作为一个事务来处理(通俗地说就是作为一件事来做),要么都成功,要么都失败。当多个SQL命令作为一个事务来处理时,如果其中有一个SQL命令失败时,那么所有SQL命令的执行结果都无效。只有当所有的SQL命令都成功时,整个事务才视为成功。使用事务处理能保证数据的整合性。
建议你可以事务处理为主线,理解本节的代码。
目录结构
代码
如果是直接由URL来追加书签的情况下(fromflg的值为1),需要取得页面的标题,因此此处调用setContentsfromUrl方法取得页面的标题以及内容介绍。
将Bookmarklet(参见后面的补充资料)传过来的标题、URL设置到书签登录页面中。
已经被收到个人收藏夹的用户数。
媒体类别与内容分类的下拉框。
addBookmarkbyid方法将其他用户的书签收到自己的个人收藏夹中。
进行书签登录前的检查。
调用setBookmarkInfo方法进行书签登录。
从Query信息中取得书签的标题(title)与链接URL,并将取得的值设置到页面中。
设置页面头部信息。
以下设置页面左边的信息。
类的构造函数(PHP5版本)中进行数据库连接对象的初始化。
在毁坏函数中关闭数据库连接对象。
定义书签登录方法。
在由URL直接登录书签的情况下,因为已经在setContentsfromUrl方法取得了书签的内容介绍,此处不再调用getContents方法进行此类处理。
beginTransaction方法定义事务处理的开始,beginTransaction方法开始至下面的commit方法之间为一个完整的事务。commit方法提交所有的数据更新。
调用setMasterInfo方法向表bm_master中登录书签基本信息。
调用setCommentInfo方法向表bm_comment中登录用户的评论。
取出评论中包含的标签(tag),以括号(“,”)包含起来的文字被看做标签。
调用setTagsInfo方法,setKeysInfo方法进行标签,关键字的登录。
出现异常时,调用rollBack方法复原前面事务中的所有更新。
定义由URL取得网页标题,内容介绍的方法setContentsfromUrl。
getContents才是真正取得网页标题、内容介绍的方法。
将取得的标题等内容设置到书签登录页面中。
变量$opts中定义网络链接选项,详细请参见本节最后的补充资料。
调用stream_context_create函数,根据链接选项的设置建立链接对象。
链接对象URL,并取得网页内容。
调用mb_convert_encoding方法强制将网页的字符代码转换为UTF-8。
一般网页中名为keywords的meta中放置着网页中包含的关键字。正则表达式$pettern1就是用于取得这些关键字用。
定义网页标题的正则表达式$pettern5。
从66行到109行进行解析HTML的工作,即将所有HTML的标签(tag)去掉,只剩下纯的文字字符串来作为书签的介绍。
数据库中保存的书签内容介绍不可能很长,取得的字符串长度超过500时,只摘取前面的500个字符。
将内容简介保存在数组中。
119行到133行取得网页的标题,也是书签的标题。
为了在书签登录页面上构造媒体类型下拉框,定义方法getStylelist。getCategorySelect方法也用于定义内容分类下拉框,与getStylelist方法大同小异,此处省略介绍。
定义登录书签基本信息(表bm_master)的方法。
首先检索表bm_master看将要登录的URL是否存在。如果存在,则不进行登录,只将对应的书签ID返回,用于用户评论(表bm_comment)的登录。
150行到161行向表bm_master中插入新的书签信息。
表bm_master的主键——书签ID是采用自增属性(auto_increment)来显示赋值,此处取出设定的书签ID(LAST_INSERT_ID())。
定义追加用户评论的方法setCommentInfo,第2个参数$bid是书签ID。
因为采用了虚拟目录的形式管理书签,此处从表folder中取出用户的根目录(parent=0时,意味是根目录)。
以书签ID、用户ID为检索表bm_comment,如果已经登录,则做更新处理(192行到197行),否则向表bm_comment中插入记录(183行到190行)。
定义将关键字字符串转换为字符串数组的方法rebuildKeys。
如果是半角字符串,则半角空格将不被看做分割符,即将以半角空格分隔的一系列半角字符串看做一个关键字保存起来。
定义标签追加方法setTagsInfo,第二个参数为书签ID。
调用方法rebuildKeys将字符串转换为字符数组。
循环向表bm_tag中追加标签。
追加前先检查是否已经存在相同的标签。否则进行追加。
定义关键字追加方法setKeysInfo,第二个参数为书签ID。方法setKeysInfo与方法setTagsInfo的逻辑基本相似,下面省略了相关解说。
定义将其他用户的书签加入到自己的个人书签收藏夹的方法。
将其他用户的书签加入到自己的个人书签收藏夹时,只用在表bm_comment中追加一个评论列为空的记录即可。
为了区别利用Bookmarklet来追加书签,此处设置一个区分标志fromflg。
书签登录页面除了进行上面介绍过的服务器端数据检查,还进行了客户端数据检查(检查函数chk)。
保存标示fromflg,contents等。
补充:
Bookmarklet
为了更方便地追加书签,本系统使用了一种称为“Bookmarklet”的方式。Bookmarklet就是使登录书签更方便、简单的JavaScript脚本。Bookmarklet的使用方法也非常简单,只用事先在浏览器的“收藏夹”中登录下面的JavaScript即可。以后,在收藏对象网页的出现时,点击浏览器收藏夹中那个链接后,就能够调用出书签登录的表单。
当然,所有信息都在书签登录的表单中直接输入也是可以的,这样的处理方式,在本系统中具备了,与在浏览器的收藏夹中追加网址几乎完全相同的方式追加书签。
MySQL的表类型
在MySQL中,根据用途提供了几种表的类型,如表5-28所示。
表5-28 MySQL的表类型
通常,没有进行任何设置时,默认做成是MyISAM类型的表的,这样的表不支持事务处理(执行时不会发生例外,只是不支持事务处理)。使用事务处理时,必须明确将表定义为InnoDB类型。参见下面的例子:
如何将他人的书签加入到自己的收藏夹
在系统的首页以及各分类书签一览中,如果书签不是登录用户的情况下,都在最后有一个用于追加的图片按钮(如图5-5所示),单击这个按钮就可以将此书签加入到自己的收藏夹中。
图5-5 用于追加的图片按钮
具体的程序如下。
首先判断这个书签是否是登录用户的(以及处于登录状态,且所有标志为真,所有标志的设置见5.14节),如果不是的,则显示追加图片按钮。
单击“追加”按钮,则调用/bm/submit/add动作将书签加入到自己的收藏夹。关于动作/bm/submit/add本节的代码中已有介绍。
关于取得HTML网页内容
在PHP中取得远程URL的网页内容(即打开远程文件)时,就如同打开(open)本地文件一样。只不过使用file_get_contents函数(而不是file函数)。file_get_contents函数的第三个参数$context为打开文件的上下文件选项。因此必须事先使用stream_context_create函数创建打开文件的上下文件选项。
而$opts为上下文件选项的联想数组,包含如用POST访问、使用代理、发送header等。例如如果要使用代理服务器时,$opts的设置如下。
其中,tcp://www-proxy:10080为Web代理服务器的地址。