
简介这是一份面向Java Web初学者与在校学生的练手项目源码对应大二上学期期末大作业——图书管理系统适合用来巩固Servlet、JSP、MyBatis与Ajax等知识。项目围绕书籍、学生、管理员、留言四类实体展开涵盖借还书日志记录、书籍状态管理、信誉等级与借阅数量控制、学号异步查重、Cookie记住账号、Session登录校验等典型业务逻辑登录注册流程完整可作为课程设计或自学练手的参考。资源包共59个文件以26个java源码、13个html页面、7个xml配置、3个jsp及sql脚本、js、图片等为主压缩包约879KB结构清晰便于按模块阅读。目前已有1915人学习下载读者可从中获取完整的项目分层结构、数据库建表脚本与前后端交互实现思路对照理解Ajax异步验证、正则校验与登录状态保持等常见考点快速搭建自己的Java Web实践项目。1. 大二期末的 Javaweb 图书管理系统一个能跑通的练手项目长什么样如果你正在搜「javaweb项目完整案例mysql」大概率是课程设计或期末大作业卡在某个环节了。这份图书管理系统LMS-master就是一个典型的大二上学期期末作业级别的 Javaweb 练手项目技术栈是 Servlet MyBatis MySQL用 Maven 管理依赖前端是 JSP 页面配合 Ajax 异步交互。它不花哨但把「学生注册登录、管理员登录、图书借还、留言、信誉积分」这条主线走通了适合拿来当第一个完整案例拆解。它解决的核心问题是让你有一个能部署、能登录、能借书还书的闭环系统而不是一堆散落的 CRUD 代码。适合谁适合刚学完 Servlet 和 JDBC、想找一个结构完整但不复杂的项目练手的人也适合需要交期末作业、想参考登录鉴权和借阅状态机怎么落地的人。源码包里带了mybatis.sql建表脚本和pom.xml导入 IDEA 配好数据库就能跑省去从零搭环境的折腾。2. 环境搭建与数据库初始化从 zip 到能登录的完整链路拿到Javaweb图书馆.zip之后别急着点运行。这个项目是标准 Maven 结构src/main下分 Java 代码和资源文件根目录有pom.xml和mybatis.sql。先把数据库跑起来再让 IDEA 认识这个项目顺序反了会多花半小时排查玄学问题。2.1 导入 IDEA 与 Maven 依赖解析解压后直接用 IDEA 的 Open 打开项目根目录有pom.xml的那一层不要用 Import Module 一层层点容易把目录结构搞乱。打开后 IDEA 会自动识别为 Maven 项目并开始下载依赖右下角有进度条。如果依赖一直卡着不动常见原因是 Maven 中央仓库网络慢。我一般会在settings.xml里配一个国内镜像然后右键pom.xml→ Maven → Reload project 重新拉。依赖拉完后检查 Project Structure 里的 SDK 是不是 1.8 或以上这个项目的 Servlet 版本和 Tomcat 版本要匹配Tomcat 9 配javax.servlet没问题Tomcat 10 会因为包名从javax变成jakarta直接报 ClassNotFound这是新手最容易翻车的地方。!-- pom.xml 里关键依赖确认这几个坐标存在 -- dependency groupIdjavax.servlet/groupId artifactIdjavax.servlet-api/artifactId version4.0.1/version scopeprovided/scope !-- provided 表示由 Tomcat 提供不打进 war 包 -- /dependency dependency groupIdorg.mybatis/groupId artifactIdmybatis/artifactId version3.5.x/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.x/version /dependencyscope设为provided的依赖不会被打进最终产物因为 Tomcat 自己带了 Servlet API重复打包反而冲突。MySQL 驱动版本要和你本地 MySQL 服务端版本对齐8.0 的驱动连 5.7 的库通常没问题但连接 URL 要加时区和 SSL 参数否则启动就报时区错误。2.2 执行 mybatis.sql 建表与数据核对用 Navicat 或命令行登录 MySQL新建一个库比如library字符集选utf8mb4然后执行项目根目录的mybatis.sql。这个脚本会建出书籍、学生、管理员、留言等表。# 命令行方式导入避免图形工具编码问题 mysql -u root -p --default-character-setutf8mb4 library mybatis.sql导入后一定要核对两件事一是book表的state字段默认值它决定了书籍初始状态二是student表的credit、book_cnt、book_renewal这几个信誉相关字段有没有初始值。如果credit是 null登录后借书逻辑里做数值比较时会抛空指针页面直接 500。我一般会手动给测试账号补一个credit100的初始值。2.3 数据库连接配置与 Tomcat 部署数据库连不上后面全是白搭。找到项目里的 MyBatis 配置文件通常是mybatis-config.xml或db.properties把 URL、用户名、密码改成你本地的。!-- mybatis-config.xml 数据源部分 -- dataSource typePOOLED property namedriver valuecom.mysql.cj.jdbc.Driver/ property nameurl valuejdbc:mysql://localhost:3306/library?useUnicodetrueamp;characterEncodingutf8amp;serverTimezoneAsia/Shanghaiamp;useSSLfalse/ property nameusername valueroot/ property namepassword value你的密码/ /dataSourceserverTimezone不写会报The server time zone value is unrecognizeduseSSLfalse是本地开发省去证书校验。配好后在 IDEA 里配置 TomcatRun → Edit Configurations → 加一个 Tomcat Server → LocalDeployment 里加 Artifact选war explodedApplication context 设成/或/lms。启动后浏览器访问http://localhost:8080/能看到 index 介绍页就说明部署成功了。3. 登录注册模块Ajax Session Cookie 的落地细节这个项目最有参考价值的部分就是登录注册因为它把「前端异步校验 后端 Session 记录 Cookie 记住账号」这条链路完整走了一遍。很多人写课程设计时登录就是 form 直接提交页面刷新体验差这里用 Ajax 提交 JSON值得照着抄。3.1 学生注册的 Ajax 异步校验与 JSON 提交注册页面的核心诉求是学号不能重复、密码长度受限、手机号和邮箱格式要对而且这些校验要在不刷新页面的情况下完成。项目里明确要求不用 form 直接提交而是用 Ajax 以 JSON 形式发到后台。// 注册提交先本地正则校验再异步查重最后提交 function checkNumber(number) { // 学号格式假设 8 到 12 位数字按你学校规则改 return /^\d{8,12}$/.test(number); } function register() { const data { number: document.getElementById(number).value, username: document.getElementById(username).value, password: document.getElementById(password).value, tel: document.getElementById(tel).value, email: document.getElementById(email).value }; // 本地正则先拦一道减少无效请求 if (!checkNumber(data.number)) { alert(学号格式不对); return; } if (data.password.length 6) { alert(密码至少6位); return; } fetch(student?actioncheckNumbernumber data.number) .then(res res.text()) .then(text { if (text exist) { alert(学号已存在); return; } // 学号可用正式提交注册 return fetch(student?actionregister, { method: POST, headers: {Content-Type: application/json}, body: JSON.stringify(data) }); }) .then(res res res.text()) .then(result { if (result success) { alert(注册成功); location.href login.jsp; // 成功后跳登录页 } else { alert(注册失败请重试); // 失败留在当前页 } }); }这里有两段请求第一段查学号是否重复第二段才是真正注册。分开的好处是查重可以单独触发比如输入框失焦时注册时再查一次防止并发。后端 Servlet 用request.getParameter或读 JSON body 拿到数据调 MyBatis 的insert返回纯文本success或fail。注意Content-Type必须是application/json后端如果用getParameter读不到 body得用流读取再解析这是很多人卡住的地方。3.2 学生登录Session 记录与 Cookie 十天免登录学生登录用学号 密码登录成功后用 Session 存用户信息同时可选地写一个加密 Cookie 实现「十天内记住账号」。这里要提醒一句Cookie 存密码是绝对不行的存的是服务端生成的令牌或加密后的标识。// 登录 Servlet 核心逻辑 String number request.getParameter(number); String password request.getParameter(password); Student stu studentService.login(number, password); if (stu ! null) { HttpSession session request.getSession(); session.setAttribute(student, stu); // 后续页面靠这个判断登录态 // 记住账号写 Cookie有效期 10 天 if (true.equals(request.getParameter(remember))) { Cookie cookie new Cookie(studentNumber, number); cookie.setMaxAge(10 * 24 * 60 * 60); // 单位是秒 cookie.setPath(/); response.addCookie(cookie); } response.getWriter().write(success); } else { response.getWriter().write(fail); // 前端在本页提示不跳转 }setMaxAge的单位是秒10 天就是10*24*60*60写成10*24*60就变成 10 分钟了这个坑我踩过。Session 默认 30 分钟过期如果用户操作到一半掉线多半是 Session 超时可以在web.xml里调session-timeout。登录失败时后端只返回fail前端在当前页弹提示不跳转这样用户不用重新输一遍。3.3 管理员登录与页面跳转控制管理员登录逻辑和学生几乎一样区别是查admin表、Session 里存的是admin对象。跳转控制上学生登录成功进学生菜单页管理员进管理页。项目里提到两者之间可以相互跳转实现方式就是页面上放两个链接或者用response.sendRedirect做重定向。// 登录态拦截在需要登录的 Servlet 或 Filter 里判断 Object user request.getSession().getAttribute(student); if (user null) { response.sendRedirect(login.jsp); // 没登录踢回登录页 return; }常见做法是写一个 Filter 统一拦截/student/*和/admin/*路径避免每个 Servlet 都写一遍判断。重定向用sendRedirect地址栏变化转发用forward地址栏不变登录成功后跳转用重定向更合适防止用户刷新页面重复提交。4. 图书借还与信誉积分状态机与业务逻辑的坑借书还书看着简单其实是这个项目里业务逻辑最密的地方。书籍有 7 种状态学生有信誉等级和借阅数量限制这些字段之间互相牵制写不好就是一堆 if-else 堆在一起改一个地方崩三个地方。4.1 书籍状态字段的设计与流转Book类里state字段用整数表示状态0 未借出、1 已借出、2 已续借、3 逾期、4 丢失、5 损毁、6 其他。这个设计的好处是查询方便SELECT * FROM book WHERE state0就能统计可借数量不用额外维护一个总数。// 借书先判断状态再更新 owner 和日期 public String borrowBook(Integer bookId, Integer studentId) { Book book bookMapper.selectById(bookId); if (book.getState() ! 0) { return 该书当前不可借; // 状态不是 0 直接拒绝 } book.setState(1); book.setOwner(studentId); book.setBorrowBookDate(new SimpleDateFormat(yyyy-MM-dd).format(new Date())); bookMapper.update(book); return success; }还书时要把state改回 0同时把owner、borrowBookDate、returnBookDate清空或置 null。这里有个坑如果只改state不清owner下次统计「谁借了书」时会把已还的书也算进去。续借则是把state改成 2并延长归还日期逻辑上要判断当前是否已逾期逾期的不允许续借。4.2 信誉积分与借阅数量限制Student类里的credit、bookCnt、bookRenewal是联动的。信誉等级决定这个学生每月能借几本、能续借几次。常见做法是写一个方法根据credit返回可借数量。// 根据信誉分返回每月可借数量 public int getMaxBorrow(Integer credit) { if (credit 90) return 5; if (credit 70) return 3; if (credit 50) return 2; return 1; // 信誉低于 50 只能借 1 本 }借书前先查这个学生当前已借数量SELECT COUNT(*) FROM book WHERE owner? AND state IN (1,2)再和上限比较。逾期不还会扣信誉分扣到阈值以下就限制借阅。这套逻辑用数据库触发器或定时任务都能做但课程设计阶段在 Service 层用 Java 判断就够了别过度设计。4.3 借还日志记录到文件项目摘要里提到「可以将借书还书的活动写成日志记录到某个文件当中」。这是个加分项实现也简单用 Java 的FileWriter追加写就行。// 追加写借还日志注意用 try-with-resources 自动关流 public void writeLog(String action, Integer bookId, Integer studentId) { String line new SimpleDateFormat(yyyy-MM-dd HH:mm:ss).format(new Date()) | action | bookId bookId | studentId studentId; try (FileWriter fw new FileWriter(borrow.log, true); BufferedWriter bw new BufferedWriter(fw)) { bw.write(line); bw.newLine(); } catch (IOException e) { e.printStackTrace(); // 日志失败不能影响主流程 } }FileWriter第二个参数true表示追加模式不写就每次覆盖。日志文件路径建议用绝对路径或配置项用相对路径时工作目录取决于 Tomcat 启动位置容易找不到文件。日志写入失败要 catch 住不能让它把借书主流程带崩。5. 避坑与排查那些让项目跑不起来的常见问题这一章全是血泪经验每条都是「现象 → 原因 → 解决」的结构照着排查能省不少时间。现象启动 Tomcat 报ClassNotFoundException: javax.servlet.http.HttpServlet。原因Tomcat 10 及以上版本把 Servlet API 的包名从javax.servlet改成了jakarta.servlet而项目用的是旧包名。 解决换 Tomcat 9或者把项目里所有javax.servlet改成jakarta.servlet并升级依赖版本。课程设计阶段建议直接用 Tomcat 9改动最小。现象页面能打开但一登录就 500后台报NullPointerException。原因多半是 MyBatis 映射文件的字段名和数据库列名对不上或者credit等字段为 null 参与运算。 解决检查resultMap里的column和实体类属性是否一一对应给数据库里可能为 null 的数值字段设默认值 0。现象Ajax 提交注册后端getParameter拿到的全是 null。原因前端用application/json发的 bodygetParameter只能读表单编码的参数读不了 JSON body。 解决后端改用request.getReader()读字符流再用 JSON 库如 fastjson、jackson解析或者前端改成application/x-www-form-urlencoded提交。现象中文书名或留言存进数据库变成乱码。原因数据库、表、连接三处字符集不一致。 解决建库时用utf8mb4连接 URL 加characterEncodingutf8JSP 页面顶部设pageEncodingUTF-8三处统一。现象Cookie 记住账号功能失效关掉浏览器再打开还是要重新登录。原因setMaxAge参数写错单位或者setPath没设导致路径不匹配。 解决确认setMaxAge用的是秒10 天 864000setPath(/)让整个应用都能读到。6. 进阶技巧用 Filter 统一登录拦截与日志增强项目跑通之后想让代码更规范我一般会做两件事加一个登录拦截 Filter把借还日志从「写文件」升级成「文件 控制台」双输出方便调试。6.1 登录拦截 Filter 的写法不用在每个 Servlet 里判断 Session写一个 Filter 拦所有请求放行登录页、注册页和静态资源。WebFilter(/*) public class LoginFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) resp; String uri request.getRequestURI(); // 放行登录、注册、静态资源 if (uri.endsWith(login.jsp) || uri.endsWith(register.jsp) || uri.contains(/css/) || uri.contains(/js/) || uri.contains(actionlogin) || uri.contains(actionregister)) { chain.doFilter(req, resp); return; } // 判断登录态 if (request.getSession().getAttribute(student) null request.getSession().getAttribute(admin) null) { response.sendRedirect(login.jsp); return; } chain.doFilter(req, resp); } }WebFilter(/*)表示拦截所有路径放行规则用uri.endsWith或contains判断。注意别把登录请求本身也拦了否则永远登不进去。这个 Filter 加上之后新增页面不用再操心登录判断代码干净很多。6.2 日志增强与验证方法把第 4 章的writeLog稍微改一下同时输出到文件和控制台调试时不用去翻文件。public void writeLog(String action, Integer bookId, Integer studentId) { String line new SimpleDateFormat(yyyy-MM-dd HH:mm:ss).format(new Date()) | action | bookId bookId | studentId studentId; System.out.println([BORROW-LOG] line); // 控制台实时看 try (FileWriter fw new FileWriter(borrow.log, true); BufferedWriter bw new BufferedWriter(fw)) { bw.write(line); bw.newLine(); } catch (IOException e) { System.err.println(日志写入失败 e.getMessage()); } }验证方法很简单借一本书看控制台有没有输出再去项目工作目录找borrow.log文件确认内容追加而不是覆盖。还书后再看一次确认两条记录都在。如果文件没生成检查 Tomcat 的工作目录IDEA 里默认是项目根目录不是webapp目录。6.3 参数速查表配置项推荐值说明Tomcat 版本9.x兼容 javax.servlet 包名JDK 版本1.8项目依赖兼容性最好MySQL 字符集utf8mb4支持中文和特殊字符连接 URL 参数serverTimezoneAsia/ShanghaiuseSSLfalse避免时区和证书报错Cookie 有效期864000 秒即 10 天Session 超时30 分钟web.xml 可调从那以后我每次拿到一个 Javaweb 项目都强制先核对 Tomcat 版本和 Servlet 包名再动数据库配置最后才跑代码。顺序对了能少熬好几个晚上。希望帮到你。本文还有配套的精品资源点击获取