ARTICLE DETAIL

资讯详情

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

宠物寄养平台微信小程序+SSM实战:从数据库设计到答辩经验

宠物寄养平台微信小程序+SSM实战:从数据库设计到答辩经验 “宠物寄养平台”这个题目我看这两年选的人越来越多原因其实很简单业务场景清楚用户角色也容易划分技术栈又是经典的“小程序 SSM”组合不管是用来做毕业设计还是拿来练手都能把前端、后端、数据库整条链路完整走一遍。我自己完整做完这个项目之后最大的感受是——这个题目看着不大但真正动手做里面的细节远比想象中多尤其是小程序端到后端的联调、以及预约流程的状态流转不踩几次坑根本拿不下来。这篇文章就按我一个做过完整项目的从业者视角把整个设计思路、核心实现、常见坑点和答辩演示经验一起梳理出来项目源代码结构、数据库脚本、接口文档这些后续都可以再展开。内容适合计算机相关专业正在准备毕业设计的同学也适合刚接触微信小程序开发、想搞懂一个完整业务闭环的新手。1. 项目定位与核心需求拆解1.1 这个系统到底要解决什么问题先别急着写代码第一步应该想清楚宠物寄养平台到底解决的是谁的什么麻烦养过宠物的人都知道逢年过节要出远门的时候宠物怎么办是个真问题。放在朋友家怕麻烦别人找线下宠物店又不知道哪家靠谱、价格是不是透明、评价是不是刷的。反过来宠物店或者寄养家庭有闲置的寄养位却找不到稳定的客源全靠线下口头传播。所以这个平台本质上是做一个信息撮合加交易管理的双边平台核心要解决的痛点有三个宠物主人侧快速找到附近可寄养的商家、查看环境图片和评价、在线预约下单、随时查看订单状态。寄养商家侧管理可接待的寄养位、接收预约请求、更新订单状态比如已接单、服务中、已完成。平台管理侧审核商家资质、处理投诉、维护基础数据。把这三个角色拆出来之后整个系统的功能边界就清楚了后面做数据库表设计、接口设计都不会跑偏。我在实际做的时候还额外加了一个“我的宠物档案”模块可以让用户提前录入宠物信息品种、年龄、是否绝育、有无过敏史等下单的时候直接选择不用每次重复填写。这个功能虽然看起来不起眼但演示的时候特别加分因为它体现了你对真实业务场景的理解。1.2 为什么是微信小程序 SSM项目标题里锁定了“微信小程序”和“SSM”两个关键词这其实不是一个随意的组合它背后的逻辑值得聊一聊。小程序端解决的是获客和使用门槛问题。宠物寄养是一个低频但刚需的场景用户不可能为了偶尔寄养一次专门去下载一个App。微信小程序“用完即走、扫码即用”的特性跟这种低频服务场景可以说是天然匹配。而且小程序可以调用微信生态的登录能力、订阅消息能力用户体验比H5页面好很多。后端选择SSMSpring 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_timeJava 实体类习惯用驼峰比如createTime。如果不开启驼峰映射查询出来的结果 createTime 永远是 null。解决方案是在 MyBatis 全局配置里加上settings setting namemapUnderscoreToCamelCase valuetrue/ /settings第二个坑是时间字段的处理。如果数据库用的是 DATETIME实体类用的是java.util.Date查询出来给前端序列化后默认格式是2023-07-11T12:00:00.0000800这种格式。小程序端用new Date()解析这种带时区的ISO格式是没问题的但如果数据库存的是2023-07-11 12:00:00这种字符串前端 iOS 上new Date()解析会直接挂掉。我的解决办法是写一个 Json 序列化配置让后端返回的时间统一格式化为yyyy-MM-dd HH:mm:ssJsonFormat(pattern yyyy-MM-dd HH:mm:ss, timezone GMT8) private Date createTime;第三个坑是 MyBatis 的动态 SQL。列表页通常要支持多个筛选条件的组合查询如果直接写死 SQL每次都要重新写一条。用where加if可以优雅解决select idselectMerchantList resultTypecom.example.entity.Merchant SELECT * FROM tb_merchant where if testkeyword ! null and keyword ! AND name LIKE CONCAT(%, #{keyword}, %) /if if testregion ! 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 完整链路小程序的登录流程本质上就是三个角色之间交换凭证小程序端、微信服务器、你自己的后端。正常的流程是小程序端调用wx.login()拿到一个临时凭证 code这个 code 有效期只有5分钟而且只能使用一次。小程序端把 code 通过 wx.request 发给自己的后端接口。后端拿到 code 后调用微信接口https://api.weixin.qq.com/sns/jscode2session带上小程序的 appid 和 secret向微信服务器换取 openid 和 session_key。后端用 openid 去数据库查用户如果不存在就自动注册一个新用户然后生成一个自定义 token我用的 UUID缓存在 Redis 里有效期 7 天返回给小程序端。小程序端把 token 存储到 storage 里后续所有接口请求都在 header 里带上这个 token。这里最大的坑在于后端请求微信的 jscode2session 接口时appid 和 secret 一定要配置正确。secret 在小程序后台可以重置但重置之后旧的小程序版本会全部失效。我第一次联调的时候就是卡在这一步后台一直报invalid code查了半天才发现是小程序后台的 secret 被别人重置过而我用的还是旧的值。另外要提醒一个安全细节jscode2session 接口返回的 session_key 不要返回给小程序端openid 也不要直接返回到前端。这些信息只需要后端自己保留前端只要拿到 token 就够了。不然别人抓包拿到你的 openid就能伪造你的身份调用接口。3.4 商家列表与搜索筛选商家列表是小程序端的门面也是大部分用户进入系统后看到的第一个页面。这个页面的核心功能是列表展示、关键词搜索、区域筛选、下拉刷新、上拉加载更多。列表数据直接从/api/merchant/list接口拿支持keyword、region、page、pageSize四个参数。后端用 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 指的是手机本身而不是你的电脑。所以真机预览的时候所有请求都会失败。解决办法有两个。第一个是开发阶段使用的把本地后端服务启动后用ipconfigWindows或ifconfigMac查一下电脑在局域网里的 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.uploadFile的formData里的普通参数默认以multipart/form-data格式提交后端的 Controller 参数要用RequestParam接收不能用RequestBody。很多人直接把 Postman 里 JSON 格式的测试用例逻辑硬套到小程序上传上结果永远拿到空参数。第二个是文件字段名不一致。小程序端wx.uploadFile的name参数指定的是后端接收文件的字段名比如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 idmultipartResolver classorg.springframework.web.multipart.commons.CommonsMultipartResolver property namemaxUploadSize value10485760/ property namedefaultEncoding valueUTF-8/ /bean4.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 指定为所有异常都回滚包括运行时异常和受检异常这是最容易漏掉的地方。如果不写 rollbackForSpring 默认只在遇到运行时异常时回滚受检异常抛出来并不会触发回滚。事务这块还有一个细节MyBatis 的一级缓存是 SqlSession 级别的同一个 SqlSession 里执行同一条 SQL 两次第二次会直接命中缓存。在 Service 层里先查后更可能查出来的是旧数据。我当时踩坑的场景是创建订单前先查了一次商家容量然后插订单再查容量第二次查询拿到的是旧值导致前端显示剩余容量不对。解决方法是事务方法里不要重复查同一个查询条件或者让 MyBatis 的二级缓存按需关闭。5. 文档、测试与毕业答辩实战经验5.1 毕业设计文档怎么写得又快又好这个项目的文档写作是整个答辩流程里不可忽视的部分。很多同学技术做得不错但论文写得像流水账评阅老师一看就觉得敷衍分数自然上不去。我整理了一个比较高效的写作顺序跟做项目的顺序不太一样但写作效率会高很多。第一篇要写的是开题报告/需求分析。这部分不用等代码写完才开始数据库设计出来之后就可以动笔。核心内容是系统背景与意义、国内外现状不用写太长两三段就行、可行性分析技术、经济、操作三个维度、功能需求和非功能需求。这里面的用例图可以直接根据我前面说的用户角色来画每个角色画三到四个用例就能覆盖核心功能。第二篇是数据库设计。画 E-R 图的时候注意实体之间的关系要跟表结构一一对应。比如用户和宠物是一对多、用户和订单是一对多、商家和订单是一对多、订单和评价是一对一。这些关系如果不明确后面的外键和关联查询都会受影响。第三篇是系统详细设计。按照“架构设计 - 功能模块设计 - 数据库设计 - 接口设计”的顺序来写。架构设计画一个简单的分层图表现层、业务逻辑层、数据访问层呼应 SSM 的天然分层即可。接口设计部分把每个核心接口的请求参数和返回结果写成表格这部分可以直接复制代码里的注释然后再润色一下。最后一篇是系统测试。用表格整理测试用例格式大致是“测试编号、测试功能、操作步骤、预期结果、实际结果、是否通过”。我当时列出了 20 多个用例覆盖了用户登录、商家搜索、宠物管理、创建订单、状态流转、评价等核心场景。这个表格是凑字数神器而且老师确实会认真看因为能直接反映你做没做过测试。5.2 演示Demo怎么准备才不翻车答辩演示环节翻车的概率比想象中高很多我总结了几条实战经验。首先是环境准备。答辩前一天把后端服务、数据库全部在演示电脑上启动一遍确认能正常访问。不要用自己笔记本上配置好的环境因为现场的网络、JDK 版本、数据库密码都可能不一样。建议导出 sql 脚本和项目 war 包在演示电脑上重新部署一遍确保从零开始能跑通。第二是演示路径设计。不要从头到尾把每个页面都点一遍那样时间不够老师也没耐心看。我建议的演示路径是从微信开发者工具打开小程序展示登录过程进入首页。搜索一个关键词展示列表筛选功能。点进一家商家详情展示评价信息。选择宠物、设置日期创建一个订单展示价格自动计算。切到商家视角接单然后一路流转状态到完成。回到用户视角评价订单。这条路径把系统所有的核心功能都串起来了而且逻辑连贯老师能很清楚看到业务的完整闭环。第三是准备一张“查漏补缺”的速查表。我把常用的 SQL 查询语句、后端重启命令、数据库账号密码贴在电脑桌面一个 txt 文件里现场出问题的时候可以直接复制粘贴不用临时回忆。这个习惯看起来笨但关键时刻真的能救场。5.3 答辩时高频追问与应对思路答辩环节老师问的问题大多数并不是要难为你而是想确认这个项目确实是你自己做的、你真的理解里面的逻辑。我把当时被问到的几类高频问题和应对思路整理出来。第一个必问性的问题“为什么用 SSM不用 Spring Boot” 应对思路是强调 SSM 更适合教学和体现基础原理因为 Spring 的 IOC、AOP、SpringMVC 的请求流程、MyBatis 的 SQL 管理都是自己要手动配置的写一遍才能加深理解。顺便可以补一句“项目的分层结构后续可以平滑迁移到 Spring Boot”既表达基础扎实又说明你了解技术演进。第二个问题“订单状态是怎么设计的为什么没有用枚举” 应对思路说明状态机设计的业务背景解释状态之间不允许跳跃并说明 TINYINT 存状态的缘由是兼容老数据库和简化查询。如果老师追问可以补充如果后续状态多了可以考虑用枚举类或状态模式重构。第三个问题“并发情况下商家容量会不会超卖” 这个问题如果能答上来基本就能稳过。对应思路用事务加行锁。创建订单前查询商家容量时用SELECT ... FOR UPDATE锁住商家记录然后判断容量是否充足不足则抛异常回滚充足则插入订单并扣减容量。简单画一下这个流程再补充一句“生产环境可能会引入 Redis 分布式锁但毕设场景下数据库锁已经足够”就很完整了。第四个问题“小程序端怎么保证用户登录态的安全性” 应对思路后端不信任前端传过来的 openid而是通过 code 换 token 的链路自己从微信服务器获取 openidtoken 用 UUID 生成并缓存设置过期时间拦截器校验 token过期的请求直接返回 401。把这三个点讲清楚安全方面的提问基本都能应对。5.4 方向扩展这个项目还能往哪些方向发展如果做完核心功能还有时间或者想在答辩里做出差异化以下几个方向扩展性价比比较高。订阅消息是最推荐的一个扩展点。小程序里可以申请“服务提醒”模板在用户预约成功、商家接单、订单状态变化时给用户发模板消息。这个功能业务价值明显寄养这种周期性的服务用户真的很需要状态提醒技术实现也不难后端调用微信接口推送前端在订单创建时请求用户授权订阅。第二个扩展是地图定位。在商家详情页接入腾讯地图或高德地图展示商家位置用户可以直接导航过去。这个功能就是调用地图 SDK接入成本不高但演示效果很好。第三个扩展是支付环节。把预定模式改成“先支付、后服务”接入微信支付。微信支付的商户号申请流程比较麻烦但开发测试可以用沙箱环境。如果时间紧张可以在论文里描述“支付功能预留接口”答辩时口头说明设计方案就足够体现系统完整性了。第四个扩展是数据可视化。管理后台里加一个简单的统计报表展示每天新增订单数、订单收入趋势、热门寄养区域排行等。用 ECharts 或者小程序端的图表组件都能实现这个功能能显著提升系统的大局观也是评阅老师比较喜欢的亮点。最后分享一点个人体会整个项目做下来我最深的一个体会是技术本身并不难难的是如何把一个模糊的业务需求拆成清晰的数据结构和接口。开始动手之前我花了大概一周时间画流程图、设计表结构、列接口清单后面写代码反而特别顺。如果你正打算做类似的项目我强烈建议你也这样来——前期设计阶段多花的时间后期一定会加倍还给你。还有一个细节想特别提醒微信小程序的版本更新和 API 调整非常频繁网上搜到的很多教程里的 API 已经过时了比如老的wx.getUserInfo现在已经不能直接拿用户头像和昵称了新版本要用头像昵称填写能力。做项目过程中遇到接口调试不通优先去微信官方文档查最新说明不要浪费时间在过时教程上。这个项目做完之后我也把源代码、数据库脚本、接口文档都整理了一遍后续如果有人需要我可以专门写一篇部署和代码走读的详细文章把整个项目的目录结构、每个类的职责、每段核心逻辑的代码都过一遍。祝正在做毕设的同学都能顺利通过答辩。
返回列表