返回目录列表
表现层详细设计与实现
以下首先实现本系统的表现层代码和配置;这部分工作比较机械和模板化;业务组件实现和持久层实现再逐个模块完成。
注意,从表现层入手只不过帮助深入掌握业务的一个步骤,表现层的一些设计是服从与业务建模的,当我们对业务深入掌握后,有可能更改Domain Model,从而更改已经设计好的表现层。
论坛Forum
本模块中完成的功能有:
首页显示所有论坛;角色是所有角色,。
论坛名称和描述的增删改查功能,角色是针对管理者。
论坛批量查询
URL: /forum/forumList.shtml
使用Jdon框架的批量查询完成上述第一个功能,首先,建立ForumListAction,然后,在struts-config-forum.xml中配置forumListForm:
<form-bean
name="forumListForm"
type="com.jdon.strutsutil.ModelListForm"/>
以及action如下:
<action
path="/forum/forumList"
type="com.jdon.jivejdon.presentation.action.ForumListAction"
name="forumListForm" scope="request"
validate="false">
<forward
name="success" path="/forum/forumList.jsp"/>
</action>
最后,建立forumList.jsp页面,显示所有论坛。
注意点:
1. 本系统页面没有采取Tiles,而是使用简单的include,处理注意时如何将动态标题title值传入../common/IncludeTop.jsp,使用下面语句:
<jsp:include
page="../common/IncludeTop.jsp" flush="true">
<jsp:param name="title" value="Jdon Forum" />
</jsp:include>
论坛显示中每个论坛的主题个数和帖子个数、以及最后更新时间等功能,为Forum增加三个字段:threadCount、messageCount、modifiedDate,这里涉及到时间格式的显示。参考前面“设计习惯”
论坛CRUD
URL: /forum/admin/forumAction.shtml
配置jdonframework.xml和struts-config.xml,实现Forum的CRUD功能。
注意struts-config.xml中path配置:path="/forum/admin/forumAction”,也就是在forum目录下的admin目录,特别设定一个admin目录是用于后面使用基于Web容器安全验证,将admin目录的权限赋予给Admin角色。
主题ForumThread
本模块完成的功能有:
显示一个论坛,也就是一个论坛中所有帖子的批量分页查询,角色是所有角色
由于ForumThread实则是一个rott ForumMessage,也就是根ForumMessage,所以ForumThread的CRUD由ForumMessage CRUD来实现。
主题批量查询
URL: /message/threadList.shtml? forumId
使用Jdon框架的批量查询实现,建立一个ModelListAction的子类ForumThreadListAction,在struts-config-forum.xml配置如下:
<action
path="/message/threadList"
type="com.jdon.jivejdon.presentation.action.ThreadListAction"
name="threadListForm" scope="request"
validate="false">
<forward
name="success" path="/message/threadList.jsp"/>
</action>
第二步是threadList.jsp的编制,这里面涉及几个细节设计:
论坛名称显示,Forum和Thread是1:N关系,需要在Thread集合中涉及关联者,我们使用ModelListAction中的customizeListForm方法或setupOneModel,这样可以使得threadList.jsp页面存在Forum对象。
路径显示,在当前页面的threadListForm中存在Forum之后,路径显示也比较好处理。
显示列名有:主题名/回复/作者/最后回复;回复/作者显示可由ForumThread相应的字段完成,最后回复则由ForumThread的lastPost字段实现,如果原来ForumThread没有这些字段,我们就加上这些字段,这属于Model的详细设计。
帖子ForumMessage
帖子的CRUD功能实现
创建主题贴URL:
/message/messageAction.shtml
创建回帖URL: /message/messageReplyAction.shtml
编辑主题贴或回帖: /message/messageAction.shtml?action=edit&messageId=XXX
主要是两部分工作:配置struts-config.xml 和jdonframework.xml
这里jsp分两个页面: /message/message.jsp或/message/messageReply.jsp,原来是设计一个message.jsp,后来ForumMessage模型更改,由此增加两个,见这章节。这两个Jsp页面主要区别是是否有parentMessage.messageId字段,它们共同调用messageFormBody.jsp。
在后面章节业务设计的相关章节有进一步述说。
帖子批量查询
URL: /message/messageList.shtml?forumId=xxx&threadId=xxx
使用jdon框架的批量查询完成,与以往区别是,messageListForm中集合是按照顺序排列的,不过这个功能由后台持久层完成,在当前表现层设计实现中无需考虑,只要拿出来显示就可以。
编制MessageListAction代码,MessageListAction是ModelListAction的子类。在MessageListAction中有一个findModelByKey方法,是根据主键查询整个Model,注意,这里调用的forumMessageService的getMessage方法, forumMessageService的getMessage方法是为CRUD使用,以往Jdon框架应用案例中,这两个获得Message方法总是合为一体的,这里分开了。
public Model
findModelByKey(HttpServletRequest request, Object key) {
ForumMessageService forumMessageService = (ForumMessageService)
WebAppUtil.getService("forumMessageService", request);
return forumMessageService. getMessage ((Long)key);
}
另外一个关键点是编制/message/messageList.jsp中:
1. 在当前页面,除了需要主要messageListForm外,还需要Forum,这个Forum我们可以在MessageListAction的customizeListForm设置,不过在messageList还可能使用到ForumThread,所以在customizeListForm设置Forum还是ForumThread,因为ForumThread中也包含了Forum,所以我们当然设置ForumThread,而且ForumThread比较直接和ForumMessage有关系,引入ForumThread比较好。
2. “回复本主题”的连接是指向/message/messageAction.shtml,到底应该给messageAction.shtml什么参数?是forumId/ThreadId都给予,还是只给ThreadId呢?
messageAction.shtml推出的是/message/message.jsp,我们看看message.jsp中Form表单需要哪些参数?
<html:hidden
property="forum.forumId" />
<html:hidden
property="parentMessage.messageId" />
<html:hidden
property="forumThread.threadId" />
看来需要forumId/messageId/threadId三个参数。
这里碰到Struts的一个弱点,html:link一般直接只能赋予一个参数,多个参数就比较麻烦,有两个解决方案:1. 使用<a href加上bean:write拼凑组合方式:
<a
href="<html:rewrite page="/thread.shtml"
paramId="forumId" paramName="forumThread"
paramProperty="forum.forumId" />&messageId=<bean:write
name="forumThread" property="rootMessage.messageId"
/>" class="forum">
2.将多个参数装入Map,然后将Map实例装入request.setArribute中。
这里采取第一种方式,第二种方式需要写另外java代码。
上下主题批量查询
在当前主题下所有帖子时,需要一个在主题之间的导航,也就是上一个主题和下一个主题。
首先需要一个threadNextPrevList对象,其中放置的是三个Thread对象,一个是上一个主题;一个是当前主题;一个是下一个主题。
这里也涉及到struts的一个弱点:在struts-config.xml配置中,一般只能一个Action对应一个ActionForm一个页面,在当前页面,ActionForm已经是messageListForm了。
Struts组件编程四种模型
一个Action对应多个ActionForm有两个解决方案;一个Action一个ActionForm形成一个流程,多个流程以串联或并行方式实现,这又是两个解决方案。
一个Action对应多个ActionForm有两个解决方案:
1.在messageListForm中再塞入一个threadNextPrevList。这种办法可以使用Jdon框架的com.jdon.controller.model.DynamicModel装载多个Model,然后将DynamicModel装入messageListForm的oneModel中。
Struts对于Map可以直接访问,以DynamicModel为例子:
Action中代码:
DynamicModel dynamicModel = new DynamicModel();
Test test = new Test();
test.setName("ooooooooooooooooooooo");
dynamicModel.put("test", test);
messageListForm.setOneModel(dynamicModel);
Jsp中代码:
<bean:define id="dynamicModel"
name="messageListForm" property="oneModel"/>
<bean:define id="test"
name="dynamicModel" property="value(test)"/>
<bean:write name="test"
property="name"/>
2.手工在Action中将threadNextPrevList放入request中。
这两种方式属于同一个Action不同ActionForm,但是本案例情况属于另外一种情形,必须一个Action对应一个ActionForm。
threadNextPrevList类似是一个ModelListForm,其中只有三个元素,按照这个过程,必须使用jdon框架的ModelListAction,这样又需要一个Action--->Jsp流程,如何将两个流程捆绑在一起?这里面又有两种解决方式:
1. 使用Tiles,不同流程输出到同一个页面的不同区域。并行处理方式。
2. 两个流程首尾相连,第一Action forward结果是第二个Action,最后输出一个Jsp中,在这个jsp中,可以使用前面两个ActionForm了,这属于串行方式。
很显然,因为本项目从一开始没有引入Tiles,所以尽量不能因为这个功能而更改大的结构,我们就采取第二种解决方案,串行方案。
在struts-config.xml中将“上下主题”和“当前主题所有帖子”两个ModelListAction串联起来:
<action
path="/message/messageList"
type="com.jdon.jivejdon.presentation.action.ThreadPrevNexListAction"
name="threadPrevNexListForm" scope="request"
validate="false">
<forward
name="success" path="/message/messageListBody.shtml"/>
</action>
<action path="/message/messageListBody"
type="com.jdon.jivejdon.presentation.action.MessageListAction"
name="messageListForm" scope="request"
validate="false">
<forward
name="success" path="/message/messageList.jsp"/>
</action>
这里要开发一个ThreadPrevNexListAction,这次查询虽然结果也是一个集合,但是查询方式不太一样,根据当前threadId,获得3个结果,一个是前一个Thread;一个是后一个Thread。这是由ForumMessageService的getThreadsPrevNext方法在业务持久层完成。
messageList.jsp最后工作是完成messageListForm的遍历,罗列出当前主题所有的帖子,messageListForm中message顺序是由回帖顺序组成,这点也是由Service层ForumMessageService的getMessages方法在业务持久层编码完成。
管理界面
管理界面是后台对全部功能进行管理设定的界面,该步骤应该在基本架构确定后,进入启动阶段,本项目中,是在业务逻辑层的几个重要功能(缓存设计、用户ACL、论坛和帖子的CRUD)实现后开始的。通过管理界面的建立,可以初步跑通管理设定整个系统的功能,对前一阶段各个模块实现功能进行一次检验。
管理界面位于Web目录的admin目录下,通过index.jsp启动,管理界面主要通过Tab标签来区分大模块功能,如论坛管理;用户管理;系统设置三个模块,在左边菜单是显示每个模块下的子菜单。因为是后台管理,对界面灵活性要求不是很高,我们可采取Html的Frame进行页面区分。
/admin/sidebar.shtml是子菜单的控制显示Action,是DispatchAction的子类,通过参数method的值直接映射到方法名。
论坛管理
论坛管理主要是实现论坛的CRUD,这些功能可参考前面章节;还有论坛中的帖子的编辑和删除功能,这两个功能可无需专设管理界面,直接在浏览帖子点按“编辑”按钮,由系统判断身份能否进入和修改。
对于帖子发布者:帖子一旦有子贴就不能修改和删除。只有在无跟帖情况下才能修改或删除。
上下主题显示
ForumMessageService中有一个特殊方法实现:
PageIterator getThreadsPrevNext(Long
forumId, Long currentThreadId)
当显示某个ForumThread下的所有帖子时,在上方有一个ForumThread本身前面或后面主题的遍历功能,如图:
这里上下主题顺序是根据前面一个操作ForumThread遍历时集合得到的,但是当前我们无法得到前面操作的ForumThread集合。
如果我们每次都直接采取基于数据库查询的策略,那么无疑增加数据库负载,使用缓存来减轻数据库的反复查询。
缓存的主键设计:一般地,我们容易是以ForumId为缓存主键,这样根据当前forumId值获得一个threadId集合,然后再在这个集合中查找符合当前的threadId,再将其前后threadId获取即可,但是,该方案缺点是:一个forumId可能有几万上十万更多threadId集合,万一有人浏览所有主题贴,这个以forumId为主键的对象集合就很大,而且无法依赖Cache的管理机制实现缓存大小控制。
我们就以当前threadId为主键,包含前后两个threadId为集合的缓存对象,这样打碎了之后,缓存机制可以在缓存即将溢满时,去除那些不经常使用的threadId集合。
在查询数据库时,我们模拟分块分页查询,这样,运气好,可以使用之前getThreads方法的结果(如果有调用的话)。
最后,我们需要实现表现层Jsp页面上下主题显示:
在后台获得的上下主题集合有多种情况:可能上主题没有;可能下主题没有;也可能上下主题都有等,在这种情况下,需要解决Jsp页面判断问题。
由于Struts的标签logic:iterator只是向一个方向遍历,因此试图通过logic:iterator实现上述各种可能性判断则就比较麻烦,我们需要的一个类似ListIterator的遍历器,能够向前或向后翻滚。
因此前期设计思路有些改变,原来的设计思路是:后台产生一个PageIterator,通过Jdon框架批量查询框架的ModelListAction输出ModelListForm视图到Jsp页面中去,然后在Jsp页面使用logic:iterator遍历结果。
现在Jsp页面需要的是ListIterator,ListIterator其实和PageIterator类似,只是PageIterator中装载的是记录ID集合,而不是完整的记录集合,需要在Service中转换以下。
首先,在持久层返回一个符合条件的主键集合,代码如下:
public List getThreadsPrevNext(Long
forumId, Long currentThreadId) {
String GET_ALL_ITEMS =
"select threadID from jiveThread WHERE forumId=? ORDER BY
creationDate ASC";
Collection params = new ArrayList(1);
params.add(forumId);
Block block = pageIteratorSolver.locate(GET_ALL_ITEMS, params,
currentThreadId);
if
(block == null){
return null;
}
return block.getList();
}
这样,我们做一个Struts的原始Action:ThreadPrevNexListAction代码如下:
public ActionForward
execute(ActionMapping actionMapping, ActionForm actionForm,
HttpServletRequest
request, HttpServletResponse response)
throws Exception {
List resultIds = forumMessageService.getThreadsPrevNext(new
Long(forumId), currentThreadIdL);
int
index = resultIds.indexOf(currentThreadIdL);
Debug.logVerbose("
found the block ,size:" + resultIds.size() + " the index=" +
index, module);
//从业务层得到的是一个ForumThread ID集合。现在要编程ForumThread集合。
List threads = new ArrayList();
Iterator iter =
resultIds.iterator();
while (iter.hasNext()) {
Long id = (Long) iter.next();
threads.add(forumMessageService.getThread(id));
}
//在ForumThread集合将指针定位到当前ForumThread
ListIterator li =
threads.listIterator();
Debug.logVerbose("
locate currentThreadId:" + currentThreadId, module);
while (li.nextIndex() !=
index) {
Object o = li.next();
}
request.setAttribute("ThreadsPrevNext",
li); //保存在request中,以便Jsp页面获得
return
actionMapping.findForward(FormBeanUtil.FORWARD_SUCCESS_NAME);
}
最后,在Jsp页面使用稍许Java代码进行前后遍历:
<%
java.util.ListIterator
iter =
(java.util.ListIterator)request.getAttribute("ThreadsPrevNext");
if (iter.hasPrevious()){
com.jdon.jivejdon.model.ForumThread forumThread =
(com.jdon.jivejdon.model.ForumThread)iter.previous();
request.setAttribute("forumThread", forumThread);
// advance the iterator pointer back to
the original index
iter.next();
%>
<a href ……
上一主题
</font>
</a>
<% } else { %>
<% } %>
如果你知道Struts有标签可以作类似hasPrevious的向前遍历,只有替换该处java代码即可,从这里我们也可以看出,如果框架(如Struts和JF)不能满足我们的需求,我们可以基于最底层Jsp+JavaBeans来编程,当然这是特殊情况下不得已采取的办法。
更多Jdon框架专题讨论
JdonFramework作为一个免费开源软件开发平台,可以商用开发大多数数据库应用软件和管理软件: 电子商务软件 在线教育软件 税务软件 Web快速开发软件 财务软件 购物车软件 医院帐务软件 crm software medical software 人事薪资软件payroll software 在线购物软件 销售软件 项目管理软件 房产不动产管理软件 生产软件 PDM软件 制造业软件 仓库软件 采购软件 进销存软件 危险源监控软件 物流软件 超市软件 银行软件 保险软件 汽车软件 医疗软件 电子软件 自动化软件 服装软件 烟草软件 分销管理软件 供应商管理软件
|