简介

通用流程可以用于一些基本的申请,例如请假、加班

大致过程是:

1. 创建申请
2. 分配给审批人(需要审批人列表,当前审批人)
-> 有下一个审批人 -> 3
-> 无 -> 4
3. 审批人审批
-> 同意 -> 2
-> 拒绝 -> 5
4. 存储数据,发送通知
5. 结束

比较简单,唯一的难点就是动态设置审批人或者审批组,下面开始代码部分。

bpmn20文件

    ...
<!-- standardRequest用来开始流程,在flowable里称为processDefinitionKey -->
<process id="standardRequest" name="标准申请流程" isExecutable="true">
<!-- 第一步:开始流程,创建申请 -->
<startEvent id="startEvent" name="创建申请"/>
<sequenceFlow sourceRef="startEvent" targetRef="assignToAuditor"/>
<!-- 第二步:分配审批人 -->
<!-- 这个AssignToAuditorDelegate类就是解决动态设置审批人的Java类 -->
<!-- 审批人列表需要从外部传入或者根据当前流程id来从数据库获取 -->
<serviceTask id="assignToAuditor" name="分配审批人" flowable:class="me.xwbz.flowable.delegate.AssignToAuditorDelegate"/>
<sequenceFlow sourceRef="assignToAuditor" targetRef="auditorExist"/>
<!-- 唯一网关:类似于switch,只能通过一个序列流 -->
<!-- 这里就是要么存在,要么不存在 -->
<!-- 使用default属性定义默认序列流,在其他序列流条件都不满足的情况下使用 -->
<exclusiveGateway id="auditorExist" name="审批人是否存在" default="auditorNotExistFlow"/>
<sequenceFlow sourceRef="auditorExist" targetRef="approveTask">
<!-- auditMethod是Spring里的一个bean,下面有提到 -->
<!-- execution是flowable内部变量,类型是org.flowable.engine.delegate.DelegateExecution,也就是serviceTask里的代理方法拿到的 -->
<conditionExpression xsi:type="tFormalExpression">
<![CDATA[
${auditMethod.existAuditor(execution)}
]]>
</conditionExpression>
</sequenceFlow>
    <span class="hljs-tag">&lt;<span class="hljs-name">sequenceFlow</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"auditorNotExistFlow"</span> <span class="hljs-attr">sourceRef</span>=<span class="hljs-string">"auditorExist"</span> <span class="hljs-attr">targetRef</span>=<span class="hljs-string">"agreeDelegate"</span> /&gt;</span>
<span class="hljs-comment">&lt;!-- 第三步:审批人审批 --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">userTask</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"approveTask"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"等待审批"</span>
<span class="hljs-attr">flowable:candidateGroups</span>=<span class="hljs-string">"${auditMethod.getCandidateGroups(execution)}"</span>
<span class="hljs-attr">flowable:candidateUsers</span>=<span class="hljs-string">"${auditMethod.getCandidateUsers(execution)}"</span>/&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">sequenceFlow</span> <span class="hljs-attr">sourceRef</span>=<span class="hljs-string">"approveTask"</span> <span class="hljs-attr">targetRef</span>=<span class="hljs-string">"decision"</span>/&gt;</span>
<span class="hljs-comment">&lt;!-- 唯一网关:一个审批一个审批人 --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">exclusiveGateway</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"decision"</span> <span class="hljs-attr">default</span>=<span class="hljs-string">"rejectFlow"</span>/&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">sequenceFlow</span> <span class="hljs-attr">sourceRef</span>=<span class="hljs-string">"decision"</span> <span class="hljs-attr">targetRef</span>=<span class="hljs-string">"assignToAuditor"</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">conditionExpression</span> <span class="hljs-attr">xsi:type</span>=<span class="hljs-string">"tFormalExpression"</span>&gt;</span>
&lt;![CDATA[
${auditMethod.isApproved(execution)}
]]&gt;
<span class="hljs-tag">&lt;/<span class="hljs-name">conditionExpression</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">sequenceFlow</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">sequenceFlow</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"rejectFlow"</span> <span class="hljs-attr">sourceRef</span>=<span class="hljs-string">"decision"</span> <span class="hljs-attr">targetRef</span>=<span class="hljs-string">"rejectDelegate"</span> /&gt;</span>
<span class="hljs-comment">&lt;!-- 第四步:同意后存储数据,发送通知 --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">serviceTask</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"agreeDelegate"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"数据存储"</span>
<span class="hljs-attr">flowable:class</span>=<span class="hljs-string">"me.xwbz.flowable.delegate.StandardRequestAgreeDelegate"</span>/&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">sequenceFlow</span> <span class="hljs-attr">sourceRef</span>=<span class="hljs-string">"agreeDelegate"</span> <span class="hljs-attr">targetRef</span>=<span class="hljs-string">"approveEnd"</span>/&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">serviceTask</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"rejectDelegate"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"回复拒绝消息"</span>
<span class="hljs-attr">flowable:class</span>=<span class="hljs-string">"me.xwbz.flowable.delegate.BaseRejectDelegate"</span>/&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">sequenceFlow</span> <span class="hljs-attr">sourceRef</span>=<span class="hljs-string">"rejectDelegate"</span> <span class="hljs-attr">targetRef</span>=<span class="hljs-string">"rejectEnd"</span>/&gt;</span>
<span class="hljs-comment">&lt;!-- 第五步:结束 --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">endEvent</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"approveEnd"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"已同意"</span>/&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">endEvent</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"rejectEnd"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"已驳回"</span>/&gt;</span> <span class="hljs-tag">&lt;/<span class="hljs-name">process</span>&gt;</span>
...</code></pre>

