news 2026/10/6 3:28:01

基于SpringBoot的古诗词学习平台系统设计、部署与踩坑完整指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于SpringBoot的古诗词学习平台系统设计、部署与踩坑完整指南

做毕设或者课程设计选到这个题目的朋友,我猜你大概率是两种情况之一:SpringBoot刚学了个大概、想通过一个完整项目把全家桶串起来,或者手头已经有一套参考源码,但对着“源码+lw+部署文档+讲解”这套交付物不知道从哪下手。基于SpringBoot的中国古诗词学习平台系统,本质上是一个典型的Web信息管理类项目,用户端做诗词的浏览、搜索、收藏和学习记录,管理端做内容维护和用户管理,技术栈以SpringBoot为核心,配MySQL数据库和一套前端页面。这类项目最大的价值在于:业务逻辑不复杂,但涵盖了一个企业级Java后端项目从建表、写接口、鉴权到打包上线的完整链路,非常适合作为毕业设计或课程设计的载体。

我接触过不少拿这套题目的同学,也帮人排查过各种部署问题。这篇就把整个项目的设计思路、核心模块、实操过程和踩坑记录完整写下来,给准备做类似系统的人一条可以直接走的路线。我会尽量把每个关键点背后的“为什么”讲清楚,而不只是贴代码。

1. 项目整体设计与技术选型

1.1 业务需求拆解:这个系统到底要做什么

古诗词学习平台,这个词听起来很文化,拆开看其实就是一个“内容浏览+用户管理”的系统。先理清楚使用者是谁,再决定做哪些功能。

普通用户的需求很直接:打开网站能看到诗词列表,按朝代、作者、分类筛选,想找某首诗可以直接搜关键词,点进去看详情(原文、注释、译文、赏析),觉得好的可以收藏,学过之后可以记录自己的学习进度。管理员的需求则是另一套:登录后台,维护诗词数据(新增、修改、删除),管理分类和作者信息,必要的时候可以查看用户列表、禁用违规账号。

这里要注意一个设计原则:前台和后台的页面、接口最好分开考虑,但共用一个数据库和一套后端服务。很多初学者容易犯的错是把管理功能直接堆在用户页面里,或者干脆不做后台,只留一个数据库脚本让老师手动插数据。这两种都会在答辩时被问住。标准做法是用户端正常展示,管理端单独走一套路径。

1.2 技术栈选型:为什么是SpringBoot + MySQL + Vue

这套项目端到端的技术选型,本质上是围绕“毕业设计能讲清楚、能部署起来、代码量适中”这三个目标来定的。

  • SpringBoot:不需要纠结Tomcat配置、Spring配置文件的繁琐整合,一个注解启动项目,这对时间有限的毕设党是最友好的。版本上建议用SpringBoot 2.7.x,不要盲目追最新版。热词里有个“springboot版本太高”的搜索词,说明很多人被3.x版本坑过——SpringBoot 3.x要求JDK17,很多学校的机房和服务器还停留在JDK8,你写代码用的是新语法,部署机器跑不起来,这种事在答辩前一周炸出来是真的想哭。
  • MySQL:5.7或8.0均可,推荐8.0。8.0的驱动类名是com.mysql.cj.jdbc.Driver,和5.x的写法不同,后面我在配置部分会单独说。
  • MyBatis-Plus:相比纯MyBatis,它自带单表CRUD方法,分页插件也集成好了,写代码能少一半。答辩时若被问到底层原理,也答得上“它是在MyBatis基础上做增强,没有侵入原框架”。
  • 前端方案“学的时候也能顺便把Vue的打包部署摸一遍”,这点在很多公司里确实是真实需求。选Vue方案就一定要学会把打包后的dist目录放进SpringBoot的resources/static,或者用Nginx转发,否则前后端分离环境在答辩现场很容易翻车。
  • 数据库连接池:默认的HikariCP就行,出题老师问起来,你就说它性能好、SpringBoot默认集成,不需要额外引入。

