项目实战--C#实现图书馆信息管理系统

本项目是要开发一个图书馆管理系统,通过这个系统处理常见的图书馆业务。这个系统主要功能是:(1)有客户端(借阅者使用)和管理端(图书馆管理员和系统管理员使用)。(2)借阅者可以对于图书馆里面存在的图书进行借阅图书、归还图书等基本操作。(3)借阅者可以对于图书馆里面的图书的书号、种类、书名关键字等信息进行查询。(4)图书管理员能够查看借阅者的借阅图书、归还图书等的记录。(5)图书管理员能够对图书信息进行查看、增加、修改、删除功能。(6)图书管理员能够对读者信息进行查看、增加、修改、删除功能。(7)借阅者还可以在系统提供的电子资源进行浏览网络书籍。(8)客户端和管理端用户都可以进行公告的发布或留言的发布以及查看动态等操作。

资源附在文章末尾。


第1章 概 述

1.1 项目的目的和意义

1.1.1本课题研究背景

一直以来人们使用传统的人工方式管理图书馆的日常工作,对于图书馆的借书和还书过程,想必大家都已很熟悉。在计算机尚未在图书管理系统广泛使用之前,借书和还书过程主要依靠手工。一个最典型的手工处理还书过程就是:读者将要借的书和借阅证交给工作人员,工作人员将每本书上附带的描述书的信息的卡片和读者的借阅证放在一个小格栏里,并在借阅证和每本书贴的借阅条上填写借阅信息。这样借书过程就完成了。还书时,读者将要还的书交给工作人员,工作人员根据图书信息找到相应的书卡和借阅证,并填好相应的还书信息,这样还书过程就完成了。随着近年来信息技术及计算机网络技术的不断发展, 图书馆也先从传统的图书馆发展到自动化图书馆,再发展到今天的数字图书馆,这些变化使得图书馆的形象越来越现代化,人们查找资料也更加方便。对于一些小图书馆和一些图书室来说,由于工作人员比较少,长期以来,作为图书馆的主要工作—图书借阅一直未能很好地开展。在平常的图书借阅工作中, 由于大部分读者不熟悉图书馆藏书,且对图书排架分类的不了解,往往花费很长时间才能找到其所需的书。为提高管理效率,更好地为读者服务,利用已有的条件,使图书查询和借阅变得更加方便快捷,从而使图书室的工作效率得到明显提高。

基于这此问题,有必要建立一个图书管理系统,使图书管理工作规范化,系统化,程序化,避免图书管理的随意性,提高信息处理的速度和准确性,能够及时、准确、有效的进行查询和修改图书情况等图书管理操作。

1.1.2本课题目的和意义

随着社会的进步,信息技术的广泛应用,数字化管理的优势日趋显著。针对中小型图书馆或图书室管理落后的情况,设计实现一个图书信息管理系统,通过与计算机的结合使用对中小型图书馆或图书室的各种图书信息进行管理可以给管理员和用户带来以下不同的方便:检索迅速、查找方便、可靠性高、存储量大、保密性好、寿命长、成本低等。这些优点能够极大地提高工作效率,也是图书馆等部门管理科学化、正规化的重要标志之一。而且计算机管理的成本不断降低。因此,开发一套这样的中小型图书管理软件已经很有必要,并且实现研究服务于实践的原则。

图书馆规模的不断扩大,导致图书数量也相应的增加,有关图书的各种信息量也成倍增加,面对着庞大的信息量,传统的人工方式管理会导致图书馆管理上的混乱,人力与物力过多浪费,图书馆管理费用的增加,从而使图书馆的负担过重,影响整个图书馆的运作和控制管理,因此,必须制定一套合理、有效,规范和实用的图书管理系统,对图书资料进行集中统一的管理。图书管理系统对于现代图书馆而言,是能否发挥其教学研究的作用的自关重要的技术平台,对于在校学生和管理员来说,是能否方便快速获取信息的关键。所以,图书管理系统应该能够为用户提供充足的信息和快捷方便的操作手段。

本系统的宗旨是提高图书管理工作的效率,减少相关人员的工作量,使图书管理工作真正做到科学、合理的规划,系统、高效的实施。目标如下:

(1)减少人力成本和管理费用

(2)提高信息的准确性和信息的安全

(3)改进管理和服务

(4)良好的人机交互界面,操作简单

1.2 技术开发基础

1.2.1 面向对象技术

面向对象技术可以用于软件的设计、开发、维护方面。面向对象技术的原理就是仿照人类解决问题的思维以及方式,将出现的问题划分解决。问题的复杂程度无论大小均被面向对象的思维中作为对象。软件无论任何规格都可以转化为对象的处理,这是是因为对象是数据以及由此产生的操作所构成的独立单元的统称。以对象为中心也是面向对象的中心思想。面向对象技术具有以下特点:

(1)在处理问题的时候把客观事物看作一个个对象无疑接近于人的思维,对象作为实际系统的基本单位,基于面向对象的开发着重的就是问题域与软件程序之间的直接映射关系。在整个研发系统中,组构模型系统以及最终软件中始终采用同一方法,将实际系统抽象表示为对象,并且构成了系统的基本组成单位。

(2)软件可以方便地修改、扩展、维护依赖于信息的封装。所谓封装就是指在面向对象的技术中对象的基本属性以及服务功能结合为一个独立的信息体,其内部信息无法被外部得知。正是由于封装使其自身的修改独立于整体,所以应用其开发的软件才会便于更改维护。除此之外,面向对象的开发也推崇软件开发中信息与数据的独立化和抽象化。

(3)类是具有继承关系的,它可以减少系统的构成过程以及文档。类就是具有相同属性与服务对象的集合,面向对象技术将其归为一类。类是对象的抽象化,是每个实例对象的集合。类按照程度的不同分为一般和特殊,特殊类会承接一般类所具有的服务。这种类的处理方式的实现也是面向对象被人们接受的原因之一。

(4)支持再利用,扩充了可再利用的范围。面向对象的应用很广,它可以应用于软件开发的整个周期,包含了分析以及编码。而目,该方法不仅支持软件的再利用,即便是其设计模型也可以这样处理,而且它还可以实现网络中不同节点之间资源的交换利用。

(5)支持难度较高的软件的开发。现在的软件系统中常见的并发、层次等现象也是面向对象所支持的。复杂的系统的层次结构多由很多子系统构成.

分布式对象技术已经成为了构建服务框架以及软件的核心技术,在大型应用系统(分布式)开发中更具有强效的作用。这也发展成为了三种最具代表性的技术:微软的COM/DCOM技术、Sun公司的Java技术和OMG的COBRA技术。

(6)支持开放式系统的开发。在开发复杂系统之时,面向对象的开发,由于通过不同的应用来进行软件的构成,使其具有更大的抗力,再加上软件生命周期之内都有系统集成的穿插,大幅度降低了开发复杂系统的风险。

1.2.2 C#.NET开发语言/框架

.NET是一个微软的技术平台,致力于敏捷、快速开发和跨平台,可以用于软件开发和网站开发。.NET Framework 4.0组件包含了.NET平台的基础委库以运行认境,开发人员在细写程序时可以自接调用微软到装好的类。C#是.NET平台中使用最为广泛的一种开发语言,基于CIL规范、用C#8写的程序或是类库在VB、F#或是任何一种基于.NET及CIL规范的编程语言中都可以调用、继承。开发环境、开发平台和开发语言,三者之间相辅相成,完成应用程序的编写。

.Net Framework是Microsoft为开发应用程序而创建的一个具有革命意义的平台。这个定义是非常广义的,首先,这句话没有说是在Windows操作系统上开发的应用程序,言外之意,.Net Framework平台不仅仅在Windows操作系统和Windows Moblie操作系统上运行,而且可以在Linux、macOS上运行。

所以.Net Framework就是一个开发应用程序的平台。还有这个定义并未指定开发程序的类型,比如桌面应用程序、Windows Store(UWP)应用程序、云/Web应用程序、Web API和其他各种类型的应用程序。

.Net Framework还可以用于各种语言进行开发编程不仅仅包含C#,还有F#、C++、JavaScript、VB以及COBOL。所以,.Net Framework就是可以在多种操作平台上运行,可以开发多种类型应用程序、可以使用多种开发语言开发应用程序的一个平台。简而言之就是开头的那句话。

1.2.3 SQL Server数据库管理系统

SQL是英文名称Structured Query Language的缩写,翻译过来就是结构化查询语言。通常对实践操作中与其相关的具体作用分析可以得出,人们可以理解成在各种不同的数据库之间建立具体联系。美国国家标准协会介于数据库管理系统,也明确地规定SQL是最标准的语言。通过使用SQL语句实现数据的更新、提取,对海量数据进行整理。在当今时代,SQL使用广泛,大部分的关系型数据库管理系统中使用的语言中都少不了它的身影,不少数据库甚至通过对SQL语句的深入开发研究,拓展出新功能,可以借助于SQL命令,有利于实现对数据库操作的专业性。在对告警数据分析处理中,不少管理信息系统都引进了Microsoft公司的SQL Server数据库系统,通过对数据库的分布存储和访问,成功实现高级别的数据管理工作,有利于降低系统的负担,系统的稳定性会有一定程度的增强。

优点:第一,它具有易用性、适合分布式组织的可伸缩性等特性较强;同时SQL Server数据库用于决策支持的数据库功能具有明显优势;具有与其他服务器软件紧密关联的特性,它的性价比也比较高。第二,在数据挂目录与分析中,它的便捷性、灵活性都很强,可以实现各种单位在快速变化的环境中对数据应用分析的便捷,在激烈的外部竞争中取得优势。这一优势在数据管理和分析中,对于原始数据转化为商业智能信息具有非常重要的意义。第三,它可以快速开发新一代企业级商业应用程序,因此为企业增强核心竞争力,在激烈的市场竞争中处于不败地位。第四,它可以保持一些重要的基准测试,对其可伸缩性和速度奖进行记录,为Internet上和防火墙外进行查询提供了极大的便利。

缺点:(1)SQL Server数据库的开放性不够强,只能在windows上运行,而windows这一平台的可靠性和安全性都是很有限的,难以实现在运行中久经考验或者在处理大数据库时有所不便。(2)SQL Server在处理日益增多用户数和数据时具有局限性,面对繁杂庞大的数据留,SOL Server在安全性方面并没有获得任何安全证书,缺少保障:加之用户较多,SQL Server数据库在处理时会面临性能上当面临多用户时性能不佳。(3)客户端支持及应用模式不够多,一般只支持C/S模式,它的涵盖面不够广,用途不够广。在以上SQL Server数据库的三个缺点中,笔者认为最重要的就是其安全性难以保障,这阻碍了其对各项功能的利用。

1.2.4 Client/Server开发模式

随着计算机网络技术的成熟和应用普及,特别是局域网的发展、PC机的出现,越来越多的用户和企业开始使用计算机管理一些事务。PC机的资源没有大型、中型甚至小型主机丰富,但将多台PC机联成网,必然会增加资源含量,各个用户都在网络上来共享所有资源。根据客户/服务器(Client/Server简记为C/S)体系结构的概念,至少用两台计算机来分别充当客户机和服务器角色。客户端可以是X86体系的风机或RISC体系的工作站等,而服务器端硬件一般比较高档,比如:高档PC服务器或SUN专用服务器;操作系统也比较高档,比如: Windows NT和 Unix。

服务器-客户机,即Client-Server(C/S)结构。C/S结构通常采取两层结构。服务器负责数据的管理,客户机负责完成与用户的交互任务。

客户机通过局域网与服务器相连,接受用户的请求,并通过网络向服务器提出请求,对数据库进行操作。服务器接受客户机的请求,将数据提交给客户机,客户机将数据进行计算并将结果呈现给用户。服务器还要提供完善安全保护及对数据完整性的处理等操作,并允许多个客户机同时访问服务器,这就对服务器的硬件处理数据能力提出了很高的要求。

在C/S结构中,应用程序分为两部分:服务器部分和客户机部分。服务器部分是多个用户共享的信息与功能,执行后台服务,如控制共享数据库的操作等;客户机部分为用户所专有,负责执行前台功能,在出错提示、在线帮助等方面都有强大的功能,并且可以在子程序间自由切换。

C/S结构在技术上已经很成熟,它的主要特点是交互性强、具有安全的存取模式、响应速度快、利于处理大量数据。但是C/S结构缺少通用性,系统维护、升级需要重新设计和开发,增加了维护和管理的难度,进一步的数据拓展困难较多,所以C/S结构只限于小型的局域网 。

1.2.5开发平台及第三方插件

本项目使用的开发平台是Visual Studio,第三方界面库Cskin和DevExpress。

