基于微信小程序的电影院选座系统:从需求拆解到落地调试的完整复盘
做这个项目之前,我印象最深的一次经历是某个周末想在家门口的影院看场电影,打开购票App翻了一圈,选座界面要加载好几秒,点一个座位响应还卡顿。当时我就在想:为什么一个选座交互能做成这样?一个"点座位-下单-支付"的链路,到底涉及多少技术细节?后来正好要用微信小程序做一个完整的实战项目,我就把"电影院选座系统"这个题目定了下来,前后写了大概三千行代码,从数据库建模、接口设计到小程序端选座交互、支付回调调试,一遍跑通之后才发现这个项目远比表面看起来有料。
这篇内容不是课程式的教程,更像是一份完整的项目复盘。我没有按"先讲原理再做Demo"的思路来,而是按我在实际开发时真正经历的顺序,把从需求拆解、技术选型、数据库设计、核心交互实现,到调试阶段踩过的坑,再到源码结构和文档整理的思路全部分享出来。如果你正准备用这个题目做毕业设计、课程设计,或者单纯想熟悉微信小程序全栈开发流程,这篇内容应该能帮你少走很多弯路。
1. 别急着写代码:这个系统要解决的真实问题是什么
1.1 一个真实场景:从买票到入场的完整链路
电影院选座系统的本质,是把线下影院的"售票窗口+选座看板"搬到用户的手机里。我们看线下场景:用户走到影院,先看大屏幕上的影片排片表,选中一部片子、定下时间场次,然后到售票处,柜员会让他看一张座位图,空座可以选,已售出的座位是灰色,交钱出票,用户拿着实体票进场。整个过程看起来简单,但涉及两个核心业务对象:场次和座位。
场次决定了用户能买什么时间的票,座位决定了用户能不能买到自己想去的位置。到了线上,这个链路会变成:用户进入小程序 -> 浏览影片列表 -> 进入某部影片的场次列表 -> 选择一个场次 -> 查看座位图 -> 选座 -> 下单 -> 支付 -> 生成取票码 -> 到影院取票或扫码进场。
这里有个关键区别:线下售票员在选座时,会直接告诉你"这个位置有人选了",但线上系统要解决的,是怎么保证同一时间大量用户看到的座位状态真实可靠。如果两个用户同时在选同一个座位,系统必须保证只有一个能成功锁定。这就是选座系统区别于普通展示类小程序的核心难点。所以我在项目一开始就没急着写页面,而是先把整个业务流程画清楚,再决定技术方案。
1.2 功能边界:做到什么程度才算"完整"
很多同学做这类系统容易犯一个错误,就是功能堆得很多,但每个都半吊子。我的建议是先定义清楚"完整的MVP(最小可用产品)"要包含哪些功能,然后再做加法。以这个电影院选座系统为例,我最终拆出了两条角色线。
用户端功能包括:微信授权登录、影片列表展示、影片详情(包括简介、海报、时长、上映日期等)、场次选择、座位图展示与选座、订单确认、微信支付、订单列表和个人中心。管理员端功能包括:影厅管理(配置每个影厅的排数和列数)、影片管理(上架和下架影片)、排片管理(在指定时间安排指定影片到指定影厅)、订单管理(查看所有用户的订单,处理退款)。
你可能会问:管理员端能不能不要?我的回答是:如果是作品展示或毕设,建议保留。原因很简单,管理员角色能让你的系统形成数据闭环,演示的时候不是空数据。如果观众想看《流浪地球3》的场次,你得先能在后台把影片和排片数据维护进去。另外,管理员端的设计也直接决定了你的数据库表结构是否完整,只有当你真正去设计"影厅表"和"排片表"时,才会理解为什么电影场次和影片不能混在一张表里。
1.3 核心流程与背后隐藏的逻辑
确定了功能边界后,需要把用例图和数据流理清楚。虽然这里不讲UML规范,但整个系统里有三条黄金流程,是开发时反复用到的:
第一条是选座锁定流程。用户进入场次座位图时,前端请求后端获取座位状态列表;用户点击某个座位,前端先做本地标记(比如变绿色),同时向后端发送"锁定座位"请求;后端锁定成功后返回确认,前端把座位状态置为"已选";如果锁定失败(座位已被别人抢走),前端要立刻把座位恢复为"空闲"并提示用户。注意,这里的"锁定"不是永久占座,而是带过期时间的临时锁,通常设5到8分钟,超时自动释放,否则一个用户选了座但不付款,座位会一直被占着,体验很糟糕。
第二条是订单状态机流转。订单的常见状态包括:待支付(已锁座未付款)、已支付(付款成功待出票)、已出票(生成取票码)、已取消(用户主动取消或超时未支付)、已退款。后续做订单管理时,如果order_status没有状态机设计,很容易出现"订单没有支付但座位被锁死"这类脏数据。
第三条是支付回调校验流程。用户点击支付后,前端调起微信支付,支付完成后微信服务器会异步通知后端。这里最容易踩的坑是:不能在前端拿到支付成功结果就直接把订单改成已支付,必须以微信支付的后台回调为准,回调后还要做金额校验、订单状态校验。流程虽小,但直接决定了项目靠不靠谱。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术选型思考:微信小程序+什么后端
2.1 为什么是微信小程序而不是App或H5
这可能是很多人在立项时会纠结的问题。我自己做过几个平台的小项目,客观来说,选微信小程序的核心理由是获客成本低、生态完整。用户不需要去应用商店下载App,扫个码或者从聊天窗口点进去就能用;对于影院这种低频次场景,用户手机里常年不会装你的App,小程序反而是最合适的存在。另外微信提供的登录体系也大大降低了开发复杂度,你不需要自己设计注册登录流程,直接调用wx.login拿到code,再通过后端接口换取openid,就能识别用户身份。
对开发者来说,小程序端的开发调试也很方便,微信开发者工具可以从浏览器模拟器无缝切换到真机预览,控制台、Network面板、Storage查看器都很齐全。这种"所见即所得"的调试体验,对于单人开发一个完整项目来说能节省大量时间。
当然也要说明一点,微信小程序在包体积上有2MB限制(主包),因此图片资源尽量不要打包进去,最好使用云存储或图床。这一点我后面的方案里会讲。
2.2 前端方案:原生小程序已经够用
关于前端开发方式,我一开始想过要不要用uni-app或Taro来做跨端,后来还是决定用原生小程序。原因有三:第一,这个项目不涉及多端复用,我只需要跑在微信里;第二,原生框架对微信API的支持最及时,尤其是支付、订阅消息、getLocation这类能力,不会遇到框架封装滞后的问题;第三,原生小程序的调试堆栈最清晰,遇到问题直接看是WXML渲染问题还是JS逻辑问题,不会在框架层多一层排查成本。
原生小程序的目录结构大致如下:app.json负责全局配置(页面路由、窗口样式、tabBar),app.js负责应用生命周期和全局数据,pages目录下每个页面包含四个文件:wxml(视图结构)、wxss(样式)、js(逻辑)、json(页面配置)。我是第一次做这个项目的人,这套结构大概花半天就能上手。
如果你已经有Vue或React基础,原生小程序的写法会让你有点"碰壁"感,因为它既不是传统HTML也不是MVVM框架,但核心思想是相似的:数据驱动视图,通过setData修改数据后触发重新渲染。适应之后你会发现,对于选座这种强交互场景,小程序的数据绑定和事件绑定已经足够用了。
2.3 后端与数据库的选型思路
后端我最终选了Node.js + Express来写,没有上Spring Boot,原因是这个项目的后端逻辑不算重,主要就是用户鉴权、影片/场次/订单的CRUD、座位锁定和支付回调。用Node.js最直观的好处是开发速度快,前端是JavaScript,后端也是JavaScript,心智负担小,而且Express生态里有很多现成的中间件,比如解析请求体、处理跨域、JWT鉴权,都是几行代码就能接好的东西。
数据库选了MySQL,这是很常规的选择,原因是关系型数据在订单和座位场景下比较清晰,而且事务支持非常重要。这里特别强调一下,订单表和座位表的操作必须放在事务里。比如用户支付成功,后端要同时完成"修改订单状态"和"将座位状态改为已售出",如果这两步之间发生异常,就会出现"用户已付款但座位仍显示空闲"的严重问题。
Redis方面,我用它做两件事:一是缓存座位图,减少数据库查询压力;二是做座位锁,利用Redis的原子性SETNX EX命令,可以保证在高并发下同一个座位不会被不同用户同时锁到。如果你们用的MySQL也足够了,可以不引入Redis,但引入它之后,系统的并发能力会明显提升,这也是项目在技术评审时的一个加分项。
2.4 云开发vs自建服务器
做这个项目前,必须先选一条路,因为后续所以代码都围绕这个来写。微信小程序云开发,是目前微信官方主推的Serverless方案,它提供了云函数、云数据库、云存储三大能力。它的好处是省去服务器运维,直接用云函数写后端逻辑,调用云开发数据库直接操作数据。坏处是:第一,数据导出和迁移没那么方便;第二,如果有复杂事务需求,云数据库的写法要花一些时间去适应;第三,后续很可能要按量付费,流量大时成本是个问题。
自建服务器方案则非常灵活,选Node.js或者Java都可以,配合MySQL和Redis,和传统Web后端一样写,部署到任意云服务器上。条件允许的情况下,我更推荐自建服务器,原因是这个项目的核心难点(事务、并发锁、支付回调)在自建方案里都能用最标准的方式解决,遇到问题也更容易排查。如果你完全没有服务器,用云开发过渡一下也是可以的,但请务必提前想清楚事务如何实现。
3. 数据库与接口设计:座位状态是系统的灵魂
3.1 五张核心表的结构设计
我最终设计了六张表:用户表(user)、影片表(movie)、影厅表(hall)、场次表(session)、座位表(seat)、订单表(order)。
用户表相对简单,核心字段是openid,这是微信用户在小程序里的唯一标识,另外包括昵称、头像、注册时间。有一点需要注意,小程序端获取不到用户手机号和头像的真实信息,现在微信改版后,头像昵称需要用户手动填写,所以这张表不要设计得太复杂。
影片表包括影片id、名称、封面图、简介、类型(喜剧/动作/科幻等)、片长(分钟)、上映日期、下映日期、状态。这里有一个容易被忽略的点:上映日期和下映日期要拿来和场次的日期做比对,你不能让影院对一部已经下映的电影继续排片,所以接口层要有这层校验逻辑。
影厅表字段包括影院id、影厅名称、排数(rows)、列数(cols)、座位总数。为什么需要这张表?因为你的座位图是动态的——不同的影厅可能有大有小,3号厅有8排每排10座,IMAX厅可能有15排每排24座。不能把座位数据写死在代码里,而是通过厅配置去生成。
场次表是整个排片的核心,字段包括场次id、影片id、影厅id、开场时间、结束时间、票价。结束时间一般根据影片片长自动计算。你们可能会有疑问:为什么结束时间不直接存进去?因为后续展示"该时段是否有冲突"时,需要用到完整的起止时间范围。
座位表要把"影厅"和"场次"连接起来。常见的设计有两种,第一种是只存储某个场次已售或锁定的座位;第二种是先按影厅生成所有座位的初始数据,然后每个场次复用一份座位快照。我最终用的方案是:seat表里每一条记录对应"某个场次+某个位置"的唯一状态,字段包括seat_id、session_id、hall_id、row_index、col_index、status(0空闲/1锁定/2已售)、锁定时间。这样做的好处是,查询某个场次的座位图时,一条SQL就可以把所有座位状态取出来,非常直观。
订单表要严格一点,字段包括订单号(order_no)、用户id、场次id、座位id列表(用逗号分隔,也可以建订单座位关联表,但简化场景下用逗号够用)、支付金额、订单状态(pending/paid/cancelled/refunded)、创建时间、支付时间、取票码。订单号不要用自增id直接暴露在小程序端,至少加日期前缀加随机数,比如OD202506201230001234。
3.2 座位状态管理:从单字段到完整状态机
座位的状态看似是一个字段,实际上涉及一个小的状态机。总结如下:
| 状态 | 说明 | 触发场景 |
|---|---|---|
| 空闲 | 可购买 | 初始化、订单取消释放、支付超时释放 |
| 锁定 | 用户已选但未支付 | 用户点击选座、后端记录锁座时间 |
| 已售 | 支付成功 | 支付回调验证通过 |
锁和销售的区别非常关键。锁定状态需要带一个超时时间,我设置了8分钟。也就是说,这个座位被锁定后,如果8分钟内订单没有变成已支付,后端会定时任务把座位状态改回空闲。这个"定时释放"看起来简单,实际实现时最方便的方式是用Redis设置带过期时间的key,比如对每个座位设置一个键,有效期8分钟,键到期后数据库如果检测到订单仍是待支付,就回滚座位状态。
为了演示方便,我最初没有做定时任务,而是用"懒释放"的方式:用户打开座位图时,后端先检查是否有超过8分钟仍然处于锁定状态的记录,如果有就批量更新成空闲。这种方式虽然不算完全实时,但对于一个单机部署的毕设项目已经够用,而且省心。
3.3 并发锁座:Redis与乐观锁的取舍
选座系统最容易被面试官追问的一个点就是并发问题。场景是这样的:用户A和用户B同时在看7排5座,A点了选座,此时B的页面里这个座位还是"空闲",然后B也点了。如果后端不做并发控制,两条SQL都会把座位更新成锁定,这就产生了超卖。
解决方案有三种:
第一种是数据库乐观锁。更新座位时用一个version字段做条件,UPDATE seat SET status=1, version=version+1 WHERE seat_id=? AND session_id=? AND status=0。如果影响行数为0,说明座位已被别人抢先锁定,返回失败。这种方案实现最简单,在单事务场景下完全够用。
第二种是Redis分布式锁。用SET seat_lock:{sessionId}:{seatId} userId EX 480 NX 这样的命令,利用Redis的原子性保证同一个座位同一时刻只能被一个用户锁定。这个方案在分布式多实例部署时更稳,但需要在业务逻辑里处理锁的续期和释放,代码复杂度会高一些。
第三种是数据库事务+行级锁。在一个事务里,先SELECT ... FOR UPDATE锁住该座位行,检查状态,再更新。这也能解决问题,但事务持有锁时间较长,在高并发下容易造成锁等待。
我实际采用的是"乐观锁+Redis"的组合方案,先试Redis锁,如果拿到锁再执行乐观锁UPDATE。这样既处理了并发冲突,也能利用Redis的过期时间防止锁忘记释放。这个设计在项目答辩时经常会被问到,建议提前理清逻辑。
4. 选座交互实现:从座位图渲染到订单生成
4.1 用view渲染座位图而不是canvas
座位图在页面中间位置,是整个项目视觉上最核心的部分。有两种渲染方案:canvas和view。对于选座图这种"不复杂但交互点多"的场景,我个人强烈建议用view + wx:for来渲染,而不是canvas。
原因有三个:第一,canvas在小程序里的触摸事件处理没有view那么顺手,你要自己计算像素坐标来映射到每个座位格子,而view天然有width和height,点击事件回调里直接有data-属性可以把座位id传出来;第二,canvas做不了无障碍和调试工具里的元素检查,出了问题不好排查;第三,view方案的视觉效果完全够用,配合flex或grid布局,一个座位格子就是一个圆角小方块,状态用class切换就行。
具体实现是这样的:从后端接口拿到场次座位数据,数据结构是一个二维数组,比如8排10列,数组中每个元素包含rowIndex、colIndex、status。前端用一个嵌套循环渲染,外层循环行,内层循环列;座位方格宽高固定,通过CSS让列间距和排间距均匀。后排密集一些,前排宽一些,这些细节通过class来控制。
code复制// 伪代码示例
<view class="seat-grid">
<view class="seat-row" wx:for="{{seatMap}}" wx:for-item="row" wx:key="index">
<view
class="seat {{item.status === 0 ? 'available' : (item.status === 1 ? 'locked' : 'sold')}}"
wx:for="{{row}}" wx:for-item="seat" wx:key="seatId"
data-row="{{seat.rowIndex}}" data-col="{{seat.colIndex}}"
bindtap="onSeatTap">
</view>
</view>
</view>
用view方案还有一个额外好处,就是当座位数量多(比如15排24列)时,页面渲染性能仍然可以接受,不会出现canvas那样频繁重绘的问题。
4.2 选座交互状态的管理
选座交互的核心逻辑是:用户点击空闲座位,座位变"选中";点击已选中的座位,座位取消选中;已经锁定或售出的座位,点击没有任何反应。同时限制最多选择5个座位,超过数量时给出提示。
这里要注意,前端维护的"选中状态"是临时状态,没有真正锁座。真正的锁座请求要等你提交订单时才发出去。于是碰到一个问题:用户选了3个座位,然后犹豫了5分钟点了别处,前端本地状态还认为这3个座位是"选中"的,但后端早就过期释放了。所以在订单确认前,一定要做一次"重新校验座位状态"的接口调用,确认所有选中的座位仍处于可购状态。
实际实现时,我的onSeatTap函数逻辑大致是:
code复制onSeatTap(e) {
const row = e.currentTarget.dataset.row;
const col = e.currentTarget.dataset.col;
const seat = this.data.seatMap[row][col];
if (seat.status === 1 || seat.status === 2) return;
// 判断是否在已选列表里
const selectedList = [...this.data.selectedList];
const idx = selectedList.findIndex(item => item.seatId === seat.seatId);
if (idx > -1) {
// 取消选中
selectedList.splice(idx, 1);
} else {
if (selectedList.length >= 5) {
wx.showToast({ title: '最多选择5个座位', icon: 'none' });
return;
}
selectedList.push(seat);
}
// 更新临时状态
const seatMap = [...this.data.seatMap];
seatMap[row][col].tempSelected = selectedList.some(item => item.seatId === seat.seatId);
this.setData({ seatMap, selectedList });
}
这里还有一个容易被忽略的细节,即使你的座位图里同一个场次只会出现一次,但前后端数据必须用seatId关联,不能只依靠row和col,因为同一行同一列在同一个场次里是唯一对应的,但后面做"根据座位ID查询订单"时,用seatId更直接。
4.3 从选座完成到订单提交
用户点"确认选座"后,前端要做一个合理的动作序列:先调后端锁座接口,后端锁座成功后,返回一个订单预创建标识,然后前端再跳转到订单确认页,展示影片信息、场次、座位、总价,用户点"立即支付"时才真正创建支付单。
很多同学把这个顺序搞反了,直接在前端把所有数据拼好,就调支付接口,结果后续订单状态和座位状态对不上。合理的时序应该是:
- 前端提交选座请求(携带sessionId和seatId列表)
- 后端对每个座位执行锁座逻辑,成功则创建订单(状态为pending),返回订单号和总价
- 前端带着订单号跳到支付页,展示订单详情
- 用户点击支付后,前端调后端统一下单接口,后端返回支付参数,前端再调wx.requestPayment
为什么要多一次"锁座接口"?因为不能把锁座放在支付创建的时候。用户确认选座后,还可能在订单确认页面停留很久,如果等到支付才锁座,那支付前这段时间里座位可能被别人抢走。而用户一确认选座就锁座,配合8分钟超时释放,可以避免这个问题。
接口设计这块,建议采用RESTful风格,比如:
| 方法 | 路径 | 作用 |
|---|---|---|
| GET | /api/movies | 影片列表 |
| GET | /api/movies/:id | 影片详情 |
| GET | /api/movies/:id/sessions?date=2025-06-20 | 某影片某天的场次列表 |
| GET | /api/sessions/:id/seatmap | 某场次的座位图 |
| POST | /api/orders/lock | 锁座+创建订单 |
| POST | /api/orders/:orderNo/pay | 调用统一下单 |
| GET | /api/orders | 当前用户的订单列表 |
实际开发时,后端要多加一层JWT中间件用于校验用户身份,wx.login得到的code通过后端访问微信接口换取openid,然后签发token返回给小程序,后续请求把token放在header的Authorization字段里即可。
5. 调试实录:那些坑,你们十有八九也会踩
5.1 微信登录失败:配置文件与权限排查
热点词里有一条"微信小程序获取登录后的微信用户失败:wx1cb4398e1413dce7",看到这个前缀我就想起第一次联调登录接口时的场景。
wx.login本身不难,调用后拿到一个code,把code发给后端,后端用这个code加上appid和secret去微信的jscode2session接口换openid和session_key。但实际开发中,我遇到过好几次"获取用户信息失败"的情况,排查下来主要集中在这几个地方:
第一,AppID和secret配置不正确。如果你用的是测试号,或者复制demo代码时没有换成自己的AppID,后端换openid时就会报"appid mismatch"之类的错误。这个最简单,去微信公众平台的小程序后台,在"开发管理-开发设置"里找到AppID和AppSecret,自行检查。
第二,开发者工具里的"不校验合法域名"开关。在本地调试时,如果后端地址是http://localhost:3000,而你没有在微信后台配置request合法域名,工具会拦截请求,这时必须勾选"开发者工具-详情-本地设置-不校验合法域名、web-view(业务域名)、TLS版本以及HTTPS证书"。上线后必须把这个勾选去掉,并把域名配置为https地址。
第三,后端返回给前端的网络数据格式问题。微信小程序的wx.request返回的数据是string类型,需要JSON.parse一次。如果你用axios之类的库就没这个问题,但原生wx.request必须自己处理。
实际生产环境里,微信的AppSecret不可以下发到小程序端,必须保存在后端,否则任何人拿到secret就能伪造请求。这一点在代码评审时会重点看,务必注意。
5.2 合法域名与真机调试
真机调试和模拟器最大的差别在于网络请求和位置权限。模拟器默认允许localhost访问,但真机预览时必须保证后端接口域名已经加入小程序后台的request合法域名列表,并且该域名必须支持HTTPS。如果你只有一台本地开发机,可以用内网穿透工具(比如ngrok)把本地服务暴露成一个临时的https域名,在本地真机调试阶段非常方便,但注意这只是开发阶段的临时手段,正式部署一定要用正式备案域名。
另外,还有一个小细节:微信开发者工具的"真机调试"模式和"预览"模式不完全一样。真机调试代码运行在你电脑的开发工具上,配合手机调试;预览模式则是把代码上传到微信服务器生成一个预览版,手机直接跑这份代码。如果资源文件(比如图片)使用了相对路径,预览模式下可能加载不出来,平时开发时要在代码里统一用https绝对路径或直接用云存储链接。
5.3 时间戳、支付回调与状态同步
我在调支付流程的时候踩过一个大坑:微信支付回调里返回的时间戳是Unix毫秒级,而我数据库里的时间字段是datetime,在后端解析时把毫秒当成秒,导致订单的支付时间在界面上显示成一个奇怪的历史年份。排查了半天,最后发现是解析时间戳时忘记除以1000。这个也许看起来是低级错误,但框架不同、语言不同,坑的位置也完全不同,务必统一好时间戳的单位。
另一个坑是支付成功后的状态同步。前端wx.requestPayment成功回调后,你可能马上刷新页面,但此时后端可能还没收到微信的异步回调,订单状态可能还是"待支付"。正确做法是,前端支付成功后不要立即把订单改为已支付,而是向后端发起一次"查询订单状态"的请求,只有后端确认订单状态已经变成paid,才在前端展示"支付成功"。或者更保险的做法是,前端支付成功后页面进入一个定时轮询,每2秒查一次订单状态,等后端从回调中更新了状态,再跳转。这样可以避免用户看到"订单已支付但系统还显示待支付"的尴尬局面。
真机调试支付时,还需要注意:测试环境要用微信支付的沙箱支付参数,而且开发版小程序无法正常呼叫真实支付,必须要用"真机预览"或"体验版"才能唤起真实的微信支付弹窗,这一点我在第一次调试时困惑了很久。
6. 源码结构、运行步骤与文档写作建议
6.1 工程目录解析
整个项目的源码结构大致是这样的:
code复制movie-seat-miniprogram/
├── miniprogram/ # 微信小程序前端
│ ├── pages/
│ │ ├── index/ # 首页-影片列表
│ │ ├── movie-detail/ # 影片详情+场次选择
│ │ ├── seat-select/ # 选座页
│ │ ├── order-confirm/ # 订单确认页
│ │ ├── order-list/ # 订单列表
│ │ ├── order-detail/ # 订单详情
│ │ └── profile/ # 个人中心
│ ├── utils/
│ │ ├── request.js # wx.request封装
│ │ └── auth.js # 登录态管理
│ ├── app.js
│ ├── app.json
│ └── app.wxss
├── server/ # Node.js 后端
│ ├── routes/
│ │ ├── movie.js
│ │ ├── session.js
│ │ ├── seat.js
│ │ └── order.js
│ ├── models/ # Sequelize模型
│ ├── middlewares/ # JWT鉴权、错误处理
│ ├── config.js
│ └── app.js
├── docs/
│ ├── 需求说明.md
│ ├── 接口文档.md
│ ├── 数据库设计.md
│ └── 测试报告.md
└── README.md
如果你们用的是小程序云开发,server目录会被替换成cloudfunctions目录,里面是一个一个独立的云函数。但整体结构思想是一致的:前端只管页面交互,后端管业务逻辑和数据库。
6.2 本地运行调试全流程
拿到源码之后,我建议按这个顺序来运行:
- 初始化数据库:在MySQL中执行database.sql建表脚本,并修改server/config.js里的数据库连接信息。
- 启动后端服务:进入server目录,执行npm install安装依赖,然后npm run dev启动开发服务。
- 导入小程序前端:用微信开发者工具导入miniprogram目录,添加自己的AppID(测试号也能用,但部分API受限)。
- 配置本地调试:如果小程序端请求的是localhost,勾选"不校验合法域名"。
- 验证登录:模拟器里点一下授权登录,确认后端收到code并返回了token,前端把token存到storage。
- 验证核心流程:先在后端接口文档指引下,手动调用一下"新增影厅-新增影片-新增场次",或者直接在MySQL里插一条测试数据,再打开小程序首页看能否刷出影片列表,进入选座页看座位图是否正常渲染。
这里我建议在数据库里提前准备好一组演示数据,否则第一次运行小程序,首页空荡荡的,会让人误以为项目崩了。演示数据可以包括:3个影厅、2部影片、每个影片未来3天各安排2个场次。
6.3 文档怎么写才能拿高分/方便交接
这套项目标配的文档包括需求说明、接口文档、数据库设计说明和测试报告。很多同学写文档时喜欢把网上查到的概念直接抄进去,结果文档和实际代码对不上。我的建议是:文档跟着代码走,写你实际实现出来的东西。
需求说明不需要太长,但要包含项目背景、用户角色、功能清单、业务流程描述。接口文档要用标准格式,写明每个接口的请求方法、路径、请求参数、返回示例,这部分在写后端代码时顺手记下来就可以。数据库设计文档要有ER图和每张表的字段解释,重点是写出为什么这样设计,比如为什么seat表需要同时包含场次id和影厅id。测试报告要写清楚测试用例、预期结果、实际结果,以及发现了哪些Bug和修复方案。
调试类的经验,我建议单独开一节,把上文提到的微信登录问题、合法域名问题、支付回调时序问题、时间戳单位问题都记录下来。这不是给老师看的,而是给未来的自己看的。等过三个月你回来看这份代码的时候,有文档在手,重构和维护都轻松得多。
最后再分享一个我实际开发中的体会。整个项目做下来,最花时间的不是写页面,也不是调接口,而是维护"座位状态的一致性"。从座位图的展示,到用户选中,到锁定,到支付,到最终售出,每一步都涉及状态切换和异常回滚。你对座位状态的理解越透彻,写出来的代码就越稳定。如果你也想做同类项目,先把第3章和第4章这两块吃透,其余大多是套路化的CRUD。遇到问题可以顺着这个排查顺序走:先看数据,再看接口,最后看前端渲染,基本都能定位到问题。
