
简介一套基于Python与SQL Server开发的Web图书管理系统完整课程设计源码包主要面向高校计算机专业需要完成数据库或Web开发课设的学生。系统参考学校图书馆借阅流程包含学生/教师借阅端与管理人员后台借阅者可执行登录、借书、还书、延期等操作管理端覆盖用户管理、图书增删改下架、超级管理员查看操作记录等模块能够完整演示图书管理业务。资源包共183个文件、约11.63MB文件类型涵盖Python后端脚本py/pyd、HTML/CSS/JS前端页面、png图片、xmind思维导图以及txt/docx等说明文件其中pyd和dll属于运行依赖html/js/css构成可视化界面xmind与docx可辅助理解项目结构。已有701人学习/下载适合参考其数据库设计、借阅流程控制与权限管理思路解压后对照目录结构即可开展学习和二次开发。1. 图书管理系统课程设计一套能直接跑通的 Python SQL Server Web 方案如果你正在为数据库课程设计头疼题目又正好是“图书管理系统”建议先看这套基于 Python SQL Server 的 Web 实现。它用 Flask 写后端、SQL Server 存业务数据、Bootstrap 搭界面把学校图书馆的借阅流程——登录、借书、还书、延期、管理员维护——做成了能在浏览器里点击演示的网页而不是控制台里输命令的作业。管理端还区分了普通管理员和超级管理员自带操作日志正好覆盖课程设计里“三层角色”的验收要求。适合正在赶课设、想少踩坑的学生也适合想补一遍 Python SQL Server 实操的从业者。下面把选型理由、数据库设计、核心代码、启动步骤和典型故障依次讲完。2. 架构与数据模型四张表搞定借阅、用户与日志三条业务线2.1 Web 框架选型课程设计场景为什么推荐 Flask课程设计和真实项目不一样你要做的东西必须能演示而且答辩老师一定会追问“这段代码是你写的吗”“这个表为什么这么设计”。所以选型的第一原则不是功能多而是你自己讲得清楚。Flask 的优势在于路由直观、模板简单、不强制你用 ORM。整个后端逻辑可以集中在 app.py 里数据库操作直接用 SQL 语句写答辩时老师问“你的 where 条件怎么写的”你直接打开文件指给他看就行。相比之下 Django 自带 Admin 后台确实省事但“你自己写的东西”变少老师追问业务逻辑时反而容易发虚。前端部分项目用的是 Bootstrap它只负责页面样式和组件不需要额外学前端框架对课设来说足够。还有一点很多人忽略Flask 的 session 机制和路由装饰器非常适合演示“登录后才能操作”“管理员才能进管理页”这类权限控制代码量小判断逻辑一目了然。这套结构基本就是课设的标准答案一个 app.py 做入口templates 目录放页面模板static 目录放 Bootstrap 静态资源再加一个 db.py 封装数据库连接。2.2 数据模型设计四张表覆盖所有业务线整个系统可以拆成三条业务线用户身份与权限、图书档案、借阅流转。再加上审计需求就是四张表。下面把核心字段和设计理由过一遍。表名核心字段说明usersuser_id, username, password, real_name, role, status学生/老师/管理员/超级管理员共用一个表booksbook_id, isbn, title, author, publisher, category, total_count, available_count, statusavailable_count 是冗余字段borrow_recordsrecord_id, user_id, book_id, borrow_date, due_date, return_date, renew_count, operator_id借阅与还书记录operation_logslog_id, user_id, action, detail, created_at管理员操作审计users 表用 role 字段区分四类角色student、teacher、admin、super_admin而不是把学生表和老师表分开建。原因很简单——课设阶段多一张表就多一倍联表查询和权限判断而学生和老师借书行为其实完全一致分开没意义。password 字段存的是哈希值不是明文我习惯用 SHA256 加固定盐答辩时问“密码怎么存的”这一下就能加分。books 表里专门留了 available_count 可借数量字段。为什么不用SELECT COUNT(*) FROM borrow_records WHERE book_id? AND return_date IS NULL现算因为借书操作非常频繁每次借出都全表统计一次不仅慢高峰期还有超借风险。直接UPDATE books SET available_count available_count - 1 WHERE available_count 0一行 SQL 就搞定原子性也有保障。borrow_records 是业务核心三个日期字段是关键borrow_date 借出时间、due_date 应还时间、return_date 实际归还时间。return_date 为空代表还没还逾期不另存状态直接比较 due_date 和当前时间就能算出来。renew_count 记录续借次数限制只能续一次。operator_id 记录是哪个管理员操作的方便日志追溯。2.3 借阅状态机借出、还书、延期的流转规则借阅流程本质是一个状态机但这里不建议用单独的 status 字段去存“借出中”“已逾期”“已归还”这些状态因为这样会导致两个地方维护状态很容易不一致。更可靠的做法是状态全部由字段组合计算出来。业务状态判定条件在架可借books.status1 AND available_count 0借出中borrow_records.return_date IS NULL已逾期due_date GETDATE() AND return_date IS NULL已归还return_date IS NOT NULL借书的约束在课设里通常要覆盖三条书必须在架且有可借库存读者不能有逾期未还的旧记录同一本书不能重复借出未还。还书则把 return_date 置为当前时间同时库存加一。延期申请必须满足“未归还、未续借过”然后把 due_date 往后加 30 天renew_count 置为 1。这样设计有个实际好处数据冗余少逻辑集中。比如“哪些人逾期了”这个需求一条SELECT ... WHERE return_date IS NULL AND due_date GETDATE()就查出来不用额外维护逾期表。课程设计答辩时老师问“逾期怎么处理”你把这个 SQL 一亮整个设计思路就立住了。3. 核心功能落地登录、借书、还书、延期的关键代码与事务写法3.1 登录与会话三种角色如何共用一套校验逻辑登录是入口也是权限控制的基础。Flask 里可以用 session 来维持用户状态登录成功把 user_id 和 role 写进 session后续每个需要权限的页面再校验。密码校验的细节值得认真写数据库里存的是哈希比对哈希而不是比对明文。app.route(/login, methods[GET, POST]) def login(): if request.method POST: username request.form[username] password request.form[password] # 注意密码统一用 sha256 盐哈希后比对库里不要存明文 hashed hashlib.sha256((password SALT).encode(utf-8)).hexdigest() cur.execute( SELECT user_id, real_name, role FROM users WHERE username? AND password? AND status1, (username, hashed) ) row cur.fetchone() if row: session[user_id] row[0] session[role] row[1] return redirect(/dashboard) return render_template(login.html, error用户名或密码错误) return render_template(login.html)这段代码有两个细节建议答辩时主动讲一是用了参数化查询?占位符由驱动处理转义是防 SQL 注入的标准做法二是登录时把 status1 加进查询条件被停用的用户直接进不来比登录成功后再单独判断要少一次查询。SALT 建议写死在配置常量里课设阶段不必引入环境变量管理。3.2 借书与还书库存扣减与逾期计算的 SQL 写法借书不能只写一条插入语句就完事必须按“查—验—写”三步走。先查图书是否在架且有库存再查读者是否符合借阅条件最后才写借阅记录并扣库存。这三步之间会有并发风险所以要用事务包起来。app.route(/borrow, methods[POST]) def borrow(): user_id session.get(user_id) book_id request.form[book_id] try: # 第1步查书是否可借这里连带锁行避免并发超借 cur.execute( SELECT title, available_count FROM books WITH (UPDLOCK) WHERE book_id? AND status1, (book_id,) ) book cur.fetchone() if not book or book[1] 0: return 该书不存在或库存为 0 # 第2步查读者是否有逾期未还有则拦截 cur.execute( SELECT COUNT(*) FROM borrow_records WHERE user_id? AND return_date IS NULL AND due_date GETDATE(), (user_id,) ) if cur.fetchone()[0] 0: return 你有逾期未还图书请先归还 # 第3步插入借阅记录应还日期默认 30 天后 cur.execute( INSERT INTO borrow_records (user_id, book_id, borrow_date, due_date) VALUES (?, ?, GETDATE(), DATEADD(day, 30, GETDATE())), (user_id, book_id) ) # 第4步扣减可借库存 cur.execute( UPDATE books SET available_count available_count - 1 WHERE book_id?, (book_id,) ) conn.commit() return 借阅成功 except Exception as e: conn.rollback() return f借阅失败{e}WITH (UPDLOCK)是这步的关键它在查询时就对这条图书记录加了更新锁两个并发请求不会同时看到同一份可借库存。课设虽然不一定有并发压测但写出来老师会觉得你考虑过真实场景。还书和延期就更明确核心是把状态流转写进 SQL而不是在 Python 里改一堆变量# 还书补 return_date同时库存加一 cur.execute( UPDATE borrow_records SET return_date GETDATE() WHERE record_id? AND return_date IS NULL, (record_id,) ) cur.execute( UPDATE books SET available_count available_count 1 WHERE book_id?, (book_id,) ) # 延期只允许未还且未续借过的记录续期 30 天 cur.execute( UPDATE borrow_records SET due_date DATEADD(day, 30, due_date), renew_count 1 WHERE record_id? AND return_date IS NULL AND renew_count 0, (record_id,) )你注意还书更新记录时带了return_date IS NULL条件这是一种乐观锁思路如果这条记录已经被还过了那受影响行数是 0后续可以判断cur.rowcount来提示用户。日期计算全用 SQL Server 的 GETDATE() 和 DATEADD原因是 Python 端生成的时间和 SQL Server 服务器时间可能有时区差异统一交给数据库算口径一致。3.3 图书管理新增、修改、下架的边界条件管理员模块的核心是图书维护。新增图书要处理 ISBN 重复修改图书要处理库存联动下架则要处理外键引用。先说新增和修改# 新增图书先检查 ISBN 是否已存在存在则提示走修改流程 cur.execute(SELECT COUNT(*) FROM books WHERE isbn?, (isbn,)) if cur.fetchone()[0] 0: return 该 ISBN 已存在请使用修改功能 cur.execute( INSERT INTO books (isbn, title, author, publisher, category, total_count, available_count, status) VALUES (?, ?, ?, ?, ?, ?, ?, 1), (isbn, title, author, publisher, category, total_count, total_count) ) # 修改图书只有未借出的数量差才能调整 cur.execute( UPDATE books SET title?, author?, publisher?, category?, total_count? WHERE book_id?, (title, author, publisher, category, total_count, book_id) )修改库存这里有一个坑之前借出的书还没归还如果你直接把 total_count 改小available_count 可能大于 total_count数据就矛盾了。常见做法是总库存只允许增加或者至少要保证available_count total_count。在页面上直接加一个校验规则available_count不允许比total_count大。下架操作同理更不能用 DELETEUPDATE books SET status 0 WHERE book_id ? AND book_id NOT IN ( SELECT DISTINCT book_id FROM borrow_records WHERE return_date IS NULL )这个NOT IN子查询保证“有未还记录的书不允许下架”从根源上避免外键纠纷。如果下架成功affected row count 为 1如果为 0说明该书存在未还记录把这种情况翻译成“请先处理借出记录再下架”提示给管理员。4. 环境搭建与启动流程从空白 SQL Server 到浏览器出页面的完整路径4.1 虚拟环境与依赖激活 venv 后需要装什么项目压缩包里已经带了一套 Python 虚拟环境能看到 activate、activate.bat、deactivate.bat 和 pyvenv.cfg 这些文件说明创建者已经提前把环境备好了。你要做的第一步是按操作系统激活它。# Windows 下直接执行 activate.bat # macOS / Linux 下执行 source activate # 激活后安装后端依赖 pip install flask pyodbc激活成功后命令行提示符前面会出现(venv)标志这时候 pip 装的东西只会进这个虚拟环境不会污染系统 Python。依赖其实就两个flask 负责 Web 服务和路由pyodbc 负责连 SQL Server。如果你在一台全新机器上跑建议先 updrade 一下 pip 再装避免源仓库里轮子不匹配导致的玄学安装错误。4.2 数据库连接pyodbc 连接串与 db.py 的写法pyodbc 是 Python 连 Microsoft SQL Server 最主流的驱动没有任何 ORM 包装写 SQL 和拿结果集都很直接。连接串的写法是整个项目最先要确认正确的地方参数不对会直接抛异常。import pyodbc conn_str ( DRIVER{ODBC Driver 17 for SQL Server}; SERVERlocalhost\\SQLEXPRESS; DATABASELibraryDB; Trusted_Connectionyes; TrustServerCertificateyes; ) conn pyodbc.connect(conn_str) cur conn.cursor()参数逐个说明DRIVER要匹配你机器上实际安装的 ODBC 驱动名常见的有ODBC Driver 17 for SQL Server、ODBC Driver 18 for SQL Server、SQL Server Native Client 11.0三种装哪个就用哪个版本不匹配连接串必挂。SERVER写法有讲究默认实例直接写localhost命名实例要写localhost\\SQLEXPRESS这种双反斜杠格式。Trusted_Connectionyes表示用 Windows 身份验证如果你的课设要求 SQL Server 账号登录改成UIDsa;PWD你的密码即可。TrustServerCertificateyes是用来跳过本地开发时 SSL 证书校验的不加它会在某些版本的驱动下报加密错误。提示首次连接报错时先用 SQL Server Management Studio 确认能不能连上同一实例。SSMS 能连而 pyodbc 不能问题九成在驱动名或连接串参数上。4.3 建库建表与初始数据一条龙跑通数据库连接串指向 LibraryDB但这个库需要先建好。项目里一般会附带 sql 脚本如果没有按下面的核心结构手动建也很快CREATE DATABASE LibraryDB; GO USE LibraryDB; CREATE TABLE users ( user_id INT IDENTITY(1,1) PRIMARY KEY, username NVARCHAR(50) NOT NULL UNIQUE, password NVARCHAR(64) NOT NULL, real_name NVARCHAR(50), role NVARCHAR(20) NOT NULL DEFAULT student, status INT NOT NULL DEFAULT 1, created_at DATETIME NOT NULL DEFAULT GETDATE() ); CREATE TABLE books ( book_id INT IDENTITY(1,1) PRIMARY KEY, isbn NVARCHAR(20), title NVARCHAR(100) NOT NULL, author NVARCHAR(50), publisher NVARCHAR(50), category NVARCHAR(50), total_count INT NOT NULL DEFAULT 0, available_count INT NOT NULL DEFAULT 0, status INT NOT NULL DEFAULT 1 ); CREATE TABLE borrow_records ( record_id INT IDENTITY(1,1) PRIMARY KEY, user_id INT NOT NULL REFERENCES users(user_id), book_id INT NOT NULL REFERENCES books(book_id), borrow_date DATETIME NOT NULL DEFAULT GETDATE(), due_date DATETIME NOT NULL, return_date DATETIME, renew_count INT NOT NULL DEFAULT 0, operator_id INT ); CREATE TABLE operation_logs ( log_id INT IDENTITY(1,1) PRIMARY KEY, user_id INT, action NVARCHAR(50), detail NVARCHAR(200), created_at DATETIME NOT NULL DEFAULT GETDATE() ); GO -- 初始化超级管理员密码为 admin 的 SHA256 哈希 INSERT INTO users(username, password, real_name, role) VALUES (admin, 8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918, 超级管理员, super_admin);所有字符串字段都用 NVARCHAR这是配合 SQL Server 中文排序规则的标准做法以后插入中文不会乱码。ID 列统一 IDENTITY 自增省去手动生成主键的麻烦。初始化那条 INSERT 里的哈希值是admin的 SHA256 结果登录时直接用 admin / admin 就能进系统。到这里环境已经齐了启动整个项目只需要一条命令python app.pyFlask 默认监听 127.0.0.1:5000浏览器打开http://127.0.0.1:5000就能看到登录页。如果 5000 端口被占用在 app.py 底部的app.run()里把端口改成 5001 或其他值。5. 实战避坑SQL Server 对接时最典型的四个翻车现场5.1 SSL 加密连接报错现象pyodbc.connect 执行时抛出“驱动程序无法通过使用安全套接字层(SSL)加密与 SQL Server 建立安全连接”程序直接中断。原因SQL Server 实例开了强制加密或者 ODBC 驱动版本太旧。旧版 SQL Server Native Client 对证书校验的处理和 SQL Server 新实例之间经常出现握手失败。很多电脑里装的是旧驱动但数据库却是 2019 或 2022两边协议对不上。解决分两步。第一步在连接串末尾追加TrustServerCertificateyes;跳过本地开发环境的证书校验。第二步如果还报错去安装最新 ODBC Driver 18 for SQL Server并把连接串 DRIVER 改成{ODBC Driver 18 for SQL Server}。这个驱动在加密参数上做得更灵活默认也能自动兼容。5.2 中文乱码现象往 books 表插入“计算机科学”查询出来是“????????”页面上也显示一堆问号。原因典型的字符集三件套问题。SQL Server 实例安装时选了英文或默认排序规则或者建表时用了 VARCHAR 而没选 NVARCHAR再或者 HTML 模板里忘记声明utf-8。这三个里面任意一个不满足中文就会出问题。解决最省事的路径是安装 SQL Server 时排序规则选Chinese_PRC_CI_AS能选中文排序规则就一劳永逸。建表时所有字符串字段用 NVARCHAR这一点上面的建表脚本已经做了。最后页面模板的head里加meta charsetutf-8。如果数据已经乱掉了先清空该表重新插入而不是在 Python 代码里做编码转换那种补丁方法治标不治本。5.3 数据库连接不上现象连接串写的是localhost\\SQLEXPRESS报错信息是“Network-related instance-specific error”或者“Cannot open database LibraryDB”。原因大概率不是连接串写错而是 SQL Server Browser 服务没启动。具名实例默认使用动态端口客户端必须通过 Browser 服务才能找到对应端口。Browser 停掉以后连接串写什么都连不上。另外 TCP/IP 协议在 SQL Server 配置管理器里被禁用也会导致同样问题。解决打开 SQL Server Configuration Manager先看“SQL Server 网络配置”里 TCP/IP 是否已启用默认可能没开。再看“SQL Server 服务”里 SQL Server Browser 的运行状态没启动就手动启动并设为自动。最后在命令行执行netstat -an | findstr 1433确认端口在监听。如果用的是命名实例强烈建议把 Browser 服务打开这是最隐蔽的坑。5.4 删除数据被外键拦截现象管理员想删掉某本图书前端直接报外键冲突或者有人为了绕过约束给外键加了 ON DELETE CASCADE结果删掉一个用户他的所有借阅历史也没了。原因borrow_records 表用 user_id 和 book_id 引用了 users 和 books参照完整性在这里是起作用的。直接 DELETE 根本不可能成功因为历史借阅记录还引用着这条书或这位读者。但加级联删除是更坏的做法日志全丢答辩时老师问一句“借阅历史为什么空了”就答不上来。解决不物理删除只做逻辑下架。图书用 status 字段从 1 改成 0用户用 status 从 1 改成 0哪怕有未还记录也能停用只是阻止新的借阅行为。历史借阅记录永远保留这也是课设答辩里能拿出来讲的设计决策。如果有同学在代码里写了 CASCADE趁早换掉。6. 验收与答辩把课程设计做成能演示的项目6.1 答辩前按这张清单走一遍项目能不能过看演示时顺不顺。我每次做完课设都会列一张验收表按角色把主流程跑通再交给老师看操作角色操作步骤预期结果学生/老师登录错误密码一次页面提示错误不进入系统学生/老师正确登录后进入 dashboard看到可借图书列表学生/老师借一本在架图书库存减 1生成借阅记录学生/老师对同书再次点击借阅被拦截“重复借阅”学生/老师还书库存加 1记录回填 return_date管理员添加一本新书图书列表立即出现管理员对有未还记录的书下架操作失败并提示超级管理员查看操作日志能看到以上所有操作记录这张表本身就是答辩时的演示脚本照它点一遍全程不到五分钟。中间故意演示一次错误操作反而能让老师看到系统的容错设计。6.2 一个容易被忽略的亮点把逾期计算统一交给 SQL Server很多同学的逾期功能是在 Python 里写一堆 if 判断把应还时间拉出来和datetime.now()比。这样不是不行但答辩时容易被问到“时区怎么处理”“为什么数据库里显示的逾期状态和页面不一致”。我建议全部用 SQL Server 的日期函数算SELECT r.*, b.title, u.real_name, CASE WHEN r.return_date IS NULL AND r.due_date GETDATE() THEN 已逾期 WHEN r.return_date IS NULL THEN 借出中 ELSE 已归还 END AS borrow_status FROM borrow_records r JOIN books b ON r.book_id b.book_id JOIN users u ON r.user_id u.user_idCASE 表达式把状态在数据库端算好Python 侧直接取结果渲染到页面上。这样不管谁什么时候开系统状态永远是数据库当前时间的准确反映。答辩时老师问逾期逻辑你把这段 SQL 调出来讲比讲十句 Python 判断更能说明你理解了“以数据库为准”的设计思想。我当年第一次做这类系统时把借阅状态记在 Python 的全局变量里演示到一半刷新页面所有状态全乱了当场社死。从那以后我每次做课程设计都强制要求自己把业务状态落成数据库字段写操作全部走事务最后一定先把验收清单过一遍再碰代码。希望帮到你。本文还有配套的精品资源点击获取