`
hejiajunsh
  • 浏览: 410023 次
  • 性别: Icon_minigender_1
  • 来自: 天津
社区版块
存档分类
最新评论

JBPM4.4总结-嵌入自己的用户体系(集成自定义用户表)

 
阅读更多

 

      很多时候,JBPM自己提供的用户系统是不够用的,这时候就要求我们自己去扩展自己的用户体系。JBPM允许外挂一个用户体系。

      如果想嵌入自己的用户体系,只需如下五步:

 

一:创建MesUser,MesGroup,MesMembership三个类,分别实现接口User,Group。由于类MesMembership是独立的,因此没有必要继承或实现其他接口。

 

Jbpm4提供了实现接口

interface User 

interface Group

interface GroupMember

interface IdentitySession

想要集成自己的表,首先需要实现jbpm提供的接口,然后再进行配置。 

MesUser.java 用户表  

package com.amnet.jbpm.identify;

import java.io.Serializable;
import java.sql.Blob;
import org.jbpm.api.identity.User;

public class MesUser implements Serializable, User {

	private static final long serialVersionUID = 1L;

	protected long dbid; // 数据库内部自生成的ID

	private String id;

	private String userNo;// 员工工号

	private String userName;// 员工姓名

	private String userSex;// 性别

	private String userPassword;// 密码

	private String userType;// 类型

	private String userMail;// 电子邮件

	private String isValid;// 是否有效Y/N

	private Blob signaturePic;// 电子签名

	private String remarks;// 备注

	protected int dbversion;

	public int getDbversion() {

		return dbversion;

	}

	public MesUser() {

	}

	public MesUser(String id, String userName, String userMail) {

		this.id = id;

		this.userName = userName;

		this.userMail = userMail;

	}

	public void setDbversion(int dbversion) {

		this.dbversion = dbversion;

	}

	public long getDbid() {

		return dbid;

	}

	public void setDbid(long dbid) {

		this.dbid = dbid;

	}

	public void setId(String id) {

		this.id = id;

	}

	public String getUserNo() {

		return userNo;

	}

	public void setUserNo(String userNo) {

		this.userNo = userNo;

	}

	public String getUserName() {

		return userName;

	}

	public void setUserName(String userName) {

		this.userName = userName;

	}

	public String getUserSex() {

		return userSex;

	}

	public void setUserSex(String userSex) {

		this.userSex = userSex;

	}

	public String getUserPassword() {

		return userPassword;

	}

	public void setUserPassword(String userPassword) {

		this.userPassword = userPassword;

	}

	public String getUserType() {

		return userType;

	}

	public void setUserType(String userType) {

		this.userType = userType;

	}

	public String getUserMail() {

		return userMail;

	}

	public void setUserMail(String userMail) {

		this.userMail = userMail;

	}

	public String getIsValid() {

		return isValid;

	}

	public void setIsValid(String isValid) {

		this.isValid = isValid;

	}

	public Blob getSignaturePic() {

		return signaturePic;

	}

	public void setSignaturePic(Blob signaturePic) {

		this.signaturePic = signaturePic;

	}

	public String getRemarks() {

		return remarks;

	}

	public void setRemarks(String remarks) {

		this.remarks = remarks;

	}

	public String getDisplayName() {

		return userName + "(" + id + ")";

	}

	// 实现User接口所必须实现的几个方法

	public String getId() {

		return this.id;

	}

	public String getGivenName() {

		return null;

	}

	public String getFamilyName() {

		return null;

	}

	public String getBusinessEmail() {

		return this.userMail;

	}

}

MesGroup.java  部门表 

package com.amnet.jbpm.identify;

import java.io.Serializable;

import org.jbpm.api.identity.Group;

public class MesGroup implements Serializable, Group {

	private static final long serialVersionUID = 1L;

	private String id;

	private String groupName;// 组织名称

	private String groupType;// 组织类型

	private MesGroup parentGroup;// 父组织

	private String remarks;// 备注

	protected long dbid;

	protected int dbversion;

	public int getDbversion() {

		return dbversion;

	}

	public void setDbversion(int dbversion) {

		this.dbversion = dbversion;

	}

	public long getDbid() {

		return dbid;

	}

	public void setDbid(long dbid) {

		this.dbid = dbid;

	}

	public String getParentGroupID() {

		return parentGroup != null ? parentGroup.getId() : null;

	}

	public String getParentGroupName() {

		return parentGroup == null ? "xxx" : parentGroup.getGroupName();

	}

	public void setId(String id) {

		this.id = id;

	}

	public String getGroupName() {

		return groupName;

	}

	public void setGroupName(String groupName) {

		this.groupName = groupName;

	}

	public String getGroupType() {

		return groupType;

	}

	public void setGroupType(String groupType) {

		this.groupType = groupType;

	}

	public MesGroup getParentGroup() {

		return parentGroup;

	}

	public void setParentGroup(MesGroup parentGroup) {

		this.parentGroup = parentGroup;

	}

	public String getRemarks() {

		return remarks;

	}

	public void setRemarks(String remarks) {

		this.remarks = remarks;

	}

	// 实现Group接口必须的几个方法

	public String getName() {

		return this.groupName;

	}

	public String getType() {

		return this.groupType;

	}

	public String getId() {

		return id;

	}

}

MesGroupMembership.java 用户部门关系表 

package com.amnet.jbpm.identify;

import java.io.Serializable;

public class MesGroupMembership implements Serializable {

	private static final long serialVersionUID = 1L;

	protected long dbid;

	protected int dbversion;

	private MesUser user;

	private MesGroup group;

	protected String role;

	public int getDbversion() {

		return dbversion;

	}

	public void setDbversion(int dbversion) {

		this.dbversion = dbversion;

	}

	public long getDbid() {

		return dbid;

	}

	public String getRole() {

		return role;

	}

	public void setRole(String role) {

		this.role = role;

	}

	public void setDbid(long dbid) {

		this.dbid = dbid;

	}

	public MesGroup getGroup() {

		return group;

	}

	public void setGroup(MesGroup group) {

		this.group = group;

	}

	public String getUserNo() {

		return user.getUserNo();

	}

	public String getUserID() {

		return user.getId();

	}

	public String getUserName() {

		return user.getUserName();

	}

	public MesUser getUser() {

		return user;

	}

	public void setUser(MesUser user) {

		this.user = user;

	}

}

 

二:第一步完成了,那么就开始第二步。第二步的主要任务就是将这些类映射到数据库里。由于JBPM的持久层的操作采用的是Hibernate进行的。所以我们必须编写一个Hibernate的映射文件将实体类映射到数据库。

jbpm.customize.identify.hbm.xml

<?xml version="1.0"?>

<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping>
	<!-- ### USER ########################################################### -->
	<class name="com.amnet.jbpm.identify.MesUser" table="JBPM4_CUSTOMIZE_USER">

		<id name="dbid" column="DBID_">
			<generator class="assigned" />
		</id>

		<version name="dbversion" column="DBVERSION_" />

		<property name="id" column="USERID_" /> <!-- 登陆ID -->

		<property name="userNo" column="USERNO_" /> <!-- 员工工号 -->

		<property name="userName" column="USERNAME_" /> <!-- 姓名 -->

		<property name="userSex" column="USERSEX_" /> <!-- 性别 -->

		<property name="userPassword" column="USERPASSWORD_" /> <!-- 密码 -->

		<property name="userType" column="USERTYPE_" /> <!-- 类型 -->

		<property name="userMail" column="USERMAIL_" /> <!-- 电子邮件 -->

		<property name="signaturePic" column="SIGNATUREPIC_" /> <!-- 电子签名 -->

		<property name="remarks" column="REMARKS_" /> <!-- 备注 -->

	</class>
	
	<!-- ### MEMBERSHIP ##################################################### -->
	<class name="com.amnet.jbpm.identify.MesGroupMembership" table="JBPM4_CUSTOMIZE_MEMBERSHIP">

		<id name="dbid" column="DBID_">
			<generator class="assigned" />
		</id>

		<version name="dbversion" column="DBVERSION_" />

		<many-to-one name="user" column="USER_"
			class="com.amnet.jbpm.identify.MesUser" foreign-key="FK_MEM_USER"
			index="IDX_MEM_USER" />

		<many-to-one name="group" column="GROUP_"
			class="com.amnet.jbpm.identify.MesGroup" foreign-key="FK_MEM_GROUP"
			index="IDX_MEM_GROUP" />

		<property name="role" column="NAME_" />

	</class>

	<!-- ### GROUP ########################################################### -->
	<class name="com.amnet.jbpm.identify.MesGroup" table="JBPM4_CUSTOMIZE_GROUP">

		<id name="dbid" column="DBID_">
			<generator class="assigned" />
		</id>

		<version name="dbversion" column="DBVERSION_" />

		<!-- private String groupID;//数据库内部ID号 private String groupName;//组织名称 
			private String groupType;//组织类型 private MesGroup parentGroup;//父组织 private 
			String remarks;//备注 -->

		<property name="id" column="ID_" />

		<property name="groupName" column="NAME_" />

		<property name="groupType" column="TYPE_" />

		<property name="remarks" column="REMARKS_" />

		<many-to-one name="parentGroup" column="PARENT_"
			class="com.amnet.jbpm.identify.MesGroup" foreign-key="FK_GROUP_PARENT"
			index="IDX_GROUP_PARENT" />
	</class>

</hibernate-mapping>

上面的代码是仿照JBPM默认的映射文件jbpm.identity.hbm.xml写的。这个文件可以在JBPM的源代码里面找到,导入jar包时,它被封装在jbpm.jar里面。映射文件配置好了之后,在applicationContext.xml注入自己的mappingResources:

		<property name="mappingResources">
			<list>
				<value>com/amnet/jbpm/identify/jbpm.customize.identify.hbm.xml</value>
			</list>
		</property>

 

三:通过以上两步,就完成了由实体类到数据库的映射,也就是说,当你启动tomcat运行JBPM的时候,数据库里就会多出三张表:JBPM4_CUSTOMIZE_USER,JBPM4_CUSTOMIZE_GROUP,JBPM4_CUSTOMIZE_MEMBERSHIP。虽然如此,但是这只是利用了hibernate的自动生成数据表的功能产生了三个表而已,JBPM凭什么知道我需要使用的是我新创建的这三张表,而不会去调用原来的那三张表呢?答案是,他不知道。所以我们要继续进行配置。找到jbpm的一个配置文件叫:spring-jbpm4.xml。在这个文件里面原来有一句话:

import resource="jbpm.identity.cfg.xml"。我们可以去查看spring-jbpm4.xml的实际内容,结果里面就一句话:  <transaction-context><identity-session /></transaction-context>。有了这句话,就相当于告诉了JBPM,它将会去调用JBPM自带的关于用户体系操作的一个接口IdentitySession。这个类的实现IdentitySessionImpl里就会默认去调用JBPM自带的用户关系管理的数据表。所以为了改变JBPM的这种习惯,我们就必须自己创建一个类MesIdentitySessionImpl.java,并实现接口IdentitySession。这样JBPM就会去调用我们自己的实现,而不会再去调用它自己的东东。要实现这个只需要把他的配置文件spring-jbpm4.xml 的import resource="jbpm.identity.cfg.xml“注释掉,然后加上一句:

<transaction-context>
<hibernate-session current="true"/>
<object class="xxx . Xxx . xxx . MesIdentitySessionImpl">
</object>
</transaction-context>

在spring-jbpm4.xml注释掉jbpm4自己的cfg.xml文件

<!-- 
<import resource="jbpm.identity.cfg.xml" />
-->

  spring-jbpm4.xml 

<?xml version="1.0" encoding="UTF-8"?>
<jbpm-configuration>
	<process-engine-context>
		<string name="spring.cfg" value="spring-jbpm4.xml" />
	</process-engine-context>
	<import resource="jbpm.default.cfg.xml" />
	<import resource="jbpm.tx.spring.cfg.xml" />
	<import resource="jbpm.jpdl.cfg.xml" />
	<import resource="jbpm.bpmn.cfg.xml" />
	<import resource="jbpm.businesscalendar.cfg.xml" />
	<import resource="jbpm.console.cfg.xml" />

	<!-- <import resource="jbpm.identity.cfg.xml" /> -->
	<!-- <import resource="jbpm.jobexecutor.cfg.xml" /> -->

	<transaction-context>
		<hibernate-session current="true" />
		<object class="com.amnet.jbpm.identify.MesIdentitySessionImpl">
		</object>
	</transaction-context>
</jbpm-configuration>

这样就OK了。下面的任务就是去实现MesIdentitySessionImpl中的方法了。

 

四:实现MesIdentitySessionImpl中的方法

 

MesIdentitySessionImpl.java 实现类 

package com.amnet.jbpm.identify;

import java.util.Arrays;
import java.util.List;
import org.hibernate.Session;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.jbpm.api.JbpmException;
import org.jbpm.api.identity.Group;
import org.jbpm.api.identity.User;
import org.jbpm.pvm.internal.env.BasicEnvironment;
import org.jbpm.pvm.internal.env.EnvironmentImpl;
import org.jbpm.pvm.internal.id.DbidGenerator;
import org.jbpm.pvm.internal.identity.spi.IdentitySession;

@SuppressWarnings("unchecked")
public class MesIdentitySessionImpl implements IdentitySession {

	protected Session session;

	public MesIdentitySessionImpl() {

		this.session = BasicEnvironment.getFromCurrent(Session.class);

	}

//	public String createH() {
//
//		Test t = new Test();
//
//		long dbid = EnvironmentImpl.getFromCurrent(DbidGenerator.class)
//
//		.getNextId();
//
//		t.setDbid(dbid);
//
//		t.setId("abc");
//
//		return null;
//
//	}

	public String createUser(String id, String userName,

	String businessEmail, String familName) {

		MesUser user = new MesUser(id, userName, businessEmail);

		long dbid = EnvironmentImpl.getFromCurrent(DbidGenerator.class)

		.getNextId();

		user.setDbid(dbid);

		session.save(user);

		return user.getId();

	}

	public MesUser findUserById(String userId) {

		return (MesUser) session.createCriteria(MesUser.class).add(

		Restrictions.eq("id", userId)).uniqueResult();

	}

	public List<User> findUsersById(String... userIds) {

		List<User> users = session.createCriteria(MesUser.class).add(

		Restrictions.in("id", userIds)).list();

		if (userIds.length != users.size()) {

			throw new JbpmException("not all users were found: "

			+ Arrays.toString(userIds));

		}

		return users;

	}

	public List<User> findUsers() {

		return session.createCriteria(MesUser.class).list();

	}

	public void deleteUser(String userId) {

		// lookup the user

		MesUser user = findUserById(userId);

		// cascade the deletion to the memberships

		List<MesGroupMembership> memberships = session.createCriteria(

		MesGroupMembership.class).add(Restrictions.eq("user", user)).list();

		// delete the related memberships

		for (MesGroupMembership membership : memberships) {

			session.delete(membership);

		}

		// delete the user

		session.delete(user);

	}

	public String createGroup(String groupName, String groupType,

	String parentGroupId) {

		MesGroup group = new MesGroup();

		String groupId = groupType != null ? groupType + "." + groupName

		: groupName;

		group.setId(groupId);

		long dbid = EnvironmentImpl.getFromCurrent(DbidGenerator.class)

		.getNextId();

		group.setDbid(dbid);

		group.setGroupName(groupName);

		group.setGroupType(groupType);

		if (parentGroupId != null) {

			MesGroup parentGroup = findGroupById(parentGroupId);

			group.setParentGroup(parentGroup);

		}

		session.save(group);

		return group.getId();

	}

	public List<User> findUsersByGroup(String groupId) {

		return session.createCriteria(MesGroupMembership.class).createAlias(

		"group", "g").add(Restrictions.eq("g.id", groupId))

		.setProjection(Projections.property("user")).list();

	}

	public MesGroup findGroupById(String groupId) {

		return (MesGroup) session.createCriteria(MesGroup.class).add(

		Restrictions.eq("id", groupId)).uniqueResult();

	}

	public List<Group> findGroupsByUserAndGroupType(String userId,

	String groupType) {

		return session.createQuery(

		"select distinct m.group" + " from "

		+ MesGroupMembership.class.getName()

		+ " as m where m.user.id = :userId"

		+ " and m.group.type = :groupType").setString("userId",

		userId).setString("groupType", groupType).list();

	}

	public List<Group> findGroupsByUser(String userId) {

		List<Group> gList = session.createQuery(

		"select distinct m.group" + " from "

		+ MesGroupMembership.class.getName()

		+ " as m where m.user.id = :userId").setString(

		"userId", userId).list();

		return gList;

	}

	public List<Group> findGroups() {

		return session.createCriteria(MesGroup.class).list();

	}

	public void deleteGroup(String groupId) {

		// look up the group

		MesGroup group = findGroupById(groupId);

		// cascade the deletion to the memberships

		List<MesGroupMembership> memberships = session.createCriteria(

		MesGroupMembership.class).add(Restrictions.eq("group", group))

		.list();

		// delete the related memberships

		for (MesGroupMembership membership : memberships) {

			session.delete(membership);

		}

		// delete the group

		session.delete(group);

	}

	public void createMembership(String userId, String groupId, String role) {

		MesUser user = findUserById(userId);

		if (user == null) {

			throw new JbpmException("user " + userId + " doesn't exist");

		}

		MesGroup group = findGroupById(groupId);

		if (group == null) {

			throw new JbpmException("group " + groupId + " doesn't exist");

		}

		MesGroupMembership membership = new MesGroupMembership();

		membership.setUser(user);

		membership.setGroup(group);

		membership.setRole(role);

		long dbid = EnvironmentImpl.getFromCurrent(DbidGenerator.class)

		.getNextId();

		membership.setDbid(dbid);

		session.save(membership);

	}

	public void deleteMembership(String userId, String groupId, String role) {

		MesGroupMembership membership = (MesGroupMembership) session
				.createCriteria(

				MesGroupMembership.class).createAlias("user", "u").createAlias(

				"group", "g").add(Restrictions.eq("u.id", userId)).add(

				Restrictions.eq("g.id", groupId)).uniqueResult();

		session.delete(membership);

	}

}

 

五:测试类 

集成自己的表就完成了。当你调用 identityService.createUser()方法时,就往你自定义的user表里插入了一条记录。

TestMesIdentitySession.java 新建用户和组

package com.amnet.action;

import org.jbpm.api.IdentityService;
import org.jbpm.api.ProcessEngine;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestMesIdentitySession {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext(
				"applicationContext.xml");
		applicationContext.start();
		ProcessEngine processEngine = (ProcessEngine) applicationContext
				.getBean("processEngine");

//		ExecutionService executionService = processEngine.getExecutionService();
//
//		TaskService taskService = processEngine.getTaskService();

		IdentityService identityService = processEngine.getIdentityService();

		identityService.createGroup("user_dept"); // 部门

		identityService.createUser("user1", "test1", "test1"); // 新建用户1

		identityService.createUser("user2", "test2", "test2"); // 新建用户2

		identityService.createMembership("user1", "user_dept"); // 绑定用户和部门的关系

		identityService.createMembership("user2", "user_dept");
	}
}

发布流程

ProcessEngine processEngine = Configuration.getProcessEngine();

RepositoryService repositoryService = processEngine

.getRepositoryService();

String deployId = repositoryService.createDeployment().addResourceFromClasspath("com/contract/contract.jpdl.xml").deploy();

删除流程

repositoryService.deleteDeploymentCascade(deployId));

开始流程实例

executionService.startProcessInstanceById(request.getParameter("id"));

 

 

 

 

分享到:
评论

相关推荐

    jbpm-4.4.zip

    4. **规则集成**:jbpm集成了Drools规则引擎,允许在流程中嵌入复杂的业务逻辑和决策规则。这样,流程可以根据特定条件动态调整其行为。 5. **事件处理**:jbpm 4.4引入了对事件处理的支持,如信号事件和时间事件,...

    jbpm4.4中文用户手册

    **jbpm4.4中文用户手册** jbpm4.4是一款功能强大的业务流程管理(BPM)和工作流系统,适用于构建和管理复杂的业务流程。该中文用户手册是官方提供的翻译版本,对于初学者来说是一份非常有价值的参考资料,旨在帮助...

    jbpm4.4开发资料

    jBPM4.4与Red Hat的另一开源产品Drools紧密集成,允许在流程中嵌入决策逻辑。Drools是一个强大的规则引擎,可以处理基于条件的业务决策。 7. **事件处理** 流程中可以定义各种类型的事件,如信号事件、边界事件和...

    jbpm4.4用户指南

    jbpm4.4用户指南是针对企业级工作流管理系统jbpm的一个详细教程,主要面向开发者,旨在帮助他们理解和使用jbpm 4.4版本进行业务流程管理(BPM)的开发工作。jbpm是一个开源的Java平台,它提供了一套完整的工具集,...

    jbpm4.4中文手册

    jBPM4.4提供了强大的脚本支持,允许在流程中嵌入JavaScript、Groovy等多种脚本语言,增强了流程的灵活性和扩展性。 ### 配置与管理 - **配置文件**:包括对邮件服务、数据库连接等系统资源的配置。 - **控制台工具...

    JBPM4.4用户手册.doc

    **JBPM4.4工作流用户手册概述** JBPM4.4是一款强大的开源工作流引擎,它基于GNU Lesser General Public License(LGPL)和JBoss End User License Agreement(EULA)发布。用户手册主要面向初学者,提供了一个逐步...

    jBPM 4.4开发指南 中文PDF

    - jBPM与Drools规则引擎紧密集成,允许在流程中嵌入业务规则,实现动态决策。 - 使用Drools工作流API,可以在流程中执行规则,并根据规则结果改变流程路径。 3. **事件处理** - 支持系统事件(如任务完成、流程...

    jBPM_4.4_用户指南.rar

    **jBPM 4.4 用户指南** jBPM(Java Business Process Management)是一个开源的工作流管理系统,专注于业务流程管理(BPM)和工作流自动化。版本4.4是其历史上的一个重要里程碑,提供了丰富的功能和改进,以帮助...

    JBPM JAR包1

    在"jbpm4.4 jar包1"中,可能包含以下组件的JAR文件: - `jbpm-bpmn2`: BPMN 2.0解析器,用于读取和执行流程定义。 - `jbpm-executor`: 负责异步任务执行和服务调用。 - `jbpm-persistence-jpa`: 与JPA集成的持久化...

    jBPM4.1中文用户手册.pdf

    - **自定义身份认证组件**:讨论了如何集成自定义的身份认证机制。 #### 十一、持久化 - 讨论了如何将流程执行的状态保存到持久存储中,以便后续恢复或审计。 #### 十二、计划执行器 - **概述**:介绍了计划执行器...

    JBPM-v3.2-userguide

    以上总结概括了JBPM-v3.2-userguide中的核心知识点,从JBPM的概览到具体的部署和配置,旨在为读者提供全面而深入的理解。JBPM作为一个强大的BPM引擎,不仅提供了图形化的流程设计工具,还支持高度定制化的流程执行和...

    JBPM视频代码

    【JBPM4.4视频代码实例详解】 JBPM(JBoss Business Process Management)是一款开源的工作流管理系统,主要用于处理业务流程的建模、部署、执行和监控。JBPM4.4是其历史版本,尽管现在已有更新的版本如JBPM7,但...

    JBPM学习视频06~10

    6. **规则集成**:JBPM集成了Drools决策管理引擎,视频可能涵盖如何在流程中嵌入规则,实现流程与规则的联动,以达到更智能的业务决策。 7. **持久化与事务管理**:了解JBPM如何利用JPA或Hibernate进行数据持久化,...

    jbpm6.0 入门(很有权威)

    jBPM 提供了一个集成在 Eclipse IDE 中的图形化编辑器,用户可以方便地绘制 BPMN2 流程图,并将它们转换为可执行的流程定义。 ##### 1.5 Workbench Web 应用 Workbench 是一个 Web 应用,用于管理和监控 jBPM 流程...

    JBPM工作流应用开发文档

    jBPM4支持多种流程定义语言,如jPDL、BPEL和Seam PageFlow,同时允许用户定制自己的流程模型和语言。其流程虚拟机(PVM)技术为未来支持更多流程语言提供了基础。 ### 安装和配置jBPM4 安装jBPM4需要的基本软件包括...

    jBPM Developers Guide.pdf

    ### jBPM开发者指南知识点概览 #### 一、引言 - **目标读者**:本文档主要面向希望深入了解jBPM工作流引擎特性和功能的开发者。 - **概述**:jBPM(Java Business Process Model)是一款开源的工作流管理系统,提供...

Global site tag (gtag.js) - Google Analytics