策略模式在行动:搞定高效可扩展的OA系统,看这一篇就够了!(中)

1. 数据库设计

在这里插入图片描述

首先我们来看看审批数据是如何存储的。在办公自动化系统中,审批流程的管理是至关重要的部分,其核心功能通过几张特定的数据库表来实现。这些表包括approval_recordapproval_stepapproval_step_recordapproval_type

如何交互的呢?

1. approval_record(审批记录)

此表用于存储整个审批流程的记录,包括每个审批的基本信息和当前状态。

这张表是审批流程的核心,记录了每一个发起的审批事务。它通过current_step_id字段与approval_step表发生直接关联,指向当前审批事务所处的具体步骤。这个字段的更新反映了审批流程的推进,每完成一个步骤后,current_step_id会更新为下一个步骤的ID,从而驱动整个审批流程向前发展。
其字段包括:
在这里插入图片描述

  • id:主键ID,唯一标识一个审批记录。
  • approval_type_id:关联的审批类型ID,指明当前审批属于哪一类审批。
  • initiator_username:发起人的用户名,记录谁启动了这个审批流程。
  • created_at:审批发起的时间。
  • update_at:记录的最后一次更新时间。
  • current_step_id:当前所处的审批步骤ID。
  • status:当前审批的状态,可能是waiting(正在审批)、success(成功)、failed(失败)。
  • watch_username_set:用户查看集合,采用JSON格式存储可查看此审批记录的用户列表。
  • document_id:关联的文档ID,如果审批过程中涉及文档处理。
  • attachment_id_set:附件ID集合,以JSON格式存储与审批相关的所有附件。

2. approval_step(审批步骤)

定义审批流程中的每一步骤。approval_step表定义了审批流程中的各个固定步骤。这些步骤记录是静态的,一旦定义,就不会发生改变,确保审批流程的标准化和一致性。每个步骤通过approval_type_id与approval_type表关联,表明该步骤属于哪一类审批类型。step_order字段用于标识审批流程中的步骤顺序字段说明如下:

  • id:主键ID,唯一标识一个步骤。
  • step_order:步骤顺序,定义步骤在审批流程中的执行顺序。
  • logic:审核逻辑,0代表“与”逻辑,1代表“或”逻辑,决定这一步的通过条件。
  • description:步骤描述,详细说明步骤的内容和要求。
  • approval_type_id:该步骤所属的审批类型ID,表明这个步骤属于哪种类型的审批。
    在这里插入图片描述

3. approval_step_record(审批步骤记录)

记录审批流程中每个步骤的具体执行情况。这张表记录了审批流程中每个步骤的具体执行情况。对于同一个approval_record,可能会有多个approval_step_record条目,每个条目记录了一次具体的审批操作。这些记录详细追踪了谁在何时对审批事务做出了哪些处理,包括审批结果、审批意见等。通过approval_username_set字段,以JSON格式存储有权审批当前记录的用户列表,这不仅保证了审批权限的正确性,还帮助前端系统正确显示相关用户的操作权限。字段包括:

  • id:主键ID。
  • approval_record_id:关联的审批记录ID,指明这个步骤记录属于哪个审批流程。
  • step_id:具体步骤ID,表示这条记录是对哪个步骤的执行情况的记录。
  • username:执行审批的用户的用户名。
  • update_at:记录更新时间。
  • logic:审批逻辑,与approval_step表中的逻辑相对应。
  • comment:审批意见,记录审批者的具体意见或反馈。
  • next_step_id:下一步步骤ID,指向流程中的下一个步骤。
  • status:步骤的审批状态,如success(成功)、failed(失败)、waiting(等待中)。
  • approval_type_id:审批类型ID,说明这个步骤属于哪种类型的审批。
  • approval_username_set:用户审批集合,以JSON格式记录参与此步骤审批的所有用户。
  • attachment_id_set:附件ID集合,存储相关的所有附件。
    在这里插入图片描述

4. approval_type(审批类型)

分类和定义不同的审批类型。approval_type表定义了系统中所有可用的审批类型。字段如下:

  • id:主键ID。
  • name:审批类型的名称。
  • description:对审批类型的详细描述。
    在这里插入图片描述

2. 一个请求是怎么发起的呢?

2.1 getHandler的动态匹配

