学习日记用户取回密码(重置密码)分析设计

  用户重置密码的流程设计如下:

  1、用户忘记密码,请求重置密码;

  2、检测用户是否在72小时内请求过重置密码,若是,给出提示消息,中止;否则继续;

  3、系统发一封包括用户ID和随机产生的6位Token到用户注册时留下的信箱,同时记下发送邮件的时间;

  4、系统每隔1小时检测取回密码的用户的邮件发送时间,如果没有超过72小时,则继续检测;如果超过72小时,则删除用户重置密码的记录,用户需要重新请求发送密码;

  5、用户收到重置密码的邮件,点击重置密码的链接,登录到重置密码的页面,用户输入新密码两遍;

  6、如果用户在重置密码页面输入的用户ID和Token都正确并且两次新密码一致,则系统接受新密码,并经MD5加密后,存入数据库,新密码生效,引导用户转往用户登录页面,同时,删除用户重置密码的记录;

  7、如果用户在重置密码页面输入的用户ID和Token有误或两次密码不一致或者密码字符非法,返回重置密码的页面,提示用户错误和重新输入新密码,直到用户输入正确或取消重置密码的过程;

  在分析了学习日记重置密码流程后,画出了用户重置密码用例的活动图后(尝试用ArgoUML0.18.1建模,模型地址:http://learndiary.tigris.org/source/browse/*checkout*/learndiary/old/documentation/login/design/resetPsd1.zargo,活动图地址(gif格式):http://learndiary.tigris.org/source/browse/learndiary/old/documentation/login/design/resetPsd1ActivityDiagram.gif?rev=1.1&view=markup),下面接着对其中涉及的对象实体作一检查:

  1、用户:重置密码的用户;

  2、邮件:用户重置密码的链接(包括:用户ID和取回密码的标志码)的邮件;

  3、邮件发送者:发送邮件的对象;

  4、用户重置密码的数据:保存用户ID、取回密码的标志码、发送重置密码邮件的时间的信息;

  5、用户重置密码的数据保管者:维护用户ID、取回密码的标志码、发送重置密码邮件的时间的信息的保管者;

  6、重置密码数据有效期限检查者:每隔一定时间间隔(如1小时)检查一次重置密码的数据是否有效,无效了就通知数据保管者抛弃数据;

  

改变学习日记登录方式余下的步骤

之前,已经把登录模式作了初步的改变。下面,计划一下后面的工作。

1、把重设密码纳入登录模式的改变中:

  1)、参考学习其他的程序的取回密码功能,决定运作方式;已完成,准备用jive3的方式;

  2)、设计用户取回密码的流程,尝试用ArgoUML0.20.alpha2进行建模,一方面可以完成设计,二方面可以验证argouml建模的可行性,取得大家的共识,还可以把这个模型提交给朋友们,请他们提意见和建议;

  3)、实现重设密码;

2、增加用户保存密码的功能,下一次登录可以不用输入密码;见http://www.learndiary.com/disDiaryContentAction.do?searchDiaryID=558&goalID=558&naviStr=a10ac0ad0改善系统的登录方式

和“用cookie保存用户密码登录流程http://www.learndiary.com/disDiaryContentAction.do?goalID=73”

3、实现1需要补充下面的知识:

  1)MD5加密,用于用户密码的加密(包括数据库中的和用户cookie中的);

  2)java中时间触发的使用Timer,用于重设密码失误期限的检测;

4、另外,是否考虑把邮件发送单独做成一个功能,在程序要发送各种邮件时都可以使用,例如,我在考虑是否单独增加对注册用户邮件的群发功能,或者把公告作为用户的默认订阅邮件。jive3的做法好像就是这样的。我前面作过这样的思考:

应该加入给注册用户发系统公告的功能

http://www.learndiary.com/disDiaryContentAction.do?searchDiaryID=1250&goalID=1250&naviStr=a10ac0ad0

    

学习jive的取回密码流程

  在这个问题走偏了。下了一个jivejdon2.5,却搞不定里面的中文化问题,本来里面没有取回密码的功能,可我还是尝试解决这个中文化问题。里面用的是is088591编码,在管理页面设置本地化和创建中文论坛,发中文帖子不行。问题暂时记在这里。先把既定的目标完成。我的环境是:Window2000+tomcat5+mysql4

  在jive3中有取回密码的功能,初步用导入导出sql文件在ultraedit比较的方法找到它存取回密码userID和Token的字段存在在表jiveuserprop中:

CREATE TABLE `jiveuserprop` (

  `userID` bigint(20) NOT NULL default '0',

  `name` varchar(100) NOT NULL default '',

  `propValue` text NOT NULL,

  PRIMARY KEY  (`userID`,`name`)

) TYPE=MyISAM;

数据:INSERT INTO `jiveuserprop` VALUES (1,'jiveAdminUserRange','15') , (1,'jiveThreadRange','15') , (1,'jiveMessageRange','15') , (1,'jiveTimeZoneID','Asia/Shanghai') , (1,'jiveAutoWatchNewTopics','false') , (1,'jiveAutoWatchReplies','false') , (1,'jiveAutoAddEmailWatch','false') , (2,'jiveSignatureVisible','true') , (2,'jive.passwordreset.token','1fgNz9LH') , (2,'jive.passwordreset.timestamp','1131889036888') , (2,'jive.passwordreset.last_sent','1131889036888') , (5,'jiveLocation','sds') , (5,'jiveOccupation','dsds') ;

可以看到,它里面的token是没有加密的。

当用户用jive3发的重设密码的链接:http://localhost:8080/jive3/resetPassword!default.jspa?userid=5&token=2qSwKIgy 重设密码后,有关的字段值就被删除了,如下:

