VB6+SQL Server图书管理系统:UML建模到数据库落地与答辩自查 简介《图书管理系统 软件工程》是一份面向软件工程课程设计、毕业设计入门与系统分析学习者的项目文档围绕图书管理业务展开需求分析、系统设计、数据库设计与实现测试等环节适合需要参考规范化开发流程、理解UML建模与VB6.0实现思路的读者。压缩包内仅含1个doc文件约535KB篇幅集中于课程设计报告式内容便于直接查阅与整理。目录涵盖绪论与关键技术介绍、系统目标与性能要求、读者借阅与管理员维护等用例图、时序图、类图及类间关系并给出数据库建立、E-R图、数据表创建与连接方式随后说明工程项目创建、模块与类模块添加、登录窗体等界面设计。已有441人学习可作为图书管理系统课程设计的需求分析、建模和数据库设计参考底稿。1. 选题与交付压力图书管理系统为什么要在两周内跑通「图—表—码」闭环「图书管理系统」在软件工程课程设计的选题表里出现的频率大概和学生成绩管理不相上下。真正让人为难的从来不是选题而是时间两周之内要交出一份从用例图、类图、时序图、E-R 图一路自洽到建表脚本和可运行窗体的完整文档。我见过大量方案栽在同一个地方——UML 图画得很漂亮数据库表却对不上号文档里写着三层架构工程目录里只有一个窗体文件。这套基于 VB6.0 SQL Server 2000 Rational ROSE 的方案看起来不新优势恰恰在于每个图形符号都能在代码里找到落点答辩时经得起一句句追问。适合正在做毕设或课程设计、需要一份能照着搭、也能照着讲的完整软件工程文档的人。2. 需求分析三类参与者、三张用例图与可验收的功能条款需求分析这一章最容易写成散文。翻十份课程设计八份都在复述「系统应方便快捷」答辩老师扫一眼就翻过去了。真正有价值的是把需求压成可以打勾的条款让后面每一张图、每一张表都能追溯回某一句话。2.1 三类参与者的诉求边界先划清先不做图先用三行字把「谁干什么」写死读者查书、查自己的借阅记录、续借、借书、还书图书管理员处理借书和还书系统管理员维护读者信息、图书信息和系统状态。这三行是后面所有图形和表结构的源头改一个字就要往下游同步。这三类人的权限边界必须在这一步就明确否则到了编码阶段会出现读者菜单里混进「删除图书」这种尴尬。我的做法是给每个参与者列一张两列表左边是他能做的动作右边是他绝对不能碰的动作。读者的禁止项包括修改图书状态、删除任何记录图书管理员不能新增读者也不能删除书目系统管理员不做具体的借还操作。禁止项写进需求文档之后登录模块的角色判断就有了依据不用等做到一半再回头补。2.2 三张用例图读者请求服务、借还书处理、系统维护用例图按参与者拆成三张不要贪图省事画成一张大图。三张图各自闭合答辩时指哪打哪也方便分别对应后面的三个主菜单。读者的用例包含系统登录、查询个人信息、查询图书信息、查询借阅信息、图书续借、借阅图书、图书归还。图书管理员的用例只有两个处理图书借阅和处理图书归还看起来单薄但它是整个系统里唯一涉及并发和事务的地方权重不低。系统管理员的用例是增加书籍、删除或更新书籍、添加读者信息、删除或更新读者信息。参与者用例数量关键用例对应界面读者7借阅图书、图书续借读者主菜单 三个查询窗体图书管理员2处理图书借阅、处理图书归还借还书操作窗体系统管理员4增加书籍、添加读者信息书目维护窗体 读者维护窗体画的时候有个细节常被忽略用例之间不要滥用包含和扩展关系。只有当两个用例确实共享一段必须复用的步骤时才用包含比如「借阅图书」和「图书续借」都包含「校验读者状态」。为了好看硬拉几条虚线过去老师第一个问的就是「这条 extend 的触发条件是什么」答不上来反而扣分。2.3 性能要求不能只写形容词原始需求里列了一串词正确性、可靠性、效率、完整性、易使用性、可维护性、可测试性、复用性、安全保密性、可理解性、可移植性、互联性。这十二个词写在文档里没问题但它们没法验收因为它们没有阈值。我一般会把其中能落地的四五条量化书目表累积到一万条记录时按书名或作者模糊查询的结果要在两秒内返回三个终端同时发起借书请求同一本书不允许出现两条有效借阅记录从点击借书按钮到弹出成功提示正常网络环境下不超过三秒任意一张核心表被误删单条记录后能从备份脚本恢复到前一天的状态。剩下那些确实不好量化的就写清楚保障手段比如可维护性靠统一的数据库连接模块保证可移植性靠把所有连接串集中在一个配置文件里保证。写清楚手段比罗列形容词有说服力得多。2.4 时序图以系统管理员添加图书为例时序图不必每张都画挑最能说明问题的那几条。系统管理员添加图书这条链路比较典型涉及查重、写入、返回三步正好覆盖「先查后写」这个通用模式。交互顺序是这样管理员在书目维护窗体点击「添加」窗体向控制层发出 add item() 请求控制层拿到书名后调用 find(string)拿书名去书目表里查是否已存在同名书目数据层返回查找结果如果存在则提示重复并终止如果不存在则调用 create(integer, title) 生成新书目记录并更新该类书的数量最后把成功与否回传给窗体。这里的 find 和 create 之间必须有一次判空否则同一种书会被反复插入库存数量越加越多到后期书目列表里全是重复行。剩下的几条时序图按同样思路补添加读者、删除或更新图书、删除或更新读者、处理借书、处理还书、读者查询图书信息。每条图的消息编号要连续返回消息用虚线别把同步调用和异步消息混在一起。3. 系统设计六个核心类、时序图与三层架构的对应关系需求定完边界接下来要解决的是「这些东西在代码里长什么样」。类图是这一章的核心但类图画完不是挂着好看它得能直接翻译成表结构否则就成了摆设。3.1 六个核心类与各自的职责系统里立得住的核心类有六个item书目、title书名也就是书的品种、loan借出记录、reader读者、administrator系统管理员、librarian图书管理员。reader 类持有读者编号、姓名、性别、年龄、已借书数目、联系电话、地址操作有读者信息查询、图书信息查询、借阅信息查询、借书、还书、续借。librarian 类持有工作编号、姓名、地址操作是借书处理和还书处理。administrator 类同样持有编号、姓名、地址操作是添加、删除、更改信息。item 类持有图书书目和图书类别操作是增删改查。title 类持有图书名称、作者、ISBN、价格、出版社、数目。loan 类持有已借出图书的名称、借出时间和图书编号操作是增删改查。这里有个容易犯的错把「书的品种」和「书的副本」合成一个类。一个 ISBN 对应五本实体书五本都能独立借出、独立归还、独立破损。合成一个类之后借出一本就只能把整个品种标成借出剩下四本没法借。所以 title 和 item 必须分开比例关系是一对多。类关键属性主要操作对应数据表reader读者编号、姓名、已借书数目查询、借书、还书、续借userlibrarian工作编号、姓名借书处理、还书处理libraryeradministrator编号、姓名添加、删除、更改admintitle书名、作者、ISBN、价格增删改查bookitem书目、类别增删改查bookloan借出图书、借出时间增删改查borrow3.2 类关系到表关系一对多怎么落成外键类图里的连线最终要变成数据库里的外键。reader 和 loan 是一对多一个读者可以有多条借阅记录落到表上就是 borrow 表里存 reader 的证件号作为外键。title 和 item 是一对多book 表用一个自增主键区分每一本实体书ISBN 只做普通字段。一条经验一对多的「多」那边永远放外键。有些同学把借阅记录列表塞进读者表用逗号拼接一长串编号查询的时候靠字符串切割。这种做法在演示时能跑通一旦要查「这本书现在在谁手上」就得全表扫描加字符串匹配而且第一个字段长度溢出就崩了。关系型数据库的规范化不是形式主义是把这类查询成本提前摊掉。多对多的情况这套系统里不多但书和分类之间勉强算一个一本书属于一个分类一个分类下有多个书。严格说这是一对多分类号作为外键放在 book 表就够不需要中间表。如果需求文档里写了「一本书可以同时属于多个分类」那才需要 bookkind 中间表这一点要在设计阶段确认清楚不要到建表时才改。3.3 三层架构在 VB6 工程里到底怎么摆三层架构写到文档里只有三行字落到 VB6 工程里要有明确的目录和文件对应否则老师追问「你的业务层在哪个文件」会答得很虚。表现层就是所有 frm 文件frmLogin 负责登录frmMain 是主菜单frmBookQuery、frmBorrow、frmReturn 分别对应查询、借书、还书。这一层只做三件事收集控件输入、调用业务层函数、把返回值显示到控件上不写 SQL。业务层用标准模块 bas 和类模块 cls 承担。basBorrow 里放借书校验和库存扣减的函数basPunish 里放超期天数计算和罚款金额计算clsReader 里封着装读者对象的属性读写。这一层接收表现层传来的参数拼装业务规则再调用数据层。数据层只有一个 basDB 模块对外暴露一个全局连接对象和若干查询函数。所有 SQL 语句集中在这一层换数据库只需要改这一个文件。把这条线摆清楚好处不只是答辩好讲。开发到第三天你一定会遇到「同一个查询在三个窗体里各写了一遍改字段名要改三处」的局面有了分层改动点收敛到一处。3.4 活动图与借还书状态流转书的实体状态只有三个在架、借出、下架。所有业务动作都是在三个状态之间迁移。借书从在架到借出前置条件是读者可借数量未超上限、无超期未还记录、书状态为在架。还书从借出到在架同时写回收日期、判断是否超期、超期则生成罚款记录。续借状态不变只改应还日期且只能续借一次。下架从在架到下架仅系统管理员可操作已被借出的书不能下架。这四个迁移写进文档时要配一张状态表标明触发事件、前置条件和后置结果。表比图更实用因为测试用例可以直接照着这张表逐行设计。4. 数据库与编码落地八张表的建表脚本、ADO 连接与登录窗体到这一章图纸要变成能跑的东西。数据库设计得对不对决定了后面编码阶段是顺利还是天天救火。4.1 八张表的分工与主键设计系统需要八张表book书籍、borrow借书、login登录、punishment罚款、user读者信息、libraryer图书管理员、admin系统管理员外加一张 bookkind图书分类承接分类号和分类名。表名职责主键关键字段book实体书库存book_id 自增bannercode 唯一、state、kindnoborrow借阅流水借阅流水号user_cardno、bannercode、borrowdatelogin账号密码user_namepsw、rolepunishment罚款记录流水号user_cardnumber、sum_accountuser读者档案user_cardnumber姓名、性别、状态、注册日期bookkind分类字典kindnokindnamelibraryer管理员档案libr_name性别、地址admin系统管理员adminname性别、地址这里有两处我通常会和原始设计不一样理由很实在。第一书名不适合做主键同名书比如两版同名的教材根本录不进去所以 book 表加一个自增 book_id 做物理主键bannercode 保持唯一约束。第二借书表不能拿读者证件号做主键一个读者同时借五本书就冲突了主键要么用流水号要么用证件号加图书编号的联合主键。这两点在答辩时属于高频追问点提前改掉能省很多解释。4.2 建表脚本book 与 borrow 两张核心表-- 书籍表物理主键用自增业务编号 bannercode 加唯一约束 CREATE TABLE book ( book_id INT IDENTITY(1,1) NOT NULL, -- 自增主键解决同名书问题 bookname VARCHAR(30) NOT NULL, -- 书名 bannercode VARCHAR(30) NOT NULL, -- 图书编号业务唯一 kindno VARCHAR(20) NOT NULL, -- 分类号外键指向 bookkind author VARCHAR(10) NULL, -- 作者 publishing VARCHAR(30) NULL, -- 出版社 publishtime DATETIME NULL, -- 出版日期 putintime DATETIME NULL, -- 入库日期 price NUMERIC(10,2) NULL, -- 价格 state VARCHAR(4) NOT NULL DEFAULT 在架, -- 在架/借出/下架 introduction VARCHAR(50) NULL, -- 简介 CONSTRAINT pk_book PRIMARY KEY (book_id), CONSTRAINT uq_book_code UNIQUE (bannercode) ); -- 借阅表主键用流水号读者与书各自是外键 CREATE TABLE borrow ( loan_id INT IDENTITY(1,1) NOT NULL, -- 借阅流水号 user_cardno VARCHAR(20) NOT NULL, -- 读者证件号 bannercode VARCHAR(30) NOT NULL, -- 图书编号 borrowdate DATETIME NOT NULL, -- 借出日期 receivedate DATETIME NULL, -- 应还/已还日期 renew VARCHAR(2) NOT NULL DEFAULT 否, -- 是否已续借 CONSTRAINT pk_borrow PRIMARY KEY (loan_id), CONSTRAINT fk_borrow_user FOREIGN KEY (user_cardno) REFERENCES user(user_cardnumber), CONSTRAINT fk_borrow_book FOREIGN KEY (bannercode) REFERENCES book(bannercode) );关于这些字段bannercode 长度取 30 是因为不少课程设计的编号规则里带年份和分类前缀短了后期会溢出state 用四个字符存中文是为了在 VB6 的表格控件里直接显示省去映射转换receivedate 允许为空因为借出时它只是「应还日期」还书时才写成实际归还时间一个字段承担两个语义是这类系统的常见简化写文档时要注明否则测试阶段会出现「还没还书怎么有归还日期」的疑问。4.3 E-R 图到关系模式冗余字段的取舍E-R 图里的实体和联系转成表之后要不要在 borrow 表里冗余存书名是个需要表态的问题。原始设计里借书信息确实同时记了读者姓名和书籍名称。我的处理是冗余存但只作为展示快照所有判断逻辑一律走外键。理由是借阅查询页面要显示的书名和读者名如果每行都去做关联查询一张一万条的借阅表在低配机器上翻页会明显卡顿而快照字段一旦参与业务判断就会出现「书改名了历史记录对不上」的麻烦。所以写清楚显示用它逻辑不用它。这个取舍在文档里说明白比单纯画一张漂亮的 E-R 图更能体现设计思考。4.4 VB6 里用 ADO 连上 SQL Server连接代码集中在一个标准模块里全工程共用不要在十二个窗体里各写一份不同的连接串。 模块 basDB.bas全局数据库连接与通用查询 Public gConn As ADODB.Connection Public Function OpenDB() As Boolean On Error GoTo ErrHandler Set gConn New ADODB.Connection Provider 指定驱动Data Source 是实例地址Initial Catalog 是库名 gConn.ConnectionString ProviderSQLOLEDB.1;Persist Security InfoFalse; _ User IDsa;Password;Initial CatalogLibraryDB; _ Data Source127.0.0.1 gConn.CursorLocation adUseClient 客户端游标窗体关闭后记录集仍可读 gConn.Open OpenDB True Exit Function ErrHandler: MsgBox 数据库连接失败 Err.Description, vbCritical, 提示 OpenDB False End Function Public Function QueryRS(ByVal sql As String) As ADODB.Recordset Dim rs As New ADODB.Recordset rs.Open sql, gConn, adOpenStatic, adLockReadOnly 只读静态游标适合列表展示 Set QueryRS rs End Function说明几点。CursorLocation 设为 adUseClient 之后记录集的数据会一次性拉到本地界面绑定和翻页都更顺代价是数据量大时内存占用高书目一万条以内问题不大。adOpenStatic 加 adLockReadOnly 用于所有列表展示场景需要改数据时另开一个可更新的记录集不要图省事全用 adLockOptimistic学生在单机上测不出问题多终端一测就露馅。连接串里出现明文 sa 账号和空密码这是课程设计的常见妥协。文档里必须写明生产环境应改为独立账号加密码并限制权限否则答辩时被问到安全性会很难接。4.5 登录窗体的详细设计从控件到校验顺序登录窗体看似简单却是整个系统里唯一同时涉及账号校验、角色分流和全局状态写入的地方值得单独画一页详细设计。界面上放两个标签、两个文本框txtUser、txtPwd、两个按钮cmdOK、cmdCancel。密码框的 PasswordChar 属性要设置回车键通过窗体的 Default 属性绑定到确认按钮。校验顺序固定为非空校验、账号存在性校验、密码匹配校验、角色读取、分流跳转。顺序不能反先查密码再查账号存在性会给暴力尝试提供「账号不存在」和「密码错误」的区分信息。 窗体 frmLogin确认按钮逻辑 Private Sub cmdOK_Click() Dim rs As ADODB.Recordset Dim sql As String If Trim(txtUser.Text) Or Trim(txtPwd.Text) Then MsgBox 账号和密码不能为空, vbExclamation, 提示 Exit Sub End If 单引号转义避免 or 11 这类输入直接绕过 sql SELECT user_name, psw, role FROM login WHERE user_name _ Replace(Trim(txtUser.Text), , ) Set rs QueryRS(sql) If rs.EOF Then MsgBox 账号不存在, vbExclamation, 提示 ElseIf Trim(rs(psw)) Trim(txtPwd.Text) Then MsgBox 密码错误, vbExclamation, 提示 Else gRole Trim(rs(role)) 全局变量主窗体据此决定显示哪套菜单 gUser Trim(rs(user_name)) Unload Me frmMain.Show End If rs.Close Set rs Nothing End Subrole 字段是我在原始登录表基础上补的。原设计里 login 表只有用户名和密码三类角色共用一张表却没有任何字段区分身份读者登录进来看到的就是管理员菜单。这个缺口不补后面所有权限判断都无从谈起。role 取值通常是 reader、librarian、admin 三个字符串主窗体 Load 事件里按它逐个设置菜单项的 Enabled 属性。5. 常见问题排查从登录角色到借书事务的五处翻车点这一章是我这些年看课程设计和带新人时反复遇到的坑几乎每年都有人踩。按「现象—原因—解决」的顺序写对着自己的文档查一遍能省掉答辩现场一半的尴尬。5.1 三类用户登录后看到同一套菜单现象读者登录进去菜单里有「删除图书」「添加读者」点下去还能用。原因登录表只有用户名和密码没有角色字段主窗体也没做权限判断登录成功后无条件显示全部菜单。更深一层的原因是需求阶段没写「禁止项」只写了「能做什么」。解决login 表补 role 字段取值限定三个主窗体 Load 事件里遍历菜单控件按角色设置 Enabled关键操作在业务层再做一次角色校验别只依赖界面隐藏——学生演示时常用快捷键或直接调用函数绕过界面。5.2 同一种书只能录一次现象系统管理员录入第二版同名教材时提示主键冲突换一个字才能存进去。原因book 表拿书名做物理主键。书名在现实中完全不唯一同一本书不同出版社、不同版次都同名。解决加自增 book_id 做物理主键书名降为普通字段业务编号 bannercode 加唯一约束。已经建好表的需要做一次数据迁移新建表、导入旧数据、补 book_id、重建外键别直接改字段容易把关联关系弄断。5.3 一个读者借了五本书记录只剩一条现象借阅查询页面里读者明明借了五本只显示最后一条。原因borrow 表用读者证件号做单字段主键插入第二条借阅记录时主键冲突被错误处理逻辑吞掉了或者干脆覆盖了前一条。解决主键改成自增流水号或者用证件号加图书编号组成联合主键。同时检查借书按钮的异常处理VB6 里 On Error Resume Next 用多了会把主键冲突悄悄跳过表面成功实际没写进去这种「静默失败」最难查建议只在明确知道会出什么错的语句上加错误处理。5.4 借书成功了库存还显示在架现象借阅记录已经写进 borrow 表book 表的 state 还是「在架」同一本书能被两个人同时借走。原因借书操作分两步执行——改状态、写记录中间没有事务包起来。任一步失败另一步已经生效。多终端环境下两个终端同时读到「在架」都判断可借。解决两步放进同一个事务用带条件的 UPDATE 判断受影响行数行数为零说明被别人抢先借走立即回滚。下面这段是核心逻辑 借书按钮状态更新和记录写入必须在同一事务内 Private Sub cmdBorrow_Click() On Error GoTo ErrHandler gConn.BeginTrans 带 state在架 条件受影响行数为 0 说明已被抢借 gConn.Execute UPDATE book SET state借出 WHERE bannercode txtCode.Text AND state在架 If gConn.Execute(SELECT ROWCOUNT).Fields(0).Value 0 Then gConn.RollbackTrans MsgBox 该书已借出或不存在, vbExclamation, 提示 Exit Sub End If 应还日期按借出日加 30 天计算续借标记默认「否」 gConn.Execute INSERT INTO borrow(user_cardno,bannercode,borrowdate,receivedate,renew) _ VALUES( gCard , txtCode.Text ,GETDATE(), _ DATEADD(day,30,GETDATE()),否) gConn.CommitTrans MsgBox 借书成功, vbInformation, 提示 Exit Sub ErrHandler: gConn.RollbackTrans MsgBox 借书失败 Err.Description, vbCritical, 提示 End Sub参数说明gCard 是登录后写入全局变量的读者证件号DATEADD(day,30,GETDATE()) 生成应还日期30 这个数字来自借阅规则改规则只改这一处ROWCOUNT 必须在 UPDATE 之后立刻读中间插任何语句都会失效。这段逻辑还漏了一个前置校验读者已借数量和超期未还应该放在事务开始之前避免开了事务才发现不合规又要回滚。5.5 文档里的连接代码和工程里的语言对不上现象数据库连接那一节贴了一段带 using 和 namespace 的代码工程里却是 VB6 的 frm 文件答辩时被问「这段代码怎么编译」。原因写文档时从网上找了段现成的 ADO 示例直接粘贴没注意语言环境。这类问题在软件工程文档里极常见因为文档和代码往往是两个人写的或者一个人隔着几天分两次写的。解决文档里每一段代码都必须来自实际工程哪怕是删减版也要标明出处文件和函数名。连接部分统一改成 VB6 的标准模块写法与工程里 basDB 模块逐行对应。我一般会在文档最后加一张「代码与文件对照表」左边写章节号右边写工程里的文件名交付前逐行核一遍五分钟能挡掉一类硬伤。6. 验收与答辩把文档里每张图都问一遍的自查清单交付前留出半天做一轮自查比再改一遍界面有用。方法是把自己当成答辩老师从文档第一页开始凡是有图形的地方就问三个问题这东西在数据库里落在哪张表在代码里落在哪个函数如果这里出错了界面上会看到什么现象。三问答不上来的地方就是漏洞。下面这张表是我常用的验收清单逐项打勾打不了勾的回去补。验收项检查方法通过标准用例图与菜单对照打开主菜单逐个点每个参与者看到的菜单项与其用例一一对应无越权项类图与表结构对照拿类图属性逐个找字段六个核心类的属性都能在八张表里找到落点状态流转覆盖按状态表逐行设计测试用例在架→借出→在架 全链路可复现异常分支有提示事务完整性借书过程中强制中断book 与 borrow 两张表数据一致无半截记录权限隔离用读者账号尝试管理员操作界面上无入口绕过界面调用也被拦截答辩现场的必答题基本固定在三个。第一个是「为什么选 VB6 而不是 Java Web」可以这样回答课程设计的核心是把软件工程的全流程走完从需求到用例图、类图、E-R 图再到编码测试工具链越短每个环节能落到的细节越多同时说明如果要扩展成多人协作的 B/S 系统数据层和业务层的设计可以直接平移表现层需要重写这样既承认了技术栈的边界又体现了对分层的理解。第二个是「三层架构具体体现在哪」直接翻到工程目录指 basDB、basBorrow、frmLogin 三个文件讲数据、业务、表现的分工不要背定义。第三个是「两个人同时借同一本书怎么办」答案就是第 5.4 节那段带条件的 UPDATE 加事务把 ROWCOUNT 的作用讲清楚即可。有一件事我吃过亏第一次做这类文档把时间几乎全花在画图上代码只是勉强跑通结果答辩时被要求现场演示还书并解释罚款怎么算卡了整整两分钟。从那以后我每次交付前都强制走一遍「关掉开发环境、重新打开工程、从登录到借还书完整走一遍」的流程把能想到的异常输入都试一次包括空输入、重复借、超期还、不存在的图书编号。这套动作只需要二十分钟但它能把文档里所有经不起推敲的地方提前暴露出来。希望帮到你。本文还有配套的精品资源点击获取