☰
SSM权限管理系统源码解析:角色菜单权限矩阵与四层架构实战
2026/10/30 0:20:37 网站建设 项目流程

简介:这是一套基于Java+MySQL+SSM框架的权限管理系统完整源码,面向计算机专业学生、Java初学者及需要完成毕设或课程设计的开发者,帮助解决角色、菜单与权限动态分配的管理需求。系统采用B/S体系结构与标准MVC模式,划分为表现层、controller层、service层和dao层,支持以角色为表头、菜单为首列在线分配权限,动态加载角色、菜单及权限并在一张表中通过开关完成操作,同时以树形结构呈现角色与菜单,整合增删改及菜单图标、按钮管理功能。压缩包共602个文件,约31.44MB,包含89个java源文件、24个jsp页面、115个js脚本、24个css样式、29个xml配置、65个jar依赖及1个sql数据库脚本,另附class编译文件与图片资源,结构完整便于二次开发。目前已有117人学习下载,适合需要快速理解SSM分层架构、权限控制逻辑与前后端交互流程的读者参考复用。

1. 一套能直接跑起来的 SSM 权限管理系统源码,到底值不值得拆

如果你正在做毕设、准备 Java 后端面试,或者想找一个结构完整、能跑通、能改的权限管理项目练手,那这套基于 SSM(Spring + SpringMVC + MyBatis)的权限管理系统源码,大概率能省掉你从零搭架子的一两周时间。它不是那种只丢几个 Controller 的半成品,而是把表现层、controller 层、service 层、dao 层四层都铺开了,数据库用 MySQL,前端用 JSP,标准的 MVC 模式,编译产物里能看到 CaptchaUtil、AuthActionController、AuthMenuController、AuthUserController、AuthRoleController、AuthAccountController 这些类,说明验证码、菜单、用户、角色、账号这几条主线都是齐的。

它最核心的卖点,是把「角色—菜单—权限」三者的关系压进一张表里:横轴是角色,纵轴是菜单,交叉点就是权限开关,点一下就能在线分配,不用来回跳页面。角色和菜单又各自用树形结构呈现,增删改都整合在树节点上。对做毕设的人来说,这种「一张表管权限」的设计既好讲清楚,又好演示;对想补 SSM 实战的开发者来说,它是一份能看清分层、能顺着改的参考实现。下面我按「先搞懂它怎么设计的,再动手跑起来,最后说清楚坑在哪」的顺序拆一遍。

2. 权限模型与四层结构:先看懂它为什么这么设计

2.1 角色为表头、菜单为首列的权限矩阵

传统 RBAC 实现里,权限分配通常是「选角色 → 勾菜单 → 保存」,页面要跳好几次,数据也要来回查。这套源码换了个思路:把角色和菜单一次性动态加载出来,渲染成一张二维表,行是菜单,列是角色,单元格里放一个开关。你在这个表里点开关,本质上就是在改「角色—菜单」这条关联记录。

这么设计的好处有三个。第一,权限全貌一眼可见,哪个角色有哪些菜单、哪个菜单被哪些角色拥有,不用点进去猜。第二,操作路径短,分配权限就是点开关,省掉了「进入角色详情 → 找菜单树 → 勾选 → 提交」这一串。第三,动态加载意味着角色和菜单变化后,表格自动跟着变,不需要写死列头。

从数据模型看,它至少需要三张核心表:角色表、菜单表、角色菜单关联表。关联表里一条记录代表「某角色拥有某菜单」,开关打开就是插入或更新状态,关闭就是删除或置为无效。按钮级别的权限通常挂在菜单下面,作为菜单的子节点或独立按钮表,树形展示时一并带出来。

提示:如果你要在这个模型上加「行级权限」(比如只能看自己部门的数据),别硬塞进这张矩阵表,行级权限属于数据过滤,应该在 service 或 dao 层用条件拼接处理,和功能权限分开。

2.2 四层结构各自负责什么

摘要里写得很清楚:表现层、controller 层、service 层、dao 层。落到代码上,大概是这样的分工。

表现层就是 JSP 页面,负责渲染权限矩阵、角色树、菜单树,以及把用户的点击转成请求。controller 层是 AuthActionController、AuthMenuController、AuthUserController、AuthRoleController、AuthAccountController 这些类,接收请求、做参数校验、调用 service、返回结果。service 层承载业务逻辑,比如「分配权限时先校验角色是否存在」「删除菜单时先检查有没有子菜单」。dao 层用 MyBatis 的 Mapper 和 XML 完成数据库读写。

