恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
数据库大作业:基于Python酒店管理系统的表设计与避坑指南
首页
资讯中心
/
数据库大作业:基于Python酒店管理系统的表设计与避坑指南
数据库大作业:基于Python酒店管理系统的表设计与避坑指南
发布时间:2026/10/9 23:44:37
简介这套基于Python开发的酒店管理系统项目主要面向数据库课程设计、期末大作业和毕业设计场景可用于解决“系统实现报告撰写”的完整需求。项目包含登录认证、客房信息管理、入住退房、消费记录、报表统计等典型业务模块源码内配有逐段注释新手也能看懂关键逻辑适合作为高分参考模板。压缩包共61个文件约8.3MB主要包含Python源码.py、Qt Designer界面文件.ui、数据库建表与初始化脚本.sql、系统设计报告.pdf另有ER图、功能结构图、项目说明文档等覆盖从表结构设计到界面开发的完整链路目录层次清晰便于按模块查阅。目前已吸引382人学习下载整理自个人手打并获得导师认可的98分项目附带的《数据库应用》系统设计报告对数据表关系、功能划分和业务流程做了说明可帮助读者快速搭建环境并运行同时为课程报告及答辩提供参考。1. 数据库大作业的第一步把酒店管理系统的评分点拆清楚不少同学拿到「数据库大作业」这类题目第一反应是先把界面做得花哨一点、按钮多一点最后却发现分数没比隔壁用命令行交作业的同学高。原因不复杂这门课叫数据库不叫软件工程评分老师第一眼看的是表怎么建、约束怎么加、事务怎么写、报表怎么查界面只是顺带看的。标题「数据库大作业-基于python酒店管理系统源代码文档说明」要交付的是一个用 Python 连接 MySQL、完成酒店订房退房全流程、并且配得上课程设计要求的小项目。本文就沿着这个标题把「表怎么设计、代码怎么写、哪些坑必须避、文档怎么组织」一次说透适合正在选课设题目、或者已经选了这个题目还不知道从哪下手的同学照着做。2. 先设计表再写代码ER 模型与建表 SQL 的关键取舍2.1 酒店管理的最小业务闭环用户、房间、订单三张表就够先别急着写代码。数据库大作业的评分标准通常落在三块概念模型是否清晰、约束与完整性是否到位、有没有说明索引和事务的必要性。酒店管理系统最适合拿来演示这三块因为它有典型的 1:N 关系、有状态流转、有金额计算。常见做法是只建三张表用户表、房间表、订单表。用户和房间之间不直接关联订单表作为中间联系把它们连起来。一次预订生成一条订单订单状态从「已预订」走到「已入住」再到「已退房」一条链路就覆盖了增删改查里最难写的部分。我在评审里见过不少同学把表拆得很碎支付表、保洁表、维修表各来一张结果数据流断成一截一截代码里全是跨表拼凑。三张表足以构成闭环要扩展可以加房间日志表或者支付流水表但基础版本三张表最稳。另一个常见问题是冗余失控有人把房间类型、房价原样复制到订单表里房价一旦调整历史订单和房间表就对不上。合理的冗余是成交价快照因为价格本来就允许变但房间类型这种属性跟着订单走就属于过度冗余。记住这个边界后面写文档时可以把这段取舍写进「数据库设计说明」是实打实的加分项。2.2 建表 SQL字符集、引擎与三个关键约束字段类型的选择其实是一道送分题。价格用 DECIMAL(10,2) 而不是 FLOAT日期用 DATE 而不是 VARCHAR状态类字段用 TINYINT 并配上 COMMENT 说明含义。外键约束放在订单表指向用户表和房间表再加一个 CHECK 约束保证退房日期晚于入住日期。引擎必须写 InnoDB因为在 MySQL 5.7 及更早版本里如果没指定引擎默认可能是 MyISAM而 MyISAM 不支持外键约束会静默失效——这是后面避坑章节要展开的一个大坑。CREATE DATABASE IF NOT EXISTS hotel_db DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci; USE hotel_db; DROP TABLE IF EXISTS booking; DROP TABLE IF EXISTS room; DROP TABLE IF EXISTS user; CREATE TABLE user ( user_id INT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(50) NOT NULL UNIQUE, password_hash VARCHAR(128) NOT NULL, real_name VARCHAR(50) NOT NULL, phone VARCHAR(20), role TINYINT NOT NULL DEFAULT 0 COMMENT 0-前台 1-管理员, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE room ( room_id INT AUTO_INCREMENT PRIMARY KEY, room_no VARCHAR(10) NOT NULL UNIQUE, room_type VARCHAR(20) NOT NULL COMMENT single/double/suite, price DECIMAL(10,2) NOT NULL, status TINYINT NOT NULL DEFAULT 1 COMMENT 1-空闲 2-已订 3-已入住 4-维修 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE booking ( booking_id INT AUTO_INCREMENT PRIMARY KEY, user_id INT NOT NULL, room_id INT NOT NULL, check_in_date DATE NOT NULL, check_out_date DATE NOT NULL, total_amount DECIMAL(10,2), status TINYINT NOT NULL DEFAULT 1 COMMENT 1-已预订 2-已入住 3-已退房 4-已取消, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_booking_user FOREIGN KEY (user_id) REFERENCES user(user_id), CONSTRAINT fk_booking_room FOREIGN KEY (room_id) REFERENCES room(room_id), CONSTRAINT chk_booking_date CHECK (check_out_date check_in_date) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这段建表语句直接存成schema.sql放进项目根目录后面初始化脚本会读它。注意 DROP 的顺序必须按外键依赖倒序先删订单表否则会报「无法删除被外键引用的表」。字符集统一用 utf8mb4 而不是 utf8因为 utf8 在 MySQL 里存不了四字节的 emoji 和部分生僻字这也埋下了后面乱码的隐患。CHECK 约束在 MySQL 8.0.16 之前会被解析但忽略掉如果你的课程环境是 MySQL 5.7这个约束不会真正生效所以代码层还要再做一次日期校验不能把完整性全部托付给数据库。2.3 选 PyMySQL 还是 SQLAlchemy一个影响文档方向的决定不少同学在选数据库驱动时纠结。这里给一张对比表按课程类型决定判断维度PyMySQL 手写 SQLSQLAlchemy ORM与数据库课程的贴合度直接对应 SQL 考点多一层对象映射事务控制显式 begin/commit/rollbackSession 隐式管理排错难度SQL 报错直观需要理解映射层才能定位上手成本会 SQL 就能写必须先理解 ORM 概念合适的课程场景数据库原理、数据库系统软件工程、系统设计这个标题大概率是数据库课所以推荐 PyMySQL。手写 SQL 能让你在文档里直接贴「核心 SQL 语句」和「事务演示」这两块是数据库课评分的重头戏。用了 ORM虽然代码更简洁但老师问「这条查询最终生成了什么 SQL」时你答不上来就很被动。如果课程强调分层架构那选 SQLAlchemy 也合理但文档里必须单独写一节「对象-关系映射说明」把 session、flush、事务边界讲清楚否则看起来像只用了黑匣子。# db.py import pymysql import config def get_connection(): 统一管理数据库连接所有业务模块都从这里取连接 return pymysql.connect( hostconfig.DB_HOST, portconfig.DB_PORT, userconfig.DB_USER, passwordconfig.DB_PASSWORD, databaseconfig.DB_NAME, charsetutf8mb4, # 与建库字符集保持一致 autocommitFalse, # 关闭自动提交事务由业务代码控制 cursorclasspymysql.cursors.DictCursor )autocommitFalse是刻意为之查询不需要提交但预订和退房必须走事务把提交时机交给业务代码才能保证一组操作要么全部成功、要么全部回滚。DictCursor让查询结果以字典形式返回代码里用row[user_id]而不是row[0]可读性好很多写文档时也更容易解释。配置文件单独放config.py里面就是 host、port、user、password、database 这几个变量不复杂但注意不要把密码写死在代码里评分老师能看懂你这个习惯。3. 从零跑通最小系统环境、初始化脚本与一条命令启动3.1 用虚拟环境隔离依赖requirements 与启动步骤做课程设计最容易翻车的场景是老师拿到你的项目后在自己电脑上跑不起来。跑不起来的原因多半不是代码问题而是依赖冲突和路径不一致。我一般强制自己用虚拟环境把项目隔离起来这样requirements.txt里写什么老师的机器上装的就是什么。Python 3.10 以上都支持内置的 venv不需要额外装 virtualenv。python -m venv .venv # Linux/macOS 激活 source .venv/bin/activate # Windows 激活 .venv\Scripts\activate pip install -r requirements.txtpymysql1.0这个标题的项目里只有一个硬依赖就是 PyMySQL。别急着往 requirements 里塞 numpy、pandas 这些与本题无关的包每多一个依赖就多一个跑不起来的风险。如果后续要读.env配置再加python-dotenv但课程设计阶段我建议直接用config.py写配置少一个依赖少一类问题。requirements 里写最低版本而不是精确版本既保证可复现又不至于在别的机器上因为版本过旧安装失败。3.2 初始化脚本让评分老师跑一次就完成建库建表有了schema.sql和config.py还需要一个初始化脚本。直接让老师打开命令行手动敲建表语句不现实一是容易敲错二是容易漏。常见做法是把建库建表封装成一个init_db.py跑一次就把数据库和表全部建好。注意连接的时候先不指定 database因为建库语句在 schema.sql 里如果此刻就指定数据库第一次执行会直接报「数据库不存在」。# init_db.py import pymysql import config def init_db(): conn pymysql.connect( hostconfig.DB_HOST, portconfig.DB_PORT, userconfig.DB_USER, passwordconfig.DB_PASSWORD, charsetutf8mb4 ) with conn.cursor() as cur: with open(schema.sql, r, encodingutf-8) as f: sql_text f.read() for statement in sql_text.split(;): statement statement.strip() if statement: cur.execute(statement) conn.commit() conn.close() print(数据库初始化完成) if __name__ __main__: init_db()这里按分号切分语句只适用于纯建表脚本。如果你的 schema.sql 里加入了存储过程或者触发器分号切分会把过程体切断那时候要改用 PyMySQL 的client_flagpymysql.constants.CLIENT.MULTI_STATEMENTS直接执行整段文本。课程设计阶段一般用不到但注释里写明这个边界说明你想过这个问题比埋在代码里更能拿印象分。重复执行init_db.py不会报错原因在 schema.sql 里的DROP TABLE IF EXISTS保证了幂等性。3.3 项目目录结构与文档说明的组织整个项目我习惯这样组织README 打开就是启动步骤老师的耐心有限别让他找文件hotel_db/ ├── schema.sql # 建库建表脚本数据库设计的最终成品 ├── init_db.py # 一键初始化数据库 ├── db.py # 数据库连接管理 ├── config.py # 连接参数配置 ├── main.py # 程序入口控制台菜单 ├── requirements.txt ├── README.md # 运行说明 └── docs/ ├── 需求分析.md ├── 数据库设计.md # ER 图、表结构说明、约束与索引设计 └── 测试截图/标题里的「文档说明」指的不是 README 里抄两三句的那种敷衍文本而是上面这个 docs 目录。数据库设计文档至少要包含几块需求背景与功能划分、ER 图与关系模式、每张表的字段说明和约束说明、核心 SQL 语句、事务与并发控制策略、测试截图。很多同学把 ER 图放在最后画结果和代码对不上正确做法是先画图再建表最后核对一遍。README 里只写环境要求、启动命令、测试账号控制在 20 行以内让评分老师五分钟内能进入正题。4. 核心功能落代码登录、预订、结算与订单状态流转4.1 登录与密码哈希不要把明文密码写进表里登录是系统的第一道门。不少课程设计项目直接明文存密码查数据库就能看到这在答辩时是硬伤。正确做法是用哈希加盐的方式存储。Python 标准库里的 hashlib 提供了pbkdf2_hmac不需要额外安装任何包比单独的 md5 安全得多。import hashlib import db import config def hash_password(password: str, salt: str) - str: 用 PBKDF2 做慢哈希迭代次数设 100000 return hashlib.pbkdf2_hmac( sha256, password.encode(utf-8), salt.encode(utf-8), 100_000 ).hex() def login(username: str, password: str): conn db.get_connection() try: with conn.cursor() as cur: cur.execute( SELECT user_id, username, password_hash, role FROM user WHERE username %s, (username,) ) row cur.fetchone() if not row: return None # 比对哈希值而不是比对明文 if hash_password(password, config.PASSWORD_SALT) ! row[password_hash]: return None return row finally: conn.close()形参一律用%s占位符坚决不用字符串拼接 SQL这是防注入的最低要求答辩时大概率会被问到。代码里使用了全局统一的盐放在config.py里课程设计够用如果以后做真实项目应该每个用户独立随机盐并单独存一列。哈希值本身是十六进制字符串长度 64 位所以 user 表里password_hash定为 VARCHAR(128) 留足了余量也兼容以后换更长的算法。4.2 房间查询与预订日期重叠判断与事务查房是预订的前置动作。按日期查可订房间核心是判断日期区间是否重叠。一段订单占用的区间是[check_in_date, check_out_date)如果新订单的入住时间小于已有订单的退房时间、且新订单的退房时间大于已有订单的入住时间就说明有重叠。这条 SQL 用 NOT IN 子查询把重叠的房间排除掉SELECT r.room_id, r.room_no, r.room_type, r.price FROM room r WHERE r.status 1 AND r.room_id NOT IN ( SELECT b.room_id FROM booking b WHERE b.status IN (1, 2) -- 只看未取消未退房的订单 AND b.check_in_date %s -- 参数1: 新订单的退房日期 AND b.check_out_date %s -- 参数2: 新订单的入住日期 )传参时两个 %s 的位置容易写反我的习惯是第一个参数传退房日期、第二个传入住日期对应 SQL 里「入住早于对方退房」和「退房晚于对方入住」两个条件写注释标清楚。光有查询还不够真正的坑在后面如果两个请求同时发现房间空闲并且同时下单就产生了超卖。解决办法是开启事务用SELECT ... FOR UPDATE把房间行锁住直到事务提交才释放。def create_booking(user_id, room_id, check_in, check_out, amount): conn db.get_connection() try: conn.begin() with conn.cursor() as cur: cur.execute( SELECT status FROM room WHERE room_id %s FOR UPDATE, (room_id,) ) room cur.fetchone() if not room or room[status] ! 1: raise Exception(房间不可预订) # 事务内再查一次日期重叠防止并发下读到旧数据 cur.execute( SELECT booking_id FROM booking WHERE room_id %s AND status IN (1,2) AND check_in_date %s AND check_out_date %s LIMIT 1, (room_id, check_out, check_in) ) if cur.fetchone(): raise Exception(该时间段已被预订) cur.execute( INSERT INTO booking(user_id, room_id, check_in_date, check_out_date, total_amount, status) VALUES (%s, %s, %s, %s, %s, 1), (user_id, room_id, check_in, check_out, amount) ) cur.execute( UPDATE room SET status 2 WHERE room_id %s, (room_id,) ) conn.commit() return True except Exception: conn.rollback() raise finally: conn.close()FOR UPDATE是这里的点睛之笔。不加锁时两个事务都先读到 status1然后各自插入订单最后都更新房间状态超卖就发生了加了锁之后第二个事务会阻塞在 SELECT 处等第一个事务提交后重新读取才能继续判断。事务内的第二次日期重叠查询不是重复劳动而是为了挡住「查房时还空闲、下单前已被别人抢先一步」的时间窗口。conn.begin()显式开启事务配commit和rollback这三行代码组合起来就是一条完整的防超卖方案写进文档的「并发控制」一节比空谈理论有力得多。4.3 退房结算与状态机让每一条记录有据可查退房结算的逻辑是把订单状态从「已入住」改成「已退房」把房间状态改回「空闲」同时算出总金额。这里有个原则订单记录永不物理删除。取消也好、退房也好都只更新状态字段这样月底报表才有数据可统计。删除订单是数据库大作业里很常见但很扣分的做法。def checkout(booking_id): conn db.get_connection() try: conn.begin() with conn.cursor() as cur: cur.execute( SELECT b.booking_id, r.room_id, r.price, b.check_in_date, b.check_out_date FROM booking b JOIN room r ON b.room_id r.room_id WHERE b.booking_id %s AND b.status 2 FOR UPDATE, (booking_id,) ) row cur.fetchone() if not row: raise Exception(订单不存在或未入住) days (row[check_out_date] - row[check_in_date]).days amount days * float(row[price]) cur.execute( UPDATE booking SET status 3, total_amount %s WHERE booking_id %s, (amount, booking_id) ) cur.execute( UPDATE room SET status 1 WHERE room_id %s, (row[room_id],) ) conn.commit() except Exception: conn.rollback() raise finally: conn.close()订单状态流转是全系统最值得在文档里画一张表的部分状态值含义流转方向1已预订可取消为 4可办理入住为 22已入住退房结算为 33已退房终态进入报表统计4已取消终态保留记录备查金额计算放在退房时而不是预订时是因为退房可能存在加收费用保留到最后一刻才定稿符合真实业务习惯。days的计算依赖数据库返回的 date 对象如果当初把日期存成了 VARCHAR这一步就会直接报错或者算错天数这也是我反复强调日期字段必须用 DATE 的原因。5. 避坑指南让酒店管理系统数据库翻车的 6 个常见原因很多问题看起来像玄学排查半天发现是字符集、引擎和事务三个点上的低级失误。这一章把课程设计里出现频率最高的 6 个坑按「现象、原因、解决」拆开每一条都附自查方法。5.1 字符集与金额类型两个最容易在演示现场暴露的问题坑 1中文乱码。现象插入中文姓名后SELECT 查出来是「???」或者 Navicat 里正常、程序读出来乱码。原因建库时没指定字符集默认落到 latin1或者建库指定了 utf8mb4但连接参数没带charsetutf8mb4程序和服务端各说各话。解决建库语句写DEFAULT CHARACTER SET utf8mb4连接串统一加 charsetschema.sql 和 db.py 两处要一致。自查方法执行SHOW CREATE DATABASE hotel_db;看默认字符集再在代码里打印一条插入后的记录只要两处都显示 utf8mb4 基本不会再出问题。坑 2金额用 FLOAT 导致结算金额有误差。现象订单金额显示成 99.30000000000001或者多笔订单加总有几分钱对不上。原因二进制浮点数无法精确表示 0.1、0.2 这类十进制小数这是计算机基础问题不是 MySQL 特有的。解决金额字段一律用DECIMAL(10,2)计算时注意把 Python 读到的值转成 Decimal 或统一用分为单位做整数运算。自查方法故意插入一笔 99.99 的订单退房后看数据库里存的 total_amount 是否还是 99.99。这一条值得写进文档的「数据库设计约束」一节。5.2 并发超卖与外键约束失效数据一致性最隐蔽的扣分点坑 3同一房间同一时间段被订两次。现象前台同事同时操作两个客户都拿到了同一间房的确认单。原因代码里「先查有没有订单再插入新订单」两个动作之间没有加锁并发时两个请求都读到「暂无冲突」。解决按 4.2 的做法在事务里用SELECT ... FOR UPDATE锁行或者给房间时间维度建唯一索引但日期区间重叠用唯一索引不太好表达锁行是更普适的方案。自查方法开两个终端同时跑同一笔预订脚本看是否只有一个成功。能演示出这个结果答辩时反而成了加分项。坑 4外键约束建了但不生效。现象DELETE FROM room 删掉了还在被订单引用的房间没有任何报错数据完整性崩了。原因表引擎不是 InnoDB。MySQL 5.7 及更早版本默认引擎可能是 MyISAMMyISAM 不支持外键MySQL 会静默忽略 FOREIGN KEY 子句。解决建表语句显式写ENGINEInnoDB不要依赖默认值。自查方法执行SHOW TABLE STATUS WHERE Namebooking;查看 Engine 列是否为 InnoDB或者直接尝试违反外键约束看数据库是否报错。5.3 时间字段与文档一致性影响最终印象分的习惯坑 5日期用 VARCHAR 存储。现象按月统计入住率时排序结果是 2024-1、2024-10、2024-2完全乱掉退房结算时计算入住天数要自己解析字符串。原因字符串排序按字典序不按时间序。解决日期字段用 DATE时间戳用 DATETIME让数据库原生支持比较和运算。自查方法看 schema.sql 里 check_in_date 的字段类型如果是 VARCHAR 直接改。这条属于一眼就能看出的基础错误遇到了必须改。时间处理上的另一个小坑是 Python 从数据库拿到的 date 对象不能直接参与strftime之外的字符串拼接代码里要做显式转换文档里提一句显得你考虑过跨语言类型差异。坑 6文档和代码对不上。现象ER 图里画了 6 张表代码和 schema.sql 里只有 3 张文档里说订单状态有 5 个代码里只有 3 个分支。原因文档是最后补的凭记忆画图没有对照实际建表语句。解决把文档的「数据库设计」章节放在 schema.sql 发布之后写表结构说明直接从建表语句导出核对状态枚举用 4 中的表格呈现ER 图只画三张表不做任何美化。这一条直接影响答辩印象分老师翻文档发现图和代码对不上前面所有努力都会被怀疑真实性。自查方法打开 docs/数据库设计.md逐表对比 schema.sql 的字段名、类型和约束任何不一致立即改文档。6. 两个进阶技巧与验收自测让大作业从「能跑」到「耐看」6.1 用视图把月度报表写进数据库很多项目把统计逻辑写在 Python 里循环累加算营收能跑但体现不出数据库能力。常见做法是在数据库层建一个视图把每月订单数和营业额算好Python 只做一次 SELECT。这样文档里能截图展示「视图 GROUP BY」的组合评分点非常明显。CREATE VIEW v_monthly_income AS SELECT DATE_FORMAT(b.check_out_date, %Y-%m) AS month, COUNT(DISTINCT b.booking_id) AS order_count, COALESCE(SUM(b.total_amount), 0) AS income FROM booking b WHERE b.status 3 GROUP BY DATE_FORMAT(b.check_out_date, %Y-%m);6.2 给查询加索引并用 EXPLAIN 验证订单表数据量一大按日期查房的子查询就会变慢。给check_in_date, check_out_date, status建联合索引再用EXPLAIN看执行计划能看到扫描行数大幅下降。这是文档里「索引设计」一节的内容一句「使用 EXPLAIN 验证索引生效」就能让老师知道你不只会写 SQL。6.3 验收自测清单交作业前按下面这张表过一遍每一项都确认后再打包检查项做法预期结果建库建表全新环境跑 init_db.py三张表存在中文正常显示功能闭环注册、登录、查房、预订、入住、退房状态依次流转金额正确事务回滚两个终端同时订同一房间只有一个成功无超卖并发锁验证退房时手动断开连接数据没有半更新状态文档一致schema.sql 与数据库设计文档逐字段核对无出入我早期做课程设计也把日期存成过 VARCHAR被老师问得哑口无言。后来凡是经手的项目第一眼永远先看表结构和金额类型这两处稳了其他都是顺水推舟的事。这个习惯我一直保持到现在也希望你能在动手写第一行代码之前先把表设计稳下来写完之后再把文档和代码对齐一遍再交出去。希望帮到你。本文还有配套的精品资源点击获取