会话跟踪技术 - Cookie 和 Session 快速上手 + 登陆注册案例

1、 会话跟踪技术概述

  • 会话

    • 概念:用户打开浏览器,访问web服务器的资源,会话建立,直到有一方断开连接,会话结束。在一次会话中可以包含多次请求和响应。

    • 举例

      • 从浏览器发出请求到服务端响应数据给前端之后,一次会话(在浏览器和服务器之间)就被建立了
      • 会话被建立后,如果浏览器或服务端都没有被关闭,则会话就会持续建立着
      • 浏览器和服务器就可以继续使用该会话进行请求发送和响应,上述的整个过程就被称之为会话
  • 会话跟踪

    • 概念:一种维护浏览器状态的方法,服务器需要识别多次请求是否来自于同一浏览器,以便在同一次会话的多次请求间共享数据

    • 举例

      • 服务器会收到多个请求,这多个请求可能来自多个浏览器,如上图中的6个请求来自3个浏览器
      • 服务器需要用来识别请求是否来自同一个浏览器
      • 服务器用来识别浏览器的过程,这个过程就是会话跟踪
      • 服务器识别浏览器后就可以在同一个会话中多次请求之间来共享数据
  • 使用会话跟踪技术的原因

    • 浏览器和服务器之间使用的是HTTP请求来进行数据传输
    • HTTP协议是无状态的,每次浏览器向服务器请求时,服务器都会将该请求视为新的请求
    • HTTP协议设计成无状态的目的是让每次请求之间相互独立,互不影响
    • 请求与请求之间独立后,就无法实现多次请求之间的数据共享
  • 应用场景举例

    • 验证码比对、保持登录状态等
  • 会话跟踪技术的具体实现方式

    • 客户端:Cookie
    • 服务端:Session

2.1 Cookie的概念和工作流程

  • 概念:客户端会话技术,将数据保存到客户端,以后每次请求都携带Cookie数据进行访问。

  • 工作流程

    1. 服务端提供了两个Servlet,分别是ServletA和ServletB
    2. 浏览器发送HTTP请求1给服务端,服务端ServletA接收请求并进行业务处理
    3. 服务端ServletA在处理的过程中可以创建一个Cookie对象并将某些数据存入Cookie
    4. 服务端ServletA在响应数据的时候,会把Cookie对象响应给浏览器
    5. 浏览器接收到响应数据,会把Cookie对象中的数据存储在浏览器内存中,此时浏览器和服务端就建立了一次会话
    6. 在同一次会话中浏览器再次发送HTTP请求2给服务端ServletB,浏览器会携带Cookie对象中的所有数据
    7. ServletB接收到请求和数据后,就可以获取到存储在Cookie对象中的数据,这样同一个会话中的多次请求之间就实现了数据共享

2.2 Cookie的基本使用

2.2.1 发送Cookie

  • 基本使用

    1. 创建Cookie对象,并设置数据

      Cookie cookie = new Cookie("key","value");
      
    2. 发送Cookie到客户端:使用response对象

      response.addCookie(cookie);
      
  • 案例

    • 具体步骤

      需求:在Servlet中生成Cookie对象并存入数据,然后将数据发送给浏览器

      1.创建Maven项目,项目名称为cookie-demo,并在pom.xml添加依赖

      2.编写Servlet类,名称为AServlet

      3.在AServlet中创建Cookie对象,存入数据,发送给前端

      4.启动测试,在浏览器查看Cookie对象中的值

    • 依赖

      <?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">
          <modelVersion>4.0.0</modelVersion>
      
          <groupId>priv.dandelion</groupId>
          <artifactId>cookie-demo</artifactId>
          <version>1.0-SNAPSHOT</version>
          <packaging>war</packaging>
      
          <properties>
              <maven.compiler.source>8</maven.compiler.source>
              <maven.compiler.target>8</maven.compiler.target>
          </properties>
      
          <dependencies>
              <!--servlet-->
              <dependency>
                  <groupId>javax.servlet</groupId>
                  <artifactId>javax.servlet-api</artifactId>
                  <version>3.1.0</version>
                  <scope>provided</scope>
              </dependency>
              <!--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>
          </dependencies>
          <build>
              <plugins>
                  <plugin>
                      <groupId>org.apache.tomcat.maven</groupId>
                      <artifactId>tomcat7-maven-plugin</artifactId>
                      <version>2.2</version>
                  </plugin>
              </plugins>
          </build>
      </project>
      
      
    • 新建Servlet,创建Cookie对象,存入数据,发送给前端

      package priv.dandelion.controller;
      
      import javax.servlet.ServletException;
      import javax.servlet.annotation.WebServlet;
      import javax.servlet.http.Cookie;
      import javax.servlet.http.HttpServlet;
      import javax.servlet.http.HttpServletRequest;
      import javax.servlet.http.HttpServletResponse;
      import java.io.IOException;
      
      @WebServlet("/cookieDemoA")
      public class AServlet extends HttpServlet {
          @Override
          protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
              // 创建Cookie对象
              Cookie cookie = new Cookie("name","zhangsan");
              // 通过resp将Cookie发送到前端
              resp.addCookie(cookie);
          }
      
          @Override
          protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
              this.doGet(req, resp);
          }
      }
      

