宠物寄养平台微信小程序+SSM实战:从数据库设计到答辩经验
2026/9/16 8:34:02 网站建设 项目流程

“宠物寄养平台”这个题目,我看这两年选的人越来越多,原因其实很简单:业务场景清楚,用户角色也容易划分,技术栈又是经典的“小程序 + SSM”组合,不管是用来做毕业设计还是拿来练手,都能把前端、后端、数据库整条链路完整走一遍。我自己完整做完这个项目之后最大的感受是——这个题目看着不大,但真正动手做,里面的细节远比想象中多,尤其是小程序端到后端的联调、以及预约流程的状态流转,不踩几次坑根本拿不下来。

这篇文章就按我一个做过完整项目的从业者视角,把整个设计思路、核心实现、常见坑点和答辩演示经验一起梳理出来,项目源代码结构、数据库脚本、接口文档这些后续都可以再展开。内容适合计算机相关专业正在准备毕业设计的同学,也适合刚接触微信小程序开发、想搞懂一个完整业务闭环的新手。

1. 项目定位与核心需求拆解

1.1 这个系统到底要解决什么问题

先别急着写代码,第一步应该想清楚:宠物寄养平台,到底解决的是谁的什么麻烦?

养过宠物的人都知道,逢年过节要出远门的时候,宠物怎么办是个真问题。放在朋友家,怕麻烦别人;找线下宠物店,又不知道哪家靠谱、价格是不是透明、评价是不是刷的。反过来,宠物店或者寄养家庭有闲置的寄养位,却找不到稳定的客源,全靠线下口头传播。

所以这个平台本质上是做一个信息撮合加交易管理的双边平台,核心要解决的痛点有三个:

  • 宠物主人侧:快速找到附近可寄养的商家、查看环境图片和评价、在线预约下单、随时查看订单状态。
  • 寄养商家侧:管理可接待的寄养位、接收预约请求、更新订单状态(比如已接单、服务中、已完成)。
  • 平台管理侧:审核商家资质、处理投诉、维护基础数据。

把这三个角色拆出来之后,整个系统的功能边界就清楚了,后面做数据库表设计、接口设计,都不会跑偏。我在实际做的时候还额外加了一个“我的宠物档案”模块,可以让用户提前录入宠物信息(品种、年龄、是否绝育、有无过敏史等),下单的时候直接选择,不用每次重复填写。这个功能虽然看起来不起眼,但演示的时候特别加分,因为它体现了你对真实业务场景的理解。

1.2 为什么是微信小程序 + SSM

项目标题里锁定了“微信小程序”和“SSM”两个关键词,这其实不是一个随意的组合,它背后的逻辑值得聊一聊。

小程序端解决的是获客和使用门槛问题。宠物寄养是一个低频但刚需的场景,用户不可能为了偶尔寄养一次专门去下载一个App。微信小程序“用完即走、扫码即用”的特性,跟这种低频服务场景可以说是天然匹配。而且小程序可以调用微信生态的登录能力、订阅消息能力,用户体验比H5页面好很多。

后端选择SSM(Spring + SpringMVC + MyBatis)而不是Spring Boot,单从技术先进性上看,Spring Boot确实更现代、配置更简洁,但在毕业设计这个特定场景下,SSM反而有它自己的优势。原因主要有三个:

  • 评阅老师对SSM更熟悉,评阅标准相对公开透明,答辩时不容易因为框架版本选择被挑战。
  • SSM的三层结构非常清晰(Controller控制层、Service业务层、Mapper持久层),论文里的架构图画出来层次分明,很好写。
  • Spring的IOC和AOP、SpringMVC的请求流程、MyBatis的动态SQL,这些都是面试常考点,用SSM做一遍项目,等于把基础课里的重点全部串了一遍。

不过如果你后续想扩展,项目里的Service层接口和Controller写法,移到Spring Boot上也就是改改配置的事,不会白做。

1.3 功能模块怎么划分才不会乱

我当时做的时候,功能模块的划分并不是按页面来分的,而是按用户角色加业务域来分。这样的好处是数据库设计和接口命名都能跟着这个逻辑走,后期加功能也很方便。