INSERT INTO `jiveuserprop` VALUES (1,'jiveAdminUserRange','15') , (1,'jiveThreadRange','15') , (1,'jiveMessageRange','15') , (1,'jiveTimeZoneID','Asia/Shanghai') , (1,'jiveAutoWatchNewTopics','false') , (1,'jiveAutoWatchReplies','false') , (1,'jiveAutoAddEmailWatch','false') , (2,'jiveSignatureVisible','true') , (5,'jiveLocation','sds') , (5,'jiveOccupation','dsds') ;

jive3是隔了72小时取回密码失效,下面验证一下,发送密码后的值:

INSERT INTO `jiveuserprop` VALUES (1,'jiveAdminUserRange','15') , (1,'jiveThreadRange','15') , (1,'jiveMessageRange','15') , (1,'jiveTimeZoneID','Asia/Shanghai') , (1,'jiveAutoWatchNewTopics','false') , (1,'jiveAutoWatchReplies','false') , (1,'jiveAutoAddEmailWatch','false') , (2,'jiveSignatureVisible','true') , (2,'jive.passwordreset.last_sent','1133261729536') , (2,'jive.passwordreset.timestamp','1133261729536') , (2,'jive.passwordreset.token','ywE1T2Q0') , (5,'jiveLocation','sds') , (5,'jiveOccupation','dsds') ;

把系统时间调后72小时以后,重设密码失败:

Forums - Reset Your Password

  Welcome, Guest 

 Login 

 Guest Settings 

 Help 

 

Please enter the userID and the token exactly as shown in the password reset email you have previously received. Please note that the token is case sensitive.

An error occurred validating the userID and token that was just entered. Please confirm the userID and the token was entered exactly as shown in the email and note that the token is case sensitive. If both are exactly as shown in your email and 72 hours have passed since the email was sent, you will have to start over.

Reset your password 

UserID:   

Token:

Invalid token   

New Password:   

Confirm Password:   

  

查看数据库中相应的字段:

INSERT INTO `jiveuserprop` VALUES (1,'jiveAdminUserRange','15') , (1,'jiveThreadRange','15') , (1,'jiveMessageRange','15') , (1,'jiveTimeZoneID','Asia/Shanghai') , (1,'jiveAutoWatchNewTopics','false') , (1,'jiveAutoWatchReplies','false') , (1,'jiveAutoAddEmailWatch','false') , (2,'jiveSignatureVisible','true') , (5,'jiveLocation','sds') , (5,'jiveOccupation','dsds') ;

可以看出,字段值已经被删除了。

至此,jive3的取回密码实现的大概思路已经掌握了。

学习日记定位

  1、学习者面对信息的海洋,无所适从;

  2、整合个人的网络学习资源;

  3、提供到自己喜爱的学习资源的同步接口;

  4、个性化的指导,适合自己的才是最好的;

  5、集中理念,分散管理,灵活;

  6、人性化的学习家园。

解决了学习日记的3个不足之处,写了导航系统文章

  见:

  1、所有目标>>目标:公告牌的日记列表>>日记:系统使用及测试反馈中的“三个问题”;http://www.learndiary.com/disDiaryContentAction.do?goalID=1159&naviStr=a10a21

  2、所有目标>>我进行中的目标>>目标:建设“学习日记”我的日记列表>>选了3篇帖子发到七个java站 (0篇)http://www.learndiary.com/disDiaryContentAction.do?searchDiaryID=&goalID=1281&naviStr=a10a60a05  

积分和等级对鼓励学友的参与积极性有益

  昨天,我在一个java站点发了两篇文章,发现自己的等级居然由"新兵"变成了"高级程序员",虽然知道这个是假的,但还是心里挺高兴的。

  看来,在学习日记的未来版本中,应该加入具有自己特色的积分和等级系统了。

  设计这个系统要吸取设计学习日记导航系统的经验(事先多思考)和教训(高层的全面设计还是不够),要有一个长期稳定的设计,不要做完了又马上要改了。

  这里想起了网友jerry的一篇帖子(http://www.learndiary.com/disGoalContentAction.do?goalID=313&naviStr=a10ac0ae0):

 “ 标题: 学习日记系统的目的是培养学习者的兴趣  作者: jerry  创建时间: 2004-12-20 08:18:47  最近更新: 2004-12-20 08:18:47  编辑  删除 

内容

学习日记系统的目的是培养学习者的兴趣,只有学习者有兴趣,学习才会进行下去,如果让学习能像游戏一样进行就更好了。所以建议在新版学习日记中添加金钱功能,学习门派功能,让不同的人有机会在一起学习,而发表的学习日记最好符合一定的规范,使系统可以在学习者学习完成后将他的学习日记整理为一篇完整的教程。这样对学习者本身和想要学习的人来说都会有用。



  他的建议是非常中肯的。

  还有自己原来的一点想法(http://www.learndiary.com/disDiaryContentAction.do?goalID=383):

“ 日记标题 应该用一种方式使我们的学习平台的学习变得有趣  作者: admin  创建时间: 2004-11-06 12:19:00  最近更新: 2005-11-26 23:04:08  编辑 我要评论  

内容

    应该用一种方式使我们的学习平台的学习变得有趣,寓学于乐。有一句名言:书山有路勤为径,学海无涯苦作舟。我们应该把它改为:书山有路笑为径,学海无涯乐作舟。

    这种方式需要大家出主意。 

学习日记网站动态导航技术探索

littlebat慎重声明:此文的内容基于我3年以前的一个不成熟的甚至是错误的设计思路所写(具体原因请参见本文后面我的后续补充评论批注),并且本文所属的“Java学习日记开源项目”也于2006年停止并删除,不再对外公布代码,此文留存于此仅供资料备查及供朋友一种动态导航的参考,请浏览者注意鉴别。
2009年5月23日 littlebat

版权声明:本文章为JAVA学习日记网站(http://java.learndiary.com)littlebat版权所有,以“Creative Commons License”http://creativecommons.org/licenses/by/2.0/方式授权。欢迎转载,但请注明文章原始出处(http://java.learndiary.com/disDiaryContentAction.do?searchDiaryID=&goalID=1255&naviStr=a10a2167)。

提纲:

一、背境;

二、需求分析:

1、学习日记网站的主要功能页面结构;

2、需要进行动态导航的主要模块;

3、需求的提出:

1)、层次导航的需求;

2)、同一列表中帖子间的导航(即上一条,下一条类似的导航);

3)、父帖与子帖列表的双向导航;

