简介这套酒店管理系统PHP源码采用PHP与Access数据库构建面向PHP学习者、中小型酒店信息化人员及二次开发者。系统围绕接待、餐饮、采购、经理四个核心部门设计覆盖客户入住退房、房间状态管理、菜单点餐结算、采购库存监控、销售报表与考勤管理等场景模块划分清晰适合作为企业级Web应用的实战样本。压缩包共125个文件以71个php文件为主承载各业务模块逻辑辅以html、css、inc配置文件搭建界面与运行环境另有jpg、gif图像资源、log日志及db数据库文件整体仅549KB轻量易部署。目前已有3698人学习下载说明其具备较高参考热度。获取源码后可快速搭建运行环境梳理各部门业务流程也可根据自身需求调整数据库结构与功能界面用于课程设计、毕业设计或酒店管理原型验证。1. 酒店管理系统PHP源码从一张Excel房态表到可上手的预订闭环朋友开的小酒店前台上贴着交接班表房态靠Excel和口头确认旺季里经常出“房间订出去了系统里还显示空着”的乌龙。我把这套PHP写的酒店管理系统源码在他机器上跑起来后前台、收银、经理从同一个后台进去预订、入住、退房、结账全串成一条业务链房间查得到才让下单住进去自动锁房退房按房价和夜数算账报表页直接看入住率和流水。这套源码适合中小型酒店、民宿拿去直接演示或改造也适合学PHP后端的新手拿真实业务练手能直观看到状态流转、并发防重和权限控制这些概念在业务代码里怎么落地。2. 项目定位与整体结构从功能清单到数据库表设计2.1 核心功能地图房源、预订、入住、结算、权限这套系统不是一个泛泛的“增删改查后台”它按酒店日常运营拆成六个模块。房型与房间管理维护房型价格、房间号、房间状态预订管理负责录入散客和协议客户的预订单并自动筛选可订日期入住登记负责把预订转成实际入住也支持无预订直接开房收银结算负责退房时算房费、押金和额外消费并生成结算单会员管理维护会员等级和折扣系统管理管理操作员账号、角色权限和操作日志。我把这套结构理解成“一条业务主线加一圈支撑功能”。主线是从查房态开始到预订、到入住、到退房结算结束支撑功能是会员、房间维护、报表和权限。很多培训项目只做主线但这套源码把权限和日志也带上了这让它更像能真正交给酒店前台使用的后台而不是课程作业。关于房态源码里房间状态是显式字段包含空闲、入住、维修、脏房四种。脏房这个概念容易被忽略但它很实用客人退房后房间没有卫生问题也不能马上卖需要保洁完成后手动切回空闲。如果直接把状态切回空闲后续报表统计“可售房”时会虚高所以这个字段本身就承担了业务状态机的职责。第一次看代码的人建议先查一遍所有UPDATE room SET status ...的调用点能快速理解哪些操作会动房态。2.2 技术栈原生 PHP MySQL 现成前端框架的选型理由技术栈不花哨后端是原生 PHP 7.x没有用 Laravel 或 ThinkPHP 这类框架数据库是 MySQL 5.7 以上前端基于 Bootstrap 组件库和 jQuery表格展示直接套 DataTables。整套代码不依赖 Composer所有类文件都是手写 require_once 引入。这意味着只要机器上能跑 PHP基本不用装额外扩展就能把项目拉起来。为什么这样选我拆完代码的感受是这个项目面向“装个集成环境就能跑”的小系统不引入框架可以省掉一堆环境问题。原生 PHP 的入口就是一个 index.phpconfig.php 里写数据库连接普通虚拟主机也能直接跑。缺点也很明显没有框架的路由和ORM模块之间的加载顺序靠约定。所以我建议阅读时先从 config.php 和 common/function.php 入手别一上来就看业务模块。数据库层用的是 PDO 预处理不是mysql_*系列函数。这一点比很多老源码要现代SQL 绑定参数能防掉一部分注入风险也让代码在不同数据库之间的切换成本低一些。业务模块里也有少量直接拼 SQL 的地方但都集中在后台管理页且只拼白名单排序字段没有把用户输入原样拼进 SQL。总体安全性在“内部后台系统”这个档次是够用的。页面渲染方式上它没有做前后端分离是后端渲染模板表单提交后回跳列表页。这种模式在这个体量下反而简单不需要跨域不需要 Token 刷新前端只负责把用户操作转化成 GET/POST 请求。如果你看惯了 Vue 项目再回来看这种写法会觉得很复古但调试链路非常短出问题能直接定位到某个 PHP 文件对新手很友好。2.3 目录结构与表清单下载后先看哪几个文件把压缩包解开之后目录结构大概长这样hotel/ ├── index.php # 登录入口未登录跳转 login.php ├── config.php # 数据库、时区、调试开关 ├── install.sql # 建库建表脚本包含默认管理员 ├── common/ │ ├── db.php # PDO 单例封装所有模块共用 │ ├── auth.php # 登录态校验、角色权限判断 │ └── function.php # 公共函数跳转、格式化金额、日志 ├── templates/ # 页面模板按模块再分子目录 ├── modules/ │ ├── room/ # 房型、房间、房态管理 │ ├── book/ # 预订查询、新增、取消 │ ├── checkin/ # 入住登记、退房结算 │ ├── finance/ # 当日流水、月度报表 │ ├── member/ # 会员等级与积分 │ └── system/ # 用户、角色、操作日志 └── assets/ # CSS、JS、图片我强调一下阅读顺序先看 config.php因为它决定所有数据库连接再看 install.sql因为表结构读懂了业务代码就懂了一半。templates 和 modules 是分开的改页面样式时不要去 modules 里翻 HTML直接改 templates 对应文件即可。数据库表清单是理解业务的关键主要表如下表名作用核心字段room_type房型及门市价id, type_name, priceroom房间与当前状态id, room_no, type_id, statusbooking_order预订和入住订单id, order_no, room_id, check_in_date, check_out_date, statusmember会员档案与等级id, name, phone, levelsys_user后台操作员id, username, password, role_idsys_role角色与权限集合id, role_name, permissionsoperation_log操作审计日志id, operator_id, action, order_id, created_atinstall.sql 末尾通常有一个默认管理员账号第一次登录后必须改成自己的账号。默认密码千万不要留到生产环境这种源码一旦放到公网半小时内就会被扫描工具盯上。后面避坑部分我会专门说这个问题。3. 核心模块拆解一个订单从查询房态到入住的完整链路3.1 数据库表关系房间表、订单表、房态表如何联动酒店管理最容易犯的设计错误是把“房间状态”当成一个字段直接存在房间表里入住时改一下退房时再改回来。这样做在小规模下没问题但一旦要查“3月5号有几间空房”就只能靠程序去判断每笔订单和查询区间是否重叠很容易出错。这套源码用了更稳的思路房间表只存当前物理状态时间维度的占用情况全部由订单表提供。CREATE TABLE room_type ( id INT NOT NULL AUTO_INCREMENT, type_name VARCHAR(50) NOT NULL COMMENT 房型名, price DECIMAL(10,2) NOT NULL COMMENT 门市价, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE room ( id INT NOT NULL AUTO_INCREMENT, room_no VARCHAR(20) NOT NULL COMMENT 房间号, type_id INT NOT NULL COMMENT 关联room_type.id, status TINYINT NOT NULL DEFAULT 1 COMMENT 1空闲 2入住 3维修 4脏房, PRIMARY KEY (id), KEY idx_type (type_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE booking_order ( id INT NOT NULL AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL COMMENT 订单号, room_id INT DEFAULT NULL COMMENT 锁定房间可空表示暂未排房, guest_name VARCHAR(50) NOT NULL, phone VARCHAR(20) DEFAULT NULL, check_in_date DATE NOT NULL, check_out_date DATE NOT NULL, total_amount DECIMAL(10,2) NOT NULL DEFAULT 0.00, status TINYINT NOT NULL DEFAULT 1 COMMENT 1预订 2入住 3退房 4取消, PRIMARY KEY (id), KEY idx_room_date (room_id, check_in_date, check_out_date) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这段建表语句只看三处。第一room.status表达的是房间此刻是否可售和预订日期无关第二booking_order里存的是check_in_date和check_out_date一个订单在时间轴上占一段区间第三联合索引idx_room_date专门为“查某房间在某个日期段是否被占用”服务。订单和房间是 N:1 关系一个房间可以有多个订单但同一日期区间内只能有一个有效订单。真正判断房间占用不是去房间表看status而是查订单表里有没有与目标区间重叠的记录。这也解释了为什么很多新手第一次用这套系统时“明明房间显示空却订不进去”——他们改的是room.status但查询却基于订单表两边状态不一致。业务里务必以订单表为准。3.2 预订与查询可用房检测的 SQL 与状态机前端做预订时用户先选入住日期和离店日期系统列出这段时间内可用的房间。这个“可用”怎么算核心是一个 NOT IN 子查询把所有与目标区间重叠的订单房间排除掉。// modules/book/availability.php public function getAvailableRooms(string $start, string $end): array { $sql SELECT r.id, r.room_no, rt.type_name, rt.price FROM room r LEFT JOIN room_type rt ON r.type_id rt.id WHERE r.status 1 AND r.id NOT IN ( SELECT b.room_id FROM booking_order b WHERE b.status IN (1, 2) AND b.check_in_date ? AND b.check_out_date ? ) ORDER BY r.room_no; return $this-db-query($sql, [$end, $start])-fetchAll(); }两个参数别搞反第一个?传入$end第二个?传入$start。SQL 里b.check_in_date $end表示已有订单的入住日早于查询的离店日b.check_out_date $start表示已有订单的离店日晚于查询的入住日。两个条件同时成立说明两个日期区间有交集这个房间在当前查询区间内不可用。这里有个容易踩的坑参数顺序和 SQL 字段出现的顺序相反。如果按直觉传[$start, $end]查出来的结果会包含所有被占用的房间等于查询功能完全失效。我一般会在方法注释里写一句口诀“旧单入住日 新单离店日 且 旧单离店日 新单入住日”。这个口诀几乎所有排房系统都能套用。状态机方面订单用status控制生命周期1 预订、2 入住、3 退房、4 取消。可用房查询只统计status IN (1, 2)的订单因为预订未入住也占用房间已退房和已取消的订单不参与占用。这种状态设计能直接支持“预订转入住”和“取消释放房态”两种操作不需要额外建关房表。3.3 入住登记与结算事务处理与金额计算预订确认后客人到店要办理入住。这一步最容易并发出错两个前台同时给同一间房办入住如果只是先查状态再 update大概率会有一单覆盖另一单。这套源码在入住登记里用了事务加条件更新来兜底。// modules/checkin/checkin_service.php public function checkIn(int $orderId, int $operatorId): bool { $pdo $this-db-getPdo(); try { $pdo-beginTransaction(); // 联表更新同时锁房间和订单只有空闲房间才会被更新成功 $stmt $pdo-prepare( UPDATE room r JOIN booking_order b ON b.room_id r.id SET r.status 2, b.status 2 WHERE b.id ? AND r.status 1 ); $stmt-execute([$orderId]); if ($stmt-rowCount() ! 1) { throw new \RuntimeException(房间已被占用请重新选择); } $this-log-write($pdo, [ order_id $orderId, action checkin, operator_id $operatorId, ]); $pdo-commit(); return true; } catch (\Throwable $e) { $pdo-rollBack(); throw $e; } }这段代码逻辑拆成四步。第一步UPDATE room JOIN booking_order把房间和订单一起改条件是订单 id 匹配且房间状态为空闲第二步用rowCount() 1判断是否确实只有一条记录被改如果房间已经是入住状态这个条件更新会影响 0 行程序抛异常从根源上避免重复入住第三步写操作日志第四步提交事务任何一步失败都会回滚房间不会变成“订单改了但房间没改”的中间态。退房结算的金额计算比较直接但有一个隐藏细节。假设入住当天晚上入住、退房当天中午退房夜数怎么算常见做法是按自然日差也就是把日期字符串转成时间戳再求差值但更规范的方式是直接用DateTime::diff。$night (int)((new DateTime($checkOut))-diff(new DateTime($checkIn))-days); $totalAmount $roomPrice * $night $extraFee - $deposit;DateTime::diff返回的是日期差不管入住和退房的具体时刻只算日期边界。$extraFee是客人消费的迷你吧、加床等杂费$deposit是入住时收取的押金。这里要特别注意$deposit是押金不代表实收退房时如果押金大于应付还要生成一笔退还记录这部分代码在 finance 模块里处理不要在订单表里直接把金额改成负数。3.4 权限与日志后台菜单按角色渲染的实现后台除了房态管理还有会员、报表、系统设置这些敏感入口不能对所有操作员放开。这个源码的做法是给角色授予一组权限标识登录时把当前用户的权限码数组写进 session前端模板渲染时根据权限码决定显示哪些菜单。// common/auth.php function can(string $permission): bool { if (empty($_SESSION[permissions])) { return false; } return in_array($permission, $_SESSION[permissions], true); }!-- templates/header.php -- nav ?php if (can(room:view)): ? a hrefmodules/room/list.php房态管理/a ?php endif; ? ?php if (can(book:create)): ? a hrefmodules/book/create.php新建预订/a ?php endif; ? ?php if (can(finance:report)): ? a hrefmodules/finance/report.php营业报表/a ?php endif; ? /nav权限标识用的是“模块:动作”的字符串约定比如room:view、book:create、finance:report。这样做的好处是不需要维护一个巨大的菜单表权限配置页勾选时也是直接勾这些字符串。坏处是如果代码里写错一个冒号比如用了全角冒号权限就会静默失效页面不报错但菜单不显示。这种问题排查起来很花时间后面避坑部分我再细说。日志我额外说一句。每条关键操作都会写进operation_log表字段包括操作员、动作、关联订单和操作时间。二次开发时这个设计很有价值如果新功能出了问题可以靠日志反推是哪一步把数据改脏了。我给这套系统做改造时第一步就是确认新业务线的每一步都有日志没日志的后台功能出问题等于黑匣子。4. 本地部署与常见问题排查从配置环境到跑通账套4.1 环境要求与快速部署步骤这套 PHP 源码对运行环境要求不高PHP 7.2 及以上、MySQL 5.7 及以上即可。Windows 上可以用常见的一键集成环境包Mac 上可以用自带 Apache PHPLinux 服务器上装 Nginx 或 Apache 都行。我建议本地调试时直接开 PHP 内置服务器最省事# 在源码根目录下执行启动 PHP 内置 Web 服务器 php -S localhost:8080 # 浏览器访问 http://localhost:8080数据库方面先用命令行或数据库管理工具建一个空库然后导入 install.sqlmysql -uroot -p -e CREATE DATABASE hotel DEFAULT CHARSET utf8mb4; mysql -uroot -p hotel install.sql第一条命令创建名为 hotel 的数据库字符集用 utf8mb4第二条把 install.sql 里的建表语句和数据一次性导入。导入成功后去改 config.php 里的数据库账号密码再访问登录页用默认管理员账号进去。如果本机已经有 Web 环境把源码目录放到 Web 根目录下访问http://localhost/hotel也行。要注意不要用太老的浏览器打开后台前端表格组件用到了一些较新的 JavaScript 特性Chrome 或 Edge 更稳妥。启动后如果页面白屏先开 PHP 错误显示把app.debug设为 true再刷新看具体报错不要对着白屏猜。4.2 配置文件与环境变量config.php 里的参数说明config.php 是整个系统的配置中心所有模块启动时都会先加载它。结构是一个数组这里是简化版?php return [ db [ host 127.0.0.1, port 3306, name hotel, user root, password your_password, charset utf8mb4, ], app [ timezone Asia/Shanghai, debug false, url_prefix , ], ];db.host是数据库地址本地填 127.0.0.1远程服务器填内网 IPdb.name必须和导入 install.sql 的库名一致charset推荐 utf8mb4否则中文姓名和地址会变成问号。app.timezone影响 PHP 的date()输出酒店报表按天统计时区错误会导致“昨天”和“今天”的边界偏移。app.debug建议本地改成 true出错时打印详细信息上线后务必关掉否则数据库账号密码可能被抛在页面里。修改配置后如果页面没变化先确认自己改的是 config.php 而不是 config.php.example。很多模板项目会留一个.example文件改错文件导致一切看起来没生效这是新手最常见的低级问题。改完配置后重启一下 PHP 内置服务器配置才会重新加载。4.3 避坑记录5个新手最常见的翻车点我在拆这个项目时把容易踩的坑按“现象-原因-解决”整理成下面几条都是实际发生过的场景。现象一预订时选的入住日期列表里查不到任何可用房间。原因可用房 SQL 的NOT IN子查询参数顺序传反了导致所有房间都被当作“已被占用”。解决记住口诀“旧单入住日 新单离店日 且 旧单离店日 新单入住日”参数按[$end, $start]传入。现象二入住办理成功后房间在房态板上还是“空闲”。原因房间表和订单表的状态没有同时更新可能是事务里某个UPDATE影响了 0 行却没抛异常比如房间 status 已经是 2再次执行相同更新语句时 rowCount 为 0。解决在更新后强制判断rowCount() 1不满足就直接回滚。现象三后台菜单某个入口消失了但页面不报错。原因权限标识里写了全角冒号或者下划线can()判断始终返回 false。解决先看 session 里的权限数组是否包含该字符串再打开菜单配置比对字符重点检查冒号是半角还是全角。现象四添加的客人姓名变成问号或者乱码。原因MySQL 表、连接字符集、页面 meta 三处字符集不一致其中连接字符集最容易漏。解决config.php 的 db 配置里加charset utf8mb4并且建库时也用DEFAULT CHARSET utf8mb4。现象五月度报表的营业额对不上总是少一天。原因结算金额的时间差算错把check_in_date和check_out_date直接相减得到的是字符串运算结果夜数恒为 0。解决用DateTime::diff计算天数差并在报表 SQL 里用 SUM 前先 CAST 金额到 decimal。这五个问题建议收藏起来等真正跑的时候再回来看。特别是第一个和第三个都是“代码看起来没毛病跑起来就是不对”的典型属于排查起来最耗精力的玄学问题。5. 二次开发给系统增加会员积分与房价联动改造改造的入口很明确先在数据库加一张积分表再在退房结算时给会员累加积分最后让会员等级参与房价计算。积分表用最简单的主键加会员 id 加积分数即可CREATE TABLE member_points ( id INT AUTO_INCREMENT PRIMARY KEY, member_id INT NOT NULL, points INT NOT NULL DEFAULT 0, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP );房价联动建议放在一个独立的计价服务类里不要在多个控制器里分别算房价。示例方法public function calcPrice(int $typeId, int $memberLevel): float { $base $this-roomTypeRepo-find($typeId)[price]; $discount [1 1.0, 2 0.95, 3 0.88][$memberLevel] ?? 1.0; return round($base * $discount, 2); }退房结算时把$payable的整数位作为新增积分再更新会员积分表。改造完的验证方法我的习惯是走一遍“建单 - 入住 - 退房 - 查报表”四步回归再加一条手动核对退房后的积分值和房价是否按折扣重算。如果积分没变先看退房结算方法里有没有漏掉事务提交。从那以后我每次改动涉及房态或金额的项目都会强制走一遍这个四步回归不跑完不敢提交代码。酒店管理系统的核心就是状态和金额这两样一旦脏了报表和房态全是错的。希望帮到你。本文还有配套的精品资源点击获取