拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

SSM与Flask协同:古诗词展演系统全栈设计与实现

SSM与Flask协同:古诗词展演系统全栈设计与实现

前些年帮朋友做毕业设计,接触过不少文化展示类项目,大多是套个后台管理系统、配个增删改查界面,做完就完事。但这次拿到“基于Java+SSM+Flask古诗词展演系统”这个题目时,我却觉得有点意思——它不是让你纯粹做管理,而是要把中国古诗词这种传统文化,用新媒体的方式“展”出来。这中间的痛点是,诗词是文字,展演是视觉和交互,如何把两者揉在一起,既不能做成PPT轮播,又不能做成纯文本阅读器。在搞清楚业务定位、梳理好SSM后端与Flask辅助服务的分工后,我搭出了一套完整可跑的数字化展演平台,源码、调试文档、讲解视频一应俱全。这篇文章就把整个项目的设计思路、技术选型、核心实现和踩坑实录从头到尾盘一遍,给正在做类似文化类全栈项目的同学一个可直接参考的样板。

1. 项目背景与整体设计思路

1.1 为什么古诗词需要“展演系统”

传统文化数字化不是简单把书搬到网上,而是要解决传播效率的问题。中国人对诗词并不陌生,但认知多数停留在课本上——你知道李白的《将进酒》写了什么,但不清楚他是在什么人生阶段写的,同时代的杜甫在经历什么,这首诗后来又被多少人唱和。传统展示方式是线性阅读,很难建立这种“时空网络感”。

展演系统的核心价值,就是把诗词置于“空间+时间+人物+主题”的多维坐标系里。用户打开系统,不是面对一个搜索框和一片列表,而是进入一个可浏览的诗词展馆:可以沿朝代轴依次走过先秦、汉魏、唐宋、明清,也可以点击李白节点,看他的生平脉络、代表作品、同时期诗人关联、后人对他的评价。这种呈现方式在信息设计上属于沉浸式内容架构,比传统图文列表更符合新媒体视域下的传播规律。

实际做下来我最大的感受是,这个项目定位对了,方向就差不了多少。技术只是实现工具,真正的门槛在于怎么把诗词的“意境”转换成系统的信息架构。比如一首《静夜思》,文字只有二十个字,但与之相关的信息维度可以拆出月亮意象、思乡母题、李白生平、唐代漂泊文化等多个展演切片。系统若能把切片组织成观展路径,就有了“展演”的质感。

1.2 技术选型:SSM为主力,Flask做外援

很多同学拿到这个题目时问,为什么不直接用Spring Boot?为什么还要引入Flask?这其实牵扯到项目的实际落地场景。

SSM(Spring + SpringMVC + MyBatis)是经典Java Web组合,在高校课程设计和毕业设计中仍是主流考核标准。它对分层架构的要求非常清晰:Controller负责接口路由,Service负责业务逻辑,Mapper负责数据库操作。用SSM去做这类系统,最直接的好处是结构规整、难度可控、岗位匹配度高——Java开发岗面试时SSM几乎是必问内容,把它吃透了比单纯会用Spring Boot对原理的理解更深。

那Flask承担什么角色?我让它在系统里做了两件事:一是搭建一个轻量的数据辅助服务,负责诗词文本的切分、关键词提取、意象标签生成等自然语言处理任务;二是为展演页面提供动态聚合接口,比如根据当前浏览的诗人实时拉取同时代文人关系数据。这些功能当然可以用Java写,但Python生态里现成的分词、文本相似度计算库更丰富,开发效率高很多。因此架构上我采用的是“Java主服务负责核心业务+Flask辅助服务负责智能处理”的混合模式,两者通过HTTP/JSON接口通信,互不干扰,部署时也是两个独立进程。

选这套方案还有一层务实考虑:纯SSM技术栈的代码仓库在毕设答辩时容易被问得很细,你把Flask部分包装成“基于Python的新媒体数据处理模块”,反而能展示技术广度。实际答辩效果也印证了这一点。

1.3 系统信息架构:从“诗词库”到“展演空间”

我把整个系统规划成四个层次:

  • 基础数据层:诗词主库、诗人库、朝代库、意象标签库,这是整个系统的数据底座。
  • 业务服务层:由SSM提供管理端API,覆盖诗词/诗人/专题的增删改查、状态管理、用户评论与收藏。
  • 展演计算层:由Flask提供内容计算能力,包括诗词分词、风格标签提取、诗人关系图谱数据组装。
  • 呈现层:前端页面负责展演效果,包含首页展演导航、朝代时间轴、诗人空间地图、诗词主题策展等视图。

