资讯动态

基于SpringBoot的校园论坛系统设计与实现:从JWT鉴权到Redis热帖排行

发布时间:2026/9/11 18:23:00 来源:尧图企业网站定制
简介一套面向高校毕业设计场景的校园论坛系统项目采用Java与Spring Boot技术栈实现覆盖用户注册登录、帖子发布、评论互动、信息检索、权限管理等论坛核心功能适合计算机相关专业学生用于毕业设计、课程作业或Spring Boot入门实践参考。压缩包共1764个文件体积约26.18MB包含138个Java源文件、137个class编译文件、100个Vue组件、306个JS脚本以及图片、样式和数据库脚本等源码层次清晰前后端结构完整。资源同时配有毕业论文文档与演示PPT可辅助理解需求分析、系统设计、功能实现与测试等完整开发流程内容预览显示部分前端页面带有备份文件便于对照修改与排错。目前已有95人学习下载适合需要快速搭建校园论坛项目或借鉴毕设框架的开发者。1. springBoot 校园论坛系统的选题边界不是写个 CRUD 那么简单“校园论坛”作为 Java 毕设选题第一眼看去很简单用户注册、发帖、评论、删帖无非是 CRUD。真把需求拆开看它还要处理登录鉴权、评论层级、点赞收藏、浏览量计数、热帖排序、关键词搜索以及普通用户和管理员两种角色的权限差异。每一块都能对应到 springBoot 的拦截器、事务、Redis、定时任务论文里的“设计”和“亮点”都从这些点长出来。下面按技术选型、数据库设计、后端接口、缓存与排行、论文 PPT 的顺序把整套方案走一遍适合正在做这套选题的学生也适合想快速搭一套论坛后端的 Java 从业者。你把它在本地跑通之后这套骨架稍加改造就能变成社区、博客、问答系统的底子。2. 基于 springBoot 校园论坛的技术选型与分层架构先把可扩展性立住2.1 为什么不用 SSM 而选 springBoot版本与 JDK 的搭配关系论坛这种业务不复杂、但涉及面很广的系统用 springBoot 比 SSM 合适得多。springBoot 把 web 容器、数据源、事务、Jackson、参数校验自动配置好一个spring-boot-starter-web就能让 Controller 跑起来SSM 里那一大堆 XML 配置放到论文里既难讲清答辩老师也会觉得你还在用上一代玩法。技术选型里最容易被忽略的是版本组合。实验室电脑装的多是 JDK 8对应 springBoot 2.7.x 最顺springBoot 3.x 强制要求 JDK 17很多新手开场就卡在“springboot 版本太高”导致的启动失败。下表是做这类毕设时的默认组合也可以当核对清单用关注点推荐选型理由基础框架springBoot 2.7.18JDK 8或 3.2.xJDK 17环境兼容优先3.x 新版特性对论坛业务没有质变ORMMyBatis-Plus 3.5.x单表 CRUD 不用写 SQL自带分页插件数据库MySQL 8.0utf8mb4 支持评论里的表情符号缓存Redis 6/7验证码、浏览量、热帖排行都靠它鉴权jjwt 0.11.5 拦截器无状态前后端分离演示方便接口文档Knife4j 4.x自动生成调试页面论文截图直接用构建工具Maven 3.8依赖树直观答辩问依赖关系好回答密码加密springSecurity-crypto只取 BCryptPasswordEncoder不引入整套安全框架代码简化Lombok 1.18.x实体和 DTO 的 getter/setter 不再手写如果电脑还没配好 Java 命令先确认 JAVA_HOME 和 PATH 都指到同一个 JDKIDE 里的 Project SDK 也选同一版本这是 springBoot 项目能启动的前提。用 IDEA 创建项目时如果 start.spring.io 一直超时把 Server URL 换成国内镜像源别在初始化这一步耗掉太多时间。2.2 分层架构与统一返回体答辩讲“设计”时有事可说包结构按职责切别一锅端。controller 层只做参数接收和结果包装service 层写事务和业务规则mapper 层只碰数据访问com.campus.forum ├── config // 拦截器、Redis、Knife4j 配置 ├── controller // 接收参数调 service返回 Result ├── service // 业务逻辑事务边界在这里 ├── mapper // 继承 MyBatis-Plus 的 BaseMapper ├── entity // 与表一一对应的实体 ├── dto / vo // 入参对象、出参视图对象 └── common // Result 统一返回体、全局异常、常量统一返回体是论坛这类前后端分离项目的地基。所有接口返回相同结构前端只需要处理 code、message、data 三个字段Data public class ResultT { private Integer code; // 200 成功400 参数错误401 未登录403 无权限500 异常 private String message; private T data; public static T ResultT ok(T data) { ResultT r new Result(); r.code 200; r.message ok; r.data data; return r; } public static T ResultT fail(Integer code, String message) { ResultT r new Result(); r.code code; r.message message; return r; } }把统一返回体配上RestControllerAdvice全局异常处理器SQL 异常、自定义业务异常就不会穿到浏览器里变成一堆英文堆栈。业务异常要定义成BusinessException extends RuntimeException抛出的位置在 service捕获的位置在全局事务不会因为异常被吞掉而出现半完成状态。这段代码在论文的“系统设计”章节值得单独占一小节。2.3 springBoot 配置文件要点多环境切换与敏感信息占位application.yml 是 springBoot 最常被考到的配置面之一。论坛项目至少要把数据源、Redis、MyBatis-Plus、JWT 四块拆开写server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/campus_forum?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: ${DB_PASSWORD:123456} redis: host: localhost port: 6379 timeout: 3000ms mybatis-plus: configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0 jwt: secret: ${JWT_SECRET:campus-forum-secret-key-change-me} expire-days: 7配置文件里的参数分三类理解数据库连接参数决定能不能连上库注意 MySQL 8 必须带serverTimezone否则会报时区错误MyBatis-Plus 的驼峰映射把category_id自动转成categoryId不开这个配置实体属性名就要被迫用下划线logic-delete-field声明逻辑删除字段后每次 delete 都会自动改成 update。密码这类敏感信息用${DB_PASSWORD:123456}从环境变量注入默认值只用于本地开发。想再展示工程能力可以引入 jasypt-spring-boot-starter 做 yml 密文加密答辩时这是一个可讲的独立亮点。SQL 日志StdOutImpl只在本地开生产环境要关掉。正规做法是建application-dev.yml和application-prod.yml启动时用--spring.profiles.activeprod指定用哪套。注意如果你用的是 springBoot 3.xRedis 配置前缀要写成spring.data.redis这是 2.7 和 3.x 之间的一个可见差异。3. 校园论坛系统数据库设计从 ER 图到核心表的建表 SQL3.1 ER 图怎么画六张表和三组关系数据库设计是论文里最能拉开差距的部分。论坛最核心的 ER 关系是用户-帖子-评论这条主线一个用户可以发布多篇帖子一篇帖子可以被多个用户评论一条评论还能回复另一条评论。点赞和收藏是用户与帖子的多对多关系靠中间表拆掉。再加上一个分类表挂到帖子上六张核心表就齐了user、category、post、comment、like_record、post_collect。实体间关系在 ER 图里画成三组user 与 post 是 1 对 Npost 与 comment 是 1 对 Ncomment 与 comment 是自关联 1 对 Nuser 与 post 的 M 对 N 通过 like_record、post_collect 分解成两个 1 对 N。这里有一个毕业设计里的常见误区为了画图好看去建数据库外键约束。我一般不建物理外键外键联系在 service 层用user_id去校验因为论坛删除操作多物理外键会拖慢删除并容易触发死锁。论文里可以写一句“逻辑外键 应用层校验避免数据库层面的级联限制”这个表述比空谈三大范式更落地。ER 图也是 Java 面试会被追问的点考官一般关心两件事多对多关系怎么拆分、删除时哪些数据保留。用户即使被封禁他发过的帖子也要在所以 user 与 post 之间不是级联删除而是通过status字段让帖子变为“不可见但不物理删除”。3.2 核心表 DDL字段、索引与字符集建表统一用 utf8mb4排序规则用utf8mb4_general_ci即可。下面是 user 和 post 两张最核心表的 DDL其余表的结构要点在代码块后面单独说明CREATE DATABASE IF NOT EXISTS campus_forum DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE campus_forum; CREATE TABLE user ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 用户ID, username VARCHAR(30) NOT NULL COMMENT 登录名, password VARCHAR(100) NOT NULL COMMENT BCrypt 密文, nickname VARCHAR(30) DEFAULT COMMENT 昵称, avatar VARCHAR(255) DEFAULT COMMENT 头像URL, role TINYINT NOT NULL DEFAULT 0 COMMENT 0 普通用户 1 管理员, status TINYINT NOT NULL DEFAULT 1 COMMENT 1 正常 0 封禁, deleted TINYINT NOT NULL DEFAULT 0 COMMENT 逻辑删除标记, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 注册时间, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB COMMENT用户表; CREATE TABLE post ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 帖子ID, user_id BIGINT UNSIGNED NOT NULL COMMENT 作者ID, category_id BIGINT UNSIGNED NOT NULL COMMENT 分类ID, title VARCHAR(100) NOT NULL COMMENT 标题, content TEXT NOT NULL COMMENT 正文存 Markdown 或富文本, view_count INT UNSIGNED NOT NULL DEFAULT 0 COMMENT 浏览量, like_count INT UNSIGNED NOT NULL DEFAULT 0 COMMENT 点赞数, comment_count INT UNSIGNED NOT NULL DEFAULT 0 COMMENT 评论数, top_flag TINYINT NOT NULL DEFAULT 0 COMMENT 1 置顶, status TINYINT NOT NULL DEFAULT 1 COMMENT 1 正常 0 待审 2 拒绝, deleted TINYINT NOT NULL DEFAULT 0, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_category_time (category_id, create_time), KEY idx_user_time (user_id, create_time) ) ENGINEInnoDB COMMENT帖子表;字段设计里三个计数冗余字段view_count、like_count、comment_count是有意为之的反规范化。列表页要显示浏览量、点赞数、评论数如果每个都去 count 子表一次列表请求会产生大量 SQL。用冗余字段换查询性能靠事务或定时任务保证计数一致性这是论坛系统的常见做法。post.title限制在 100 字符是为了列表页布局不换行service 层也要做同样的校验不能只在数据库里做限制。comment 和 like_record 两张表按同样的思路建CREATE TABLE comment ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, post_id BIGINT UNSIGNED NOT NULL COMMENT 所属帖子, user_id BIGINT UNSIGNED NOT NULL COMMENT 评论人, parent_id BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT 0 为根评论否则指向父评论, reply_to_user_id BIGINT UNSIGNED DEFAULT NULL COMMENT 被回复人用于前端显示 xxx, content VARCHAR(500) NOT NULL COMMENT 评论内容, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_post_time (post_id, create_time) ) ENGINEInnoDB COMMENT评论表; CREATE TABLE like_record ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, user_id BIGINT UNSIGNED NOT NULL, post_id BIGINT UNSIGNED NOT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_user_post (user_id, post_id) ) ENGINEInnoDB COMMENT点赞记录表;评论表用parent_id与reply_to_user_id两个字段配合既支持“回复某人的评论”又不会把层级结构写得过于复杂。parent_id 0表示根评论回复时前端传父评论 ID后端查出父评论的作者填到reply_to_user_id展示层面就拿到了“某人”需要的数据。like_record的联合唯一键是关键它从数据库层面保证同一用户对同一帖子只能点一次赞service 层只需要判断“插入成功还是冲突”就能完成点赞与取消点赞的切换。post_collect 表的结构与 like_record 一致单独建表是因为收藏是“随时可查”的数据不应和点赞混在一个表里。索引方面idx_post_time (post_id, create_time)支撑帖子详情页评论分页uk_user_post (user_id, post_id)支撑“我赞过的帖”查询加完之后建索引的意图要能对应到具体查询。4. 基于 springBoot 校园论坛的后端实现登录鉴权、发帖与评论接口4.1 Java 端实现 JWT 登录与 springBoot 拦截器登录流程是用户提交 username 和 passwordservice 层用 BCrypt 校验后生成 JWT 返回前端前端之后每个请求在 header 里带Authorization: Bearer token拦截器解析 token把 userId 放进 ThreadLocal。这个方案无状态适合前后端分离演示论文里也好画时序图。生成 token 的工具类核心是把用户身份信息编码进 tokenComponent public class JwtUtil { Value(${jwt.secret}) private String secret; Value(${jwt.expire-days}) private int expireDays; public String createToken(Long userId, String role) { // 只放 userId 和 role不放密码、手机号 return Jwts.builder() .setSubject(String.valueOf(userId)) .claim(role, role) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() expireDays * 24L * 3600 * 1000)) .signWith(Keys.hmacShaKeyFor(secret.getBytes())) .compact(); } public Claims parseToken(String token) { return Jwts.parserBuilder() .setSigningKey(Keys.hmacShaKeyFor(secret.getBytes())) .build() .parseClaimsJws(token) .getBody(); } }这段代码有三个参数要理解secret是签名密钥本地默认值只供开发部署时必须用环境变量覆盖长度不要小于 32 个字符否则 HS256 的强度形同虚设expire-days决定 token 有效期论坛项目 7 天比较合理太短让用户频繁登录太长又难处理封禁场景claim里只放必要身份信息因为 JWT 是 base64 编码的放了手机号等于明文暴露。拦截器负责识别“当前请求是谁”Component public class LoginInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { if (OPTIONS.equals(request.getMethod())) { return true; } String token request.getHeader(Authorization); if (token null || !token.startsWith(Bearer )) { throw new BusinessException(401, 未登录); } // 解析失败由全局异常捕获统一返回 401 Claims claims JwtUtil.parseToken(token.replace(Bearer , )); UserContext.set(Long.valueOf(claims.getSubject()), claims.get(role, String.class)); return true; } }拦截器里最容易漏的是 OPTIONS 预检请求。前端跨域请求会先发一个 OPTIONS这个请求头上没有 Authorization不先放行会导致业务接口根本进不来。注册拦截器时用addInterceptor(loginInterceptor).addPathPatterns(/api/**).excludePathPatterns(/api/auth/login, /api/auth/register)放行登录注册接口其余/api/**全走鉴权。拦截器和过滤器的区别是 springBoot 面试题的常客过滤器在 Servlet 容器层能拿到原始请求适合做编码和 CORS拦截器在 HandlerMapping 之后能拿到 handler适合做登录鉴权和权限判断。项目里把 CORS 放在 config 层用拦截器只做 token 鉴权两层职责分开。密码校验用BCryptPasswordEncoder.matches(rawPassword, encodePassword)不要在 controller 里比对明文这是最基础但容易被答辩老师挑出来的点。参数相关建议值整理如下参数建议值说明jwt.secret至少 32 个字符通过环境变量注入不硬编码在 ymljwt.expire-days7折中登录体验和封禁时效Authorization 头Bearer token拦截器统一从这个头读取OPTIONS 请求直接放行否则 CORS 预检会失败4.2 发帖与帖子列表的 service 层事务、分页与参数校验发帖的业务规则集中在 service 层核心是“先校验再写入失败就回滚”。分类不存在、标题超长、正文为空都要在这里拦下来Service RequiredArgsConstructor public class PostService { private final PostMapper postMapper; private final CategoryMapper categoryMapper; Transactional(rollbackFor Exception.class) public Long createPost(PostCreateDTO dto, Long userId) { Category category categoryMapper.selectById(dto.getCategoryId()); if (category null) { throw new BusinessException(400, 发帖分类不存在); } if (dto.getTitle().trim().length() 100) { throw new BusinessException(400, 标题不能超过100字); } if (dto.getContent().trim().isEmpty()) { throw new BusinessException(400, 正文不能为空); } Post post new Post(); post.setUserId(userId); post.setCategoryId(dto.getCategoryId()); post.setTitle(dto.getTitle().trim()); post.setContent(dto.getContent()); post.setStatus(1); postMapper.insert(post); return post.getId(); } }Transactional(rollbackFor Exception.class)指定遇到所有异常都回滚比默认只回滚 RuntimeException 更稳妥。为什么这个参数要显式写如果 service 里抛的是检查异常不写这个参数事务不会回滚刚插入的脏帖子就留下了。这里的userId不是前端传的是从 UserContextThreadLocal里取的防止伪造身份发帖这个设计在论文里要写清楚。列表接口用 MyBatis-Plus 分页public IPagePostVO pagePosts(Long categoryId, int pageNum, int pageSize) { LambdaQueryWrapperPost wrapper new LambdaQueryWrapper(); wrapper.eq(categoryId ! null, Post::getCategoryId, categoryId) .eq(Post::getStatus, 1) .orderByDesc(Post::getTopFlag) .orderByDesc(Post::getCreateTime); PagePost page new Page(pageNum, pageSize); IPagePost postPage postMapper.selectPage(page, wrapper); return convertToVO(postPage); // 批量查用户昵称、分类名后组装 }分页插件的三个参数要注意pageNum从 1 开始前端传 0 时会查出第一条但总数对不上service 层做pageNum Math.max(1, pageNum)兜底pageSize要限制上限论坛列表 20 条一页最合适别让前端传 1000排序用top_flag置顶优先、再按create_time倒序列表才符合用户直觉。组装 VO 时避免在循环里逐条查用户表先把 user_id 收集成集合一次selectBatchIds查回后按 ID 分组拼接。4.3 评论的嵌套回复parent_id 与 reply_to_user_id 配合评论模块最常见的实现是无限层级树但校园论坛用不到那么深。一般限制为两级根评论下面挂回复回复不能再回复。数据结构上用parent_id指向直接父评论再用reply_to_user_id记录被回复的人前端渲染时显示“某人”。Transactional(rollbackFor Exception.class) public Long addComment(CommentCreateDTO dto, Long userId) { Post post postMapper.selectById(dto.getPostId()); if (post null || post.getStatus() ! 1) { throw new BusinessException(404, 帖子不存在或不可评论); } Comment comment new Comment(); comment.setPostId(dto.getPostId()); comment.setUserId(userId); comment.setContent(dto.getContent().trim()); comment.setParentId(dto.getParentId() null ? 0L : dto.getParentId()); if (comment.getParentId() ! 0L) { Comment parent commentMapper.selectById(comment.getParentId()); if (parent null) { throw new BusinessException(400, 被回复的评论已删除); } comment.setReplyToUserId(parent.getUserId()); } commentMapper.insert(comment); postMapper.increaseCommentCount(dto.getPostId(), 1); return comment.getId(); }这里有两个并发处理点。帖子评论数增加不能写成“先 select 再 set”否则两个用户同时评论时后写的人会把前一个人的 1 覆盖掉。increaseCommentCount的 SQL 用UPDATE post SET comment_count comment_count 1 WHERE id ?数据库自己处理原子加一。点赞、浏览量的计数也都要用这种 SQL 模式。另一个点是 null 判断要写在前前端不传parent_id时默认为 0表示根评论如果parent_id传了但父评论查不到说明评论已被删除直接返回 400不能让新评论挂到一个不存在的父节点上。5. 校园论坛的“热帖排行”与缓存Redis 里到底放什么5.1 用 Redis ZSET 算热帖得分权重和时间衰减怎么配论坛首页一般都会放“热门帖子”这个需求看似简单但直接把数据库按 view_count 排序会让老帖子永远霸榜。常见做法是在 Redis 用 ZSET 存热点排行member 是 postIdscore 是综合得分利用 Redis 的 score 排序天然支持热榜。得分公式不要拍脑袋要能说清每个参数的含义。这里使用score (view_count like_count * 3 comment_count * 5) / pow(age_hours 2, 1.5)参数设计如下表参数值说明view_count1一次浏览的权重like_count3点赞的参与成本comment_count5评论的参与成本最高age_hours发布距现在小时数加 2 防止新帖除零衰减指数1.5越大老帖越早掉榜一次浏览权重为 1点赞权重为 3评论权重为 5因为评论在论坛里的参与成本最高说明用户真的在讨论age_hours是帖子发布到现在的时长2防止新帖刚发布时除以 01.5是时间衰减指数。三组数字在论文里做成参数表答辩老师问“为什么点赞权重是 3”时能答出“基于参与成本排序”就及格了。定时任务刷榜的 Java 代码Component RequiredArgsConstructor public class HotPostTask { private final StringRedisTemplate redisTemplate; private final PostMapper postMapper; // fixedRate 单位毫秒10 分钟刷一次 Scheduled(fixedRate 600000) public void refreshHotRank() { ListPost posts postMapper.selectWithinDays(7); for (Post post : posts) { long ageHours Math.max(1, Duration.between(post.getCreateTime().toInstant(), Instant.now()).toHours()); double score (post.getViewCount() post.getLikeCount() * 3.0 post.getCommentCount() * 5.0) / Math.pow(ageHours 2, 1.5); redisTemplate.opsForZSet().add(hot:post, String.valueOf(post.getId()), score); } Integer size redisTemplate.opsForZSet().size(hot:post); if (size ! null size 500) { // 按排名删除最低分的一批防止 ZSET 无限增长 redisTemplate.opsForZSet().removeRange(hot:post, 0, size - 501); } } }代码里的参数每个都对应一个坑。selectWithinDays(7)只取最近 7 天帖子防止历史数据反复霸占内存fixedRate 600000表示任务启动后每 10 分钟执行一次注意Scheduled默认单线程串行执行如果上一次没跑完下一次会排队这里只查 7 天数据不会超时所以不用上线程池。ZSET 只保留前 500防止 score 相同的老数据堆积removeRange在 Spring Data Redis 中按排名删除删的是 score 最低的那一批。读取热榜时用ZREVRANGE hot:post 0 19 WITHSCORES取出前 20再按 postId 批量查数据库组装详情。这里有个细节Redis 里的 member 一定是字符串如果之前把 “1001” 误存成其他类型用opsForZSet().rank会返回 null排查时先type hot:post确认数据结构。5.2 浏览量缓存INCR 计数与定时落库浏览量的实现有几层做法毕设做到“Redis 计数 定时落库”这层就足够讲成亮点。用户每次打开详情页对 keyview:count:{postId}执行 INCR定时任务每隔几分钟把一批 key 的计数累加到数据库再删掉这些 key。这样高并发下的浏览量不直接打数据库论文的“性能优化”章节有内容可写。public void incrView(Long postId) { String key view:count: postId; redisTemplate.opsForValue().increment(key); // 原子自增 redisTemplate.expire(key, 30, TimeUnit.MINUTES); }这里最常遇到的报错是ERR value is not an integer or out of range原因是同一个 key 之前被写入了非数字字符串比如有人把用户 ID 错存进去。排查思路是先GET view:count:{postId}看当前值到底是什么。保险做法是每次 increment 后判断返回值的数量级超过阈值立刻落库防止 Redis 里堆积太多未落库计数。帖子详情正文是读多写少的典型场景可以缓存整个详情 JSON设置 15 分钟过期。更新帖子时不要“更新缓存”直接删缓存下次读取再回填这就是 Cache Aside 模式。删除缓存和更新数据库两个操作要控制先后先改库再删缓存比先删缓存再改库的并发风险小。5.3 关键词搜索先上 LIKE再考虑 ngram 全文索引论坛的搜索功能在不同规格的系统里做法差很多。毕设阶段先接一个能用、能讲清原理的搜索不要一上来就上 Elasticsearch。最直接的是 MyBatis-Plus 的条件构造public IPagePost search(String keyword, int pageNum, int pageSize) { LambdaQueryWrapperPost wrapper new LambdaQueryWrapper(); wrapper.eq(Post::getStatus, 1) .and(w - w.like(Post::getTitle, keyword) .or().like(Post::getContent, keyword)); return postMapper.selectPage(new Page(pageNum, pageSize), wrapper); }like条件默认会在前后加%keyword%这意味着索引会失效数据量超过十万条全文检索会明显变慢。想低成本提升给 post 表加 MySQL ngram 全文索引专门解决中文分词问题ALTER TABLE post ADD FULLTEXT INDEX ft_title_content (title, content) WITH PARSER ngram;对应的查询语句换成 MATCH AGAINSTSELECT id, title, MATCH(title, content) AGAINST (校园 活动 IN NATURAL LANGUAGE MODE) AS relevance FROM post WHERE status 1 AND MATCH(title, content) AGAINST (校园 活动 IN NATURAL LANGUAGE MODE) ORDER BY relevance DESC;ngram 解析器默认按两个字符切词搜单个字时效果很差这是它和 hanlp 这类中文分词器的本质差别。论文里可以把 LIKE 方案作为基础版ngram 全文索引作为改进版优化方向写“引入中文分词器或 Elasticsearch”搜索模块从功能到优化路径就闭环了。6. 校园论坛系统的毕业设计落地论文四图、PPT 主线与答辩自测这一节把前面的代码变成论文和 PPT。论文里建议放四类图用例图、架构图、ER 图、时序图。用例图画游客注册、用户发帖评论点赞搜索、管理员审核封禁架构图画前端页面到 springBoot Controller、Service、Mapper再到 MySQL/RedisER 图把六张表的关系画对like_record、post_collect 必须作为中间表出现时序图画登录、发帖、点赞三条主流程各一张。画图工具用 draw.io 或 ProcessOn重点是关系要和代码一致不能 ER 图一张画法、表结构另一种画法。代码不要大段贴核心表放字段表接口放方法签名加不超过 20 行的实现重点写设计原因。评论表同时存在parent_id和reply_to_user_id的原因就值得单独写一节。PPT 控制在 12 到 15 页主线三步功能演示、设计亮点、改进方向。演示放登录、发帖、评论、点赞、热帖五张截图亮点只讲两个一个是 JWT 无状态登录加拦截器鉴权另一个是 Redis ZSET 热帖排行与计数缓存把得分公式放上去改进方向写真实短板比如搜索后续换 Elasticsearch、图片要接对象存储。启动配置里不要把 actuator 全部端点暴露出去否则容易被人把堆内存里的敏感信息 dump 出来只保留健康检查即可management: endpoints: web: exposure: include: health,info答辩前按真实使用路径把下面这张表过一轮每一项都应当看到明确的返回或数据变化场景预期结果未登录发帖返回 401不进入业务逻辑重复用户名注册提示用户名已存在同一用户重复点赞两次第二次取消点赞评论的父级不存在返回 400评论不落库发一条评论后查 post.comment_count原子加 1热帖排序与 score 公式计算结果一致自测时同时开 SQL 日志和 Redis MONITOR一个请求打过去能看到 SQL 是否多查、缓存是否命中。演示机器提前用java -jar target/campus-forum.jar --spring.profiles.activeprod跑通数据用演示脚本造好。如果 8080 已被其他进程占用启动时追加--server.port18080换端口不要现场改配置文件。本文还有配套的精品资源点击获取

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

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

免费获取报价