简介:这是一套基于Java Web的图书馆管理系统课程设计完整项目,围绕图书借阅、归还、查询、读者管理等核心业务,采用Servlet+JSP+MySQL技术栈,在Eclipse环境中开发,面向高校计算机相关专业学生以及希望入门Java Web开发的读者参考。资源压缩包共179个文件,大小约4.49MB,其中50个JSP页面负责前端展示,33个Java类及对应class文件涵盖核心业务模块的DAO层逻辑与请求控制,15个jar依赖库支撑JDBC连接与Web容器运行,1个sql脚本可直接导入MySQL完成建表,另含项目配置文件、CSS样式、JS脚本和图片等辅助素材,目录结构清晰便于检索。目前已有657人学习浏览。通过源码可掌握从数据库设计、Servlet请求分发到JSP页面渲染的完整Java Web开发链路,理解图书增删改查、借阅归还、用户登录等功能的编码实现方式,同时借鉴工程目录组织与代码分层思路,为独立完成课程设计或进阶学习SSM等框架打下基础。
1. 从一份「图书馆管理系统」zip 说起:Java Web 课设的标准答案长什么样
每学期期末,计算机相关专业都会冒出一批「基于 Java Web 的 XX 管理系统」课程设计。你手上这份「课程设计-基于 Java web 的图书馆管理系统(源码+数据库).zip」,大概率也是从学长手里拷来、或者从资源站下载的压缩包,里面装着一个能跑起来的 Java Web 工程加一份 SQL 脚本。它的本质不是一个产品,而是一个标准的 Java Web 增删改查教学项目:管理员登录、图书增删改查、读者管理、借书还书,四张表撑起全部业务。这类项目的价值在于——它把 JSP、Servlet、JDBC、MySQL 这几样课设最常考的东西串成了一条完整的链,非常适合用来应付课程设计答辩,也适合刚学完 Java Web 的人拿来当第一个完整项目练手。接下来的篇幅,我会按拿到压缩包之后的真实操作顺序,从项目拆解、环境搭建、代码走读一直讲到排障和答辩加分,争取让你照着走一遍就能跑通并且讲明白。
2. Java Web 课设的项目骨架:技术选型、功能模块与数据库设计
2.1 为什么课程设计爱用 Servlet + JSP + MySQL 这套组合
拿到压缩包后,先别急着双击打开,第一件事是确认技术栈。标题里写的是「Java web」而不是「Spring Boot」,这基本就锁定了项目的技术选型:Servlet + JSP + MySQL 的经典三层架构。为什么课程设计普遍用这套而不上 Spring Boot?核心原因是老师要看你对底层原理的掌握程度。Servlet 里你能讲清楚请求是怎么被容器接收、doGet/doPost 怎么分发、HttpSession 怎么维持登录态;换成 Spring Boot 之后这些细节全被框架吞掉了,答辩时老师问「你这个登录状态存在哪里」,你说不清 session 和 cookie 的配合,反而被动。
我拿到这类源码的第一反应是翻 web.xml 和目录结构,确认是 Servlet 3.0 之前的 web.xml 配置方式还是注解配置。老一点的项目用 web.xml 配置 Servlet 映射,新一点的项目直接在类上打 @WebServlet 注解。两种都常见,不影响运行,但影响你改代码时找入口的速度。看清楚之后,再打开 src 目录看包结构,一般分为 dao、service、servlet、entity(或者叫 model/bean)四层,偶尔会有 filter 和 util 包。这个分层本身就是课设评分的重点之一,后面答辩时老师一定会问「你这几层之间是怎么调用的」,所以源码拿到手之后把调用链捋一遍比自己闷头重写一个更有价值。
2.2 功能模块怎么划分才够答辩问
图书馆管理系统的功能模块看着多,拆开其实就四个核心块:图书管理、读者管理、借阅管理、系统管理。图书管理对应 book 表的增删改查,读者管理对应 reader 表的增删改查,借阅管理是借书和还书两个动作,系统管理通常就是管理员登录和密码修改。每个模块背后都是一组数据库增删改查操作,这也是热搜词里「数据库增删改查」出现的真实原因——课设项目的业务逻辑本质就是 SQL 操作。
功能清单里最容易被忽视的是「还书超期计算」。很多源码里还书功能只做了一件事:把 borrow 表里对应记录的 return_time 字段更新一下。但如果你认真看了需求文档,会发现还书时应该顺手算一下是否超期、超期多少天。这个逻辑虽然简单,却能在答辩时成为亮点,因为它在纯增删改查之上加了一层业务规则。另一个加分点是把「图书是否可借」做了校验,如果 book 表里没有 status 字段或者库存数量字段,借书时根本不知道这本书是不是已经被借走了,这种模块断层在答辩时很容易被问住。
拿到源码之后建议先画一张功能脑图,然后在代码里逐个找对应的 Servlet 路径,确认每个功能是不是真的实现了。很多下载的源码是阉割版,菜单里有「统计报表」,但点进去是 404 或者空页面。整理出「实际可用功能清单」比整理「设计功能清单」更重要,因为答辩演示时你只能演示能跑通的,一旦点了某个菜单页面崩了,整场演示的信任感就没了。
2.3 数据库表设计与 MySQL 建库脚本
数据库是这类项目的核心资产,压缩包里那个 .sql 文件比源码还重要。图书馆管理系统的表设计一般最少四张表:管理员表、图书表、读者表、借阅表,多了会加分类表和出版社表。我见过的最小可用设计就四张表,字段也精简到了极致,建库脚本大概长这样:
CREATE TABLE admin ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL ); CREATE TABLE book ( id INT PRIMARY KEY AUTO_INCREMENT, book_name VARCHAR(200) NOT NULL, author VARCHAR(100), publisher VARCHAR(100), isbn VARCHAR(20) UNIQUE, total INT DEFAULT 1, available INT DEFAULT 1 ); CREATE TABLE reader ( id INT PRIMARY KEY AUTO_INCREMENT, reader_name VARCHAR(100) NOT NULL, phone VARCHAR(20), reg_date DATE ); CREATE TABLE borrow ( id INT PRIMARY KEY AUTO_INCREMENT, book_id INT NOT NULL, reader_id INT NOT NULL, borrow_date DATE NOT NULL, return_date DATE, FOREIGN KEY (book_id) REFERENCES book(id), FOREIGN KEY (reader_id) REFERENCES reader(id) );这个脚本有几个值得注意的地方。available 字段表示「当前可借数量」,借书时减一,还书时加一,这比用 status 字段存单个状态要实用,因为同一本书可能有多本副本。borrow 表的 return_date 用 DATE 类型而不用 DATETIME,是因为课程设计场景下只需要精确到天,用日期类型可以少处理时区问题。外键在这个项目里是双刃剑:有外键能让表关系更清晰,答辩加分;但后期导入数据时外键约束会让你严格按插入顺序来,先插 book 和 reader,再插 borrow,否则直接报错。
再说一下压缩包里那个 .sql 文件的导入。常见的做法是打开 MySQL 命令行或者 Navicat,新建一个名为 library 的数据库,然后选择运行 SQL 文件。如果你用的是 Navicat,导入前先确认目标数据库选对了,不然脚本里的 CREATE TABLE 会把表建到别的库里。导入完成后执行 SHOW TABLES 确认四张表都在,这一步能省掉后面部署时的很多猜疑。记得确认脚本里有没有初始管理员账号,很多脚本只建了表结构和测试数据,admin 表里可能没有数据,或者密码是明文存的——这决定了你能不能登录成功。
3. 把源码跑起来:JDK、Tomcat、MySQL 的版本搭配与部署步骤
3.1 环境版本搭配:JDK 1.8 + Tomcat 8.5 + MySQL 5.7
课设项目的环境配置是最容易翻车的地方,大部分问题不是代码问题,是版本不匹配。老课设项目基本是按 JDK 1.8 时代的标准写的,配套的 Tomcat 版本集中在 7.0 到 8.5 之间,MySQL 则以 5.7 为主流。这个搭配不是玄学,而是有实际原因:JSP/Servlet 项目在 Tomcat 9 上跑需要 Servlet 4.0 规范,老代码如果用的是 javax.servlet 包,在 Tomcat 10 上会因为包名改成 jakarta.servlet 直接编译失败。
所以拿到源码之后第一件事是看 JDBC 驱动 jar 包。在 WEB-INF/lib 目录下看一眼 mysql-connector-java 的版本号,如果是 5.x 版本,配合 MySQL 5.7 最稳,硬连 MySQL 8.0 也能用但会出现时区警告;如果是 8.0.x 版本的驱动,配 MySQL 8.0 更省心。我见过太多人用 5.1.47 的驱动去连 MySQL 8.0,然后被 caching_sha2_password 认证插件卡住,报错信息是 Access denied for user,看着像密码错了,其实是认证方式不兼容。
版本对照下表可以作为参考:
| 组件 | 推荐版本 | 说明 |
|---|---|---|
| JDK | 1.8 | 课程设计最常见的目标环境,兼容性最好 |
| Tomcat | 8.5 | 支持 Servlet 3.1,老项目直接部署免改代码 |
| MySQL | 5.7 | 与 JDBC 5.x 驱动配合最省心 |
| IDE | Eclipse 或 IDEA | 都可以,关键是把 Tomcat 配置对 |
注意:如果你机器的 MySQL 已经装成 8.0 改不掉了,就优先把驱动换成 8.0 版本,然后按第 5 章的时区问题一起处理,别让版本问题成为第一个拦路虎。
3.2 导入源码与数据库:两步完成环境搭建
拿到 zip 后先解压,你会看到典型的 Web 项目目录:src 放 Java 源码,WebContent(或 WebRoot)下是 JSP 页面和 WEB-INF。注意看 WebContent 里有没有 .class 文件——如果有,说明这是别人编译过的完整工程,直接可以部署;如果只有 .java 源文件,则要交给 IDE 重新编译。这一步很多人忽略,导致部署后报 ClassNotFoundException。
数据库导入也在这步做。打开命令行或者数据库管理工具,执行建库操作:
mysql -u root -p CREATE DATABASE IF NOT EXISTS library DEFAULT CHARSET utf8; USE library; SOURCE /你的解压路径/library.sql;这里有几个参数要说明。CREATE DATABASE 时指定 utf8 字符集非常关键,不指定的话 MySQL 5.7 默认是 latin1,后面 JSP 页面显示中文就会乱码。SOURCE 命令后面的路径不要带中文和空格,MySQL 命令行对路径解析很弱,路径里有空格会直接报错。
导入完成后用三个命令验证一下:
USE library; SHOW TABLES; SELECT COUNT(*) FROM book;如果 book 表里能查到测试数据,说明脚本执行成功。如果 SELECT 报错说表不存在,多半是脚本里写了 USE 某个库而你当前的库名不匹配,手动把脚本里的库名改掉或者在导入前先切到目标库即可。
3.3 修改 JDBC 连接参数:URL、用户、密码一个都不能错
这一步是课设项目从「导入成功」到「真正跑通」的分水岭。打开源码里的 JDBC 工具类(通常在 util 包里,叫 DBUtil 或 JDBCUtil),你会看到类似这样的配置:
private static final String URL = "jdbc:mysql://localhost:3306/library?useUnicode=true&characterEncoding=utf8"; private static final String USER = "root"; private static final String PASSWORD = "123456"; public static Connection getConnection() throws SQLException { try { Class.forName("com.mysql.jdbc.Driver"); } catch (ClassNotFoundException e) { e.printStackTrace(); } return DriverManager.getConnection(URL, USER, PASSWORD); }这段代码要改的就是 URL、USER、PASSWORD 三个变量。URL 里 localhost:3306 是 MySQL 的默认地址和端口,如果你的 MySQL 改过端口,这里必须对应修改;library 是数据库名,一定要和建库时创建的库名一致。useUnicode=true 和 characterEncoding=utf8 这两个参数是解决中文乱码的关键,建议保留不动。
还有一个细节容易被忽略:Class.forName 里的驱动类名。用 MySQL 5.x 驱动时写法是 com.mysql.jdbc.Driver,用 8.0 驱动时应该改成 com.mysql.cj.jdbc.Driver,并且 URL 后面通常还要加 serverTimezone=Asia/Shanghai 参数。如果你发现 ClassNotFoundException,优先检查是不是驱动 jar 包没放到 WEB-INF/lib 下,再检查类名是否写对。
3.4 启动 Tomcat 与验证登录
配置完成后,在 IDE 里把项目关联到 Tomcat。Eclipse 用户是在 Servers 视图里新建一个 Tomcat 8.5 实例,然后把项目 Add 进去;IDEA 用户是配置 Tomcat Server 然后选择 Deployment 添加 war exploded 方式,这样改完代码不用重新打包。启动方式我建议直接在 IDE 里启动 Tomcat,这样控制台能直接看到启动日志,报错信息比外部启动更直观。
启动成功后在浏览器访问:http://localhost:8080/项目名/。项目名的取值取决于你部署时设置的 Application context,常见值是library或者解压后的文件夹名。不要指望直接访问 8080 端口就能看到登录页,那样只会看到 Tomcat 默认主页,真正的登录页地址通常是/login.jsp或者/index.jsp。
第一次访问如果页面空白或者报 500,先看 Tomcat 控制台有没有异常堆栈。最常见的错误是数据库连不上,报 Communications link failure 或者 Access denied。这些属于环境问题,排错路径在第 5 章单独展开。如果页面正常弹出登录框,输入脚本里预设的管理员账号密码,能跳转到主界面,这个项目就算跑起来了。跑通之后别急着提交,把登录、加书、删书、借书、还书各点一遍,确认没有半路报错,这是后续所有工作的基础。
4. 核心代码走读:登录、借书、还书背后的数据库增删改查
4.1 登录校验:从 JSP 表单到 Servlet 再到数据库
登录功能是课设答辩时老师第一个会问的模块,因为它在一次请求里串起了 JSP、Servlet、DAO、数据库四层。它的流程是:用户在 login.jsp 输入用户名密码,表单提交到 LoginServlet,Servlet 取出参数后调用 AdminDao 的查询方法,数据库执行 SELECT,有结果就跳转主页并把管理员信息放进 Session,否则返回登录页并提示错误。看代码时按这个顺序找。
核心验证逻辑一般是这样的:
@WebServlet("/login") public class LoginServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("utf-8"); String username = request.getParameter("username"); String password = request.getParameter("password"); AdminDao dao = new AdminDao(); Admin admin = dao.findByUsernameAndPassword(username, password); if (admin != null) { HttpSession session = request.getSession(); session.setAttribute("admin", admin); response.sendRedirect("main.jsp"); } else { request.setAttribute("error", "用户名或密码错误"); request.getRequestDispatcher("login.jsp").forward(request, response); } } }这里有几个点要注意。doPost 第一行设置 request 编码为 utf-8,放在读取参数之前,不然中文参数会乱码。登录成功后用 sendRedirect 做重定向,而不是 forward 转发,因为重定向会让浏览器地址变成 main.jsp,用户刷新页面时不会重复提交登录表单;forward 的话地址栏停留在 /login,用户一刷新就重新登录。这个细节老师问「为什么这里用重定向不用转发」时能答上来就是加分项。
4.2 图书查询分页:LIMIT 的偏移量与总数查询
图书列表在课设里一般都有分页功能,因为图书数据量一多,一次查全部会把页面撑爆。分页的 SQL 套路是固定的:用 LIMIT 限制每页条数,用 OFFSET 或者 LIMIT 第二个参数控制偏移量。Service 层通常有两个方法,一个是查当前页数据,一个是查总记录数用来算总页数。
分页查询的典型实现:
public List<Book> findBooks(int pageNum, int pageSize) { String sql = "SELECT * FROM book LIMIT ?, ?"; return jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Book.class), (pageNum - 1) * pageSize, pageSize); } public int countBooks() { String sql = "SELECT COUNT(*) FROM book"; return jdbcTemplate.queryForObject(sql, Integer.class); }注意 LIMIT 的第一个参数是偏移量,第二个参数是条数。第一页的数据不是 LIMIT 1, pageSize,而是 LIMIT 0, pageSize,所以传参时要算 (pageNum - 1) * pageSize,这个差值算错会导致第二页跳条或者重复。还应该先执行 countBooks 得到总记录数,再按 (total + pageSize - 1) / pageSize 算总页数,这个公式比 total / pageSize 再取整要严谨,因为它处理了总记录数不能被 pageSize 整除的情况。
分页是课程设计里最容易把代码写乱的地方。常见的不及格写法是每次翻页都把整个 List 传到页面上,在 JSP 里用 Java 代码截取当前页数据,这样确实能看到分页效果,但数据全量从数据库拉出来了,数据集一大就卡。课设答辩时直接指出这一点,说「我的分页是在 SQL 层做的,每次只查当前页数据」,老师对你是 Java Web 课设的预期会瞬间拉高。
4.3 借书还书事务:两个 update 要么都成功要么都回滚
借书和还书是图书管理系统里最有技术含量的两个操作,因为每个动作都涉及两张表的更新。借书时,要在 borrow 表插入一条记录,同时把 book 表里 available 减一;还书时,要更新 borrow 表的 return_date,同时把 available 加一。这两个操作必须在一个数据库事务里完成,否则会出现「借阅记录有了但库存没减」的数据不一致问题。
看源码时要注意那个finally块里的写法:
public boolean borrowBook(int bookId, int readerId) { Connection conn = null; PreparedStatement psInsert = null; PreparedStatement psUpdate = null; try { conn = DBUtil.getConnection(); conn.setAutoCommit(false); // 关键:关闭自动提交 String sqlInsert = "INSERT INTO borrow(book_id, reader_id, borrow_date) VALUES(?, ?, CURDATE())"; psInsert = conn.prepareStatement(sqlInsert); psInsert.setInt(1, bookId); psInsert.setInt(2, readerId); psInsert.executeUpdate(); String sqlUpdate = "UPDATE book SET available = available - 1 WHERE id = ? AND available > 0"; psUpdate = conn.prepareStatement(sqlUpdate); psUpdate.setInt(1, bookId); int rows = psUpdate.executeUpdate(); if (rows == 0) { conn.rollback(); // 没有可借库存,回滚插入的借阅记录 return false; } conn.commit(); return true; } catch (SQLException e) { e.printStackTrace(); try { if (conn != null) conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } return false; } finally { // 依次关闭 psUpdate、psInsert、conn,注意关闭顺序与判断非空 } }这段代码最关键的是 setAutoCommit(false),它把一次借书操作变成了手动提交的事务。UPDATE 语句里那个 available > 0 条件是个防并发的小陷阱:如果两个管理员同时给同一个人借最后一本书,加了条件后第二次 update 影响行数为 0,触发回滚,不会把库存减成负数。这就是为什么 rows == 0 时要回滚而不是直接返回 false——借阅记录已经插进去了,必须撤销才能保证数据一致。
代价是关闭资源时要多写几行 finally 代码。很多课设源码为了省事用 DriverManager.getConnection 然后不关事务,借书和还书各写一条 SQL 完事。你在走读代码时如果发现源码没做事务,这其实是你的机会:把这段代码补上去,答辩时就说「我补了事务处理,避免数据不一致」,这是最实在的加分项。
5. 课设部署的常见坑:从 404 到乱码的五条排障记录
5.1 数据库连不上:驱动版本与端口冲突
现象:启动 Tomcat 后访问登录页,输入账号密码点登录,页面卡一下然后报 500,控制台显示 Communications link failure 或者 Cannot create PoolableConnectionFactory。原因分三种:MySQL 服务没启动、端口不是默认的 3306、驱动类名和驱动版本不匹配。MySQL 没启动时错误信息会明确说 Connection refused;端口不对会显示连接超时;最隐蔽的是驱动版本问题,5.x 驱动连 MySQL 8.0 会报 SSL 连接异常或者认证失败。解决:先用netstat -ano | findstr 3306确认 MySQL 端口在监听,再用命令行工具用同样的用户名密码尝试连接,确认账号没问题后再检查 WEB-INF/lib 下的驱动 jar 和 JDBC 配置里的类名。
5.2 中文乱码:Tomcat 与 MySQL 两层编码要统一
现象:数据库里的中文正常,但页面上显示问号;或者页面上填的中文存进数据库变成乱码。原因:请求编码、Tomcat 连接器编码、数据库连接串编码、表字段编码四处不一致。解决:先确认 JSP 页面顶部有<%@ page contentType="text/html;charset=UTF-8" %>,再确认 doPost 里设置了 request.setCharacterEncoding("utf-8"),最后确认 JDBC URL 带上了 useUnicode=true&characterEncoding=utf8 并且建库时指定了 utf8。改完要重启 Tomcat 并重新导入数据库,乱码问题一般是复合原因,改一处不够。
5.3 部署后 404:WebContent 目录与访问路径
现象:Tomcat 启动成功,访问 http://localhost:8080/ 能看到 Tomcat 默认页面,但访问 http://localhost:8080/library/login.jsp 报 404。原因:项目没有被部署到 Tomcat 的 webapps 目录下,或者 Application context 设置不对。注意,看到 Tomcat 默认页面只能说明 Tomcat 本身活着,不能说明你的项目部署成功。解决:在 IDE 里打开 Server 视图看有没有把项目 Add 进去,IDEA 则检查 Artifacts 是否选了 lib 目录进来。还有一种隐蔽情况是项目里有 WEB-INF/web.xml 配置了 servlet-mapping,把所有请求拦截到了 servlet 上,直接访问 jsp 文件反而 404,这时候应该访问 servlet 的配置路径而不是文件路径。
5.4 借书成功但还书失败:漏了事务处理
现象:借书操作后图书的 available 减一了,但还书后 available 没有加回去,或者报空指针。原因:这种源码没有把还书做成一个完整事务,还书时只更新了 borrow 表的 return_date,漏掉了 book 表的 available 更新;空指针则通常是还书时按 reader_id 去查记录查到了多条,代码取第一条却拿不到对象。解决:按第 4.3 的模板把还书逻辑重写,一个事务里完成两步操作。还书时最好用 borrow 表的主键 id 去定位记录,而不是用 book_id 和 reader_id 组合查,避免同一个人多次借同一本书时更新错行。
5.5 MySQL 8.0 的时区与 SSL 问题
现象:项目在别人电脑上跑得好好的,到你电脑上报错The server time zone value 'Öйú±ê׼ʱ¼ä' is unrecognized或者 SSL 连接警告。原因:MySQL 8.0 默认时区信息与 JDBC 驱动的解析方式不兼容。解决:在 JDBC URL 末尾加serverTimezone=Asia/Shanghai&useSSL=false,然后重启 Tomcat。顺手把驱动 jar 换成 8.0 版本,并把 Class.forName 的类名改成 com.mysql.cj.jdbc.Driver。这个坑专属于新装 MySQL 8.0 的人,老 5.7 环境反而遇不到。
6. 从「能跑」到「高分」:答辩加分与二次开发的三个技巧
6.1 把连接池换上去,告别 DriverManager
课设源码里大多数用 DriverManager.getConnection 每次去连数据库,性能差而且答辩时容易被问「如果有一百个人同时登录你的系统怎么办」。常见做法是把连接逻辑改成 DBCP 或 C3P0 连接池,但引入新 jar 包有风险。更轻量的一招是用一个静态代码块注册驱动,再用 ThreadLocal 保存 Connection,让同一线程内的多次数据库操作用同一条连接,这样既不用引第三方包,又能讲出「连接复用」的概念。
6.2 用 PreparedStatement 堵住 SQL 注入
如果你在源码里看到 String 拼接 SQL 的写法,比如"SELECT * FROM admin WHERE username='" + username + "'",一定要改成 PreparedStatement 的占位符写法。这不仅是为了安全,更是答辩时的送分题——老师几乎必问「你的系统怎么防 SQL 注入」,你直接说「我用 PreparedStatement 预编译,参数和 SQL 分离,数据库端会做转义」,一句话就能答到点子上。
6.3 给借阅记录加索引与数据验证
最后一个小技巧是给 borrow 表的 book_id 和 reader_id 加联合索引,并且在借书前验证读者是否存在、图书是否可借。这两个动作代码量极小,但能在答辩时展示你有「数据一致性和查询效率」的意识。数据验证我一般放在 Service 层做,不放在 Servlet 里,这样一次校验能被多个入口复用。索引则直接用一条 SQL 写进建库脚本重新导入即可。
我自己的血泪经验是:课设别贪功能多,把登录、分页、事务、防注入这四件事做好,胜过堆十个半成品模块。我当年就是拼命加「图书推荐」功能,结果演示时主功能卡死翻车,被老师问得哑口无言。后来带学弟做课设,我只让他们先把借书还书的事务和分页捋清楚,反而全部顺利过关。希望帮到你。
本文还有配套的精品资源,点击获取