这个题目我太熟了。去年做课程设计选型调研时,翻了好几个月的项目库,发现"基于微信小程序的XX系统"永远是毕设和课设的高频题,而雨伞借取又是这类题目里少有的"场景真实、逻辑清楚、工作量适中"的典型。不需要虚构需求,不需要硬凑功能,用户愿意用,评委看得懂,代码量又在三四周内可以完成——对大多数想拿高分又不想把自己折腾到秃的同学来说,这几乎是理想选题的形状。
这篇文章我按完整项目的推进顺序来写:先讲选题与需求拆解,再讲系统架构和数据库设计,然后深入小程序端从扫码借伞到还伞计费的关键链路,接着分享我实际部署和联调时踩过的坑,最后聊聊怎么把"能跑的项目"包装成"像样工程"。所有代码片段按 Spring Boot + 微信小程序 + MySQL 这条最主流的技术路线给出,其他语言的后端思路完全一致,读着替换就行。
1. 为什么是"智能雨伞借取系统":选题价值与需求边界
先聊选题。图书馆门口、地铁站、教学楼大厅,下雨天永远有人没伞,晴天伞架上一堆无人认领的伞。借伞的需求是天然的,但传统的"登记本借伞"体验太差:没带笔、字看不清、忘了还、丢了没人管。做成小程序之后,整个流程变成了扫码、取伞、还伞、结算四个动作,用户的认知成本极低,这个"需求真实性"正是做项目时最值钱的东西——它决定了你汇报时能不能一句话讲清楚"这个系统解决什么问题"。
从技术考察的角度看,这个题目覆盖得也很完整:
- 前端:微信小程序基础(WXML、WXSS、JS)、页面路由、组件化、授权登录、扫码、定位;
- 后端:接口设计、鉴权、状态管理、事务处理、定时任务;
- 数据库:核心表和字段设计、索引、并发控制、计费计算;
- 部署:HTTPS 域名、服务器环境、数据库初始化、小程序后台配置。
这一套组合下来,几乎把 Web 全栈必考的技能点都带上了,但每一样都不算特别深,属于"够得着"的难度。
1.1 需求拆解:谁在用、怎么用、管什么
做项目第一件事不是写代码,是把角色和动作列清楚。这个系统的角色只有三类:
- 普通用户:微信授权登录后扫码借伞,使用完毕后到任意还伞点还伞,可以查看历史借还记录、订单状态,接收还伞提醒;
- 站点管理员:维护借还伞点信息、管理每个站点的雨伞库存、处理超时未还或丢失的异常订单;
- 系统管理员:在后台管理全部站点、雨伞和订单流水。
用户侧的核心流程一句话就能说清:打开小程序 → 扫码借伞 → 带着伞走 → 到还伞点还伞 → 系统计费结算 → 订单完成。这个流程里含着一串关键状态:雨伞在库、雨伞已借出、订单进行中、订单已完成、订单超时、订单异常。把这六个状态理清楚,整个系统的骨架就出来了。
1.2 功能清单:砍掉花里胡哨,留下核心闭环
很多同学一上来就加了一堆功能:积分商城、社区留言、充值、优惠券……结果项目做了两个月还没跑通主流程。我的建议是严格按照"能不能支撑借伞还伞闭环"来决定功能的去留。
| 模块 | 功能点 | 优先级 | 说明 |
|---|---|---|---|
| 用户端 | 微信授权登录 | 必须有 | 每个动作的前提 |
| 用户端 | 扫码借伞 | 必须有 | 核心链路入口 |
| 用户端 | 还伞 | 必须有 | 反向闭环 |
| 用户端 | 订单列表与状态展示 | 必须有 | 用户可查、演示好看 |
| 用户端 | 附近站点地图 | 建议有 | 支撑"还到哪里"的问题 |
| 用户端 | 订阅消息提醒 | 加分项 | 超时提醒用微信订阅消息做 |
| 管理端 | 雨伞库存与状态管理 | 必须有 | 支撑运营闭环 |
| 管理端 | 订单查询与异常处理 | 必须有 | 演示时展示管理能力的关键 |
关于硬件,我多说一句:这个题目天然容易让人联想到"智能伞架""电子锁""RFID 标签",但在纯软件项目中,雨伞的状态可以用伞身上的唯一编号二维码 + 后端状态机来管理,不需要单片机。如果你手里没有实物硬件条件,不要硬做,软件方案讲清楚"每个二维码对应一把唯一雨伞"就完全成立。
1.3 用户流程与页面结构的一一对应
需求拆完顺手把页面树拉出来,后面写代码就不迷路:
pages/ index/ 首页:附近站点列表、快捷入口 map/ 站点地图:查看站点位置和存量 scan/ 扫码/手动输入伞码 borrow/ 借伞确认:展示伞信息、用户信用状态 return/ 还伞确认:展示订单费用 orders/ 借还记录:进行中/已完成/异常三档 mine/ 个人中心:信用分、使用统计 admin/ dashboard/ 管理端概览:库存总量、进行中订单 stations/ 站点管理:增删改查、站点存量调整 umbrellas/ 雨伞管理:伞状态变更 orders/ 订单管理:标记超时、处理异常页面树和功能清单对上了,开发的时候就可以一个页面一个页面往下推,不会出现"做到后面忘了自己还剩几个页面"的失控感。
2. 三段式架构与技术选型:别让后端方案拖垮你
这个项目的架构其实没什么悬念:微信小程序做客户端,后端提供 JSON API,数据库负责持久化。三层结构各管各的,边界非常清晰。难的是后端用什么技术栈,这一步选错了后面会很难受。
2.1 三种后端方案怎么选
我见过很多同学在这个题上纠结,其实就三条路:
| 方案 | 优点 | 缺点 | 适合谁 |
|---|---|---|---|
| 微信云开发(CloudBase) | 免服务器、免域名备案、登录鉴权现成 | 无法展示独立后端能力,答辩时"含金量"略低 | 时间极紧、以纯前端为主的课程设计 |
| Spring Boot + MySQL(自建后端) | 技术主流、面试和答辩最好讲、事务控制清晰 | 需要服务器/部署,配置略多 | 有余力部署、希望完整展示全栈能力 |
| Node.js/Koa + MySQL | 轻量、JS 语法延续性好 | 相比 Spring Boot 生态和资料略少 | 更熟悉 JS 的同学 |
如果让我推荐,默认走 Spring Boot + MySQL。原因很简单:网上关于 Spring Boot 的资料密度太高,任何报错都能搜到方案,而且 Java 的强类型让"菜鸟容易把类型搞混"的问题缓解不少。更重要的是答辩时老师问"你的后端怎么保证借伞不被两个人同时扫走",Spring Boot 里事务和乐观锁的实现非常成熟,能很容易讲出深度。
2.2 后端分层:Controller、Service、Mapper 的职责线
Spring Boot 工程内部的包结构建议按业务边界分,不要按字母序堆一堆类:
com.example.umbrella ├── controller/ # 只做参数接收和结果封装 │ ├── AuthController.java # 登录换 openid │ ├── BorrowController.java # 借伞、还伞 │ ├── StationController.java # 站点查询 │ └── OrderController.java # 订单查询 ├── service/ # 业务逻辑,事务都在这一层 │ ├── BorrowService.java │ └── OrderService.java ├── mapper/ # MyBatis 或 JPA 的数据访问 ├── entity/ # 对应数据库表的实体类 ├── config/ # 拦截器、CORS、Web 配置 └── common/ # 统一返回体、异常、工具类一个小技巧:统一返回结构体。无论成功失败,都返回{ code, message, data }三段式,小程序端封装一个 Request 方法统一处理错误码。千万别今天一处返回字符串、明天一处返回 JSON,联调的时候你会被自己坑死。
2.3 小程序端请求封装与鉴权思路
小程序端不要在每次用到wx.request时重复写 url 和 header,应该封装一个api/request.js:
const BASE_URL = 'https://yourdomain.com/api'; function request(path, method, data, needAuth = true) { return new Promise((resolve, reject) => { wx.request({ url: BASE_URL + path, method: method, data: data, header: needAuth ? { 'Authorization': wx.getStorageSync('token') } : {}, success(res) { if (res.data.code === 0) { resolve(res.data.data); } else if (res.data.code === 401) { // 登录态过期,重新登录 wx.removeStorageSync('token'); wx.navigateTo({ url: '/pages/login/login' }); reject(res.data); } else { wx.showToast({ title: res.data.message, icon: 'none' }); reject(res.data); } }, fail(err) { wx.showToast({ title: '网络异常', icon: 'none' }); reject(err); } }); }); } module.exports = { get: (path, data) => request(path, 'GET', data), post: (path, data) => request(path, 'POST', data) };这个封装的意义不只是少写几行代码,而是把"错误处理策略"统一到了一处。后端 401 时前端自动触发重新登录,网络异常时统一弹提示,比每个页面各写一份 try-catch 清爽得多。
3. 数据库设计是重头戏:表结构、状态机与并发控制
很多课设项目的数据库就是随便建两张表,用户表一张、订单表一张,字段能省则省。但这不够。雨伞借取系统的数据库设计直接决定了借还流程是否严谨,我花在这上面的时间不比写代码少。
3.1 五张核心表:字段不是拍脑袋定的
整个系统的业务可以落成五张表,我不建议再加更多,表太多反而让演示变得繁琐:
-- 用户表 CREATE TABLE `user` ( `id` BIGINT PRIMARY KEY AUTO_INCREMENT, `openid` VARCHAR(64) NOT NULL UNIQUE COMMENT '微信openid,一个用户唯一', `nickname` VARCHAR(64) DEFAULT '', `phone` VARCHAR(20) DEFAULT '', `credit_score` INT DEFAULT 100 COMMENT '信用分,违规扣分', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP, KEY `idx_openid` (`openid`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 站点表 CREATE TABLE `station` ( `id` BIGINT PRIMARY KEY AUTO_INCREMENT, `name` VARCHAR(64) NOT NULL, `address` VARCHAR(255) DEFAULT '', `latitude` DECIMAL(10, 7) NOT NULL COMMENT '纬度', `longitude` DECIMAL(10, 7) NOT NULL COMMENT '经度', `umbrella_count` INT NOT NULL DEFAULT 0 COMMENT '当前在库量', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 雨伞表 CREATE TABLE `umbrella` ( `id` BIGINT PRIMARY KEY AUTO_INCREMENT, `umbrella_code` VARCHAR(32) NOT NULL UNIQUE COMMENT '伞上的二维码编号', `status` TINYINT NOT NULL DEFAULT 0 COMMENT '0在库 1已借出 2维修 3丢失', `station_id` BIGINT COMMENT '所在站点id,借出时为空', `borrow_count` INT DEFAULT 0 COMMENT '累计借出次数', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP, KEY `idx_status` (`status`), KEY `idx_station` (`station_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 借还记录表 CREATE TABLE `borrow_record` ( `id` BIGINT PRIMARY KEY AUTO_INCREMENT, `order_no` VARCHAR(32) NOT NULL UNIQUE COMMENT '业务订单号', `user_id` BIGINT NOT NULL, `umbrella_id` BIGINT NOT NULL, `borrow_station_id` BIGINT NOT NULL COMMENT '借伞站点', `return_station_id` BIGINT DEFAULT NULL COMMENT '还伞站点,可为空', `borrow_time` DATETIME NOT NULL, `return_time` DATETIME DEFAULT NULL, `status` TINYINT NOT NULL DEFAULT 0 COMMENT '0进行中 1已完成 2超时 3异常', `fee` DECIMAL(10,2) DEFAULT 0.00 COMMENT '实际费用', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP, KEY `idx_user` (`user_id`), KEY `idx_umbrella` (`umbrella_id`), KEY `idx_status_time` (`status`, `borrow_time`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 管理员表 CREATE TABLE `admin` ( `id` BIGINT PRIMARY KEY AUTO_INCREMENT, `username` VARCHAR(32) NOT NULL UNIQUE, `password_hash` VARCHAR(128) NOT NULL, `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;几个字段需要特别说明:
openid加了唯一索引,这是小程序用户体系的核心,绝不能重复;order_no用业务号而不是自增 ID 当对外订单号,避免用户能从单号推测出平台单量;umbrella_code对应伞身上的二维码,加了唯一约束就是保证"一伞一码";borrow_record.status和borrow_time建了联合索引,因为后端定时任务要高频查询"超时未还的进行中订单",这样查询才不会全表扫描。
3.2 借伞还伞的状态机:先画状态,再写代码
我见过不少项目直接在代码里散落着一堆 if-else 判断状态,改着改着就乱了。正确做法是先定义状态迁移,代码为辅:
伞状态(umbrella.status): 0 在库 --(借伞成功)--> 1 已借出 1 已借出 --(还伞成功)--> 0 在库 1 已借出 --(管理员标记)--> 2 维修 / 3 丢失 订单状态(borrow_record.status): 0 进行中 --(正常还伞)--> 1 已完成 0 进行中 --(超过N小时未还)--> 2 超时 2 超时 --(用户补还)--> 1 已完成 0/2 --(无法找回)--> 3 异常两条状态线不是孤立的,伞的状态和订单状态必须同步变更。这一步要用数据库事务保证:还伞时,先查活跃订单,然后同时更新umbrella.status = 0、borrow_record.status = 1、station.umbrella_count++,任何一步失败全部回滚。这是整个系统最容易出并发问题的地方,也是答辩时必问的点。
3.3 防重复借伞:乐观锁比你想的简单
经典面试题场景来了:用户 A 和用户 B 同时扫了同一把伞,后端同时收到两个借伞请求,都查到这把伞status=0,然后各自往后走,最后就借出了两单。怎么解决?
我的做法是用带条件的 UPDATE 当乐观锁,而不是先查再改:
@Transactional public BorrowResult borrow(Long umbrellaId, Long userId, Long stationId) { // 1. 锁伞:只有当前状态为 0 时才能更新为 1,返回 1 表示抢锁成功 int updated = umbrellaMapper.lockUmbrella(umbrellaId); // UPDATE umbrella SET status = 1, station_id = null // WHERE id = #{umbrellaId} AND status = 0 if (updated == 0) { throw new BizException("这把伞刚刚被别人借走了"); } // 2. 查用户有没有未完成订单 Integer activeCount = recordMapper.countActiveByUser(userId); if (activeCount != null && activeCount > 0) { // 回滚伞状态 umbrellaMapper.releaseUmbrella(umbrellaId); throw new BizException("你有未完成的借伞订单"); } // 3. 生成订单,返回 BorrowRecord record = buildRecord(userId, umbrellaId, stationId); recordMapper.insert(record); return new BorrowResult(record.getOrderNo()); }数据库层面的UPDATE ... WHERE status = 0在 InnoDB 下会锁住这行记录,第二个请求会等到第一个事务提交后再执行,此时 status 已经是 1,更新影响行数为 0,直接抛异常。这个方案既不依赖 Redis,也不需要额外加锁表,一个 SQL 就解决了。
3.4 计费规则:别把账算得让用户骂街
计费设计要朴素。参考市面上共享设备的思路,我采用的是"免费时长 + 阶梯计费 + 每日封顶":
免费时长:30 分钟 超出部分:1 元/小时(不足 1 小时按 1 小时计) 每日封顶:10 元后端计算费用的代码可以这样写:
public BigDecimal calcFee(Date borrowTime, Date returnTime) { long minutes = Duration.between(borrowTime.toInstant(), returnTime.toInstant()).toMinutes(); long payableMinutes = Math.max(0L, minutes - 30); if (payableMinutes == 0) { return BigDecimal.ZERO; } long hours = (payableMinutes + 59) / 60; // 向上取整 BigDecimal fee = BigDecimal.valueOf(hours); if (fee.compareTo(new BigDecimal("10")) > 0) { fee = new BigDecimal("10"); } return fee; }费用计算放在服务端而不是前端算,前端只做展示。原因是服务端可以直接拿到可信时间,而且前端格式稍微变一下,算出来的费用"对不上",用户投诉时你根本说不清。
4. 小程序端关键链路:登录、扫码借伞、还伞计费
后端骨架有了,数据库表建好了,接下来是小程序端的硬骨头。这一段的重点是几条核心链路的时序设计,代码本身反而简单。
4.1 登录态设计:code 换 openid,token 换信任
微信小程序没有传统意义的账号密码,登录链路是固定的:前端wx.login拿到一个临时 code → 后端拿 code 加上 AppID/AppSecret 去微信接口换 openid → 后端用 openid 查用户,不存在则自动注册 → 后端生成 session token 下发给前端 → 前端把 token 放到请求头里。
这里有两个细节很容易翻车:
- code 只能用一次,而且有效期五分钟。所以前端每次登录都调
wx.login获取新 code,不要缓存旧 code。 - openid 不能直接下发到前端。你想想,如果前端拿到 openid,别人拿这个 openid 伪造请求就能冒用任意用户。正确做法是服务端用 openid 生成一串随机 token,把 token 写入 Redis(或缓存)并设置有效期,前端只持有 token。
4.2 扫码借伞链路:一条流水线走到底
小程序端最核心的页面就是"扫码借伞"。它的完整链路是:
- 用户调用
wx.scanCode扫伞上的二维码,得到内容umbrella_code=U1001&station_id=3,也可能是一个短地址重定向; - 前端把参数带进
navigateTo跳转到借伞确认页; - 用户在借伞确认页看到伞的信息和站点信息,点"确认借伞";
- 前端调
POST /api/borrow,把umbrellaCode、stationId传给后端; - 后端完成第 3 章讲的锁伞、校验、建单事务;
- 返回订单号与预计还伞提醒时间,前端展示成功页。
页面时序的关键代码大致是这样:
// 扫码页 scanAndBorrow() { wx.scanCode({ onlyFromCamera: true, success: (res) => { // 二维码内容形如:umbrellaCode=U1001&stationId=3 const params = parseQrContent(res.result); if (!params.umbrellaCode || !params.stationId) { wx.showToast({ title: '无效的伞码', icon: 'none' }); return; } wx.navigateTo({ url: `/pages/borrow/borrow?umbrellaCode=${params.umbrellaCode}&stationId=${params.stationId}` }); } }); }这里有个容易被忽略的小坑:二维码内容不要用过长的 JSON 或带特殊符号的链接。小程序码 scene 字段有长度和字符限制,直接传普通字符串最省心。如果以后接硬件伞锁,再改成动态加密二维码也不迟。
4.3 还伞链路:与借伞方向相反的同一套逻辑
还伞流程是借伞的逆向:
- 用户到达还伞点(某个站点),在首页选择"我要还伞";
- 用户再次扫描伞上的二维码,或者从最近借伞订单中选择"去还伞";
- 前端调
POST /api/return,传umbrellaCode和returnStationId; - 后端查该伞对应的"进行中"订单,校验站点有效,然后开启事务:更新订单状态、更新伞状态、增加站点库存、计算费用;
- 返回结算页面:借了多久、费用多少、还伞成功。
还伞时有一个常见需求:万一伞上的二维码磨损扫不了怎么办?所以页面里一定要保留"手动输入伞码"的入口,输入 6-8 位编号也能触发还伞逻辑。这个入口平时没人用,但真遇到几次用户伞码扫不出来,你就会发现它有多救命。
4.4 站点地图与距离计算:Haversine 公式直接用
"我在哪还伞"是这个系统体验的关键。我建议在地图页展示所有站点位置,并且按距离从近到远排序。排序逻辑放后端,前端只拿排好的数组渲染。
距离计算用球面两点间的 Haversine 公式,Java 实现如下:
public static double calcDistance(double lat1, double lng1, double lat2, double lng2) { double radLat1 = Math.toRadians(lat1); double radLat2 = Math.toRadians(lat2); double a = Math.sin((radLat1 - radLat2) / 2); double b = Math.sin(Math.toRadians(lng1 - lng2) / 2); double s = 2 * Math.asin(Math.sqrt(a * a + Math.cos(radLat1) * Math.cos(radLat2) * b * b)); return s * 6371; // 单位:千米 }用户授权获取位置后,前端把经纬度传给后端,后端算距离并排序返回。这一步不用引第三方地图 SDK,纯自包含,也能在答辩时展示"我把算法写在了后端而不是 SDK 免费功能里"。
4.5 订阅消息:超时提醒怎么发
小程序不能随便给用户发消息,必须通过订阅消息。流程是:在借伞成功页让用户点击一次"同意超时提醒",前端调wx.requestSubscribeMessage请求订阅授权,拿到touser(openid)、page、data等参数后,后端的定时任务扫描超时订单时,调用微信订阅消息接口给用户推送。
这个功能对实际体验很有帮助,雨天借伞的人经常忘记自己在哪个站点借的伞。但作为课设,它是加分项,优先级最低,时间紧可以拿掉,不影响主流程。
5. 联调与部署的实战记录:这些坑,文档里一般不写
很多同学的部署文档写得像教程,但真正操作起来还是各种崩。这节的每一句话都是我实际敲过命令、踩过坑之后总结的,按步骤走能帮你省下至少一个通宵。
5.1 环境初始化的正确顺序
不要一上来就写代码。先做这几件事,按顺序来:
- 登录微信公众平台注册小程序账号,拿到 AppID 和 AppSecret;
- 本地创建 Spring Boot 工程,配置 MySQL 数据源,建库建表;
- 本地把后端跑起来,用 Postman 或 Apifox 测通一个最简单的接口(比如站点列表);
- 微信开发者工具导入小程序项目,填 AppID,把
BASE_URL改成局域网 IP,先跑通登录; - 最后才是写业务功能。
这个顺序的核心逻辑是"先打通链路,再堆功能"。很多人反着来,先吭哧吭哧写了一个月业务代码,最后发现后端接口不能被小程序访问,整个推倒重来,心态直接爆炸。
5.2 域名与 HTTPS:真机调试的必经之坎
开发者工具里勾上"不校验合法域名"可以轻松跑通,但一用真机预览就废了——真机只认识配置在小程序后台的合法域名。所以如果你的后端不是微信云开发,必须准备:
- 一台有公网 IP 的服务器;
- 一个已经备案的域名;
- 域名解析到服务器,并配置 HTTPS 证书。
整个链路通常是 Nginx 监听 443,把/api/路径反向代理到本地 Java 服务的 8080 端口:
server { listen 443 ssl; server_name yourdomain.com; ssl_certificate /etc/nginx/cert/xxx.pem; ssl_certificate_key /etc/nginx/cert/xxx.key; location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }然后在微信公众平台"开发管理-服务器域名"里配置 request 合法域名。这一步的坑在于:域名必须备案,且配置完要等一小会儿才生效。别临演示前一天才配,出问题你连排查时间都没有。
5.3 局域网联调的两个细节
如果后端还没部署到服务器,又想在手机上预览,可以走局域网方案。这时注意两点:
- 后端地址要填电脑的局域网 IP,如
http://192.168.1.100:8080/api,绝对不能用http://localhost:8080(localhost 在手机上指手机自己); - 手机和电脑必须连同一个 WiFi,且手机预览时开发者工具里要勾选"不校验合法域名"。
这个方案的局限在于,只要一离开 WiFi,小程序就访问不了后端。所以只适合开发调试,正式演示还是老老实实走云服务器。
5.4 数据库与时间的一堆坑
我在测试计费功能时遇到过一个诡异问题:明明借了一个小时,订单费用却是 0。排查之后发现是 MySQL 时区与服务端默认时区不一致,读出来的borrow_time比实际时间少了 8 小时,免费时长判断永远不成立。
解决办法是 JDBC 连接串加参数:
jdbc:mysql://127.0.0.1:3306/umbrella_db? useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8mb4另外建库建表时统一用utf8mb4字符集,否则存中文用户名容易变 ????,查出来一脸懵。这两件事说起来简单,但几乎每个第一次做全栈项目的人都会遇到。
5.5 高频报错速查表
| 现象 | 根本原因 | 解决方案 |
|---|---|---|
request:fail | 域名没配白名单 / 没走 HTTPS | 后台配置合法域名;开发环境勾选忽略校验 |
errcode: 40029 | code 已过期或重复使用 | 每次wx.login重新拿 code |
| 401 循环跳登录 | token 过期后没有重新登录的兜底逻辑 | 封装 Request 时统一处理 401 |
| 订单状态和伞状态对不上 | 业务代码没用事务 | Service 方法加@Transactional,再梳理状态机 |
| 数据库 Lock wait timeout | 事务里执行了太多无关查询导致持锁时间过长 | 缩短事务范围,锁伞和插订单放一起,别的查询放到事务外 |
6. 从"功能跑通"到"像正经工程":最后一步怎么补
说实话,一个课设项目做到"能跑"大概只需要两周,但想要演示时不被问倒、代码不被评审嫌弃,还得再花一周做工程化的收尾。这一节分享几个性价比极高的动作。
6.1 目录与命名就是你的门面
评阅人拿到代码第一眼看的不是功能,是目录。小程序端我建议这样组织:
miniprogram/ ├── app.js # App 入口,全局登录态处理 ├── app.json # 页面路由和窗口配置 ├── utils/ │ ├── request.js # 请求封装 │ ├── format.js # 时间/金额格式化 │ └── geo.js # 距离计算、坐标转换 ├── api/ │ ├── auth.js # 登录相关接口 │ ├── borrow.js # 借还伞接口 │ └── station.js # 站点接口 ├── components/ # 公共组件 └── pages/ ├── index/ # 首页 ├── borrow/ # 借伞确认页 ├── return/ # 还伞确认页 └── orders/ # 订单列表每个页面目录下放着四个同名文件:.wxml、.wxss、.js、.json。不要把所有页面都塞在pages根目录下,几十个文件名堆在一起,自己都容易认错。后端每个 API 模块在 Controller、Service、Mapper 三层都有对应的类,命名统一用业务名,不要用test1、ceshi这种名字。
6.2 接口文档和演示脚本是两回事
不要以为设计文档只是应付检查。把接口列表整理成表格放在文档里,联调时自己和队友都能快速定位问题。一个接口表格至少包含:接口路径、请求方式、参数说明、返回值示例。我在项目里通常维护一个docs/api.md,边写接口边填,比最后统一补文档轻松得多。
到了演示环节,建议提前写一个"演示剧本",按顺序列好动作:
1. 打开小程序 → 展示首页站点列表 → 选一个站点查看位置 2. 选择演示账号登录 → 展示该用户无欠费/无活跃订单 3. 扫码借伞 → 展示"借伞成功"与开始计时 4. 切到管理端 → 展示该站点库存-1、伞状态变为已借出 5. 操作还伞 → 展示费用明细 6. 切到数据库/后台 → 展示借还记录表中新增的完整订单记录这个剧本的价值是让你的演示形成"用户端 → 管理端 → 数据库"三层闭环,每一层都在为上一层背书。不要小看这件事,我见过很多功能全做对了、演示时却东点一下西点一下、最后没讲清楚逻辑的同学,非常可惜。
6.3 答辩前必查清单
根据我对这类项目的了解,演示前你最少要检查这三件事:
- 数据干净:把所有测试订单、脏数据清掉,让演示从"用户从未使用过"的状态开始;
- 测试账号可用:提前用测试账号走一遍完整流程,确保没有"上次演示留下的进行中订单"卡住流程;
- 服务器够稳:演示前重启后端服务、检查数据库连接、把磁盘日志清一下,避免演示一半服务内存溢出崩溃。
还有一个小技巧:把后端的日志级别调到 DEBUG,演示时一旦出问题,可以直接现场翻日志说明原因,比干巴巴说"可能是网络问题"有说服力得多。
6.4 还能往哪扩展:给想加分的同学三条路
主流程走完以后,如果你还有时间,我最推荐的三个扩展方向:
- 信用分机制:超时未还扣信用分,低于某个阈值禁止借伞。这个功能直接把系统从"工具"拔高到"机制",评委很吃这一套;
- 微信支付押金与费用结算:接入微信支付预授权或押金流程,整个商业闭环就完整了;
- 设备端硬件对接:如果条件允许,把伞架改造成带锁控的单片机设备,小程序扫码后通过后端 MQTT 消息控制开锁,这也意味着你要引入网络通信层,项目深度立刻上一个台阶。
功能不用贪多,把一个扩展点做扎实,比铺五个半成品更能展示能力。
这个项目看起来是典型的"课设小品",但真正动手做一遍就会发现,它的难点恰好藏在那些不起眼的角落:状态一致性、并发控制、计费规则、域名部署。把这些点每一个都想透,你收获的就不只是一套能演示的代码,而是一套处理真实业务问题的思维方式。如果你正在做类似的微信小程序管理类项目,希望这篇总结能帮你少走几步我当时走过的弯路。