系统整体分成四个大模块:

  • 用户模块:微信登录、个人信息管理、我的宠物档案(增删改查)。
  • 寄养服务模块:寄养商家列表(支持按关键词搜索、按区域筛选)、商家详情(包括环境图片、费用说明、用户评价)、寄养位信息。
  • 订单模块:创建寄养预约、订单列表(区分用户视角和商家视角)、订单状态流转、取消订单、评价订单。
  • 管理后台模块:商家入驻审核、用户管理、订单监管、反馈处理。这一块理论上应该做成一个单独的后台管理系统,但如果时间紧张,也可以在同类目里用不同的角色权限来区分实现。

有一个容易被忽略的点:寄养订单和普通电商订单不一样,它是有时间维度的,而且涉及到“宠物送入”和“宠物接出”两个物理动作。所以订单状态不能简单地用“待付款/已付款/已完成”来套,而要设计成类似这种流转:

待确认 -> 已确认(待送入) -> 寄养中 -> 待接出 -> 已完成

中间还要允许“用户取消”和“商家取消”两个入口,以及“超时未确认自动取消”这种兜底逻辑。这个状态设计是项目里最核心的业务难点之一,后面我单独展开讲。

2. 数据库设计与SSM后端落地

2.1 核心表结构设计

表结构设计是整项目的地基,这块我在实际操作中返工过两次,第一次是把用户表、商家表混在一起设计,导致后面加角色权限的时候特别痛苦;第二次是订单表里没有冗余宠物和商家的快照信息,导致后来商家改了价格或者宠物信息变了,历史订单的数据就乱了。现在我把最终定稿的核心表结构分享出来。

第一张是用户表,这里要注意一个关键点:表里必须存微信的 openid,而且要给 openid 加上唯一索引,因为同一部手机上的小程序卸载重装后,wx.login 拿到的 code 换出来的 openid 是一致的,它是识别用户身份的唯一凭证。字段设计如下:

CREATE TABLE `tb_user` ( `id` INT PRIMARY KEY AUTO_INCREMENT, `openid` VARCHAR(64) NOT NULL UNIQUE, `nickname` VARCHAR(64), `avatar` VARCHAR(255), `phone` VARCHAR(20), `role` TINYINT DEFAULT 1 COMMENT '1-普通用户 2-寄养商家 3-管理员', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP );

第二张是宠物档案表。这里有一个在实际开发中才意识到的点:宠物的name字段是可空的,因为在真实场景中,有些人会填昵称,有些人填的是品种名(比如“金毛”),所以最后我干脆允许空字符串,不做强制校验,前端在展示的时候自动取 nickname 或者品种名。

CREATE TABLE `tb_pet` ( `id` INT PRIMARY KEY AUTO_INCREMENT, `user_id` INT NOT NULL, `name` VARCHAR(32), `breed` VARCHAR(32), `age` INT, `gender` TINYINT DEFAULT 0 COMMENT '0-未知 1-公 2-母', `weight` DECIMAL(5,2), `sterilized` TINYINT DEFAULT 0 COMMENT '0-未绝育 1-已绝育', `allergy` VARCHAR(255) COMMENT '过敏史', `remark` VARCHAR(255), `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP );

第三张是寄养商家表。设计时要注意:这张表的信息并不只属于商家角色的用户表,而是与用户表通过 user_id 关联的商家详情表。这样做的好处是 tb_user 里统一管理登录认证, tb_merchant 里存店铺的个性化信息,两件事互不干扰。

CREATE TABLE `tb_merchant` ( `id` INT PRIMARY KEY AUTO_INCREMENT, `user_id` INT NOT NULL, `name` VARCHAR(64) NOT NULL, `address` VARCHAR(255), `region` VARCHAR(64) COMMENT '区域,如市区/郊区', `price` DECIMAL(8,2) COMMENT '每日寄养费用', `capacity` INT COMMENT '可同时寄养宠物数量', `description` TEXT, `images` VARCHAR(1024) COMMENT '环境图片,逗号分隔', `status` TINYINT DEFAULT 0 COMMENT '0-待审核 1-正常 2-禁用', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP );

第四张是订单表,这也是全项目最核心的一张表。上面提到过,订单要存商家信息快照和宠物信息快照。快照的意思就是在下单那一刻把商家名称、价格、宠物品种都作为普通字段复制一份到订单表里。不然以后商家改了店铺名字、改了价格,你去查历史订单就会显示旧的还是新的?逻辑上会非常混乱。这个设计是当时一个做了多年电商开发的学长提醒我的,实践下来确实能避免很多问题。

CREATE TABLE `tb_order` ( `id` INT PRIMARY KEY AUTO_INCREMENT, `order_no` VARCHAR(32) NOT NULL UNIQUE, `user_id` INT NOT NULL, `pet_id` INT NOT NULL, `pet_name` VARCHAR(32) COMMENT '宠物名快照', `pet_breed` VARCHAR(32) COMMENT '品种快照', `merchant_id` INT NOT NULL, `merchant_name` VARCHAR(64) COMMENT '商家名称快照', `merchant_price` DECIMAL(8,2) COMMENT '下单时每日价格快照', `start_date` DATE NOT NULL, `end_date` DATE NOT NULL, `total_price` DECIMAL(10,2), `status` TINYINT DEFAULT 0 COMMENT '0-待确认 1-已确认 2-寄养中 3-待接出 4-已完成 5-已取消', `remark` VARCHAR(255), `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP );

