Spring Boot诗词管理系统实战:从需求拆解到部署运行

发布时间:2026/10/20 3:20:55
Spring Boot诗词管理系统实战:从需求拆解到部署运行 你拿到“java基于springboot的诗词管理系统820”这个标题的时候第一反应是什么我第一反应是这多半又是一套课程设计或者毕业设计项目编号820八成是内部仓库或者选题序号。但别小看这个题目诗词管理系统听起来简单真正要把它做得能跑、能演示、能经得起答辩和验收里面涉及的东西并不少。我前后带过好几个用Spring Boot做内容管理类系统的开发者诗词管理系统刚好属于“业务不复杂、但细节特别多”的典型场景数据怎么来、分类怎么建、全文搜索怎么做、收藏功能怎么实现、管理端权限怎么控制每一样都够你踩几个坑。今天我就围绕这套系统把从需求拆解到表结构设计、从核心接口到部署运行的全过程拆开讲代码给到位坑也给你标出来。1. 项目定位与选题思路1.1 从标题信息拆解出的核心需求光看标题它的特征词有三个Java、Spring Boot、诗词管理系统。Java说明技术栈落在JVM生态不是Python、Go那些。Spring Boot说明开发方式是现代化Java后端框架路线不是Servlet/JSP老古董。诗词管理系统说明业务领域是内容管理核心是诗词的增删改查、分类浏览、检索、详情展示加上用户侧和管理侧两个视角。再结合项目编号820我把它理解为“第820个课程设计/实训项目”的可能性最大。这种项目通常要求的核心功能是用户注册登录、诗词列表展示、按作者或分类筛选、关键词搜索、诗词详情、管理员对诗词的维护。再高级一点的会加收藏、评分、随机推荐、数据统计这类扩展功能。在实际做这个项目之前建议先把一句话需求写清楚这是面向普通诗词爱好者提供诗词浏览、检索、收藏的平台管理员负责维护诗词和分类数据。需求定下来后面所有表结构和接口设计都有依据不会做到一半来回改。1.2 为什么使用Spring Boot来承载这套系统Spring Boot不是性能最强、也不是代码最少的选择但它是课程设计和中小型管理系统场景下最合适的方案。原因有三点第一生态成熟。Spring Boot自带Spring MVC、数据访问、参数校验、统一异常处理等能力网上资料极其丰富遇到问题基本搜得到答案。对一个交付时间有限的项目来说这一点太重要了。第二简化配置。传统Spring项目里那些XML配置文件在Spring Boot中大幅简化内嵌Tomcat也免去了单独安装服务器的麻烦。开发机上有Java和Maven就能启动演示的时候特别方便。第三岗位对接顺利。Spring Boot是现在Java后端开发的主流框架做完这个系统简历上写“基于Spring Boot的诗词管理系统”面试官至少不会陌生也方便往微服务方向延伸。总结一下我对选题的看法做这套系统的目的不是“发明一个新的诗词平台”而是通过一个完整业务闭环把Java EE核心知识串起来顺便证明你有独立完成全栈功能的能力。所以设计上宁可中规中矩也不要过度设计——这是这类项目最容易犯的毛病。2. 系统整体设计从功能模块到数据模型2.1 功能模块如何划分才合理建议把系统拆成三个端用户端、管理端、后端服务层。用户端面向普通用户核心功能有注册与登录邮箱或用户名注册密码加密后落库。诗词列表按朝代、作者、分类筛选分页展示。诗词搜索按标题、作者、正文内容做关键词检索。诗词详情展示全文、注释、赏析提供收藏和取消收藏按钮。个人收藏查看我收藏过的诗词列表支持移除。管理端面向管理员核心功能有诗词管理新增、编辑、删除诗词。分类管理维护诗词的朝代、类型、作者信息。数据统计可选加分项展示诗词总数、分类占比、最热收藏Top10。后端服务层就是REST API统一返回JSON前后端分离。用户端和管理端可以放在一个Spring Boot工程里用不同控制器区分也可以拆成两个前端页面后端接口共用。考虑到项目规模单工程、双模块划分是效率最高的选择。不要在一开始就想着做评论、私信、积分那些功能不仅拖慢进度还容易引入性能和安全问题。把上面这张清单做完系统已经是一个完整闭环答辩时也完全拿得出手。2.2 数据库表设计与关联关系数据库我建议用MySQL 8.x如果你本机已经装了5.7也能用只要把驱动版本调一下就行。表设计这块我直接给出核心表的字段规划。诗词表 poem 是这个系统的中心表字段建议如下字段名类型说明idbigint主键自增titlevarchar(128)诗词标题authorvarchar(64)作者姓名冗余存储方便展示dynastyvarchar(32)朝代如唐、宋typevarchar(32)类型如五言绝句、七言律诗、词contenttext正文内容translationtext译文可空appreciationtext赏析可空created_timedatetime创建时间updated_timedatetime更新时间用户表 sys_user 字段包含 id、username、password、nickname、avatar、role、created_time。password使用BCrypt加密role区分 admin 和 user。收藏表 favorite 做联合唯一约束字段名类型说明idbigint主键user_idbigint用户IDpoem_idbigint诗词IDcreated_timedatetime收藏时间favorite表加上 unique(user_id, poem_id)防止同一个用户对同一首诗收藏两次。分类这块不需要单独建表直接使用 poem 表里的 dynasty 和 type 字段做枚举查询时 distinct 去重即可。这样设计的原因是课程设计量级的系统不会有动态分类需求硬建分类表反而让关联查询变复杂。2.3 实体类与Repository层代码骨架以JPA方式为例先引入spring-boot-starter-data-jpa然后在实体类上做映射。poem实体的核心代码Entity Table(name poem) public class Poem { Id GeneratedValue(strategy GenerationType.IDENTITY) private Long id; Column(nullable false) private String title; private String author; private String dynasty; private String type; Column(columnDefinition text) private String content; Column(columnDefinition text) private String translation; Column(columnDefinition text) private String appreciation; Column(name created_time, updatable false) private LocalDateTime createdTime; Column(name updated_time) private LocalDateTime updatedTime; // getter / setter 省略 }Repository层建议继承 JpaSpecificationExecutor这样在做组合条件查询的时候会很灵活public interface PoemRepository extends JpaRepositoryPoem, Long, JpaSpecificationExecutorPoem { ListPoem findByAuthor(String author); PagePoem findByDynasty(String dynasty, Pageable pageable); PagePoem findByTitleContaining(String keyword, Pageable pageable); }如果你更习惯MyBatis-Plus那可以把JPA换成MyBatis-Plus的分页插件原理相同。核心提示是无论用哪个ORM实体字段和表字段的映射一定要仔细检查尤其是那种数据库字段带下划线、Java字段驼峰的对应关系。3. 核心功能实现与关键代码讲解3.1 后端接口统一响应规范这类系统很常见的问题是前后端各写各的返回格式乱成一锅粥。有人接口返回Map有人直接返回实体还有人把错误信息塞在HTTP code里前端拿到完全不知道该怎么办。我强烈建议在写第一个接口之前先定义一个统一响应类。Data public class ResultT { private int code; private String msg; private T data; public static T ResultT success(T data) { ResultT r new Result(); r.setCode(200); r.setMsg(操作成功); r.setData(data); return r; } public static T ResultT error(int code, String msg) { ResultT r new Result(); r.setCode(code); r.setMsg(msg); return r; } }所有Controller统一返回Result成功返回Result.success(data)业务异常返回Result.error(500, xxx)。前端拿到的永远是同一种结构解析逻辑只需写一遍。这个习惯会帮你省掉非常多联调时间。3.2 诗词列表分页查询接口诗词列表是系统使用频率最高的接口通常支持关键词、作者、朝代、类型四个条件加上分页参数。我用JPA Specification来实现动态条件查询作用是当条件为空时自动忽略该查询条件。Service public class PoemService { Autowired private PoemRepository poemRepository; public PagePoem search(String keyword, String author, String dynasty, String type, int pageNum, int pageSize) { SpecificationPoem spec (root, query, cb) - { ListPredicate predicates new ArrayList(); if (StringUtils.hasText(keyword)) { predicates.add(cb.like(root.get(title), % keyword %)); } if (StringUtils.hasText(author)) { predicates.add(cb.equal(root.get(author), author)); } if (StringUtils.hasText(dynasty)) { predicates.add(cb.equal(root.get(dynasty), dynasty)); } if (StringUtils.hasText(type)) { predicates.add(cb.equal(root.get(type), type)); } return cb.and(predicates.toArray(new Predicate[0])); }; Pageable pageable PageRequest.of(pageNum - 1, pageSize, Sort.by(Sort.Direction.DESC, createdTime)); return poemRepository.findAll(spec, pageable); } }这里有个容易弄错的地方前端传第1页时JPA的PageRequest接收的是页码0作为第一页。所以接口里做了 pageNum - 1 的转换这是个细节但做不好前端每一页都错位。分页接口返回的Page对象建议转成自己封装的PageResult只返回总数和当前页列表避免把JPA的Page内部结构直接透传给前端。可以这样写Data public class PageResultT { private long total; private ListT list; }3.3 诗词关键词搜索的两种做法这个系统对搜索的要求其实很直接输入一个词标题、作者、正文里和它匹配的都找出来。最简单、最可靠的是用LIKE模糊查询ListPoem findByTitleContainingOrContentContaining(String keyword1, String keyword2);这种写法的问题是多条件组合起来SQL很容易写得特别长。建议直接用Specification把“标题或正文包含关键词”统一处理String pattern % keyword.trim() %; predicates.add(cb.or( cb.like(root.get(title), pattern), cb.like(root.get(content), pattern), cb.like(root.get(author), pattern) ));至于是不是要上Elasticsearch或者MySQL全文索引课程设计不需要只会增加部署难度。用普通LIKE配合数据量可控的前提足够应付演示和答辩。3.4 用户收藏与取消收藏的实现细节收藏功能看上去就是insert和delete但要注意两个细节一是防重复收藏二是收藏状态的查询。防重复收藏最有效的方式是建表时加唯一约束然后在Service里捕获异常或者先查再插Transactional public boolean toggleFavorite(Long userId, Long poemId) { OptionalFavorite fav favoriteRepository .findByUserIdAndPoemId(userId, poemId); if (fav.isPresent()) { favoriteRepository.delete(fav.get()); return false; // 表示已取消收藏 } else { Favorite f new Favorite(); f.setUserId(userId); f.setPoemId(poemId); favoriteRepository.save(f); return true; // 表示已收藏 } }收藏状态查询则是根据当前用户和当前诗词查favorite表里有没有记录有就返回true前端据此渲染红心或星星的选中态。3.5 管理端诗词新增与修改管理端的核心功能是维护诗词数据主要考验参数校验和事务控制。新增接口建议这样处理用Validated注解校验必填字段。诗的标题和正文不能为空其他字段允许为空。创建时设置createdTime和updatedTime为当前时间。修改时只更新updatedTime不覆盖创建时间。删除建议使用软删除字段方便误删恢复。表里加一个deleted字段tinyint0正常1已删除查询时默认过滤掉已删除数据。这样课程设计阶段不用但后面如果做数据恢复就会感谢自己当初留了这个字段。PostMapping(/admin/poem) public ResultVoid addPoem(RequestBody Valid PoemDto dto) { Poem poem new Poem(); BeanUtils.copyProperties(dto, poem); poem.setCreatedTime(LocalDateTime.now()); poem.setUpdatedTime(LocalDateTime.now()); poemRepository.save(poem); return Result.success(null); }这里用了BeanUtils.copyProperties做对象拷贝但在POJO字段较多、有类型不匹配风险时这个方案容易踩坑。比如前端传了字符串到LocalDateTime字段就会报转换错误。更稳妥的方案是手动赋值或者写一个清晰的转换方法。3.6 诗词数据的初始化导入方案这是好多做诗词管理系统的人卡壳的地方代码写完了数据库里没有数据页面空空如也。怎么办我建议准备一份JSON或SQL文件里面放几十首经典诗词。要求至少有不同朝代、不同作者、不同类型的数据这样做筛选功能才有效果。导入方式有两种方式一Spring Boot启动时自动初始化。在resources目录下放 import.sqlSpring Boot的JPA会在启动时自动执行。但默认机制有些限制文件里的SQL必须是纯HQL或Native SQL且不好控制执行时机。方式二写一个CommandLineRunner启动后检查诗词表是否为空为空则读取JSON批量插入。Component public class DataInitializer implements CommandLineRunner { Autowired private PoemRepository poemRepository; Override public void run(String... args) throws Exception { if (poemRepository.count() 0) { return; } // 读取 classpath:data/poems.json // 解析为ListPoem后批量保存 } }方式二可控性更好也方便以后做数据版本升级。诗词数据我建议至少准备80首以上类别覆盖唐诗三百首、宋词三百首里最著名的作品这样用户随便点开都是熟悉的内容演示效果远比冷门作品好。3.7 前端页面如何快速搭起来如果你熟悉后端前端时间紧张可以用Bootstrap或者Layui这类现成UI框架配jQuery写AJAX请求。用户端和管理端直接做成两个独立的HTML入口公共服务放在 /js/common.js。页面规划用户端首页顶部导航、搜索框、诗词列表卡片区。用户端详情页标题、朝代作者、正文、译文、赏析、收藏按钮。用户端收藏页我收藏的诗词列表支持取消收藏。管理端诗词管理页表格展示诗词列表提供新增、编辑、删除按钮。管理端分类管理页朝代、作者、类型分布展示支持直接维护。前端调用后端接口务必统一处理token或者session。建议登录成功后把用户信息存到sessionStorage每次请求都在header带上token后端用拦截器校验。如果系统规模不大最简单的方案是登录后用session保存用户后端Controller直接通过HttpSession获取当前用户省去token管理的复杂度。这个取舍要提前想清楚不要两头都做浪费时间。4. 实操过程中最常见的坑与排查方法4.1 中文乱码问题这几乎是我见到的第一个高频报错。登录后管理端输入诗词标题是中文保存到数据库变成问号或者前端页面上显示乱码。排查路径先看MySQL数据库连接串有没有指定UTF-8jdbc:mysql://localhost:3306/poem_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai再看MySQL数据库表和字段的字符集。建库时建议明确指定CREATE DATABASE poem_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;最后确认HTML页面meta标签charset是utf-8以及Spring Boot的server.servlet.encoding配置为UTF-8。防止这个问题的根本办法是全链路统一UTF-8数据库、JDBC连接串、Spring Boot配置、HTML页面任何一环是GBK就会出乱码。4.2 JPA分页排序字段不存在异常有开发者在使用Sort.by(Sort.Direction.DESC, createdTime)报错提示创建时间字段找不到。原因多半是实体里字段名是createdTime但数据库列名是created_timeJPA通过字段名映射时应该没问题出问题往往是实体里漏写了createdTime字段或者字段名大小写不一致。JPA的属性名是Java类属性名不是数据库表列名排查时先看实体类再看查询代码。4.3 前后端联调时跨域问题前端页面用8081端口后端接口跑在8080端口必然存在跨域。最简单的解决方法是后端加全局跨域配置Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .allowCredentials(true); } }注意allowedOriginPatterns()配合allowCredentials(true)在Spring Boot新版本里是允许的但旧版本写法容易起冲突。如果遇到报错把allowedOrigins()改成allowedOriginPatterns(*)即可。4.4 前端页面上传数据时日期格式问题前端管理端编辑页面如果有“创建时间”输入框提交的字符串类似 “2025-01-15 10:30:00”后端LocalDateTime字段可能解析报错。解决方式是在DTO字段上增加JsonFormat注解JsonFormat(pattern yyyy-MM-dd HH:mm:ss) private LocalDateTime createdTime;或者让前端直接忽略创建时间字段创建由后端自动填充展示时再格式化。推荐后者因为人工维护创建时间本来就不合理。4.5 诗词详情展示时正文的换行与排版textarea编辑的诗词正文包含大量换行符HTML展示时默认情况下空白字符会被压缩。解决方案有两个一是后端返回时把\n替换为再存库二是在前端展示时把换行符替换成HTML换行标签。推荐第二点因为数据尽量保持原始格式是更好的习惯。用Vue或者jQuery格式化都行正则把\n替换成br即可。4.6 关于权限控制的通用思路管理员接口必须做权限校验不然普通用户调用/admin/poem就能新增诗词系统就失去了管理意义。最简单的做法是写一个LoginInterceptor拦截所有接口请求从session里取出用户信息没登录统一返回Result.error(401, 未登录)。再配合HandlerInterceptor对含/admin前缀的接口做角色校验只有role为admin才放行。不推荐使用复杂的Shiro或Spring Security配置至少我这个项目不会上来就上Security因为那会引入登录页、会话管理、过滤链等多出一倍的知识点。public class AdminInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { User user (User) request.getSession().getAttribute(loginUser); if (user ! null admin.equals(user.getRole())) { return true; } response.setStatus(401); response.setContentType(application/json;charsetUTF-8); response.getWriter().write({\code\:401,\msg\:\无权限\}); return false; } }这里有个小细节拦截器里直接写JSON响应时Content-Type必须包含charsetUTF-8否则前端收到中文消息会出现乱码。4.7 数据库连接不上和时区报错如果启动的时候报类似“The server time zone value Öйú±ê׼ʱ¼ä is unrecognized”的错误这是MySQL时区问题。连接串加上serverTimezoneAsia/Shanghai即可。如果报Communications link failure先检查MySQL服务是否启动再检查端口是否被占用。Spring Boot默认端口是8080如果你的MySQL也占用8080基本上不可能但本地有过别的服务占用8080比如某聊天工具的端口冲突改掉Spring Boot端口server.port80814.8 诗词数据的SQL初始化文件小技巧如果你选择用SQL文件初始化数据要注意MySQL默认的SQL模式可能不允许一次性插入过多记录。稳妥做法是把数据分成多条INSERT每条约10到20首诗词避免单条SQL过大导致max_allowed_packet报错。5. 打包、部署与后续优化方向5.1 本地启动和打包运行这个项目最推荐的交付方式本地打包成JAR使用内置Tomcat直接启动。打包命令mvn clean package -DskipTests生成target目录下的jar包然后执行java -jar poem-system-0.0.1-SNAPSHOT.jar如果部署的服务器没有Java环境记得先装JDK建议版本是JDK 8或JDK 17对应Spring Boot 2.7.x或3.x。用Spring Boot 3.x要特别注意javax.servlet改成jakarta.servlet很多老教程的代码复制过来会编译报错。做这个项目前先确认自己熟悉的版本是哪一个再决定使用2.x还是3.x。5.2 系统演示时的加分操作有一套系统演示的加分策略我几乎每次都会用到准备一份“演示流程脚本”包括注册新用户、浏览列表、搜索关键词、收藏、退出登录、切换管理员、新增诗词、修改诗词、删除诗词。尽量使用真实、熟悉的诗词数据不要用弄几行测试数据就上台。评委随便输入“床前明月光”就能搜到结果比什么都强。管理端展示数据统计图表时如果前端不熟直接用ECharts饼图展示朝代分布效果拉满代码量也不大。5.3 后续扩展方向如果你做完基础功能还想加亮点有几种扩展方案在诗词基础上增加“每日一诗”推荐后端从表中随机取一条数据返回前端轮播。增加热门诗词排行根据收藏表做聚合查询统计收藏次数最多的前十首。用Redis缓存诗词列表接口的热门查询缓解数据库压力。这个操作可以通过缓存穿透、缓存雪崩等问题在答辩中展开描述。把系统打包成Docker镜像部署到服务器体现出基本的容器化能力。5.4 我对这个项目的一些体会做完这套系统我对“诗词管理系统”最直观的感受是它看起来是个内容展示系统实际上考察的是你对数据建模、条件搜索、前后端联动、权限设计和细节处理的综合能力。做的过程中会遇到很多看起来不起眼、却特别耗时间的问题比如中文字体编码、日期格式化、分页边界、跨域请求。这些问题单独拿出来都不难但放在一个完整项目里一起出现时才是真正考验耐心和排查能力的时候。我个人的建议是在开发前先把接口清单列出来一个接口一个接口调试通再进入下一环节。不要想着写完全部代码再统一大调一次调几十个接口出问题你根本不知道先查哪里。开发这种系统的效率秘诀就是“小步快跑接口先行”这也符合真实项目里前后端协作的方式。你先跑通第一个列表接口再跑通详情再跑收藏每跑通一个功能点信心就会多一分整个进度也会扎实很多。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询