ARTICLE DETAIL

资讯详情

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

基于SSM+Flask混合架构的阅微文学网站设计与实现

基于SSM+Flask混合架构的阅微文学网站设计与实现 1. 项目整体设计与技术选型思路1.1 为什么选 SSM Flask 混合架构先说结论这套组合在毕设里的出场率非常高不是因为某一个框架天下无敌而是因为它天然覆盖了展示基础工程能力 展示跨技术栈协作能力这两个评分点。在阅微文学网站这个题目里SSMSpring SpringMVC MyBatis负责主站的核心业务包括小说列表、详情页、阅读页、排行榜、用户登录注册、书架、收藏、评论等。这一套东西用 Java 写教科书味道很正Spring 管理对象、SpringMVC 走 MVC 分层、MyBatis 写 SQL 也灵活基本覆盖了后端开发最常用的技能树。那 Flask 干什么用最开始我也觉得在一个毕设里硬塞两个后端框架有点多余但看了这类题目的要求之后就明白了——Flask 通常用来承担辅助功能比如爬虫入库、关键词推荐、文本相似度匹配、书籍标签分类这种偏 Python 生态的活儿。阅微作为一个文学网站小说数据不可能全靠手工录入用 Python 写爬虫采集数据、用 jieba 分词做关键词提取、再基于 Cosine 相似度给出相似书籍推荐这正好把 Flask 的价值体现出来也避免了 SSM 那边所有功能都大包大揽的臃肿。两者之间的通信方式也不用搞得复杂Java 端通过 HttpClient 或 RestTemplate 调 Flask 暴露的 HTTP 接口Flask 返回 JSONJava 端解析后渲染到页面。跨语言协作这套逻辑一旦跑通答辩被问为什么要用两个框架的时候你回答的空间就很大了。1.2 功能模块怎么拆才合理阅微文学网站这个名字起得挺文雅但拆开看就是标准的文学阅读平台。当时我按用户角色和业务域做了如下划分门户模块首页轮播、推荐位、热门标签、最新入库、分类浏览。小说模块小说列表、详情页、章节目录、内容阅读、书架加入/移除、收藏、评论。排行模块按点击量、推荐票、收藏数、更新时间分别出排行榜单。作者与作品模块作者入驻、作者主页、作品管理作者视角下的小说/章节新增与编辑。用户系统注册、登录、个人信息维护密码用 MD5 加盐处理别用明文。Flask 辅助服务小说数据采集入库、关键词提取、相似书籍推荐。这个拆法有一条好处每一个模块都能独立讲清楚答辩和写说明文档LW的时候不用把所有东西揉成一团。模块之间的依赖关系也简单用户、小说、章节、评论、收藏这五张核心表撑起主站Flask 那边只跟小说表和标签表打交道。从我自己的经验来看不要一上来就想着做小说在线阅读器那种高难度功能把钱花在核心 CRUD 和推荐链路上就够毕业了。一个标准的 PDF 阅读器、复杂的前端排版、阅读进度同步这些都属于加分项而不是必选项。先把基础功能做扎实比什么都强。2. 核心模块细节解析与数据建模2.1 小说模块和排行逻辑小说模块是阅微文学网站的门面排行榜又是小说模块里最抓眼球的功能。在读这类需求的时候很多人第一反应是按点击量排个序不就行了但实际操作里要注意几个点。第一排行不能只按一个指标硬排。我设计的排行榜分了四种总点击榜、推荐票榜、收藏榜、最新更新榜。对应 SQL 也简单比如总点击榜就是SELECT * FROM novel ORDER BY click_count DESC LIMIT 10收藏榜就是按favorite_count排。但问题是如果小说刚入库点击量为 0永远排不上来所以需要加一个时间窗口WHERE create_time DATE_SUB(NOW(), INTERVAL 7 DAY)用于本周热门这样新书也有机会露脸。第二小说详情页的访问量不能每次点击都写一次数据库。如果是毕设级并发量每次UPDATE novel SET click_count click_count 1其实也能跑但更好的做法是把计数放到内存里定时批量写回。我当时用了一个静态 Map 做计数器定时任务每分钟 flush 一次到数据库。这种做法在面试和答辩中非常加分说明你理解读写分离/延迟写入的思想。第三章节阅读页需要缓存。小说章节内容一般是一段长文本每次请求都从 MySQL 取会有压力更合理的做法是加载完章节后放到 Redis 里缓存 key 可以是chapter:detail:{chapterId}设置 30 分钟过期。如果项目要求里没强制 Redis也可以先用本地ConcurrentHashMap做简单缓存但换成 Redis 后整个系统的话语权完全不一样。2.2 作者与作品的关系建模作者和作品这块很多同学会犯一个错误把作者字段直接写成小说表里的一个字符串列。刚开始方便是方便但一遇到作者有多部作品作者主页展示全部小说作者信息需要被编辑这些需求字符串字段就完全不够用了。常规且正确的建模方式是这样作者表authorauthor_id、author_name、avatar、intro、create_time。小说表novelnovel_id、author_id外键关联作者表、title、cover、category_id、status连载/完结、intro、click_count、favorite_count、recommend_count、create_time、update_time。这样做的好处是一对多关系清晰作者主页只需SELECT * FROM novel WHERE author_id ?就能把所有作品拿出来。如果题目还要求展示作品的最新章节章节目录预览那就再关联chapter表按时间倒序取出最新章节标题即可。在页面展示上作者主页要区别于普通用户主页。普通用户看重的是书架和评论作者主页看重的是作品列表和作品数据趋势。我当时在作者主页左侧放了头像和简介右侧是作品表格每部作品后面配了点击量、收藏量、推荐票三个统计数字表格底部再放一个写新书按钮整体感觉比单独堆列表好很多。2.3 数据库表设计的取舍细节讲真阅微文学网站的数据库表设计并不需要很多表核心表控制在 8~10 张就非常合理了。以下是我比较推荐的一套结构表名关键字段说明useruser_id, username, password, salt, email, role用户表role 区分读者/作者/管理员authorauthor_id, user_id, author_name, intro, avatar作者信息与 user 一对一或一对多novelnovel_id, author_id, category_id, title, intro, status, click_count, favorite_count, recommend_count小说主表chapterchapter_id, novel_id, chapter_no, title, content章节内容categorycategory_id, category_name分类标签commentcomment_id, user_id, novel_id, content, create_time读者评论favoritefavorite_id, user_id, novel_id, create_time收藏/书架recommendrecommend_id, novel_id, user_id, create_time推荐票记录确保一用户一小说一票tagtag_id, tag_name标签novel_tagnovel_id, tag_id小说-标签多对多关联表在字段设计上有几个细节值得留意所有时间字段统一用DATETIME并且加默认值CURRENT_TIMESTAMP省得每次插入都手动写时间小说内容、简介这类长文本统一走TEXT章节内容用MEDIUMTEXT也正常密码字段必须有salt列注册时生成随机 salt密码存MD5(password salt)的结果。答辩时考官最怕见到明文密码一旦看到基本就凉了外键不是越多越好能通过代码保证关联的业务就尽量少加物理外键。物理外键在查询和删除时会引起很多锁和约束问题毕设项目完全可以只在 Java 层校验。这些设计看上去不复杂但每一个取舍背后都是为了后续联调少出问题。3. 实操过程从空项目到跑通全站3.1 环境搭建、依赖版本和初始化准备动手写代码之前先把环境配平。我的建议配置是这样的组件版本备注JDK1.8与企业主流一致不要上 17/21 给自己找事Maven3.6管理 SSM 依赖Tomcat8.5/9.0部署 SSM 的容器MySQL5.7 或 8.0注意 8.0 驱动的serverTimezone问题Python3.8/3.9Flask 主流适配版本Flask2.x不要上 3.x 太新的版本插件兼容性有风险Maven 的pom.xml里最核心的依赖就是spring-webmvc、mybatis、mybatis-spring、mysql-connector-java、jackson-databind再配一个druid连接池。很多人卡在Spring 和 MyBatis 版本不兼容上这里可以直接抄一套成熟版本组合比如 Spring 5.1.8.RELEASE MyBatis 3.5.2 mybatis-spring 2.0.1这套我在多个项目里跑过稳定。初始化数据库也建议写个init.sql脚本一次性把库、表结构、测试数据全建好。测试数据很关键小说表至少插 30 条以上章节每个小说插 5~10 章不然排行榜和推荐效果根本看不出来。3.2 SSM 后端接口开发的关键细节SSM 的代码结构我建议严格执行 Controller-Service-Mapper 三层虽然写起来感觉有点绕但后面的坑会少很多。每个接口的开发路线基本都是Controller 层接参数 → Service 层做业务判断 → Mapper 层执行 SQL → 返回 JSON 或跳转页面。我实际开发时会把接口分成两类返回 JSON 的接口注册登录校验、评论新增、收藏切换、推荐票投票。这类接口用ResponseBody返回统一结构{code: 200, msg: success, data: ...}。返回视图的接口首页、小说详情、排行榜、作者主页这些页面用 JSP 模板渲染数据放进ModelAndView。一个容易踩的坑是统一返回结构这件事。不少同学在每一个 Controller 里都手动拼 Map结果接口和前端不好对接。我直接写了一个Result工具类里面提供success(data)和error(msg)静态方法所有接口都返回同一个结构。虽然是个很小的设计但联调时省了大量时间。再说一个非常实际的细节分页。阅微的小说列表和评论列表没有分页几乎不可能我当时用了一个轻量的 PageHelper在 MyBatis 的SqlSessionFactory里配置插件PageHelper.startPage(pageNum, pageSize)一行代码就能拿到分页结果。这个工具是中国人写的在中文社区用得非常普遍毕设里出现它也算加分项。在实现章节阅读接口的时候要注意上一章/下一章的跳转逻辑。最稳定的是在 Service 层先查出当前章节的chapter_no再按chapter_no 1或chapter_no - 1去查相邻章节而不是用主键 ID 加减一。因为主键 ID 在数据导入时不一定连续但章节编号一定是连续的。3.3 Flask 辅助模块的实现思路和步骤Flask 端在阅微文学网站里的定位不是主业务而是数据服务和推荐服务。我实现的三个核心功能分别是小说数据采集入库用 Requests BeautifulSoup 抓取公开的小说网页解析出书名、作者、简介、封面图、分类标签然后通过 PyMySQL 写入 MySQL。注意爬虫只是辅助工具不要把整个系统建在爬虫上数据来源多了容易碰版权边界毕设里自用测试没问题但扩展成公开服务就要小心了。我从第三方书城抓了一些公开免费章节做测试数据入库之后立刻把采集源断开了后面全部靠本地数据跑演示。关键词提取用jieba.analyse.extract_tags从小说简介里提取关键词再关联到标签体系。这一步做得很粗糙也没关系重要的是把链路跑通小说简介 → 关键词 → 写入novel_tag表。相似书籍推荐这是 Flask 端最能出彩的功能。基本做法是把每本小说的简介分词后向量化计算每本书之间的 Cosine 相似度然后在详情页底部展示喜欢这本书的人也喜欢。Flask 项目结构我建议这样flask_service/ app.py recommend.py crawler.py requirements.txtapp.py里只挂路由from flask import Flask, jsonify, request from recommend import get_similar_novels app Flask(__name__) app.route(/api/recommend, methods[GET]) def recommend(): novel_id int(request.args.get(novel_id)) result get_similar_novels(novel_id) return jsonify({code: 200, data: result})recommend.py里做向量化和相似度计算import jieba from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.metrics.pairwise import cosine_similarity def get_similar_novels(novel_id, top_n5): # 从数据库取出所有小说简介构造 corpus corpus [...] # 每项为 novel_id:title:content tfidf TfidfVectorizer(tokenizerjieba.lcut) matrix tfidf.fit_transform(corpus) sim cosine_similarity(matrix[novel_id], matrix).flatten() top_indices sim.argsort()[::-1][1: top_n 1] return [corpus[i].split(:)[0] for i in top_indices]当时踩了一个小坑TfidfVectorizer 默认的 tokenizer 对中文不友好必须传tokenizerjieba.lcut否则分出来的全是单字。这个问题如果不改推荐结果烂得没法看。3.4 Java 端怎么调用 Flask 接口Java 端调用 Flask 接口不是简单的知道有接口就行要注意超时和异常处理。我当时的做法是写一个独立的FlaskClient工具类统一封装调用逻辑Component public class FlaskClient { Value(${flask.server.url}) private String flaskUrl; public String getRecommendByNovelId(Long novelId) { String url flaskUrl /api/recommend?novel_id novelId; try { RestTemplate restTemplate new RestTemplate(); ResponseEntityString resp restTemplate.getForEntity(url, String.class); if (resp.getStatusCode().is2xxSuccessful()) { return resp.getBody(); } } catch (Exception e) { log.warn(Flask recommend service unavailable: {}, e.getMessage()); } return {\data\: []}; } }关键点在于即使 Flask 服务挂了Java 端也不能报 500。返回一个空的结果集合页面渲染成暂无推荐书籍系统的可用性就有了保障。这个异常兜底做得好的话答辩时值得主动提。在返回 JSON 给前端展示的时候我是用 Jackson 把字符串解析成JsonNode再转成ListNovelVO避免把 Flask 的字段结构直接透传到页面。这个解耦能让 Java 端和 Flask 端各自演化不至于改一个字段就要两端一起改。4. 常见问题与排查技巧实录4.1 配置类问题毕设里最难缠的问题永远不是业务代码而是环境配置。我在搭阅微文学网站时遇到最多的就是 Java 报错找不到Mapper或者数据库连接失败。遇到这类问题先看控制台前三行再查配置和依赖版本最后再怀疑代码。症状原因解决方案Invalid bound statement (not found)Mapper XML 没被扫描到检查mybatis.mapper-locations是否指向classpath:mapper/*.xml并确认 XML 里的 namespace 跟接口全限定名一致Access denied for user rootlocalhostMySQL 用户权限或密码错误确认jdbc.properties里的用户名密码MySQL 8 还要检查allowPublicKeyRetrievaltrueThe server time zone value Öйú±ê׼ʱ¼äMySQL 时区问题JDBC URL 加上serverTimezoneAsia/Shanghai或serverTimezoneUTCSpringMVC 访问 Controller 404web.xml 中前端控制器路径配错确认 DispatcherServlet 的 url-pattern 是否为/注意别配成/*java.lang.ClassNotFoundException: org.springframework.web.servlet.DispatcherServletTomcat 发布没把 Maven 依赖带进 lib右键项目 → Properties → Deployment Assembly 确认 Maven Dependencies 已添加插一句强烈建议把 Maven 的依赖树先打印一遍看一眼。有一次我的 MyBatis 报错查了半天发现是 spring-core 版本被其他依赖覆盖了导致 Bean 创建失败。用mvn dependency:tree能直接看出冲突比瞎猜高效十倍。4.2 跨域与接口联调问题阅微文学网站如果采用前后端分离比如前端用 Vue 或纯 HTML 做静态页就会遇到跨域问题。Java 端可以统一在拦截器里配置 CORSComponent public class CorsFilter implements Filter { Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletResponse response (HttpServletResponse) res; response.setHeader(Access-Control-Allow-Origin, *); response.setHeader(Access-Control-Allow-Methods, GET, POST, PUT, DELETE); response.setHeader(Access-Control-Allow-Headers, Content-Type); chain.doFilter(req, res); } }如果项目用的是 JSP Bootstrap 这套经典组合跨域问题基本不会遇到因为所有页面都是同一个 Tomcat 服务里渲染的。但要注意的是Flask 端对 Java 端来说是另一个域名/端口如果直接在浏览器里调 Flask 的接口一样会跨域。所以我做联调时坚持让浏览器只和 Java 端通信Java 端再去调 Flask避免在页面上写 Flask 的地址。除此之外联调时还见过一个比较坑的 bugPOST请求提交表单数据Controller 用RequestParam接收但是前端用application/json格式传参导致后端参数全部为 null。在 JSP 原生表单的情况下一定要让前端用application/x-www-form-urlencoded或者后端改用RequestBody接收 JSON。两种方式要提前约定好不然后面调试特别痛苦。4.3 数据一致性与脏数据排行榜和数据统计是阅微文学网站内容一致性的重灾区。我的排行数据不是实时算的而是用定时任务每 5 分钟更新一次rank相关的冗余字段这样列表查询极其快但会存在延迟。如果答辩老师问你为什么排行榜不算实时数据你要能答上来实时统计消耗大对用户体验来说 5 分钟内的延迟完全可以接受。评论和推荐票的写入一致性也很关键。推荐票最常见的bug是用户疯狂点按钮一秒钟给同一本小说投几十票。我的解决办法是数据库层面加唯一约束uk_user_novel (user_id, novel_id)然后代码里捕获DuplicateKeyException给前端返回今日已投过票。这个方案比起先查后插在高并发下更靠谱因为查和插之间永远存在时间差。5. 调试、部署与文档整理心得5.1 本地调试的实用技巧和常见踩坑先吐槽一句调试这种多端系统最重要的不是写代码快而是日志打得好。我在 Service 层和 Mapper 层每个关键入口都打了日志用log.info记录参数和返回数据量。一开始觉得麻烦后期排查问题时真的太香了尤其涉及排序不对推荐结果为空这种玄学问题日志一看就能定位到是 SQL 写错还是 Flask 返回格式不对。开发环境里我配置了热部署。Spring Boot 项目可以用 devtools但 SSM 传统打包到 Tomcat 的方式没法一键热部署只能靠 IDE 里的 JRebel 或者直接把项目加入 Tomcat 的 Context修改代码后自动编译重载。虽然没有 Spring Boot 那么丝滑但比每改一次代码就重启整个 Tomcat 快太多了。前端页面在调试时也容易出问题尤其是小说内容排版。我一开始直接输出长文本页面上一坨文字挤在一起毫无可读性。后面在大纲里加入display: block 首行缩进 行高才算能看。做文学类网站阅读观感比功能复杂程度重要得多答辩老师打开首页第一眼就会看页面是否清爽。5.2 部署上线要避开的几个雷阅微文学网站这种 SSM 项目部署有两种常见方式一是打 WAR 包丢进 Tomcat二是用 Spring Boot 插件打可执行 JAR。传统 SSM 我用的是 WAR 包方案。部署时最容易犯的错误是只上传了 WAR 包但忘了把jdbc.properties里的数据库连接串改成生产环境地址。这个错误低级但非常常见我在给一个朋友检查部署日志时就见过整整查了半小时才发现是数据库连到了本地。Flask 端部署时注意别用自带的app.run()直接对外提供服务它不支持并发也不安全。正确用法是用 Gunicorn 起服务gunicorn -w 4 -b 0.0.0.0:5000 app:app-w 4表示 4 个 worker 进程能扛住并发量。如果 Windows 本机没有 Gunicorn本地调试用app.run(threadedTrue)也是可以的但部署到 Linux 服务器务必换 Gunicorn。另外部署后开放防火墙端口也是老生常谈。Tomcat 的 8080 和 Flask 的 5000 都要在安全组里放行不然本地访问正常、服务商上打开全没反应。每次部署前先curl -I http://localhost:8080验证本机是否通了再去排查外部访问问题。5.3 说明文档与答辩准备的整理思路这类项目文件夹里通常包含源码 LW论文/说明书 调试文档 讲解视频说明文档的质量直接影响答辩评分。我的整理原则是先写清楚系统模块划分再画核心流程图最后用表格列出核心接口。不用长篇大论复制代码重点是让评审人快速理解系统是怎么运转的。调试文档我建议盖章成问题排查手册的形式把上面提到的Invalid bound statement、时区问题、Mapper 扫描不到、推荐接口超时等问题每个都写三行现象、原因、处理方式。答辩时老师就喜欢问这种你遇到过什么 bug手上有这份文档你就能对答如流。讲解视频不要录得太长12~15 分钟是黄金长度。推荐这样分配开场 1 分钟讲架构图8 分钟按首页→小说列表→详情→阅读→评论→收藏→推荐→作者后台的顺序演示最后 2 分钟讲 Flask 推荐链路是怎么跑的。能讲到把三个角色读者、作者、管理员的操作都覆盖一遍不熟练都难。最后再分享一个让我自己收益很大的小习惯项目做完全套之后找一个同学从零开始按你的 README 部署一遍你会惊讶地发现里面至少有五到十处没说清楚的地方。把这些坑补进调试文档这个毕业设计才是真的源码文档讲解都能打的高质量交付物。
返回列表