常量部分

这次没有另外存储数据,所以变量都是直接存储到flowable自带的变量表里
强烈建议大家另外存储,自带的查询起来非常麻烦!

  • 审批人列表:AUDITOR_LIST_KEY = "AUDITOR_LIST";
  • 当前审批人:AUDITOR_KEY = "AUDITOR";
  • 当前审批人下标:AUDITOR_IDX_KEY = "AUDITOR_IDX";
  • 是否已审批:APPROVED_KEY = "AUDIT_APPROVED";
  • 申请类型:AUDIT_TYPE_KEY = "AUDIT_TYPE";
  • 申请状态:AUDIT_STATUS_KEY = "AUDIT_STATUS";
  • 其他参数:AUDIT_PARAMS_KEY = "AUDIT_PARAMS";
  • 申请状态
public enum AuditStatus {
/** 待审批 */
WAIT_AUDIT,
/** 已同意申请 */
AGREE_AUDIT,
/** 已拒绝申请 */
REJECT_AUDIT,
/** 已取消 */
CANCEL
}
  • 申请人类型
public enum CandidateType{
/** 候选人 */
USER,
<span class="hljs-comment">/** 候选组 */</span>
GROUP

}

审批使用的方法定义

一个普通的Java类

package me.xwbz.flowable.method;

import com.alibaba.fastjson.JSONObject;

import org.flowable.engine.delegate.DelegateExecution; /**
  • 审批相关的方法
  • 用于flowable流程使用

    */

    public class AuditMethod { /**
    • 是否存在审批者
    • <sequenceFlow sourceRef="decision" targetRef="assignToAuditor">
    •    &lt;conditionExpression xsi:type="tFormalExpression"&gt;
    •             &lt;![CDATA[
    •                 ${auditMethod.existAuditor(execution)}
    •             ]]&gt;
    • </conditionExpression>
    • </sequenceFlow>

      */

      public boolean existAuditor(DelegateExecution execution){

      return execution.hasVariable(AUDITOR_KEY);

      }

    /**

    • 获取当前审批者

      /

      public JSONObject getCurrentAuditor(DelegateExecution execution){

      return JSONObject.parseObject((String)execution.getVariable(AUDITOR_KEY));

      }

      /
      *
    • 获取当前候选组

      */

      public String getCandidateGroups(DelegateExecution execution){

      JSONObject candidate = getCurrentAuditor(execution);

      if(candidate.getIntValue("type") == CandidateType.GROUP.ordinal()) {

      return candidate.getString("id");

      }

      return null;

      }

    public String getCandidateUsers(DelegateExecution execution){

    JSONObject candidate = getCurrentAuditor(execution);

    if(candidate.getIntValue("type") == CandidateType.USER.ordinal()) {

    return candidate.getString("id");

    }

    return null;

    }

    /**

    • 获取当前审批者id
    • <userTask id="approveTask" name="等待审批" flowable:assignee="${auditMethod.getCurrentAuditorId(execution)}" />

      */

      public String getCurrentAuditorId(DelegateExecution execution){

      JSONObject auditor = getCurrentAuditor(execution);

      return JSONObject.toJavaObject(auditor, User.class).getId();

      }

    /**

    • 是否同意申请

      */

      public boolean isApproved(DelegateExecution execution){

      Boolean approved = execution.getVariable(APPROVED_KEY, Boolean.class);

      return approved != null && approved;

      }

      }

