基于J2EE的图书管理系统

图书管理系统

项目创建

环境

  • maven 3.8.8
  • javaEE 8
  • Tomcat 10.1.13
  • jdk 17
  • Thymeleaf 3.0.12.RELEASE
  • lombook
  • mybatis
  • bootstrapv3
  • IDEA 2023.2
  • mysql 8.0.31
  • DataGrip 2023.2.1

项目介绍

该项目是基于 J2EE 框架开发的图书管理系统,使用 MyBatis 框架与数据库进行交互,前端使用了 Bootstrap 框架设计页面。实现了用户的登录退出,以及对图书的增删改查和借阅功能。

创建项目

image-20231025154127605

image-20231025154216610

创建完成后,要将pom.xml文件中

<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>

更改为

<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>5.0.0</version>
<scope>provided</scope>
</dependency>

原因是JavaEE框架改名了,具体时间不知道,总之之后的所有框架和包引用的名称都从javax变成了jakarta。

在此部分pom.xml的完整内容如下

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
&lt;groupId&gt;com.book&lt;/groupId&gt;
&lt;artifactId&gt;bookManage&lt;/artifactId&gt;
&lt;version&gt;1.0-SNAPSHOT&lt;/version&gt;
&lt;name&gt;bookManage&lt;/name&gt;
&lt;packaging&gt;war&lt;/packaging&gt;
&lt;properties&gt;
&lt;project.build.sourceEncoding&gt;UTF-8&lt;/project.build.sourceEncoding&gt;
&lt;maven.compiler.target&gt;17&lt;/maven.compiler.target&gt;
&lt;maven.compiler.source&gt;17&lt;/maven.compiler.source&gt;
&lt;junit.version&gt;5.9.2&lt;/junit.version&gt;
&lt;/properties&gt;
&lt;dependencies&gt;
&lt;dependency&gt;
&lt;groupId&gt;jakarta.servlet&lt;/groupId&gt;
&lt;artifactId&gt;jakarta.servlet-api&lt;/artifactId&gt;
&lt;version&gt;5.0.0&lt;/version&gt;
&lt;scope&gt;provided&lt;/scope&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
&lt;groupId&gt;org.thymeleaf&lt;/groupId&gt;
&lt;artifactId&gt;thymeleaf&lt;/artifactId&gt;
&lt;version&gt;3.0.12.RELEASE&lt;/version&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
&lt;groupId&gt;org.junit.jupiter&lt;/groupId&gt;
&lt;artifactId&gt;junit-jupiter-api&lt;/artifactId&gt;
&lt;version&gt;${junit.version}&lt;/version&gt;
&lt;scope&gt;test&lt;/scope&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
&lt;groupId&gt;org.junit.jupiter&lt;/groupId&gt;
&lt;artifactId&gt;junit-jupiter-engine&lt;/artifactId&gt;
&lt;version&gt;${junit.version}&lt;/version&gt;
&lt;scope&gt;test&lt;/scope&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
&lt;groupId&gt;org.projectlombok&lt;/groupId&gt;
&lt;artifactId&gt;lombok&lt;/artifactId&gt;
&lt;version&gt;1.18.28&lt;/version&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
&lt;groupId&gt;org.mybatis&lt;/groupId&gt;
&lt;artifactId&gt;mybatis&lt;/artifactId&gt;
&lt;version&gt;3.5.13&lt;/version&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
&lt;groupId&gt;mysql&lt;/groupId&gt;
&lt;artifactId&gt;mysql-connector-java&lt;/artifactId&gt;
&lt;version&gt;8.0.28&lt;/version&gt;
&lt;/dependency&gt;
&lt;/dependencies&gt;
&lt;build&gt;
&lt;plugins&gt;
&lt;plugin&gt;
&lt;groupId&gt;org.apache.maven.plugins&lt;/groupId&gt;
&lt;artifactId&gt;maven-war-plugin&lt;/artifactId&gt;
&lt;version&gt;3.3.2&lt;/version&gt;
&lt;/plugin&gt;
&lt;/plugins&gt;
&lt;/build&gt;
&lt;groupId&gt;com.book&lt;/groupId&gt;
&lt;artifactId&gt;bookManage&lt;/artifactId&gt;
&lt;version&gt;1.0-SNAPSHOT&lt;/version&gt;
&lt;name&gt;bookManage&lt;/name&gt;
&lt;packaging&gt;war&lt;/packaging&gt;
&lt;properties&gt;
&lt;project.build.sourceEncoding&gt;UTF-8&lt;/project.build.sourceEncoding&gt;
&lt;maven.compiler.target&gt;17&lt;/maven.compiler.target&gt;
&lt;maven.compiler.source&gt;17&lt;/maven.compiler.source&gt;
&lt;junit.version&gt;5.9.2&lt;/junit.version&gt;
&lt;/properties&gt;
&lt;dependencies&gt;
&lt;dependency&gt;
&lt;groupId&gt;jakarta.servlet&lt;/groupId&gt;
&lt;artifactId&gt;jakarta.servlet-api&lt;/artifactId&gt;
&lt;version&gt;5.0.0&lt;/version&gt;
&lt;scope&gt;provided&lt;/scope&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
&lt;groupId&gt;org.thymeleaf&lt;/groupId&gt;
&lt;artifactId&gt;thymeleaf&lt;/artifactId&gt;
&lt;version&gt;3.0.12.RELEASE&lt;/version&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
&lt;groupId&gt;org.junit.jupiter&lt;/groupId&gt;
&lt;artifactId&gt;junit-jupiter-api&lt;/artifactId&gt;
&lt;version&gt;${junit.version}&lt;/version&gt;
&lt;scope&gt;test&lt;/scope&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
&lt;groupId&gt;org.junit.jupiter&lt;/groupId&gt;
&lt;artifactId&gt;junit-jupiter-engine&lt;/artifactId&gt;
&lt;version&gt;${junit.version}&lt;/version&gt;
&lt;scope&gt;test&lt;/scope&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
&lt;groupId&gt;org.projectlombok&lt;/groupId&gt;
&lt;artifactId&gt;lombok&lt;/artifactId&gt;
&lt;version&gt;1.18.28&lt;/version&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
&lt;groupId&gt;org.mybatis&lt;/groupId&gt;
&lt;artifactId&gt;mybatis&lt;/artifactId&gt;
&lt;version&gt;3.5.13&lt;/version&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
&lt;groupId&gt;mysql&lt;/groupId&gt;
&lt;artifactId&gt;mysql-connector-java&lt;/artifactId&gt;
&lt;version&gt;8.0.28&lt;/version&gt;
&lt;/dependency&gt;
&lt;/dependencies&gt;
&lt;build&gt;
&lt;plugins&gt;
&lt;plugin&gt;
&lt;groupId&gt;org.apache.maven.plugins&lt;/groupId&gt;
&lt;artifactId&gt;maven-war-plugin&lt;/artifactId&gt;
&lt;version&gt;3.3.2&lt;/version&gt;
&lt;/plugin&gt;
&lt;/plugins&gt;
&lt;/build&gt;
</project>

注意,pom.xml中部分内容由项目创建时决定,如下所示,切勿无脑复制

<groupId>com.book</groupId>
<artifactId>bookManage</artifactId>
<version>1.0-SNAPSHOT</version>
<name>bookManage</name>
<packaging>war</packaging>

<groupId> 包名
<artifactId> 自己的项目名

登录部分开发

前端登录页面开发,前端不是主要研究的内容,因此直接使用了bootstrap提供的组件。在src/main/resources路径下创建login.html文件,内容如下。

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<!-- 最新版本的 Bootstrap 核心 CSS 文件 -->
<link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.min.css" integrity="sha384-HSMxcRTRxnN+Bdg0JdbxYKrThecOKuH5zCYotlSAcp1+c8xmyTe9GYg1l9a69psu" crossorigin="anonymous">
<link rel="stylesheet" href="./static/css/login.css">
<title>登录</title>
</head>
<body>
<div class="container">
&lt;form class=&quot;form-signin&quot; method=&quot;post&quot; action=&quot;login&quot;&gt;
&lt;h2 class=&quot;form-signin-heading&quot; align=&quot;center&quot;&gt; 登录&lt;/h2&gt;
&lt;p th:if=&quot;${failure}&quot; style=&quot;color: red&quot;&gt;用户名或密码错误&lt;/p&gt;
&lt;label for=&quot;inputUsername&quot; class=&quot;sr-only&quot;&gt;username&lt;/label&gt;
&lt;input type=&quot;text&quot; name=&quot;username&quot; id=&quot;inputUsername&quot; class=&quot;form-control&quot; placeholder=&quot;用户名&quot; required autofocus&gt;
&lt;label for=&quot;inputPassword&quot; class=&quot;sr-only&quot;&gt;Password&lt;/label&gt;
&lt;input type=&quot;password&quot; name=&quot;password&quot; id=&quot;inputPassword&quot; class=&quot;form-control&quot; placeholder=&quot;密码&quot; required&gt;
&lt;div class=&quot;checkbox&quot;&gt;
&lt;label&gt;
&lt;input type=&quot;checkbox&quot; name=&quot;remember-me&quot; value=&quot;remember-me&quot;&gt; 记住我
&lt;/label&gt;
&lt;/div&gt;
&lt;button class=&quot;btn btn-lg btn-primary btn-block&quot; type=&quot;submit&quot;&gt;登录&lt;/button&gt;
&lt;/form&gt;
&lt;form class=&quot;form-signin&quot; method=&quot;post&quot; action=&quot;login&quot;&gt;
&lt;h2 class=&quot;form-signin-heading&quot; align=&quot;center&quot;&gt; 登录&lt;/h2&gt;
&lt;p th:if=&quot;${failure}&quot; style=&quot;color: red&quot;&gt;用户名或密码错误&lt;/p&gt;
&lt;label for=&quot;inputUsername&quot; class=&quot;sr-only&quot;&gt;username&lt;/label&gt;
&lt;input type=&quot;text&quot; name=&quot;username&quot; id=&quot;inputUsername&quot; class=&quot;form-control&quot; placeholder=&quot;用户名&quot; required autofocus&gt;
&lt;label for=&quot;inputPassword&quot; class=&quot;sr-only&quot;&gt;Password&lt;/label&gt;
&lt;input type=&quot;password&quot; name=&quot;password&quot; id=&quot;inputPassword&quot; class=&quot;form-control&quot; placeholder=&quot;密码&quot; required&gt;
&lt;div class=&quot;checkbox&quot;&gt;
&lt;label&gt;
&lt;input type=&quot;checkbox&quot; name=&quot;remember-me&quot; value=&quot;remember-me&quot;&gt; 记住我
&lt;/label&gt;
&lt;/div&gt;
&lt;button class=&quot;btn btn-lg btn-primary btn-block&quot; type=&quot;submit&quot;&gt;登录&lt;/button&gt;
&lt;/form&gt;
</div> <!-- /container -->
<!-- jQuery (Bootstrap 的所有 JavaScript 插件都依赖 jQuery,所以必须放在前边) -->
<script src="https://cdn.jsdelivr.net/npm/jquery@1.12.4/dist/jquery.min.js" integrity="sha384-nvAa0+6Qg9clwYCGGPpDQLVpLNn0fRaROjHqs13t4Ggj3Ez50XnGQqc/r8MhnRDZ" crossorigin="anonymous"></script>
<!-- 最新的 Bootstrap 核心 JavaScript 文件 -->
<script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/js/bootstrap.min.js" integrity="sha384-aJ21OjlMXNL5UyIl/XNwTMqvzeRMZH2w8c5cRVpzpU8Y5bApTppSuUkhZXN0VxHd" crossorigin="anonymous"></script>
</body>
</html>

