JavaWeb图书管理系统开发实战项目

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:JavaWeb图书管理系统是一个基于Servlet和JSP构建的Web应用,提供用户注册、登录、图书分页查询、借阅归还等功能,并支持管理员对书籍和用户信息进行管理。系统采用JavaWeb核心技术栈,结合数据库操作,帮助初学者深入理解Web开发流程。本项目结构清晰,涵盖前后端交互、数据库连接与业务逻辑处理,是学习JavaWeb开发的实用实战案例。
图书管理系统

1. JavaWeb技术概述

JavaWeb 是基于 Java 语言构建动态 Web 应用的技术体系,广泛应用于企业级应用开发,尤其是图书管理系统这类数据交互密集型系统。其核心由 Servlet、JSP、JavaBean、Filter、Listener 等组件构成,结合 Tomcat、Jetty 等 Web 容器运行。

开发环境通常包括:JDK、IDE(如 IntelliJ IDEA 或 Eclipse)、Web 容器(如 Apache Tomcat)、数据库(如 MySQL),以及构建工具(如 Maven)。通过这些工具,开发者可以实现前后端交互、数据持久化、用户权限管理等功能。

在图书管理系统中,JavaWeb 能够支撑用户登录、图书查询、借阅管理等模块,实现高内聚、低耦合的系统架构,提升开发效率与系统稳定性。

2. Servlet请求处理机制

Servlet 是 JavaWeb 开发中处理客户端请求的核心组件,理解其请求处理机制对于构建高效、稳定的 Web 应用至关重要。本章将深入剖析 Servlet 的基本概念、请求与响应的处理流程,并结合图书管理系统的实际应用,展示如何通过 Servlet 实现业务逻辑处理。同时,还将探讨 Servlet 的线程安全问题以及配置优化技巧,帮助开发者构建更加健壮的服务端程序。

2.1 Servlet的基本概念

2.1.1 Servlet的定义与生命周期

Servlet 是运行在 Web 服务器或应用服务器上的 Java 程序,用于扩展服务器的功能,响应来自客户端的请求。Servlet 本质上是一个 Java 类,继承自 HttpServlet 或实现 Servlet 接口,并重写相应的方法以处理请求。

Servlet 的生命周期由 Web 容器(如 Tomcat)管理,主要包括三个阶段:

  1. 初始化阶段(init) :在第一次请求某个 Servlet 时,Web 容器会调用 init() 方法进行初始化,该方法在整个生命周期中只执行一次。
  2. 服务阶段(service) :每次客户端请求到达时,容器调用 service() 方法,该方法根据请求类型(GET、POST 等)调用相应的 doGet() doPost() 方法。
  3. 销毁阶段(destroy) :当 Web 应用关闭或 Servlet 被卸载时,容器调用 destroy() 方法释放资源。

下面是一个简单的 Servlet 示例:

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class HelloServlet extends HttpServlet {
    public void init() throws ServletException {
        System.out.println("Servlet 初始化完成");
    }

    public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        out.println("<h1>Hello, Servlet!</h1>");
    }

    public void destroy() {
        System.out.println("Servlet 正在销毁");
    }
}

代码分析:

  • init() :输出初始化信息,适用于加载数据库连接、配置文件等一次性操作。
  • doGet() :设置响应类型为 HTML,并向客户端输出欢迎信息。
  • destroy() :在 Servlet 销毁时释放资源,如关闭数据库连接等。

2.1.2 Servlet与CGI的区别

Servlet 与传统的 CGI(Common Gateway Interface)在处理 Web 请求方面有显著差异:

对比项 CGI Servlet
启动方式 每次请求启动一个新进程 多线程复用同一个实例
性能 低,进程创建开销大 高,线程复用效率高
平台依赖 依赖操作系统 基于 Java,跨平台
安全性 较低,容易受到攻击 更高,Java 安全机制
部署方式 依赖脚本路径 通过 web.xml 或注解配置

Servlet 通过多线程机制复用实例,避免了每次请求都创建新进程的开销,大大提高了服务器性能,尤其适用于高并发场景。

2.2 请求与响应的处理流程

2.2.1 HttpServletRequest与HttpServletResponse对象详解

Servlet 通过 HttpServletRequest HttpServletResponse 对象与客户端进行数据交互。这两个对象分别封装了客户端请求和服务器响应的信息。

  • HttpServletRequest :包含客户端请求的所有信息,包括请求方法、参数、头信息、会话信息等。
  • HttpServletResponse :用于向客户端发送响应数据,如 HTML、JSON、状态码等。

示例代码:获取请求参数并输出响应

public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
    String username = request.getParameter("username");
    String password = request.getParameter("password");

    response.setContentType("text/html;charset=UTF-8");
    PrintWriter out = response.getWriter();
    out.println("<h2>用户名:" + username + "</h2>");
    out.println("<h2>密码:" + password + "</h2>");
}

参数说明:

  • getParameter("username") :从请求中获取名为 username 的参数值。
  • setContentType("text/html;charset=UTF-8") :设置响应类型为 HTML,并指定字符集为 UTF-8。
  • getWriter() :获取字符输出流,用于向客户端发送文本响应。

2.2.2 GET与POST请求的处理方式

GET 和 POST 是 HTTP 协议中最常用的两种请求方法,Servlet 通过重写 doGet() doPost() 方法分别处理这两种请求。

对比项 GET 请求 POST 请求
参数传递 通过 URL 传递(查询字符串) 通过请求体传递
安全性 不安全,参数暴露在 URL 中 相对安全
数据长度限制 有限(URL 长度限制) 几乎无限制
缓存与书签 可缓存,可加入书签 不可缓存,不适合书签

代码示例:POST 请求处理

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    request.setCharacterEncoding("UTF-8");
    String username = request.getParameter("username");
    String email = request.getParameter("email");

    response.setContentType("text/html;charset=UTF-8");
    PrintWriter out = response.getWriter();
    out.println("<h2>欢迎 " + username + "</h2>");
    out.println("<p>您的邮箱为:" + email + "</p>");
}

逻辑分析:

  • setCharacterEncoding("UTF-8") :设置请求体的字符编码,防止中文乱码。
  • getParameter() :获取 POST 请求体中的参数。
  • 输出响应内容,展示用户提交的信息。

2.3 Servlet在图书管理系统中的实际应用

2.3.1 用户登录请求的处理逻辑

在图书管理系统中,用户登录是一个典型的业务场景。我们可以通过 Servlet 实现用户的登录验证功能。

代码示例:登录请求处理

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    request.setCharacterEncoding("UTF-8");
    String username = request.getParameter("username");
    String password = request.getParameter("password");

    // 模拟数据库验证
    if ("admin".equals(username) && "123456".equals(password)) {
        HttpSession session = request.getSession();
        session.setAttribute("user", username);
        response.sendRedirect("dashboard.jsp");
    } else {
        response.sendRedirect("login.jsp?error=1");
    }
}

