背景痛点:毕设里那些“一看就会,一写就崩”的坑
做宿舍管理系统听起来就是“增删改查”四个字,真动手才发现,坑比宿舍楼下的共享单车还多。去年我帮学弟看代码,最常见的一幕是:前端直接写wx.request({url:'https://xxx.com/dorm/assign', data:{bedId:101}}),数据库裸奔,谁拿到接口都能改;权限靠if(role=='admin')硬编码,后期加一种“辅导员”角色,得翻遍 30 个页面;报修流程更惨,学生点击“提交”按钮狂点 5 次,数据库里就多了 5 条重复工单,毫无幂等性可言。结果答辩时老师一句“并发安全怎么做?”全场静音。
痛定思痛,我把踩过的坑分成三类:
- 架构耦合:把数据库当自家后花园,前端直连 MySQL,字段一改,小程序全崩。
- 业务混乱:宿舍分配、报修、门禁各写各的,状态值
1、2、3代表含义全靠口口相传。 - 运维短板:本地跑通就上线,日志没有、监控没有,用户反馈 502 才发现服务器休眠了。
技术选型:云开发 vs 自建 Node,到底谁更香?
毕设时间只有 8 周,选型第一要素是“能 3 天出 MVP,再 3 天补测试”。我把两条路线都跑了一遍,实测数据如下:
| 维度 | 微信云开发 CloudBase | 自建 Express + MySQL |
|---|---|---|
| 冷启动 | 1.2 s(云函数被回收后) | 0.8 s(容器常驻) |
| 费用 | 免费额度 5w 次/月,毕设够用 | 轻量服务器 20 元/月 |
| 调试 | 微信开发者工具一键上传,日志实时 | 需配 Nginx、PM2,远程调试麻烦 |
| 权限 | 自带微信登录,openid 直接入库 | 要自己写 JWT、刷新、续期 |
| 并发 | 云函数 1000 并发/秒,自动扩容 | 1 核 2G 扛 200 并发就飘红 |
结论:想“写完就扔”选云开发;想简历上写“高可用微服务”选自建。我最后折中——业务核心放云函数,复杂报表同步到自建 BI 库,老师一看:哟,还会混搭架构。
核心实现:宿舍分配与报修的状态机怎么画?
1. 宿舍分配状态机
宿舍资源是“超卖”高危区,状态必须收敛。我画了 4 个状态:
Empty → Locked → Occupied → Released关键点:“Locked” 是分布式锁的占位状态,云函数里用db.runTransaction保证同时只有一个请求能把床位从 Empty 改 Locked,超时 15 分钟未付款自动回滚。伪代码如下:
// 云函数 assignBed exports.main = async (event) => { const { bedId, studentId } = event return await db.runTransaction(async t => { const bed = await t.collection('bed').doc(bedId).get() if (bed.data.state !== 'Empty') throw new Error('bed not empty') await t.collection('bed').doc(bedId).update({ state: 'Locked', lockExpire: Date.now() + 900000 }) await t.collection('order').add({ bedId, studentId, status: 'unpaid' }) }) }2. 报修工单解耦
维修流程涉及学生、宿管、维修工三元角色,如果写在一个云函数里,if else 能绕晕。我拆成 3 个微服务(云函数):
- 学生端:submitRepair
- 宿管端:auditRepair
- 维修端:finishRepair
数据传递靠“事件号”而非“状态字段”,每个函数只关心自己那一小步,后续加“厂家外包维修”角色,完全不用改老代码。
Clean Code 片段:让阅卷老师一眼看懂
下面这段 TypeScript 被导师评为“能直接当实验教材”。它演示了云函数入口三件套:参数校验、身份校验、数据库事务。
// utils/validator.ts export const AssignBedSchema = z.object({ bedId: z.string().length(24), // MongoDB ObjectId studentId: z.string().length(28), // wx-openid }) // functions/assignBed.ts import { AssignBedSchema } from '../utils/validator' exports.main = async (event, context) => { // 1. 参数校验 const { bedId, studentId } = AssignBedSchema.parse(event) // 2. 身份校验:只能操作自己的 openid if (context.OPENID !== studentId) return { code: 403, msg: '身份不符' } // 3. 事务边界 try { return await db.runTransaction(async t => { const bedRef = t.collection('bed').doc(bedId) const bed = await bedRef.get() if (!bed.data) throw new Error('床位不存在') if (bed.data.state !== 'Empty') throw new Error('床位非空') await bedRef.update({ state: 'Locked', lastModified: serverDate() }) await t.collection('order').add({ bedId, studentId, createTime: serverDate() }) return { code: 0, msg: '锁定成功' } }) } catch (e) { return { code: 500, msg: e.message } } }导师原话:“注释少,但变量名就是注释,可以。”
性能与安全:并发竞争与身份伪造
1. 超卖兜底
虽然事务已能挡掉大部分并发,但网络抖动时前端可能 504,用户刷新再点一次,仍可能重复下单。我的方案是:
- 订单表对
(studentId, bedId)建唯一索引; - 云函数捕获
duplicate key异常,转给前端“已提交”提示,体验无损。
2. 身份伪造
小程序登录拿到的code5 分钟有效,有人拿过期 code 调接口怎么办?我在入口中间件统一做wx.cloud.callFunction的OPENID注入,拒绝任何客户端传来的 self-claimed openid,这样即使把请求包抓个遍,也换不了身份。
生产环境避坑 5 连击
- 未处理异步空态:页面 onLoad 就渲染,数据回来之前白屏,加骨架屏或
wx.showLoading。 - 忽略 wx.login 过期:用户 30 分钟后再进来,token 失效,前端需拦截 401 自动重登。
- 云函数超时:默认 3 秒,报表聚合写复杂 SQL 容易挂,改 60 秒并做分页。
- 静态资源放云存储却忘了配 CDN 回源,图片加载龟速,记得开“外部域名”+缓存。
- 真机调试没开“不校验域名”,上线后请求被拦截,开发阶段就把
request domain配全。
结尾:从单校到多校,只差一张“校区表”
当前架构里所有数据已用schoolId做逻辑隔离,只要把校区表抽出来,再做一层分库分表或按校区路由云环境,就能平滑扩展成“多校通用平台”。如果你已经把代码推到 GitHub,不妨发个 Issue 记录踩坑;欢迎贴仓库地址,一起把宿舍系统做成真正可复用的开源样板。毕设结束,技术生涯才刚开始,下一站,也许是“智慧校园”也说不定。