简介本资源是一套完整的古诗词学习网站课程设计项目基于SpringBoot后端Java与MySQL数据库构建前端融合React技术栈面向计算机专业本科生及Java Web初学者解决传统文化类Web应用开发的全流程实践需求。压缩包共596个文件含88个核心Java控制器类如PoetryController、AdminController等、26个HTML页面、72个Class编译文件、26个JS脚本及183张PNG界面截图涵盖前后端代码、数据库脚本、静态资源与配置文件整体体积39.9MB结构清晰便于模块化学习与二次开发。已有951人下载学习资源提供可直接运行的完整系统包含用户浏览/收藏/评论/分享、管理员诗词/用户/通知/资源审核等全功能模块附带SQL建表语句与典型业务逻辑实现细节是理解MVC分层架构、RESTful接口设计与权限管理落地的优质教学案例。1. 为什么古诗词网站不能只靠前端堆页面SpringBoot MySQL 是真正在跑业务的底座你见过太多“古诗词网站”首页飘着水墨动画点开《静夜思》能朗读、带注释、有赏析——但后台一查用户收藏夹是空的搜索框输“月落乌啼霜满天”返回 0 条管理员想看本周最热诗句数据库里连访问日志表都没有。这不是设计问题是底座没立住。这个标题里的JavaSpringBoot MySQL不是凑数的技术栈组合而是解决三个刚性问题的最小可靠方案用户状态必须持久化登录/收藏/错题、诗句数据要支持结构化检索按朝代/作者/体裁/关键字模糊匹配、并发访问下不能丢数据比如多人同时提交背诵记录。它不追求炫技但能扛住真实教学场景——中学语文老师用它布置每日一诗打卡学生交作业、系统自动判对错、生成班级掌握热力图高校古典文学课用它做词频统计实验从《全唐诗》5万首中抽样分析“酒”字在李白与杜甫诗中的共现模式。如果你正卡在“前端做得像模像样一加登录就崩”“MySQL建了10张表但不知道哪张该加索引”“SpringBoot启动报错说找不到 DataSource”那这篇就是为你写的血泪复盘笔记。2. 从零初始化项目Maven 依赖、数据库建模与 SpringBoot 自动装配链路2.1 用 Spring Initializr 选对依赖避开版本地狱别直接spring-boot-starter-web一把梭。古诗词网站的核心矛盾是文本字段长一首诗正文可能300字、查询条件多朝代作者是否含典故是否押韵、写操作频次低但一致性要求高用户收藏不能重复。所以依赖必须精准!-- pom.xml 关键依赖 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-jpa/artifactId /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId scoperuntime/scope /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-validation/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-thymeleaf/artifactId /dependency注意spring-boot-starter-data-jpa是关键。它让Entity注解的 POJO 直接映射到 MySQL 表省去手写 JDBC 模板代码而spring-boot-starter-validation用于校验用户输入的诗词标题长度防 SQL 注入、作者名非空等。thymeleaf虽然不如 Vue 前卫但对古诗词这类内容型网站足够——模板里直接写th:text${poem.content}比 React 的 JSX 更贴近语文老师编辑习惯。2.2 MySQL 建模三张核心表的设计逻辑与字段陷阱古诗词不是博客文章它的元数据结构特殊一首诗可属多个标签如“边塞诗”“七言绝句”一个作者写多首诗用户收藏跨朝代。ER 图必须拆解为Poem诗→ Author作者→ Tag标签→ UserCollection用户收藏四张表。重点说三张表名字段设计要点为什么这么设poemid BIGINT PRIMARY KEY AUTO_INCREMENT,title VARCHAR(100) NOT NULL,content TEXT NOT NULL,dynasty ENUM(唐,宋,元,明,清) NOT NULL,author_id BIGINT NOT NULL,created_at DATETIME DEFAULT CURRENT_TIMESTAMPcontent用TEXT而非VARCHAR(2000)王维《山居秋暝》全文 128 字但加上注释和赏析可能超 2000 字dynasty用ENUM而非VARCHAR避免用户输入“唐朝”“唐代”“唐朝盛”等不一致值保证后续GROUP BY dynasty统计准确authorid BIGINT PRIMARY KEY,name VARCHAR(50) UNIQUE NOT NULL,intro TEXT,birth_year INT,death_year INTname加UNIQUE防止“李白”和“李太白”被当两个作者birth_year/death_year用INT而非DATE古人出生年份常存疑如杜甫生卒年有公元712/713两种说法存整数更灵活user_collectionuser_id BIGINT NOT NULL,poem_id BIGINT NOT NULL,collected_at DATETIME DEFAULT CURRENT_TIMESTAMP,PRIMARY KEY (user_id, poem_id)复合主键(user_id, poem_id)防止同一用户重复收藏同一首诗不设自增id节省空间且语义清晰2.3 SpringBoot 启动时如何自动建表application.yml 的生死配置JPA 默认hibernate.hbm2ddl.autonone即不碰数据库。开发阶段必须显式开启自动建表但绝不能在生产环境用create# application-dev.yml仅开发环境 spring: datasource: url: jdbc:mysql://localhost:3306/tangsong?useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue username: root password: your_password jpa: hibernate: ddl-auto: update # 关键update 会对比实体类与表结构只新增字段/索引不删数据 show-sql: true properties: hibernate: format_sql: true dialect: org.hibernate.dialect.MySQL8Dialect逻辑说明ddl-auto: update是安全底线。假设你给Poem实体加了Column(name is_famous)字段JPA 会自动执行ALTER TABLE poem ADD COLUMN is_famous TINYINT(1) DEFAULT 0但如果把Column(name title)改成Column(name poem_title)它不会重命名字段而是新建poem_title并留着旧title字段——这就是为什么上线前必须用schema.sql手动执行 DDL。MySQL8Dialect必须指定MySQL 8.0 默认启用caching_sha2_password认证插件老版方言不兼容。3. 诗句检索功能落地从 LIKE 模糊查到全文索引的渐进式优化3.1 初期用 JPQL 写搜索但必须规避 N1 查询陷阱用户在搜索框输入“春风”后端 Controller 接收参数后常见错误写法是// ❌ 错误触发 N1 查询 ListPoem poems poemRepository.findByTitleContainingOrContentContaining(keyword, keyword); for (Poem p : poems) { System.out.println(p.getAuthor().getName()); // 每次 getAuthor() 都发一次 SQL }正确做法是在 Repository 层预加载关联对象// ✅ 正确用 Query JOIN FETCH 一次性查出诗作者 Repository public interface PoemRepository extends JpaRepositoryPoem, Long { Query(SELECT p FROM Poem p JOIN FETCH p.author WHERE p.title LIKE %:keyword% OR p.content LIKE %:keyword%) ListPoem searchByKeyword(Param(keyword) String keyword); }参数说明JOIN FETCH p.author告诉 JPA 在查poem表时用LEFT JOIN author ON poem.author_id author.id一起把作者信息捞出来避免循环中反复查库。%:keyword%中的%符号必须写在 Java 代码里传参不能写在 JPQL 字符串中否则会被当成字面量。3.2 当搜索变慢时MySQL 全文索引是唯一出路测试发现当poem.content表达量超 10 万行LIKE %春风%查询耗时从 50ms 暴涨到 2s。此时必须放弃LIKE改用 MySQL 原生全文索引-- 在 poem 表的 title 和 content 字段上建 FULLTEXT 索引 ALTER TABLE poem ADD FULLTEXT(title, content); -- 查询语句改为 MATCH AGAINST SELECT * FROM poem WHERE MATCH(title, content) AGAINST(春风 IN NATURAL LANGUAGE MODE);为什么不用 Elasticsearch古诗词网站的搜索需求极窄用户只搜关键词“落花”“孤帆”不要相关度排序、不要拼音纠错、不要同义词扩展。MySQL 8.0 的NATURAL LANGUAGE MODE已足够——它会自动忽略停用词如“之”“乎”“者”并按词频加权。而引入 ES 会增加运维复杂度需要维护独立集群、同步数据、处理分词器古汉语分词和现代汉语不同HanLP 在 SpringBoot 中整合反而更重。3.3 高频搜索词缓存用 Spring Cache Caffeine 防止 DB 被打爆用户搜“月”字可能每秒 100 次请求。即使有全文索引MySQL 连接池也会吃紧。加一层本地缓存// 在 Service 方法上加注解 Cacheable(value poemSearch, key #keyword, unless #result.size() 0) public ListPoem searchPoems(String keyword) { return poemRepository.searchByKeyword(keyword); }// 配置 Caffeine 缓存策略application.yml spring: cache: type: caffeine caffeine: spec: maximumSize1000,expireAfterWrite10m参数说明maximumSize1000限制缓存最多存 1000 个搜索结果expireAfterWrite10m表示结果 10 分钟后自动失效——避免用户搜“李白”后缓存长期不更新新录入的李白冷门诗。unless #result.size() 0是关键空结果不缓存防止恶意刷search?keywordasdkjfhaksdjf耗尽内存。4. 用户学习行为闭环收藏、背诵记录与错题本的事务一致性保障4.1 收藏功能必须用 Transactional否则出现“已收藏却查不到”用户点击“收藏”按钮后端需同时做两件事插入user_collection记录、更新poem表的collect_count字段。若不用事务可能出现插入user_collection成功但UPDATE poem SET collect_count collect_count 1失败 → 数据库显示“已收藏”但排行榜里这首诗的收藏数没变或反之 → 排行榜数字涨了但用户个人收藏夹里没有。Service Transactional // 整个方法在一个事务里 public class CollectionService { Autowired private UserCollectionRepository collectionRepo; Autowired private PoemRepository poemRepo; public void collect(Long userId, Long poemId) { // 先检查是否已收藏幂等性 if (collectionRepo.existsByUserIdAndPoemId(userId, poemId)) { throw new RuntimeException(Already collected); } // 插入收藏记录 UserCollection collection new UserCollection(); collection.setUserId(userId); collection.setPoemId(poemId); collectionRepo.save(collection); // 更新诗的收藏数 poemRepo.incrementCollectCount(poemId); // 自定义 JPQL UPDATE } }// PoemRepository.java 中定义原子更新 Modifying Query(UPDATE Poem p SET p.collectCount p.collectCount 1 WHERE p.id :poemId) void incrementCollectCount(Param(poemId) Long poemId);为什么用ModifyingJPA 默认Query是只读的。Modifying告诉 Spring 这是个写操作会清空一级缓存确保后续查询拿到最新值。incrementCollectCount方法名比updateCollectCount更精准——它只做1不覆盖原值避免并发时丢失计数。4.2 错题本设计用 JSON 字段存用户作答详情而非硬拆表学生做默写题“默写《春晓》后两句”系统需记录用户填的原文可能错字“花落知多少”写成“花洛知多少”系统标准答案错误位置第3字“落”错写为“洛”错误类型形近字如果为每个字段建单独列表会膨胀。MySQL 5.7 支持JSON类型直接存结构化数据ALTER TABLE user_mistake ADD COLUMN answer_detail JSON;// Java 实体类用 Convert 转换 Entity public class UserMistake { Convert(converter AnswerDetailConverter.class) private AnswerDetail answerDetail; } // 自定义转换器 public class AnswerDetailConverter implements AttributeConverterAnswerDetail, String { Override public String convertToDatabaseColumn(AnswerDetail attribute) { return new Gson().toJson(attribute); // 序列化为 JSON 字符串 } Override public AnswerDetail convertToEntityAttribute(String dbData) { return new Gson().fromJson(dbData, AnswerDetail.class); } }优势查询时仍可走 MySQL JSON 函数如SELECT * FROM user_mistake WHERE JSON_CONTAINS(answer_detail, 形近字)未来加新字段如“语音识别置信度”无需改表结构。5. 避坑指南SpringBoot MySQL 在古诗词项目中最常踩的 5 个深坑5.1 现象SpringBoot 启动报错Failed to configure a DataSource原因application.yml中spring.datasource.url漏了?useSSLfalse参数MySQL 8.0 默认强制 SSL 连接而本地开发环境通常没配证书。解决URL 末尾必须加?useSSLfalseserverTimezoneAsia/Shanghai。serverTimezone也不能少否则created_at字段存的时间比实际晚 8 小时MySQL 服务端时区和 JVM 时区不一致。5.2 现象搜索“山”字返回包含“火山”“江山”的诗但用户只想找描写自然山峦的原因MySQL 全文索引的NATURAL LANGUAGE MODE对单字敏感度低且无法排除特定词组。解决前端加筛选项——让用户先选“主题”山水/边塞/咏物再搜关键词后端用WHERE dynasty IN (唐,宋) AND MATCH(content) AGAINST(山 IN BOOLEAN MODE)BOOLEAN MODE支持山 -火山语法。5.3 现象用户收藏 100 首诗后个人页面加载变慢原因UserCollection表没建复合索引SELECT * FROM user_collection WHERE user_id ?全表扫描。解决立即执行CREATE INDEX idx_user_poem ON user_collection(user_id, poem_id)。注意顺序user_id在前因为查询总是以用户为维度。5.4 现象Poem.content字段存入带换行的古诗网页显示成一团无br原因Thymeleaf 默认转义 HTML 字符\n被当成普通字符渲染。解决模板中用th:utext替代th:textdiv th:utext${poem.content}/div它会把\n自动转为br且不转义符号需确保 content 字段不含恶意脚本。5.5 现象部署到 Linux 服务器后MySQL 连接偶尔超时断开原因MySQL 服务端wait_timeout默认 8 小时SpringBoot 连接池未配置心跳检测空闲连接被服务端主动 kill。解决在application-prod.yml中加连接池配置spring: datasource: hikari: connection-timeout: 30000 validation-timeout: 3000 idle-timeout: 600000 max-lifetime: 1800000 keepalive-time: 30000 # 每30秒发一次 SELECT 1 检测连接存活6. 进阶技巧用 MySQL 窗口函数生成“诗人影响力排行榜”不写一行 Java 代码古诗词网站常需展示“李白写了多少首诗”“杜甫诗作被收藏次数 Top10”。传统做法是 Java 代码查出所有诗人再用MapString, Integer统计——但数据量大时内存溢出。更好的方案是把聚合逻辑下推到 MySQL用窗口函数一步到位。6.1 用ROW_NUMBER()生成实时排行榜假设要查“各朝代收藏数 Top3 的诗人”SQL 如下SELECT dynasty, author_name, collect_count, rank_in_dyna FROM ( SELECT a.dynasty, a.name AS author_name, COUNT(uc.poem_id) AS collect_count, ROW_NUMBER() OVER (PARTITION BY a.dynasty ORDER BY COUNT(uc.poem_id) DESC) AS rank_in_dyna FROM author a JOIN poem p ON a.id p.author_id JOIN user_collection uc ON p.id uc.poem_id GROUP BY a.dynasty, a.name ) ranked WHERE rank_in_dyna 3;6.2 在 SpringBoot 中调用原生 SQL 获取结果集Repository public class RankingRepository { PersistenceContext private EntityManager entityManager; public ListAuthorRanking getTopAuthorsByDynasty() { String sql SELECT ... -- 上面那段 SQL ; Query query entityManager.createNativeQuery(sql); ListObject[] results query.getResultList(); return results.stream() .map(row - new AuthorRanking( (String) row[0], // dynasty (String) row[1], // author_name ((BigInteger) row[2]).longValue(), // collect_count ((BigInteger) row[3]).intValue() // rank_in_dyna )) .collect(Collectors.toList()); } }为什么不用 JPQLJPQL 不支持ROW_NUMBER()等窗口函数必须用原生 SQL。entityManager.createNativeQuery是安全的——它不拼接字符串参数用?1占位符防注入。BigInteger转long是 MySQL 驱动对大整数的默认映射别用Long.parseLong()强转会抛异常。6.3 把排行榜做成定时任务每天凌晨更新缓存Component Scheduled(cron 0 0 2 * * ?) // 每天凌晨2点执行 public class RankingScheduler { Autowired private RedisTemplateString, Object redisTemplate; Autowired private RankingRepository rankingRepo; public void refreshRankingCache() { ListAuthorRanking rankings rankingRepo.getTopAuthorsByDynasty(); redisTemplate.opsForValue().set(ranking:author:top3, rankings, Duration.ofHours(24)); } }教训我曾在线上环境把排行榜查询放在用户请求链路里结果某天语文老师布置全班搜“李白”瞬间 2000 并发触发排行榜计算MySQL CPU 100%。后来改成定时任务Redis 缓存接口响应从 1.2s 降到 80ms。永远把重计算、重 IO 的逻辑从用户请求流里剥离开——这是 SpringBoot 项目从能跑走向能扛的分水岭。希望帮到你。本文还有配套的精品资源点击获取