以插入一条OA表单为例子,使用apifox来发起一条请求。
在上面的表内,发现合法的type_id只有1~9号,也就是一共只有9类OA类型。
在此之前,先输入请求一个非法的ID,比如10号:
在这里插入图片描述
可以预料到,返回肯定不存在对应的OA类型。
在这里插入图片描述
OK,那现在输入一个正确的id数值,比如说id == 5,对应的是校外转专业的类型。
在这里插入图片描述
debug进入OfficeAutomationService#getHandler(Long typeId)之内,通过typeId在枚举类内部匹配到当前的操作类型
在这里插入图片描述在这里插入图片描述
由于笔者当前分支的枚举类并没有完全合并其余的分支的字段,仅供参考,正确的情况应该刚好是9个字段。
在这里插入图片描述
但没有关系,在match函数内开始进行匹配,在上篇文章内,我们已经讨论过,在match内先通过id在数据库中查询到对应的OA审批类型,和枚举类的字段进行匹配。
在这里插入图片描述
匹配成功,返回value给上一层的getHandler方法。通过当前的这个handler完成数据插入。又因为OfficeAutomationHandler是一个抽象类,子类重写抽象类的方法,通过动态绑定的方式会调用子类重写的方法,从而实现了策略模式匹配不同的OA审批类型。
在这里插入图片描述

在这里插入图片描述
那么其余的方法也是完全一样的,都是先传入typeId然后去数据库进行查询,并且之后和枚举的字段进行匹配,如果成功匹配,就调用这个方法查询到具体的handler,调用当前的handler完成特定的数据操作。

2.2 MongoDB表单管理

​表单管理是一个核心功能,它支持创建、更新、查询和删除表单的操作。用户填报者在前端输入表单并且提交,这个表单在整个OA审批流程中不断流转。这一过程需要对MongoDB的集合进行操作。

插入表单

当需要在OA系统中创建一个新的审批表单时,首先需要定义一个insertDocument方法。这个方法接受一个键值对集合Map<String, Object>作为表单数据,以及一个typeId表示表单的类型。方法的实现如下:

public String insertDocument(Map<String, Object> map, Long typeId) {
    if (Objects.isNull(typeId)) {
        throw new BusinessException("类型 id 为空");
    }
    // 获取对应类型的处理器
    String id = getHandler(typeId).insertDocument(map);
    if (StrUtil.isBlank(id)) {
        throw new BusinessException("插入失败");
    }
    return id;
}

这里,getHandler(typeId)方法负责获取处理指定类型表单的处理器,该处理器实现了具体的插入逻辑,并返回生成的文档ID。如果插入失败,方法会抛出一个业务异常。

更新表单

更新表单也是一个常见需求,updateDocument方法允许对已存在的表单进行修改:

public Object updateDocument(Map<String, Object> map, Long typeId) {
    if (Objects.isNull(typeId)) {
        throw new BusinessException("类型 id 为空");
    }
    if (!map.containsKey("id")) {
        throw new BusinessException("表单 id 为空");
    }
    return getHandler(typeId).updateById(map, String.valueOf(map.get("id")));
}

此方法确保表单ID和类型ID均有效,然后调用处理器的updateById方法,根据表单ID更新数据。

查询表单

查询特定表单的selectDocumentById方法可以根据表单ID和类型ID获取表单数据:

public Object selectDocumentById(String id, Long typeId) {
    if (Objects.isNull(typeId)) {
        throw new BusinessException("类型 id 为空");
    }
    if (StrUtil.isBlank(id)) {
        throw new BusinessException("表单 id 为空");
    }
    return getHandler(typeId).selectDocument(id);
}

这一方法通过调用处理器的selectDocument方法返回指定ID的文档。

删除表单

最后,deleteDocument方法实现了根据表单ID删除文档的功能:

public Integer deleteDocument(String id, Long typeId) {
    if (Objects.isNull(typeId)) {
        throw new BusinessException("类型 id 为空");
    }
    if (StrUtil.isBlank(id)) {
        throw new BusinessException("表单 id 为空");
    }
    return getHandler(typeId).deleteDocument(id);
}

这一方法调用处理器的deleteDocument方法来移除文档,并返回操作结果。

2.3 如何使用这些数据库?

approval_step,approval_type这两个表一直存储的都是“死数据”,都是后台管理员手动插入数据。

如果是正常的推进,在OfficeAutomationHandler#createApprovalStepRecord方法中,先使用buildApprovalUsernameSet方式构建构建一条approvalStepRecordPO的类数据,并且插入数据库,状态为等待审批。

在这里插入图片描述

在这里插入图片描述
新的数据已经被插入了,同时系统消息也已经被插入到数据库内。
在这里插入图片描述

3. 流转函数

1. process(ApprovalStepRecordPO approvalStepRecordPO)

