毕设选题实用小程序:基于 Serverless 架构的高效开发与部署实践
一、背景:毕设周期短,别再被服务器拖后腿
每年 3-4 月,高校实验室里最常听到的两句话:
“选题系统怎么又挂了?”
“离答辩只剩 40 天,我连服务器还没买!”
传统打法是:买云主机 → 装 MySQL → 配 Nginx → 写接口 → 调 HTTPS → 压测 → 修 bug → 续费。
对于只有 1-2 名开发者的毕设团队,这一套流程直接把 30% 时间吃光,还附赠半夜收到“磁盘打满”告警的惊喜。
痛点总结:
- 周期紧:从开题到答辩平均 90 天,留给纯开发不足 45 天
- 零运维经验:学生党对 Linux、Docker、CI/CD 不熟,排障全靠搜索
- 预算有限:低配 ECS 也要 300 元/月,万一并发突增,机器扛不住,加钱又心疼
- 需求简单却完整:账号体系、选题发布、抢选、退选、查询、导出,麻雀虽小五脏俱全
一句话:我们需要“不碰服务器、按量付费、写完即上线”的方案——Serverless 小程序云开发正是答案。
二、技术选型:为什么放弃“传统 Web 框架”
| 维度 | 传统 Web 框架(SpringBoot / Django + ECS) | Serverless 小程序云开发(云函数 + 云数据库) |
|---|---|---|
| 运维成本 | 需要装环境、配域名、打补丁 | 0 运维,平台全托管 |
| 弹性伸缩 | 手动升降配,高峰怕挂 | 自动扩缩容,按请求计费 |
| 开发语言 | 任选 | Node.js(云函数) |
| 学习曲线 | Linux + 框架 + 部署 | 会 JS 即可,前端同学也能写后端 |
| 冷启动 | 常驻内存,无冷启动 | 有冷启动,但可优化(见第四节) |
| 费用 | 包年包月,最低 300 元/月 | 免费额度足够毕设,流量突增也按量 |
| 上线速度 | 平均 3-5 天 | 最快 2 小时 |
结论:在“资源、时间、人力”三重约束下,Serverless 方案综合得分更高。对毕设这种生命周期 < 90 天、峰值 QPS < 200 的小系统,云开发是性价比之王。
三、系统全景:一张图看懂架构
- 小程序端:WXML + WXSS + JS,全走 wx.cloud.callFunction,不直接连数据库
- 云函数层:按领域拆成 user、topic、select 三个子目录,单函数单路由,利于并行开发
- 云数据库:collection = user、topic、select_log,权限全走云规则,后台不可直连
- 云存储:导出 Excel 时临时写文件,签名 URL 5 分钟失效,防泄漏
四、核心实现拆解
下面按“用户鉴权 → 数据模型 → API 路由 → 并发竞争写入”四个关键点展开,给出可直接落地的代码片段(Node.js 14)。
1. 用户鉴权:借助微信登录,省掉自建账号
// cloudfunctions/user/login/index.js const cloud = require('wx-server-sdk') cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }) exports.main = async (event, context)席卷而来 { const wxContext = cloud.getWXContext() const { OPENID, UNIONID } = wxContext // 首次登录自动写库 const userCol = cloud.database().collection('user') const exist = await userCol.where({ _openid: OPENID }).limit(1).get() if (exist.data.length === 0) { await userCol.add({ data: { _openid: OPENID, unionid: UNIONID || '', name: '', stuNo: '', avatar: '', role: 'student', // 默认学生 教师可在后台改 gmtCreate: new Date() } }) } return { openid: OPENID, role: exist.data[0]?.role || 'student' } }前端一行代码即可拿到用户身份,无需自己管理 session。
2. 数据结构设计:三表搞定选题
- user:存储身份、角色
- topic:教师发布的选题,字段含 title、desc、maxSelect、remain、teacherName、status
- select_log:学生每抢选/退选一条记录,用于幂等与日志追踪
// topic 示例文档 { "_id": "自动", "title": "基于深度学习的口罩检测", "desc": "使用 YOLOv5 实现 blabla…", "maxSelect": 3, "remain": 2, "teacherName": "王老师", "status": 1, // 1 可抢选,0 已满 "gmt_create": "2024-03-01T08:00:00Z" }3. API 路由组织:单函数单路由,Clean Code
// cloud/router.js const routes = { 'topic/list': 'topic/list/index', 'topic/select': 'topic/select/index', 'topic/export': 'topic/export/index', 'user/login': 'user/login/index' } module.exports = routes入口函数只做分发,逻辑下沉到子模块,方便单元测试。
4. 并发竞争写入:remain 字段的“减库存”怎么防超卖
云数据库不支持事务,但提供了“原子自增”指令 inc,利用它即可避免竞态:
// cloud/topic/select/index.js const cloud = require('wx-server-sdk') cloud.init() const db = cloud.database() const _ = db.command exports.main = async (event, context) => { const { topicId } = event const { OPENID } = cloud.getWXContext() // 1. 幂等:同一学生不能重复选 const logCol = db.collection('select_log') const exist = await logCol.where({ topicId, studentId: OPENID }).count() if (exist.total > 0) return { ok: false, msg: '已选过' } // 2. 原子减 remain const topicCol = db.collection('topic') const updateRes = await topicCol.doc(topicId).update({ data: { remain: _.inc(-1) } }) // 3. 减后校验 const after = await topicCol.doc(topicId).get() if (after.data.remain < 0) { // 回滚 await topicCol.doc(topicId).update({ data: { remain: _.inc(1) } }) return { ok: false, msg: '名额已满' } } // 4. 写日志 await logCol.add({ data: { topicId, studentId: OPENID, action: 'select', gmt: new Date() } }) return { ok: true } }要点:
- 用 inc 保证“读-改-写”原子
- remain<0 时立即回滚,数据不会脏
- 日志表可后续做退选、统计、审计
五、性能与安全:冷启动、脱敏、限流
1. 冷启动优化
云函数首次调用会拉容器,平均 600-800 ms。毕设评审现场可接受,但演示时第一次等 1 s 会尴尬。做法:
- 预暖:在小程序 onLaunch 里调用一次空函数
cloud.callFunction({name: 'warm'}) - 打包瘦身:node_modules 只留依赖,把 dev 包剔除,体积 < 1 MB 时冷启动缩短 30%
- 单函数多路由:拆太碎会放大冷启动,建议按“领域”聚合,不超过 10 个函数
2. 敏感数据脱敏
导出 Excel 含学生手机号、成绩时,要在云函数里脱敏:
const desensitize = (str, start, end) => { return str.substring(0, start) + '*'.repeat(str.length - start - end) + str.slice(-end) }同时把文件放云存储,签名 URL 有效期 5 分钟,过期自动 403。
3. 接口限流
虽然云函数有平台级 QPS 限制(默认 100/s),但毕设现场可能 200 人同时刷新。可在前端加“防抖”+“随机退让”,后端用 redis 计数器(云托管 Redis)或内存 LRU 简单限流,防止把配额打满。
六、生产环境避坑指南
本地调试与线上差异
- 微信开发者工具的云开发环境变量是“dev”,上传后自动切“release”,数据库权限规则不同,一定在“云开发控制台-权限管理”里把两条环境都配好
- 时间字段用
new Date()时,本地是电脑时区,线上是 UTC,建议统一存时间戳或 ISO string,前端再格式化成北京时间
配额超限预警
- 免费额度:数据库读 5 万次/天,写 3 万次/天,云函数调用 20 万次/天。毕设 200 人抢选,峰值写操作约 600 次,不会超;但导出 Excel 每次扫描全表,容易读爆。实现时加分页,限制一次最多 1000 条
- 订阅“微信云开发助手”小程序,配额达 80% 会推送,提前降量或升级按量
云函数日志
- 控制台默认只保留 7 天,定位问题要趁早。关键节点用
console.log('[select] topicId:', topicId)打标签,方便检索 - 若出现“Function execution failed”,先查内存是否超限(默认 256 MB),导出大文件时建议升到 512 MB
- 控制台默认只保留 7 天,定位问题要趁早。关键节点用
小程序审核
- 类目选“教育-在线教育”,无需《ICP 许可证》
- 在“用户隐私说明”里把收集的 openid、stuNo 列清楚,否则审核打回
七、模板化交付:把上述代码变成“一键模板”
我已把整个仓库打成脚手架,目录如下:
miniprogram-bx ├─ cloud/ // 云函数 ├─ miniprogram/ // 小程序端 ├─ script/ // 批量导入模拟数据 └─ docs/ // API 说明 & 部署流程使用步骤:
- 打开微信开发者工具 → 新建 → 选用“云开发模板” → 把本仓库拖进去
npm install安装依赖,点击“上传并部署:所有文件”- 在云开发控制台 → 数据库 → 新建 collection:user、topic、select_log
- 运行
node script/init.js快速写入 50 条模拟选题 - 真机扫码 → 即刻体验抢选
整个流程 15 分钟,即可拥有一个“能跑、能抢、能导出”的毕设选题小程序。
八、可扩展方向 & 架构取舍思考
- 把“选题”改成“实验室预约”、“竞赛报名”同样适用,只需替换字段
- 若要支持“学生自主申报题目”,可在 topic 表加 type 字段,流程一样
- 如果校教务处要求部署在校内机房,则 Serverless 优势消失,需回退到 SpringBoot + Docker,模板里已预留接口层,改个 baseURL 即可
- 冷启动对实时对战类不可接受,但管理类系统完全够用;架构选择永远是在“资源、时间、体验”三角里找平衡
写在最后
整套方案做下来,我们组 2 个人,从开题到上线仅用了 3 个周末,老师验收时甚至没意识到后台“没有服务器”。省下的时间,全拿去卷论文和 PPT 了。
如果你也正被“选题系统”折磨,不妨直接拿模板开刀;在真实资源受限的场景里,先让系统跑起来,再谈高并发、微服务、可观测——这大概就是毕设带给我们的最大收获。祝你编码愉快,答辩顺利!