简介这份《图书管理系统 软件工程》文档面向软件工程课程设计、实验报告及毕业设计前期参考的读者围绕图书管理系统的完整开发流程展开可帮助梳理从需求分析到系统实现的文档结构与建模思路。内容覆盖系统目标、需求描述与功能性能要求并给出读者请求服务、借还书处理、系统维护等用例图以及时序图、类关系和E-R图数据库部分涉及建库、数据表创建与连接实现部分说明开发工具与支持平台并以登录窗体为例展开详细设计还包括创建工程项目、添加模块和类模块、界面设计等环节。压缩包内为1个doc文件约535KB目录层级清晰便于按绪论、需求分析、数据库设计、系统实现等章节检索。已有442人学习适合需要参考UML建模、关系数据库设计及VB6.0与SQL Server 2000实现思路的读者查漏补缺。1. 一份课程设计文档为什么值得当项目来拆图书管理系统软件工程的完整骨架接手一份标着「图书管理系统 软件工程」的文档时多数人第一反应是翻两页就归档——又一个课程设计。真动手才明白它把一套 MIS 系统从需求到建库到窗体的链路全走了一遍而且用的是 VB6 SQL Server 2000 这套老家伙坑点密集得很。这套东西的价值不在代码多漂亮在于它是一份可以逐章对照实现的施工图三类参与者的用例划分、6 个核心类的职责边界、8 张业务表的主外键、ADO 连库的调用方式全部写死在里面。适合刚接触软件工程课程设计的学生照着跑通全流程也适合要从零搭一套小型信息管理系统的开发者抄架构。2. 需求分析怎么落到能开发三类参与者、7 张时序图与 6 个核心类需求分析这一章最容易写成散文写完之后开发根本没法照着动手。这份文档在这块做得相对扎实它把「谁能做什么」拆成了参与者、用例、时序、类四层一层比一层接近代码。2.1 参与者与用例的边界读者、图书管理员、系统管理员文档把系统参与者收敛成三类这个划分不是拍脑袋来的而是按「谁碰数据、谁改数据、谁管数据」切的读者只碰和自己相关的数据——查个人信息、查图书信息、查自己的借阅记录、续借、借书、还书。图书管理员是业务流的执行者负责处理借书和还书两个动作本质上是在把读者发起的申请落成一条borrow记录。系统管理员负责元数据的增删改包括新增书目、删除或更新书目、添加读者账户、删除或更新读者信息。这种划分的好处是每个用例都能直接映射到一张表或者一组表的操作上。举个具体的读者「查询自己的借阅信息」这个用例落到实现层就是拿user_cardnumber去borrow表做一次等值查询图书管理员「处理还书」就是给borrow记录补上receivedate并把book.state从「借出」改回「在架」。常见的翻车是把「图书管理员」和「系统管理员」合并成一个角色。教学演示无所谓真跑起来就会发现权限边界模糊——管理员能删书目书架就空了能删读者借阅记录就成孤儿。文档在这里坚持拆开是对的。提示用例图里不要出现「读者登录」「管理员登录」这种把登录当作独立用例的画法。登录是前置约束不是业务用例画进去会让后面的时序图无从下手。2.2 时序图拆到函数级以「系统管理员添加图书」为例文档给了 7 张时序图覆盖添加图书、添加读者、删除更新图书、删除更新读者、处理借书、处理还书、读者查询图书。数量看着多但真正值得逐帧看的是「添加图书」这张因为它把调用链写到了函数签名级别。原文给出的三个消息是1: add item() —— 添加图书函数 2: find(string) —— 根据图书查找相应图书的函数 3: create(integer, title) —— 修改图书数目的函数这三步的顺序很关键是个典型的「先查重、再落库」模式。系统管理员在 maintenance windows 里填完书目信息点保存先走find按书名或编号去库里探一下是否已存在存在就走create增加数目不存在才真正插入一条新记录。落到 VB6 里这段调用链一般会写成这样 frmBookAdd 窗体中的保存按钮 Private Sub cmdSave_Click() Dim sBookName As String Dim nCount As Integer sBookName Trim(txtBookName.Text) If Len(sBookName) 0 Then MsgBox 书名不能为空, vbExclamation, 提示 Exit Sub End If 第一步按书名查重对应时序图的 find(string) nCount BookExists(sBookName) If nCount 0 Then 第二步已存在只增加库存数目对应 create(integer, title) Call IncreaseStock(sBookName, CInt(txtCount.Text)) Else 第三步不存在插入完整书目记录 Call InsertBook(GetFormData()) End If MsgBox 保存成功, vbInformation, 提示 End SubBookExists返回的是同名书籍的现有记录数用SELECT COUNT(*)实现最省事IncreaseStock需要的两个参数分别是书名和本次入库数量文档里的create(integer, title)签名就是这个意思integer是数量title是书名。要注意这里其实埋了个设计缺陷——用书名当查重键同名不同出版社的书会被合并后面第 3 章会细说怎么改。2.3 类图与关系6 个核心类、1:n 与 n:1 的判定文档明确了 6 个核心类item书目、title书名、loan借出、reader读者、librarian图书管理员、administrator系统管理员。前三类是领域实体后三类是参与者。参与者的类属性写得很直白。reader类有读者编号、姓名、性别、年龄、已借书数目、联系电话、地址操作是读者信息查询、图书信息查询、借阅信息查询、借书、还书、续借。librarian有工作 ID、姓名、地址操作是借书处理、还书处理。administrator有 ID、姓名、地址操作是添加信息、删除信息、更改信息。领域实体这块item书目承载书名、作者、ISBN、价格、出版社、数目操作是增删改查loan借出承载书名、借出时间、图书标号同样是增删改查。类和类之间的关系文档标了1和n翻译成人话是关系基数含义reader → loan1 : n一个读者可以有多条借阅记录item → loan1 : n一本书目可以被多次借出librarian → loan1 : n一个管理员处理多条借还记录administrator → item1 : n一个管理员维护多条书目这个基数判断是整个设计的地基。判断方法很土但有效问一句「一条 A 记录能不能对应多条 B 记录」。一个读者当然能借多本书所以 1:n一本具体的书同一时刻只能被一个人借着那是库存副本层面的 1:1不是书目层面的关系。文档把item到loan定成 1:n是按「书目」而不是「副本」建模的这在课程设计里够用但做真系统就得再加一层book_copy表。2.4 需求到模块的映射表需求分析的最后一步是把用例翻译成开发任务清单。按文档的功能说明实际的模块划分大致是模块对应表主要操作使用者书籍类别管理bookkindno/kindname类别编号、名称、关键词、备注的增删改系统管理员书籍信息管理book按编号、名称、类别、作者、出版社查询录入与修改系统管理员读者管理user新读者注册、信息更改、注销系统管理员借书处理borrow book写入借书编号、读者编号、书籍编号、借书日期图书管理员还书处理borrow book补还书日期、恢复库存状态、超期提示图书管理员综合查询book borrow按书名、编号、作者检索超期自动提示读者这张表的价值在于它把文档里散落在 2.4 节的五段功能描述压缩成六行后面写 SQL、画窗体、分模块都能直接照着这张表推进。我一般会在动手写代码前先出这张表出不来就说明需求还没吃透硬写只会返工。3. 数据库设计拆解8 张表的主键、约束与 E-R 落库数据库这块文档写得最像施工图字段名、类型、长度、是否允许为空都给了。但也正因为写得太细几处设计上的硬伤反而藏得更深得逐个拎出来看。3.1 表清单与字段设计的取舍文档开头说「需建立 8 个表」往下数实际列出来的只有 7 张book、borrow、login、punishment、user、libraryer、admin。这个数字对不上不是笔误那么简单缺的那张大概率是图书类别表——因为book表里塞了kindno和kindname两个字段类别名跟着每条书目重复存储典型的部分依赖冗余。先说能看到的 7 张。book表字段最全列名类型长度说明booknamevarchar30书名文档标注为主键bannercodevarchar30图书编号唯一约束kindnovarchar20分类号kindnamevarchar20分类名authorvarchar10作者publishingvarchar30出版社publishtimeDate30出版日期putintimeDate30入库日期pricenumber10价格statevarchar4状态introductionvarchar50简介borrow表是借阅流水user_cardno证件号码、bannercode图书编号、borrowdate借书日期、receivedate还书日期、renew是否续借。user表存读者档案user_cardnumber是证件号码另有姓名、性别、身份、地址、注册日期、有效日期、状态。login表只有两列user_name和psw。punishment表存罚款user_cardnumber加sum_account。剩下三张表结构高度雷同libraryer和admin都只有姓名、性别、地址三列。这种「三张表一个结构」的写法在课程设计里很常见方便分别建表。代价是登录逻辑要做三次判断或者干脆把权限判断塞进login表加一个role字段。我倾向后者一张login表加role列鉴权时一次查询就能定位角色比对着三张表做UNION清爽。3.2 主键、唯一约束与外键为什么这么定是错的文档在原表里标了两处主键都值得拿出来说。第一处book表拿bookname当主键。这几乎必然翻车。《数据结构》和《数据结构C 语言版》是两本书出版社不同、ISBN 不同但书名不一致还算好的万一碰上同名教材插入第二条直接主键冲突。正确的做法是拿bannercode图书编号当主键——它本身就是图书馆给每本书贴的唯一条码号天然唯一。bookname顶多加个普通索引加速检索。第二处borrow表拿user_cardno当主键。这更离谱一个读者会借很多次书多行记录的user_cardno全一样主键直接冲突。借阅表的正确主键应该是一个自增的流水 ID或者退而求其次用(user_cardno, bannercode, borrowdate)三元组合主键。组合主键能表达「同一个人同一本书同一天只借一次」这个业务约束但续借之后borrowdate不变、renew变会有更新异常还是自增 ID 更稳。外键这块文档基本没写。borrow.user_cardno应该指向user.user_cardnumberborrow.bannercode应该指向book.bannercode。加上外键之后删读者时数据库会拦住你逼你先处理他的借阅记录这比在业务层写一堆IF判断靠得住。3.3 建表 SQL 与索引能直接跑的版本按上面修正过的设计核心表的建表语句大致长这样-- 书目表bannercode 做主键bookname 只做索引 CREATE TABLE book ( bannercode VARCHAR(30) NOT NULL PRIMARY KEY, -- 图书唯一编号 bookname VARCHAR(30) NOT NULL, -- 书名允许重复 kindno VARCHAR(20) NULL, -- 分类号 kindname VARCHAR(20) NULL, -- 分类名 author VARCHAR(10) NULL, publishing VARCHAR(30) NULL, publishtime DATETIME NULL, -- SQL2000 里 Date 即 datetime putintime DATETIME NULL, price NUMERIC(10,2) NULL, state VARCHAR(4) NOT NULL DEFAULT 在架, -- 在架 / 借出 / 遗失 introduction VARCHAR(50) NULL ); -- 借阅表自增流水号做主键两个外键指向用户和书目 CREATE TABLE borrow ( loan_id INT IDENTITY(1,1) NOT NULL PRIMARY KEY, user_cardno VARCHAR(20) NOT NULL, bannercode VARCHAR(30) NOT NULL, borrowdate DATETIME NOT NULL DEFAULT GETDATE(), receivedate DATETIME NULL, -- 为空表示尚未归还 renew VARCHAR(2) NOT NULL DEFAULT 否, CONSTRAINT fk_borrow_user FOREIGN KEY (user_cardno) REFERENCES [user](user_cardnumber), CONSTRAINT fk_borrow_book FOREIGN KEY (bannercode) REFERENCES book(bannercode) ); -- 查询加速按书名检索、按证件号查借阅历史 CREATE INDEX idx_book_name ON book(bookname); CREATE INDEX idx_borrow_card ON borrow(user_cardno, receivedate);loan_id用IDENTITY(1,1)自增省掉手工维护流水号的麻烦。receivedate允许为空是关键设计——一条借阅记录在归还前这一列就是NULL查「当前在借」只需要WHERE receivedate IS NULL比加个状态字段更不容易出现状态和数据不一致。idx_borrow_card把user_cardno和receivedate放一起是因为最常见的查询就是「某读者当前在借的书」组合索引能直接命中。注意publishtime Date 30这种写法里30是没意义的。SQL Server 2000 的datetime固定 8 字节长度括号只对varchar/char这类字符类型有效。3.4 E-R 图与范式什么时候该反规范化文档给了 E-R 图但没展开讲这里补一句判断标准。按 E-R 转关系模式的常规规则多对多关系要拆中间表。reader和book之间就是典型的多对多——一个读者借多本书一本书被多个读者借过。borrow表就是这个关系拆出来的中间表同时还挂了borrowdate、receivedate这些关系属性这是标准做法。反规范化只有一个理由查询太慢或者 SQL 太绕。book表里kindno和kindname并存就是一次反规范化因为每查一次书都要JOIN类别表才能拿到分类名。数据量小的时候这么干省事但如果类别名要改比如「计算机」改成「计算机科学」就得UPDATE全表遗漏一条就数据不一致。真要做就把kindname拆出去单独建book_kind表book里只留kindno。判断要不要反规范化我的土办法是这个冗余字段一年改几次不改的就是合理的反规范化改得勤的就是埋雷。分类名、出版社名这种基本不动留着无妨读者姓名、图书状态这种天天变的坚决不冗余。4. VB6 工程实现从新建工程到 ADO 连库的落地步骤设计图再漂亮落不到 VB6 的工程树里就是废纸。这一章按文档第 4、5 章的顺序把「新建工程 → 加模块 → 加类模块 → 设计界面 → 连数据库」这条链路走一遍重点讲文档写错或者没写全的地方。4.1 工程结构与模块划分VB6 的标准工程Standard EXE默认只有一个Form1正规做法是按职责拆成三类文件BookMIS.vbp 工程文件记录所有组件引用 ├── frmLogin.frm 登录窗体 ├── frmMain.frm 主窗体按角色动态显示菜单 ├── frmBook.frm 书目维护窗体 ├── frmBorrow.frm 借还书窗体 ├── frmReader.frm 读者管理窗体 ├── basDB.bas 标准模块放数据库连接和通用函数 ├── clsReader.cls 类模块读者实体 ├── clsLibrarian.cls 类模块图书管理员实体 └── clsAdmin.cls 类模块系统管理员实体标准模块.bas里放的是全局函数和全局变量声明比如连接字符串、OpenConn/CloseConn这类工具函数。类模块.cls对应类图里的参与者Public属性配Property Get/Property LetPublic Sub对应操作。窗体只负责界面和事件响应业务逻辑一律下沉到模块和类里。这套分层在 VB6 时代叫「三层」表示层是窗体业务层是类模块和标准模块数据层是 ADO 加 SQL Server。文档摘要里写的 Presentation / Application / Data 三层落到具体文件就是这个对应关系。4.2 类模块写法把 reader / librarian / administrator 变成 clsReader类图里reader的属性有读者编号、姓名、性别、年龄、已借书数目、联系电话、地址操作有信息查询、借书、还书、续借。翻译成 VB6 类模块 clsReader.cls —— 读者实体类 Option Explicit Private mCardNo As String 读者编号证件号 Private mName As String 姓名 Private mSex As String 性别 Private mAge As Integer 年龄 Private mBorrowed As Integer 已借书数目 Private mPhone As String 联系电话 Private mAddress As String 地址 属性读写编号只读其余可写 Public Property Get CardNo() As String CardNo mCardNo End Property Public Property Let CardNo(ByVal v As String) mCardNo v End Property Public Property Get ReaderName() As String ReaderName mName End Property Public Property Let ReaderName(ByVal v As String) mName v End Property Public Property Get BorrowedCount() As Integer BorrowedCount mBorrowed End Property 借书先校验额度再写借阅表并回写库存 Public Function BorrowBook(ByVal sBannerCode As String) As Boolean If mBorrowed 5 Then 单读者最多同时借 5 本 MsgBox 已达借阅上限请先归还, vbExclamation BorrowBook False Exit Function End If If Not IsBookAvailable(sBannerCode) Then MsgBox 该书当前不在架, vbExclamation BorrowBook False Exit Function End If Call InsertLoan(mCardNo, sBannerCode) 写 borrow 表 Call SetBookState(sBannerCode, 借出) 改 book.state mBorrowed mBorrowed 1 BorrowBook True End FunctionCardNo这个属性我写成了可读可写实际项目里更常见的是只在构造函数里赋值一次、之后只读避免运行中改主键。BorrowBook里两步校验的顺序有讲究先查额度再查库存。额度是内存里的计数判断代价为零库存要查库放后面能省一次数据库往返。这个顺序不是玄学是血泪经验——早期版本我写反了高并发下每次都要多做一次查询。mBorrowed这一列在user表里其实没存是靠SELECT COUNT(*) FROM borrow WHERE user_cardno? AND receivedate IS NULL实时算的。类里冗余一个计数是为了少查库但得在还书时同步递减漏一处就对不上。4.3 ADO 连接封装文档里的 C# 片段在 VB6 里该怎么改这是整份文档最大的坑。3.4 节「数据库的连接」贴的代码是 C#public class ConnectionClass { string strConn; SqlDataReader reader; SqlCommand comm; SqlConnection myConn; StreamReader strFile; // ... 从 F://conn.txt 读取连接字符串 }SqlConnection、SqlDataReader这些是 .NET 的类VB6 里根本不存在编译直接报「用户定义类型未定义」。文档显然是从某个 .NET 项目里摘过来的跟 VB6 工程对不上。VB6 的正确写法是用 ADODB basDB.bas —— 数据库连接标准模块 Option Explicit Public gConn As ADODB.Connection 全局连接对象 打开连接连接串不再读 txt 文件改为常量或注册表 Public Function OpenConn() As Boolean On Error GoTo ErrHandler If gConn Is Nothing Then Set gConn New ADODB.Connection End If If gConn.State adStateOpen Then OpenConn True Exit Function End If SQL Server 2000 用 SQLOLEDB 提供者 gConn.ConnectionString ProviderSQLOLEDB;Data Source.; _ Initial CatalogBookMIS;User IDsa;Password****; gConn.ConnectionTimeout 10 gConn.Open OpenConn True Exit Function ErrHandler: MsgBox 数据库连接失败 Err.Description, vbCritical OpenConn False End Function 查询返回断开连接的记录集避免连接被长期占用 Public Function QueryRS(ByVal sSQL As String) As ADODB.Recordset Dim rs As New ADODB.Recordset If Not OpenConn() Then Exit Function rs.CursorLocation adUseClient 客户端游标支持断开 rs.Open sSQL, gConn, adOpenStatic, adLockReadOnly Set rs.ActiveConnection Nothing 断开窗体关掉后不占连接 Set QueryRS rs End Function 执行增删改返回受影响行数 Public Function ExecSQL(ByVal sSQL As String) As Long If Not OpenConn() Then Exit Function gConn.Execute sSQL, ExecSQL End FunctionProviderSQLOLEDB是 SQL Server 2000 的原生提供者换成SQLNCLI或MSOLEDBSQL是后续版本的事。CursorLocation adUseClient配Set rs.ActiveConnection Nothing这一对组合很关键——默认的服务端游标会一直占着连接窗体开多了连接池就爆报「超出连接数上限」。改成客户端游标断开连接之后记录集在内存里随便遍历。原文从F://conn.txt读连接串这个做法要坚决改掉。硬编码盘符换台机器就打不开而且密码明文写在文件里安全上是白给。VB6 时代的常规做法是写进注册表SaveSetting/GetSetting或者干脆做成配置窗体让用户填一次。注意VB6 里用 ADO 之前必须先加引用菜单「工程 → 引用」勾选Microsoft ActiveX Data Objects 2.8 Library。漏了这一步ADODB.Connection一样报未定义。4.4 登录窗体详细设计一次请求的完整链路文档第 4.2 节拿登录窗体当详细设计的例子这个选择很聪明——登录虽然简单但把界面、校验、数据库、会话四件事全串了一遍。界面层一个txtUser、一个txtPwdPasswordChar设成*、两个按钮cmdLogin/cmdCancel。业务层点登录时先做非空校验再查login表比对密码通过后按role加载主窗体菜单。数据层一次参数化查询别拼字符串。 frmLogin.frm —— 登录按钮 Private Sub cmdLogin_Click() Dim sUser As String, sPwd As String Dim rs As ADODB.Recordset Dim sSQL As String sUser Trim(txtUser.Text) sPwd Trim(txtPwd.Text) If Len(sUser) 0 Or Len(sPwd) 0 Then MsgBox 用户名和密码不能为空, vbExclamation, 提示 Exit Sub End If 用参数化写法避免密码里的单引号把 SQL 拼坏 sSQL SELECT user_name, psw, role FROM login _ WHERE user_name Replace(sUser, , ) Set rs QueryRS(sSQL) If rs Is Nothing Then Exit Sub If rs.EOF Then MsgBox 用户不存在, vbExclamation, 提示 rs.Close: Set rs Nothing Exit Sub End If If Trim(rs!psw ) sPwd Then MsgBox 密码错误, vbExclamation, 提示 rs.Close: Set rs Nothing Exit Sub End If 登录成功把角色和用户名存到全局变量供主窗体判断权限 gUserName rs!user_name gUserRole rs!role rs.Close: Set rs Nothing Unload Me frmMain.Show End SubReplace(sUser, , )是手写的单引号转义防不住所有注入手法但在 VB6 ADO 环境下比裸拼字符串强得多。更正规的做法是用ADODB.Command加CreateParameter代价是代码变长课程设计量级用转义足够。密码明文比对是这份文档的另一个历史包袱。SQL2000 时代没强制要求加密但现在哪怕做练习项目也建议在login表里存 MD5 或 SHA1 摘要登录时对输入取摘要再比对。VB6 没有内置哈希函数得引msxml3.dll里的DOMDocument或者自己写一个不算难。frmMain.Show之前必须先Unload Me否则登录窗体挂在后面用户关掉主窗体程序也不退出。这个细节文档没写我第一次做的时候卡了半小时。5. 避坑与排查这份文档落地时最容易翻车的 5 个点文档看着完整真按它敲代码五个坑蹲在那等着。现象一数表的时候发现只有 7 张跟正文写的 8 张对不上。原因文档在 3.1 节说「需建立 8 个表」往下只列了book、borrow、login、punishment、user、libraryer、admin七张。缺的那张大概率是图书类别表因为book表里塞了kindno和kindname两个字段说明类别信息原本是独立实体被反规范化压进了书目表。 解决补一张book_kind(kindno PK, kindname, keywords, remark)book表只留kindno做外键。补上之后 8 张表就齐了类别改名也不用扫全表。现象二录入第二本同名书时报主键冲突。原因book表拿bookname当主键。书名在业务上根本不唯一同名不同版、同名不同出版社的教材一抓一大把。 解决把主键换成bannercodebookname降级为普通索引。如果历史数据已经按书名建过主键先ALTER TABLE book DROP CONSTRAINT掉原主键再加新主键最后补索引三步不能省顺序。改完之后查重逻辑也要跟着改find函数从按书名查改成按编号查。现象三数据库连接那段代码贴进工程编译报「用户定义类型未定义」。原因3.4 节给的是 C# 代码SqlConnection、SqlDataReader全是 .NET 的类VB6 工程里不存在。 解决整段替换成ADODB.ConnectionADODB.Recordset见 4.3 节的写法。同时检查「工程 → 引用」里有没有勾选Microsoft ActiveX Data Objects 2.8 Library漏勾一样报错。现象四窗体开到第五六个就报「连接数超限」或者程序关不掉。原因用默认的服务端游标打开记录集Recordset对象没关连接一直挂着。SQL2000 默认连接数不大开几个查询窗体就爆。 解决CursorLocation设为adUseClientOpen完之后Set rs.ActiveConnection Nothing断开。每个用到Recordset的窗体在Form_Unload里补rs.Close: Set rs Nothing。这条我踩过不止一次后来养成习惯凡是Set rs ...后面必跟三行收尾代码。现象五换台机器部署程序启动就提示数据库连接失败。原因连接串从F://conn.txt读的盘符和路径写死换机器文件就找不到Data Source里写的是具体机器名目标机没这个实例。 解决连接串改成常量放标准模块Data Source用.表示本机默认实例或者做一个配置窗体用SaveSetting BookMIS, DB, ConnStr, s写进注册表启动时GetSetting读出来。生产环境更彻底的做法是配 ODBC 数据源程序里只写 DSN 名。这五条里前两条是设计缺陷后三条是环境和代码细节。设计缺陷必须在动手写窗体之前修掉改晚了一天的工作量就变成三天。6. 二次开发把教学文档改造成能交付的小系统按文档原样实现出来功能是通的但离「能交付」还差几步。这几步恰好也是最能体现二次开发功力的地方。第一件事是把book.state做成真正的状态机。文档里state只是varchar(4)取值随便填。定死三个值——在架、借出、遗失再写一个统一的状态流转函数所有改状态的入口都走它 状态流转只允许 在架-借出、借出-在架、任意-遗失 Public Function ChangeBookState(ByVal sCode As String, _ ByVal sTo As String) As Boolean Dim sFrom As String sFrom GetBookState(sCode) Select Case sFrom - sTo Case 在架-借出, 借出-在架, 在架-遗失, 借出-遗失 ExecSQL UPDATE book SET state sTo _ WHERE bannercode sCode ChangeBookState True Case Else MsgBox 非法状态流转 sFrom - sTo, vbExclamation ChangeBookState False End Select End Function这个函数把「什么状态能变成什么状态」写死在一处比散落在借书、还书、报损三个窗体里的UPDATE语句可靠得多。后面要加「预约」状态只在Select Case里加一行不用满工程找。第二件事是补超期罚款。文档有punishment表但没写计算逻辑其实一条 SQL 就能出来-- 计算某读者所有未还且超期的天数与应罚金额按 0.1 元/天宽限期 30 天 SELECT b.user_cardno, b.bannercode, DATEDIFF(DAY, b.borrowdate, GETDATE()) - 30 AS overdue_days, (DATEDIFF(DAY, b.borrowdate, GETDATE()) - 30) * 0.1 AS fine FROM borrow b WHERE b.user_cardno R2024001 AND b.receivedate IS NULL AND DATEDIFF(DAY, b.borrowdate, GETDATE()) 30;DATEDIFF减 30 是宽限期receivedate IS NULL保证只算在借的。这条查询直接挂到读者登录后的首页上超期自动弹窗提示文档里「对超期情况自动给出提示信息」这条需求就闭环了。注意金额用decimal或numeric算别用float浮点误差在罚款场景里很扎眼。第三件事是给自己留验证方法。改完之后别靠手点窗体验证写一张对照表把每个用例的输入、预期、实际三列填满每改一处就回归一遍用例输入预期验证点添加书目已有编号提示已存在走库存增加book行数不变数目1借书已借满 5 本的读者拒绝借阅borrow无新行state不变还书超期 40 天的记录生成罚款 1.0 元punishment有新行删除读者该读者有在借记录拒绝删除外键报错user行保留第四件事是迁移路径。VB6 SQL2000 这套组合在新机器上装都费劲真要长期维护我一般会把数据层先切到 SQLiteADO 走 ODBC 驱动能连窗体保留这样至少不用装数据库服务再往后就是把业务逻辑抽出来重写成 Web 版borrow表的读写逻辑几乎能平移。这套文档我前后对着敲过两遍第一遍按原样抄卡在 C# 连接代码那儿第二遍边改边记才把上面这些坑一条条标出来。从那以后我每次拿这类课程设计文档开工都强制先把表数量和主键设计核一遍再动手写第一行窗体代码——省下的返工时间比核对的十分钟多得多。希望帮到你。本文还有配套的精品资源点击获取
企业数字化 ERP 产品动态
相关推荐
海湾火灾报警MODBUS RTU规约:寄存器映射与串口对接避坑 简介:面向消防报警系统集成与上位机开发人员的《海湾MODBUS RTU通讯规约》技术文档,聚焦火灾报警控制器与主机之间的数据采集对接。文档围绕MODBUS RTU从站实现展开,给出4800波特率、无奇偶校验、1位停止位的通讯参数配置,说明功能… · 2026/11/4 10:38:12
TSL1401线性CCD:128像素时序、曝光与采集实践 简介:面向智能车竞赛光电组、嵌入式视觉初学者及需要快速上手线阵传感器的开发者,这份简明技术文档围绕 TSL1401 线性 CCD 的实际使用展开,帮助读者理解其与面阵 CCD 的差异、128 像素一维图像采集流程及常见调试问题。资源共 1 个 PDF 文件&… · 2026/11/4 10:38:12
基于RDMA的高性能KV存储同步优化实践 1. 项目背景与核心挑战这个基于EPOLL的高性能KV存储项目(kvstore)原本采用传统的TCP协议实现主从数据同步。在高并发场景下,频繁的send系统调用成为了性能瓶颈。每次同步操作都需要将数据从用户空间拷贝到内核空间,再由网卡发送出去,这种数据… · 2026/11/4 10:38:12
MAS 激活脚本实操:4 种方式永久激活 Windows 与 Office MAS 激活脚本实操:4 种方式永久激活 Windows 与 Office 【免费下载链接】Microsoft-Activation-Scripts Open-source Windows and Office activator featuring HWID, Ohook, TSforge, and Online KMS activation methods, along with advanced troubleshooting. 项… · 2026/11/4 11:25:17
如何快速部署Ideogram-4模型到ComfyUI?新手必备安装指南 如何快速部署Ideogram-4模型到ComfyUI?新手必备安装指南 【免费下载链接】Ideogram-4 项目地址: https://ai.gitcode.com/hf_mirrors/Comfy-Org/Ideogram-4
想要在ComfyUI中体验Ideogram-4的强大AI图像生成能力吗?这篇快速部署指南将为你提供完整… · 2026/11/4 11:24:17
JoyAI-Echo实战教程:如何编写故事提示JSON生成连贯多镜头视频 JoyAI-Echo实战教程:如何编写故事提示JSON生成连贯多镜头视频 【免费下载链接】JoyAI-Echo JoyAI-Echo,这是一个独立的、仅用于推理的版本,旨在实现分钟级多镜头音视频生成。它采用了经过蒸馏的DMD生成器、配对的跨模态记忆以及故事级别的一致… · 2026/11/4 11:24:17
Hermes Agent与区块链存储集成:去中心化数据管理的终极指南 Hermes Agent与区块链存储集成:去中心化数据管理的终极指南
在数字化时代,数据安全与隐私保护成为重中之重。Hermes Agent作为一款强大的AI代理工具,通过与区块链存储技术的创新集成,为用户提供了前所未有的去中心化数据管理解决… · 2026/11/4 11:24:17
Hermes Agent模型部署实战指南:量化与剪枝技术深度解析 Hermes Agent模型部署实战指南:量化与剪枝技术深度解析
在AI应用开发领域,模型部署的效率直接决定了产品的响应速度和资源成本。Hermes Agent作为一款功能强大的AI代理框架,通过内置的量化与剪枝技术,能够将大型语言模型的推理速… · 2026/11/4 11:24:17
如何使用Hermes Agent构建智能物流系统:路径优化与配送管理完整指南 如何使用Hermes Agent构建智能物流系统:路径优化与配送管理完整指南
在当今快节奏的商业环境中,物流配送的效率直接影响企业的竞争力。Hermes Agent作为一款强大的自动化工具,提供了路径优化与配送管理的核心功能,帮助企业实现智… · 2026/11/4 11:23:17
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 3:16:49
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 6:59:29
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 7:02:38
AI辅助技术栈评估:提升研发决策效率60% 1. 项目背景与核心价值技术决策一直是研发团队面临的重大挑战。随着技术生态的快速演进,技术栈的选择、维护和迭代变得愈发复杂。我们团队在过去两年里,先后经历了三次重大技术栈迁移,每次都需要投入大量人力进行技术评估和风险分析。直到去年… · 2026/11/4 0:00:17
从生成Token到直接决策:Jev如何重塑AI Agent工程实践 1. 从"生成文字"到"拍板决策":Jev 到底在解决什么问题大多数人第一次接触大模型,脑子里建立的模型是"输入问题、输出文字"。你问它今天天气怎么样,它给你一段描述;你让它写个周报,它给你… · 2026/11/4 0:00:17
32G统一内存跑大模型:内存账、TPS实测与端云分流 想在一台 32G 统一内存的小主机上把大模型跑成"日常可用",这件事我前后折腾了小半年。标题里说的这台 Mac mini M6,是我们这次讨论的目标平台,具体到手规格以实物为准;但下面这套推算方法和实测思路,对任何统… · 2026/11/4 0:00:17
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 3:16:49
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 6:59:29
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 7:02:38