这个架构的好处是“管理”和“展演”逻辑分离。管理员在后台维护数据,普通用户在前台消费展演内容,Flask位于中间,把原始数据加工成更符合可视化展示的信息结构。比如前端要展示“月”意象的关联诗词,Flask先从诗词库分词后打上“月”标签的诗词清单,再按情感倾向分组,前端拿到分组结果就可以做主题卡片墙。

实际编码前,我花了两天时间把表结构和接口文档梳理清楚,才动手写代码。信息架构设计阶段偷懒,后面重构付出的代价是十倍。这个教训值得每一个做全栈项目的人牢记。

2. 数据库与核心接口设计

2.1 五张核心表的建模细节

古诗词展演系统看起来功能不复杂,但数据建模要想清楚,否则写业务代码时经常卡壳。我最终设计了五张主表,字段设计经过几轮调整,踩过不少坑后才稳定下来。

诗词表是系统的核心。字段包含诗词ID、标题、作者ID、朝代ID、正文、译文、注释、赏析、意象标签、创作背景和浏览量,其中意象标签用逗号分隔存储。刚开始我只存了标题、正文和作者,结果做“主题策展”功能时根本没法按意象筛选,只能在代码里硬匹配关键词,效率极低且不准。后来新增了意象标签字段,由Flask自动分析正文后回填,才彻底解决问题。

诗人表不能只存姓名和生卒年。为了满足展演页的“人物志”效果,我增加了字号、别称、籍贯、生平简介、代表作数量、诗词风格标签等字段。籍贯和生卒年一起用,可以画诗人的地域分布图和时间轴。风格标签(如豪放、婉约、田园、边塞)由Flask依据诗人作品词频统计生成,为后续按风格聚合诗人提供数据支撑。

朝代表结构最简单,就是朝代名称、起止年份和简介。为什么需要单独建表而不是直接把朝代作为诗词表的一个文本字段?因为展演系统需要一条“时间轴”,从先秦到明清,朝代必须有先后顺序和年份跨度,前端才能渲染出连续的时间刻度。

另外两张表是用户评论表和用户收藏表,用于前台互动。评论表记录用户ID、诗词ID、评论内容、评论时间和父评论ID支持楼中楼;收藏表记录用户ID和诗词ID的关联。收藏和评论功能是毕设答辩中体现“系统完整性”的重要抓手,建议不要省略。

2.2 管理端API:SSM的Controller层这样组织

SSM接口层我按照资源维度拆分,没有把接口全部堆在一个Controller里。诗词管理接口、诗人管理接口、朝代管理接口、用户互动接口各建一个Controller,每个Controller内部再按REST风格细分,代码结构非常直观。

以诗词管理接口为例,核心接口包括:

  • POST /api/poem:新增诗词,请求体包含标题、作者ID、朝代ID、正文、译文等字段。
  • DELETE /api/poem/{id}:删除诗词,同时会级联删除该诗词的评论和收藏记录。
  • PUT /api/poem:更新诗词信息,可用于管理员修改解析内容或修正意象标签。
  • GET /api/poem/{id}:获取诗词详情,返回所含字段及作者和朝代的联表信息。
  • GET /api/poem/page:分页查询诗词,支持关键字搜索和按作者/朝代/意象筛选。

分页查询这个接口最值得拿出来说说。我的实现方式是:Mapper层接收PageNum、PageSize、Keyword、AuthorId、DynastyId、Tag等参数,用动态SQL拼接查询条件。MyBatis的<where>标签和<if>标签在这里非常方便,条件不确定时不用担心SQL语句出错,也不会出现全表扫描——因为未传参数时where条件不生效,返回的就是常规分页结果。

@Controller @RequestMapping("/api/poem") public class PoemController { @Autowired private PoemService poemService; @RequestMapping(value = "/page", method = RequestMethod.GET) @ResponseBody public Result pagePoem(@RequestParam(defaultValue = "1") Integer pageNum, @RequestParam(defaultValue = "10") Integer pageSize, String keyword, Integer authorId, Integer dynastyId, String tag) { PageHelper.startPage(pageNum, pageSize); List<PoemVO> list = poemService.queryPoemPage(keyword, authorId, dynastyId, tag); PageInfo<PoemVO> pageInfo = new PageInfo<>(list); return Result.success(pageInfo); } }

2.3 展演数据接口:Flask这样与Java配合

Flask服务的核心价值有两个:做文本分析、给前端提供聚合接口。这两个功能分两个Blueprint实现,一个叫analysis,一个叫exhibition。

