尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Python+SQLite图书管理系统实战:从数据库设计到命令行应用开发

Python+SQLite图书管理系统实战:从数据库设计到命令行应用开发 1. 项目概述与核心价值最近在整理个人项目时翻出了一个几年前写的图书管理系统用Python实现的。当时是为了练手也为了帮一个开小型社区图书馆的朋友解决点实际问题。没想到这个看似简单的“增删改查”项目在实际跑起来后遇到了不少教科书里不会讲的细节问题。今天我就把这个项目的完整实现思路、核心代码拆解以及那些踩过的坑和优化技巧系统地分享出来。无论你是刚学完Python基础语法正在寻找一个综合性的练手项目来巩固知识还是需要为学校课程设计、毕业设计寻找一个靠谱的模板这篇文章都能给你提供一个从零到一、可直接复现的参考方案。一个图书管理系统核心功能无非就是图书信息的录入、查询、修改、删除以及借阅和归还记录的管理。但正是这个“无非”恰恰是检验我们能否将零散的知识点变量、函数、文件操作、数据库串联成一个完整应用的最佳试金石。通过这个项目你能实战演练如何设计程序结构、如何处理用户输入、如何持久化存储数据以及如何让代码更健壮、易维护。下面我们就抛开那些华而不实的框架用最纯粹的Python标准库和一点SQLite来构建一个在命令行下运行流畅、逻辑清晰的管理系统。2. 系统整体设计与技术选型在动手写代码之前先花点时间想清楚整个系统要怎么组织。这就像盖房子先画图纸能避免后面写到一半推倒重来。2.1 核心需求与功能模块拆解首先我们要明确这个系统需要管理哪些实体以及它们之间的关系。对于一个最基础的图书管理系统至少需要管理两类核心信息图书和借阅记录。图书是静态资源借阅记录是动态行为。基于此我们可以划分出以下几个核心功能模块图书信息管理模块负责图书的“增删改查”。增录入新书包括书名、作者、ISBN、出版社、库存数量等。删根据ISBN或书名等标识删除图书通常需要谨慎可能只做标记而非物理删除。改更新图书信息如修改库存数量、更正作者名等。查提供多种查询方式如按书名模糊查询、按作者查询、按ISBN精确查询。借阅与归还管理模块负责处理借书和还书流程。借阅记录谁读者在什么时间借了哪本书。需要检查图书库存是否大于0。归还记录还书时间并更新图书库存。通常还需要考虑超期处理虽然我们基础版可以先不做计费但要能记录和提示。数据持久化模块所有数据不能只存在内存里程序关闭就没了。我们需要把图书信息和借阅记录保存到硬盘上。2.2 技术选型背后的考量为什么选择Python SQLite这个组合这是基于项目复杂度、学习成本和部署便捷性的综合考量。编程语言Python语法简洁明了标准库强大特别适合快速开发原型和处理各种数据操作。对于初学者来说能把更多精力集中在业务逻辑而非语言特性上。数据库SQLite这是一个嵌入式数据库不需要像MySQL或PostgreSQL那样单独安装和配置一个数据库服务。它的整个数据库就是一个单独的.db文件非常适合小型桌面应用或学习项目。Python标准库中的sqlite3模块提供了完整的支持开箱即用。为什么不直接用文件如JSON、CSV初期学习时用文本文件存储确实更简单。但当数据关系变复杂比如图书和借阅记录需要关联查询、需要保证数据操作的原子性避免同时读写导致数据错乱时文件操作的缺点就暴露了。SQLite虽然轻量但提供了完整的SQL支持、事务处理能更优雅地处理这些问题。为什么不用Django或Flask等Web框架如果目标是做一个带网页前端的系统那当然该用Web框架。但我们当前的目标是深入理解后端业务逻辑和数据流转一个命令行程序能让我们更聚焦于核心。后续如果你想把它改造成Web版今天的核心逻辑几乎可以完全复用。基于以上设计我们系统的架构就清晰了一个基于命令行的主程序循环根据用户输入调用不同的功能函数所有数据通过sqlite3模块与SQLite数据库文件交互。3. 数据库设计与核心表结构数据库是系统的基石设计得好后面编程就顺风顺水设计得不好会处处掣肘。我们这里设计两张核心表。3.1books图书信息表这张表存放所有图书的静态信息。CREATE TABLE IF NOT EXISTS books ( id INTEGER PRIMARY KEY AUTOINCREMENT, -- 主键自增长用于内部关联 isbn TEXT UNIQUE NOT NULL, -- 国际标准书号唯一标识一本图书 title TEXT NOT NULL, -- 书名 author TEXT NOT NULL, -- 作者 publisher TEXT, -- 出版社 publish_date TEXT, -- 出版日期用TEXT存储方便处理 stock INTEGER DEFAULT 1 CHECK (stock 0), -- 库存数量必须非负 created_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP -- 记录创建时间 );字段设计解析与避坑点主键id为什么不用isbn当主键虽然ISBN是唯一的但有些内部资料或旧书可能没有ISBN。设置一个独立的、无意义的自增ID作为主键是数据库设计的常见最佳实践它更稳定且有利于作为外键被其他表引用。isbn字段的UNIQUE约束这确保了同一本ISBN的书不会重复录入。NOT NULL确保每本书都有这个标识即使填“无”。stock字段的CHECK约束这是一个重要的数据完整性保障。数据库层面就强制库存不能为负数避免了程序逻辑漏洞导致库存被借成“-1”的情况。日期存储这里用TEXT类型存储publish_date。SQLite的日期类型比较弱用TEXT存储格式化的日期如‘2023-01-01’在查询和显示时更灵活。created_time使用了CURRENT_TIMESTAMP由数据库自动记录插入时间。3.2borrow_records借阅记录表这张表记录所有的借阅行为。CREATE TABLE IF NOT EXISTS borrow_records ( id INTEGER PRIMARY KEY AUTOINCREMENT, book_id INTEGER NOT NULL, -- 外键关联books表的id borrower_name TEXT NOT NULL, -- 借阅人姓名 borrower_id TEXT, -- 借阅人学号/工号可选 borrow_date TEXT NOT NULL, -- 借出日期 due_date TEXT NOT NULL, -- 应还日期 return_date TEXT, -- 实际归还日期NULL表示未归还 FOREIGN KEY (book_id) REFERENCES books(id) ON DELETE CASCADE );关键设计解析外键关联book_id关联到books.id。这建立了图书和借阅记录的关系。ON DELETE CASCADE表示如果一本书被从books表删除那么所有它的借阅记录也会自动级联删除。在实际应用中你可能更倾向于ON DELETE SET NULL或禁止删除有借阅记录的图书这里为了演示用了级联删除。归还状态判断return_date字段是核心。如果它为NULL表示该书尚未归还如果它有日期值则表示已归还。通过这个字段可以轻松统计当前在借书目。日期字段borrow_date借出日、due_date应还日、return_date归还日都使用TEXT存储。计算借阅时长、判断是否超期都需要在程序逻辑里进行日期字符串的解析和比较。注意SQLite外键支持默认关闭。为了确保外键约束生效我们必须在每次数据库连接后执行一条命令conn.execute(“PRAGMA foreign_keys ON;”)。这是新手极易忽略导致外键失效的坑。4. 核心功能模块的Python实现数据库设计好后我们就可以用Python来搭建桥梁了。我会分模块讲解关键代码并附上详细的注释和注意事项。4.1 数据库连接与初始化首先我们需要一个稳定的方式来连接和初始化数据库。import sqlite3 import os from datetime import datetime, timedelta class DatabaseManager: def __init__(self, db_namelibrary.db): self.db_name db_name self._init_database() def _init_database(self): 初始化数据库创建表如果不存在 conn sqlite3.connect(self.db_name) # 开启外键支持非常重要 conn.execute(PRAGMA foreign_keys ON;) cursor conn.cursor() # 创建books表 cursor.execute( CREATE TABLE IF NOT EXISTS books ( id INTEGER PRIMARY KEY AUTOINCREMENT, isbn TEXT UNIQUE NOT NULL, title TEXT NOT NULL, author TEXT NOT NULL, publisher TEXT, publish_date TEXT, stock INTEGER DEFAULT 1 CHECK (stock 0), created_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ) # 创建borrow_records表 cursor.execute( CREATE TABLE IF NOT EXISTS borrow_records ( id INTEGER PRIMARY KEY AUTOINCREMENT, book_id INTEGER NOT NULL, borrower_name TEXT NOT NULL, borrower_id TEXT, borrow_date TEXT NOT NULL, due_date TEXT NOT NULL, return_date TEXT, FOREIGN KEY (book_id) REFERENCES books(id) ON DELETE CASCADE ) ) conn.commit() conn.close() print(f[初始化] 数据库 {self.db_name} 就绪。) def get_connection(self): 获取一个数据库连接并确保外键开启 conn sqlite3.connect(self.db_name) conn.execute(PRAGMA foreign_keys ON;) return conn实操心得将数据库操作封装成一个类DatabaseManager是很好的实践。它集中管理数据库名称、初始化逻辑并提供统一的连接获取方法。_init_database方法使用IF NOT EXISTS来创建表这样无论程序运行多少次都不会出错。务必记得PRAGMA foreign_keys ON;而且要在每次获取新连接后执行。我曾在早期版本忘了这个导致删除图书时借阅记录还残留排查了半天。4.2 图书信息管理模块实现这个模块对应books表的增删改查。我们为DatabaseManager类添加相关方法。class DatabaseManager: # ... 接上面的 __init__ 和 _init_database 方法 ... def add_book(self, isbn, title, author, publisher, publish_date, stock1): 添加一本新书 conn self.get_connection() cursor conn.cursor() try: cursor.execute( INSERT INTO books (isbn, title, author, publisher, publish_date, stock) VALUES (?, ?, ?, ?, ?, ?) , (isbn, title, author, publisher, publish_date, stock)) conn.commit() print(f[成功] 添加图书《{title}》成功。) return True except sqlite3.IntegrityError: # 捕获唯一性约束错误如ISBN重复 print(f[错误] 添加失败ISBN {isbn} 可能已存在。) return False except Exception as e: print(f[错误] 添加图书时发生未知错误{e}) return False finally: conn.close() def search_books(self, keyword, bytitle): 查询图书。 :param keyword: 查询关键词 :param by: 查询字段可选 title, author, isbn conn self.get_connection() cursor conn.cursor() # 使用LIKE进行模糊查询%用于匹配任意字符 if keyword: if by title: cursor.execute(SELECT * FROM books WHERE title LIKE ?, (%keyword%,)) elif by author: cursor.execute(SELECT * FROM books WHERE author LIKE ?, (%keyword%,)) elif by isbn: # ISBN通常精确查询 cursor.execute(SELECT * FROM books WHERE isbn ?, (keyword,)) else: cursor.execute(SELECT * FROM books WHERE title LIKE ? OR author LIKE ?, (%keyword%, %keyword%)) else: # 如果没有关键词返回所有图书谨慎使用数据多时需分页 cursor.execute(SELECT * FROM books) books cursor.fetchall() conn.close() return books def update_book_stock(self, book_id, delta): 更新图书库存。 :param book_id: 图书ID :param delta: 库存变化量正数表示入库负数表示出库如借出 :return: 成功返回True失败如库存不足返回False conn self.get_connection() cursor conn.cursor() try: # 首先检查当前库存 cursor.execute(SELECT stock FROM books WHERE id ?, (book_id,)) result cursor.fetchone() if not result: print(f[错误] 未找到ID为 {book_id} 的图书。) return False current_stock result[0] new_stock current_stock delta # 利用建表时的CHECK约束如果new_stock0数据库会报错 if new_stock 0: print(f[错误] 库存不足。当前库存{current_stock}尝试减少{-delta}) return False cursor.execute(UPDATE books SET stock ? WHERE id ?, (new_stock, book_id)) conn.commit() print(f[成功] 图书ID {book_id} 库存更新{current_stock} - {new_stock}) return True except sqlite3.IntegrityError as e: # 捕获CHECK约束违反错误 print(f[错误] 库存更新失败可能违反非负约束{e}) conn.rollback() return False finally: conn.close() def delete_book(self, book_id): 根据ID删除图书会级联删除借阅记录 conn self.get_connection() cursor conn.cursor() try: cursor.execute(DELETE FROM books WHERE id ?, (book_id,)) conn.commit() # rowcount 属性返回受影响的行数 if cursor.rowcount 0: print(f[成功] 已删除ID为 {book_id} 的图书及其借阅记录。) return True else: print(f[提示] 未找到ID为 {book_id} 的图书。) return False except Exception as e: print(f[错误] 删除图书时发生错误{e}) return False finally: conn.close()注意事项与技巧参数化查询所有SQL语句中的变量都用?占位符然后将参数以元组形式传入execute()方法。这至关重要它能有效防止SQL注入攻击是编写数据库程序的安全底线。异常处理对sqlite3.IntegrityError违反唯一性、CHECK约束等进行专门捕获给用户更清晰的错误提示。库存更新原子性update_book_stock方法在一个事务内完成“查询当前库存 - 计算新库存 - 更新”的操作。虽然这里没有显式用BEGIN TRANSACTION但单条UPDATE语句本身是原子的。更复杂的业务逻辑可能需要显式事务。模糊查询使用LIKE和%通配符实现模糊查询这是提升用户体验的关键。注意%符号需要在参数外拼接参数本身只传关键词。4.3 借阅与归还管理模块实现这是业务逻辑最集中的部分涉及两张表的联动操作。class DatabaseManager: # ... 接上面的所有方法 ... def borrow_book(self, book_id, borrower_name, borrower_id, borrow_days30): 借出一本书 conn self.get_connection() cursor conn.cursor() try: # 1. 检查图书是否存在且库存0 cursor.execute(SELECT id, title, stock FROM books WHERE id ?, (book_id,)) book cursor.fetchone() if not book: print(f[错误] 未找到ID为 {book_id} 的图书。) return False if book[2] 0: # book[2] 是 stock print(f[错误] 图书《{book[1]}》库存为0无法借出。) return False # 2. 计算借出和应还日期 borrow_date datetime.now().strftime(%Y-%m-%d) due_date (datetime.now() timedelta(daysborrow_days)).strftime(%Y-%m-%d) # 3. 插入借阅记录 cursor.execute( INSERT INTO borrow_records (book_id, borrower_name, borrower_id, borrow_date, due_date) VALUES (?, ?, ?, ?, ?) , (book_id, borrower_name, borrower_id, borrow_date, due_date)) # 4. 更新图书库存减1 # 这里直接调用我们写好的方法它内部有库存检查 if not self.update_book_stock(book_id, -1): # 如果更新库存失败回滚借阅记录插入 raise Exception(更新库存失败借阅操作已回滚。) conn.commit() print(f[成功] 《{book[1]}》 借出给 {borrower_name}应还日期{due_date}。) return True except Exception as e: conn.rollback() # 发生任何错误回滚所有操作 print(f[错误] 借阅失败{e}) return False finally: conn.close() def return_book(self, record_id): 归还一本书 conn self.get_connection() cursor conn.cursor() try: # 1. 查找借阅记录并确认未归还 cursor.execute( SELECT br.id, br.book_id, b.title, br.borrower_name FROM borrow_records br JOIN books b ON br.book_id b.id WHERE br.id ? AND br.return_date IS NULL , (record_id,)) record cursor.fetchone() if not record: print(f[错误] 未找到未归还的借阅记录ID {record_id}或该记录已归还。) return False # 2. 更新归还日期 return_date datetime.now().strftime(%Y-%m-%d) cursor.execute(UPDATE borrow_records SET return_date ? WHERE id ?, (return_date, record_id)) # 3. 更新图书库存加1 if not self.update_book_stock(record[1], 1): # record[1]是book_id raise Exception(更新库存失败归还操作已回滚。) conn.commit() print(f[成功] 《{record[2]}》 已由 {record[3]} 归还。) return True except Exception as e: conn.rollback() print(f[错误] 归还失败{e}) return False finally: conn.close() def list_current_borrows(self): 列出所有未归还的借阅记录 conn self.get_connection() cursor conn.cursor() cursor.execute( SELECT br.id, b.title, b.isbn, br.borrower_name, br.borrow_date, br.due_date FROM borrow_records br JOIN books b ON br.book_id b.id WHERE br.return_date IS NULL ORDER BY br.due_date ASC -- 按应还日期升序排列快逾期的在前 ) records cursor.fetchall() conn.close() return records核心逻辑与避坑指南事务处理borrow_book和return_book方法都包含了多个数据库操作查书、插入记录、更新库存。这些操作必须作为一个整体要么全部成功要么全部失败。我们使用try...except...块在try内执行所有操作一旦出错就在except中调用conn.rollback()回滚成功则conn.commit()提交。这是保证数据一致性的关键。连接管理注意在borrow_book中我们调用了self.update_book_stock而它内部会自己获取新连接。这在事务上下文中是有问题的因为两个独立的连接不在同一个事务里。上面的代码为了清晰展示了逻辑但实际上存在缺陷。正确的做法是将update_book_stock的核心逻辑即执行UPDATE语句提取出来让borrow_book在同一个连接和事务内调用它或者将连接对象作为参数传递。这是一个高级但非常重要的细节。关联查询list_current_borrows使用了JOIN语句将borrow_records表和books表通过book_id关联起来一次性获取图书标题等信息避免了在Python循环中多次查询数据库效率更高。日期处理我们使用Python的datetime模块生成格式化的日期字符串‘YYYY-MM-DD’存入数据库。在比较日期判断是否超期时需要将字符串再解析回datetime对象进行计算。5. 命令行交互界面的搭建有了强大的后端我们需要一个友好的前端来与用户交互。这里我们构建一个简单的文本菜单。class LibraryCLI: def __init__(self): self.db DatabaseManager() # 初始化数据库管理器 def display_menu(self): print(\n *30) print( 图书管理系统) print(*30) print(1. 添加新书) print(2. 查询图书) print(3. 借阅图书) print(4. 归还图书) print(5. 查看在借记录) print(6. 删除图书) print(7. 退出系统) print(*30) def run(self): while True: self.display_menu() choice input(请选择操作 (1-7): ).strip() if choice 1: self._add_book_flow() elif choice 2: self._search_book_flow() elif choice 3: self._borrow_book_flow() elif choice 4: self._return_book_flow() elif choice 5: self._list_borrows_flow() elif choice 6: self._delete_book_flow() elif choice 7: print(感谢使用再见) break else: print(输入无效请重新选择。) def _add_book_flow(self): print(\n--- 添加新书 ---) isbn input(ISBN: ).strip() title input(书名: ).strip() author input(作者: ).strip() publisher input(出版社 (可选): ).strip() publish_date input(出版日期 (YYYY-MM-DD可选): ).strip() stock input(库存数量 (默认1): ).strip() stock int(stock) if stock.isdigit() else 1 self.db.add_book(isbn, title, author, publisher, publish_date, stock) def _search_book_flow(self): print(\n--- 查询图书 ---) print(1. 按书名查询) print(2. 按作者查询) print(3. 按ISBN查询) print(4. 显示全部) sub_choice input(请选择查询方式 (1-4): ).strip() keyword by title if sub_choice in [1, 2, 3]: keyword input(请输入关键词: ).strip() if sub_choice 1: by title elif sub_choice 2: by author elif sub_choice 3: by isbn elif sub_choice ! 4: print(输入无效返回主菜单。) return books self.db.search_books(keyword, by) if books: print(f\n找到 {len(books)} 条记录:) print(-*80) print(f{ID:5}{ISBN:15}{书名:25}{作者:15}{库存:6}) print(-*80) for book in books: print(f{book[0]:5}{book[1]:15}{book[2]:25}{book[3]:15}{book[6]:6}) else: print(未找到相关图书。) def _borrow_book_flow(self): print(\n--- 借阅图书 ---) book_id_input input(请输入要借阅的图书ID: ).strip() if not book_id_input.isdigit(): print(图书ID必须是数字。) return book_id int(book_id_input) borrower_name input(借阅人姓名: ).strip() if not borrower_name: print(借阅人姓名不能为空。) return borrower_id input(借阅人学号/工号 (可选): ).strip() self.db.borrow_book(book_id, borrower_name, borrower_id) def _return_book_flow(self): print(\n--- 归还图书 ---) # 可以先显示未归还记录方便用户选择 records self.db.list_current_borrows() if not records: print(当前没有在借记录。) return print(当前在借记录:) for r in records: print(f 记录ID: {r[0]}, 图书: 《{r[1]}》, 借阅人: {r[3]}, 应还: {r[5]}) record_id_input input(\n请输入要归还的记录ID: ).strip() if not record_id_input.isdigit(): print(记录ID必须是数字。) return self.db.return_book(int(record_id_input)) def _list_borrows_flow(self): print(\n--- 当前在借记录 ---) records self.db.list_current_borrows() if records: for r in records: # 简单计算是否超期 due_date datetime.strptime(r[5], %Y-%m-%d) today datetime.now().date() status 已超期 if due_date.date() today else print(fID:{r[0]} 《{r[1]}》(ISBN:{r[2]}) - {r[3]}借于{r[4]}应还{r[5]}{status}) else: print(暂无在借记录。) def _delete_book_flow(self): print(\n--- 删除图书 ---) book_id_input input(请输入要删除的图书ID: ).strip() if not book_id_input.isdigit(): print(图书ID必须是数字。) return confirm input(f确认删除ID为 {book_id_input} 的图书吗此操作将同时删除其所有借阅记录(y/N): ).strip().lower() if confirm y: self.db.delete_book(int(book_id_input)) else: print(操作已取消。) if __name__ __main__: cli LibraryCLI() cli.run()用户体验优化点输入验证对用户输入的ID进行了简单的.isdigit()检查防止程序因类型错误而崩溃。确认机制在删除操作前要求二次确认防止误操作。信息展示查询结果以对齐的表格形式打印更直观。在归还和查看借阅时会先列出相关信息辅助用户操作。流程清晰每个功能都是一个独立的_flow方法主循环run非常简洁。这种结构易于维护和扩展。6. 常见问题、调试技巧与进阶优化在实际编写和运行过程中你肯定会遇到各种问题。这里我总结几个常见的坑和解决办法。6.1 数据库连接与并发问题问题在多线程或未来改造成Web应用时多个操作同时访问数据库可能导致“数据库被锁定”错误。解决方案SQLite的默认模式是每个连接独立事务写操作会锁定数据库。对于简单的命令行程序这没问题。但如果需要并发可以考虑使用sqlite3的isolation_level参数或显式管理事务。更重要的确保及时关闭连接。我们的代码在每个数据库方法最后都调用了conn.close()这是好习惯。也可以使用with sqlite3.connect(...) as conn:上下文管理器自动关闭。对于高并发场景SQLite可能不是最佳选择应考虑MySQL或PostgreSQL。6.2 日期处理的陷阱问题我们直接用字符串存日期在查询“某个月份的借阅记录”或计算“超期天数”时需要手动解析和计算容易出错。优化方案可以使用SQLite的DATE函数如果日期字符串是标准格式YYYY-MM-DD例如WHERE due_date DATE(now)查找超期记录。在Python中使用datetime.strptime()解析字符串时要做好异常处理因为用户输入可能格式错误。考虑在borrow_book时将borrow_date和due_date直接存储为SQLite的DATE类型实际上仍是TEXT但SQLite会识别方便使用日期函数。# 示例使用SQLite日期函数查询超期记录 cursor.execute( SELECT br.id, b.title, br.borrower_name, br.due_date, JULIANDAY(now) - JULIANDAY(br.due_date) as overdue_days FROM borrow_records br JOIN books b ON br.book_id b.id WHERE br.return_date IS NULL AND br.due_date DATE(now) )6.3 事务处理的正确姿势前面提到borrow_book中调用另一个方法导致事务分离的问题。这里给出一个修正方案将核心的库存更新操作提取为类内部的一个“私有”方法它接收一个数据库游标对象。def _update_stock_with_cursor(self, cursor, book_id, delta): 内部方法使用提供的游标更新库存用于在外部事务中调用 cursor.execute(SELECT stock FROM books WHERE id ?, (book_id,)) result cursor.fetchone() if not result: raise ValueError(fBook ID {book_id} not found.) current_stock result[0] new_stock current_stock delta if new_stock 0: raise ValueError(fInsufficient stock. Current: {current_stock}, required: {-delta}) cursor.execute(UPDATE books SET stock ? WHERE id ?, (new_stock, book_id)) return new_stock def borrow_book(self, book_id, borrower_name, borrower_id, borrow_days30): 修正后的借书方法保证事务原子性 conn self.get_connection() cursor conn.cursor() try: # ... 检查图书存在性和库存的代码 ... # 插入借阅记录 cursor.execute( INSERT INTO borrow_records (book_id, borrower_name, borrower_id, borrow_date, due_date) VALUES (?, ?, ?, ?, ?) , (book_id, borrower_name, borrower_id, borrow_date, due_date)) # 使用内部方法传入同一个游标 self._update_stock_with_cursor(cursor, book_id, -1) conn.commit() # ... 成功提示 ... return True except Exception as e: conn.rollback() print(f[错误] 借阅失败{e}) return False finally: conn.close()6.4 如何扩展这个系统这个基础版本已经具备了核心功能。你可以在此基础上进行丰富用户认证增加users表实现管理员和普通读者的登录、权限控制。更复杂的查询实现组合查询同时按作者和书名查、分页显示LIMIT和OFFSET。数据统计增加统计功能如最受欢迎图书、借阅排行榜、超期率分析等这需要编写更复杂的SQL聚合查询。图形界面使用Tkinter、PyQt或Kivy为程序制作一个桌面GUI或者用Flask/Django快速搭建一个Web管理后台。数据导出增加将图书目录或借阅记录导出为Excel或PDF的功能可使用openpyxl或reportlab库。从零开始实现一个完整的项目最大的收获不是敲了多少行代码而是学会了如何将需求分解成模块如何设计数据存储如何处理异常和边界情况以及如何让代码结构清晰、易于维护。这个图书管理系统项目就像一块很好的敲门砖掌握了它你再去学习Web开发、桌面开发或者更复杂的业务系统都会发现底层逻辑是相通的。编程的乐趣就在于看到自己设计的逻辑在代码中流畅运行并真正解决实际问题。
返回列表