三、具体实现过程:

1、层次导航的实现过程:

1)列出导航功能需求列表,看都有哪些导航路径;

2)分析定位特定类型的页面所需的参数;

3)确定层次导航的实现方法;

4)进行层次导航系统的设计:

(1)、进行页面节点导航封装字符串格式的设计;

(2)、页面完整导航字符串的设计;

(3)、将封装的导航字符串还原为显示在页面上的URL地址导航条;

(4)、在页面上显示层次导航的URL字符串;

2、同一列表中帖子间的导航(即上一条,下一条类似的导航)的实现过程:

1)列出导航需求列表;

2)分析实现上一条、下一条导航所需的参数;

3)确定实现上一条、下一条导航的实现方法;

4)进行上一条、下一条导航的设计:

(1)、根据层次导航的导航字符串确定上一条、下一条导航所在的层次导航位置;

(2)、确定在哪几个Struts的Action中需要处理上一条、下一条导航;

(3)、在页面上显示上一条、下一条导航的URL字符串;

3、父帖与子帖列表的双向导航的实现过程:

1)列出导航需求列表;

2)分析实现双向导航所需的参数;

3)确定实现方法;

4)进行双向导航的设计:

(1)、确定在哪几个Struts的Action中需要处理双向导航;

(2)、在页面上显示双向导航的URL字符串;

四、总结:

1、心得;

2、优点;

3、缺点;

4、愿望;

五、联系方式;

六、附件(附学习日记V0.9.0.4和学习日记系统ArgoUML建模(学习日记V0.9.0.4反向工程类图) );

七、附图

关键词:学习日记 Struts 动态导航 学习日记动态导航技术(简称:LDDN技术 ) 学习日记开发小组(简称:LDDG )

××××××××××××××××××××××××××××××××××××××××××××××××××××××××××

正文

一、背境:

JAVA学习日记(http://java.learndiary.com )网站是采用Struts框架的开源项目learndiary(http://develop.learndiary.com )的实际运行示范站点。目前由学习日记开发小组进行开发,致力于以JAVA技术构建一个普遍适用的开源网络学习交流平台。为了改善用户的浏览体验,故提出导航系统的改善计划。已经包含了这个导航系统的的最新版本为learndiaryV0.9.0.4。本文所提到的程序均可以在本站的下载菜单中获取:http://java.learndiary.com/download/download.html 。本文希望起到一个抛砖引玉的作用,引发一场关于java技术构建的web系统的导航系统设计的讨论。

二、基本思路:

1、学习日记网站的主要功能页面结构:

核心为:目标列表-》目标内容及评论-》目标下的日记列表-》日记内容及评论

示意图:(图1 学习日记网站的主要功能页面结构)

2、需要进行动态导航的主要模块:

需要进行动态导航的主要模块有3个,见学习日记网站的菜单栏:

1)、所有目标:http://java.learndiary.com/index0Action.do?pageNum=1&naviStr=a10 (显示学友提出的所有学习目标列表 )

2)、检索:http://java.learndiary.com/toSearchAction.do?naviStr=a10ac0 (检索本站的所有学习目标和学习日记 )

3)、您的目标:http://java.learndiary.com/processGoalAction.do?currentGoalState=1&pageNum=1&naviStr=a10a60 (学友自己的学习目标归类:包括:进行中的目标、退出的目标、已完成的目标 )

3、需求的提出:

1)、层次导航的需求:

我们想建成一个导航系统,它可以跟踪用户在系统中浏览页面的过程,每浏览到一个新的页面,就在导航条中加入这个页面的URL,于是,当用户浏览到别的页面,可以点击返回系统导航条中的前面浏览过的页面;当浏览一个页面是与导航条中已经存在的页面类型一致时,就截去导航条中此类型页面后面的导航URL。

征对这个需求,我大概看了一下动网的实现方式和phpWind论坛的实现方式,他们用的导航基本上是静态的,也就是说,一种页面上的导航条是固定的,例如:显示一条帖子的内容,他们的方式是:论坛首页-》论坛版块-》主题内容。

而我们的需求是,例如:显示一篇日记的内容:

在所有目标模块中,导航条为:所有目标>>日记列表>>日记内容;

图例:(图2 所有目标模块中显示一篇日记的内容)

在检索模块中,导航条为:所有目标>>检索>>检索日记列表>>日记内容;

图例:(图3 检索模块模块中显示一篇日记的内容)

在您的目标中,导航条为:所有目标>>我进行中的目标>>本目标我的日记列表>>日记内容;

图例:(图4 您的目标模块中显示一篇日记的内容)

2)、同一列表中帖子间的导航(即上一条,下一条类似的导航 ):

另外,除了上面层次导航的需求,同一列表中帖子间的导航(即上一条,下一条类似的导航 ),我们也想在网上一般的导航系统的基础有所改良。例如:在一般的导航系统中,如果你在检索结果的列表中点击查看一篇帖子,然后,你再点击这篇帖子的上一条和下一条链接,新点开的页面不是检索结果列表中的上一篇或下一篇帖子,而是这条帖子所有的版块的上一条和下一条帖子。