本项目基于Microsoft Visual Studio 2022开发环境,该软件集成了.NET程序的便捷功能,可编程性强。它可以利用使用者所用的语言(如C、C++、C#等)进行编程,也可以和.NET平台协同开发程序,具有强大的功能和优势:该软件内部提供了诸多实用的功能,如代码片段、代码复用等,能帮助程序员快速开发应用程序;通过图形化模型的设计,集成多种组建和控件,便于普通开发者简单快速完成项目开发;集成了多种调试工具,方便程序员快速完成代码调试:具有丰富的扩展机制,用户可以根据自己的需要在此开发软件上拓展更多功能。

.NET平台,c# 语言,开发很方便,支持拖拉拽生成界面,程序员只需要专注自己的业务逻辑即可,大大节省了开发时间。但是呢,原生系统的界面比较丑陋,不太友好,需要再美化一下。.net平台因为是封闭的,缺乏生态系统,所以其上的UI库很少,CSkin是我在CSDN社区寻到的一个广泛被推荐的第三方控件。CSkin界面库是完全免费的,可以任意使用,并且代码中无任何限制。

但DevExpress界面库更加强大,支持Edit编辑且自提供编辑器进行一定程度上的控件外观设计。

1.2.6三层架构技术

三层架构来源于后端开发的一种分层思想。三层架构(3-tier architecture)通常意义上的三层架构就是将整个业务应用划分为:界面层(User Interface layer)、业务逻辑层(Business Logic Layer)、数据访问层(Data access layer)。

区分层次的目的即为了“高内聚低耦合”的思想。在软件体系架构设计中,分层式结构是最常见,也是最重要的一种结构。微软推荐的分层式结构一般分为三层,从下至上分别为:数据访问层、业务逻辑层(又或称为领域层)、表示层。

1.表示层:主要对用户的请求接受,以及数据的返回,为客户端提供应用程序的访问。

2.业务逻辑层:主要负责对数据层的操作。也就是说把一些数据层的操作进行组合。

3.数据访问层:主要看数据层里面有没有包含逻辑处理,实际上它的各个函数主要完成各个对数据文件的操作。而不必管其他操作。

1.3 系统基本功能

本项目是要开发一个图书馆管理系统,通过这个系统处理常见的图书馆业务。这个系统主要功能是:(1)有客户端(借阅者使用)和管理端(图书馆管理员和系统管理员使用)。(2)借阅者可以对于图书馆里面存在的图书进行借阅图书、归还图书等基本操作。(3)借阅者可以对于图书馆里面的图书的书号、种类、书名关键字等信息进行查询。(4)图书管理员能够查看借阅者的借阅图书、归还图书等的记录。(5)图书管理员能够对图书信息进行查看、增加、修改、删除功能。(6)图书管理员能够对读者信息进行查看、增加、修改、删除功能。(7)借阅者还可以在系统提供的电子资源进行浏览网络书籍。(8)客户端和管理端用户都可以进行公告的发布或留言的发布以及查看动态等操作。


第2章 系统设计

2.1系统功能模块设计

图书馆管理系统可以进行用户的注册,登录,数据的统计,管理员可以发布系统公告以及对图书、书类、普通用户的信息进行添加、修改、删除、查询等工作,读者可以进行借阅与归还、留言建议等操作。

图书馆信息管理系统是图书馆系统中不可或缺的一个子系统,它涉及到图书、书类、人员、时间、罚金等信息的结合。图书馆信息系统包括七个小模块:

1.登录注册:注册模块主要是为用户提供注册登录的机会,并存储个人数据,保护个人信息,用户提供相应信息即可注册账户。登录模块需要用户提供用户名和密码进行登录的操作,并匹配权限与账户密码。

2.找回密码:找回密码模块需要用户提供账号、注册时使用的手机号,并使用支持扫描QRCode二维码的应用进行扫描获取本次验证码,方可从数据库中找回密码。

3.借阅图书。借阅者登录以后选择借阅图书进入,在这借阅者可以输入想要借的图书的名字,图书馆管理系统会去进入查询书籍信息检测是否有该图书存在和进入预借图书功能检测用户是否预借了图书,并且查询借阅者的借书数量是否超过了限制。如果图书馆有该书,用户借书数量没有超过限制并且准备借这本图书,系统会记录用户的借书时间和借阅图书的信息,并且显示借书的最长时间给用户。

4.归还图书。借阅者登录以后选择归还图书进入,在这借阅者可以归还之前在图书馆里面借阅的图书,通过图书馆管理系统检测图书没有过期的书可以归还,过期的图书借阅会计算罚款信息然后归还

5.用户管理。该模块使管理员可以对已注册的读者用户进行信息的查询、注销、修改,并为读者注册账户。读者用户可以查询自身非密码的所有用户注册信息,并可以修改自身非管理员锁定数据信息外的信息,即修改密码。

6.图书管理。该模块为管理员提供了对图书的录入、删除、查询、修改以及图书种类信息的增、删、查、改的权限。

7.留言动态。本模块是系统管理员发布系统公告的渠道,提交留言和建议使管理员了解用户反馈,查看系统公告、了解其他用户体验等。

8.拓展设计。除留言功能以外,拓展设计还包括了网络数字资源、报表与数据统计等

图书馆信息管理系统功能模块如图2-1系统功能模块图所示。

图2-1 系统功能模块图

2.2 数据库设计

通过使用数据库可以对我们所需要的信息进行存储。从而进行添加和修改删除查询等操作的实现。

根据上文要求分析,图书馆信息管理系统数据库(Stark图书馆管理系统)中包括读者信息”tblReader”、管理员信息”tblAdmin”、图书信息”tblBooks”、图书类型”tblType”、借阅信息”tblBorrow”、留言信息”tblMsg”六个数据表。表的结构、表字段的数据类型及相关说明如下:

1.读者信息表

该表主要存放读者用户的相关信息,包括读者用户名,密码,读者编号,读者姓名,读者性别,注册日期,电话号码,所属单位,家庭住址,可借阅书籍最大数量等,其结构如表2-1读者信息表所示

表2-1 读者信息表

列名

说明

数据类型

约束

Uid

用户名

nchar(20)

主键

Pwd

密码

nchar(20)

非空

Rno

读者编号

nchar(20)

主键

Name

读者姓名

ncahr(50)

非空

Sex

读者性别

ncahr(10)

非空

Date

注册日期

ncahr(20)

非空

Tel

电话号码

nchar(11)

非空

Dept

所属单位

nvarchar(MAX)

非空

Address

家庭住址

nvarchar(MAX)

非空

Number

可借阅书籍

int

非空

2.管理员信息表

该表用于存放图书管理员的信息,包括用户名及密码,管理员姓名,管理员联系方式,用户权限等,其结构如表2-2管理员信息表所示。

表2-2 管理员信息表

列名

说明

数据类型

约束

Uid

用户名

nchar(20)

主键

Pwd

密码

nchar(20)

非空

Name

姓名

ncahr(50)

非空

Tel

电话

ncahr(11)

非空

Limit

权限

nchar(10)

非空

3.图书信息表表

该表用于存放图书信息,包括书号,书名,类别,出版社,作者,价格,登记时间,借阅状态等,其结构如表2-3图书信息表所示。

表2-3 图书信息表

列名

说明

数据类型

约束

Bno

书号

nchar(20)

主键

Title

书名

nchar(50)

非空

Tno

类别编号

ncahr(5)

主键

Publisher

出版社

nchar(50)

非空

Author

作者

nchar(50)

非空

Price

价格

int

非空

Date

登记日期

nchar(20)

非空

State

借阅状态

nchar(10)

非空

4.图书类型表

该表用于存放图书类型的基本信息,包括类别编号,类别,该类别图书最大借阅天数,逾期每日罚金等,其结构如表2-4图书类型表所示。

表2-4 图书类型表

列名

说明

数据类型

约束

Tno

类别编号

nchar(5)

主键

Type

类名

nchar(50)

非空

Day

最大借阅天数

int

非空

Fine

逾期每日罚金

int

非空

5.借阅信息表

该表用于存放借阅的相关信息,包括所借阅书籍的编号,借阅人编号,借阅日期和归还日期,逾期天数,总罚金,归还状态等,其结构如表2-5借阅信息表所示。

表2-5 借阅信息表

列名

说明

数据类型

约束

Bno

借阅书号

nchar(20)

主键

Rno

借阅人编号

nchar(20)

主键

Bdate

借阅日期

nchar(20)

非空

Rdate

归还日期

nchar(20)

可空

Exday

逾期天数

int

非空

Fine

罚款

int

非空

State

归还状态

nchar(10)

非空

6.留言信息表

该表用于存放留言人、留言内容和留言日期,结构如表2-6留言信息表所示。

表2-6 留言信息表

列名

说明

数据类型

约束

Uid

留言人标识

nchar(20)

主键

Msg

留言内容

nvarchar(MAX)

非空

Date

留言时间

nchar(50)

非空


第3章 系统的详细设计及实现

3.1 公共类

本系统大多模块都需要访问数据库,因此在开发时编写了一些访问数据库的方法,如返回数据集的公共查询方法,执行数据操作的公共方法,并把它们放在了一个公共的类(SQLHelper)中,然后在各模块中调用这些方法来实现对数据库的访问。

同样,在实现访问时,需要记录一些基本信息,因此也需要使用到一些公共的静态变量,本系统在开发时把这些变量放在了Model类库中,根据各数据表建立了各个类来存储静态变量如类AdminInfo、类ReaderInfo、类BooksInfo、类BooksType、类BorrowInfo、类MsgInfo等。

编写SQLHelper公共类及公共方法:

为系统添加一个”SQLHelper”的公共类,用于存放访问数据库的公共方法,为SQLHelper类声明数据库连接字符串。代码如下:

Static string constr = "server=LAPTOP-B3JDLUIF\\SQLEXPRESS;

uid=sa;pwd=2396573637;database=Stark图书馆管理系统;";

1.链接数据库方法Link()

数据操作的前提是链接并打开数据库,Link()可以将这个操作封装以便使用,避免了程序不断的链接打开时造成的代码冗余。代码如下:

static SqlConnection conn = null;

private static void Link()

{

conn = new SqlConnetion(constr);

conn.open();

}

2.查询方法Query(string sql)

公共数据操作方法Query执行SQL查询(SELECT)语句,返回DataTable。需要通过Link()方法链接打开设置的数据库,然后通过适配器SqlDataAdpter将查询的数据填充到数据集DataSet,关闭数据库,最后以数据表DataTable的形式返回。具体代码如下:

public static DataTable Query(string sql)

{

    Link();

    SqlDataAdapter da = new SqlDataAdapter(sql, conn);

    DataSet ds = new DataSet();

    da.Fill(ds);

    conn.Close();

    return ds.Tables[0];

}

3.增、删、改数据操作方法NoQuery(string sql)

公共数据操作方法NoQuery执行SQL非查询(INSERT、UPDATE、DELECT)语句,返回受影响的行数。首先链接并打开数据库,然后创建SqlCommand变量,使用ExcuteNonQuery()方法执行SQL命令,返回操作影响到的行数。代码如下:

public static int NoQuery(string sql)

{

    Link();

    SqlCommand cmd = new SqlCommand(sql, conn);

    int rows = cmd.ExecuteNonQuery();

    conn.Close();

    return rows;

}

3.2系统登录、注册

登录是每一个成功的项目中不可或缺的模块,好的登录模块可以保证系统的可靠性和安全性。首先为”图书馆信息管理系统”制作了一个简单的登录模块,该模块可进入用户注册界面,注册成功后进行登录,如果忘记密码可以进行找回密码,登录成功后,会根据权限进入各自的系统主窗体(主页面)。

3.2.1登录界面设计

新建一个Windows应用程序,命名为”frmLogin”,更改继承关系(父类由System.Windows.Forms.Form改为CCWin.Skin_Color),使用SkinLabel、TextBox、SkinButton控件等设计登录界面。

根据设计要求的一致性原则,本套登录界面字体一致选择微软雅黑,颜色为黑白二色、具体取决于字体的背景对比色。可点击的模块选择使用按钮、下划线标签控件。根据准确性原则使用一致的标记、明确信息的含义,使用户不需要参考其它信息源。

点击【管理申请】和【读者注册】分别进入两类用户注册界面。点击【忘记密码】会进入找回密码的界面。点击【登录】则根据TextBox的文本属性text以及访问数据库进行数据匹配,然后进入主界面。具体设计如图3-1登录界面所示。

图3-1登录界面

3.2.2登录模块代码实现

在如图3-1所示的登录界面中,【登录】按钮用于验证输入的用户名和用户密码以及用户权限是否匹配,若正确则进入系统主页面;否则弹出相应错误信息,并返回重新输入信息。

1.UI代码

首先系统会根据TextBox.Text的值判断输入的用户信息是否为空,有任何一项为空的话就给予提示,例如不输入用户名会触发以下提示:

if (txtUid.Text == string.Empty)

{

   MessageBox.Show("用户名不可为空");

   return;

}

否则调用BLL层AdminManger或ReaderManger类的State()方法检测是否存在该用户,不存在则有以下提示:

if (AdminManger.State(admin))

{

    MessageBox.Show("用户不存在,请尝试注册用户或更换权限");

    return;

}

没有进入上面的if入口,代表存在用户。存在的话会调用BLL.Manger.Get()方法获取用户全部信息,然后根据用户类的Pwd属性匹配密码做出进一步判断:

if(txtPwd.Text.Trim()!= AdminManger.Get(admin).Pwd.Trim())

{

    MessageBox.Show("密码错误,请尝试找回密码或更换权限");

    return;

}

层层异常排除之后进入主界面,如果选择关闭界面的话,会导致进程结束,后续代码执行不了,所以选择通过this.Hide()隐藏登陆界面,然后打开用户窗体,这里执行ShowDialog()方法不可对该进程之前隐藏的界面进行交互。

this.Hide();

frmAdmin frmAdmin = new frmAdmin(admin);

frmAdmin.ShowDialog();

如果选择的权限不是管理员,而是读者用户的话,上述操作同理。

if (skinComboBox1.SelectedIndex == 1)

{

    ReaderInfo reader = new ReaderInfo();

    reader.Uid = txtUid.Text.Trim();

}

2.BLL层代码

通过调用DAL层代码,State()方法,执行查询操作,如果查询到的数据表的行数为0,代表没有用户,返回true,无法登录。否则返回false,在数据库中找到了该用户,可以进行下一步的密码判断。密码判断是根据用户的账号信息这一唯一标识来查询所有信息并返回一个用户类,然后将获取到的用户信息的密码这一信息与输入的密码进行比较。其中注意到查询结果多用字符串表示,但实体类的个别字段属性为其它类型,需要进行强制类型转换,以确保数据类型准确,查询结果转化完整。

由于查询结果是DataTable类型的数据,该类型有属性Rows.Count。查询结果的行数就是由该属性确定的。具体实现方法如下:

public static bool State(AdminInfo admin)

{

return (AdminService.Query(admin).Rows.Count == 0);

}

public static bool State(ReaderInfo reader)

{

return (ReaderService.Query(reader).Rows.Count == 0);

}

而将数据转化为Model 层的实体信息会更容易判断,由于读者信息过多且二者方法一致,避免代码冗余,下面只陈述管理员的Get()方法:该方法首先会生成一个虚拟用户:

AdminInfo adm = new AdminInfo();

通过使用Query()查询用户所有信息。

adm.Uid = AdminService.Query(admin).Rows[0]["Uid"].ToString().Trim();

由于索引条件是主键,主键设计时保证了唯一性。所以第零行的数据Rows[0]就是该用户所有信息。再根据列名一一获取用户的所有信息,使用ToString()方法将信息转化为字符串,经过去空处理Trim(),将信息存储到新建的用户信息中:

adm.Pwd = AdminService.Query(admin).Rows[0]["Pwd"].ToString().Trim();

adm.Name = AdminService.Query(admin).Rows[0]["Name"].ToString().Trim();

adm.Tel = AdminService.Query(admin).Rows[0]["Tel"].ToString().Trim();

adm.Limit = AdminService.Query(admin).Rows[0]["Limit"].ToString().Trim();

最后将用户类型的数据作为返回值返回给引用处,从而时调用该方法的用户得到具体的信息。例如 realUser=Manger.Get(realUser)。realUser必须具有非null的Uid属性。

3.DAL层代码

调用SQLHelper工具类的查询方法,将用户信息传入,只要用户有账号信息即可查询其它所有信息。两类用户查询的SQL语句分别为:

string sql = string.Format("select * from tblAdmin where Uid='{0}'",a.Uid);

string sql = string.Format("select * from tblReader where Uid='{0}'",r.Uid);

执行操作的语句是:(返回数据表)

return SQLHelper.Query(sql);

4.Model层代码

管理员类,包括账户、密码、姓名、电话、权限信息。

public class AdminInfo

{

    private string uid;

    private string pwd;

    private string name;

    private string tel;

    private string limit;

    public string Uid { get => uid; set => uid = value; }

    public string Pwd { get => pwd; set => pwd = value; }

    public string Name { get => name; set => name = value; }

    public string Tel { get => tel; set => tel = value; }

    public string Limit { get => limit; set => limit = value; }

}

读者类,包括信息如注释所示。

public class ReaderInfo

{

private string uid;//账号

private string pwd;//密码

private string rno;//读者编号

private string name;//姓名

private string sex;//性别

private DateTime date;//办证日期

private string tel;//电话

private string dept;//所属单位

private string address;//家庭住址

private int number;//可借阅书籍

public string Uid { get => uid; set => uid = value; }

public string Pwd { get => pwd; set => pwd = value; }

public string Rno { get => rno; set => rno = value; }

public string Name { get => name; set => name = value; }

public string Sex { get => sex; set => sex = value; }

public DateTime Date { get => date; set => date = value; }

public string Tel { get => tel; set => tel = value; }

public string Dept { get => dept; set => dept = value; }

public string Address { get => address; set => address = value; }

public int Number { get => number; set => number = value; }

}

如果用户填写信息无误后,则进入系统主界面、系统登录界面隐藏。如果单击登录界面的【取消】按钮,则关闭登录界面,退出系统,其单击事件主要代码是:

Application.Exit();//this.Close();同样有效

3.2.3注册界面设计

在系统登录界面,单击【读者注册】/【管理申请】,进入两类用户的注册界面。所以需要提前添加两个注册的窗体”frmRegister1””frmRegister2”给读者和管理员使用。两个窗体均包括TextBox、SkinLable、SkinButton、Panel、RadioButton等控件。

坚持以用户体验为中心设计原则,界面直观、简洁,操作方便快捷,用户对界面上的功能一目了然,不需要经过任何培训就可以使用本应用系统。两个界面最终设计为如图3-2注册界面所示。

图3-2 注册界面

3.2.3注册模块代码实现

点击确认按钮时,首先判断输入信息是否完整非空,然后通过BLL层逻辑功能进行注册,在注册时,会对用户输入信息进行判断,看是否存在用户,存在的话,不予注册。不存在的话,调用再DAL层增加一条用户记录。

1.UI层代码

首先根据TextBox.Text属性值对信息的完整性进行判断:

if (txtName.Text==""||txtPwd.Text==""||txtTel.Text==""||txtUid.Text=="")

MessageBox.Show("信息不完整,请填写完整后重试");

如果信息完整,使用TextBox的Text属性继续下一步的信息填充,注意填充时用字符串的内置方法Trim()方法将两端空字符去除。默认注册管理时的权限等级为“管理员”。

AdminInfo admin = new AdminInfo();

admin.Limit = "管理员";

admin.Uid=txtUid.Text.Trim();

admin.Name=txtName.Text.Trim();

admin.Tel=txtTel.Text.Trim();

admin.Pwd =txtPwd.Text.Trim();

这时候如果填充的信息中账号信息经AdminManger.Register(AdminInfo)方法的检测该账号已被使用,则提示用户账户存在,无法注册:

if(AdminManger.Register(admin)==0)

{ MessageBox.Show("用户存在,无法注册"); return; }

否则的话提示注册成功。关闭注册窗体,关闭该线程。

MessageBox.Show("注册成功");

this.Close();

普通读者用户注册时,性别信息用到了RidioButton控件与管理员注册有细微差别。其中需要使用Checked属性判断哪个选项被选中,并获取其上Text文本信息。进而判断用户注册时是否选则了性别的有效信息。

string sex = string.Empty;

if (rbMale.Checked) sex = "男";

if (rbFemale.Checked) sex = "女";

if (sex == string.Empty)

{

MessageBox.Show("请选择性别");

return;

}

读者的注册日期信息需要使用System.DateTime这一结构体类型的Now成员属性获取当前计算机时间。

reader.Date = DateTime.Now;

2.BLL层代码

通过调用BLL层的Register()方法,进行注册,在此方法内,需要使用State()方法进行状态判断,看是否已经存在过该用户。其中又使用到了登录时的存在状态方法bool State(User)。

public static int Register(ReaderInfo reader)

{

    if (!State(reader))  return 0; //代表存在用户,不能注册

    return ReaderService.Insert(reader);

}

3.DAL层代码

该层代码调用SQLHelper工具类的非查询方法noQuery()进行添加操作,将用户的全部信息添加到数据库,即注册功能。

两类用户注册时的SQL语句分别为:

string sql = string.Format("insert into tblAdmin values('{0}','{1}','{2}','{3}')", a.Uid, a.Pwd, a.Name, a.Tel);

string sql = string.Format("insert into tblReader values('{0}','{1}','{2}','{3}','{4}','{5}','{6}','{7}','{8}','{9}')",r.Rno, r.Name, r.Sex, r.Date.ToString(), r.Tel, r.Dept, r.Address, r.Number, r.Uid, r.Pwd);

执行SQL语句时调用SQLHelper工具类的非查询方法NoQuery()。

return SQLHelper.NoQuery(sql);

4.Model层代码

Model层代码同登录界面的Model层是两大用户类AdminInfo和ReaderInfo。

3.2.5找回密码功能设计

在登录界面点击“找回密码”,进入提前建好的frmForgetpwd窗体,进行找回密码的操作。窗体包括pictureBox、TextBox、Label、Button等控件,其中按照习惯将取消按钮的背景色设置为红色。

考虑到密码信息安全等级比较高,找回密码的操作需要验证更多的信息。因此加入了BarCodeControl控件使用二维码获取验证码功能。找回密码功能一共有两个按钮事件。

取消按钮;关闭本窗体,返回登录界面。确认按钮,在界面加载时,进行信息提示如何操作,并生成六位数字字符串作为本次找回密码的验证码,并生成一个可扫描二维码,例如通过微信、支付宝、浏览器等扫描工具可以获取六位数字字符串。首先判断输入信息是否完整,然后判断账户手机号信息是否匹配,最后判断验证码是否与本次页面加载时生成的二维码一致,若一致,则会弹出账户的密码,用户应当记下密码用于登录。界面如图3-3找回密码界面。

图3-3 找回密码界面

取消按钮代码如下:

this.Close();//关闭该线程,返回主线程。

确认功能分析:

在界面加载时,进行信息提示具体操作流程。

MessageBox.Show("请用微信、支付宝、浏览器扫码获取本次验证码,验证码可用于找回账户密码");

生成六位数字字符串作为本次找回密码的验证码,并生成一个可扫描二维码,例如通过微信、支付宝、浏览器等扫描工具可以获取六位数字字符串。通过循环生成六位数字,转化为字符串存储在变量ans中。

Random r = new Random();//随机种子

string ans=string.Empty;//验证码答案

for (int i = 0; i < txtStrCode.MaxLength; i++)

{

    int rand=r.Next(9);

    string item = rand.ToString().Trim();

    ans += item;

}

qrCode.Text = "请记住你的验证码: "+ans;//将随机答案放入二维码中

执行代码时,需要先对文本信息的完整性进行判断。在这里可以使用TextBox.Text与String.Empty进行比较,当然也可以使用TextBox.Text.Length与0进行比较。此处使用第一种实现方法。

if (txtUser.Text == "" || txtTel.Text == "")

{

MessageBox.Show("请输入账户信息");

return;

}

if (txtStrCode.Text == "")

{

MessageBox.Show("请扫描二维码后获取验证码信息填入文本框内");

return;

}

还需要对输入的用户信息进行存在性判断。使用的方法依旧是用户的State()方法

if (ReaderManger.State(reader) && AdminManger.State(admin))

{

MessageBox.Show("用户不存在,请检测用户名是否正确");

return;

}

最后通过输入验证码与ans的存储信息进行匹配,判断正误。使用上文提到过的Get()方法根据账户信息获取电话信息与密码信息。

//判断验证码后的信息与输入文本框的字符串是否一致

if (txtStrCode.Text != ans)

{

MessageBox.Show("验证码错误");

TxtStrCode.Focus();

return;

}

string msg = string.Empty;

if (!ReaderManger.State(reader)&&txtTel.Text==ReaderManger.Get(reader).Tel)

{

msg = ReaderManger.Get(reader).Pwd;

}

最后通过信息弹窗给出密码

MessageBox.Show(msg);

3.3图书信息管理

图书信息管理系统最核心的功能就是对图书的各种信息进行管理。在此,通过使用数据库和C#开发语言将图书的增删查改基本功能和一些异常情况处理进行设计实现。

3.3.1图书管理页面设计

新建一个窗体,命名为frmAdmin。添加一个TabControl控件,新增tabPage,并命名tabPage_Book。页面包括Button、ComboBox、TextBox、Label、DataGridView等控件

界面左边两个按钮的功能是导出图书信息(包括种类的详细信息)到Excel中,查询按钮可以根据关键信息索引相关图书,其它按钮是图书的增改删操作,页面下方的DataGridView显示具体数据。页面如图3-4图书管理页面所示。

图3-4 图书管理页面

3.3.2图书管理代码实现

在如图3-4所示的图书管理页面中,查询按钮用于根据comboBox1选项的方式以及txtMsg文本框内容查询指定的图书。删除图书时,根据点击dgvBooks的单元格获取当前行的书号显示到txtInfo中,删除该书号的书籍。录入图书和修改信息都将打开子窗体录入或修改信息,注意修改时需要先点击单元格获取当前行书的信息传入子窗体。附:导出Excel功能在3.7拓展功能设计给出。

1.查询功能

创建一个临时实例,作为虚拟书籍,无任何信息,根据选择的方式,赋予其对应的信息,根据该信息进行查询返回数据结果。

(1)UI层

使用实体类BooksInfo创建一个不具有任何信息的虚拟书籍实例book:

BooksInfo book = new BooksInfo();

通过复合框的选项对虚拟书籍进行部分真实化,使可以根据该部分真实信息访问数据库并查询到全部信息。不同方式使用不同的方法的功能使用switch...case语法实现。主要代码如下:

switch (skinComboBox1.SelectedIndex)

{

case 0:  book.Bno = txtMsg.Text.Trim();//当选择该项时赋予虚拟书籍书号

       dgvBooks.DataSource = BooksManger.QueryBybno(book);break;

case 1:  book.Title = txtMsg.Text.Trim();//当选择该项时赋予虚拟书籍书名

       dgvBooks.DataSource = BooksManger.QueryBytitle(book);break;

case 2:  book.Tno = txtMsg.Text.Trim();//当选择该项时赋予虚拟书籍种类号

       dgvBooks.DataSource = BooksManger.QueryBytno(book);break;

}

(2)BLL层

条件查询时,以按书号、书名、书类分别调用了三个方法QueryBybno(BookInfo book)、QueryBytitle(BookInfo book)、

QueryBytno(BookInfo book)来返回查询结果。具体代码如下:

BooksService.Query(book, WayIndex);

其中对应的WayIndex的0,1,2分别对应上述三种条件查询方式。

(3)DAL层

通过switch...case语句,调用SQLHelper工具类的Query(sqlStr)方法在一段代码域内实现三种查询。具体代码如下;

方法声明为:

public static DataTable Query(BooksInfo book,int method)

初始化sql语句为string sql = "select * from tblBooks"

如果需要根据条件查询,那么就可以根据条件判断进行不同的查询操作

三条sql语句分别是:

sql = string.Format("select * from tblBooks where Bno='{0}'", book.Bno);

sql = string.Format("select * from tblBooks where Tno='{0}'", book.Tno);

sql = string.Format("select * from tblBooks where Title like '%{0}%'", book.Title);

执行数据访问的具体操作为:

return SQLHelper.Query(sql);

2.删除功能

选择数据表中需要删除的行,对应书号信息会展示到文本框中,在删除前会进行询问检查,以免误删。删除成功后会重新查询数据库所有信息,来刷新数据表、避免了数据不同步的情况。

DialogResult re = MessageBox.Show("是否确认删除该书?", "提示", MessageBoxButtons.OKCancel, MessageBoxIcon.Warning);

if (re == DialogResult.Cancel) return;

(1)UI层

获取当前点击单元格的行索引,将改行索引的“书号”列的值Value转化为字符串在经过Trim()处理传给新建的book的Bno属性

int index =dgvBooks.CurrentCell.RowIndex;

book.Bno = dgvBooks.Rows[index].Cells["Bno"].Value.ToString().Trim();

调用BooksManger.Delete方法删除数据表中的该图书。如果数据表中受影响行数大于0,即是操作成功,提示删除成功。将代码简写为:

if (BooksManger.Delete(book) > 0) MessageBox.Show("删除成功");

最后重新查询图书信息表来刷新数据

dgvBooks.DataSource = BooksManger.Query();

(2)BLL层

删除操作核心方法是BLL层BookManger类库的Delete(BookInfo book),具体代码如下:

public static int Delete(BooksInfo book)

{

    return BooksService.Delete(book);

}

(3)DAL层

在此删除功能只许知晓书号这一唯一标识信息即可准确删除一条数据记录。直接执行SQL语句即可,代码如下:

public static int Delete(BooksInfo book)

{

    string sql = string.Format("delete from tblBooks where Bno='{0}'", book.Bno);

    return SQLHelper.NoQuery(sql);

}

3.录入与修改

录入与修改图书功能是在子窗体frmBook中实现的。窗体主要由TextBox、Label、Button、GroupBox控件构成。界面如图3-5图书操作界面。

图书录入信息时,登记时间由确认录入时刻确认,即DateTime.Now,将其转化为字符串ToString()后存入数据库。新登记图书必定属于未借出状态,该子控件为只读属性。其余信息需要管理员录入。

图书修改时,会将获取到的信息传入子窗体,管理员只需修改需要更改的内容即可。通过方法重载和条件判断来将同一窗体实现类似操作的两种功能。

图3-5 图书操作界面

打开新窗体时,需要预先进行的代码操作:

录入图书时直接打开窗体

frmBook bfrm = new frmBook();

bfrm.ShowDialog();

修改图书信息时,会将修改前获取的图书信息传入到本界面,然后选择有需要的信息进行修改,点击确认按钮调用数据库,修改数据信息记录。首先创建一个临时的书籍作为参数(桥梁作用)。

BooksInfo books = new BooksInfo();

将本次图书信息全部填充到一个虚拟图书books中。传入新窗体内,这样的话会直接显示需要更改的书籍的全部信息,选择性的更改书籍信息就可以了,避免了更改图书的工作量。

try{

books.Bno =txtInfo.Text.ToString().Trim();

books.Tno= BooksManger.QueryBybno(books).Rows[0]["Tno"].ToString().ToUpper().Trim();

books.Title= BooksManger.QueryBybno(books).Rows[0]["Title"].ToString().Trim();

books.Publisher= BooksManger.QueryBybno(books).Rows[0]["Publisher"].ToString().Trim();

books.Author= BooksManger.QueryBybno(books).Rows[0]["Author"].ToString().Trim();

books.State= BooksManger.QueryBybno(books).Rows[0]["State"].ToString().Trim();

由于部分信息不是字符串String类型的数据,需要使用Convert类进行强制类型转换。(部分类型可以使用数据类型内置方法Parse()转化)

books.Price= Convert.ToInt32(BooksManger.QueryBybno(books).Rows[0]["Price"]

.ToString().Trim());

}catch { MessageBox.Show("未选中图书");throw; }

获取完信息后,打开新窗体的主要代码为:

修改时,图书书号作为主键,传入时需要避免被修改。所以本次选择frmBook的重载方法,传入books参数。

frmBook bfrm = new frmBook(books);

bfrm.ShowDialog();

(1)UI层

界面生成时需要对构造函数进行方法重载。

新增图书界面方法重载:

public frmBook(){

InitializeComponent();

txtState.Text = "未借出";//新增图书一定是未借出

txtState.ReadOnly = true;//录入时不可更改借阅状态

}

修改图书界面方法重载:

BooksInfo books = new BooksInfo();

public frmBook(BooksInfo booksInfo){

InitializeComponent();

books.Bno = booksInfo.Bno;//进行图书信息修改的依据数据

设置所有文本框的text属性值,例如:

txtBno.Text = booksInfo.Bno.Trim();

设置存储特殊信息的TextBox的ReadOnly属性为True值。

txtBno.ReadOnly = true;//书号不可更改

txtState.ReadOnly=true;}//状态不可更改

具体功能是实现:

当功能为增添图书时,需要判断输入的书别类型编号是否存在,不存在的话,询问管理员用户是否需要添加新类别编号。具体操作在书类信息管理时交代,此处不做赘述。修改图书时,只需将传入的信息进行修改,重新上传数据库,使用Update语句操作即可更改图书信息,书号作为主键,无法被更改。

输入信息判断,检测文本内容是否为空。如果为空给出读者提示:信息不可为空,然后返回让用户重新操作。

if (txtBno.Text == "" || txtTitle.Text == "" || txtPrice.Text == "")

{

MessageBox.Show("信息不可空");

return;

}

if(txtAuthor.Text==""||txtPublisher.Text==""||txtType.Text=="")

{

MessageBox.Show("信息不可空");

return;

}

在录入图书前需要检测图书馆收录种类是否存在,不存在的话,需要询问是否先录入新图书种类,然后录入完毕再进行新类型图书的录入工作。主要是根据图书种类查询,判断是否有该种类的查询结果。

BooksType type = new BooksType();

type.Tno = txtType.Text.Trim();

if (TypeManger.Query(type).Rows.Count <= 0)

{

    DialogResult re = MessageBox.Show("未查询到该类别,是否需要录入新种类", "温馨提示", MessageBoxButtons.YesNo);

    if (re == DialogResult.No) return;

下面是打开新增种类的一个新窗体,窗体设计在后文中如图3-7所示。

    frmType ft = new frmType();

    ft.ShowDialog();

    ft.Dispose();

}

将文本信息传入预操作的虚拟书籍,避免修改时书本信息改为null值。

books.Bno = txtBno.Text.Trim();

books.Title = txtTitle.Text.Trim();

books.Tno =txtType.Text.Trim();

books.Publisher = txtPublisher.Text.Trim();

books.Author = txtAuthor.Text.Trim();

books.Price=Convert.ToInt32(txtPrice.Text.Trim());

books.State = txtState.Text.Trim();

books.Date = DateTime.Now;

如果进入的是录入图书界面,先判断书号是否被占用,如果书号空闲,执行添加操作,否则提示用户“该图书的书号被使用,无需录入或者尝试录入其它的书号”。

if (BooksManger.QueryBybno(books).Rows.Count > 0)

{

    MessageBox.Show("该书号被使用,无需录入或尝试其他书号");

    return;

}

BooksManger.Add(books);

如果进入的是修改图书信息界面,由于书号不可被修改,直接执行修改操作。

BooksManger.Change(books);

二者操作结束后,都会关闭本窗体。

this.Close();

(2)BLL层

图书增删改查方法在BLL层的Book Manger类库中。

增添图书:Add(BookInfo book)

修改图书:Change(BookInfo book)

public static int Add(BooksInfo book)

{

    return BooksService.Insert(book);

}

public static int Change(BooksInfo book)

{

    return BooksService.Update(book);

}

(3)DAL层

增改图书的DAL层代码主要靠sql语句实现。

增加的sql语句代码实现:

string sql = string.Format("insert into tblBooks values('{0}','{1}','{2}','{3}','{4}','{5}','{6}','{7}')",book.Bno.Trim(), book.Title.Trim(), book.Tno.Trim(), book.Publisher.Trim(), book.Author.Trim(), book.Price, book.Date.ToString().Trim(), book.State.Trim());

return SQLHelper.NoQuery(sql);

修改的sql语句代码实现:

string sql = string.Format("update tblBooks set Title='{0}',Tno='{1}',Publisher='{2}',Author='{3}',Price='{4}',Date='{5}',State='{6}' where Bno='{7}'", book.Title.Trim(), book.Tno.Trim(), book.Publisher.Trim(), book.Author.Trim(), book.Price, book.Date.ToString().Trim(), book.State.Trim(), book.Bno.Trim());

return SQLHelper.NoQuery(sql);

5.Model层

由于四项功能均是操作书籍信息,所以Model层代码一样。在录入图书时涉及到图书类别信息,在下面书种信息管理代码实现时给出。

public class BooksInfo

{

    private string bno;

    private string title;

    private string tno;

    private string publisher;

    private string author;

    private int price;

    private DateTime date;

    private string state;

    public string Bno { get => bno; set => bno = value; }

    public string Title { get => title; set => title = value; }

    public string Tno { get => tno; set => tno = value; }

    public string Publisher { get => publisher; set => publisher = value; }

    public string Author { get => author; set => author = value; }

    public int Price { get => price; set => price = value; }

    public DateTime Date { get => date; set => date = value; }

    public string State { get => state; set => state = value; }

}

3.4书类信息管理

图书的管理必然伴随着分门别类,如此方能方便在不同类的图书中快速找到需要的图书,或者为读者想要了解某方面图书时提供了范围压缩。是图书信息管理模块不可或缺的辅助信息。图书类别的信息包括类别编号、类别名称、该类图书可借阅最大天数、逾期每日罚款金额等信息,设计时完成了对这些信息进行增删查改。

3.4.1书类管理页面设计

在上述的tabControl控件中新增页面tabPage_Type.页面主要控件有Button、dataGridView、treeView等。在此页面中分别有五个按钮,主要是对书类增删查改,以及通过treeView展示。

具体布局:左面是树状图TreeView将类型编号和类型名称匹配到一起方便观察,右面是图书种类的具体信息,包括不同管理功能的不同配色的Button按钮。

具备功能:【刷新查询】按钮会将页面的所有数据重新查询生成。【新增种类】按钮会打开一个添加种类的子窗体进行填充信息操作,【修改种类】按钮会打开子窗体进行修改信息。【删除种类】按钮会删除选中行种类信息。的如图3-6书类管理页面。

图3-6书类管理页面

3.4.2书类管理代码实现

1.Model层

图书种类的Model层代码也只有四个字段属性,操作起来相对图书的操作是非常简单的。先让我们见识一下书类的Model实体:

public class BooksType

{

    private string tno;

    private string type;

    private  int day;//可借阅天数

    private int fine;//每天罚金

    public string Tno { get => tno; set => tno = value; }

    public string Type { get => type; set => type = value; }

    public int Day { get => day; set => day = value; }

    public int Fine { get => fine; set => fine = value; }

}

2.刷新查询

(1)UI层

展示树图。在刷新树图前,将树节点TreeView.Nodes全部清除Clear(),然后将数据表中的类别数据,利用for循环,其中dgvType的Rows.Count属性作为循环次数标准使用Nodes.Add()一一添加类别树节点。此处节点值TreeNode类型为String字符串类型,所以会事先准备好类型编号和类型名称值。

myTree.Nodes.Clear();

for (int i = 0; i < dgvType.Rows.Count - 1; i++)

{

    TreeNode item = new TreeNode();

    item.Text = dgvType.Rows[i].Cells["Tno"].Value.ToString().Trim();

    Item.Text+= dgvType.Rows[i].Cells["Type"].Value.ToString().Trim();

    myTree.Nodes.Add(item);

}

获取数据表:将种类信息查询到dgvType数据表视图中。

dgvType.DataSource = TypeManger.Show();//图书种类信息查询

string[] str = { "类号", "类名", "天数", "罚金" };

for (int i = 0; i < str.Length; i++)

{

    dgvType.Columns[i].HeaderCell.Value = str[i];

}

设置视图信息并刷新树节点,避免了两处数据展示不同步的问题。

btnRefresh_Click(this, e);

(2)BLL层

上述功能只调用了TypeManger的查询方法Show().其代码为:

public static DataTable Show()

{

    return TypeService.Query();

}

(3)DAL层

public static DataTable Query()

{

    string sql = "select * from tblType";

    return SQLHelper.Query(sql);

}

3.删除种类

(1)UI层

点击dataGridView控件进行获取改行信息,通过主键Tno类别编号进行删除。在此之前询问用户是否确认删除。删除完毕,执行查询刷新数据,与数据库数据进行同步操作。

执行删除操作时,需要向用户进行提示,以免用户误删

DialogResult re = MessageBox.Show("是否确认删除该图书种类?", "提示", MessageBoxButtons.OKCancel, MessageBoxIcon.Warning);

if (re == DialogResult.Cancel) return;

如果用户确认需要删除,那么就获取选中行的种类信息,从数据表中移除一条种类信息记录。

BooksType type = new BooksType();

type.Tno=dgvType.Rows[dgvType.CurrentCell.RowIndex].Cells["Tno"].Value.ToString().Trim();

if (TypeManger.Del(type) > 0) MessageBox.Show("删除成功");

(2)BLL层

删除的BLL层调用的方法是Del(BooksType type)

public static int Del(BooksType type)

{

    return TypeService.Delete(type);

}

(3)DAL层

public static int Delete(BooksType type)

{

    string sql = string.Format("delete from tblType where Tno='{0}'", type.Tno.Trim());

    return SQLHelper.NoQuery(sql);

}

4.增加和修改

同图书管理的增改一样,书类的增改也在子窗体执行。创建窗体并命名为frmType,窗体控件有TextBox、Button、Label等。用户只需要向窗体内的TextBox中填充信息,然后点击提交即可,如果操作有问题,系统会向用户进行信息提示。如图3-7 书类操作界面。

图3-7 书类操作界面

(1)UI层

增加的UI层就是将输入的文本信息赋与临时书类类型变量,然后传入业务逻辑层再进行数据访问插入一条添加记录,新增书类数据。点击按钮的前提是判断信息是否全部完整、种类编号是否空闲未被使用。修改的操作就是通过构造函数重载、同界面条件语句进行改变实现。

private void btnSubmit_Click(object sender, EventArgs e)

{

如果信息有空信息,向用户进行信息提示,引导用户填写相关信息,然后再进行下一步操作。

    if (txtDaylimit.Text == "" || txtTno.Text == "" || txtFine.Text == "" || txtType.Text == "")

    { MessageBox.Show("信息不可空"); return; }

当填写的信息完整后,填充种类信息

    type.Tno = txtTno.Text.Trim();

    type.Type = txtType.Text.Trim();

    type.Day = Convert.ToInt32(txtDaylimit.Text.Trim());

    type.Fine = Convert.ToInt32(txtFine.Text.Trim());

    int rows = 0;

如果调用前点击的按钮是【增加】按钮,那么就执行添加功能的具体操作:

    if (this.Text == "新增种类")

    {

事先判断数据表中是否已经存在过该信息,如果存在的话,不能新增种类,否则主键冲突影响其它操作。向用户进行提示后返回主线程。

        if (TypeManger.Query(type).Rows.Count > 0)

        {

           MessageBox.Show("该书号对应图书已分类,请尝试修改操作");

           return;

       }

        rows = TypeManger.Add(type);

    }

如果调用窗体前点击的是【修改种类】,那么就执行修改功能的具体操作,修改时的种类号是存在的种类传进来的,所以不需要判断是否种类号存在。

    if (this.Text == "修改种类")

    {

        rows = TypeManger.Update(type);

    }

    if (rows > 0) MessageBox.Show(this.Text + "成功");

    this.Close();

}

(2)BLL层

public static int Add(BooksType type)

{

    return TypeService.Insert(type);

}

public static int Update(BooksType type)

{

    return TypeService.Update(type);

}

(3)DAL层

public static int Insert(BooksType type)

{

    string sql = string.Format("insert into tblType values('{0}','{1}','{2}','{3}')", type.Tno.Trim(), type.Type.Trim(), type.Day,type.Fine);    

    return SQLHelper.NoQuery(sql);

}

public static int Update(BooksType type)

{

   string sql=string.Format("update tblType set Type='{0}',Day='{1}',Fine='{2}' where Tno='{3}'", type.Type, type.Day, type.Fine, type.Tno);

   return SQLHelper.NoQuery(sql);

}

3.5用户信息管理

图书信息管理系统,归根结底是方便管理员的管理和读者的使用。所以系统的设计是为了用户的需求。那么用户信息的管理也必将占据系统的一席之地。用户信息分为两类,管理员和读者。前者信息包括账户密码、姓名电话,后者信息包括读者编号、姓名性别、单位、住址、电话、注册日期、最大可借阅数量。由于前者的信息比较单一,且管理员的信息管理归于所在图书馆单位进行档案管理,所以只需实现管理的修改密码和用户信息的管理即可。考虑到信息安全问题,读者信息一旦注册只许联系管理员进行修改、不可擅自更改避免伪造虚假信息等。

3.5.1用户管理界面设计

简洁美观的界面能够大大提升用户的体验。管理端新建一个tabPage_Home作为用户管理中心,可以进行自身的密码修改以及读者信息的增删查改。读者端除具体设计外同管理端一样操作,页面包括groupBox、label、button、textBox、dataGridView、pictureBox等控件。

页面内部的左上方是管理员用户的基本信息以及修改密码操作功能处,右面是对读者用户的管理功能的Button按钮和TextBox文本框。页面下方是用户的所有信息,左右滚轮的隐藏两项信息是账号密码。

管理端在本界面可以进行【修改密码】的操作,【浏览用户】、【注销用户】、【注册用户】、【修改信息】等操作。并且可以单击数据表获取本行用户信息,展示到界面的文本框内,便于上述的修改、删除操作。

读者端可以修改密码和查询信息。

图3-8 用户信息页面(管理端)

图3-9 用户信息界面(读者端)

3.5.2用户管理代码实现

读者端:在如图3-9 用户信息界面中,刷新数据用于查询用户的基本信息。修改密码用于更改用户的密码。

1.UI代码

刷新数据:将登录时传入的用户信息填充到文本框中。

txtUid.Text = reader.Uid;

txtRno.Text = reader.Rno;

txtSex.Text = reader.Sex;

txtTel.Text = reader.Tel;

txtAddress.Text = reader.Address;

txtDate.Text = reader.Date.ToString().Trim();

txtName.Text = reader.Name;

txtNum.Text = reader.Number.ToString().Trim();

txtDept.Text = reader.Dept;

修改密码:通过验证文本框信息是否为空、原密码是否正确、两次新密码是否一致等异常情况。

if (txtOldPwd.Text == string.Empty)

{MessageBox.Show("请输入有效信息");}

if (txtNewPwd.Text.Trim() != txtRePwd.Text.Trim())

{MessageBox.Show("两次密码不一致");}

if (txtOldPwd.Text.Trim() != ReaderManger.Get(reader).Pwd.Trim())

{MessageBox.Show("原密码不正确");}

层层验证后,才能修改密码,成功后给予提示。并清空输入的文本信息。

reader.Pwd = txtNewPwd.Text.Trim();

ReaderManger.newPwd(reader);

MessageBox.Show("密码修改成功");

txtNewPwd.Text = string.Empty;

txtOldPwd.Text = string.Empty;

txtRePwd.Text = string.Empty;

2.BLL层代码

创建一个虚拟读者,接收传入的读者的编号,通过调用同层的Get()方法获取读者的全部信息,改变读者的登录密码,调用DAL层的Update()方法更新数据库中的数据。

public static int newPwd(ReaderInfo readerInfo)

{

    ReaderInfo reader = new ReaderInfo();

    reader = Get(readerInfo);

    reader.Pwd = readerInfo.Pwd.Trim();

    return ReaderService.Update(reader);

}

3.DAL层代码

编辑sql语句:string.Format("update tblReader set Rno='{0}',Name='{1}',Sex='{2}',Date='{3}',Tel='{4}',Dept='{5}',Address='{6}',Number='{7}',Pwd='{8}' where Uid='{9}'" , r.Rno.Trim(), r.Name.Trim(), r.Sex.Trim(), r.Date.ToString().Trim(), r.Tel.Trim(), r.Dept.Trim(), r.Address.Trim(), r.Number, r.Pwd.Trim(), r.Uid.Trim());调用SQLHelper工具类执行SQL语句SQLHelper.NoQuery(sql);完成修改密码的操作。

管理端:在如图3-8 用户信息管理(管理端)中,包括读者用户的增删查改、自身用户的密码修改。

1.密码修改

同读者端的密码修改

if (txtOldPwd.Text == string.Empty || txtNewPwd.Text == "" || txtRePwd.Text == ""){

    MessageBox.Show("请输入有效信息");

    return;}

if (txtNewPwd.Text.Trim() != txtRePwd.Text.Trim())

    MessageBox.Show("两次密码不一致");

if (txtOldPwd.Text.Trim() != AdminManger.Get(admin).Pwd.Trim()){

    MessageBox.Show("原密码不正确");

return;}

验证过所有不合理操作后才能执行真正的修改密码NewPwd(user)操作。

admin.Pwd = txtNewPwd.Text.Trim();

if (AdminManger.newPwd(admin) == 0) { MessageBox.Show("error"); ; return; }

MessageBox.Show("密码修改成功");

2.注册用户

打开注册界面,相当于登录界面的读者注册功能,此处不做赘述。

3.注销用户

选择DataGridView控件dgvReaders的单元格,将基础信息展示到文本框内,点击删除,系统提示,是否确认注销用户xxx?选择取消,则不继续执行后续代码,本次删除无效。如果确认,将删除所选行的用户账号。删除后重新查询数据库,刷新数据。

(1)UI代码

删除操作,需要提示,以免误删

DialogResult re = MessageBox.Show("是否确认注销该用户,在删除之前请确认需要删除的用户", "提示",MessageBoxButtons.OKCancel,MessageBoxIcon.Warning);

if (re == DialogResult.Cancel) return;

ReaderInfo reader = new ReaderInfo();

int index=dgvReaders.CurrentCell.RowIndex;

reader.Uid =dgvReaders.Rows[index].Cells["Uid"].Value.ToString();

ReaderManger.Del(reader);

(2)BLL代码

UI层调用BLL层的Del(ReaderInfo reader)方法,该方法通过访问数据库ReaderService.Delete(reader)请求删除用户数据。

(3)DAL代码

编辑SQL语句串并执行SQL语句,访问数据库。

string.Format("delete from tblReader where uid='{0}'",r.Uid.Trim());

SQLHelper.NoQuery(sql);

4.查询用户

提供一键查询所有用户信息的功能,能够浏览所有系统使用者的信息。点击【浏览信息】按钮,通过BLL间接访问数据库查询,查询语句为:

string.Format("select * from tblReader")

5.修改信息

修改用户信息是通过修改展示到文本框中的信息后,点击【修改信息】按钮即可修改。具体实现方法为:ReaderInfo reader = new ReaderInfo();

int index = dgvReaders.CurrentCell.RowIndex;

不可更改内容,读者的编号、账号、密码、注册日期等内容在注册时就已经被确定,由于信息安全,不应该被修改,所以此处的信息经由Get方法将查询结果重新赋予该读者。

reader.Uid = dgvReaders.Rows[index].Cells["Uid"].Value.ToString().Trim();

reader.Rno= dgvReaders.Rows[index].Cells["Rno"].Value.ToString().Trim();

reader.Number = Int32.Parse(dgvReaders.Rows[index].Cells["Number"].Value.ToString());

reader.Pwd = dgvReaders.Rows[index].Cells["Pwd"].Value.ToString().Trim() ;

reader.Date = Convert.ToDateTime(dgvReaders.Rows[index].Cells["Date"].Value.ToString());

可更改内容,包括用户的非用户基本信息,如家庭住址、所属单位、联系电话、读者姓名和读者性别。

reader.Address=txtAddress.Text.Trim();

reader.Dept =txtDept.Text.Trim();

reader.Tel =txtTel.Text.Trim();

reader.Name = txtName.Text.Trim();

reader.Sex=cmbSex.Text.Trim();

最后一句代码通过调用BLL间接调用DAL层访问数据库,执行修改操作。

if (ReaderManger.Update(reader) > 0) MessageBox.Show("修改成功");修改的sql语句为:

string.Format("update tblReader set Rno='{0}',Name='{1}',Sex='{2}',Date='{3}',Tel='{4}',Dept='{5}',Address='{6}',Number='{7}',Pwd='{8}' where Uid='{9}'" , r.Rno.Trim(), r.Name.Trim(), r.Sex.Trim(), r.Date.ToString().Trim(), r.Tel.Trim(), r.Dept.Trim(), r.Address.Trim(), r.Number, r.Pwd.Trim(), r.Uid.Trim());

3.6图书借阅管理

图书借阅是用户端的核心功能,即借阅图书,归还图书两项基本核心操作。借阅记录的留存统计也是图书馆信息管理系统的重要内容之一,有了借阅的记录,才能分析图书借阅情况、用户喜好情况、书籍流向等信息。在此,用读者可以借还图书、管理员可以查看借阅记录。借阅信息包括:借阅书籍编号、读者编号、借阅时间、归还时间、逾期天数、罚款金额(默认为0),归还状态等。

3.6.1 图书借阅界面设计

用户端(图3-10借阅归还操作界面)在frmReaders的选项卡中添加一页xpBook,添加控件dataGridView、groupBox、label、textBox、Button等。

最上方是索引导航、根据选择的索引方式快速查询自己想要借阅的书籍,读者端的上方表格是图书馆的资源,下面的表格是自己借阅的未归还的书籍,右下角是具体的借阅归还功能Button。管理端(图3-11借阅记录查询页面)在frmAdmin的选项卡中添加一页pageBorrow,添加控件dataGridView控件仅供查询浏览。

图3-10 借阅归还操作界面(读者端)

图3-11 借阅记录查询界面(管理端)

3.6.2 图书借阅代码实现

1.管理端查询。代码:

查询数据库中的借阅信息表dgvBorrow.DataSource = BorrowManger.Query();

通过DataGridView.Columns.Width设置列宽使信息在一页内展示完整:

dgvBorrow.Columns[0].Width = 105;

dgvBorrow.Columns[1].Width = 105;

dgvBorrow.Columns[2].Width = 140;

dgvBorrow.Columns[3].Width = 140;

将表的列名更改为中文信息,使用户一目了然。我认为最佳设置方法是创建一个字符串数组str[],然后按照数据库中的列的顺序赋予中文列名存储到字符串数组中。然后用循环依次赋予第i列的列名为str[i]储存的字符串。

string[] str = { "借出书号", "借阅人", "借阅时间", "归还时间", "逾期天数", "结算罚款", "借阅状态" };

for (int i = 0; i < str.Length; i++)

{

dgvBorrow.Columns[i].HeaderCell.Value = str[i];

}

DAL层的SQL查询语句为:"select * from tblBorrow";

2.读者端借书

通过点击预操作行的图书,获取书号,点击【我要借书】按钮,加入到我的书架中,将图书的状态改为已借出,插入一条借阅记录,并默认状态为未归还。

(1)UI代码

查询获取图书状态,检测是否可以借阅

string BookState = BooksManger.QueryBybno(book).Rows[0]["State"].ToString().Trim();

if (BookState == "已借出")

{

    MessageBox.Show("书籍已被借阅,尚未归还,请静待几日");

    return;

}

Get()读者借阅书籍数量上限

int number = ReaderManger.Get(reader).Number;

如果上限数量小于等于0,即已经借阅了太多书,图书馆自动认为用户看不过来,不可再借书了,给予用户提示。

if (number <= 0) MessageBox.Show("可借阅书籍数量已达上线");

如果没有达到上限,将可借阅书籍-1,达到书籍借阅成功的一个标志

reader.Number = number - 1;

ReaderManger.Update(reader);//可借阅书籍数量减一

然后调用BLL层的BooksManger()方法根据书号查询该图书的类别编号,引用书类信息表,使用TypeManger()方法获取相关信息,提示读者用户本书的借阅时长限制和罚款情况。

第一步获取类型编号:

string tno = BooksManger.QueryBybno(book).Rows[0]["Tno"].ToString().Trim();

BooksType type = new BooksType();

第二步获取最大借阅天数和逾期每日罚金:

type.Tno = tno;

string maxDay=TypeManger.Query(type).Rows[0]["Day"].ToString().Trim();

string fine = TypeManger.Query(type).Rows[0]["Fine"].ToString().Trim();

第三步进行提示:

string msg = string.Format("本次可借阅时长:{0}天\r\n逾期将每日罚款{1}元\r\n是否继续借阅?", maxDay, fine);

DialogResult re= MessageBox.Show(msg,"提示",MessageBoxButtons.OKCancel);

if (re == DialogResult.Cancel) return;

如果读者同意继续借书,将调用BLL层的BorrowManger.BorrowBook()方法,插入一条借阅书籍的记录。

BorrowManger.BorrowBook(book, reader);

(2)BLL代码

BorrowManger.BorrowBook(book, reader);

1.将tblBooks中图书的借阅状态改为已借出

book.State = "已借出";

BooksService.Update(book);

2.插入一条借阅信息,包括 book.Bno;reader.Rno,其他有默认值;

BorrowInfo borrowInfo = new BorrowInfo();

borrowInfo.Bno = book.Bno;

borrowInfo.Rno = reader.Rno;

borrowInfo.State = "未归还";

borrowInfo.Bdate = DateTime.Now.Date;

borrowInfo.Rdate = DateTime.MinValue;

borrowInfo.Exday = 0;

borrowInfo.Fine = 0;

调用DAL层Insert()方法访问数据库BorrowService.Insert(borrowInfo);

(3)DAL代码

BooksService.Update(book);return SQLHelper.NoQuery(sql);

具体SQL语句如下:

string sql = string.Format("update tblBooks set Title='{0}',Tno='{1}',Publisher='{2}',Author='{3}',Price='{4}',Date='{5}',State='{6}' where Bno='{7}'", book.Title.Trim(), book.Tno.Trim(), book.Publisher.Trim(), book.Author.Trim(), book.Price, book.Date.ToString().Trim(), book.State.Trim(), book.Bno.Trim());

BorrowService.Insert(borrowInfo);return SQLHelper.NoQuery(sql);

具体SQL语句如下:

string sql = string.Format("insert into tblBorrow values('{0}','{1}','{2}','{3}','{4}','{5}','{6}')",b.Bno, b.Rno, b.Bdate.ToString().Trim(), b.Rdate.ToString().Trim(), b.Exday,b.Fine,b.State);

3.读者端还书

通过点击预操作行的图书,获取书号,点击【我要还书】按钮,从我的书架中移除,将图书的状态改为未借出,更改该条借阅记录,并默认状态为已归还。本条借阅记录就此完整。

(1)UI代码

还书时更新用户的最大借阅书籍限制,+1。

reader.Number = ReaderManger.Get(reader).Number + 1;

ReaderManger.Update(reader);//可借阅书籍数量加一

某用户借阅某书,需要用户编号和书籍编号

BooksInfo book = new BooksInfo();

book.Bno = txtBno.Text;

BorrowManger.ReturnBook(book, reader);

(2)BLL代码

BorrowManger.ReturnBook(book, reader);

具体代码如下:

BorrowInfo borrowInfo = new BorrowInfo();

borrowInfo.Bno = book.Bno;

borrowInfo.Rno = reader.Rno;

borrowInfo.Bdate = Convert.ToDateTime(BorrowService.Query(borrowInfo, -1).Rows[0]["Bdate"].ToString().Trim()).Date;

1.将tblBooks中图书的借阅状态改为未借出

book.State = "未借出";

BooksService.Update(book);

2.获取tblType图书所属类别规定的最大可借阅时长,逾期每天需罚款多少钱

BooksType type = new BooksType();

type.Tno = book.Tno;

int maxDay = Int32.Parse(TypeService.Query(type).Rows[0]["Day"].ToString().Trim());

int price = Convert.ToInt32(TypeService.Query(type).Rows[0]["Fine"].ToString().Trim());

3.获取tblBorrow借书时间与还书时间差并截取Days属性作为天数差然后分别计算逾期天数,罚款金额

int day = (DateTime.Now - borrowInfo.Bdate).Days;

int exday = 0;

int fine = 0;

如果没有逾期就不用计算,直接就是0,0。如果天数差day比最大借阅天数maxDay大,那么就计算罚金。

if (day > maxDay)

{

    exday = day - maxDay;

    fine = exday * price;

}

4.更改tblBorrow的超出天数,罚款金额

borrowInfo.Exday = exday;

borrowInfo.Fine = fine;

borrowInfo.Rdate = DateTime.Now.Date;

borrowInfo.State = "已归还";

调用DAL.BorrowService.Update()方法更改本次借阅记录

BorrowService.Update(borrowInfo);

(3)DAL代码

BooksService.Update(book);SQLHelper.NoQuery(sql);

具体SQL代码如下:

string sql = string.Format("update tblBooks set Title='{0}',Tno='{1}',Publisher='{2}',Author='{3}',Price='{4}',Date='{5}',State='{6}' where Bno='{7}'", book.Title.Trim(), book.Tno.Trim(), book.Publisher.Trim(), book.Author.Trim(), book.Price, book.Date.ToString().Trim(), book.State.Trim(), book.Bno.Trim());

BorrowService.Update(borrowInfo);SQLHelper.NoQuery(sql);

具体SQL代码如下:

string sql = string.Format("update tblBorrow set State='{0}',Rdate='{1}',Exday='{2}',Fine='{3}' where Bno='{4}' and Rno='{5}'",b.State , b.Rdate.ToString().Trim(), b.Exday,b.Fine,b.Bno, b.Rno);

4.Model层

该层的图书类在上面已经提到,此处只写出借阅类的代码:

public class BorrowInfo

{

    private string bno;//书号

    private string rno;//读者编号

    private DateTime bdate;//借书时间

    private DateTime rdate;//还书时间

    private int exday;//超出天数

    private int fine;//罚款金额

    private string state;//是否还书

    public string Bno { get => bno; set => bno = value; }

    public string Rno { get => rno; set => rno = value; }

    public DateTime Bdate { get => bdate; set => bdate = value; }

    public DateTime Rdate { get => rdate; set => rdate = value; }

    public int Exday { get => exday; set => exday = value; }

    public int Fine { get => fine; set => fine = value; }

    public string State { get => state; set => state = value; }

}

3.7拓展功能设计

为了更好地用户体验和便捷高效地管理,只有基础的功能是不够地。对此,系统做了一定功能地拓展。例如报表统计、留言建议、系统公告;数据统计、最优推荐;联网资源拓宽书籍资源库;界面合理性和美化等。

*读者功能导航

打开DevExpress提供的第三方Edit编辑器,选择主题Office 2019 Colorful,添加导航栏。增加导航栏按钮并向按钮注册事件。

this.barBtnLibrary.ItemClick += new DevExpress.XtraBars.ItemClickEventHandler(this.barBtnLibrary_ItemClick);

this.barBtnUser.ItemClick += new DevExpress.XtraBars.ItemClickEventHandler(this.barBtnUser_ItemClick);

this.barBtnBorrow.ItemClick += new DevExpress.XtraBars.ItemClickEventHandler(this.barBtnBorrow_ItemClick);

this.barBtnElectric.ItemClick += new DevExpress.XtraBars.ItemClickEventHandler(this.barBtnElectric_ItemClick);

this.barBtnNotice.ItemClick += new DevExpress.XtraBars.ItemClickEventHandler(this.barBtnNotice_ItemClick);

this.barBtnMore.ItemClick += new DevExpress.XtraBars.ItemClickEventHandler(this.barBtnMore_ItemClick);

this.barButtonItem2.ItemClick += new DevExpress.XtraBars.ItemClickEventHandler(this.barButtonItem2_ItemClick);

this.barButtonItem1.ItemClick += new DevExpress.XtraBars.ItemClickEventHandler(this.barButtonItem1_ItemClick);

其中离开系统的功能实现是关闭所有进程,Application.Exit();返回登陆是关闭进程并重启,Applica.ReStart();这是本系统返回登陆的一个特色实现方法。其它方法是打开不同的tabPage或form并配置。

*导出Excel表

public static void ExportToExcel(System.Data.DataTable dt, string path)

导出数据公共类。首先定义一个流写入变量sw。

StreamWriter sw = null;

为变量创建实例,path是保存路径,false即选择覆盖,不追加。导出字符为Unicode字符集。

sw = new StreamWriter(path, false, Encoding.Unicode);

创建一个临时存储变量。并通过StringBuilder.Append()方法依次导入数据表的列头,附加制表符。并换行。

StringBuilder sb = new StringBuilder();

for (int i = 0; i < dt.Columns.Count; i++)

    {sb.Append(dt.Columns[i].ColumnName + "\t");}

sb.Append(Environment.NewLine);

通过StringBuilder.Append()方法,通过循环将具体数据以行为基础单位读入sb。并换行。

for (int i = 0; i < dt.Rows.Count; i++)

{

for (int j = 0; j < dt.Columns.Count; j++)

{sb.Append(dt.Rows[i][j].ToString() + "\t");}

sb.Append(Environment.NewLine);

}

数据读取完毕后,使用StreamWrite.Write()方法将sb转化为字符串写入Excel表。写入后使用内置方法Flush()清空缓存。

    sw.Write(sb.ToString());

    sw.Flush();

}

