基于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 框架设计页面。实现了用户的登录退出,以及对图书的增删改查和借阅功能。
创建项目
创建完成后,要将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> <groupId>com.book</groupId> <artifactId>bookManage</artifactId> <version>1.0-SNAPSHOT</version> <name>bookManage</name> <packaging>war</packaging> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.target>17</maven.compiler.target> <maven.compiler.source>17</maven.compiler.source> <junit.version>5.9.2</junit.version> </properties> <dependencies> <dependency> <groupId>jakarta.servlet</groupId> <artifactId>jakarta.servlet-api</artifactId> <version>5.0.0</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.thymeleaf</groupId> <artifactId>thymeleaf</artifactId> <version>3.0.12.RELEASE</version> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> <version>${junit.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-engine</artifactId> <version>${junit.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.28</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.13</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.28</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> <version>3.3.2</version> </plugin> </plugins> </build> <groupId>com.book</groupId> <artifactId>bookManage</artifactId> <version>1.0-SNAPSHOT</version> <name>bookManage</name> <packaging>war</packaging> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.target>17</maven.compiler.target> <maven.compiler.source>17</maven.compiler.source> <junit.version>5.9.2</junit.version> </properties> <dependencies> <dependency> <groupId>jakarta.servlet</groupId> <artifactId>jakarta.servlet-api</artifactId> <version>5.0.0</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.thymeleaf</groupId> <artifactId>thymeleaf</artifactId> <version>3.0.12.RELEASE</version> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> <version>${junit.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-engine</artifactId> <version>${junit.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.28</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.13</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.28</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> <version>3.3.2</version> </plugin> </plugins> </build> </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"> <form class="form-signin" method="post" action="login"> <h2 class="form-signin-heading" align="center"> 登录</h2> <p th:if="${failure}" style="color: red">用户名或密码错误</p> <label for="inputUsername" class="sr-only">username</label> <input type="text" name="username" id="inputUsername" class="form-control" placeholder="用户名" required autofocus> <label for="inputPassword" class="sr-only">Password</label> <input type="password" name="password" id="inputPassword" class="form-control" placeholder="密码" required> <div class="checkbox"> <label> <input type="checkbox" name="remember-me" value="remember-me"> 记住我 </label> </div> <button class="btn btn-lg btn-primary btn-block" type="submit">登录</button> </form> <form class="form-signin" method="post" action="login"> <h2 class="form-signin-heading" align="center"> 登录</h2> <p th:if="${failure}" style="color: red">用户名或密码错误</p> <label for="inputUsername" class="sr-only">username</label> <input type="text" name="username" id="inputUsername" class="form-control" placeholder="用户名" required autofocus> <label for="inputPassword" class="sr-only">Password</label> <input type="password" name="password" id="inputPassword" class="form-control" placeholder="密码" required> <div class="checkbox"> <label> <input type="checkbox" name="remember-me" value="remember-me"> 记住我 </label> </div> <button class="btn btn-lg btn-primary btn-block" type="submit">登录</button> </form> </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
添加一条用户信息,用于测试。
接下来开始正式开发
本项目遵循三层架构,持久层(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()响应给浏览器。
此时启动项目,可以看到登陆页面已经可以正确显示
如果未正确打开页面,检查服务器部署Url和服务器启动时自动打开的Url是否正确更改。
接下来,我们需要完成用户登录的操作,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("select * from admin where username = #{username} and password = #{password}") User getUser(@Param("username") String username, @Param("password") String password); } @Select("select * from admin where username = #{username} and password = #{password}") User getUser(@Param("username") String username, @Param("password") 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("login-failure") != null){ context.setVariable("failure", true); req.getSession().removeAttribute("login-failure"); } ThymeleafUtil.process("login.html",context, resp.getWriter()); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 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!"); return; } req.getSession().setAttribute("login-failure", 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("login-failure") != null){ context.setVariable("failure", true); req.getSession().removeAttribute("login-failure"); } ThymeleafUtil.process("login.html",context, resp.getWriter()); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 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!"); return; } req.getSession().setAttribute("login-failure", 1); this.doGet(req, resp); } }
首先将用户业务类对象的创建放到了初始化方法中,其次在登录失败后不是直接调用doGet跳转到登陆页面,而是携带一条登录失败的标志信息在session中,然后再调用doGet。
而在doGet方法中,会判断session中是否存在登录失败的信息,如果存在,则会往Context对象中添加一条登录失败的信息,并清除session中登陆失败的信息。Context是Thymeleaf中的一个类,该类创建的对象可以携带键值对信息(键是字符串,值可以是对象),Thymeleaf可以在html中解析Context对象中的信息。
而在html中,利用Thymeleaf模板语言,添加了一个只有在解析到登录失败的信息存在时才会显示的内容。
调整后,当登录失败时,会显示用户名或密码错误。
至此,用户登录部分开发完成!
过滤器
但是,我们可以注意到,我们平时浏览的一些需要登录的网站,如果我们没有登录,那么它会自动给我们跳转到登录页,因此,我们需要为该项目添加一个拦截器,使得我们的网站在用户没有登录的情况自动跳转到登录页面。
在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)让访问通过。
借阅信息页面开发(同时也是首页)
先看页面完成后的样子
可以看出,页面包含导航栏,侧边栏,以及借阅信息页面。其中,导航栏和侧边栏是固定存在于每个页面的,因此我们要利用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> </div> </nav> <div th:replace="${content}"> 内容插入处 </div> </div> <!-- 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编译后项目结构
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> </div> <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main"> <h1 class="page-header">借阅信息总览</h1> <div class="row placeholders"> <div class="col-xs-6 col-sm-3 placeholder" style="border:3px solid #428bca"> <h4>书籍数量</h4> <span class="text-muted" th:text="${bookNum}">Something else</span> </div> <div class="col-xs-6 col-sm-3 placeholder" style="border:3px solid #428bca"> <h4>学生人数</h4> <span class="text-muted" th:text="${studentNum}">Something else</span> </div> <div class="col-xs-6 col-sm-3 placeholder" style="border:3px solid #428bca"> <h4>总借阅数</h4> <span class="text-muted" th:text="${borrowNum}">Something else</span> </div> </div> <h2 class="sub-header">借阅信息列表</h2> <a href="add-borrow" class="btn btn-success">添加借阅记录</a> <br/> <div class="table-responsive"> <table class="table table-striped"> <thead> <tr> <th>书籍编号</th> <th>书名</th> <th>借阅时间</th> <th>学号</th> <th>学生姓名</th> <th>归还情况</th> <th>操作</th> </tr> </thead> <tbody> <tr th:each="borrow:${borrowList}"> <td th:text="${borrow.getBookId()}">书id</td> <td th:text="${borrow.getBookName()}">书名</td> <td th:text="${borrow.getTime()}">借阅时间</td> <td th:text="${borrow.getStudentId()}">学号</td> <td th:text="${borrow.getStudentName()}">姓名</td> <td th:text="${borrow.getBack()?'已归还':'未归还'}">归还情况</td> <td> <span> <a th:href="'delete-borrow?id='+${borrow.getId()}" class="btn btn-xs btn-danger" >删除</a> </span> </td> </tr> </tbody> </table> </div> </div> </div> </div> </div> <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main"> <h1 class="page-header">借阅信息总览</h1> <div class="row placeholders"> <div class="col-xs-6 col-sm-3 placeholder" style="border:3px solid #428bca"> <h4>书籍数量</h4> <span class="text-muted" th:text="${bookNum}">Something else</span> </div> <div class="col-xs-6 col-sm-3 placeholder" style="border:3px solid #428bca"> <h4>学生人数</h4> <span class="text-muted" th:text="${studentNum}">Something else</span> </div> <div class="col-xs-6 col-sm-3 placeholder" style="border:3px solid #428bca"> <h4>总借阅数</h4> <span class="text-muted" th:text="${borrowNum}">Something else</span> </div> </div> <h2 class="sub-header">借阅信息列表</h2> <a href="add-borrow" class="btn btn-success">添加借阅记录</a> <br/> <div class="table-responsive"> <table class="table table-striped"> <thead> <tr> <th>书籍编号</th> <th>书名</th> <th>借阅时间</th> <th>学号</th> <th>学生姓名</th> <th>归还情况</th> <th>操作</th> </tr> </thead> <tbody> <tr th:each="borrow:${borrowList}"> <td th:text="${borrow.getBookId()}">书id</td> <td th:text="${borrow.getBookName()}">书名</td> <td th:text="${borrow.getTime()}">借阅时间</td> <td th:text="${borrow.getStudentId()}">学号</td> <td th:text="${borrow.getStudentName()}">姓名</td> <td th:text="${borrow.getBack()?'已归还':'未归还'}">归还情况</td> <td> <span> <a th:href="'delete-borrow?id='+${borrow.getId()}" class="btn btn-xs btn-danger" >删除</a> </span> </td> </tr> </tbody> </table> </div> </div> </div> </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("select count(*) from borrow;") Integer countBorrow(); // 插入借阅记录 @Insert("insert into borrow(time, book_id, student_id) values (#{localDateTime}, #{bookId}, #{studentId});") void insertBorrow(@Param("localDateTime") LocalDateTime localDateTime, @Param("bookId") Integer bookId, @Param("studentId") Integer studentId); // 删除借阅记录 @Delete("delete from borrow where id = #{id};") void deleteBorrowById(Integer id); // 计数借阅记录 @Select("select count(*) from borrow;") Integer countBorrow(); // 插入借阅记录 @Insert("insert into borrow(time, book_id, student_id) values (#{localDateTime}, #{bookId}, #{studentId});") void insertBorrow(@Param("localDateTime") LocalDateTime localDateTime, @Param("bookId") Integer bookId, @Param("studentId") Integer studentId); // 删除借阅记录 @Delete("delete from borrow where id = #{id};") void deleteBorrowById(Integer id); }
查询借阅列表部分使用的多表联查,并且给字段取了别名,这样便于映射到Java实体类中的属性。@Results注解也是为了将数据库字段映射到实体类属性,@Results可以不加,这里加上去也没什么用,只是懒得删了。
BookMapper接口
public interface BookMapper { // 展示所有书 @Select("select id,name,book_number bookNumber from books;") List<Book> listBooks(); // 统计书数量 @Select("select sum(book_number) from books;") Integer countBooks(); // 列出数量>0的书 @Select("select * from books where book_number>0;") List<Book> listExistBooks(); // 根据书的id,-1该书数量 @Update("update books set book_number = book_number-1 where books.id=#{id};") void minusBookNumber(Integer id); // 根据书id查询该书数量 @Select("select book_number from books where books.id = #{id};") Integer getNumberById(Integer id); // 插入书目 @Insert("insert into books(id, name, book_number) values (#{id}, #{name}, #{bookNumber});") void insertBook(Book book); // 根据id查询书目 @Select("select * from books where id=#{id};") Book selectBookById(Integer id); // 调整书籍数量 @Update("update books set book_number = #{number} where books.id=#{id};") void modifyBookNumber(@Param("id") Integer id, @Param("number") Integer number); // 删除书目 @Delete("delete from books where id=#{id}") void deleteById(Integer id); // 统计书数量 @Select("select sum(book_number) from books;") Integer countBooks(); // 列出数量>0的书 @Select("select * from books where book_number>0;") List<Book> listExistBooks(); // 根据书的id,-1该书数量 @Update("update books set book_number = book_number-1 where books.id=#{id};") void minusBookNumber(Integer id); // 根据书id查询该书数量 @Select("select book_number from books where books.id = #{id};") Integer getNumberById(Integer id); // 插入书目 @Insert("insert into books(id, name, book_number) values (#{id}, #{name}, #{bookNumber});") void insertBook(Book book); // 根据id查询书目 @Select("select * from books where id=#{id};") Book selectBookById(Integer id); // 调整书籍数量 @Update("update books set book_number = #{number} where books.id=#{id};") void modifyBookNumber(@Param("id") Integer id, @Param("number") Integer number); // 删除书目 @Delete("delete from books where id=#{id}") void deleteById(Integer id); }
StudentMapper接口
public interface StudentMapper { // 查询学生列表 @Select("select * from students;") List<Student> listStudents(); // 统计学生数量 @Select("select count(*) from students;") Integer countStudents(); // 查询学生 @Select("select * from students where id=#{id}") Student getById(Integer id); // 查询学生列表 @Select("select * from students;") List<Student> listStudents(); // 统计学生数量 @Select("select count(*) from students;") Integer countStudents(); // 查询学生 @Select("select * from students where id=#{id}") 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<Book> 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<Book> 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<Book> 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<Book> 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<Borrow> borrowList = borrowService.getBorrowList(); // 从session中获取用户昵称 User user = (User)req.getSession().getAttribute("user"); String nikeName =user.getNickname(); // 创建Thymeleaf模板的Context对象用来向Html传递数据 context.setVariable("nickName",nikeName); context.setVariable("borrowList", borrowList); Integer bookNum = bookService.countBooks(); Integer studentNum = studentService.countStudents(); Integer borrowNum = borrowService.countBorrow(); context.setVariable("bookNum", bookNum); context.setVariable("studentNum", studentNum); context.setVariable("borrowNum", borrowNum); ThymeleafUtil.process("index.html",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<Borrow> borrowList = borrowService.getBorrowList(); // 从session中获取用户昵称 User user = (User)req.getSession().getAttribute("user"); String nikeName =user.getNickname(); // 创建Thymeleaf模板的Context对象用来向Html传递数据 context.setVariable("nickName",nikeName); context.setVariable("borrowList", borrowList); Integer bookNum = bookService.countBooks(); Integer studentNum = studentService.countStudents(); Integer borrowNum = borrowService.countBorrow(); context.setVariable("bookNum", bookNum); context.setVariable("studentNum", studentNum); context.setVariable("borrowNum", borrowNum); ThymeleafUtil.process("index.html",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> </div> <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main"> <h2 class="sub-header">添加借阅记录</h2> <p th:if="${warning}" style="color: red">该书暂时不可借阅</p> <p th:if="${incorrect}" style="color: red">输入的信息不合法</p> <p th:if="${noId}" style="color: red">输入项不能为空</p> <form class="form-inline" method="post" action="add-borrow"> <div class="form-group"> <label for="book">书籍编号</label> <input name="bookName" list="bookList" type="text" class="form-control" id="book" placeholder="降龙十八掌"> <datalist id="bookList"> <option th:each="book: ${books}" th:value="${book.getId()+'-'+book.getName()}"></option> </datalist> </div> <div class="form-group"> <label for="student">学号</label> <input name="studentName" list="studentList" type="text" class="form-control" id="student" placeholder="乔峰"> <datalist id="studentList"> <option th:each="student:${students}" th:value="${student.getId()+'-'+student.getName()}"></option> </datalist> </div> <button type="submit" class="btn btn-default">添加记录</button> </form> </div> </div> </div> </div> <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main"> <h2 class="sub-header">添加借阅记录</h2> <p th:if="${warning}" style="color: red">该书暂时不可借阅</p> <p th:if="${incorrect}" style="color: red">输入的信息不合法</p> <p th:if="${noId}" style="color: red">输入项不能为空</p> <form class="form-inline" method="post" action="add-borrow"> <div class="form-group"> <label for="book">书籍编号</label> <input name="bookName" list="bookList" type="text" class="form-control" id="book" placeholder="降龙十八掌"> <datalist id="bookList"> <option th:each="book: ${books}" th:value="${book.getId()+'-'+book.getName()}"></option> </datalist> </div> <div class="form-group"> <label for="student">学号</label> <input name="studentName" list="studentList" type="text" class="form-control" id="student" placeholder="乔峰"> <datalist id="studentList"> <option th:each="student:${students}" th:value="${student.getId()+'-'+student.getName()}"></option> </datalist> </div> <button type="submit" class="btn btn-default">添加记录</button> </form> </div> </div> </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<Student> students = studentService.listStudents(); List<Book> books = bookService.listExistBooks(); User user = (User)req.getSession().getAttribute("user"); if(req.getSession().getAttribute("warning") != null){ // 将session中warning标志传递到context context.setVariable("warning", req.getSession().getAttribute("warning")); req.getSession().removeAttribute("warning"); } if (req.getSession().getAttribute("incorrect")!=null){ // 将session中incorrect标志传递到context context.setVariable("incorrect", 1); req.getSession().removeAttribute("incorrect"); } if(req.getSession().getAttribute("noId") != null){ // 将session中noId标志传递到context context.setVariable("noId", req.getSession().getAttribute("noId")); req.getSession().removeAttribute("noId"); } context.setVariable("students", students); context.setVariable("books", books); context.setVariable("nickName", user.getNickname()); ThymeleafUtil.process("add_borrow.html", context, resp.getWriter()); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String bookName = req.getParameter("bookName"); String studentName = req.getParameter("studentName"); if(!bookName.isEmpty() && !studentName.isEmpty()){ // 判断提交的表单不为空 try{ // 判断提交的信息是否合法 int bookId = Integer.valueOf(bookName.split("-")[0]); int studentId = Integer.valueOf(studentName.split("-")[0]); if(bookService.selectBookById(Integer.valueOf(bookId))!=null && studentService.getById(Integer.valueOf(studentId))!=null){ // 判断书籍和学生存在 if(bookService.getNumberById(Integer.valueOf(bookId))>0){ // 判断该书数量>0 borrowService.insertBorrow(LocalDateTime.now(), Integer.valueOf(bookId), Integer.valueOf(studentId)); // 插入借阅记录 bookService.minusBookNumber(Integer.valueOf(bookId)); // 根据id,书数量-1 resp.sendRedirect("index"); return; }else { req.getSession().setAttribute("warning", 1); // 向session中添加书籍数量为0警告 } }else { req.getSession().setAttribute("incorrect", 1); // 向session中添加输入不合法标志 } }catch (Exception e){ req.getSession().setAttribute("incorrect", 1); // 向session中添加输入不合法标志 } }else { req.getSession().setAttribute("noId", 1); // 向session中添加无Id标志 } resp.sendRedirect("add-borrow"); } @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<Student> students = studentService.listStudents(); List<Book> books = bookService.listExistBooks(); User user = (User)req.getSession().getAttribute("user"); if(req.getSession().getAttribute("warning") != null){ // 将session中warning标志传递到context context.setVariable("warning", req.getSession().getAttribute("warning")); req.getSession().removeAttribute("warning"); } if (req.getSession().getAttribute("incorrect")!=null){ // 将session中incorrect标志传递到context context.setVariable("incorrect", 1); req.getSession().removeAttribute("incorrect"); } if(req.getSession().getAttribute("noId") != null){ // 将session中noId标志传递到context context.setVariable("noId", req.getSession().getAttribute("noId")); req.getSession().removeAttribute("noId"); } context.setVariable("students", students); context.setVariable("books", books); context.setVariable("nickName", user.getNickname()); ThymeleafUtil.process("add_borrow.html", context, resp.getWriter()); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String bookName = req.getParameter("bookName"); String studentName = req.getParameter("studentName"); if(!bookName.isEmpty() && !studentName.isEmpty()){ // 判断提交的表单不为空 try{ // 判断提交的信息是否合法 int bookId = Integer.valueOf(bookName.split("-")[0]); int studentId = Integer.valueOf(studentName.split("-")[0]); if(bookService.selectBookById(Integer.valueOf(bookId))!=null && studentService.getById(Integer.valueOf(studentId))!=null){ // 判断书籍和学生存在 if(bookService.getNumberById(Integer.valueOf(bookId))>0){ // 判断该书数量>0 borrowService.insertBorrow(LocalDateTime.now(), Integer.valueOf(bookId), Integer.valueOf(studentId)); // 插入借阅记录 bookService.minusBookNumber(Integer.valueOf(bookId)); // 根据id,书数量-1 resp.sendRedirect("index"); return; }else { req.getSession().setAttribute("warning", 1); // 向session中添加书籍数量为0警告 } }else { req.getSession().setAttribute("incorrect", 1); // 向session中添加输入不合法标志 } }catch (Exception e){ req.getSession().setAttribute("incorrect", 1); // 向session中添加输入不合法标志 } }else { req.getSession().setAttribute("noId", 1); // 向session中添加无Id标志 } resp.sendRedirect("add-borrow"); } }
重写init()方法,初始化业务类。重写doGet()方法,通过Thymeleaf返回添加borrow的页面。重写doPost()方法,将添加borrow的表单数据接收并插入到数据表中。
此外做了必要的检查,防止:
- 添加借阅记录的表单内容为空
- 添加借阅记录的表单内容不合法(如未提交书籍编号和学号或是提交的书籍编号或学号不存在)
- 添加的借阅记录中书籍的数量为0
至此我们的添加借阅记录功能开发完毕,且该程序十分健壮。
删除借阅记录功能开发
删除借阅记录功能的前端部分已包含在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("id"); borrowService.deleteBorrowById(Integer.valueOf(id)); resp.sendRedirect("index"); } @Override public void init() throws ServletException { borrowService = new BorrowServiceImpl(); } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String id = req.getParameter("id"); borrowService.deleteBorrowById(Integer.valueOf(id)); resp.sendRedirect("index"); } }
学生列表页面开发
先预览页面成品
学生列表页面的功能十分简单,仅仅是显示数据库中的学生学号和姓名。
关于学生相关的持久层与业务层的开发已在上文交代过,这里不再赘述,直接进行前端页面和表现层的开发。在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> </div> <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main"> <h2 class="sub-header">学生列表</h2> <div class="table-responsive"> <table class="table table-striped"> <thead> <tr> <th>学号</th> <th>姓名</th> </tr> </thead> <tbody> <tr th:each="student:${studentList}"> <td th:text="${student.getId()}">学号</td> <td th:text="${student.getName()}">姓名</td> </tr> </tbody> </table> </div> </div> </div> </div> </body> </div> <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main"> <h2 class="sub-header">学生列表</h2> <div class="table-responsive"> <table class="table table-striped"> <thead> <tr> <th>学号</th> <th>姓名</th> </tr> </thead> <tbody> <tr th:each="student:${studentList}"> <td th:text="${student.getId()}">学号</td> <td th:text="${student.getName()}">姓名</td> </tr> </tbody> </table> </div> </div> </div> </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("user"); String nickName = user.getNickname(); List<Student> studentList = studentService.listStudents(); context.setVariable("nickName", nickName); context.setVariable("studentList",studentList); ThymeleafUtil.process("students.html", 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("user"); String nickName = user.getNickname(); List<Student> studentList = studentService.listStudents(); context.setVariable("nickName", nickName); context.setVariable("studentList",studentList); ThymeleafUtil.process("students.html", context, resp.getWriter()); } }
没什么好说的,初始化业务类对象,获取学生信息存入Context对象,然后通过Thymeleaf处理前端页面并返回给浏览器。
书籍列表页面开发
先预览页面成品
可以看出,该页面包含了显示书籍列表,添加书目,删除书目,修改书籍数量四项功能,其中显示书籍列表和删除书目都在同一个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> </div> <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main"> <h2 class="sub-header">书籍列表</h2> <div class="btn-group" role="group" aria-label="..."> <a href="add-book" class="btn btn-success">添加书目</a> <a href="modify-book-number" class="btn btn-primary">修改书籍数量</a> </div> <div class="table-responsive"> <table class="table table-striped"> <thead> <tr> <th>书籍编号</th> <th>书名</th> <th>剩余数量</th> <th>操作</th> </tr> </thead> <tbody> <tr th:each="book:${books}"> <td th:text="${book.getId()}">书id</td> <td th:text="${book.getName()}">书名</td> <td th:text="${book.getBookNumber()}">剩余数量</td> <td> <span> <a th:href="'delete-book?id='+${book.getId()}" class="btn btn-xs btn-danger" >删除</a> </span> </td> </tr> </tbody> </table> </div> </div> </div> </div> </body> </div> <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main"> <h2 class="sub-header">书籍列表</h2> <div class="btn-group" role="group" aria-label="..."> <a href="add-book" class="btn btn-success">添加书目</a> <a href="modify-book-number" class="btn btn-primary">修改书籍数量</a> </div> <div class="table-responsive"> <table class="table table-striped"> <thead> <tr> <th>书籍编号</th> <th>书名</th> <th>剩余数量</th> <th>操作</th> </tr> </thead> <tbody> <tr th:each="book:${books}"> <td th:text="${book.getId()}">书id</td> <td th:text="${book.getName()}">书名</td> <td th:text="${book.getBookNumber()}">剩余数量</td> <td> <span> <a th:href="'delete-book?id='+${book.getId()}" class="btn btn-xs btn-danger" >删除</a> </span> </td> </tr> </tbody> </table> </div> </div> </div> </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("user"); String nickName = user.getNickname(); context.setVariable("nickName", nickName); List<Book> books = bookService.listBooks(); context.setVariable("books", books); ThymeleafUtil.process("books.html", context, resp.getWriter()); } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { Context context = new Context(); User user = (User)req.getSession().getAttribute("user"); String nickName = user.getNickname(); context.setVariable("nickName", nickName); List<Book> books = bookService.listBooks(); context.setVariable("books", books); ThymeleafUtil.process("books.html", 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("id"); bookService.deleteById(Integer.valueOf(id)); resp.sendRedirect("books"); } @Override public void init() throws ServletException { bookService = new BookServiceImpl(); } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String id = req.getParameter("id"); bookService.deleteById(Integer.valueOf(id)); resp.sendRedirect("books"); } }
添加书目
预览添加书目页面
前端页面开发,在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> </div> <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main"> <h2 class="sub-header">添加书目</h2> <p th:if="${exist}" style="color: red">该书目编号已使用</p> <p th:if="${warning}" style="color: red">书目信息不能为空</p> <form class="form-inline" method="post" action="add-book"> <div class="form-group"> <label for="bookId">书目编号</label> <input name="bookId" type="text" class="form-control" id="bookId" placeholder="1"> </div> <div class="form-group"> <label for="bookName">书名</label> <input name="bookName" type="text" class="form-control" id="bookName" placeholder="降龙十八掌"> </div> <div class="form-group"> <label for="bookNumber">数目</label> <input name="bookNumber" type="text" class="form-control" id="bookNumber" placeholder="3"> </div> <button type="submit" class="btn btn-default">添加书籍</button> </form> </div> </div> </div> </div> <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main"> <h2 class="sub-header">添加书目</h2> <p th:if="${exist}" style="color: red">该书目编号已使用</p> <p th:if="${warning}" style="color: red">书目信息不能为空</p> <form class="form-inline" method="post" action="add-book"> <div class="form-group"> <label for="bookId">书目编号</label> <input name="bookId" type="text" class="form-control" id="bookId" placeholder="1"> </div> <div class="form-group"> <label for="bookName">书名</label> <input name="bookName" type="text" class="form-control" id="bookName" placeholder="降龙十八掌"> </div> <div class="form-group"> <label for="bookNumber">数目</label> <input name="bookNumber" type="text" class="form-control" id="bookNumber" placeholder="3"> </div> <button type="submit" class="btn btn-default">添加书籍</button> </form> </div> </div> </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("user"); if (req.getSession().getAttribute("exist") != null) { context.setVariable("exist",1); req.getSession().removeAttribute("exist"); } if (req.getSession().getAttribute("warning") != null){ context.setVariable("warning",1); req.getSession().removeAttribute("warning"); } context.setVariable("nickName",user.getNickname()); ThymeleafUtil.process("add_book.html", context, resp.getWriter()); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { if(!req.getParameter("bookId").isEmpty() & !req.getParameter("bookName").isEmpty() & !req.getParameter("bookNumber").isEmpty()){ String bookId = req.getParameter("bookId"); String bookName = req.getParameter("bookName"); String bookNumber = req.getParameter("bookNumber"); if (bookService.selectBookById(Integer.valueOf(bookId)) != null) { req.getSession().setAttribute("exist", 1); resp.sendRedirect("add-book"); return; } bookService.insertBook(new Book(Integer.valueOf(bookId), bookName, Integer.valueOf(bookNumber))); resp.sendRedirect("books"); }else { req.getSession().setAttribute("warning", 1); resp.sendRedirect("add-book"); } } @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("user"); if (req.getSession().getAttribute("exist") != null) { context.setVariable("exist",1); req.getSession().removeAttribute("exist"); } if (req.getSession().getAttribute("warning") != null){ context.setVariable("warning",1); req.getSession().removeAttribute("warning"); } context.setVariable("nickName",user.getNickname()); ThymeleafUtil.process("add_book.html", context, resp.getWriter()); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { if(!req.getParameter("bookId").isEmpty() & !req.getParameter("bookName").isEmpty() & !req.getParameter("bookNumber").isEmpty()){ String bookId = req.getParameter("bookId"); String bookName = req.getParameter("bookName"); String bookNumber = req.getParameter("bookNumber"); if (bookService.selectBookById(Integer.valueOf(bookId)) != null) { req.getSession().setAttribute("exist", 1); resp.sendRedirect("add-book"); return; } bookService.insertBook(new Book(Integer.valueOf(bookId), bookName, Integer.valueOf(bookNumber))); resp.sendRedirect("books"); }else { req.getSession().setAttribute("warning", 1); resp.sendRedirect("add-book"); } } }
修改书籍数量
页面预览
这里书籍名称使用的是单选框,而不是像添加借阅记录页面中使用输入框和选择框组合,显然书籍数量一旦多起来,单选框就会很不适用,因此这里有很大的改进空间,不过暂时就这样了。
接下来是修改书籍数量的前端页面开发,在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> </div> <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main"> <h2 class="sub-header">修改书籍数量</h2> <p th:if="${warning}" style="color: red">书籍数量不能为空</p> <form class="form-inline" method="post" action="modify-book-number"> <div class="form-group"> <label for="bookName">书籍名称</label> <select id="bookName" class="form-control" name="bookId"> <option th:each="book:${books}" th:text="${book.getName()}" th:value="${book.getId()}"></option> </select> </div> <div class="form-group"> <label for="bookNumber">数量</label> <input name="bookNumber" type="text" class="form-control" id="bookNumber" placeholder="3"> </div> <button type="submit" class="btn btn-default">添加修改</button> </form> </div> </div> </div> </div> <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main"> <h2 class="sub-header">修改书籍数量</h2> <p th:if="${warning}" style="color: red">书籍数量不能为空</p> <form class="form-inline" method="post" action="modify-book-number"> <div class="form-group"> <label for="bookName">书籍名称</label> <select id="bookName" class="form-control" name="bookId"> <option th:each="book:${books}" th:text="${book.getName()}" th:value="${book.getId()}"></option> </select> </div> <div class="form-group"> <label for="bookNumber">数量</label> <input name="bookNumber" type="text" class="form-control" id="bookNumber" placeholder="3"> </div> <button type="submit" class="btn btn-default">添加修改</button> </form> </div> </div> </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<Book> books = bookService.listBooks(); User user = (User) req.getSession().getAttribute("user"); if(req.getSession().getAttribute("warning")!=null){ context.setVariable("warning", 1); req.getSession().removeAttribute("warning"); } context.setVariable("books", books); context.setVariable("nickName", user.getNickname()); ThymeleafUtil.process("modify_book_number.html", context, resp.getWriter()); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { if(!req.getParameter("bookId").isEmpty() && !req.getParameter("bookNumber").isEmpty()){ String bookId = req.getParameter("bookId"); String bookNumber = req.getParameter("bookNumber"); bookService.modifyBookNumber(Integer.valueOf(bookId), Integer.valueOf(bookNumber)); resp.sendRedirect("books"); }else { req.getSession().setAttribute("warning", 1); resp.sendRedirect("modify-book-number"); } } @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<Book> books = bookService.listBooks(); User user = (User) req.getSession().getAttribute("user"); if(req.getSession().getAttribute("warning")!=null){ context.setVariable("warning", 1); req.getSession().removeAttribute("warning"); } context.setVariable("books", books); context.setVariable("nickName", user.getNickname()); ThymeleafUtil.process("modify_book_number.html", context, resp.getWriter()); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { if(!req.getParameter("bookId").isEmpty() && !req.getParameter("bookNumber").isEmpty()){ String bookId = req.getParameter("bookId"); String bookNumber = req.getParameter("bookNumber"); bookService.modifyBookNumber(Integer.valueOf(bookId), Integer.valueOf(bookNumber)); resp.sendRedirect("books"); }else { req.getSession().setAttribute("warning", 1); resp.sendRedirect("modify-book-number"); } } }
最后调整
关于本项目的功能基本开发完成,不过登录时的记住我功能还未开发,以及导航栏的退出登录功能。
记住我功能开发
对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("username")) username = cookie.getValue(); if (cookie.getName().equals("password")) password = cookie.getValue(); } if (service.auth(username,password, req.getSession())){ resp.sendRedirect("index"); return; } } Context context = new Context(); if(req.getSession().getAttribute("login-failure") != null){ context.setVariable("failure", true); req.getSession().removeAttribute("login-failure"); } ThymeleafUtil.process("login.html",context, resp.getWriter()); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String username = req.getParameter("username"); String password = req.getParameter("password"); if(service.auth(username,password, req.getSession())){ if (req.getParameter("remember")!=null){ Cookie usernameCookie = new Cookie("username", username); Cookie passwordCookie = new Cookie("password", password); usernameCookie.setMaxAge(60*60*24*7); // 7天 passwordCookie.setMaxAge(60*60*24*7); resp.addCookie(usernameCookie); resp.addCookie(passwordCookie); } resp.sendRedirect("index"); return; } req.getSession().setAttribute("login-failure", 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("username")) username = cookie.getValue(); if (cookie.getName().equals("password")) password = cookie.getValue(); } if (service.auth(username,password, req.getSession())){ resp.sendRedirect("index"); return; } } Context context = new Context(); if(req.getSession().getAttribute("login-failure") != null){ context.setVariable("failure", true); req.getSession().removeAttribute("login-failure"); } ThymeleafUtil.process("login.html",context, resp.getWriter()); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String username = req.getParameter("username"); String password = req.getParameter("password"); if(service.auth(username,password, req.getSession())){ if (req.getParameter("remember")!=null){ Cookie usernameCookie = new Cookie("username", username); Cookie passwordCookie = new Cookie("password", password); usernameCookie.setMaxAge(60*60*24*7); // 7天 passwordCookie.setMaxAge(60*60*24*7); resp.addCookie(usernameCookie); resp.addCookie(passwordCookie); } resp.sendRedirect("index"); return; } req.getSession().setAttribute("login-failure", 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
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 托管堆内存泄露/CPU异常的常见思路
· PostgreSQL 和 SQL Server 在统计信息维护中的关键差异
· C++代码改造为UTF-8编码问题的总结
· DeepSeek 解答了困扰我五年的技术问题
· 为什么说在企业级应用开发中,后端往往是效率杀手?
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 推荐几款开源且免费的 .NET MAUI 组件库
· 实操Deepseek接入个人知识库
· 易语言 —— 开山篇
· Trae初体验