我们需要在检索结果列表中,点击检索到的一篇日记中的“上一条”和“下一条”链接时,显示的是检索结果列表中本日记的上一条和下一条日记。

例如,我们以“中文显示”为关键字检索日记得到一个检索结果列表:

图例:(图5 检索结果列表层次导航)

当我点击查看“解决:jsp页面中文显示问题”这篇日记中的下一条链接时,我们需要打开检索结果列表的下一篇日记:“问题:jsp中文显示,<c:set>的值可否是对象?me ”:

图例:(图6 检索结果列表层次导航2)

,而不是“解决:jsp页面中文显示问题”这篇日记所在的目标中的下一篇日记。

在“您的目标”这个模块中的“上一条”和“下一条”的导航同样存在在检索页面中点击“上一条”和“下一条”链接的问题。

3)、父帖与子帖列表的双向导航:

学习日记征对自身的功能结构特点,还需要实现:“目标<──>目标下的所有日记列表”的双向导航和“目标<──>目标下我的日记列表”的双向导航:显示目标内容

图例:(图7 双向导航显示目标内容)

,您可以看到页面右边中上有两个链接分别是:“查看所有日记”和“查看我的日记”,点击“查看所有日记”链接出现的是页面

图例:(图8 双向导航查看所有日记)

,点击“查看我的日记”链接出现的是页面

图例:(图9 双向导航查看我的日记)

;后面这两个页面的右中上部又都有到日记所在的目标中的链接:“查看目标内容”。

以上总结了学习日记需要实现的导航系统的三个方面的需求,征对这个需求,我看了一些论坛,均没有现存的东西可以参考,于是,我们决定探索一种能够实现上面动态导航需求的方法。因为我们实现的导航系统是动态变化的,故把这种实现的方法称之为“学习日记动态导航技术”(简称:LDDN技术 )。下面把我们的具体设计过程总结一下。

三、具体实现过程:

具体的设计分析过程我已经在进行的过程中记录在日记中了,所以,如果你要详细的了解我的分析设计过程的话,可以去看这两篇日记:

第一篇日记:“提高学习日记导航能力的思路”(http://java.learndiary.com/disDiaryContentAction.do?searchDiaryID=&goalID=1128&naviStr=a10a2506ah1167 ),主要记录的是我在对层次导航部分的分析设计过程;

第二篇日记:“分析学习日记横向导航及开几个窗口的思路”(http://java.learndiary.com/disDiaryContentAction.do?searchDiaryID=&goalID=1167&naviStr=a10a2506ah1128 ),主要记录的是我在对“上一条”和“下一条”导航部分的分析与设计过程。

当然,细节总是太烦琐和令人不愉快的,我把大概的设计过程总结如下:

1、层次导航的实现过程:

1)列出导航功能需求列表,看都有哪些导航路径,例如:

(5)所有目标列表->相关日记列表->显示日记内容->编辑评论;

(14)所有目标列表->进行中的目标列表->您的日记列表->显示日记内容->编辑评论;

(21)所有目标列表->检索页面->搜索结果日记列表->相关日记列表->显示日记内容->撰写评论;

全部列表请见我上面提到的第一篇日记中的“一、学习日记导航路径列举:”(您可以在页面中搜索位置,前后的双引号除外,下同 )。

2)分析定位特定类型的页面所需的参数(这样,就可以根据页面类型和参数唯一的确定一个页面了 ):

经过分析,唯一定位一个页面所需要的参数变为:

1》页面类型;

2》参数ID;

其中参数ID分为下面几种情况:

1》列表:需要列表的parentID;

2》单个条目:需要它的ID;

3》用户的进行、完成等目标列表:什么都不需要,用户ID在Session中;

4》用户的进行、完成等目标的用户日记列表;目标ID,所需的用户ID在Session中;

5》搜索列表:什么都不需要,因为搜索条件字符串已保留在全局Session中了;

具体的分析过程请见我上面提到的第一篇日记中的“(2 )、如何唯一的定位一个特定的页面呢? ”

3)确定层次导航的实现方法:

当点击一个新的页面时,就把定位这个页面所需的参数加入到层次导航的参数链表中(链表的一个节点储存的是一个新的页面的定位参数 ),这时链表变长;当点击一个在链表中已经存在这种类型的页面(例如:显示一篇日记的内容的页面 )时,就把在导航链表中这个节点及后面的节点删除,再加上这个点击的页面的定位参数。

实现这种层次导航的需求有两种方法:

(1)、为每条导航路径在session中设置一个层次导航所需的属性,在这条路径上导航链表的增长和缩短信息就由这个属性来维持;

(2)、把这个链表封装成字符串,这个字符串在访问不同的页面时,会根据上面的思路不断增长和缩短。把这个封装的字符串连同用户新请求的页面的定位参数节点字符串一起传给请求这个页面前的Struts的action,形成新的页面上导航条所需的编码字符串,然后把这个编码字符串保存在request中,供请求的页面中的下一个链接使用;并同时由action把这个形成的编码导航字符串解码处理成用户请求的页面上需要的导航字符串。

经过分析,我决定采用第(2 )种方法来实现层次导航。

具体的分析过程请见我上面提到的第一篇日记中的“我知道有2种方法可以解决这个问题 ”。

4 )进行层次导航系统的设计:

(1)、进行页面节点导航封装字符串格式的设计:

