☰
微信小程序校园班车查询与座位预约系统设计解析
2026/10/1 12:43:08 网站建设 项目流程

每年毕设开题季,“微信小程序”这几个字能撑起半个计算机学院的题目清单。但同样是做小程序,有人交出来就是“换皮商城”,有人却能靠一个题目把并发控制、状态机、请求缓存全部讲清楚。今天想复盘一个我反复打磨过的方向——基于微信小程序的校园班车时刻表查询与座位预约系统。表面看它只是把一张校园班车时刻表搬进手机,真正做下去你会发现,座位预约背后的锁、状态流转和超时释放,才是这个项目的灵魂,也正好是答辩时最能拉开档次的内容。

这个系统解决的是校园通勤里一个非常具体的矛盾:高峰时段校车运力有限,学生只能靠运气排队;发车间隔不透明,到了站点才发现上一班刚走;好不容易挤上车,又发现没座位,要站十几分钟。有了小程序之后,学生能提前看到当天所有班次、每个班次剩余座位,自己选座预约,到点直接核销上车。司机和管理员也能从后台提前知道这趟车大概有多少人,需不需要加开一班。适合谁参考?计算机专业毕业生、想在小程序里做预约类系统的开发者,甚至负责后勤管理的老师,都可以拿它当蓝本——只要目标是把“查询+预约”这条链路做扎实。

1. 为什么校园班车需要一个专门的预约小程序

很多同学看到“校园班车”四个字,第一反应是:这不就是把一张Excel时刻表做进小程序吗?实际上真不是。你只要在早八前站到校车站看过一次,就会明白问题远不止“时刻表不清晰”这么简单。

1.1 早八高峰的真实痛点:看起来够坐,实际永远不够坐

我在做需求调研那几天,蹲了三个早高峰和一个晚自习下课高峰,观察到的现象很直接:早上7:40到8:10是最大高峰,每条线路基本趟趟满员,后排站着七八个人;晚上9:30之后,末班车时间不固定,很多同学在风里等了二十分钟才知道“最后一班已经走了”。司机师傅给我的反馈更有意思,他说最怕的不是路堵,是高峰期不知道后面还有多少人要来,没办法判断要不要申请加开一辆加班车。站点排队的人也只能干等,没有任何一个渠道能看到“这趟还有几个座”。

这些痛点落到系统需求上,就是三件事:第一,把每天每个班次的准确时刻表数字化;第二,把每个班次的实时余座亮出来;第三,允许学生提前预约座位,减少现场不确定性。注意,这里并没有“要一辆车的实时GPS轨迹”这种需求,那是另一个量级的工程,后边我会专门讲为什么不做。

1.2 需求收集不能只抄模板:我实际调研了什么

现在网上随便一搜就有大量“校园巴士管理系统”需求文档,这些文档结构化程度很高,但最大的问题是没跟真正坐车的人聊过。我这次调研只做了三件事:蹲点发车点,记录每趟高峰班车的实际上座人数,比如周五下午那趟车经常超员,周日上午的班次却空一半,数据波动非常大;找司机师傅聊,问他们管理上最缺什么信息,答案是“下一波还有多少人”;在班级群发了一个只能填三项的问卷,问大家坐校车最烦什么。收回来的七成回答集中在“到了才发现没座位”和“不知道下一班还要等多久”。

这三件事交叉下来,核心需求排序就很清楚了:余座信息加预约能力,优先级大于路线站点详情,大于车辆实时位置。很多毕设项目习惯把地图轨迹做成亮点,但在这个题目里,做轨迹既增加前端复杂度,数据源的准确性也很难保证,反而把真正的核心链路带偏了。所以我的结论是:先做时刻表查询,再做座位预约,地图轨迹可以作为后期扩展方向而不是首版需求。

1.3 功能边界的取舍:哪些功能坚决不做

一个毕设项目如果什么都想做,通常什么都做不深。我当时列了一个“不做清单”:不做在线支付,因为校园接驳车多数是免费或刷卡,支付会引入退款、风控这些无关因素;不做用户社区和失物招领板块,那跟“预约”核心链路毫无关系;不做车辆实时轨迹图,因为车辆GPS定位涉及硬件设备,不是一个前端小程序能凭空实现的;不做聊天客服,顶多放一个后勤值班电话。最后保留的学生端功能是:查看线路班次、按日期查看当天时刻表、余座展示、座位选择预约、取消预约、我的预约记录、订阅消息提醒。管理端功能是:线路维护、班次生成、预约数据列表、二维码核销。把主线做透,比堆一堆花哨页面有价值得多。

