搞定高效可扩展的OA系统,看这一篇就够了!(下:系统消息 & 审批消息发送))

1. 系统消息 & 审批消息发送

SystemMessageService类主要负责管理系统消息,包括创建、更新和查询等功能。此服务使用Spring的@Service注解进行标识,并通过@Transactional注解确保方法执行的事务性。

关键方法和功能

1. 创建系统消息
  • 功能描述: 创建系统消息,并向指定的用户集合发送。
  • 输入参数:
    • SystemMessageRO systemMessageRO:包含消息相关信息的数据传输对象。
  • 处理流程:
    • 验证输入参数的合法性,确保发送者和接收者信息完整。
    • 检查接收者用户名是否在数据库中存在,若不存在或为空则返回错误。
    • 在数据库中创建消息实体,并为每个接收者生成对应的消息实体,然后批量保存。
  • 返回值:
    • boolean: 成功返回true,失败返回false。
2. 根据关联ID更新或新增消息
  • 功能描述: 根据消息的关联ID和类型判断是否需要新增或更新系统消息。
  • 输入参数:
    • SystemMessageRO systemMessageRO:消息数据传输对象。
  • 处理流程:
    • 完整性验证输入参数,查询数据库判断消息是否已存在。
    • 根据查询结果决定是创建新消息还是更新现有消息。
  • 返回值:
    • Long: 成功操作返回消息ID,失败返回null。
3. 更新或新增审批相关系统消息
  • 功能描述: 根据审批记录更新或新增与审批相关的系统消息。
  • 输入参数:
    • ApprovalRecordPO approvalRecordPO:审批记录实体。
    • Set<String> usernameSet:消息接收用户的用户名集合。
    • OfficeAutomationHandlerType officeAutomationHandlerType:办公自动化处理类型。
  • 处理流程:
    • 验证审批记录的有效性和状态,构造系统消息并调用更新或新增方法处理。
  • 返回值:
    • Long: 成功时返回系统消息ID,失败时返回null。
4. 分页查询系统消息
  • 功能描述: 按照给定的分页信息和筛选条件查询系统消息,并返回视图对象。
  • 输入参数:
    • PageRO<SystemMessageRO> systemMessageROPageRO:包含分页和筛选信息的请求对象。
  • 处理流程:
    • 执行查询,将结果转换为视图对象(VO)。
  • 返回值:
    • PageVO<SystemMessageVO>: 包含分页结果的视图对象。

最主要是需要了解如何去实现对不同的人群进行消息发送。

我们把消息的类型分为两种,第一种是比较泛泛的系统消息,第二种是具体的审批消息。我们主要讲解第二类,因为第二类针对第一类增加了单阶段的人员批量插入和默认全体审批人都去通知 以及 修改和新建消息 都统一设置在一个saveOrUpdateApprovalMessage方法内。

只要了解了第二类的实现逻辑,搞懂第一种的实现逻辑就很简单了。

先po上代码~

其实大家可以看到我写代码的一个思路,往往暴露在service层的一个public的方法并不是直接嵌入业务逻辑,而是做一些参数认证。而在内部的return部分,调用具体的业务逻辑函数,由这个函数完成。