分隔符(用'a'作分隔符 )+页面节点类型(用一位字符代表,封装成一个页面类型常量类,用字符1-9,英文字符b-z和A-Z表示 )+页面参数ID(如显示目标的日记列表所需的目标ID,显示日记内容所需的日记ID )。如显示一篇日记的页面类型常量为字符:‘h’,那么,显示“解决:jsp页面中文显示问题”这篇日记内容(ID为292 )这一页面的封装字符串为:ah292;

(2)、页面完整导航字符串的设计:

在下面的讨论中会用到的相关源文件:

负责封装学习日记所需导航的页面的类型常量(/WEB-INF/src/com/learndiary/website/PageTypeConsts.java );

负责导航字符串封装的方法(/WEB-INF/src/com/learndiary/website/util/Pager.java中的public static String encodeNaviStr(String naviStr, char toPageType, String parameter) );

当用户提出一个新的页面请求,把当前页面中的完整的导航封装字符串和新的页面的类型和新请求页面的参数ID传给处理新页面显示前的Struts中的Action中,由Action调用一个方法,负责把这些参数组装成下一新页面所需的导航字符串。如:当前正显示的页面是“系统导航:所有目标>>我进行中的目标>>目标:一起学习Struts(MVC)我的日记列表”中的“目标:一起学习Struts(MVC)我的日记列表”

图例:(图10 我的日记列表)

,你可以看到当点击显示日记“解决:jsp页面中文显示问题 (0篇) ”的URL中的导航字符串为:“naviStr=a10a60a0167”(图例中左下方红圈中 ),把这个字符串和请求的显示日记的页面类型(日记为:‘h’ )和显示这篇日记所需的ID(292 )传给显示日记内容前的Action中(disDiaryContentAction.do,源文件为:/WEB-INF/src/com/learndiary/website/action/disgoal/DisGoalContentAction.java ),由负责导航字符串封装的方法(源文件为:/WEB-INF/src/com/learndiary/website/util/Pager.java中的public static String encodeNaviStr(String naviStr, char toPageType, String parameter) )进行处理,处理流程为:以显示日记这个页面的类型ID为‘h’搜索已有导航字符串,没有相同的页面,于是就把这个页面导航字符串节点(ah292 )加到完整的导航字符串(a10a60a0167)后面得到新的导航字符串(a10a60a0167ah292 ),

见例图:(图11 我的日记列表2)

,你可以看到当点击“日记:解决:jsp页面中文显示问题”这篇日记中的“我要评论”的URL中的导航字符串为:“naviStr=a10a60a0167ah292”(图例中左下方红圈中 );

当点击当前页面中的回到上级导航节点的链接时,如:“系统导航:所有目标>>我进行中的目标>>目标:一起学习Struts(MVC)我的日记列表”中的“我进行中的目标”,这时显示“我进行中的目标”页面的类型代码是‘6’,参数ID是“0”,那么以新页面的节点字符串(a60 )的类型代码‘6’搜索已有导航字符串,已经存在相同的类型代码,于是,就把导航字符串中这个节点和后面的所有节点删除得到字符串“a10”,再加上新页面的节点字符串为“a60”,得到显示“我进行中的目标”页面的导航字符串为“a10a60”,

见例图:(图12 我进行中的目标)

,你可以看到当点击“一起学习Struts(MVC) (19篇)”这篇目标的URL中的导航字符串为:“naviStr=a10a60”(图例中左下方红圈中 );

详细分析设计过程我上面提到的第一篇日记中的“2、设计中用到的方法: ”;

(3)、将封装的导航字符串还原为显示在页面上的URL地址导航条:

同样用上面的显示在“我进行中的目标”中目标“一起学习Struts(MVC)我的日记列表”中的日记“解决:jsp页面中文显示问题”来进行说明,也就是说:如何把导航封装字符串“a10a60a0167ah292”转换成字符串“系统导航:<a href="/learndiary/indexAction.do?searchDiaryID=&pageNum=1&naviStr=a10a60a0167ah292">所有目标</a>>><a href="/learndiary/processGoalAction.do?searchDiaryID=&currentGoalState=1&pageNum=1&naviStr=a10a60">我进行中的目标</a>>><a href="/learndiary/myDiaryAction.do?searchDiaryID=&pageNum=1&goalID=167&naviStr=a10a60a0167ah292">目标:一起学习Struts(MVC)我的日记列表</a>>>日记:解决:jsp页面中文显示问题<p>”,

使它在页面上显示导航条为“系统导航:所有目标>>我进行中的目标>>目标:一起学习Struts(MVC)我的日记列表>>日记:解决:jsp页面中文显示问题”?

见图例:(图13 我的日记列表2)

解决这个问题我用两步走的方法:

<1>、用一个方法可以把每一个导航节点的封装字符串转换为导航URL字符串。主要是根据需要显示的页面类型和参数ID来进行转换,实现过程比较简单,请查看源文件中负责解封导航节点封装字符串的方法(/WEB-INF/src/com/learndiary/website/util/Pager.java中的public static String decodeNodeStr(String naviStr, String nodeNaviStr, HttpServletRequest request, boolean ifLast) throws Exception );

<2>、把每个节点封装的字符串联接在一起,形成完成的页面层次导航所需的URL字符串。根据惯例,当前(也就是最后一个节点的导航URL灰化,无链接 )。具体实现请查看源文件中负责解封整个导航封装字符串的方法(/WEB-INF/src/com/learndiary/website/util/Pager.java中的public static String decodeNaviStr(String naviStr, HttpServletRequest request) throws Exception );

具体的分析设计过程请见我上面提到的第一篇日记中的“把封装的字符串转化为下一个页面显示导航条所需要的字符串”。

(4)、在页面上显示层次导航的URL字符串

在需要层次导航的页面上,把从request中获得的属性“navigation”显示在页面的左上部,并把从request中获得的相应的封装导航字符串naviStr作为参数附在每一个URL的后面就行了。