导出Excel表的BLL层代码:

第一步生成一个保存文件的系统窗体SaveFileDialog saveFileDialog1 = new SaveFileDialog();

第二步将调用的窗体的标题更改一下作为提示:saveFileDialog1.Title = "保存文件";

第三步设置保存格式/路径saveFileDialog1.Filter = "Excel 文件(*.xls)|*.xls|Excel 文件(*.xlsx)|*.xlsx|所有文件(*.*)|*.*";

第四步导出数据表:

1.导出图书信息

设置文件名为Book,拓展名为excel的文件格式.xls

saveFileDialog1.FileName = "Book.xls";

查询图书信息表并存到DataTable变量中。

DataTable dt = BooksManger.Query();

if (saveFileDialog1.ShowDialog() == DialogResult.OK)

{

    string path = saveFileDialog1.FileName;//保存路径

    ExcelHelper.ExportToExcel(dt, path);//将dt表通过path的路径格式保存为Excel文件

}

2.导出书类信息

设置文件名为Type,拓展名为excel的文件格式.xls

saveFileDialog1.FileName = "Type.xls";

查询图书种类信息表并存到DataTable变量中。

DataTable dt = TypeManger.Show();

if (saveFileDialog1.ShowDialog() == DialogResult.OK)

{

    string path = saveFileDialog1.FileName;//保存路径

    ExcelHelper.ExportToExcel(dt, path);//将dt表通过path的路径格式保存为Excel文件

}

