15云借阅图书管理系统
本章将通过SSM(Spring+Spring MVC+MyBatis)框架知识来实现一个简单的云借阅图书管理系统。云借阅图书管理系统在开发过程中,整合了三大框架,并在框架整合的基础上实现了系统功能。
15.1 系统概述
15.1.1 系统功能介绍
本系统后台使用SSM框架编写,前台页面使用当前主流的Bootstrap和jQuery框架编写。
云借阅图书管理系统主要实现了两大功能模块:用户登录模块和图书管理模块;
- 用户登录模块主要用于实现用户的登录与注销;
- 图书管理模块主要用于管理图书,如新书推荐、图书借阅等。
15.1.2 系统架构设计
云借阅系统结构层次
根据功能的不同,云借阅图书管理系统项目结构可以划分为以下几个层次。
- 持久对象层(持久层或持久化层):该层由若干持久化类(实体类)组成。
- 数据访问层(DAO层):该层由若干DAO接口和MyBatis映射文件组成。DAO接口的名称统一以Mapper结尾,且MyBatis的映射文件名称要与接口的名称相同。
- 业务逻辑层(Service层):该层由若干Service接口和实现类组成。逻辑层主要用于实现系统的业务逻辑。
- Web表现层:该层主要包括Spring MVC中的Controller类和JSP页面。
15.1.3 文件组织结构
15.1.4 系统开发及运行环境
云借阅图书管理系统开发环境如下。
- 操作系统:Windows 10。
- Web服务器:Tomcat 7。
- Java开发包:JDK 8。
- 开发工具:IntelliJ IDEA 2021.1 x64。
- 数据库:MySQL 5.7.17。
- 浏览器:Google Chrome 102.0.5005.115(正式版本) (64 位)、360安全浏览器 13.1.5360.0。
15.2 数据库设计
云借阅系统所涉及的表
云借阅图书管理系统中主要包括用户登录和图书管理两大模块,用户登录模块会用到用户表,图书管理模块会用到图书信息表。除此之外,在图书管理模块中,每次图书借阅完成后,系统会记录图书借阅情况,因此,图书管理模块还需要一个借阅记录表。
用户表(user)的表结构
字段名 | 类型 | 长度 | 是否主键 | 说明 |
---|---|---|---|---|
user_id | int | 32 | 是 | 用户id |
user_password | varchar | 32 | 否 | 用户名称 |
user_password | varchar | 32 | 否 | 用户密码 |
user_email | varchar | 32 | 否 | 用户邮箱(用户账号) |
user_role | varchar | 32 | 否 | 用户角色(ADMIN:管理员,USER:普通用户) |
user_status | varchar | 1 | 否 | 用户状态(0:正常,1:禁用) |
图书信息表(book)的表结构
字段名 | 类型 | 长度 | 是否主键 | 说明 |
---|---|---|---|---|
book_id | int | 32 | 是 | 图书编号 |
book_name | varchar | 32 | 否 | 图书名称 |
book_isbn | varchar | 32 | 否 | 图书标准ISBN编号 |
book_press | varchar | 32 | 否 | 图书出版社 |
book_author | varchar | 32 | 否 | 图书作者 |
book_pagination | int | 32 | 否 | 图书页数 |
book_price | double | 32 | 否 | 图书价格 |
book_uploadtime | varchar | 32 | 否 | 图书上架时间 |
book_status | varchar | 1 | 否 | 图书状态(0:可借阅,1:已借阅,2:归还中,3:已下架) |
book_borrower | varchar | 32 | 否 | 图书借阅人 |
book_borrowtime | varchar | 32 | 否 | 图书借阅时间 |
book_returntime | varchar | 32 | 否 | 图书预计归还时间 |
借阅记录表(record)的表结构
字段名 | 类型 | 长度 | 是否主键 | 说明 |
---|---|---|---|---|
record_id | varchar | 32 | 是 | 借阅记录id |
record_bookname | varchar | 32 | 否 | 借阅的图书名称 |
record_bookisbn | varchar | 32 | 否 | 借阅的图书的ISBN编号 |
record_borrower | varchar | 32 | 否 | 图书借阅人 |
record_borrowtime | varchar | 32 | 否 | 图书借阅时间 |
record_remandtime | varchar | 32 | 否 | 图书归还时间 |
15.3 系统环境搭建
15.3.1 需要引入的依赖
由于云借阅图书管理系统基于SSM框架和Maven开发,因此需要在项目中引入这三大框架的依赖。此外,项目中还涉及到数据库连接、JSTL标签等,因此还要引入数据库连接、JSTL标签等其他依赖。整个系统所需要引入的依赖如下所示。
1.Spring框架相关的依赖:
- spring-context(Spring上下文);
- spring-tx (Spring事务管理);
- spring-jdbc(Spring JDBC)。
2.Spring MVC框架相关的依赖:spring-webmvc(Spring MVC核心)。
3.MyBatis框架相关的依赖:mybatis(MyBatis核心)。
4.分页插件相关的依赖:pagehelper(分页插件)。
5.MyBatis与Spring整合的依赖:mybatis-spring(MyBatis与Spring整合)。
6.数据库驱动依赖:mysql-connector-java(mysql的数据库驱动)。
7.数据源相关依赖:druid(阿里提供的数据库连接池)。
8.ServletAPI相关的依赖:
- jsp-api(jsp页面使用request等对象)
- servlet-api(java文件使用request等对象)。
9.JSTL标签库相关依赖:
- jstl(jsp标准标签库);
- taglibs(taglibs指令);
10Jackson相关依赖:
- jackson-core(jackson核心);
- jackson-databind(jackson数据转换);
jackson-annotations(jackson核心注解)。
15.3.2 准备数据库资源
创建数据库并导入数据的具体SQL命令如下(以DataGrip为例)。
(1)创建数据库CREATE DATABASE cloudlibrary;
(2)选择所创建的数据库USE cloudlibrary;
(3)导入数据库文件
查看是否成功导入数据:15.3.3 准备项目环境
本项目使用纯注解的方式整合SSM框架,并在SSM整合之后引入已经提供好的页面资源,具体如下所示:
1.在IntelliJ IDEA中,创建一个名称为cloudlibrary的Maven Web项目,将系统所需要的依赖配置到项目的pom.xml文件中。(Maven项目创建过程见第1章笔记;MavenWeb项目创建过程见第10章笔记)
pom.xml文件: ```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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4.0.0 war cn.edu.imust cloudlibrary 1.0-SNAPSHOT <!--Spring核心容器--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.8.RELEASE</version> </dependency> <!--Spring事务管理--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>5.2.8.RELEASE</version> </dependency> <!--Spring的jdbc操作数据库的依赖,包含Spring自带数据源,jdbcTemplate--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.2.8.RELEASE</version> </dependency> <!--Spring MVC的核心--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.2.8.RELEASE</version> </dependency> <!--MyBatis核心--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.2</version> </dependency> <!--MyBatis的分页插件--> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>5.1.10</version> </dependency> <!--MyBatis整合Spring的依赖--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.0.1</version> </dependency> <!--mysql数据库驱动--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.16</version> </dependency> <!--Druid数据源--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.20</version> </dependency> <!--servlet-api :引入servlet的功能--> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope> </dependency> <!--jsp-api: jsp页面的功能包 --> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>jsp-api</artifactId> <version>2.2</version> <scope>provided</scope> </dependency> <!-- JSTL标签库 --> <dependency> <groupId>jstl</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <dependency> <groupId>taglibs</groupId> <artifactId>standard</artifactId> <version>1.1.2</version> </dependency> <!--jackson坐标--> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <version>2.9.2</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.9.2</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-annotations</artifactId> <version>2.9.0</version> </dependency>
<!--日志开始-->
<!--引入日志的门脸-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.6.1</version>
</dependency>
<!-- 日志工具包 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.10.0</version>
</dependency>
<!--日志核心包-->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.10.0</version>
</dependency>
</dependencies>
<!--构建-->
<build>
<!--设置插件-->
<plugins>
<!--具体的插件配置-->
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<configuration>
<port>8080</port>
<path>/cloudlibrary</path>
</configuration>
</plugin>
</plugins>
</build>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
**2.编写配置文件和配置类**<br />**(1)在项目的src\main\resources目录下创建数据库连接信息的配置文件jdbc.properties**
```properties
jdbc.driverClassName=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/cloudlibrary?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
jdbc.username=root
jdbc.password=123456
在项目的src\main\resources目录下创建日志配置文件log4j.properties
#定义LOG输出级别
log4j.rootLogger=INFO,Console
#定义日志输出目的地为控制台
log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.Target=System.out
#可以灵活的指定日志输出格式,下面一行是指定具体的格式
log4j.appender.Console.layout=org.apache.log4j.PatternLayout
log4j.appender.Console.layout.ConversionPattern=[%c]-%m%n
#输出所有日志,如果换成DEBUG表示输出DEBUG以上级别日志
log4j.appender.File.Threshold=ALL
log4j.appender.File.layout=org.apache.log4j.PatternLayout
log4j.appender.File.layout.ConversionPattern=[%p][%d{yyyy-MM-dd HH\:mm|\:ss}][%c]%m%n
log4j.logger.cn.edu.imust.mapper=DEBUG
(2)本项目使用纯注解的方式整合SSM框架,使用配置类替代框架的相关配置文件。在项目的src\main\java目录下创建一个名称为cn.edu.imust.config的类包,并在config类包下分别创建并配置以下6个配置类。
- JdbcConfig.java:用于读取数据库连接信息的配置类。
- MyBatisConfig.java:MyBatis相关的配置类。
- SpringConfig.java:Spring相关的配置类。
- SpringMvcConfig.java:Spring MVC相关的配置类。
- EncodingFilter.java:编码拦截器。
- ServletContainersInitConfig.java:用于初始化Servlet容器的配置类。
在这些配置类中,需要用到controller包和service包的信息;在创建这些配置类前,先将这两个包创建完成;
分别在src/main/java目录下 ,创建包cn.edu.imust.controller和包cn.edu.imust.service包。
JdbcConfig.java
package cn.edu.imust.config;
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.PropertySource;
import javax.sql.DataSource;
/*
等同于
<context:property-placeholder location="classpath*:jdbc.properties"/>
*/
@PropertySource("classpath:jdbc.properties")
public class JdbcConfig {
/*
使用注入的形式,读取properties文件中的属性值,
等同于<property name="*******" value="${jdbc.driver}"/>
*/
@Value("${jdbc.driverClassName}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String userName;
@Value("${jdbc.password}")
private String password;
/*定义dataSource的bean,
等同于<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
*/
@Bean("dataSource")
public DataSource getDataSource(){
//创建对象
DruidDataSource ds = new DruidDataSource();
/*
等同于set属性注入<property name="driverClassName" value="driver"/>
*/
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(userName);
ds.setPassword(password);
return ds;
}
}
MyBatisConfig.java
package cn.edu.imust.config;
import com.github.pagehelper.PageInterceptor;
import org.apache.ibatis.plugin.Interceptor;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import javax.sql.DataSource;
import java.util.Properties;
public class MyBatisConfig {
/**配置PageInterceptor分页插件*/
@Bean
public PageInterceptor getPageInterceptor() {
PageInterceptor pageIntercptor = new PageInterceptor();
Properties properties = new Properties();
properties.setProperty("value", "true");
pageIntercptor.setProperties(properties);
return pageIntercptor;
}
/*
定义MyBatis的核心连接工厂bean,
等同于<bean class="org.mybatis.spring.SqlSessionFactoryBean">
参数使用自动装配的形式加载dataSource,
为set注入提供数据源,dataSource来源于JdbcConfig中的配置
*/
@Bean
public SqlSessionFactoryBean getSqlSessionFactoryBean(@Autowired DataSource dataSource, @Autowired PageInterceptor pageIntercptor){
SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();
//等同于<property name="dataSource" ref="dataSource"/>
ssfb.setDataSource(dataSource);
Interceptor[] plugins={pageIntercptor};
ssfb.setPlugins(plugins);
return ssfb;
}
/*
定义MyBatis的映射扫描,
等同于<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
*/
@Bean
public MapperScannerConfigurer getMapperScannerConfigurer(){
MapperScannerConfigurer msc = new MapperScannerConfigurer();
//等同于<property name="basePackage" value="com.itheima.dao"/>
msc.setBasePackage("com.itheima.mapper");
return msc;
}
}
SpringConfig.java
package cn.edu.imust.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
/**
* @类名 SpringConfig.java
* @作者 ZLY
* @版本 1.0.0
* @描述 TODO
* @创建时间 2022年06月15日 12:04:00
*/
@Configuration
/*
将MyBatisConfig类和JdbcConfig类交给Spring管理
*/
@Import({MyBatisConfig.class,JdbcConfig.class})
/**
*等同于<context:component-scan base-package="com.itheima.service">
*/
@ComponentScan( "cn.edu.imust.service")
/*开启事务管理
等同于<tx:annotation-driven transaction-manager="transactionManager"/>
*/
@EnableTransactionManagement
public class SpringConfig {
/*
等同于<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
*/
@Bean("transactionManager")
public DataSourceTransactionManager getDataSourceTxManager(@Autowired DataSource dataSource){
DataSourceTransactionManager dtm = new DataSourceTransactionManager();
//等同于<property name="dataSource" ref="dataSource"/>
dtm.setDataSource(dataSource);
return dtm;
}
}
SpringMvcConfig.java
package cn.edu.imust.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.web.servlet.config.annotation.*;
import java.util.List;
/**
* @类名 SpringMvcConfig.java
* @作者 ZLY
* @版本 1.0.0
* @描述 TODO
* @创建时间 2022年06月15日 12:06:00
*/
@Configuration
//@PropertySource("classpath:ignoreUrl.properties")
//等同于<context:component-scan base-package="com.itheima.controller"/>
@ComponentScan({"cn.edu.imust.controller"})
/*@Import({MyWebMvcConfig.class})*/
@EnableWebMvc
public class SpringMvcConfig implements WebMvcConfigurer {
/*
*开启对静态资源的访问
* 类似在Spring MVC的配置文件中设置<mvc:default-servlet-handler/>元素
*/
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.jsp("/admin/",".jsp");
}
}
EncodingFilter.java
package cn.edu.imust.config;
import javax.servlet.*;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
@WebFilter(filterName = "encodingFilter",urlPatterns = "/*")
public class EncodingFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) {}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
servletRequest.setCharacterEncoding("UTF-8");
servletResponse.setCharacterEncoding("UTF-8");
filterChain.doFilter(servletRequest,servletResponse);
}
@Override
public void destroy() {}
}
ServletContainersInitConfig.java
package cn.edu.imust.config;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
/*
加载Spring配置类中的信息,
初始化Spring容器
*/
protected Class<?>[] getRootConfigClasses() {
return new Class[]{SpringConfig.class};
}
/*
加载Spring MVC配置类中的信息,
初始化Spring MVC容器
*/
protected Class<?>[] getServletConfigClasses() {
return new Class[]{SpringMvcConfig.class};
}
//配置DispatcherServlet的映射路径
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
3.将项目运行所需要的CSS文件、图片、js和JSP文件按照项目文件组织结构引入到项目中。其中系统首页index.jsp实现了一个转发功能,在访问时会转发到登录页面,其实现代码如下所示。
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<jsp:forward page="/admin/login.jsp"/>
4.页面效果展示
启动项目cloudlibrary,并在浏览器中访问项目首页,访问地址为http://localhost:8080/cloudlibrary/index.jsp,访问效果如图所示。
15.4 用户登录模块
15.4.1 用户登录
用户登录流程图
用户登录过程中首先要验证用户名和密码是否正确,如果正确,可以成功登录系统,系统会自动跳转到主页;如果错误,则在登录页面给出错误提示信息。
实现用户登录功能,步骤如下:
1.创建持久化类:在项目的src/main/java目录下,创建包cn.edu.imust.domain;在该包中,创建用户持久化类User,并在User类中定义用户相关属性以及相应的getter/setter方法。
package cn.edu.imust.domain;
import java.io.Serializable;
public class User implements Serializable {
private Integer id; //用户id
private String name; //用户名称
private String password; //用户密码
private String email; //用户邮箱(用户账号)
private String role; //用户角色
private String status; //用户状态
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
}
2.实现DAO:在java文件夹下创建一个cn.edu.imust.mapper包,在包中创建一个用户接口UserMapper,并在接口中定义login()方法,login()方法通过用户账号和用户密码查询用户信息。
package cn.edu.imust.mapper;
import cn.edu.imust.domain.User;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select;
public interface UserMapper {
@Select("select * from user where user_email=#{email} AND user_password=#{password} AND user_status!='1'")
@Results(id = "userMap",value = {
//id字段默认为false,表示不是主键
//column表示数据库表字段,property表示实体类属性名。
@Result(id = true,column = "user_id",property = "id"),
@Result(column = "user_name",property = "name"),
@Result(column = "user_password",property = "password"),
@Result(column = "user_email",property = "email"),
@Result(column = "user_role",property = "role"),
@Result(column = "user_status",property = "status")
})
User login(User user);
}
3.实现Service:(1)在cn.edu.imust包内创建service包,在该包中,创建UserService接口,并在该接口中定义login()方法,login()方法通过用户账号和用户密码查询用户信息。
package cn.edu.imust.service;
import cn.edu.imust.domain.User;
public interface UserService {
//通过User的用户账号和用户密码查询用户信息
User login(User user);
}
(2)在service包中,创建包impl;在该包中创建UserService接口的实现类UserServiceImpl,在类中重写接口的login()方法。
package cn.edu.imust.service.impl;
import cn.edu.imust.domain.User;
import cn.edu.imust.mapper.UserMapper;
import cn.edu.imust.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService {
//注入userMapper
@Autowired
private UserMapper userMapper;
//通过User的用户账号和用户密码查询用户信息
@Override
public User login(User user) {
return userMapper.login(user);
}
}
注意:此时上面的第13行,可能会有出错提示;因为在前面只声明了UserMapper的接口,并未定义实现类;如果这样不影响程序执行,可以忽略;否则可以用以下两个方法尝试解决:
1.用 @Resource 替换 @Autowired
2.在前面的UserMapper接口上加上@Repository注解
来自:https://blog.csdn.net/JFENG14/article/details/123281224
4.实现Controller:在java目录下,创建一个cn.edu.imust.controller包,在包中创建用户控制器类UserController,类中定义用户登录的方法login()。
package cn.edu.imust.controller;
import cn.edu.imust.domain.User;
import cn.edu.imust.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpServletRequest;
@Controller
public class UserController {
//注入userService
@Autowired
private UserService userService;
/*
用户登录
*/
@RequestMapping("/login")
public String login(User user, HttpServletRequest request){
try {
User u=userService.login(user);
/*
用户账号和密码是否查询出用户信息
是:将用户信息存入Session,并跳转到后台首页
否:Request域中添加提示信息,并转发到登录页面
*/
if(u!=null){
request.getSession().setAttribute("USER_SESSION",u);
return "redirect:/admin/main.jsp";
}
request.setAttribute("msg","用户名或密码错误");
return "forward:/admin/login.jsp";
}catch(Exception e){
e.printStackTrace();
request.setAttribute("msg","系统错误");
return "forward:/admin/login.jsp";
}
}
}
5.实现登录页面功能:在15.3.3节中引入页面资源时,已经把登录页面login.jsp导入到项目中了,登录页面主要包含一个登录表单。
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<html>
<head>
<meta charset="UTF-8">
<title>云借阅-图书管理系统</title>
<link rel="stylesheet" type="text/css" href="${pageContext.request.contextPath}/css/webbase.css"/>
<link rel="stylesheet" type="text/css" href="${pageContext.request.contextPath}/css/pages-login-manage.css"/>
<script type="text/javascript" src="${pageContext.request.contextPath}/js/jquery.min.js"></script>
</head>
<body>
<div class="loginmanage">
<div class="py-container">
<h4 class="manage-title">云借阅-图书管理系统</h4>
<div class="loginform">
<ul class="sui-nav nav-tabs tab-wraped">
<li class="active">
<h3>账户登录</h3>
</li>
</ul>
<div class="tab-content tab-wraped">
<%--登录提示信息--%>
<span style="color: red">${msg}</span>
<div id="profile" class="tab-pane active">
<form id="loginform" class="sui-form" action="${pageContext.request.contextPath}/login"
method="post">
<div class="input-prepend"><span class="add-on loginname">用户名</span>
<input type="text" placeholder="企业邮箱" class="span2 input-xfat" name="email">
</div>
<div class="input-prepend"><span class="add-on loginpwd">密码</span>
<input type="password" placeholder="请输入密码" class="span2 input-xfat" name="password">
</div>
<div class="logined">
<a class="sui-btn btn-block btn-xlarge btn-danger"
href='javascript:document:loginform.submit();' target="_self">登 录</a>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</body>
<script type="text/javascript">
/**
* 登录超时 展示区跳出iframe
*/
var _topWin = window;
while (_topWin != _topWin.parent.window) {
_topWin = _topWin.parent.window;
}
if (window != _topWin)
_topWin.document.location.href = '${pageContext.request.contextPath}/admin/login.jsp';
</script>
</html>
6.启动项目,登录测试:在执行登录操作之前,先查看一下数据库中user表中的数据。
启动项目;在浏览器地址栏中输入:http://localhost:8080/cloudlibrary/index.jsp访问登录页面;
在登录页面中分别输入账号admin2@imust.edu.cn和密码123456,单击“登录”按钮登录系统,登录成功后系统后台首页如图所示。
15.4.2 实现登录验证
虽然现在已经实现了用户登录功能,但此功能还并不完善。假设控制器类中也存在其他访问系统首页的方法,那么用户完全可以绕过登录步骤,而直接通过访问该方法进入系统后台首页。
为了验证上述假设,在UserController控制器类中新增一个方法toMainPage(),用于跳转到系统后台首页,其代码如下所示。
@RequestMapping("/toMainPage")
public String toMainPage(){
return "main";
}
如上代码中toMainPage()方法只用于页面跳转。toMainPage()方法会处理URL为toMainPage的请求,并跳转到名称为main的页面。
当其他系统访问系统首页时,此时,不进行用户登录,直接在浏览器访问跳转到系统后台首页的地址http://localhost:8080/cloudlibrary/toMainPage,页面跳转如下图所示。
从上图能看出来,未登录也能直接访问到系统后台首页。显然,让未登录的用户直接访问到系统的后台页面,将是十分不安全的。
未登录用户也能直接访问到系统后台首页,这对系统来说是不安全的。为了避免此种情况的发生,提升系统的安全性,可以创建一个拦截器来拦截所有请求。当用户处于登录状态时,直接放行该用户的请求;如果用户没有登录,但是访问的是登录相关的请求,也放行;否则将请求转发到登录页面,并提示用户登录。
拦截器的执行流程图
用户登录验证的具体实现如下:
1.创建登录拦截器类:在项目的src\main\java目录下,创建一个cn.edu.imust.interceptor包,并在包中创建登录拦截器类ResourcesInterceptor,用于对用户访问进行拦截控制。
ResourcesInterceptor类中包含的内容如下:
(1)定义访问路径;
(2)获取请求的路径;
(3)对登录用户进行校验;
(4)对用户登录的相关请求放行;
(5)其他情况都直接跳转到登录页面。
package cn.edu.imust.interceptor;
import cn.edu.imust.domain.User;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 资源拦截器
*/
public class ResourcesInterceptor extends HandlerInterceptorAdapter {
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws
Exception {
User user = (User) request.getSession().getAttribute("USER_SESSION");
//获取请求的路径
String uri = request.getRequestURI();
//如果用户是已登录状态,判断访问的资源是否有权限
if (user != null) {
return true;
}
//对用户登录的相关请求,放行
if (uri.indexOf("login") >= 0) {
return true;
}
//其他情况都直接跳转到登录页面
request.setAttribute("msg", "您还没有登录,请先登录!");
request.getRequestDispatcher("/admin/login.jsp").forward(request, response);
return false;
}
}
2.配置拦截器:在SpringMvcConfig配置类中重写addInterceptors()方法,将自定义的资源拦截器添加到拦截器注册类中,重写的addInterceptors()方法具体代码如下所示。
/*
* 在注册的拦截器类中添加自定义拦截器
* addPathPatterns()方法设置拦截的路径
* excludePathPatterns()方法设置不拦截的路径
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor( new ResourcesInterceptor())
.addPathPatterns("/**")
.excludePathPatterns("/css/**","/js/**","/img/**");
}
3.查看运行结果:启动cloudlibrary项目,不进行用户登录,直接访问跳转到系统后台页面的地址http://localhost:8080/cloudlibrary/toMainPage,页面跳转如图所示。
从图中可以看出,未登录的用户直接执行访问控制器方法后,并没有成功跳转到系统后台首页,而是转发到了系统登录页面,同时在登录页面中也给出了用户未登录的提示信息。这表明用户登录验证功能已成功实现。
15.4.3 注销登录
接下来,实现用户登录模块的注销登录功能,具体实现如下。
1.main.jsp 文件中,“注销”超链接的代码如下:
<!-- 头部导航 -->
<nav class="navbar navbar-static-top">
<div class="navbar-custom-menu">
<ul class="nav navbar-nav">
<li class="dropdown user user-menu">
<a>
<img src="${pageContext.request.contextPath}/img/user.jpg" class="user-image"
alt="User Image">
<span class="hidden-xs">${USER_SESSION.name}</span>
</a>
</li>
<li class="dropdown user user-menu">
<a href="${pageContext.request.contextPath}/logout">
<span class="hidden-xs">注销</span>
</a>
</li>
</ul>
</div>
</nav>
2.在UserController控制器类中新增一个注销登录的方法logout(),该方法中,首先清除Session中的用户信息,然后跳转到登录页面。logout()方法的具体代码如下所示。
/*
注销登录
*/
@RequestMapping("/logout")
public String logout( HttpServletRequest request){
try {
HttpSession session = request.getSession();
//销毁Session
session.invalidate();
return "forward:/admin/login.jsp";
}catch(Exception e){
e.printStackTrace();
request.setAttribute("msg","系统错误");
return "forward:/admin/login.jsp";
}
}
3.重启项目并登录系统后,单击系统后台页面中的“注销”链接,跳转到的页面如图所示。
从图中可以看出,用户管理员小二的登录状态已经被注销,并跳转到了登录页面。
15.5 图书管理模块
图书管理模块是云借阅图书管理系统的核心模块,该模块中包含了新书推荐、图书借阅、当前借阅和借阅记录4个子模块。
15.5.1 新书推荐
云借阅图书管理系统的新书推荐模块,主要包含查询图书和借阅图书2个功能;
查询图书功能是根据图书的上架时间将图书相关信息展示在页面,本系统中固定推荐最新上架的5本图书;
借阅图书功能是在用户发起借阅请求时,修改该图书的借阅状态、借阅人、借阅时间和预计归还的时间。接下来分别实现这2个功能。
15.5.1.1查询图书
查询图书功能的具体实现步骤如下:
1.创建持久化类:在cn.edu.imust.domain包中,创建图书持久化类Book,在Book类中声明与图书数据表对应的属性并定义各个属性的getter/setter方法。
package cn.edu.imust.domain;
import java.io.Serializable;
public class Book implements Serializable {
private Integer id; //图书编号
private String name; //图书名称
private String isbn; //图书标准ISBN编号
private String press; //图书出版社
private String author; //图书作者
private Integer pagination;//图书页数
private Double price; //图书价格
private String uploadTime; //图书上架时间
private String status; //图书状态
private String borrower; //图书借阅人
private String borrowTime; //图书借阅时间
private String returnTime; //图书预计归还时间
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getIsbn() {
return isbn;
}
public void setIsbn(String isbn) {
this.isbn = isbn;
}
public String getPress() {
return press;
}
public void setPress(String press) {
this.press = press;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public Integer getPagination() {
return pagination;
}
public void setPagination(Integer pagination) {
this.pagination = pagination;
}
public Double getPrice() {
return price;
}
public void setPrice(Double price) {
this.price = price;
}
public String getUploadTime() {
return uploadTime;
}
public void setUploadTime(String uploadTime) {
this.uploadTime = uploadTime;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getBorrower() {
return borrower;
}
public void setBorrower(String borrower) {
this.borrower = borrower;
}
public String getBorrowTime() {
return borrowTime;
}
public void setBorrowTime(String borrowTime) {
this.borrowTime = borrowTime;
}
public String getReturnTime() {
return returnTime;
}
public void setReturnTime(String returnTime) {
this.returnTime = returnTime;
}
}
2.实现DAO层:在cn.edu.imust.mapper包中,创建一个BookMapper接口,并在接口中定义方法selectNewBooks(),selectNewBooks()根据上架时间查询图书信息。
package cn.edu.imust.mapper;
import cn.edu.imust.domain.Book;
import com.github.pagehelper.Page;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select;
public interface BookMapper {
@Select("SELECT * FROM book where book_status !='3' order by book_uploadtime DESC")
@Results(id = "bookMap",value = {
//id字段默认为false,表示不是主键
//column表示数据库表字段,property表示实体类属性名。
@Result(id = true,column = "book_id",property = "id"),
@Result(column = "book_name",property = "name"),
@Result(column = "book_isbn",property = "isbn"),
@Result(column = "book_press",property = "press"),
@Result(column = "book_author",property = "author"),
@Result(column = "book_pagination",property = "pagination"),
@Result(column = "book_price",property = "price"),
@Result(column = "book_uploadtime",property = "uploadTime"),
@Result(column = "book_status",property = "status"),
@Result(column = "book_borrower",property = "borrower"),
@Result(column = "book_borrowtime",property = "borrowTime"),
@Result(column = "book_returntime",property = "returnTime")
})
Page<Book> selectNewBooks();
}
3.实现Service层:
(1)在项目的src\main\java目录下,创建一个cn.edu.imust.entity包,在包中创建分页结果实体类PageResult,用于将查询的结果展示在页面。
package cn.edu.imust.entity;
import java.io.Serializable;
import java.util.List;
/**
* 分页结果的实体类
*/
public class PageResult implements Serializable {
private long total; // 总数
private List rows; // 返回的数据集合
public PageResult(long total, List rows) {
super();
this.total = total;
this.rows = rows;
}
public long getTotal() {
return total;
}
public void setTotal(long total) {
this.total = total;
}
public List getRows() {
return rows;
}
public void setRows(List rows) {
this.rows = rows;
}
}
(2)在cn.edu.imust.service包中,创建Service层的图书接口BookService,接口中定义查询最新上架图书的方法。
package cn.edu.imust.service;
import cn.edu.imust.entity.PageResult;
public interface BookService {
//查询最新上架的图书
PageResult selectNewBooks(Integer pageNum, Integer pageSize);
}
(3)在cn.edu.imust.service.impl包中,创建Service层的图书接口的实现类BookServiceImpl,重写接口中的selectNewBooks()方法。
package cn.edu.imust.service.impl;
import cn.edu.imust.domain.Book;
import cn.edu.imust.entity.PageResult;
import cn.edu.imust.mapper.BookMapper;
import cn.edu.imust.service.BookService;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@Transactional
public class BookServiceImpl implements BookService {
@Autowired
private BookMapper bookMapper;
/**
* 根据当前页码和每页需要展示的数据条数,查询最新上架的图书信息
* @param pageNum 当前页码
* @param pageSize 每页显示数量
*/
@Override
public PageResult selectNewBooks(Integer pageNum, Integer pageSize) {
// 设置分页查询的参数,开始分页
PageHelper.startPage(pageNum, pageSize);
Page<Book> page=bookMapper.selectNewBooks();
return new PageResult(page.getTotal(),page.getResult());
}
}
4.实现Controller:在cn.edu.imust.controller包中,创建图书控制器类BookController,在BookController类中定义方法selectNewbooks(),用于查询最新上架的图书,并将查询结果响应到新书推荐页面。
package cn.edu.imust.controller;
import cn.edu.imust.entity.PageResult;
import cn.edu.imust.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
/*
图书信息Controller
*/
@Controller
@RequestMapping("/book")
public class BookController {
//注入BookService对象
@Autowired
private BookService bookService;
/**
* 查询最新上架的图书
*/
@RequestMapping("/selectNewbooks")
public ModelAndView selectNewbooks() {
//查询最新上架的5个的图书信息
int pageNum = 1;
int pageSize = 5;
PageResult pageResult = bookService.selectNewBooks(pageNum, pageSize);
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("books_new");
modelAndView.addObject("pageResult", pageResult);
return modelAndView;
}
}
5.实现页面显示:在books_new.jsp中接收响应的数据,响应的数据是一个集合对象,遍历该集合对象,将遍历出来的内容展示在页面的数据表格中。
<!-- 数据表格 -->
<table id="dataList" class="table table-bordered table-striped table-hover dataTable text-center">
<thead>
<tr>
<th class="sorting_asc">图书名称</th>
<th class="sorting">图书作者</th>
<th class="sorting">出版社</th>
<th class="sorting">标准ISBN</th>
<th class="sorting">书籍状态</th>
<th class="sorting">借阅人</th>
<th class="sorting">借阅时间</th>
<th class="sorting">预计归还时间</th>
<th class="text-center">操作</th>
</tr>
</thead>
<tbody>
<c:forEach items="${pageResult.rows}" var="book">
<tr>
<td> ${book.name}</td>
<td>${book.author}</td>
<td>${book.press}</td>
<td>${book.isbn}</td>
<td>
<c:if test="${book.status ==0}">可借阅</c:if>
<c:if test="${book.status ==1}">借阅中</c:if>
<c:if test="${book.status ==2}">归还中</c:if>
</td>
<td>${book.borrower}</td>
<td>${book.borrowTime}</td>
<td>${book.returnTime}</td>
<td class="text-center">
<c:if test="${book.status ==0}">
<button type="button" class="btn bg-olive btn-xs" data-toggle="modal" data-target="#borrowModal"
onclick="findBookById(${book.id},'borrow')"> 借阅
</button>
</c:if>
<c:if test="${book.status ==1 ||book.status ==2}">
<button type="button" class="btn bg-olive btn-xs" disabled="true">借阅</button>
</c:if>
</td>
</tr>
</c:forEach>
</tbody>
</table>
<!-- 数据表格 /-->
本系统设计时,后台首页main.jsp的内容展示区默认展示新书推荐的内容,因此需要在后台首页的内容展示区引入新书推荐的路径。后台首页main.jsp具体的代码实现如下所示。
<!-- 内容展示区域 -->
<div class="content-wrapper">
<iframe width="100%" id="iframe" name="iframe" onload="SetIFrameHeight()"
frameborder="0" src="${pageContext.request.contextPath}/book/selectNewbooks"></iframe>
</div>
main.jsp中编写了如上代码,在main.jsp加载时会向
15.5.1.2借阅图书
借阅图书的功能大致如下:
(1)当用户登录成功后,单击图书列表中可借阅状态的“借阅”按钮,系统会弹出图书借阅的模态框,并发送根据图书 id 查询图书信息的异步请求。
(2)查询成功后,将查询到的图书信息回显到图书借阅的模态框中。
(3)在图书借阅的模态框中填写预计归还的日期并提交借阅请求,从而完成图书借阅。
接下来,根据如上功能要求,实现借阅图书。
1.实现DAO层:借阅图书功能包含根据id查询图书信息和借阅图书2个操作,其中借阅图书其实就是更新图书信息中的借阅的相关字段。在BookMapper接口中新增2个方法findById()和editBook()。
@Select("SELECT * FROM book where book_id=#{id}")
@ResultMap("bookMap")
//根据id查询图书信息
Book findById(String id);
//编辑图书信息
Integer editBook(Book book);
在项目的src/main/resources目录下创建层级为cn/edu/imust/mapper的文件夹;在该文件夹下创建于BookMapper接口同名的映射文件BookMapper.xml,在该映射文件中,使用
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.itheima.mapper.BookMapper">
>
<!--修改book信息-->
<update id="editBook" >
update book
<trim prefix="set" suffixOverrides=",">
<if test="name != null" >
book_name = #{name},
</if>
<if test="isbn != null" >
book_isbn = #{isbn},
</if>
<if test="press != null" >
book_press = #{press},
</if>
<if test="author != null" >
book_author = #{author},
</if>
<if test="pagination != null" >
book_pagination = #{pagination},
</if>
<if test="price != null" >
book_price = #{price},
</if>
<if test="uploadTime != null" >
book_uploadtime = #{uploadTime},
</if>
<if test="status != null" >
book_status = #{status},
</if>
<if test="borrower!= null" >
book_borrower= #{borrower },
</if>
<if test="borrowTime != null" >
book_borrowtime = #{borrowTime},
</if>
<if test="returnTime != null" >
book_returntime = #{returnTime}
</if>
</trim>
where book_id = #{id}
</update>
</mapper>
2.实现Service层:在文件BookService.java中的BookService接口中新增findById()方法,根据id查询图书信息;新增borrowBook()方法,用于借阅图书。
//根据id查询图书信息
Book findById(String id);
//借阅图书
Integer borrowBook(Book book);
在BookServiceImpl类中重写BookService接口的findById()方法和borrowBook()方法。
/**
* 根据id查询图书信息
* @param id 图书id
*/
public Book findById(String id) {
return bookMapper.findById(id);
}
/**
* 借阅图书
* @param book
* @return
*/
@Override
public Integer borrowBook(Book book) {
//根据id查询出需要借阅的完整图书信息
Book b = this.findById(book.getId()+"");
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
//设置当天为借阅时间
book.setBorrowTime(dateFormat.format(new Date()));
//设置所借阅的图书状态为借阅中
book.setStatus("1");
//将图书的价格设置在book对象中
book.setPrice(b.getPrice());
//将图书的上架设置在book对象中
book.setUploadTime(b.getUploadTime());
return bookMapper.editBook(book);
}
3.实现Controller:在entity包下创建一个结果信息类Result,用于封装页面操作结果和提示信息,具体代码如下所示。
package cn.edu.imust.entity;
import java.io.Serializable;
public class Result <T> implements Serializable {
private boolean success; //是否成功操作成功
private String message; //需要传递的信息
private T data; //需要传递的数据
public Result(boolean success, String message) {
super();
this.success=success;
this.message = message;
}
public Result(boolean success, String message, T data) {
this.success = success;
this.message = message;
this.data = data;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public boolean isSuccess() {
return success;
}
public void setSuccess(boolean success) {
this.success = success;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
在BookController类中新增根据id查询图书的方法findById(),新增借阅图书的方法borrowBook()。
/**
* 根据图书id查询图书信息
* @param id 查询的图书id
*/
@ResponseBody
@RequestMapping("/findById")
public Result<Book> findById(String id) {
try {
Book book=bookService.findById(id);
if(book==null){
return new Result(false,"查询图书失败!");
}
return new Result(true,"查询图书成功",book);
}catch (Exception e){
e.printStackTrace();
return new Result(false,"查询图书失败!");
}
}
/**
* 借阅图书
* @param book 借阅的图书
* @return
*/
@ResponseBody
@RequestMapping("/borrowBook")
public Result borrowBook(Book book, HttpSession session) {
//获取当前登录的用户姓名
String pname = ((User) session.getAttribute("USER_SESSION")).getName();
book.setBorrower(pname);
try {
//根据图书的id和用户进行图书借阅
Integer count = bookService.borrowBook(book);
if (count != 1) {
return new Result(false, "借阅图书失败!");
}
return new Result(true, "借阅成功,请到行政中心取书!");
} catch (Exception e) {
e.printStackTrace();
return new Result(false, "借阅图书失败!");
}
}
4.实现页面显示:在books_new.jsp页面代码中绑定鼠标单击事件,单击按钮时,将调用my.js文件中的findBookById()方法,findBookById()方法中会发起异步请求,并将响应数据回显到book_modal.jsp页面的模态对话框中。books_new.jsp页面中的“借阅”按钮新增代码如下所示。
<button type="button" class="btn bg-olive btn-xs" data-toggle="modal"
data-target="#borrowModal"
onclick="findBookById(${book.id},'borrow')"> 借阅
</button>
5.启动cloudlibrary项目,浏览器地址栏输入http://localhost:8080/cloudlibrary/index.jsp登录系统,对《边城》进行借阅,单击图书《边城》右侧的“借阅”按钮,弹出图书信息模态对话框,如图所示。
在上图中,填写图书归还时间。由于“保存”按钮绑定了onclick事件,触发事件后,程序会执行my.js文件中的borrow()方法,borrow()方法会将book_modal.jsp中的表单数据,提交到映射路径为“/book/borrowBook”的控制器,如果借阅成功,borrow()方法会异步查询所有的图书信息。
填写好归还时间后,单击“保存”按钮,弹出借阅成功提示框,如图所示。
需要注意的是,图书的归还时间不能早于借阅当天
由于尚未完成查询所有的图书信息的功能,此时单击“确定”按钮内容显示区域将出现404提示。
再次单击菜单栏的“首页”链接查询出新书推荐的图书信息,页面显示如下图所示。
从上图可以看出,《边城》的“借阅”按钮变成了灰色,表明图书借阅功能已经完成。
页面引入的my.js文件中,包含了本系统的绝大部分的自定义js代码。my.js文件的js代码实现都不复杂,本章在讲解时将会着重讲解Java代码和页面的逻辑代码,对my.js文件中的代码不进行讲解。大家可以自行查看。
15.5.2 图书借阅
图书借阅模块包括查询图书、新增图书、编辑图书和借阅图书这4个功能,其中,图书借阅功能和新书推荐模块中的图书借阅功能执行的是同样的代码,在此,就不进行重复讲解。新增图书和编辑图书是管理员角色才有的权限,当普通用户登录时,不会展示和开放对应的功能。
接下来,分别对查询图书、新增图书和编辑图书这3个功能的实现进行介绍。
15.5.2.1查询图书
查询图书时,用户可以根据条件查询所有未下架的图书信息,如果没有输入查询条件,就查询所有图书信息。由于数据库中的数据可能有很多,如果让这些数据在一个页面中全部显示出来,势必会使页面数据的可读性变得很差,所以本系统将查询的数据进行分页,每页默认展示10条数据。
登录系统后,在浏览器中输入地址http://localhost:8080/cloudlibrary/admin/books.jsp,访问图书借阅页面,显示效果如图所示。
从图中可以看出,图书借阅页面的查询条件包括图书名称、图书作者和出版社。用户在查询图书信息时,可以输入相应的查询条件进行查询,如果不输入任何条件,则系统会展示所有图书信息。
查询图书功能的具体实现步骤如下。
1.实现DAO层:根据查询条件是否为空进行查询语句的动态拼接。在BookMapper接口中新增查询方法searchBooks()。
@Select({"<script>" +
"SELECT * FROM book " +
"where book_status !='3'" +
"<if test=\"name != null\"> AND book_name like CONCAT('%',#{name},'%')</if>" +
"<if test=\"press != null\"> AND book_press like CONCAT('%', #{press},'%') </if>" +
"<if test=\"author != null\"> AND book_author like CONCAT('%', #{author},'%')</if>" +
"order by book_borrowtime" +
"</script>"
})
@ResultMap("bookMap")
//分页查询图书
Page<Book> searchBooks(Book book);
2.实现Service层:在文件BookService.java的BookService接口中新增查询图书的方法search(),具体代码如下所示。
//分页查询图书
PageResult search(Book book, Integer pageNum, Integer pageSize);
在BookServiceImpl类中重写BookService接口的search()方法,具体代码如下所示。
/**
* @param book 封装查询条件的对象
* @param pageNum 当前页码
* @param pageSize 每页显示数量
*/
@Override
public PageResult search(Book book, Integer pageNum, Integer pageSize) {
// 设置分页查询的参数,开始分页
PageHelper.startPage(pageNum, pageSize);
Page<Book> page=bookMapper.searchBooks(book);
return new PageResult(page.getTotal(),page.getResult());
}
3.在BookController类中新增查询图书的方法search()。
/**
* 分页查询符合条件且未下架图书信息
* @param book 查询的条件封装到book中
* @param pageNum 数据列表的当前页码
* @param pageSize 数据列表1页展示多少条数据
*/
@RequestMapping("/search")
public ModelAndView search(Book book, Integer pageNum, Integer pageSize, HttpServletRequest request) {
if (pageNum == null) {
pageNum = 1;
}
if (pageSize == null) {
pageSize = 10;
}
//查询到的图书信息
PageResult pageResult = bookService.search(book, pageNum, pageSize);
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("books");
//将查询到的数据存放在 ModelAndView的对象中
modelAndView.addObject("pageResult", pageResult);
//将查询的参数返回到页面,用于回显到查询的输入框中
modelAndView.addObject("search", book);
//将当前页码返回到页面,用于分页插件的分页显示
modelAndView.addObject("pageNum", pageNum);
//将当前查询的控制器路径返回到页面,页码变化时继续向该路径发送请求
modelAndView.addObject("gourl", request.getRequestURI());
return modelAndView;
}
4.实现页面效果:在后台首页main.jsp的导航侧栏中,配置“图书借阅”超链接的目标路径,配置代码如下所示。
<li >
<a href="${pageContext.request.contextPath}/book/search" target="iframe">
<i class="fa fa-circle-o"></i>图书借阅
</a>
</li>
编写了如上代码,当单击“图书借阅”超链接时,会向URL为“/book/search”的控制器发送请求,查询出当前未下架的图书信息。
5.启动项目,浏览器地址栏输入http://localhost:8080/cloudlibrary/index.jsp,使用管理员账号登录系统;
单击导航侧栏的“图书借阅”超链接,页面显示效果如图所示。
6.接下来,测试条件查询的效果,在上图书名称输入框中输入“Java”,单击“查询”按钮,页面显示效果如图所示。
从步骤5和步骤6的图中可以看出,图书信息按既定的查询条件查询出来了。至此,图书借阅模块中的查询图书功能已经完成。
15.5.2.2新增图书
单击上图书借阅页面左上角的“新增”按钮,系统会弹出一个图书信息模态对话框,如下图所示的效果图。下图的模态框用于填写新增的图书信息。模态框内的信息必须填写完整才能进行提交,其中图书的ISBN必须是13位数的ISBN编号。
新增图书功能的具体实现步骤如下。
1.实现DAO层:(1)在BookMapper接口中新增一个新增图书的方法addBook()。
//新增图书
Integer addBook(Book book);
(2)在BookMapper.xml映射文件中,使用
<!--新增图书-->
<insert id="addBook" parameterType="cn.edu.imust.domain.Book">
insert into book(book_id,book_name,book_isbn,book_press,book_author,book_pagination,book_price,book_uploadtime,book_status,book_borrower,book_borrowtime,book_returntime)
values (#{id},#{name},#{isbn},#{press},#{author},#{pagination},#{price},#{uploadTime},#{status},#{borrower},#{borrowTime},#{returnTime})
</insert>
2.实现Service层:(1)在BookService接口中添加新增图书的方法addBook(),具体代码如下所示。
//新增图书
Integer addBook(Book book);
(2)在BookServiceImpl类中重写BookService接口的addBook()方法,具体代码如下所示。
/**
* 新增图书
* @param book 页面提交的新增图书信息
*/
@Override
public Integer addBook(Book book) {
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
//设置新增图书的上架时间
book.setUploadTime(dateFormat.format(new Date()));
return bookMapper.addBook(book);
3.实现Controller:在BookController类中新增一个新增图书的方法addBook(),新增addBook()方法的代码如下所示。
/**
* 新增图书
* @param book 页面表单提交的图书信息
* 将新增的结果和向页面传递信息封装到Result对象中返回
*/
@ResponseBody
@RequestMapping("/addBook")
public Result addBook(Book book) {
try {
Integer count=bookService.addBook(book);
if(count!=1){
return new Result(false, "新增图书失败!");
}
return new Result(true, "新增图书成功!");
}catch (Exception e){
e.printStackTrace();
return new Result(false, "新增图书失败!");
}
}
4.实现页面效果:填好图书信息后,单击下图中的新增图书信息模态框的“保存”按钮后,模态对话框会自动隐藏。由于“保存”按钮同时绑定了鼠标单击事件,单击“保存”按钮触发事件,系统会执行my.js文件中的addOrEdit()方法,addOrEdit()方法判断操作是新增图书还是编辑图书,如果是新增图书的操作,addOrEdit()方法会将表单数据异步提交到映射路径为“/book/addBook”的控制器,并将新增的结果响应在页面。如果图书新增成功,页面会将最新的图书信息刷新显示。
5.重新启动项目,浏览器地址栏输入http://localhost:8080/cloudlibrary/index.jsp,使用管理员账号登录系统,点击图书借阅,再点击新增按钮;在新增图书的模态框中填写下图所示的内容。
在上图中,填写完新增图书信息后,单击“保存”按钮,页面弹出对话框如图所示。
在上图中,单击“确定”按钮,页面显示效果如下图所示。
从上面的页面显示效果图可以看出,提交的图书信息已经成功添加到数据库中。此时数据库中数据大于10条,数据列表分成2页展示。
至此,图书借阅模块的新增图书功能已经完成。
注意:由于在创建book数据表时,图书名称这个数据项的长度设置为32;故在新增图书时,书名不要过长,否则不能成功添加图书,可去IDEA的控制台查看输出的错误提示信息。
15.5.2.3编辑图书
编辑图书也是管理员用户才能执行的操作,且只有图书状态为可借阅时,才可以对图书进行编辑。编辑图书之前需要将对应的图书信息先查询出来显示在编辑的对话框中。单击《自在独行》图书对应的“编辑”按钮,
图书信息模态框显示内容如下图所示。
由于“编辑”按钮绑定了鼠标单击事件,单击“编辑”按钮触发事件,系统会调用my.js文件中的findBookbyId()方法。findBookbyId()方法根据参数判断当前执行的操作是否是编辑图书的操作,如果是,则发送根据id查询对应的图书信息的异步请求,并将响应数据回显在图书信息的模态框中。
在IDEA中上述操作过程所涉及到的内容如下:
编辑图书功能的具体步骤如下所示。
1.实现DAO层:由于编辑图书和之前实现的图书借阅类似,都是将当前操作的图书信息进行更新,所以编辑图书无须在BookMapper接口中新增方法,可以直接复用BookMapper接口中的editBook()方法即可。
2.实现Service层:在BookService接口中添加新增图书的方法editBook(),具体代码如下所示。
//编辑图书信息
Integer editBook(Book book);
在BookServiceImpl类中重写BookService接口的editBook()方法,具体代码如下所示。
/**
* 编辑图书信息
* @param book 图书信息
*/
@Override
public Integer editBook(Book book) {
return bookMapper.editBook(book);
}
3.实现Controller:在BookController类中新增一个编辑图书的方法editBook(),新增editBook()方法的代码如下所示。
/**
* 编辑图书信息
* @param book 编辑的图书信息
*/
@ResponseBody
@RequestMapping("/editBook")
public Result editBook(Book book) {
try {
Integer count= bookService.editBook(book);
if(count!=1){
return new Result(false, "编辑失败!");
}
return new Result(true, "编辑成功!");
}catch (Exception e){
e.printStackTrace();
return new Result(false, "编辑失败!");
}
}
4.实现页面效果:单击下图右下角“保存”按钮,图书信息模态框将自动隐藏。由于“保存”按钮绑定了鼠标单击事件,单击“保存”按钮触发事件,系统将会调用my.js文件中的addOrEdit()方法。addOrEdit()方法根据表单中的图书id是否为空,判断是添加图书还是编辑图书的操作。如果是编辑图书操作,将表单数据异步发送到映射路径为“/book/editBook”的控制器 。
<div class="modal-footer">
<button class="btn btn-success" data-dismiss="modal" aria-hidden="true" id="aoe" disabled onclick="addOrEdit()">保存
</button>
<button class="btn btn-default" data-dismiss="modal" aria-hidden="true">关闭</button>
</div>
IDEA中所涉及到的内容:
5.重启项目,浏览器地址栏输入http://localhost:8080/cloudlibrary/index.jsp,使用管理员账号登录系统,点击图书借阅;
点击上图的《自在独行》该信息右侧的编辑按钮;
将图书信息中的上架状态修改为下架,单击“保存”按钮,页面弹出编辑成功对话框,如下图所示。
在上图中,单击“确定”按钮,页面显示效果如下图所示。
从上面的页面显示效果图可以看出,刚才编辑的图书《自在独行》已经不见,说明图书已经下架成功,页面数据列表又变回1页显示了。
至此,图书借阅模块的编辑图书功能已经完成。
15.5.3 当前借阅
当前借阅模块包括查询图书、归还图书和确认归还这3个功能,其中,确认归还为管理员的权限。
下面分别对这3个功能的实现进行介绍。
15.5.3.1查询图书
单击上一个图的导航侧栏的“当前借阅”超链接时,系统会展示当前登录用户借阅但未归还的图书。由于用户申请图书归还时,需要管理员确认归还后才算真正归还图书,所以管理员查询出的当前借阅图书包括两部分,自己借阅未归还的图书和所有待归还确认的图书。
当前借阅模块的图书查询和图书借阅模块中的图书查询类似,可以按条件查询图书,如果不输入查询条件,就查询全部图书。对查询结果也进行分页显示。
查询图书功能的具体步骤如下所示。
1.实现DAO层:由于普通用户和管理员用户查询出的当前借阅的图书信息并不相同,所以可以定义2个不同的方法来供Service层调用。
在BookMapper接口中新增2个方法selectMyBorrowed()和selectBorrowed();selectMyBorrowed()方法用于查询当前用户未归还的图书信息;
selectBorrowed()方法用于查询当前用户未归还和所有待确认归还的图书信息。
@Select(
{"<script>" +
"SELECT * FROM book " +
"where book_borrower=#{borrower}" +
"AND book_status ='1'"+
"<if test=\"name != null\"> AND book_name like CONCAT('%',#{name},'%')</if>" +
"<if test=\"press != null\"> AND book_press like CONCAT('%', #{press},'%') </if>" +
"<if test=\"author != null\"> AND book_author like CONCAT('%', #{author},'%')</if>" +
"or book_status ='2'"+
"<if test=\"name != null\"> AND book_name like CONCAT('%',#{name},'%')</if>" +
"<if test=\"press != null\"> AND book_press like CONCAT('%', #{press},'%') </if>" +
"<if test=\"author != null\"> AND book_author like CONCAT('%', #{author},'%')</if>" +
"order by book_borrowtime" +
"</script>"})
@ResultMap("bookMap")
//查询借阅但未归还的图书和待归还确认的图书
Page<Book> selectBorrowed(Book book);
@Select({"<script>" +
"SELECT * FROM book " +
"where book_borrower=#{borrower}" +
"AND book_status in('1','2')"+
"<if test=\"name != null\"> AND book_name like CONCAT('%',#{name},'%')</if>" +
"<if test=\"press != null\"> AND book_press like CONCAT('%', #{press},'%') </if>" +
"<if test=\"author != null\"> AND book_author like CONCAT('%', #{author},'%')</if>" +
"order by book_borrowtime" +
"</script>"})
@ResultMap("bookMap")
//查询借阅但未归还的图书
Page<Book> selectMyBorrowed(Book book);
2.实现Service层:在BookService接口中添加一个查询当前借阅图书的方法searchBorrowed(),具体代码如下所示。
//查询当前借阅的图书
PageResult searchBorrowed(Book book, User user, Integer pageNum, Integer pageSize);
3.在BookServiceImpl类中重写BookService接口的searchBorrowed()方法,具体代码如下所示。
/**
* 查询用户当前借阅的图书
* @param book 封装查询条件的对象
* @param user 当前登录用户
* @param pageNum 当前页码
* @param pageSize 每页显示数量
*/
@Override
public PageResult searchBorrowed(Book book, User user, Integer pageNum, Integer pageSize) {
// 设置分页查询的参数,开始分页
PageHelper.startPage(pageNum, pageSize);
Page<Book> page;
//将当前登录的用户放入查询条件中
book.setBorrower(user.getName());
//如果是管理员,查询自己借阅但未归还的图书和所有待确认归还的图书
if("ADMIN".equals(user.getRole())){
page= bookMapper.selectBorrowed(book);
}else {
//如果是普通用户,查询自己借阅但未归还的图书
page= bookMapper.selectMyBorrowed(book);
}
return new PageResult(page.getTotal(),page.getResult());
}
4.实现Controller:在BookController类中新增一个查询当前借阅图书的方法searchBorrowed()。
/**
*分页查询当前被借阅且未归还的图书信息
* @param pageNum 数据列表的当前页码
* @param pageSize 数据列表1页展示多少条数据
*/
@RequestMapping("/searchBorrowed")
public ModelAndView searchBorrowed(Book book,Integer pageNum, Integer pageSize, HttpServletRequest request) {
if (pageNum == null) {
pageNum = 1;
}
if (pageSize == null) {
pageSize = 10;
}
//获取当前登录的用户
User user = (User) request.getSession().getAttribute("USER_SESSION");
PageResult pageResult = bookService.searchBorrowed(book,user, pageNum, pageSize);
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("book_borrowed");
//将查询到的数据存放在 ModelAndView的对象中
modelAndView.addObject("pageResult", pageResult);
//将查询的参数返回到页面,用于回显到查询的输入框中
modelAndView.addObject("search", book);
//将当前页码返回到页面,用于分页插件的分页显示
modelAndView.addObject("pageNum", pageNum);
//将当前查询的控制器路径返回到页面,页码变化时继续向该路径发送请求
modelAndView.addObject("gourl", request.getRequestURI());
return modelAndView;
}
5.实现页面效果:在后台首页main.jsp的导航侧栏中,配置“当前借阅”超链接的目标路径。配置代码如下所示。
<li>
<a href="${pageContext.request.contextPath}/book/searchBorrowed" target="iframe">
<i class="fa fa-circle-o"></i>当前借阅
</a>
</li>
6.启动项目,浏览器地址栏输入http://localhost:8080/cloudlibrary/index.jsp,使用管理员账号登录系统,单击导航侧栏中的“当前借阅”超链接,页面显示效果如图所示。(也可以用普通用户的账号登录,测试该功能)
从上图可以得出,单击“当前借阅”超链接后,系统将当前登录用户的借阅情况展示在页面中了。
至此,当前借阅模块的查询图书功能已经完成。
15.5.3.2归还图书
在归还图书时,需要由借阅者先在系统中提交归还图书的申请,然后将图书归还到指定还书点,管理员确认图书归还后,图书才真正归还成功。
当用户申请归还图书时,只需在当前借阅的图书列表中,单击右侧的“归还”按钮选择归还图书即可,申请归还后,图书的状态由借阅中变为归还中。
归还图书的具体实现步骤如下:
1.实现DAO层:提交归还图书的申请,只是修改图书的借阅状态,因此复用BookMapper接口的editBook()方法即可。
2.实现Service层:在BookService接口中添加一个归还图书的方法returnBook(),具体代码如下所示。
//归还图书
boolean returnBook(String id,User user);
3.在BookServiceImpl类中重写BookService接口的returnBook()方法,具体代码如下所示。
/**
* 归还图书
* @param id 归还的图书的id
* @param user 归还的人员,也就是当前图书的借阅者
*/
@Override
public boolean returnBook(String id, User user) {
//根据图书id查询出图书的完整信息
Book book = this.findById(id);
//再次核验当前登录人员和图书借阅者是不是同一个人
boolean rb=book.getBorrower().equals(user.getName());
//如果是同一个人,允许归还
if(rb){
//将图书借阅状态修改为归还中
book.setStatus("2");
bookMapper.editBook(book);
}
return rb;
}
4.实现Controller:在BookController类中新增一个归还图书的方法returnBook(),新增的代码如下所示。
/**
* 归还图书
* @param id 归还的图书的id
*/
@ResponseBody
@RequestMapping("/returnBook")
public Result returnBook(String id, HttpSession session) {
//获取当前登录的用户信息
User user = (User) session.getAttribute("USER_SESSION");
try {
boolean flag = bookService.returnBook(id, user);
if (!flag) {
return new Result(false, "还书失败!");
}
return new Result(true, "还书确认中,请先到行政中心还书!");
}catch (Exception e){
e.printStackTrace();
return new Result(false, "还书失败!");
}
}
5.实现页面效果:当前借阅图书列表中的“归还”按钮绑定了鼠标单击事件,当事件触发时,会执行文件my.js中的returnBook()方法。
returnBook()方法将归还的图书id作为参数向映射路径为“/book/returnBook”的控制器发送异步请求,并将请求结果的信息展示在页面中,显示当前借阅列表最新的信息。
book_borrowed.jsp页面中“归还”按钮的代码如下所示。
<c:if test="${book.status ==1}">
<button type="button" class="btn bg-olive btn-xs" onclick="returnBook(${book.id})">归还
</button>
</c:if>
6.启动项目,浏览器地址栏输入http://localhost:8080/cloudlibrary/index.jsp,使用普通用户账号登录系统,单击后台首页导航侧栏中的“当前借阅”超链接,页面显示效果如图所示。
从上图可以看出,用户张三当前有一本借阅中的图书《沉默的巡游》。在上图中单击数据列表右侧的“归还”按钮,弹出确认归还图书提示框,如下图所示。
单击上图中的“确定”按钮,此时页面会弹出图书归还提示框,具体如下图所示。
单击上图所示的“确定”按钮,确认图书归还中,页面显示效果如下图所示。
从图中可以看出,图书归还申请后,图书的状态变为了归还中。此时,将图书归还到指定还书点后,由管理员确认图书归还,以完成图书的真正归还。
至此,当前借阅模块的归还图书功能已经完成。
15.5.3.3确认归还
用户在申请图书归还后,需要由图书管理员进行归还确认。使用管理员账号登录系统,单击下图所示导航侧栏中的“当前借阅”超链接。
可以看出,管理员在当前借阅的页面中,可以看到本人的当前借阅情况和所有用户的待归还确认的图书信息。
当管理员进行归还确认的操作后,本次图书归还完成。
图书归还完成之后,需要将本次借阅情况记录在借阅记录表中(本节未实现),并且清空数据库的图书表中的当前图书的借阅信息,图书又变为可借阅状态。
确认归还时应先清空数据库中当前图书的借阅信息,具体步骤如下:
1.实现DAO层:归还确认的操作,只是将图书的借阅信息进行清除,所以复用BookMapper接口的editBook()方法即可。
2.实现Service层:在BookService接口中添加一个归还图书的方法returnConfirm(),具体代码如下所示。
//归还确认
Integer returnConfirm(String id);
3.在BookServiceImpl类中重写BookService接口的returnConfirm()方法,具体代码如下所示。
/**
* 归还确认
* @param id 待归还确认的图书id
*/
@Override
public Integer returnConfirm(String id) {
// 根据图书id查询图书的完整信息
Book book = this.findById(id);
// 将图书的借阅状态修改为可借阅
book.setStatus("0");
book.setBorrower(""); // 清除当前图书的借阅人信息
book.setBorrowTime(""); // 清除当前图书的借阅时间信息
// 清除当前图书的预计归还时间信息
book.setReturnTime("");
return bookMapper.editBook(book);
}
4.实现Controller:在BookController类中新增一个图书归还确认的方法returnConfirm(),新增的代码如下所示。
/**
* 确认图书归还
* @param id 确认归还的图书的id
*/
@ResponseBody
@RequestMapping("/returnConfirm")
public Result returnConfirm(String id) {
try {
Integer count=bookService.returnConfirm(id);
if(count!=1){
return new Result(false, "确认失败!");
}
return new Result(true, "确认成功!");
}catch (Exception e){
e.printStackTrace();
return new Result(false, "确认失败!");
}
}
5.实现页面效果:当前借阅数据列表中的“归还确认”按钮绑定了鼠标单击事件,当事件触发时,会执行文件my.js中的returnConfirm()方法。book_borrowed.jsp页面中“归还确认”按钮的代码如下所示。
<c:if test="${book.status ==2}">
<button type="button" class="btn bg-olive btn-xs" disabled="true">归还中</button>
<c:if test="${USER_SESSION.role =='ADMIN'}">
<button type="button" class="btn bg-olive btn-xs" onclick="returnConfirm(${book.id})">
归还确认
</button>
</c:if>
</c:if>
6.启动项目,浏览器地址栏输入http://localhost:8080/cloudlibrary/index.jsp,使用管理员账号登录系统,点击下图右侧的当前借阅,有如下显示:
单击上图所示右侧的“归还确认”按钮。页面会弹出归还确认提示框,如下图所示。
单击上图所示的“确认”按钮,会弹出归还确认的结果提示框,如下图所示。
单击上图所示的“确认”按钮,页面将会显示当前借阅最新的图书数据列表,具体如下图所示。
单击上图所示的导航侧栏中“图书借阅”超链接,查看最新的图书借阅的数据列表,具体如下图所示。
从图中所示的数据列表可以看出,归还确认后的图书《沉默的巡游》已经清除借阅信息,其状态已变为可借阅。
至此,当前借阅的确认归还功能已经完成。
15.5.4 借阅记录
本系统设定图书借阅是指从借阅到归还确认后的一次完整借阅,借阅记录主要是记录系统用户每次的完整借阅情况。
借阅记录包含新增借阅记录和查询借阅记录2个功能,其中,借阅记录在归还确认时新增,查询借阅记录分为全部查询和按条件查询。
15.5.4.1新增借阅记录
实现步骤如下:
1.创建持久化类:在cn.edu.imust.domain包中,创建借阅记录类Record,在Record 类中声明与借阅记录数据表对应的属性并定义各个属性的getter/setter方法。
package cn.edu.imust.domain;
import java.io.Serializable;
public class Record implements Serializable {
private Integer id; //图书借阅id
private String bookname; //借阅的图书名称
private String bookisbn; //借阅的图书的ISBN编号
private String borrower; //图书借阅人
private String borrowTime; //图书借阅时间
private String remandTime; //图书归还时间
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getBookname() {
return bookname;
}
public void setBookname(String bookname) {
this.bookname = bookname;
}
public String getBookisbn() {
return bookisbn;
}
public void setBookisbn(String bookisbn) {
this.bookisbn = bookisbn;
}
public String getBorrower() {
return borrower;
}
public void setBorrower(String borrower) {
this.borrower = borrower;
}
public String getBorrowTime() {
return borrowTime;
}
public void setBorrowTime(String borrowTime) {
this.borrowTime = borrowTime;
}
public String getRemandTime() {
return remandTime;
}
public void setRemandTime(String remandTime) {
this.remandTime = remandTime;
}
}
2.实现DAO层:(1)在cn.edu.imust.mapper包中,创建一个Record接口,创建一个Record接口,并在接口中定义方法addRecord()用于新增借阅记录操作。
package cn.edu.imust.mapper;
import cn.edu.imust.domain.Record;
public interface RecordMapper {
//新增借阅记录
Integer addRecord(Record record);
}
3.实现DAO层:(2)在resources文件夹的cn\edu\imust\mapper文件夹下创建RecordMapper接口同名的映射文件RecordMapper.xml,在映射文件中使用
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.edu.imust.mapper.RecordMapper">
<insert id="addRecord">
insert into record(record_id,record_bookname,record_bookisbn,record_borrower,record_borrowtime,record_remandtime)
values (#{id},#{bookname},#{bookisbn},#{borrower},#{borrowTime},#{remandTime})
</insert>
</mapper>
4.实现Service层:(1)在cn.edu.imust.service包中,创建Service层的借阅记录接口RecordService ,在RecordService接口中定义新增借阅记录的方法addRecord()。
package cn.edu.imust.service;
import cn.edu.imust.domain.Record;
/**
* 借阅记录接口
*/
public interface RecordService {
//新增借阅记录
Integer addRecord(Record record);
}
5.实现Service层:(2)在cn.edu.imust.service.impl包中,创建Service层的借阅记录接口的实现类RecordServiceImpl,在RecordServiceImpl类中重写RecordService接口中的addRecord()方法。
package cn.edu.imust.service.impl;
import cn.edu.imust.domain.Record;
import cn.edu.imust.mapper.RecordMapper;
import cn.edu.imust.service.RecordService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@Transactional
public class RecordServiceImpl implements RecordService {
@Autowired
private RecordMapper recordMapper;
/**
* 新增借阅记录
* @param record 新增的借阅记录
*/
@Override
public Integer addRecord(Record record) {
return recordMapper.addRecord(record);
}
}
6.修改BookServiceImpl类中的returnConfirm()方法;首先在BookServiceImpl类中声明并注入RecordService对象;定义setRecord()方法实现根据图书信息设置借阅记录的信息;在returnConfirm()方法执行归还确认无误时,调用setRecord()方法设置借阅记录信息,并且调用RecordService的addRecord()方法新增借阅记录;
@Autowired
//注入RecordService对象
private RecordService recordService;
/**
* 归还确认
* @param id 待归还确认的图书id
*/
@Override
public Integer returnConfirm(String id) {
// 根据图书id查询图书的完整信息
Book book = this.findById(id);
//根据归还确认的图书信息,设置借阅记录
Record record = this.setRecord(book);
// 将图书的借阅状态修改为可借阅
book.setStatus("0");
book.setBorrower(""); // 清除当前图书的借阅人信息
book.setBorrowTime(""); // 清除当前图书的借阅时间信息
// 清除当前图书的预计归还时间信息
book.setReturnTime("");
Integer count= bookMapper.editBook(book);
//如果归还确认成功,则新增借阅记录
if(count==1){
return recordService.addRecord(record);
}
return 0;
}
/**
* 根据图书信息设置借阅记录的信息
* @param book 借阅的图书信息
*/
private Record setRecord(Book book){
Record record=new Record();
//设置借阅记录的图书名称
record.setBookname(book.getName());
//设置借阅记录的图书isbn
record.setBookisbn(book.getIsbn());
//设置借阅记录的借阅人
record.setBorrower(book.getBorrower());
//设置借阅记录的借阅时间
record.setBorrowTime(book.getBorrowTime());
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
//设置图书归还确认的当天为图书归还时间
record.setRemandTime(dateFormat.format(new Date()));
return record;
}
15.5.4.2查询借阅记录
在借阅记录页面record.jsp中,可以根据查询条件借阅人和图书名称来查询对应的借阅记录,其中根据借阅人查询借阅记录是管理员才有的权限。如果查询条件为空,则忽略查询条件查询所有借阅记录。
实现步骤如下。
1.实现DAO层:在文件RecordMapper接口中新增searchRecords()方法用于查询借阅记录的。
@Select({"<script>" +
"SELECT * FROM record " +
"where 1=1" +
"<if test=\"borrower != null\">AND record_borrower like CONCAT('%',#{borrower},'%')</if>" +
"<if test=\"bookname != null\">AND record_bookname like CONCAT('%',#{bookname},'%') </if>" +
"order by record_remandtime DESC" +
"</script>"
})
@Results(id = "recordMap",value = {
//id字段默认为false,表示不是主键
//column表示数据库表字段,property表示实体类属性名。
@Result(id = true,column = "record_id",property = "id"),
@Result(column = "record_bookname",property = "bookname"),
@Result(column = "record_bookisbn",property = "bookisbn"),
@Result(column = "record_borrower",property = "borrower"),
@Result(column = "record_borrowtime",property = "borrowTime"),
@Result(column = "record_remandtime",property = "remandTime")
})
//查询借阅记录
Page<Record> searchRecords(Record record);
2.实现Service层:(1)在RecordService接口中,添加查询借阅记录的方法searchRecords(),具体代码如下所示。
//查询借阅记录
PageResult searchRecords(Record record, User user, Integer pageNum, Integer pageSize);
3.实现Service层:(2)在RecordServiceImpl类中重写RecordService接口中的searchRecords()方法,具体代码如下所示。
/**
* 查询借阅记录
* @param record 借阅记录的查询条件
* @param user 当前的登录用户
* @param pageNum 当前页码
* @param pageSize 每页显示数量
*/
@Override
public PageResult searchRecords(Record record, User user, Integer pageNum, Integer pageSize) {
// 设置分页查询的参数,开始分页
PageHelper.startPage(pageNum, pageSize);
//如果不是管理员,则查询条件中的借阅人设置为当前登录用户
if(!"ADMIN".equals(user.getRole())){
record.setBorrower(user.getName());
}
Page<Record> page= recordMapper.searchRecords(record);
return new PageResult(page.getTotal(),page.getResult());
}
4.实现Controller:在cn.edu.imust.contorller包中,创建借阅记录控制器类RecordController,在该类中定义方法searchRecords()用于查询借阅记录,并将查询结果响应到借阅记录的页面 。
package cn.edu.imust.controller;
import cn.edu.imust.domain.Record;
import cn.edu.imust.domain.User;
import cn.edu.imust.entity.PageResult;
import cn.edu.imust.service.RecordService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
@Controller
@RequestMapping("/record")
public class RecordController {
@Autowired
private RecordService recordService;
/**
* 查询借阅记录
* @param record 借阅记录的查询条件
* @param pageNum 当前页码
* @param pageSize 每页显示数量
*/
@RequestMapping("/searchRecords")
public ModelAndView searchRecords(Record record, HttpServletRequest request, Integer pageNum, Integer pageSize){
if(pageNum==null){
pageNum=1;
}
if(pageSize==null){
pageSize=10;
}
//获取当前登录用户的信息
User user = ((User) request.getSession().getAttribute("USER_SESSION"));
PageResult pageResult=recordService.searchRecords(record,user,pageNum,pageSize);
ModelAndView modelAndView=new ModelAndView();
modelAndView.setViewName("record");
//将查询到的数据存放在 ModelAndView的对象中
modelAndView.addObject("pageResult",pageResult);
//将查询的参数返回到页面,用于回显到查询的输入框中
modelAndView.addObject("search",record);
//将当前页码返回到页面,用于分页插件的分页显示
modelAndView.addObject("pageNum",pageNum);
//将当前查询的控制器路径返回到页面,页码变化时继续向该路径发送请求
modelAndView.addObject("gourl", request.getRequestURI());
return modelAndView;
}
}
5.实现页面效果:在后台首页main.jsp的导航侧栏中,配置“借阅记录”超链接的目标路径。配置代码如下所示。
<li >
<a href="${pageContext.request.contextPath}/record/searchRecords" target="iframe">
<i class="fa fa-circle-o"></i>借阅记录
</a>
</li>
至此,借阅记录模块的查询借阅记录的功能已经完成。
下面测试借阅记录的添加借阅记录和查询借阅记录的功能。
6.启动项目,浏览器地址栏输入http://localhost:8080/cloudlibrary/index.jsp,使用普通用户借阅2本图书,借阅完成后单击导航侧栏中“当前借阅”,如图所示。
7.在上图中,普通用户有2本借阅中待归还的图书,单击图书《SpringBoot企业级开发教程》右侧的“归还”按钮,确认之后,此时普通用户“当前借阅”如下图所示。
8.在上图中,普通用户有2本借阅未归还的图书,其中图书《SpringBoot企业级开发教程》状态是归还中,图书《Java EE企业级应用开发教程第2版》状态是待归还。
退出当前普通用户;然后使用管理员用户登录,管理员用户的“当前借阅”页面如下图所示。
9.单击上图中的“归还确认”按钮,页面效果如下图所示。
10.在上图中,单击导航侧栏中“借阅记录”,借阅记录页面如下图所示。
从图中可以看出,归还确认后,新增了《SpringBoot企业级开发教程》的借阅记录。说明借阅记录的新增借阅记录和查询借阅记录成功实现了。
此时,退出管理员账号;然后以普通用户账号登录,点击借阅记录,也会有相应数据显示。
15.6 访问权限控制
至此,云借阅图书管理系统主要模块功能已全部实现,但是项目此时存在一定的隐患,普通用户登录成功后,访问控制器类中管理员才有的添加和编辑方法也不会被拒绝。为了解决这个隐患,可以将普通用户能访问的控制器路径存放在配置文件中,然后在拦截器中对用户访问进行判断,如果访问的用户具有普通用户的权限,则放行,否则对请求不予放行。
接下来,根据上述方案实现访问权限的控制,具体步骤如下所示。
1.创建并编写配置文件:在项目的resources文件夹中,创建普通用户可以访问的资源配置文件ignoreUrl.properties,在配置文件中编写普通用户可以访问的资源路径,具体如下所示。
ignoreUrl=/logout,/selectNewbooks,/findById,/borrowBook,/search,/searchBorrowed,/returnBook,/searchRecords
2.加载配置文件:在cn.edu.imust.config包中,在SpringMvcConfig配置类中加载ignoreUrl.properties配置文件,并将读取到的配置文件的内容设置到自定义拦截器中。
package cn.edu.imust.config;
import cn.edu.imust.interceptor.ResourcesInterceptor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.web.servlet.config.annotation.*;
import java.util.List;
/**
* @类名 SpringMvcConfig.java
* @作者 ZLY
* @版本 1.0.0
* @描述 TODO
* @创建时间 2022年06月15日 12:06:00
*/
@Configuration
@PropertySource("classpath:ignoreUrl.properties")
//等同于<context:component-scan base-package="cn.edu.imust.controller"/>
@ComponentScan({"cn.edu.imust.controller"})
/*@Import({MyWebMvcConfig.class})*/
@EnableWebMvc
public class SpringMvcConfig implements WebMvcConfigurer {
@Value("#{'${ignoreUrl}'.split(',')}")
private List<String> ignoreUrl;
@Bean
public ResourcesInterceptor resourcesInterceptor(){
return new ResourcesInterceptor(ignoreUrl);
}
/*
* 在注册的拦截器类中添加自定义拦截器
* addPathPatterns()方法设置拦截的路径
* excludePathPatterns()方法设置不拦截的路径
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor( new ResourcesInterceptor())
.addPathPatterns("/**")
.excludePathPatterns("/css/**","/js/**","/img/**");
}
/*
*开启对静态资源的访问
* 类似在Spring MVC的配置文件中设置<mvc:default-servlet-handler/>元素
*/
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.jsp("/admin/",".jsp");
}
}
3.更新自定义拦截器:之前自定义的拦截器中,只对用户的登录状态和登录访问进行判断,为判断当前用户是否对当前访问路径有权限,需要更新cn.edu.imust.interceptor包中的ResourcesInterceptor拦截器 。
package cn.edu.imust.interceptor;
import cn.edu.imust.domain.User;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
/**
* 资源拦截器
*/
public class ResourcesInterceptor extends HandlerInterceptorAdapter {
//任意角色都能访问的路径
private List<String> ignoreUrl;
public ResourcesInterceptor(List<String> ignoreUrl) {
this.ignoreUrl = ignoreUrl;
}
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws
Exception {
User user = (User) request.getSession().getAttribute("USER_SESSION");
//获取请求的路径
String uri = request.getRequestURI();
//如果用户是已登录状态,判断访问的资源是否有权限
if (user != null) {
// return true;
//如果是管理员,放行
if ("ADMIN".equals(user.getRole())) {
return true;
}
//如果是普通用户
else if (!"ADMIN".equals(user.getRole())) {
for (String url : ignoreUrl) {
//访问的资源不是管理员权限的资源,放行
if (uri.indexOf(url) >= 0) {
return true;
}
}
}
}
//对用户登录的相关请求,放行
if (uri.indexOf("login") >= 0) {
return true;
}
//其他情况都直接跳转到登录页面
request.setAttribute("msg", "您还没有登录,请先登录!");
request.getRequestDispatcher("/admin/login.jsp").forward(request, response);
return false;
}
}
仔细与之前的拦截器对比,有如下内容增加:
但在修改了拦截器的代码后,造成了SpringMvcConfig类产生了异常:
可以用如下方式解决:
至此,自定义拦截器对访问权限的控制已经完成。
4.接下来测试用户的访问权限。启动项目,浏览器地址栏输入http://localhost:8080/cloudlibrary/index.jsp,使用普通用户登录系统;
在该浏览器中,打开一个新的标签页面,在其中访问图书下架的方法,访问路径为http://localhost:8080/cloudlibrary/book/editBook?id=1&status=3,此时页面跳转到用户登录页面,如图所示。
5.在上图使用管理员登录系统后,浏览器中访问图书下架的方法,访问路径为http://localhost:8080/cloudlibrary/book/editBook?id=1&status=3,此时页面跳转如图所示。
从图中所示的内容可以得出,系统响应回来的的JSON数据提示编辑成功。从步骤4和步骤5所示的图中可以看出来,拦截器的访问权限已经设置成功。