目录
[2、在Spring Boot中集成Activiti](#2、在Spring Boot中集成Activiti)
[4.3、 抄送规则配置](#4.3、 抄送规则配置)
[4.4、 应用配置文件](#4.4、 应用配置文件)
[4.5、 流程定义中的抄送配置](#4.5、 流程定义中的抄送配置)
1、实现思路
在Activiti工作流中,抄送功能通常不是直接提供的,但可以通过扩展来实现。抄送功能可以理解为将某个任务的信息发送给相关人员,而不需要他们直接处理任务。在Spring Boot整合Activiti的项目中,我们可以通过以下方式实现抄送功能:
- 自定义抄送表:记录抄送的任务、抄送给谁、抄送时间等信息。
- 在任务创建或完成时触发抄送逻辑,将任务信息插入抄送表。
- 提供接口供用户查看自己被抄送的任务。
在Spring Boot项目中整合Activiti工作流的抄送功能,可以通过以下方案实现:
2、在Spring Boot中集成Activiti
在pom.xml中添加Activiti依赖:
XML
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-spring-boot-starter</artifactId>
<version>7.1.0.M6</version>
</dependency>
2.1、设计抄送表
创建抄送表,表结构如下:
sql
CREATE TABLE `act_cc_task` (
`id` varchar(64) NOT NULL COMMENT '主键',
`task_id` varchar(64) DEFAULT NULL COMMENT '任务ID',
`proc_inst_id` varchar(64) DEFAULT NULL COMMENT '流程实例ID',
`proc_def_id` varchar(64) DEFAULT NULL COMMENT '流程定义ID',
`title` varchar(255) DEFAULT NULL COMMENT '抄送标题',
`content` text COMMENT '抄送内容',
`from_user_id` varchar(64) DEFAULT NULL COMMENT '发起人',
`to_user_ids` text COMMENT '接收人(多个用逗号分隔)',
`status` int(1) DEFAULT '0' COMMENT '状态(0:未读,1:已读)',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`read_time` datetime DEFAULT NULL COMMENT '读取时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
2.2、抄送实体类
java
@Data
@TableName("act_cc_task")
public class CcTask {
@TableId(type = IdType.ASSIGN_ID)
private String id;
private String taskId;
private String procInstId;
private String procDefId;
private String title;
private String content;
private String fromUserId;
private String toUserIds; // 多个用户用逗号分隔
private Integer status;
private Date createTime;
private Date readTime;
@TableField(exist = false)
private List<String> toUserList; // 接收人列表
}
2.3、实现抄送服务
创建一个抄送服务,用于处理抄送逻辑。例如,在任务完成时,将任务抄送给指定人员。
服务接口:
java
public interface CcTaskService {
/**
* 创建抄送任务
*/
void createCcTask(String taskId, List<String> toUserIds,
String title, String content);
/**
* 获取用户的抄送列表
*/
PageInfo<CcTask> getUserCcTasks(String userId, Integer pageNum,
Integer pageSize, Integer status);
/**
* 标记为已读
*/
void markAsRead(String ccTaskId, String userId);
/**
* 批量抄送
*/
void batchCcTask(List<String> taskIds, List<String> toUserIds,
String reason);
}
服务实现:
在任务完成时触发抄送,可以在任务完成事件监听器中触发抄送。例如,创建一个任务监听器
java
@Service
@Slf4j
public class CcTaskServiceImpl implements CcTaskService {
@Autowired
private CcTaskMapper ccTaskMapper;
@Autowired
private TaskService taskService;
@Autowired
private RuntimeService runtimeService;
@Override
@Transactional
public void createCcTask(String taskId, List<String> toUserIds,
String title, String content) {
Task task = taskService.createTaskQuery()
.taskId(taskId)
.singleResult();
if (task == null) {
throw new BusinessException("任务不存在");
}
// 获取当前登录用户
String currentUserId = SecurityUtils.getCurrentUserId();
CcTask ccTask = new CcTask();
ccTask.setId(IdUtil.fastSimpleUUID());
ccTask.setTaskId(taskId);
ccTask.setProcInstId(task.getProcessInstanceId());
ccTask.setProcDefId(task.getProcessDefinitionId());
ccTask.setTitle(title);
ccTask.setContent(content);
ccTask.setFromUserId(currentUserId);
ccTask.setToUserIds(StringUtils.join(toUserIds, ","));
ccTask.setStatus(0);
ccTask.setCreateTime(new Date());
ccTaskMapper.insert(ccTask);
// 发送通知(可集成消息推送)
sendNotification(ccTask);
}
/**
* 使用监听器自动抄送
*/
@Component
public static class TaskCcListener implements TaskListener {
@Autowired
private CcTaskService ccTaskService;
@Override
public void notify(DelegateTask delegateTask) {
String eventName = delegateTask.getEventName();
// 在任务创建时自动抄送
if ("create".equals(eventName)) {
Object ccUsers = delegateTask.getVariable("ccUsers");
if (ccUsers != null) {
List<String> userIds = (List<String>) ccUsers;
if (!CollectionUtils.isEmpty(userIds)) {
String taskName = delegateTask.getName();
String content = String.format("任务【%s】需要您知晓", taskName);
ccTaskService.createCcTask(
delegateTask.getId(),
userIds,
taskName,
content
);
}
}
}
}
}
}
然后在流程定义中,在任务完成事件上绑定这个监听器。可以在BPMN文件中配置,也可以通过代码配置。
控制器层:
java
@RestController
@RequestMapping("/api/cc")
@Api(tags = "抄送管理")
public class CcTaskController {
@Autowired
private CcTaskService ccTaskService;
@PostMapping("/create")
@ApiOperation("创建抄送")
public Result createCcTask(@RequestBody CcTaskCreateDTO dto) {
ccTaskService.createCcTask(
dto.getTaskId(),
dto.getToUserIds(),
dto.getTitle(),
dto.getContent()
);
return Result.success();
}
@GetMapping("/list")
@ApiOperation("获取抄送列表")
public Result<PageInfo<CcTask>> getCcList(
@RequestParam(defaultValue = "1") Integer pageNum,
@RequestParam(defaultValue = "10") Integer pageSize,
@RequestParam(required = false) Integer status) {
String userId = SecurityUtils.getCurrentUserId();
PageInfo<CcTask> result = ccTaskService.getUserCcTasks(
userId, pageNum, pageSize, status
);
return Result.success(result);
}
@PostMapping("/read/{id}")
@ApiOperation("标记已读")
public Result markAsRead(@PathVariable String id) {
String userId = SecurityUtils.getCurrentUserId();
ccTaskService.markAsRead(id, userId);
return Result.success();
}
}
3、前端集成
3.1、抄送组件
javascript
<template>
<div class="cc-task-container">
<!-- 抄送按钮 -->
<el-button type="text" @click="showCcDialog">
<i class="el-icon-s-promotion"></i> 抄送
</el-button>
<!-- 抄送对话框 -->
<el-dialog title="任务抄送" :visible.sync="ccDialogVisible">
<el-form :model="ccForm">
<el-form-item label="接收人">
<el-select
v-model="ccForm.userIds"
multiple
filterable
placeholder="请选择接收人">
<el-option
v-for="user in userList"
:key="user.id"
:label="user.name"
:value="user.id">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="抄送说明">
<el-input
type="textarea"
v-model="ccForm.content"
placeholder="请输入抄送说明"
rows="4">
</el-input>
</el-form-item>
</el-form>
<div slot="footer">
<el-button @click="ccDialogVisible = false">取消</el-button>
<el-button type="primary" @click="submitCc">确定</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
export default {
props: {
taskId: String,
taskName: String
},
data() {
return {
ccDialogVisible: false,
userList: [],
ccForm: {
userIds: [],
content: ''
}
}
},
methods: {
showCcDialog() {
this.ccDialogVisible = true
this.loadUserList()
},
async loadUserList() {
const res = await this.$api.user.getUserList()
this.userList = res.data
},
async submitCc() {
const params = {
taskId: this.taskId,
toUserIds: this.ccForm.userIds,
title: `任务【${this.taskName}】抄送`,
content: this.ccForm.content
}
await this.$api.cc.create(params)
this.$message.success('抄送成功')
this.ccDialogVisible = false
this.$emit('cc-success')
}
}
}
</script>
3.2、抄送列表页面
javascript
<template>
<div class="cc-task-list">
<el-tabs v-model="activeStatus" @tab-click="handleTabClick">
<el-tab-pane label="未读" name="0"></el-tab-pane>
<el-tab-pane label="已读" name="1"></el-tab-pane>
<el-tab-pane label="全部" name=""></el-tab-pane>
</el-tabs>
<el-table :data="ccList" v-loading="loading">
<el-table-column prop="title" label="标题" width="200">
<template slot-scope="{row}">
<span :class="{'unread': row.status === 0}">
{{ row.title }}
</span>
</template>
</el-table-column>
<el-table-column prop="content" label="内容"></el-table-column>
<el-table-column prop="fromUserName" label="发起人" width="100">
</el-table-column>
<el-table-column prop="createTime" label="时间" width="180">
<template slot-scope="{row}">
{{ formatDate(row.createTime) }}
</template>
</el-table-column>
<el-table-column label="操作" width="120">
<template slot-scope="{row}">
<el-button
v-if="row.status === 0"
type="text"
@click="markAsRead(row.id)">
标记已读
</el-button>
<el-button
type="text"
@click="viewProcess(row.procInstId)">
查看流程
</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="pagination.pageNum"
:page-sizes="[10, 20, 50, 100]"
:page-size="pagination.pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="pagination.total">
</el-pagination>
</div>
</template>
前端可以调用上述接口展示抄送列表,并允许用户标记已读。
注意事项
-
抄送功能可以根据实际需求进行扩展,例如增加抄送类型(任务创建时抄送、任务完成时抄送等)。
-
抄送人员可以从任务变量、流程变量、固定配置或者从用户选择中获取。
-
抄送任务可能不需要处理,但需要记录,因此抄送表的设计可以根据业务需求调整。
4、高级功能扩展
4.1、邮件通知集成
java
@Component
public class EmailCcNotifier {
@Autowired
private JavaMailSender mailSender;
@Value("${spring.mail.username}")
private String from;
public void sendCcNotification(CcTask ccTask, User user) {
SimpleMailMessage message = new SimpleMailMessage();
message.setFrom(from);
message.setTo(user.getEmail());
message.setSubject("工作流抄送通知:" + ccTask.getTitle());
message.setText(buildEmailContent(ccTask));
mailSender.send(message);
}
private String buildEmailContent(CcTask ccTask) {
return String.format(
"您收到一条工作流抄送:\n" +
"标题:%s\n" +
"内容:%s\n" +
"发起人:%s\n" +
"时间:%s\n" +
"请登录系统查看详情。",
ccTask.getTitle(),
ccTask.getContent(),
ccTask.getFromUserId(),
DateUtil.formatDateTime(ccTask.getCreateTime())
);
}
}
4.2、消息推送集成(WebSocket)
java
@ServerEndpoint("/websocket/cc")
@Component
public class CcWebSocket {
private static Map<String, Session> sessions = new ConcurrentHashMap<>();
@OnOpen
public void onOpen(Session session) {
String userId = getUserIdFromSession(session);
sessions.put(userId, session);
}
/**
* 向指定用户推送抄送消息
*/
public static void sendCcMessage(String userId, CcTask ccTask) {
Session session = sessions.get(userId);
if (session != null && session.isOpen()) {
try {
String message = JSON.toJSONString(
Map.of("type", "cc", "data", ccTask)
);
session.getBasicRemote().sendText(message);
} catch (IOException e) {
log.error("发送WebSocket消息失败", e);
}
}
}
}
4.3、 抄送规则配置
java
@Component
public class CcRuleEngine {
/**
* 根据规则自动抄送
* 规则示例:
* 1. 特定节点自动抄送
* 2. 金额大于阈值抄送
* 3. 特定部门任务抄送
*/
public List<String> getCcUsersByRule(
String processDefinitionId,
String taskDefinitionKey,
Map<String, Object> variables) {
List<String> userIds = new ArrayList<>();
// 示例:根据任务节点配置抄送
if ("approve".equals(taskDefinitionKey)) {
// 审批节点抄送给部门经理
String deptId = (String) variables.get("deptId");
userIds.addAll(getDeptManagers(deptId));
}
// 示例:根据金额阈值抄送
Double amount = (Double) variables.get("amount");
if (amount != null && amount > 10000) {
userIds.addAll(getFinanceUsers());
}
return userIds.stream().distinct().collect(Collectors.toList());
}
}
4.4、 应用配置文件
XML
# application.yml
activiti:
cc:
enabled: true
# 是否启用邮件通知
email-notify: true
# 是否启用站内信
message-notify: true
# 默认抄送人(角色)
default-cc-roles: ROLE_DEPT_MANAGER,ROLE_ADMIN
4.5、 流程定义中的抄送配置
XML
<!-- 在BPMN文件中添加抄送配置 -->
<userTask id="approveTask" name="审批任务">
<extensionElements>
<activiti:taskListener event="create"
class="com.xxx.listener.AutoCcTaskListener"/>
<activiti:formProperty id="ccUsers"
name="抄送人" type="users"/>
</extensionElements>
</userTask>
5、总结
抄送功能的实现要点:
-
数据持久化:设计抄送表记录抄送信息
-
业务逻辑:提供抄送CRUD服务
-
流程集成:通过监听器自动触发抄送
-
用户通知:集成多种通知方式(站内信、邮件、推送)
-
权限控制:确保用户只能操作自己的抄送记录
-
配置灵活:支持规则引擎和动态配置
这种实现方式既保持了工作流引擎的纯净性,又通过扩展实现了业务需要的抄送功能。

"人的一生会经历很多痛苦,但回头想想,都是传奇"。