analysis服务的接口是POST /analysis/tags,接收parameter poemText,返回该诗文的意象标签和情感倾向。它内部先对文本做分句,再用停用词表过滤虚词,保留名词性词汇并与预设的意象词库做匹配,最后把匹配到的意象按权重返回。为了让SSM在保存诗词时能自动完成这个打标签的过程,我在Java侧写了一个调度工具——诗词入库后通过HTTP Client调用Flask接口,拿到标签回填到诗词表的tag字段,整个流程对管理端完全透明。

exhibition服务则是面向展演页面的聚合接口,比如GET /exhibition/poet/{id}/network,传入诗人ID后返回诗人的关系图谱数据。这里的“关系”不是我手工维护的,而是Flask根据诗人朝代、风格标签和作品高频词的相似度计算出来的——同朝代优先、风格标签重合、高频意象接近的诗人会被标记为关联节点。前端拿到节点和连线数据后,用ECharts的关系图就能渲染出诗人社交网络。

这里有个技术细节值得说:Java和Flask通信时,需要统一JSON格式,并约定好错误码。我给两个服务定义了统一的返回结构,包括code、message和data三个字段,Java侧解析时先判断code是否为200,再做数据类型转换。这个约定在联调阶段避免了大半沟通成本。

3. 核心功能模块的实现与实操

3.1 展演导航与主题策展首页

首页是整个系统的门面,也是“展演感”最强的页面。我没有做传统的列表式首页,而是设计了一套模块化展演布局,从上到下依次是朝代长卷区、名篇推荐区、主题策展区、诗人图谱入口和最新评论滚动区。

朝代长卷区是首页的视觉重心。数据来源于朝代表和诗词表的时间聚合,前端按照朝代起止年份计算相对宽度,渲染成一条横向时间轴。每个朝代节点显示该朝代的诗词总量和代表诗人头像,点击后进入该朝代的诗词展演子页面。这里的优化点是:如果直接用图片做长卷,后续维护成本太高,换朝代还要重新出图。我改成了纯CSS渲染的时间轴,数据库里朝代数据变化,页面自动跟着变化,管理员只需要维护数据就行。

主题策展区是体现“新媒体”特点的重点模块。它背后是策展专题表,每条专题记录包含专题名称、封面图、简介和关联诗词ID列表。管理员在后台创建专题时,可多选诗词加入专题,系统自动生成专题的唯一路径。前台展演页加载专题后,以卡片墙的形式铺开展示,每张卡片是诗词的标题和首句,点进去才会展开全文和赏析。这种“先看题、再读文”的交互设计,比直接展示全文更符合展演场景的观看节奏。

<div class="exhibition-section"> <div class="section-title"> <h2>主题策展</h2> <span class="section-more">进入展馆</span> </div> <div class="card-wall" id="topicCardWall"> <!-- 由JavaScript根据专题数据动态渲染 --> </div> </div>

3.2 诗词详情:从单一阅读到多维度呈现

诗词详情页是整个系统里投入精力最多的页面。标题和正文展示部分和常规模式一致,但正文下方多了一行意象标签,每个标签都是可点击的。点击标签后,前端调用展演聚合接口,返回该意象在所有诗词中的分布情况——比如点击“月”,页面下方区域会展开一个横向滑块,展示所有含“月”意象的诗词卡片,并统计该意象在不同朝代出现的次数,渲染成迷你柱状图。

这个设计的思路是:让用户在阅读一首诗时,沿着意象的线索“逛”到更多诗,打破单篇阅读的闭环,形成展演的“逛展感”。实现层面其实不复杂,前端用Ajax请求Flask的意象分布接口,返回JSON后拼接DOM渲染即可。这里有一个操作细节需要留意,Ajax请求的URL不要写死成localhost:5001,要配置成相对路径然后由Nginx做反向代理,否则部署到服务器上之后接口会全部404。

诗词详情页还加入了作者信息和创作背景Tab。作者信息Tab展示诗人的生平简介、风格标签和代表作列表;创作背景Tab则由管理员在后台维护,内容来源可以是《唐诗鉴赏辞典》等公共资料,也可以是自己整理的历史背景。这个部分对于用户理解诗词非常重要,尤其是《春江花月夜》这类需要结合初唐时代氛围才能读透的作品,背景信息的展演价值远高于注释本身。

3.3 交互功能:评论、收藏与用户中心

为了让系统具备完整的闭环,我做了用户注册登录、收藏诗词、发表评论三件套。用户表放在MySQL里,密码用加盐的MD5存储。虽然MD5不算高安全方案,但对毕设项目而言是标准做法,讲解时你能说清楚加盐的作用就足够了。实际工作中应该用BCrypt或PBKDF2这样的算法,这点在调试文档里有补充说明。

