15云借阅图书管理系统

本章将通过SSM(Spring+Spring MVC+MyBatis)框架知识来实现一个简单的云借阅图书管理系统。云借阅图书管理系统在开发过程中,整合了三大框架,并在框架整合的基础上实现了系统功能。

15.1 系统概述

15.1.1 系统功能介绍

本系统后台使用SSM框架编写,前台页面使用当前主流的Bootstrap和jQuery框架编写。
云借阅图书管理系统主要实现了两大功能模块:用户登录模块图书管理模块

  • 用户登录模块主要用于实现用户的登录与注销;
  • 图书管理模块主要用于管理图书,如新书推荐、图书借阅等。

云借阅系统功能结构图
image.png

15.1.2 系统架构设计

云借阅系统结构层次
根据功能的不同,云借阅图书管理系统项目结构可以划分为以下几个层次。

  • 持久对象层(持久层或持久化层):该层由若干持久化类(实体类)组成。
  • 数据访问层(DAO层):该层由若干DAO接口和MyBatis映射文件组成。DAO接口的名称统一以Mapper结尾,且MyBatis的映射文件名称要与接口的名称相同。
  • 业务逻辑层(Service层):该层由若干Service接口和实现类组成。逻辑层主要用于实现系统的业务逻辑。
  • Web表现层:该层主要包括Spring MVC中的Controller类和JSP页面。

云借阅系统各个层次的关系和作用
image.png

15.1.3 文件组织结构

image.png
image.png

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;
    image.png
    (2)选择所创建的数据库
    USE cloudlibrary;
    image.png
    (3)导入数据库文件
    image.png
    image.png
    查看是否成功导入数据:
    image.png

    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

    1. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    2. 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,访问效果如图所示。

image.png
至此,项目环境搭建完成,项目目录结构如下:
image.png

15.4 用户登录模块

15.4.1 用户登录

用户登录流程图
image.png
用户登录过程中首先要验证用户名和密码是否正确,如果正确,可以成功登录系统,系统会自动跳转到主页;如果错误,则在登录页面给出错误提示信息。
实现用户登录功能,步骤如下:
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">登&nbsp;&nbsp;录</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表中的数据。
image.png
启动项目;在浏览器地址栏中输入:http://localhost:8080/cloudlibrary/index.jsp访问登录页面;
image.png
在登录页面中分别输入账号admin2@imust.edu.cn和密码123456,单击“登录”按钮登录系统,登录成功后系统后台首页如图所示。
image.png

15.4.2 实现登录验证

虽然现在已经实现了用户登录功能,但此功能还并不完善。假设控制器类中也存在其他访问系统首页的方法,那么用户完全可以绕过登录步骤,而直接通过访问该方法进入系统后台首页。
为了验证上述假设,在UserController控制器类中新增一个方法toMainPage(),用于跳转到系统后台首页,其代码如下所示。

    @RequestMapping("/toMainPage")
    public String  toMainPage(){
        return "main";
    }

如上代码中toMainPage()方法只用于页面跳转。toMainPage()方法会处理URL为toMainPage的请求,并跳转到名称为main的页面。
当其他系统访问系统首页时,此时,不进行用户登录,直接在浏览器访问跳转到系统后台首页的地址http://localhost:8080/cloudlibrary/toMainPage,页面跳转如下图所示。
image.png
从上图能看出来,未登录也能直接访问到系统后台首页。显然,让未登录的用户直接访问到系统的后台页面,将是十分不安全的。
未登录用户也能直接访问到系统后台首页,这对系统来说是不安全的。为了避免此种情况的发生,提升系统的安全性,可以创建一个拦截器来拦截所有请求。当用户处于登录状态时,直接放行该用户的请求;如果用户没有登录,但是访问的是登录相关的请求,也放行;否则将请求转发到登录页面,并提示用户登录。
拦截器的执行流程图
image.png
用户登录验证的具体实现如下:
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,页面跳转如图所示。
image.png
从图中可以看出,未登录的用户直接执行访问控制器方法后,并没有成功跳转到系统后台首页,而是转发到了系统登录页面,同时在登录页面中也给出了用户未登录的提示信息。这表明用户登录验证功能已成功实现。

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.重启项目并登录系统后,单击系统后台页面中的“注销”链接,跳转到的页面如图所示。
image.png
image.png
从图中可以看出,用户管理员小二的登录状态已经被注销,并跳转到了登录页面。

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加载时会向