这种分层的价值在于:你改权限分配规则,只动 service;你换前端展示,只动 JSP 和 controller 的返回格式;你优化 SQL,只动 Mapper XML。对毕设答辩来说,能把这四层讲清楚,比堆功能更能体现工程能力。

2.3 验证码与工具类的位置

编译产物里有 CaptchaUtil$ImgFontByte、UseTool、DateUtil 这几个类。CaptchaUtil 负责生成图形验证码,内部类 ImgFontByte 大概率是用来控制验证码字体样式的;UseTool 是通用工具类,可能包含字符串处理、MD5 加密、随机数生成;DateUtil 处理日期格式化。这些类通常放在 util 包下,被 controller 或 service 调用。

登录流程里,验证码的校验一般是这样:前端请求验证码接口,后端生成图片并把验证码文本存进 session;用户提交登录时,controller 先比对 session 里的验证码,再走账号密码校验。这个顺序不能反,否则验证码形同虚设。

3. 把源码跑起来:环境、数据库与启动步骤

3.1 环境版本与依赖准备

这套源码是 SSM 技术栈,不是 Spring Boot,所以没有内嵌 Tomcat,需要你自备 Web 容器。常见做法是用 Tomcat 8 或 9,JDK 用 8(SSM 项目对 JDK 8 兼容性最好,上 11 或 17 容易碰到反射和模块化问题)。MySQL 用 5.7 或 8.0 都行,但要注意驱动包版本和连接串参数的差异。

依赖管理看项目用的是 Maven 还是直接放 jar 包。如果是 Maven 项目,pom.xml 里应该有 spring-webmvc、spring-jdbc、mybatis、mybatis-spring、mysql-connector-java、jstl、servlet-api 这些。如果 lib 目录下直接是 jar,那就确认 mysql 驱动版本和你的 MySQL 服务端匹配。

组件推荐版本说明
JDK1.8SSM 项目兼容性最好
Tomcat8.5 / 9.0非 Spring Boot,需外部容器
MySQL5.7 / 8.0注意驱动版本与连接串
Maven3.6+若项目为 Maven 结构

3.2 导入数据库并核对表结构

先建库,再导入 SQL。常见做法是库名和配置文件里保持一致,字符集用 utf8mb4,排序规则用 utf8mb4_general_ci,避免中文菜单名乱码。

-- 创建数据库,字符集要和连接串一致 CREATE DATABASE auth_system DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; -- 导入项目自带的 sql 文件(在命令行执行,不在客户端里跑) -- mysql -u root -p auth_system < auth_system.sql

导入后重点核对三张表:角色表、菜单表、角色菜单关联表。关联表里应该有 role_id 和 menu_id 两个外键字段,可能还有一个 status 或 checked 字段表示开关状态。如果导入后菜单树是空的,先查菜单表有没有数据,再查关联表有没有把角色和菜单绑上。

注意:MySQL 8.0 默认认证插件是 caching_sha2_password,老版本驱动可能连不上,报「Unable to load authentication plugin」。要么升级 mysql-connector-java 到 8.x,要么把用户认证方式改成 mysql_native_password。

3.3 改配置文件里的数据库连接

SSM 项目通常有两个地方要改:一是 jdbc.properties 或 db.properties,二是 spring 的 applicationContext.xml 或 mybatis 配置。找到 jdbc.url、jdbc.username、jdbc.password 这几项,改成你本地的。

# jdbc.properties 常见写法 jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/auth_system?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false jdbc.username=root jdbc.password=你的密码

参数说明:useUnicode 和 characterEncoding 保证中文不乱码;serverTimezone 必须设,否则 MySQL 8 会报时区错误;useSSL=false 是本地开发常用做法,避免 SSL 握手告警。改完确认 driver 类名和你的驱动版本匹配,8.x 用 com.mysql.cj.jdbc.Driver,5.x 用 com.mysql.jdbc.Driver。

3.4 部署到 Tomcat 并验证登录

把项目打成 war 包,或者直接在 IDE 里配置 Tomcat 运行。启动后先访问登录页,看验证码图片能不能出来。验证码出不来,多半是 CaptchaUtil 里的图片输出流或字体路径有问题。能出验证码但登录报错,看控制台有没有 SQL 异常,重点查账号表和角色关联表。

登录成功后,进入权限分配页,确认角色列和菜单行都能动态加载出来。如果表格是空的,打开浏览器开发者工具看接口返回,再顺着 controller → service → dao 查下去。这一步跑通,说明环境、数据库、分层调用链路都没问题,后面改功能才有底。