2. 技术选型与整体架构:原生小程序还是uni-app

这个题目最热门的技术路线无非两条:原生微信小程序开发,或者uni-app跨端开发。我在正式动手前也犹豫过,毕竟uni-app能顺带出App版本,听起来很划算。但把毕设这个特殊场景摆进去,结论其实很明确。

2.1 原生开发在毕设场景下的优势

先看对比:

对比维度原生微信小程序uni-app
开发语言JavaScript + WXML + WXSSVue语法,需要编译层转换
调试效率微信开发者工具直接热更新需要HBuilderX编译到小程序端
问题排查报错定位到原始代码报错链多一层编译映射,难定位
跨端能力仅微信可发布到App、H5、支付宝小程序
答辩可讲内容原生组件、小程序生命周期、分包多端适配、Vue响应式原理

这张表最关键的一行是“答辩可讲内容”。原生开发能让你直接面对微信小程序自己的生命周期、缓存机制、导航栏适配,这些都是考官熟悉且会追问的。uni-app确实跨端,但一个毕设项目通常只需要跑在微信里,那“跨端”这个优点就没有用武之地,反而要在答辩时解释“为什么Vue代码会有些微信里才出现的问题”,徒增风险。另外从审核角度讲,原生版本在提审、发布、体验版测试时的行为最可控,不容易因为编译层差异出现“工具里正常、真机上样式错乱”的情况。

2.2 云开发还是自建后端:两条路线怎么选

后端方案是另一个必须提前定的问题。微信云开发(云函数加云数据库)现在很成熟,可以免去服务器部署,特别适合时间紧的同学。但我个人更推荐计算机相关专业的学生选择自建后端,典型组合是Spring Boot加MySQL,原因是数据库表设计、事务回滚、接口鉴权这些内容,在答辩里全部是真考点。我建议两条路线的判断标准如下:如果你对后端不熟,目标只是顺利毕业,那选云开发,把云函数写好,数据库集合设计合理,一样能过;如果你希望在答辩时拿出有真实业务深度的系统,或者打算把这套代码当作品集找实习,那就用Spring Boot加MySQL,哪怕慢一点也值得。

云开发的隐藏成本也要提前知道:云函数冷启动,第一次调用有时会让你等两三秒,体验版高峰期可能被平台限流;免费额度够用,但数据库读写次数、云函数调用次数在开学高峰那几天有可能超。自建后端就没有这些平台限制,只要服务器带宽够,处理三五万用户的学校场景绰绰有余,而且你能完全掌控事务和锁。

2.3 整体数据流与部署形态

我最终采用的架构很朴素:原生微信小程序前端,请求到Spring Boot后端API,后端连MySQL数据库,部署在一台2核4G的云服务器上。用户登录流程是:小程序端调用wx.login拿到code,传给后端,后端再拿code换openid,生成一个自定义登录态token返回给小程序,后续请求都在header里带这个token。管理员账号单独建数据库记录,不做微信授权登录,这样后台权限边界清晰,答辩也更方便演示。整个链路不复杂,但每一环都有明确分工:小程序管页面交互,后端管业务逻辑和数据一致性,数据库管持久化。这也是我建议毕设保持的状态,过度设计反而会让项目失控。

3. 数据库设计:座位与班次的数据关系才是核心

这个项目里最不能拍脑袋设计的就是数据库。线路、班次、预约记录这三张表之间的关系如果没理清,后面写查询和预约逻辑时每写一步都会别扭。

3.1 核心表结构:线路、班次、预约记录

我设计的核心表如下,这里先给出基础版本,去掉了一些冗余字段,方便大家理解主线。

