简介:这是一套基于PHP4.2与IIS5.0环境开发的酒店管理系统Web源码,采用Access数据库存储数据,面向酒店信息化管理人员、PHP初学者及需要二次开发的开发者,帮助实现接待、餐饮、采购、经理四大部门业务流程的自动化与规范化。压缩包共125个文件,约549KB,其中71个php文件承载各模块业务逻辑,23个jpg与5个gif构成界面图像资源,10个html与1个css负责视图呈现,另有3个db数据库文件、3个inc包含文件及日志、说明文档等,结构完整便于安装调试。系统覆盖客户入住退房预订、房间状态管理、餐厅点餐结算、菜品库存跟踪、采购申请与供应商管理、销售财务报表及员工考勤等功能,可作为企业级PHP应用的学习范例。目前已有3697人学习下载,适合通过阅读源码理解Web业务分层设计,并在此基础上进行定制扩展。
1. 酒店管理系统PHP源码:从一份能跑起来的代码看中小酒店数字化的真实门槛
很多人第一次接触酒店管理系统PHP源码,是在毕业设计选题或者给朋友的小旅馆做一套前台登记工具的时候。它本质上就是一套用 PHP 写的、围绕客房、订单、入住人、账单四个核心对象转的业务系统,常见形态是「PHP + MySQL + 前端模板」,跑在 Apache 或 Nginx 上。它解决的问题很具体:把纸质的房态表、微信里零散的订房消息、Excel 里的押金记录,收敛到一个能查空房、能办入住、能退房结账的后台里。适合谁?预算有限的中小酒店、民宿、公寓式短租,以及需要一套可二次开发的骨架做课程设计或内部工具的人。但源码拿到手只是起点,真正决定它能不能上线的是权限模型、房态并发和账单一致性这三件事,后面几章就围绕这些展开。
2. 拿到源码先别急着改:环境、目录与数据库三件事的落地顺序
一套酒店管理系统 PHP 源码能不能跑起来,八成取决于你第一步有没有按正确顺序处理环境、目录和数据库。我见过太多人上来就打开编辑器改页面样式,结果连登录都进不去,最后发现是 PHP 版本不对或者数据库没导入。这一章把顺序讲清楚,照着做基本能在一个小时内看到登录页。
2.1 环境选型:PHP 版本、扩展和 Web 服务器的匹配
这类系统绝大多数是传统 LAMP/ LNMP 结构,代码里常见mysql_*或mysqli的写法,也有用 PDO 的。选型上我一般这样定:
| 组件 | 推荐版本 | 原因 |
|---|---|---|
| PHP | 7.4 或 8.0 | 兼容大多数老源码,8.1+ 对部分旧写法会报废弃警告 |
| MySQL | 5.7 或 8.0 | 8.0 注意默认字符集和认证插件差异 |
| Web 服务器 | Apache 2.4 / Nginx 1.20+ | 看源码里有没有 .htaccess,有就优先 Apache |
| 扩展 | mysqli、pdo_mysql、gd、mbstring | gd 用于验证码和图片,mbstring 处理中文 |
判断 PHP 版本最直接的办法是看源码里有没有<?php之外的短标签<?,以及有没有用到each()、create_function()这类在 8.0 被移除的函数。有就降到 7.4。扩展方面,验证码、房型图片、导出报表都会用到 gd,缺了会白屏。
# 以 Ubuntu + Apache 为例,安装基础环境 sudo apt update sudo apt install apache2 php7.4 libapache2-mod-php7.4 php7.4-mysql php7.4-gd php7.4-mbstring mysql-server -y # 启动服务并设置开机自启 sudo systemctl start apache2 mysql sudo systemctl enable apache2 mysql # 确认 PHP 模块加载正常 php -m | grep -E 'mysqli|pdo_mysql|gd|mbstring'这段命令做完,环境层面就齐了。php -m的输出里必须能看到 mysqli、pdo_mysql、gd、mbstring 四个,缺哪个就单独装对应的php7.4-xxx包。注意 Apache 下 PHP 是以模块方式跑的,改完配置要sudo systemctl restart apache2,不是 reload。
2.2 目录结构与入口文件:找到真正的登录入口
解压源码后先别乱动,用tree -L 2看一眼结构。典型布局是这样:
# 查看两层目录结构,快速定位入口 tree -L 2 /var/www/html/hotel # 常见输出 # ├── admin/ 后台管理 # ├── api/ 接口目录 # ├── config/ 数据库配置 # ├── includes/ 公共函数 # ├── uploads/ 上传的房型图 # ├── index.php 前台入口 # └── login.php 登录入口入口文件通常是index.php或login.php。先打开config/下的配置文件,把数据库地址、库名、用户名、密码改成你本地的。很多源码把配置写死在includes/config.php或config/database.php,用编辑器全局搜localhost、root、password能快速定位。
// config/database.php 典型内容,改这四项即可 return [ 'host' => '127.0.0.1', 'port' => 3306, 'database' => 'hotel_db', 'username' => 'root', 'password' => 'your_password', 'charset' => 'utf8mb4', ];参数说明:charset一定要用utf8mb4,否则客人姓名里的生僻字和 emoji 会存成问号;host用127.0.0.1比localhost更稳,避免 socket 连接问题。改完配置先别急着访问,下一步导入数据库。
2.3 导入 SQL 与初始账号:让登录页真正可用
数据库文件一般在sql/或根目录,后缀.sql。导入前先建库,字符集选utf8mb4。
# 建库并导入,注意库名要和配置文件一致 mysql -uroot -p -e "CREATE DATABASE hotel_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;" mysql -uroot -p hotel_db < sql/hotel_db.sql # 导入后确认表数量和关键表 mysql -uroot -p hotel_db -e "SHOW TABLES; SELECT id,username,role FROM admin_user LIMIT 5;"导入后重点看三张表:admin_user(后台账号)、room(客房)、order(订单)。初始账号密码通常在 SQL 文件末尾的 INSERT 语句里,密码可能是明文也可能是 md5。如果是 md5,用SELECT MD5('123456');生成后替换。登录页能打开但提示密码错误,九成是密码哈希方式对不上,别怀疑环境。
提示:导入 SQL 报
Unknown collation: utf8mb4_0900_ai_ci说明 SQL 是从 MySQL 8.0 导出的,而你本地是 5.7。把文件里的utf8mb4_0900_ai_ci全部替换成utf8mb4_general_ci再导入。
3. 房态、订单与账单:酒店管理系统PHP源码里最该先读懂的三个模块
环境跑通只是让你看到了界面,真正决定这套源码能不能用于生产的是业务模块的设计。酒店业务的核心矛盾是「同一间房在同一时间段只能卖给一个人」,这个约束在代码里怎么表达,直接决定了系统会不会出现重复开房。这一章拆房态、订单、账单三个模块,讲清楚读代码的顺序和改造点。
3.1 房态表设计:状态字段怎么定才不打架
房态的本质是一张room表加一张order表,通过时间区间判断占用。常见做法有两种:一种是在room表上放一个status字段(空闲/已订/入住/维修),另一种是不存状态,每次查询时用订单时间区间实时计算。前者简单但容易和订单不同步,后者准确但查询稍重。
-- 推荐:room 表只存物理属性,状态由订单推导 CREATE TABLE `room` ( `id` int NOT NULL AUTO_INCREMENT, `room_no` varchar(10) NOT NULL COMMENT '房号', `type_id` int NOT NULL COMMENT '房型ID', `floor` int DEFAULT NULL, `status` tinyint DEFAULT '1' COMMENT '1可用 0维修', PRIMARY KEY (`id`), UNIQUE KEY `uk_room_no` (`room_no`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 订单表用 check_in / check_out 表达占用区间 CREATE TABLE `order` ( `id` int NOT NULL AUTO_INCREMENT, `room_id` int NOT NULL, `guest_name` varchar(50) NOT NULL, `check_in` date NOT NULL, `check_out` date NOT NULL, `status` tinyint DEFAULT '1' COMMENT '1预订 2入住 3退房 4取消', PRIMARY KEY (`id`), KEY `idx_room_time` (`room_id`,`check_in`,`check_out`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;参数说明:idx_room_time这个联合索引是查空房的关键,没有它,房态查询在订单量上千后会明显变慢。status只表达物理可用性(维修),不表达业务占用,这样退房、取消都不会污染房态。判断某天某房是否可订,用区间重叠逻辑:check_in < 目标退房 AND check_out > 目标入住。
3.2 订单创建:防重复开房的判断写在哪一层
防重复开房不能只靠前端置灰,必须在写库前做一次数据库层面的校验。我一般把校验放在事务里,先查冲突再插入。
// 创建订单时的防冲突逻辑 $pdo->beginTransaction(); try { // 锁定该房间相关行,避免并发插入 $check = $pdo->prepare( "SELECT COUNT(*) FROM `order` WHERE room_id = ? AND status IN (1,2) AND check_in < ? AND check_out > ? FOR UPDATE" ); $check->execute([$roomId, $checkOut, $checkIn]); if ($check->fetchColumn() > 0) { throw new Exception('该房型在所选时段已被预订'); } $ins = $pdo->prepare( "INSERT INTO `order` (room_id, guest_name, check_in, check_out, status) VALUES (?, ?, ?, ?, 1)" ); $ins->execute([$roomId, $guestName, $checkIn, $checkOut]); $pdo->commit(); } catch (Exception $e) { $pdo->rollBack(); echo $e->getMessage(); }逻辑说明:FOR UPDATE在事务里锁住符合条件的行,防止两个请求同时通过校验。参数上check_in < ?传的是新订单的退房日期,check_out > ?传的是新订单的入住日期,这是区间重叠的标准写法。注意status IN (1,2)只把预订和入住算作占用,退房和取消不占。如果源码里没有这层校验,只在前端判断,那并发下必然翻车,这是改造的第一优先级。
3.3 账单与押金:金额字段的类型和计算时机
账单模块最容易出的问题是金额用 float 存,导致 0.1+0.2 这类精度问题。正确做法是用decimal(10,2)或者以分为单位存整数。
-- 账单表:金额用 decimal,避免浮点误差 CREATE TABLE `bill` ( `id` int NOT NULL AUTO_INCREMENT, `order_id` int NOT NULL, `room_fee` decimal(10,2) DEFAULT '0.00' COMMENT '房费', `deposit` decimal(10,2) DEFAULT '0.00' COMMENT '押金', `other_fee` decimal(10,2) DEFAULT '0.00' COMMENT '其他消费', `total` decimal(10,2) DEFAULT '0.00' COMMENT '应收合计', `paid` decimal(10,2) DEFAULT '0.00' COMMENT '实收', `created_at` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_order` (`order_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;计算时机上,房费在退房时按实际入住天数结算,押金在入住时收取、退房时退还。total建议在退房时一次性算好写入,不要每次查询都实时算,否则历史账单会随房价调整而变化。参数上decimal(10,2)表示最多 10 位、2 位小数,对酒店场景足够。如果源码用 float,改造时记得同步改 PHP 里的计算逻辑,用bcadd、bcmul做精确运算。
4. 二次开发避坑:酒店管理系统PHP源码改造中最容易翻车的五件事
源码能跑、模块能读,接下来就是按自己需求改。这一章是我踩过的坑里挑出来最典型的五条,每条按现象、原因、解决写,能帮你省下大量排查时间。
4.1 中文乱码:从数据库到页面的字符集链路
现象:客人姓名、房型名称显示成问号或乱码,导出 Excel 更严重。原因:字符集在数据库、连接、页面三个环节不一致,常见是库是 utf8mb4 但连接用了 latin1。解决:数据库建库用 utf8mb4,PDO 连接串加charset=utf8mb4,PHP 文件本身存为 UTF-8 无 BOM,HTML 里加<meta charset="utf-8">。四者缺一不可,用SHOW VARIABLES LIKE 'character%';确认服务端设置。
4.2 登录后跳转空白:session 和输出缓冲的冲突
现象:输入正确账号密码后页面全白,查看源码无报错。原因:源码在session_start()之前有输出(比如 BOM 或空行),导致 header 无法发送。解决:检查登录文件开头有没有多余空行或 BOM,用ob_start()放在最前面兜底。另外确认session.save_path可写,php -i | grep session.save_path看路径权限。
4.3 房态不刷新:缓存和查询条件的双重问题
现象:明明退了房,房态图还显示占用。原因:一是页面用了浏览器缓存,二是查询条件把已退房订单也算进去了。解决:查询时严格用status IN (1,2),并在响应头加Cache-Control: no-store。如果用了 Redis 缓存房态,退房时要主动删对应 key,别等过期。
4.4 上传房型图失败:目录权限和 PHP 上传限制
现象:后台上传图片提示失败或图片显示裂图。原因:uploads/目录没有写权限,或上传文件超过upload_max_filesize。解决:chmod -R 755 uploads并确保属主是 web 用户,改php.ini里upload_max_filesize=10M、post_max_size=12M,重启服务。注意post_max_size要大于upload_max_filesize。
4.5 报表导出乱码或超时:大数据量下的分批处理
现象:导出月度报表时 Excel 打开乱码,或者订单多时请求超时。原因:CSV 没加 BOM 导致 Excel 识别错编码,以及一次性查全部订单内存爆掉。解决:CSV 开头输出\xEF\xBB\xBF,查询用分页或游标分批写文件,set_time_limit(0)解除超时。数据量上万时,建议直接生成文件而不是输出到浏览器。
5. 从能跑到能用:给酒店管理系统PHP源码加一层可验证的权限与日志
前面几章解决的是「跑起来」和「改得动」,这一章讲怎么判断它「能用」。中小酒店系统最容易被忽视的是权限和日志,出了纠纷查不到谁改的房价、谁办的退房,系统就成了黑匣子。我一般会给源码补两样东西:基于角色的权限校验和关键操作日志,成本不高但价值很大。
5.1 用中间件思路做权限校验,而不是在每个页面写 if
老源码常见写法是在每个后台页面顶部写if ($_SESSION['role'] != 'admin') exit;,改起来痛苦。更好的做法是抽一个统一的校验函数,在入口处调用。
// includes/auth.php:统一权限校验 function require_role($roles = []) { if (session_status() === PHP_SESSION_NONE) { session_start(); } if (empty($_SESSION['admin_id'])) { header('Location: /login.php'); exit; } if ($roles && !in_array($_SESSION['role'], $roles, true)) { http_response_code(403); exit('无权限访问'); } } // 在需要管理员权限的页面顶部调用 require_role(['admin']); // 前台和财务都能访问的页面 require_role(['admin', 'frontdesk']);逻辑说明:require_role把登录校验和角色校验合并,页面只需声明允许的角色。参数$roles为空数组时只校验登录,传数组时校验角色。这样改造后,新增角色或调整权限只改一处。注意in_array第三个参数传true做严格比较,避免类型松散导致的越权。
5.2 关键操作日志:记录谁在什么时候改了什么
日志表设计要能回答「谁、何时、对哪个对象、做了什么、改前改后是什么」。
CREATE TABLE `op_log` ( `id` bigint NOT NULL AUTO_INCREMENT, `admin_id` int NOT NULL, `action` varchar(50) NOT NULL COMMENT '操作类型', `target` varchar(50) NOT NULL COMMENT '对象类型', `target_id` int DEFAULT NULL, `before` json DEFAULT NULL COMMENT '改前快照', `after` json DEFAULT NULL COMMENT '改后快照', `ip` varchar(45) DEFAULT NULL, `created_at` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_admin_time` (`admin_id`,`created_at`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;写入时机放在业务操作成功后,比如改房价、办退房、删订单。before和after用 JSON 存,方便对比。参数上ip用 varchar(45) 兼容 IPv6。查询时按admin_id和时间范围过滤,idx_admin_time保证速度。有了这张表,客人投诉房价不对时,能直接查到是哪次操作改的。
5.3 验证方法:用三条测试用例确认系统真的可用
改完之后别凭感觉说「应该没问题」,用三条用例验证:
| 用例 | 操作 | 预期结果 |
|---|---|---|
| 并发订房 | 两个浏览器同时订同一间房同一时段 | 只有一个成功,另一个提示冲突 |
| 权限越权 | 用前台账号访问管理员页面 | 返回 403,不泄露数据 |
| 账单精度 | 房价 199.99 住 3 天加押金 100 | 合计 699.97,无浮点误差 |
这三条覆盖了并发、权限、金额三个最容易出事的点。跑通之后再考虑上线。我自己现在的习惯是,拿到任何一套酒店管理系统 PHP 源码,先不改界面,先把这三条用例跑一遍,跑不过就先修底层,界面再好看也白搭。希望帮到你。
本文还有配套的精品资源,点击获取