3.7.1 留言动态

留言和公告包括了发布者、发布内容和发布时间等必要信息。因此留言模块的Model层代码如下

public class MsgInfo

{

    private string uid;//发布者

    private string msg;//发布内容

    private string date;//发布日期

    public string Uid { get => uid; set => uid = value; }

    public string Msg { get => msg; set => msg = value; }

    public string Date { get => date; set => date = value; }

}

界面设计:如图3-12 系统公告(管理端)和图3-13留言动态(读者端)所示。界面包括了TextBox、Button、dataGridView等控件。

管理端设计:TextBox文本框设置为MutiLine可多行编辑显示,放在页面内的最上方,【发布系统公告】按钮与【查看留言建议】按钮依次分布其右。下方是具体的留言动态表。

读者端设计:页面上方是留言动态表、留言内容编辑文本框以及功能按钮,下方是一个ReadOnly的内容显示文本框,作用是显示表格中被隐藏的留言内容。

图3-12 系统公告(管理端)

图3-13 留言动态(读者端)

代码实现:

两端发布公告/留言:(对应DML类sql语句的的增加)

如果发布的内容为空被系统认定为不发布,也就是内容完整性判断:

if (txtMessage.Text == string.Empty)

{

    MessageBox.Show("无法发布内容为空的公告");

    return;

}