1.3 功能模块清单:照着这张表做不会漏

模块功能点说明
用户模块注册、登录、退出登录成功后返回Token或记录Session
诗词浏览列表展示、分页按时间或热度排序
分类筛选按朝代、作者、分类查询可用下拉框或多条件组合查询
诗词搜索标题/作者/内容模糊搜索关键词非空时动态拼接SQL
诗词详情原文、注释、译文、赏析详情页单独接口
收藏管理收藏/取消收藏、收藏列表关联用户ID和诗词ID
学习记录记录学习进度、历史列表可选,但如果做了绝对是加分项
后台管理诗词CRUD、分类管理、用户管理管理员登录后操作

功能不用贪多,把上面这张表做扎实,配合文档就已经是完整度很高的系统了。关键是每张表之间的外键关系要理清楚,后面建表才不会乱。

2. 数据库设计与后端架构

2.1 数据库建模:五张表的核心设计

这套系统的核心数据模型,我用五张表就能覆盖绝大部分场景。设计的时候记住一个原则:表结构宁可稍微冗余,也别过度设计,保证CRUD顺手最重要。

用户表(sys_user)

字段包括id、username、password、nickname、avatar、role(0表示普通用户,1表示管理员)、status(是否禁用)、create_time。密码不能存明文,建议用MD5或BCrypt做加密。曾见过一个同学的源码里直接把密码明文存在数据库里,被老师质疑安全问题,现场很尴尬。

诗词表(poem)

这是核心表。字段有id、title(标题)、author(作者)、dynasty(朝代)、category_id(分类外键)、content(原文)、translation(译文)、annotation(注释)、appreciation(赏析)、create_time。创建索引时一定要给title和author加上普通索引,因为搜索功能主要就是查这两个字段,数据量上来之后全表扫描会明显变慢。

分类表(category)

id、name、description。用于管理“唐诗”“宋词”“元曲”等分类,也可以细分到“边塞诗”“田园诗”,看你自己想怎么划分。

收藏表(favorite)

id、user_id、poem_id、create_time,联合唯一索引(user_id, poem_id),防止同一个用户重复收藏同一首诗。

学习记录表(study_record)

id、user_id、poem_id、learn_time、note(用户写的笔记)。这个表是可选的,但加上之后整个项目的功能纵深就出来了,答辩时老师会觉得你有完整的业务思考。

初始化数据不要自己手打几百条,写一个SQL脚本,找一些公开的古诗词数据集转成INSERT语句,100首打底、300首更好,列表和搜索的效果才会真实。网上有不少现成的SQL数据文件,导入前记得检查编码,统一用utf8mb4,否则中文乱码会折磨你很久。

2.2 后端分层与接口规范

SpringBoot项目最忌讳把所有逻辑堆在Controller里。标准做法是四层结构:Controller接收参数——Service处理业务——Mapper操作数据库——Entity映射表结构。

统一返回结果集是一件值得一开始就做好的事情。定义一个Result类,包含code、msg、data三个字段,所有接口都返回这个对象。这样前端处理数据时只需要判断code是否为200,业务逻辑清爽很多。别让每个接口返回不同的JSON结构,后期维护的时候会想骂人。

分页接口的设计也要统一。接收pageNum和pageSize两个参数,返回total(总数)、list(当前页数据)、pages(总页数)。MyBatis-Plus的Page对象天然支持这套结构,直接用就行。

2.3 登录鉴权:Session还是Token

很多毕设项目在登录鉴权上很纠结,其实就看你的前端方案。

  • 前后端分离(Vue单独跑):用JWT Token。用户登录成功后,后端生成一个Token返回给前端,前端存在localStorage里,后续每个请求在Header里带上Authorization: Bearer token。后端用一个拦截器或过滤器统一校验。
  • 前后端不分离(Thymeleaf模板):用Session就够了,登录成功后把用户信息放进session,需要登录的接口用拦截器判断session是否为空。

