JiveJdon业务核心规则之过滤器设计2
设立两种过滤器:In类型和Out类型;如下图:

In类型过滤器:主要是帖子形成或修改时发生作用,主要是对ForumMessage进行一些增添和修改。
Out类型过滤器:主要是帖子输出显示时发生作用,用于在ForumMessage显示输出时对内容进行适当的修饰。
这两种过滤器的特点:就是对ForumMessage一些字段如subject或body 或者Property进行修改,当然可以拓展到其他字段进行修改。
com.jdon.jivejdon.manager.message.InFilterManager属于In类型过滤器,InFilterManager目前和UploadFilterManager有依赖,因为上传文件或图片需要加入img等特殊语法在ForumMessage的body中,以后如果有其他属于In类型过滤器类型的可加入。
com.jdon.jivejdon.manager.message.OutFilterManager属于Out类型过滤器
业务过滤器专门在划分为In和Out主要是因为在考虑ForumMessage的Property设计时促成的。
附加属性Property
业务过滤器专门在划分为In和Out主要是因为在考虑ForumMessage的Property设计时促成的。
任何模型一般都不可能在设计时函括所有字段属性,我们需要增加一个可扩展的字段属性,可以由用户或者管理者在运行过程中动态增加。
第一步:建立Model Property,如下:
public class Property {
private String name;
private String value;
private String type;
……
}
第二步:建立功能列表,围绕Propery有哪些功能需要实现?
- 当新增时,需要加入发帖者的IP地址。
- 当修改时,需要加入修改者名称和修改时间,表明该贴被随修改过。
- 当删除时,需要和主Model ForumMessage联动,当ForumMessage删除时,需要将与其联系的Property删除。
- 当查询时,将全部属性查询,取出需要显示属性,合并到Body中。
这些功能设想在MessagePropsManager中实现,那么MessagePropsManager如何和ForumMessage操作协同激活呢?
使用过滤器模式。
关键是我们这个MessageProps过滤器插入在哪里?目前有两个板块:业务层或持久层。因为MessageProps过滤器中有一些具体和当前业务功能有关,如:加入修改日志并且需要显示等,所以,该过滤器是需要加在业务层。
MessagePropsManager的代码如下,目前有两个方法:发帖者IP和修改者信息。
public class MessagePropsManager {
public final static String IP = "IP";
public final static String MOD = "MOD";
public void addPostIp(Collection propertys, Account poster){
addProperty(propertys, IP, poster.getPostIP());
}
public void addModifier(Collection propertys, Account modifier){
long now = System.currentTimeMillis();
String saveDateTime = ToolsUtil.dateToMillis(now);
String displayDateTime = ToolsUtil.getDateTimeDisp(saveDateTime);
StringBuffer buffer = new StringBuffer();
buffer.append("[该贴被")
buffer.append(modifier.getRoleName()).append(modifier.getUsername());
buffer.append("于").append(displayDateTime).append("修改过]");
addProperty(propertys, MOD, buffer.toString());
}
public void addProperty(Collection propertys, String propName, String propValue){
if (propertys == null){
propertys = new ArrayList();
}
Property property = new Property();
property.setName(propName);
property.setValue(propValue);
propertys.add(property);
}
}
将MessagePropsManager的两个方法addPostIp和addModifier插入在ForumMessageShell的beforeCreate方法和updateMessage方法中。这样可以实现在新增或修改时记录下操作痕迹。
前面我们实现了围绕property的业务逻辑功能,在这些功能中我们并没有混入数据库操作功能,因为在业务层只要围绕Model操作即可,还无需考虑持久化,Property的持久化保存以及和ForumMessage的关联关系持久都在持久层实现。
Propert其实是FourmMessage的关联类,是多对一关联,因此,我们可以专门做一个和ForumMessage有松散关联关系的Dao操作,所谓松散关联就是不象FourmMessage和Forum那样紧密联系(基本关联,必须具备的)。
建立com.jdon.jivejdon.dao.filter.MessageAssociationDao代码如下:
public class MessageAssociationDao extends MessageDaoSql{
public ForumMessage getMessage(Long messageId){
logger.debug("MessageAssociationDao.getMessage");
Collection props = propertyDao.getAllPropertys(Constants.MESSAGE, messageId);
ForumMessage message = super.getMessage(messageId);
message.setPropertys(props);
return message;
}
public void updateMessage(ForumMessage forumMessage){
logger.debug("MessageAssociationDao.updateMessage");
super.updateMessage(forumMessage);
Collection props = forumMessage.getPropertys();
propertyDao.insertProperties(Constants.MESSAGE, forumMessage.getMessageId(), props);
}
}
当修改帖子后保存时,我们将一些新增的property(如记录修改信息)也保存起来,当然,这里并不是Property的修改操作,这也是表示Property和ForumMessage的松散关系所在,两者并不是数据库操作一致性的。
我们注意到在MessageAssociationDao的getMessage方法中,我们将当前ForumMessage的所有Property都从数据库中加载出来了,保存在ForumMessage的Property字段中,那么关于查询功能中:将全部属性查询,取出需要显示属性,合并到Body中,该功能我们并没有实现,是否在MessageAssociationDao的getMessage实现呢?
考虑这个”合并到Body”功能属于业务功能,因为以后,可能专门增加ForumMessage的字段来表示或显示,因此不适合在Dao层来实现这种合并组装功能,应该在业务层实现。
该功能和之前的内容过滤功能非常类似,可以做成一个动态的内容过滤器,动态过滤器可以在系统运行时,由管理员动态决定插入,过滤器本身也可以动态更换,但是,由于“修改日志合并到Body”功能属于内部功能之一,没有必要做成太动态。
前面虽然我们将Property的Dao操作抽象出来,和具体属性业务没有关系,但是MessagePropsManager实际内容只是IP地址操作,IP地址操作只是众多Property中一个属性,如何将Proeperty设计得易于拓展,如果我们增加新属性,如帖子的关键字,如何方便拓展?很显然,一个MessagePropsManager根本不能适应这样的拓展。
我们需要重新树立一个新的设计目标:
- 由于ForumMessage与Property是一对多关系,不希望ForumMessage从数据库恢复后,无需将所有Property从数据库读取恢复,也就是懒加载。
- 在ForumMessage的操作中,插入Property的操作,但是不和具体Property操作发生耦合。
为了实现以上设计目标,我们需要重新考虑Property介入ForumMessage业务处理的整个过程,如果能够对这个过程进行抽象,那么就具备实现目标的基础:
- Property的新增:例如加入发帖者IP地址;也可能是加入帖子的关键字。
- Property的修改:IP地址修改时,还需要记录修改痕迹;帖子关键字修改可直接采取输入项目。
- Property的删除:这和具体Property无关,根据1:N关系进行。
- Property的显示:IP地址显示根据角色权限决定;
实际上,我们需要建立各种的Property对象,例如IP地址Property;关键字Propterty;这样继承实现拓展。将以上总结Property过程行为加入这些Property对象。
最重要的是,我们需要一个broker,将这些各种Property对象整入ForumMessage主流程,这实际应该是MessagePropsManager具备的功能。
考虑这个Broker或者所谓Manager,实际是一个FilterManager,重新反思以前设计,发现在业务服务中,我们需要对MessageRenderingFilter中过滤器进行重新设计。
综合显示过滤器设计,和现在业务层过滤器,由此产生了将过滤专门划分为两种In和Out,如本章开始设计图所示。
帖子屏蔽功能
需求:如果帖子中有不恰当内容不适合公开,管理员可以将其隐藏,这比采取删除的方式要更人性尊重作者。
初步分析:帖子屏蔽实际就是显示帖子时,将全部内容不显示,替代以“该贴已经被管理员屏蔽”字样,这个功能是一种帖子过滤功能,和对帖子中敏感字眼过滤器com.jdon.jivejdon.model.message.output.shield.Profanity类似。
设计思路:Profanity是以敏感词语为核心,而该功能以帖子为核心,管理员通过管理界面将某个帖子设置为隐藏屏蔽或取消屏蔽,该帖子显示时屏蔽过滤器根据管理员设置的开关进行过滤与否判断。
ForumMessage作为帖子的模型,我们需要在其中增加一个boolean字段maked,用来表示屏蔽与否的开关,按正常思维,ForumMessage映射的数据表同样需要一个字段,这种方式没有扩展性,以后再需要新字段,还要更改数据表,一种灵活动态方式,就是我们可以通过Property来实现字段的添加。ForumMessage中增加方法:
public boolean isMasked() {
if (getPropertyValue(PROPERTY_MASKED) != null)
return true;
else
return false;
}
public void setMasked(boolean masked){
if (masked)
addProperty(PROPERTY_MASKED, PROPERTY_MASKED);
else
removeProperty(PROPERTY_MASKED);
}
ForumMessage中已经有了屏蔽与否的开关,下一步实现对屏蔽的操作,以及屏蔽效果的过滤两个功能。
屏蔽设置实现
首先实现管理员对帖子设置屏蔽的功能,该功能链接可以和删除帖子放在一起,很显然,这个业务功能代码应该在ForumMessageService中实现,在该接口增加如下方法:
void maskMessage(EventModel em) throws Exception ;
那么链接和这个ForumMessageService方法之间无疑是通过struts的Action实现,如
<html:link page=”/message/messageMaskAction.shtml”></html:link>
我们需要在struts-config.xml配置这个messageMaskAction,
问题是,对应这个messageMaskAction的具体类应该是什么呢?Jdon框架的ModelViewAction和ModelSaveAction是针对CRUD功能,而屏蔽标志设立好像没有CRUD那么复杂,不需要,我们现在需要的是通过messageMaskActio.shtml直接激活ForumMessageService的maskMessage方法,无疑这里联系需要一个Action。
Jdon框架提供了一种命令服务功能可以帮助我们省却手工编码Action,这个类就是com.jdon.strutsutil.ServiceMethodAction,这样我们只要参考Jdon框架开发指南命令服务的使用,直接在struts-config-message.xml配置如下:
<action name="messageForm" path="/message/messageMaskAction" type="com.jdon.strutsutil.ServiceMethodAction" scope="request"
validate="false">
<forward name="success" path="/message/result.jsp" />
</action>
而jsp中链接调用形式如下:
/message/messageMaskAction.shtml?method=maskMessage
通过参数method方法的值maskMessage,Jdon框架就将这个Action直接去调用ForumMessageService的maskMessage方法。
当然,messageMaskAction.shtm还有其他参数,比如设置屏蔽和取消屏蔽以及对应的ForumMessage的messageId。
设置屏蔽的链接形式:
<html:link page="/message/messageMaskAction.shtml?method=maskMessage&masked=true" paramId="messageId" paramName="forumMessage" paramProperty="messageId" >
取消设置的链接形式:
<html:link page="/message/messageMaskAction.shtml?method=maskMessage&masked=false" paramId="messageId" paramName="forumMessage" paramProperty="messageId" >
这样,以上链接将直接激活ForumMessageService的maskMessage方法,该方法是该功能业务核心,在ForumMessageService实现子类ForumMessageShell内容如下:
public void maskMessage(EventModel em) throws Exception {
logger.debug("enter maskMessage");
ForumMessage forumMessage = (ForumMessage) em.getModelIF();
boolean masked = forumMessage.isMasked();
forumMessage = getMessage(forumMessage.getMessageId());
if (forumMessage == null) {
logger.error("the message don't existed: " + forumMessage.getMessageId());
return;
}
forumMessage.setMasked(masked);//modify full message
em.setModelIF(forumMessage);
this.updateMessage(em);
}
注意:EventModel中获得的forumMessage实际就是前台链接messageMaskAction.shtml提交的参数,但不是一个真正完整的内容的forumMessage,因此我们需要从后台数据库获得一个完整forumMessage,然后修改这个老的forumMessage的屏蔽属性值,再将这个修改后的完整的forumMessage交由后台完成修改。
过滤类实现
以上实现的是帖子屏蔽功能的设置,下面实现屏蔽效果在显示发生作用的功能,可以参考com.jdon.jivejdon.model.message.output.shield. Profanity,建立一个过滤类: com.jdon.jivejdon.model.message.output.shield. Bodymasking
public class Bodymasking extends MessageRendering {
private String maskLocalization = "THIS MESSAGE HAS BEEN MASKED";
public String applyFilteredBody() {
if (message.isMasked()){
return maskLocalization;
}else
return message.getFilteredBody();
}
其中maskLocalization值可以通过论坛管理界面进行定制。
关于Bodymasking过滤类的安装有两种方式:
- 如果当前论坛已经正常运行,各种过滤器已经设置好,那么新的过滤类就需要通过论坛管理的” 添加过虑器”方式进行安装。
- 在manage.xml中配置加入新的过滤类,这样一个新论坛初始化时就会加入这个过滤类,如下:
<component name="renderingAvailableFilters"
class="com.jdon.jivejdon.model.message.output.RenderingAvailableFilters">
<constructor value="com.jdon.jivejdon.model.message.output.html.HTMLFilter"/>
<constructor value="com.jdon.jivejdon.model.message.output.html.Newline"/>
<constructor value="com.jdon.jivejdon.model.message.output.html.TextStyle"/>
<constructor value="com.jdon.jivejdon.model.message.output.html.URLConverter"/>
<constructor value="com.jdon.jivejdon.model.message.output.shield.Profanity"/>
<constructor value="com.jdon.jivejdon.model.message.output.codeviewer.CodeHighlighter"/>
<constructor value="com.jdon.jivejdon.model.message.output.html.WordBreak"/>
<constructor value="com.jdon.jivejdon.model.message.output.html.QuoteFilter"/>
<constructor value="com.jdon.jivejdon.model.message.output.emotion.Emoticon"/>
<constructor value="com.jdon.jivejdon.model.message.output.html.ImageFilter"/>
<constructor value="com.jdon.jivejdon.model.message.output.shield.Bodymasking"/>
</component>