尝试使用MsgManger.WriteMsg()方法将写入文本框的内容上传到数据表中,传入的三个参数分别是:发布用户User.Id.ToString().Trim();发布内容TextBox.Text.Trim();发布时间DateTime.Now.ToString().Trim()。例如管理员发布内容时的代码为:

try

{

    MsgManger.WriteMsg(admin.Uid.ToString().Trim(), "系统公告: "+txtMessage. Text.Trim() , DateTime.Now.ToString().Trim());

    MessageBox.Show("发布成功");

}

如果中间补货到了异常,提示用户发布失败,是系统问题,不是用户操作问题。

catch (Exception)

{MessageBox.Show("发布失败,请联系技术员检查后台");throw;}

紧接着清空本次发布内容,并刷新留言表,同步数据

txtMessage.Text = string.Empty;

btnViewHistory_Click(this, e);

Sql语句为:

string.Format("insert into tblMsg values('{0}','{1}','{2}')",uid.Trim(), msg.Trim(), date.Trim());

两端查询动态的操作:(对应DQL类sql语句-查询数据表语句)

dgvNotice.DataSource = MsgManger.GetMsg();

根据列头和列宽设置查询结果的表的外观:

dgvNotice.Columns[0].HeaderText = "用户";

dgvNotice.Columns[1].HeaderText = "内容";