4. 权限矩阵与树形结构的实现细节

4.1 动态加载角色与菜单生成表格

权限矩阵的核心是「先查角色列表,再查菜单列表,然后渲染成表格」。后端一般提供两个接口:一个返回所有角色,一个返回所有菜单(可能带层级)。前端 JSP 拿到数据后,用循环生成表头和首列,单元格里放 checkbox 或开关组件。

// 前端拿到角色和菜单后,动态拼表格的简化逻辑 function renderMatrix(roles, menus) { // 表头:第一列是菜单名,后面每列一个角色 let head = '<tr><th>菜单</th>'; roles.forEach(r => head += `<th>${r.roleName}</th>`); head += '</tr>'; // 每一行:菜单名 + 各角色对该菜单的权限开关 let body = ''; menus.forEach(m => { body += `<tr><td>${m.menuName}</td>`; roles.forEach(r => { // checked 表示该角色已拥有该菜单 const checked = m.roleIds && m.roleIds.includes(r.id) ? 'checked' : ''; body += `<td><input type="checkbox" ${checked} onchange="toggleAuth(${r.id}, ${m.id}, this.checked)"></td>`; }); body += '</tr>'; }); document.getElementById('matrix').innerHTML = head + body; }

逻辑说明:roles 和 menus 来自后端接口,menus 里每个菜单对象带一个 roleIds 数组,表示哪些角色已经拥有它。toggleAuth 在开关变化时发请求,后端根据 roleId、menuId、checked 三个参数插入或删除关联记录。参数说明:roleId 和 menuId 是联合主键,checked 为 true 走插入,false 走删除。

4.2 角色树与菜单树的增删改整合

角色树和菜单树通常用 zTree 或类似的前端树组件。后端返回带 parentId 的扁平列表,前端组装成树。每个节点上挂增删改按钮,点击时弹窗或行内编辑。

菜单树比角色树多一层复杂度:菜单下面还有按钮。按钮一般作为菜单的子节点,类型字段区分「菜单」和「按钮」。删除菜单时要先判断有没有子菜单或按钮,有就阻止删除,提示先处理子节点。这个校验放在 service 层,不要只靠前端拦。

// service 层删除菜单前的子节点校验(示意) public String deleteMenu(Integer menuId) { int childCount = menuMapper.countByParentId(menuId); if (childCount > 0) { return "存在子菜单或按钮,请先删除子节点"; } menuMapper.deleteById(menuId); // 同时清理角色菜单关联表里的残留记录 roleMenuMapper.deleteByMenuId(menuId); return "success"; }

逻辑说明:先查子节点数量,大于零直接返回失败;删除菜单后必须清理关联表,否则关联表里会留下指向已删菜单的脏记录,下次加载权限矩阵时可能报空指针。参数说明:menuId 是菜单主键,countByParentId 按 parent_id 统计。

4.3 菜单图标与按钮权限的挂载

菜单树支持改图标,通常是给菜单表加一个 icon 字段,存图标类名或图片路径,前端渲染时读取。按钮权限则是在菜单下新增按钮记录,按钮的权限标识(比如user:add、user:delete)存进表里,后端在接口上加拦截或注解校验。

常见做法是用自定义注解加拦截器:在需要权限的 controller 方法上标@RequiresPermission("user:add"),拦截器解析当前登录用户的角色和权限集合,判断是否包含该标识。这样按钮权限就和后端接口校验对上了,不会出现「前端藏了按钮但接口还能调」的漏洞。

5. 避坑与排查:这套源码最容易翻车的几个地方

5.1 验证码图片不显示或登录总提示验证码错误

现象:登录页验证码位置是裂图,或者输入正确验证码仍提示错误。原因通常是 CaptchaUtil 里图片输出流没写好,或者 session 在验证码生成和登录校验之间不是同一个。也有可能是 Tomcat 临时目录权限问题导致图片写不出去。解决:先看浏览器请求验证码接口的响应是不是图片类型,再看后端有没有把验证码文本存进 session;如果是集群或重启后 session 丢失,改成存 Redis 或前端携带 token 校验。

5.2 权限矩阵加载为空或列头错位

现象:进入权限分配页,表格没有角色列或菜单行,或者列头和单元格对不齐。原因多半是接口返回的数据结构和前端渲染逻辑不匹配,比如角色列表返回的是对象数组,前端却按字符串数组遍历;或者菜单的 roleIds 字段名和前端取的不一致。解决:打开开发者工具看接口原始返回,逐字段核对;列头错位通常是表头行和内容行的列数不一致,检查有没有菜单缺少角色关联数据导致少渲染一列。