2.2.2 获取Cookie

  • 获取客户端携带的所有Cookie数组,使用Request对象

    Cookie[] cookies = request.getCookies();
    
  • 使用Cookie对象的方法获取数据

    cookie.getName();
    cookie.getValue();
    
  • 案例

    需求:在Servlet中获取前一个案例存入在Cookie对象中的数据

    1.编写一个新Servlet类,名称为BServlet

    2.在BServlet中使用request对象获取Cookie数组,遍历数组,从数据中获取指定名称对应的值

    3.启动测试,在控制台打印出获取的值

    • 编写Servlet,使用request对象获取Cookie数组,遍历数组,从数据中获取指定名称对应的值,并在控制台打印

      package priv.dandelion.controller;
      
      import javax.servlet.ServletException;
      import javax.servlet.annotation.WebServlet;
      import javax.servlet.http.Cookie;
      import javax.servlet.http.HttpServlet;
      import javax.servlet.http.HttpServletRequest;
      import javax.servlet.http.HttpServletResponse;
      import java.io.IOException;
      
      @WebServlet("/cookieDemoB")
      public class BServlet extends HttpServlet {
          @Override
          protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
              Cookie[] cookies = request.getCookies();
              for (Cookie cookie : cookies) {
                  if ("username".equals(cookie.getName())) {
                      System.out.println(cookie.getName() + "," + cookie.getValue());
                      break;
                  }
              }
          }
      
          @Override
          protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
              this.doGet(req, resp);
          }
      }
      

2.3 Cookie的原理分析

  • Cookie的实现原理是基于HTTP协议的,其中涉及到HTTP协议中的两个请求头信息:

    • 响应头:set-cookie,为浏览器设置Cookie,携带在响应数据中
    • 请求头: cookie,告诉服务器现有的Cookie,携带在请求数据中,键值对,以分号分隔

2.4 Cookie的使用细节

2.4.1 Cookie的存活时间

  • 默认:存储在浏览器内存

    • 浏览器关闭时,内存释放,将被销毁
  • setMaxAge(int seconds):设置Cookie存活时间

    • 正数:将Cookie写入浏览器所在电脑的硬盘,持久化存储,到时间自动删除
    • 负数(默认):将Cookie存储到浏览器内存,浏览器关闭时销毁
    • 零:删除对应Cookie

2.4.2 Cookie存储中文

笔者使用tomcat 8.5.76,其Cookie已支持中文

  • 若字符集不匹配,则浏览器报错500并给出相应提示

  • 如果需要存储中文,邪恶需要使用URL编码进行转码

    • 对将要存储的Cookie值进行编码

      // import java.net.URLEncoder;
      String value = "中文值";
      value = URLEncoder.encode(value, "UTF-8");
      Cookie cookie = new Cookie("name",value);
      
    • 对浏览器的请求中的Cookie值进行解码

      // import java.net.URLDecoder;
      String value = URLDecoder.decode(value, "UTF-8");
      

3、 Session

3.1 Session的基本使用

  • 概念:服务端会话跟踪技术——将数据保存到服务端。

    • Session是存储在服务端而Cookie是存储在客户端
    • 存储在客户端的数据容易被窃取和截获,存在很多不安全的因素
    • 存储在服务端的数据相比于客户端来说就更安全
  • 工作流程

    • 在服务端的AServlet获取一个Session对象,把数据存入其中
    • 在服务端的BServlet获取到相同的Session对象,从中取出数据
    • 通过上述两步就可以实现一次会话中多次请求之间的数据共享

3.2 Session的基本使用

在JavaEE中提供了HttpSession接口,来实现一次会话的多次请求之间数据共享功能

  • 获取Session对象

    HttpSession session = request.getSession();
    
  • Session功能对象

    // 存储数据到 session 域中
    void setAttribute(String name, Object o);
    // 根据 key,获取值
    Object getAttribute(String name);
    // 根据 key,删除该键值对
    void removeAttribute(String name);
    
  • 案例

    • 向Session中写入数据

3.2 Session的原理分析

Session是基于Cookie的

  • 前提条件

    • Session要想实现一次会话多次请求之间的数据共享,就必须要保证多次请求获取Session的对象是同一个
  • Session 原理

    1. 浏览器发送请求到服务器,服务器在第一次获取session对象的时候,session对象会有一个唯一的标识,假如是id:10
    2. 在session中存入其他数据并处理完成所有业务后,需要通过Tomcat服务器响应结果给浏览器
    3. Tomcat服务器发现业务处理中使用了session对象,就会把session的唯一标识id:10当做一个cookie,添加Set-Cookie:JESSIONID=10到响应头中,并响应给浏览器
    4. 浏览器接收到响应结果后,会把响应头中的coookie数据存储到浏览器的内存中
    5. 浏览器在同一会话在此请求服务器的时候,会把cookie中的数据按照cookie: JESSIONID=10的格式添加到请求头中并发送给服务器Tomcat
    6. 获取到请求后,从请求头中就读取cookie中的JSESSIONID值为10,然后就会到服务器内存中寻找id:10的session对象,如果找到了,就直接返回该对象,如果没有则新创建一个session对象
    7. 关闭打开浏览器后,因为浏览器的cookie已被销毁,所以就没有JESSIONID的数据,服务端获取到的session就是一个全新的session对象

3.3 Session的使用细节

3.3.1 Session钝化与活化

注意:该部分仅仅是服务器的正常关闭和重启时,Session不会丢失,但浏览器重启时,Session依然会丢失(浏览器中将使用Cookie存储所需Session的id)

  • 钝化与活化

    • 钝化:在服务器正常关闭后,Tomcat会自动将Session数据写入硬盘的文件中

      钝化的数据路径为:项目目录\target\tomcat\work\Tomcat\localhost\项目名称\SESSIONS.ser

    • 活化:再次启动服务器后,从文件中加载数据到Session中

  • Session销毁

