简介这套酒店管理系统开箱即可部署由后台管理、官方网站与微信小程序三大板块组成覆盖酒店预订、入住、餐饮等核心业务。系统内置实时房间动态、实时信息推送、订单管理、订餐管理、房间管理、小程序下单与订餐、微信在线支付及退款等功能能帮助运营者高效掌控客房状态并提升客户满意度。资源为ZIP压缩包体积约51.88MB包含1477个文件以JavaScript、WXSS、JSON、WXML等前后端与小程序的代码文件为主同时还有TypeScript、CSS、HTML与图片素材源码组织规范便于直接部署或二次开发。已有59人学习下载适合需要快速搭建酒店管理系统或研究多端联动的开发者。借助这套完整资源可以获得一整套可运行的后台、网站与小程序源码及配置理解微信支付、退款及消息推送的实现思路其模块化设计也可作为课程设计、毕业设计或商业项目的起点减少从零搭建的人力和时间成本。1. 酒店管理系统翻车点不在订单在房间状态同步很多酒店管理系统表面上都有订单、会员、报表实际上手才发现最痛的是房间状态不同步。前台刚把 301 改成「维修中」小程序上还在卖这间房客人下单成功到店却说满房矛盾全堆在总台。这套开箱即用的酒店管理系统把后台、网站、微信小程序三个端放在同一套数据上房间动态实时同步订单管理、订餐管理、实时信息推送都在一个链路里适合小酒店直接部署也适合拿来做毕业设计参考——它不是只做演示的壳子房态、订单、推送这些核心链路是通的。这套系统我给它的定位是「边跑边改的底座」把三大版块的数据模型和接口拆清楚你拿到之后先跑通默认流程再按自己酒店的分房规则改状态机、改消息模板。下面按「结构 → 实时推送 → 订单链路 → 踩坑 → 进阶」的顺序拆每段都会给能直接抄的代码和参数最后说清楚哪几个坑最容易让你改到一半想重写。建议先把第二章的数据模型看明白后面的所有功能都是长在它身上的。2. 先拆三大版块后台、网站、小程序各自该管什么这套系统之所以叫「开箱即用」不是因为它功能全而是三个端的分工给得清楚后台负责管网站负责展示和引流小程序负责让客人在手机上下单和查消息。很多项目三个端共用一个登录体系、接口也没切结果前台改个房价官网和小程序全被带崩。先讲清边界后续所有功能的实现才不会拧巴。所谓智慧酒店底子也就是这一张实时房态图和它后面的状态机没有边界清晰的数据模型智慧二字就是空话。2.1 后台管理系统管的是「状态」不是表格后台是整条链路的主控端。前台值班要看的不是一堆订单列表而是一张房态图——每个房间一个格子颜色代表状态点进去就是这间房今天的所有操作记录。我一般会把后台分成五个模块房态总览、订单中心、订餐管理、消息推送、基础配置房型、价格、班次、用户权限。日常操作频率最高的是房态总览所以它的查询接口必须扛压后面提到的实时推送就是从这里发出的。这个资源里的后台用了 Vue3 做界面配合 Spring Boot 这类后端表结构上房间表至少要落这些字段字段名类型建议说明room_idvarchar(20)房间号如 301一个酒店内唯一floorint楼层楼层是房态订阅的最小分组单位room_type_idint房型 ID关联 price_plan 表statusvarchar(20)六态之一IDLE/BOOKED/CHECKED_IN/CLEANING/MAINTENANCE/LOCKEDlock_expire_timedatetime锁房到期时间超时自动释放versionint乐观锁版本号防并发下单同一间房其中 last_status_time 这类时间字段很容易被忽略没有它前端没法做「这个状态是什么时候变的」的展示排班交接也没依据。表设计上还有一个容易踩的把价格直接存在房间表里。房价会随淡旺季浮动正确做法是建 price_plan 表和房型表关联房间表只存房型 ID。你后续改房价就只动计划表不用一间间更新房间。2.2 网站端预订入口和展示页的取舍网站端的定位是「给还没决定住哪的客人看的」所以它管的是信息展示和意向收集不是全流程预订。常见的做法是首页房型展示 在线预订表单 订单查询三个页面就够会员体系、优惠券这些重逻辑放在小程序和后台别在官网堆功能。网站端最容易翻车的地方是直接把后台接口暴露给官网。比如有些项目为了让官网显示实时房态把后台的 room/list 接口透传出去结果任何人都能通过接口看到你整个酒店的房间布局和管理信息。正确做法是单独建一组对外接口只返回「房型 ID、名称、可订数量、价格区间」不返回房间号、不返回状态明细。网站端还要注意 SEO 的事房型页 URL 用 /room/{roomTypeId} 这种静态化结构详情页里加上结构化数据标记。这块不是酒店系统的核心但你想让官网真正带来订单就得在部署时配好伪静态规则和 robots 文件不然搜索引擎只收录到首页。评论区也别开酒店官网的评论区一旦有人刷差评你删都删不过来这是做官网一个很实在的经验。2.3 微信小程序端为什么我建议用 uniapp 而不是原生微信小程序端承担的是高频操作查房、下单、支付、查订单、接收推送。如果你只想发微信小程序原生是没问题的但考虑后续可能出支付宝小程序或抖音小程序这个资源里用 uniapp 是更稳的思路——同一套 Vue 语法改改条件编译就能多端发布。小程序端的核心其实就两件事请求封装和会话保持。微信小程序的 wx.request 默认不会自动带 cookie所以登录态的维护要靠 token 手动注入 header。下面是我习惯用的请求封装在这个资源里同样适用// utils/request.js const BASE_URL https://api.example.com // 改成你自己的网关地址 export function request(path, options {}) { const token uni.getStorageSync(token) || return new Promise((resolve, reject) { uni.request({ url: BASE_URL path, method: options.method || GET, data: options.data || {}, header: { Content-Type: application/json, Authorization: Bearer token }, success: (res) { if (res.data.code 401) { // token 失效先尝试刷新刷新失败再跳登录页 uni.removeStorageSync(token) uni.reLaunch({ url: /pages/login/index }) reject(res.data) return } if (res.data.code 0) { resolve(res.data.data) } else { uni.showToast({ title: res.data.msg || 请求失败, icon: none }) reject(res.data) } }, fail: (err) { uni.showToast({ title: 网络异常请稍后重试, icon: none }) reject(err) } }) }) }这段封装的逻辑要点有三个。第一所有请求统一走一个函数后续加签名、加埋点只改一处第二401 处理放在这一层避免每个页面重复写 token 过期的跳转第三后端返回的统一结构是 { code, msg, data }code 为 0 表示成功这样前端不用针对每个接口单独判断。如果你后端返回结构不是这样记得先统一后端响应体再动前端——我在项目里见过前后端各写一套判断导致线上 bug 的改起来比想象中费劲。除了普通请求还要处理 token 有效期的问题。小程序的 token 不建议用长期有效方式而是每次登录换取 access_token2 小时和 refresh_token7 天在 401 时用 refresh_token 静默续期。上面封装里 401 直接跳登录对真正投入使用来说太粗暴改进方向是加一个 pendingQueue在刷新 token 期间把失败请求缓存起来刷新成功后再重放。这个机制放进去以后客人长时间挂着小程序到期后不会突然被踢回登录页体验会顺很多。小程序还有一个容易忽视的坑是域名白名单。开发工具里不校验合法域名能跑通但真机预览和发布时必须把接口域名加到小程序后台的 request 合法域名里而且必须是 HTTPS。很多第一次做小程序的人在这一步卡一天建议在部署阶段就把域名和证书准备好别等到要提审了才去办。3. 实时房间动态与实时信息推送状态机配合 WebSocket 才不漏消息实时是这套系统的核心卖点但「实时」这事做好不容易。常见的错误做法是前端定时 3 秒轮询一次房态接口房间少的时候没问题房间一多或者值班高峰期数据库就被查爆了。更关键的是轮询会有延迟窗口A 客人刚在前台下单锁房B 客人那端还在轮询周期内看到「可订」照样会产生超卖。3.1 先定房态状态机每个状态变更都要有「出处」实时推送的基础不是消息通道而是房间状态机。没有明确状态机推送出去的每条消息都可能把前端搞乱。我给这套系统定的状态是六态空闲IDLE、已预订BOOKED、已入住CHECKED_IN、清洁中CLEANING、维修中MAINTENANCE、锁定LOCKED。其中锁定是运营锁房比如预留房和内部用房它和已预订的区别是不产生订单。状态变更不是任意跳的。已入住不能直接变空闲必须经过清洁中已预订不能直接变维修中得先取消订单锁定房间可以转到空闲或维修中。这些规则如果散落在后端代码的 if else 里早晚出漏洞。我习惯把状态机做在数据库或 Java 枚举里让非法流转直接抛异常public enum RoomStatus { IDLE, BOOKED, CHECKED_IN, CLEANING, MAINTENANCE, LOCKED; private static final MapRoomStatus, SetRoomStatus TRANSITIONS new EnumMap(RoomStatus.class); static { TRANSITIONS.put(IDLE, EnumSet.of(BOOKED, LOCKED, MAINTENANCE)); TRANSITIONS.put(BOOKED, EnumSet.of(CHECKED_IN, IDLE, LOCKED)); TRANSITIONS.put(CHECKED_IN, EnumSet.of(CLEANING, IDLE)); TRANSITIONS.put(CLEANING, EnumSet.of(IDLE, MAINTENANCE)); TRANSITIONS.put(MAINTENANCE, EnumSet.of(IDLE)); TRANSITIONS.put(LOCKED, EnumSet.of(IDLE, MAINTENANCE)); } public boolean canTransitionTo(RoomStatus target) { return TRANSITIONS.get(this).contains(target); } }使用时在 Service 层先调 canTransitionTo 校验不通过就直接抛业务异常同时在状态表里记录「谁、在什么时间、从什么状态改到什么状态」。别小看这条变更记录后面排查问题全靠它。参数上要注意 TRANSITIONS 的配置要跟着实际排房规则走比如有的酒店维修中的房间也允许被预订那 MAINTENANCE 就要加一条到 BOOKED 的边不能照抄。这里没有对错只有和实际运营规则一致。3.2 推送通道怎么选WebSocket 是平均成本最低的方案实时推送有短轮询、长轮询、SSE、WebSocket 几种方案。酒店这个场景房态变更和订单通知都是低频事件但要求秒级送达WebSocket 比轮询省数据库压力比 SSE 好在双工——客人订餐或者前台备注消息也能从客户端推给服务端。如果你团队是 Python 栈用 django-channels 也能实现同样的事情这套系统的推送模型是通用的。服务端的关键不是「发消息」而是「维护连接」和「决定把消息推给谁」。每个 WebSocket 连接建立时要绑定一个业务身份。我的做法是给每个连接传一个 subscribe 参数比如房态订阅传 roomId 或 floor订单推送传 userId服务端把它和 WebSocketSession 存到 ConcurrentHashMap# 连接示例前台订阅 2 楼层房态 ws://api.example.com/ws?scopefloorscopeId2tokenxxx// 简化版推送服务 public class RoomStatusPushService { private final ConcurrentHashMapString, CopyOnWriteArrayListWebSocketSession subscribers new ConcurrentHashMap(); public void subscribe(String key, WebSocketSession session) { subscribers.computeIfAbsent(key, k - new CopyOnWriteArrayList()).add(session); } public void publish(String key, RoomStatusMessage message) { ListWebSocketSession sessions subscribers.get(key); if (sessions null) return; for (WebSocketSession session : sessions) { if (session.isOpen()) { session.sendMessage(new TextMessage(JSON.toJSONString(message))); } else { sessions.remove(session); } } } // 房态变更后调用同一楼层和全楼的订阅都会收到 public void onRoomStatusChanged(Room room, RoomStatus oldStatus) { RoomStatusMessage msg new RoomStatusMessage(room.getId(), oldStatus, room.getStatus(), System.currentTimeMillis()); publish(room: room.getId(), msg); publish(floor: room.getFloor(), msg); } }这里参数要解释清楚key 用「scope:scopeId」的方式组织粒度可以细到单间房也可以粗到整楼。前台值班室订阅 floor: 前缀客人端订阅 room: 前缀这样同一间房的状态变化前台大屏和客人小程序能同时收到。CopyOnWriteArrayList 是给并发遍历用的连接多了以后要换成带读写锁的容器不然 WebSocket 断线重连频繁时会有并发问题。前端收到推送后千万别整页 reload。房态大屏上一个房间变色只需要改那一个格子的背景色和文案如果直接 setData 整个房间列表几千间房会发生明显闪烁。另外要相信服务端的 sequence前端本地维护 lastSeq收到消息时先判断 seq 是否大于 lastSeq小于等于的丢弃。这能解决推送重试和 outbox 补拉带来的顺序重复问题。别忘了加心跳机制服务端每 30 秒 ping 一次客户端回 pong连续 3 次没收到就判定连接失效、触发重连小程序切后台再回前台时要重新做一次 WebSocket 状态检查这是小程序最容易丢推送的场景。3.3 消息可靠性推送发出去了怎么确认对方收到WebSocket 只负责把消息传出去不保证对方一定处理成功。酒店场景里「房间状态变了」这种事不能靠运气我的做法是给每条推送消息带一个全局递增的 sequence前端收到后回一个 ack。如果服务端发现某个连接超过 10 秒没 ack就把这条消息落库到 message_outbox 表等前端重连后拉取补发。这个「推送 ack outbox 补拉」三层结构是这套系统里实时功能最值得抄的部分。很多项目只做了 WebSocket 就往上线走高峰期丢几条房态更新客诉一来就说不清是谁的锅。把 outbox 表建好以后值班员在前台看到的每一条异常房态都能追到推送记录处理投诉时有据可查。4. 订单与订餐从下单到入账的完整状态流转订单是酒店系统的钱袋子连带订餐一起必须经得起对账。很多系统把订单做成「一张表 几个状态」就完事等到退房结算、押金、加收、退款混在一起时才发现根本算不清。这章把订单链路和订餐链路拆开讲重点在状态流转和并发控制。4.1 订单主链路锁房、下单、支付、入住、退房五个节点酒店订单和电商订单最大的区别是它绑定物理房间所以订单状态必须和房间状态联动。这条链路上的节点我建议固定为待支付PENDING→ 已支付PAID→ 已入住CHECKED_IN→ 已退房CHECKED_OUT另外有已取消CANCELLED和退款中REFUNDING两个异常态。订单状态触发动作房间状态联动PENDING用户下单IDLE - BOOKED带 15 分钟锁PAID支付回调成功BOOKED 保持CHECKED_IN前台登记入住BOOKED - CHECKED_INCHECKED_OUT退房结算CHECKED_IN - CLEANINGCANCELLED手动/定时取消BOOKED - IDLE最容易出问题的是待支付到已支付这一段。客人选了房、下了单但没付款房间要不要锁我的做法是下单即锁房锁定期 15 分钟超时自动释放并取消订单。这样做的好处是不会出现「两个人都能支付同一间房」的尴尬代价是前台要有释放锁定的手动功能客人电话里说不付了前台敢紧解锁换个卖法。锁房是要防并发的不能只靠代码里 if (room.status IDLE) 判断。常见的做法是给房间表加乐观锁字段 version更新时带上 where version ?影响行数为 0 说明被改过直接提示「房间已被预订」。下面是一个简化示例-- 锁房操作乐观锁防止并发下单同一间房 UPDATE room SET status BOOKED, version version 1, lock_expire_time DATE_ADD(NOW(), INTERVAL 15 MINUTE) WHERE room_id #{roomId} AND status IDLE AND version #{oldVersion}这条 SQL 最关键的是 where 条件里同时带 status 和 version两个条件都满足才会更新成功。返回的影响行数由 ORM 框架拿回来看等于 1 就是锁成功了等于 0 就要提示客人重新选房。注意 lock_expire_time 是个容易被忽略的字段定时任务每 5 分钟扫一次把过期订单置为 CANCELLED、房间置回 IDLE不然锁死的房永远放不出来。退房结算的时候房费、押金、加收、退款会搅在一起。这里我建议按「先算可退押金再算房费最后算加收」的顺序生成结算单每一笔都留来源订单号和操作人。别在一笔交易里扣来扣去对账的时候会想骂人。4.2 订餐管理把餐品订单做成主订单的子单而不是第二套系统订餐在酒店管理系统里经常被做成独立的「点餐模块」其实它应该挂在酒店订单下面。原因很简单住店客人订餐要送到房间金额常常挂房账、退房时一起结如果订餐订单和房费订单分两套表到最后账就平不上。这个资源里的做法是订餐子单挂在主订单下存 order_id 关联餐品项存到 order_item 表每项包含餐品 ID、数量、单价、制作状态。制作状态在厨房端流转已下单 → 制作中 → 已出餐 → 已送达。如果客人是到店散客点餐就为他生成一个独立的 quick_order不挂房间。订餐的并发难点在库存比如「今日例汤限量 10 份」两个客人同时点最后一份怎么办。和锁房一个思路用乐观锁扣库存// 扣减餐品库存stock 0 才允许扣 int rows dishMapper.deductStock(dishId, 1, 0); if (rows 0) { throw new BizException(该餐品已售罄); }对应的 SQL 大概是 update dish set stock stock - 1 where id ? and stock 0。这里把 stock 0 直接放在 where 里数据库层面的行锁会保证两个并发请求只有一个成功代码里不需要再加 synchronized。订餐消息推送也复用上一章的通道只是订阅 key 换成 table: 或者 room:。厨房大屏订餐来单用的是和房态一样的推送链路没必要再单独写一套即时通信逻辑。4.3 订单对账每天凌晨跑一次日结别靠人工订单和订餐都走完后必须有一个日结对账任务。凌晨 2 点把昨天的订单汇总成「应收入账表」再和支付渠道的结算单对比差额落在差异表里。这笔差异表是酒店财务最需要的东西没有它钱错了只能一笔一笔翻流水。日结脚本建议用定时任务触发扫描前一天创建的订单按支付渠道、金额、状态聚合。日结任务我习惯分成两步跑。第一步 01:00 先做「订单校正」把状态停留在 PENDING 超过 15 分钟的单子关掉、把房间释放回 IDLE第二步 02:00 再跑「营收汇总」汇总昨日支付成功的订单。两步之间留一小时是为了让凌晨还在处理的订单有机会落定减少和渠道对账的差异。遇到状态还停在已支付但订单日期跨天的要先做订单跨天校正不然日结数据和前台报表永远对不上。这个坑我在后面避坑章节里会再提一次因为它是日结最容易翻车的地方。5. 避坑 / 常见问题 / 排查从部署到上线最容易踩的五个坑这套系统我从拿到到跑通一共踩了五类比较典型的坑拿出来按「现象 → 原因 → 解决」写清楚。有些坑不是代码 bug而是数据模型和部署习惯的问题遇到时别急着重构。5.1 小程序请求全部走通真机预览却一片白现象开发工具里接口正常、页面展示正常改用真机预览后所有请求全部失败页面空白。原因小程序在开发工具里默认关闭了「合法域名校验」真机验证时会严格检查 wx.request 的域名必须是 HTTPS 且在 mp 后台配置过白名单。开发时用了 http 局域网 IP真机没法访问内网域名又不合法。解决联调阶段用「开发版小程序 不校验合法域名」跑内网需要真机预览时把后端接口部署到 HTTPS 域名并加到 request 合法域名。如果只是临时演示可以在小程序后台开启「开发环境不校验请求域名」但要记住正式版发布前关掉。踩过这坑以后我都是先把域名和证书准备好再动小程序代码。5.2 房态明明改了客人端却一直显示旧状态现象前台下单锁房后台大屏正确显示已预订但客人小程序和网站端仍然看到空闲并重新下单成功。原因订单服务改库后直接 return 了成功结果没有触发实时推送客人端拿到旧的房态快照后前端本地缓存没失效。问题出在「数据更新」和「消息推送」没有在同一个事务里保障。解决把房态变更和推送做成同一个事务的边界或者退一步用事务后事件事务提交成功后再调推送方法。前端收到推送后也要对比本地 sequence比当前大的才更新。从那以后我每次改房态代码都会先问自己一句「这行 update 后面有没有跟着 publish」。5.3 未支付订单把房间锁死了放不出来现象有客人下单没付款15 分钟过去房间还是已预订状态前台手动也解不开只能找程序员改库。原因订单表有状态但房间表的 lock_expire_time 没有定时任务清理或者是锁房和 order 创建的先后顺序写反了——订单没建成功房间已经锁上事务回滚漏了房间状态。解决启动一个定时任务每 3~5 分钟扫描所有 lock_expire_time 小于当前时间的房间把状态改回 IDLE同步取消对应 PENDING 订单同时检查锁房逻辑必须保证「创建订单 锁房」在同一个事务里任何一步失败都要回滚房间状态。后台再加一个「手动解锁」按钮值班员能自救就别走工单。5.4 WebSocket 连接越来越多CPU 被打满现象系统跑了几天后后台 WebSocket 连接数只增不减服务器负载飙升房态推送延迟明显。原因前端页面切换或小程序切后台时没有主动关闭旧连接服务端也没做空闲检测一堆死连接占着内存心跳只在服务端发、客户端不回积累了垃圾连接。解决服务端启动空闲超时清理任务超过 60 秒没收到任何消息的连接直接关闭小程序端在 onHide 时主动 closeonShow 时重新连接。心跳要双向客户端要回 pong服务端在 3 次没收到 pong 就把连接踢掉。配置上可以把 ping 间隔调到 30 秒超时次数设为 3 次这两个参数在小并发时看不出区别连接数上千后就是生与死的差别。5.5 日结对账差异全挤在「支付成功」状态现象日结跑完对账差异表里大量记录停在前一天「已支付」状态财务说不清楚钱到底进没进账。原因支付回调通知延迟或丢失订单状态没有及时更新日结脚本统计的是订单创建时间而不是支付完成时间跨天订单被算到了前一天。解决支付回调要做补偿轮询每 10 分钟扫一次 PENDING 状态且超过 5 分钟的订单主动查支付渠道结果并更新状态日结脚本把统计口径改到「支付成功时间 昨天」新老口径切换的过渡期要留两天让跨天数据自然落账。这个坑纯属统计口径问题改代码容易和财务对齐口径才是关键。从那以后我接任何系统日结都先把「统计哪个时间字段」写进需求文档第一页。6. 进阶用法从单店版拆出连锁版的第一步这套系统默认是单店模型但实际场景里很多酒店是连锁或「一店多楼」的结构。想在不动三大版块的基础上把它升级成连锁可用不需要重写只需要在数据层加一层「门店归属」。具体拆法给房间表加 hotel_id 外键订单表加 hotel_id 冗余字段所有查询强制带 hotel_id 条件小程序端用户登录后绑定默认门店切换门店时重新拉取房态和订单。后台用户表加 role 与 data_scope 字段role 决定能做什么操作权限data_scope 决定能看哪些店的数据数据权限。这个拆分是连锁版的地基后端的 WebSocket 推送 key 也要改成 hotel:1:floor:2 这种带门店前缀的粒度不然不同门店之间会串消息。注意别在第一步就搞「分布式、多租户、分库分表」。单店跑顺之后数据量撑到几十万订单再考虑迁移提前上中间件只会把运维复杂度翻倍这是老话但每次都有项目在这上面翻车。再给一个能立刻用上的验证方法把这套系统部署完后模拟三端并发操作——后台把 302 改为维修中同时在小程序端刷新该房间看推送是否一秒钟内让小程序房态变红再用两个微信号同时订同一间房看乐观锁是不是只放行一个。这条验证路径 10 分钟就能跑完跑完了你对这个系统的实时性和并发控制就有数了。我自己第一次部署完这套系统时以为把三端跑通就算收工结果前台一个「怎么把订单跨楼转给另一家店」的需求把我问住了才回头把 hotel_id 和数据权限补上。从那以后我每次搭酒店系统都先画一张「数据归属边界」图再谈功能和界面。希望这个习惯也能帮到你少走我走过的弯路。本文还有配套的精品资源点击获取