在src/main/webapp/static/css`路径下创建login.css文件,css文件内容如下

/* stylelint-disable selector-no-qualifying-type, property-no-vendor-prefix */
body {
padding-top: 40px;
padding-bottom: 40px;
background-color: #eee;
}
.form-signin {
max-width: 330px;
padding: 15px;
margin: 0 auto;
}
.form-signin .form-signin-heading,
.form-signin .checkbox {
margin-bottom: 10px;
}
.form-signin .checkbox {
font-weight: 400;
}
.form-signin .form-control {
position: relative;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
height: auto;
padding: 10px;
font-size: 16px;
}
.form-signin .form-control:focus {
z-index: 2;
}
.form-signin input[type="email"] {
margin-bottom: -1px;
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
}
.form-signin input[type="password"] {
margin-bottom: 10px;
border-top-left-radius: 0;
border-top-right-radius: 0;
}

既然要登陆,那么必须要有用户名和密码。因此在mysql中创建books_manage_test数据库,在books_manage_test中创建admin数据表。

建表语句

CREATE TABLE `admin` (
`id` int NOT NULL AUTO_INCREMENT,
`username` varchar(255) DEFAULT NULL,
`nickname` varchar(255) DEFAULT NULL,
`password` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

添加一条用户信息,用于测试。

image-20231025155940511

接下来开始正式开发

本项目遵循三层架构,持久层(DAO)-业务层(Service)-表现层(Controller)。

持久层用于数据访问,业务层用于具体的逻辑处理,表现层负责处理请求和响应。作为一个小项目,本项目中对于各层职能的划分并没有那么准确。

首先在Source Root下创建com.book.servlet包,该包中内容在此项目中对应表现层。在src/mian/java/com/book/servlet/auth下创建LoginServlet类继承HttpServlet,并添加@WebServlet("/login")注解。

作为处理登录请求的类,首先要重写doGet方法,这样在浏览器访问登录页面的网址时将登录页返回给浏览器。这里我们使用Thymeleaf引擎解析html并将页面返回给浏览器。因此需要先创建一个Thymeleaf工具类。

src/mian/java/com/book/下创建utils包,在该包下创建ThymeleafUtil类,代码如下

public class ThymeleafUtil {
private static final TemplateEngine engine;
static {
engine = new TemplateEngine(); // 创建模板引擎
ClassLoaderTemplateResolver r = new ClassLoaderTemplateResolver(); // 创建模板解析器
r.setCharacterEncoding(StandardCharsets.UTF_8.name()); // 设置解析器的字符编码
engine.setTemplateResolver(r); // 设置模板引擎使用的解析器
}
// 创建静态方法,调用模板引擎的process方法
public static void process(String template, IContext context, Writer writer){
engine.process(template, context, writer);
}
private static final TemplateEngine engine;
static {
engine = new TemplateEngine(); // 创建模板引擎
ClassLoaderTemplateResolver r = new ClassLoaderTemplateResolver(); // 创建模板解析器
r.setCharacterEncoding(StandardCharsets.UTF_8.name()); // 设置解析器的字符编码
engine.setTemplateResolver(r); // 设置模板引擎使用的解析器
}
// 创建静态方法,调用模板引擎的process方法
public static void process(String template, IContext context, Writer writer){
engine.process(template, context, writer);
}
}

必须将解析器的字符编码设置为与Html页面编码一致,否则模板引擎解析过的返回到浏览器的页面将乱码。

接着回到LoginServlet中继续doGet的重写。

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Context context = new Context();
ThymeleafUtil.process("login.html",context, resp.getWriter());
}

Context是Thymeleaf中的一个类,其功能是可以创建一个对象,通过调用该对象setVariable(String name, Object value)方法往其中存储键值对。Thymeleaf引擎可以解析Context对象中的值。Thymeleaf调用process方法,先加载login.html,接着解析context中内容,并且根据login.html中的Thymeleaf语法进行处理,最终将处理完毕的html通过resp.getWriter()响应给浏览器。

此时启动项目,可以看到登陆页面已经可以正确显示

image-20231025170433792

如果未正确打开页面,检查服务器部署Url和服务器启动时自动打开的Url是否正确更改。

image-20240111162917647

image-20240111162936024

接下来,我们需要完成用户登录的操作,html中登录表单使用post方式提交请求,因此我们需要重写LoginServlet类中的doPost方法。这一步骤中,用户提交的用户名密码登录信息要与数据库中信息进行对比,因此需要到数据库中查询数据。这里我们使用的是Mybatis来操作数据库,Mybatis是一款当今主流的持久层框架,用于简化JDBC的开发。

为了使用Mybatis,我们需要创建一个Mybatis配置文件,并创建一个工具类,最后创建一个Mapper接口,通过在Mapper接口中定义抽象方法并在抽象方法上使用注解,我们可以对数据库进行操作,下面开始。

首先创建一个Mybatis配置文件mybatis-config.xml,内容如下,放在src/main/resources路径下。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/books_manage_test"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper class="com.book.dao.UserMapper" />
</mappers>
</configuration>

<dataSource>标签下,分别定义了使用的驱动类、数据库地址、用户名、密码。

注意:使用的驱动类必须在pom文件中导入

接着创建Mybatis工具类,文件放在src/main/java/com/book/utils路径下。

public class MybatisUtil {
private static SqlSessionFactory factory;
static {
try {
factory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader("mybatis-config.xml"));
} catch (IOException e) {
e.printStackTrace();
}
}
public static SqlSession getSession(){
return factory.openSession(true);
}
public static SqlSession getSession(){
return factory.openSession(true);
}
}

sqlSessionFactoryBuilder通过提供的mybatis配置文件创建sqlSessionFactory对象

sqlSessionFactory可以创建多个sqlSession,每个sqlSession就相当于一个访问数据库的会话终端

然后,我们去定义Mapper接口,用户登录需要查询的是用户信息,因此我们需要的是一个对数据库中用户信息进行操作的Mapper接口,以及一个存放用户信息的实体类,所以我们在src/main/java/com/book/dao下创建UserMapper接口,关于用户信息的所有数据库操作都放到这个接口中,并在src/main/java/com/book/entity下创建一个User实体类。

UserMapper接口

public interface UserMapper {
@Select(&quot;select * from admin where username = #{username} and password = #{password}&quot;)
User getUser(@Param(&quot;username&quot;) String username, @Param(&quot;password&quot;) String password);
}
@Select(&quot;select * from admin where username = #{username} and password = #{password}&quot;)
User getUser(@Param(&quot;username&quot;) String username, @Param(&quot;password&quot;) String password);

别忘了,创建的Mapper接口要在Mabatis配置文件mybatis-config.xml中注册。

<mappers>
<mapper class="com.book.dao.UserMapper" />
</mappers>

User实体类

@Data
@AllArgsConstructor
public class User {
private int id;
private String username;
private String nickname;
private String password;
}

实体类中我们使用了Lombok的注解,Lombok是一个实用的Java类库,可以通过简单的注解来简化和消除一些必须有但显得很臃肿的Java代码。

前面我们说过,我们的项目遵循的是三层架构,持久层-业务层-表现层,因此,关于用户登录,在持久层方面的操作已经完毕,接下里要做的就是在业务层中对用户登录做逻辑判断。

src/main/java/com/book/service下创建UserService接口,关于用户的逻辑操作都由这个接口定义,接着在该路径下创建impl包,在该包下创建UserServiceImpl类,用来实现UserService接口。

public interface UserService {
boolean auth(String username, String password, HttpSession session);
}
public class UserServiceImpl implements UserService {
@Override
public boolean auth(String username, String password, HttpSession session) {
try(SqlSession sqlSession = MybatisUtil.getSession()){
UserMapper mapper = sqlSession.getMapper(UserMapper.class); // sqlSession根据Mapper接口创建一个mapper对象
User user = mapper.getUser(username, password); // 通过mapper对象可以调用该接口中的方法查询数据
if(user == null) return false;
session.setAttribute("user",user); // 将查询到的用户对象存入服务器的session中
return true;
}
}
}

此时我们可以回到表现层中,继续重写doPost方法,

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
UserService service = new UserServiceImpl();
String username = req.getParameter("username");
String password = req.getParameter("password");
String remember = req.getParameter("remember-me");
if(service.auth(username,password, req.getSession())){
resp.getWriter().write("Success!");
resp.getWriter().write("Success!");
return;
}
this.doGet(req, resp);
}

首先创建用户业务类的对象,在请求对象中获取表单提交的用户信息,调用用户业务类对象的方法判断用户是否可以登录,如果可以,返回给页面Success!,否则跳转会登录页面。

至此用户登录的逻辑已经完毕。接下来对其进行一些调整,调整后LoginServlet代码如下

@WebServlet("/login")
public class LoginServlet extends HttpServlet {
UserService service;
@Override
public void init() throws ServletException {
service = new UserServiceImpl(); // 初始化时创建UserService实现类的对象
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Context context = new Context();
if(req.getSession().getAttribute(&quot;login-failure&quot;) != null){
context.setVariable(&quot;failure&quot;, true);
req.getSession().removeAttribute(&quot;login-failure&quot;);
}
ThymeleafUtil.process(&quot;login.html&quot;,context, resp.getWriter());
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String username = req.getParameter(&quot;username&quot;);
String password = req.getParameter(&quot;password&quot;);
String remember = req.getParameter(&quot;remember-me&quot;);
if(service.auth(username,password, req.getSession())){
resp.getWriter().write(&quot;Success!&quot;);
return;
}
req.getSession().setAttribute(&quot;login-failure&quot;, 1);
this.doGet(req, resp);
}
UserService service;
@Override
public void init() throws ServletException {
service = new UserServiceImpl(); // 初始化时创建UserService实现类的对象
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Context context = new Context();
if(req.getSession().getAttribute(&quot;login-failure&quot;) != null){
context.setVariable(&quot;failure&quot;, true);
req.getSession().removeAttribute(&quot;login-failure&quot;);
}
ThymeleafUtil.process(&quot;login.html&quot;,context, resp.getWriter());
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String username = req.getParameter(&quot;username&quot;);
String password = req.getParameter(&quot;password&quot;);
String remember = req.getParameter(&quot;remember-me&quot;);
if(service.auth(username,password, req.getSession())){
resp.getWriter().write(&quot;Success!&quot;);
return;
}
req.getSession().setAttribute(&quot;login-failure&quot;, 1);
this.doGet(req, resp);
}
}

首先将用户业务类对象的创建放到了初始化方法中,其次在登录失败后不是直接调用doGet跳转到登陆页面,而是携带一条登录失败的标志信息在session中,然后再调用doGet。

而在doGet方法中,会判断session中是否存在登录失败的信息,如果存在,则会往Context对象中添加一条登录失败的信息,并清除session中登陆失败的信息。Context是Thymeleaf中的一个类,该类创建的对象可以携带键值对信息(键是字符串,值可以是对象),Thymeleaf可以在html中解析Context对象中的信息。

而在html中,利用Thymeleaf模板语言,添加了一个只有在解析到登录失败的信息存在时才会显示的内容。

调整后,当登录失败时,会显示用户名或密码错误。

image-20231026201213905

至此,用户登录部分开发完成!

过滤器

但是,我们可以注意到,我们平时浏览的一些需要登录的网站,如果我们没有登录,那么它会自动给我们跳转到登录页,因此,我们需要为该项目添加一个拦截器,使得我们的网站在用户没有登录的情况自动跳转到登录页面。

src/main/java/com/book/filter下创建MainFilter类继承HttpFilter类。添加@WebFilter("/*")注解,这表示拦截所有路径。完整代码如下

@WebFilter("/*")
public class MainFilter extends HttpFilter {
@Override
protected void doFilter(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws IOException, ServletException {
String url = req.getRequestURI().toString();
if(!url.contains("/static/") && !url.endsWith("login")){
HttpSession session = req.getSession();
User user = (User)session.getAttribute("user");
if(user == null){
res.sendRedirect("login");
return; // 必须返回,否则会导致页面崩溃
}
}
chain.doFilter(req, res); // 放行
}
}

该程序会对所有url做判断,如果不是访问静态资源或者登录页,就判断其是否登录,如果未登录就跳转到登录页。如果判断已经登录,就调用chain.doFilter(req, res)让访问通过。

借阅信息页面开发(同时也是首页)

先看页面完成后的样子

image-20231031152443072

可以看出,页面包含导航栏,侧边栏,以及借阅信息页面。其中,导航栏和侧边栏是固定存在于每个页面的,因此我们要利用Thymeleaf的模板继承功能,编写一个页面作为模板,将这些共有的部分放到模板中。实际情况下,由于侧边栏要随着不同的页面改变,放到模板里反而会更加麻烦,因此侧边栏部分还是放到了每个页面中。在src/main/resources/下创建layout.html,文件内容如下:

layout.html

<!DOCTYPE html>
<!--此文件为模板-->
<html lang="en" th:fragment="layout(content)" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.min.css"
integrity="sha384-HSMxcRTRxnN+Bdg0JdbxYKrThecOKuH5zCYotlSAcp1+c8xmyTe9GYg1l9a69psu" crossorigin="anonymous">
<link rel="stylesheet" href="static/css/dashboard.css">
<title>图书借阅系统</title>
</head>
<body>
<nav class="navbar navbar-inverse navbar-fixed-top">
<div class="container-fluid">
<div class="navbar-header">
<a class="navbar-brand" href="index" style="color: white">图书借阅系统</a>
</div>
<div id="navbar" class="navbar-collapse collapse">
<ul class="nav navbar-nav navbar-right">
<li><a href="quit">退出登录</a></li>
<li><a href="#" th:text="${nickName}"></a></li>
</ul>
</div>
&lt;/div&gt;
</nav>
<div th:replace="${content}">
内容插入处
</div>
&lt;/div&gt;
<!-- jQuery (Bootstrap 的所有 JavaScript 插件都依赖 jQuery,所以必须放在前边) -->
<script src="https://cdn.jsdelivr.net/npm/jquery@1.12.4/dist/jquery.min.js"
integrity="sha384-nvAa0+6Qg9clwYCGGPpDQLVpLNn0fRaROjHqs13t4Ggj3Ez50XnGQqc/r8MhnRDZ"
crossorigin="anonymous"></script>
<!-- 最新的 Bootstrap 核心 JavaScript 文件 -->
<script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/js/bootstrap.min.js"
integrity="sha384-aJ21OjlMXNL5UyIl/XNwTMqvzeRMZH2w8c5cRVpzpU8Y5bApTppSuUkhZXN0VxHd"
crossorigin="anonymous"></script>
</body>
</html>

引入的css中,dashboard.css是存在于本地的src/main/webapp/static/css/路径下,引用url从webapp之后的路径开始,mavenWeb项目编译后的项目结构导致如此。

mavenWeb编译后项目结构

image-20231031163131970

bookManage-1.0-SNAPSHOT表示该项目,可以看到,src/main/java下的代码文件以及src/main/resource下的资源文件全部到了bookManage-1.0-SNAPSHOT/WEB-INF下,WEB-INF下的内容无法通过url直接访问,而webapp下的内容在bookManage-1.0-SNAPSHOT下,可以通过拼接url直接访问静态资源文件,但是要在web.xml文件中作相应的配置。

dashboard.css

/*
* Base structure
*/
/* Move down content because we have a fixed navbar that is 50px tall */
body {
padding-top: 50px;
}
/*
Global add-ons
*/
.sub-header {
padding-bottom: 10px;
border-bottom: 1px solid #eee;
}
/*
Top navigation
Hide default border to remove 1px line.
*/
.navbar-fixed-top {
border: 0;
}
/*
Sidebar
*/
/* Hide for mobile, show later /
.sidebar {
display: none;
}
@media (min-width: 768px) {
.sidebar {
position: fixed;
top: 51px;
bottom: 0;
left: 0;
z-index: 1000;
display: block;
padding: 20px;
overflow-x: hidden;
overflow-y: auto; / Scrollable contents if viewport is shorter than content. */
background-color: #f5f5f5;
border-right: 1px solid #eee;
}
}
/* Sidebar navigation /
.nav-sidebar {
margin-right: -21px; / 20px padding + 1px border */
margin-bottom: 20px;
margin-left: -20px;
}
.nav-sidebar > li > a {
padding-right: 20px;
padding-left: 20px;
}
.nav-sidebar > .active > a,
.nav-sidebar > .active > a:hover,
.nav-sidebar > .active > a:focus {
color: #fff;
background-color: #428bca;
}
/*
Main content
*/
.main {
padding: 20px;
}
@media (min-width: 768px) {
.main {
padding-right: 40px;
padding-left: 40px;
}
}
.main .page-header {
margin-top: 0;
}
/*
Placeholder dashboard ideas
*/
.placeholders {
margin-bottom: 30px;
text-align: center;
}
.placeholders h4 {
margin-bottom: 0;
}
.placeholder {
margin-bottom: 20px;
}
.placeholder img {
display: inline-block;
border-radius: 50%;
}

借阅信息页面开发

接下来编写首页也就是借阅信息页面,创建index.html,内容如下:

<!DOCTYPE html>
<html lang="en" th:replace="~{layout.html::layout(~{:: #content})}">
<body>
<div class="container-fluid" id="content">
<div class="row">
<div class="col-sm-3 col-md-2 sidebar">
<ul class="nav nav-sidebar">
<li class="active"><a href="index">借阅信息</a></li>
<li><a href="students">学生列表</a></li>
<li><a href="books">书籍列表</a></li>
</ul>
&lt;/div&gt;
&lt;div class=&quot;col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main&quot;&gt;
&lt;h1 class=&quot;page-header&quot;&gt;借阅信息总览&lt;/h1&gt;
&lt;div class=&quot;row placeholders&quot;&gt;
&lt;div class=&quot;col-xs-6 col-sm-3 placeholder&quot; style=&quot;border:3px solid #428bca&quot;&gt;
&lt;h4&gt;书籍数量&lt;/h4&gt;
&lt;span class=&quot;text-muted&quot; th:text=&quot;${bookNum}&quot;&gt;Something else&lt;/span&gt;
&lt;/div&gt;
&lt;div class=&quot;col-xs-6 col-sm-3 placeholder&quot; style=&quot;border:3px solid #428bca&quot;&gt;
&lt;h4&gt;学生人数&lt;/h4&gt;
&lt;span class=&quot;text-muted&quot; th:text=&quot;${studentNum}&quot;&gt;Something else&lt;/span&gt;
&lt;/div&gt;
&lt;div class=&quot;col-xs-6 col-sm-3 placeholder&quot; style=&quot;border:3px solid #428bca&quot;&gt;
&lt;h4&gt;总借阅数&lt;/h4&gt;
&lt;span class=&quot;text-muted&quot; th:text=&quot;${borrowNum}&quot;&gt;Something else&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 class=&quot;sub-header&quot;&gt;借阅信息列表&lt;/h2&gt;
&lt;a href=&quot;add-borrow&quot; class=&quot;btn btn-success&quot;&gt;添加借阅记录&lt;/a&gt;
&lt;br/&gt;
&lt;div class=&quot;table-responsive&quot;&gt;
&lt;table class=&quot;table table-striped&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;书籍编号&lt;/th&gt;
&lt;th&gt;书名&lt;/th&gt;
&lt;th&gt;借阅时间&lt;/th&gt;
&lt;th&gt;学号&lt;/th&gt;
&lt;th&gt;学生姓名&lt;/th&gt;
&lt;th&gt;归还情况&lt;/th&gt;
&lt;th&gt;操作&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr th:each=&quot;borrow:${borrowList}&quot;&gt;
&lt;td th:text=&quot;${borrow.getBookId()}&quot;&gt;书id&lt;/td&gt;
&lt;td th:text=&quot;${borrow.getBookName()}&quot;&gt;书名&lt;/td&gt;
&lt;td th:text=&quot;${borrow.getTime()}&quot;&gt;借阅时间&lt;/td&gt;
&lt;td th:text=&quot;${borrow.getStudentId()}&quot;&gt;学号&lt;/td&gt;
&lt;td th:text=&quot;${borrow.getStudentName()}&quot;&gt;姓名&lt;/td&gt;
&lt;td th:text=&quot;${borrow.getBack()?'已归还':'未归还'}&quot;&gt;归还情况&lt;/td&gt;
&lt;td&gt;
&lt;span&gt;
&lt;a th:href=&quot;'delete-borrow?id='+${borrow.getId()}&quot; class=&quot;btn btn-xs btn-danger&quot; &gt;删除&lt;/a&gt;
&lt;/span&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
</div>
&lt;/div&gt;
&lt;div class=&quot;col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main&quot;&gt;
&lt;h1 class=&quot;page-header&quot;&gt;借阅信息总览&lt;/h1&gt;
&lt;div class=&quot;row placeholders&quot;&gt;
&lt;div class=&quot;col-xs-6 col-sm-3 placeholder&quot; style=&quot;border:3px solid #428bca&quot;&gt;
&lt;h4&gt;书籍数量&lt;/h4&gt;
&lt;span class=&quot;text-muted&quot; th:text=&quot;${bookNum}&quot;&gt;Something else&lt;/span&gt;
&lt;/div&gt;
&lt;div class=&quot;col-xs-6 col-sm-3 placeholder&quot; style=&quot;border:3px solid #428bca&quot;&gt;
&lt;h4&gt;学生人数&lt;/h4&gt;
&lt;span class=&quot;text-muted&quot; th:text=&quot;${studentNum}&quot;&gt;Something else&lt;/span&gt;
&lt;/div&gt;
&lt;div class=&quot;col-xs-6 col-sm-3 placeholder&quot; style=&quot;border:3px solid #428bca&quot;&gt;
&lt;h4&gt;总借阅数&lt;/h4&gt;
&lt;span class=&quot;text-muted&quot; th:text=&quot;${borrowNum}&quot;&gt;Something else&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 class=&quot;sub-header&quot;&gt;借阅信息列表&lt;/h2&gt;
&lt;a href=&quot;add-borrow&quot; class=&quot;btn btn-success&quot;&gt;添加借阅记录&lt;/a&gt;
&lt;br/&gt;
&lt;div class=&quot;table-responsive&quot;&gt;
&lt;table class=&quot;table table-striped&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;书籍编号&lt;/th&gt;
&lt;th&gt;书名&lt;/th&gt;
&lt;th&gt;借阅时间&lt;/th&gt;
&lt;th&gt;学号&lt;/th&gt;
&lt;th&gt;学生姓名&lt;/th&gt;
&lt;th&gt;归还情况&lt;/th&gt;
&lt;th&gt;操作&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr th:each=&quot;borrow:${borrowList}&quot;&gt;
&lt;td th:text=&quot;${borrow.getBookId()}&quot;&gt;书id&lt;/td&gt;
&lt;td th:text=&quot;${borrow.getBookName()}&quot;&gt;书名&lt;/td&gt;
&lt;td th:text=&quot;${borrow.getTime()}&quot;&gt;借阅时间&lt;/td&gt;
&lt;td th:text=&quot;${borrow.getStudentId()}&quot;&gt;学号&lt;/td&gt;
&lt;td th:text=&quot;${borrow.getStudentName()}&quot;&gt;姓名&lt;/td&gt;
&lt;td th:text=&quot;${borrow.getBack()?'已归还':'未归还'}&quot;&gt;归还情况&lt;/td&gt;
&lt;td&gt;
&lt;span&gt;
&lt;a th:href=&quot;'delete-borrow?id='+${borrow.getId()}&quot; class=&quot;btn btn-xs btn-danger&quot; &gt;删除&lt;/a&gt;
&lt;/span&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
</body>
</html>

在<html>标签中引入模板文件,并将模板中指定位置的标签内容替换为id="content"的内容。

书籍数量,学生人数和总借阅数的标签内容都用th:text语法替换为后端传来的内容。

借阅记录的表格内容,利用Thymeleaf的th:each语法,将后端传递过来的borrow列表中的内容填充进去。

接下来是后端内容的编写,后端部分包括持久层、业务层和表现层,持久层用来操作数据库,因此我们必须先有对应的数据表可供操作。数据库部分,借阅信息中包含书籍信息,学生信息,因此需要创建三个表,分别是学生表、书籍表以及借阅信息表(书籍与学生关系)。学生表包含学生信息,书籍表包含书籍信息,借阅信息表包含学生和书籍关系也就是借阅信息。以下是三个表的建表语句。

CREATE TABLE `books` (
`id` int NOT NULL,
`name` varchar(64) DEFAULT NULL,
`book_number` int DEFAULT NULL COMMENT '书籍数量',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
CREATE TABLE `students` (
`id` int NOT NULL,
`name` varchar(64) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
CREATE TABLE `borrow` (
`id` int NOT NULL AUTO_INCREMENT,
`time` datetime NOT NULL,
`book_id` int DEFAULT NULL,
`student_id` int DEFAULT NULL,
`back` tinyint DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

接下来是持久层(DAO)的开发,在src/mian/java/com/book/dao下创建BookMapper接口,StudentMapper接口和BorrowMapper接口,分别用于操作books,students和borrow表。

为了让mybatis可以识别到src/mian/java/com/book/dao下所有Mapper接口,对mybatis配置文件进行更改,将原来的

<mappers>
<mapper class="com.book.dao.UserMapper" />
</mappers>

更改为

<mappers>
<package name="com.book.dao" />
</mappers>

这样mybatis就会扫描这整个包,识别其中所有的Mapper接口。

接下来编写BorrowMapper接口,内容如下:

public interface BorrowMapper {
// 查询借阅列表
@Results({
@Result(column = "bookId",property = "bookId"),
@Result(column = "bookName",property = "bookName"),
@Result(column = "time",property = "time"),
@Result(column = "studentId",property = "studentId"),
@Result(column = "studentName",property = "studentName"),
@Result(column = "back",property = "back")
})
@Select("select borrow.id, book_id bookId,books.name bookName,time,student_id studentId,students.name studentName, back from borrow,books,students where student_id=students.id and book_id=books.id;")
List<Borrow> getBorrowList();
// 计数借阅记录
@Select(&quot;select count(*) from borrow;&quot;)
Integer countBorrow();
// 插入借阅记录
@Insert(&quot;insert into borrow(time, book_id, student_id) values (#{localDateTime}, #{bookId}, #{studentId});&quot;)
void insertBorrow(@Param(&quot;localDateTime&quot;) LocalDateTime localDateTime, @Param(&quot;bookId&quot;) Integer bookId, @Param(&quot;studentId&quot;) Integer studentId);
// 删除借阅记录
@Delete(&quot;delete from borrow where id = #{id};&quot;)
void deleteBorrowById(Integer id);
// 计数借阅记录
@Select(&quot;select count(*) from borrow;&quot;)
Integer countBorrow();
// 插入借阅记录
@Insert(&quot;insert into borrow(time, book_id, student_id) values (#{localDateTime}, #{bookId}, #{studentId});&quot;)
void insertBorrow(@Param(&quot;localDateTime&quot;) LocalDateTime localDateTime, @Param(&quot;bookId&quot;) Integer bookId, @Param(&quot;studentId&quot;) Integer studentId);
// 删除借阅记录
@Delete(&quot;delete from borrow where id = #{id};&quot;)
void deleteBorrowById(Integer id);
}

查询借阅列表部分使用的多表联查,并且给字段取了别名,这样便于映射到Java实体类中的属性。@Results注解也是为了将数据库字段映射到实体类属性,@Results可以不加,这里加上去也没什么用,只是懒得删了。

BookMapper接口

public interface BookMapper {
// 展示所有书
@Select("select id,name,book_number bookNumber from books;")
List<Book> listBooks();
// 统计书数量
@Select(&quot;select sum(book_number) from books;&quot;)
Integer countBooks();
// 列出数量&gt;0的书
@Select(&quot;select * from books where book_number&gt;0;&quot;)
List&lt;Book&gt; listExistBooks();
// 根据书的id,-1该书数量
@Update(&quot;update books set book_number = book_number-1 where books.id=#{id};&quot;)
void minusBookNumber(Integer id);
// 根据书id查询该书数量
@Select(&quot;select book_number from books where books.id = #{id};&quot;)
Integer getNumberById(Integer id);
// 插入书目
@Insert(&quot;insert into books(id, name, book_number) values (#{id}, #{name}, #{bookNumber});&quot;)
void insertBook(Book book);
// 根据id查询书目
@Select(&quot;select * from books where id=#{id};&quot;)
Book selectBookById(Integer id);
// 调整书籍数量
@Update(&quot;update books set book_number = #{number} where books.id=#{id};&quot;)
void modifyBookNumber(@Param(&quot;id&quot;) Integer id, @Param(&quot;number&quot;) Integer number);
// 删除书目
@Delete(&quot;delete from books where id=#{id}&quot;)
void deleteById(Integer id);
// 统计书数量
@Select(&quot;select sum(book_number) from books;&quot;)
Integer countBooks();
// 列出数量&gt;0的书
@Select(&quot;select * from books where book_number&gt;0;&quot;)
List&lt;Book&gt; listExistBooks();
// 根据书的id,-1该书数量
@Update(&quot;update books set book_number = book_number-1 where books.id=#{id};&quot;)
void minusBookNumber(Integer id);
// 根据书id查询该书数量
@Select(&quot;select book_number from books where books.id = #{id};&quot;)
Integer getNumberById(Integer id);
// 插入书目
@Insert(&quot;insert into books(id, name, book_number) values (#{id}, #{name}, #{bookNumber});&quot;)
void insertBook(Book book);
// 根据id查询书目
@Select(&quot;select * from books where id=#{id};&quot;)
Book selectBookById(Integer id);
// 调整书籍数量
@Update(&quot;update books set book_number = #{number} where books.id=#{id};&quot;)
void modifyBookNumber(@Param(&quot;id&quot;) Integer id, @Param(&quot;number&quot;) Integer number);
// 删除书目
@Delete(&quot;delete from books where id=#{id}&quot;)
void deleteById(Integer id);
}

StudentMapper接口

public interface StudentMapper {
// 查询学生列表
@Select(&quot;select * from students;&quot;)
List&lt;Student&gt; listStudents();
// 统计学生数量
@Select(&quot;select count(*) from students;&quot;)
Integer countStudents();
// 查询学生
@Select(&quot;select * from students where id=#{id}&quot;)
Student getById(Integer id);
// 查询学生列表
@Select(&quot;select * from students;&quot;)
List&lt;Student&gt; listStudents();
// 统计学生数量
@Select(&quot;select count(*) from students;&quot;)
Integer countStudents();
// 查询学生
@Select(&quot;select * from students where id=#{id}&quot;)
Student getById(Integer id);
}

接下来是业务层(Service)的开发,在src/main/java/com/book/service下创建BorrowService接口,定义borrow相关的业务方法。

public interface BorrowService {
List<Borrow> getBorrowList();
Integer countBorrow();
Integer insertBorrow(LocalDateTime localDateTime, Integer bookId, Integer studentId);
Integer deleteBorrowById(Integer id);
}
Integer countBorrow();
Integer insertBorrow(LocalDateTime localDateTime, Integer bookId, Integer studentId);
Integer deleteBorrowById(Integer id);

然后在src/main/java/com/book/service/impl下创建BorrowServiceImpl实现类实现BorrowService接口。

public class BorrowServiceImpl implements BorrowService {
@Override
public List<Borrow> getBorrowList() {
// 获取借阅列表
try (SqlSession sqlSession = MybatisUtil.getSession()) {
BorrowMapper borrowMapper = sqlSession.getMapper(BorrowMapper.class);
List<Borrow> borrows = borrowMapper.getBorrowList();
// mapper操作数据库必须放在try-resource方法体内,因为sql会话只在方法体内有效,方法体内代码执行完就会关闭sql会话
// 如果在try-resource方法体外操作数据库,会收到Executor was closed 报错
return borrows;
}
}
@Override
public Integer countBorrow() {
// 统计借阅数量
try(SqlSession sqlSession = MybatisUtil.getSession()){
BorrowMapper borrowMapper = sqlSession.getMapper(BorrowMapper.class);
return borrowMapper.countBorrow();
}
}
@Override
public Integer insertBorrow(LocalDateTime localDateTime, Integer bookId, Integer studentId) {
try(SqlSession sqlSession = MybatisUtil.getSession()){
BorrowMapper borrowMapper = sqlSession.getMapper(BorrowMapper.class);
borrowMapper.insertBorrow(localDateTime, bookId, studentId);
return 1;
}
}
@Override
public Integer deleteBorrowById(Integer id) {
try(SqlSession sqlSession = MybatisUtil.getSession()){
BorrowMapper borrowMapper = sqlSession.getMapper(BorrowMapper.class);
borrowMapper.deleteBorrowById(id);
return 1;
}
}
}
@Override
public Integer countBorrow() {
// 统计借阅数量
try(SqlSession sqlSession = MybatisUtil.getSession()){
BorrowMapper borrowMapper = sqlSession.getMapper(BorrowMapper.class);
return borrowMapper.countBorrow();
}
}
@Override
public Integer insertBorrow(LocalDateTime localDateTime, Integer bookId, Integer studentId) {
try(SqlSession sqlSession = MybatisUtil.getSession()){
BorrowMapper borrowMapper = sqlSession.getMapper(BorrowMapper.class);
borrowMapper.insertBorrow(localDateTime, bookId, studentId);
return 1;
}
}
@Override
public Integer deleteBorrowById(Integer id) {
try(SqlSession sqlSession = MybatisUtil.getSession()){
BorrowMapper borrowMapper = sqlSession.getMapper(BorrowMapper.class);
borrowMapper.deleteBorrowById(id);
return 1;
}
}
}

BookService接口

public interface BookService {
List<Book> listBooks();
Integer countBooks();
List&lt;Book&gt; listExistBooks();
Integer minusBookNumber(Integer id);
Integer getNumberById(Integer id);
Integer insertBook(Book book);
Book selectBookById(Integer id);
void modifyBookNumber(Integer id, Integer number);
void deleteById(Integer id);
}
Integer countBooks();
List&lt;Book&gt; listExistBooks();
Integer minusBookNumber(Integer id);
Integer getNumberById(Integer id);
Integer insertBook(Book book);
Book selectBookById(Integer id);
void modifyBookNumber(Integer id, Integer number);
void deleteById(Integer id);

BookServiceImpl实现类

public class BookServiceImpl implements BookService {
@Override
public List<Book> listBooks() {
try (SqlSession sqlSession = MybatisUtil.getSession()){
BookMapper bookMapper = sqlSession.getMapper(BookMapper.class);
return bookMapper.listBooks();
}
}
@Override
public Integer countBooks() {
try(SqlSession sqlSession = MybatisUtil.getSession()){
BookMapper bookMapper = sqlSession.getMapper(BookMapper.class);
return bookMapper.countBooks();
}
}
@Override
public List&lt;Book&gt; listExistBooks() {
try(SqlSession sqlSession = MybatisUtil.getSession()){
BookMapper bookMapper = sqlSession.getMapper(BookMapper.class);
return bookMapper.listExistBooks();
}
}
@Override
public Integer minusBookNumber(Integer id) {
try(SqlSession sqlSession = MybatisUtil.getSession()){
BookMapper bookMapper = sqlSession.getMapper(BookMapper.class);
bookMapper.minusBookNumber(id);
return 1; // 成功执行返回1
}
}
@Override
public Integer getNumberById(Integer id) {
try(SqlSession sqlSession = MybatisUtil.getSession()){
BookMapper bookMapper = sqlSession.getMapper(BookMapper.class);
return bookMapper.getNumberById(id);
}
}
@Override
public Integer insertBook(Book book) {
try(SqlSession sqlSession = MybatisUtil.getSession()){
BookMapper bookMapper = sqlSession.getMapper(BookMapper.class);
bookMapper.insertBook(book);
}
return 1;
}
@Override
public Book selectBookById(Integer id) {
try(SqlSession sqlSession = MybatisUtil.getSession()){
BookMapper bookMapper = sqlSession.getMapper(BookMapper.class);
return bookMapper.selectBookById(id);
}
}
@Override
public void modifyBookNumber(Integer id, Integer number) {
try(SqlSession sqlSession = MybatisUtil.getSession()){
BookMapper bookMapper = sqlSession.getMapper(BookMapper.class);
bookMapper.modifyBookNumber(id, number);
}
}
@Override
public void deleteById(Integer id) {
try(SqlSession sqlSession = MybatisUtil.getSession()){
BookMapper bookMapper = sqlSession.getMapper(BookMapper.class);
bookMapper.deleteById(id);
}
}
}
@Override
public Integer countBooks() {
try(SqlSession sqlSession = MybatisUtil.getSession()){
BookMapper bookMapper = sqlSession.getMapper(BookMapper.class);
return bookMapper.countBooks();
}
}
@Override
public List&lt;Book&gt; listExistBooks() {
try(SqlSession sqlSession = MybatisUtil.getSession()){
BookMapper bookMapper = sqlSession.getMapper(BookMapper.class);
return bookMapper.listExistBooks();
}
}
@Override
public Integer minusBookNumber(Integer id) {
try(SqlSession sqlSession = MybatisUtil.getSession()){
BookMapper bookMapper = sqlSession.getMapper(BookMapper.class);
bookMapper.minusBookNumber(id);
return 1; // 成功执行返回1
}
}
@Override
public Integer getNumberById(Integer id) {
try(SqlSession sqlSession = MybatisUtil.getSession()){
BookMapper bookMapper = sqlSession.getMapper(BookMapper.class);
return bookMapper.getNumberById(id);
}
}
@Override
public Integer insertBook(Book book) {
try(SqlSession sqlSession = MybatisUtil.getSession()){
BookMapper bookMapper = sqlSession.getMapper(BookMapper.class);
bookMapper.insertBook(book);
}
return 1;
}
@Override
public Book selectBookById(Integer id) {
try(SqlSession sqlSession = MybatisUtil.getSession()){
BookMapper bookMapper = sqlSession.getMapper(BookMapper.class);
return bookMapper.selectBookById(id);
}
}
@Override
public void modifyBookNumber(Integer id, Integer number) {
try(SqlSession sqlSession = MybatisUtil.getSession()){
BookMapper bookMapper = sqlSession.getMapper(BookMapper.class);
bookMapper.modifyBookNumber(id, number);
}
}
@Override
public void deleteById(Integer id) {
try(SqlSession sqlSession = MybatisUtil.getSession()){
BookMapper bookMapper = sqlSession.getMapper(BookMapper.class);
bookMapper.deleteById(id);
}
}

StudentService接口

public interface StudentService {
List<Student> listStudents();
Integer countStudents();
Student getById(Integer id);
Integer countStudents();
Student getById(Integer id);
}

StudentServiceImpl实现类

public class StudentServiceImpl implements StudentService {
@Override
public List<Student> listStudents() {
try(SqlSession sqlSession = MybatisUtil.getSession()){
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
return studentMapper.listStudents();
}
}
@Override
public Integer countStudents() {
try(SqlSession sqlSession = MybatisUtil.getSession()){
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
return studentMapper.countStudents();
}
}
@Override
public Student getById(Integer id) {
try(SqlSession sqlSession = MybatisUtil.getSession()){
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
return studentMapper.getById(id);
}
}
@Override
public Integer countStudents() {
try(SqlSession sqlSession = MybatisUtil.getSession()){
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
return studentMapper.countStudents();
}
}
@Override
public Student getById(Integer id) {
try(SqlSession sqlSession = MybatisUtil.getSession()){
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
return studentMapper.getById(id);
}
}
}

别忘了实体类创建

Borrow实体类

@Data
@AllArgsConstructor
public class Borrow {
private int id;
private int bookId;
private String bookName;
private LocalDateTime time;
private int studentId;
private String studentName;
private int back;
}

Book实体类

@Data
@AllArgsConstructor
public class Book {
private Integer id;
private String name;
private Integer bookNumber;
}

Student实体类

@Data
@AllArgsConstructor
public class Student {
private Integer id;
private String name;
}

最后是表现层的开发,在src/main/java/com/book/servlet/manage下创建BorrowServlet继承HttpServlet抽象类,并添加@WebServlet("/index")注解。

@WebServlet("/index")
public class BorrowServlet extends HttpServlet {
BorrowService borrowService;
BorrowService studentService;
BookService bookService;
@Override
public void init() throws ServletException {
borrowService = new BorrowServiceImpl();
bookService = new BookServiceImpl();
studentService = new StudentServiceImpl();
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Context context = new Context();
List&lt;Borrow&gt; borrowList = borrowService.getBorrowList();
// 从session中获取用户昵称
User user = (User)req.getSession().getAttribute(&quot;user&quot;);
String nikeName =user.getNickname();
// 创建Thymeleaf模板的Context对象用来向Html传递数据
context.setVariable(&quot;nickName&quot;,nikeName);
context.setVariable(&quot;borrowList&quot;, borrowList);
Integer bookNum = bookService.countBooks();
Integer studentNum = studentService.countStudents();
Integer borrowNum = borrowService.countBorrow();
context.setVariable(&quot;bookNum&quot;, bookNum);
context.setVariable(&quot;studentNum&quot;, studentNum);
context.setVariable(&quot;borrowNum&quot;, borrowNum);
ThymeleafUtil.process(&quot;index.html&quot;,context,resp.getWriter());
}
@Override
public void init() throws ServletException {
borrowService = new BorrowServiceImpl();
bookService = new BookServiceImpl();
studentService = new StudentServiceImpl();
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Context context = new Context();
List&lt;Borrow&gt; borrowList = borrowService.getBorrowList();
// 从session中获取用户昵称
User user = (User)req.getSession().getAttribute(&quot;user&quot;);
String nikeName =user.getNickname();
// 创建Thymeleaf模板的Context对象用来向Html传递数据
context.setVariable(&quot;nickName&quot;,nikeName);
context.setVariable(&quot;borrowList&quot;, borrowList);
Integer bookNum = bookService.countBooks();
Integer studentNum = studentService.countStudents();
Integer borrowNum = borrowService.countBorrow();
context.setVariable(&quot;bookNum&quot;, bookNum);
context.setVariable(&quot;studentNum&quot;, studentNum);
context.setVariable(&quot;borrowNum&quot;, borrowNum);
ThymeleafUtil.process(&quot;index.html&quot;,context,resp.getWriter());
}
}

重写init()方法,用来对BorrowService等业务类初始化,

重写doGet()方法,完成各种数据的获取并存入Context对象中,使用Thymeleaf模板引擎将Context对象传递到前端页面。

这一步完成后,借阅信息页面已经可以正常展示了,然而添加借阅记录的功能还没有开发。

添加借阅记录功能开发

src/main/resources下创建add_borrow.html。

<!DOCTYPE html>
<html lang="en" th:replace="~{layout.html::layout(~{:: #content})}" xmlns:th="http://www.thymeleaf.org">
<body>
<div id="content">
<div class="row">
<div class="col-sm-3 col-md-2 sidebar">
<ul class="nav nav-sidebar">
<li class="active"><a href="index">借阅信息</a></li>
<li><a href="students">学生列表</a></li>
<li><a href="books">书籍列表</a></li>
</ul>
&lt;/div&gt;
&lt;div class=&quot;col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main&quot;&gt;
&lt;h2 class=&quot;sub-header&quot;&gt;添加借阅记录&lt;/h2&gt;
&lt;p th:if=&quot;${warning}&quot; style=&quot;color: red&quot;&gt;该书暂时不可借阅&lt;/p&gt;
&lt;p th:if=&quot;${incorrect}&quot; style=&quot;color: red&quot;&gt;输入的信息不合法&lt;/p&gt;
&lt;p th:if=&quot;${noId}&quot; style=&quot;color: red&quot;&gt;输入项不能为空&lt;/p&gt;
&lt;form class=&quot;form-inline&quot; method=&quot;post&quot; action=&quot;add-borrow&quot;&gt;
&lt;div class=&quot;form-group&quot;&gt;
&lt;label for=&quot;book&quot;&gt;书籍编号&lt;/label&gt;
&lt;input name=&quot;bookName&quot; list=&quot;bookList&quot; type=&quot;text&quot; class=&quot;form-control&quot; id=&quot;book&quot;
placeholder=&quot;降龙十八掌&quot;&gt;
&lt;datalist id=&quot;bookList&quot;&gt;
&lt;option th:each=&quot;book: ${books}&quot; th:value=&quot;${book.getId()+'-'+book.getName()}&quot;&gt;&lt;/option&gt;
&lt;/datalist&gt;
&lt;/div&gt;
&lt;div class=&quot;form-group&quot;&gt;
&lt;label for=&quot;student&quot;&gt;学号&lt;/label&gt;
&lt;input name=&quot;studentName&quot; list=&quot;studentList&quot; type=&quot;text&quot; class=&quot;form-control&quot; id=&quot;student&quot;
placeholder=&quot;乔峰&quot;&gt;
&lt;datalist id=&quot;studentList&quot;&gt;
&lt;option th:each=&quot;student:${students}&quot;
th:value=&quot;${student.getId()+'-'+student.getName()}&quot;&gt;&lt;/option&gt;
&lt;/datalist&gt;
&lt;/div&gt;
&lt;button type=&quot;submit&quot; class=&quot;btn btn-default&quot;&gt;添加记录&lt;/button&gt;
&lt;/form&gt;
&lt;/div&gt;
&lt;/div&gt;
</div>
&lt;/div&gt;
&lt;div class=&quot;col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main&quot;&gt;
&lt;h2 class=&quot;sub-header&quot;&gt;添加借阅记录&lt;/h2&gt;
&lt;p th:if=&quot;${warning}&quot; style=&quot;color: red&quot;&gt;该书暂时不可借阅&lt;/p&gt;
&lt;p th:if=&quot;${incorrect}&quot; style=&quot;color: red&quot;&gt;输入的信息不合法&lt;/p&gt;
&lt;p th:if=&quot;${noId}&quot; style=&quot;color: red&quot;&gt;输入项不能为空&lt;/p&gt;
&lt;form class=&quot;form-inline&quot; method=&quot;post&quot; action=&quot;add-borrow&quot;&gt;
&lt;div class=&quot;form-group&quot;&gt;
&lt;label for=&quot;book&quot;&gt;书籍编号&lt;/label&gt;
&lt;input name=&quot;bookName&quot; list=&quot;bookList&quot; type=&quot;text&quot; class=&quot;form-control&quot; id=&quot;book&quot;
placeholder=&quot;降龙十八掌&quot;&gt;
&lt;datalist id=&quot;bookList&quot;&gt;
&lt;option th:each=&quot;book: ${books}&quot; th:value=&quot;${book.getId()+'-'+book.getName()}&quot;&gt;&lt;/option&gt;
&lt;/datalist&gt;
&lt;/div&gt;
&lt;div class=&quot;form-group&quot;&gt;
&lt;label for=&quot;student&quot;&gt;学号&lt;/label&gt;
&lt;input name=&quot;studentName&quot; list=&quot;studentList&quot; type=&quot;text&quot; class=&quot;form-control&quot; id=&quot;student&quot;
placeholder=&quot;乔峰&quot;&gt;
&lt;datalist id=&quot;studentList&quot;&gt;
&lt;option th:each=&quot;student:${students}&quot;
th:value=&quot;${student.getId()+'-'+student.getName()}&quot;&gt;&lt;/option&gt;
&lt;/datalist&gt;
&lt;/div&gt;
&lt;button type=&quot;submit&quot; class=&quot;btn btn-default&quot;&gt;添加记录&lt;/button&gt;
&lt;/form&gt;
&lt;/div&gt;
&lt;/div&gt;
</body>
</html>

接着在src/main/java/com/book/servlet/action/下创建AddBorrowServlet继承HttpServlet并添加注解@WebServlet("/add-borrow")。

AddBorrowServlet内容

@WebServlet("/add-borrow")
public class AddBorrowServlet extends HttpServlet {
BookService bookService;
StudentService studentService;
BorrowService borrowService;
@Override
public void init() throws ServletException {
bookService = new BookServiceImpl();
studentService = new StudentServiceImpl();
borrowService = new BorrowServiceImpl();
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Context context = new Context();
List&lt;Student&gt; students = studentService.listStudents();
List&lt;Book&gt; books = bookService.listExistBooks();
User user = (User)req.getSession().getAttribute(&quot;user&quot;);
if(req.getSession().getAttribute(&quot;warning&quot;) != null){
// 将session中warning标志传递到context
context.setVariable(&quot;warning&quot;, req.getSession().getAttribute(&quot;warning&quot;));
req.getSession().removeAttribute(&quot;warning&quot;);
}
if (req.getSession().getAttribute(&quot;incorrect&quot;)!=null){
// 将session中incorrect标志传递到context
context.setVariable(&quot;incorrect&quot;, 1);
req.getSession().removeAttribute(&quot;incorrect&quot;);
}
if(req.getSession().getAttribute(&quot;noId&quot;) != null){
// 将session中noId标志传递到context
context.setVariable(&quot;noId&quot;, req.getSession().getAttribute(&quot;noId&quot;));
req.getSession().removeAttribute(&quot;noId&quot;);
}
context.setVariable(&quot;students&quot;, students);
context.setVariable(&quot;books&quot;, books);
context.setVariable(&quot;nickName&quot;, user.getNickname());
ThymeleafUtil.process(&quot;add_borrow.html&quot;, context, resp.getWriter());
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String bookName = req.getParameter(&quot;bookName&quot;);
String studentName = req.getParameter(&quot;studentName&quot;);
if(!bookName.isEmpty() &amp;&amp; !studentName.isEmpty()){
// 判断提交的表单不为空
try{
// 判断提交的信息是否合法
int bookId = Integer.valueOf(bookName.split(&quot;-&quot;)[0]);
int studentId = Integer.valueOf(studentName.split(&quot;-&quot;)[0]);
if(bookService.selectBookById(Integer.valueOf(bookId))!=null &amp;&amp;
studentService.getById(Integer.valueOf(studentId))!=null){
// 判断书籍和学生存在
if(bookService.getNumberById(Integer.valueOf(bookId))&gt;0){
// 判断该书数量&gt;0
borrowService.insertBorrow(LocalDateTime.now(), Integer.valueOf(bookId), Integer.valueOf(studentId)); // 插入借阅记录
bookService.minusBookNumber(Integer.valueOf(bookId)); // 根据id,书数量-1
resp.sendRedirect(&quot;index&quot;);
return;
}else {
req.getSession().setAttribute(&quot;warning&quot;, 1); // 向session中添加书籍数量为0警告
}
}else {
req.getSession().setAttribute(&quot;incorrect&quot;, 1); // 向session中添加输入不合法标志
}
}catch (Exception e){
req.getSession().setAttribute(&quot;incorrect&quot;, 1); // 向session中添加输入不合法标志
}
}else {
req.getSession().setAttribute(&quot;noId&quot;, 1); // 向session中添加无Id标志
}
resp.sendRedirect(&quot;add-borrow&quot;);
}
@Override
public void init() throws ServletException {
bookService = new BookServiceImpl();
studentService = new StudentServiceImpl();
borrowService = new BorrowServiceImpl();
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Context context = new Context();
List&lt;Student&gt; students = studentService.listStudents();
List&lt;Book&gt; books = bookService.listExistBooks();
User user = (User)req.getSession().getAttribute(&quot;user&quot;);
if(req.getSession().getAttribute(&quot;warning&quot;) != null){
// 将session中warning标志传递到context
context.setVariable(&quot;warning&quot;, req.getSession().getAttribute(&quot;warning&quot;));
req.getSession().removeAttribute(&quot;warning&quot;);
}
if (req.getSession().getAttribute(&quot;incorrect&quot;)!=null){
// 将session中incorrect标志传递到context
context.setVariable(&quot;incorrect&quot;, 1);
req.getSession().removeAttribute(&quot;incorrect&quot;);
}
if(req.getSession().getAttribute(&quot;noId&quot;) != null){
// 将session中noId标志传递到context
context.setVariable(&quot;noId&quot;, req.getSession().getAttribute(&quot;noId&quot;));
req.getSession().removeAttribute(&quot;noId&quot;);
}
context.setVariable(&quot;students&quot;, students);
context.setVariable(&quot;books&quot;, books);
context.setVariable(&quot;nickName&quot;, user.getNickname());
ThymeleafUtil.process(&quot;add_borrow.html&quot;, context, resp.getWriter());
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String bookName = req.getParameter(&quot;bookName&quot;);
String studentName = req.getParameter(&quot;studentName&quot;);
if(!bookName.isEmpty() &amp;&amp; !studentName.isEmpty()){
// 判断提交的表单不为空
try{
// 判断提交的信息是否合法
int bookId = Integer.valueOf(bookName.split(&quot;-&quot;)[0]);
int studentId = Integer.valueOf(studentName.split(&quot;-&quot;)[0]);
if(bookService.selectBookById(Integer.valueOf(bookId))!=null &amp;&amp;
studentService.getById(Integer.valueOf(studentId))!=null){
// 判断书籍和学生存在
if(bookService.getNumberById(Integer.valueOf(bookId))&gt;0){
// 判断该书数量&gt;0
borrowService.insertBorrow(LocalDateTime.now(), Integer.valueOf(bookId), Integer.valueOf(studentId)); // 插入借阅记录
bookService.minusBookNumber(Integer.valueOf(bookId)); // 根据id,书数量-1
resp.sendRedirect(&quot;index&quot;);
return;
}else {
req.getSession().setAttribute(&quot;warning&quot;, 1); // 向session中添加书籍数量为0警告
}
}else {
req.getSession().setAttribute(&quot;incorrect&quot;, 1); // 向session中添加输入不合法标志
}
}catch (Exception e){
req.getSession().setAttribute(&quot;incorrect&quot;, 1); // 向session中添加输入不合法标志
}
}else {
req.getSession().setAttribute(&quot;noId&quot;, 1); // 向session中添加无Id标志
}
resp.sendRedirect(&quot;add-borrow&quot;);
}
}

重写init()方法,初始化业务类。重写doGet()方法,通过Thymeleaf返回添加borrow的页面。重写doPost()方法,将添加borrow的表单数据接收并插入到数据表中。

此外做了必要的检查,防止:

  • 添加借阅记录的表单内容为空
  • 添加借阅记录的表单内容不合法(如未提交书籍编号和学号或是提交的书籍编号或学号不存在)
  • 添加的借阅记录中书籍的数量为0

至此我们的添加借阅记录功能开发完毕,且该程序十分健壮。

image-20231031224312197

删除借阅记录功能开发

删除借阅记录功能的前端部分已包含在index.html页面中,该a标签被做成按钮的模样,点击后会携带该行借阅记录的id访问对应删除借阅记录的url

<span>
<a th:href="'delete-borrow?id='+${borrow.getId()}" class="btn btn-xs btn-danger" >删除</a>
</span>

删除借阅记录的表现层开发,在src/mian/java/com/book/servlet/action下创建DeleteBorrowServlet类继承HttpServlet并添加@WebServlet("/delete-borrow")注解,内容如下

@WebServlet("/delete-borrow")
public class DeleteBorrowServlet extends HttpServlet {
BorrowService borrowService;
@Override
public void init() throws ServletException {
borrowService = new BorrowServiceImpl();
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String id = req.getParameter(&quot;id&quot;);
borrowService.deleteBorrowById(Integer.valueOf(id));
resp.sendRedirect(&quot;index&quot;);
}
@Override
public void init() throws ServletException {
borrowService = new BorrowServiceImpl();
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String id = req.getParameter(&quot;id&quot;);
borrowService.deleteBorrowById(Integer.valueOf(id));
resp.sendRedirect(&quot;index&quot;);
}
}

学生列表页面开发

先预览页面成品

image-20231101124917304

学生列表页面的功能十分简单,仅仅是显示数据库中的学生学号和姓名。

关于学生相关的持久层与业务层的开发已在上文交代过,这里不再赘述,直接进行前端页面和表现层的开发。在src/main/resources/下创建students.html,内容如下

<!DOCTYPE html>
<html lang="en" th:replace="~{layout.html::layout(~{:: #content})}">
<body>
<div class="container-fluid" id="content">
<div class="row">
<div class="col-sm-3 col-md-2 sidebar">
<ul class="nav nav-sidebar">
<li><a href="index">借阅信息</a></li>
<li class="active"><a href="students">学生列表</a></li>
<li><a href="books">书籍列表</a></li>
</ul>
&lt;/div&gt;
&lt;div class=&quot;col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main&quot;&gt;
&lt;h2 class=&quot;sub-header&quot;&gt;学生列表&lt;/h2&gt;
&lt;div class=&quot;table-responsive&quot;&gt;
&lt;table class=&quot;table table-striped&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;学号&lt;/th&gt;
&lt;th&gt;姓名&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr th:each=&quot;student:${studentList}&quot;&gt;
&lt;td th:text=&quot;${student.getId()}&quot;&gt;学号&lt;/td&gt;
&lt;td th:text=&quot;${student.getName()}&quot;&gt;姓名&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
</div>
</body>
&lt;/div&gt;
&lt;div class=&quot;col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main&quot;&gt;
&lt;h2 class=&quot;sub-header&quot;&gt;学生列表&lt;/h2&gt;
&lt;div class=&quot;table-responsive&quot;&gt;
&lt;table class=&quot;table table-striped&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;学号&lt;/th&gt;
&lt;th&gt;姓名&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr th:each=&quot;student:${studentList}&quot;&gt;
&lt;td th:text=&quot;${student.getId()}&quot;&gt;学号&lt;/td&gt;
&lt;td th:text=&quot;${student.getName()}&quot;&gt;姓名&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
</html>

然后在src/main/java/com/book/manage/包下创建StudentServlet类继承HttpServlet并添加@WebServlet("/students")注解,以下是StudentServlet的内容

@WebServlet("/students")
public class StudentServlet extends HttpServlet {
StudentService studentService;
@Override
public void init() throws ServletException {
studentService = new StudentServiceImpl();
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Context context = new Context();
User user = (User)req.getSession().getAttribute(&quot;user&quot;);
String nickName = user.getNickname();
List&lt;Student&gt; studentList = studentService.listStudents();
context.setVariable(&quot;nickName&quot;, nickName);
context.setVariable(&quot;studentList&quot;,studentList);
ThymeleafUtil.process(&quot;students.html&quot;, context, resp.getWriter());
}
@Override
public void init() throws ServletException {
studentService = new StudentServiceImpl();
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Context context = new Context();
User user = (User)req.getSession().getAttribute(&quot;user&quot;);
String nickName = user.getNickname();
List&lt;Student&gt; studentList = studentService.listStudents();
context.setVariable(&quot;nickName&quot;, nickName);
context.setVariable(&quot;studentList&quot;,studentList);
ThymeleafUtil.process(&quot;students.html&quot;, context, resp.getWriter());
}
}

没什么好说的,初始化业务类对象,获取学生信息存入Context对象,然后通过Thymeleaf处理前端页面并返回给浏览器。

书籍列表页面开发

先预览页面成品

image-20231101131456174

可以看出,该页面包含了显示书籍列表,添加书目,删除书目,修改书籍数量四项功能,其中显示书籍列表和删除书目都在同一个html页面中完成。添加书目和修改书籍数量分别用一个单独页面去完成。

显示书籍列表和删除书目

src/main/resources下创建books.html,内容如下

<!DOCTYPE html>
<html lang="en" th:replace="~{layout.html::layout(~{:: #content})}">
<body>
<div class="container-fluid" id="content">
<div class="row">
<div class="col-sm-3 col-md-2 sidebar">
<ul class="nav nav-sidebar">
<li><a href="index">借阅信息</a></li>
<li><a href="students">学生列表</a></li>
<li class="active"><a href="books">书籍列表</a></li>
</ul>
&lt;/div&gt;
&lt;div class=&quot;col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main&quot;&gt;
&lt;h2 class=&quot;sub-header&quot;&gt;书籍列表&lt;/h2&gt;
&lt;div class=&quot;btn-group&quot; role=&quot;group&quot; aria-label=&quot;...&quot;&gt;
&lt;a href=&quot;add-book&quot; class=&quot;btn btn-success&quot;&gt;添加书目&lt;/a&gt;
&lt;a href=&quot;modify-book-number&quot; class=&quot;btn btn-primary&quot;&gt;修改书籍数量&lt;/a&gt;
&lt;/div&gt;
&lt;div class=&quot;table-responsive&quot;&gt;
&lt;table class=&quot;table table-striped&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;书籍编号&lt;/th&gt;
&lt;th&gt;书名&lt;/th&gt;
&lt;th&gt;剩余数量&lt;/th&gt;
&lt;th&gt;操作&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr th:each=&quot;book:${books}&quot;&gt;
&lt;td th:text=&quot;${book.getId()}&quot;&gt;书id&lt;/td&gt;
&lt;td th:text=&quot;${book.getName()}&quot;&gt;书名&lt;/td&gt;
&lt;td th:text=&quot;${book.getBookNumber()}&quot;&gt;剩余数量&lt;/td&gt;
&lt;td&gt;
&lt;span&gt;
&lt;a th:href=&quot;'delete-book?id='+${book.getId()}&quot; class=&quot;btn btn-xs btn-danger&quot; &gt;删除&lt;/a&gt;
&lt;/span&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
</div>
</body>
&lt;/div&gt;
&lt;div class=&quot;col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main&quot;&gt;
&lt;h2 class=&quot;sub-header&quot;&gt;书籍列表&lt;/h2&gt;
&lt;div class=&quot;btn-group&quot; role=&quot;group&quot; aria-label=&quot;...&quot;&gt;
&lt;a href=&quot;add-book&quot; class=&quot;btn btn-success&quot;&gt;添加书目&lt;/a&gt;
&lt;a href=&quot;modify-book-number&quot; class=&quot;btn btn-primary&quot;&gt;修改书籍数量&lt;/a&gt;
&lt;/div&gt;
&lt;div class=&quot;table-responsive&quot;&gt;
&lt;table class=&quot;table table-striped&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;书籍编号&lt;/th&gt;
&lt;th&gt;书名&lt;/th&gt;
&lt;th&gt;剩余数量&lt;/th&gt;
&lt;th&gt;操作&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr th:each=&quot;book:${books}&quot;&gt;
&lt;td th:text=&quot;${book.getId()}&quot;&gt;书id&lt;/td&gt;
&lt;td th:text=&quot;${book.getName()}&quot;&gt;书名&lt;/td&gt;
&lt;td th:text=&quot;${book.getBookNumber()}&quot;&gt;剩余数量&lt;/td&gt;
&lt;td&gt;
&lt;span&gt;
&lt;a th:href=&quot;'delete-book?id='+${book.getId()}&quot; class=&quot;btn btn-xs btn-danger&quot; &gt;删除&lt;/a&gt;
&lt;/span&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
</html>

其中,删除书目的功能由包含在books.html的如下语句完成

<span>
<a th:href="'delete-book?id='+${book.getId()}" class="btn btn-xs btn-danger" >删除</a>
</span>

书籍列表展示的表现层代码如下,在src/mian/java/com/book/servlet/manage包中创建BookServlet类

@WebServlet("/books")
public class BookServlet extends HttpServlet {
BookService bookService;
@Override
public void init() throws ServletException {
bookService = new BookServiceImpl();
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Context context = new Context();
User user = (User)req.getSession().getAttribute(&quot;user&quot;);
String nickName = user.getNickname();
context.setVariable(&quot;nickName&quot;, nickName);
List&lt;Book&gt; books = bookService.listBooks();
context.setVariable(&quot;books&quot;, books);
ThymeleafUtil.process(&quot;books.html&quot;, context, resp.getWriter());
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Context context = new Context();
User user = (User)req.getSession().getAttribute(&quot;user&quot;);
String nickName = user.getNickname();
context.setVariable(&quot;nickName&quot;, nickName);
List&lt;Book&gt; books = bookService.listBooks();
context.setVariable(&quot;books&quot;, books);
ThymeleafUtil.process(&quot;books.html&quot;, context, resp.getWriter());
}
}

删除书目的表现层代码如下,在src/mian/java/com/book/servlet/action包中创建DeleteBookServlet类

@WebServlet("/delete-book")
public class DeleteBookServlet extends HttpServlet {
BookService bookService;
@Override
public void init() throws ServletException {
bookService = new BookServiceImpl();
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String id = req.getParameter(&quot;id&quot;);
bookService.deleteById(Integer.valueOf(id));
resp.sendRedirect(&quot;books&quot;);
}
@Override
public void init() throws ServletException {
bookService = new BookServiceImpl();
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String id = req.getParameter(&quot;id&quot;);
bookService.deleteById(Integer.valueOf(id));
resp.sendRedirect(&quot;books&quot;);
}
}

添加书目

预览添加书目页面

image-20231101134742738

前端页面开发,在src/mian/resources下创建add_book.html,内容如下

<!DOCTYPE html>
<html lang="en" th:replace="~{layout.html::layout(~{:: #content})}" xmlns:th="http://www.thymeleaf.org">
<body>
<div id="content">
<div class="row">
<div class="col-sm-3 col-md-2 sidebar">
<ul class="nav nav-sidebar">
<li><a href="index">借阅信息</a></li>
<li><a href="students">学生列表</a></li>
<li class="active"><a href="books">书籍列表</a></li>
</ul>
&lt;/div&gt;
&lt;div class=&quot;col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main&quot;&gt;
&lt;h2 class=&quot;sub-header&quot;&gt;添加书目&lt;/h2&gt;
&lt;p th:if=&quot;${exist}&quot; style=&quot;color: red&quot;&gt;该书目编号已使用&lt;/p&gt;
&lt;p th:if=&quot;${warning}&quot; style=&quot;color: red&quot;&gt;书目信息不能为空&lt;/p&gt;
&lt;form class=&quot;form-inline&quot; method=&quot;post&quot; action=&quot;add-book&quot;&gt;
&lt;div class=&quot;form-group&quot;&gt;
&lt;label for=&quot;bookId&quot;&gt;书目编号&lt;/label&gt;
&lt;input name=&quot;bookId&quot; type=&quot;text&quot; class=&quot;form-control&quot; id=&quot;bookId&quot; placeholder=&quot;1&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;form-group&quot;&gt;
&lt;label for=&quot;bookName&quot;&gt;书名&lt;/label&gt;
&lt;input name=&quot;bookName&quot; type=&quot;text&quot; class=&quot;form-control&quot; id=&quot;bookName&quot;
placeholder=&quot;降龙十八掌&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;form-group&quot;&gt;
&lt;label for=&quot;bookNumber&quot;&gt;数目&lt;/label&gt;
&lt;input name=&quot;bookNumber&quot; type=&quot;text&quot; class=&quot;form-control&quot; id=&quot;bookNumber&quot;
placeholder=&quot;3&quot;&gt;
&lt;/div&gt;
&lt;button type=&quot;submit&quot; class=&quot;btn btn-default&quot;&gt;添加书籍&lt;/button&gt;
&lt;/form&gt;
&lt;/div&gt;
&lt;/div&gt;
</div>
&lt;/div&gt;
&lt;div class=&quot;col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main&quot;&gt;
&lt;h2 class=&quot;sub-header&quot;&gt;添加书目&lt;/h2&gt;
&lt;p th:if=&quot;${exist}&quot; style=&quot;color: red&quot;&gt;该书目编号已使用&lt;/p&gt;
&lt;p th:if=&quot;${warning}&quot; style=&quot;color: red&quot;&gt;书目信息不能为空&lt;/p&gt;
&lt;form class=&quot;form-inline&quot; method=&quot;post&quot; action=&quot;add-book&quot;&gt;
&lt;div class=&quot;form-group&quot;&gt;
&lt;label for=&quot;bookId&quot;&gt;书目编号&lt;/label&gt;
&lt;input name=&quot;bookId&quot; type=&quot;text&quot; class=&quot;form-control&quot; id=&quot;bookId&quot; placeholder=&quot;1&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;form-group&quot;&gt;
&lt;label for=&quot;bookName&quot;&gt;书名&lt;/label&gt;
&lt;input name=&quot;bookName&quot; type=&quot;text&quot; class=&quot;form-control&quot; id=&quot;bookName&quot;
placeholder=&quot;降龙十八掌&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;form-group&quot;&gt;
&lt;label for=&quot;bookNumber&quot;&gt;数目&lt;/label&gt;
&lt;input name=&quot;bookNumber&quot; type=&quot;text&quot; class=&quot;form-control&quot; id=&quot;bookNumber&quot;
placeholder=&quot;3&quot;&gt;
&lt;/div&gt;
&lt;button type=&quot;submit&quot; class=&quot;btn btn-default&quot;&gt;添加书籍&lt;/button&gt;
&lt;/form&gt;
&lt;/div&gt;
&lt;/div&gt;
</body>
</html>

后端持久层与业务层的开发都已在上文提到过,这里只讲表现层,在src/mian/java/com/book/servlet/action下创建AddBookServlet继承HttpServlet并添加@WebServlet("/add-book")注解,内容如下

@WebServlet("/add-book")
public class AddBookServlet extends HttpServlet {
BookService bookService;
@Override
public void init() throws ServletException {
bookService = new BookServiceImpl();
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Context context = new Context();
User user = (User) req.getSession().getAttribute(&quot;user&quot;);
if (req.getSession().getAttribute(&quot;exist&quot;) != null) {
context.setVariable(&quot;exist&quot;,1);
req.getSession().removeAttribute(&quot;exist&quot;);
}
if (req.getSession().getAttribute(&quot;warning&quot;) != null){
context.setVariable(&quot;warning&quot;,1);
req.getSession().removeAttribute(&quot;warning&quot;);
}
context.setVariable(&quot;nickName&quot;,user.getNickname());
ThymeleafUtil.process(&quot;add_book.html&quot;, context, resp.getWriter());
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
if(!req.getParameter(&quot;bookId&quot;).isEmpty() &amp; !req.getParameter(&quot;bookName&quot;).isEmpty() &amp; !req.getParameter(&quot;bookNumber&quot;).isEmpty()){
String bookId = req.getParameter(&quot;bookId&quot;);
String bookName = req.getParameter(&quot;bookName&quot;);
String bookNumber = req.getParameter(&quot;bookNumber&quot;);
if (bookService.selectBookById(Integer.valueOf(bookId)) != null) {
req.getSession().setAttribute(&quot;exist&quot;, 1);
resp.sendRedirect(&quot;add-book&quot;);
return;
}
bookService.insertBook(new Book(Integer.valueOf(bookId), bookName, Integer.valueOf(bookNumber)));
resp.sendRedirect(&quot;books&quot;);
}else {
req.getSession().setAttribute(&quot;warning&quot;, 1);
resp.sendRedirect(&quot;add-book&quot;);
}
}
@Override
public void init() throws ServletException {
bookService = new BookServiceImpl();
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Context context = new Context();
User user = (User) req.getSession().getAttribute(&quot;user&quot;);
if (req.getSession().getAttribute(&quot;exist&quot;) != null) {
context.setVariable(&quot;exist&quot;,1);
req.getSession().removeAttribute(&quot;exist&quot;);
}
if (req.getSession().getAttribute(&quot;warning&quot;) != null){
context.setVariable(&quot;warning&quot;,1);
req.getSession().removeAttribute(&quot;warning&quot;);
}
context.setVariable(&quot;nickName&quot;,user.getNickname());
ThymeleafUtil.process(&quot;add_book.html&quot;, context, resp.getWriter());
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
if(!req.getParameter(&quot;bookId&quot;).isEmpty() &amp; !req.getParameter(&quot;bookName&quot;).isEmpty() &amp; !req.getParameter(&quot;bookNumber&quot;).isEmpty()){
String bookId = req.getParameter(&quot;bookId&quot;);
String bookName = req.getParameter(&quot;bookName&quot;);
String bookNumber = req.getParameter(&quot;bookNumber&quot;);
if (bookService.selectBookById(Integer.valueOf(bookId)) != null) {
req.getSession().setAttribute(&quot;exist&quot;, 1);
resp.sendRedirect(&quot;add-book&quot;);
return;
}
bookService.insertBook(new Book(Integer.valueOf(bookId), bookName, Integer.valueOf(bookNumber)));
resp.sendRedirect(&quot;books&quot;);
}else {
req.getSession().setAttribute(&quot;warning&quot;, 1);
resp.sendRedirect(&quot;add-book&quot;);
}
}
}

修改书籍数量

页面预览

image-20231101135705745

这里书籍名称使用的是单选框,而不是像添加借阅记录页面中使用输入框和选择框组合,显然书籍数量一旦多起来,单选框就会很不适用,因此这里有很大的改进空间,不过暂时就这样了。

接下来是修改书籍数量的前端页面开发,在src/main/resources下创建modify_book_number.html,内容如下

<!DOCTYPE html>
<html lang="en" th:replace="~{layout.html::layout(~{:: #content})}" xmlns:th="http://www.thymeleaf.org">
<body>
<div id="content">
<div class="row">
<div class="col-sm-3 col-md-2 sidebar">
<ul class="nav nav-sidebar">
<li><a href="index">借阅信息</a></li>
<li><a href="students">学生列表</a></li>
<li class="active"><a href="books">书籍列表</a></li>
</ul>
&lt;/div&gt;
&lt;div class=&quot;col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main&quot;&gt;
&lt;h2 class=&quot;sub-header&quot;&gt;修改书籍数量&lt;/h2&gt;
&lt;p th:if=&quot;${warning}&quot; style=&quot;color: red&quot;&gt;书籍数量不能为空&lt;/p&gt;
&lt;form class=&quot;form-inline&quot; method=&quot;post&quot; action=&quot;modify-book-number&quot;&gt;
&lt;div class=&quot;form-group&quot;&gt;
&lt;label for=&quot;bookName&quot;&gt;书籍名称&lt;/label&gt;
&lt;select id=&quot;bookName&quot; class=&quot;form-control&quot; name=&quot;bookId&quot;&gt;
&lt;option th:each=&quot;book:${books}&quot; th:text=&quot;${book.getName()}&quot; th:value=&quot;${book.getId()}&quot;&gt;&lt;/option&gt;
&lt;/select&gt;
&lt;/div&gt;
&lt;div class=&quot;form-group&quot;&gt;
&lt;label for=&quot;bookNumber&quot;&gt;数量&lt;/label&gt;
&lt;input name=&quot;bookNumber&quot; type=&quot;text&quot; class=&quot;form-control&quot; id=&quot;bookNumber&quot;
placeholder=&quot;3&quot;&gt;
&lt;/div&gt;
&lt;button type=&quot;submit&quot; class=&quot;btn btn-default&quot;&gt;添加修改&lt;/button&gt;
&lt;/form&gt;
&lt;/div&gt;
&lt;/div&gt;
</div>
&lt;/div&gt;
&lt;div class=&quot;col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main&quot;&gt;
&lt;h2 class=&quot;sub-header&quot;&gt;修改书籍数量&lt;/h2&gt;
&lt;p th:if=&quot;${warning}&quot; style=&quot;color: red&quot;&gt;书籍数量不能为空&lt;/p&gt;
&lt;form class=&quot;form-inline&quot; method=&quot;post&quot; action=&quot;modify-book-number&quot;&gt;
&lt;div class=&quot;form-group&quot;&gt;
&lt;label for=&quot;bookName&quot;&gt;书籍名称&lt;/label&gt;
&lt;select id=&quot;bookName&quot; class=&quot;form-control&quot; name=&quot;bookId&quot;&gt;
&lt;option th:each=&quot;book:${books}&quot; th:text=&quot;${book.getName()}&quot; th:value=&quot;${book.getId()}&quot;&gt;&lt;/option&gt;
&lt;/select&gt;
&lt;/div&gt;
&lt;div class=&quot;form-group&quot;&gt;
&lt;label for=&quot;bookNumber&quot;&gt;数量&lt;/label&gt;
&lt;input name=&quot;bookNumber&quot; type=&quot;text&quot; class=&quot;form-control&quot; id=&quot;bookNumber&quot;
placeholder=&quot;3&quot;&gt;
&lt;/div&gt;
&lt;button type=&quot;submit&quot; class=&quot;btn btn-default&quot;&gt;添加修改&lt;/button&gt;
&lt;/form&gt;
&lt;/div&gt;
&lt;/div&gt;
</body>
</html>

然后是修改书籍数量的后端表现层代码开发,在src/mian/java/com/book/servlet/action下创建ModifyBookNumber类继承ModifyBookNumber并添加@WebServlet("/modify-book-number")注解,内容如下

@WebServlet("/modify-book-number")
public class ModifyBookNumber extends HttpServlet {
BookService bookService;
@Override
public void init() throws ServletException {
bookService = new BookServiceImpl();
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Context context = new Context();
List&lt;Book&gt; books = bookService.listBooks();
User user = (User) req.getSession().getAttribute(&quot;user&quot;);
if(req.getSession().getAttribute(&quot;warning&quot;)!=null){
context.setVariable(&quot;warning&quot;, 1);
req.getSession().removeAttribute(&quot;warning&quot;);
}
context.setVariable(&quot;books&quot;, books);
context.setVariable(&quot;nickName&quot;, user.getNickname());
ThymeleafUtil.process(&quot;modify_book_number.html&quot;, context, resp.getWriter());
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
if(!req.getParameter(&quot;bookId&quot;).isEmpty() &amp;&amp; !req.getParameter(&quot;bookNumber&quot;).isEmpty()){
String bookId = req.getParameter(&quot;bookId&quot;);
String bookNumber = req.getParameter(&quot;bookNumber&quot;);
bookService.modifyBookNumber(Integer.valueOf(bookId), Integer.valueOf(bookNumber));
resp.sendRedirect(&quot;books&quot;);
}else {
req.getSession().setAttribute(&quot;warning&quot;, 1);
resp.sendRedirect(&quot;modify-book-number&quot;);
}
}
@Override
public void init() throws ServletException {
bookService = new BookServiceImpl();
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Context context = new Context();
List&lt;Book&gt; books = bookService.listBooks();
User user = (User) req.getSession().getAttribute(&quot;user&quot;);
if(req.getSession().getAttribute(&quot;warning&quot;)!=null){
context.setVariable(&quot;warning&quot;, 1);
req.getSession().removeAttribute(&quot;warning&quot;);
}
context.setVariable(&quot;books&quot;, books);
context.setVariable(&quot;nickName&quot;, user.getNickname());
ThymeleafUtil.process(&quot;modify_book_number.html&quot;, context, resp.getWriter());
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
if(!req.getParameter(&quot;bookId&quot;).isEmpty() &amp;&amp; !req.getParameter(&quot;bookNumber&quot;).isEmpty()){
String bookId = req.getParameter(&quot;bookId&quot;);
String bookNumber = req.getParameter(&quot;bookNumber&quot;);
bookService.modifyBookNumber(Integer.valueOf(bookId), Integer.valueOf(bookNumber));
resp.sendRedirect(&quot;books&quot;);
}else {
req.getSession().setAttribute(&quot;warning&quot;, 1);
resp.sendRedirect(&quot;modify-book-number&quot;);
}
}
}

最后调整

关于本项目的功能基本开发完成,不过登录时的记住我功能还未开发,以及导航栏的退出登录功能。

记住我功能开发

对LoginServlet进行调整

@WebServlet("/login")
public class LoginServlet extends HttpServlet {
UserService service;
@Override
public void init() throws ServletException {
service = new UserServiceImpl(); // 初始化时创建UserService实现类的对象
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
if(req.getCookies()!=null){
String username = null;
String password = null;
for (Cookie cookie : req.getCookies()) {
if (cookie.getName().equals(&quot;username&quot;)) username = cookie.getValue();
if (cookie.getName().equals(&quot;password&quot;)) password = cookie.getValue();
}
if (service.auth(username,password, req.getSession())){
resp.sendRedirect(&quot;index&quot;);
return;
}
}
Context context = new Context();
if(req.getSession().getAttribute(&quot;login-failure&quot;) != null){
context.setVariable(&quot;failure&quot;, true);
req.getSession().removeAttribute(&quot;login-failure&quot;);
}
ThymeleafUtil.process(&quot;login.html&quot;,context, resp.getWriter());
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String username = req.getParameter(&quot;username&quot;);
String password = req.getParameter(&quot;password&quot;);
if(service.auth(username,password, req.getSession())){
if (req.getParameter(&quot;remember&quot;)!=null){
Cookie usernameCookie = new Cookie(&quot;username&quot;, username);
Cookie passwordCookie = new Cookie(&quot;password&quot;, password);
usernameCookie.setMaxAge(60*60*24*7); // 7天
passwordCookie.setMaxAge(60*60*24*7);
resp.addCookie(usernameCookie);
resp.addCookie(passwordCookie);
}
resp.sendRedirect(&quot;index&quot;);
return;
}
req.getSession().setAttribute(&quot;login-failure&quot;, 1);
this.doGet(req, resp);
}
UserService service;
@Override
public void init() throws ServletException {
service = new UserServiceImpl(); // 初始化时创建UserService实现类的对象
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
if(req.getCookies()!=null){
String username = null;
String password = null;
for (Cookie cookie : req.getCookies()) {
if (cookie.getName().equals(&quot;username&quot;)) username = cookie.getValue();
if (cookie.getName().equals(&quot;password&quot;)) password = cookie.getValue();
}
if (service.auth(username,password, req.getSession())){
resp.sendRedirect(&quot;index&quot;);
return;
}
}
Context context = new Context();
if(req.getSession().getAttribute(&quot;login-failure&quot;) != null){
context.setVariable(&quot;failure&quot;, true);
req.getSession().removeAttribute(&quot;login-failure&quot;);
}
ThymeleafUtil.process(&quot;login.html&quot;,context, resp.getWriter());
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String username = req.getParameter(&quot;username&quot;);
String password = req.getParameter(&quot;password&quot;);
if(service.auth(username,password, req.getSession())){
if (req.getParameter(&quot;remember&quot;)!=null){
Cookie usernameCookie = new Cookie(&quot;username&quot;, username);
Cookie passwordCookie = new Cookie(&quot;password&quot;, password);
usernameCookie.setMaxAge(60*60*24*7); // 7天
passwordCookie.setMaxAge(60*60*24*7);
resp.addCookie(usernameCookie);
resp.addCookie(passwordCookie);
}
resp.sendRedirect(&quot;index&quot;);
return;
}
req.getSession().setAttribute(&quot;login-failure&quot;, 1);
this.doGet(req, resp);
}
}

主要调整的部分在于,doPost()方法中,判断用户是否勾选了“记住我”复选框,若是用户勾选了“记住我”并且成功登录,就将用户的用户名和密码存储到Cookie中,有效期为7天。doGet()方法中,判断浏览器请求是否携带Cookie,若携带Cookie就尝试从Cookie中提取用户名和密码,然后调用业务层逻辑判断用户名和密码是否能够通过登录,如果能,就自动登录并跳转到首页。

也就是说,用户点了“记住我”后,七天内访问本项目都会自动登录并跳转到首页。

退出登录功能开发

sec/main/java/com/book/servlet/auth下创建QuitServlet类继承HttpServlet并添加注解@WebServlet("/quit"),QuitServlet完整内容如下

@WebServlet("/quit")
public class QuitServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.getSession().removeAttribute("user");
Cookie cookie1 =new Cookie("username", "任意");
Cookie cookie2 =new Cookie("password", "任意");
cookie1.setMaxAge(0);
cookie2.setMaxAge(0);
resp.addCookie(cookie1);
resp.addCookie(cookie2);
// cookie无法直接清除,因此只能使用相同键的cookie覆盖
resp.sendRedirect("login");
}
}

而退出登录的前端内容之前就已经写在layout.html里面了,href="quit",访问注解为@WebServlet("/quit")的QuitServlet。

<li><a href="quit">退出登录</a></li>

用户点击退出登录,将清除用户的session和Cookie信息。用户将需要重新登录才能访问本项目网站。

至此,本项目结束。

最后,附上本项目的项目结构和项目地址。

项目树

src
├─main
│ ├─java
│ │ └─com
│ │ └─book
│ │ ├─dao
│ │ ├─entity
│ │ ├─filter
│ │ ├─service
│ │ │ └─impl
│ │ ├─servlet
│ │ │ ├─action
│ │ │ ├─auth
│ │ │ └─manage
│ │ └─utils
│ ├─resources
│ └─webapp
│ ├─static
│ │ ├─css
│ │ ├─html
│ │ └─img
│ └─WEB-INF
└─test
├─java
└─resources

项目地址

XRain/bookManage - 码云 - 开源中国 (gitee.com)

posted @   贰拾散人  阅读(5)  评论(0编辑  收藏  举报
编辑推荐:
· .NET Core 托管堆内存泄露/CPU异常的常见思路
· PostgreSQL 和 SQL Server 在统计信息维护中的关键差异
· C++代码改造为UTF-8编码问题的总结
· DeepSeek 解答了困扰我五年的技术问题
· 为什么说在企业级应用开发中,后端往往是效率杀手?
阅读排行:
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 推荐几款开源且免费的 .NET MAUI 组件库
· 实操Deepseek接入个人知识库
· 易语言 —— 开山篇
· Trae初体验
点击右上角即可分享
微信分享提示