ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Spring Boot毕业设计博客系统:Shiro权限+MyBatis-Plus+动态菜单

Spring Boot毕业设计博客系统:Shiro权限+MyBatis-Plus+动态菜单 简介这是一套基于Spring Boot开发的前后台分离博客系统专为计算机专业本科生毕业设计提供完整参考方案涵盖系统实现、技术选型与设计报告全流程。资源包含可直接运行的源码、MySQL数据库脚本及配套文档适用于Java Web课程设计、毕设快速启动与Shiro权限管理实践学习。压缩包共2000个文件44.8MB其中Java类文件371个含Controller、Service、Mapper层结构清晰HTML模板485个Thymeleaf渲染页、JS脚本698个支撑前台交互与后台管理功能CSS样式186个含Semantic UI、Element UI、Bootstrap等多框架整合另有XML配置、SQL脚本与Properties配置文件体现典型企业级分层架构。目前已有75人学习下载读者可获得完整角色权限体系含用户/管理员双端菜单与导航配置、分类标签管理模块、友情链接前台展示能力以及Quartz定时任务、Druid连接池、Fastjson工具集成等进阶实践细节具备良好的教学示范性与工程复用价值。1. 这不是又一个“Hello World”博客而是一套能过答辩、可上线、带完整权限链的 Spring Boot 毕业设计系统你手头正赶着计算机专业毕业设计 deadline导师要求“有前后台、有用户角色、有数据库设计、能跑通、最好还能写进设计报告”但网上搜到的所谓“Spring Boot 博客系统”要么只有前台静态页面要么后台连登录校验都靠 session 硬编码更别说菜单动态加载、分类/标签树形管理、友情链接热更新这些答辩常问点。这套资源不是 Demo而是按真实项目结构组织的可交付系统它用 Apache Shiro 实现 RBAC 权限模型管理员/编辑/游客三级用 MyBatis-Plus 自动生成 CRUD 并支持逻辑删除Thymeleaf 模板层与后台 Controller 完全解耦连style.min.css和semantic.min.css都做了版本分离——说明前端资源已走构建流程。它不教你怎么配 JDK而是直接给出mysql5.7下可执行的建表 SQL 和初始化数据不讲 Spring Boot 基础概念而是把Quartz定时任务嵌在博客统计模块里每天凌晨自动刷新热门文章排行。适合正在写开题报告、做 ER 图、画系统流程图、填功能模块表的应届生也适合需要快速验证权限控制逻辑的初级开发。2. 权限体系落地Shiro 自定义 Realm 动态菜单加载的三重实现2.1 为什么选 Shiro 而非 Spring Security在毕业设计场景下Shiro 的轻量级和显式权限控制逻辑更利于答辩展示。Spring Security 的自动配置虽强但PreAuthorize(hasRole(ADMIN))这类注解背后是复杂的代理机制和 AOP 切面学生调试时容易卡在AccessDeniedException却找不到拦截点而 Shiro 的Subject.isPermitted(blog:delete)是直白的方法调用配合自定义Realm可清晰看到“用户 → 角色 → 权限字符串”的映射链条。本系统中ShiroConfig.java显式配置了SecurityManager、ShiroFilterFactoryBean和CredentialsMatcher其中ShiroFilterFactoryBean的filterChainDefinitionMap定义了/admin/**authc,roles[admin]这类路径级权限比注解方式更易在设计报告中画出权限矩阵表。2.2 自定义 Realm 的核心逻辑与数据库关联BlogRealm.java继承AuthorizingRealm重写doGetAuthenticationInfo和doGetAuthorizationInfo。前者通过UserMapper.selectByUsername(username)查询用户用ByteSource.Util.bytes(salt)处理密码盐值返回SimpleAuthenticationInfo后者调用RoleMapper.selectRolesByUserId(userId)获取角色列表再通过PermissionMapper.selectPermissionsByRoleId(roleId)关联查询权限字符串如blog:list、menu:edit。关键点在于权限字符串不是硬编码而是从数据库sys_permission表读取该表字段含permission_code唯一标识、permission_name中文名、url_pattern匹配路径和parent_id支持菜单层级。这样答辩时可演示“新增一个‘标签管理’权限 → 分配给编辑角色 → 前台菜单实时刷新”的完整闭环。// BlogRealm.java 中 doGetAuthorizationInfo 方法节选 Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { String username (String) principals.getPrimaryPrincipal(); SimpleAuthorizationInfo info new SimpleAuthorizationInfo(); // 1. 查询用户角色 ListRole roles roleMapper.selectRolesByUserName(username); SetString roleSet roles.stream().map(Role::getRoleCode).collect(Collectors.toSet()); info.addRoles(roleSet); // 2. 根据角色查权限支持多角色叠加 SetString permissionSet new HashSet(); for (Role role : roles) { ListPermission perms permissionMapper.selectPermissionsByRoleId(role.getId()); permissionSet.addAll(perms.stream().map(Permission::getPermissionCode).collect(Collectors.toList())); } info.addStringPermissions(permissionSet); return info; }提示permission_code必须与 Controller 方法上的RequiresPermissions(blog:publish)严格一致大小写敏感。若数据库中存的是BLOG:PUBLISH而代码写成blog:publishShiro 将静默拒绝访问日志中仅显示UnauthorizedException需检查logback-spring.xml中org.apache.shiro日志级别设为DEBUG才能看到权限校验失败详情。2.3 前台动态菜单渲染Thymeleaf Shiro 标签的协同菜单数据不写死在 HTML 里而是由MenuController.java的getMenuTree()方法返回 JSON 树结构前端通过th:each遍历。关键在于使用 Shiro 的shiro:hasPermission标签控制菜单可见性!-- admin/layout.html -- div classui vertical menu th:fragmentsidebar a href/admin classitem th:classappend${#strings.equals(menuPath,/admin)} ? active : i classhome icon/i 首页 /a !-- 遍历后台查询的菜单列表 -- div th:eachmenu : ${menus} th:if${#strings.length(menu.permissionCode) 0} !-- 仅当用户有该权限才渲染此菜单项 -- shiro:hasPermission name${menu.permissionCode} a th:href{${menu.url}} classitem th:classappend${#strings.contains(menuPath, menu.url)} ? active : i th:class${menu.icon}/i span th:text${menu.name}菜单名/span /a /shiro:hasPermission /div /divMenuService.java中的buildMenuTree()方法递归组装树形结构其 SQL 使用 MyBatis-Plus 的QueryWrapper构建条件wrapper.eq(status, 1).orderByAsc(sort_order)确保启用状态且按排序序号升序。sys_menu表设计包含id,parent_id,name,url,icon,permission_code,sort_order,status字段其中parent_id0表示一级菜单status1表示启用。这种设计让答辩时可现场修改数据库sys_menu.status字段立即看到前台菜单消失或出现直观证明权限控制有效性。3. 博客核心模块MyBatis-Plus 分页 文章富文本存储 分类/标签树形管理3.1 文章表设计与富文本处理策略blog_article表采用contentLONGTEXT存储 Markdown 源码content_htmlLONGTEXT存储渲染后的 HTML避免每次请求都调用CommonMark解析器。create_time和update_time字段由 MyBatis-Plus 的TableField(fill FieldFill.INSERT)和TableField(fill FieldFill.INSERT_UPDATE)自动填充is_top置顶、is_recommend推荐、status草稿/发布/删除字段支撑后台审核流。关键技巧ArticleMapper.xml中的selectListWithCategoryAndTag查询使用resultMap映射关联的Category和Tag对象而非简单JOIN防止 N1 查询问题!-- ArticleMapper.xml -- resultMap idArticleWithCategoryTagMap typecom.example.blog.entity.Article id propertyid columnid/ result propertytitle columntitle/ result propertycontent columncontent/ association propertycategory javaTypecom.example.blog.entity.Category id propertyid columncategory_id/ result propertyname columncategory_name/ /association collection propertytags ofTypecom.example.blog.entity.Tag id propertyid columntag_id/ result propertyname columntag_name/ /collection /resultMap select idselectListWithCategoryAndTag resultMapArticleWithCategoryTagMap SELECT a.*, c.id as category_id, c.name as category_name, t.id as tag_id, t.name as tag_name FROM blog_article a LEFT JOIN sys_category c ON a.category_id c.id LEFT JOIN blog_article_tag at ON a.id at.article_id LEFT JOIN sys_tag t ON at.tag_id t.id WHERE a.status 1 ORDER BY a.create_time DESC /select3.2 分类与标签的树形结构与多对多关系sys_category表支持无限级分类id,parent_id,name,code,sort_order,status。CategoryService.java的listCategoryTree()方法用递归查询构建树SQL 中WITH RECURSIVEMySQL 8.0或 Java 层递归兼容 MySQL 5.7均可实现。标签sys_tag是扁平表但通过blog_article_tag关联表实现文章与标签多对多。后台TagController提供saveBatch接口接收articleId和tagNames数组先根据tagNames查询或创建标签再批量插入关联记录// TagController.java PostMapping(/batchSave) ResponseBody public Result batchSave(RequestParam Long articleId, RequestBody ListString tagNames) { ListTag tags new ArrayList(); for (String tagName : tagNames) { Tag tag tagMapper.selectOne(new QueryWrapperTag().eq(name, tagName)); if (tag null) { tag new Tag(); tag.setName(tagName); tag.setStatus(1); tagMapper.insert(tag); } tags.add(tag); } // 清空旧关联 articleTagMapper.delete(new QueryWrapperArticleTag().eq(article_id, articleId)); // 插入新关联 ListArticleTag articleTags tags.stream() .map(t - { ArticleTag at new ArticleTag(); at.setArticleId(articleId); at.setTagId(t.getId()); return at; }) .collect(Collectors.toList()); articleTagMapper.insertBatchSomeColumn(articleTags); return Result.success(); }注意articleTagMapper.insertBatchSomeColumn()是 MyBatis-Plus 3.4 的批量插入方法比循环insert性能提升 5 倍以上。若使用旧版 MyBatis-Plus需改用executeBatch或foreach标签。3.3 前台分页与搜索的性能优化前台/blog/list接口使用PageArticle分页但ArticleMapper.selectListWithCategoryAndTag在大数据量下会因LEFT JOIN导致慢查询。解决方案先分页查blog_article主表 ID再用IN子查询关联分类和标签// ArticleService.java public IPageArticle pageWithCategoryAndTag(IPageArticle page, QueryWrapperArticle wrapper) { // 第一步分页查主表ID IPageLong idPage articleMapper.selectPage(page, wrapper.select(id)); // 第二步查出ID列表 ListLong ids idPage.getRecords(); if (ids.isEmpty()) return new Page(page.getCurrent(), page.getSize()); // 第三步用IN查询完整数据避免JOIN QueryWrapperArticle fullWrapper new QueryWrapper(); fullWrapper.in(id, ids); ListArticle articles articleMapper.selectList(fullWrapper); // 第四步批量查询分类和标签N1优化 MapLong, Category categoryMap categoryMapper.selectBatchIds( articles.stream().map(Article::getCategoryId).filter(Objects::nonNull).collect(Collectors.toList()) ).stream().collect(Collectors.toMap(Category::getId, c - c)); // 返回分页对象手动设置records PageArticle resultPage new Page(page.getCurrent(), page.getSize(), idPage.getTotal()); resultPage.setRecords(articles); return resultPage; }4. 前台展示与 SEO 适配Thymeleaf 模板继承 静态资源分离 友情链接热更新4.1 Thymeleaf 模板继承体系与 SEO 元信息注入templates/layout.html定义基础布局templates/blog/index.html通过th:replace~{layout :: content}继承并在head中使用th:fragmentseo注入动态元信息!-- layout.html -- head th:fragmenthead meta charsetUTF-8/ title th:text${title} ?: 我的博客博客标题/title meta namekeywords th:content${keywords} ?: java,springboot,毕业设计/ meta namedescription th:content${description} ?: 基于Spring Boot的个人博客系统/ link relstylesheet th:href{/css/semantic.min.css}/ link relstylesheet th:href{/css/style.min.css}/ /head!-- blog/index.html -- div th:replace~{layout :: head(title首页, keywordsspringboot博客,java毕业设计, description基于Spring Boot开发的前后台博客系统含完整权限管理与数据库设计)}/div这种写法让每页可独立设置title/keywords/description满足答辩对 SEO 基础能力的要求。style.min.css和semantic.min.css分离存放便于在application.yml中配置spring.resources.static-locationsclasspath:/static/,file:/opt/blog/static/实现生产环境静态资源外挂。4.2 友情链接的缓存与热更新机制友情链接数据存在sys_friend_link表字段含name,url,logo,sort_order,status。为避免每次请求都查库FriendLinkService.java使用Cacheable(value friendLinks, key all)缓存全部启用链接。关键点在于CacheEvict的触发时机当后台执行updateStatus或save时必须清除缓存Service public class FriendLinkService { Cacheable(value friendLinks, key all) public ListFriendLink listAll() { return friendLinkMapper.selectList(new QueryWrapperFriendLink().eq(status, 1).orderByAsc(sort_order)); } CacheEvict(value friendLinks, key all) public void updateStatus(Long id, Integer status) { FriendLink link new FriendLink(); link.setId(id); link.setStatus(status); friendLinkMapper.updateById(link); } CacheEvict(value friendLinks, key all) public void save(FriendLink link) { friendLinkMapper.insert(link); } }前台blog/sidebar.html通过th:eachlink : ${friendLinkService.listAll()}直接调用服务方法Thymeleaf 在渲染时自动触发缓存逻辑。答辩时可演示修改数据库sys_friend_link.status为0→ 刷新前台侧边栏 → 链接消失再改回1→ 再次刷新 → 链接重现全程无需重启应用。4.3 导航配置的 JSON 驱动与前端解析导航栏内容不写死而是由sys_nav_config表存储 JSON 字符串如{home:首页,about:关于,contact:联系}。NavConfigService.java提供getNavConfig()方法解析 JSONpublic MapString, String getNavConfig() { NavConfig config navConfigMapper.selectOne(new QueryWrapperNavConfig().eq(code, main_nav)); if (config ! null StringUtils.isNotBlank(config.getConfigJson())) { try { return new ObjectMapper().readValue(config.getConfigJson(), new TypeReferenceMapString, String() {}); } catch (JsonProcessingException e) { log.error(解析导航配置JSON失败, e); } } return Collections.emptyMap(); }前台模板用th:eachentry : ${navConfigService.getNavConfig().entrySet()}遍历a th:href{/${entry.key}}生成链接。这种设计让答辩老师提问“如何快速增加‘作品集’导航项”时你只需在数据库sys_nav_config.config_json字段追加,portfolio:作品集无需改代码、无需重启立刻生效。5. 毕业设计交付包实操数据库初始化、IDEA 配置要点与设计报告素材提取5.1 数据库初始化脚本执行与常见报错修复提供的blog_db.sql文件需在 MySQL 5.7 执行。常见错误及修复错误ERROR 1071 (42000): Specified key was too long因utf8mb4字符集下索引长度超限。解决在my.cnf中添加innodb_large_prefixON并确保innodb_file_formatBarracuda。错误Unknown column create_time in field listSQL 文件中blog_article表缺少create_time datetime DEFAULT CURRENT_TIMESTAMP字段。手动补全ALTER TABLE blog_article ADD COLUMN create_time datetime DEFAULT CURRENT_TIMESTAMP, ADD COLUMN update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;Shiro 登录后跳转 404检查ShiroConfig.java中shiroFilterFactoryBean.setLoginUrl(/login)是否与LoginController.login()的RequestMapping(/login)匹配且login.html模板位于templates/login.html。5.2 IDEA 项目导入关键配置JDK 版本File → Project Structure → Project → SDK 设置为1.8不可用 11因 Shiro 1.4.x 不完全兼容。Maven 依赖pom.xml中spring-boot-starter-thymeleaf版本需与 Spring Boot 2.3.x 匹配本系统用2.3.12.RELEASE若自动下载失败手动在 Maven Repositories 中添加阿里云镜像mirror idaliyunmaven/id mirrorOf*/mirrorOf nameAliyun Maven/name urlhttps://maven.aliyun.com/repository/public/url /mirror运行配置Edit Configurations → Spring Boot → Main class 选择BlogApplication.javaVM options 添加-Dfile.encodingUTF-8Environment variables 设置SPRING_PROFILES_ACTIVEdev。5.3 设计报告可直接复用的技术点表格报告章节可直接引用的系统实现细节对应源码位置系统架构设计采用 B/S 架构后端 Spring Boot 2.3.x Shiro 1.4.x MyBatis-Plus 3.4.x前端 Thymeleaf Semantic UIpom.xml,ShiroConfig.java数据库设计blog_article表含content(Markdown) 和content_html(渲染后) 字段支持富文本双存储策略blog_article.sql,Article.java权限模块设计RBAC 模型用户-角色-权限三级映射权限字符串如blog:delete存于sys_permission表sys_permission.sql,BlogRealm.java功能模块描述友情链接支持缓存Cacheable与热更新CacheEvict修改数据库即生效FriendLinkService.java,sys_friend_link.sql系统测试用例登录测试输入错误密码返回login?error权限测试编辑角色访问/admin/menu返回 403LoginController.java,ShiroConfig.java提示答辩 PPT 中“关键技术难点”页可截图BlogRealm.java的doGetAuthorizationInfo方法圈出info.addStringPermissions(permissionSet)行标注“权限字符串动态加载非硬编码支持后台配置”。5.4 定时任务 Quartz 的实际应用场景BlogStatisticsJob.java实现每日凌晨 2 点执行的博客统计任务更新blog_article.view_count和sys_category.article_count。其Scheduled(cron 0 0 0 2 * ?)注解需配合EnableScheduling启用。关键点在于任务中使用Transactional保证统计更新原子性且查询时加FOR UPDATE锁防止并发更新Component public class BlogStatisticsJob { Scheduled(cron 0 0 0 2 * ?) // 每天凌晨2点 Transactional public void updateStatistics() { // 更新热门文章按阅读量TOP10 ListArticle topArticles articleMapper.selectList( new QueryWrapperArticle().orderByDesc(view_count).last(LIMIT 10) ); // 更新分类文章数子查询效率高 articleMapper.updateCategoryCount(); } }articleMapper.updateCategoryCount()对应 XML 中的UPDATE sys_category c SET article_count (SELECT COUNT(*) FROM blog_article a WHERE a.category_id c.id)。此任务在答辩中可作为“系统自动化能力”的佐证说明系统具备后台运维思维不止于 CRUD。本文还有配套的精品资源点击获取
返回列表