-- 班车线路表 CREATE TABLE bus_line ( id BIGINT PRIMARY KEY AUTO_INCREMENT, line_name VARCHAR(50) NOT NULL COMMENT '线路名称,如A线', departure_station VARCHAR(50) NOT NULL COMMENT '起点站', destination_station VARCHAR(50) NOT NULL COMMENT '终点站', start_time VARCHAR(10) NOT NULL COMMENT '首班车时间', end_time VARCHAR(10) NOT NULL COMMENT '末班车时间', interval_minutes INT NOT NULL COMMENT '常规发车间隔', status TINYINT NOT NULL DEFAULT 1 COMMENT '1启用 0停用', create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ); -- 班次表:某个线路在具体日期的具体发车时间 CREATE TABLE bus_schedule ( id BIGINT PRIMARY KEY AUTO_INCREMENT, line_id BIGINT NOT NULL COMMENT '所属线路', banji_date DATE NOT NULL COMMENT '发车日期', departure_time VARCHAR(10) NOT NULL COMMENT '发车时间', capacity INT NOT NULL DEFAULT 45 COMMENT '座位总数', reserved_count INT NOT NULL DEFAULT 0 COMMENT '已预约人数', status TINYINT NOT NULL DEFAULT 0 COMMENT '0未发车 1已发车 2已取消', UNIQUE KEY uk_line_date_time (line_id, banji_date, departure_time), KEY idx_date_status (banji_date, status) ); -- 预约记录表 CREATE TABLE reservation ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL COMMENT '用户ID', schedule_id BIGINT NOT NULL COMMENT '班次ID', seat_row TINYINT NOT NULL COMMENT '座位排号', seat_col TINYINT NOT NULL COMMENT '座位列号', status TINYINT NOT NULL DEFAULT 0 COMMENT '0已预约 1已取消 2已过期 3已核销', reserve_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, cancel_time DATETIME COMMENT '取消时间', checkin_time DATETIME COMMENT '核销时间', UNIQUE KEY uk_schedule_seat (schedule_id, seat_row, seat_col), KEY idx_user_schedule (user_id, schedule_id) );

这里最要紧的设计决策是:bus_schedule 存的是“具体日期加发车时间”的班次实例,而不是只存一套周期模板。如果你只存模板,那么“本周五下午4点这趟车已满”这种状态就无处安放,预约记录也没法挂靠到唯一的具体班次。虽然需要每天晚上或每周批量生成未来班次数据,但这笔工作量换来的是状态管理上的清爽,非常值得。

3.2 座位唯一索引是防超卖的第一道防线

座位预约最怕的就是“超卖”——明明只剩一个座,两个用户同时下单,都显示成功。在毕设项目里,防超卖的核心手段不用搞得太复杂,一个数据库唯一索引就足够了。reservation 表上的唯一约束 uk_schedule_seat (schedule_id, seat_row, seat_col),意味着同一个班次的同一个座位,数据库层面只允许存在一条记录,重复插入直接报唯一键冲突。当两个用户同时抢同一个座位时,只有一个INSERT成功,另一个会收到DuplicateKey错误,后端捕获这个异常后返回“座位已被选择”即可。

这种做法比悲观锁优雅,也比Redis分布式锁简单得多。因为座位本身就是天然的唯一资源,数据库唯一索引就是最强有力的约束。当然,还需要配合事务让库存字段准确回补,这一点我在第五章详细说。唯一索引的代价也很清楚:数据库需要维护索引,预约记录多的时候插入会略慢,但对校园班车这种一天几千条预约的场景来说完全可以忽略。答辩时能把“为什么唯一索引能防止超卖”讲清楚,就已经胜过了不少只会写增删改查的同学。

3.3 预约状态机:从已预约到已核销的四种状态

预约记录的状态我用一个TINYINT字段表示,状态码定义如下:

状态值状态名称触发条件说明
0已预约用户预约成功正常等待乘车
1已取消用户主动取消或超时取消座位需要释放
2已过期发车时间到达且未核销座位需要释放,并扣信用
3已核销上车时扫码确认完成履约

为什么不用字符串“waiting”“cancelled”这种写法?两个原因:数字状态在数据库中占空间小、索引效率高,而且在代码里用switch做状态迁移判断非常直观。更关键的是,状态机迁移必须由服务端控制,不能允许“已过期”直接被前端改成“已核销”。我在 Service 里写了一个迁移校验方法,任何更新操作必须带上当前状态和目标状态,不匹配就抛异常。这个设计在答辩时是明显的加分点,因为很多同学的状态字段就是从0改成1,压根不校验合法路径。

