1. 中转流程业务分析
基础设置模块: 为其他模块提供基础数据
取派模块(业务受理) : 接收客户配送请求,自动分单,由指定取派员去客户家取货, 送货和客户签收
中转模块
上海到北京进行物流, 途径南京, 货物在上海出发进行打包,到达南京后,进行拆包 ,将南京为目的地货物,就地配送,将南京到北京货物重新和 上海到北京货物重新合包,进行配送,到达每个地域,货物要拆包和合包,涉及到入库和出库的问题
建议完成 仓库管理系统
练习重点: 结合中转配送流程, 讲解一套JBPM项目解决方案 (应用JBPM )
需要: 各位先将JBPM 整合到项目 (课程第九天 )
2. 流程定义管理模块
使用工作流框架,将业务流程管理起来,实现业务流程自动化(基于表单提交和任务办理 )
该模块,与业务无关 ,先通过这个模块,将业务流程部署工作流系统中!
2.1. 业务流程定制和部署
2.1.1. 流程定制
设计流程分为在线设计和线下设计
在线设计: 打开网页 ,通过HTML和CSS、JS制作设计器,在线设计业务流程, 直接将流程发布到系统
线下设计:MyEclipse 安装GPD插件,设计流程生成 jpdl.xml 和 png 图片,将设计好流程,制作zip压缩包,通过文件上传的方式,将业务流程部署JBPM中
2.1.2. 编写流程定义发布功能
BOS项目,提供流程定义管理 模块
发布新流程、 流程定义列表查看、流程图查看
/WEB-INF/pages/workflow/processdefinition_deploy.jsp 发布新业务流程页面
编写服务器代码 ,建立workflow包,提供 ProcessDefinitionAction
public class ProcessDefinitionAction extends BaseAction {
}
发布新流程定义 RepositoryService
在 BaseAction 注入 ProcessEngine 对象
编写业务流程发布代码
2.2. 流程定义信息查看
datagrid 不用json ,直接对HTML 数据渲染
不是Ajax 流程,在访问 list页面前,先访问Action ,查询所有流程定义,放入值栈,跳转到jsp显示
在 ProcessDefinitionAction 中,提供 list方法
问题: 只显示每个相同key的最高版本的流程
// 查询所有流程定义
RepositoryService repositoryService = processEngine.getRepositoryService();
List<ProcessDefinition> list = repositoryService.createProcessDefinitionQuery()
.orderAsc(ProcessDefinitionQuery.PROPERTY_VERSION).list(); // 所有流程定义
// 每个相同key的流程,只显示最高版本
// map 的key 就是 pdKey 流程关键字, map的value 就是 流程定义
Map<String, ProcessDefinition> map = new HashMap<String, ProcessDefinition>();
for (ProcessDefinition processDefinition : list) {
map.put(processDefinition.getKey(), processDefinition);
}
// 放入值栈返回
ActionContext.getContext().put("processDefinitions", map.values());
在 struts-workflow.xml 配置 result
<!-- 流程定义管理 -->
<action name="processdefinition_*" class="processDefinitionAction" method="{1}">
<!-- 发布新流程 -->
<result name="deploySUCCESS" type="redirectAction">processdefinition_list</result>
<!-- 查询所有流程定义 -->
<result name="listSUCCESS">/WEB-INF/pages/workflow/processdefinition_list.jsp</result>
</action>
修改 admin.json 中 “流程定义管理” 链接
{ "id":"1004", "pId":"100", "name":"
流程定义管理”, "t":"","page":"processdefinition_list.action"},
2.3. 流程图查看
查看流程图,需要发布id 和 图片资源name
<s:a action="processdefinition_viewpng" namespace="/" cssClass="easyui-linkbutton" data-options="iconCls:'icon-search'">查看流程图
<s:param name="deploymentId" value="deploymentId" />
<s:param name="imageResourceName" value="imageResourceName" />
</s:a>
点击按钮,服务器查询图片,将图片返回,不要下载
在 ProcessDefinitionAction 添加 viewpng
文件下载,涉及一个流,两个头信息
ContentType 设置 MIME类型
ContentDisposition 打开方式 ,默认值 inline,设置 attachment 弹出下载窗口
// 获得图片资源流
RepositoryService repositoryService = processEngine.getRepositoryService();
in = repositoryService.getResourceAsStream(deploymentId, imageResourceName);
配置结果
<!-- 查看图片 -->
<result name="viewpngSUCCESS" type="stream">
<param name="contentType">image/png</param>
</result>
3. 中转业务流程管理分析
3.1. 分析业务流程,绘制jpdl.xml 业务流程图
3.2. 分析JBPM开发中考虑问题
问题: 如何启动业务流程 ?
工作单审核, 将WorkOrderManage 的 managerCheck 改为 1 ,启动流程
问题: 启动流程后,流程中很多任务节点,有谁来办理任务 ?
使用JBPM 组任务方式 candidate-groups 属性
问题: 办理任务,填写form表单 ,如何做到不同任务有不同的表单 ?
使用 <task>
节点 form属性,执行任务对应表单
问题: form提交存在业务数据,业务数据如何JBPM流程实例关联 ?
对每一个任务表单 设计一张表,通过一张流程实例整体对象,关联所有任务节点的table
transferinfo 表
instore 表
outstore 表
receivegoodsinfo 表
定义 总表 ZhongZhuanInfo 关联以上四张表数据
3.3. 发布中转流程
<task name="中转环节" g="196,99,92,52" candidate-groups="业务员" form="page_zhongzhuan__transferinfo.action">
<transition name="to 入库" to="入库" g="-47,-17"/>
</task>
<task name="入库" g="381,118,92,52" candidate-groups="仓库管理员" form="page_zhongzhuan__instore_complete.action">
<transition name="to 出库" to="出库" g="-47,-17"/>
</task>
<task name="出库" g="383,198,92,52" candidate-groups="仓库管理员" form="page_zhongzhuan__outstore_complete.action">
<transition name="to 配送签收" to="配送签收" g="-71,-17"/>
</task>
<task name="配送签收" g="382,277,92,52" candidate-groups="取派员" form="page_zhongzhuan__receiveinfo_complete.action">
<transition name="to end1" to="end1" g="-47,-17"/>
</task>
4. 中转配送流程实例启动 —- 工作单审核
4.1. 未审核工作单列表查询
在auth_function 表,已经准备了 工作单审核 Action 地址
查看所有未审核工作单信息 ,跳转 /WEB-INF/pages/zhongzhuan/check.jsp
编写 WorkOrderManageAction 工作单管理,添加 list 方法
<query name="WorkOrderManage.listUnChecked">
<![CDATA[from WorkOrderManage where managerCheck = '0']]>
</query>
配置结果页面
<!-- 工作单审核列表 -->
<result name="listSUCCESS">
/WEB-INF/pages/zhongzhuan/check.jsp
</result>
4.2. 对未审核工作单审核
一个工作单,对应一个中转配送流程 实例 ! 审核工作 启动中转流程 !
<s:a action="workordermanage_check" cssClass="easyui-linkbutton" iconCls="icon-edit">审核
<s:param name="id" value="#workOrderManage.id"></s:param>
</s:a>
编写 WorkOrderManageAction 提供 check 方法
—- 启动流程,使用ExecutionService
—- 将 ProcessEngine 对象,注入 BaseService
问题: 在启动流程实例,创建全局变量对象 ZhongZhuanInfo 关联流程实例
对ZhongZhuanInfo 持久化, 需要将ZhongZhuanInfoDAO 注入 BaseService
@Override
public void check(WorkOrderManage workOrderManage) {
// 操作一 : 将 工作单 managerCheck 属性值 设置为 1
WorkOrderManage persistWorkOrderManage = workOrderManageDAO.findById(workOrderManage.getId()); // 持久工作单对象
persistWorkOrderManage.setManagerCheck("1");
// 操作二 : 启动中转配送流程
ExecutionService executionService = processEngine.getExecutionService();
// 在启动流程时,关联流程实例 对应 全局 中转信息对象
ZhongZhuanInfo zhongZhuanInfo = new ZhongZhuanInfo();
zhongZhuanInfo.setArrive("0");// 未到达
zhongZhuanInfo.setWorkOrderManage(persistWorkOrderManage);// 关联工作单 信息
// 对ZhongZhuanInfo 进行持久化
zhongZhuanInfoDAO.save(zhongZhuanInfo);
Map<String, Object> variables = new HashMap<String, Object>();
variables.put("zhongZhuanInfo", zhongZhuanInfo);
executionService.startProcessInstanceByKey("transfer", variables);
}
5. JBPM系统用户初始化
问题: 使用组任务办理方式 ,JBPM系统中没有用户和组的信息 ,保存
<task name="中转环节" g="196,99,92,52" candidate-groups="业务员" form="page_zhongzhuan__transferinfo.action">
<transition name="to 入库" to="入库" g="-47,-17"/>
</task>
如何让JBPM用户认证功能和 系统用户统一起来 ,参考 14.3
方案一:
方案二: 将系统用户管理 与 JBPM 用户管理同步起来
用户管理 ,添加用户时,向 jbpm4_id_users 表插入一条数据
权限管理 ,添加角色时,向 jbpm4_id_groups 表插入一条数据
为用户授予角色时, 向 jbpm4_id_memebershipt 表插入一条数据
5.1. 修改 UserServiceImpl 添加用户,建立JBPM用户
使用 IdentityService
@Override
public void saveUser(User user) {
// 防止 Role的id 为空串
if (user.getRole() != null && user.getRole().getId() != null && user.getRole().getId().trim().length() == 0) {
user.setRole(null);
}
// 对密码 进行 md5 加密
user.setPassword(MD5Utils.md5(user.getPassword()));
userDAO.save(user);
// 在添加用户的同时,向 JBPM系统插入一个用户
IdentityService identityService = processEngine.getIdentityService();
identityService.createUser(user.getId(), user.getUsername(), user.getUsername()); // 建立JBPM用户
if (user.getRole() != null) {
// 在添加用户时,建立了和角色关系
Role role = roleDAO.findById(user.getRole().getId());
// 建立关系,JBPM 组id 使用 角色name属性
identityService.createMembership(user.getId(), role.getName());
}
}
5.2. 修改RoleServiceImpl 添加角色,建立JBPM 组
使用 IdentityService
@Override
public void saveRole(Role role, String functionIds) {
// 将role信息保存角色表
roleDAO.save(role); // 持久态
// 建立 role 和 function联系,向role_function 中间表插入数据
if (functionIds != null) {
String[] ids = functionIds.split(",");
for (String id : ids) {
Function function = funtionDAO.findById(id); // 功能权限
role.getFunctions().add(function); // 多对多关联,向中间表插入数据
}
}
// 建立JBPM 系统 组信息
IdentityService identityService = processEngine.getIdentityService();
identityService.createGroup(role.getName());
}
5.3. 为用户授予角色,建立用户和组的关系
修改 UserServiceImpl
为用户授予角色,先删除 JBPM 该用户和之前组关系
@Override
public void grantRole(User user) {
User exist = userDAO.findById(user.getId());
exist.setRole(user.getRole()); // 关联角色 自动更新
// 建立 JBPM 用户和组关系 一个用户只属于一个组
// 先删除 这个用户和原来组关系,建立新关系
IdentityService identityService = processEngine.getIdentityService();
// 获得用户原来的组
List<Group> list = identityService.findGroupsByUser(exist.getId());
for (Group group : list) {
identityService.deleteMembership(exist.getId(), group.getId(), null);
}
// 建立新关系
Role role = roleDAO.findById(user.getRole().getId());
identityService.createMembership(exist.getId(), role.getName());
}
5.4. 重新构建用户和角色信息
删除 user表 和 role表 所有信息
建立 zhangsan、lisi、wangwu 三个用户
jbpm4_id_user 建立数据
建立 业务员 、仓库管理员、取派员 三个角色
Jbpm4_id_group建立数据
建立关系 jbpm4_id_membership
zhangsan —- 业务员
lisi —- 仓库管理员
wangwu —- 取派员
6. 中转流程中 任务办理
6.1. 组任务列表查询
查看 auth_function 数据表
为 组任务添加 访问地址 task_findgrouptask.action
编写服务器端代码,添加TaskAction 提供 findgrouptask 方法
使用 TaskService 提供 findGroupTasks 方法查询 组任务
查询组任务:
// 查询组任务,使用 TaskService
TaskService taskService = processEngine.getTaskService();
User user = (User) ServletActionContext.getRequest().getSession().getAttribute("user");
List<Task> tasks = taskService.findGroupTasks(user.getId());
配置结果 :
<!-- 任务管理 -->
<action name="task_*" class="taskAction" method="{1}">
<!-- 查询组任务 -->
<result name="findgrouptaskSUCCESS">/WEB-INF/pages/workflow/grouptask.jsp</result>
</action>
问题: 如果页面是这样,用户怎么知道该去拾取哪个任务,没有显示任何业务数据 ?
页面获得 task对象,在查看Task接口API 后,没有显示业务数据的方法
如果显示业务数据,参考 TaskServiceImpl 实现 API
<s:iterator value="#tasks" var="task">
<tr>
<td><s:property value="id"/> </td>
<td><s:property value="name"/></td>
<td>
<!-- task.getVariables 返回 map -->
<s:iterator value="variables" var="entry">
<s:property value="key"/>=<s:property value="value"/><br/>
</s:iterator>
</td>
<td><a href="#" class="easyui-linkbutton">拾取</a></td>
</tr>
</s:iterator>
6.2. 将组任务拾取为个人任务
通过 TaskService 提供
修改 grouptask.jsp 为拾取按钮,添加 参数传递
<s:a action="task_takeTask" namespace="/" cssClass="easyui-linkbutton">拾取
<s:param name="taskId" value="id"></s:param>
</s:a>
编写服务器,在TaskAction 添加 takeTask 的方法
// 调用 TaskService 方法 进行组任务拾取
TaskService taskService = processEngine.getTaskService();
User user = (User) ServletActionContext.getRequest().getSession().getAttribute("user");
taskService.takeTask(taskId, user.getId());
配置结果
<!-- 拾取组任务 -->
<result name="takeTaskSUCCESS" type="redirectAction">
task_findgrouptask
</result>
6.3. 个人任务列表查询
功能和 组任务列表查询非常类似
查询个人任务
参考auth_function 数据表
在TaskAction 添加 findpersonaltask 业务方法
使用 TaskService
// 使用TaskService 查询个人任务
TaskService taskService = processEngine.getTaskService();
User user = (User) ServletActionContext.getRequest().getSession().getAttribute("user");
List<Task> tasks = taskService.findPersonalTasks(user.getId());
配置跳转结果:
<!-- 查询个人任务列表 -->
<result name="findpersonaltask">
/WEB-INF/pages/workflow/personaltask.jsp
</result>
在绘制流程图,为每个任务节点,指定form属性,办理任务,根据流程任务节点指定 form属性,跳转到不同页面去办理 !!!
6.4. 个人任务办理
6.4.1. 跳转到 任务对应 form表单页面
<task name="中转环节" g="196,99,92,52" candidate-groups="业务员" form="page_zhongzhuan__transferinfo.action">
<transition name="to 入库" to="入库" g="-47,-17"/>
</task>
获取form属性
Task 提供 getFormResourceName 方法
修改 personaltask.jsp
<!-- formResourceName 和 id 都是从struts2 值栈获得 -->
<a href="${pageContext.request.contextPath }/${formResourceName}?taskId=${id}" class="easyui-linkbutton">
办理任务
</a>
transferinfo.jsp 提供 隐藏域,用来保存 当前办理任务id
填写中转环节表单,提交
${pageContext.request.contextPath }/task_saveTransferinfo.action
编写服务器代码, 在TaskAction 添加 saveTransferinfo的 方法 (完成中转环节 任务)
非常重要!
当前按照form属性 编写form 进行提交后,服务器代码通常完成三件事
1、 将业务数据持久化
2、 将业务数据 关联流程实例上 (成为流程变量 )
3、 办理任务,流程自动向后流转
public interface BosTaskService {
}
public class BosTaskServiceImpl extends BaseService implements BosTaskService {
}
将 BosTaskService 注入 BaseAction
将 TransferInfoDAO 注入 BaseService
业务实现代码
public void completeTransferInfoTask(TransferInfo transferInfo, String taskId) {
// 1 、业务数据持久化
transferInfoDAO.save(transferInfo);
// 2、将业务数据 关联到 流程变量
TaskService taskService = processEngine.getTaskService();
ZhongZhuanInfo zhongZhuanInfo = (ZhongZhuanInfo) taskService.getVariable(taskId, "zhongZhuanInfo");
zhongZhuanInfo.setArrive(transferInfo.getArrive());
zhongZhuanInfo.getTransferInfos().add(transferInfo); // 向集合中添加一个中转环节信息
// 3、 办理任务,自动流转
if (zhongZhuanInfo.getArrive().equals("0")) {
// 未到达, 继续中转
// 使用自由流转实现
Task task = taskService.getTask(taskId);
completeTaskByCreateTransiton(task, "中转环节", "to 中转环节xxx");
} else {
// 到达
taskService.completeTask(taskId, "to 入库"); // 流向入库
}
}
办理任务后,重新跳回个人任务列表
<!-- 办理 中转环节 -->
<result name="saveTransferinfoSUCCESS"
type="redirectAction">task_findpersonaltask</result>