3.3.2 Session销毁

  • 默认情况:无操作,默认30分钟内销毁,可再web.xml中进行配置

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
             version="3.1">
    
        <session-config>
            <session-timeout>30</session-timeout>
        </session-config>
    </web-app>
    
  • 调用Session对象的invalidate()方法

    // 从Session中获取数据
    // 获取Session对象
    HttpSession session = req.getSession();
    
    // 销毁Session
    session.invalidate();
    
    // 销毁后获取数据将报错
    Object username = session.getAttribute("username");
    System.out.println(username);
    

5、 Cookie和Session的区别

  • Cookie是用来保证用户在未登录情况下的身份识别
  • Session是用来保存用户登录后的数据
  • 区别

    • 存储位置:Cookie 是将数据存储在客户端,Session 将数据存储在服务端
    • 安全性:Cookie不安全,Session安全
    • 数据大小:Cookie最大3KB,Session无大小限制
    • 存储时间:Cookie可以通过setMaxAge()长期存储,Session默认30分钟
    • 服务器性能:Cookie不占服务器资源,Session占用服务器资源
  • 应用场景

    • 购物车——使用Cookie来存储
    • 以登录用户的名称展示——使用Session来存储
    • 记住我功能——使用Cookie来存储
    • 验证码——使用session来存储

4、 用户登录注册案例

4.1 需求分析

  • 登录

    • 输入用户名和密码进行登录
    • 勾选记住我时下次进入页面将自动填充上次登录的用户名和密码,一周内未登录则作废
    • 用户信息错误时将进行报错
    • 登录成功后展示全部品牌信息
  • 注册

    • 用户填写用户名和密码以及验证码进行注册
    • 验证码可以刷新
    • 若用户名已存在则进行报错
    • 若验证码错误则进行报错
    • 注册完成后将跳转到登录页面,并自动填充用户名和密码