逻辑分析:

  • 首先获取用户输入的用户名和密码。
  • 进行简单验证(实际应调用数据库)。
  • 如果验证成功,将用户名存入 Session,并跳转到主页;否则跳回登录页并携带错误参数。

2.3.2 图书查询请求的封装与响应

图书查询是另一个常见功能。我们可以通过 Servlet 接收查询条件,调用数据库接口,返回查询结果。

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    request.setCharacterEncoding("UTF-8");
    String keyword = request.getParameter("keyword");

    // 模拟数据库查询
    List<Book> books = BookDAO.search(keyword);

    response.setContentType("application/json;charset=UTF-8");
    PrintWriter out = response.getWriter();
    out.print(toJSON(books));
    out.flush();
}

参数说明:

  • getParameter("keyword") :获取用户输入的搜索关键词。
  • BookDAO.search(keyword) :模拟数据库查询方法,返回图书列表。
  • 设置响应类型为 JSON,并输出 JSON 格式的图书数据。

辅助方法:将 List 转换为 JSON 字符串

private String toJSON(List<Book> books) {
    StringBuilder sb = new StringBuilder("[");
    for (int i = 0; i < books.size(); i++) {
        Book book = books.get(i);
        sb.append("{");
        sb.append("\"id\":").append(book.getId()).append(",");
        sb.append("\"title\":\"").append(book.getTitle()).append("\",");
        sb.append("\"author\":\"").append(book.getAuthor()).append("\"");
        sb.append("}");
        if (i < books.size() - 1) sb.append(",");
    }
    sb.append("]");
    return sb.toString();
}

逻辑分析:

  • 使用 StringBuilder 构建 JSON 字符串,提升性能。
  • 遍历图书列表,拼接 JSON 对象。
  • 最终输出 [{"id":1,"title":"Java编程思想","author":"Bruce Eckel"}] 格式的数据。

2.4 Servlet的线程安全与配置优化

2.4.1 多线程访问下的资源冲突问题

Servlet 是单例的,即 Web 容器只创建一个实例来处理所有请求。多个线程同时访问该实例时,如果存在共享变量,可能会引发线程安全问题。

示例:线程不安全的 Servlet

public class CounterServlet extends HttpServlet {
    private int count = 0;

    public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
        count++;
        response.getWriter().println("访问次数:" + count);
    }
}

问题分析:

  • count 是类的成员变量,被多个线程共享。
  • 多线程并发访问时, count++ 操作不是原子的,可能导致数据不一致。

解决方案:

  • 使用 synchronized 关键字控制访问:
synchronized void incrementCount() {
    count++;
}
  • 使用线程安全的类如 AtomicInteger
private AtomicInteger count = new AtomicInteger(0);

2.4.2 web.xml文件的配置实践

web.xml 是 Web 应用的部署描述文件,用于配置 Servlet、Filter、Listener 等组件。以下是一个典型的配置示例:

<web-app>
    <servlet>
        <servlet-name>HelloServlet</servlet-name>
        <servlet-class>com.example.HelloServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>HelloServlet</servlet-name>
        <url-pattern>/hello</url-pattern>
    </servlet-mapping>

    <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>
</web-app>

配置说明:

  • <servlet> :声明一个 Servlet,指定其类名。
  • <servlet-mapping> :将 URL 路径 /hello 映射到对应的 Servlet。
  • <welcome-file-list> :指定默认欢迎页面,当用户访问根路径时,服务器会自动加载 index.jsp。

此外,还可以配置过滤器、监听器、上下文参数等高级功能,提升应用的安全性和可维护性。

通过本章的学习,读者应掌握 Servlet 的基本概念、请求处理流程、在图书管理系统中的应用以及线程安全和配置优化技巧。这些内容为后续 JSP 页面开发、用户登录模块实现等打下坚实基础。

3. JSP动态页面生成技术

JSP(JavaServer Pages)作为Java Web开发中用于构建动态网页的核心技术之一,其本质是基于Servlet的封装,通过在HTML中嵌入Java代码,实现动态内容的输出。在图书管理系统中,JSP承担着页面展示、数据渲染和用户交互的重要职责。本章将深入剖析JSP的工作机制、语法结构以及在实际开发中的应用方式,帮助开发者掌握JSP在构建企业级Web应用中的核心能力。

3.1 JSP的基本原理与执行流程

3.1.1 JSP与Servlet的关系

JSP本质上是Servlet的一种简化写法,其最终会被编译成Servlet来执行。例如,一个简单的 index.jsp 文件:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head><title>图书管理系统首页</title></head>
<body>
    <h1>欢迎访问图书管理系统</h1>
</body>
</html>

该JSP页面在运行时会被Tomcat编译为一个继承 HttpJspBase 类的Java类,而 HttpJspBase 又继承自 HttpServlet 。因此,JSP本质上仍然是Servlet,只是其语法更贴近HTML编写,便于前端与后端逻辑的分离。

对比项 JSP Servlet
编写方式 HTML为主,嵌入Java代码 Java为主,手动输出HTML
编译过程 自动编译为Servlet 直接编译为字节码
职责 页面展示 请求处理与逻辑控制
调试难度 相对简单 需要处理HTML拼接,调试较复杂

3.1.2 JSP的编译与运行机制

JSP的执行流程可以分为以下几个阶段:

  1. 请求到达 :客户端浏览器发送HTTP请求访问JSP页面。
  2. JSP解析 :Web容器(如Tomcat)检查JSP是否为第一次请求,若是则进入编译流程。
  3. JSP编译 :将JSP转换为Servlet源码( .java 文件),再编译为 .class 文件。
  4. Servlet执行 :调用Servlet的 service() 方法生成响应内容。
  5. 响应返回 :将生成的HTML内容返回给客户端。

流程图如下:

graph TD
    A[客户端请求JSP页面] --> B{JSP是否已编译?}
    B -- 是 --> C[直接执行Servlet]
    B -- 否 --> D[解析JSP文件]
    D --> E[生成Servlet源码]
    E --> F[编译为.class文件]
    F --> G[执行Servlet生成HTML]
    G --> H[返回HTML响应]

这种机制使得JSP在首次访问时会稍慢,但后续访问则与Servlet无异,性能接近原生Servlet。

3.2 JSP核心语法与内置对象

3.2.1 常用JSP指令与动作标签

JSP提供了多种指令(Directive)和动作标签(Action Tag),用于控制页面行为和组件调用。

常用指令
  • page指令 :设置页面的属性,如编码、内容类型、引入类等。
<%@ page import="java.util.Date" %>
<%@ page contentType="text/html;charset=UTF-8" %>
  • include指令 :静态包含另一个JSP或HTML文件。
<%@ include file="header.jsp" %>
  • taglib指令 :引入自定义标签库(如JSTL)。
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
动作标签
  • <jsp:include> :动态包含页面内容。
<jsp:include page="footer.jsp" />
  • <jsp:forward> :将请求转发到另一个页面。
<jsp:forward page="error.jsp" />
  • <jsp:useBean> :创建或查找JavaBean对象。
<jsp:useBean id="book" class="com.example.Book" scope="request"/>