评论功能采用了异步提交模式。用户在文本框中输入评论,点击发表后通过Ajax提交到后端,后端校验用户登录状态后写库,同时更新诗词的评论总数。评论列表默认按时间倒序排列,支持分页加载。为了防刷屏和脏数据,我在后端做了两层校验:第一层是Session里必须存在用户ID,不存在直接返回401;第二层是评论字数限制在2到500字之间,包含空字符串和纯空格判断。

收藏功能比较简单,但有个交互细节值得说一下:前端在诗词详情页会先请求一个收藏状态接口,判断当前诗歌是否已被当前用户收藏,然后决定按钮显示为“收藏”还是“已收藏”。点击收藏时,前端按钮立刻变为“已收藏”状态,然后才发Ajax请求,这样用户体验会好很多,不会因为网络延迟让人觉得没点上。

3.4 后台管理:管理员视角的内容维护

管理端和前台的展演端在同一个项目里,只是通过登录用户的角色字段区分权限。管理员登录后,菜单栏展演导航会多出内容管理分组,与管理相关的接口都在/api/admin前缀下。

诗词管理页面采用经典表格布局:左侧是筛选区,可按照朝代、作者、意象标签筛选;右侧是诗词列表,展示标题、作者、朝代、意象标签和操作按钮。操作按钮包含编辑、删除、查看评论和置顶推荐。列表中新增诗词时用弹窗表单,表单里最麻烦的字段是“正文”,太长的诗会撑开弹窗影响布局。我最终把正文录入改成了textarea自适应高度,并在保存时用ModelAttribute自动绑定接收,绕开了手动拼接参数的笨办法。

朝代管理相对简单,就是名称、年份范围、简介的维护。但要注意:朝代年份跨度一定要填写完整,否则前台时间轴会显示错乱。举例来说,如果唐朝只填了618和907,时间轴渲染正常;如果只填了618没填907,那么唐朝的宽度计算会出现负数,页面样式会直接崩掉。我在后端做了校验,起止年份不得为空且结束年份必须大于开始年份,这样从源头上规避了脏数据。

3.5 环境搭建与部署流程

项目开发环境用的JDK 1.8、Maven 3.6、MySQL 5.7,Flask端用Python 3.8。JDK版本建议不要高于1.8,因为SSM框架对高版本JDK的兼容性偶尔会有坑,特别是CGLIB代理在某些JDK版本下的反射警告,虽然不影响运行但看着烦。数据库连接URL必须加上useUnicode=true&characterEncoding=utf8参数,否则中文写入后会出现乱码,这个问题在Windows本地开发时最容易踩。

SSM端我建的Maven工程包含四个模块:controller、service、mapper和common。common模块放统一返回结果类、分页工具类、MD5工具类等公共组件。mapper模块放MyBatis的Mapper接口和XML文件,XML文件放在resources目录的mapper子目录下。配置文件主要管理数据源、事务管理器、MyBatis的mapper扫描路径和SpringMVC的注解驱动。

Flask端工程结构更轻量:app.py负责创建应用并注册蓝图,config.py存放配置项,blueprints目录下按功能拆分路由,utils目录放分词工具和意象匹配工具。Flask端建议开启CORS跨域支持,用Flask-CORS库即可,但要配置成只在开发环境开启,生产环境由Nginx统一转发,不暴露5001端口。

部署时我用Nginx做统一入口,监听80端口,把/api/poem、/api/comment等Java接口反向代理到8080端口的Tomcat,把/exhibition、/analysis等Flask接口反向代理到5001端口的Gunicorn。这样的部署架构在讲解时非常加分,别人问“两个服务怎么协同”时,你可以直接画出这份代理关系图。

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

4.1 数据库中文乱码

这是本地开发时最常遇到的问题。表现是:存入的中文正常,但读取时变成问号。原因多半是数据库连接URL缺少编码参数,或者是MySQL表本身的字符集不是utf8。修复方法是两步走:第一步建库时指定字符集为utf8mb4,建表时如果没有指定,用ALTER TABLE语句修改;第二步在数据源URL后面加上?useUnicode=true&characterEncoding=utf8。两个地方都改了,重启Tomcat后问题基本消失。

有一个坑是:MySQL 5.7及以下版本的默认字符集是latin1,即使你在URL里指定了编码也没用。这时需要直接修改数据库层面的字符集配置,进入MySQL执行ALTER DATABASE 库名 CHARACTER SET utf8mb4,再对每张表执行ALTER TABLE 表名 CONVERT TO CHARACTER SET utf8mb4。做完之后重新导入数据,问题才会根治。

