目录
1、实现思路
2、在Spring Boot中集成Activiti
2.1、设计抄送表
2.2、抄送实体类
2.3、实现抄送服务
3、前端集成
3.1、抄送组件
3.2、抄送列表页面
4、高级功能扩展
4.1、邮件通知集成
4.2、消息推送集成(WebSocket)
4.3、 抄送规则配置
4.4、 应用配置文件
4.5、 流程定义中的抄送配置
5、总结
1、实现思路
在Activiti工作流中,抄送功能通常不是直接提供的,但可以通过扩展来实现。抄送功能可以理解为将某个任务的信息发送给相关人员,而不需要他们直接处理任务。在Spring Boot整合Activiti的项目中,我们可以通过以下方式实现抄送功能:
- 自定义抄送表:记录抄送的任务、抄送给谁、抄送时间等信息。
- 在任务创建或完成时触发抄送逻辑,将任务信息插入抄送表。
- 提供接口供用户查看自己被抄送的任务。
在Spring Boot项目中整合Activiti工作流的抄送功能,可以通过以下方案实现:
2、在Spring Boot中集成Activiti
在pom.xml中添加Activiti依赖:
<dependency> <groupId>org.activiti</groupId> <artifactId>activiti-spring-boot-starter</artifactId> <version>7.1.0.M6</version> </dependency>2.1、设计抄送表
创建抄送表,表结构如下:
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、抄送实体类
@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、实现抄送服务
创建一个抄送服务,用于处理抄送逻辑。例如,在任务完成时,将任务抄送给指定人员。
服务接口:
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); }服务实现:
在任务完成时触发抄送,可以在任务完成事件监听器中触发抄送。例如,创建一个任务监听器
@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文件中配置,也可以通过代码配置。
控制器层:
@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、抄送组件
<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、抄送列表页面
<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、邮件通知集成
@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)
@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、 抄送规则配置
@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、 应用配置文件
# application.yml activiti: cc: enabled: true # 是否启用邮件通知 email-notify: true # 是否启用站内信 message-notify: true # 默认抄送人(角色) default-cc-roles: ROLE_DEPT_MANAGER,ROLE_ADMIN4.5、 流程定义中的抄送配置
<!-- 在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服务
流程集成:通过监听器自动触发抄送
用户通知:集成多种通知方式(站内信、邮件、推送)
权限控制:确保用户只能操作自己的抄送记录
配置灵活:支持规则引擎和动态配置
这种实现方式既保持了工作流引擎的纯净性,又通过扩展实现了业务需要的抄送功能。
“人的一生会经历很多痛苦,但回头想想,都是传奇”。