拦截器里要注意一个坑:必须放行登录接口、注册接口、静态资源路径(比如/css/**、/js/**、/images/**),否则前端页面会全部404或者无限重定向。放行列表用Ant表达式,写/api/user/login、/api/user/register,再放行静态资源,其余接口一律校验Token。

2.4 搜索和筛选的动态SQL处理

搜索功能看起来简单,写起来最容易出现“SQL拼接字符串漏洞”或“空条件报错”。推荐直接用MyBatis-Plus的LambdaQueryWrapper,用条件判断动态拼接。

比如用户输入了关键词才去查title和author,没输入就查全部;选了朝代才拼dynasty条件。用StringUtils.hasText()做空判断,避免模糊查询时传入空字符串导致查询结果不对。分页查询用Page对象配合IPage返回,比手写LIMIT更安全、更规范。

3. 实操过程与核心功能实现

3.1 快速搭建SpringBoot项目骨架

直接用IDEA的Spring Initializr创建项目,选好Maven和JDK版本。依赖上建议勾选:

  • Spring Web
  • MySQL Driver
  • Lombok(如果你们允许用,能少写大量getter/setter)
  • Validation(参数校验)

pom.xml里手动加MyBatis-Plus依赖(它的mybatis-plus-boot-starter没有进Initializr的可选项,需要自己填)。版本选择要和SpringBoot版本兼容,我用SpringBoot 2.7.x配的是mybatis-plus 3.5.x,实测很稳。

项目目录建议这样建:

com.example.poetry ├── controller // 接口层 ├── service // 业务层 │ └── impl ├── mapper // 数据访问层 ├── entity // 实体类 ├── config // 配置类(拦截器、跨域、MyBatis-Plus分页) ├── common // 统一返回结果、异常处理、工具类 └── PoetryApplication.java // 启动类

结构清晰是给老师看的,也是给自己看的。项目拖到后期如果包结构乱成一团,改一个功能要找半天文件,心态特别容易崩。

3.2 核心配置与数据库连接

application.yml是整个项目的命脉,80%的启动失败都是这里配错了。给一个可以直接抄的模板:

server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/poetry?useUnicode=true&characterEncoding=utf8mb4&useSSL=false&serverTimezone=Asia/Shanghai username: root password: your_password jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8 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

这里有几个关键细节:

  • characterEncoding=utf8mb4,不是utf-8,MySQL的utf8mb4才完整支持emoji和所有生僻字,古诗词里繁体字和生僻字不少,用错编码会出现问号乱码。
  • serverTimezone=Asia/Shanghai必须加,否则MySQL 8.0默认时区会让时间字段差8小时,或者直接报警告。
  • map-underscore-to-camel-case: true开起来后,数据库的create_time字段能自动映射到Java的createTime属性,不用手写一堆@TableField的别名校验。
  • log-impl在开发阶段打开,能直接在控制台看到每次执行的SQL,排查问题非常爽,部署上线前建议关掉或改为不输出。

3.3 诗词列表分页和搜索接口的实现

列表分页和搜索是整个项目被调用最频繁的接口,也最能体现代码功底。Controller层接收前端传的参数,Service层组装查询条件。

接口定义:

@GetMapping("/poem/page") public Result page(@RequestParam(required = false) Integer pageNum, @RequestParam(required = false) Integer pageSize, @RequestParam(required = false) String keyword, @RequestParam(required = false) String dynasty, @RequestParam(required = false) Integer categoryId) { if (pageNum == null) pageNum = 1; if (pageSize == null) pageSize = 10; return Result.success(poemService.getPoemPage(pageNum, pageSize, keyword, dynasty, categoryId)); }

Service实现:

@Override public IPage<Poem> getPoemPage(int pageNum, int pageSize, String keyword, String dynasty, Integer categoryId) { Page<Poem> page = new Page<>(pageNum, pageSize); LambdaQueryWrapper<Poem> wrapper = Wrappers.lambdaQuery(); // 关键词搜索:标题或作者 if (StringUtils.hasText(keyword)) { wrapper.and(w -> w.like(Poem::getTitle, keyword).or().like(Poem::getAuthor, keyword)); } if (StringUtils.hasText(dynasty)) { wrapper.eq(Poem::getDynasty, dynasty); } if (categoryId != null) { wrapper.eq(Poem::getCategoryId, categoryId); } wrapper.orderByDesc(Poem::getCreateTime); return poemMapper.selectPage(page, wrapper); }

注意keyword搜索的写法,用and(w -> w.like(...).or().like(...))包起来,否则多个条件叠加时SQL拼接逻辑会出错。分页插件必须配置好MybatisPlusInterceptor,否则Page只会返回全部数据,分页根本不起作用。

3.4 收藏功能的幂等设计

收藏是个看起来简单但容易出Bug的功能:用户连续点两次“收藏”,数据库出现两条相同记录,取消收藏又取消不掉。

我的做法是:先查favorite表是否已有该用户和该诗词的记录,有则返回“已收藏”,没有则新增。删除时只删当前用户在当前诗词下的那条记录。接口层面加个校验,防止用户传别人的userId去查或删数据。

@PostMapping("/favorite/add") public Result addFavorite(@RequestBody Favorite favorite) { LambdaQueryWrapper<Favorite> wrapper = Wrappers.lambdaQuery(); wrapper.eq(Favorite::getUserId, favorite.getUserId()) .eq(Favorite::getPoemId, favorite.getPoemId()); if (favoriteMapper.selectCount(wrapper) > 0) { return Result.error("不能重复收藏"); } favoriteMapper.insert(favorite); return Result.success("收藏成功"); }

批量操作时注意用户只能查自己的收藏列表,SQL里必须带上user_id条件,这是安全上的低级坑,但每年都会有人踩。

3.5 后台管理的增删改查与权限保护

后台接口建议单独用/admin前缀,并在拦截器里做角色校验——只有role=1的管理员才能访问。这是很多人忽略的细节:用户能直接调用后台删除诗词的接口,整个系统就形同虚设了。实现不复杂,写一个AdminInterceptor,在preHandle里从Token或Session解析出用户信息,判断角色即可。

@Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token = request.getHeader("Authorization"); if (!StringUtils.hasText(token)) { throw new RuntimeException("未登录"); } // 解析token获取用户信息,这里按你自己的方案取 User user = userService.getUserFromToken(token); if (user == null || user.getRole() != 1) { response.setStatus(403); return false; } return true; }

值得注意的是,/admin接口放在拦截器里后,务必注册到拦截器配置中时,排除掉登录接口和静态资源,否则后台页面也会一起被拦。实际项目中我见到过后台轮播图加载不出来的情况,最后定位到是静态资源被拦截了。

3.6 前端整合:Vue打包进SpringBoot

很多人的项目源码里,前端是单独的Vue工程,后端是SpringBoot工程,两者分开跑没问题,但交付时要求“一个jar包跑起来”,这就涉及前端静态资源整合。

Vue项目打包前,修改vue.config.js,把publicPath设为'./'而不是默认的'/',否则资源路径会找不到。打包后会生成dist目录,里面有index.html、static或assets等子目录,直接把整个dist里的内容复制到SpringBoot的src/main/resources/static/下。打包SpringBoot时,Maven会把static目录一并打进去。

启动后端后访问http://localhost:8080/,就能直接看到前端页面。注意如果你在前端用axios请求接口时写死了http://localhost:8081之类的地址,生产环境就会跨域,建议用相对路径/api/...或者配置好CORS。

3.7 本地打包与服务器部署全流程

这部分是最容易让新手崩溃的环节。完整流程走通一次,后面的问题基本都能迎刃而解。

第一步,在项目根目录执行Maven清理和打包命令:

mvn clean package -DskipTests

打包成功后在target/下会生成一个xxx.jar文件。这里有个命令行的坑:如果你是在Windows下打包,拿到服务器上是Linux环境,Java路径和文件编码都可能不同。执行前确认JDK版本一致,我见过同一个人本地JDK8打包,服务器上是JDK11,运行直接报UnsupportedClassVersionError。

第二步,上传jar包到服务器,使用nohup后台运行:

nohup java -jar poetry-system.jar > log.log 2>&1 &

这样退出SSH后程序还能继续跑。查看日志用tail -f log.log,启动失败时能第一时间看到堆栈信息。

第三步,配置MySQL数据库。先把SQL脚本导入到服务器MySQL中,记得检查和本地数据库的用户名、密码、端口是否一致,不一致就在application.yml里改,或者用外置配置文件覆盖。

4. 常见问题与排查技巧实录

4.1 数据库连接与编码问题

这类问题占了毕设调试过程中至少一半的比重。下面是我整理的高频问题排查表:

问题现象常见原因解决思路
启动报ClassNotFoundException: com.mysql.cj.jdbc.Driverpom.xml里没有MySQL驱动,或版本不匹配检查依赖,SpringBoot 2.7用8.x驱动即可
中文显示为??数据库表不是utf8mb4编码ALTER TABLE 表名 CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
时间字段少8小时或报时区错误连接URL缺serverTimezone在URL后加?serverTimezone=Asia/Shanghai
连接超时服务器防火墙未放行3306端口云服务器安全组放行MySQL端口,并确认数据库配置允许远程连接

4.2 启动失败的常见原因

端口被占用是新手遇到的第一道坎。启动时提示Port 8080 was already in use,说明有别的程序占了8080端口。Windows下用netstat -ano | findstr 8080找到占用进程的PID,去任务管理器结束进程,或者在application.yml里换一个端口。

Maven依赖下载失败、卡住很常见。国内下载SpringBoot相关依赖,建议配置阿里云镜像仓库,在settings.xml里加:

<mirror> <id>aliyunmaven</id> <mirrorOf>central</mirrorOf> <url>https://maven.aliyun.com/repository/public</url> </mirror>

这能省下大量等待时间。还有那种SpringBoot版本太高导致JDK不兼容的问题,如果你是JDK8,就回退到2.7.x,别硬刚。

4.3 接口调通但页面数据不显示

这种情况往往不是后端问题,而是前端请求地址不对或跨域。打开浏览器F12看Console和Network,一目了然。

如果是跨域报错,在后端加一个全局CORS配置类:

@Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOriginPatterns("*") .allowedMethods("GET", "POST", "PUT", "DELETE") .allowedHeaders("*") .allowCredentials(true) .maxAge(3600); } }

加了之后前端跨域请求就能正常发出。但要注意allowCredentials(true)时allowedOrigins("*")会失效,要用allowedOriginPatterns代替,这个坑我见过不止一次。

4.4 打包后静态资源404

Vue打包放进SpringBoot后找不到JS和CSS,最常见的两个原因:

  • publicPath配置错误。Vue默认的publicPath是'/',打包后资源路径是/js/chunk.js,部署在非根路径下会404,改成'./'用相对路径就好。
  • SpringBoot拦截器放行了/api/**但没放行静态资源,页面本身加载了,JS却被拦截器拦住了。在拦截器排除列表里加上/static/**、/css/**、/js/**、/img/**、/favicon.ico等路径。

还有个偏门的坑:IDEA中Maven打包时没有把resources/static里的资源打进去。检查pom.xml是否配置了<resources>标签且指定了src/main/resources,确认一下范围,别把资源目录漏掉。

4.5 日志定位:控制台SQL输出是神器

MyBatis-Plus配置了StdOutImpl之外,开发时可以在application.yml开启SQL日志。查数据查不到、更新失败,统一先看控制台打印的SQL语句,再倒推到参数传值问题。

排查问题的顺序建议:先看日志堆栈异常信息,再看SQL语句,最后看前端请求是否正确。很多人一报错就满屏找“Exception”,其实日志开头那几行就写清楚了。把这个问题想清楚,能少走很多弯路。

我的几个实操体会

这类带完整交付物的项目,真正考验人的往往不是写代码本身,而是把文档、部署、演示这整条链路走通。我印象里最深的一件事是有个同学本地跑得好好的,答辩前一晚部署到学校服务器,页面英文正常、中文全变成问号,后来发现是导入SQL文件时没有指定--default-character-set=utf8。所以数据初始化那一步,务必确认数据库、表、连接串三处的字符集一致。这个坑很小,但炸起来真的非常影响心态。

另外建议把项目的README写好,写明环境要求(JDK版本、MySQL版本、Node版本)、启动步骤(导入SQL、改配置、运行jar)、默认账号(admin用户和管理员账号)。这份东西既是给老师看的,也是给你自己留的“急救手册”。几个人做同一个项目的话,互相传阅时顺手很多。

最后说一句,如果时间充裕,试着在诗词详情页加一个“作者生平”或“相关推荐”,后端多一个扩展接口就行。这种小功能不会增加太多负担,但在答辩展示环节非常出彩,能让老师觉得你在做产品而不是在应付作业。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/6 3:26:30

Ansys Workbench中启用Cable280单元:Link180迁移绳索分析全攻略

如果你和我一样&#xff0c;2020年底那阵子还在用 Link180 硬顶着做绳索类分析&#xff0c;那你大概率遇到过这种场景&#xff1a;一个简简单单的索道张拉模型&#xff0c;算到一半就给你报“负主元”或者“发散”&#xff1b;明明索处于松弛状态&#xff0c;结果应力云图上却出…

作者头像 李华
网站建设 2026/10/6 3:26:12

PHP票务系统源码实战:锁座防超卖与订单状态机设计

简介&#xff1a;一份基于PHP的票务管理系统源码&#xff0c;面向需要在线售票、订单管理和后台票务维护的PHP开发者与学生。资源围绕框架式分层结构展开&#xff0c;整合MVC模式、前端交互与数据库设计&#xff0c;覆盖用户注册登录、活动浏览、选座购票、在线支付、订单追踪、…

作者头像 李华
网站建设 2026/10/6 3:25:59

SDM660电源调试实战:PM660/PM660L双PMIC架构与电源轨配置详解

SDM660是Qualcomm骁龙660移动平台的代号&#xff0c;这颗芯片在2017年发布后&#xff0c;几乎成了中端机的代名词。OPPO R11、小米Note 3、vivo X20这些当年的爆款都用它&#xff0c;一直到今天&#xff0c;还有不少IoT设备、行业终端在沿用这套方案。所以如果你手里正好有一块…

作者头像 李华
网站建设 2026/10/6 3:24:07

Flink面试高频考点与实战异常排查:从状态管理到CDC同步

最近不少准备面试的朋友都在找flink面试题及答案&#xff0c;我做了这么多年实时计算&#xff0c;也被问过、也问过别人。实际上面试官问来问去&#xff0c;核心并不是让你背概念&#xff0c;而是看你能不能把原理讲透、把坑说清。这篇我就结合自己用Flink做实时项目的经验&…

作者头像 李华
网站建设 2026/10/6 3:23:40

云号如何帮中小电商破局通信成本与效率困局

1. 中小电商的经营困局&#xff1a;钱花在哪、效率漏在哪做电商的都知道&#xff0c;前几年“开店就能赚钱”的窗口期早就过去了。现在中小卖家的真实处境是这样的&#xff1a;流量成本一年比一年贵&#xff0c;平台规则越来越复杂&#xff0c;客服人力成本持续上涨&#xff0c…

作者头像 李华
网站建设 2026/10/6 3:21:52

IDEA+MySQL搞定JavaWeb项目:从配置到用户管理实战

我之前带过好几个实习生&#xff0c;发现一个特别有意思的现象&#xff1a;大家学JavaWeb的时候&#xff0c;视频看了、笔记抄了&#xff0c;但真到自己用IDEA新建一个项目、连上MySQL、跑通一个完整案例&#xff0c;往往要折腾好几天。尤其是搜"idea运行javaweb项目配置&…

作者头像 李华