4.2 JSON序列化日期格式异常

SSM返回JSON时,日期字段默认序列化成时间戳或带时区的格式,前端拿到后无法直接显示为“2024-05-20”这样的标准格式。我在实践中找到了最简单的处理方式——在字段上直接标注JSON格式化注解。

@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") private Date createTime;

在实体类的日期字段上统一加上这个注解,前端拿到的就都是格式化好的字符串。要注意timezone必须指定GMT+8,否则不同时区的服务器会返回相差8小时的时间,前端的“刚刚”判断就会出错。

4.3 Flask依赖冲突与兼容性

Flask端最容易出问题的地方是依赖库版本冲突。我在本地安装分词库时遇到一个典型情况:jieba库的最新版本与Flask框架对Werkzeug的依赖存在版本冲突,导致Flask启动时直接抛ImportError。解决方法是手动固定Werkzeug版本为与Flask兼容的版本,并在requirements.txt中明确锁定。

另一个细节是Flask开发时用的内置服务器性能差,并发一高就卡,但接口供前端调用频率并不高,这个问题不突出。如果要上生产环境,建议部署到Gunicorn上,用gunicorn -w 4 -b 127.0.0.1:5001 app:app启动,4个worker进程对展演系统而言完全够用。

4.4 跨域问题:Java与Flask的前端联调

开发展演页面时,前端在一个端口,Java接口和Flask接口分别在另外两个端口,浏览器会因为跨域限制拦截请求。我在项目中做了两层处理:开发阶段用Webpack DevServer的proxy配置,把/api和/exhibition前缀的请求分别代理到对应服务;生产部署阶段用Nginx统一反向代理。两层方案核心逻辑一样,都是让浏览器认为请求是同源的。

这里有个新手常犯的错误:在Java端使用@CrossOrigin注解或者在Flask端直接设置Access-Control-Allow-Origin: *来解决问题。这两个方案短期内有效,但会埋下安全隐患——任何网站都能跨域请求你的接口。我在调试文档里明确写了不建议生产环境这么干,而是优先使用代理方式解决。

4.5 MyBatis动态SQL报错排查

写多条件查询时,注释掉某个<if>后SQL拼接报错,多半是XML里多写了逗号或漏了空格。MyBatis的<where>标签能自动处理掉多余的AND或OR,但前提是SQL里的AND/OR必须写在条件前面而不是后面。还有一个排查技巧:启动项目时把MyBatis的SQL日志打开,控制台直接打印执行的SQL语句,一眼就能看出拼接问题出在哪。

另外一个隐蔽的坑是动态SQL里使用了<比较符,XML文件会直接解析报错。处理方式是改用&lt;转义,或者把所有参数传入后再用判断标签,不在SQL语句里写小于号。这个细节在讲代码时顺带提一句,老师会觉得你考虑得很周全。

5. 从项目落地中沉淀的几点体会

这套系统从设计到跑通,前后花了两周多的时间。回头复盘,我认为最有价值的不是代码本身,而是这个过程中对“展演系统”这个概念的落地理解。文化类项目的核心从来不是技术复杂度,而是你如何用有限的技术手段营造出场景感。SSM也好,Flask也好,都只是工具,关键在于你怎么组织数据、设计交互、引导视线。

给正在做类似项目的同学三个建议。第一,先画信息架构图再写代码,数据表字段想不全就动手,后面一定返工。第二,前端展演效果的实现优先级不要低于后端接口,这个项目叫“展演系统”,首页丑到没人愿意点第二下,功能做得再深也没意义。第三,调试文档从第一天开发就要同步写,不要等项目跑通了再回忆过程。我这次把每日开发遇到的问题、解决思路和改动记录全部实时记录在调试文档里,最后整理成册时省了巨大的精力,答辩时老师翻着文档问项目细节也全都能接上。

如果后续方向继续扩展,可以考虑接入地图API做诗人生平行迹追踪,用时间轴拉出诗词创作编年长卷,甚至可以引入简单的AI生成功能,根据用户输入的关键词自动创作一首仿古体诗作为展演互动的收尾。技术上的扩展空间还很大,但眼下这套系统的骨架已经足够稳健,SSM做业务底子,Flask做智能外挂,新媒体展演的思路也完整跑通了。踩过坑的同学再回头看这套结构,应该会有同感:好的架构不是设计出来的,是在迭代中被验证出来的。

返回列表