4. 时刻表查询模块:列表分页与缓存策略的落地实现

这个模块虽然是整个系统里最“peace”的部分,但做好它直接决定了用户体验。一个校园班车信息查询页面,如果每次进入都白屏三秒,用户马上就会关掉。实际开发中,我把缓存、分页、顶部导航适配都做了处理。

4.1 接口设计与页面结构

后端提供的核心查询接口如下:

  • GET /api/line/list——返回所有启用线路的基本信息,调用频率低,适合长缓存
  • GET /api/schedule/list?lineId=1&date=2025-05-20&page=1&size=10——返回某线路某日期的班次列表,包含每个班次已预约人数
  • GET /api/schedule/detail?scheduleId=123——返回班次详情和座位图,预约页面打开时调用

小程序端页面结构分三层:首页展示线路列表,点击线路进入班次列表页,顶部是一排横向滚动的日期Tab,默认选中当天;班次列表再往下是分页数据,每个班次卡片显示发车时间和余座数,点击某个班次进入详情页,底部弹出座位图,学生选座确认预约。这套页面结构和用户心智是匹配的:从线路到班次到座位,每一步递进都清晰。

4.2 时刻表缓存的正确姿势:缓存多久、怎么失效

班车时刻表有一个特点:一天之内极少变动,但余座情况每秒钟都在变。所以缓存策略必须按数据分层处理。我的做法是:

const CACHE_KEYS = { lineList: 'cache_line_list', scheduleList: (lineId, date, userId) => `cache_schedule_${lineId}_${date}_${userId}` }; function getCache(key) { const data = wx.getStorageSync(key); if (!data) return null; if (Date.now() > data.expireTime) { wx.removeStorageSync(key); return null; } return data.value; } function setCache(key, value, seconds) { wx.setStorageSync(key, { value, expireTime: Date.now() + seconds * 1000 }); }

线路列表这种基本不怎么变的,缓存600秒甚至一天都没问题;班次列表要带着余座信息展示,余座数据时效性要求高,所以我只缓存60秒,超过一分钟就重新拉取。特别注意一个坑:班次列表缓存key里必须带上当前用户ID,否则一个学生预约了座位,另一个学生打开小程序直接读到了上一个人的缓存,页面就会显示错误的余座数。很多同学写缓存不带用户维度,这种问题在联调时非常隐蔽,不容易发现。

座位详情页我选择了彻底不做缓存。因为用户进入这个页面的目的就是选座,如果展示的数据是几秒前的,两个用户可能同时看到同一个空座,很容易产生预期落差。为了这一点点性能去牺牲准确性,不划算。

4.3 列表触底加载与自定义导航栏高度

班次列表和预约记录列表都需要触底加载。小程序里用onReachBottom这个页面生命周期触发,配合分页参数实现:

Page({ data: { lists: [], page: 1, pageSize: 10, hasMore: true, loading: false }, async onReachBottom() { if (!this.data.hasMore || this.data.loading) return; this.setData({ loading: true }); const nextPage = this.data.page + 1; const res = await request('/api/schedule/list', { lineId: this.data.lineId, date: this.data.date, page: nextPage, size: this.data.pageSize }); this.setData({ lists: this.data.lists.concat(res.list), page: nextPage, hasMore: res.list.length === this.data.pageSize, loading: false }); } });

重点说下自定义顶部导航栏高度的问题。如果毕设直接用了系统默认导航栏,页面标题会很普通,但只要想自定义导航,就绕不开这个适配公式:

const menuBtn = wx.getMenuButtonBoundingClientRect(); const { statusBarHeight } = wx.getWindowInfo(); const navBarHeight = (menuBtn.top - statusBarHeight) * 2 + menuBtn.height;

原理很简单:微信胶囊按钮在顶部呈居中排列,从状态栏底部到胶囊按钮顶部的距离是固定的,这个间距乘以2再加上胶囊本身的高度,就是自定义导航栏应有的总高度。我在不同机型上测试过,用这个公式计算出来的导航栏位置,和系统导航基本一致。如果不做这个适配,iPhone SE和iPhone 15 Pro Max上同样的页面,导航栏要不顶到边上要不空一大截,视觉效果很糟。

