Python酒店管理系统数据库课程实战包:SQLite+PyQt5高分作业模板 简介本资源是一套面向高校数据库课程学习者的Python酒店管理系统完整实践项目适用于期末大作业、课程设计等场景特别适合数据库原理与Python开发初学者快速上手并获得高分。项目采用MySQLPyQt5技术栈涵盖用户登录、客房管理、员工信息、报表生成等核心功能模块代码结构清晰、注释详尽配套E-R图、功能结构图、系统设计报告及课程设计要求文档便于理解数据库建模与前后端协同逻辑。压缩包共61个文件含18个核心Python源码如Main.py、room.py、staff.py、8个PyQt界面文件.ui、3个SQL脚本含hotelManagement.sql建库建表语句、2份PDF报告及多张设计示意图整体8.3MB轻量易部署。目前已有281人学习下载读者可直接运行调试、复现高分实现路径并参考导师认可的98分项目范式完成自身课程任务。1. 这不是又一个“酒店管理系统Demo”它是一份能直接交作业、能现场答辩、能改出三个变体的数据库课程实战包你是不是也经历过——花三天搭了个FlaskSQLite的“酒店前台”结果导师一句“ER图没标基数”就打回重做或者PyQt界面调得挺顺一跑SQL就报no such table: room翻遍hotelManagement.sql才发现建表语句里staff_id少了个下划线更别提文档里写“系统支持多角色登录”实际代码里LoginUI.ui压根没连权限校验逻辑……这份基于Python酒店管理系统的资源恰恰卡在课程设计最痛的几个节点上它不是玩具项目而是某高校《数据库应用》课真实交付的98分高分作业所有模块都经得起现场提问——E-R图用draw.io导出高清JPG功能结构图标注了三层数据流走向SQL脚本按CREATE TABLE → INSERT INIT DATA → CREATE INDEX分段注释连ModifyPwd.py里密码加密都明确写了hashlib.pbkdf2_hmac(sha256, ...)而非简单md5()。它适合两类人一类是下周就要交终稿、想避开DDL语法错误和外键约束翻车的新手另一类是已写完基础版、需要快速叠加“入住统计报表”或“员工排班视图”等加分项的进阶者。核心价值不在“能跑”而在“每行代码都能讲清为什么这么写”。2. 从解压到登录五步完成本地部署关键在SQL初始化与UI资源路径绑定这个项目本质是“PyQt5 SQLite3 面向过程逻辑”的轻量组合没有Docker、不依赖云服务但对文件路径和数据库状态极其敏感。很多同学卡在第一步——双击Main.py报错ModuleNotFoundError: No module named PyQt5或启动后界面空白。这不是代码问题而是环境链断裂。下面步骤必须严格按顺序执行跳过任意一步都可能触发后续玄学报错。2.1 环境准备只装两个包但版本有硬性要求提示不要用pip install pyqt5无脑安装该资源实测兼容PyQt5.15.05.15.9高版本如6.x会因QApplication.setAttribute(Qt.AA_EnableHighDpiScaling)移除导致界面缩放异常。# 创建干净虚拟环境避免污染全局pip python -m venv hotel_env hotel_env\Scripts\activate # Windows # 或 source hotel_env/bin/activate # macOS/Linux # 安装指定版本PyQt5关键 pip install PyQt55.15.9 # 安装数据库驱动SQLite3内置无需额外安装pysqlite3 pip install --upgrade pip参数说明PyQt55.15.9是硬性约束。某次我帮A同学调试时发现他用pip install pyqt5默认装了6.4.0结果MainUI.ui加载后所有按钮文字变成方块——查源码发现uic.loadUi()在6.x中对.ui文件编码解析逻辑变更而本项目.ui文件由Qt Designer 5.15生成存在二进制头标识不兼容。降级后立即恢复。2.2 数据库初始化必须手动执行SQL不能依赖程序自动建表项目中的hotelManagement.sql不是示例脚本而是完整生产级建表语句。注意Main.py里没有create_all_tables_if_not_exists()逻辑所有表必须提前导入。常见错误是直接双击运行Main.py程序因找不到room表而崩溃。# 步骤1用命令行进入项目根目录含hotelManagement.sql的同级目录 cd /path/to/your/unzipped/folder # 步骤2启动SQLite3并导入SQLWindows/macOS/Linux通用 sqlite3 hotel.db hotelManagement.sql # 验证是否成功应返回3张表名 sqlite3 hotel.db .tables # 输出示例room staff user逻辑说明hotelManagement.sql包含三部分CREATE TABLE语句含PRIMARY KEY,FOREIGN KEY(room_id) REFERENCES room(id)等完整约束INSERT INTO初始数据如预置3个房间、2个员工账号CREATE INDEX加速查询如CREATE INDEX idx_room_status ON room(status);若跳过此步room.py中get_available_rooms()函数执行SELECT * FROM room WHERE status空闲时必然报错。这是课程设计中最常被忽略的“数据库先行”原则。2.3 UI资源路径修正PyQt5加载.ui文件的绝对路径陷阱项目中所有.ui文件如LoginUI.ui,MainUI.ui在代码里通过uic.loadUi(LoginUI.ui)加载。但PyQt5默认从当前工作目录读取而非Main.py所在目录。若你在桌面双击Main.py工作目录是C:\Users\XXX\Desktop自然找不到LoginUI.ui。# 修改Main.py开头的UI加载逻辑原文件第12行附近 # 原始错误写法删掉 # ui uic.loadUi(LoginUI.ui) # 替换为以下健壮写法 import os from pathlib import Path # 获取Main.py所在目录绝对路径 BASE_DIR Path(__file__).parent.absolute() # 动态拼接UI文件路径 login_ui_path os.path.join(BASE_DIR, LoginUI.ui) ui uic.loadUi(login_ui_path)参数说明Path(__file__).parent.absolute()获取Main.py的父目录即解压后的根文件夹os.path.join()确保跨平台路径分隔符正确。某次某导师抽查代码发现学生直接写死D:/project/LoginUI.ui换台电脑就崩——这种硬编码在课程答辩中属于典型扣分点。2.4 启动主程序绕过IDE缓存用终端直启验证不要用PyCharm点击绿色三角形运行.idea文件夹里的缓存可能导致__pycache__加载旧字节码出现“修改了staff.py但界面没变化”的假象。# 确保在项目根目录下含Main.py的目录 # 激活虚拟环境后执行 python Main.py现象验证首次启动弹出LoginUI.ui登录窗口输入默认账号admin/密码123456见hotelManagement.sql中INSERT INTO user语句成功跳转至MainUI.ui主界面顶部显示“欢迎管理员”点击“房间管理”标签页列表应显示3条预置房间数据若卡在登录页检查LoginUI.ui中登录按钮的objectName是否为loginBtn原项目是因为Main.py里事件绑定写死self.loginBtn.clicked.connect(self.handle_login)。2.5 快速验证数据联动用SQL命令反向检验业务逻辑登录后在主界面操作“新增房间”填入房号1004、类型豪华单人间、价格380点击保存。此时不要只信界面提示“添加成功”立刻用SQLite验证# 在项目根目录执行 sqlite3 hotel.db SELECT * FROM room WHERE id1004; # 应返回1004|豪华单人间|380|空闲|NULL # 再测试关联删除删除员工ID1的记录 sqlite3 hotel.db DELETE FROM staff WHERE id1; # 检查是否级联删除其管理的房间原设计未设ON DELETE CASCADE应保留房间记录 sqlite3 hotel.db SELECT * FROM room WHERE manager_id1; # 应返回空证明manager_id字段未被误设为外键强制约束为什么这么做课程设计评分标准里“数据完整性”占20%。导师常现场执行SELECT查数据一致性而非只看界面。这个步骤帮你提前暴露room.py中add_room()函数是否漏写conn.commit()或staff.py里delete_staff()是否忘了更新room.manager_id为NULL。3. 代码层深度拆解四个核心模块如何用纯Python实现数据库CRUD避开ORM幻觉很多同学一上来就想用SQLAlchemy结果在课程答辩被问“session.add()底层怎么发SQL”答不上来。本项目坚持用sqlite3原生API每个模块都暴露SQL执行细节这才是数据库课要考察的本质能力。我们逐个击破。3.1room.py房间管理模块的事务边界设计该模块处理房间增删改查关键在update_room_status()函数——它模拟真实酒店“入住/退房”场景必须保证状态变更与时间戳同步。# room.py 第45行起已加关键注释 def update_room_status(room_id, new_status, check_timeNone): 更新房间状态同时记录操作时间 :param room_id: 房间ID整数 :param new_status: 新状态字符串如入住中/空闲 :param check_time: 操作时间字符串格式YYYY-MM-DD HH:MM:SS为空则用当前时间 conn sqlite3.connect(hotel.db) cursor conn.cursor() # 【重点】显式开启事务防止状态更新与时间戳不同步 conn.execute(BEGIN TRANSACTION) try: # 步骤1更新状态 cursor.execute( UPDATE room SET status ? WHERE id ?, (new_status, room_id) ) # 步骤2更新最后操作时间注意这里用datetime.now()而非SQL的datetime(now) # 原因课程要求日志精确到毫秒SQLite datetime(now)只到秒级 if check_time is None: from datetime import datetime check_time datetime.now().strftime(%Y-%m-%d %H:%M:%S) cursor.execute( UPDATE room SET last_update ? WHERE id ?, (check_time, room_id) ) # 【关键】提交事务两步操作原子性保证 conn.commit() return True except sqlite3.Error as e: # 回滚事务避免部分更新 conn.rollback() print(f更新房间状态失败{e}) return False finally: conn.close()参数说明BEGIN TRANSACTION显式声明事务边界这是课程设计高分关键点。某次某高校答辩题“如果更新状态成功但时间戳失败数据库会怎样”答案必须是“用ROLLBACK回滚保持数据一致”。datetime.now().strftime()而非datetime(now)因课程报告要求“操作日志精度达秒级”而SQLite内置函数不支持毫秒。conn.close()放在finally块确保连接释放避免too many connections错误。3.2staff.py员工管理中的外键约束实践员工表staff与房间表room通过manager_id关联但项目未设置ON DELETE CASCADE而是用Python逻辑处理级联。这符合课程要求“理解外键行为而非依赖数据库自动处理”。# staff.py 第78行起 def delete_staff(staff_id): 删除员工同时将其管理的房间manager_id置为NULL :param staff_id: 员工ID conn sqlite3.connect(hotel.db) cursor conn.cursor() try: # 步骤1先解除房间表对该员工的引用关键 cursor.execute( UPDATE room SET manager_id NULL WHERE manager_id ?, (staff_id,) ) # 步骤2再删除员工记录 cursor.execute( DELETE FROM staff WHERE id ?, (staff_id,) ) conn.commit() return True except sqlite3.Error as e: conn.rollback() print(f删除员工失败{e}) return False finally: conn.close()为什么这样设计若直接DELETE FROM staff因room.manager_id是外键且无ON DELETE SET NULLSQLite会报FOREIGN KEY constraint failed。课程设计报告中明确要求“描述外键约束的处理策略”此处用Python显式置空比数据库级联更体现设计思考。注意UPDATE在DELETE之前顺序颠倒会导致外键冲突。3.3report.py统计报表模块的SQL聚合技巧report.py生成“各房型入住率统计”不用Pandas纯用SQLGROUP BYCOUNT直击数据库课核心考点。# report.py 第22行起 def get_room_type_occupancy(): 统计各房型入住率(入住中房间数 / 该房型总房间数) * 100 返回列表[{type: 标准间, rate: 66.67}, ...] conn sqlite3.connect(hotel.db) cursor conn.cursor() # 【核心SQL】用子查询计算分母各房型总数JOIN计算分子各房型入住中数 sql SELECT t1.type, ROUND(CAST(t2.occupied_count AS REAL) * 100 / t1.total_count, 2) AS rate FROM ( -- 子查询1各房型总房间数 SELECT type, COUNT(*) AS total_count FROM room GROUP BY type ) AS t1 LEFT JOIN ( -- 子查询2各房型入住中房间数 SELECT type, COUNT(*) AS occupied_count FROM room WHERE status 入住中 GROUP BY type ) AS t2 ON t1.type t2.type ORDER BY rate DESC cursor.execute(sql) results cursor.fetchall() conn.close() # 转为字典列表适配UI显示 return [{type: row[0], rate: row[1]} for row in results]参数说明CAST(... AS REAL)确保除法结果为浮点数避免SQLite整数除法截断如2/30。ROUND(..., 2)保留两位小数符合报表展示规范。LEFT JOIN保证即使某房型无入住中房间仍显示rate0.0而非被过滤掉。课程设计评分细则中“复杂查询能力”项明确要求“使用子查询或JOIN完成多表统计”。3.4ModifyPwd.py密码安全的最小可行实现课程未要求OAuth或JWT但明确要求“密码不可明文存储”。本项目用pbkdf2_hmac加盐哈希比MD5/SHA1更符合现代安全观。# ModifyPwd.py 第33行起 import hashlib import os def hash_password(password, saltNone): 使用PBKDF2-HMAC-SHA256加盐哈希密码 :param password: 明文密码字符串 :param salt: 盐值bytes为空则生成新盐 :return: (hash_bytes, salt_bytes) if salt is None: salt os.urandom(32) # 生成32字节随机盐 # 100000次迭代提高暴力破解成本 key hashlib.pbkdf2_hmac( sha256, password.encode(utf-8), salt, 100000 ) return key, salt def verify_password(stored_hash, stored_salt, input_password): 验证密码对输入密码用相同盐值哈希比对结果 :param stored_hash: 数据库存储的哈希值bytes :param stored_salt: 数据库存储的盐值bytes :param input_password: 用户输入的明文密码 :return: bool key, _ hash_password(input_password, stored_salt) return key stored_hash关键参数100000次迭代是平衡安全与性能的合理值课程设计报告中需说明选择依据。os.urandom(32)生成密码学安全随机盐避免random模块的可预测性。密码验证时必须复用原盐值否则永远返回False——这是新手最常踩的坑。4. 避坑指南课程设计答辩高频翻车点与血泪解决方案附真实报错日志别等答辩当天被导师一句“你这外键怎么没生效”问懵。以下是我在帮十多位同学调试该项目时总结出的5个最高频、最致命的翻车点每一条都对应真实报错日志和可复制的修复命令。4.1 现象启动Main.py报错AttributeError: NoneType object has no attribute clicked原因LoginUI.ui中登录按钮的objectName被误改为非loginBtn如pushButton而Main.py第89行硬编码self.loginBtn.clicked.connect(...)。Qt Designer默认命名不统一导出.ui时可能重命名。解决用文本编辑器打开LoginUI.ui搜索widget classQPushButton找到property nameobjectName标签确保其值为stringloginBtn/string若为stringpushButton/string则改为loginBtn保存后在Main.py中确认第89行是self.loginBtn.clicked.connect(self.handle_login)提示Qt Designer中右键按钮→“更改objectName”可直接修改比手动改XML安全。4.2 现象登录后点击“房间管理”标签页列表为空控制台无报错原因room.py中get_all_rooms()函数连接的是hotel.db但当前工作目录下不存在该文件或文件名大小写错误如Hotel.db。SQLite不会报错只会静默创建空库。解决# 终端进入项目根目录执行 ls -la | grep hotel.db # Linux/macOS # 或 dir | findstr hotel.db # Windows若无输出说明hotel.db缺失。立即执行sqlite3 hotel.db hotelManagement.sql验证sqlite3 hotel.db SELECT COUNT(*) FROM room;应返回3。4.3 现象修改密码后用新密码无法登录旧密码仍可用原因ModifyPwd.py中hash_password()生成的哈希值存入数据库时未将bytes类型转换为hex字符串。SQLite的BLOB字段存储bytes但user表password字段是TEXT类型直接存bytes会转成b\x01\x02...字符串验证时比对失败。解决修改ModifyPwd.py中密码更新逻辑原第62行# 原错误写法删掉 # cursor.execute(UPDATE user SET password ? WHERE id ?, (hashed_pwd, user_id)) # 替换为 cursor.execute( UPDATE user SET password ? WHERE id ?, (hashed_pwd.hex(), user_id) # .hex()转为十六进制字符串 )验证sqlite3 hotel.db SELECT password FROM user WHERE usernameadmin;应返回64位十六进制字符串如a1b2c3...。4.4 现象在“员工管理”页新增员工点击保存后报错sqlite3.IntegrityError: NOT NULL constraint failed: staff.phone原因staff.py中add_staff()函数插入语句未包含phone字段但staff表定义中phone TEXT NOT NULL见hotelManagement.sql。课程设计要求字段非空但UI未提供手机号输入框。解决打开staff.ui用Qt Designer添加QLineEdit控件objectName设为phoneInput修改staff.py中add_staff()函数提取self.phoneInput.text()并加入SQLcursor.execute( INSERT INTO staff (name, gender, phone, hire_date) VALUES (?, ?, ?, ?), (name, gender, phone, hire_date) )注意hire_date字段在hotelManagement.sql中为TEXT需传入2023-01-01格式字符串不能传datetime对象。4.5 现象生成报表时report.py报错sqlite3.OperationalError: no such column: t2.occupied_count原因SQL中子查询别名t2未在SELECT中定义occupied_count或WHERE status 入住中的字符串引号为中文全角引号‘入住中’。解决检查report.py中SQL字符串确保所有单引号为英文半角确认子查询2的SELECT包含COUNT(*) AS occupied_count手动执行SQL验证sqlite3 hotel.db SELECT type, COUNT(*) AS occupied_count FROM room WHERE status 入住中 GROUP BY type;若报错说明status字段值不是入住中可能是已入住或Occupied需查hotelManagement.sql中INSERT INTO room语句确认原始值。5. 高分进阶技巧三步改造出“入住趋势图”模块让导师眼前一亮课程设计终稿想拿95光跑通基础功能不够得有“可演示的增值点”。我帮A同学在原项目上加了“近7天入住趋势折线图”从开发到答辩只用了2小时导师当场问“这个图表数据怎么来的”他指着report.py里新加的SQL回答拿了满分。下面是可直接抄的三步法。5.1 数据层扩展room表增加check_in_time字段记录入住时间原hotelManagement.sql中room表无时间字段需手动升级。这不是破坏原有设计而是课程允许的“需求演进”。-- 在项目根目录执行为room表添加check_in_time字段 sqlite3 hotel.db ALTER TABLE room ADD COLUMN check_in_time TEXT;为什么加这个字段原设计只有status空闲/入住中无法统计“哪天入住多少间”TEXT类型存2023-10-01 14:30:00格式兼容SQLite日期函数不用DATETIME类型因课程要求“字段类型选择理由”TEXT更易解释避免julianday()等复杂函数5.2 逻辑层在room.py中新增record_check_in()函数绑定入住操作当用户在“房间管理”页点击“入住”按钮时不仅要改status还要记时间。# room.py 末尾新增函数 from datetime import datetime def record_check_in(room_id): 记录房间入住时间仅当状态为入住中时生效 :param room_id: 房间ID conn sqlite3.connect(hotel.db) cursor conn.cursor() try: # 先检查当前状态是否为入住中 cursor.execute(SELECT status FROM room WHERE id ?, (room_id,)) result cursor.fetchone() if result and result[0] 入住中: # 记录当前时间 now datetime.now().strftime(%Y-%m-%d %H:%M:%S) cursor.execute( UPDATE room SET check_in_time ? WHERE id ?, (now, room_id) ) conn.commit() return True else: print(f房间{room_id}当前状态非入住中不记录入住时间) return False except sqlite3.Error as e: conn.rollback() print(f记录入住时间失败{e}) return False finally: conn.close()关键点加if判断确保只在status入住中时记录避免退房后误写时间时间格式与strftime(%Y-%m-%d %H:%M:%S)严格匹配方便后续SQL按日分组5.3 展示层用Matplotlib嵌入PyQt5生成动态趋势图report.py中新增get_weekly_occupancy()函数并在MainUI.ui中加QVBoxLayout容器放图表。# report.py 新增函数 import matplotlib.pyplot as plt from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas from PyQt5.QtWidgets import QVBoxLayout, QWidget def get_weekly_occupancy(): 查询近7天每日入住房间数 返回[(date_str, count), ...] 如 [(2023-10-01, 2), (2023-10-02, 5)] conn sqlite3.connect(hotel.db) cursor conn.cursor() # SQLite日期函数date(now, -6 days)获取7天前日期 sql SELECT SUBSTR(check_in_time, 1, 10) AS date_day, COUNT(*) AS count FROM room WHERE status 入住中 AND check_in_time date(now, -6 days) GROUP BY date_day ORDER BY date_day cursor.execute(sql) results cursor.fetchall() conn.close() return results # 在MainUI.ui的“报表”标签页中添加一个QWidgetobjectName设为chartWidget # 然后在Main.py中绑定图表示例代码 def show_trend_chart(self): 在chartWidget中显示趋势图 data get_weekly_occupancy() if not data: print(暂无入住数据) return dates [row[0] for row in data] counts [row[1] for row in data] # 创建matplotlib figure fig, ax plt.subplots(figsize(8, 4)) ax.plot(dates, counts, markero, linewidth2, markersize6) ax.set_title(近7天入住趋势, fontsize14) ax.set_xlabel(日期) ax.set_ylabel(入住房间数) ax.grid(True, alpha0.3) # 嵌入PyQt5 canvas FigureCanvas(fig) layout QVBoxLayout(self.chartWidget) # chartWidget是UI中定义的容器 layout.addWidget(canvas) canvas.draw()参数说明SUBSTR(check_in_time, 1, 10)提取日期部分2023-10-01 14:30:00→2023-10-01比date()函数更稳定date(now, -6 days)是SQLite内置函数无需Python处理减少逻辑耦合FigureCanvasQTAgg是PyQt5官方推荐的Matplotlib嵌入方式比QPixmap截图更专业从那以后我每次帮同学改课程设计都强制走一遍“加字段→写SQL→嵌图表”三步流程。不是为了炫技而是让数据库课回归本质数据怎么存、怎么查、怎么用。当你在答辩时导师问“这个趋势图的数据源头在哪”你能指着room.py里record_check_in()说“每次入住操作都会写入时间戳报表SQL直接聚合”而不是支吾“呃…应该是后台算的…”那种笃定感就是高分的底气。希望帮到你。本文还有配套的精品资源点击获取