4.2 环境准备

  • 依赖

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>
    
    <dependencies>
        <!-- junit单元测试依赖 -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>
        <!--mybatis 依赖-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.5</version>
        </dependency>
        <!--mysql 驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.46</version>
        </dependency>
        <!-- 添加slf4j日志api -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.20</version>
        </dependency>
        <!-- 添加logback-classic依赖 -->
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.3</version>
        </dependency>
        <!-- 添加logback-core依赖 -->
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-core</artifactId>
            <version>1.2.3</version>
        </dependency>
        <!-- servlet依赖 -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <scope>provided</scope>
        </dependency>
        <!-- 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>
        <!-- JSTL标准标签库依赖 -->
        <dependency>
            <groupId>taglibs</groupId>
            <artifactId>standard</artifactId>
            <version>1.1.2</version>
        </dependency>
    </dependencies>
    
    <build>
        <plugins>
            <plugin>
                <!-- tomcat插件 -->
                <groupId>org.apache.tomcat.maven</groupId>
                <artifactId>tomcat7-maven-plugin</artifactId>
                <version>2.2</version>
            </plugin>
        </plugins>
    
    </build>
    
  • 数据库

    • tb_user

      -- 删除tb_user表
      drop table if exists tb_user;
      -- 创建用户表
      CREATE TABLE tb_user(
      	id int primary key auto_increment,
      	username varchar(20) unique,
      	password varchar(32)
      );
      
      -- 添加数据
      INSERT INTO tb_user(username,password) values('zhangsan','123'),('lisi','234');
      
      SELECT * FROM tb_user;
      
    • tb_brand

      -- 删除tb_brand表
      drop table if exists tb_brand;
      -- 创建tb_brand表
      create table tb_brand
      (
          -- id 主键
          id           int primary key auto_increment,
          -- 品牌名称
          brand_name   varchar(20),
          -- 企业名称
          company_name varchar(20),
          -- 排序字段
          ordered      int,
          -- 描述信息
          description  varchar(100),
          -- 状态:0:禁用  1:启用
          status       int
      );
      -- 添加数据
      insert into tb_brand (brand_name, company_name, ordered, description, status)
      values ('三只松鼠', '三只松鼠股份有限公司', 5, '好吃不上火', 0),
             ('华为', '华为技术有限公司', 100, '华为致力于把数字世界带入每个人、每个家庭、每个组织,构建万物互联的智能世界', 1),
             ('小米', '小米科技有限公司', 50, 'are you ok', 1);
             
      SELECT * FROM tb_brand;
      
  • 实体类

    • User.java

      package priv.dandelion.entity;
      
      public class Brand {
          // id 主键
          private Integer id;
          // 品牌名称
          private String brandName;
          // 企业名称
          private String companyName;
          // 排序字段
          private Integer ordered;
          // 描述信息
          private String description;
          // 状态:0:禁用  1:启用
          private Integer status;
      
          public Brand() {
          }
      
          public Brand(String brandName, String companyName, Integer ordered, String description, Integer status) {
              this.id = null;
              this.brandName = brandName;
              this.companyName = companyName;
              this.ordered = ordered;
              this.description = description;
              this.status = status;
          }
      
          public Brand(Integer id, String brandName, String companyName, Integer ordered, String description, Integer status) {
              this.id = id;
              this.brandName = brandName;
              this.companyName = companyName;
              this.ordered = ordered;
              this.description = description;
              this.status = status;
          }
      
      
          public Integer getId() {
              return id;
          }
      
          public void setId(Integer id) {
              this.id = id;
          }
      
          public String getBrandName() {
              return brandName;
          }
      
          public void setBrandName(String brandName) {
              this.brandName = brandName;
          }
      
          public String getCompanyName() {
              return companyName;
          }
      
          public void setCompanyName(String companyName) {
              this.companyName = companyName;
          }
      
          public Integer getOrdered() {
              return ordered;
          }
      
          public void setOrdered(Integer ordered) {
              this.ordered = ordered;
          }
      
          public String getDescription() {
              return description;
          }
      
          public void setDescription(String description) {
              this.description = description;
          }
      
          public Integer getStatus() {
              return status;
          }
      
          public void setStatus(Integer status) {
              this.status = status;
          }
      
          @Override
          public String toString() {
              return "Brand{" +
                      "id=" + id +
                      ", brand_name='" + brandName + '\'' +
                      ", company_name='" + companyName + '\'' +
                      ", ordered=" + ordered +
                      ", description='" + description + '\'' +
                      ", status=" + status +
                      '}';
          }
      }
      
    • Brand.java

      package priv.dandelion.entity;
      
      public class User {
          private Integer id;
          private String username;
          private String password;
      
          public User(Integer id, String username, String password) {
              this.id = id;
              this.username = username;
              this.password = password;
          }
      
          public User() {
          }
      
          public Integer getId() {
              return id;
          }
      
          public void setId(Integer id) {
              this.id = id;
          }
      
          public String getUsername() {
              return username;
          }
      
          public void setUsername(String username) {
              this.username = username;
          }
      
          public String getPassword() {
              return password;
          }
      
          public void setPassword(String password) {
              this.password = password;
          }
      
          @Override
          public String toString() {
              return "User{" +
                      "id=" + id +
                      ", username='" + username + '\'' +
                      ", password='" + password + '\'' +
                      '}';
          }
      }
      
  • Mybatis核心配置文件

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE configuration
            PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>
        <!--起别名-->
        <typeAliases>
            <package name="priv.dandelion.entity"/>
        </typeAliases>
    
        <environments default="development">
            <environment id="development">
                <!-- 采用JDBC的事务管理方式 -->
                <transactionManager type="JDBC"/>
                <!-- 数据库连接信息 -->
                <dataSource type="POOLED">
                    <property name="driver" value="com.mysql.jdbc.Driver"/>
                    <!-- value的值一定不能换行,一定!一定!不能换行 -->
                    <property name="url" value="jdbc:mysql:///db1?useSSL=false&amp;useUnicode=true&amp;characterEncoding=utf-8&amp;useServerPrepStmts=true"/>
                    <property name="username" value="root"/>
                    <property name="password" value="123456"/>
                </dataSource>
            </environment>
        </environments>
        <!-- 扫描mapper,加载SQL映射文件 -->
        <mappers>
            <package name="priv.dandelion.dao"/>
        </mappers>
    </configuration>
    
  • Mapper映射文件

    • UserMapper.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="priv.dandelion.dao.UserMapper">
      </mapper>
      
    • BrandMapper.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="priv.dandelion.dao.BrandMapper">
      
          <!-- 解决数据库与实体类命名不一致问题 -->
          <resultMap id="brandResultMap" type="brand">
              <result column="brand_name" property="brandName"></result>
              <result column="company_name" property="companyName"></result>
          </resultMap>
      
      </mapper>
      
  • 工具类

    • 生成验证码

      package priv.dandelion.utils;
      
      import javax.imageio.ImageIO;
      import java.awt.*;
      import java.awt.geom.AffineTransform;
      import java.awt.image.BufferedImage;
      import java.io.File;
      import java.io.FileOutputStream;
      import java.io.IOException;
      import java.io.OutputStream;
      import java.util.Arrays;
      import java.util.Random;
      
      /**
       * 生成验证码工具类
       */
      public class CheckCodeUtil {
      
          public static final String VERIFY_CODES = "123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
          private static Random random = new Random();
      
      
      
          /**
           * 输出随机验证码图片流,并返回验证码值(一般传入输出流,响应response页面端,Web项目用的较多)
           *
           * @param width 图片宽度
           * @param height 图片高度
           * @param os 输出流
           * @param verifySize 验证码长度
           * @return String
           * @throws IOException
           */
          public static String outputVerifyImage(int width, int height, OutputStream os, int verifySize) throws IOException {
              String verifyCode = generateVerifyCode(verifySize);
              outputImage(width, height, os, verifyCode);
              return verifyCode;
          }
      
          /**
           * 使用系统默认字符源生成验证码
           *
           * @param verifySize 验证码长度
           * @return
           */
          public static String generateVerifyCode(int verifySize) {
              return generateVerifyCode(verifySize, VERIFY_CODES);
          }
      
          /**
           * 使用指定源生成验证码
           *
           * @param verifySize 验证码长度
           * @param sources    验证码字符源
           * @return
           */
          public static String generateVerifyCode(int verifySize, String sources) {
              // 未设定展示源的字码,赋默认值大写字母+数字
              if (sources == null || sources.length() == 0) {
                  sources = VERIFY_CODES;
              }
              int codesLen = sources.length();
              Random rand = new Random(System.currentTimeMillis());
              StringBuilder verifyCode = new StringBuilder(verifySize);
              for (int i = 0; i < verifySize; i++) {
                  verifyCode.append(sources.charAt(rand.nextInt(codesLen - 1)));
              }
              return verifyCode.toString();
          }
      
          /**
           * 生成随机验证码文件,并返回验证码值 (生成图片形式,用的较少)
           *
           * @param w
           * @param h
           * @param outputFile
           * @param verifySize
           * @return
           * @throws IOException
           */
          public static String outputVerifyImage(int w, int h, File outputFile, int verifySize) throws IOException {
              String verifyCode = generateVerifyCode(verifySize);
              outputImage(w, h, outputFile, verifyCode);
              return verifyCode;
          }
      
      
      
          /**
           * 生成指定验证码图像文件
           *
           * @param w
           * @param h
           * @param outputFile
           * @param code
           * @throws IOException
           */
          public static void outputImage(int w, int h, File outputFile, String code) throws IOException {
              if (outputFile == null) {
                  return;
              }
              File dir = outputFile.getParentFile();
              //文件不存在
              if (!dir.exists()) {
                  //创建
                  dir.mkdirs();
              }
              try {
                  outputFile.createNewFile();
                  FileOutputStream fos = new FileOutputStream(outputFile);
                  outputImage(w, h, fos, code);
                  fos.close();
              } catch (IOException e) {
                  throw e;
              }
          }
      
          /**
           * 输出指定验证码图片流
           *
           * @param w
           * @param h
           * @param os
           * @param code
           * @throws IOException
           */
          public static void outputImage(int w, int h, OutputStream os, String code) throws IOException {
              int verifySize = code.length();
              BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
              Random rand = new Random();
              Graphics2D g2 = image.createGraphics();
              g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
      
              // 创建颜色集合,使用java.awt包下的类
              Color[] colors = new Color[5];
              Color[] colorSpaces = new Color[]{Color.WHITE, Color.CYAN,
                      Color.GRAY, Color.LIGHT_GRAY, Color.MAGENTA, Color.ORANGE,
                      Color.PINK, Color.YELLOW};
              float[] fractions = new float[colors.length];
              for (int i = 0; i < colors.length; i++) {
                  colors[i] = colorSpaces[rand.nextInt(colorSpaces.length)];
                  fractions[i] = rand.nextFloat();
              }
              Arrays.sort(fractions);
              // 设置边框色
              g2.setColor(Color.GRAY);
              g2.fillRect(0, 0, w, h);
      
              Color c = getRandColor(200, 250);
              // 设置背景色
              g2.setColor(c);
              g2.fillRect(0, 2, w, h - 4);
      
              // 绘制干扰线
              Random random = new Random();
              // 设置线条的颜色
              g2.setColor(getRandColor(160, 200));
              for (int i = 0; i < 20; i++) {
                  int x = random.nextInt(w - 1);
                  int y = random.nextInt(h - 1);
                  int xl = random.nextInt(6) + 1;
                  int yl = random.nextInt(12) + 1;
                  g2.drawLine(x, y, x + xl + 40, y + yl + 20);
              }
      
              // 添加噪点
              // 噪声率
              float yawpRate = 0.05f;
              int area = (int) (yawpRate * w * h);
              for (int i = 0; i < area; i++) {
                  int x = random.nextInt(w);
                  int y = random.nextInt(h);
                  // 获取随机颜色
                  int rgb = getRandomIntColor();
                  image.setRGB(x, y, rgb);
              }
              // 添加图片扭曲
              shear(g2, w, h, c);
      
              g2.setColor(getRandColor(100, 160));
              int fontSize = h - 4;
              Font font = new Font("Algerian", Font.ITALIC, fontSize);
              g2.setFont(font);
              char[] chars = code.toCharArray();
              for (int i = 0; i < verifySize; i++) {
                  AffineTransform affine = new AffineTransform();
                  affine.setToRotation(Math.PI / 4 * rand.nextDouble() * (rand.nextBoolean() ? 1 : -1), (w / verifySize) * i + fontSize / 2, h / 2);
                  g2.setTransform(affine);
                  g2.drawChars(chars, i, 1, ((w - 10) / verifySize) * i + 5, h / 2 + fontSize / 2 - 10);
              }
      
              g2.dispose();
              ImageIO.write(image, "jpg", os);
          }
      
          /**
           * 随机颜色
           *
           * @param fc
           * @param bc
           * @return
           */
          private static Color getRandColor(int fc, int bc) {
              if (fc > 255) {
                  fc = 255;
              }
              if (bc > 255) {
                  bc = 255;
              }
              int r = fc + random.nextInt(bc - fc);
              int g = fc + random.nextInt(bc - fc);
              int b = fc + random.nextInt(bc - fc);
              return new Color(r, g, b);
          }
      
          private static int getRandomIntColor() {
              int[] rgb = getRandomRgb();
              int color = 0;
              for (int c : rgb) {
                  color = color << 8;
                  color = color | c;
              }
              return color;
          }
      
          private static int[] getRandomRgb() {
              int[] rgb = new int[3];
              for (int i = 0; i < 3; i++) {
                  rgb[i] = random.nextInt(255);
              }
              return rgb;
          }
      
          private static void shear(Graphics g, int w1, int h1, Color color) {
              shearX(g, w1, h1, color);
              shearY(g, w1, h1, color);
          }
      
          private static void shearX(Graphics g, int w1, int h1, Color color) {
      
              int period = random.nextInt(2);
      
              boolean borderGap = true;
              int frames = 1;
              int phase = random.nextInt(2);
      
              for (int i = 0; i < h1; i++) {
                  double d = (double) (period >> 1)
                          * Math.sin((double) i / (double) period
                          + (6.2831853071795862D * (double) phase)
                          / (double) frames);
                  g.copyArea(0, i, w1, 1, (int) d, 0);
                  if (borderGap) {
                      g.setColor(color);
                      g.drawLine((int) d, i, 0, i);
                      g.drawLine((int) d + w1, i, w1, i);
                  }
              }
      
          }
      
          private static void shearY(Graphics g, int w1, int h1, Color color) {
      
              int period = random.nextInt(40) + 10; // 50;
      
              boolean borderGap = true;
              int frames = 20;
              int phase = 7;
              for (int i = 0; i < w1; i++) {
                  double d = (double) (period >> 1)
                          * Math.sin((double) i / (double) period
                          + (6.2831853071795862D * (double) phase)
                          / (double) frames);
                  g.copyArea(i, 0, 1, h1, 0, (int) d);
                  if (borderGap) {
                      g.setColor(color);
                      g.drawLine(i, (int) d, i, 0);
                      g.drawLine(i, (int) d + h1, i, h1);
                  }
      
              }
      
          }
      }
      
      
      
    • 字符重编码

      package priv.dandelion.utils;
      
      import java.nio.charset.StandardCharsets;
      
      public class ReEncoding {
      
          public static String reEncodingToUtf8(String str) {
              // 使用ISO-8859-1编码将乱码字符编回二进制码,得到其字节数组
              byte[] bytes = str.getBytes(StandardCharsets.ISO_8859_1);
              // 将字节数组使用UTF-8重新编码
              return new String(bytes, StandardCharsets.UTF_8);
          }
      }
      
      
    • 创建SqlSessionFactory

      package priv.dandelion.utils;
      
      import org.apache.ibatis.io.Resources;
      import org.apache.ibatis.session.SqlSessionFactory;
      import org.apache.ibatis.session.SqlSessionFactoryBuilder;
      
      import java.io.IOException;
      import java.io.InputStream;
      
      public class SqlSessionFactoryUtils {
      
          // 提升作用域,用于再方法内进行返回
          private static SqlSessionFactory sqlSessionFactory;
      
          // 静态代码块会随着类的加载自动执行且只执行一次
          static {
              String resource = "mybatis-config.xml";
              InputStream inputStream = null;
              try {
                  inputStream = Resources.getResourceAsStream(resource);
              } catch (IOException e) {
                  e.printStackTrace();
              }
              sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
          }
      
          public static SqlSessionFactory getSqlSessionFactory() {
              return sqlSessionFactory;
          }
      
      }
      
      