5. 座位预约核心:从锁库存到超时释放的完整时序

进入这个章节,才是整个项目的重头戏。预约动作看起来只是插入一条预约记录,但要让它在高峰并发下不超卖、可取消、能正常回补库存,必须把时序和细节抠干净。

5.1 预约接口执行顺序:先锁库存,再抢座位

我推荐的后端执行逻辑如下,放在同一个事务里:

  1. 校验班次存在且状态为未发车;
  2. 校验当前时间距离发车时间至少30分钟,否则不允许预约;
  3. 执行原子更新,尝试给班次加库存:
UPDATE bus_schedule SET reserved_count = reserved_count + 1 WHERE id = #{scheduleId} AND reserved_count < capacity AND status = 0;
  1. 如果上一步影响行数为0,说明班次已满或已发车,直接返回“该班次已满”或“已停止预约”;
  2. 如果上一步影响行数为1,继续插入预约记录,插入时如果命中唯一索引冲突,说明这个座位刚被别人抢走,需要回滚库存,再返回“座位已被选择”。

这个顺序为什么重要?核心原因是:先锁库存再抢座位,能保证“整个班次不会超卖”的强约束在最前面生效。反过来如果先插入预约记录,再更新库存,就会出现一种中间态:预约记录已经存在,但reserved_count还没来得及加一,此时另一个请求看到的是“余座还有”,实际座位可能已经被占,数据就不一致了。而且先插入后更新,失败回滚时要同时删预约记录和扣库存,逻辑更绕。先更新库存再插记录,失败时只需要把reserved_count减回去,清晰得多。

库存回滚操作要放在catch块里,并且必须和插入操作在同一个事务中。我用Spring的@Transactional注解包裹整个方法,任何一步抛异常,数据库自动回滚,不会出现“库存减了但座位没成功”或“座位成功但库存没变”的尴尬状态。

5.2 取消预约与回补库存的顺序问题

取消预约看似简单,实际上也有一个顺序陷阱。正确做法是:先更新reservation状态为已取消,再更新bus_schedule把reserved_count减一,两步同样放在一个事务里。原因很简单,如果反着来,先减库存,再改状态,用户连续点击两次取消按钮,第二个请求进来发现状态还是“已预约”,又执行了一次库存减一,库存就会越减越多,出现“余座数大于实际可用座位数”的脏数据。先改状态再加一层业务校验:只有状态为已预约的记录才允许执行取消逻辑,第二次点击时状态已经是已取消,直接返回“重复操作”。

另外,我加了一个限制:发车前30分钟不允许取消。这个时间门槛不是拍脑袋定的,是跟司机师傅确认过的——临近发车时座位基本定型,频繁的取消和重新分配会让他没法判断到底有多少人坐车。这个业务规则在答辩时可以顺势讲出一层“你们是怎么和真实用户确认业务边界的”,比单纯讲CRUD有说服力得多。

5.3 发车前过期未乘车:定时任务处理方案

总有人预约了座位但睡过头或临时改主意,人没到车上,座位却一直占着。如果不处理,余座数据会越来越失真。我的方案是用Spring Boot的@Scheduled定时任务,每分钟扫描一次:

@Scheduled(fixedDelay = 60 * 1000) @Transactional public void expireReservations() { List<Reservation> expiredList = reservationMapper.findExpired( LocalDate.now(), LocalTime.now().minusMinutes(10)); for (Reservation r : expiredList) { reservationMapper.updateStatus(r.getId(), 0, 2, LocalDateTime.now()); scheduleMapper.decreaseReserved(r.getScheduleId()); } }

这里有一个关键细节:过期判断不能只看“发车时间已经过了”,还要给乘客留出10分钟的核销宽限期。比如发车时间是8:00,那8:10之前依然允许核销,8:10一到还没核销的记录统一标记为已过期。每条过期的预约记录都需要单独回补一次库存,不能用一个简单的“reserved_count减去N”的SQL批量处理,因为不同预约记录可能对应不同班次。循环处理虽然慢,但每分钟几百条记录的范围,对数据库压力很小,胜在准确。这一步做完,整个系统的状态循环就闭环了:预约、取消、过期、核销,每个状态都有明确去向。