最后加一张评价表,关联订单和用户:

CREATE TABLE `tb_review` ( `id` INT PRIMARY KEY AUTO_INCREMENT, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `merchant_id` INT NOT NULL, `score` TINYINT COMMENT '1-5分', `content` VARCHAR(500), `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP );

这五张表是系统的骨架,实际做的时候还可以根据需要补充管理员日志表、收藏表、反馈表等。但核心思路是:先保证业务主链路能跑通,再考虑附加功能。

2.2 后端接口怎么设计和实现

数据库设计好之后,后端接口设计就是下一步。接口设计的原则是面向小程序端的实际需要,而不是面向数据库表。也就是说,前端页面需要什么数据,你才提供什么数据,不要把整张表直接丢给前端。

我习惯把所有接口放在/api前缀下,方便部署时区分动态接口和静态资源。核心接口清单如下:

模块接口路径方法说明
用户/api/user/loginPOST微信登录,传 code 返回 token
用户/api/user/infoGET获取当前用户信息
宠物/api/pet/listGET当前用户的宠物列表
宠物/api/pet/addPOST新增宠物
宠物/api/pet/deleteDELETE删除宠物
商家/api/merchant/listGET商家列表,支持关键字和区域筛选
商家/api/merchant/detailGET商家详情,包含评价
订单/api/order/createPOST创建寄养订单
订单/api/order/myGET我的订单列表(根据角色区分)
订单/api/order/updateStatusPOST更新订单状态
订单/api/order/cancelPOST取消订单
评价/api/review/addPOST添加评价
上传/api/uploadPOST图片上传

接口返回格式必须统一。我当时写了一个统一的 Result 类,所有接口都返回这种结构:

{ "code": 200, "message": "success", "data": {} }

这样前端在小程序里封装 wx.request 的时候,只需要统一判断 code 是不是 200,不用每个接口单独处理异常,代码会简洁很多。

SSM 的三层架构落地的时候,我注意到一个绝大多数教程没有强调的细节:Service 层接口方法名不要直接用addOrder这种,而应该用业务语义命名,比如createBoardingOrder。这样在 Service 实现类里写业务逻辑的时候,方法名本身就是一段可读的文档。Controller 层要足够薄,只负责参数接收和调用 Service,不要在里面写业务判断。

2.3 MyBatis 的坑与配置优化

SSM 项目里 MyBatis 这一层看着简单,实际运行起来最容易出问题的地方就在这一层。

第一个坑是驼峰映射。数据库字段通常用下划线命名(比如create_time),Java 实体类习惯用驼峰(比如createTime)。如果不开启驼峰映射,查询出来的结果 createTime 永远是 null。解决方案是在 MyBatis 全局配置里加上:

<settings> <setting name="mapUnderscoreToCamelCase" value="true"/> </settings>

第二个坑是时间字段的处理。如果数据库用的是 DATETIME,实体类用的是java.util.Date,查询出来给前端序列化后,默认格式是"2023-07-11T12:00:00.000+0800"这种格式。小程序端用new Date()解析这种带时区的ISO格式是没问题的,但如果数据库存的是2023-07-11 12:00:00这种字符串,前端 iOS 上new Date()解析会直接挂掉。我的解决办法是写一个 Json 序列化配置,让后端返回的时间统一格式化为"yyyy-MM-dd HH:mm:ss"

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

第三个坑是 MyBatis 的动态 SQL。列表页通常要支持多个筛选条件的组合查询,如果直接写死 SQL,每次都要重新写一条。用<where><if>可以优雅解决:

<select id="selectMerchantList" resultType="com.example.entity.Merchant"> SELECT * FROM tb_merchant <where> <if test="keyword != null and keyword != ''"> AND name LIKE CONCAT('%', #{keyword}, '%') </if> <if test="region != null and region != ''"> AND region = #{region} </if> AND status = 1 </where> ORDER BY create_time DESC </select>

这样前端只需要传 keyword 和 region 两个可选参数,就能实现组合筛选,不用为每一种筛选单独建接口。

3. 微信小程序端核心功能实现

3.1 页面结构与公共封装

小程序端的页面结构直接决定了开发效率。我当时把页面分成四个 tab,分别对应首页(商家列表)、订单、消息、个人中心。这个划分比较常规,但比较稳妥的是把“宠物档案”功能入口放在个人中心里,而不是单独一个 tab,因为它的使用频率不如前三个高。

所有页面都会用到几个公共能力,我在项目初始化的时候优先封装好了:

  • 请求封装:基于 wx.request 封装了 http.js,自动带上 token、统一处理 401 跳转登录。
  • 登录态管理:在 app.js 里封装login()方法,确保在任何页面调用接口前,如果 token 不存在或者过期,先自动完成登录流程。
  • 时间格式化工具:封装了formatTime方法,专门处理后端返回的各种时间格式。
  • 图片上传组件:基于 wx.chooseMedia + wx.uploadFile 封装了 upload.js,支持多图上传和进度提示。

这些公共封装看起来费时间,但做完整项目的时候会给你节省大量的调试时间。我见过太多人的代码,每个页面里各写一遍 wx.request,每个请求里面又单独处理 token,结果改一个公共逻辑的时候要改几十个文件,这种教训一次就能记住。

3.2 一个容易被忽略的启动加载优化

项目里有个需求是“修改刚进入的加载页面”,这个热搜词其实就是指小程序的启动加载体验优化。

小程序默认从app.json里的pages数组第一项启动,默认界面是白屏加右上角一个胶囊按钮。如果你在首页 onLoad 里做了很多数据请求,用户看到的白屏时间会很长,体验很差。我的优化方案是三层:

第一,把启动页单独拆出来。设计一个简单的 splash 页面,里面放平台 logo 和一句宣传语,onLoad 里并行请求“初始化接口”和“首页数据”,等数据都返回之后再跳转到首页。这样用户看到的是有内容的页面在加载,而不是白屏等待。

第二,首页的商家列表用骨架屏代替加载动画。小程序里手写骨架屏很简单,就是画几个灰色矩形,样式和真实列表布局一致。数据回来后把骨架屏替换掉,观感提升非常明显。

第三,业务数据请求之前先读本地缓存。我封装了一个loadMerchantList方法,优先读取wx.getStorageSync('merchant_cache'),如果缓存存在先渲染缓存,再拉取新数据覆盖更新。这种“缓存优先、网络更新”模式在弱网环境下特别有用。

3.3 微信登录:code 换 token 完整链路

小程序的登录流程本质上就是三个角色之间交换凭证:小程序端、微信服务器、你自己的后端。

正常的流程是:

  1. 小程序端调用wx.login(),拿到一个临时凭证 code,这个 code 有效期只有5分钟,而且只能使用一次。
  2. 小程序端把 code 通过 wx.request 发给自己的后端接口。
  3. 后端拿到 code 后,调用微信接口https://api.weixin.qq.com/sns/jscode2session,带上小程序的 appid 和 secret,向微信服务器换取 openid 和 session_key。
  4. 后端用 openid 去数据库查用户,如果不存在就自动注册一个新用户,然后生成一个自定义 token(我用的 UUID,缓存在 Redis 里,有效期 7 天),返回给小程序端。
  5. 小程序端把 token 存储到 storage 里,后续所有接口请求都在 header 里带上这个 token。

这里最大的坑在于:后端请求微信的 jscode2session 接口时,appid 和 secret 一定要配置正确。secret 在小程序后台可以重置,但重置之后旧的小程序版本会全部失效。我第一次联调的时候就是卡在这一步,后台一直报invalid code,查了半天才发现是小程序后台的 secret 被别人重置过,而我用的还是旧的值。

另外要提醒一个安全细节:jscode2session 接口返回的 session_key 不要返回给小程序端,openid 也不要直接返回到前端。这些信息只需要后端自己保留,前端只要拿到 token 就够了。不然别人抓包拿到你的 openid,就能伪造你的身份调用接口。

3.4 商家列表与搜索筛选

商家列表是小程序端的门面,也是大部分用户进入系统后看到的第一个页面。这个页面的核心功能是列表展示、关键词搜索、区域筛选、下拉刷新、上拉加载更多。

列表数据直接从/api/merchant/list接口拿,支持keywordregionpagepageSize四个参数。后端用 MyBatis 分页查询,返回结构如下:

{ "code": 200, "message": "success", "data": { "list": [], "total": 35, "page": 1, "pageSize": 10 } }

小程序端的处理逻辑是:下拉刷新时从第1页重新拉,上拉触底时 page 加1往后追加。这里有个细节:商家列表里的封面图不要直接展示原图,因为商家上传的图片可能是几兆的大图,小程序端加载会很慢。我的做法是后端上传时生成缩略图,列表接口返回缩略图地址,详情页才展示原图。这个优化体验差异非常明显,尤其是在 4G 网络下测试的时候。

区域筛选我用的不是下拉框,而是页面上方一排可横向滚动的 tag,这个在小程序里用 scroll-view 加enable-flex就能实现。选中某个区域后重新请求列表,同时更新 URL 参数,保证页面分享出去后别人也能看到当前筛选结果。

3.5 寄养预约下单:时间选择与价格计算

下单页是整个小程序端业务逻辑最复杂的页面,因为它涉及三个联动数据:宠物选择、寄养日期范围选择、价格自动计算。

宠物选择比较简单,从/api/pet/list接口拿数据,用 picker 组件展示,每项显示“宠物名 + 品种”。

日期范围选择是这个页面的核心难点。小程序自带的 picker 的 mode 是date,只能选单个日期,不支持范围选择。我当时用的是miniprogram-datetime-picker这个第三方组件,或者自己封装两个日期选择器(开始日期和结束日期),在用户选择结束日期时校验必须大于开始日期。

价格计算的逻辑是:

const days = Math.floor((endDate - startDate) / (1000 * 60 * 60 * 24)) + 1; const totalPrice = days * merchantPrice;

这里要注意一个细节:宠物寄养通常是按“天”收费,而且“当天送入、明天接出”也算两天,所以天数计算要+1,不然会少算一天费用。这个规则最好在下单页明确告知用户,避免后续产生纠纷。

下单成功后,后端会生成订单号。订单号的生成我用的规则是时间戳 + 用户ID + 随机数,然后转成大写字符串,确保唯一性。

String orderNo = "BO" + System.currentTimeMillis() + String.format("%04d", userId) + String.format("%02d", (int)(Math.random() * 100));

3.6 订单状态流转与商家端操作

订单列表页是另一个容易出问题的页面。因为同一个接口要同时服务普通用户和寄养商家,只是返回的数据维度不同。普通用户看到的是“我下的单”,商家看到的是“我的店铺接到的单”。

我在后端/api/order/my接口里根据当前用户角色做了分流:

  • 普通用户:查user_id = 当前用户的订单,状态按创建时间倒序。
  • 寄养商家:先查出当前用户关联的 merchant_id,再查merchant_id = 商家ID的订单。

订单详情页展示的信息包括宠物快照、商家快照、日期、金额、状态,底部有两个动态按钮区域:

  • 用户视角:状态为待确认时可以取消;状态为待接出、已完成时可以评价。
  • 商家视角:状态为待确认时可以接单(进入已确认)和拒绝(进入已取消);状态为已确认时可以点“宠物已送达”(进入寄养中);状态为寄养中时可以点“宠物已接出”(进入待接出)。

这个状态流转我用一个状态机类来管理,不允许跨状态跳跃。比如待确认状态下,商家不能直接把订单改成寄养中,必须先确认再接单。这个逻辑虽然增加了代码量,但能避免很多并发场景下的脏数据问题。

4. 联调与部署中的高频问题排查

4.1 真机预览连不上本地后端服务

这个问题出现的频率极高,而且几乎所有第一次做小程序项目的人都会遇到。

原因很简单:小程序开发者工具里,你可以勾选“不校验合法域名”,然后直接用http://localhost:8080访问本地后端,但在手机真机上,localhost 指的是手机本身,而不是你的电脑。所以真机预览的时候,所有请求都会失败。

解决办法有两个。第一个是开发阶段使用的:把本地后端服务启动后,用ipconfig(Windows)或ifconfig(Mac)查一下电脑在局域网里的 IP,然后把小程序里的请求地址改成http://192.168.x.x:8080。注意手机和电脑必须在同一个 WiFi 下。

第二个是正式阶段使用的:把后端部署到云服务器,配置 HTTPS 域名,然后在微信公众平台里把域名加到 request 合法域名里。小程序正式上线强制要求 HTTPS,而且域名必须备案。

我实际做的过程比较波折,因为本地 IP 会变,每次换网络都要改配置文件。后来我把 api baseUrl 单独放到一个 config.js 文件里,页面里统一引入:

module.exports = { devBaseUrl: 'http://192.168.1.100:8080', prodBaseUrl: 'https://api.example.com', getBaseUrl() { return this.prodBaseUrl; } };

切换环境的时候只改一个地方就可以,非常方便。

4.2 时间格式在苹果手机上显示 NaN

这个坑是在测试阶段发现的,Android 手机上一切正常,一到 iPhone 上,订单列表里的时间就显示 "NaN年NaN月NaN日"。

原因在于 iOS 的 JavaScript 引擎对日期字符串的解析规则跟 Android 不一样。iOS 只认2023/07/11 12:00:00这种带斜杠的格式,或者带 T 的 ISO 格式2023-07-11T12:00:00,而2023-07-11 12:00:00这种带横杠加空格的格式,iOS 的new Date()会解析失败。

解决办法是在前端做一次兼容处理:

function formatTime(time) { if (!time) return ''; time = time.replace(/-/g, '/'); const date = new Date(time); const y = date.getFullYear(); const m = date.getMonth() + 1; const d = date.getDate(); return `${y}年${m}月${d}日`; }

当然,更好的做法还是按刚才说的,后端统一返回yyyy-MM-dd HH:mm:ss格式,然后前端解析前先做兼容替换。那个跟用户显示日期的场景各自处理,就不会再出问题。

4.3 上传图片后后端收不到文件

图片上传用的是小程序自带的wx.uploadFile接口,这个接口和后端 SpringMVC 的文件接收方式有三个最容易出错的地方。

第一个是参数格式没有对应。wx.uploadFileformData里的普通参数默认以multipart/form-data格式提交,后端的 Controller 参数要用@RequestParam接收,不能用@RequestBody。很多人直接把 Postman 里 JSON 格式的测试用例逻辑硬套到小程序上传上,结果永远拿到空参数。

第二个是文件字段名不一致。小程序端wx.uploadFilename参数指定的是后端接收文件的字段名,比如:

wx.uploadFile({ url: app.globalData.baseUrl + '/api/upload', filePath: tempFilePath, name: 'file', formData: { type: 'merchant' }, success(res) { ... } })

后端 Controller 里必须用@RequestParam("file") MultipartFile file来对应,两边字段名完全一致才行。

第三个是后端配置文件对上传文件大小的限制。SpringMVC 默认上传单个文件最大 1MB,超过会直接报错。商家传环境照片用手机拍,动辄五六兆,很容易触发这个限制。需要在 spring-mvc.xml 里配:

<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <property name="maxUploadSize" value="10485760"/> <property name="defaultEncoding" value="UTF-8"/> </bean>

4.4 token 过期与重复登录弹窗

登录态管理不到位的话,会出现一个很烦人的现象:token 过期后,用户点开详情页,接口返回 401,然后前端弹一个“请先登录”,用户明明刚才还在浏览,突然被踢回登录页。

更烦的是多个接口同时请求,每个都返回 401,前端就会连续弹好几次登录提示,体验极其糟糕。

我的处理方案是在封装的 http.js 里做统一处理:

  • 后端约定 401 状态码表示 token 无效或过期。
  • 前端在 http.js 里设置一个isRefreshing标志,第一次收到 401 的时候,调用登录接口刷新 token,并把这个请求“挂起”,等新 token 拿到后重放请求;同时期其他接口收到的 401 不再触发重复登录,而是等待重放。

这算是前端异步并发控制的一个典型场景,逻辑不复杂,但能明显改善用户体验。答辩的时候如果老师问“并发请求场景怎么处理”,这一段就能体现你的思考深度。

4.5 数据库连接与事务处理

SSM 项目里事务处理也容易踩坑。默认情况下,Spring 事务是自动提交的,意思是只要你调用 Service 层的方法,每一条 SQL 都独立成一个事务。这样带来的隐患是:一个业务操作涉及多条 SQL 时,如果中间的某一条失败了,前面的 SQL 已经提交了,数据就会不一致。

典型场景是创建订单时需要同时更新寄养商家的剩余容量。如果只插了订单、没扣减容量,商家超卖的问题就出现了。解决办法是在 Service 实现类上增加@Transactional注解:

@Transactional(rollbackFor = Exception.class) public Order createBoardingOrder(OrderCreateRequest request) { // 插入订单 orderMapper.insert(order); // 扣减商家容量 merchantMapper.decreaseCapacity(order.getMerchantId()); return order; }

RollbackFor 指定为所有异常都回滚,包括运行时异常和受检异常,这是最容易漏掉的地方。如果不写 rollbackFor,Spring 默认只在遇到运行时异常时回滚,受检异常抛出来并不会触发回滚。

事务这块还有一个细节:MyBatis 的一级缓存是 SqlSession 级别的,同一个 SqlSession 里执行同一条 SQL 两次,第二次会直接命中缓存。在 Service 层里先查后更,可能查出来的是旧数据。我当时踩坑的场景是创建订单前先查了一次商家容量,然后插订单,再查容量,第二次查询拿到的是旧值,导致前端显示剩余容量不对。解决方法是事务方法里不要重复查同一个查询条件,或者让 MyBatis 的二级缓存按需关闭。

5. 文档、测试与毕业答辩实战经验

5.1 毕业设计文档怎么写得又快又好

这个项目的文档写作是整个答辩流程里不可忽视的部分。很多同学技术做得不错,但论文写得像流水账,评阅老师一看就觉得敷衍,分数自然上不去。我整理了一个比较高效的写作顺序,跟做项目的顺序不太一样,但写作效率会高很多。

第一篇要写的是开题报告/需求分析。这部分不用等代码写完才开始,数据库设计出来之后就可以动笔。核心内容是:系统背景与意义、国内外现状(不用写太长,两三段就行)、可行性分析(技术、经济、操作三个维度)、功能需求和非功能需求。这里面的用例图可以直接根据我前面说的用户角色来画,每个角色画三到四个用例就能覆盖核心功能。

第二篇是数据库设计。画 E-R 图的时候注意,实体之间的关系要跟表结构一一对应。比如用户和宠物是一对多、用户和订单是一对多、商家和订单是一对多、订单和评价是一对一。这些关系如果不明确,后面的外键和关联查询都会受影响。

第三篇是系统详细设计。按照“架构设计 -> 功能模块设计 -> 数据库设计 -> 接口设计”的顺序来写。架构设计画一个简单的分层图(表现层、业务逻辑层、数据访问层),呼应 SSM 的天然分层即可。接口设计部分把每个核心接口的请求参数和返回结果写成表格,这部分可以直接复制代码里的注释,然后再润色一下。

最后一篇是系统测试。用表格整理测试用例,格式大致是“测试编号、测试功能、操作步骤、预期结果、实际结果、是否通过”。我当时列出了 20 多个用例,覆盖了用户登录、商家搜索、宠物管理、创建订单、状态流转、评价等核心场景。这个表格是凑字数神器,而且老师确实会认真看,因为能直接反映你做没做过测试。

5.2 演示Demo怎么准备才不翻车

答辩演示环节翻车的概率比想象中高很多,我总结了几条实战经验。

首先是环境准备。答辩前一天把后端服务、数据库全部在演示电脑上启动一遍,确认能正常访问。不要用自己笔记本上配置好的环境,因为现场的网络、JDK 版本、数据库密码都可能不一样。建议导出 sql 脚本和项目 war 包,在演示电脑上重新部署一遍,确保从零开始能跑通。

第二是演示路径设计。不要从头到尾把每个页面都点一遍,那样时间不够,老师也没耐心看。我建议的演示路径是:

  1. 从微信开发者工具打开小程序,展示登录过程,进入首页。
  2. 搜索一个关键词,展示列表筛选功能。
  3. 点进一家商家详情,展示评价信息。
  4. 选择宠物、设置日期,创建一个订单,展示价格自动计算。
  5. 切到商家视角,接单,然后一路流转状态到完成。
  6. 回到用户视角,评价订单。

这条路径把系统所有的核心功能都串起来了,而且逻辑连贯,老师能很清楚看到业务的完整闭环。

第三是准备一张“查漏补缺”的速查表。我把常用的 SQL 查询语句、后端重启命令、数据库账号密码贴在电脑桌面一个 txt 文件里,现场出问题的时候可以直接复制粘贴,不用临时回忆。这个习惯看起来笨,但关键时刻真的能救场。

5.3 答辩时高频追问与应对思路

答辩环节老师问的问题,大多数并不是要难为你,而是想确认这个项目确实是你自己做的、你真的理解里面的逻辑。我把当时被问到的几类高频问题和应对思路整理出来。

第一个必问性的问题:“为什么用 SSM,不用 Spring Boot?” 应对思路是强调 SSM 更适合教学和体现基础原理,因为 Spring 的 IOC、AOP、SpringMVC 的请求流程、MyBatis 的 SQL 管理都是自己要手动配置的,写一遍才能加深理解。顺便可以补一句“项目的分层结构后续可以平滑迁移到 Spring Boot”,既表达基础扎实,又说明你了解技术演进。

第二个问题:“订单状态是怎么设计的?为什么没有用枚举?” 应对思路:说明状态机设计的业务背景,解释状态之间不允许跳跃,并说明 TINYINT 存状态的缘由是兼容老数据库和简化查询。如果老师追问,可以补充如果后续状态多了,可以考虑用枚举类或状态模式重构。

第三个问题:“并发情况下商家容量会不会超卖?” 这个问题如果能答上来,基本就能稳过。对应思路:用事务加行锁。创建订单前查询商家容量时,用SELECT ... FOR UPDATE锁住商家记录,然后判断容量是否充足,不足则抛异常回滚,充足则插入订单并扣减容量。简单画一下这个流程,再补充一句“生产环境可能会引入 Redis 分布式锁,但毕设场景下数据库锁已经足够”,就很完整了。

第四个问题:“小程序端怎么保证用户登录态的安全性?” 应对思路:后端不信任前端传过来的 openid,而是通过 code 换 token 的链路自己从微信服务器获取 openid;token 用 UUID 生成并缓存,设置过期时间;拦截器校验 token,过期的请求直接返回 401。把这三个点讲清楚,安全方面的提问基本都能应对。

5.4 方向扩展:这个项目还能往哪些方向发展

如果做完核心功能还有时间,或者想在答辩里做出差异化,以下几个方向扩展性价比比较高。

订阅消息是最推荐的一个扩展点。小程序里可以申请“服务提醒”模板,在用户预约成功、商家接单、订单状态变化时给用户发模板消息。这个功能业务价值明显(寄养这种周期性的服务,用户真的很需要状态提醒),技术实现也不难,后端调用微信接口推送,前端在订单创建时请求用户授权订阅。

第二个扩展是地图定位。在商家详情页接入腾讯地图或高德地图,展示商家位置,用户可以直接导航过去。这个功能就是调用地图 SDK,接入成本不高,但演示效果很好。

第三个扩展是支付环节。把预定模式改成“先支付、后服务”,接入微信支付。微信支付的商户号申请流程比较麻烦,但开发测试可以用沙箱环境。如果时间紧张,可以在论文里描述“支付功能预留接口”,答辩时口头说明设计方案,就足够体现系统完整性了。

第四个扩展是数据可视化。管理后台里加一个简单的统计报表,展示每天新增订单数、订单收入趋势、热门寄养区域排行等。用 ECharts 或者小程序端的图表组件都能实现,这个功能能显著提升系统的大局观,也是评阅老师比较喜欢的亮点。

最后分享一点个人体会

整个项目做下来,我最深的一个体会是:技术本身并不难,难的是如何把一个模糊的业务需求拆成清晰的数据结构和接口。开始动手之前,我花了大概一周时间画流程图、设计表结构、列接口清单,后面写代码反而特别顺。如果你正打算做类似的项目,我强烈建议你也这样来——前期设计阶段多花的时间,后期一定会加倍还给你。

还有一个细节想特别提醒:微信小程序的版本更新和 API 调整非常频繁,网上搜到的很多教程里的 API 已经过时了,比如老的wx.getUserInfo现在已经不能直接拿用户头像和昵称了,新版本要用头像昵称填写能力。做项目过程中遇到接口调试不通,优先去微信官方文档查最新说明,不要浪费时间在过时教程上。

这个项目做完之后,我也把源代码、数据库脚本、接口文档都整理了一遍,后续如果有人需要,我可以专门写一篇部署和代码走读的详细文章,把整个项目的目录结构、每个类的职责、每段核心逻辑的代码都过一遍。祝正在做毕设的同学都能顺利通过答辩。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询