返回目录列表
业务设计
主题贴与回贴
主题贴定义:第一个帖子,没有父贴;一系列帖子的根贴;
回帖:回复帖子,一定有父贴。
一般论坛显示时,从主题贴开始显示一系列的帖子,一系列帖子称为一个Thread;好像这些帖子是穿在一条线上。
模型的区分
前面我们设计了帖子的模型是ForumMessage,现在需要在ForumMessage中进行主题贴或回帖的区分,有多个方法进行区分:
在ForumMessage增加字段isTopic来进行判断,这种设计其实可能为日后增加if else的判断。
我们建立两个模型ForumMessage代表主题贴;而ForumMessage的子类ForumMessageReply代表回帖。这两个模型只有在创建时才严格分开;而在修改删除和查询时,都是以ForumMessage为代表,是否回帖等属性则属于帖子关系范畴,我们后面专门就帖子的树形关系建模(为TreeModel)。
数据库区分
在JiveMessage表中如下结构:
CREATE TABLE jiveMessage (
messageID BIGINT NOT NULL,
parentMessageID BIGINT
NULL, //缺省是NULL
threadID BIGINT NOT NULL,
……
}
其中parentMessageID和messageID记录帖子之间的父子关系。如果该贴是根贴,也就是主题贴,那么parentMessageID就表示为NULL。
当是主题贴时,parentMessageID这个字段我们就不插入任何值,让其为缺省值NULL;而是回帖时,则插入parentMessageID值。
因此,在MessageDao中,有两个方法:
//插入主题贴
void
createMessage(ForumMessage forumMessage);
//插入回帖
void
createMessageReply(ForumMessageReply forumMessageReply);
这两个实现方法唯一区别就是SQL语句中是否有parentMessageID字段。
以后,有必要将表示树形结构的数据库ID和parentID独立出来,专门形成一个数据表,message中已经设计的parentID弃置不用,独立出来从性能上说好处也大,否则查找某个帖子的单独的树形关系,还要在message表中查询,而message表结构是包含body帖子内容大型数据表,应尽量避免。在目前开发工作中进行分离工作会分散重点,只要我们编程时留有可拓展的余地即可。
创建帖子
在进行方法权限访问设计时,尽量将功能需求细分到方法,原来我们设计时,创建新贴都是在ForumMessageService类的createMessage一个方法完成,但是考虑到业务需求,在有些情况下,论坛管理者可能不希望普通注册用户有发言权力,只能有回贴,例如当该系统使用在文章发布系统就存在这样的需求。
因此我们有必要将createMessage划分成两个方法:
//创建一个主题贴
void
createTopicMessage(EventModel em);
//创建回帖
void
createReplyMessage(EventModel em);
将主题贴创建和回贴创建两个权限分开有利扩展性,有利于权限设置,这样只要我们利用上节对不同方法的角色设置就可以实现对这两个不同权限的实现。
主题贴创建和回帖创建这两种方式主要区别还是处理parentID或parentMessage的区别,在处理这个问题上,如果前期对ForumMessage建模时没有看到这种区分,还是将主题和回帖认为用一个ForumMessage代表,那么就会在Service层产生一些噪音,会多不少if else判断,而且这种判断是危险的。我们首先分析这样的思路:
创建主题贴服务设计:
通过ForumMessageService的createTopicMessage方法实现,前台传入的ForumMessage只要有subject和body等具体内容即可,无需传入有关树形结构的参数,在createTopicMessage方法实现中,我们会将该FourmMessage的parentMessageID设为NULL,表示其是一个主题贴。
回帖创建服务设计:
通过ForumMessageService的createReplyMessage方法实现,前台传入的ForumMessage除了有subject和body等具体内容外,必须传入其父贴的ID号,在createReplyMessage方法内部根据父贴的ID号获得该回帖的其他信息,如回帖所在的ForumThread 所在的论坛Forum等,这些关系信息需要持久化保存到数据库中的。
将createMessage分成两个方法,涉及到表现层设计的变动,那么在Jdon增删改查框架下,如何实现一个Model,两个创建方法呢?
这里,我们还可以加一个前提限制:在不手工编制Action类的前提下如何实现,因为通过编码编写一个ModelHandler或Action是比较容易实现的,那有没有更简便的解决方法呢?
只有通过jdonframework.xml配置了,原来的配置如下:
<model
key="messageId"
class="com.jdon.jivejdon.model.ForumMessage">
<actionForm
name="messageForm"/>
<handler>
<service
ref="forumMessageService">
<getMethod
name="findtMessage"/>
<createMethod
name="createMessage"/>
<updateMethod
name="updateMessage"/>
<deleteMethod
name="deleteMessage"/>
</service>
</handler>
</model>
现在我们要将creatMethod
name的值改为createTopicMessage,那么createReplyMessage怎么配置呢?
再增加一行Model配置即可,不过注意,在Jdon框架中,相同Model的配置是否可以重复两行呢?所幸的是,Jdon框架中,只以Model配置中ActionForm为标识符,也就是主键的意思,只要ActionForm不一样就可以多行同一个Model配置。
我们将messageForm划分成两个ActionForm:messageForm和messageReplyForm,两个Form对应不同的Action Path,也就是不同名称的*.do,这样外界可以分别调用这个两种.do,当创建主题贴时,我们调用/message/messageAction.do;当创建跟贴是,我们调用/message/messageReplyAction.do。
jdonframework.xml配置一分为二,变成两种配置如下:
<model
key="messageId"
class="com.jdon.jivejdon.model.ForumMessage">
<actionForm
name="messageForm"/>
<handler>
<service
ref="forumMessageService">
<getMethod
name=" findtMessage "/>
<createMethod
name="createTopicMessage"/>
<updateMethod
name="updateMessage"/>
<deleteMethod
name="deleteMessage"/>
</service>
</handler>
</model>
<model
key="messageId"
class="com.jdon.jivejdon.model.ForumMessage">
<actionForm
name="messageReplyForm"/>
<handler>
<service
ref="forumMessageService">
<getMethod
name=" findtMessage "/>
<createMethod
name="createReplyMessage"/>
<updateMethod
name="updateMessage"/>
<deleteMethod
name="deleteMessage"/>
</service>
</handler>
</model>
当然,这涉及到原来的一个message.jsp需要划分成两个,因为message.jsp的Html:form表单中的Action值需要修改,这里也反应了Strut使用不够灵活。
message.jsp分为message.jsp和messageReply.jsp,将其中相当的表单内容使用include,这样求同存异,Jsp修改维护起来方便。
至此,第一套设计方案描述完毕,我们将其结构意图表现如下:
这个方案是基于一个Model ForumMessage 设计的,在界面段区分两个Action;然后在ForumMessageService中使用两个方法分别处理来自两个Action提交的请求。
这个方案唯一的缺点是:没有进行建立两个业务模型,而是使用一个,这就造成在ForumMessageService的两个方法中,需要对ForumMessage的父节点进行判断,这是危险和杂音,如果我们对主题贴和回帖的区分标志进行更改,将要涉及这段代码的修改,而且不能排除将来在其他地方更多的if else判断,这些其实源自我们的业务建模问题,所以,Service服务的设计其实应该是和Domain Model一起设计,而且如果业务建模正确;会导致Service代码是否简洁清晰。
如图,原来一个Model分为两个,这样改动也非常方便,只要改动一下jdonframework.xml配置就可以。
当然,界面模型ActionForm也可以分离成两个,但是可能比较琐碎,这取决于界面需求,这里ForumMessage的字段是按照ForumMessageReply设计的,因为在界面显示时,可以单独处理对象的每个字段,在这两个Jsp页面我们可以决定是否有ParentMessage这个字段,通过这种选择来决定两种模型的区别。
显示界面这样的字段细化和持久层字段细化也是类似的,一个完整的Domain Model对象在界面显示或数据库持久时,都会细化条目。
帖子初始化
主题贴或回贴的创建其实就是MessageForm的新增,新增功能是通过Jdon框架的CRUD实现的,前面已经描述,通过URL实现:
创建主题贴URL:
/message/messageAction.shtml? forumId=xxxx
创建回帖URL: /message/messageReplyAction.shtml?
forumId=xxxx
相应的struts-config.xml中配置如下:
<action
name="messageForm" path="/message/messageAction"
type="com.jdon.strutsutil.ModelViewAction"
scope="request" validate="false">
<forward
name="create" path="/message/message.jsp" />
<forward
name="edit" path="/message/message.jsp" />
</action>
所以,主题贴或回贴的新增页面/message/message.jsp是由Jdon框架的ModelViewAction推出的。
由于message.jsp中将使用MessageForm的Forum字段,页面显示“在XXX处论坛发言”。因此,必须首先初始化MessageForm中的Forum字段。
Jdon框架提供了三种种初始化ModelForm的方法:
第一个方法是在表现层直接实现,通过继承XmlModelHandler实现initForm方法,初始化MessageForm中的Forum字段。
第二个方法是通过配置initMethod在业务Service层中实现,通过Service接口的initMessage方法,需要在jdonframework.xml中配置<initMethod name="initMessage"/>。
为了降低表现层编码,让业务代码更多集中在业务层,我们采取第2个方案。
在ForumMessageService中增加initMessage(EventModel em)方法
一旦增加一个新方法,我们就要为其配置访问权限,initMessage是为了推出创建新贴页面用,因此只有注册用户才可以访问,配置jivejdon_permission.xml。
这里有一个非常重要的细节需要注意:
因为我们是在业务层的initMessage中页面初始化工作,ForumMessageService的initMessage代码如下:
public ForumMessage
initMessage(EventModel em) {
logger.debug(" enter service: initMessage ");
ForumMessage forumMessage = (ForumMessage) em.getModel();
try
{
if (forumMessage.getForum() == null) {
logger.error(" no Forum in this ForumMessage");
return forumMessage;
}
//获取forumMessage中的Forum中的fourmId
Long forumId = forumMessage.getForum().getForumId();
logger.debug(" paremter forumId =" + forumId);
Forum forum = forumDao.getForum(forumId);
forumMessage.setForum(forum);
}
catch (Exception e) {
logger.error(e);
}
return forumMessage;
}
在initMessage方法中,是从initMessage方法参数中获取一个ForumMessage对象,这个ForumMessage虽然也以Model形式存在,但是它的性质是一种数据参数(DTO),我们要搞清楚它是从哪里来的,它实际是由表现层的界面对象ActionForm(ModelForm)转换过来的,那么ActionForm对象是从哪里来的?
ActionForm对象是从客户端调用URL:/message/messageAction.shtml?
forumId=xxxx,所以,要使得initMessage方法中能够从ForumMessage获取到ForumMessage中的Forum的forumId,我们的调用参数形式就要改变如下:
/message/messageAction.shtml? forum.forumId=xxxx
另外还有一个小的Struts技术使用细节,这时MessagForm这个ActionForm需要对其中Forum这个字段对象进行初始化,可在MessagForm构造方法中写入:
public MessageForm(){
forum = new Forum(); // for parameter forum.forumId=xxx
}
贴子属性
帖子属性Property是一个name-value组合对象,可用于拓展新的字段,例如我们需要知道帖子发布或修改者IP地址,使用帖子属性来实现。
首先当创建帖子时,我们要推出一个创建页面,在
修改贴子
具体内容
当帖子所有者修改了帖子内容后,需要调用updateMessage来保存修改后的内容,一旦帖子被修改,该帖的modifiedDate就要被赋予最新的更新时间,如果该贴是主题贴,那么还要修改ForumThread的modifiedDate。
首先,在MessageDao中提供ForumThread的modifiedDate修改方法为void
updateThreadDate(ForumThread forumThread)
注意,这里方法参数是forumThreadId 还是应该是整个forumThread对象呢?一般倾向与最好是整个对象forumThread。
但是在调用者updateMessage方法中,因为forumMessage对象是从前台传过来,是一个DTO,forumThread是从forumMessage中获得的,但是从前台界面传来的forumMessage能否给出一个完整的forumThread呢?这一切取决于updateMessage方法的传入参数ForumMessage是如何有前台传入参数的,修改贴传入参数是该贴的ID值,以及ForumMessage的实体内容(如新的主题和内容等),其他ForumMessage对象属性就没有了,因此我们必须从数据库获得真正的ForumMessage。
获取ForumMessage
这就需要我们在调用者updateMessage中,将ForumMessage充实完整,再填入新的实体内容,所以必须调用MessageDao的getMessage,如下:
//获得一个完整的forumMessage
ForumMessage
forumMessage = getMessage(modForumMessage.getMessageId());
If (forumMessage ==
null) return; //如果为空,表示客户端传入参数错误
//将前台传入的修改后的内容逐个修改
forumMessage.setBody(modForumMessage.getBody());
forumMessage.setSubject(modForumMessage.getSubject());
messageDao.updateMessage(forumMessage);
如果前台除了修改subject和Body外,还需要修改其他字段,那么必须在这里加入,有过Hibernate或CMP编程经验的人会发现:这种语法操作类似我们操作Hibernate或CMP,实际上我们使用JdbcTemp和缓存组成的Dao层来完成了Hibernate等框架完成的同样的持久化工作,工作原理类似,我们这种手工式特定式完成在效率上肯定比Hiberante等通用框架要高,性能要好得多,节约不少内存。
数据访问权限实现
修改帖子对于主题贴或回帖目前没有区别,但是,修改帖子有一个业务约束前提:帖子如果有回帖,帖子发布者就不能进行修改(管理员除外)。
这个约束需要实现两点:
当不符合编辑条件时,用户调用messageAction.shtml进入编辑页面时,应该推出不符合编辑条件禁止更新的页面。
当用户符合条件进入后,向messageSaveAction.shtml提交修改后的帖子内容时,我们也必须再次检验是否符合条件编辑,这样避免安全漏洞。
先谈第二个约束的实现,这个可以放在ForumMessageShell中实现,和以上业务核心分离,这样便于修改拓展。其实,从前面用户访问权限章节可以得知,在ForumMessageShell已经加入了权限的验证,只有是本贴作者或Admin角色才能修改帖子;这个功能已经实现,还有另外一个权限约束功能:如果该贴已经有回帖,就是创建者本人也无法修改,这个权限判断不但涉及登陆安全系统;又涉及业务数据系统,有一定难度,关键问题是:如何判断一个帖子是否有回帖,也就是是否子贴Childern,这个涉及后面章节将要解决的树形结构问题。
再来谈谈第一个约束实现:
第一个约束实现有一定技巧,因为在Jdon框架册CRUD下设计,与messageAction.shtml对应的ModelViewAction是在Jdon框架中,除非我们继承ModelViewAction自己再做一个子类,然后在这个子类中进行是否符合编辑条件判断,这个方式将权限约束洒在Service层以外,难以维护,我们还是需要在业务Service层实现权限约束。
我们知道Jdon框架ModelViewAction的原理主要是通过jdonframework.xml和服务层的方法发生调用的:
<model
key="messageId"
class="com.jdon.jivejdon.model.ForumMessage">
<actionForm
name="messageForm"/>
<handler>
<service
ref="forumMessageService">
<!—ModelViewAction调用这个方法获取ForumMessage -->
<getMethod
name="getMessageForUpdate"/>
<createMethod
name="createTopicMessage"/>
<updateMethod
name="updateMessage"/>
<deleteMethod
name="deleteMessage"/>
</service>
</handler>
</model>
ModelViewAction通过ForumMessageService的getMessage方法获得一个已经存在的ForumMessage对象,然后推到Jsp页面中,这样,浏览器前的用户可以在原来的内容上进行修改。
如果我们在ForumMessageService的getMessageForUpdate方法中进行权限验证一下,如果当前操作不符合编辑条件时,我们就通过getMessage方法返回一个空值,详细代码如下:
public ForumMessage
getMessageForUpdate(Long messageId) {
ForumMessage forumMessage = super.getMessage(messageId);
if
(forumMessage == null)
return null;
//是否是该贴的创建者
if
(!updateMessageAuthCheck1(forumMessage)) {
return null;
}
//该贴是否有跟帖
if
(!updateMessageAuthCheck2(forumMessage)) {
return null;
}
return forumMessage;
}
这样ModelViewAction得到一个空值,会推出一个错误页面,这会通过” id.notfound”标识将application.properties中信息定义显示出来,我们可以定义这个id.notfound值:
id.notfound = sorry, not
found the data, maybe no permission to operate it
当然,如果你觉得这样做显示信息比较粗糙,可以通过继承ModelViewAction推出更详细的出错信息显示。
删除帖子
帖子删除除了实体内容的删除外,主要是帖子关系的删除,这种关系是和ForumThread和Forum相关,也需要区分是否是主题贴。
如果是主题贴,不但删除该贴实体,还要删除ForumThread,然后再删除该主题贴所有的跟帖。
如果是回帖,则只要删除该贴实体,然后删除该贴的所有回帖。
关于是否删除该贴的所有回帖,需要提供删除者一个可选功能,当然,删除的权限只能由管理员Admin实现。对于管理者,需要提供一个界面功能选择:是否删除其所有子贴?这个我们又要修改ForumMessage模型了,增加includeChildern字段。
这里有两种情况:
只删除一个贴,如果这个贴有回帖,那么就不能删除,建议其选择第2中情况:删除所有根贴。
删除本帖及其所有跟帖。
对于第一种情况,我们在ForumMessageShell的deleteMessage方法中增加如下代码
public void deleteMessage(EventModel
em) {
ForumMessage forumMessage = (ForumMessage)em.getModel();
boolean deleteChildern = forumMessage.isIncludeChildern();
……..
if
((!deleteChildern) && (hasChildern(forumMessage))){
em.setErrors(Constants.NOPERMISSIONS2);
return;
}
super.deleteMessage(em);
}
如果前台选择不删除子贴,但是该贴又有子贴,返回错误,让管理员或发布者必须选择删除子贴。实际上,发布者自己只能删除那些没有回帖的帖子。
对于第二种情况,我们在ForumMessageService再增加一种删除方法:
void deleteRecursiveMessage(EventModel em)
这是将本帖及其子贴全部删除。
该方法的权限只有Admin可以访问,设置方法访问权限,在jivejdon_permission.xml中设置方法配置:
<method
name="deleteRecursiveMessage">
<role>Admin</role>
</method>
删除帖子的界面设计:
在界面设计时,根据选择不同提供不同的删除方法调用,在deleteMessage.jsp中:
<input type="checkbox"
name="deleteRecursiveMessage" value="checkbox">删除子贴
<html:hidden
property="method" value="delete"/>
<script
type="text/JavaScript">
function
delConfirm(theForm){
if (confirm( '删除你自己发表的贴子吗? ! \n\n 你肯定吗? '))
{
if
(theForm.deleteRecursiveMessage.checked){
theForm.method.value="deleteRecursiveMessage";
}
theForm.submit();
return true;
}else{
return false;
}
}
</script>
当用户选择checkbox删除子贴时,那么该表单提交时,method(或者action)的只就被改变,根据Jdon框架的服务调用命令模式,虽然deleteRecursiveMessage不在jdonframework.xml的定义中,但是将直接调用ForumMessageService的deleteRecursiveMessage方法。
业务设计:
删除所有回帖之前必须查询获得该贴的所有跟帖,也就是查询parentMessageID为该贴ID的所有帖子。在解决“删除主题贴将会删除其所有回帖”之前,首先我们需要获得一个主题下的所有回帖,这涉及到树形结构的解决。
帖子的删除其他权限设计等同与帖子修改权限。
更多Jdon框架专题讨论
JdonFramework作为一个免费开源软件开发平台,可以商用开发大多数数据库应用软件和管理软件: 电子商务软件 在线教育软件 税务软件 Web快速开发软件 财务软件 购物车软件 医院帐务软件 crm software medical software 人事薪资软件payroll software 在线购物软件 销售软件 项目管理软件 房产不动产管理软件 生产软件 PDM软件 制造业软件 仓库软件 采购软件 进销存软件 危险源监控软件 物流软件 超市软件 银行软件 保险软件 汽车软件 医疗软件 电子软件 自动化软件 服装软件 烟草软件 分销管理软件 供应商管理软件
|