实际上,调用的核心业务方法就是saveOrUpdateApprovalMessage 以及return saveOrUpdateBySystemRelatedId(systemMessageRO);此处!

    /**
     * 新增或更新审批信息
     * <p>当usernameSet为空时,默认发送给所有审批人</p>
     * <p>底层调用 {@link SystemMessageService#saveOrUpdateBySystemRelatedId(SystemMessageRO)}</p>
     *
     * @param approvalRecordPO            审批记录
     * @param usernameSet                 接收人群username
     * @param officeAutomationHandlerType {@link OfficeAutomationHandlerType}
     * @return 系统消息id
     */
    public Long saveOrUpdateApprovalMessage(ApprovalRecordPO approvalRecordPO, Set<String> usernameSet, OfficeAutomationHandlerType officeAutomationHandlerType) {
        if (Objects.isNull(approvalRecordPO)
                || Objects.isNull(approvalRecordPO.getId())
                || Objects.isNull(approvalRecordPO.getStatus())) {
            log.info("审批记录信息校验失败 {}", approvalRecordPO);
            return null;
        }
        if (CollUtil.isEmpty(usernameSet)) {
            // 拉取所有步骤的审核人,然后都通知一次
            List<ApprovalStepRecordPO> approvalStepRecordPOS = approvalStepRecordService.selectByApprovalRecordId(approvalRecordPO.getId());
            if (CollUtil.isEmpty(approvalStepRecordPOS)) {
                log.info("获取审批步骤记录失败");
                return null;
            }
            usernameSet = approvalStepRecordPOS.stream()
                    .map(ApprovalStepRecordPO::getApprovalUsernameSet)
                    .filter(CollUtil::isNotEmpty)
                    .flatMap(Collection::stream)
                    .collect(Collectors.toSet());
        }
        // 发送消息
        SystemMessageRO systemMessageRO = SystemMessageRO.builder()
                .systemMessageType1(SystemMessageType1Enum.TRANSACTION_APPROVAL.getTypeName())
                .systemMessageType2(SystemMessageType2Enum.match(officeAutomationHandlerType).getTypeName())
                .senderUsername(approvalRecordPO.getInitiatorUsername())
                .systemRelatedId(approvalRecordPO.getId())
                .receiverUsernameSet(usernameSet)
                .build();
        if (SUCCESS.getStatus().equals(approvalRecordPO.getStatus())) {
            // 发送成功消息
            systemMessageRO.setMessageStatus(SystemMessageStatus.SUCCESS.getDescription());
        } else if (FAILED.getStatus().equals(approvalRecordPO.getStatus())) {
            systemMessageRO.setMessageStatus(SystemMessageStatus.FAILED.getDescription());
        } else if (WAITING.getStatus().equals(approvalRecordPO.getStatus())) {
            systemMessageRO.setMessageStatus(SystemMessageStatus.WAITING.getDescription());
        }
        return saveOrUpdateBySystemRelatedId(systemMessageRO);
    }


    /**
     * 新增或更新审批信息
     * <p>当usernameSet为空时,默认发送给所有审批人</p>
     * <p>底层调用 {@link SystemMessageService#saveOrUpdateBySystemRelatedId(SystemMessageRO)}</p>
     *
     * @param approvalRecordPO            审批记录
     * @param usernameSet                 接收人群username
     * @param officeAutomationHandlerType {@link OfficeAutomationHandlerType}
     * @return 系统消息id
     */
    public Long saveOrUpdateApprovalMessage(ApprovalRecordPO approvalRecordPO, Set<String> usernameSet, OfficeAutomationHandlerType officeAutomationHandlerType) {
        if (Objects.isNull(approvalRecordPO)
                || Objects.isNull(approvalRecordPO.getId())
                || Objects.isNull(approvalRecordPO.getStatus())) {
            log.info("审批记录信息校验失败 {}", approvalRecordPO);
            return null;
        }
        if (CollUtil.isEmpty(usernameSet)) {
            // 拉取所有步骤的审核人,然后都通知一次
            List<ApprovalStepRecordPO> approvalStepRecordPOS = approvalStepRecordService.selectByApprovalRecordId(approvalRecordPO.getId());
            if (CollUtil.isEmpty(approvalStepRecordPOS)) {
                log.info("获取审批步骤记录失败");
                return null;
            }
            usernameSet = approvalStepRecordPOS.stream()
                    .map(ApprovalStepRecordPO::getApprovalUsernameSet)
                    .filter(CollUtil::isNotEmpty)
                    .flatMap(Collection::stream)
                    .collect(Collectors.toSet());
        }
        // 发送消息
        SystemMessageRO systemMessageRO = SystemMessageRO.builder()
                .systemMessageType1(SystemMessageType1Enum.TRANSACTION_APPROVAL.getTypeName())
                .systemMessageType2(SystemMessageType2Enum.match(officeAutomationHandlerType).getTypeName())
                .senderUsername(approvalRecordPO.getInitiatorUsername())
                .systemRelatedId(approvalRecordPO.getId())
                .receiverUsernameSet(usernameSet)
                .build();
        if (SUCCESS.getStatus().equals(approvalRecordPO.getStatus())) {
            // 发送成功消息
            systemMessageRO.setMessageStatus(SystemMessageStatus.SUCCESS.getDescription());
        } else if (FAILED.getStatus().equals(approvalRecordPO.getStatus())) {
            systemMessageRO.setMessageStatus(SystemMessageStatus.FAILED.getDescription());
        } else if (WAITING.getStatus().equals(approvalRecordPO.getStatus())) {
            systemMessageRO.setMessageStatus(SystemMessageStatus.WAITING.getDescription());
        }
        return saveOrUpdateBySystemRelatedId(systemMessageRO);
    }

系统如何发送消息的逻辑非常明确,并针对不同的人群进行了优化处

步骤 1: 确定消息接收者

首先,方法检查usernameSet(即消息接收者的用户名集合)是否为空。如果为空,这意味着没有指定具体的消息接收者。在这种情况下,代码会从数据库中查询当前审批记录相关的所有审批步骤记录(ApprovalStepRecordPO),通过的是使用当前的步骤的id数值。