5.4 订阅消息提醒:一次性订阅的正确用法

预约完成后的消息提醒,用的是小程序的订阅消息能力。这里学生最常踩的坑是:以为用户点了“允许”就能无限发消息。实际上微信的一次性订阅规则是,用户每点击一次授权按钮,你才能给他发送一条模板消息。预约时收集到一次订阅,发送完“预约成功通知”就没了,之后想再发“发车前提醒”还需要用户再次授权。

所以我在预约成功页放了一个订阅授权面板,把“发车提醒”这个模板单独让用户再点一次授权,收集两条订阅额度,一条用于通知预约成功,一条用于发车前提醒。发送接口由后端调用,不能在小程序端直接发。模板消息的文案要写清楚“什么时间、哪个班次、还剩几分钟发车”,这样用户看到推送才会及时行动。这一个功能虽然小,但它在答辩演示中效果很好,能让老师看到你考虑了“用户会不会遗忘”这个真实问题。

6. 真机测试与收集反馈阶段最容易踩的坑

功能开发完以后,真正折磨人的是联调和体验阶段。我见过太多项目在开发者工具里跑得好好的,一上传体验版就凉了,问题基本都出在下面这几个环节。

6.1 预览码和体验版的区别:给导师试用选哪种

很多同学习惯点开发者工具里的“预览”按钮,把生成的二维码发给导师,结果导师打开没几分钟二维码就失效了。这是因为预览二维码是临时的,主要给开发者自己快速调试用。想收集几天的使用反馈,正确姿势是走“上传体验版”的流程:

对比维度预览二维码体验版
有效期短,临时可用相对稳定,可持续
成员管理不限制扫码人,但很快就过期需要在后台添加体验成员
用途本地快速验证给测试用户持续体验、收集反馈
上线要求无不要求正式发布,但必须配好域名

具体步骤是:在开发者工具点击“上传”,代码会变成一个开发版本;到微信公众平台的“版本管理”里,把这个版本设为体验版;然后在“成员管理”里把导师微信号添加为体验成员。导师之后扫码就能打开体验版,只要你不主动替换版本或删除项目,它就能一直供试用。收集到的反馈可以自然沉淀到后端日志和预约记录里,比如哪个班次真实乘坐人数和预约人数差距最大,这本身就是很宝贵的分析数据。

6.2 合法域名配置:开发者工具能跑、真机全挂

这个坑几乎是所有小程序毕设都会遇到的。在开发者工具里,你可以勾选“不校验合法域名”来绕过域名限制,局域网IP、localhost都能请求。但一旦到真机的体验版,这个勾选不会生效,所有wx.request都会因为域名不合法被微信直接拦截,页面会全部空白,只剩请求失败日志,导师看到就以为系统坏了。

正确做法是提前准备一个已备案的HTTPS域名,解析到后端服务器,然后在微信公众平台“开发管理-服务器域名”里把request合法域名加上。注意几个细节:域名必须是HTTPS,不能用IP直接配置;开发阶段如果要改接口路径,后台配置完域名后通常有几分钟生效延迟;还有一个小技巧是先把域名配好再上传体验版,免得到体验版环境里排查半天还是404。

注意:开发者工具里勾选“不校验合法域名”只能用于本地调试,体验版和正式版都不会生效。域名配置必须提前完成,否则真机测试阶段会全线崩溃。

6.3 请求封装与token过期跳转

小程序端所有的网络请求如果都靠wx.request裸请求,代码会非常散乱。我封装了一个统一请求函数,把基础URL、header、错误码处理都收敛到一处:

const request = (url, data = {}, method = 'GET') => { return new Promise((resolve, reject) => { wx.request({ url: BASE_URL + url, method, data, header: { 'Content-Type': 'application/json', Authorization: wx.getStorageSync('token') || '' }, success(res) { const { code, data, message } = res.data; if (code === 200) { resolve(data); } else if (code === 401) { wx.removeStorageSync('token'); wx.reLaunch({ url: '/pages/login/login' }); reject(new Error(message)); } else { reject(new Error(message)); } }, fail(err) { reject(err); } }); }); };