4.3 登录部分

  • Dao层

    • UserMapper接口

      package priv.dandelion.dao;
      
      import org.apache.ibatis.annotations.Insert;
      import org.apache.ibatis.annotations.Param;
      import org.apache.ibatis.annotations.Select;
      import priv.dandelion.entity.User;
      
      import java.util.List;
      
      public interface UserMapper {
      
          @Select("select * from tb_user")
          public List<User> selectAll();
      
          @Select("select * from tb_user where username = #{name} and password = #{password};")
          public User select(@Param("name") String name, @Param("password") String password);
      
          @Select("select * from tb_user where username = #{name}")
          public User selectByName(@Param("name") String name);
      
          @Insert("insert into tb_user values(null, #{name}, #{password});")
          public void insert(@Param("name") String name, @Param("password") String password);
      }
      
    • BrandMapper接口

      package priv.dandelion.dao;
      
      import org.apache.ibatis.annotations.ResultMap;
      import org.apache.ibatis.annotations.Select;
      import priv.dandelion.entity.Brand;
      
      import java.util.List;
      
      public interface BrandMapper {
      
          // 本部分对字段进行了重新命名,见对应xml,使用时需要注明ResultMap,必须写在@Select之前
      
          @ResultMap("brandResultMap")
          @Select("select * from tb_brand;")
          public List<Brand> selectAll();
      
      }
      
  • Service层

    • UserService.java

      package priv.dandelion.service;
      
      import org.apache.ibatis.session.SqlSession;
      import org.apache.ibatis.session.SqlSessionFactory;
      import priv.dandelion.dao.UserMapper;
      import priv.dandelion.entity.User;
      import priv.dandelion.utils.SqlSessionFactoryUtils;
      
      public class UserService {
      
          private SqlSessionFactory factory = SqlSessionFactoryUtils.getSqlSessionFactory();
      
          public User login(String username, String password){
              // 获取SqlSession
              SqlSession sqlSession = factory.openSession();
              // 获取UserMapper
              UserMapper mapper = sqlSession.getMapper(UserMapper.class);
              // 调用dao
              User user = mapper.select(username, password);
              // 释放资源
              sqlSession.close();
      
              return user;
          }
      
          public void register(String username, String password) {
              // 获取SqlSession
              SqlSession sqlSession = factory.openSession(true);
              // 获取UserMapper
              UserMapper mapper = sqlSession.getMapper(UserMapper.class);
              // 调用dao
              mapper.insert(username, password);
              // 释放资源
              sqlSession.close();
          }
      
      }
      
    • BrandService.java

      package priv.dandelion.service;
      
      import org.apache.ibatis.session.SqlSession;
      import org.apache.ibatis.session.SqlSessionFactory;
      import priv.dandelion.dao.BrandMapper;
      import priv.dandelion.entity.Brand;
      import priv.dandelion.utils.SqlSessionFactoryUtils;
      
      import java.util.List;
      
      public class BrandService {
          private SqlSessionFactory factory = SqlSessionFactoryUtils.getSqlSessionFactory();
      
          public List<Brand> selectAll() {
              // 获取SqlSession
              SqlSession sqlSession = factory.openSession();
              // 获取Mapper
              BrandMapper mapper = sqlSession.getMapper(BrandMapper.class);
              // 调用dao
              List<Brand> brands = mapper.selectAll();
              // 释放资源
              sqlSession.close();
      
              return brands;
          }
      }
      
  • Controller层

    • 用户登录 LoginServlet.java

      使用请求转发跳转页面,没有直接将用户信息存储到Request域,验证Session,且要全局使用,不适合存储再Request域中

      package priv.dandelion.controller;
      
      import priv.dandelion.entity.User;
      import priv.dandelion.service.UserService;
      import priv.dandelion.utils.ReEncoding;
      
      import javax.servlet.ServletException;
      import javax.servlet.annotation.WebServlet;
      import javax.servlet.http.*;
      import java.io.IOException;
      
      @WebServlet("/login")
      public class LoginServlet extends HttpServlet {
          private UserService service = new UserService();
      
          @Override
          protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
              // 获取用户名和密码
              String username = req.getParameter("username");
              String password = req.getParameter("password");
              String remember = req.getParameter("remember");
      
              // 重新编码为UTF-8,以便于后续操作
              username = ReEncoding.reEncodingToUtf8(username);
      
              // 调用Service查询
              User user = service.login(username, password);
      
              // 判断是否存在
              if (user != null) {
      
                  // 若需要记住用户
                  if ("1".equals(remember)) {
      
                      // 创建Cookie
                      Cookie c_username = new Cookie("username", username);
                      Cookie c_password = new Cookie("password", password);
      
                      // 设置Cookie存活时间
                      c_username.setMaxAge( 60 * 60 * 24 * 7 );
                      c_password.setMaxAge( 60 * 60 * 24 * 7 );
      
                      // 将Cookie写入response
                      resp.addCookie(c_username);
                      resp.addCookie(c_password);
      
                  }
      
                  // 将登录成功的User对象存储到Session,用于后续的页面显示
                  HttpSession session = req.getSession();
                  session.setAttribute("user", user);
      
                  // 登录成功,跳转到系统页面
                  // 重定向到查询到所有Brand
                  String contextPath = req.getContextPath();
                  resp.sendRedirect(contextPath + "/selectAll");
      
              } else {
                  // 登录失败
                  // 存储错误信息到request
                  req.setAttribute("login_msg", "用户名或密码错误");
                  // 跳转回login.jsp
                  req.getRequestDispatcher("/login.jsp").forward(req, resp);
              }
          }
      
          @Override
          protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
              this.doGet(req, resp);
          }
      }
      
    • 查询品牌信息 SelectAllBrandsServlet

      package priv.dandelion.controller;
      
      import org.apache.ibatis.annotations.ResultMap;
      import priv.dandelion.entity.Brand;
      import priv.dandelion.service.BrandService;
      
      import javax.servlet.ServletException;
      import javax.servlet.annotation.WebServlet;
      import javax.servlet.http.HttpServlet;
      import javax.servlet.http.HttpServletRequest;
      import javax.servlet.http.HttpServletResponse;
      import java.io.IOException;
      import java.util.List;
      
      @WebServlet("/selectAll")
      public class SelectAllBrandsServlet extends HttpServlet {
      
          private BrandService service = new BrandService();
      
          @Override
          protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
      
              // 查询数据存放到request域
              List<Brand> brands = service.selectAll();
              req.setAttribute("brands", brands);
              // 将请求转发到页面
              req.getRequestDispatcher("/brand.jsp").forward(req, resp);
          }
      
          @Override
          protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
              this.doPost(req, resp);
          }
      }
      
  • 页面

    • 登录页面

      使用EL表达式${cookie.username.value}获取Cookie中key为username的值

      <%@ page contentType="text/html;charset=UTF-8" language="java" %>
      <%-- 禁止忽略EL表达式 --%>
      <%@ page isELIgnored="false" %>
      
      <!DOCTYPE html>
      <html lang="en">
      
      <head>
          <meta charset="UTF-8">
          <title>login</title>
          <link href="css/login.css" rel="stylesheet">
      </head>
      
      <body>
      <div id="loginDiv" style="height: 350px">
          <form action="/cookie_demo/login" method="post" id="form">
              <h1 id="loginMsg">LOGIN IN</h1>
              <div id="errorMsg">${login_msg}</div>
              <%-- 此处使用了EL表达式,读取cookie中的内容自动填入输入框中 --%>
              <p>Username:<input id="username" name="username" value="${cookie.username.value}" type="text"></p>
      
              <p>Password:<input id="password" name="password" value="${cookie.password.value}" type="password"></p>
      
              <p>Remember:<input id="remember" name="remember" value="1" type="checkbox"></p>
              <div id="subDiv">
                  <input type="submit" class="button" value="login up">
                  <input type="reset" class="button" value="reset">&nbsp;&nbsp;&nbsp;
                  <a href="register.html">没有账号?</a>
              </div>
          </form>
      </div>
      </body>
      </html>
      
    • 展示品牌信息页面

      使用EL表达式获取Session中数据

      <%@ page contentType="text/html;charset=UTF-8" language="java" %>
      <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
      <%-- 禁止忽略EL表达式 --%>
      <%@ page isELIgnored="false" %>
      
      <!DOCTYPE html>
      <html lang="en">
      <head>
          <meta charset="UTF-8">
          <title>Title</title>
      </head>
      <body>
      
      <h1>${user.username},欢迎您</h1>
      
      <input type="button" value="新增" id="add"><br>
      <hr>
      <table border="1" cellspacing="0" width="80%">
          <tr>
              <th>序号</th>
              <th>品牌名称</th>
              <th>企业名称</th>
              <th>排序</th>
              <th>品牌介绍</th>
              <th>状态</th>
              <th>操作</th>
      
          </tr>
      
      
          <c:forEach items="${brands}" var="brand" varStatus="status">
              <tr align="center">
                  <%--<td>${brand.id}</td>--%>
                  <td>${status.count}</td>
                  <td>${brand.brandName}</td>
                  <td>${brand.companyName}</td>
                  <td>${brand.ordered}</td>
                  <td>${brand.description}</td>
                  <c:if test="${brand.status == 1}">
                      <td>启用</td>
                  </c:if>
                  <c:if test="${brand.status != 1}">
                      <td>禁用</td>
                  </c:if>
      
                  <td><a href="/cookie_demo/selectById?id=${brand.id}">修改</a> <a href="#">删除</a></td>
              </tr>
      
          </c:forEach>
      
      </table>
      
      <script>
          document.getElementById("add").onclick = function (){
              // location.href = "/brand_demo/addBrand.jsp";
              location.href = "#";
          }
      
      </script>
      </body>
      </html>
      

