[size=small]偶然机会,认识了工作流系统,并且在www.open-open.com(相当不错的开源项目站点,极力推荐!)上了解了些相当出色的工作流系统,不过呼声最高的应该属JBoss 的JBPM工作流组件了。
正好有个项目需要用到工作流机制,遂学习了下JBPM,感受了下JBPM带来的的便捷体验。现在我就把我的学习经历记录下来。和大家共同分享,也希望对那些刚研究JBPM的同仁们有所帮助。
ok,那就开始吧!
一。对于没有接触过JBPM的,可以先了解下JBPM应用场合,和JBPM的相关概念。我想如下这些东西将对你的JBPM学习很有帮助。
1。JBPM开发指南.pdf---------http://dl2.csdn.net/fd.php?i=12492886032537&s=020d83ba442aba86919657b97e51a699----对JBPM中的配置进行了比较详细的阐述。
2。还有就是大师级人物--陈刚 的教程http://www.blogjava.net/chengang/archive/2006/07/13/57986.html
,不过,这个教程只是简单的介绍了下JBPM的使用。对于初次接触JBPM的学习者来说,是个很好的选择。它将让你对JBPM有个较清晰的认识。
3。还有就是传智播客的一套关于JBPM的视频,verycd上就可以search到。这里就不给地址了。自己动手找找吧。
好了,如果你对JBPM有些认识了,那就开始来做个Struts+Hibernate+Spring+JBPM的文章审批系统吧。
首先介绍下该系统具体做些什么吧!
用户在该系统中发表文章,然后请求发布,该文章需要经过层层审批,再确定是否能发布。就像一个出版社中,当要出版一篇文章,需要经过一级审批,二级审批,三级审批.......最终确定发表该文章。需要说明的是,为了体现JBPM对流程的适配的特性,我们做的系统会定义4个不同的流程来展示JBPM的这一特性。对于不同的流程,系统会自动按照不同的流程定义文件来执行。
准备工作:
该系统的数据库环境为MySQL,如果要在其他数据库上完成也很简单,只需要在目标数据库建立起JBPM的表结构和业务表就可以,这里,就可以参考前面陈刚老师的教程了。就不再赘述了(如果需要可到附件中获得)。业务表建表语句如下:
/*==============================================================*/
/* Table: Article */
/*==============================================================*/
create table Article
(
ArticleNo int AUTO_INCREMENT not null comment '文章号',
UserNo int comment '用户号',
TypeNo int comment '文章类型号',
ArticleName varchar(128) comment '文章名称',
Content text comment '文章内容',
PiId bigint comment '对应流程实例号',
AuditState varchar(64) comment '审批状态',
AuditComment varchar(255) comment '审批说明',
State int comment '文章状态',
primary key (ArticleNo)
);
alter table Article comment '文章表';
/*==============================================================*/
/* Table: ArticleType */
/*==============================================================*/
create table ArticleType
(
TypeNo int AUTO_INCREMENT not null comment '文章类型号',
PdName varchar(255) comment '流程名称',
TypeName varchar(40) comment '类型名称',
primary key (TypeNo)
);
alter table ArticleType comment '文章类型表';
/*==============================================================*/
/* Table: User */
/*==============================================================*/
create table User
(
UserNo int AUTO_INCREMENT not null comment '用户号',
DutyNo int comment '职责号',
LoginName varchar(50) not null comment '账号',
Password varchar(32) comment '密码',
UserName varchar(50) comment '姓名',
primary key (UserNo)
);
alter table User comment '用户表';
/*==============================================================*/
/* Table: UserDuty */
/*==============================================================*/
create table UserDuty
(
DutyNo int AUTO_INCREMENT not null comment '职责号',
Name varchar(255) comment '名称',
DutyType varchar(20) comment '职责类型',
DutyValue varchar(64) comment '职责值',
primary key (DutyNo)
);
alter table UserDuty comment '用户职责表';
alter table Article add constraint FK_Relationship_1 foreign key (TypeNo)
references ArticleType (TypeNo) on delete restrict on update restrict;
alter table Article add constraint FK_Relationship_2 foreign key (UserNo)
references User (UserNo) on delete restrict on update restrict;
alter table User add constraint FK_Relationship_3 foreign key (DutyNo)
references UserDuty (DutyNo) on delete restrict on update restrict;
insert into `userduty`(`DutyNo`,`Name`,`DutyType`,`DutyValue`)
values (1,'管理员','管理员','管理员');
insert into `userduty`(`DutyNo`,`Name`,`DutyType`,`DutyValue`)
values (2,'普通用户','普通用户','普通用户');
insert into `userduty`(`DutyNo`,`Name`,`DutyType`,`DutyValue`)
values (3,'一级审批员','审批员','一级审批');
insert into `userduty`(`DutyNo`,`Name`,`DutyType`,`DutyValue`)
values (4,'二级审批员','审批员','二级审批');
insert into `userduty`(`DutyNo`,`Name`,`DutyType`,`DutyValue`)
values (5,'三级审批员','审批员','三级审批');
insert into `userduty`(`DutyNo`,`Name`,`DutyType`,`DutyValue`)
values (6,'四级审批员','审批员','四级审批');
insert into `user`(`UserNo`,`DutyNo`,`LoginName`,`Password`,`UserName`)
values (1,1,'manager','manager','管理员');
insert into `user`(`UserNo`,`DutyNo`,`LoginName`,`Password`,`UserName`)
values (2,2,'guest','guest','普通用户');
insert into `user`(`UserNo`,`DutyNo`,`LoginName`,`Password`,`UserName`)
values (3,3,'one','one','一级审批员');
insert into `user`(`UserNo`,`DutyNo`,`LoginName`,`Password`,`UserName`)
values (4,4,'two','two','二级审批员');
insert into `user`(`UserNo`,`DutyNo`,`LoginName`,`Password`,`UserName`)
values (5,5,'three','three','三级审批员');
insert into `user`(`UserNo`,`DutyNo`,`LoginName`,`Password`,`UserName`)
values (6,6,'four','four','四级审批员');
当数据库建立完成后,下载源代码,部署到服务器后,即可以开始体验完整的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项目中的具体应用就介绍完了。希望通过本教程学习的朋友,能够得到确实的提高。并且在教程中的一些个人拙见,望大侠们积极的批正!
从数据库中表的定义不难看出,每篇文章的审批将新建一个JBPM流程实例来跟踪。
文章的表article中,利用PiId来关联一个流程实例,并且定义State字段来表示文章的当前状态(编辑中,审批中,审批通过,nulll),定义AuditState字段来表示文章当前由何权限的人员来审批(一级审批,二级审批,三级审批,四级审批,当然还可以自定义权限)
下面就来演示如何自定义权限
首先在数据库中插入自己定义的权限
insert into userduty(`Name`,`DutyType`,`DutyValue`)
values ('自定义流程权限1','自定义流程权限1','自定义流程权限1');
insert into userduty(`Name`,`DutyType`,`DutyValue`)
values ('自定义流程权限2','自定义流程权限2','自定义流程权限2');
insert into userduty(`Name`,`DutyType`,`DutyValue`)
values ('自定义流程权限3','自定义流程权限3','自定义流程权限3');
insert into userduty(`Name`,`DutyType`,`DutyValue`)
values ('自定义流程权限4','自定义流程权限4','自定义流程权限4');
insert into userduty(`Name`,`DutyType`,`DutyValue`)
values ('自定义流程权限5','自定义流程权限5','自定义流程权限5');
insert into userduty(`Name`,`DutyType`,`DutyValue`)
values ('自定义流程权限6','自定义流程权限6','自定义流程权限6');
然后就是插入自定义用户(需要注意用户与权限之间的关联关系)
insert into user(DutyNo,`LoginName`,`Password`,`UserName`)
values (7,'zpchen1','zpchen1','zpchen1_自定义流程权限1');
insert into user(DutyNo,`LoginName`,`Password`,`UserName`)
values (8,'zpchen2','zpchen1','zpchen2_自定义流程权限2');
insert into user(DutyNo,`LoginName`,`Password`,`UserName`)
values (9,'zpchen3','zpchen3','zpchen3_自定义流程权限3');
insert into user(DutyNo,`LoginName`,`Password`,`UserName`)
values (10,'zpchen4','zpchen4','zpchen4_自定义流程权限4');
insert into user(DutyNo,`LoginName`,`Password`,`UserName`)
values (11,'zpchen5','zpchen15,'zpchen5_自定义流程权限5');
insert into user(DutyNo,`LoginName`,`Password`,`UserName`)
values (12,'zpchen6','zpchen6','zpchen6_自定义流程权限6');
(注意,如果直接copy上面的mysql语句,需要修改下蓝色部分标记的DutyNo,与你自己机子上的值对应起来,上面为我自己机子上数据的值)
OK,数据库要做的变化就是这些了!
接着就是自定义流程定义文件了.借助JBPM的Eclipse插件轻松实现之。
定义好的流程定义文件如下:
<?xml version="1.0" encoding="UTF-8"?>
<process-definition xmlns="urn:jbpm.org:jpdl-3.1"
name="zpchenProcess">
<start-state name="start">
<transition name="编辑我的任务" to="这个任务完
成"></transition>
</start-state>
<state name="这个任务完成">
<transition name="发布任务" to="自定义流程权限1">
<action name="action"
class="c20.jbpm.action.PubActionHandler">
</action>
</transition>
</state>
<state name="自定义流程权限1">
<transition name="同意" to="自定义流程权限2">
<action name="action"
class="c20.jbpm.action.AuditYesActionHandler">
</action>
</transition>
<transition name="实在糟糕" to="这个任务完成">
<action name="action"
class="c20.jbpm.action.AuditNoActionHandler">
</action>
</transition>
</state>
<state name="自定义流程权限2">
<transition name="同意" to="自定义流程权限3">
<action name="action"
class="c20.jbpm.action.AuditYesActionHandler">
</action>
</transition>
<transition name="不太满意" to="这个任务完成">
<action name="action"
class="c20.jbpm.action.AuditNoActionHandler">
</action>
</transition>
</state>
<state name="自定义流程权限3">
<transition name="同意" to="自定义流程权限4">
<action name="action"
class="c20.jbpm.action.AuditYesActionHandler">
</action>
</transition>
<transition name="重新编辑" to="这个任务完成">
<action name="action"
class="c20.jbpm.action.AuditNoActionHandler">
</action>
</transition>
</state>
<state name="自定义流程权限4">
<transition name="同意" to="自定义流程权限5">
<action name="action"
class="c20.jbpm.action.AuditYesActionHandler">
</action>
</transition>
<transition name="不同意" to="自定义流程权限2">
<action name="action"
class="c20.jbpm.action.AuditNoActionHandler">
</action>
</transition>
</state>
<state name="自定义流程权限5">
<transition name="同意" to="自定义流程权限6">
<action name="action"
class="c20.jbpm.action.AuditYesActionHandler">
</action>
</transition>
<transition name="继续完善" to="自定义流程权限3">
<action name="action"
class="c20.jbpm.action.AuditNoActionHandler">
</action>
</transition>
</state>
<state name="自定义流程权限6">
<transition name="很好,同意发表" to="end1">
<action name="action"
class="c20.jbpm.action.AuditFinishYesActionHandler">
</action>
</transition>
<transition name="继续完善" to="这个任务完成">
<action name="action"
class="c20.jbpm.action.AuditFinishNoActionHandler">
</action>
</transition>
</state>
<end-state name="end1"></end-state>
</process-definition>
从流程定义的文件中可以看出,流程的定义相当的灵活。
那么,系统是怎么将一个文章的审批过程按照这个流程定义来执行了?
本文开头就指出了,article表中定义了AuditState字段来表示文章当前由何权限的人员
来审批,当流程在运作当中,利用JBPM的ActionHandler句柄来动态的改变文章的
AuditState状态。然后当OA系统的用户(这里应该是具有文章审批等操作权限的用户,
而非guest,或manager)登录系统后,判断该用户的权限,如果该用户的权限正好和
article表中AuditState相同时候,就将这些权限级别的文章展现给该登录用户审批。这
样就达到了每篇文章的级级审批的过程了。
不过到这里,你登录管理员后可以发布这个自定义流程,guest用户也可以发布文章了,
但是当你以自己定义的用户zpchen1,zpchen2。。。。登录后页面会显示不了审批文章
的菜单,
你可以查看下menu.jsp的代码就会发现问题所在了,
找到如下代码:
<%
}
//如果具有管理员权限
if(userSession != null && userSession.getDutyValue() != null &&
userSession.getDutyType().equals("审批员")){
%>
修改成
<%
}
//如果具有管理员权限
if(userSession != null && userSession.getDutyValue() != null &&
(userSession.getDutyType().equals("审批员")||userSession.getDutyType
().contains("自定义流程权限"))){
%>
OK,到这里应用就完成了。
OVER!
在原来项目的基础上来实现流程图动态显示的功能,发现利用JBPM自己写的标签会报错,原因是项目是用Spring整合JBPM的,当引用标签的时候,标签中的处理函数调用JbpmContext就会发生Hibernate 方言错误。
那如何来处理该错误呢?google了一下,发现了一篇不错的文章,就讲到了这个问题。
下面我就把关键的部分引用过来,当然为了支持原创,我把地址附上
http://hi.baidu.com/xiaolangs/blog/item/ea0625fb1d3ff061034f5685.html
在JSP页面中显示JBPM流程图
下载jbpm-starters-kit-3.1.x工具包。在包中有.java文件,\jbpm.3\src\java.webapp\org \jbpm\webapp\tag\ProcessImageTag.java与他的\WEB-INF\jbpm.tld,还有文件\jbpm.3 \src\java.webapp\org\jbpm\webapp\servlet\ProcessImageServlet.java,在JBPM的例子里,这三个文件共同完成JBPM流程图的显示。
要使两个文件发生作用,必须得在web.xml里配置,配置方法:
<!-- jBPM FileServlet -->
<servlet>
<servlet-name>ProcessImageServlet</servlet-name>
<servlet-class>org.jbpm.webapp.servlet.ProcessImageServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ProcessImageServlet</servlet-name>
<url-pattern>/processimage</url-pattern>
</servlet-mapping>
在页面中使用:<jbpm:processimage task="${taskBean.taskInstanceId}"/> 就会把流程图显示在当前位置。
在实际环境中可能会出现的问题与必要的修改
问题一
如果你是用spring整合的jbpm,那么在两个类里的取JbpmContext方法会出错。
JbpmContext jbpmContext = JbpmContext.getCurrentJbpmContext(); //这个是会出问题的。
修改:
1).ProcessImageTag.java的private void initialize()方法里。
WebApplicationContext wac = WebApplicationContextUtils.getRequiredWebApplicationContext(pageContext.getServletContext());
JbpmTemplate jbpmTemplate = (JbpmTemplate) wac.getBean("jbpmTemplate");
jbpmTemplate.execute(new JbpmCallback() {
public Object doInJbpm(JbpmContext context) {
if (taskInstanceId > 0) {
TaskInstance taskInstance = context.getTaskMgmtSession().loadTaskInstance(taskInstanceId);
currentToken = taskInstance.getToken();
}
else
{
if (tokenInstanceId > 0)
currentToken = context.getGraphSession().loadToken(tokenInstanceId);
}
return null;
}
});
2).
public class ProcessImageServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
private ProcessDefinition processDefinition;
private byte[] bytes;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
final long processDefinitionId = Long.parseLong( request.getParameter( "definitionId" ) );
WebApplicationContext wac = WebApplicationContextUtils.getRequiredWebApplicationContext(request.getSession().getServletContext());
JbpmTemplate jbpmTemplate = (JbpmTemplate) wac.getBean("jbpmTemplate");
jbpmTemplate.execute(new JbpmCallback() {
public Object doInJbpm(JbpmContext context) {
processDefinition = context.getGraphSession().loadProcessDefinition(processDefinitionId);
bytes = processDefinition.getFileDefinition().getBytes("processimage.jpg");
return null;
}
});
OutputStream out = response.getOutputStream();
out.write(bytes);
out.flush();
}
}
问题二
如果你的流程是用中文字符,且数据库字符集为utf-8,再数据库中为正确的utf-8内容,也可能会乱码错误。
如:我的数据库里的gpd.xml
出错的类:ProcessImageTag.java
出错行:result[0] = Integer.valueOf(node.attribute("x").getValue());
如果此地为nullpoint错误,是因为上下文的Element root中的字符为乱码。
修改:
原代码:Element rootDiagramElement = DocumentHelper.parseText(new String(gpdBytes)).getRootElement();
修改后:Element rootDiagramElement = DocumentHelper.parseText(new String(gpdBytes, "utf-8")).getRootElement();
说明:有个地方要注意,<jbpm:processimage task="${taskBean.taskInstanceId}"/> 显示图片是以当前的路径为基础的。style="background- image:url(processimage?definitionId=1)",也就是说当页面为:http://localhost/jbpm/workflow/showTaskInstance.jsp时,你所访问的流程图地址为:http://localhost/jbpm/workflow/processimage?definitionId=1,这个地址是错误的,实际地址为:http://localhost/jbpm/processimage?definitionId=1,要正确显示需修改ProcessImageTag.java
原代码:background-image:url(" + imageLink + ");
修改后:background-image:url(" + ((HttpServletRequest) pageContext.getRequest()).getContextPath() + "/" + imageLink + ");
[/size]
正好有个项目需要用到工作流机制,遂学习了下JBPM,感受了下JBPM带来的的便捷体验。现在我就把我的学习经历记录下来。和大家共同分享,也希望对那些刚研究JBPM的同仁们有所帮助。
ok,那就开始吧!
一。对于没有接触过JBPM的,可以先了解下JBPM应用场合,和JBPM的相关概念。我想如下这些东西将对你的JBPM学习很有帮助。
1。JBPM开发指南.pdf---------http://dl2.csdn.net/fd.php?i=12492886032537&s=020d83ba442aba86919657b97e51a699----对JBPM中的配置进行了比较详细的阐述。
2。还有就是大师级人物--陈刚 的教程http://www.blogjava.net/chengang/archive/2006/07/13/57986.html
,不过,这个教程只是简单的介绍了下JBPM的使用。对于初次接触JBPM的学习者来说,是个很好的选择。它将让你对JBPM有个较清晰的认识。
3。还有就是传智播客的一套关于JBPM的视频,verycd上就可以search到。这里就不给地址了。自己动手找找吧。
好了,如果你对JBPM有些认识了,那就开始来做个Struts+Hibernate+Spring+JBPM的文章审批系统吧。
首先介绍下该系统具体做些什么吧!
用户在该系统中发表文章,然后请求发布,该文章需要经过层层审批,再确定是否能发布。就像一个出版社中,当要出版一篇文章,需要经过一级审批,二级审批,三级审批.......最终确定发表该文章。需要说明的是,为了体现JBPM对流程的适配的特性,我们做的系统会定义4个不同的流程来展示JBPM的这一特性。对于不同的流程,系统会自动按照不同的流程定义文件来执行。
准备工作:
该系统的数据库环境为MySQL,如果要在其他数据库上完成也很简单,只需要在目标数据库建立起JBPM的表结构和业务表就可以,这里,就可以参考前面陈刚老师的教程了。就不再赘述了(如果需要可到附件中获得)。业务表建表语句如下:
/*==============================================================*/
/* Table: Article */
/*==============================================================*/
create table Article
(
ArticleNo int AUTO_INCREMENT not null comment '文章号',
UserNo int comment '用户号',
TypeNo int comment '文章类型号',
ArticleName varchar(128) comment '文章名称',
Content text comment '文章内容',
PiId bigint comment '对应流程实例号',
AuditState varchar(64) comment '审批状态',
AuditComment varchar(255) comment '审批说明',
State int comment '文章状态',
primary key (ArticleNo)
);
alter table Article comment '文章表';
/*==============================================================*/
/* Table: ArticleType */
/*==============================================================*/
create table ArticleType
(
TypeNo int AUTO_INCREMENT not null comment '文章类型号',
PdName varchar(255) comment '流程名称',
TypeName varchar(40) comment '类型名称',
primary key (TypeNo)
);
alter table ArticleType comment '文章类型表';
/*==============================================================*/
/* Table: User */
/*==============================================================*/
create table User
(
UserNo int AUTO_INCREMENT not null comment '用户号',
DutyNo int comment '职责号',
LoginName varchar(50) not null comment '账号',
Password varchar(32) comment '密码',
UserName varchar(50) comment '姓名',
primary key (UserNo)
);
alter table User comment '用户表';
/*==============================================================*/
/* Table: UserDuty */
/*==============================================================*/
create table UserDuty
(
DutyNo int AUTO_INCREMENT not null comment '职责号',
Name varchar(255) comment '名称',
DutyType varchar(20) comment '职责类型',
DutyValue varchar(64) comment '职责值',
primary key (DutyNo)
);
alter table UserDuty comment '用户职责表';
alter table Article add constraint FK_Relationship_1 foreign key (TypeNo)
references ArticleType (TypeNo) on delete restrict on update restrict;
alter table Article add constraint FK_Relationship_2 foreign key (UserNo)
references User (UserNo) on delete restrict on update restrict;
alter table User add constraint FK_Relationship_3 foreign key (DutyNo)
references UserDuty (DutyNo) on delete restrict on update restrict;
insert into `userduty`(`DutyNo`,`Name`,`DutyType`,`DutyValue`)
values (1,'管理员','管理员','管理员');
insert into `userduty`(`DutyNo`,`Name`,`DutyType`,`DutyValue`)
values (2,'普通用户','普通用户','普通用户');
insert into `userduty`(`DutyNo`,`Name`,`DutyType`,`DutyValue`)
values (3,'一级审批员','审批员','一级审批');
insert into `userduty`(`DutyNo`,`Name`,`DutyType`,`DutyValue`)
values (4,'二级审批员','审批员','二级审批');
insert into `userduty`(`DutyNo`,`Name`,`DutyType`,`DutyValue`)
values (5,'三级审批员','审批员','三级审批');
insert into `userduty`(`DutyNo`,`Name`,`DutyType`,`DutyValue`)
values (6,'四级审批员','审批员','四级审批');
insert into `user`(`UserNo`,`DutyNo`,`LoginName`,`Password`,`UserName`)
values (1,1,'manager','manager','管理员');
insert into `user`(`UserNo`,`DutyNo`,`LoginName`,`Password`,`UserName`)
values (2,2,'guest','guest','普通用户');
insert into `user`(`UserNo`,`DutyNo`,`LoginName`,`Password`,`UserName`)
values (3,3,'one','one','一级审批员');
insert into `user`(`UserNo`,`DutyNo`,`LoginName`,`Password`,`UserName`)
values (4,4,'two','two','二级审批员');
insert into `user`(`UserNo`,`DutyNo`,`LoginName`,`Password`,`UserName`)
values (5,5,'three','three','三级审批员');
insert into `user`(`UserNo`,`DutyNo`,`LoginName`,`Password`,`UserName`)
values (6,6,'four','four','四级审批员');
当数据库建立完成后,下载源代码,部署到服务器后,即可以开始体验完整的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项目中的具体应用就介绍完了。希望通过本教程学习的朋友,能够得到确实的提高。并且在教程中的一些个人拙见,望大侠们积极的批正!
从数据库中表的定义不难看出,每篇文章的审批将新建一个JBPM流程实例来跟踪。
文章的表article中,利用PiId来关联一个流程实例,并且定义State字段来表示文章的当前状态(编辑中,审批中,审批通过,nulll),定义AuditState字段来表示文章当前由何权限的人员来审批(一级审批,二级审批,三级审批,四级审批,当然还可以自定义权限)
下面就来演示如何自定义权限
首先在数据库中插入自己定义的权限
insert into userduty(`Name`,`DutyType`,`DutyValue`)
values ('自定义流程权限1','自定义流程权限1','自定义流程权限1');
insert into userduty(`Name`,`DutyType`,`DutyValue`)
values ('自定义流程权限2','自定义流程权限2','自定义流程权限2');
insert into userduty(`Name`,`DutyType`,`DutyValue`)
values ('自定义流程权限3','自定义流程权限3','自定义流程权限3');
insert into userduty(`Name`,`DutyType`,`DutyValue`)
values ('自定义流程权限4','自定义流程权限4','自定义流程权限4');
insert into userduty(`Name`,`DutyType`,`DutyValue`)
values ('自定义流程权限5','自定义流程权限5','自定义流程权限5');
insert into userduty(`Name`,`DutyType`,`DutyValue`)
values ('自定义流程权限6','自定义流程权限6','自定义流程权限6');
然后就是插入自定义用户(需要注意用户与权限之间的关联关系)
insert into user(DutyNo,`LoginName`,`Password`,`UserName`)
values (7,'zpchen1','zpchen1','zpchen1_自定义流程权限1');
insert into user(DutyNo,`LoginName`,`Password`,`UserName`)
values (8,'zpchen2','zpchen1','zpchen2_自定义流程权限2');
insert into user(DutyNo,`LoginName`,`Password`,`UserName`)
values (9,'zpchen3','zpchen3','zpchen3_自定义流程权限3');
insert into user(DutyNo,`LoginName`,`Password`,`UserName`)
values (10,'zpchen4','zpchen4','zpchen4_自定义流程权限4');
insert into user(DutyNo,`LoginName`,`Password`,`UserName`)
values (11,'zpchen5','zpchen15,'zpchen5_自定义流程权限5');
insert into user(DutyNo,`LoginName`,`Password`,`UserName`)
values (12,'zpchen6','zpchen6','zpchen6_自定义流程权限6');
(注意,如果直接copy上面的mysql语句,需要修改下蓝色部分标记的DutyNo,与你自己机子上的值对应起来,上面为我自己机子上数据的值)
OK,数据库要做的变化就是这些了!
接着就是自定义流程定义文件了.借助JBPM的Eclipse插件轻松实现之。
定义好的流程定义文件如下:
<?xml version="1.0" encoding="UTF-8"?>
<process-definition xmlns="urn:jbpm.org:jpdl-3.1"
name="zpchenProcess">
<start-state name="start">
<transition name="编辑我的任务" to="这个任务完
成"></transition>
</start-state>
<state name="这个任务完成">
<transition name="发布任务" to="自定义流程权限1">
<action name="action"
class="c20.jbpm.action.PubActionHandler">
</action>
</transition>
</state>
<state name="自定义流程权限1">
<transition name="同意" to="自定义流程权限2">
<action name="action"
class="c20.jbpm.action.AuditYesActionHandler">
</action>
</transition>
<transition name="实在糟糕" to="这个任务完成">
<action name="action"
class="c20.jbpm.action.AuditNoActionHandler">
</action>
</transition>
</state>
<state name="自定义流程权限2">
<transition name="同意" to="自定义流程权限3">
<action name="action"
class="c20.jbpm.action.AuditYesActionHandler">
</action>
</transition>
<transition name="不太满意" to="这个任务完成">
<action name="action"
class="c20.jbpm.action.AuditNoActionHandler">
</action>
</transition>
</state>
<state name="自定义流程权限3">
<transition name="同意" to="自定义流程权限4">
<action name="action"
class="c20.jbpm.action.AuditYesActionHandler">
</action>
</transition>
<transition name="重新编辑" to="这个任务完成">
<action name="action"
class="c20.jbpm.action.AuditNoActionHandler">
</action>
</transition>
</state>
<state name="自定义流程权限4">
<transition name="同意" to="自定义流程权限5">
<action name="action"
class="c20.jbpm.action.AuditYesActionHandler">
</action>
</transition>
<transition name="不同意" to="自定义流程权限2">
<action name="action"
class="c20.jbpm.action.AuditNoActionHandler">
</action>
</transition>
</state>
<state name="自定义流程权限5">
<transition name="同意" to="自定义流程权限6">
<action name="action"
class="c20.jbpm.action.AuditYesActionHandler">
</action>
</transition>
<transition name="继续完善" to="自定义流程权限3">
<action name="action"
class="c20.jbpm.action.AuditNoActionHandler">
</action>
</transition>
</state>
<state name="自定义流程权限6">
<transition name="很好,同意发表" to="end1">
<action name="action"
class="c20.jbpm.action.AuditFinishYesActionHandler">
</action>
</transition>
<transition name="继续完善" to="这个任务完成">
<action name="action"
class="c20.jbpm.action.AuditFinishNoActionHandler">
</action>
</transition>
</state>
<end-state name="end1"></end-state>
</process-definition>
从流程定义的文件中可以看出,流程的定义相当的灵活。
那么,系统是怎么将一个文章的审批过程按照这个流程定义来执行了?
本文开头就指出了,article表中定义了AuditState字段来表示文章当前由何权限的人员
来审批,当流程在运作当中,利用JBPM的ActionHandler句柄来动态的改变文章的
AuditState状态。然后当OA系统的用户(这里应该是具有文章审批等操作权限的用户,
而非guest,或manager)登录系统后,判断该用户的权限,如果该用户的权限正好和
article表中AuditState相同时候,就将这些权限级别的文章展现给该登录用户审批。这
样就达到了每篇文章的级级审批的过程了。
不过到这里,你登录管理员后可以发布这个自定义流程,guest用户也可以发布文章了,
但是当你以自己定义的用户zpchen1,zpchen2。。。。登录后页面会显示不了审批文章
的菜单,
你可以查看下menu.jsp的代码就会发现问题所在了,
找到如下代码:
<%
}
//如果具有管理员权限
if(userSession != null && userSession.getDutyValue() != null &&
userSession.getDutyType().equals("审批员")){
%>
修改成
<%
}
//如果具有管理员权限
if(userSession != null && userSession.getDutyValue() != null &&
(userSession.getDutyType().equals("审批员")||userSession.getDutyType
().contains("自定义流程权限"))){
%>
OK,到这里应用就完成了。
OVER!
在原来项目的基础上来实现流程图动态显示的功能,发现利用JBPM自己写的标签会报错,原因是项目是用Spring整合JBPM的,当引用标签的时候,标签中的处理函数调用JbpmContext就会发生Hibernate 方言错误。
那如何来处理该错误呢?google了一下,发现了一篇不错的文章,就讲到了这个问题。
下面我就把关键的部分引用过来,当然为了支持原创,我把地址附上
http://hi.baidu.com/xiaolangs/blog/item/ea0625fb1d3ff061034f5685.html
在JSP页面中显示JBPM流程图
下载jbpm-starters-kit-3.1.x工具包。在包中有.java文件,\jbpm.3\src\java.webapp\org \jbpm\webapp\tag\ProcessImageTag.java与他的\WEB-INF\jbpm.tld,还有文件\jbpm.3 \src\java.webapp\org\jbpm\webapp\servlet\ProcessImageServlet.java,在JBPM的例子里,这三个文件共同完成JBPM流程图的显示。
要使两个文件发生作用,必须得在web.xml里配置,配置方法:
<!-- jBPM FileServlet -->
<servlet>
<servlet-name>ProcessImageServlet</servlet-name>
<servlet-class>org.jbpm.webapp.servlet.ProcessImageServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ProcessImageServlet</servlet-name>
<url-pattern>/processimage</url-pattern>
</servlet-mapping>
在页面中使用:<jbpm:processimage task="${taskBean.taskInstanceId}"/> 就会把流程图显示在当前位置。
在实际环境中可能会出现的问题与必要的修改
问题一
如果你是用spring整合的jbpm,那么在两个类里的取JbpmContext方法会出错。
JbpmContext jbpmContext = JbpmContext.getCurrentJbpmContext(); //这个是会出问题的。
修改:
1).ProcessImageTag.java的private void initialize()方法里。
WebApplicationContext wac = WebApplicationContextUtils.getRequiredWebApplicationContext(pageContext.getServletContext());
JbpmTemplate jbpmTemplate = (JbpmTemplate) wac.getBean("jbpmTemplate");
jbpmTemplate.execute(new JbpmCallback() {
public Object doInJbpm(JbpmContext context) {
if (taskInstanceId > 0) {
TaskInstance taskInstance = context.getTaskMgmtSession().loadTaskInstance(taskInstanceId);
currentToken = taskInstance.getToken();
}
else
{
if (tokenInstanceId > 0)
currentToken = context.getGraphSession().loadToken(tokenInstanceId);
}
return null;
}
});
2).
public class ProcessImageServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
private ProcessDefinition processDefinition;
private byte[] bytes;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
final long processDefinitionId = Long.parseLong( request.getParameter( "definitionId" ) );
WebApplicationContext wac = WebApplicationContextUtils.getRequiredWebApplicationContext(request.getSession().getServletContext());
JbpmTemplate jbpmTemplate = (JbpmTemplate) wac.getBean("jbpmTemplate");
jbpmTemplate.execute(new JbpmCallback() {
public Object doInJbpm(JbpmContext context) {
processDefinition = context.getGraphSession().loadProcessDefinition(processDefinitionId);
bytes = processDefinition.getFileDefinition().getBytes("processimage.jpg");
return null;
}
});
OutputStream out = response.getOutputStream();
out.write(bytes);
out.flush();
}
}
问题二
如果你的流程是用中文字符,且数据库字符集为utf-8,再数据库中为正确的utf-8内容,也可能会乱码错误。
如:我的数据库里的gpd.xml
出错的类:ProcessImageTag.java
出错行:result[0] = Integer.valueOf(node.attribute("x").getValue());
如果此地为nullpoint错误,是因为上下文的Element root中的字符为乱码。
修改:
原代码:Element rootDiagramElement = DocumentHelper.parseText(new String(gpdBytes)).getRootElement();
修改后:Element rootDiagramElement = DocumentHelper.parseText(new String(gpdBytes, "utf-8")).getRootElement();
说明:有个地方要注意,<jbpm:processimage task="${taskBean.taskInstanceId}"/> 显示图片是以当前的路径为基础的。style="background- image:url(processimage?definitionId=1)",也就是说当页面为:http://localhost/jbpm/workflow/showTaskInstance.jsp时,你所访问的流程图地址为:http://localhost/jbpm/workflow/processimage?definitionId=1,这个地址是错误的,实际地址为:http://localhost/jbpm/processimage?definitionId=1,要正确显示需修改ProcessImageTag.java
原代码:background-image:url(" + imageLink + ");
修改后:background-image:url(" + ((HttpServletRequest) pageContext.getRequest()).getContextPath() + "/" + imageLink + ");
[/size]