流程结束处理

下面是同意处理,主要是更改状态。“拒绝处理”同理

可以根据业务增加消息通知,保存数据等。

package me.xwbz.flowable.delegate;

import lombok.extern.slf4j.Slf4j;

import org.flowable.engine.delegate.DelegateExecution;

import org.flowable.engine.delegate.JavaDelegate; @Slf4j

public class BaseAgreeDelegate implements JavaDelegate {
<span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">execute</span><span class="hljs-params">(DelegateExecution execution)</span> </span>{
log.info(<span class="hljs-string">"{}已被同意"</span>, execution.getVariables());
execution.setVariable(AUDIT_STATUS_KEY, AuditStatus.AGREE_AUDIT.toString());
}

}

flowable结合Spring可以直接使用Spring里的bean。

像下面这样定义,然后直接${auditMethod.isApproved(execution)}就可以调用auditMethod里的isApproved方法。

import me.xwbz.flowable.method.AuditMethod;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; @Configuration

public class FlowableConfig {

@Bean(name = "auditMethod")

public AuditMethod auditMethod(){

return new AuditMethod();

}

}

动态设置审批人

这个是配置在serviceTask里的,所以需要实现JavaDelegate接口

package me.xwbz.flowable.delegate;

import com.alibaba.fastjson.JSON;

import com.alibaba.fastjson.JSONArray;

import com.alibaba.fastjson.JSONObject;

import org.flowable.engine.delegate.DelegateExecution;

import org.flowable.engine.delegate.JavaDelegate; /**
  • delegate - 分配审批人

    */

    public class AssignToAuditorDelegate implements JavaDelegate { @Override

    public void execute(DelegateExecution execution) {

    // 初始化变量,清空临时变量

    execution.removeVariable(APPROVED_KEY);

    execution.removeVariable(AUDITOR_KEY);

    execution.setVariable(AUDIT_STATUS_KEY, AuditStatus.WAIT_AUDIT.toString());
     <span class="hljs-comment">// 拿到审批人列表</span>
    <span class="hljs-type">JSONArray</span> auditorList = <span class="hljs-type">JSON</span>.parseArray(execution.getVariable(<span class="hljs-type">AUDITOR_LIST_KEY</span>).<span class="hljs-built_in">toString</span>());
    <span class="hljs-comment">// 当前审批人在审批人列表的下标</span>
    <span class="hljs-type">Integer</span> auditorIdx = execution.getVariable(<span class="hljs-type">AUDITOR_IDX_KEY</span>, <span class="hljs-type">Integer</span>.<span class="hljs-keyword">class</span>);
    <span class="hljs-keyword">if</span> (auditorIdx == null) {
    <span class="hljs-comment">// 第一次分配,初始化为第一个</span>
    auditorIdx = <span class="hljs-number">0</span>;
    } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (auditorIdx + <span class="hljs-number">1</span> &gt;= auditorList.size()) {
    <span class="hljs-comment">// 所有审批人审批完成,结束分配</span>
    <span class="hljs-keyword">return</span>;
    } <span class="hljs-keyword">else</span> {
    <span class="hljs-comment">// 下一个</span>
    auditorIdx++;
    }
    <span class="hljs-type">JSONObject</span> auditor = auditorList.getJSONObject(auditorIdx);
    execution.setVariable(<span class="hljs-type">AUDITOR_KEY</span>, auditor.toJSONString());
    execution.setVariable(<span class="hljs-type">AUDITOR_IDX_KEY</span>, auditorIdx);

    }

    }

开始流程

使用runtimeService#startProcessInstanceByKey开始这个流程,记得开始之前要使用identityService#setAuthenticatedUserId设置当前用户编号,这个是绑定到线程的,单线程环境下设置一次就行了。