这些标签和指令的使用,使得JSP页面结构更清晰,便于模块化开发。

3.2.2 pageContext、session等对象的使用

JSP内置了多个隐式对象,无需声明即可直接使用。常见对象如下:

内置对象 类型 用途说明
request HttpServletRequest 获取客户端请求信息
response HttpServletResponse 设置响应内容
pageContext PageContext 页面上下文,可访问其他对象
session HttpSession 保存用户会话数据
application ServletContext 应用全局共享数据
out JspWriter 向客户端输出内容
config ServletConfig 获取Servlet配置信息
page Object 当前JSP对应的Servlet实例
exception Throwable 处理异常信息(需isErrorPage=true)

示例:使用session对象保存用户登录信息

<%
    String username = (String) session.getAttribute("username");
    if (username == null) {
        response.sendRedirect("login.jsp");
    } else {
%>
        <h2>欢迎,<%= username %>!</h2>
<%
    }
%>

上述代码通过 session.getAttribute() 获取登录用户名,若为空则跳转至登录页;否则输出欢迎信息。该逻辑在图书管理系统中常用于控制页面访问权限。

3.3 EL表达式与JSTL标签库

3.3.1 EL表达式的基本语法与应用场景

EL(Expression Language)是一种简化JSP中Java代码书写的表达式语言。其语法为 ${表达式} ,可直接访问JSP的隐式对象和JavaBean属性。

示例:EL访问request作用域数据
<jsp:useBean id="user" class="com.example.User" scope="request"/>
<%
    user.setName("张三");
%>
<p>用户名:${user.name}</p>

输出:

用户名:张三

EL表达式避免了传统JSP中频繁使用 <%= %> 输出变量的繁琐,提升代码可读性。

3.3.2 JSTL核心标签库的使用技巧

JSTL(JSP Standard Tag Library)是JSP标准标签库,包含核心标签、格式化标签、SQL标签等,其中最常用的是核心标签库。

常用核心标签:
  • <c:if> :条件判断
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<c:if test="${user.role == 'admin'}">
    <p>管理员权限</p>
</c:if>
  • <c:forEach> :循环遍历
<c:forEach items="${books}" var="book">
    <p>书名:${book.title},作者:${book.author}</p>
</c:forEach>
  • <c:choose> <c:when> <c:otherwise> :多条件判断
<c:choose>
    <c:when test="${user.role == 'admin'}">
        <p>管理员</p>
    </c:when>
    <c:otherwise>
        <p>普通用户</p>
    </c:otherwise>
</c:choose>
示例:图书列表展示

假设后端将图书列表 List<Book> 存入request域中:

<table border="1">
    <tr><th>书名</th><th>作者</th><th>价格</th></tr>
    <c:forEach items="${books}" var="book">
        <tr>
            <td>${book.title}</td>
            <td>${book.author}</td>
            <td>${book.price}</td>
        </tr>
    </c:forEach>
</table>

该表格将动态展示图书信息,适用于图书管理系统中的书籍查询页面。

3.4 图书管理系统中的JSP页面设计

3.4.1 首页与用户中心页面的布局实现

图书管理系统的首页通常包含导航栏、欢迎语、图书推荐等模块。采用JSP + CSS + JSTL的方式构建,可实现模块化与动态数据的结合。

示例:图书管理系统首页布局(index.jsp)
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html>
<head>
    <title>图书管理系统</title>
    <style>
        .nav { background: #333; color: white; padding: 10px; }
        .content { margin: 20px; }
    </style>
</head>
<body>
    <div class="nav">
        <a href="index.jsp">首页</a> |
        <a href="books.jsp">图书列表</a> |
        <a href="login.jsp">登录</a>
    </div>
    <div class="content">
        <h2>欢迎来到图书管理系统</h2>
        <p>当前登录用户:${sessionScope.username}</p>
    </div>
</body>
</html>

该页面使用了 <a> 标签实现导航,通过EL表达式显示当前登录用户,体现了JSP在页面结构与动态数据结合上的优势。

3.4.2 动态数据在JSP中的展示方式

在图书管理系统中,动态数据如图书信息、用户借阅记录等,通常通过Servlet从数据库中查询后传递到JSP页面展示。

示例:图书查询Servlet(BookServlet.java)
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    List<Book> books = bookService.findAll();
    request.setAttribute("books", books);
    request.getRequestDispatcher("/books.jsp").forward(request, response);
}
对应JSP页面(books.jsp)
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head><title>图书列表</title></head>
<body>
    <h2>图书列表</h2>
    <table border="1">
        <tr><th>书名</th><th>作者</th><th>价格</th></tr>
        <c:forEach items="${books}" var="book">
            <tr>
                <td>${book.title}</td>
                <td>${book.author}</td>
                <td>${book.price}</td>
            </tr>
        </c:forEach>
    </table>
</body>
</html>

上述流程展示了从数据库查询数据 → 通过Servlet传递 → JSP渲染输出的完整路径,是图书管理系统中常见的数据展示模式。

通过本章的深入讲解,读者应能够理解JSP在Java Web开发中的核心地位,掌握其语法结构、内置对象、EL表达式与JSTL的使用方法,并能在图书管理系统中灵活应用JSP构建动态页面。下一章将聚焦于用户注册与登录功能的实现,进一步提升系统的完整性和安全性。

4. 用户注册与登录功能实现

在现代Web应用中,用户注册与登录模块是系统安全性的核心环节。本章将围绕图书管理系统的用户注册与登录功能展开,从页面设计、后端验证、状态管理到安全增强,全面剖析其开发流程与实现细节。通过本章内容,开发者将掌握如何构建一个安全、稳定、可扩展的用户认证模块。

4.1 用户注册模块的设计与实现

用户注册是用户与系统建立联系的第一步。设计良好的注册模块不仅能提升用户体验,也为后续的安全控制打下基础。

4.1.1 注册页面的前端表单设计

一个标准的注册页面通常包含用户名、密码、确认密码、邮箱、手机号等字段。使用HTML和CSS可以快速构建一个结构清晰、样式美观的表单。

<form action="/register" method="post">
    <label>用户名:<input type="text" name="username" required /></label><br/>
    <label>密码:<input type="password" name="password" required /></label><br/>
    <label>确认密码:<input type="password" name="confirmPassword" required /></label><br/>
    <label>邮箱:<input type="email" name="email" required /></label><br/>
    <label>手机号:<input type="text" name="phone" required /></label><br/>
    <button type="submit">注册</button>
</form>

代码分析:

  • action="/register" :指定表单提交的URL路径,由后端Servlet处理。
  • method="post" :使用POST方法提交数据,避免敏感信息暴露在URL中。
  • required :HTML5属性,确保字段不能为空。
  • type="email" :浏览器会自动校验邮箱格式。

页面设计建议:
- 使用CSS美化表单,如添加边框、阴影、过渡动画等。
- 增加前端JavaScript校验,提升用户体验,如实时检测密码一致性。

4.1.2 后端数据验证与数据库插入操作

