ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于JavaWeb的图书借阅管理系统设计与实现:从建表到部署完整指南

基于JavaWeb的图书借阅管理系统设计与实现:从建表到部署完整指南 简介这是一套基于JavaWeb的图书借阅管理系统完整项目源码采用JSPJavaBeanMySQLTomcat经典架构适合JavaWeb初学者、课程设计或毕业设计参考。系统分为读者端与管理端读者可注册登录、查询与借阅图书、查看借阅历史、归还图书、修改个人信息管理员可对图书进行增删改查对读者进行删除与修改并查询所有借阅记录。资源共38个文件以JSP页面、Java类、SQL脚本、配置文件及项目说明为主压缩包大小1.76MB其中17个JSP页面负责前端展示JavaBean类封装核心业务逻辑SQL脚本用于初始化数据表数据库脚本和运行必读说明一应俱全导入MySQL并调整连接配置即可运行。已有533人学习下载适合需要完整可运行案例来理解JSPJavaBean分层开发与数据库交互流程也适合复现图书借阅、归还、用户管理等典型场景并在此基础上扩展二次开发。1. 基于JavaWeb的图书借阅管理系统课程设计背后到底在考你什么如果你正在搜索“基于JavaWeb的图书借阅管理系统”大概率是接到了课程设计或毕业设计的任务。这个标题几乎是国内计算机专业出镜率最高的JavaWeb实战项目但它绝不只是一个“做个网页、连个数据库”的练手作业。把图书借阅管理系统完整跑通意味着你绕过了JavaWeb的四座大山HTTP请求如何被Servlet接收、业务逻辑怎么分层、数据库连接和事务怎么管、JSP页面怎么把数据渲染回前端。这套系统虽小五脏俱全——用户登录、图书增删改查、借书还书、逾期记录每个模块都对应着真实业务系统的标准动作。它的目标用户也不是泛泛的“编程学习者”而是需要交付一个能演示、能答辩、能写进简历的完整项目的在校生和初级开发者。你不需要微服务不需要Redis不需要Nginx——一台装好JDK、Tomcat、MySQL的电脑就足以承载这个项目的全部重量。但如果只是照着网上视频敲一遍你大概率会在环境配置、中文乱码、驱动冲突这些“非业务问题”上耗掉大半时间。这篇文章会按照一条可复现的路径把从建表到部署的每一步讲透包括那些视频里不会告诉你的坑。2. 数据库设计先行图书借阅系统的三张核心表与字段取舍动手写任何Java代码之前先把数据库表结构定下来。图书借阅管理系统的业务边界很清晰绝大多数实现都围绕三张基础表展开用户表、图书表、借阅记录表。表结构设计得好后面的Servlet和JSP代码会写得非常顺设计得不好业务逻辑里全是补丁式的SQL拼接。2.1 用户表与图书表字段设计的基本盘用户表保存两类人管理员和普通借阅者。常见做法是用一个role字段区分而不是拆成两张表——拆表意味着登录逻辑要做联合查询对课程设计这个体量来说得不偿失。图书表的核心字段包括书名、作者、ISBN、分类、库存总量、当前可借数量。这里有一个很容易被忽略的点stock库存总量和available可借数量必须分开。否则每次借书都要去borrow_records表里统计未归还的数量SQL复杂度陡然上升。下面是一份可以直接执行的建表语句编码统一使用utf8mb4避免中文字符和生僻字在存储时翻车CREATE DATABASE IF NOT EXISTS library_system DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE library_system; CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL, real_name VARCHAR(50) DEFAULT NULL, role TINYINT NOT NULL DEFAULT 0 COMMENT 0-普通用户 1-管理员, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE book ( id INT PRIMARY KEY AUTO_INCREMENT, isbn VARCHAR(20) NOT NULL UNIQUE, title VARCHAR(200) NOT NULL, author VARCHAR(100) DEFAULT NULL, publisher VARCHAR(100) DEFAULT NULL, category VARCHAR(50) DEFAULT NULL, stock INT NOT NULL DEFAULT 1 COMMENT 库存总量, available INT NOT NULL DEFAULT 1 COMMENT 当前可借数量 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE borrow_record ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, book_id INT NOT NULL, borrow_time DATETIME DEFAULT CURRENT_TIMESTAMP, due_time DATETIME DEFAULT NULL COMMENT 应还时间, return_time DATETIME DEFAULT NULL COMMENT 实际归还时间, status TINYINT NOT NULL DEFAULT 0 COMMENT 0-借出 1-已还 2-逾期未还, KEY idx_user (user_id), KEY idx_book (book_id), CONSTRAINT fk_borrow_user FOREIGN KEY (user_id) REFERENCES user(id), CONSTRAINT fk_borrow_book FOREIGN KEY (book_id) REFERENCES book(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这段建表语句里有三个设计决策值得说明。第一user表名加上了反引号因为user是MySQL的保留字不加引号会直接报语法错误。第二borrow_record表把due_time和return_time分开存放逾期判断只需要比较这两个字段不必每次查询都做日期运算。第三外键约束在课程设计里建议保留虽然有人说外键影响性能但这个体量的系统根本感知不到差异保留外键反而能在答辩时讲清楚数据完整性。2.2 借阅记录表的查询索引与关联关系别让联表查询变噩梦借阅记录表是系统里查询频次最高的表——用户查自己的借阅列表、管理员查全量借阅记录、统计逾期明细全部要走这张表。索引的设计直接影响页面加载速度。上面建表语句里已经加了idx_user和idx_book两个普通索引覆盖了“按用户查记录”和“按图书查记录”两条最常用路径。关联查询的常见写法是内连接三张表一次查出借阅人姓名、书名和借阅时间。这里有一个性能与代码简洁度的权衡如果每次查询都用JOINSQL写起来冗长而且一旦索引缺失会做全表扫描。我一般会让borrow_record表作为一个准事实表存在查询时只关联user和book的少量字段并且确保WHERE条件里的字段都有索引覆盖。-- 查询某用户的借阅记录含书名和作者 SELECT br.id, b.title, b.author, br.borrow_time, br.due_time, br.return_time, br.status FROM borrow_record br JOIN book b ON br.book_id b.id WHERE br.user_id ? ORDER BY br.borrow_time DESC;参数说明?占位符对应传入的user_id在Java端用PreparedStatement填充。ORDER BY按借书时间倒序让最新记录排在最前面这是列表页的默认期望行为。如果后续数据量达到十万级可以考虑在borrow_time上加索引课程设计阶段不需要。2.3 为什么不用数据库存储过程JavaWeb项目的分层边界有些教材喜欢把借书、还书逻辑写成存储过程让数据库层把事务做完Java端只调用一个CALL语句。这种做法的确能减少Java代码量但有一个致命问题业务逻辑被搬到了数据库里JavaWeb的分层架构就名存实亡了——Service层变成空壳代码评审时会非常被动。更合理的边界是数据库只负责存储和简单的查询约束借书时检查库存、更新可借数量、插入借阅记录这些动作放在Java的Service层用事务控制每一行逻辑都在代码里可见、可断点调试、可写单元测试。3. 从IDEA到TomcatJavaWeb项目跑起来的三个环境坑与标准配置选题定了、表建好了接下来是让项目在本地跑起来。这个环节是劝退率最高的地方大量时间花在启动报错和版本冲突上而不是业务代码上。从配置JDK到打开浏览器看到登录页每一步都有对应的坑位。3.1 JDK、Tomcat与IDEA的版本匹配逻辑JavaWeb项目的经典组合是JDK 8 Tomcat 8.5 MySQL 5.7这套组合经过大量项目验证兼容性最稳。JDK 8对应javax.servlet命名空间Tomcat 8.5支持到Servlet 3.1规范MySQL 5.7的驱动用com.mysql.jdbc.Driver。如果你装了JDK 17和Tomcat 10代码里要用jakarta.servlet而不是javax.servlet绝大多数视频教程的代码会直接编译报错——这不是你的代码有问题是Servlet命名空间整体迁移了。IDEA中的配置顺序建议是先装JDK并在Project Structure里指定 Project SDK再配置Tomcat。很多人习惯先把Tomcat跑起来验证端口这没错但IDEA里需要的是Tomcat的安装目录不是bin目录在Run Configuration中添加Tomcat Server时定位到根目录即可。3.2 创建JavaWeb工程手动导入jar包还是用Maven这是一个分岔路直接影响后续的依赖管理体验。网上大量教程是传统的“手动导入jar包”方式——在WEB-INF下建lib目录把mysql-connector.jar、jstl.jar拖进去。这种方式对课程设计够用但坑在于jar包版本容易冲突而且换机器部署时要重新配置。另一种方式是使用Maven在pom.xml里声明依赖IDEA自动下载。考虑到答辩时被问到“你用什么构建工具”Maven显然是加分项。如果选择Maven最简配置如下dependencies dependency groupIdjavax.servlet/groupId artifactIdjavax.servlet-api/artifactId version4.0.1/version scopeprovided/scope /dependency dependency groupIdjavax.servlet/groupId artifactIdjstl/artifactId version1.2/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version5.1.49/version /dependency /dependencies注意两个细节。第一javax.servlet-api的scope是provided因为Tomcat本身已经带了一份Servlet API如果再打包进WEB-INF/lib会出现类冲突运行时报NoClassDefFoundError或奇怪的ClassCastException。第二MySQL驱动选5.1.49而不是8.x版本是考虑到你大概率连的是MySQL 5.x如果用的是MySQL 8驱动需要换成8.0.x并且DriverManager的驱动类名改为com.mysql.cj.jdbc.DriverURL里还要追加serverTimezoneAsia/Shanghai参数否则时间字段会报错。3.3 部署配置与启动参数Artifact和Application Server的必调项在IDEA里配置Tomcat运行需要确认两个关键设置。第一Deployment选项卡里把Artifact选为war exploded形态这样修改JSP或Java文件后IDEA的Hot Swap能直接生效——用war包形态的话每次改动都要重新build整个包非常影响调试节奏。第二Application Server的VM Options里加上一行内存参数-Xms256m -Xmx512m -XX:MaxMetaspaceSize256mTomcat默认堆内存是128MB项目级应用跑起来后JSP编译、连接池初始化都会吃内存128MB会频繁触发Full GC页面响应肉眼可见地卡。给到512MB后整个系统运行顺畅很多。还有一个容易忽略的细节Tomcat的HTTP端口默认8080如果本机有其它服务占用了8080比如Nginx或另一个Tomcat在Server选项卡里把Port改成8081即可否则启动会报Port 8080 is already in use。4. 核心功能落地LoginServlet到借书还书的完整请求链路环境通了数据库表建好了接下来就是真正的业务代码。这个系统最核心的三段逻辑是登录、借书、还书。这三段逻辑写扎实剩下的图书管理就是一个标准的CRUD照葫芦画瓢即可。这里我给出每一段的思路和关键代码你可以根据自己的包结构做调整但链路必须完整JSP发起请求 → Servlet接收 → Service处理业务 → DAO访问数据库 → 返回结果。4.1 登录会话管理从LoginServlet到Session与Cookie的分工登录是所有页面的入口闸门。它的职责不只是验证用户名密码还要把当前用户身份写入Session让后续的借书、还书请求知道“你是谁”。基于Servlet的实现里登录成功后调用request.getSession().setAttribute(currentUser, user)然后在每个需要登录的Servlet里检查这个属性是否存在不存在就重定向到登录页。这就是最朴素的会话管理课程设计阶段不需要引入Shiro或Spring Security那种重量级框架。WebServlet(/login) public class LoginServlet extends HttpServlet { private UserService userService new UserService(); Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding(UTF-8); String username req.getParameter(username); String password req.getParameter(password); User user userService.login(username, password); if (user ! null) { HttpSession session req.getSession(); session.setAttribute(currentUser, user); if (user.getRole() 1) { resp.sendRedirect(admin/index.jsp); } else { resp.sendRedirect(user/index.jsp); } } else { req.setAttribute(errorMsg, 用户名或密码错误); req.getRequestDispatcher(login.jsp).forward(req, resp); } } }这段代码的逻辑链路是设置请求编码否则中文参数会乱码从请求里取出用户名密码调用Service层做校验成功则写入Session并重定向到对应首页失败则把错误信息放回请求域中并转发回登录页。这里有一个细节值得说明失败时用的是forward而不是sendRedirect因为forward能保留request域里的errorMsg属性登录页能直接通过EL表达式渲染出错误提示如果用重定向request域会丢失用户会看到一片空白误以为页面坏了。4.2 借书操作的事务处理库存检查、扣减库存与写入记录的顺序借书的业务规则很直接——检查图书可借数量如果大于0就扣减同时插入一条借阅记录。但这里的难点在于“同时”二字扣库存和写记录必须是一个原子操作要么都成功要么都失败。如果先扣库存但插入记录失败图书就凭空少了一本如果先插入记录但扣库存失败则超借了。解决方式是用Connection的事务控制在Service层获取数据库连接关闭自动提交完成两个操作后再统一commit。public boolean borrowBook(int userId, int bookId) { Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); BookDao bookDao new BookDao(); Book book bookDao.findById(conn, bookId); if (book null || book.getAvailable() 0) { conn.rollback(); return false; } // 扣减可借数量 boolean updated bookDao.decreaseAvailable(conn, bookId); if (!updated) { conn.rollback(); return false; } // 写入借阅记录 boolean inserted borrowDao.insert(conn, userId, bookId); if (!inserted) { conn.rollback(); return false; } conn.commit(); return true; } catch (Exception e) { if (conn ! null) { try { conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } } e.printStackTrace(); return false; } finally { if (conn ! null) { try { conn.setAutoCommit(true); conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } }这段代码的核心是setAutoCommit(false)之后的每一步都是可回退的。注意到bookDao.findById、decreaseAvailable和borrowDao.insert都接收Connection参数这意味着整个事务期间使用的是同一个数据库连接而不是每个DAO方法内部再去DBUtil.getConnection()获取新连接——如果那样做事务就失效了因为MySQL默认每个连接一个事务跨连接无法保证原子性。这是很多新手踩坑最深的点代码写完运行看结果发现库存扣了但记录没插上。4.3 还书与逾期状态更新时间这一列在哪儿算清楚还书逻辑比借书简单一些但有个容易忽视的业务点逾期状态的判断时机。当用户点击还书时系统需要拿到当前时间和due_time比较决定这条记录是“正常归还”还是“逾期归还”。这里有一个设计建议borrow_record.status在还书时直接从0更新为1已还逾期与否不需要单独落一个状态值——判断依据是return_time due_time查询时用SQL直接比较即可。也就避免了一个状态字段引起的同步问题。public boolean returnBook(int recordId) { Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); BorrowDao borrowDao new BorrowDao(); BorrowRecord record borrowDao.findById(conn, recordId); if (record null || record.getStatus() ! 0) { // 记录不存在或已归还 conn.rollback(); return false; } // 更新归还时间和状态 Date now new Date(); boolean updated borrowDao.updateReturnInfo(conn, recordId, now, 1); if (!updated) { conn.rollback(); return false; } // 把图书可借数量加回去 boolean recovered bookDao.increaseAvailable(conn, record.getBookId()); if (!recovered) { conn.rollback(); return false; } conn.commit(); return true; } catch (Exception e) { if (conn ! null) { try { conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } } return false; } finally { try { if (conn ! null) { conn.setAutoCommit(true); conn.close(); } } catch (SQLException e) { e.printStackTrace(); } } }还书操作也是对两个表的联动更新——先改borrow_record的状态和归还时间再把book表的available加一。两个动作依然要放在同一个事务里否则会出现书还了但库存没恢复或者库存恢复但记录还是借出状态的数据不一致。4.4 图书管理页面的CRUD与分页为什么JSPJSTL比纯脚本更值得写图书管理是纯粹的标准CRUD但它有一个课程设计必考的知识点——分页。如果一次性把所有图书查询出来在页面上渲染数据量小没问题一旦上百条页面加载就会变慢答辩时很容易被问到“怎么优化”。标准做法是LIMIT offset, size实现物理分页每次只查询一页数据同时查出总条数用于渲染页码。public PageResultBook findBooks(int pageNum, int pageSize) { int offset (pageNum - 1) * pageSize; ListBook list bookDao.findPage(offset, pageSize); int totalCount bookDao.countAll(); return new PageResult(list, totalCount, pageNum, pageSize); }对应的SQL是SELECT * FROM book ORDER BY id LIMIT ?, ?第一个参数是偏移量第二个参数是返回的行数。页面端用JSTL的c:forEach遍历列表用c:if控制上一页/下一页按钮的显隐。关键点是JSP中禁止使用Java脚本片段% %统一用EL表达式和JSTL标签这样页面代码简洁可读也让页面和Java逻辑的边界更清晰。5. 图书借阅系统避坑指南本地跑通后最容易翻车的5个问题课程设计走到这里项目基本成型了但距离“能稳定演示”还有一段距离。以下5个问题是我见过的出现频率最高的踩坑点每一个都对应着真实项目运行中的典型症状按“现象 → 原因 → 解决”的方式列出建议你对照排查。5.1 启动报ClassNotFoundException: com.mysql.jdbc.Driver现象Tomcat启动时或首次访问数据库时报找不到驱动类堆栈里有ClassNotFoundException。原因绝大多数情况是jar包没进WEB-INF/lib。Maven项目如果忘记设置打包方式为warIDEA的Artifact构建时不会把依赖拷进lib目录手动导jar包时如果漏了这一步也一样。另一个可能原因是JDK 9以上模块化限制但课程设计环境基本是JDK 8先不考虑这条。解决检查Artifact的Output Layout确认mysql-connector-java的jar包已经出现在WEB-INF/lib下。如果用的Maven在pom.xml确保打包方式为packagingwar/packaging然后clean重新build。也可以在部署目录的WEB-INF/lib下直接确认jar文件是否存在。5.2 登录页提交中文用户名后乱码现象页面上显示正常的汉字提交到后端后request.getParameter(username)取出来变成乱码插入数据库也乱。原因HTTP请求体的编码和Servlet解析编码不一致。Tomcat 8以上默认请求编码是UTF-8但JSP页面本身的编码可能不是UTF-8或者前端的Content-Type没有包括charsetutf-8。解决在JSP第一行声明% page contentTypetext/html;charsetUTF-8 languagejava %同时在Servlet的doPost方法里先调用req.setCharacterEncoding(UTF-8)。如果你的Servlet配置了过滤器用CharacterEncodingFilter统一处理把编码设置在过滤器里一次性全局生效比在每个Servlet里重复写要干净得多。5.3 数据库连接池报Communications link failure现象项目刚启动时一切正常过几分钟后第一次点击查询报错Communications link failure或者Connection is not available。原因MySQL的wait_timeout默认是8小时但连接池里的连接如果在空闲时被MySQL服务端断掉连接池不知道再次取出这条连接时就会通信失败。课程设计里的DBUtil通常是一个简单的DriverManager.getConnection()每次新建连接不会遇到这个问题如果你用了C3P0或Druid必须设置连接有效性检查。解决如果用Druid配置里加上testWhileIdletrue和validationQuerySELECT 1如果用C3P0设置idleConnectionTestPeriod。最省事的做法是直接用Druid并打开连接复用检测这样空闲连接被MySQL断了之后连接池会自动丢弃并新建连接。5.4 JSP页面显示JSTL标签源码明文而不是渲染后的内容现象页面上直接显示c:forEach items${list} varitem这样的源码。原因JSP页面引用JSTL库的taglib指令没写对或者JSTL的jar包没有完整引入。JSTL使用中有一个很经典的坑jstl.jar和standard.jar缺一不可但Maven坐标javax.servlet:jstl:1.2是一个聚合包不需要再额外引入standard。解决检查JSP头部确认% taglib prefixc urihttp://java.sun.com/jsp/jstl/core %引用的uri是否正确。另外注意在web.xml里把web-app版本声明为3.1以上后JSP引擎对taglib的解析更严格uri必须和jar包里的tld描述文件完全匹配。5.5 借书成功但用户借阅列表里查不到记录现象借书提示成功书籍可借数量也减少了但用户页面的“我的借阅”列表里没有这条记录。原因几乎可以断定是查询时条件写错了。比如页面查询用的user_id是从Session里取的User对象但Session里存入的User对象只设置了部分字段比如只set了username没set id导致取出的user_id是0或null查询结果自然为空。解决在登录成功时把完整User对象写入Session不要只塞一个用户名。另一个常见做法是在借阅列表查询时重新从数据库按id查一次User确保拿到的是完整字段。这个问题定位很方便——在Service层查询代码打个断点看传入的user_id是多少立刻就知道是不是Session里的数据不完整。6. 把系统从“能跑”做到“能答辩”测试数据构造与逾期计算技巧项目功能都完成了最后一步是让它看起来像一个“正经系统”。答辩时的演示体验很大程度上取决于测试数据是否真实、页面是否连贯。这个阶段我会优先做两件事造一批有说服力的测试数据把一个容易出错但展示效果极强的场景打通——逾期自动标记。6.1 批量造数用SQL脚本生成图书和用户数据手工在页面上一条一条添加图书不仅耗时数据还显得假。写个SQL脚本批量插入让列表页一进来就有几十本书视觉上立刻充实起来。-- 批量插入测试图书用笛卡尔积生成多条记录 INSERT INTO book (isbn, title, author, publisher, category, stock, available) SELECT CONCAT(978-7-, LPAD(id, 5, 0), -, LPAD(ROUND(RAND()*1000), 3, 0), -, ROUND(RAND()*9)) AS isbn, CONCAT(图书样本_, id) AS title, CONCAT(作者_, CHAR(65 ROUND(RAND()*25))) AS author, 人民邮电出版社 AS publisher, ELT(1 FLOOR(RAND() * 5), 计算机, 文学, 历史, 科学, 艺术) AS category, 10 AS stock, 10 AS available FROM information_schema.tables LIMIT 50;这段脚本利用MySQL的information_schema.tables作为行来源生成50条图书记录。ISBN用CONCAT和LPAD拼出一个19位标准格式随机但不重复。库存统一设置为10方便测试借书时库存不足的边界场景。运行时注意一条MySQL 8.0.19以上版本的information_schema.tables行数可能少于50如果生成数量不够可以改成CROSS JOIN两张系统表凑足行数。6.2 逾期未还的判断在查询时实时计算而不是等定时任务课程设计系统一般不会真的跑定时任务去扫描逾期记录更简单的做法是在每次查询借阅列表时动态计算逾期状态。SQL层面可以这样处理SELECT br.id, b.title, br.borrow_time, br.due_time, br.return_time, CASE WHEN br.return_time IS NULL AND br.due_time NOW() THEN 逾期未还 WHEN br.return_time IS NOT NULL AND br.return_time br.due_time THEN 逾期已还 ELSE 正常 END AS overdue_status FROM borrow_record br JOIN book b ON br.book_id b.id WHERE br.user_id ?这个技巧的好处是把状态判断从Java代码挪到了SQL里Java端拿到overdue_status字段后直接渲染在页面上省掉一整套状态计算逻辑。注意br.return_time IS NULL AND br.due_time NOW()这个条件——它表示“尚未归还且已超过应还时间”这是逾期未还的准确定义。如果你漏掉IS NULL判断已归还但超过应还时间的记录会被错误标记为逾期未还。6.3 从演示到交付导出SQL文件、编写README、验证干净部署答辩前最后一步是确保换一台电脑也能从零跑起来。建议把完整的建库脚本、示例数据、部署步骤整理成一个README里面写清楚三件事JDK/Tomcat/MySQL的版本要求数据库初始化的命令以及IDEA里启动的步骤。做完这些你可以尝试删掉本地Tomcat的部署目录重新用IDEA启动一遍验证从零部署的流程没有遗漏。这个习惯在项目交付时特别重要它能把“我机器上能跑”变成“任何机器上都能跑”。我自己的习惯是每次答辩前强制自己执行一遍“毁灭测试”——把数据库删掉、构建目录清空照着README从零开始搭。做到第几次会踩坑就改README直到一次通过为止。这个流程看似麻烦但它能把你从“演示时环境挂了只能尬笑”的窘境里彻底解救出来。希望帮到你。本文还有配套的精品资源点击获取
返回列表