资讯动态

MVC权限管理系统与审批流程全解析:从RBAC到状态机落地

发布时间:2026/9/12 22:51:41 来源:尧图企业网站定制
简介这是一套基于MVC架构的权限管理与流程审批系统完整源码配套详细说明文档适合.NET开发人员、企业内部信息化建设者用于学习或项目改造。系统覆盖角色与权限分配、动态访问控制、工作流引擎、多级审批节点与状态跟踪同时整合文档上传下载、版本管理、搜索和分类等功能能够帮助理解从业务建模到界面展示的完整MVC实践路径。压缩包共2000个文件大小约52.72MB以cshtml视图、cs控制器与模型、css/js前端交互、png/gif图标、dll依赖库及sql数据库脚本为主另外包含Visual Studio解决方案、配置文件和说明文档目录结构清晰便于按模块定位代码。已有505人学习源码中数据库、库文件与配置基本齐全可编译运行或直接二次开发尤其适合作为企业内部文档审批、权限管控类系统的参考实现。1. 从“源码文档”压缩包看 MVC 权限与流程审批的典型构成拿到一个名为“MVC权限管理流程审批系统源码文档.zip”的项目包大多数人的第一反应是解压、改数据库连接、跑起来。但这类包通常不是单纯的“增删改查 demo”而是“文档管理系统 权限控制 审批流程”三件事的结合体文档作为业务对象权限管谁能看、谁能改、谁能删流程则负责“提交→审批→归档”这条链路。这里的技术核心其实是三件事MVC 分层是否干净、权限模型是 RBAC 还是简单的 is_admin 字段、审批流是用状态机硬编码还是接入了工作流引擎。标题里“流程”二字往往决定了系统的复杂度上限。这套东西适合两类人一类是要给企业或学校做内部 OA 类系统的开发者另一类是正在学 Spring MVC 或 ASP.NET MVC、想找一份完整代码做参照的初中级工程师。对于五年以上经验的人这份包的价值不在于功能列表而在于权限与流程的建模方式这也是本文重点拆解的部分。2. 拆包即复现MVC 审批系统的最小部署路径2.1 先用目录结构认清 MVC 三层的“厚薄”解压后先看 Controller 层。常见做法是每个业务模块对应一个 Controller比如DocumentController、ApprovalController、AuthController。看几个关键方法体如果 Controller 里直接写了 SQL 或复杂业务循环说明这是一个“瘦模型、胖控制器”的项目维护成本偏高如果 Controller 只有参数接收和调用 Service 的代码业务逻辑在 Service 层事务边界也清晰这类代码更适合二次开发。判断方法很简单搜索Transactional或TransactionTemplate出现在哪一层。出现在 Service 层是正常设计出现在 Controller 层就要警惕。/mvc-approval ├── src/main/java │ ├── controller // 路由与参数校验 │ ├── service // 业务逻辑与事务 │ ├── dao/mapper // 数据访问 │ ├── model/entity // 实体 │ └── interceptor // 登录与权限拦截 ├── src/main/resources │ ├── mapper // MyBatis XML │ ├── static // JS/CSS │ └── templates // 视图页面 ├── sql // 初始化脚本 └── docs // 设计文档这类项目的技术栈通常是 Spring MVC MyBatis JSP/Thymeleaf也可能见到 ASP.NET MVC EF 的变体。原理是同一个 MVC 设计模式差异在路由配置和依赖注入方式上。先确认技术栈再决定怎么启动。2.2 三件必配的配置项数据源、会话、事务打开application.properties或web.config优先核对三个配置。第一个是数据源jdbc:mysql://localhost:3306/approval_db这种地址要改成自己的库。第二个是会话超时时间很多包默认 30 分钟审批系统常有“填写一半被踢出”的体验问题建议改到 60 分钟以上。第三个是事务管理器Spring MVC 项目里常见两种配置方式注解驱动和 XML AOP都是把DataSourceTransactionManager挂到容器上。spring.datasource.urljdbc:mysql://localhost:3306/approval_db?useUnicodetruecharacterEncodingutf8 spring.datasource.usernameroot spring.datasource.passwordyour_password spring.mvc.view.prefix/WEB-INF/views/ spring.mvc.view.suffix.jsp # 会话超时单位秒 server.servlet.session.timeout3600这段配置里前三行决定了系统能不能连上数据库后两行决定了视图解析路径超时配置则直接影响审批人在线填单的体验。关键词是“会话”审批类的 web 应用和纯 API 服务不同用户操作时间长会话不能按默认 30 分钟来。如果配置里出现了spring.session.store-typejdbc说明会话存在数据库集群部署时不会丢登录态但要注意spring_session表要能建成功。2.3 用 SQL 脚本对账而不是先跑起来很多人习惯直接启动报错了再回头补数据库。更稳妥的顺序是先打开sql目录下的脚本数一数建表语句确认里面至少包含下面几张核心表再导入数据表名作用对应模块sys_user用户账号与状态认证sys_role角色定义权限sys_menu菜单与按钮权限点权限user_role/role_menu用户角色关联、角色权限关联RBACdocument_info文档元数据文档管理approval_order审批单主表流程approval_trace审批流转痕迹流程如果发现没有role_menu这张表而是用user.role_id一个字段表示权限那说明包的权限模型是简化的角色字段后期如果要增加菜单级操作权限必须补表。对账时注意 SQL 脚本里的DROP TABLE IF EXISTS在已有数据的库上执行前先备份。提示导入 SQL 时用source命令或客户端整本执行不要只复制部分建表语句。这类项目常有外键或初始化数据依赖顺序分段执行容易漏掉字典表。3. 权限管理不只是角色字段MVC 项目的 RBAC 落地与扩展3.1 五张基础表看懂“用户-角色-资源”模型经典的 RBAC 模型在 MVC 项目里落地时最少需要五张表用户表、角色表、菜单资源表、用户角色关联表、角色菜单关联表。菜单资源表比想象的更重要因为它不只存“左侧导航菜单”还存“查询按钮”“删除按钮”“导出按钮”。在页面权限控制成熟的项目里每一个button都对应资源表里的一条记录。CREATE TABLE sys_menu ( menu_id INT PRIMARY KEY AUTO_INCREMENT, parent_id INT DEFAULT 0, menu_name VARCHAR(64) NOT NULL, menu_type CHAR(1) DEFAULT M, -- M目录 C菜单 F按钮 perms VARCHAR(128), -- 权限标识如 document:approve path VARCHAR(128), sort_no INT DEFAULT 0 );menu_type字段是关键。只做目录和菜单控制是初级的“页面权限”把按钮也纳入资源表统一管理才能做到操作级控制。perms字段的命名建议用模块:动作的格式比如document:delete后面写拦截器匹配时就不需要改代码。3.2 用拦截器实现按钮级权限校验MVC 的拦截器机制天然适合做权限校验。Spring MVC 里实现HandlerInterceptor在preHandle方法里取当前登录用户查其角色对应的perms集合再和请求的RequiresPermissions注解比对。public class AuthInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (!(handler instanceof HandlerMethod)) { return true; } HandlerMethod method (HandlerMethod) handler; RequiresPermission annotation method.getMethodAnnotation(RequiresPermission.class); if (annotation null) { return true; } // 从 ThreadLocal 或 Session 获取当前用户 LoginUser user UserContext.get(); if (user null) { response.sendRedirect(/login); return false; } if (!user.getPerms().contains(annotation.value())) { response.setStatus(403); return false; } return true; } }这段代码的逻辑是先判断 HandlerMethod 上有没有权限注解没有就直接放行有就取当前用户权限集合比对。参数说明annotation.value()就是RequiresPermission(document:approve)里的字符串UserContext通常是基于 ThreadLocal 的工具类在登录过滤器里写入请求结束后清除。提示比按钮权限更容易被忽略的是数据范围权限。同一张文档列表“普通员工只能看自己提交的部门经理看本部门管理员看全部”这不是perms集合能解决的要在 SQL 层加data_scope参数。做法是查询方法里带上DataScope注解MyBatis 的拦截器根据注解拼WHERE user_id IN (部门成员子查询)这种条件。这里频繁出现的权限管理概念要和前面坑位对应上避免明明做了菜单控制导出数据时却出现越权。3.3 文件权限管理与系统权限的边界中文互联网里提到“文件权限管理”时经常和 Linux 系统的文件权限混淆。MVC 审批系统里的文档权限和 Linux 文件权限是两回事但思路可比照。Linux 下用chmod 640 file区分属主、属组、其他人审批系统里则是通过“用户-角色-资源”三元组判断某个人对某份文档有没有下载权。更严格的系统会把“文档”本身资源化文档表里加owner_id、dept_id字段查询时强制带上范围判断这时页面上的“有权限”和数据库层的“能查出”保持一致才不算白做。访问控制列表ACL比 RBAC 更细但维护成本高一般项目用 RBAC 加数据范围就够了。4. 从“待办/已办/抄送”看懂审批流程的状态机与操作矩阵4.1 审批单的核心字段与状态迁移流程审批系统最核心的数据模型不是流程引擎而是“审批单”和“审批记录”。审批单可以是一张请假单、一份文档发布申请、一笔采购申请。字段通常包括apply_user_id申请人、current_approver当前审批人、status状态、approval_level当前层级、attachments附件。状态字段建议用整数枚举而不是中文中文存库看起来直观但代码里写if (已通过.equals(status))极易在改文案时出 bug。public enum ApprovalStatus { DRAFT(0, 草稿), SUBMITTED(1, 已提交), APPROVING(2, 审批中), APPROVED(3, 通过), REJECTED(4, 驳回), WITHDRAWN(5, 已撤回), ARCHIVED(6, 已归档); public final int code; public final String desc; }状态迁移必须画一张操作矩阵否则代码里会出现“从已驳回状态直接归档”之类的漏洞。合法迁移路径如下表当前状态可执行操作目标状态草稿提交审批中审批中同意通过 / 下一审批人审批中驳回已驳回审批中转办审批中审批人变化已驳回重新提交审批中已驳回撤回已撤回通过归档已归档用你自己的话说审批流的本质是对这张迁移表做“校验 持久化”。每次操作先查当前状态是否允许该动作再插入一条审批记录最后更新主表状态。三步缺一不可顺序也不能颠倒否则并发时会出现“两个审批人同时操作一张单子”的问题。4.2 会签、或签与条件分支的代码实现没有引入 Activiti 这类流程引擎时MVC 项目里最常见的审批流设计是“多级审批 节点配置表”。用一张approval_node表定义每个流程有几个节点每个节点有几个审批人。会签表示节点内所有人都要同意或签表示一人同意即通过。public synchronized void approve(Long orderId, Long nodeId, Long approverId, boolean passed) { ApprovalOrder order orderMapper.selectById(orderId); // 1. 记录本次审批意见 ApprovalTrace trace new ApprovalTrace(); trace.setOrderId(orderId); trace.setNodeId(nodeId); trace.setApproverId(approverId); trace.setResult(passed ? 1 : 0); trace.setCreateTime(new Date()); traceMapper.insert(trace); // 2. 判断该节点审批人总数与会签人数 NodeInfo node nodeMapper.selectById(nodeId); long total nodeMapper.countApprovers(nodeId); long approvedCount traceMapper.countApproved(nodeId); long rejectedCount traceMapper.countRejected(nodeId); if (会签.equals(node.getType()) approvedCount total rejectedCount 0) { // 未完成会签或签保持审批中 return; } if (或签.equals(node.getType()) approvedCount 0) { // 有一人同意直接流转到下一节点 } // 3. 关闭当前节点流转下一个节点或终结流程 // 此处省略根据当前节点流水号定位下一节点更新 order 的 current_node 和 status }同步关键字和synchronized是第一个关键点审批操作必须防并发。两个审批人同时点了同意如果没有同步或乐观锁状态会被覆盖。参数说明nodeId是节点配置表的主键approverId是当前操作人countApproved查询的是审批记录表中该节点已同意的数量而不是去审批单主表数状态因为一条审批记录代表一次操作主表只存汇总结果。4.3 流程痕迹与文档附件的时间线合并审批系统的“可追溯性”体现在审批记录上。好的approval_trace表会记录操作人、操作动作、审批意见、节点名称、操作时间。界面上的时间线就是查这张表按时间排序。进一步可以把文档操作也纳入同一张日志表用biz_type区分“提交文档”和“审批文档”这样用户在文档详情页看到的是完整生命周期创建 → 修改 → 提交 → 审批通过 → 归档而不是只有审批记录或只有操作日志。SELECT trace_id, node_name, approve_action, comment, create_time, approver_name FROM approval_trace WHERE order_id #{orderId} ORDER BY create_time ASC;这条 SQL 是时间线的核心简单但容易被忽略。注意approve_action最好存代码而非文案文案在页面上根据代码映射因为同一动作的名称可能因流程不同而不同。文档管理系统中附件存的是文件路径审批记录不存文件内容只在attachment_id引用防止日志表过大。5. 验收与加固文档管理系统从 demo 到可用的四个细节5.1 三个必做的验证点演示环境跑通只算完成一半。第一新建一个没有任何角色的“裸账号”登录后应该能进入页面但菜单为空按钮请求全部返回 403能把“页面能看但按钮不能点”做出来才算权限控制生效。第二准备两份公文文档一份是 PDF一份是伪装成.pdf的 JS 文件上传后打不开或类型校验直接被拦说明文件校验是“读文件头”而不是只看扩展名。第三把spring.jpa.show-sql或 MyBatis 的 SQL 日志打开发起一次驳回后再提交观察状态更新 SQL 是否带WHERE status 2条件如果只有UPDATE approval_order SET status 1这样的无条件语句说明有并发覆盖风险。5.2 管控附件目录的“文件权限”边界文档管理系统里上传文件保存到本地磁盘时需要把上传目录放在 web 应用根目录之外并给目录设置严格的操作系统文件权限。Linux 下用chmod 750 /data/uploads属主是运行 Web 服务的账号属组是运维账号其他人无权限。这一段是和系统文件权限管理最容易混淆的地方正文补一句即可不深入展开。应用层还要做两层校验Spring MVC 的文件上传大小限制以及服务端对文件扩展名和 MIME 类型的双重白名单校验。spring: servlet: multipart: max-file-size: 20MB max-request-size: 100MBmax-file-size限制单个文件max-request-size限制一次请求多个文件的总大小。审批场景和普通上传不同附件数量多但单个文件不大20MB 和 100MB 是比较合理的初始值。若把第二个参数设得太小会出现在线审批时几张单据扫描件因总大小超标而提交失败的情况。5.3 用数据快照解决“驳回后再次提交”的追踪难题流程审批系统最常见的需求迭代是“驳回后的再提交”。很多项目是回到草稿状态由用户修改后重新提交但这样再看历史时看不到第一版和废版长什么样。更稳妥的方案是加一张“表单快照”字段用户编辑和提交时把前一个版本编号列表放在version字段里提交动作发生前把当前内容写入form_snapshot表。审批记录里关联snapshot_id那么每次查看审批单详情时既能看最新内容也能通过快照对比前几个版本“改了哪里”。这一招能让二次开发时少改一张表也能让审批人驳回理由更有依据——他们可以回复“第 3 版提交里预算金额变了”而不必贴截图。“待办数量”这个页面上最显眼的数字不要用COUNT(*) FROM approval_order WHERE current_approver ?直接查。正确做法是在审批主表维护冗余字段pending_flag审批动作时维护这个字段列表页只查索引覆盖的普通字段。你用几次就会发现这个字段配合定时任务做超时提醒比在千万行表上有current_approver上建索引更省心。到这里从拆包到上线前加固一条完整的落地路径就够用了。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价