繼續上一篇。
當數據庫建立完成後,下載源代碼,部署到服務器後,即可以開始體驗完整的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項目中的具體應用就介紹完了。希望通過本教程學習的朋友,能夠得到確實的提高。並且在教程中的一些個人拙見,望大俠們積極的批正!