版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認領(lǐng)
文檔簡介
J2EE工作流管理系統(tǒng)jBPM詳解(一)一、jBPM入門簡介概述工作流業(yè)務(wù)流程管理技術(shù)是基于SOA技術(shù)實現(xiàn)的一個核心部分。使用工作流能夠在軟件開發(fā)和業(yè)務(wù)兩個層次受益:1、方便開發(fā)工作流管理系統(tǒng)能夠簡化企業(yè)級軟件開發(fā)甚至維護?!艚档烷_發(fā)風(fēng)險-通過使用狀態(tài)和動作這樣的術(shù)語,業(yè)務(wù)分析師和開發(fā)人員使用同一種語言交談。這樣開發(fā)人員就不必將用戶需求轉(zhuǎn)化成軟件設(shè)計了。
◆實現(xiàn)的集中統(tǒng)一-業(yè)務(wù)流程經(jīng)常變化,使用工作流系統(tǒng)的最大好處是:業(yè)務(wù)流程的實現(xiàn)代碼,不再是散落在各種各樣的系統(tǒng)中。
◆加快應(yīng)用開發(fā)-你的軟件不用再關(guān)注流程的參與者,開發(fā)起來更快,代碼更容易維護。2、業(yè)務(wù)流程管理(BPM)在自動化業(yè)務(wù)流程之前,分析并將它們規(guī)格化是一件艱苦但會有很好回報的工作:◆提高效率-許多流程在自動化過程中會去除一些不必要的步驟較好的流程控制-通過標(biāo)準(zhǔn)的工作方法和跟蹤審計,提高了業(yè)務(wù)流程的管理
◆改進客戶服務(wù)-因為流程的一致性,提高了對客戶響應(yīng)的可預(yù)見性
◆靈活-跨越流程的軟件控制,使流程可以按照業(yè)務(wù)的需要重新設(shè)計。
◆業(yè)務(wù)流程改進-對流程的關(guān)注,使它們趨向于流暢和簡單但從長遠的角度,工作流流程管理技術(shù)的研究可為兩個階段進行:1.目前解決華研今后新項目中復(fù)雜業(yè)務(wù)流程如何使用工作流引擎技術(shù)進行實現(xiàn)的問題。2.上升到面向服務(wù)體系架構(gòu),實現(xiàn)各個服務(wù)之間的業(yè)務(wù)流程。jBPM,全稱是JavaBusinessProcessManagement,是一種基于J2EE的輕量級工作流管理系統(tǒng)。jBPM是公開源代碼項目,它使用要遵循ApacheLicense.jBPM在2004年10月18日,發(fā)布了2.0版本,并在同一天加入了JBoss,成為了JBoss企業(yè)中間件平臺的一個組成部分,它的名稱也改成JBossjBPM.隨著jBPM加入JBoss組織,jBPM也將進入一個全新的發(fā)展時代,它的前景是十分光明的。JBossjBPM只有最小的倚賴性,它可以很容易的作為java庫來使用。當(dāng)然它也可以用在訪問量很大的J2EE群應(yīng)用服務(wù)器環(huán)境中。JBossjBPM可以同任何數(shù)據(jù)庫配置可以部署在任何應(yīng)用服務(wù)器上。jBPM最大的特色就是它的商務(wù)邏輯定義沒有采用目前的一些規(guī)范,如WfMCXPDL,BPML,ebXML,BPEL4WS等,而是采用了它自己定義的JBossjBPMProcessdefinitionlanguage(jPdl)。jPdl認為一個商務(wù)流程可以被看作是一個UML狀態(tài)圖。jPdl就是詳細定義了這個狀態(tài)圖的每個部分,如起始、結(jié)束狀態(tài),狀態(tài)之間的轉(zhuǎn)換等。jBPM的另一個特色是它使用Hibernate來管理它的數(shù)據(jù)庫。Hibernate是目前Java領(lǐng)域最好的一種數(shù)據(jù)持久層解決方案。通過Hibernate,jBPM將數(shù)據(jù)的管理職能分離出去,自己專注于商務(wù)邏輯的處理。(具體選型報告可參考工作流技術(shù)選型評估報告)。本文檔主要是工作流研究小組關(guān)于開源工作流引擎jbpm的研究成果總結(jié)。內(nèi)容包括工作流研究場景的介紹、jbpm的環(huán)境配置,并包括以下六個研究主題:◆JBPM同開發(fā)如何結(jié)合,又如何保持獨立性;對應(yīng)用系統(tǒng)的設(shè)計實現(xiàn)有什么樣的制約
◆用戶/角色如何同應(yīng)用系統(tǒng)結(jié)合,變化又如何處理
◆流程中每個活動,如何動態(tài)指定執(zhí)行者
◆類似傳閱功能如何實現(xiàn)
◆子流程如何實現(xiàn)
◆流程執(zhí)行步驟耗時閥值和自動提醒設(shè)置
◆jBPM當(dāng)前版本的穩(wěn)定性評估術(shù)語、定義和縮略語應(yīng)用場景本場景是從房地產(chǎn)營銷系統(tǒng)的需求中提取出來的,在房地產(chǎn)項目中,客戶選戶購房的過程是一個非常典型的工作流場景。我們將這一過程提取出來,作為jbpm技術(shù)研究的場景。在購房過程中,會有以下人員參與。
客戶:購房的主體
銷售人員:在購房過程中指導(dǎo)看房,購房。
銷售經(jīng)理:確定認購信息。
財務(wù)人員:收取定金。
客服人員:打印收據(jù),打印客戶認購書。
客戶購房的過程可以描述如下。
◆客戶來到售樓中心,銷售人員進行接待。
◆客戶向銷售人員提供預(yù)先申請的服務(wù)號。
◆銷售人員確認服務(wù)號是否有效,如果無效現(xiàn)場給出一個新的服務(wù)號。
◆銷售人員引導(dǎo)客戶選房。
◆客戶確定戶型,房號。
◆銷售人員錄入客戶認購資料。
◆銷售人員確認認購資料填寫完整。
◆銷售人員將認購資料提交給銷售經(jīng)理審批。
◆確定是否可以獲得優(yōu)惠,如果客戶是一次交清房款,獲得0.1%優(yōu)惠。
◆交上級經(jīng)理審批。
◆銷售人員引導(dǎo)客戶到財務(wù)處交納定金。
◆交納定金后,客服人員打印收據(jù)。
◆客服人員打印認購書。為了使用研究的場景更有代表性,揉合其他典型的工作流應(yīng)用場景,將上面的流程作一些刪改??蛻粽J購的流程改為:
◆客戶確定購房,銷售人員錄入認購資料。
◆提交認購資料,必須保證錄入資料的完整性。
◆提交審核,按總金額進行分類審核。如果金額小于50萬銷售經(jīng)理審核即可,大于50萬還要上級經(jīng)理兩人同時審核通過。
◆審批完成,客戶到財務(wù)處交納定金。
◆客服人員為客戶打印收據(jù),打印認購書,認購?fù)瓿?。上面的流程中一些異常情況(流程中的一些分支),在這里先不作考慮,如,客戶在交納定金之前可以隨時放棄認購。客戶的認購要錄入的資料內(nèi)容包括:客戶姓名,房號,定金,總金額,流程號等,這里僅選取幾個重要的數(shù)據(jù)作為保存對象。保存錄入資料時自動取得一個流程號,任何時候,可以根據(jù)此流程號跟蹤流程狀態(tài),所以在保存認購資料時要記錄流程號。
通用的標(biāo)準(zhǔn)規(guī)范
jBPM是個功能全面的WorkflowEngine,融合了4大功能:Workflow,BPM,BPEL,PageFlow。它自己有個BPEL擴展,采用jbossHibernate實現(xiàn),它使用自定義的自定義標(biāo)準(zhǔn)jpdl,不支持目前公開的工作流(業(yè)務(wù)流程)標(biāo)準(zhǔn),如:
◆JCP
◆JSR208JavaBusinessIntegration
◆JSR207ProcessDefinitionforJava
◆OASIS
◆WS-BPEL
◆WorkflowManagementCoalition(WFMC)
◆WFMCXPDL
◆BusinessProcessManagementInitiative(BPMI)
◆BPMN
◆ObjectManagementGroup(OMG)
◆StateChartXML(SCXML)處理測試階段的JBossjBPMBPEL擴展準(zhǔn)備支持BPEL1.1標(biāo)準(zhǔn)(非標(biāo)準(zhǔn)組織通過的標(biāo)準(zhǔn))。技術(shù)方案
由于公司采取的架構(gòu)是ejb3+tapestry4架構(gòu),所以我們的場景實現(xiàn)架構(gòu)也是基于以上架構(gòu)進行實現(xiàn)的(tapestry4+ejb3+jbpm3.2),我們所采取的jbpm版本是3.2,這也是目前jbpm最新的版本。這里我們使用jboss作為我們的web服務(wù)器開發(fā)環(huán)境
這里我們是采用eclipse3.2開發(fā)工具進行開發(fā),Tapestry4+ejb3的開發(fā)環(huán)境配置和以往的項目配置基本保持一致,這里就不再進行詳細介紹,關(guān)鍵就是jbpm3.2的配置方法。我們從jBoss官方網(wǎng)站()上下載jbpm-jpdl-suite-3.2.GA.zip,最新的版本是3.2.GA,這個包括:
◆jbpm-server,預(yù)先配置的jboss應(yīng)用服務(wù)器.
◆jbpm-designer,jBPM流程圖形化設(shè)計器的eclipse插件
.
◆jbpm-db,jBPM數(shù)據(jù)庫兼容包(參看下面).
◆jbpm,核心jbpm組件包括庫和用戶手冊.
◆jbpm-bpel,JBossjBPMBPEL擴展參考.預(yù)配置的JBoss應(yīng)用服務(wù)器有下列安裝組件:
核心jBPM組件,打包作為JBoss服務(wù)檔案一個包括所有jBPM表格的完整數(shù)據(jù)庫:默認的hypersonic數(shù)據(jù)庫包含所有的jBPM表格和已經(jīng)定義的流程.jBPM控制臺web應(yīng)用程序這個可以用來為jBPM管理員.
jBPM調(diào)度程序所有定時器的執(zhí)行.調(diào)度程序在新手工具箱里配置成了一個servlet.這個Servlet會為產(chǎn)生一個線程來監(jiān)視每個定時器的執(zhí)行.一個流程例子已經(jīng)發(fā)布進了jBPM數(shù)據(jù)庫,關(guān)于jbpm-jpdl-suite-3.2套件包的具體介紹,可以參考JBossjBPMCookbook手冊。JBossjBPM是一個Java庫.因此,它可以用在任何java環(huán)境比如web應(yīng)用程序,Swing應(yīng)用程序,EJB,WebService等等,JBossjBPM核心組件被打包成一個簡單的Java庫文件.它依賴你所使用的功能性,庫jbpm-jpdl.jar及一些相關(guān)的第三方的庫比如.hibernate3.2,dom4j等等在我們實現(xiàn)場景中。要使用jbpm,我們首先需要將jbpm3.2的包導(dǎo)入我們的項目當(dāng)中,jbpm3.2的主要包括兩個包jbpm-jpdl.jar和jbpm-identity.jar。而jbpm-jpdl.jar是jbpm的核心包,jbpm-identity.jar則是jbpm自帶的用戶角色權(quán)限管理包。我們將這兩個包導(dǎo)入我們的項目中。(具體可參考jbpm的用戶手冊)在jbpm中,流程的定義主要是編寫在xml文件中的,我們需要將具體的業(yè)務(wù)流程在xml中定義。所以我們需要在項目的根目錄上新建一個源目錄,這里我們命名為“processes”,在該目錄我們可以保存流程定義xml文件。JBossjBPM包括一個圖形化的流程設(shè)計工具.這個設(shè)計器是用來創(chuàng)作商業(yè)流程的,該圖形設(shè)計器是一個Eclipse插件,圖形化設(shè)計工具最重要的特性是它同時支持業(yè)務(wù)分析員和技術(shù)開發(fā)人員.這樣就可以在業(yè)務(wù)流程建模和實際執(zhí)行之間平滑轉(zhuǎn)換。使用該插件你可以通過界面來拖拉描繪你的業(yè)務(wù)流程,而不需要靠手寫編碼來設(shè)計。該插件位于jbpm-jpdl-suite-3.2.GA.zip中,插件的安裝方法可以參考elipse插件的安裝方法。安裝完畢后,我們下面可以通過該插件新建流程設(shè)計文件。步驟如下:1、在processes目錄中右建選擇“New”—》“other”2、選擇“ProcessDefinition”,點擊“Next”輸入Processname,點擊完成。打開設(shè)計界面,我們就可以在上面根據(jù)我們的業(yè)務(wù)來設(shè)計工作流流程文件。Jbpm本身包含很多自己的jbpm數(shù)據(jù)庫表,jBPM內(nèi)部使用hibernate來管理它的數(shù)據(jù)庫,通過Hibernate,jBPM將數(shù)據(jù)的管理職能分離出去,自己專注于商務(wù)邏輯的處理,而且我們可以使jBPM移植在不同的數(shù)據(jù)庫.。我們進行的每一步流程操作都保存在jbpm數(shù)據(jù)表中,通過調(diào)用jbpm提供的接口,我們可以對jBPM數(shù)據(jù)庫進行存儲,更新和檢索流程信息的服務(wù)。這里我們使用Mysql5.0數(shù)據(jù)庫,下面我們在項目中新建一個源目錄“config.files”,名字可以任意,在該目錄建立hibernate.cfg.xml文件:hibernate.cfg.xml<?xmlversion='1.0'encoding='utf-8'?><!DOCTYPEhibernate-configurationPUBLIC"-//Hibernate/HibernateConfigurationDTD3.0//EN""/hibernate-configuration-3.0.dtd"><hibernate-configuration><session-factory><!--hibernatedialect--><!--propertyname="hibernate.dialect">org.hibernate.dialect.HSQLDialect</property--><propertyname="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property><!--JDBCconnectionproperties(begin)===<propertyname="hibernate.connection.driver_class">org.hsqldb.jdbcDriver</property><propertyname="hibernate.connection.url">jdbc:hsqldb:mem:jbpm</property><propertyname="hibernate.connection.username">sa</property><propertyname="hibernate.connection.password"></property>====JDBCconnectionproperties(end)--><!--JDBCconnectionforMySQLdatabase--><propertyname="connection.driver_class">com.mysql.jdbc.Driver</property><propertyname="connection.url">jdbc:mysql://localhost/myjbpm</property><propertyname="connection.username">root</property><propertyname="connection.password">1234</property><!--JDBCconnectionpool(usethebuilt-in)--><propertyname="connection.pool_size">1</property><propertyname="vider_class">org.hibernate.cache.HashtableCacheProvider</property><!--usedatasource--><!--DataSourceproperties(begin)<propertyname="hibernate.connection.datasource">java:comp/env/JbpmDS</property>DataSourceproperties(end)--><!--Dropandre-createthedatabaseschemaonstartup--><propertyname="hbm2ddl.auto">create</property><!--JTAtransactionproperties(begin)===<propertyname="hibernate.transaction.factory_class">org.hibernate.transaction.JTATransactionFactory</property><propertyname="hibernate.transaction.manager_lookup_class">org.hibernate.transaction.JBossTransactionManagerLookup</property>====JTAtransactionproperties(end)--><!--CMTtransactionproperties(begin)===<propertyname="hibernate.transaction.factory_class">org.hibernate.transaction.CMTTransactionFactory</property><propertyname="hibernate.transaction.manager_lookup_class">org.hibernate.transaction.JBossTransactionManagerLookup</property>====CMTtransactionproperties(end)--><!--loggingproperties(begin)===<propertyname="hibernate.show_sql">true</property><propertyname="hibernate.format_sql">true</property><propertyname="hibernate.use_sql_comments">true</property>====loggingproperties(end)--><!--############################################--><!--#mappingfileswithexternaldependencies#--><!--############################################--><!--followingmappingfilehasadependendyon--><!--'bsh-{version}.jar'.--><!--uncommentthisifyoudon'thavebshonyour--><!--classpath.youwon'tbeabletousethe--><!--scriptelementinprocessdefinitionfiles--><mappingresource="org/jbpm/graph/action/Script.hbm.xml"/><!--followingmappingfileshaveadependendyon--><!--'jbpm-identity.jar',mappingfiles--><!--ofthepluggablejbpmidentitycomponent.--><!--Uncommentthefollowing3linesifyou--><!--wanttousethejBPMidentitymgmgt--><!--component.--><!--identitymappings(begin)--><mappingresource="org/jbpm/identity/User.hbm.xml"/><mappingresource="org/jbpm/identity/Group.hbm.xml"/><mappingresource="org/jbpm/identity/Membership.hbm.xml"/><!--identitymappings(end)--><!--followingmappingfileshaveadependendyon--><!--theJCRAPI--><!--jcrmappings(begin)===<mappingresource="org/jbpm/context/exe/variableinstanc/JcrNodeInstance.hbm.xml"/>====jcrmappings(end)--><!--######################--><!--#jbpmmappingfiles#--><!--######################--><!--hqlqueriesandtypedefs--><mappingresource="org/jbpm/db/hibernate.queries.hbm.xml"/><!--graph.defmappingfiles--><mappingresource="org/jbpm/graph/def/ProcessDefinition.hbm.xml"/><mappingresource="org/jbpm/graph/def/Node.hbm.xml"/><mappingresource="org/jbpm/graph/def/Transition.hbm.xml"/><mappingresource="org/jbpm/graph/def/Event.hbm.xml"/><mappingresource="org/jbpm/graph/def/Action.hbm.xml"/><mappingresource="org/jbpm/graph/def/SuperState.hbm.xml"/><mappingresource="org/jbpm/graph/def/ExceptionHandler.hbm.xml"/><mappingresource="org/jbpm/instantiation/Delegation.hbm.xml"/><!--graph.nodemappingfiles--><mappingresource="org/jbpm/graph/node/StartState.hbm.xml"/><mappingresource="org/jbpm/graph/node/EndState.hbm.xml"/><mappingresource="org/jbpm/graph/node/ProcessState.hbm.xml"/><mappingresource="org/jbpm/graph/node/Decision.hbm.xml"/><mappingresource="org/jbpm/graph/node/Fork.hbm.xml"/><mappingresource="org/jbpm/graph/node/Join.hbm.xml"/><mappingresource="org/jbpm/graph/node/State.hbm.xml"/><mappingresource="org/jbpm/graph/node/TaskNode.hbm.xml"/><!--context.defmappingfiles--><mappingresource="org/jbpm/context/def/ContextDefinition.hbm.xml"/><mappingresource="org/jbpm/context/def/VariableAccess.hbm.xml"/><!--taskmgmt.defmappingfiles--><mappingresource="org/jbpm/taskmgmt/def/TaskMgmtDefinition.hbm.xml"/><mappingresource="org/jbpm/taskmgmt/def/Swimlane.hbm.xml"/><mappingresource="org/jbpm/taskmgmt/def/Task.hbm.xml"/><mappingresource="org/jbpm/taskmgmt/def/TaskController.hbm.xml"/><!--module.defmappingfiles--><mappingresource="org/jbpm/module/def/ModuleDefinition.hbm.xml"/><!--bytesmappingfiles--><mappingresource="org/jbpm/bytes/ByteArray.hbm.xml"/><!--file.defmappingfiles--><mappingresource="org/jbpm/file/def/FileDefinition.hbm.xml"/><!--scheduler.defmappingfiles--><mappingresource="org/jbpm/scheduler/def/CreateTimerAction.hbm.xml"/><mappingresource="org/jbpm/scheduler/def/CancelTimerAction.hbm.xml"/><!--graph.exemappingfiles--><mappingresource="org/jbpm/graph/exe/Comment.hbm.xml"/><mappingresource="org/jbpm/graph/exe/ProcessInstance.hbm.xml"/><mappingresource="org/jbpm/graph/exe/Token.hbm.xml"/><mappingresource="org/jbpm/graph/exe/RuntimeAction.hbm.xml"/><!--module.exemappingfiles--><mappingresource="org/jbpm/module/exe/ModuleInstance.hbm.xml"/><!--context.exemappingfiles--><mappingresource="org/jbpm/context/exe/ContextInstance.hbm.xml"/><mappingresource="org/jbpm/context/exe/TokenVariableMap.hbm.xml"/><mappingresource="org/jbpm/context/exe/VariableInstance.hbm.xml"/><mappingresource="org/jbpm/context/exe/variableinstance/ByteArrayInstance.hbm.xml"/><mappingresource="org/jbpm/context/exe/variableinstance/DateInstance.hbm.xml"/><mappingresource="org/jbpm/context/exe/variableinstance/DoubleInstance.hbm.xml"/><mappingresource="org/jbpm/context/exe/variableinstance/HibernateLongInstance.hbm.xml"/><mappingresource="org/jbpm/context/exe/variableinstance/HibernateStringInstance.hbm.xml"/><mappingresource="org/jbpm/context/exe/variableinstance/LongInstance.hbm.xml"/><mappingresource="org/jbpm/context/exe/variableinstance/NullInstance.hbm.xml"/><mappingresource="org/jbpm/context/exe/variableinstance/StringInstance.hbm.xml"/><!--jobmappingfiles--><mappingresource="org/jbpm/job/Job.hbm.xml"/><mappingresource="org/jbpm/job/Timer.hbm.xml"/><mappingresource="org/jbpm/job/ExecuteNodeJob.hbm.xml"/><mappingresource="org/jbpm/job/ExecuteActionJob.hbm.xml"/><!--taskmgmt.exemappingfiles--><mappingresource="org/jbpm/taskmgmt/exe/TaskMgmtInstance.hbm.xml"/><mappingresource="org/jbpm/taskmgmt/exe/TaskInstance.hbm.xml"/><mappingresource="org/jbpm/taskmgmt/exe/PooledActor.hbm.xml"/><mappingresource="org/jbpm/taskmgmt/exe/SwimlaneInstance.hbm.xml"/><!--loggingmappingfiles--><mappingresource="org/jbpm/logging/log/ProcessLog.hbm.xml"/><mappingresource="org/jbpm/logging/log/MessageLog.hbm.xml"/><mappingresource="org/jbpm/logging/log/CompositeLog.hbm.xml"/><mappingresource="org/jbpm/graph/log/ActionLog.hbm.xml"/><mappingresource="org/jbpm/graph/log/NodeLog.hbm.xml"/><mappingresource="org/jbpm/graph/log/ProcessInstanceCreateLog.hbm.xml"/><mappingresource="org/jbpm/graph/log/ProcessInstanceEndLog.hbm.xml"/><mappingresource="org/jbpm/graph/log/ProcessStateLog.hbm.xml"/><mappingresource="org/jbpm/graph/log/SignalLog.hbm.xml"/><mappingresource="org/jbpm/graph/log/TokenCreateLog.hbm.xml"/><mappingresource="org/jbpm/graph/log/TokenEndLog.hbm.xml"/><mappingresource="org/jbpm/graph/log/TransitionLog.hbm.xml"/><mappingresource="org/jbpm/context/log/VariableLog.hbm.xml"/><mappingresource="org/jbpm/context/log/VariableCreateLog.hbm.xml"/><mappingresource="org/jbpm/context/log/VariableDeleteLog.hbm.xml"/><mappingresource="org/jbpm/context/log/VariableUpdateLog.hbm.xml"/><mappingresource="org/jbpm/context/log/variableinstance/ByteArrayUpdateLog.hbm.xml"/><mappingresource="org/jbpm/context/log/variableinstance/DateUpdateLog.hbm.xml"/><mappingresource="org/jbpm/context/log/variableinstance/DoubleUpdateLog.hbm.xml"/><mappingresource="org/jbpm/context/log/variableinstance/HibernateLongUpdateLog.hbm.xml"/><mappingresource="org/jbpm/context/log/variableinstance/HibernateStringUpdateLog.hbm.xml"/><mappingresource="org/jbpm/context/log/variableinstance/LongUpdateLog.hbm.xml"/><mappingresource="org/jbpm/context/log/variableinstance/StringUpdateLog.hbm.xml"/><mappingresource="org/jbpm/taskmgmt/log/TaskLog.hbm.xml"/><mappingresource="org/jbpm/taskmgmt/log/TaskCreateLog.hbm.xml"/><mappingresource="org/jbpm/taskmgmt/log/TaskAssignLog.hbm.xml"/><mappingresource="org/jbpm/taskmgmt/log/TaskEndLog.hbm.xml"/><mappingresource="org/jbpm/taskmgmt/log/SwimlaneLog.hbm.xml"/><mappingresource="org/jbpm/taskmgmt/log/SwimlaneCreateLog.hbm.xml"/><mappingresource="org/jbpm/taskmgmt/log/SwimlaneAssignLog.hbm.xml"/></session-factory></hibernate-configuration>我們在web應(yīng)用項目的web.xml文件中定義JbpmContextFilter過濾類,它用于實現(xiàn)JBPMCONTEXT的初始化<filter><filter-name>JbpmContextFilter</filter-name><filter-class>org.jbpm.web.JbpmContextFilter</filter-class></filter><filter-mapping><filter-name>JbpmContextFilter</filter-name><url-pattern>/*</url-pattern></filter-mapping>到這里,jbpm的配置已經(jīng)基本完成,關(guān)于jbpm詳細資料大家可以參考jbpm用戶指南,這里就不多說。子流程的使用成果介紹詳細闡述開發(fā)成果
評審標(biāo)準(zhǔn):清楚介紹開發(fā)成果
當(dāng)一個流程的業(yè)務(wù)邏輯非常復(fù)雜的時候,可以考慮使用子流程。子流程和主流程是相對獨立的。設(shè)計思路描述主要的設(shè)計思路,開發(fā)方法以及技術(shù)要點
評審標(biāo)準(zhǔn):清晰表達設(shè)計思路和技術(shù)要點
在jbpm中,我們可以將一個復(fù)雜的業(yè)務(wù)流程文件根據(jù)業(yè)務(wù)邏輯的不同劃分為父流程和子流程,這樣一方面可以令我們的流程定義文件不會設(shè)計得太臃腫,二來可以方便我們將來的維護,只對需要修改的流程進行修改,而不影響其他流程。如何使用闡述如何結(jié)合項目需要應(yīng)用成果進行開發(fā)。這部分需要詳細描述,讓其他開發(fā)人員按照此成果報告,能夠進行一般簡單的開發(fā),具有較強的可操作性。
評審標(biāo)準(zhǔn):開發(fā)人員按此使用說明基本能應(yīng)用成果進行開發(fā)這里我們介紹下關(guān)于jbpm子流程的使用,這里我們定義兩個流程定義xml文件,一個是父流程定義文件,一個是子流程定義文件。這里我想當(dāng)執(zhí)行完P(guān)ayfirst任務(wù)的時候,jbpm流程能自動去我的子流程文件中去執(zhí)行那邊定義的任務(wù)。這里是父流程processdefinition.xml<?xmlversion="1.0"encoding="UTF-8"?><process-definitionxmlns="urn::jpdl-3.2"name="myapp">。。。。。。<task-nodename="PayFirst"><taskname="PayFirstTask"swimlane="finance"></task><transitionname="gethousecontract"to="subprocess"><actionname="action"class="com.myapp.action.MessageActionHandler"><message>Haspayfirstbulkofmoney.Printconstractnow!</message></action></transition></task-node><process-statename="subprocess"><sub-processname="subprocessdefinition"/><transitionto="end"></transition></process-state><task-nodename="passroundforperusal"signal="last-wait"create-tasks="false"><taskname="perusal"><assignmentactor-id="#{processStarter}"></assignment></task><eventtype="node-enter"><actionname="createInstance"class="com.myapp.action.CreateTaskInstanceAction"></action></event><transitionname="backto"to="OnePersonAudit"></transition></task-node></process-definition>可以看到,上面我們使用到了,在jbpm中,process-state標(biāo)簽代表的是引用子流程。這里我們接著定義子流程文件。子流程subprocessdefinition定義文件<?xmlversion="1.0"encoding="UTF-8"?><process-definitionxmlns="urn::jpdl-3.2"name="subprocessdefinition"><swimlanename="service"><assignmentactor-id="service1"/></swimlane><start-statename="subStart"><transitionto="PrintContract"></transition></start-state><task-nodename="PrintContract"><taskname="PrintContractTask"swimlane="service"></task><transitionname="PrintContract"to="end"><actionname="action"class="com.myapp.action.MessageActionHandler"><message>Finishtheprocessinstancenow.</message></action></transition></task-node><end-statename="end"></end-state></process-definition>示例實現(xiàn)結(jié)合項目需要實現(xiàn),采用開發(fā)成果開發(fā)一個簡單應(yīng)用示例??梢枣溄拥狡渌臋n,如示例實現(xiàn)的項目工程評審標(biāo)準(zhǔn):能簡單展示開發(fā)成果的開發(fā)應(yīng)用上面我們定義了兩個XML文件,一個是父流程,一個是子流程。下面我們說下如何使用這兩個文件。首先我們要先部署這兩個文件,使用子流程要注意,部署的時候一定要先部署子流程,然后在部署父流程。ProcessDefinitionsubProcess=ProcessDefinition.parseXmlResource("subprocessdefinition/processdefinition.xml");jbpmContext.deployProcessDefinition(subProcess);ProcessDefinitionprocessDefinition=ProcessDefinition.parseXmlResource("processdefinition.xml");jbpmContext.deployProcessDefinition(processDefinition);部署完后,jbpm會將這兩個流程定義文件保存在jbpm_processinstance表中,在調(diào)用中,與單個流程文件調(diào)用沒有任何區(qū)別,我們調(diào)用PrintContract任務(wù)的end()方法,jbpm會根據(jù)的流程文件,自動找到子流程文件所定義的任務(wù)進行執(zhí)行。使用規(guī)范描述使用該技術(shù)的規(guī)范(如接口設(shè)計、接口實現(xiàn)、框架設(shè)計、數(shù)據(jù)結(jié)構(gòu)設(shè)計等)、約定、約束等
評審標(biāo)準(zhǔn):清晰、詳細描述出其應(yīng)用規(guī)范注意事項描述配置、開發(fā)等需要注意的問題,包括各種關(guān)鍵點和難點??芍鸩窖a充
評審標(biāo)準(zhǔn):開發(fā)過程中遇到的關(guān)于應(yīng)用開發(fā)成果開發(fā)的問題,大部分都可以從這里找到答案使用子流程要注意:要先部署子流程,然后再部署主流程,否則,主流成執(zhí)行的時候會報找不到子流程的異常直接查看jbpm_Token或者jbpm_log無法找到流程間的關(guān)系,需要查看jbpm_processinstance表,才能找到父流程,因為Token在離開processstate的時候就會刪除subprocessid,直接看jbpm_log也無法看出兩個token之間的關(guān)系。應(yīng)用系統(tǒng)與jBPM的結(jié)合成果介紹在實際開發(fā)使用jBPM,可以采用jBPM系統(tǒng)與業(yè)務(wù)系統(tǒng)完全分離的策略。jBPM系統(tǒng)只負責(zé)流程的監(jiān)控和執(zhí)行,業(yè)務(wù)的重心仍然是實際業(yè)務(wù)需求。設(shè)計思路客戶端訪問系統(tǒng)時,一切業(yè)務(wù)相關(guān)的操作在業(yè)務(wù)系統(tǒng)中實現(xiàn),需要流程監(jiān)控的業(yè)務(wù)在jBPM流程系統(tǒng)中建立相關(guān)流程,提供相關(guān)流程的監(jiān)控和執(zhí)行接口,客戶端可以通過這些接口對流程進行操作。啟動一個流程實例時,首先訪問流程系統(tǒng),取得一個新的流程實例ID。在業(yè)務(wù)系統(tǒng)中保存這個ID。在進行流程監(jiān)控和執(zhí)行時,根據(jù)這個ID對流程實例進行操作。如何使用以上面購房流程為例說明,將客戶購房過程在一個Order中進行處理??蛻舻怯浛捶浚瑔右粋€流程實例,取得流程ID,保存在Order中
銷售人員,銷售經(jīng)理,財務(wù)人員,都可以通過流程系統(tǒng)提供的API查找當(dāng)前任務(wù),執(zhí)行任務(wù)時,一方面執(zhí)行流程,一方面修改Order記錄。示例實現(xiàn)Order要記錄流程ID。
publicclassOrderimplementsSerializable{
privateLongid;
privateLongprocessId;
}流程和業(yè)務(wù)系統(tǒng)的接口為OrderManager和BpmManager。
客戶看房登錄時先啟動一個流程。BpmManagerbpmManger=...;
LongprocessId=bpmManager.createProcess();
Orderorder=newOrder();
order.setProcessId(processId);
session.save(order);在后面的步驟中,可以根據(jù)Order的processId取得流程ID,執(zhí)行流程任務(wù)。
bpmManager.executeProcessTask();
session.update(order);
注意事項應(yīng)用系統(tǒng)中用戶角色如何與jBPM結(jié)合成果介紹應(yīng)用系統(tǒng)中的用戶應(yīng)該與jBPM流程系統(tǒng)中一致,必須統(tǒng)一起來才能使用。一方面可以采用用戶帳號同步的策略,從業(yè)務(wù)系統(tǒng)復(fù)制必要的用戶信息到j(luò)BPM流程系統(tǒng)中,另一方面可以使用共用用戶賬號的策略,保持最基本的用戶賬號獨立性,業(yè)務(wù)系統(tǒng)從最基本的用戶賬號上擴展用戶信息。設(shè)計思路由于兩個系統(tǒng)中用戶在需求上有一定的差別,得益Hibernate的映射機制,可以使用一個用戶賬號表,不同映射文件,保持系統(tǒng)的相對獨立性。如何使用jBPM中用戶是由identity模塊提供,在實際開發(fā)中,可以以jBPM中提供的用戶表為基礎(chǔ),應(yīng)用系統(tǒng)的較詳細的用戶信息在上面擴展。也可以建立一個基礎(chǔ)的用戶帳號,jBPM中的用戶與應(yīng)用系統(tǒng)中的用戶在它的基礎(chǔ)上擴展。示例實現(xiàn)jBPM中User提供了幾最基本的字段。publicclassUserextendsEntityimplementsPrincipal{privatestaticfinallongserialVersionUID=1L;protectedStringpassword=null;protectedStringemail=null;protectedSetmemberships=null;publicUser(){}publicUser(Stringname){super(name);}}Hibernate映射文件內(nèi)容為:<?xmlversion="1.0"?><!DOCTYPEhibernate-mappingPUBLIC"-//Hibernate/HibernateMappingDTD3.0//EN""/hibernate-mapping-3.0.dtd"><hibernate-mappingauto-import="false"default-access="field"><classname="org.jbpm.identity.User"table="JBPM_ID_USER"discriminator-value="U"><idname="id"column="ID_"><generatorclass="native"/></id><discriminatortype="char"column="CLASS_"/><propertyname="name"column="NAME_"/><propertyname="email"column="EMAIL_"/><propertyname="password"column="PASSWORD_"/><setname="memberships"cascade="all"><keycolumn="USER_"/><o(jì)ne-to-manyclass="org.jbpm.identity.Membership"/></set><setname="permissions"cascade="all"table="JBPM_ID_PERMISSIONS"><keycolumn="ENTITY_"foreign-key="none"/><elementtype="org.jbpm.identity.hibernate.PermissionUserType"><columnname="CLASS_"/><columnname="NAME_"/><columnname="ACTION_"/></element></set></class></hibernate-mapping>這里,應(yīng)用系統(tǒng)用戶為CustomUser,這里采用從jBPM中的User中繼承的策略,它多出一個字段carId。publicclassCustomUserextendsUser{privateStringcardId;publicStringgetCardId(){returncardId;}publicvoidsetCardId(StringcardId){this.cardId=cardId;}}映射文件為:<?xmlversion="1.0"?><!DOCTYPEhibernate-mappingPUBLIC"-//Hibernate/HibernateMappingDTD3.0//EN""/hibernate-mapping-3.0.dtd"><hibernate-mappingauto-import="false"default-access="field"><subclassname="com.sample.model.CustomUser"extends="org.jbpm.identity.User"discriminator-value="U"><jointable="CUSTOM_USER"><keycolumn="ID_"></key><propertyname="cardId"column="CARDID_"/></join></subclass></hibernate-mapping>這里,CustomUser是從jBPM中的User繼承的。jBPM當(dāng)前版本的穩(wěn)定性評估成果介紹通過官方j(luò)bpmRoadMap以及jbpmjira上面所寫的計劃,得出目前jbpm的版本更新速度將會比較頻繁,計劃上說將在今年年底完成jbpm4.0,目前版本已經(jīng)更新到j(luò)bpm3.2.1版本,而且從jira上發(fā)現(xiàn)jbpm3.3的版本也在快速開發(fā)中。而且從jira上看,目前版本升級主要是bug的修改和功能的完善。流程執(zhí)行步驟耗時閥值和自動提醒設(shè)置成果介紹Jbpm內(nèi)置調(diào)度功能,jbpm的調(diào)度部分分為2塊,timer主要是流程設(shè)計人員的工作,將timer放置到流程中;scheduler是jbpm自己維護的,我們只需要在后臺進行調(diào)用即可。設(shè)計思路流程執(zhí)行可以建立或刪除定時器.定時器存放在一個timerstore里.當(dāng)一個定時器的運行必須先從timerstore里面取得并且在根據(jù)指定的時間來啟動該定時器Jbpm時間管理思路整體來說實現(xiàn)的非常清晰:1、引擎解析流程定義xml時,給相應(yīng)的事件掛接上create-timer和cancel-timer動作2、流程實例實際運轉(zhuǎn)時,create-timer動作在相應(yīng)事件觸發(fā)時執(zhí)行3、create-timer在job表里插入相應(yīng)時間job記錄,給該job記錄附上計算完畢的執(zhí)行時間4、JobExecutorServlet在后臺啟動一到多個JobExecutorThread線程5、JobExecutorThread線程不停的每隔一段時間對job表掃描一次,找出需要執(zhí)行的job記錄,執(zhí)行之6、只執(zhí)行一次的job記錄,執(zhí)行完畢后刪除之;重復(fù)執(zhí)行的job記錄,寫入新的執(zhí)行時間,更新之7、相應(yīng)事件觸發(fā)cancel-timer動作,將對應(yīng)job記錄從job表里刪除如何使用jBPM通過定時器(timer)實現(xiàn)日程調(diào)度。在node中加入timer元素,即可實現(xiàn)基于定時器的節(jié)點執(zhí)行監(jiān)控,實現(xiàn)自動提醒功能。jbpm提供了2種調(diào)用scheduler的方法:一種是用在web應(yīng)用的,采用org.jbpm.scheduler.impl.SchedulerServlet,具體的方法這個類的javadoc有很好的示例,我們只需在web.xml中加載它就行了;
另一種是針對的c-s程序,jbpm提供了一個很好的示例org.jbpm.scheduler.impl.SchedulerMain,我們可以參照它編寫我們自己的Scheduler。實例實現(xiàn)最容易的方法指定一個定時器是在節(jié)點里加入定時器元素.
運用action的timer的例子<statename='catchcrooks'><timername='reminder'duedate='3businesshours'repeat='10businessminutes'transition='time-out-transition'><actionclass='the-remainder-action-class-name'/>timer>state>運用script的timer的例子<statename='catchcrooks'><timername='reminder'duedate='3businesshours'repeat='10businessminutes'transition='time-out-transition'><script>System.out.println(newDate())script>timer>state>在上例中,一旦流程實例運行進入state'catchcrooks',定時器reminder即被創(chuàng)建。該定時器延遲3businesshours開始執(zhí)行動作,每10businessminutes重復(fù)執(zhí)行一次,到期后馬上執(zhí)行action類中的Java代碼,然后實施time-out-transition(或script打印時間)遷移。通過在事件的action中加入create-timer和cancel-timer動作,可以分別實現(xiàn)事件對定時器的創(chuàng)建和取消。定時器timer可以被用于decisionforkjoinnodeprocess-statestatesuper-statetask-node等節(jié)點,可以設(shè)置開始時間duedate和頻率repeat,定時器動作可以是所支持的任何動作元素,如action或script,會運行我們設(shè)置的動作。定時器通過動作創(chuàng)建和取消,有兩個動作元素create-timer和cancel-timer。事實上,默認的定時器元素只是create-timer動作依附于node-enter事件、cancel-timer動作依附于node-leave事件的一個簡略表示。
說說整個過程:1、令牌進入節(jié)點catchcrooks
2、timer被觸發(fā)(實際這時是在執(zhí)行create-timer動作)
3、3businesshours后timer事件觸發(fā)
4、定義的action被執(zhí)行
5、令牌順著time-out-transition路徑離開catchcrooks節(jié)點
6、cancel-timer動作被執(zhí)行即timer終止(沒有給repeat的機會)另注:運用timer要先啟動scheduler,如果是web項目則只要在web.xml中配置JbpmThreadsServlet,這樣在項目啟動后會自動開啟scheduler。JbpmThreadsServlet配置如下:
<!--JbpmThreadsServlet--><servlet><servlet-name>JbpmThreadsServletservlet-name><servlet-class>org.jbpm.web.JbpmThreadsServletservlet-class><load-on-startup>1load-on-startup>servlet><servlet-mapping><servlet-name>JbpmThreadsServletservlet-name><url-pattern>/threadsurl-pattern>servlet-mapping>注意事項對time節(jié)點來說name、repeat、transition都是可選屬性。對一個流程定義來說,每一個time節(jié)點的name必須唯一,如果你不定義name屬性,引擎會默認把node節(jié)點的name賦給timer。在上面這個例子里,如果你不定義timer節(jié)點的name,則它的name就會是catchcrooks。說說repeat屬性,如果你不定義它,則timer就會只執(zhí)行一次動作不會重復(fù)執(zhí)行。transition屬性,如果定義了這個屬性,流程令牌會在timer執(zhí)行動作完畢后,順著這個路徑離開node節(jié)點。所以在上面這個例子里,盡管定義了repeat屬性,action還是會只執(zhí)行一次。action節(jié)點,可選,即timer節(jié)點在時間到時執(zhí)行的動作,可以是任意action類型,包括script。注意與時間有關(guān)的兩種action類型:create-timer和cancel-timer。其實一個timer節(jié)點在被引擎解釋時就是被分解為create-timer和cancel-timer兩個action,create-timer掛接到node-enter事件中,cancel-timer掛接到node-leave事件中。action節(jié)點最多只可以掛一個。傳閱功能的實現(xiàn)成果介紹傳閱功能是管理系統(tǒng)中比較常見的一個功能,這里使用jbpm實現(xiàn)該功能。設(shè)計思路這里通過使用jbpm的transition來實現(xiàn)傳閱功能。如何使用關(guān)于jbpm的transition使用很簡單,大家可以參考jbpm用戶指南示例實現(xiàn)<task-nodename="Coding"><taskname="Coding"swimlane="programmer"/><transitionname="to_CodeReview"to="CodeReview"></transition><transitionname="to_IntegratedTest"to="IntegratedTest"></transition></task-node><task-nodename="CodeReview"><taskname="ReviewCode"swimlane="manager"/></task><transitionname="to_Coding"to="Coding"></transition></task-node>上面是一個“代碼檢查”的類似傳閱的流程,程序員編寫完代碼之后需要傳給manager進行代碼審查,manager審查完畢需要發(fā)回給程序員。動態(tài)指定執(zhí)行者成果介紹上面講了傳閱功能的實現(xiàn),但大家可以發(fā)現(xiàn),上面的例子只能傳閱給流程定義xml文件上面指定的人閱讀,即不能是吸納動態(tài)指定傳閱。如果不能動態(tài)指定執(zhí)行者,則上面的實現(xiàn)意義不大,在實際操作中,很多操作都充滿了不確定性,即可能執(zhí)行者會經(jīng)常改變。這里我們介紹如何給任務(wù)動態(tài)指定執(zhí)行者。設(shè)計思路這里我們是通過jbpm的ActionHandler操作動態(tài)指定執(zhí)行者的操作,當(dāng)進入該任務(wù)節(jié)點的時候,我們可以通過為該任務(wù)指定一個action操作,該操作根據(jù)業(yè)務(wù)規(guī)則進行任務(wù)執(zhí)行者的動態(tài)指定。如何使用我們可以在一個任務(wù)task節(jié)點使用assignment標(biāo)簽指定運行該任務(wù)的執(zhí)行者,如果沒指定的人則不能執(zhí)行該任務(wù),另外我們也可以通過action操作來在程序中動態(tài)設(shè)置assignment中的執(zhí)行人來實現(xiàn),這里可以是一個或多個執(zhí)行人。示例實現(xiàn)首先我們將流程在processdefinition.xml定義,示例如下:<?xmlversion="1.0"encoding="UTF-8"?><process-definitionxmlns="urn::jpdl-3.2"name="myapp">......<task-nodename="OnePersonAudit"><taskname="OnePersonAuditTask"swimlane="manager"><controller><variablename="pass"access="read,write,required"></variable></controller></task><!--eventtype="node-leave"><actionname="createInstance"class="com.myapp.action.CreateTaskInstanceAction"></action></event--><transitionname="OnePersonAduit"to="IsAgreeAduit"/><transitionname="perusaltoone"to="passroundforperusal"></transition></task-node><task-nodename="passroundforperusal"signal="last-wait"create-tasks="false"><taskname="perusal"><assignmentactor-id="#{processStarter}"></assignment></task><eventtype="node-enter"><actionname="createInstance"class="com.myapp.action.CreateTaskInstanceAction"></action></event><transitionname="backto"to="OnePersonAudit"></transition></task-node></process-definition>上面我們有個任務(wù)OnePersonAudit,里面有個transition為perusaltoone,它指向任務(wù)passroundforperusal,這里是多人傳閱的一個流程,在passroundforperusal任務(wù)節(jié)點中我們使用來指定該任務(wù)的執(zhí)行者,我們還在該任務(wù)中使用了<eventtype="node-enter"><actionname="createInstance"class="com.myapp.action.CreateTaskInstanceAction"></action></event>事件類型“node-enter”表示當(dāng)進入該任務(wù)時執(zhí)行CreateTaskInstanceAction類的操作,我們在該類中動態(tài)設(shè)定該任務(wù)的執(zhí)行者CreateTaskInstanceAction的代碼如下:publicclassCreateTaskInstanceActionimplementsActionHandler{publicvoidexecute(ExecutionContextexecutionContext)throwsException{//TODOAuto-generatedmethodstubSystem.out.println("************************************");System.out.println("CreateTaskInstanceAction");System.out.println("************************************");Tokentoken=executionContext.getToken();TaskMgmtInstancetmi=executionContext.getTaskMgmtInstance();TaskNodetaskNode=(TaskNode)executionContext.getNode();Tasktask=taskNode.getTask("perusal");tmi.createTaskInstance(task,token).setActorId("mytest1");tmi.createTaskInstance(task,token).s
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 2024門店咖啡館門店承包經(jīng)營合同3篇
- 行政單位財務(wù)自查整改報告范文(7篇)
- 節(jié)能環(huán)保標(biāo)語
- 2024智能客服機器人研發(fā)與租賃服務(wù)合同
- 2024汽車銷售租賃合同
- 二零二五年度石油化工產(chǎn)品國際貿(mào)易實務(wù)買賣合同標(biāo)的運輸與儲存3篇
- 幼兒園教師外出跟崗培訓(xùn)交流匯報
- 2024配音工作室與廣告公司配音項目合作協(xié)議
- 2024年老舊小區(qū)水電線路改造專項施工合同3篇
- 專項工程分包2024年協(xié)議模板版A版
- 血液透析室護士長年終總結(jié)報告
- 露天礦山邊坡穩(wěn)定性分析與防治措施
- 《眼附屬器的解剖》課件
- 功能材料課件-形狀記憶合金
- 山地光伏安全文明施工方案
- 中醫(yī)醫(yī)院運營方案
- 公務(wù)員報考指南
- 烏頭堿中毒急診科培訓(xùn)課件-
- 貴州茅臺2023審計報告
- 高速鐵路沉降觀測與評估
- 家長要求學(xué)校換老師的申請書
評論
0/150
提交評論