
如果让我给今年的毕设课题排个优先级基于SpringBoot的冬奥会科普平台属于那种一眼看过去就让人觉得有讲头的类型。它不是一个脑门一热造出来的伪需求而是把SpringBoot技术栈的所有核心能力都通过一个真实可用的内容型Web应用串了起来用户认证、内容发布、数据检索、缓存加速、知识答题、后台管理、部署上线一个都不少。你要是正在选毕设题或者想用Java后端做一套能拿得出手的项目这个方向刚好卡在技术有深度和实现有边界之间的最佳位置。我做完这套平台最大的感受是它表面是一个冬奥知识站点本质是一套通用的内容管理轻互动系统。文章、视频、题库、排行榜、收藏、后台审核这些模块拆开看都是SpringBoot开发里最高频的业务场景换一个主题照样能跑。所以这篇就把我整个设计和实现过程完整复盘一遍从需求拆解到技术选型从核心代码到Docker部署再到那些不踩一遍根本记不住的坑全部分享出来。1. 项目概述与需求拆解1.1 为什么选冬奥科普这个方向做毕设最容易犯的错是选一个纯管理系统——用户表、订单表、CRUD一套做完自己都说不清解决了什么问题。冬奥科普平台不一样它的价值在于真实存在的科普缺口。冬奥会项目多、规则杂冰壶、冬季两项、雪车雪橇这些项目的规则和装备大部分人是一知半解的。传统资讯站虽然内容全但是以单向输出为主用户看两页就划走了。科普平台要想留住人必须解决两个问题一是内容要成体系不能东一篇西一篇二是学习要有反馈不能干巴巴地看。所以我把平台定位成可看、可学、可考三位一体。可看是指图文和视频科普可学是指按项目分类的知识库可考是指答题闯关和积分排行。这三个词定下来后面的功能模块、数据库表、接口设计就都有了主线。1.2 功能需求分析基于这个定位我把系统拆成三个端前台门户、用户中心、后台管理。前台门户解决看什么。首页做轮播图推荐放置热门科普文章和精选视频项目百科按照冬奥会的15个分项来组织内容每个分项有项目介绍、竞赛规则、比赛场馆、历史渊源视频专区负责冬奥科普短视频的播放和展示。用户中心解决学得怎么样。注册登录后可以参与知识竞答系统会随机抽取题目组成一套试卷交卷后立即判分分数计入排行榜。答题记录在个人中心里可以随时回看同时用户可以收藏感兴趣的科普文章形成自己的知识清单。第三个是后台管理也就是管理员视角。管理员负责内容发布、文章审核、视频上传、题目维护、用户管理、数据统计。我特别强调审核这个环节因为科普平台面向大众内容准确性很重要不能谁都能直接发文章管理员审核通过后才能上线展示。1.3 用户角色与权限规划系统按角色分为游客、注册用户、管理员三种权限边界必须清晰。游客只能访问公开的科普内容和搜索结果权限最小注册用户在前台内容的基础上多了答题、收藏、评论、查看个人中心的功能管理员则拥有后台的所有操作权限。这里我用SpringBoot的拦截器加角色标记来解决登录时把角色写进JWT接口层用注解做权限校验。页面规划就顺着权限走游客落地页是首页注册用户落地页是知识广场管理员落地页是后台工作台。每个人进来看到的第一屏都不一样这个细节对答辩加分很关键能直接体现你考虑过用户分层。2. 技术选型与架构设计2.1 SpringBoot版本怎么选版本这块我得先说一句别盲目追新。我项目里用的是SpringBoot 2.7.18不是3.x。为什么因为2.7.x是javax命名空间JDK 8直接跑网上资料最多各种中间件版本的兼容性最成熟3.x切到jakarta命名空间后很多老教程和老依赖会踩坑。2026年做毕设2.7.x依然是最稳妥的选择。顺带讲一下SpringBoot为什么省事这几乎是必被问到的点。它靠的是自动装配机制启动类上的SpringBootApplication是一个组合注解里面包含了SpringBootConfiguration、EnableAutoConfiguration和ComponentScan。SpringBoot通过META-INF下的spring.factories文件加载大量的AutoConfiguration类再用ConditionalOnClass、ConditionalOnMissingBean这类条件注解判断当前环境下要不要装配这个配置。比如你引入了redis依赖RedisAutoConfiguration就生效自动创建RedisTemplate的Bean。这就是为什么我们不需要写一堆XML配置。2.2 数据库设计与持久层方案持久层我选的是MyBatis-Plus理由很简单单表CRUD不用手写SQL内置分页插件代码生成器一键生成实体、Mapper、Service能把大量重复劳动省下来。数据库用MySQL 8.0。核心表我设计了7张用户表user、管理员表admin、文章分类表category、科普文章表article、视频表video、题目表question、答题记录表answer_record。另外加了一张用户收藏表user_collection。这里给两个关键表的结构做参考。文章表article的关键字段id主键title文章标题category_id所属项目分类content文章正文LongText类型cover_image封面图地址view_count浏览量like_count点赞数status状态0草稿、1待审核、2已发布create_time、update_time时间字段题目表question的关键字段id主键category_id所属项目分类question_type题型0判断题、1单选题title题干option_a到option_d选项判断题只用A和Banswer正确答案analysis答案解析difficulty难度等级MyBatis-Plus的分页插件用法也顺手说下。先在配置类里注册一个MybatisPlusInterceptor Bean在里面添加PaginationInnerInterceptor然后在Mapper层接收Page参数即可Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }查询代码就是按IPage传参PageArticle page new Page(current, size); LambdaQueryWrapperArticle wrapper new LambdaQueryWrapper(); wrapper.eq(Article::getStatus, 2).orderByDesc(Article::getCreateTime); articleService.page(page, wrapper);在MyBatis-Plus里分页查询的核心诀窍是如果你写了自定义SQL并且带分页Mapper接口的方法参数里必须有一个IPage类型的参数否则分页不会生效。这个坑我后面还会在踩坑实录里提。2.3 缓存、文件存储与前端方案Redis我用在两个场景一个是热门文章的缓存另一个是答题排行榜的存储。热门文章列表是首页高频访问数据每次都查MySQL完全没有必要文章发布后写进缓存设置2小时过期。排行榜用Redis的ZSet结构member是用户IDscore是答题总分天然支持按分数排序省去数据库的排序压力。文件存储没有引入MinIO因为科普平台是毕设体量图片和视频用本地磁盘存储就足够了。我在application.yml里配置了一个自定义的上传目录通过SpringBoot的WebMvcConfigurer做静态资源映射让upload目录下的文件能通过URL直接访问。如果以后要扩展成生产级系统再平滑替换成MinIO或OSS接口设计时把文件访问统一走一个返回URL的方法这点很重要。前端我用的Vue3加Element-Plus前后端分离开发。Vue负责页面交互SpringBoot只提供JSON接口。不过我也要提醒一句如果前端基础不够用Thymeleaf模板引擎做服务端渲染也能完成这个项目。Thymeleaf的好处是没有跨域问题、不用单独部署Node环境但前后端分离的架构在答辩时更能体现工程化能力团队协作也方便。有精力就上Vue时间紧就用模板引擎不要因为追求架构导致完不成。2.4 项目目录结构设计一个好的目录结构能让后期维护少掉很多头发。我的后端工程是这样分的com.example.olympics ├── controller // 接口层 ├── service // 业务逻辑层 │ └── impl ├── mapper // 数据访问层 ├── entity // 实体类 ├── dto // 数据传输对象 ├── vo // 视图对象 ├── config // 配置类跨域、拦截器、序列化 ├── common // 通用类结果封装、异常处理、常量 ├── utils // 工具类JWT、文件上传 ├── interceptor // 登录拦截器 └── OlympiCsApplication.javacontroller层只负责参数接收和结果返回不写业务逻辑service层处理具体业务mapper层只写SQL或MyBatis-Plus的封装方法。dto和vo分开的原因也值得说一句前端需要什么字段vo就给什么字段不直接把entity暴露出去避免把密码、状态码这些内部字段泄漏给前端。这个设计在工程界叫接口隔离答辩时是实打实的加分项。3. 核心功能实现与关键代码细节3.1 用户登录与JWT权限拦截用户认证我用的是JWT配合拦截器方案。用户登录成功后服务端生成一个token返回给前端前端把token存在localStorage里每次请求在Authorization请求头带上。后端拦截器统一校验token解析出用户ID和角色存进ThreadLocal供后续业务使用。核心拦截器代码如下public class LoginInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (request.getMethod().equals(OPTIONS)) { return true; } String token request.getHeader(Authorization); if (token null || .equals(token)) { throw new BusinessException(401, 未登录请先登录); } // 解析JWT拿到用户信息 Claims claims JwtUtil.parseToken(token); UserContext.set(claims.get(userId), claims.get(role)); return true; } Override public void afterCompletion(...) { UserContext.clear(); } }这里有个容易忽略的细节ThreadLocal一定要在请求结束后清理使用afterCompletion方法执行clear否则Tomcat线程池复用时会把上一个请求的用户信息带到下一个请求。我不止一次见过有人漏掉这步结果用户A能看到用户B的收藏列表属于严重的安全事故。密码存储我用的BCrypt加密不用MD5加盐。BCrypt是spring-security-crypto里现成的工具每次加密结果带随机盐即使两个用户密码相同密文也不一样更安全。JWT的密钥放在配置里用Base64编码的随机字符串主密钥不硬编码在代码中这是原则问题。3.2 首页内容聚合与关键词检索首页要聚合轮播图、热门文章、推荐视频、项目分类导航几个数据源。我最开始是每个接口各查一次前端依次请求后来发现接口多了加载慢就把首页聚合做成一个接口后端用CompletableFuture并发查文章、视频、分类汇合后返回。并发查的效率比串行快了不少也展示了为性能做设计的思路。检索功能给了两个方案。原始方案是MySQL的LIKE模糊查询简单直接数据量在几千条时完全够用进阶方案是引入Elasticsearch或HanLP分词能对搜索词做语义匹配和分词处理。对于毕设体量的科普平台LIKE加倒序已经能覆盖使用场景但要理解ES方案的存在意义万一答辩老师问起来能说出区别就是加分项。我实际用的是LIKE加复合条件同时支持按分类过滤、按标题搜索、按内容搜索。热门文章的缓存策略我花了点心思。Redis存的key设计成hot_article_list每次查询先查缓存有就直接返回没有就查库再回填并且给key设置2小时过期。文章浏览量更新不直接写库先生自增Redis里的计数器定时任务每5分钟把增量同步到MySQL。这一步跳过了很多高并发教程讲的复杂方案但对个人项目来说性价比最高。3.3 知识竞答与排行榜模块答题模块是互动性的核心也是我最喜欢写的一块。题库维护交给后台题目按冬奥项目分类每道题有题型、选项、正确答案、答案解析和难度。用户每次答题后端从题库里按分类随机抽取10道题组卷确保每次进来题目顺序和内容都不一样。前端把用户答案以数组形式提交后端逐个判分然后生成答题记录并计算总分。判分核心逻辑public QuizResult submitAnswer(QuizSubmitDTO dto) { ListQuestion questions questionService.listByIds(dto.getQuestionIds()); int score 0; for (Question q : questions) { String userAnswer dto.getAnswers().get(q.getId().toString()); if (userAnswer ! null userAnswer.equals(q.getAnswer())) { score q.getDifficulty(); // 难度越高分值越大 } } // 保存答题记录 answerRecordService.saveRecord(dto.getUserId(), dto.getQuestionIds(), score); // 更新排行榜 redisTemplate.opsForZSet().incrementScore(quiz_ranking, String.valueOf(dto.getUserId()), score); return new QuizResult(score, totalQuestions); }这里我给不同难度的题目设置了不同分值简单题5分中等题8分难题10分排行榜的名次就更有区分度。用户答题后能看到正确答案和解析这个设计既能吸引用户反复挑战又达到了科普的目的。排行榜页面直接用ZSet的反序查询两条代码搞定SetZSetOperations.TypedTupleString range redisTemplate.opsForZSet().reverseRangeWithScores(quiz_ranking, 0, 49);排行榜分数存在Redis里同时每周把快照持久化到MySQL防止Redis重启数据丢失这个备份机制在上线后很关键。3.4 后台内容管理与XSS攻击防护后台编辑器我选了wangEditor富文本编辑器。这里必须提一个安全问题富文本内容里可以被注入JavaScript脚本也就是XSS攻击。SpringBoot默认不会帮你过滤参数必须自己处理。我的做法是写一个全局过滤器对提交的content参数做白名单式清洗。简单说就是保留下正常教学需要的标签把script、iframe、onerror这类危险内容和属性全部剔除同时结合HttpServletRequestWrapper的包装机制在参数进入Controller之前就完成过滤。public class XssFilter implements Filter { Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) { XssHttpServletRequestWrapper xssRequest new XssHttpServletRequestWrapper((HttpServletRequest) request); chain.doFilter(xssRequest, response); } }XssHttpServletRequestWrapper里重写getParameter和getInputStream方法对富文本内容做标签清洗。注意上传PDF、图片这类二进制内容时不要走XSS过滤链否则文件内容会被破坏这个坑在热词里也有人提到。我的处理方式是仅对参数类型为文本格式的字段启用过滤白名单之外一律转义。文件上传部分要注意两点一是限制上传文件大小spring.servlet.multipart.max-file-size设置成10MB避免大文件拖垮服务二是上传类型校验不能只靠前端后端要做二次校验通过扩展名和文件头两种方式判断真实类型防止伪造文件上传。3.5 视频科普与转码方案视频科普是平台的重头戏之一。最直接的做法是上传MP4前端用video标签播放成本最低。但我做了个加分项用FFmpeg把视频转成m3u8分片格式前端通过hls.js播放。为什么要转码因为m3u8是流媒体协议边下边播初始加载快。MP4则通常需要下载完整文件才能拖动播放用户网速不好时看视频特别卡。FFmpeg转码命令很简单ffmpeg -i input.mp4 -codec copy -bsf:v h264_mp4toannexb -m3u8 slist 1 -hls_time 10 -hls_list_size 0 output.m3u8我把转码做成异步任务视频上传后立即返回上传成功的URL后端线程池排队处理转码任务转码完成后更新视频状态。这样做的好处是管理员上传大视频时不会被请求阻塞用户体验和后台体验都好。考虑到视频数量不大转码失败的视频重新跑一次任务就行不需要搞复杂的任务调度队列。4. 项目部署、测试与常见问题排查4.1 本地开发调试环境搭建开发环境我用IDEA加MavenJava版本选了JDK 8配合SpringBoot 2.7.18。Maven依赖下载慢的问题在settings.xml里配置阿里云镜像可以大幅提速。接口调试这块我没有用Postman而是接入了knife4j这是swagger的增强版界面好看、分组清晰。SpringBoot里只需引入依赖并加一个EnableKnife4j注解访问/doc.html就能看到所有接口文档还能在线调试。这个工具对写接口联调实在太友好一个Controller写完就能在页面上直接传参测试前端同事也不用追着问接口参数。另外必须开热部署devtools。不加这个每次改Java代码都要重启服务开发效率至少低三分之一。devtools的配置方式是引入spring-boot-devtools依赖开发时它会监听classpath变化自动重启应用。注意它只在本地开发时启用打包生产时排除掉不然线上每次配置文件变化都会触发重启。4.2 Docker部署SpringBoot实战部署这块我用Docker加docker-compose编排一台服务器从零到整个系统跑起来大概需要三条命令。先写后端服务的Dockerfile我用了多阶段构建第一个阶段用maven容器编译打包第二个阶段用JRE镜像运行镜像体积能小很多FROM maven:3.8-openjdk-8 AS builder WORKDIR /app COPY pom.xml . RUN mvn dependency:go-offline COPY src ./src RUN mvn clean package -DskipTests FROM openjdk:8-jdk-alpine WORKDIR /app COPY --frombuilder /app/target/olympics-platform.jar app.jar EXPOSE 8080 ENTRYPOINT [java, -jar, app.jar]然后是docker-compose编排把MySQL、Redis、后端服务、Nginx四块串起来version: 3.8 services: mysql: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: root123 MYSQL_DATABASE: olympics_db volumes: - mysql_data:/var/lib/mysql ports: - 3306:3306 redis: image: redis:7.0 ports: - 6379:6379 app: build: . depends_on: - mysql - redis ports: - 8080:8080 environment: SPRING_PROFILES_ACTIVE: prod nginx: image: nginx:1.25 volumes: - ./nginx.conf:/etc/nginx/nginx.conf - dist:/usr/share/nginx/html ports: - 80:80 depends_on: - appNginx负责静态页面和反向代理前端构建产物放入镜像/api路径代理到后端的8080端口。上传的图片和视频单独挂载一个数据卷这样容器重建后文件还在。数据库密码和JWT密钥这类敏感配置不要直接写在docker-compose里用环境变量方式是更好的实践。4.3 高频踩坑记录速查表这个列表是血的教训换来的每一条都对应一次实际排障。问题现象排查思路解决方法前端请求跨域报错检查后端Cors配置写一个WebMvcConfigurer的addCorsMappings或加CrossOrigin数据库连接报时区错误连接串缺时区参数URL加serverTimezoneAsia/ShanghaiRedis存中文变成乱码默认JDK序列化器问题配置StringRedisSerializer和Jackson序列化器分页不生效返回全量数据Mapper方法缺IPage参数确保IPage在参数列表里且作为第一个参数上传大视频转码报错请求大小超限调大multipart max-file-size和Tomcat的max-swallow-size本地启动端口占用了其他进程占8080换端口或用lsof -i:8080查出占用进程分页不生效是最隐蔽的。如果你用MyBatis-Plus写自定义SQL比如多表联查带分页Mapper方法里如果没有Page参数插件根本不会执行分页逻辑最后返回全部数据。查半天发现是少传了一个参数。另外再分享一下源码备份的教训。有一次我误删了项目的部分源码只剩一个打好的jar包最后是借助反编译工具还原了类结构才找回关键代码。所以一定记得用Git管理工程打包后的jar包也保留一份双保险。这里想提醒所有做毕设的同学与其指望反编译不如每次改完代码顺手git push。4.4 系统测试巡检清单最后说说测试。很多毕设做完了功能但整体跑一遍总会发现零零散散的问题。我列了一个检查清单上线前按顺序排查一遍能挡掉80%的现场翻车事故。按角色走流程先游客访问首页、看文章、看视频、搜索关键词再注册新号登录写收藏、答一轮题、查看排行榜、检查个人中心的答题记录最后切管理员账号进后台试发布一篇文章、上传一条视频、改一道题、审核一篇待发布内容。每个环节都带数据对比比如浏览量是否自增、积分是否入榜、审核前后前端是否可见。性能层面重点关注首页响应时长打开浏览器Network面板观察聚合接口的耗时。如果大于2秒优先看是否有循环查库的坏味道其次看Redis缓存有没有生效。Redis是否命中可以通过查询接口前先查看Redis是否有值的日志来验证。安全层面必查入口未登录直接调用户中心和加分机制接口应被拦截管理员接口游客不能访问上传接口拒绝非白名单格式。这四项在答辩演示时是老师必点的雷。最后的经验之谈这套科普平台做完我个人最大的收获不是背熟了SpringBoot的API而是搞清楚了一个完整项目是怎么从需求长成代码的。科普类系统的核心逻辑是内容组织加用户互动文章、视频、题库这三类内容资产通过分类和标签建立连接再通过答题、收藏、排行这些激励机制让用户留下来。这个模式不是冬奥限定你换成环保科普、传统文化科普、健康知识科普照样能跑。如果你也要做类似的课题我的建议是别一上来就写代码先用两天把角色、功能、数据表三条线捋清楚。功能可以砍但每条数据表之间的关系必须想明白。实际编码时优先把登录权限和后台管理做完再补前台页面因为后台管理承载了系统最复杂的操作逻辑做完了它前台只是数据的展示层。动手之前记得配好Git仓库每完成一个模块提交一次。答辩前的晚上你会感激这个习惯。就这些祝你的SpringBoot科普平台一次跑通。