每个ApprovalStepRecordPO对象包含了该审批步骤的审核人用户名集合。

如果从数据库中成功拉取到相关的审批步骤记录,那么代码会从每个记录中提取出审核人的用户名集合,依次添加进入Set集合,并将它们合并成一个不重复的用户名集合(使用Set来存储以保证唯一性)。这个过程通过流操作完成,其中使用flatMap方法将多个集合“平铺”成一个流,然后收集成一个Set

如果拉取审批步骤记录失败,则记录日志信息,并返回null,表示消息发送过程无法继续。

 List<ApprovalStepRecordPO> approvalStepRecordPOS = approvalStepRecordService.selectByApprovalRecordId(approvalRecordPO.getId());
            if (CollUtil.isEmpty(approvalStepRecordPOS)) {
                log.info("获取审批步骤记录失败");
                return null;
            }
            usernameSet = approvalStepRecordPOS.stream()
                    .map(ApprovalStepRecordPO::getApprovalUsernameSet)
                    .filter(CollUtil::isNotEmpty)
                    .flatMap(Collection::stream)
                    .collect(Collectors.toSet());
步骤 2: 构造消息对象

一旦确定了消息接收者,接下来会构建一个SystemMessageRO对象,这是准备发送的消息的表示形式。这个对象通过构建器模式创建,并填充以下信息:

  • systemMessageType1systemMessageType2:这些字段代表消息类型,当前的一级标题类型 以及 二级的标题。
  • senderUsername:消息的发送者,这里使用了审批记录的发起人用户名。
  • systemRelatedId:与系统消息相关联的ID,通常是审批记录的ID。
  • receiverUsernameSet:接收消息的用户的用户名集合。
步骤 3: 发送消息

消息对象构建完成后,该对象会被传递到负责发送消息的系统组件。通常这一步会涉及到调用消息服务的API或将消息推送到消息队列中,具体取决于系统的架构和设计。消息服务将负责实际的消息发送逻辑,包括邮件发送、短信通知或应用内通知等。

优势

这种消息发送策略的优势在于它的灵活性和自动化程度。通过在用户未明确指定接收者的情况下自动查询相关的审批步骤记录并提取审核人信息,系统能够确保所有相关人员都能及时收到必要的通知,从而提高了审批流程的透明度和效率。

此外,通过利用枚举和构建器模式,代码保持了清晰和易于维护,同时也便于未来的扩展和修改。
一个完整的流程图是这样的——
在这里插入图片描述

追点进入看看

    /**
     * 新增或更新系统消息
     * <p>根据systemType1、systemType2、systemRelateId来判断消息是否存在</p>
     *
     * @param systemMessageRO
     * @return
     */
    public Long saveOrUpdateBySystemRelatedId(SystemMessageRO systemMessageRO) {
        if (Objects.isNull(systemMessageRO)
                || Objects.isNull(systemMessageRO.getSystemRelatedId())
                || Objects.isNull(systemMessageRO.getSystemMessageType1())
                || Objects.isNull(systemMessageRO.getSystemMessageType2())) {
            log.warn("系统消息部分为空 {}", systemMessageRO);
            return null;
        }
        SystemMessagePO po = systemMessageInverter.ro2PO(systemMessageRO);
        Date date = new Date();
        po.setCreatedAt(date);
        po.setUpdatedAt(date);
        SystemMessagePO systemMessagePO = baseMapper.selectOne(Wrappers.<SystemMessagePO>lambdaQuery()
                .eq(SystemMessagePO::getSystemRelatedId, systemMessageRO.getSystemRelatedId())
                .eq(SystemMessagePO::getSystemMessageType1, systemMessageRO.getSystemMessageType1())
                .eq(SystemMessagePO::getSystemMessageType2, systemMessageRO.getSystemMessageType2()));
        if (Objects.isNull(systemMessagePO)) {
            Long messageId = createSystemMessage(systemMessageRO);
            if (Objects.isNull(messageId)) {
                return null;
            }
            return messageId;
        } else {
            systemMessageRO.setId(systemMessagePO.getId());
            Boolean updated = updateById(systemMessageRO);
            if (updated) {
                return systemMessageRO.getId();
            }
        }
        return null;
    }

存入PO之后,将回复消息已经完成正常插入的结果。至此,需要审批的人点开收件箱的时候,调用getSystemMessagesByPage分页查询目前所有的系统消息(在RO内部封装的是审批者个人的用户id)。以本人的审批者的身份去查询消息。

最后,我们可以梳理一下一条消息的完整流向
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值