dgvNotice.Columns[2].HeaderText = "时间";

dgvNotice.Columns[1].Width = 550;

dgvNotice.Columns[2].Width = 180;

Sql语句为:string.Format("select * from tblMsg")

3.7.2 数据统计

数据统计分析是为了发现图书馆服务中可能存在的问题。对于后续系统的维护和改进有很大的帮助。

统计分析一:本馆概况,如图3-14 数据统计(管理端)。图表展示内容是本系统使用者借阅的所有图书种类的占比情况。

图3-14 数据统计(管理端)

设置图书馆情况信息。通过查阅数据表中一共存在多少行来判断一共有多少条数据,也就是通过图书表、借阅记录表、读者用户表依次可以获取馆内图书总数、累计借阅次数、使用本系统的读者数目。

labelBooks.Text = "馆内在册图书:"+BooksManger.Query().Rows.Count.ToString()+"册";

labelCnt.Text ="累计借阅次数:" + BorrowManger.Query().Rows.Count.ToString().+"次";

labelReader.Text ="注册读者数目:"+ReaderManger.Query().Rows.Count.ToString()+"人";

chart1.Titles.Clear();//将上次的统计结果清除,以便后续重新统计。

chart1.Titles.Add("图书分类借阅数据分析");

List<string> XList = new List<string>();//x轴存储字符串

