Spring Boot整合Activiti的项目中实现抄送功能

目录

1、实现思路

[2、在Spring Boot中集成Activiti](#2、在Spring Boot中集成Activiti)

2.1、设计抄送表

2.2、抄送实体类

2.3、实现抄送服务

3、前端集成

3.1、抄送组件

3.2、抄送列表页面

4、高级功能扩展

4.1、邮件通知集成

4.2、消息推送集成(WebSocket)

[4.3、 抄送规则配置](#4.3、 抄送规则配置)

[4.4、 应用配置文件](#4.4、 应用配置文件)

[4.5、 流程定义中的抄送配置](#4.5、 流程定义中的抄送配置)

5、总结


1、实现思路

在Activiti工作流中,抄送功能通常不是直接提供的,但可以通过扩展来实现。抄送功能可以理解为将某个任务的信息发送给相关人员,而不需要他们直接处理任务。在Spring Boot整合Activiti的项目中,我们可以通过以下方式实现抄送功能:

  1. 自定义抄送表:记录抄送的任务、抄送给谁、抄送时间等信息。
  2. 在任务创建或完成时触发抄送逻辑,将任务信息插入抄送表。
  3. 提供接口供用户查看自己被抄送的任务。

在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、总结

抄送功能的实现要点:

  1. 数据持久化:设计抄送表记录抄送信息

  2. 业务逻辑:提供抄送CRUD服务

  3. 流程集成:通过监听器自动触发抄送

  4. 用户通知:集成多种通知方式(站内信、邮件、推送)

  5. 权限控制:确保用户只能操作自己的抄送记录

  6. 配置灵活:支持规则引擎和动态配置

这种实现方式既保持了工作流引擎的纯净性,又通过扩展实现了业务需要的抄送功能。


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


相关推荐
初心灬2 小时前
Java 对接coze工作流
java
愿你天黑有灯下雨有伞2 小时前
实战演练:如何在Spring Boot项目中优雅地使用参数校验
spring boot
代衡_Monster2 小时前
通过位运算实现Java逻辑的包含关系
java·java-ee
毕设源码-朱学姐2 小时前
【开题答辩全过程】以 基于Java的失物招领系统设计与实现为例,包含答辩的问题和答案
java·开发语言
清晓粼溪2 小时前
统一异常处理
java·开发语言
TH_13 小时前
4、前台界面,表格列名写错
java
Victor3563 小时前
Netty(7)如何实现基于Netty的TCP客户端和服务器?
后端
Victor3563 小时前
Netty(8)什么是Netty的ChannelPipeline和ChannelHandler?
后端
没有bug.的程序员3 小时前
高频IO服务优化实战指南
java·jvm·spring·容器