至此,学习动态导航系统中的层次导航部分已经设计完成,下面继续进行显示“上一条”和“下一条”的水平导航部分的探索。

2、同一列表中帖子间的导航(即上一条,下一条类似的导航 )的实现过程:

1)列出导航需求列表:

(1)、在所有目标列表中:

1>目标内容;

2>目标的日记列表(上一条:在这里即上一目标的日记列表);

3>日记列表中的日记内容;

(2)、在检索结果页面中:

1>检索目标列表:

<1>目标内容;

<2>目标的日记列表;

<3>日记列表中的日记内容;

2>检索日记列表:

<1>日记内容;

<2>所在目标的日记列表中的日记内容;

(3)、您的进行中的目标列表:

1>目标内容;

2>目标的全部日记列表;

3>目标的我的日记列表;

4>目标的全部日记列表中的日记;

5>目标的我的日记列表中的日记;

2)分析实现上一条、下一条导航所需的参数:

因为是实现同一列表中的同一级别的帖子之间的导航,所以只需要得到需要导航的条目的ID就行了,其它所有参数都不必改变。

3)确定实现上一条、下一条导航的实现方法:

现在的问题是:如何根据当前条目的ID,得到上一条目和下一条目的ID呢?

答案是:条目的列表,条目在列表中的排序方式,当前条目的ID。为了在查询结果集中得到当前条目的前后条目的ID,可以有下面的方法:

(1)在一个直接操纵数据库的方法中从查询结果集中取出每个ID(整型)后,保存在数组中,马上关闭数据库连接,减少数据库连接开销。然后在同个方法中取得前面、当前、后面记录的ID,只返回这3个元素的数组给Pager类(负责产生页面导航所需要的URL字符串的工具类)处理,这样,可以保证每条数据都是最新的,但是要不停的开启和关闭数据库连接;

(2)把查询结果产生的数组全部存在session中,Pager在session中取数据。这样,可以减少数据库的查询,但是存在两个问题,那个比较长的数组在session中始终占用内存,还有,取出的数据的排序关系可能是过期的(这时,有人往数据库中增加或修改了数据)。

我觉得第一种方法可以减轻对网站虚拟主机资源的压力,决定采用第一种方法。

另外,在直接操纵数据库产生的结果集中查询邻近的ID会出现几种结果呢?

这里用“-1”表示没有相应的帖子。

1>在用户查看帖子期间,这篇帖子被删除了,结果返回:{-1,-1,-1};

2>只有一篇符合要求的帖子,结果返回:{-1,当前帖子ID,-1};

3>当前帖子是第一篇帖子,结果返回:{-1,当前帖子ID,下一条帖子ID};

4>当前帖子是最后一篇帖子,结果返回:{上一篇帖子ID,当前帖子ID,-1};

5>当前帖子前后都有帖子,结果返回:{上一篇帖子ID,当前帖子ID,上一篇帖子ID};

现在,就可以把这个含有前一条、当前、后一条帖子ID的整型数组传给Pager类中的产生上一条、下一条导航URL字符串的相应方法进行处理了。

下面是上一条、下一条导航的具体设计。

4)进行上一条、下一条导航的设计:

(1)、根据层次导航的导航字符串确定上一条、下一条导航所在的层次导航位置:

进行上一条、下一条的导航需要知道被导航的帖子所在的层次导航的位置。例如:显示一篇日记的内容:

在所有目标模块中,导航条为:所有目标-》日记列表-》日记内容;

图例(图14 层次导航路径1):

在检索模块中,导航条为:所有目标>>检索>>检索日记列表>>日记内容;

图例(图15 层次导航路径2):

在您的目标中,导航条为:所有目标>>我进行中的目标>>本目标我的日记列表>>日记内容;

图例(图16 层次导航路径3):

在上一条、下一条的导航中,层次导航条除了上一条目和下一条目内容的改变,其余是不会变的;而且,要得到当前条目的前后条目的ID,在不同的层次导航中是不同的。例如:在上面的路径1中,得到日记列表的查询条件是本目标下的所有日记;在路径2中,得到日记列表的查询条件是检索页面的条件组合;在路径3中,得到日记列表的查询条件是本目标下的用户的所有日记。而且,在学习日记的设计中,这三种情况的排序方式是分开的,可以由用户在浏览时自选的。

