简介:一套面向中小型酒店的信息化管理系统源代码,基于PHP4.2与IIS5.0构建,以Access数据库存储业务数据,定位是帮助酒店实现日常运营的信息化管理,提升各部门协同与办公效率。系统按接待部、餐饮部、采购部、经理部四个核心部门划分功能,覆盖客房预订、入住退房、点餐结算、食材采购、库存监控、财务报表等完整业务流程,既可直接部署试用,也适合作为PHP Web开发的学习案例。压缩包共125个文件,以71个PHP业务代码文件为主,配合HTML页面、CSS样式、Access数据库(db)、图片素材及少量配置文件,完整呈现从前端界面到数据层的项目结构,整体仅549KB,非常轻量。源码采用较旧但稳定的PHP版本,具备跨平台运行特性,开发者可在此基础上二次开发,适配不同酒店的特殊需求。已有3698人学习使用,对想理解企业级应用模块划分、熟悉酒店行业业务流程的PHP学习者而言,是一份结构清晰、可运行可改造的实践素材。
1. 酒店管理系统到底难在哪:不是页面,是房态和账目
第一次拿到这套酒店管理系统PHP源码时,我下意识觉得复杂度不高:页面少,表结构也不复杂。等真把预订到退房的完整流程跑通,才发现最麻烦的不是写页面,而是房态、订单、金额这三件事必须在一套数据库里保持一致。前台订房、后台排房、入住退房、账单结算、经营报表,整条业务闭环全靠几个关键模块串起来。项目核心业务很完整,适合刚学完 PHP 想接触完整项目的人,也适合接中小酒店信息办公系统改造的工程师。前者能对照源码理解原生 PHP 的组织方式和 SQL 实战,后者能在现有基础上快速改成自己能交付的版本。
2. 技术选型与目录结构:原生PHP管理系统怎么把前后台组织起来
2.1 不用框架,反而是这套源码最省事的地方
项目没有依赖 Laravel、ThinkPHP 这类重型框架,而是用原生 PHP 配合 MySQL 实现了完整的前后台。这个选择在今天看会显得“原始”,但对管理系统和办公系统这类以增删改查为核心的场景,框架带来的路由、ORM、模板引擎反而成了多余概念。直接打开源码就能看到“页面怎么调函数、函数怎么查库”,哪怕只学了半年 PHP 也能跟下来。
我一般会把这种项目归为“信息办公型”系统,特点是业务规则集中在少数几张表上,没有高并发和大流量,部署环境也往往是一台普通服务器或虚拟机。原生 PHP 在这类项目里的优势恰恰是低成本:不要求服务器装额外扩展,不强制构建工具,改完代码刷新就能看到结果。缺点也很明显,比如没有自动加载、没有统一的路由分发,所以这套源码用了 include 引入公共文件的方式把结构固定住,约定大于配置。
从改造角度看,原生 PHP 反而“好动刀”。想加一个字段,直接在 SQL 和管理页里改;想加一个报表,复制一个列表页改查询条件就行。如果是框架项目,你得先搞懂模型、控制器、中间件的运行顺序。整套跑通之后我的感受是:从学习到二次开发,原生 PHP 更适合作为理解业务系统的起点。
2.2 根目录结构:前后台分离,公共代码单独收口
源码目录划分比较规整,前台和后台没有混在一起。下面是我整理的根目录结构,拿到压缩包后先对照这份清单确认文件完整性。
hotel/ ├── admin/ # 后台管理端(登录后进入) │ ├── login.php # 后台登录页 │ ├── index.php # 后台首页/仪表盘 │ ├── room.php # 房型与客房管理 │ ├── reservation.php # 预订管理 │ ├── order.php # 订单与结算 │ ├── report.php # 经营报表 │ └── logout.php # 退出登录 ├── index.php # 前台门户,展示房型与在线预订入口 ├── install.sql # 数据库初始化脚本 ├── includes/ │ ├── config.php # 数据库连接配置 │ ├── db.php # PDO 公共连接类 │ ├── function.php # 公共函数库 │ └── auth.php # 登录校验与权限判断 ├── assets/ # CSS、JavaScript、图片资源 └── uploads/ # 房型图片上传目录这个结构最有价值的一点是“前台一套、后台一套、公共层收口”。前台 index.php 负责展示可预订房型和提交预订;admin 下的每个 PHP 文件对应一个后台功能页,页面之间通过参数或隐藏字段跳转;includes 里放的是所有页面都要用到的连接、函数和权限校验。
改动时最需要注意的分区是 includes:所有页面级代码都允许各有各的写法,但公共函数和数据库连接必须唯一,否则会出现“这边改了字符集那边还是乱码”的问题。我接手后习惯先打开 includes 看一遍,确认配置文件、数据库连接、公共函数三层是否各自独立,再去看业务页面。这套源码的划分符合这个原则,排查问题时能省不少时间。
2.3 数据表设计:几张核心表撑起全部业务
数据库是整个系统的地基,这套源码的表数量不算多,但每一张都有清晰定位。我整理出的核心表结构如下。
| 表名 | 作用 | 关键字段 |
|---|---|---|
| admin_user | 后台登录账号 | id, username, password, last_login |
| room_type | 房型定义 | id, type_name, price, bed_num, area, image |
| room | 物理客房 | id, room_type_id, room_no, floor, status |
| reservation | 预订单 | id, room_id, customer_name, phone, check_in_date, check_out_date, status |
| orders | 结算订单 | id, reservation_id, total_amount, pay_time, operator |
| member | 会员/散客档案 | id, name, phone, id_card, level |
预订和订单是分开的,这个设计比较关键。reservation 只记录“客人要住哪间房、住几天、什么状态”,orders 才记录“实际收了多少钱、谁操作的”。如果没做过酒店类的系统,很容易把这两件事混在一张表里,最后退房改价时找不到原始预订信息,报表也统计不准。
客房状态我建议重点关注 room.status 字段,常见取值有 0 空闲、1 在住、2 维修。预订产生的“锁房”不需要直接改 room.status,而是通过 reservation 表里存在未完成的订单来间接表达。这样设计的好处是:取消预订时不用恢复客房状态,只要把预订单改成取消,房态自然释放。
数据库字符集和排序规则建议统一使用 utf8mb4 和 utf8mb4_unicode_ci,安装脚本里已经带了建库语句。新加表时不要混用 utf8 和 utf8mb4,否则关联查询一旦涉及中文,排序和 WHERE 匹配都容易出问题。
2.4 公共配置与函数库:改一处,全局生效
数据库连接集中在 config.php 里定义,业务页面统一通过 includes/db.php 获取 PDO 实例,没有散落的 mysql_connect 调用。这给二次开发省了很大力气,改库只需动一个文件。
// includes/config.php <?php define('DB_HOST', '127.0.0.1'); define('DB_NAME', 'hotel_db'); define('DB_USER', 'root'); define('DB_PASS', '123456'); define('DB_CHARSET', 'utf8mb4');// includes/db.php <?php require_once __DIR__ . '/config.php'; function db(): PDO { static $pdo = null; if ($pdo === null) { $dsn = sprintf( 'mysql:host=%s;dbname=%s;charset=%s', DB_HOST, DB_NAME, DB_CHARSET ); $pdo = new PDO($dsn, DB_USER, DB_PASS, [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]); } return $pdo; }db.php 里用了静态变量保存 PDO 实例,同一个请求周期内多次调用不会重复建连接。PDO 设置了异常模式和关联数组取回,这是我比较推荐的写法:SQL 出错时会直接抛出异常,便于定位;FETCH_ASSOC 让每条记录以字段名为键返回,模板里直接用$row['room_no']。常见的坑是把 PDO 当成每次 new 一下用完就丢的连接工具,页面多了以后连接数会很难看。DB_CHARSET 必须和建库时的字符集一致,这里统一 utf8mb4 能避免后面出现中文乱码。
3. 核心模块拆解:从登录到退房结算的完整调用链
3.1 登录与会话控制:为什么用 Session 而不是自己写 Token
后台所有页面在执行业务代码之前,先要经过登录校验。这套源码在 auth.php 里做了一层统一的会话检查,每个后台页面顶部固定引入,逻辑不复杂但非常关键。
// includes/auth.php <?php session_start(); if (!isset($_SESSION['admin_id']) || $_SESSION['admin_id'] <= 0) { header('Location: /admin/login.php'); exit; }使用时每个后台页面第一行写require_once __DIR__ . '/../includes/auth.php';,如果会话失效就直接跳回登录页,后面的业务代码根本不会执行。Session 方案最直观的好处是服务端可控,注销时销毁服务端数据就能让登录状态立即失效,不需要维护 token 黑名单。对于这种后台管理系统,Session 比前端存储 Token 的方式更省心。
登录时也不要直接比对明文密码。源码里用户表的 password 字段建议存 password_hash 生成的结果,校验用 password_verify,不要自己发明加密算法。
// admin/login.php 中的密码校验片段 $stmt = db()->prepare('SELECT * FROM admin_user WHERE username = ?'); $stmt->execute([$_POST['username']]); $admin = $stmt->fetch(); if ($admin && password_verify($_POST['password'], $admin['password'])) { session_regenerate_id(true); $_SESSION['admin_id'] = $admin['id']; header('Location: index.php'); } else { exit('账号或密码错误'); }这里的核心细节是session_regenerate_id(true),登录成功后重新生成会话 ID,可以防止会话固定攻击。如果你接手时源码里还是普通 md5 密码,建议第一件事就把登录校验改成上面这套写法。
3.2 预订功能:房态冲突检查的 SQL 写法与锁定策略
预订是酒店管理系统最核心的业务环节。前台选好房型、日期提交预订后,后台要把这条预订写进 reservation 表,同时检查“这间房在客人选的日期里是否已经被占了”。
我在这套源码里看到的冲突检查思路是:预订记录包含入住日期和退房日期,判断两段日期是否重叠,用一条 SQL 就能做到。
-- 检查某客房在指定日期区间是否已被预订或在住 SELECT COUNT(*) AS conflict_count FROM reservation WHERE room_id = ? AND status IN ('booked', 'checked_in') AND check_in_date < ? -- 已有订单的入住日 < 新订单的退房日 AND check_out_date > ? -- 已有订单的退房日 > 新订单的入住日第三个参数传新订单的退房日期,第四个参数传新订单的入住日期。这个判断能覆盖所有重叠场景,不只是“完全包含”才算冲突。如果冲突数量大于 0,直接提示客人该时段无房,不再写任何数据;如果为 0,再做插入操作。
实际编码时还需要注意事务和并发。常见做法是先把预订插入 reservation 表,再对同房同区间做一次冲突查询,但并发高时两条请求可能同时通过检查。我习惯把插入和冲突检查放进同一个数据库事务里,并在 room 表加一版计数或使用SELECT ... FOR UPDATE锁住相关行,让第二个请求等第一个提交后再判断。对于中小酒店,并发量不高,事务加唯一索引就能挡掉大部分重复预订。
3.3 入住与退房:状态机切换和金额计算的边界
预订通过后,客人在前台办理入住,此时更新客房状态和订单状态。这个环节最大的风险是状态与金额不一致:客人已经入住了,订单却停留在“已预订”;或者退房结算完了,客房状态还停在“在住”,导致后面无法再排房。
状态切换需要严格的顺序,入住和退房是一对互逆动作。入住时把 reservation.status 从 booked 改为 checked_in,同时把 room.status 改为 1 在住;退房时生成订单金额、把订单标记为已支付、把 room.status 改回 0 空闲。理想情况是前后台共用同一套状态枚举,不要前台定义一个常量字符串、后台又写另一个字符串,否则查询时一旦拼错,页面数据就空掉。
退房金额的常见计算方式是按晚数计价,入住当天算一天,退房日默认中午 12 点前不算费用。
// 退房结算片段 $checkin = strtotime($reservation['check_in_date']); $checkout = strtotime($reservation['check_out_date']); // 退房日和入住日相减得到天数,至少算 1 晚 $nights = (int) ceil(($checkout - $checkin) / 86400); if ($nights < 1) { $nights = 1; } // 总价 = 晚数 x 房型单价,另加可能的加床费或超时费 $total = $nights * $roomType['price'] + $extraFee;这套源码里房价是按晚数计算,这种方式更贴合酒店行业习惯。需要注意 PHP 时区:如果服务器默认 UTC,strtotime('2025-06-20')解析出的时间戳会偏移 8 小时,跨天计算容易少算一晚。所以调用的入口地方都要设置date_default_timezone_set('Asia/Shanghai'),让日期计算稳定在同一时区下。退房动作整体放进事务,先做订单入账,再改客房状态,任何一步失败都能回滚,避免“钱收了房还没退”的翻车现场。
3.4 经营报表:聚合统计不能只靠页面遍历
report.php 展示核心经营数据,比如本月营业额、开房率、每日订单量。如果直接用 PHP 循环遍历订单明细再求和,数据量一大页面就卡。正确做法是把聚合推给 MySQL 完成,只返回汇总结果。
-- 按月统计营收 SELECT DATE_FORMAT(pay_time, '%Y-%m') AS month, COUNT(*) AS order_count, SUM(total_amount) AS revenue FROM orders WHERE status = 'paid' GROUP BY month ORDER BY month DESC;需要按天看趋势时,把DATE_FORMAT(pay_time, '%Y-%m')换成DATE_FORMAT(pay_time, '%Y-%m-%d'),别的条件不用动。报表查询建议加只读连接,避免统计逻辑里误写数据。
报表模块还容易踩一个坑:状态过滤不完整。如果 orders 表里存在“未支付”的挂账单,而报表没有加WHERE status = 'paid',营业额会虚高。看到数字异常时,第一反应就是检查订单状态字段是否被过滤干净。对于历史订单多的部署,我会额外建一张月统计缓存表,每天凌晨用定时任务把前一天的数据聚合写入,报表页面只读缓存表,能明显减轻数据库压力。
4. 避坑指南:本地跑通这套源码的常见问题与排查
4.1 环境准备与安装步骤
这套原生 PHP 项目对环境要求不高,本地跑通我通常建议直接用集成环境,不用单独装 Apache、MySQL、PHP。把源码放到集成环境的网站根目录,然后按下面顺序操作。
# 1. 启动 Apache 和 MySQL 服务 # 2. 用 phpMyAdmin 创建数据库 hotel_db,导入 install.sql # 3. 修改 includes/config.php 里的数据库账号密码 # 4. 浏览器访问 http://localhost/hotel/admin/login.php如果想用 PHP 内置服务器极简启动,在项目根目录执行php -S localhost:8000,再在浏览器打开http://localhost:8000/admin/login.php。这里 Index.php 在前台展示页面,入口路径以实际目录名称为准。导入 install.sql 时如果提示表已存在,先删除旧库再重新导入,避免残留表影响判断。
4.2 打开页面白屏或 500 错误
现象:页面完全空白,没有输出任何 PHP 报错信息。
原因:多数是生产环境把错误显示关掉了,PHP 遇到语法错误或未捕获异常时直接沉默,页面只剩一片白。这种情况很影响判断,因为问题可能出在任何一行。
解决:本地调试时临时在入口文件顶部打开错误显示:
ini_set('display_errors', '1'); error_reporting(E_ALL);加了这两行后重新刷新,页面上会直接打出具体的错误文件和行号。定位完问题记得删掉这两行,避免上线后把报错暴露给访客。如果是 500 错误且加了上面代码仍无效,去查看 PHP 错误日志,日志路径在 php.ini 的 error_log 配置项里。
4.3 “数据库连接失败”但账号密码没错
现象:页面提示无法连接数据库,反复确认 config.php 账号密码和 phpMyAdmin 一致,仍然连不上。
原因:大概率是数据库地址写错或 MySQL 服务没有启动,也可能因为 PDO 的 MySQL 扩展pdo_mysql没有在 PHP 中启用。
解决:先确认 MySQL 服务确实在运行,再执行php -m | grep pdo_mysql看扩展是否加载。集成环境一般默认开启,但如果单独安装 PHP 缺少扩展,需要到 php.ini 里去掉extension=pdo_mysql前的分号。这套源码统一走 PDO,没有 pdo_mysql 扩展时所有数据库操作都会报错,影响面非常大。
4.4 中文乱码:建库、连接、页面三层要统一
现象:后台列表里中文正常,但新增的房型名称或客户姓名显示成问号。
原因:建库时用了 utf8 字符集,而网站连接用的是 utf8mb4,或反了过来。中文字符在两种字符集里大部分能兼容,但插入生僻字或特殊符号时,utf8 存不下就变成问号。
解决:检查 install.sql 建库语句里的 charset,并和 config.php 的 DB_CHARSET 保持一致。我用的最稳方案是全部统一为 utf8mb4。如果旧数据已经出现乱码,导出 SQL 前用文本编辑器把文件转成 UTF-8 无 BOM 格式,再重新导入。
4.5 日期区间冲突判断失效
现象:客人预订明明已经存在,前台查询同一房型同一日期却显示可订。
原因:这段的坑比较隐蔽,问题不在 SQL 而在日期字段类型。如果 check_in_date 和 check_out_date 存的是 datetime 类型,接口传入的日期没有拼接时间,数据库默认补 00:00:00,导致退房日期的判断边界少了一天。
解决:只存日期时,把字段类型改为 DATE,不接受带时间部分的值。同时后端统一校验入参格式,比如强制date('Y-m-d', strtotime($input)),防止“2025-06-20 00:00:00”和“2025-06-20”两种写法混用。改完后我会用最早评论里说的那组典型用例去测:入住和退房完全重叠、相邻不重叠、只重叠一晚的三种情况,各跑一遍才能放心。
5. 从“能跑”到“能交付”:二次开发最值得做的三件事
拿到源码先把演示跑通,接下来要考虑的就是怎么把它变成真正可交付的系统。我会优先做三件事:把数据库变更纳入版本管理、加入操作日志、统一接口参数校验。
第一件事是数据库版本管理。很多人只给代码建 Git,数据库却不进版本控制,改了一张表后,另一台环境上怎么都跑不起来。我从这套源码里学到的习惯是:所有表结构变更都写进增量 SQL 文件,命名带日期,例如20250610_add_member_level.sql,放在 install.sql 同目录。代码和 SQL 一起提交,新环境直接从初始化脚本建库,老环境按增量文件逐步升级,不再手动去表结构里加字段。
第二件事是加操作日志。酒店管理的敏感性在于订单和房价都有据可查,但源码里订单表和房间表都没有记录“谁在什么时间改的”。我会加一张 operate_log 表,字段包括 admin_id、action、target_table、target_id、before_data、after_data、created_at。在修改订单状态、改价、删除预订这三个入口统一写入日志,不需要复杂中间件,只要在这些 PHP 页面里调一个公共函数即可。
第三件事是把散落的请求参数校验收口。原生 PHP 项目的通病是每个页面各自$_POST['xxx'],一旦字段没传,页面就出现 undefined index 警告。我通常在 includes/function.php 里加一个param($name, $default = '')函数,统一读取并做 trim 和默认值处理,然后全局替换。这样做不仅减少报错,还为后期加参数规则校验留了统一入口。
从那以后我每次拿到一套 PHP 源码,都会先看它的数据库脚本和公共函数库,把所有状态字段和调用入口列成清单再动手改代码,而不是急着改页面。这个习惯帮我避开了很多“表面改好了、数据其实错了”的尴尬。希望帮到你。
本文还有配套的精品资源,点击获取