Map<String, Object> vars = new HashMap<>();
// 放入申请类型
vars.put(AUDIT_TYPE_KEY, param.getType());
// 放入审批人人列表
vars.put(AUDITOR_LIST_KEY, JSONObject.toJSONString(param.getAuditors()));
// 放入其他参数
vars.put(AUDIT_PARAMS_KEY, param.getParams());
// 放入审批状态
vars.put(AUDIT_STATUS_KEY, AuditStatus.WAIT_AUDIT.toString()); logger.debug("当前用户id: {} ", Authentication.getAuthenticatedUserId());

// 设置发起人

// identityService.setAuthenticatedUserId(user.getId());

ProcessInstance pro = runtimeService.startProcessInstanceByKey("standardRequest", 生成的编号, vars);

// 文件材料

if (param.getFiles() != null && !param.getFiles().isEmpty()) {

// 上传附件,可以直接上传字节流(不建议)

param.getFiles().forEach(file ->

taskService.createAttachment("application/octet-stream", null,

pro.getId(), file.getName(), null, file.getId()));

}

查看待我审批的任务

taskService.createTaskQuery()只能查询到正在进行的任务

要是想既能查询到正在进行的,也要结束的可以使用下面的语句:

TaskInfoQueryWrapper taskInfoQueryWrapper = runtime ? new TaskInfoQueryWrapper(taskService.createTaskQuery()) : new TaskInfoQueryWrapper(historyService.createHistoricTaskInstanceQuery());

也就是说你要先确定是要那种。

