一个简单的审批流程系统设计

一个简单的审批流程系统设计

1 背景

​ 最近在做一个企业管理系统的外包,该管理系统主要分为两个端,管理端(web端)和生产端(移动端)。管理端的功能有人员管理项目管理工作量管理审批流程管理等,生产端就是给员工用的,功能有上报工作量请假调岗打卡等。生产端的一个特色就是有一个审批的功能,员工提交的各个模块的申请都要走审批流程,审批流程通过后,该申请才算生效。

2 审批功能设计

2.1 数据库设计

2.1.1 用例分析

​ 一个完整的审批过程需要这几类角色:申请人申请详情审批流程审批规则

申请人点击app上面的某个申请,比如请假,然后填入必要的内容作为申请详情,比如请假原因,请假时间等(不同模块的申请,有着不同的申请详情),接着申请人指定一个审批流程后提交。审批流程中的每个审批人收到待审批待办依次按规则审批通过后申请生效,驳回后申请失效。通过我的申请已审批等页面可以查看审批详情。

2.1.2 表设计

​ 无论是申请人,还是审批人,对应到数据库都算是一个用户,所以理所当然要有用户表。

在这里插入图片描述

-- users表
CREATE TABLE `users` (
  `user_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '员工编码',
  `username` varchar(16) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '用户名/员工号',
  `password` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '密码',
  `phone` char(11) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '手机号',
  `name` varchar(16) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '员工姓名',
  `dept_id` bigint(20) DEFAULT NULL COMMENT '部门编码',
  `dept_name` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '部门名称',
  `postion_id` bigint(20) DEFAULT NULL COMMENT '职位编码',
  `postion_name` varchar(16) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '职位名',
  `base_salary` decimal(10,2) DEFAULT NULL COMMENT '基础薪资',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `modify_time` datetime DEFAULT NULL COMMENT '修改时间',
  `last_login_time` datetime DEFAULT NULL COMMENT '最后登陆时间',
  `status` tinyint(4) DEFAULT '0' COMMENT '员工状态 0有效 1无效',
  PRIMARY KEY (`user_id`) USING BTREE,
  UNIQUE KEY `users_username` (`username`) USING BTREE,
  KEY `users_dept_id` (`dept_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=46 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;

​ 由于审批详情和功能模块绑定,且与审批一一对应,所以使用相同的id就可以定位到某个审批及其审批详情。而数据库就需要一张审批表(用来记录某一个审批的全部信息)和多张审批详情表(请假详情,工作量详情等)来存储数据。

审批表

在这里插入图片描述

-- approval表
CREATE TABLE `approval` (
  `approval_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '审批编码',
  `module_id` tinyint(4) NOT NULL COMMENT '模块编码',
  `applicant_id` bigint(20) DEFAULT NULL COMMENT '申请人编码',
  `applicant_name` varchar(16) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '申请人名字',
  `approver_id` bigint(20) DEFAULT NULL COMMENT '审批人编码',
  `approver_name` varchar(16) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '审批人名字',
  `process_id` bigint(20) DEFAULT NULL COMMENT '审批流程编码',
  `levels` tinyint(4) DEFAULT NULL COMMENT '等级',
  `reject_reason` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '审批驳回原因',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `modify_time` datetime DEFAULT NULL COMMENT '修改时间',
  `complete_time` datetime DEFAULT NULL COMMENT '审批完成时间',
  `status` tinyint(4) DEFAULT '0' COMMENT '审批状态 0审批中 1审批通过 2审批驳回',
  PRIMARY KEY (`approval_id`) USING BTREE,
  KEY `index_approval_applicant` (`applicant_id`) USING BTREE,
  KEY `index_approval_approver` (`approver_id`) USING BTREE,
  KEY `index_approval_create_time` (`create_time`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=168 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;

审批详情表(工作量)

​ 在上面审批表显示的数据中其审批id为142,对应的模块id为0,也就是工作量模块(可以自定),其对应的审批详情表为work_report,而这条数据的id也是142。相应的,其他审批详情表(比如请假,调岗,离职等)也是一样。

在这里插入图片描述

-- work_report表 该表为工作量表,
CREATE TABLE `work_report` (
  `work_report_id` bigint(20) NOT NULL COMMENT '报工编码',
  `applicant_id` bigint(20) DEFAULT NULL COMMENT '报工申请人编码',
  `applicant_name` varchar(16) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '申请人名字',
  `sum_workload_price` decimal(10,2) DEFAULT NULL COMMENT '报工总价',
  `project_id` bigint(20) DEFAULT NULL COMMENT '项目编码',
  `project_name` varchar(16) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '项目名称',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `modify_time` datetime DEFAULT NULL COMMENT '修改时间',
  `complete_time` datetime DEFAULT NULL COMMENT '审批完成时间',
  `status` tinyint(4) DEFAULT NULL COMMENT '报工状态 0审批中 1审批通过 2审批驳回',
  PRIMARY KEY (`work_report_id`) USING BTREE,
  KEY `index_work_report_user_id` (`applicant_id`) USING BTREE,
  KEY `index_work_report_create_time` (`create_time`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;

审批流程实际上是一个个的审批人还有标志其先后顺序的审批等级。可以通过idtop_id来组织这张表。每一个process_id确定一个审批人,每一个top_id确定一个审批记录,如下图所示。

在这里插入图片描述

-- process表
CREATE TABLE `process` (
  `process_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '流程id',
  `top_id` bigint(20) DEFAULT NULL COMMENT '最顶级id',
  `approver_id` bigint(20) DEFAULT NULL COMMENT '审核人id',
  `approver_name` varchar(16) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '审核人名字',
  `next_id` bigint(20) DEFAULT NULL COMMENT '下一流程id(null表示无后序流程)',
  `levels` tinyint(4) DEFAULT '0' COMMENT '流程级别(从1开始)',
  PRIMARY KEY (`process_id`) USING BTREE,
  KEY `process_top_id` (`top_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=127 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;

​ 我们看到无论是审批表还是详情表,都有一个status字段,来标志审批状态。

​ 通过模块化的思想,可以将审批详情分为多张表。这是从审批系统的角度思考,而从各个模块的角度去想,因其都需要进行审批,所以只用一张表来存储就行,不需要为其单独的建表。

​ 而所谓的审批规则是根据业务场景确定的,比如我这个项目,审批规则就是最简单的分级审批,每个申请只有一个审批流程审批流程中每一级只有一个审批人任何一级审批驳回后整个申请就宣告结束。其实只要有了基础的审批表、审批详情表、审批流程表,任何审批规则都是可以实现的,无非是复杂程度不同。

​ 如果有查看每一级审批记录的需求,也可以再建立一张审批历史表,其中记录了每一级审批的审批人、时间等信息。

在这里插入图片描述

-- approval_history表
CREATE TABLE `approval_history` (
  `approval_id` bigint(20) NOT NULL COMMENT '审批编码',
  `approver_id` bigint(20) NOT NULL COMMENT '审批人编码',
  `approver_name` varchar(16) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '审批人名字',
  `process_id` bigint(20) DEFAULT NULL COMMENT '审批流程编码',
  `levels` tinyint(4) DEFAULT NULL COMMENT '等级',
  `complete_time` datetime DEFAULT NULL COMMENT '审批完成时间',
  `status` tinyint(4) DEFAULT '0' COMMENT '审批状态 0审批通过 1审批驳回',
  PRIMARY KEY (`approval_id`,`approver_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;

2.2 功能拆分

​ 通过对用例的分析,设计出了五张表。分别是用户表审批表审批详情表审批流程表审批历史表

在审批表和审批详情表之间通过相同的id进行数据绑定,并且根据模块id来确定不同的审批详情。接下来就是设计功能,来支撑起整个审批系统了。

2.2.1 对于审批流程的思考

​ 对于整个审批的操作大致可以分为四块,即:1 上报申请2 通过申请3 驳回申请4 查看申请详情

​ 对应到数据库层面的操作就是:1 开启一级审批流程;生成审批表记录;审批详情表记录2 修改审批状态为通过3 修改审批状态为驳回4 查询审批表及其详情

​ 最难的是上报,其次是查询,最后是通过/审批,体现在代码层面就是其代码行数的减少。而整个线性的审批流程,也正对应着其开发流程。

​ 抽象出来的四个操作,完全适用于每个功能模块,对应到代码层面就是四个接口。前端完全可以通过这四个接口来完成不同功能模块审批操作,只需要向后端传输相应的模块id即可。该抽象思想,也可以用在数据校验上,后面做数据校验时也会用到类似抽象的思想。

2.2.2 四大接口

​ 话不多说上代码

2.2.2.1 举一反三设计上报接口

​ 上报的流程大致分为三个:1 生成审批2 生成审批详情3 通知第一审批人审批

/**
 * 审批上报
 *
 * @param input
 * @return
 */
@RequestMapping(value = "/reporting", method = RequestMethod.POST)
@Approval(opType = Approval.REPORTING)
@Transactional
public RespBean reporting(@RequestBody Input input) {
    // 开启审批流程
    approvalService.addApproval(input);
    // 上报
    input.getService().reporting(input);
    // 生成待办
    todoService.generateTodo(input);
    return RespBean.ok("上报成功");
}

​ approvalService:用来操作审批,主要有以下四个操作

public interface IApprovalService {
    // 生成审批
    void addApproval(Input input);
    // 查询审批详情
    JSONObject getApprovalDetail(Input input);
    // 审批通过
    void passApproval(Input input);
    // 审批驳回
    void rejectApproval(Input input);
}

​ 在上报接口中,调用addApproval方法使用其来生成审批,并返回其id,来供下游生成同id的审批详情。

​ 接着就是模块的上报流程,其实就是从入参中获取对用service层的类,调用reporting方法。而不同的service都会实现同一个接口:IApprovalDetailService,实现该接口的业务类,执行不同的业务操作。

public interface IApprovalDetailService {
    // 执行上报
    void reporting(Input input);
    // 查询审批详情
    JSONObject todoDetail(JSONObject res);
    // 完成整个审批
    void pass(Input input);
    // 审批驳回
    void reject(Input input);
}

​ 上报完成后,就可以调用todoService生成待办给审批人看了

public interface ITodoService {
    // 生成待办
    void generateTodo(Input input);
    // 审批通过 完成代办
    void pass(Input input);
    // 审批驳回 完成待办
    void reject(Input input);
    // 查询待办
    Todo getTodo(Long todoId, Long userId);
} 

​ 其余三个操作的流程附上

/**
 * 查看审批详情
 *
 * @param input
 * @return
 */
@RequestMapping(value = "/todoDetail", method = RequestMethod.POST)
@Approval(opType = Approval.TODO_DETAIL)
public RespBean todoDetail(@RequestBody Input input) {
    // 查询审批详情
    JSONObject approvalRes = approvalService.getApprovalDetail(input);
    // 查询业务详情
    JSONObject res = input.getService().todoDetail(approvalRes);
    return RespBean.ok("查询成功", res);
}

/**
 * 审批通过
 *
 * @param input
 * @return
 */
@RequestMapping(value = "/pass", method = RequestMethod.POST)
@Approval(opType = Approval.PASS)
@Transactional
public RespBean pass(@RequestBody Input input) {
    // 完成待办
    todoService.pass(input);
    // 通过审批 并 尝试推进下一审批
    approvalService.passApproval(input);
    if (null == input.getApproval()) {
        // 完成整个审批流程
        input.getService().pass(input);
    } else {
        // 如果审批未完成 就生成待办(新审批)
        todoService.generateTodo(input);
    }
    return RespBean.ok("审批通过成功");
}

/**
 * 审批驳回
 *
 * @param input
 * @return
 */
@RequestMapping(value = "/reject", method = RequestMethod.POST)
@Approval(opType = Approval.REJECT)
public RespBean reject(@RequestBody Input input) {
    // 完成代办
    todoService.reject(input);
    // 结束审批流程
    approvalService.rejectApproval(input);
    // 结束业务流程
    input.getService().reject(input);
    return RespBean.ok("审批驳回成功");
}

2.3 AOP根据自定义注解确定运行时接口

​ 在四大接口执行之前,需要做的事也有很多,如参数校验,参数注入。而这些操作通过AOP可以统一去做。

​ 只需要根据前端传入的模块id,就能够确定相应的校验器业务类

​ AOP中的代码复用,使用到了java反射,通过获取到运行时触发方法上的注解,来判断调用了哪个接口,从而注入不同的参数,供下游使用。

​ 比如在校验时,通过实现定义好的枚举类,使用前端传入的模块id找到相应的beanName,然后通过ioc容器获取对应的校验器bean,执行相应的校验方法。而不同模块的校验器实现已经做好了参数校验,下游直接执行业务代码即可。

在这里插入图片描述

Valid接口

/**
 * @author: theTian
 * @date: 2020/11/12 15:02
 */
public interface Valid {
    /**
     * 上报验证
     * @param input
     */
    void validReporting(Input input);

    /**
     * 查询审批验证
     * @param input
     */
    default void validTodoDetail(Input input){};

    /**
     * 审批通过验证
     * @param input
     */
    default void validPass(Input input){};

    /**
     * 审批驳回验证
     * @param input
     */
    default void validReject(Input input){};
}


​ 在获取相应的业务bean时,也用到了相同的想法,即通过模块id找到对应的业务bean。其中获取bean借助到了ioc容器,所以枚举类中的beanName要和ioc中的beanName一致。

在这里插入图片描述

/**
 * 审批四个接口的参数校验及参数注入
 *
 * @param point
 * @throws Throwable
 */
@Around("execution(* x.x.controller.ApprovalController.*(x.x.x.Input))")
public Object approvalValid(ProceedingJoinPoint point) throws Throwable {
    Input input = (Input) point.getArgs()[0];
    logger.info(point.getSignature().getName() + "入参:" + input);
    // 根据注解注入参数
    Signature signature = point.getSignature();
    MethodSignature msg = (MethodSignature) signature;
    Method method = point.getTarget().getClass().getMethod(msg.getName(), msg.getParameterTypes());
    Approval annotation = method.getAnnotation(Approval.class);
    String opType = annotation.opType();
    if (StringUtils.isNotEmpty(opType)) {
        input.setUser(UserUtils.getUser());
        input.setValidMethod(opType);
    }
    // 校验
    ValidUtils.approvalValid(input);
    // 获取service
    ApprovalService service = ServiceFactory.getService(input.getModule().getModuleId());
    JgerpAssert.notNull(service, "不存在该服务");
    // 装填service
    input.setService(service);
    // 执行后序方法
    RespBean res = (RespBean) point.proceed();
    logger.info(point.getSignature().getName() + "出参:" + JSONObject.toJSONString(res));
    return res;
}

核心代码:

// 校验
ValidUtils.approvalValid(input);
// 获取service
ApprovalService service = ServiceFactory.getService(input.getModule().getModuleId());

3 总结

​ 在参考网上的博客,结合项目实际情况后逐渐找到了思路。其实就是在设计各种功能的时候考虑到要做模块化,把审批系统各个功能模块独立出来,一定要降低模块之间的耦合度,并提升自身模块的复用性,这样的好处就是在开发了审批系统之后开发新的功能模块时,就可以直接接入已经做好的审批系统,从而达到代码复用。

​ 可是知易行难,真正落实到代码层面,需要注意很多的细节

​ 比如,由于是前后端分离开发,要考虑到各个功能模块的数据校验。起码不能因为字段过长而导致插入数据库失败,与此同时也要考虑特殊字符处理,业务规则校验等。

​ 再比如审批系统设计的要足够独立,对于新模块的开发,要做到一次开发,处处使用。这就需要运用到OOP以及AOP的思想。

​ 随着项目的推进,如今一期的基本功能都已经实现了,但仅仅只做到了够用,自己的设计肯定不是业界最佳方案,也可能隐藏着诸多的漏洞,但是从需求分析,功能设计,数据库设计,功能开发和测试,在这个过程中通过思考问题并实践验证,确实收获很多

  • 46
    点赞
  • 291
    收藏
    觉得还不错? 一键收藏
  • 10
    评论
根据提供的引用内容,OA系统流程审批数据库设计是所有OA系统流程的基础,它包含了大部分自定义的流程。具体的数据库设计需要根据具体的业务需求进行设计,但是一般来说,OA系统流程审批数据库设计需要包含以下几个方面的内容: 1. 流程定义表:用于存储流程的基本信息,如流程名称、流程编号、流程类型等。 2. 流程步骤表:用于存储流程的步骤信息,如步骤名称、步骤编号、步骤类型等。 3. 流程步骤处理表:用于存储流程步骤的处理信息,如处理人、处理时间、处理结果等。 4. 流程附件表:用于存储流程相关的附件信息。 5. 流程日志表:用于存储流程的操作日志信息,如操作人、操作时间、操作内容等。 6. 流程权限表:用于存储流程相关的权限信息,如流程创建人、流程处理人、流程查看人等。 下面是一个简单的OA系统流程审批数据库设计的示例: ```sql -- 流程定义表 CREATE TABLE process_definition ( id INT PRIMARY KEY, name VARCHAR(50), code VARCHAR(50), type VARCHAR(50) ); -- 流程步骤表 CREATE TABLE process_step ( id INT PRIMARY KEY, name VARCHAR(50), code VARCHAR(50), type VARCHAR(50), process_id INT, FOREIGN KEY (process_id) REFERENCES process_definition(id) ); -- 流程步骤处理表 CREATE TABLE process_step_handler ( id INT PRIMARY KEY, step_id INT, handler VARCHAR(50), handle_time DATETIME, handle_result VARCHAR(50), FOREIGN KEY (step_id) REFERENCES process_step(id) ); -- 流程附件表 CREATE TABLE process_attachment ( id INT PRIMARY KEY, name VARCHAR(50), path VARCHAR(100), process_id INT, FOREIGN KEY (process_id) REFERENCES process_definition(id) ); -- 流程日志表 CREATE TABLE process_log ( id INT PRIMARY KEY, operator VARCHAR(50), operate_time DATETIME, operate_content VARCHAR(100), process_id INT, FOREIGN KEY (process_id) REFERENCES process_definition(id) ); -- 流程权限表 CREATE TABLE process_permission ( id INT PRIMARY KEY, process_id INT, creator VARCHAR(50), handler VARCHAR(50), viewer VARCHAR(50), FOREIGN KEY (process_id) REFERENCES process_definition(id) ); ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值