为了得到不同层次导航下的上一条、下一条URL导航字符串,我在页面导航URL字符串产生工具类Pager((/WEB-INF/src/com/learndiary/website/util/Pager.java)中用了3个重载的、用于产生上一条、下一条导航URL字符串的方法来征对不同的三种情况(与前面说的3种路径不是一一对应的),分别是:

1>、(包括对这一路径下的目标列表中目标的浏览和目标的日记列表的浏览,和检索目标列表中目标的浏览,和检索日记列表中日记的浏览):public static String getPreNextNaviStr( char toPageType, String url, HttpServletRequest request, String naviStr, int currentID, String condition,int orderType, int direction) throws Exception。输入参数是:toPageType-请求的页面类型,url-请求的页面的“/***Action.do”路径,request-请求对象,naviStr-当前页面的导航封装字符串,currentID-当前条目的ID,condition-查询的where子句,orderType-排序类型,direction-排序方向。为了分离数据库访问的代码,在这个方法中调用了一个直接访问数据库的类(/WEB-INF/src/com/learndiary/website/db/PageDB.java)中的方法(public int[] getAdjacentIDs(String tableName, int currentID, String condition, int orderType, int direction) throws Exception )来得到含有前一条、当前、后一条帖子ID的数组;

2>、(包括对检索日记列表的日记所在目标、日记所在目标下的日记列表的浏览):public static String getPreNextNaviStr(String url, HttpServletRequest request, String naviStr, int searchDiaryID, String condition,int orderType, int direction) throws Exception。输入参数是:url-请求的页面的“/***Action.do”路径,request-请求对象,naviStr-当前页面的导航封装字符串,searchDiaryID-搜索日记列表中当前日记的ID,condition-查询的where子句,orderType-排序类型,direction-排序方向。为了分离数据库访问的代码,在这个方法中调用了一个直接访问数据库的类(/WEB-INF/src/com/learndiary/website/db/PageDB.java)中的方法(public int[] getAdjacentIDs(String tableName, int currentID, String condition, int orderType, int direction) throws Exception )来得到含有前一条、当前、后一条帖子ID的数组;

3>、(包括对进行中的目标、退出的目标、完成的目标列表中目标的浏览):public static String getPreNextNaviStr(String url, HttpServletRequest request, String naviStr, int userID, int currentID, int myGoalTypeFlag, int orderType, int direction) throws Exception。输入参数是:url-请求的页面的“/***Action.do”路径,request-请求对象,naviStr-当前页面的导航封装字符串,userID-当前用户ID,currentID-当前条目的ID,myGoalTypeFlag-用户目标的类型(进行、退出、或者完成),orderType-排序类型,direction-排序方向。为了分离数据库访问的代码,在这个方法中调用了一个直接访问数据库的类(/WEB-INF/src/com/learndiary/website/db/PageDB.java)中的方法(public int[] getAdjacentIDs(int userID,int currentID, int myGoalTypeFlag, int orderType, int direction) throws Exception)来得到含有前一条、当前、后一条帖子ID的数组;

现在的问题是怎么样来区别上面1>、2>、3>中列出的各种情况,并调用对应的“getPreNextNaviStr”方法来产生正确的上一条、下一条的URL导航字符串呢?

我的答案是根据当前页面的封装导航字符串来确定,我通过分析上面1>、2>、3>中列出的各种情况的导航字符串的特征码,然后在程序中通过检索特征码来确定当前页面的层次导航位置(对应于上面不同的几种情况),这是一个烦琐的过程,这里仅列举一二:

例如:在检索目标的列表中的目标页面中的封装导航串一定会含有“ae”两个字符,且帖子类型为“目标”;在检索日记的列表中的日记页面中的封装导航串一定会含有“ad”两个字符,且帖子类型为“日记”。

(2)、确定在哪几个Struts的Action中需要处理上一条、下一条导航:

1)、首先,需要导航的地方有三种情况:

1>、目标内容

2>、日记内容

3>、目标的日记列表

2)、

1>、1)的1>和2>需要放在DisGoalContentAction.java(/WEB-INF/src/com/learndiary/website/action/disgoal/DisGoalContentAction.java)中处理;

2>、1)的3>需要分在几个地方处理,分别是:

1>检索目标的日记列表,所有目标的日记列表,进行、完成、退出的日记列表:在DiaryAction.java(/WEB-INF/src/com/learndiary/website/action/disdiary/DiaryAction.java)中处理;

2>进行、完成、退出的我的日记列表:在MyDiaryAction.java(/WEB-INF/src/com/learndiary/website/action/mydiaries/MyDiaryAction.java)中处理;

(3)、在页面上显示上一条、下一条导航的URL字符串;

在需要上一条、下一条导航的页面上,把从request中获得的属性“preNextNavigation”显示在页面的右上部和右下部就行了。

上一条、下一条导航完整的设计分析过程见我上面提到的第二篇日记:“分析学习日记横向导航及开几个窗口的思路”(http://java.learndiary.com/disDiaryContentAction.do?searchDiaryID=&goalID=1167&naviStr=a10a2506ah1128 )。

3、父帖与子帖列表的双向导航的实现过程:

1)列出导航需求列表:

(1)“目标<──>目标下的所有日记列表”的双向导航;

(2)“目标<──>目标下我的日记列表”的双向导航;

2)分析实现双向导航所需的参数:

只需要目标的ID或日记列表的目标ID和导航封装字符串;

3)确定实现方法:

直接在Action中得到当前目标的ID或日记列表的目标ID和导航封装字符串,附在“***Action.do?”的后面就行了。

4)进行双向导航的设计:

(1)、确定在哪几个Struts的Action中需要处理双向导航:

1>、在“目标──>目标下的所有日记列表”和“目标──>目标下我的日记列表”的导航,需要放在DisGoalContentAction.java(/WEB-INF/src/com/learndiary/website/action/disgoal/DisGoalContentAction.java)中处理;

2>、在“目标下的所有日记列表──>目标”的导航,需要放在DiaryAction.java(/WEB-INF/src/com/learndiary/website/action/disdiary/DiaryAction.java)中处理;

3>、在“目标下的我的日记列表──>目标”的导航,需要放在在MyDiaryAction.java(/WEB-INF/src/com/learndiary/website/action/mydiaries/MyDiaryAction.java)中处理;

(2)、在页面上显示双向导航的URL字符串:

在需要“目标──>目标下的所有日记列表”和“目标──>目标下我的日记列表”的导航的页面上,把从request中获得的属性“jumpToViewDiaries”显示在页面的右上部和右下部;在“目标下的所有日记列表──>目标”和“目标下的我的日记列表──>目标”的导航中,把从request中获得的属性“jumpToViewGoal”显示在页面的右上部和右下部就行了。

四、总结:

1、心得:

1)、编码前的分析设计是非常重要的,这一步工作做好了,编码就很容易了(但我还做得不够,如下面的第3)条心得。可是,也许是我的水平有限,有些应该放在设计时的工作不到编码的时候就是想不到,还望大家给予指点);

2)、Struts框架把程序的逻辑实现代码和页面显示部分能比较好的分离,有利于功能模块的新增和程序的后期维护;

3)、在进行类和类的方法的设计时(如前面提到的Pager类和PageDB类),没有先进行完整的高层设计,是采用边编码边设计的方式,致使类的设计不够面向对象,给后期的理解和维护会造成困难;

4)、如果这种导航设计思路真的有用,有必要把它进行精心的设计,做成插件的形式,这样可以把它方便的应用于需要这种动态导航的各种java的web程序中;

2、这种动态导航的优点:

1)、能够极大的提高用户的浏览体验,使网站的导航更符合逻辑和人们的思维习惯;

2)、能够无限的进行需要导航的页面的增加和减少,后期的维护代码少量增加就行了;

3、这种动态导航的缺点:

1)、实现过程较为复杂,牵涉的页面和逻辑代码较多,权衡实现的代价和收到的效果,真的值得吗?;

2)、动态导航能被用户的使用习惯接受吗?这是一个未知数;

3)、还有什么缺点呢?暂时还没有想出来,大家帮我们想一想吧。

4、愿望:

1)、希望朋友们能够对学习日记开发小组提出的这种动态导航技术展开充分的讨论,论证这种导航方式的可行性;

2)、希望与软件相关(尤其是java )、学习相关(尤其是计算机软件自学 )、开源相关(尤其是java开源 )的网站结为友站关系并交换网站链接,共谋发展。我们的链接代码(请直接复制代码,我们的新logo正在酝酿中 ):图片链接:“<a href="http://java.learndiary.com" target=_blank><img border=0 src="http://java.learndiary.com/pictures/learndiarylink.gif</img>" alt="JAVA学习日记:分享目标,分享快乐。"></a>”,文字链接:“<a href="http://java.learndiary.com" target=_blank title="JAVA学习日记:分享目标,分享快乐。">JAVA学习日记</a>”。如果,你愿意与我们交换链接,请把你的链接代码发至:“mdx-xx@tom.com”。

五、联系方式:

1、在学习日记网站对这篇文章进行评论(建议注册登录后再评论,否则是以guest身份评论):http://java.learndiary.com/disDiaryContentAction.do?searchDiaryID=&goalID=1255&naviStr=a10a2167

2、在学习日记网站留言(不需登录,但留言内容只有本站管理员才能查看与回复):http://java.learndiary.com/toWriteMessageAction.do?typeID=4&goalID=0&naviStr=a10af0

3、电子邮件:mdx-xx@tom.com

4、QQ:81251712

六、附件

1、学习日记V0.9.0.4,简介:http://java.learndiary.com/disDiaryContentAction.do?goalID=1245&naviStr=a10a21

下载地址:http://java.learndiary.com/download/learndiaryV0.9.0.4.war

2、学习日记系统ArgoUML建模(学习日记V0.9.0.4反向工程类图),下载地址:http://java.learndiary.com/download/learndiaryV0.9.0.4.zargo

用ArgoUML V0.20.alpha2打开,ArgoUML下载地址:http://argouml-downloads.tigris.org/nonav/argouml-0.20.ALPHA_2/ArgoUML-0.20.ALPHA_2.zip )。

-全文完

作者:JAVA学习日记 http://java.learndiary.com littlebat )

2005年11月25日下午

七、附图

********************************************************************************************

图1 学习日记网站的主要功能页面结构

********************************************************************************************

图2 所有目标模块中显示一篇日记的内容

********************************************************************************************

图3 检索模块模块中显示一篇日记的内容

********************************************************************************************

图4 您的目标模块中显示一篇日记的内容

********************************************************************************************

图5 检索结果列表层次导航

********************************************************************************************

图6 检索结果列表层次导航2

********************************************************************************************

图7 双向导航显示目标内容

********************************************************************************************

图8 双向导航查看所有日记

********************************************************************************************

图9 双向导航查看我的日记

********************************************************************************************

图10 我的日记列表

********************************************************************************************

图11 我的日记列表2

********************************************************************************************

图12 我进行中的目标

********************************************************************************************

图13 我的日记列表2

********************************************************************************************

图14 层次导航路径1

********************************************************************************************

图15 层次导航路径2

********************************************************************************************

图16 层次导航路径3

应该加入给注册用户发系统公告的功能

  学习日记发新版了,却不能通知在学习日记注册的朋友们,看来应该考虑怎么样把通告发给朋友们。

  1、一个简单的办法是把公告这条目标都加为用户的订阅目标。这样每发一个公告,就当作一个目标下日记更新发给用户了。但是,这样一次连续的发几百上千个公告,虚拟空间的服务器支持吗?发送邮件的邮件服务器支持吗?怎么样确保公告都成功的发给了每个注册用户呢?

  征对前一个问题,我可以问一下虚拟空间的提供商;后一个问题可以看邮件服务器的资料。

  2、另一个方法是专门写一个发消息给所有注册用户的action,这样就可以灵活的处理上面有可能的问题。

  但是,如果第一种方法能满足我的需要,我又何必去费心写多余的代码呢:)

我们写代码一定要遵守代码规范,完善文档

  文档现在在我的心里越来越重要,学习日记不完善的文档成了我心里的一个疙瘩,文档不完善,学习日记的继续向前发展将岌岌可危。主要是:

  1、大家难以交流;

  2、设计思路没有整理,有的代码实现日后再看将是一头雾水,成为向前的羁绊。

  不管是否立刻开始新系统的设计,还是继续完善学习日记现有的一些网友早就提出的紧要问题,把学习日记的文档完善成了一个很大的挑战摆在了我的眼前。

  怎么办?To be or not to be?