该方法负责处理审批步骤的工作流程。包括验证、更新步骤记录、根据步骤结果确定后续行动(成功、失败或转移),以及处理步骤完成后的后续活动。具体流程如下:

  • 验证:使用 check() 方法确保输入参数的正确性和用户具有必要的权限。
  • 更新步骤记录:调用 approvalStepRecordService.updateApprovalStepRecordById(approvalStepRecordPO) 来更新数据库中对应的审批步骤记录的状态。如果更新失败(即返回的更新计数为0),则抛出异常,指出“更新当前步骤失败”。
  • 确定结果并处理:
    • SUCCESS:调用 success() 方法,处理步骤成功的后续操作。
    • FAILED:调用 failed() 方法,处理步骤失败的后续操作。
    • TRANSFER:调用 transfer() 方法,将审批流程转移到下一个指定的步骤。
    • 其他情况:返回 false,表示未能识别或处理的状态。
  • 查询更新完的审批记录:使用 approvalRecordService.selectById(approvalStepRecordPO.getApprovalId()) 查询此步骤对应的完整审批记录。如果查询失败(记录为空),则抛出异常,指出“获取审核记录失败”。
  • 后处理:更新步骤后,获取更新后的审批记录,并触发 afterProcess() 执行步骤完成后的任何附加动作。
  • 审批最终处理:如果当前审批记录的状态为 SUCCESS 或 FAILED,则说明审批流程已经结束。此时,调用 afterApproval() 方法来执行审批完成后的任何必要操作,如资源清理、最终通知等。

方法的一个大致的骨架像是这样:

检查参数
成功
失败
转交
默认
非空
成功或失败
其他
process - 处理审批
check - 检查
检查结果
抛出业务异常: 参数非法
updateApprovalStepRecordById - 更新步骤记录
更新结果
抛出业务异常: 更新当前步骤失败
match status - 匹配状态
状态
success - 处理成功
selectById - 查询更新记录
failed - 处理失败
transfer - 转交处理
返回假
查询结果
抛出业务异常: 获取审核记录失败
afterProcess - 审批后处理
检查最终状态
afterApproval - 审批完成后处理
返回真

具体的代码像是这样:

    /**
     * 审核当前步骤
     *
     * @param approvalStepRecordPO 审核参数
     * @return true-成功
     */
    public Boolean process(ApprovalStepRecordPO approvalStepRecordPO) {
        // 检查参数
        if (!check(approvalStepRecordPO)) {
            throw new BusinessException("参数非法");
        }
        // 基本参数
        DateTime date = DateUtil.date();
        // 更新当前步骤记录状态
        int count = approvalStepRecordService.updateApprovalStepRecordById(approvalStepRecordPO);
        if (count == 0) {
            throw new BusinessException("更新当前步骤失败");
        }
        switch (Objects.requireNonNull(match(approvalStepRecordPO.getStatus()))) {
            case SUCCESS:
                success(approvalStepRecordPO.getApprovalRecordId(), approvalStepRecordPO.getStepId(), date);
                break;
            case FAILED:
                failed(approvalStepRecordPO.getApprovalRecordId(), date);
                break;
            case TRANSFER:
                transfer(approvalStepRecordPO.getApprovalRecordId(), date, approvalStepRecordPO.getNextStepId());
                break;
            default:
                return false;
        }
        // 查询更新完的审批记录
        ApprovalRecordPO approvalRecordPO = approvalRecordService.selectById(approvalStepRecordPO.getApprovalRecordId());
        if (Objects.isNull(approvalRecordPO)) {
            throw new BusinessException("获取审核记录失败");
        }
        // 执行当前步骤完成后的函数
        afterProcess(approvalStepRecordPO, approvalRecordPO);
        // 如果当前审批记录为success或者failed状态则说明已经完成,执行完成的步骤
        if (SUCCESS.getStatus().equals(approvalRecordPO.getStatus()) || FAILED.getStatus().equals(approvalRecordPO.getStatus())) {
            afterApproval(approvalRecordPO, approvalStepRecordPO);
        }
        return true;
    }
  • 职责定位:afterProcess 主要处理单个步骤的后续动作,而 afterApproval 处理整个审批过程的终结。
  • 调用条件:afterProcess 在每个审批步骤后调用,而 afterApproval 仅在审批流程完全结束后调用。
  • 业务逻辑:afterProcess 可能包括一些通用的后处理操作,如更新数据库、发送状态消息等;afterApproval 更多地关注如何根据审批的最终结果进行响应。