TaskQuery query = taskService.createTaskQuery()
// or() 和 endOr()就像是左括号和右括号,中间用or连接条件
// 指定是我审批的任务或者所在的组别审批的任务
// 实在太复杂的情况,建议不使用flowable的查询
.or()
.taskAssignee(user.getId())
.taskCandidateUser(user.getId())
.taskCandidateGroup(user.getGroup())
.endOr();
// 查询自定义字段
if (StringUtils.isNotEmpty(auditType)) {
query.processVariableValueEquals(AUDIT_TYPE_KEY, auditType);
}
if(auditStatus != null){
query.processVariableValueEquals(AUDIT_STATUS_KEY, auditStatus.toString());
}
// 根据创建时间倒序
query.orderByTaskCreateTime().desc()
// 分页
.listPage(0, 10)
.stream().map(t -> {
// 拿到这个任务的流程实例,用于显示流程开始时间、结束时间、业务编号
HistoricProcessInstance p = historyService.createHistoricProcessInstanceQuery()
.processInstanceId(t.getProcessInstanceId())
.singleResult();
return new Process(p).withTask(t) // 拿到任务编号和任务名称
// 拿到创建时和中途加入的自定义参数
.withVariables(taskService.getVariables(t.getId()))
.withFiles(taskService.getProcessInstanceAttachments(p.getId()));
}).collect(Collectors.toList()

查看我已审批的任务

任务审批后就走下一个序列流,这里只能从历史纪录里获取已审批的。

当前设置历史纪录(HistoryLevel)粒度为audit,这是默认的。

注意这里不能筛选自定义参数,所以要么自定义sql,要么另外存储。

// 如果不需要筛选自定义参数
if(auditStatus == null && StringUtils.isEmpty(auditType)){
return historyService.createHistoricActivityInstanceQuery()
// 我审批的
.taskAssignee(assignee)
// 按照结束时间倒序
.orderByHistoricActivityInstanceEndTime().desc()
// 已结束的(其实就是判断有没有结束时间)
.finished()
// 分页
.listPage(firstIdx, pageSize);
}
// 否则需要自定义sql
// managementService.getTableName是用来获取表名的(加上上一篇提到的liquibase,估计flowable作者对数据表命名很纠结)
// 这里从HistoricVariableInstance对应的表里找到自定义参数
// 筛选对象类型不支持二进制,存储的时候尽量使用字符串、数字、布尔值、时间,用来比较的值也有很多限制,例如null不能用like比较。
String sql = "SELECT DISTINCT RES.* " +
"FROM " + managementService.getTableName(HistoricActivityInstance.class) + " RES " +
"INNER JOIN " + managementService.getTableName(HistoricVariableInstance.class) + " var " +
"ON var.PROC_INST_ID_ = res.PROC_INST_ID_ " +
"WHERE RES.ASSIGNEE_ = #{assignee} " +
"AND RES.END_TIME_ IS NOT NULL ";
if(auditStatus != null && StringUtils.isNotEmpty(auditType)){
sql += "AND ((var.name_ = #{typeKey} AND var.TEXT_ = #{typeValue}) OR (var.name_ = #{statusKey} AND var.TEXT_ = #{statusValue}))";
} else if(auditStatus != null){
sql += "AND var.name_ = #{statusKey} AND var.TEXT_ = #{statusValue}";
} else {
sql += "AND var.name_ = #{typeKey} AND var.TEXT_ = #{typeValue}";
}
sql += " ORDER BY RES.END_TIME_ DESC";
return historyService.createNativeHistoricActivityInstanceQuery().sql(sql)
// 参数用#{assignee}占位后,再调用parameter("assignee", assignee)填入值
// 参数值可以多出来没用到的,比hibernate好多了
.parameter("assignee", assignee)
.parameter("typeKey", AUDIT_TYPE_KEY)
.parameter("typeValue", auditType)
.parameter("statusKey", AUDIT_STATUS_KEY)
.parameter("statusValue", auditStatus == null ? null : auditStatus.toString())
.listPage(firstIdx, pageSize);

后续获取详细和自定义参数

list.stream().map(a -> {
// 同上面的拿到这个任务的流程实例
HistoricProcessInstance p = historyService.createHistoricProcessInstanceQuery()
.processInstanceId(a.getProcessInstanceId())
.singleResult();
// 因为任务已结束(我看到有提到删除任务TaskHelper#completeTask),所以只能从历史里获取
Map<String, Object> params = historyService.createHistoricVariableInstanceQuery()
.processInstanceId(a.getProcessInstanceId()).list()
// 拿到的是HistoricVariableInstance对象,需要转成原来存储的方式
.stream().collect(Collectors.toMap(HistoricVariableInstance::getVariableName, HistoricVariableInstance::getValue));
<span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> Process(p).withActivity(a).withVariables(params);

}).collect(Collectors.toList())

查看我创建的任务

这个比较方便拿到,但是当前最新的任务比较难拿到,有时还不准确

// startedBy:创建任务时设置的发起人
HistoricProcessInstanceQuery instanceQuery = historyService.createHistoricProcessInstanceQuery()
.startedBy(user.getId());
// 自定义参数筛选
if (StringUtils.isNotEmpty(auditType)) {
instanceQuery.variableValueEquals(AUDIT_TYPE_KEY, auditType);
}
if(auditStatus != null){
instanceQuery.variableValueEquals(AUDIT_STATUS_KEY, auditStatus.toString());
} instanceQuery

.orderByProcessInstanceStartTime().desc()

.listPage(firstIdx, pageSize).stream()

// 获取其中的详细和自定义参数

.map(this::convertHostoryProcess)

.collect(Collectors.toList())

获取其中的详细和自定义参数

    private Process convertHostoryProcess(HistoricProcessInstance p) {
// 不管流程是否结束,到历史里查,最方便
Map<String, Object> params = historyService.createHistoricVariableInstanceQuery().processInstanceId(p.getId()).list()
.stream().collect(Collectors.toMap(HistoricVariableInstance::getVariableName, HistoricVariableInstance::getValue));
// 获取最新的一个userTask,也就是任务活动纪录
List<HistoricActivityInstance> activities = historyService.createHistoricActivityInstanceQuery()
.processInstanceId(p.getId())
.orderByHistoricActivityInstanceStartTime().desc()
.orderByHistoricActivityInstanceEndTime().asc().
listPage(0, 1);
Process data = new Process(p);
if (!activities.isEmpty()) {
data.withActivity(activities.get(0));
}
return data.withVariables(params);
}

撤销流程实例(标记删除)

撤销后,流程直接中断,除了用户不能操作和多了结束时间、删除理由外,其他停留在撤销前的状态。

ProcessInstance process = runtimeService.createProcessInstanceQuery().processInstanceId(id).singleResult();
if (process == null) {
throw new RuntimeException("该流程不在运行状态");
}
Task task = taskService.createTaskQuery().processInstanceId(id).singleResult();
runtimeService.setVariable(task.getExecutionId(), AUDIT_STATUS_KEY, AuditStatus.CANCEL.toString());
runtimeService.deleteProcessInstance(id, "用户撤销");

用户操作(同意、拒绝)

注意:如果beforeAgreeOrRejecttaskService.complete在同一个事物里,且beforeAgreeOrReject里跟AssignToAuditorDelegate有更新数据库数据,会导致事物异常。

是否有审批权限需要自己判断。

操作后进入下一序列流,再次拿这个taskId会获取不到这个Task,所以上传审批意见和附件什么的要在操作前。

if(!isAssigneeOrCandidate(user, taskId)){
throw new RuntimeException("无法操作");
}
// 同意前设置,上传审批意见和附件
beforeAgreeOrReject(user, taskId, AuditStatus.AGREE_AUDIT, "同意", reason);
// 拒绝前设置,上传审批意见和附件
// beforeAgreeOrReject(user, taskId, AuditStatus.REJECT_AUDIT, "拒绝", reason);
// 同意
taskService.complete(taskId, Collections.singletonMap(APPROVED_KEY, true));
// 拒绝
// taskService.complete(taskId, Collections.singletonMap(APPROVED_KEY, false));

判断是否有权限

public boolean isAssigneeOrCandidate(User user, String taskId){
long count = taskService.createTaskQuery()
.taskId(taskId)
.or()
.taskAssignee(user.getId())
.taskCandidateUser(user.getId())
.taskCandidateGroup(user.getGroup())
.endOr().count();
return count > 0;
}

操作前设置,上传审批意见和附件

附件一般都另外存储,这里存一个id就行了
这里是存在ftp文件服务器里的

public void beforeAgreeOrReject(User user, String taskId, AuditStatus auditStatus, String operate, ReasonParam reason){
// 组成员操作后方便查询
taskService.setAssignee(taskId, user.getId());
if(StringUtils.isNotEmpty(reason.getText())){
// 审批意见
taskService.addComment(taskId, null, null, reason.getText());
}
if (reason.getFiles() != null && !reason.getFiles().isEmpty()) {
files.forEach(file ->
// 上传附件,可以直接上传字节流(不建议)
taskService.createAttachment("application/octet-stream", taskId,
null, file.getName(), null, file.getId()));
}
}

收工

刚写博客,不太会用文字表达,欢迎大家提出意见。

以前没有用文字记事的习惯,这一篇全是代码的文章都写了两三个小时。。。

感谢阅读,欢迎点赞up~

更新日志

2018-12-17

支持审批组,遇到的坑提醒

2019-01-05

代码上传到github,github地址

同时发现文中有很多地方代码没更新,得找时间更新,

感谢@久伴1101 提出的建议

2019-01-06

修改之前错误的地方,当然建议是看看上方的github代码

原文地址:https://www.cnblogs.com/xwbz/p/9886696.html

flowable笔记 - 简单的通用流程的更多相关文章

  1. Liferay7 BPM门户开发之10: 通用流程实现从Servlet到Portlet(Part1)

    开发目的: 实现通用流程自动化处理(即实现不需要hardcode代码的bpm统一处理后台,仅需要写少量前端html form代码和拖拽设计BPM定义) 既可独立运行或可依托于Liferay或依托其它门 ...

  2. TIJ读书笔记02-控制执行流程

      TIJ读书笔记02-控制执行流程 TIJ读书笔记02-控制执行流程 if-else 迭代 无条件分支 switch语句 所有条件语句都是以条件表达式的真假来决定执行路径,也就是通过布尔测试结果来决 ...

  3. Springboot 整合Activiti流程设计器 完成一个简单的请假流程

    目录 1.前言 2.准备 3.下载解压 4.开始整合 mysql + activiti + thymeleaf 2.配置文件 3.复制文件 4.加入控制器 5.修改配置文件 6.剔除启动类里面的安全校 ...

  4. 《Effective Java》笔记45-56:通用程序设计

    将局部变量的作用域最小化,可以增强代码的可读性和可维护性,并降低出错的可能性. 要使用局部变量的作用域最小化,最有力的方法就是在第一次使用它的地方才声明,不要过早的声明. 局部变量的作用域从它被声明的 ...

  5. IIC驱动学习笔记,简单的TSC2007的IIC驱动编写,测试

    IIC驱动学习笔记,简单的TSC2007的IIC驱动编写,测试 目的不是为了编写TSC2007驱动,是为了学习IIC驱动的编写,读一下TSC2007的ADC数据进行练习,, Linux主机驱动和外设驱 ...

  6. Pyinstaller打包通用流程

    Pyinstaller打包通用流程 前言 什么是Pyinstaller Pyinstaller是用于打包python项目的一个工具, 可以将项目代码打包成可执行文件, 在其他机器上使用. 通俗的说, ...

  7. 一个简单的通用Makefile实现

    一个简单的通用Makefile实现   Makefile是Linux下程序开发的自动化编译工具,一个好的Makefile应该准确的识别编译目标与源文件的依赖关系,并且有着高效的编译效率,即每次重新ma ...

  8. 如何做个简单安卓App流程

    有同学做毕业设计,问怎样做个简单安卓App流程,我是做服务端的,也算是经常接触app,想着做app应该很简单吧,不就做个页面,会跳转,有数据不就行了,我解释了半天,人家始终没听懂,算了,我第二天问了下 ...

  9. 【javascript】Promise/A+ 规范简单实现 异步流程控制思想

    ——基于es6:Promise/A+ 规范简单实现 异步流程控制思想  前言: nodejs强大的异步处理能力使得它在服务器端大放异彩,基于它的应用不断的增加,但是异步随之带来的嵌套.难以理解的代码让 ...

随机推荐

  1. 使用JSONObject进行序列化时,避开定义get或set为开头的方法名称

    从结果中可以看到,JSONObject对Test对象进行序列化时,把fileName也当做属性了. 原因:涉及到JavaBean规范(参考:https://www.cnblogs.com/yusimi ...

  2. c++中merge的操作

    merge:将两个有序序列合并成一个新的序列,并对新的序列排序 所在库:<algorithm> 注意:排序规则必须和原序列规则相同.存储时下标从0开始. 函数参数:merge(first1 ...

  3. Python2.7用sys.stdout.write实现打印刷新

    如何能在控制台实现在一行中显示进度的信息呢,就像使用pip安装时的进度那样. 如果用print则会打印成多行,下面这个小技巧可以在一行中打印: import time import sys if __ ...

  4. ubuntu上安装notepadpp

    Notepad++是一套非常有特色的自由软件的纯文字编辑器(许可证:GPL).有完整的中文化接口及支持多国语言编写的功能(UTF8 技术).它的功能比 Windows 中的 Notepad(记事本)强 ...

  5. Effective Modern C++:07并发API

    C++11的志伟功勋之一,就是将并发融入了语言和库中,因此在C++的历史上,程序员可以首次跨越所有平台撰写具有标准行为的多线程程序. 35:优先选用基于任务而非基于线程的程序设计 如果需要以异步的方式 ...

  6. MySQL数据库操作语句(补充1)(cmd环境运行)

    一.字符串类型 enum枚举类型 /* 也叫做枚举类型,类似于单选! 如果某个字段的值只能从某几个确定的值中进行选择,一般就使用enum类型, 在定义的时候需要将该字段所有可能的选项都罗列出来: */ ...

  7. 大数据技术之Flume

    第1章 概述 1.1 Flume定义 Flume是Cloudera提供的一个高可用的,高可靠的,分布式的海量日志采集.聚合和传输的系统.Flume基于流式架构,灵活简单. 1.2 Flume组成架构 ...

  8. Mongodb停止和启动

    mongodb开启.停止.重启操作 #开启service mongodb start#停止service mongodb stop#重启service mongodb restart

  9. SDUT-3375_数据结构实验之查找三:树的种类统计

    数据结构实验之查找三:树的种类统计 Time Limit: 400 ms Memory Limit: 65536 KiB Problem Description 随着卫星成像技术的应用,自然资源研究机 ...

  10. springboot 自定义starter之AutoConfiguration【原】

    八.自定义starter AutoConfiguration: 1.这个场景需要使用到的依赖是什么? 没有特别依赖的配置 2.如何编写自动配置 @Configuration //指定这个类是一个配置类 ...