提纲一背境
二需求分析
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缺点
关键词学习日记Struts动态导航学习日记动态导航技术(简称LDDN技术 )学习日记开发小组(简称LDDG )
××××××××××××××××××××××××××××××××××××××××××××××××××××××××××
正文
一背境
学习日记( )网站是采用Struts框架的开源项目learndiary( )的实际运行示范站点目前由学习日记开发小组进行开发致力于以JAVA技术构建一个普遍适用的开源网络学习交流平台为了改善用户的浏览体验故提出导航系统的改善计划已经包含了这个导航系统的的最新版本为learndiaryV本文所提到的程序均可以在本站的下载菜单中获取: 本文希望起到一个抛砖引玉的作用引发一场关于java技术构建的web系统的导航系统设计的讨论
二基本思路
1学习日记网站的主要功能页面结构
核心为目标列表-》目标内容及评论-》目标下的日记列表-》日记内容及评论
(示意图_//gif )
2需要进行动态导航的主要模块
需要进行动态导航的主要模块有3个见学习日记网站的菜单栏
1)所有目标;naviStr=a (显示学友提出的所有学习目标列表 )
2)检索 (检索本站的所有学习目标和学习日记 )
3)您的目标;pageNum=&naviStr=aa (学友自己的学习目标归类包括进行中的目标退出的目标已完成的目标 )
3需求的提出
1)层次导航的需求
我们想建成一个导航系统它可以跟蹤用户在系统中浏览页面的过程每浏览到一个新的页面就在导航条中加入这个页面的URL于是当用户浏览到别的页面可以点击返回系统导航条中的前面浏览过的页面当浏览一个页面是与导航条中已经存在的页面类型一致时就截去导航条中此类型页面后面的导航URL
征对这个需求我大概看了一下动网的实现方式和phpWind论坛的实现方式他们用的导航基本上是静态的也就是说一种页面上的导航条是固定的例如显示一条帖子的内容他们的方式是论坛首页-》论坛版块-》主题内容
而我们的需求是例如显示一篇日记的内容
在所有目标模块中导航条为所有目标>>日记列表>>日记内容(图例_//gif )
在检索模块中导航条为所有目标>>检索>>检索日记列表>>日记内容(图例_//gif )
在您的目标中导航条为所有目标>>我进行中的目标>>本目标我的日记列表>>日记内容(图例_//gif )
2)同一列表中帖子间的导航(即上一条下一条类似的导航 )
另外除了上面层次导航的需求同一列表中帖子间的导航(即上一条下一条类似的导航 )我们也想在网上一般的导航系统的基础有所改良例如在一般的导航系统中如果你在检索结果的列表中点击查看一篇帖子然后你再点击这篇帖子的上一条和下一条链接新点开的页面不是检索结果列表中的上一篇或下一篇帖子而是这条帖子所有的版块的上一条和下一条帖子
我们需要在检索结果列表中点击检索到的一篇日记中的上一条和下一条链接时显示的是检索结果列表中本日记的上一条和下一条日记
例如我们以中文显示为关键字检索日记得到一个检索结果列表(图例_//gif )
当我点击查看解决:jsp页面中文显示问题这篇日记中的下一条链接时我们需要打开检索结果列表的下一篇日记问题jsp中文显示<c:set>的值可否是对象?me (图例_//gif )而不是解决:jsp页面中文显示问题这篇日记所在的目标中的下一篇日记
在您的目标这个模块中的上一条和下一条的导航同样存在在检索页面中点击上一条和下一条链接的问题
3)父帖与子帖列表的双向导航
学习日记征对自身的功能结构特点还需要实现目标<──>目标下的所有日记列表的双向导航和目标<──>目标下我的日记列表的双向导航显示目标内容(图例_//gif )您可以看到页面右边中上有两个链接分别是查看所有日记和查看我的日记点击查看所有日记链接出现的是页面(图例_//gif )点击查看我的日记链接出现的是页面(图例_//gif )后面这两个页面的右中上部又都有到日记所在的目标中的链接查看目标内容
以上总结了学习日记需要实现的导航系统的三个方面的需求征对这个需求我看了一些论坛均没有现存的东西可以参考于是我们决定探索一种能够实现上面动态导航需求的方法因为我们实现的导航系统是动态变化的故把这种实现的方法称之为学习日记动态导航技术(简称LDDN技术 )下面把我们的具体设计过程总结一下
三具体实现过程
具体的设计分析过程我已经在进行的过程中记录在日记中了所以如果你要详细的了解我的分析设计过程的话可以去看这两篇日记
第一篇日记提高学习日记导航能力的思路(;goalID=&naviStr=aaah )主要记录的是我在对层次导航部分的分析设计过程
第二篇日记分析学习日记横向导航及开几个窗口的思路(;goalID=&naviStr=aaah )主要记录的是我在对上一条和下一条导航部分的分析与设计过程
当然细节总是太烦琐和令人不愉快的我把大概的设计过程总结如下
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英文字符bz和AZ表示 )+页面参数ID(如显示目标的日记列表所需的目标ID显示日记内容所需的日记ID )如显示一篇日记的页面类型常量为字符h那么显示解决:jsp页面中文显示问题这篇日记内容(ID为292 )这一页面的封装字符串为ah
(2)页面完整导航字符串的设计
在下面的讨论中会用到的相关源文件
负责封装学习日记所需导航的页面的类型常量(/WEBINF/src/com/learndiary/website/PageTypeConstsjava )
负责导航字符串封装的方法(/WEBINF/src/com/learndiary/website/util/Pagerjava中的public static String encodeNaviStr(String naviStr char toPageType String parameter) )
当用户提出一个新的页面请求把当前页面中的完整的导航封装字符串和新的页面的类型和新请求页面的参数ID传给处理新页面显示前的Struts中的Action中由Action调用一个方法负责把这些参数组装成下一新页面所需的导航字符串如当前正显示的页面是系统导航所有目标>>我进行中的目标>>目标一起学习Struts(MVC)我的日记列表中的目标一起学习Struts(MVC)我的日记列表(图例_//gif )你可以看到当点击显示日记解决:jsp页面中文显示问题 (篇) 的URL中的导航字符串为naviStr=aaa(图例中左下方红圈中 )把这个字符串和请求的显示日记的页面类型(日记为h )和显示这篇日记所需的ID( )传给显示日记内容前的Action中(disDiaryContentActiondo源文件为/WEBINF/src/com/learndiary/website/action/disgoal/DisGoalContentActionjava )由负责导航字符串封装的方法(源文件为/WEBINF/src/com/learndiary/website/util/Pagerjava中的public static String encodeNaviStr(String naviStr char toPageType String parameter) )进行处理处理流程为以显示日记这个页面的类型ID为h搜索已有导航字符串没有相同的页面于是就把这个页面导航字符串节点(ah )加到完整的导航字符串(aaa)后面得到新的导航字符串(aaaah )见例图(_//gif )你可以看到当点击日记解决:jsp页面中文显示问题这篇日记中的我要评论的URL中的导航字符串为naviStr=aaaah(图例中左下方红圈中 )
当点击当前页面中的回到上级导航节点的链接时如系统导航所有目标>>我进行中的目标>>目标一起学习Struts(MVC)我的日记列表中的我进行中的目标这时显示我进行中的目标页面的类型代码是参数ID是那么以新页面的节点字符串(a )的类型代码搜索已有导航字符串已经存在相同的类型代码于是就把导航字符串中这个节点和后面的所有节点删除得到字符串a再加上新页面的节点字符串为a得到显示我进行中的目标页面的导航字符串为aa见例图(_//gif )你可以看到当点击一起学习Struts(MVC) (篇)这篇目标的URL中的导航字符串为naviStr=aa(图例中左下方红圈中 )
详细分析设计过程我上面提到的第一篇日记中的2设计中用到的方法
(3)将封装的导航字符串还原为显示在页面上的URL地址导航条
同样用上面的显示在我进行中的目标中目标一起学习Struts(MVC)我的日记列表中的日记解决:jsp页面中文显示问题来进行说明也就是说如何把导航封装字符串aaaah转换成字符串系统导航<a /learndiary/indexActiondo?searchDiaryID=&pageNum=&naviStr=aaaah>所有目标</a>>><a /learndiary/processGoalActiondo?searchDiaryID=¤tGoalState=&pageNum=&naviStr=aa>我进行中的目标</a>>><a /learndiary/myDiaryActiondo?searchDiaryID=&pageNum=&goalID=&naviStr=aaaah>目标一起学习Struts(MVC)我的日记列表</a>>>日记解决:jsp页面中文显示问题<p>
使它在页面上显示导航条为系统导航所有目标>>我进行中的目标>>目标一起学习Struts(MVC)我的日记列表>>日记解决:jsp页面中文显示问题(见图例_//gif )?
解决这个问题我用两步走的方法
<1>用一个方法可以把每一个导航节点的封装字符串转换为导航URL字符串主要是根据需要显示的页面类型和参数ID来进行转换实现过程比较简单请查看源文件中负责解封导航节点封装字符串的方法(/WEBINF/src/com/learndiary/website/util/Pagerjava中的public static String decodeNodeStr(String naviStr String nodeNaviStr HttpServletRequest request boolean ifLast) throws Exception )
<2>把每个节点封装的字符串联接在一起形成完成的页面层次导航所需的URL字符串根据惯例当前(也就是最后一个节点的导航URL灰化无链接 )具体实现请查看源文件中负责解封整个导航封装字符串的方法(/WEBINF/src/com/learndiary/website/util/Pagerjava中的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)根据层次导航的导航字符串确定上一条下一条导航所在的层次导航位置
进行上一条下一条的导航需要知道被导航的帖子所在的层次导航的位置例如显示一篇日记的内容
在所有目标模块中导航条为所有目标-》日记列表-》日记内容(图例_//gif )(路径1)
在检索模块中导航条为所有目标>>检索>>检索日记列表>>日记内容(图例_//gif ) (路径2)
在您的目标中导航条为所有目标>>我进行中的目标>>本目标我的日记列表>>日记内容(图例_//gif ) (路径3)
在上一条下一条的导航中层次导航条除了上一条目和下一条目内容的改变其余是不会变的而且要得到当前条目的前后条目的ID在不同的层次导航中是不同的例如在上面的路径1中得到日记列表的查询条件是本目标下的所有日记在路径2中得到日记列表的查询条件是检索页面的条件组合在路径3中得到日记列表的查询条件是本目标下的用户的所有日记而且在学习日记的设计中这三种情况的排序方式是分开的可以由用户在浏览时自选的
为了得到不同层次导航下的上一条下一条URL导航字符串我在页面导航URL字符串产生工具类Pager((/WEBINF/src/com/learndiary/website/util/Pagerjava)中用了3个重载的用于产生上一条下一条导航URL字符串的方法来征对不同的三种情况(与前面说的3种路径不是一一对应的)分别是
1>(包括对这一路径下的目标列表中目标的浏览和目标的日记列表的浏览和检索目标列表中目标的浏览和检索日记列表中日记的浏览)public static String getPreNextNaviStr( char toPageType String url HttpServletRequest request String naviStr int currentID String conditionint orderType int direction) throws Exception输入参数是toPageType请求的页面类型url请求的页面的/***Actiondo路径request请求对象naviStr当前页面的导航封装字符串currentID当前条目的IDcondition查询的where子句orderType排序类型direction排序方向为了分离数据库访问的代码在这个方法中调用了一个直接访问数据库的类(/WEBINF/src/com/learndiary/website/db/PageDBjava)中的方法(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 conditionint orderType int direction) throws Exception输入参数是url请求的页面的/***Actiondo路径request请求对象naviStr当前页面的导航封装字符串searchDiaryID搜索日记列表中当前日记的IDcondition查询的where子句orderType排序类型direction排序方向为了分离数据库访问的代码在这个方法中调用了一个直接访问数据库的类(/WEBINF/src/com/learndiary/website/db/PageDBjava)中的方法(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请求的页面的/***Actiondo路径request请求对象naviStr当前页面的导航封装字符串userID当前用户IDcurrentID当前条目的IDmyGoalTypeFlag用户目标的类型(进行退出或者完成)orderType排序类型direction排序方向为了分离数据库访问的代码在这个方法中调用了一个直接访问数据库的类(/WEBINF/src/com/learndiary/website/db/PageDBjava)中的方法(public int[] getAdjacentIDs(int userIDint 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>需要放在DisGoalContentActionjava(/WEBINF/src/com/learndiary/website/action/disgoal/DisGoalContentActionjava)中处理
2>1)的3>需要分在几个地方处理分别是
1>检索目标的日记列表所有目标的日记列表进行完成退出的日记列表在DiaryActionjava(/WEBINF/src/com/learndiary/website/action/disdiary/DiaryActionjava)中处理
2>进行完成退出的我的日记列表在MyDiaryActionjava(/WEBINF/src/com/learndiary/website/action/mydiaries/MyDiaryActionjava)中处理
(3)在页面上显示上一条下一条导航的URL字符串
在需要上一条下一条导航的页面上把从request中获得的属性preNextNavigation显示在页面的右上部和右下部就行了
上一条下一条导航完整的设计分析过程见我上面提到的第二篇日记分析学习日记横向导航及开几个窗口的思路(;goalID=&naviStr=aaah )
3父帖与子帖列表的双向导航的实现过程
1)列出导航需求列表
(1)目标<──>目标下的所有日记列表的双向导航
(2)目标<──>目标下我的日记列表的双向导航
2)分析实现双向导航所需的参数
只需要目标的ID或日记列表的目标ID和导航封装字符串
3)确定实现方法
直接在Action中得到当前目标的ID或日记列表的目标ID和导航封装字符串附在***Actiondo?的后面就行了
4)进行双向导航的设计
(1)确定在哪几个Struts的Action中需要处理双向导航
1>在目标──>目标下的所有日记列表和目标──>目标下我的日记列表的导航需要放在DisGoalContentActionjava(/WEBINF/src/com/learndiary/website/action/disgoal/DisGoalContentActionjava)中处理
2>在目标下的所有日记列表──>目标的导航需要放在DiaryActionjava(/WEBINF/src/com/learndiary/website/action/disdiary/DiaryActionjava)中处理
3>在目标下的我的日记列表──>目标的导航需要放在在MyDiaryActionjava(/WEBINF/src/com/learndiary/website/action/mydiaries/MyDiaryActionjava)中处理
(2)在页面上显示双向导航的URL字符串
在需要目标──>目标下的所有日记列表和目标──>目标下我的日记列表的导航的页面上把从request中获得的属性jumpToViewDiaries显示在页面的右上部和右下部在目标下的所有日记列表──>目标和目标下的我的日记列表──>目标的导航中把从request中获得的属性jumpToViewGoal显示在页面的右上部和右下部就行了
四总结
1心得
1)编码前的分析设计是非常重要的这一步工作做好了编码就很容易了(但我还做得不够如下面的第3)条心得可是也许是我的水平有限有些应该放在设计时的工作不到编码的时候就是想不到还望大家给予指点)
2)Struts框架把程序的逻辑实现代码和页面显示部分能比较好的分离有利于功能模块的新增和程序的后期维护
3)在进行类和类的方法的设计时(如前面提到的Pager类和PageDB类)没有先进行完整的高层设计是采用边编码边设计的方式致使类的设计不够面向对象给后期的理解和维护会造成困难
4)如果这种导航设计思路真的有用有必要把它进行精心的设计做成插件的形式这样可以把它方便的应用于需要这种动态导航的各种java的web程序中
2这种动态导航的优点
1)能够极大的提高用户的浏览体验使网站的导航更符合逻辑和人们的思维习惯
2)能够无限的进行需要导航的页面的增加和减少后期的维护代码少量增加就行了
3这种动态导航的缺点
1)实现过程较为复杂牵涉的页面和逻辑代码较多权衡实现的代价和收到的效果真的值得吗?
2)动态导航能被用户的使用习惯接受吗?这是一个未知数
3)还有什么缺点呢?暂时还没有想出来大家帮我们想一想吧