注册请求由Servlet接收后,需进行数据验证、密码加密、数据库插入等操作。

1. 数据验证逻辑
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    String username = request.getParameter("username");
    String password = request.getParameter("password");
    String confirmPassword = request.getParameter("confirmPassword");
    String email = request.getParameter("email");
    String phone = request.getParameter("phone");

    // 简单字段校验
    if (username == null || password == null || email == null || phone == null || 
        !password.equals(confirmPassword)) {
        response.sendRedirect("register.jsp?error=1");
        return;
    }

    // 密码加密
    String hashedPassword = hashPassword(password);

    // 插入数据库
    try (Connection conn = DBUtil.getConnection();
         PreparedStatement ps = conn.prepareStatement("INSERT INTO users(username,password,email,phone) VALUES(?,?,?,?)")) {
        ps.setString(1, username);
        ps.setString(2, hashedPassword);
        ps.setString(3, email);
        ps.setString(4, phone);
        ps.executeUpdate();
        response.sendRedirect("login.jsp");
    } catch (SQLException e) {
        e.printStackTrace();
        response.sendRedirect("register.jsp?error=2");
    }
}

private String hashPassword(String password) {
    // 使用BCrypt加密
    return BCrypt.hashpw(password, BCrypt.gensalt());
}

代码分析:

  • getParameter() :获取客户端传来的参数。
  • BCrypt :使用BCrypt加密算法对密码进行哈希处理,增强安全性。
  • 数据库插入使用 PreparedStatement 防止SQL注入。
  • 出现错误时,重定向至注册页并传递错误码,前端可据此显示提示信息。
2. 数据库设计(MySQL示例)
CREATE TABLE users (
    id INT PRIMARY KEY AUTO_INCREMENT,
    username VARCHAR(50) NOT NULL UNIQUE,
    password VARCHAR(255) NOT NULL,
    email VARCHAR(100),
    phone VARCHAR(20),
    create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

字段说明:

  • username :唯一用户名。
  • password :存储哈希值,不可逆。
  • create_time :用户注册时间。
3. 注册流程流程图(Mermaid)
graph TD
    A[用户访问注册页面] --> B[填写注册表单]
    B --> C[点击注册按钮]
    C --> D[Servlet接收POST请求]
    D --> E{数据验证是否通过}
    E -->|是| F[加密密码并插入数据库]
    F --> G[跳转登录页面]
    E -->|否| H[返回注册页并提示错误]

流程说明:

  • 用户填写信息后提交,Servlet进行验证。
  • 验证通过则插入数据库,否则返回错误提示。

4.2 用户登录模块的开发流程

登录功能是系统安全的核心,需要确保用户身份验证的准确性和会话管理的安全性。

4.2.1 登录页面的构建与提交处理

登录页面一般包含用户名和密码两个字段。

<form action="/login" method="post">
    <label>用户名:<input type="text" name="username" required /></label><br/>
    <label>密码:<input type="password" name="password" required /></label><br/>
    <button type="submit">登录</button>
</form>

优化建议:
- 增加“记住我”功能,使用Cookie实现。
- 增加验证码机制,防止暴力破解。

4.2.2 Session机制在登录状态管理中的应用

登录验证通过后,应使用Session机制保存用户信息。

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    String username = request.getParameter("username");
    String password = request.getParameter("password");

    try (Connection conn = DBUtil.getConnection();
         PreparedStatement ps = conn.prepareStatement("SELECT * FROM users WHERE username=?")) {
        ps.setString(1, username);
        ResultSet rs = ps.executeQuery();

        if (rs.next()) {
            String storedPassword = rs.getString("password");
            if (BCrypt.checkpw(password, storedPassword)) {
                // 创建Session
                HttpSession session = request.getSession();
                session.setAttribute("user", rs.getString("username"));
                session.setAttribute("role", rs.getString("role")); // 假设用户角色字段
                response.sendRedirect("index.jsp");
                return;
            }
        }
        response.sendRedirect("login.jsp?error=1");
    } catch (SQLException e) {
        e.printStackTrace();
        response.sendRedirect("login.jsp?error=2");
    }
}

代码分析:

  • 使用 BCrypt.checkpw() 验证用户输入密码是否匹配数据库哈希值。
  • 登录成功后,将用户名和角色信息存入Session中。
  • Session信息可用于后续页面的身份验证。
Session生命周期管理:
属性 说明
session.setMaxInactiveInterval(30*60) 设置Session过期时间为30分钟
session.invalidate() 手动销毁Session,实现登出功能

登录流程流程图(Mermaid)

graph TD
    A[用户访问登录页面] --> B[填写用户名和密码]
    B --> C[点击登录按钮]
    C --> D[Servlet接收POST请求]
    D --> E{数据库验证用户是否存在}
    E -->|否| F[返回登录页提示错误]
    E -->|是| G{密码是否匹配}
    G -->|否| H[返回登录页提示错误]
    G -->|是| I[创建Session并保存用户信息]
    I --> J[跳转首页]

流程说明:

  • 登录验证包括用户存在性检查和密码正确性检查。
  • 成功登录后,用户信息存储于Session中供后续使用。

4.3 用户权限控制与访问拦截

为了实现不同用户访问权限的控制,需要使用Filter进行请求拦截。

4.3.1 Filter过滤器的使用方法

Filter可以在请求到达Servlet之前进行拦截处理,常用于权限控制。

public class AuthFilter implements Filter {
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse res = (HttpServletResponse) response;

        HttpSession session = req.getSession(false);
        String path = req.getServletPath();

        if (session == null || session.getAttribute("user") == null) {
            if (path.contains("admin")) {
                res.sendRedirect("login.jsp");
                return;
            }
        }

        chain.doFilter(request, response);
    }
}

配置web.xml:

