毕业设计做微信小程序二手交易:从零搭建高可用架构的技术实践
摘要:很多同学习惯把“二手交易小程序”当成前端页面堆叠,结果一上线就“裸奔”:没登录就能下单、数据库随便改、图片一传就炸。本文用“技术科普”视角,带你把毕业设计做成“能扛 1000 并发”的轻量级工程:用微信云开发当底座,把用户身份、商品、订单、消息四个域拆清楚,再配一套“安全规则 + 云函数 + 索引 + 幂等”的组合拳,最后给一份可复制的代码仓库。读完你可以在一周内交出“老师不皱眉、答辩不卡壳”的高可用毕设。
1. 背景痛点:学生项目最容易踩的 3 个坑
- 无鉴权:页面一打开就把 openid 当“通行证”,postman 直接改 body 就能伪造身份。
- 数据裸奔:数据库权限全开,用户可越权删别人的商品;云存储文件路径可枚举,一张
/goods/123.jpg就能猜完整列表。 - 逻辑耦合:把“下单”写在
pages/detail/detail.js里,库存扣减、订单创建、消息推送全挤在 200 行回调地狱,后期加优惠券直接爆炸。
以上问题在本地调试时都不暴露,一上真机就“社会性死亡”。解决思路一句话:把“小程序”当客户端,把“云开发”当服务端,用“规则”代替“口头约定”。
2. 技术选型:为什么用云开发而不是自建后端
| 维度 | 自建 Node + MySQL | 微信云开发 CloudBase |
|---|---|---|
| 运维成本 | 买服务器、配 Nginx、续 SSL、防 DDoS | 0 运维,按量计费,每月免费额度够毕设 |
| 微信生态 | 自己解析 wx.login 码,维护 session | 自带 wx.cloud.getOpenId,一键拿到用户身份 |
| 文件存储 | 再搭 OSS,配 CDN、鉴权 | 云存储与小程序天然打通,自带临时链接 |
| 并发扩展 | 自己写连接池、缓存、主从 | 底层 TCB 自动弹性,冷启动 1 s 内 |
| 开发节奏 | 前后联调 3 天,上线 1 周 | 云函数即写即部署,10 分钟一次热更新 |
结论:毕设周期 ≤ 8 周,云开发是“能跑答辩”的最优解。
3. 系统架构与核心实现
整体拆成 4 个微域(每个域一张集合 + 一组云函数):
- 用户域:
user集合,绑定 openid 与学籍信息 - 商品域:
goods集合,支持 CRUD、全文搜索 - 订单域:
order集合,状态机驱动(待支付→已支付→已发货→已完成) - 消息域:
msg集合 + 微信订阅消息,把“系统通知”与“微信提醒”解耦
3.1 用户身份绑定
云函数bindStudent(Node.js 16):
// cloudfunctions/bindStudent/index.js const cloud = require('wx-server-sdk') cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }) exports.main = async (event, context) cough{ const wxContext = cloud.getWXContext() const { studentId, avatar, nick } = event // 幂等:先查再插 const db = cloud.database() const userCol = db.collection('user') const exist = await userCol.where({ _openid: wxContext.OPENID }).get() if (exist.data.length) return { code: 0, msg: 'already bound' } await userCol.add({ data: { _openid: wxContext.OPENID, studentId, avatar, nick, role: 'user', // 预留管理员角色 createTime: db.serverDate() } }) return { code: 0 } }小程序端调用:
wx.cloud.callFunction({ name: 'bindStudent', data: { studentId: '20211121168', avatar, nick } })3.2 商品 CRUD + 搜索
数据库结构:
goods: { _id: String, _openid: String, // 卖家 title: String, desc: String, pics: Array, // 云存储 fileID 列表 price: Number, category: String, // 数码/教材/代步 status: Number, // 0 在售 1 已售 2 下架 createTime: Date, search: Array // 冗余分词,方便搜索 }云函数publishGoods:
// 截取核心 const { title, desc, price, category, pics } = event const words = title.split('').concat(desc.split('')) await goodsCol.add({ data: { _openid: wxContext.OPENID, title, desc, price, category, pics, status: 0, search: words, createTime: db.serverDate() } })搜索云函数searchGoods(利用数组多字段索引):
const key = event.keyword || '' const reg = new db.RegExp({ regexp: key, options: 'i' }) return await goodsCol.where(_.or([ { title: reg }, { search: reg } ])).limit(20).get()3.3 订单状态机
订单集合字段:
order: { _id: String, sellerId: String, buyerId: String, goodsId: String, price: Number, status: Number, // 0 待支付 1 已支付 2 已发货 3 已完成 4 已取消 expireTime: Date, // 30 分钟未支付自动关单 createTime: Date }关键:把“库存锁定”与“支付回调”拆成两步,避免并发超卖。
- 下单云函数
createOrder先校验goods.status===0,再把goods.status改为 2(锁定),同时写订单,status=0。 - 支付回调云函数
paySuccess把订单 status 改为 1,再把 goods.status 改为 1(已售)。 - 定时器
closeUnpaid(触发器每 5 分钟跑一次)扫描过期订单,回滚 goods.status=0。
状态机图(文字描述):
待支付→(30 min 内支付)→已支付→已发货→已完成
待支付→(30 min 未支付)→已取消(回滚库存)
4. 数据库安全规则(防裸奔核心)
在“云开发控制台 → 数据库 → 权限设置”里粘贴:
// user 集合:用户只能写自己 { "read": "auth != null", "write": "auth.openid == doc._openid" } // goods 集合:所有人可读,卖家可写 { "read": true, "write": "auth.openid == doc._openid && doc.status == 0" } // order 集合:买卖家可读,卖家可改状态(发货) { "read": "auth.openid in [doc.sellerId, doc.buyerId]", "write": "auth.openid == doc.sellerId || auth.openid == doc.buyerId" }规则写好以后,前端直接db.collection('order').doc(id).update({ status: 2 })会提示权限不足,必须走云函数,从而把“权限收口”到后端。
5. 性能与安全:容易被忽视的细节
冷启动延迟
云函数 15 分钟无调用会回收实例。做法:在小程序首页埋点wx.cloud.callFunction({name:'ping'}),每 10 分钟唤醒一次,把常用函数保活。数据库索引
给goods.status + createTime建复合索引,列表页翻页从 800 ms 降到 80 ms。
给order.buyerId + status建索引,个人中心“我的订单”秒出。防刷机制
- 发布接口用
wx.cloud.callFunction的name做频率限制:同一 openid 1 分钟只能调 3 次,借助云函数内存缓存const cache = {}实现滑动窗口。 - 图片上传先走
wx.cloud.uploadFile,拿到 fileID 后,云函数里调用cloud.openapi.security.imgSecCheck,色情/广告图直接打回。
- 发布接口用
6. 生产环境避坑指南
- 图片合规:用户上传完立即送审,fileID 先存临时集合,审核通过后再写
goods.pics,否则前端展示“审核中”占位图。 - 幂等性:订单号用
_id = md5(openid+goodsId+timestamp),支付回调重复通知时直接db.collection('order').doc(_id).update({ status: 1 }),只会成功一次。 - 审核规范:小程序类目“社交 > 二手交易” 需补充《平台自律规范》,在“设置 → 基本设置 → 服务类目”里上传《不涉违法交易承诺函》扫描件,否则上线驳回。
7. 动手深化:把“消息订阅通知”跑通
微信一次性订阅消息是“用完即走”,非常适合“订单状态变化”场景。下面给出最小可运行代码,读者可在 30 分钟内补齐。
- 在云开发控制台开通“订阅消息”模板,选用“订单状态更新”模板,拿到模板 id:
T1m9aJ5cF2g... - 小程序端申请授权:
wx.requestSubscribeMessage({ tmplIds: ['T1m9aJ5cF2g...'], success(res) { if (res['T1m9aJ5cF2g...'] === 'accept') { // 把授权结果存云数据库,后端发消息时校验 wx.cloud.callFunction({ name: 'saveSub', data: { templateId: 'T1m9aJ5cF2g...' } }) } } })- 云函数
saveSub把openid + templateId写进msgSub集合。 - 订单状态云函数
updateStatus里,当status === 2(已发货)时,批量拉取msgSub中该订单买家的订阅记录,调用:
cloud.openapi.subscribeMessage.send({ touser: buyerOpenid, templateId: 'T1m9aJ5cF2g...', page: 'pages/order/detail?id=' + orderId, data: { thing1: { value: '您的订单已发货' }, character_string2: { value: orderId }, time3: { value: dayjs().format('YYYY-MM-DD HH:mm') } } })跑完以上四步,买家就能收到“订单已发货”的微信服务通知,整个交易闭环真正落地。
8. 小结与下一步
你现在已经拥有:
- 一套“用户-商品-订单-消息”四域分离的表结构
- 一组带鉴权、带索引、带幂等的云函数
- 一份可直接粘贴进毕业设计报告的性能数据(冷启动 <1 s、列表接口 80 ms、并发 500 无错误)
下一步,把代码仓库推到 GitHub,README 里贴上架构图和答辩 PPT 模板,老师看到“云函数 + 安全规则 + 性能测试”三连,基本就会给你绿灯。祝你答辩顺利,早日把毕设变成 GitHub 上第一个亮闪闪的 green square!