List<int> YList = new List<int>();//y周存放数据

for(int i=0; i< TypeManger.Show().Rows.Count;i++)

{

    string str = TypeManger.Show().Rows[i]["Tno"].ToString().Trim() + "." + TypeManger.Show().Rows[i]["Type"].ToString().Trim();

    XList.Add(str);//横坐标就是种类

    BooksType t = new BooksType();

    t.Tno = str[0].ToString();

    int cnt = 0;//假设本次横坐标种类一共被借阅的次数为0

    for (int j = 0; j < BorrowManger.Query().Rows.Count; j++)

    {

        BooksInfo books = new BooksInfo();

        books.Bno = BorrowManger.Query().Rows[j]["Bno"].ToString().Trim();

        if (BooksManger.QueryBybno(books).Rows.Count > 0 &&t.Tno == BooksManger.QueryBybno(books).Rows[0]["Tno"].ToString().Trim())

        {

             cnt++;//每次在借阅记录表中查询到之后都可以让这类的y轴数据+1.

        }

    }

    YList.Add(cnt);//循环结束后,将数据添加到y轴

}

chart1.Series[0].IsVisibleInLegend = true;//标注可见

chart1.Series["TypeCnt"].Points.DataBindXY(XList, YList);//Points()方法绘图

统计分析二:最多借阅书籍,图表展示了本馆所有图书的借阅次数,并将借阅次数最多的书籍的信息推荐给读者,如图3-16数据分析(读者端)。

图3-16 数据分析(读者端)

int maxIndex = 0;

int max = 0;

设置X、Y轴,等待填充具体内容。

List<string> Xlist = new List<string>();

List<int> Ylist = new List<int>();

for (int j = 0; j < BooksManger.Query().Rows.Count; j++){

string str = BooksManger.Query().Rows[j]["Title"].ToString().Trim();

Xlist.Add(str);//横坐标就是书名

int cnt = 0;

string bookId = BooksManger.Query().Rows[j]["Bno"].ToString().Trim();

for (int i = 0; i < BorrowManger.Query().Rows.Count; i++)

{

if (BorrowManger.Query().Rows[i]["Bno"].ToString().Trim() == bookId){cnt++;}

}

if (cnt > max){ maxIndex = j; max = cnt; }

Ylist.Add(cnt);

}

将x轴信息和y轴信息添加到图表的Series[0]中,并将图表的示例Legend设为可见。

chartBestBooks.Series[0].IsVisibleInLegend = true;

chartBestBooks.Series[0].Points.DataBindXY(Xlist, Ylist);

将表格结果进行分析。通过TextBox的文本属性展示分析出的最佳借阅书籍结果。并调用BLL层方法查询该书籍信息,并写入TextBox中。

txtAnalysis.Text = string.Format("本馆最多借阅书籍:\r\n\r\n书名: 《{0}》\r\n借阅次数: {1}\r\n", Xlist[maxIndex].Trim(),max);

txtAnalysis.Text += string.Format("作者: {0}\r\n", BooksManger.Query().Rows[maxIndex]["Author"].ToString().Trim());

txtAnalysis.Text += string.Format("书号: {0}\r\n", BooksManger.Query().Rows[maxIndex]["Bno"].ToString().Trim());

string type = BooksManger.Query().Rows[maxIndex]["Tno"].ToString();

BooksType t=new BooksType();

t.Tno = type;

string tname = TypeManger.Query(t).Rows[0]["Type"].ToString().Trim();

txtAnalysis.Text += string.Format("类型: {0}.{1}\r\n", type.Trim(), tname.Trim());

txtAnalysis.Text += string.Format("出版社: {0}\r\n", BooksManger.Query().Rows[maxIndex]["Publisher"].ToString().Trim());

txtAnalysis.Text += string.Format("售价: {0}\r\n", BooksManger.Query().Rows[maxIndex]["Price"].ToString().Trim());

图3-15 数据展示(读者端)

3.7.3 电子资源

图3-17 链接跳转(管理端)

管理端设计如图3-17所示,页面集成了一部分网页和图书馆信息,点击网页时会获取本机默认浏览器并跳转到超链接内容。

读者端界面,包括左侧的菜单网页、搜索引擎以及右侧隐藏的WebBrowser浏览器窗体。

图3-17 访问网页(用户端)

界面初始化时,加载浏览器内核,自动配置浏览器内核。

var settings = new CefSettings();

try

{CefSharp.Cef.Initialize(settings);}