5.3 删除菜单后权限矩阵报错

现象:删掉一个菜单后,再进权限分配页报空指针或 500。原因是关联表里还留着指向已删菜单的记录,加载时按 menuId 查菜单对象查不到。解决:删除菜单的 service 里必须同步清理关联表,前面 4.2 的代码已经体现了这一点;如果已经产生脏数据,手动执行 delete 语句清理关联表中 menu_id 不在菜单表里的记录。

5.4 MySQL 8 连接报时区或 SSL 错误

现象:启动时报「The server time zone value is unrecognized」或「Establishing SSL connection without server's identity verification is not recommended」。原因是连接串没设 serverTimezone,或者没关 SSL。解决:在 jdbc.url 里加上serverTimezone=Asia/Shanghai&useSSL=false,同时确认驱动版本是 8.x。如果公司要求开 SSL,那就配上 trustCertificateKeyStoreUrl 等参数,本地开发直接关掉最省事。

5.5 中文菜单名乱码

现象:数据库里菜单名正常,页面上显示问号或乱码。原因是数据库字符集、连接串字符集、JSP 页面编码三者不一致。解决:数据库和表用 utf8mb4,连接串加 useUnicode=true&characterEncoding=utf8,JSP 页面顶部设<%@ page contentType="text/html;charset=UTF-8" %>,三处统一后基本不会再乱码。

6. 在源码上做二次开发:加一个「按角色过滤菜单」的进阶技巧

跑通之后,很多人会想在这个权限矩阵上加自己的东西。我拿一个最常见的需求举例:登录后,左侧菜单只显示当前用户有权限的菜单,而不是把所有菜单都列出来。这个功能在毕设答辩里很加分,因为它把「权限分配」和「权限使用」串起来了。

思路分三步。第一步,登录成功后,根据用户所属角色查出角色拥有的菜单 id 集合。第二步,用这个集合过滤完整菜单树,只保留有权限的节点,同时保留父节点,避免树断掉。第三步,把过滤后的菜单树返回给前端渲染。

// 根据当前用户角色过滤菜单树(示意) public List<Menu> getMenusByUser(Integer userId) { // 1. 查用户角色 List<Integer> roleIds = userRoleMapper.findRoleIdsByUserId(userId); // 2. 查这些角色拥有的菜单 id Set<Integer> ownedMenuIds = new HashSet<>(); for (Integer roleId : roleIds) { ownedMenuIds.addAll(roleMenuMapper.findMenuIdsByRoleId(roleId)); } // 3. 查全部菜单,过滤出有权限的,并补全父节点 List<Menu> all = menuMapper.findAll(); List<Menu> result = new ArrayList<>(); for (Menu m : all) { if (ownedMenuIds.contains(m.getId()) || hasOwnedChild(m, all, ownedMenuIds)) { result.add(m); } } return result; }

逻辑说明:先拿角色,再拿菜单 id 集合,然后遍历全部菜单。判断条件是「自己有权限」或者「子节点里有有权限的」,后者保证父菜单不会因为自己没直接授权而消失。参数说明:userId 是当前登录用户,roleIds 可能多个,ownedMenuIds 是去重后的菜单 id 集合。hasOwnedChild 递归检查子节点,实现时注意别写成死循环。

这里有个容易忽略的点:菜单的父节点可能本身没有直接授权,但子节点有。如果只按 ownedMenuIds 过滤,父菜单会被砍掉,前端树就断了。所以补全父节点这一步不能省。另外,按钮权限的过滤逻辑类似,但按钮一般不参与左侧菜单渲染,而是在页面内控制显示隐藏。

验证方法很简单:用两个不同角色的账号登录,看左侧菜单是否不同;再用一个只授权了子菜单的角色登录,看父菜单是否还在、子菜单是否出现。如果父菜单丢了,就是补全逻辑没写对;如果子菜单没出现,检查关联表里有没有这条记录。

从那以后我每次拿到一套权限系统源码,都会先跑一遍「不同角色登录看菜单差异」这个动作,因为它能一次性暴露关联表数据、过滤逻辑、树形渲染三个环节的问题。这套 SSM 权限管理系统源码的价值,不在于它功能多炫,而在于它把权限分配和权限使用这两端都摆出来了,你能顺着改、顺着查、顺着讲。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询