4.4 注册部分

  • Dao层

    见登录部分

  • Service层

    见登录部分

  • Controller层

    • 验证码的生成存储和展示

      package priv.dandelion.controller;
      
      import priv.dandelion.utils.CheckCodeUtil;
      
      import javax.servlet.ServletException;
      import javax.servlet.ServletOutputStream;
      import javax.servlet.annotation.WebServlet;
      import javax.servlet.http.HttpServlet;
      import javax.servlet.http.HttpServletRequest;
      import javax.servlet.http.HttpServletResponse;
      import javax.servlet.http.HttpSession;
      import java.io.IOException;
      
      /**
       * 验证码展示,并存储到Session
       */
      @WebServlet("/checkCode")
      public class CheckCodeServlet extends HttpServlet {
      
          @Override
          protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
              // 存储验证码图片到本地
              // OutputStream fos = new FileOutputStream("d://check-code.jpg");
      
              // 使用Response字节输出流获取验证码
              ServletOutputStream outputStream = resp.getOutputStream();
              String checkCode = CheckCodeUtil.outputVerifyImage(100, 50, outputStream, 4);
      
              // 将验证码存储到Session中
              HttpSession session = req.getSession();
              session.setAttribute("checkCodeGenerate", checkCode);
          }
      
          @Override
          protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
              this.doGet(req, resp);
          }
      }
      
    • 注册

      校验验证码不区分大小写,使用equalsIgnoreCase()进行比对

      package priv.dandelion.controller;
      
      import priv.dandelion.service.UserService;
      import priv.dandelion.utils.ReEncoding;
      
      import javax.servlet.ServletException;
      import javax.servlet.annotation.WebServlet;
      import javax.servlet.http.HttpServlet;
      import javax.servlet.http.HttpServletRequest;
      import javax.servlet.http.HttpServletResponse;
      import javax.servlet.http.HttpSession;
      import java.io.IOException;
      
      @WebServlet("/register")
      public class RegisterServlet extends HttpServlet {
      
          private UserService service = new UserService();
      
          @Override
          protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
              // 获取用户名和密码
              String username = req.getParameter("username");
              String password = req.getParameter("password");
              String checkCode = req.getParameter("checkCode");
      
              // 获取Session中的验证码
              HttpSession session = req.getSession();
              String checkCodeGenerate = (String) session.getAttribute("checkCodeGenerate");
      
              // 重新编码
              username = ReEncoding.reEncodingToUtf8(username);
      
              if ("".equals(username) || "".equals(password)){
                  req.setAttribute("register_msg", "用户名和密码不能为空");
                  req.getRequestDispatcher("/register.jsp").forward(req, resp);
                  return;
              }
      
              // 校验验证码,忽略大小写
              if (!checkCodeGenerate.equalsIgnoreCase(checkCode)) {
                  req.setAttribute("register_msg", "验证码错误");
                  req.getRequestDispatcher("/register.jsp").forward(req, resp);
                  return;
              }
      
              // 调用Service
              boolean flag = service.register(username, password);
              if (flag) {
                  // 注册成功
                  req.setAttribute("register_msg", "注册成功,请登录");
                  req.getRequestDispatcher("/login.jsp").forward(req, resp);
              } else {
                  // 注册失败
                  req.setAttribute("register_msg", "用户名已存在");
                  // 跳转回注册页面
                  req.getRequestDispatcher("/register.jsp").forward(req, resp);
              }
          }
      
          @Override
          protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
              this.doGet(req, resp);
          }
      }
      
  • 页面

    验证码的刷新需要注意,见script代码快注释

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <%-- 禁止忽略EL表达式 --%>
    <%@ page isELIgnored="false" %>
    
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>欢迎注册</title>
        <link href="css/register.css" rel="stylesheet">
    </head>
    <body>
    
    <div class="form-div">
        <div class="reg-content">
            <h1>欢迎注册</h1>
            <span>已有帐号?</span> <a href="login.html">登录</a>
        </div>
        <form id="reg-form" action="/cookie_demo/register" method="post">
    
            <table>
    
                <tr>
                    <td>用户名</td>
                    <td class="inputs">
                        <input name="username" type="text" id="username">
                        <br>
                        <span id="username_err" class="err_msg" >${register_msg}</span>
                    </td>
    
                </tr>
    
                <tr>
                    <td>密码</td>
                    <td class="inputs">
                        <input name="password" type="password" id="password">
                        <br>
                        <span id="password_err" class="err_msg" style="display: none">密码格式有误</span>
                    </td>
                </tr>
    
    
                <tr>
                    <td>验证码</td>
                    <td class="inputs">
                        <input name="checkCode" type="text" id="checkCode">
                        <img id="checkCodeImg" src="/cookie_demo/checkCode">
                        <a href="#" id="changeImg" >看不清?</a>
                    </td>
                </tr>
    
            </table>
    
            <div class="buttons">
                <input value="注 册" type="submit" id="reg_btn">
            </div>
            <br class="clear">
        </form>
    
    </div>
    
    <script>
        document.getElementById("changeImg").onclick = function () {
            // 图片路径已经被缓存,需要在后面加参数进行刷新,为保证多次刷新,可以以时间毫秒数作为参数
            document.getElementById("checkCodeImg").src = "/cookie_demo/checkCode?"+new Date().getMilliseconds();
        }
    
    </script>
    </body>
    </html>
    
posted @ 2022-10-11 16:15  Dandelion_000  阅读(271)  评论(0编辑  收藏  举报