2. createApprovalStepRecord(Long approvalId, Date date, Long stepId)

为新记录设置具有审批权限的用户,并以等待状态初始化。
这个方法 createApprovalStepRecord(Long approvalId, Date date, Long stepId) 是用来在审批流程中创建新的审批步骤记录的。其中会调用 buildApprovalUsernameSet(approvalStepPO, approvalRecordPO.getDocumentId()) 方法,根据审批步骤和关联的文档ID构建一个拥有审核权限的用户集合 (Set)。这个集合指定了哪些用户有权进行当前审批步骤的审核。

引用了这个方法,分别构建审核步骤记录的审核人群:

    /**
     * 构建审核步骤记录的审核人群
     *
     * @param approvalStepPO 审核步骤
     * @param documentId     申请表单编号
     * @return username集合
     */
    protected abstract Set<String> buildApprovalUsernameSet(ApprovalStepPO approvalStepPO, String documentId);

    /**
     * 构建审核记录可见人群
     *
     * @param approvalRecordPO 审核记录
     * @return 可见人群的文档编号
     */
    protected abstract Set<String> buildWatchUsernameSet(ApprovalRecordPO approvalRecordPO);

3. check(ApprovalStepRecordPO approvalStepRecordPO)

验证处理步骤所需的信息。检查步骤记录数据的完整性和正确性,并确保用户有权对步骤进行操作。

步骤流转函数配置

1. 步骤流转处理函数(transfer)

  • 1.1 新建步骤记录:

    • 调用 createApprovalStepRecord(approvalId, date, nextStepId) 方法创建一个新的审批步骤记录。
    • 如果创建失败(返回值为0),抛出 BusinessException 异常,错误信息为“新增步骤记录失败”。
  • 1.2 更新审批记录信息:

    • 调用 approvalRecordService.updateApprovalRecordById(approvalId, date, nextStepId, TRANSFER) 更新审批记录的状态为流转(TRANSFER)。
    • 如果更新失败(返回值为0),抛出 BusinessException 异常,错误信息为“修改OA信息失败”。
  • 1.3 返回操作结果:

    • 方法成功执行后返回 true。

2. 步骤失败处理函数(failed)

  • 2.1 更新审批记录为失败状态:

    • 调用 approvalRecordService.updateApprovalRecordById(approvalId, date, null, FAILED) 更新审批记录的状态为失败(FAILED)。
    • 如果更新失败(返回值为0),抛出 BusinessException 异常,错误信息为“修改OA信息失败”。
  • 2.2 返回操作结果:

    • 方法成功执行后返回 true。

3. 步骤成功处理函数(success)

  • 3.1 获取当前步骤信息:

    • 调用 approvalStepService.selectById(stepId) 获取当前审批步骤的详细信息。
    • 如果步骤信息获取失败(返回值为 null),抛出 BusinessException 异常,错误信息为“获取审核步骤失败”。
  • 3.2 分析跳过的步骤:

    • 调用 approvalStepRecordService.selectApprovalRecordWithApprovalRecordId(approvalId) 获取与审批ID关联的所有步骤记录信息。
    • 如果未获取到任何步骤记录,抛出 BusinessException 异常,错误信息为“获取审核步骤记录失败”。
    • 将步骤记录按步骤顺序进行分组。
  • 3.3 确定下一步骤ID:

    • 遍历分组后的步骤记录,查找未完成的步骤。
    • 如果存在未完成的步骤,则设置下一步骤ID为未完成步骤的第一条记录的步骤ID。
    • 如果当前步骤是流程中的最后一步,或者没有未完成的步骤,跳转至3.4。
  • 3.4 创建或更新步骤记录:

    • 如果存在下一步骤ID,创建新的步骤记录,并更新当前审批记录为等待状态(WAITING)。
    • 如果不存在下一步骤(当前步骤是最后一步或已跳过所有未完成的步骤),更新当前审批记录为成功状态(SUCCESS)。
  • 3.5 返回操作结果:

    • 方法成功执行后返回 true。
支持方法和抽象钩子
  • 抽象方法:该类包括几个抽象方法,如 createApprovalRecord()、afterProcess()、afterApproval() 和与文档相关的操作(insertDocument()、deleteDocument()、selectDocument()、updateById())。这些方法需要在派生类中实现,以处理特定类型的审批流程的特定行为。
  • 实用功能:如 buildApprovalUsernameSet() 方法帮助确定哪些用户被允许参与给定的审批步骤,可以根据审批步骤的具体情况或组织规则进行定制。
  • 13
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值