背景痛点:为什么“能跑”≠“能毕业”
每年 3-4 月,校园打印店都会挤满抱着电脑改 BUG 的大四同学。微信小程序因为“无需下载、即点即用”成了毕设热门选题,但评审老师给出的评语却惊人一致:
- 代码一锅粥:Page 里既拉数据又改 DOM,业务逻辑和 UI 耦合到无法单测
- 密钥硬编码:AppSecret、数据库密码直接写在
app.js,GitHub 一搜就能撞库 - 并发当单机:测试时只有一个人点击,答辩现场三位老师同时扫码,接口瞬间 502
这些问题本质上是把“玩具项目”思维带进了生产场景。毕设不只是“能跑”,还要“说得清、改得动、撑得住”。下面按“选型→架构→实现→优化→上线”五段式,拆解如何把一个小程序做成老师挑不出刺的“小生产级”项目。
技术选型:云开发 or 自建后端?一张表看懂
| 维度 | 微信云开发 CloudBase | 自建 Node/Java 后端 |
|---|---|---|
| 上线速度 | 免域名、免备案,一键开通 | 需买服务器、备案、配 Nginx |
| 并发成本 | 按调用量计费,起步免费 | 1 核 2G 只能撑 200 并发,续费要花钱 |
| 运维门槛 | 无服务器概念,日志在云端 | 得懂 PM2、Docker、Linux 命令 |
| 生态耦合 | 内置微信登录、支付、云函数 | 要自己对接 wx-sdk、证书 |
| 可扩展性 | 云函数灰度、CMS 已成熟 | 代码自己写,想加缓存、队列随意 |
结论:
如果团队 2-3 人、时间 6 周以内、无专职运维,优先云开发;想折腾微服务、练手 Docker/K8s,再选自建。下文以云开发为主线,但关键代码同时给出“自建版”接口,方便迁移。
核心实现:三层解耦 + 幂等调用 + JWT 鉴权
1. 目录先分层
miniprogram/ ├─ pages/ // 只放 UI ├─ components/ // 可复用业务组件 ├─ models/ // 云函数或 wx.request 封装 ├─ utils/ │ ├─ request.js // 带节流、重试、幂等 │ └─ auth.js // JWT 解析与刷新2. 页面只关心“数据长什么样”
使用 Component 构造器把页面拆成“壳+组件”:
// components/post-list.js Component({ properties: { list: Array // 只负责渲染 }, methods: { onTap(e) { this.triggerEvent('itemtap', e.currentTarget.dataset.id) } } })页面变成“数据调度器”:
// pages/index.js Page({ data: { posts: [] }, async onLoad() { const list = await PostModel.list() // 云函数 or 请求 this.setData({ posts: list }) }, navToDetail(e) { wx.navigateTo({ url: `/pages/detail?id=${e.detail}` }) } })UI 与数据流彻底解耦,老师问“如果以后换 Vue 怎么办?”你可以 5 分钟把组件移植过去。
3. 统一请求层:带幂等 token
云开发版:
// utils/request.js const request = (name, data = {}) => { const uuid = wx.getStorageSync('uuid') || Date.now()+'' wx.setStorageSync('uuid', uuid) return wx.cloud.callFunction({ name, data: { ...data, _nonce_id: uuid } // 云函数内做幂等校验 }) }自建后端版:
const axios = require('./axios-miniprogram') // 封装了 wx.request const queue = new Map() const request = (url, data) => { const key = url + JSON.stringify(data) if (queue.has(key)) return queue.get(key) // 相同请求直接返回 const p = axios.post(url, data).finally(() => queue.delete(key)) queue.set(key, p) return p }4. JWT 鉴权流程(云开发同样适用)
登录云函数:
// cloudfunctions/login/index.js const cloud = require('wx-server-sdk') cloud.init() const jwt = require('jsonwebtoken') exports.main = async (event) => { const wxContext = cloud.getWXContext() const { OPENID } = wxContext const token = jwt.sign({ openid: OPENID }, process.env.SECRET, { expiresIn: '7d' }) return { token } }前端拦截器:
// utils/auth.js const TOKEN_KEY = 'jwt' export const getToken = () => wx.getStorageSync(TOKEN_KEY) export const setToken = t => wx.setStorageSync(TOKEN_KEY, t) export const login = async () => { const res = await wx.cloud.callFunction({ name: 'login' }) setToken(res.result.token) }此后所有业务请求在 Header 带Authorization: Bearer <token>,云函数/后端统一用中间件校验,避免 openid 裸奔。
安全性与性能:别让“小项目”变成“大事故”
openid 泄露风险
不要把 openid 当主键返回给前端,更不可当 userId 拼接在 URL。JWT 里存 openid,前端只拿“无意义”的 userId。冷启动优化
云函数 1 秒冷启动是常态。把“登录”与“首页数据”合并成一个云函数,减少一次往返;或者开“常驻”云函数(最小实例数=1),牺牲 0.1 元/天换 200 ms。请求节流
按钮置灰只是 UI 层面,网络层同样要做防抖。上文 request.js 用 Map 做队列,200 ms 内相同参数直接返回缓存 Promise,后台压力瞬间降 70%。
生产环境避坑指南
审核规范
- 涉及“抽奖”“红包”必须提前申请类目,否则提审秒拒
- 用户发布内容先做关键词过滤,再调用
security.msg.send异步审核,留日志备查
本地存储滥用
wx.setStorageSync有 10 MB 上限,图片 Base64 别往里塞;用户敏感信息(手机号、地址)用完即清,否则老师一抓一个准。真机调试技巧
- 开发工具里把“不校验域名”关掉,提前暴露 HTTPS 证书问题
- 安卓与 iOS 的键盘弹出高度不同,固定定位元素用
padding-bottom: env(safe-area-inset-bottom)做适配 - 网络面板看 waterfall,超过 600 ms 的接口优先优化,答辩现场外网信号差,老师可不会等你 3 秒白屏
可复用模板:登录+列表+详情
// cloudfunctions/post-list/index.js const db = cloud.database() const _ = db.command exports.main = async (event, ctx) => { const { openid } = ctx.userInfo // 已鉴权 return await db.collection('posts') .where({ openid }) .orderBy('createTime', 'desc') .limit(20) .get() }前端调用:
// models/post.js import { request } from '../utils/request' export const PostModel = { list() { return request('post-list') }, detail(id) { return request('post-detail', { id }) } }一套代码,既能在云开发跑,也能把request换成axios直连 NestJS,老师问“如何迁移”时你能当场演示。
思考题:如何把毕设扩展成多租户 SaaS?
- 数据库层:给每条数据加
tenantId字段,云开发新建一个tenant集合存配额与配置 - 鉴权层:JWT payload 里再存
tenantId,中间件校验用户是否在该租户下 - 资源隔离:云开发环境暂不支持分库,可用集合前缀
t${tenantId}_posts做软隔离;自建后端则一个租户一个 schema - 计费拆分:云开发按调用量计费天然支持“谁用谁付”,后端需自己记录 API 调用次数,定时跑批价任务
动手把单租户代码改成“登录时选租户”,你就拥有了一个能放进简历的 SaaS 原型,而不再只是“毕业设计”。
写完这篇笔记,我把整套模板丢给学弟,他两周就搭出了“校园二手书”小程序,答辩时老师只问了一个问题:“为什么你的代码比上届清晰这么多?”答案其实很简单——把毕设当成上线产品做,而不是当作业交。如果你也在折腾微信毕设,不妨从“分层+幂等+JWT”这三板斧开始,先让代码跑得动,再让架构站得住,最后留给评审一个“我敢上线”的底气。