1. 项目整体设计与思路拆解我最近把一个之前做过的毕业设计项目重新整理了一遍项目包名是“weixin155高质量阅读微信小程序ssm(文档源码)”里面是小程序端源码、后端SSM工程和配套的论文文档。先说结论这套东西确实能跑但如果你只是把它当成“下载下来双击运行”的模板很难真正学到东西。我这次花了一个周末从环境搭建、数据库初始化、后端启动、小程序联调全部走了一遍今天把整个项目的拆解思路、核心实现和踩坑经验都记录下来给准备做类似阅读类小程序、或者正在用SSM做毕设项目的朋友一个参考。这个项目的核心其实很直接一个能看书的微信小程序后端用 SSM 提供数据接口。说“阅读类小程序”而不是“看书App”是因为它没有复杂的电子书解析、音频播放这些功能主打的是“内容聚合 阅读记录 收藏评论”。从毕业设计角度看这种业务规模刚刚好既不会太简单又能把前端交互、后端接口、数据库设计、权限验证这些知识点串起来。1.1 为什么是微信小程序SSM这套组合如果你在选技术栈可能会纠结用不用 Spring Boot。我的看法是Spring Boot 确实省事但 SSM 在这个项目里并不是落后而是故意保留了三层架构的完整链路。你会在代码里看到 SpringMVC 的 Controller、Service、Mapper 三层结构看到 XML 里手写的 SQL看到拦截器怎么处理登录态。这些恰恰是答辩时老师最爱问的东西比如“前端请求到后端经过了哪些环节”“SpringMVC 怎么找到 Controller 方法”“MyBatis 的动态 SQL 有哪些用法”。用 SSM 能把这些讲清楚用 Spring Boot 反而不容易展开。小程序端我建议用原生开发不要一上来就上 uni-app。原生小程序虽然写起来啰嗦但目录结构直观页面生命周期、组件通信、setData 这些概念都是通的。如果你之后想迁移到 uni-app理解了原生逻辑再学框架也就一两天的事。反过来一开始就用框架遇到问题反而不知道是框架的问题还是小程序的问题。前后端职责我是这样划分的小程序端只负责展示和交互不做业务数据计算后端提供 JSON 接口负责登录校验、数据存储、分页查询、内容过滤。这样划分的好处是接口可以复用后期如果要做管理后台、PC 端页面直接调同一套 API 就行。1.2 “高质量阅读”到底需要做哪些功能很多人看到“高质量阅读”会想得很大以为要做电子书阅读器、笔记同步、朋友圈式书评。我做需求边界的时候没这么贪核心就三个动作找书、看书、存进度。然后在这个基础上加了收藏和评论形成基础闭环。我整理出来的功能模块大概是这样的模块功能点后端支撑首页轮播图、精选推荐、分类入口banner 查询、推荐书单接口书城分类浏览、搜索、分页列表分类表、图书分页查询书籍详情封面、简介、目录、收藏图书详情、收藏接口阅读器章节展示、上一章/下一章章节内容接口书架/收藏收藏列表、最近阅读favorite、reading_progress评论评论列表、发表评论comment 表状态过滤个人中心用户信息、我的收藏、阅读历史用户登录态、关联查询后台管理图书/轮播图/评论/用户管理SSM 后端 页面接口这样拆分下来小程序端大约需要 8 个页面左右后端大约需要 15 个接口数据结构也不复杂。对一个人开发来说一到两周能把核心代码写完剩下的时间可以拿来调样式和写文档。1.3 项目目录结构先看一眼再动手不慌拿到源码第一件事不是直接启动而是先把目录结构捋一遍知道每个文件夹里放了什么。这个项目的后端是标准 Maven 工程目录大致是这样ssm-reading ├── pom.xml ├── sql │ └── reading.sql └── src ├── main │ ├── java │ │ └── com/reading │ │ ├── controller │ │ ├── service │ │ ├── mapper │ │ ├── entity │ │ ├── common │ │ └── interceptor │ └── resources │ ├── mapper │ ├── spring │ ├── mybatis-config.xml │ └── db.properties └── webapp └── WEB-INF/web.xml小程序端是原生目录pages下按页面分包utils里放 request 封装和工具函数components放可复用组件。这种结构的核心原则是“一个功能对应一个页面和一个接口”定位问题的时候会非常快。2. 数据库与后端核心实现2.1 数据库表设计先建模后写代码我见过很多人一上来就写代码写到一半发现字段不够用回头改表越改越乱。这个项目我第一件事是先把表设计列出来。阅读类项目里最关键的是内容数据所以图书表是主表其他表都围绕它来设计。我最终用了 7 张表user用户表存 openid、昵称、头像、角色、状态。category分类表存书籍分类比如文学、历史、科技。book图书/文章表存标题、作者、封面、简介、阅读量、上下架状态。book_content章节内容表存每本书的章节目录和正文。banner首页轮播图表关联 book_id控制排序和展示状态。favorite收藏表记录用户收藏了哪些书。comment评论表记录用户对某本书的评论带审核/删除状态。reading_progress阅读进度表记录用户读到哪本书的哪个章节、进度百分比。图书表我截一段核心 SQL 出来CREATE TABLE book ( id INT NOT NULL AUTO_INCREMENT, title VARCHAR(100) NOT NULL COMMENT 书名/文章标题, author VARCHAR(50) DEFAULT NULL, category_id INT DEFAULT NULL, cover VARCHAR(255) DEFAULT NULL, summary TEXT COMMENT 内容简介, read_count INT DEFAULT 0, status TINYINT DEFAULT 1 COMMENT 1上架 0下架, create_time DATETIME DEFAULT NULL, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这里要特别提醒字符集一定用utf8mb4不要用utf8。utf8在 MySQL 里是 3 字节编码存不了 emoji现在小程序端用户昵称、评论里乱七八糟的字符很多不用 utf8mb4 就会出现插入报错或者乱码。阅读进度表是很多人会忽略的但这个表特别重要。它决定了“继续阅读”功能能不能做。CREATE TABLE reading_progress ( id INT NOT NULL AUTO_INCREMENT, user_id INT NOT NULL, book_id INT NOT NULL, chapter_id INT DEFAULT NULL, progress INT DEFAULT 0 COMMENT 阅读进度百分比, update_time DATETIME DEFAULT NULL, PRIMARY KEY (id), KEY idx_user_book (user_id, book_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;联合索引idx_user_book一定加上因为“我的书架”页面最常见查询就是根据用户 id 找收藏和阅读记录没有索引表大了以后会非常慢。2.2 后端分层、统一返回与接口规范后端我按标准的 Controller-Service-Mapper 分层中间用接口和实现类隔离。业务逻辑放 Service数据库操作放 MapperController 只做参数接收和结果返回。一个比较容易被忽略的点是接口返回值必须统一。如果有的接口返回{code:0, data:...}有的直接返回 JSON 数组小程序端解析逻辑就会写得非常痛苦。我定义了一个ResultT类public class ResultT { private Integer code; private String msg; private T data; public static T ResultT ok(T data) { ResultT result new Result(); result.code 0; result.msg success; result.data data; return result; } public static T ResultT error(Integer code, String msg) { ResultT result new Result(); result.code code; result.msg msg; return result; } }所有成功返回 code 都是 0失败返回非 0 码比如 401 未登录、500 服务异常。小程序端拿到响应之后先看 code再看 data逻辑非常统一。分页接口我用了 PageHelper配置在 MyBatis 插件里。Controller 里接收 page 和 size 两个参数ServiceImpl 里调用 PageHelper.startPage然后正常写 list 查询返回时再用 PageInfo 把 total 带出来。一个示例 Controller 是这样RestController RequestMapping(/api/book) public class BookController { Resource private BookService bookService; GetMapping(/list) public ResultPageResultBookVO list( RequestParam(defaultValue 1) Integer page, RequestParam(defaultValue 10) Integer size, RequestParam(required false) Integer categoryId) { return Result.ok(bookService.pageQuery(page, size, categoryId)); } }注意一个细节如果 Spring 版本是 4 以前Controller 方法上要用ResponseBody否则返回字符串不会被序列化成 JSON。新版 Spring 可以直接用RestController但如果你的项目里还有 JSP 页面最好混用而不是把所有 Controller 都改成 RestController。2.3 登录、Token 与权限控制小程序登录的后端流程是这样小程序调wx.login()拿到临时 code把 code 传给后端后端拿 code 去微信服务端换 openid然后用 openid 去查用户表查不到就自动注册最后生成一个 token 返回给小程序小程序后续请求都带上这个 token。token 我一开始用的 UUID后来换成了 JWT。在 SSM 项目里用 JWT 稍微麻烦一点需要加依赖和一个解析工具类但好处是不用在服务端存 session适合前后端分离。核心代码是public class AuthInterceptor extends HandlerInterceptorAdapter { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { String token request.getHeader(Authorization); if (token null || !JwtUtil.verify(token)) { response.setStatus(401); return false; } return true; } }在 SpringMVC 配置文件里注册拦截器时我会把/api/login、/api/book/**下一些公开接口排除掉把需要登录的接口加入拦截范围。这样就不用每个 Controller 方法都手动判断用户身份代码干净很多。还有一个很容易踩的坑注册拦截器时如果不排除静态资源后台管理页面的 js、css 全被拦下来页面样式全丢。当时我排查了很久才发现是拦截器把静态资源也拦了。3. 微信小程序端关键功能落地3.1 封装 request 和顶部导航适配小程序端的网络请求如果直接在每个页面写wx.request后期改接口地址、加 token、统一提示错误工作量会爆炸。我习惯在utils/request.js里做一个统一封装const BASE_URL http://localhost:8080/ssm-reading/api; function request(url, method GET, data {}) { return new Promise((resolve, reject) { wx.request({ url: BASE_URL url, method, data, header: { Content-Type: application/json, Authorization: wx.getStorageSync(token) || }, success(res) { if (res.data.code 0) { resolve(res.data.data); } else if (res.statusCode 401) { wx.navigateTo({ url: /pages/login/login }); } else { reject(res.data); } }, fail(err) { reject(err); } }); }); } module.exports { request };这样页面里只需要const res await request(/api/book/list, GET, { page: 1, size: 10 });微信小程序的顶部导航栏争议很大。默认样式不用管但一旦用了自定义导航就要自己计算状态栏高度和胶囊按钮位置。我用的方法是const menuButton wx.getMenuButtonBoundingClientRect(); const systemInfo wx.getSystemInfoSync(); const statusBarHeight systemInfo.statusBarHeight; const navBarHeight menuButton.height (menuButton.top - statusBarHeight) * 2;这个计算逻辑里menuButton.top - statusBarHeight是胶囊和状态栏之间的空隙乘以 2 是因为上下各有一份空隙再加胶囊自身高度就是整个自定义导航栏高度。实测在 iPhone 和安卓上基本都能适配。3.2 首页、书城与详情页怎么渲染首页的数据流是进入页面后同时请求轮播图接口和推荐图书接口两个接口都返回后用setData一次性赋值给页面数据。这里不要一个个 set能合并就合并减少渲染次数。书城页重点是分类筛选和搜索。分类筛选我用的是一个横向滚动的分类栏最开始我用原生radio-group做选中状态后来发现样式太丑且不可控就直接改成view加自定义 CSS 类。每个分类项绑定一个>inputHandler(e) { clearTimeout(this.timer); this.timer setTimeout(() { this.search(e.detail.value); }, 300); }搜索请求的 URL 不能直接拼中文。wx.request的 url 里如果带中文部分手机会编码出问题。正确的做法是用encodeURIComponent(keyword)把关键词转义后再拼到地址里后端再正常解码。详情页核心就是书籍简介、作者、封面、目录列表、收藏按钮。这里要注意书籍简介是长文本小程序 rich-text 组件可以支持不要用text组件直接渲染因为text组件对 HTML 标签不支持。3.3 收藏、评论、阅读进度三个关键交互收藏功能很简单后端就是一个 favorite 表的 insert 和 delete。前端按钮要即时反馈点击收藏之后图标变实心再点一次取消。这里有一个交互细节请求成功后要更新本地的收藏状态而不是等页面重新加载否则用户感觉按钮“没反应”。评论区列表用分页加载上拉触底时追加下一页。发表评论时输入框要控制长度比如最多 200 字超过就不让输入。后端在做评论保存时我加了简单的敏感词过滤把命中内容替换成*避免垃圾文本直接展示。阅读进度保存是最容易被忽略的。我的做法是进入阅读页时加载章节内容离开页面或切换章节时上报当前章节 id 和进度百分比。小程序里有onHide和onUnload生命周期我会在onHide里保存一次防止用户切出去回微信时丢进度。阅读记录拿到后书架页“继续阅读”就可以直接定位到上次章节。4. 前后端联调、部署与文档整理4.1 从零跑通项目的环境准备如果你拿到的是源码而不是直接可运行的包建议先按这个顺序把环境准备一遍安装 JDK 8、Maven 3.6、Tomcat 8/9。安装 MySQL 5.7 或 8.0导入sql/reading.sql。修改db.properties里的数据库账号密码。用 IDEA 导入 Maven 工程等待依赖下载。启动 Tomcat确认后端能访问。用微信开发者工具导入小程序目录修改request.js里的BASE_URL。在开发者工具里勾选“不校验合法域名”。这些步骤里最容易卡住的是 Maven 依赖下载慢。SSM 项目依赖不算多但第一次没有本地仓库时确实需要等。如果公司或学校网络不好可以考虑把 Maven 镜像源改成国内仓库这个优化非常实际。Tomcat 启动之后先用浏览器访问后端接口比如http://localhost:8080/ssm-reading/api/book/list如果能看到 JSON 返回说明后端已经通了。然后在微信开发者工具里请求同一个接口注意问题出在接口地址写没写对。很多时候不是代码问题而是/ssm-reading这个上下文路径漏掉了。4.2 真机调试和上线前配置本地联调没问题后一定要用手机真机再走一遍。电脑上的开发者工具模拟器只是模拟很多兼容性问题只有真机才会暴露。真机调试第一步是保证手机和电脑在同一个局域网然后把BASE_URL里的localhost改成电脑的局域网 IP。注意 Windows 防火墙有时候会拦截入站请求如果手机上访问不了先关掉 JDK/Tomcat 的防火墙限制再试。上线微信小程序时有两点绕不开接口域名必须是 HTTPS必须在小程序管理后台配置 request 合法域名。开发阶段可以不校验但提交审核时没有合法域名直接会被打回。个人主体和企业主体的能力边界也不同比如getPhoneNumber获取手机号个人主体基本用不了我在项目里换成头像昵称填写的方式避免上线时因为这个功能被卡住。真机预览时还要注意图片域名。小程序 image 组件加载远程图片时图片域名也要在 downloadFile 合法域名里配置否则图片裂开。4.3 文档和源码怎么整理才加分一个完整的 SSM 项目如果只有代码没有文档评委或面试官看着会很累。我这次整理文档时按下面这个清单准备内容README写清楚项目简介、技术栈、如何导入数据库、如何启动前后端。数据库说明表关系图、核心表字段注释。接口文档每个接口的 URL、请求方式、参数、返回示例。运行截图后台管理页面、小程序页面截图。常见问题自己遇到过的坑相当于给下一任开发者留备注。接口文档我习惯用表格写字段清晰后端改一个参数时能快速对照。比如接口方法参数返回/api/user/loginPOSTcodetoken, userInfo/api/book/listGETpage, size, categoryIdtotal, list/api/book/detailGETidbookInfo/api/favorite/addPOSTbookIdnull/api/comment/listGETbookId, pagetotal, list源码目录里一定要把 SQL 脚本单独放到 sql 文件夹不要放在某个不知道的地方。我见过太多项目拿到手数据库脚本找不到或者初始化数据全靠手工 insert这种项目基本没法复现。5. 常见问题与排查技巧实录5.1 后端问题404、端口冲突、事务失效先说说后端最容易出现的 404。如果你确认接口路径写对了但还是 404先看项目部署的上下文路径。Tomcat 里访问路径通常是http://ip:端口/项目名/接口路径少了项目名这一层肯定 404。端口被占用也很常见。Tomcat 默认 8080如果本机服务很多启动时经常报Port already in use。我一般直接改 Tomcat 的server.xml端口或者把占用端口进程结束掉。事务失效是一个隐蔽问题。Service 方法上加了Transactional但实际没生效最常见原因是 Spring 配置里没有开启注解事务管理。在spring-mvc.xml或applicationContext.xml里如果没有加tx:annotation-driven/注解是不会生效的。还有一个原因是同一个类内部方法调用比如 service 方法 A 调本类方法 BB 上的事务不会生效因为 Spring AOP 默认只拦截外部调用。5.2 中文乱码、日期格式和 MyBatis 驼峰映射中文乱码问题我会从三个方向排查数据库连接串里有没有加characterEncodingutf8。后端 response 编码有没有设置成 UTF-8。数据库表、字段的字符集是不是 utf8mb4。日期格式是另一个高频踩坑点。Java 后端返回Date类型给小程序默认序列化后可能是时间戳毫秒值或者显示成英文格式。小程序端想要yyyy-MM-dd HH:mm:ss这种格式我直接用JsonFormat(pattern yyyy-MM-dd HH:mm:ss, timezone GMT8)注解在实体类的日期字段上这样接口返回的就是格式化字符串。MyBatis 的驼峰映射也容易漏。数据库字段是create_time实体类属性是createTime如果 MyBatis 没有开启驼峰映射查询出来的对象里这个字段就是 null。我在mybatis-config.xml里加一句setting namemapUnderscoreToCamelCase valuetrue/这样所有下划线字段都能自动映射成驼峰属性省掉一堆 resultMap。5.3 小程序端常见坑setData、登录态、搜索传参小程序像是个惯坏了的孩子很多坑和浏览器完全不同。第一个是setData的数据量限制单次不能超过 1MB章节内容很多时不要一次把整本书塞进去。我阅读器页面只加载当前章节内容切换章节时再请求下一章这样内存也稳。登录态问题也要注意。wx.login()返回的 code 有效期只有 5 分钟而且用一次就失效所以不要把 code 存起来一直用。每次进入小程序时重新获取 code再换取后端 token。另外onLaunch里的登录是异步的如果页面同时请求接口可能 token 还没拿到就已经发请求了。我处理的办法是做一个loginReadyPromise登录完成后再让后续请求继续执行。搜索传参时如果直接拼接中文某些真机会得到乱码或者请求失败。用encodeURIComponent转义后再拼接后端再用 URLDecoder 解码。这种问题在模拟器里不一定能复现但真机上非常容易出现。还有一个体验问题自定义导航栏后如果页面里用了 fixed 定位很容易被胶囊按钮遮挡。计算导航栏高度时不要把statusBarHeight和胶囊直接相加就完事要按我前面给出的公式算否则 iPhone X 这类刘海屏机型会出现严重的错位。5.4 关于这个项目我最后想啰嗦几句把整个项目重新整理完之后我有一个很深的体会技术栈本身没有新鲜东西SSM 加微信小程序在 2025 年已经不算热门但对做课程设计和入门后端开发的人来说它依然是一个非常合适的训练场。因为业务简单、流程完整你能在两天内看到“从数据库到接口到小程序页面”全过程这种正反馈比看十篇教程都强。我在整理源码时做的一个小习惯是每修一个问题就在文档的常见问题区追加一条记录。别觉得这是浪费时间这个习惯让我在最后写答辩材料时几乎不用额外回忆所有“为什么这么做”“遇到什么问题”全是现成的。如果你也想基于这个项目二次开发我建议优先改两个方向一是把后台管理页面的前端框架换成 Vue 或 React让项目形态更像现代企业应用二是给阅读器加上字号调节和夜间模式这两个功能实现不难但非常提升“高质量阅读”这个词的说服力。动手改起来吧踩坑才是真正学会的开始。