继续上一篇。
当数据库建立完成后,下载源代码,部署到服务器后,即可以开始体验完整的JBPM+SSH应用了。
项目采用的是ssh+jbpm开发模式,而且,项目功能比较齐全,代码量比较大,所以,本教程就不介绍开发过程了。
如果你对SSH整合开发应用已经够熟悉了的话,相信本项目对你来说将并不会有多大难度 。
下面我们把重点放在JBPM上。当然你得先把项目部署成功,才好边做边理解jbpm是怎么在项目中管理流程的。
ok,假定你已经把项目跑起来了吧。
先以管理员manager登录系统,管理员具有添加新文章类型的权限。当你添加一个文章类型后,需要指定该类型的文章到时候是按哪个流程来进行审批的。你需要上传一个zip格式的流程定义文件文件(其中压缩了gpd.xml,processdifinition.xml,和processimage.jpg)。点击发布,系统转到articletypeaddsub.do,执行ArticleTypeAddSubAction.java
......
import org.jbpm.JbpmConfiguration;
import org.jbpm.JbpmContext;
import org.jbpm.graph.def.ProcessDefinition;
........
/**
*
* 增加文章类型操作Action类
*
* @struts.action path="/articletypeaddsub" scope="request" validate="false"
*/
public class ArticleTypeAddSubAction extends Action {
private static final Log log = LogFactory.getLog(MainAction.class);
/**
* JBPM服务接口实现对象
*/
private JbpmConfiguration jbpmConfiguration; //参见spring的配置文件applicationContext.xml
/**
* 文章类型服务接口实现对象
*/
private ArticleTypeService articleTypeService;
/**
* Method execute
*
* @param mapping
* @param form
* @param request
* @param response
* @return ActionForward
*/
public ActionForward execute(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response) {
log.debug("MainAction.execute()");
UploadDeployForm theForm = (UploadDeployForm) form;
FormFile file = theForm.getFiles();// 取得上传的文件
//得到JBPM环境
JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext();
try {
//解压zip文件流
ZipInputStream zipInputStream = new ZipInputStream(file
.getInputStream());
ProcessDefinition processDefinition = ProcessDefinition
.parseParZipInputStream(zipInputStream);
//发布流程文件
jbpmContext.deployProcessDefinition(processDefinition);
jbpmContext.close();
zipInputStream.close();
//增加文章类型
ArticleType articleType = new ArticleType();
articleType.setPdName(processDefinition.getName());
articleType.setTypeName(theForm.getTypeName());
articleTypeService.addArticleType(articleType);
} catch (IOException e) {
log.error("exception", e);
}
request.setAttribute("success","发布成功");
return mapping.findForward("success");
}
/**
* 得到JBPM服务接口实现对象
* @return jbpmConfiguration
*/
public JbpmConfiguration getJbpmConfiguration() {
return jbpmConfiguration;
}
/**
* 设置JBPM服务接口实现对象
* @param jbpmConfiguration
* 要设置的 jbpmConfiguration
*/
public void setJbpmConfiguration(JbpmConfiguration jbpmConfiguration) {
this.jbpmConfiguration = jbpmConfiguration;
}
/**
* 得到文章类型服务接口实现对象
* @return articleTypeService
*/
public ArticleTypeService getArticleTypeService() {
return articleTypeService;
}
/**
* 设置文章类型服务接口实现对象
* @param articleTypeService 要设置的 articleTypeService
*/
public void setArticleTypeService(ArticleTypeService articleTypeService) {
this.articleTypeService = articleTypeService;
}
}
执行到这步后,你可以去查看下数据库中表jbpm_processdifinition,你会发现表中多出里一条记录,并且名字就是你上传的压缩文件中processdifinition.xml中的name属性的值。其他的表也会有相应的变化,具体看字段的定义就会大概明白了。
好了,流程已经发布到了系统中了。当然你还可以增加其他的文章类型并且指定不同的流程定义。
我们退出系统,以guest用户登录系统,然后编写文章,这里需要说明的是,当你选择不同的文章类型后,该文章的审批过程就会与你刚才定义的执行流程相关了。
点击保存,
系统调用的ACTION为:
MyArticleAddSubAction.java
.........
import org.jbpm.JbpmConfiguration;
import org.jbpm.JbpmContext;
import org.jbpm.graph.def.ProcessDefinition;
import org.jbpm.graph.exe.ProcessInstance;
import org.jbpm.graph.exe.Token;
........
/**
*
* 撰写文章操作Action类
* @struts.action path="/myarticleaddsub" scope="request" validate="false"
*/
public class MyArticleAddSubAction extends Action{
private static final Log log = LogFactory.getLog(MyArticleAddSubAction.class);
/**
* 文章服务接口实现对象
*/
private ArticleService articleService;
/**
* 文章类型服务接口实现对象
*/
private ArticleTypeService articleTypeService;
/**
* JBPM服务接口实现对象
*/
private JbpmConfiguration jbpmConfiguration;
/**
* Method execute
* @param mapping
* @param form
* @param request
* @param response
* @return ActionForward
*/
public ActionForward execute(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response) {
log.debug("MyArticleAction.execute()");
UserSession userSesssion = UserSession.getSession(request, response);
//得到文章信息
String stypeNo = request.getParameter("typeNo");
int typeNo = ConvertUtil.convertInt(stypeNo);
String articleName = request.getParameter("articleName");
String content = request.getParameter("content");
Article article = new Article();
article.setArticleName(articleName);
article.setContent(content);
article.setState(Article.EDITED);
article.setTypeNo(new Integer(typeNo));
article.setUserNo(new Integer(userSesssion.getUserNo()));
//得到相应的文章类型
ArticleType articleType = articleTypeService.getArticleType(article.getTypeNo().intValue());
//得到相应的流程定义,启动流程实例
JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext();
ProcessDefinition processDefinition = jbpmContext.getGraphSession().findLatestProcessDefinition(articleType.getPdName());
ProcessInstance processInstance = new ProcessInstance(processDefinition);
//让流程往下进行一步
Token token = processInstance.getRootToken();
token.signal();
//保存流程实例与状态
jbpmContext.save(processInstance);
jbpmContext.save(token);
jbpmContext.close();
article.setPiId(processInstance.getId());
//增加文章
articleService.addArticle(article);
return mapping.findForward("success");
}
/**
* 得到文章服务接口实现对象
* @return articleService
*/
public ArticleService getArticleService() {
return articleService;
}
/**
* 设置文章服务接口实现对象
* @param articleService 要设置的 articleService
*/
public void setArticleService(ArticleService articleService) {
this.articleService = articleService;
}
/**
* 得到文章类型服务接口实现对象
* @return articleTypeService
*/
public ArticleTypeService getArticleTypeService() {
return articleTypeService;
}
/**
* 设置文章类型服务接口实现对象
* @param articleTypeService 要设置的 articleTypeService
*/
public void setArticleTypeService(ArticleTypeService articleTypeService) {
this.articleTypeService = articleTypeService;
}
/**
* 得到JBPM服务接口实现对象
* @return jbpmConfiguration
*/
public JbpmConfiguration getJbpmConfiguration() {
return jbpmConfiguration;
}
/**
* 设置JBPM服务接口实现对象
* @param jbpmConfiguration 要设置的 jbpmConfiguration
*/
public void setJbpmConfiguration(JbpmConfiguration jbpmConfiguration) {
this.jbpmConfiguration = jbpmConfiguration;
}
}
执行该action后,则就创建了一个与之匹配的流程实例。
查看数据库中article表的变化。可以发现
PiId记录了一个数字编号,同时jbpm_processinstance表中最大的一个id号与之匹配,这说明当保存文章时,系统后台创建了一个流程实例,该流程实例就是记录该文章的审批过程的JBPM实例。(你可以运行一个文章的审批全过程来跟踪与之匹配的流程实例变化情况)。
下一步就是发布该文章了。
到你的文章列表中点击“发布”。
系统调用ACTION:MyArticlePubAction.java继而转到MyArticlePubSubAction.java
.......
import org.jbpm.JbpmConfiguration;
import org.jbpm.JbpmContext;
import org.jbpm.graph.def.Transition;
import org.jbpm.graph.exe.ProcessInstance;
import org.jbpm.graph.exe.Token;
.........
/**
*
* 发布文章操作Action类
* @struts.action path="/myarticle" scope="request" validate="false"
*/
public class MyArticlePubAction extends Action{
private static final Log log = LogFactory.getLog(MyArticlePubAction.class);
private ArticleService articleService;
private ArticleTypeService articleTypeService;
private JbpmConfiguration jbpmConfiguration;
/**
* Method execute
* @param mapping
* @param form
* @param request
* @param response
* @return ActionForward
*/
public ActionForward execute(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response) {
log.debug("MyArticleAction.execute()");
UserSession userSesssion = UserSession.getSession(request, response);
//得到文章信息
//得到文章号
String sarticleNo = request.getParameter("articleNo");
int articleNo = ConvertUtil.convertInt(sarticleNo);
Article article = articleService.getArticle(articleNo);
request.setAttribute("article", article);
//判断是否是此用户文章
if(article.getUserNo() != null && article.getUserNo().intValue() == userSesssion.getUserNo()){
//创建相应的流程实例
//得到相应的文章类型
ArticleType articleType = articleTypeService.getArticleType(article.getTypeNo().intValue());
request.setAttribute("articleType", articleType);
//得到相应的流程
JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext();
ProcessInstance processInstance = jbpmContext.getProcessInstance(article.getPiId());
log.error("instance:" + processInstance.getId());
//得到当前的执行令牌
Token token = processInstance.getRootToken();
//得到当前的可执行转换
//Set transitions = token.getNode().getArrivingTransitions();
List transitions = token.getNode().getLeavingTransitions();
Set transitionnames = new HashSet();
if(transitions != null){
for(int i=0; i<transitions.size(); i++){
Transition transition = (Transition)transitions.get(i);
System.err.println("transition.getName()" + transition.getName());
transitionnames.add(transition.getName());
}
}
request.setAttribute("transitionnames", transitionnames);
jbpmContext.close();
}
return mapping.findForward("success");
}
public ArticleService getArticleService() {
return articleService;
}
public void setArticleService(ArticleService articleService) {
this.articleService = articleService;
}
public JbpmConfiguration getJbpmConfiguration() {
return jbpmConfiguration;
}
public void setJbpmConfiguration(JbpmConfiguration jbpmConfiguration) {
this.jbpmConfiguration = jbpmConfiguration;
}
public ArticleTypeService getArticleTypeService() {
return articleTypeService;
}
public void setArticleTypeService(ArticleTypeService articleTypeService) {
this.articleTypeService = articleTypeService;
}
}
ok,到这里,你仍然可以去查看数据库中article表的变化情况。你会发现表中的Auditstate字段
由null变成了一级审批,这是为什么,因为该文章对应的流程的下一个节点就是一级审批。
然后我们以one(一级审批)用户登录,就可以看到需要一级审批用户审批的文章列表了。
需要说明的是:这些文章是按照登录用户的权限来显示的,只要是该权限级别的用户,就可以看到系统中所有的Auditstate为该状态(权限名和状态名相同,方便查询)的文章article了 。
执行相关的审批操作。继续调用相应的action,然后按照流程定义,一直执行下去,知道该文章的审批流程结束(这里就不再一一说明了)。
OK,流程执行完成。一个完整的JBPM实例执行结束。
思考的问题来了!
我们并不知道articl表中的Auditstate是怎么变化的啊?
在Struts的action中并没有看见显示的代码调用来修改数据库Auditstate字段啊,难道是JBPM自动做的处理?
当然不是!不过我们可以让JBPM帮助我们来完成。
你注意到了processdefinition.xml配置文件吗?
-<state name="编辑完成">
-<transition name="发布" to="一级审批">
<action name="action" class="c20.jbpm.action.PubActionHandler" />
</transition>
</state>
没错,就是<action name="action" class="c20.jbpm.action.PubActionHandler" /> 的功劳。
当一个流程中一个state执行完,需要transition 到下一个State时,JBPM将会自动执行class指定的句柄。
.......
import org.jbpm.graph.def.ActionHandler;
import org.jbpm.graph.def.Node;
import org.jbpm.graph.def.Transition;
import org.jbpm.graph.exe.ExecutionContext;
import org.jbpm.graph.exe.ProcessInstance;
........
/**
* 文章发布处理器
* @author yuxd
*
*/
public class PubActionHandler implements ActionHandler {
private static final long serialVersionUID = 1L;
/**
* A message process variable is assigned the value of the message
* member. The process variable is created if it doesn't exist yet.
*/
public void execute(ExecutionContext context) throws Exception {
//得到对应实例ID
ProcessInstance processInstance = context.getContextInstance().getProcessInstance();
//得到当前执行转换
Transition transition = context.getTransition();
Node node = transition.getTo();
//得到对应的文章
ArticleService articleService = (ArticleService)BeanFactory.getBean("articleService");
List list = articleService.getArticlesByPdInstance(processInstance.getId());
//设置文章状态为发布中
if(list != null){
for(int i=0; i<list.size(); i++){
Article article = (Article)list.get(i);
if(article.getState() != null && article.getState().intValue() == Article.EDITED){
article.setState(new Integer(Article.PUBLISH));
article.setAuditState(node.getName());
articleService.modArticle(article);
}
}
}
}
}
由此,可以得知,JBPM中句柄是怎么在流程运作的过程中对业务数据做出处理的吧!,这也正是JBPM句柄的作用之所在!
到这里,JBPM的具体应用就介绍的已经很详细了。
下面来说说项目中是怎么巧妙的将业务和JBPM流程结合使用的吧。
我们来看看业务表article的结构,
article CREATE TABLE `article` (
`ArticleNo` int(11) NOT NULL auto_increment COMMENT '文章号',
`UserNo` int(11) default NULL COMMENT '用户号',
`TypeNo` int(11) default NULL COMMENT '文章类型号',
`ArticleName` varchar(128) default NULL COMMENT '文章名称',
`Content` text COMMENT '文章内容',
`PiId` bigint(20) default NULL COMMENT '对应流程实例号',
`AuditState` varchar(64) default NULL COMMENT '审批状态',
`AuditComment` varchar(255) default NULL COMMENT '审批说明',
`State` int(11) default NULL COMMENT '文章状态',
PRIMARY KEY (`ArticleNo`),
KEY `FK_Relationship_1` (`TypeNo`),
KEY `FK_Relationship_2` (`UserNo`),
CONSTRAINT `FK_Relationship_1` FOREIGN KEY (`TypeNo`) REFERENCES `articletype` (`TypeNo`),
CONSTRAINT `FK_Relationship_2` FOREIGN KEY (`UserNo`) REFERENCES `user` (`UserNo`)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=gb2312 COMMENT='文章表'
不然看出,恰恰是表中的 `PiId` bigint(20) default NULL COMMENT '对应流程实例号字段,完美的和流程的ID映射起来,使得一篇文章绑定到一个具体的流程实例。并且文章的状态在流程的运作当中利用JBPM句柄ActionHandler动态变化。最终实现业务的运作。
so,JBPM在web项目中的具体应用就介绍完了。希望通过本教程学习的朋友,能够得到确实的提高。并且在教程中的一些个人拙见,望大侠们积极的批正!