<filter>
    <filter-name>AuthFilter</filter-name>
    <filter-class>com.example.filter.AuthFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>AuthFilter</filter-name>
    <url-pattern>/admin/*</url-pattern>
</filter-mapping>

代码分析:

  • doFilter() :处理请求前的逻辑。
  • chain.doFilter() :继续执行后续过滤器或Servlet。
  • 若用户未登录且访问的是管理员路径(/admin/*),则重定向至登录页。

4.3.2 角色权限的判断与页面访问限制

可以在Filter中加入角色判断逻辑,限制不同角色访问不同页面。

if (path.contains("admin")) {
    String role = (String) session.getAttribute("role");
    if (!"admin".equals(role)) {
        res.sendRedirect("no_permission.jsp");
        return;
    }
}

权限控制表格:

角色 可访问路径 权限说明
普通用户 /user/* 只能访问用户中心
管理员 /admin/* 可访问后台管理页面

4.4 安全性增强与密码加密处理

用户认证系统必须具备足够的安全性,防止常见的攻击方式如SQL注入、暴力破解等。

4.4.1 MD5与SHA算法在密码存储中的应用

虽然MD5和SHA-256是常见的哈希算法,但它们并不适合直接用于密码存储。建议使用更安全的算法如BCrypt或PBKDF2。

// BCrypt加密示例
String hashed = BCrypt.hashpw("123456", BCrypt.gensalt());
// 验证密码
boolean valid = BCrypt.checkpw("123456", hashed);

对比表格:

算法 是否推荐 说明
MD5 已被破解,不安全
SHA-256 ⚠️ 可用于数据完整性校验,但不适合密码存储
BCrypt 推荐使用,自带盐值和慢哈希机制

4.4.2 防止SQL注入的安全编码实践

SQL注入是Web应用中最常见的安全漏洞之一。通过使用 PreparedStatement 可以有效防止注入攻击。

String sql = "SELECT * FROM users WHERE username = ?";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setString(1, username); // 参数绑定

安全建议:

  • 永远不要拼接SQL字符串。
  • 对用户输入进行过滤和校验,如使用正则表达式。
  • 启用Web应用防火墙(WAF)进一步防护。

本章小结:

  • 注册模块应注重表单设计与数据加密。
  • 登录模块依赖Session机制实现状态管理。
  • Filter过滤器可用于实现权限控制。
  • 安全性方面应使用BCrypt加密和PreparedStatement防注入。

本章内容为后续章节中用户行为控制、管理员权限划分等模块提供了坚实的基础。

5. 图书分页查询功能设计

图书管理系统中,随着图书数据的不断增长,用户在浏览图书信息时不可能一次性加载全部内容。因此,分页查询成为系统设计中不可或缺的一部分。本章将深入讲解图书分页查询功能的设计与实现,涵盖数据库层面的SQL实现、Java代码中的封装逻辑以及前端页面中的展示方式,帮助开发者构建高效、可扩展的分页机制。

5.1 分页查询的基本原理与SQL实现

分页查询的核心在于通过数据库语句限制返回结果的数量和起始位置,从而实现对数据的“分块”展示。MySQL 中主要通过 LIMIT OFFSET 子句来实现这一功能。

5.1.1 LIMIT与OFFSET的使用方法

MySQL 提供了 LIMIT OFFSET 两个关键字用于实现分页:

  • LIMIT n :表示最多返回 n 条记录。
  • OFFSET m :表示跳过前 m 条记录。

基本语法如下:

SELECT * FROM books
ORDER BY id DESC
LIMIT 10 OFFSET 20;

上述语句表示:从 books 表中按 id 降序排列,跳过前 20 条记录,返回接下来的 10 条记录。

参数说明
- LIMIT 10 :每页显示10条数据。
- OFFSET 20 :跳过前20条数据,表示查询第3页(每页10条)。

分页公式

给定每页条数 pageSize 和当前页码 pageNum ,则:

int offset = (pageNum - 1) * pageSize;

例如,每页10条数据,查询第3页时, offset = (3-1)*10 = 20

性能考量

虽然 LIMIT OFFSET 简单易用,但在大数据量下频繁使用 OFFSET 可能导致性能下降,因为数据库需要扫描前面的所有记录。在高并发或数据量巨大的系统中,建议结合索引优化或使用基于游标的分页方式(如记录上一页最后一条数据的ID)。

5.1.2 分页参数的传递与处理

在 JavaWeb 应用中,分页参数通常由前端传递,如通过 URL 参数:

http://localhost:8080/books?page=2&pageSize=10

Servlet 中通过 request.getParameter() 获取参数,并转换为整数:

String pageStr = request.getParameter("page");
String pageSizeStr = request.getParameter("pageSize");

int page = pageStr == null ? 1 : Integer.parseInt(pageStr);
int pageSize = pageSizeStr == null ? 10 : Integer.parseInt(pageSizeStr);

int offset = (page - 1) * pageSize;

随后,将 pageSize offset 作为参数传入 DAO 层进行数据库查询。

逻辑分析
- 若未传入参数,默认当前页为第一页,每页10条。
- 计算出 offset 后,构建 SQL 查询语句执行分页查询。

SQL 构建示例
String sql = "SELECT * FROM books ORDER BY id DESC LIMIT ? OFFSET ?";
PreparedStatement stmt = connection.prepareStatement(sql);
stmt.setInt(1, pageSize);
stmt.setInt(2, offset);
ResultSet rs = stmt.executeQuery();

参数说明
- ? 是占位符,分别代表 LIMIT OFFSET 的值。
- 使用 PreparedStatement 防止 SQL 注入。

分页查询流程图
graph TD
    A[前端发起分页请求] --> B{参数是否完整?}
    B -->|是| C[获取page和pageSize]
    B -->|否| D[设置默认值]
    C --> E[计算offset]
    E --> F[构建SQL语句]
    F --> G[执行查询]
    G --> H[返回当前页数据]

5.2 Java代码中分页逻辑的封装

为了提高代码的复用性和可维护性,应将分页逻辑封装成一个独立的类或接口。

5.2.1 Page类的设计与封装

定义一个 Page<T> 泛型类,用于封装分页数据:

public class Page<T> {
    private int pageNum;            // 当前页码
    private int pageSize;           // 每页记录数
    private int total;              // 总记录数
    private List<T> list;           // 当前页数据列表

    public Page(int pageNum, int pageSize) {
        this.pageNum = pageNum;
        this.pageSize = pageSize;
    }

    // 计算总页数
    public int getTotalPages() {
        return (int) Math.ceil((double) total / pageSize);
    }

    // Getter & Setter
}

逻辑分析
- pageNum pageSize 由前端传入。
- total 通过数据库查询获得总记录数。
- list 是当前页的数据集合。
- getTotalPages() 方法用于计算总页数,用于前端展示导航。

数据库查询中使用 Page

在 DAO 层中,先查询总记录数,再查询当前页数据:

public Page<Book> findBooksByPage(int pageNum, int pageSize) throws SQLException {
    Connection conn = null;
    PreparedStatement stmt = null;
    ResultSet rs = null;
    Page<Book> page = new Page<>(pageNum, pageSize);

    try {
        conn = DBUtil.getConnection();
        // 查询总记录数
        String countSql = "SELECT COUNT(*) FROM books";
        stmt = conn.prepareStatement(countSql);
        rs = stmt.executeQuery();
        if (rs.next()) {
            page.setTotal(rs.getInt(1));
        }
        rs.close();
        stmt.close();

        // 查询当前页数据
        String sql = "SELECT * FROM books ORDER BY id DESC LIMIT ? OFFSET ?";
        stmt = conn.prepareStatement(sql);
        stmt.setInt(1, pageSize);
        stmt.setInt(2, (pageNum - 1) * pageSize);
        rs = stmt.executeQuery();

        List<Book> bookList = new ArrayList<>();
        while (rs.next()) {
            Book book = new Book();
            book.setId(rs.getInt("id"));
            book.setTitle(rs.getString("title"));
            book.setAuthor(rs.getString("author"));
            bookList.add(book);
        }
        page.setList(bookList);

    } finally {
        DBUtil.close(conn, stmt, rs);
    }

    return page;
}

参数说明
- pageNum :当前页码。
- pageSize :每页记录数。
- bookList :封装后的当前页图书列表。
- page :最终封装好的分页对象,包含所有信息。

5.2.2 分页查询接口的调用与结果返回

在 Servlet 中调用 DAO 方法,并将结果返回给前端页面:

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    String pageStr = request.getParameter("page");
    String pageSizeStr = request.getParameter("pageSize");

    int pageNum = pageStr == null ? 1 : Integer.parseInt(pageStr);
    int pageSize = pageSizeStr == null ? 10 : Integer.parseInt(pageSizeStr);

    BookDAO bookDAO = new BookDAO();
    Page<Book> page = bookDAO.findBooksByPage(pageNum, pageSize);

    request.setAttribute("page", page);
    request.getRequestDispatcher("/books.jsp").forward(request, response);
}

逻辑分析
- 接收前端分页参数并转换。
- 调用 DAO 获取分页对象。
- 将 page 对象存入 request 域,供 JSP 页面使用。

分页查询封装流程图
graph TD
    A[Servlet接收分页参数] --> B[调用DAO查询方法]
    B --> C[DAO查询总记录数]
    C --> D[DAO查询当前页数据]
    D --> E[封装为Page对象]
    E --> F[返回Page对象]
    F --> G[Servlet设置request属性]
    G --> H[转发至JSP页面]

5.3 前端页面中的分页展示

分页功能的最终目的是在前端页面上展示分页按钮,并允许用户点击跳转。本节将介绍如何在 JSP 页面中动态生成分页按钮,并实现页码高亮显示。

5.3.1 分页按钮的动态生成

在 JSP 页面中,从 page 对象中获取总页数、当前页码等信息,并使用 JSTL 和 EL 表达式动态生成分页按钮:

<c:forEach begin="1" end="${page.totalPages}" var="i">
    <c:if test="${i == page.pageNum}">
        <span class="current">${i}</span>
    </c:if>
    <c:if test="${i != page.pageNum}">
        <a href="books?page=${i}&pageSize=${page.pageSize}">${i}</a>
    </c:if>
</c:forEach>

逻辑分析
- 使用 <c:forEach> 循环生成页码链接。
- 当前页码使用 <span> 显示,非当前页使用 <a> 链接跳转。
- 通过 books?page=${i}&pageSize=${page.pageSize} 实现分页跳转。

分页按钮样式示例(CSS)
.pagination {
    margin: 20px 0;
}
.pagination a, .pagination span {
    display: inline-block;
    padding: 5px 10px;
    margin: 0 2px;
    text-decoration: none;
    color: #007bff;
    border: 1px solid #ddd;
    border-radius: 4px;
}
.pagination span.current {
    background-color: #007bff;
    color: white;
    border-color: #007bff;
}

5.3.2 当前页码的高亮显示

在前端实现中,高亮当前页码是提升用户体验的重要手段。通过判断当前页码与循环变量是否一致,使用不同的样式:

<c:forEach begin="1" end="${page.totalPages}" var="i">
    <c:choose>
        <c:when test="${i == page.pageNum}">
            <span class="current">${i}</span>
        </c:when>
        <c:otherwise>
            <a href="books?page=${i}&pageSize=${page.pageSize}">${i}</a>
        </c:otherwise>
    </c:choose>
</c:forEach>

逻辑分析
- <c:choose> 判断当前页码是否为循环变量 i
- 若是,则使用 <span> 高亮显示。
- 若否,则使用 <a> 标签生成跳转链接。

分页导航结构示意图(表格)
元素 描述
<c:forEach> 循环生成页码
<c:when> 判断当前页码
<c:otherwise> 非当前页码处理
class="current" 高亮样式类名
href="books?page=..." 分页跳转链接

小结

通过本章内容,我们详细讲解了图书分页查询功能的设计与实现。从数据库层面的 LIMIT OFFSET 使用,到 Java 层面的 Page 类封装,再到前端 JSP 页面的动态分页按钮展示,形成了一个完整的分页功能闭环。在后续章节中,我们将继续深入图书管理系统的其他核心功能,如借阅记录管理、权限控制等,帮助开发者构建完整的企业级图书管理系统。

6. 借书还书业务逻辑实现

在图书管理系统中,借书还书功能是核心业务之一。该模块不仅涉及图书状态的变更,还关系到用户借阅记录的维护,直接影响系统的数据完整性与用户体验。实现该功能需要结合前端页面设计、后端Servlet处理、数据库操作以及事务管理等多个层面。本章将深入探讨借书与还书功能的完整实现流程,包括业务逻辑设计、数据操作、状态更新机制等,并通过代码示例和流程图展示其内部执行机制。

6.1 借书功能的流程设计与实现

6.1.1 借书页面的构建与请求处理

借书功能的第一步是构建用户可操作的借书页面。该页面通常展示图书列表,用户选择目标图书后点击“借阅”按钮触发请求。

页面结构设计(HTML + JSTL)
<table class="table">
    <thead>
        <tr>
            <th>书名</th>
            <th>作者</th>
            <th>库存</th>
            <th>操作</th>
        </tr>
    </thead>
    <tbody>
        <c:forEach items="${books}" var="book">
            <tr>
                <td>${book.title}</td>
                <td>${book.author}</td>
                <td>${book.stock}</td>
                <td>
                    <c:if test="${book.stock > 0}">
                        <a href="borrowBook?id=${book.id}" class="btn btn-success">借阅</a>
                    </c:if>
                    <c:if test="${book.stock <= 0}">
                        <span class="text-muted">无库存</span>
                    </c:if>
                </td>
            </tr>
        </c:forEach>
    </tbody>
</table>
Servlet请求处理逻辑(Java)

当用户点击“借阅”按钮后,请求将被发送到 BorrowBookServlet ,该Servlet负责处理借书业务逻辑。

@WebServlet("/borrowBook")
public class BorrowBookServlet extends HttpServlet {
    private BookService bookService = new BookServiceImpl();
    private BorrowRecordService borrowRecordService = new BorrowRecordServiceImpl();

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        int bookId = Integer.parseInt(request.getParameter("id"));
        User user = (User) request.getSession().getAttribute("user");

        // 借书核心逻辑
        boolean success = bookService.borrowBook(bookId, user.getId());

        if (success) {
            response.sendRedirect("borrowSuccess.jsp");
        } else {
            response.sendRedirect("borrowFail.jsp");
        }
    }
}

代码逻辑分析:

  • bookId 从请求参数中获取图书ID。
  • 从Session中获取当前登录用户。
  • 调用 bookService.borrowBook() 方法进行借书操作。
  • 根据操作结果跳转至成功或失败页面。

6.1.2 图书库存更新与用户借阅记录保存

借书操作的核心在于更新图书库存并保存借阅记录到数据库中。

数据库表设计(MySQL)
CREATE TABLE borrow_record (
    id INT PRIMARY KEY AUTO_INCREMENT,
    user_id INT,
    book_id INT,
    borrow_time DATETIME,
    return_time DATETIME DEFAULT NULL,
    status ENUM('borrowed', 'returned') DEFAULT 'borrowed',
    FOREIGN KEY (user_id) REFERENCES user(id),
    FOREIGN KEY (book_id) REFERENCES book(id)
);
业务逻辑实现(Java)
public class BookServiceImpl implements BookService {
    private Connection connection;

    public BookServiceImpl() {
        this.connection = DBUtil.getConnection();
    }

    @Override
    public boolean borrowBook(int bookId, int userId) {
        try {
            connection.setAutoCommit(false); // 开启事务

            // 查询图书库存
            String checkStockSql = "SELECT stock FROM book WHERE id = ?";
            PreparedStatement ps = connection.prepareStatement(checkStockSql);
            ps.setInt(1, bookId);
            ResultSet rs = ps.executeQuery();
            if (!rs.next() || rs.getInt("stock") <= 0) {
                return false; // 库存不足
            }

            // 减少库存
            String updateStockSql = "UPDATE book SET stock = stock - 1 WHERE id = ?";
            ps = connection.prepareStatement(updateStockSql);
            ps.setInt(1, bookId);
            ps.executeUpdate();

            // 插入借阅记录
            String insertRecordSql = "INSERT INTO borrow_record(user_id, book_id, borrow_time) VALUES (?, ?, NOW())";
            ps = connection.prepareStatement(insertRecordSql);
            ps.setInt(1, userId);
            ps.setInt(2, bookId);
            ps.executeUpdate();

            connection.commit(); // 提交事务
            return true;
        } catch (SQLException e) {
            try {
                connection.rollback(); // 回滚事务
            } catch (SQLException ex) {
                ex.printStackTrace();
            }
            e.printStackTrace();
            return false;
        }
    }
}

代码逐行分析:

  • connection.setAutoCommit(false) :开启事务处理,确保库存减少与借阅记录插入操作的原子性。
  • checkStockSql :查询当前图书的库存数量。
  • 若库存为0,返回 false 表示无法借阅。
  • updateStockSql :将图书库存减1。
  • insertRecordSql :向借阅记录表中插入一条记录,记录借阅时间。
  • 使用 commit() 提交事务,若发生异常则通过 rollback() 回滚。
借书流程图(Mermaid)
graph TD
    A[用户点击借阅按钮] --> B{图书库存是否大于0?}
    B -->|是| C[减少图书库存]
    B -->|否| D[提示库存不足]
    C --> E[插入借阅记录]
    E --> F[事务提交]
    F --> G[跳转借阅成功页面]
    D --> H[跳转借阅失败页面]

6.2 还书功能的开发与状态更新

6.2.1 还书请求的接收与业务处理

用户还书操作通常在“我的借阅”页面中发起,系统需要接收请求并更新图书状态与借阅记录。

请求处理Servlet(Java)
@WebServlet("/returnBook")
public class ReturnBookServlet extends HttpServlet {
    private BorrowRecordService borrowRecordService = new BorrowRecordServiceImpl();

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        int recordId = Integer.parseInt(request.getParameter("recordId"));

        boolean success = borrowRecordService.returnBook(recordId);

        if (success) {
            response.sendRedirect("returnSuccess.jsp");
        } else {
            response.sendRedirect("returnFail.jsp");
        }
    }
}
参数说明:
  • recordId :借阅记录的唯一标识,用于定位具体借阅条目。

6.2.2 图书状态与用户借阅记录的更新逻辑

还书逻辑包括:

  1. 更新图书库存。
  2. 更新借阅记录中的 return_time status 字段。
数据库操作代码(Java)
public class BorrowRecordServiceImpl implements BorrowRecordService {
    private Connection connection;

    public BorrowRecordServiceImpl() {
        this.connection = DBUtil.getConnection();
    }

    @Override
    public boolean returnBook(int recordId) {
        try {
            connection.setAutoCommit(false);

            // 获取借阅记录对应的图书ID
            String getBookIdSql = "SELECT book_id FROM borrow_record WHERE id = ?";
            PreparedStatement ps = connection.prepareStatement(getBookIdSql);
            ps.setInt(1, recordId);
            ResultSet rs = ps.executeQuery();
            if (!rs.next()) {
                return false; // 记录不存在
            }
            int bookId = rs.getInt("book_id");

            // 更新图书库存
            String updateStockSql = "UPDATE book SET stock = stock + 1 WHERE id = ?";
            ps = connection.prepareStatement(updateStockSql);
            ps.setInt(1, bookId);
            ps.executeUpdate();

            // 更新借阅记录状态与归还时间
            String updateRecordSql = "UPDATE borrow_record SET return_time = NOW(), status = 'returned' WHERE id = ?";
            ps = connection.prepareStatement(updateRecordSql);
            ps.setInt(1, recordId);
            ps.executeUpdate();

            connection.commit();
            return true;
        } catch (SQLException e) {
            try {
                connection.rollback();
            } catch (SQLException ex) {
                ex.printStackTrace();
            }
            e.printStackTrace();
            return false;
        }
    }
}

逻辑分析:

  • 使用事务控制,确保还书过程中的两个操作(库存+1与记录更新)要么都成功,要么都失败。
  • 先查询借阅记录,获取图书ID。
  • 更新图书库存。
  • 更新借阅记录状态为“已归还”,并记录归还时间。

6.3 借阅记录的查询与展示

6.3.1 查询用户历史借阅信息

用户可在“我的借阅”页面中查看所有历史借阅记录。

查询接口实现(Java)
public List<BorrowRecord> getBorrowRecordsByUserId(int userId) {
    List<BorrowRecord> records = new ArrayList<>();
    String sql = "SELECT * FROM borrow_record WHERE user_id = ? ORDER BY borrow_time DESC";

    try (PreparedStatement ps = connection.prepareStatement(sql)) {
        ps.setInt(1, userId);
        ResultSet rs = ps.executeQuery();

        while (rs.next()) {
            BorrowRecord record = new BorrowRecord();
            record.setId(rs.getInt("id"));
            record.setBookId(rs.getInt("book_id"));
            record.setBorrowTime(rs.getTimestamp("borrow_time"));
            record.setReturnTime(rs.getObject("return_time", LocalDateTime.class));
            record.setStatus(rs.getString("status"));
            records.add(record);
        }
    } catch (SQLException e) {
        e.printStackTrace();
    }

    return records;
}

参数说明:

  • userId :用于查询指定用户的借阅记录。
  • 返回 List<BorrowRecord> :包含所有借阅记录的列表。
JSP页面展示借阅记录
<table class="table">
    <thead>
        <tr>
            <th>图书名称</th>
            <th>借阅时间</th>
            <th>归还时间</th>
            <th>状态</th>
        </tr>
    </thead>
    <tbody>
        <c:forEach items="${borrowRecords}" var="record">
            <tr>
                <td>${record.title}</td>
                <td>${record.borrowTime}</td>
                <td>${record.returnTime != null ? record.returnTime : '未归还'}</td>
                <td>${record.status}</td>
            </tr>
        </c:forEach>
    </tbody>
</table>

6.3.2 借阅状态的分类展示

为了提升用户体验,可以将借阅状态分类展示,如“借阅中”与“已归还”。

状态分类逻辑(Java)
public List<BorrowRecord> getBorrowingBooks(int userId) {
    String sql = "SELECT * FROM borrow_record WHERE user_id = ? AND status = 'borrowed'";
    return executeQuery(userId, sql);
}

public List<BorrowRecord> getReturnedBooks(int userId) {
    String sql = "SELECT * FROM borrow_record WHERE user_id = ? AND status = 'returned'";
    return executeQuery(userId, sql);
}
状态分类展示页面(JSP)
<h3>借阅中</h3>
<c:if test="${not empty borrowingRecords}">
    <table class="table">
        <!-- 表格内容 -->
    </table>
</c:if>

<h3>已归还</h3>
<c:if test="${not empty returnedRecords}">
    <table class="table">
        <!-- 表格内容 -->
    </table>
</c:if>

本章小结

第六章系统地介绍了借书还书功能的实现流程,从页面构建、Servlet处理、事务控制到借阅记录的展示与分类。通过结合数据库操作与Java业务逻辑,实现了完整的借阅闭环管理。该章节不仅展示了实际开发中如何组织代码结构,还通过流程图和代码分析帮助读者深入理解事务处理与状态变更机制,适用于中高级JavaWeb开发者进行业务模块的开发与优化。

7. 管理员后台管理功能开发

管理员后台是图书管理系统的重要组成部分,负责对图书、用户以及借阅信息的集中管理。本章将重点介绍管理员登录与权限验证机制、图书信息的增删改查功能以及用户管理与借阅审核的实现方式,帮助开发者掌握构建安全高效的后台管理系统的开发流程。

7.1 管理员登录与权限验证

管理员登录是进入后台管理系统的首要步骤,必须确保其安全性与权限控制的准确性。

7.1.1 管理员登录页面的开发

管理员登录页面应包含用户名和密码输入框,以及提交按钮。前端页面使用JSP编写,示例如下:

<%@ page contentType="text/html;charset=UTF-8" %>
<html>
<head>
    <title>管理员登录</title>
</head>
<body>
    <h2>管理员登录</h2>
    <form action="adminLogin" method="post">
        用户名:<input type="text" name="username"><br><br>
        密码: <input type="password" name="password"><br><br>
        <input type="submit" value="登录">
    </form>
</body>
</html>

7.1.2 管理员身份的权限控制

Servlet 中处理登录请求,并通过数据库验证用户名和密码是否匹配管理员账户。验证成功后,将管理员身份信息存入 session ,用于后续权限判断。

@WebServlet("/adminLogin")
public class AdminLoginServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String username = request.getParameter("username");
        String password = request.getParameter("password");

        // 假设使用数据库验证管理员信息
        if ("admin".equals(username) && "123456".equals(password)) {
            HttpSession session = request.getSession();
            session.setAttribute("admin", username);
            response.sendRedirect("adminHome.jsp");
        } else {
            response.sendRedirect("adminLogin.jsp?error=1");
        }
    }
}

权限验证可通过 Filter 实现,确保未登录管理员无法访问后台页面:

public class AdminAuthFilter implements Filter {
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse res = (HttpServletResponse) response;
        HttpSession session = req.getSession(false);

        if (session == null || session.getAttribute("admin") == null) {
            res.sendRedirect("adminLogin.jsp");
        } else {
            chain.doFilter(request, response);
        }
    }
}

7.2 图书信息的增删改查操作

图书信息的增删改查(CRUD)是管理员后台的核心功能,涉及前端页面、Servlet处理和数据库交互。

7.2.1 图书信息的添加与编辑页面

添加图书页面使用表单收集信息,如书名、作者、出版社、ISBN等:

<form action="addBook" method="post">
    书名:<input type="text" name="title"><br>
    作者:<input type="text" name="author"><br>
    出版社:<input type="text" name="publisher"><br>
    ISBN:<input type="text" name="isbn"><br>
    <input type="submit" value="添加">
</form>

编辑图书页面则需先根据图书ID查询数据并填充表单:

Book book = bookDAO.getBookById(bookId);
request.setAttribute("book", book);
RequestDispatcher dispatcher = request.getRequestDispatcher("editBook.jsp");
dispatcher.forward(request, response);

7.2.2 图书信息的删除与状态更新

删除图书可通过 DELETE 请求或 POST 提交图书ID实现:

@WebServlet("/deleteBook")
public class DeleteBookServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String bookId = request.getParameter("bookId");
        bookDAO.deleteBook(bookId);
        response.sendRedirect("bookList.jsp");
    }
}

图书状态更新(如是否可借阅)可通过更新数据库字段实现:

UPDATE books SET status = '不可借' WHERE id = ?

7.3 用户管理与借阅审核功能

用户管理和借阅审核功能帮助管理员控制用户行为和审核借阅请求,提升系统管理能力。

7.3.1 用户信息的查询与管理

管理员可通过表格形式展示所有用户信息,并提供删除或禁用功能:

用户ID 用户名 注册时间 状态
1001 user1 2024-03-01 正常
1002 user2 2024-04-15 禁用

后端通过数据库查询获取用户列表并传入JSP页面展示:

List<User> userList = userDAO.getAllUsers();
request.setAttribute("users", userList);
RequestDispatcher dispatcher = request.getRequestDispatcher("userList.jsp");
dispatcher.forward(request, response);

7.3.2 借阅申请的审核与处理

借阅审核页面展示待审核的借阅记录,管理员可点击“通过”或“拒绝”按钮进行处理:

<table>
    <tr>
        <th>借阅ID</th>
        <th>用户ID</th>
        <th>图书ID</th>
        <th>申请时间</th>
        <th>操作</th>
    </tr>
    <c:forEach items="${pendingLoans}" var="loan">
        <tr>
            <td>${loan.id}</td>
            <td>${loan.userId}</td>
            <td>${loan.bookId}</td>
            <td>${loan.applyTime}</td>
            <td>
                <a href="approveLoan?id=${loan.id}">通过</a> |
                <a href="rejectLoan?id=${loan.id}">拒绝</a>
            </td>
        </tr>
    </c:forEach>
</table>

审核通过后更新借阅状态并减少图书库存:

public void approveLoan(int loanId) {
    String sql = "UPDATE loans SET status = '已批准' WHERE id = ?";
    jdbcTemplate.update(sql, loanId);

    // 同步更新图书库存
    updateBookStock(loanId, false);
}

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:JavaWeb图书管理系统是一个基于Servlet和JSP构建的Web应用,提供用户注册、登录、图书分页查询、借阅归还等功能,并支持管理员对书籍和用户信息进行管理。系统采用JavaWeb核心技术栈,结合数据库操作,帮助初学者深入理解Web开发流程。本项目结构清晰,涵盖前后端交互、数据库连接与业务逻辑处理,是学习JavaWeb开发的实用实战案例。


本文还有配套的精品资源,点击获取
menu-r.4af5f7ec.gif

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值