这个封装的收益不只体现在少写几十行代码,更重要的是统一了错误处理。当token过期时,任何页面发起请求都会得到401,然后统一跳回登录页。如果每个页面都自己调wx.request,就会有的页面跳登录、有的页面弹窗报错,体验参差不齐。对于“我的预约记录”这种进入时必须校验登录态的页面,统一跳转机制能让整个流程顺畅很多。

6.4 高峰期“全民抢座”的缓存击穿思路

校园班车的预约高峰很有特点,不是随机分布,而是集中在开课前半小时。比如早八前,整栋宿舍楼的学生同时打开小程序,如果每次查询都打到数据库,后端压力会瞬间拉满。我的缓解方案有两层:第一,把余座数量这种实时但变化不够剧烈的数据,在服务端做一层定时缓存,每个班次每5秒刷新一次,响应速度大幅提升,查询压力从每秒几十次降到每秒一次;第二,真正的写操作,也就是预约、取消,不做缓存,仍然走数据库事务,保证数据一致性。

如果学有余力,还可以在答辩扩展示介绍Redis方案:用Redis的INCR或DECR做预扣库存,写入MySQL作为持久化记录,Redis负责高性能,MySQL负责最终一致。但坦白讲,对毕设场景,第一步的定时缓存已经足够应对,先把一致性问题解决,再谈性能优化。

7. 答辩时值得讲的亮点和后续扩展方向

这个题目能讲的东西很多,但有的同学答辩时只会展示“点一下按钮,出现一个列表”,最后被导师问倒。提前把几个技术深水区的内容准备好,效果会完全不一样。

7.1 导师可能会这样问:你如何兜住这些技术细节

每年答辩,导师对“小程序+预约”类题目的提问都是有套路的。我整理几个最常见的问题和准备思路:

  • 问:两个学生同时抢最后一个座位,你的系统如何保证不超卖?回答要点:reservation表的(schedule_id, seat_row, seat_col)唯一索引是从数据库层拦截重复插入,同时我的预约事务先原子更新bus_schedule的reserved_count,只要更新行数不为1就返回满座,两个约束叠加,从源头杜绝超卖。
  • 问:你的状态字段为什么用数字而不是字符串?回答要点:数字状态值适合索引和查询,并且状态迁移都通过服务端校验,不会出现非法状态跳变。
  • 问:如果缓存里还有旧数据,用户看到的余座不准怎么办?回答要点:线路缓存长,班次列表缓存只有60秒,座位详情不做缓存,写操作永远走实时事务。
  • 问:用户预约后不来怎么办?回答要点:定时任务在发车之后加10分钟宽限期,超时标记为已过期并回补库存,同时可以在扩展里加信用机制,累计三次未乘车限制预约三天。

这些问题准备充分,答辩时回答起来就会像在讲自己的实际项目,而不是背稿子。导师最怕的是学生做完项目但完全不懂为什么这么做,这套内容正好能补齐“知其所以然”这一层。

7.2 加分扩展:蓝牙ibeacon自动核销、司机端、数据看板

如果时间有余力,这个题目还有几个自然延伸方向。第一个是司机端小程序,司机通过小程序或工作台扫码核销,比管理员手动看Excel更高效;第二个是蓝牙ibeacon核销,在车厢内放一个低功耗蓝牙设备,学生靠近时微信小程序自动感知并完成核销,可以顺带判断学生“确实上车了”而不只是“预约了”,这是当前弱定位场景下比较务实的技术路线;第三个是数据看板,统计每个班次预约数、实际核销数、常取消用户,辅助后勤判断要不要加开加班车。这些扩展不必全部完成,甚至在答辩时只作为“后续计划”提一下都行,但能展示你对真实业务的理解深度。

我个人带完这个项目最大的体会是:这类题目的出彩点根本不在页面多炫,而在于那些看不见的小细节——每一次取消,库存有没有准确回补;每一个过期的座位,定时任务有没有真正释放;每一次高峰并发,缓存和事务有没有配合好。把这些细节做扎实,你讲出来的就是一个有真实业务深度的系统,而不是又一张换皮的表单。准备开工的同学,建议先把小程序账号、名称、服务器HTTPS域名一次性配置好,这几个环境问题拖着,最后一定会在答辩前几天集中爆炸。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询