catch (Exception) {;//空语句,目的是捕获错误置之不理,避免影响系统运行}

浏览网页初始化:(实质就是添加一个浏览器选项卡)

void webInit(string str)

{

    webChannel.Controls.Clear();//清理上次的浏览页面

    webChannel.Controls.Add(new ChromiumWebBrowser(str));//增加新的浏览页面

}

str就是网址字符串。如图3-18所示,系统给提供了一部分网址串。可以直接打开。

3.7.4 界面加载

添加一个progressBar进度条控件,一个timer组件。通过timer的Tick事来增加进度条,并改变进度显示字体。如图3-19,界面主体是一个动漫图片,目的是使读者等待时有所看,设想未来可以发展为广告位获取系统维护资金等。加载的目的是让后台准备进程、匹配用户信息等。等到加载完成后进度条消失、显示进入系统的按钮,如末尾附图3-20。

图3-19 加载中

for (int i = 0; i < 3; i++)

{

    barProgress.Increment(new Random().Next(1500) + 1);//随机增加数字

    labelLoding.Text="加载中...   "+Math.Round(barProgress.Value/100.0,2).ToString("#00.00")+"%";//规定字符数据格式。

    System.Threading.Thread.Sleep(300);//模拟令timer休眠300毫秒,防止字体不断闪烁,造成不显示字符的情况。

}

当进度条的当前位置比设置好的最大值10000相等或一样的话就停止模拟时间流失。通过Visible属性将进度条设为不可见,进入系统的按钮设为可见,设置timer控件的Enabled属性设为false,防止后续系统运行时,timer继续执行占用系统资源。

if (barProgress.Value >= barProgress.Maximum)

{

    this.barProgress.Visible = false;//进度条不可见

    this.labelLoding.Visible = false;//进度数据不可见

    this.timer1.Enabled = false;//防止占用资源导致卡顿

    this.btnEnter.Visible = true;//显示加载后的按钮

}

图3-20 加载完成(附)


第4章 系统测试

软件生命周期的每个阶段都不可回避地会产生差错,力求在每个阶段加速之前通过严格的技术审查尽可能的早发现并纠正错误。测试的目的就是在软件投入实际使用前,尽可能地多发现软件中的错误,实现对软件规格说明设计和编码的最后复查。测试是不可穷尽的,测试人员不可能发现系统中所有的缺陷,每个版本发布前也不可能保证所有已知的缺陷都会得到修复,所以反复测试是为了发现更多的缺陷,预防风险。测试人员跟踪需求、验证质量、提交缺陷的同时也促进了开发人员技术的提升,在这个过程中牵扯到项目流程管理的问题,一个优秀的测试在这个过程中会建立一套完成的体系来提高整个团队的工作效率从而来降低开发成本进而把控产品质量,但需明确的是,软件的质量不只是测试人员来把关,最终质量好坏是整个团队的结果。软件测试整体是验证功能的实现、可用性,检查程序的错误,最终目的是为了提高用户体验;在测试过程中,有一些缺陷级别低,解决与否都不影响用户使用,且缺陷存在本身用户也不会有感知,这时就需要从用户体验的角度去考量是否要定义该类问题为缺陷。

4.1 系统测试的原则

通过阅读《软件评测师教程》,我对本次系统设计的测试工作有了以下的十一点认识:

1.尽早地和不断地进行软件测试

越早进行测试,缺陷的修复成本就会越低。应当把软件测试贯穿到整个软件开发的过程中,而不应该把软件测试看作是其过程中的一个独立阶段。因为在软件开发的每一环节都有可能产生意想不到的问题,其影响因素有很多,比如软件本身的抽象性和复杂性、软件所涉及问题的复杂性、软件开发各个阶段工作的多样性,以及各层次工作人员的配合关系等。所以要坚持软件开发各阶段的技术评审,把错误克服在早期,从而减少成本,提高软件质量。在本系统的开发编码阶段,我就在不停的测试,每实现一个模块,我就测试一下,看看与其他模块是否能连接上,提高系统的可靠性。

2.严格执行测试计划,排除测试的随意性,以避免发生疏漏或者重复无效工作。

3.程序员应避免检查自己的程序,由第三方进行测试更客观有效;

4.穷举测试是不可能的;

5.充分注意测试中的群集现象,一段程序中一发现的错误数越多,其中存在的错误概率越大,因此对发现错误较多的程序段,应进行更深入的测试;

6.设计测试用例时应包括合理输入和不合理输入,以及各种边界条件、特殊情况下要制造极端状态和意外状态;

7.注意回归测试的关联性,往往修改一个错误会引起更多错误;

8.测试应从“小规模”开始,逐步转向“大规模”;

9.测试用例式设计出来,不是写出来的,应根据测试的目的,采用相应的方法设计测试用例,从而提高测试的效率,更多的发现错误,提高程序的可靠性;

10.重视并妥善保存一切测试过程文档(测试计划,测试用例,测试报告等);对测试错误结果一定要有一个确认的过程

11.所有测试的标准都是建立在用户需求之上的,测试的目的在于发现系统是否满足规定的需求。

在遵守以上原则的基础上进行软件测试,可以以最少的时间和人力找出软件中的各种缺陷,从而达到保证软件质量的目的。 

4.2 测试结果

本次测试针对开发的图书馆管理系统进行,包括功能测试,界面测试,负载测试,文档测试。按照规格需求说明书中的功能进行测试,在测试过程中发现软件的漏洞不足并予以改正。检验软件本身的功能是否达到了预期的想法,在众多的测试当中,性能和功能都在不断的进行完善,设计的合理,达到了人们的一些生活需求,在以后的测试极其维护该改进中都有非常良好空间。

主要对系统功能模块进行测试,得到的测试结果如下表4-1所示。

表4-1测试结果

测试项目

验证过程

预期结果

实际结果

结论说明

管理员登录

系统管理员登录输入种植户名:Stark密码:123456

进入系统管理员管理界面

进入系统管理员管理界面

测试成功通过

读者用户登录

注册会员登录输入种植户名:Stark密码:123456

进入系统读者用户浏览界面

进入系统读者用户浏览界面

测试成功通过

读者用户注册

点击注册按键,进入新读者用户注册页面,填写注册信息并提交

显示注册成功

显示注册成功

测试成功通过

找回密码

点击找回密码选项,进入找回密码界面,输入用户信息,扫描二维码获取验证码并提交

显示用户密码

显示用户密码

测试成功通过

密码修改

提交用户新旧密码,点击密码修改按键

显示密码修改成功

显示密码修改成功

测试成功通过

添加读者用户信息

进入图书馆信息管理系统,点击进入用户管理页面添加读者信息,添加相关信息并提交

提交后增加新数据信息

提交后增加新数据信息

测试成功通过

修改读者用户信息

进入图书馆信息管理系统,点击进入用户管理页面修改读者信息,修改相关信息并提交

提交后数据信息发生改变

提交后数据信息发生改变

测试成功通过

删除读者用户信息

进入图书馆信息管理系统,点击进入用户管理页面删除读者信息,点击删除

删除后列表记录数减少

删除后列表记录数减少

测试成功通过

查询读者用户信息

进入图书馆信息管理系统用户管理页面,点击浏览用户

页面显示所有读者用户相关数据

页面显示所有读者用户相关数据

测试成功通过

浏览图书信息

进入图书管理页面,点击进入图书信息管理页面

列表显示所有图书信息

列表显示所有图书信息

测试成功通过

查询图书信息

进入图书管理页面,点击查询方式,按要求填入关键信息,点击查询

页面显示查询相关数据

页面显示查询相关数据

测试成功通过

添加图书信息

进入图书管理页面,点击进入图书管理界面添加图书信息,添加相关信息并提交

提交后增加新数据信息

提交后增加新数据信息

测试成功通过

修改图书信息

进入图书管理页面,点击进入图书管理界面修改图书信息,修改相关信息并提交

提交后数据信息发生改变

提交后数据信息发生改变

测试成功通过

删除图书信息

进入图书管理页面,选中预操作行,点击删除

删除后列表记录数减少

删除后列表记录数减少

测试成功通过

增加图书种类

进入图书种类管理页面,点击进入图书种类管理界面,添加相关信息并提交

提交后增加新数据信息

提交后增加新数据信息

测试成功通过

修改图书种类

进入图书种类管理页面,点击进入图书种类管理界面修改图书图书种类,修改相关信息并提交

提交后数据信息发生改变

提交后数据信息发生改变

测试成功通过

删除图书种类

进入图书种类管理页面,选中预操作行,点击删除

删除后列表记录数减少

删除后列表记录数减少

测试成功通过

浏览图书种类

进入图书种类管理页面,点击进入图书种类信息列表页面

列表显示所有图书种类信息

列表显示所有图书种类信息

测试成功通过

图书借阅

进入图书借阅页面,选中预操作行,点击借阅按钮

图书列表信息更改、插入一条借阅记录

图书列表信息更改、插入一条借阅记录

测试成功通过

图书归还

进入图书借阅页面,选中预操作行,点击归还按钮

图书列表信息更改、修改一条借阅记录

图书列表信息更改、修改一条借阅记录

测试成功通过

浏览留言记录

进入留言界面,点击浏览记录按钮

列表显示所有留言信息

列表显示所有留言信息

测试成功通过

发布留言

进入留言界面,点击发布按钮

提交后增加新数据信息

提交后增加新数据信息

测试成功通过

导出图书信息

进入图书管理页面,点击导出按钮

弹出保存Excel文件系统窗口

弹出保存Excel文件系统窗口

测试成功通过

导出图书种类

进入图书管理页面,点击导出按钮

弹出保存Excel文件系统窗口

弹出保存Excel文件系统窗口

测试成功通过

浏览网页

点击进入电子资源界面,选择列表网址或自行输入有效网址

页面内打开网址内容网页信息

页面内打开网址内容网页信息

测试成功通过

界面加载

选择进入读者系统,进入中间加载界面

随加载显示进度,加载完成显示进入按钮。

随加载显示进度,加载完成显示进入按钮。

测试成功通过


结论

图书馆信息管理系统是基于C/S结构、C#开发语言、Visual Studio开发环境以及SQL Server数据库管理系统进行构建开发的。具体实现的功能如下:

每个用户都可以注册、找回密码以及使用自己的账号密码登录系统或更改密码,管理员可以任意修改读者的部分账户信息;管理员可以对图书馆的图书进行增删查改,用户可以对图书进行借阅归还(更改图书的借阅状态信息);管理员可以对图书的种类进行增删查改;管理端可以查看所有的借阅记录,读者端可以查看自己的未归还状态的借阅记录,读者端借书时插入一条借阅记录、还书时更改借阅记录的归还状态;管理端可以发布系统公告、读者端发可以布使用方面的留言反馈,两端都可以互相查看内容;数据统计,系统会根据读者的借阅情况,为管理端提供种类借阅情况饼状图,以便引进更多该种类的书籍、同时给读者端提供每本书的借阅次数、给新手一个阅读方向。Excel报表,管理员可以在图书管理页面将图书信息和图书种类两种图书馆核心信息导出Excel表。

在系统开发过程中使用和扩展的关键技术如下:

1.页面布局设计方面,使用了DevExpress皮肤插件和Cskin皮肤插件,优化界面布局、提供配套设计,高度的自定义属性和大量的方法给了本人更灵活的设计思路和方向,以适应客户需求,提高用户体验;

2.逻辑层面上,通过学习表的约束和表的相互之间的配合使用、绘制系统功能逻辑图等,在逻辑层面上使得系统功能更加完善,功能不单一。

3.三层架构方面,程序通过建立Model实体类,减少了传参时代码的冗余,分层调用方法,避免了UI层直接访问数据库,导致数据信息不安全的问题,为测试、维护、升级、开发等各方面都提供了极大的便利。

4.编程技巧方面,学习运用了面向对象设计的思想,例如:继承实现Cskin的换肤操作、封装功能减少代码冗余、每个不同窗体的建立就是多态的实现。OOP的三大特点让系统的维护和开发更加有利。

5.拓展控件方面,例如chart控件绘制图表并设计分析使系统功能全面化、具体化,codeControl控件(QRcode),字符串生成二维码找回密码使得系统更加现代化,timer控件与progressBar控件进行搭配进行进度条加载、使得系统界面更加美观,timer控件与pictureBox控件搭配进行图片轮播,使得系统实现了简易版的主流主页模式。

系统具有一定的局限性,具体体现在:

由于系统是由C#.NET写出来的,在Linux操作系统上使用会非常复杂,需要准备工具AnyExec(工具在Linux.NET有,且目前只支持64位)。


【免费】实践项目-图书馆管理系统(C#.NET)_图书馆数据库管理系统资源-CSDN文库

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/49653.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Mac装虚拟机占内存吗 Mac用虚拟机装Windows流畅吗

如今&#xff0c;越来越多的Mac用户选择在他们的设备上安装虚拟机来运行不同的操作系统。其中&#xff0c;最常见的是使用虚拟机在Mac上运行Windows。然而&#xff0c;许多人担心在Mac上装虚拟机会占用大量内存&#xff0c;影响电脑系统性能。此外&#xff0c;有些用户还关心在…

C++实现LRU缓存(新手入门详解)

LRU的概念 LRU&#xff08;Least Recently Used&#xff0c;最近最少使用&#xff09;是一种常用的缓存淘汰策略&#xff0c;主要目的是在缓存空间有限的情况下&#xff0c;优先淘汰那些最长时间没有被访问的数据项。LRU 策略的核心思想是&#xff1a; 缓存空间有限&#xff1…

Linux:传输层(2) -- TCP协议(1)

目录 1. TCP协议段格式 2. 解包/分用 3. 确认应答(ACK)机制 4. 超时重传机制 5. 连接管理机制 5.1 三次握手 5.2 四次挥手 5.3 TIME_WAIT状态 5.4 CLOSE_WAIT状态 1. TCP协议段格式 源/目的端口号: 表示数据是从哪个进程来, 到哪个进程去; 32位序号/32位确认号: 后面详…

FreeModbus学习——读输入寄存器eMBFuncReadInputRegister

FreeModbus版本&#xff1a;1.6 当功能码为04时&#xff0c;也就是读输入寄存器MB_FUNC_READ_INPUT_REGISTER 看一下它是怎么调用读输入寄存器处理函数的 当功能码为04时&#xff0c;调用读输入寄存器处理函数 这个函数在数组xFuncHandlers中&#xff0c;也就是eMBFuncRead…

实战:MyBatis适配多种数据库:MySQL、Oracle、PostGresql等

概叙 很多时候&#xff0c;一套代码要适配多种数据库&#xff0c;主流的三种库&#xff1a;MySQL、Oracle、PostGresql&#xff0c;刚好mybatis支持这种扩展&#xff0c;如下图所示&#xff0c;在一个“namespace”&#xff0c;判断唯一的标志是iddatabaseId&#xff0c;刚好写…

mysql索引结构

多种数据结构 在数据库索引领域&#xff0c;特别是MySQL的InnoDB存储引擎中&#xff0c;聚簇索引&#xff08;Clustered Index&#xff09;和非聚簇索引&#xff08;也称为二级索引&#xff0c;Secondary Index&#xff09;是两种主要的索引类型。这些索引类型在数据结构的选择…

最优化原理(笔记)

内积是线性代数运算的一个结果&#xff0c;一行*一列。 内积的性质&#xff01; 什么是范数&#xff1f;&#xff1f;&#xff1f; 对称矩阵&#xff1a;关于主对角线对称&#xff01; 正定对称矩阵&#xff1a; 二阶导是正定的&#xff0c;f(x)就是严格的凸函数&#xff01;&a…

spring部分源码分析及Bean的生命周期理解

前言&#xff1a; 本文整体框架是通过refresh方法这个入口进入分析&#xff1a;分析IOC容器的创建及一些Bean的生命周期的知识点&#xff0c;写得确实一般般&#xff0c;感觉自己的有些前置知识并没有理解的很到位&#xff0c;所以&#xff0c;这篇文件先记录一下&#xff0c;…

推荐一款开箱即用、开源、免费的中后台管理系统模版

项目介绍 vue-pure-admin 是推荐一款开箱即用、开源&#xff08;遵循MIT License开源协议&#xff09;、免费的中后台管理系统模版&#xff0c;完全采用 ECMAScript 模块&#xff08;ESM&#xff09;规范来编写和组织代码&#xff0c;使用了最新的 Vue3、 Vite、Element-Plus、…

无人机图像目标检测技术详解

当前研究领域的热点之一。无人机搭载的高清摄像头能够实时捕获大量图像数据&#xff0c;对这些数据进行有效的目标检测对于军事侦察、环境监测、灾害救援等领域具有重要意义。本文将对无人机图像目标检测技术进行详解&#xff0c;包括图像处理技术、目标检测算法、关键技术应用…

pdf2docx - pdf 提取内容转 docx

文章目录 一、关于 pdf2docx主要功能限制 二、安装1、 PyPI2、从remote安装3、从源码安装4、卸载 三、转化 PDF例 1: convert all pages例 2: 转换指定页面例 3: multi-Processing例 4: 转换加密的pdf 四、提取表格五、命令行交互1、按页面范围2、按页码3、Multi-Processing 六…

gitee设置ssh公钥密码频繁密码验证

gitee中可以创建私有项目&#xff0c;但是在clone或者push都需要输入密码&#xff0c; 比较繁琐。 公钥则可以解决该问题&#xff0c;将私钥放在本地&#xff0c;公钥放在gitee上&#xff0c;当对项目进行操作时带有的私钥会在gitee和公钥进行验证&#xff0c;避免了手动输入密…

C语言数据结构课设:基于EasyX前端界面的飞机订票系统

数据结构课程设计说明书 学 院、系&#xff1a; 软件学院 专 业&#xff1a; 软件工程 班 级&#xff1a; 学 生 姓 名&#xff1a; 范 学 号&#xff1a; 设 计 题 目&#xff1a; 飞机订票系统 起 迄 日 期: 2024年6月18日~ 20…

【测试能力提升-AI】AI介绍

注释&#xff1a; 搞python的最终梦想&#xff0c;搞机器&#xff0c;玩深度&#xff0c;通网络&#xff0c;知模型&#xff0c;拿下AI技术&#xff0c;尽管只是测试&#xff0c;但是也是有梦想的 1. 目标 完成AI任务 ---- 掌握成熟、标准的任务解决方法掌握AI工具 ---- 完成…

2022 年中高职组“网络安全”赛项-海南省省竞赛任务书-1-B模块-B-4Web渗透测试

前言 本章节我将尝试操作B-4模块的渗透测试&#xff0c;搭建环境很难&#xff0c;还望大家点点赞多多支持&#xff01; 任务概览 最后4、5、6有一定的难度。 环境要求 kali Linux192.168.41.2Web服务器&#xff08;假设为PYsystem 2020 模拟平台&#xff09;192.168.41.7交换…

postman接口测试实战篇

击杀小游戏接口测试 接口测试简单介绍击杀小游戏代码下载单接口测试(postman)接口关联并参数化接口测试简单介绍 首先思考两个问题:1.接口是什么?2.接口测试是什么? 1.我们总是把接口想的很复杂,其实呢,它就是一个有特定输入和输出参数的交互逻辑处理单元,它不需要知…

【实战】Spring Cloud Stream3.0 整合RocketMq

文章目录 前言技术积累Spring Cloud Stream3.0新特性RocketMq简介 实战演示引入Maven依赖增加application配置消息生产者消息消费者 前言 相信很多同学用使用过rocketmq消息中间件&#xff0c;且大多情况下是使用原生的rocketmq-spring-boot-starter 进行集成然后创建一个rock…

Spring中Bean的循环依赖

目录 定义&#xff1a; 循环依赖的后果&#xff1a; 一&#xff1a;三级缓存 1、大概的思路&#xff1a; 注意&#xff1a; 2、执行过程&#xff1a; A半完成&#xff1a; B完成&#xff1a; A完成&#xff1a; 注&#xff1a; 二&#xff1a;Lazy 定义&#xff1a; …

入门C语言只需一个星期(星期三)

点击上方"蓝字"关注我们 01、基本数据类型 char 1 字节 −128 ~ 127 单个字符/字母/数字/ASCIIsigned char 1 字节 −128 ~ 127 -unsigned char 1 字节 0 ~ 255 -int…

【SpringCloud】微服务远程调用OpenFeign

工作原理流程图 上代码 common中添加依赖&#xff1a; <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <dependency><groupId>org.spri…