当前位置: 首页 > news >正文

大数据------JavaWeb------会话跟踪技术(Cookie、Session)(完整知识点汇总)

会话跟踪技术(Cookie&Session

  • 注意: HTTP协议是无状态 的,即每次浏览器向服务器请求时,服务器都会将该请求视为新的请求,因此我们需要会话跟踪技术来实现会话内的数据共享

  • 会话

    在这里插入图片描述

    • 当用户打开浏览器,访问Web服务器资源时会话建立,直到有一方断开连接时会话结束。在一次会话中可包含多次请求和响应
  • 会话跟踪

    在这里插入图片描述

    • 它是一种维护浏览器状态的方法。服务器需要识别多次请求是否来自于同一个浏览器,以便可以在同一次会话的多次请求中共享数据
  • 会话跟踪技术实现的方式

    • 客户端会话跟踪技术:Cookie
    • 服务端会话跟踪技术:Session

Cookie

Cookie基本使用

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

  • 工作流程

    在这里插入图片描述

    • 假设有两个Servlet,为A和B。此时客户端在请求AServlet数据时,AServlet就会创建一个Cookie对象(里面包含Cookie数据),在AServlet进行响应时就会把Cookie发送给客户端,客户端会将其保存到客户端的内存中,当客户端在同一次会话中访问另一个BServlet时会自动携带着Cookie数据进行对BServlet的访问

发送Cookie

用到的Cookie构造器解释
public Cookie(String name, String value)创建Cookie对象。name:表示Cookie的名称。这个名称用于标识Cookie,必须是唯一的,并且不能为空。value:表示Cookie的值。这个值是与Cookie名称相关联的数据。
用到的Response的方法value:一个String类型的参数,表示Cookie的值。这个值是与Cookie名称相关联的数据。解释
void addCookie(Cookie cookie)将一个 Cookie 对象添加到 HTTP 响应中。这使得服务器可以向客户端发送 Cookie,客户端浏览器会接收并保存这个 Cookie,并在后续请求中将其发送回服务器。
  • 发送Cookie步骤

    • Step1: 创建Cookie对象并设置数据Cookie cookie = new Cookie("key","value");
    • Step2: 使用Response对象发送Cookie到客户端response.addCookie(cookie);
  • 代码示例

    • 客户端访问AServlet,AServlet就会创建一个Cookie对象(里面包含Cookie数据),在AServlet进行响应时就会把Cookie发送给客户端,客户端会将其保存到客户端的内存中

      package at.guigu.web.cookie;import javax.servlet.*;
      import javax.servlet.http.*;
      import javax.servlet.annotation.*;
      import java.io.IOException;@WebServlet("/aServlet")
      public class AServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {//发送Cookie//1 创建Cookie对象并设置数据Cookie cookie = new Cookie("username", "zhangsan");//2 发送Cookieresponse.addCookie(cookie);}@Overrideprotected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {this.doGet(request, response);}
      }
      

      Tomcat运行该Web项目后,截图如下

      在这里插入图片描述

获取Cookie

用到的Request接口中的方法解释
Cookie[] getCookies()获取客户端发送到服务器的所有Cookie。
用到的Cookie对象的方法解释
public String getName()返回Cookie的名称
public String getValue()返回Cookie名对应的值
  • 获取Cookie步骤

    • Step1: 使用Request对象获取客户端携带的所有Cookie Cookie[] cookies = request.getCookies();
    • Step2: 遍历数组获取每一个Cookie对象
    • Step3: 使用Cookie对象方法获取数据:cookie.getName(); cookie.getValue();
      • 注意:由于在实际项目中浏览器会保存很多Cookie信息,所以我们需要用到条件判断来筛选出我们需要的Cookie信息
  • 代码示例

    客户端访问AServlet后(此时Cookie在客户端内存中),访问另一个BServlet,BServlet获取客户端保存的Cookie

    package at.guigu.web.cookie;import javax.servlet.*;
    import javax.servlet.http.*;
    import javax.servlet.annotation.*;
    import java.io.IOException;@WebServlet("/bServlet")
    public class BServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {//获取Cookie//1 通过request对象获取Cookie数组Cookie[] cookies = request.getCookies();//2 遍历Cookie并获取Cookie的键值对for (Cookie cookie : cookies) {//3 通过Cookie对象获取AServlet发送的Cookie数据String name = cookie.getName();if ("username".equals(name)) {String value = cookie.getValue();System.out.println(name + ":" + value);break;}}}@Overrideprotected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {this.doGet(request, response);}
    }
    

    Tomcat运行该Web项目后运行截图如下

    在这里插入图片描述

Cookie原理

  • Cookie的实现是基于HTTP协议的

    • 响应头:set-cookie
      • 发送Cookie用到响应头
    • 请求头:cookie
      • 获取Cookie用到请求头
  • 工作流程解释

    在这里插入图片描述

    • AServlet发送Cookie AServlet中创建了Cookie数据,Tomcat会将该Cookie对象响应给客户端浏览器,由于Tomcat是基于Http协议做出响应的,所以发送给客户端的是Http协议的响应数据,当Tomcat发现服务端要发送Cookie给客户端浏览器时,会在响应数据中给Cookie加上一个响应头即set-cookie,然后将其发送给客户端浏览器,客户端接收到该响应数据后通过响应头set-cookie知道服务端发送的是Cookie,则会对其进行解析,解析后则会拿到username=zhangsan这个数据并将其存储到客户端的内存中
    • BServlet接收Cookie :当客户端携带Cookie数据来访问BServlet时,浏览器会自动为Cookie设置一个请求头cookie,然后将其发送给BServlet,然后BServlet即可通过对应的方法来获取到客户端发送到服务器的所有Cookie
  • 代码示例

    • 代码示例即为发送Cookie和获取Cookie的代码示例,运行截图如下

      在这里插入图片描述

      在这里插入图片描述

Cookie存活时间

Cookie类中的方法解释
public void setMaxAge(int seconds)设置 Cookie 的最大存活时间。seconds为秒数
  • 默认情况下 Cookie存储在浏览器内存中,当浏览器被关闭时内存释放,则Cookie被销毁

    • 即BServlet能访问到AServlet发送给浏览器的Cookie是因为浏览器在发送请求给AServlet后并没有关闭浏览器,如果浏览器在发送请求给AServlet后关闭了浏览器,此时浏览器发送请求给BServlet则BServlet接收不到AServlet发送给浏览器的Cookie,除非AServlet设置Cookie存活时间。
  • 可利用setMaxAge(int seconds)方法来设置Cookie存活时间

    • 正数:Cooke写入浏览器所在电脑的硬盘,持久化存储,但到时间会自动删除
    • 负数: 默认值,Cookie在当前浏览器内存中,当浏览器关闭时Cookie会被立即销毁
    • 零: 删除对应Cookie
  • 代码示例

    • AServlet代码如下

      package at.guigu.web.cookie;import javax.servlet.*;
      import javax.servlet.http.*;
      import javax.servlet.annotation.*;
      import java.io.IOException;@WebServlet("/aServlet")
      public class AServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {//发送Cookie//1 创建Cookie对象并设置数据Cookie cookie = new Cookie("username", "zhangsan");//设置Cookie存活时间--此处设置为2天cookie.setMaxAge(60*60*24*2);//2 发送Cookieresponse.addCookie(cookie);}@Overrideprotected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {this.doGet(request, response);}
      }
      
    • BServlet代码详见获取Cookie,Tomcat运行该Web项目后运行步骤截图如下所示

      • Step1:访问AServlet后关闭浏览器

        在这里插入图片描述

      • Step2:重新打开一个浏览器,访问BServlet,访问后服务端显示出Cookie

        在这里插入图片描述

Cookie存储中文

  • Cookie默认情况下不能存储中文,若存储则会报错

  • AServletCookie存储中文方法:利用URL编码进行转码

    • Step1:单独设置Cookie构造器的第二个参数value的值

    • Step2:将value进行转码:value = URLEncoder.encode(value,"UTF-8");

    • Step3:创建Cookie对象:Cookie cookie = new Cookie("key", value)

    • AServlet代码示例如下

      package at.guigu.web.cookie;import javax.servlet.*;
      import javax.servlet.http.*;
      import javax.servlet.annotation.*;
      import java.io.IOException;
      import java.net.URLEncoder;@WebServlet("/aServlet")
      public class AServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {//发送Cookie//1 创建Cookie对象并设置数据String value = "张三";value = URLEncoder.encode(value, "UTF-8");Cookie cookie = new Cookie("username", value);//设置Cookie存活时间--此处设置为2天cookie.setMaxAge(60*60*24*2);//2 发送Cookieresponse.addCookie(cookie);}@Overrideprotected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {this.doGet(request, response);}
      }
      
  • BServlet获取Cookie中的中文的方法:利用URL编码进行解码

    • Step1:首先获取到对应的value

    • Step2:将value值利用URLDecoder.decode(value, "UTF-8");方法进行解码

    • BServlet代码示例如下

      package at.guigu.web.cookie;import javax.servlet.*;
      import javax.servlet.http.*;
      import javax.servlet.annotation.*;
      import java.io.IOException;
      import java.net.URLDecoder;
      import java.net.URLEncoder;@WebServlet("/bServlet")
      public class BServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {//获取Cookie//1 通过request对象获取Cookie数组Cookie[] cookies = request.getCookies();//2 遍历Cookie并获取Cookie的键值对for (Cookie cookie : cookies) {//3 通过Cookie对象获取AServlet发送的Cookie数据String name = cookie.getName();if ("username".equals(name)) {String value = cookie.getValue();value = URLDecoder.decode(value, "UTF-8");System.out.println(name + ":" + value);break;}}}@Overrideprotected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {this.doGet(request, response);}
      }
      

Session

  • Session:服务端会话跟踪技术,它会将数据保存到服务端

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

  • 工作流程

    在这里插入图片描述

    • 假设有两个Servlet,为A和B。此时客户端在请求AServlet数据时,AServlet就会创建一个Cookie对象(里面包含Cookie数据),在AServlet进行响应时就会把Cookie发送给客户端,客户端会将其保存到客户端的内存中,当客户端在同一次会话中访问另一个BServlet时会自动携带着Cookie数据进行对BServlet的访问,这个过程就会造成Cookie暴露在网络中,就会很不安全,为了解决该问题就有了Session
    • A和B两个Servlet可以创建同一个Session对象,我们可以通过AServletSession对象中存入数据;然后BServlet也可以通过Session对象来获取存入的数据
    • 以上步骤即可实现一次会话中两个请求的数据共享
  • 注意:一次会话的多次请求之间使用的是同一个Session对象

Session基本使用

用到的Request接口的方法解释
HttpSession getSession();获取当前会话,如果没有会话则创建一个新的会话Session对象
用到的Session对象的方法解释
void setAttribute(String name, Object o)存储数据到session域中
Object getAttribute(String name)根据key获取对应的值
void removeAttribute(String name)根据key删除对应的键值对
  • 使用步骤

    • Step1:通过Request对象来获取Session对象即HttpSession session = request.getSession();
    • Step2:利用Session对象相关方法执行对应的功能
  • 代码示例

    • ASessionServlet类代码

      package at.guigu.web.session;import javax.servlet.*;
      import javax.servlet.http.*;
      import javax.servlet.annotation.*;
      import java.io.IOException;@WebServlet("/aSessionServlet")
      public class ASessionServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {//向Session中存储数据//1 获取Session对象HttpSession session = request.getSession();//2 存储数据session.setAttribute("username", "zhangsan");}@Overrideprotected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {this.doGet(request, response);}
      }
      
    • BSessionServlet类代码

      package at.guigu.web.session;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("/bSessionServlet")
      public class BSessionServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {//从Session中获取数据//1 获取Session对象HttpSession session = request.getSession();//2 获取对应数据String username = (String) session.getAttribute("username");System.out.println(username);}@Overrideprotected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {this.doGet(request, response);}
      }
      

      运行截图如下

      在这里插入图片描述

Session原理

  • Session是基于Cookie实现的

  • 工作流程解释

    在这里插入图片描述

    • 浏览器在一次会话的第一次请求中,服务端ASessionServlet会首次创建一个Session对象,并会自动会该对象分配一个唯一标识的ID,在向Session域中存储数据后,Tomcat会给浏览器做出响应,在做出响应的这个过程中会识别到Session对象,当识别到Session对象时会自动将该Session对象对应的ID作为Cookie传给客户端浏览器(即set-cookie:JSESSIONID=ID值
    • 当浏览器第二次请求时会将该Cookie(即cookie:JSESSIONID=ID值)传给对应的服务端BSessionServlet,当BSessionServlet接收到该Cookie后会自动根据ID值来找到对应的Session对象
    • 以上步骤即可保证在一次会话的多次请求间,服务器使用的是同一个Session对象
  • 代码示例

    • 代码示例即为Session基本使用那一部分内容中的代码示例,访问服务端ASessionServlet后再次访问服务端BSessionServlet的运行截图分别如图一和图二所示

      在这里插入图片描述

      在这里插入图片描述

Session钝化、活化

  • 服务器 正常 关闭或 正常 重启后,Session中的数据依然存在,前提是浏览器处于一次会话

    • 钝化:在服务器正常关闭后,Tomcat会自动将Session数据写入到硬盘的文件中
    • 活化:再次启动服务器后,Tomcat会自动将其重新加载到Session
  • 若浏览器处于不同次会话,则重新访问服务器时,服务器会重新创建一次Session对象,而不是原来的Session对象(即获得的Session不会是同一个Session),详见以下示例

    • ASessionServlet代码如下:

      package at.guigu.web.session;import javax.servlet.*;
      import javax.servlet.http.*;
      import javax.servlet.annotation.*;
      import java.io.IOException;@WebServlet("/aSessionServlet")
      public class ASessionServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {//向Session中存储数据//1 获取Session对象HttpSession session = request.getSession();//2 存储数据session.setAttribute("username", "zhangsan");System.out.println(session);}@Overrideprotected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {this.doGet(request, response);}
      }
      

      运行截图如下,由图可知两次获得的Session对象不是同一个

      在这里插入图片描述

  • 浏览器多次请求可获得同一个Session的情景(即Session共享的条件)

    • 浏览器处于同一次会话的多个请求
      • 若浏览器完全关闭就不属于同一次会话
      • 此时服务端若正常关闭或正常重启,只要浏览器处于同一次会话就仍可获得同一个Session对象

Session存活时间及销毁

  • 默认情况下,若浏览器无任何操作则会在30分钟后自动销毁Session对象

  • 更改自动销毁时间的方法

    • 在web.xml文件中的<web-app>标签体内加入以下标签代码

      <session-config><session-timeout>自己要配置的时间,以分钟为单位</session-timeout>
      </session-config>
      
  • 在Session自动销毁之前就销毁Session对象的方法

    • 在浏览器请求的服务端(此处以第二次请求的服务端BSessionServlet为例)调用Session对象的invalidate()方法,代码如下所示

      • 销毁之后就无法获取Session中的任何数据了
      package at.guigu.web.session;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("/bSessionServlet")
      public class BSessionServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {//从Session中获取数据//1 获取Session对象HttpSession session = request.getSession();//销毁Session对象session.invalidate();//2 获取对应数据String username = (String) session.getAttribute("username");System.out.println(username);}@Overrideprotected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {this.doGet(request, response);}
      }
      

Cookie和Session的异同及应用场景

  • 相同点

    • 都是来完成一次会话内多次请求见的数据共享的
  • 不同点

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

    • 购物车:使用Cookie来存储

    • 记住我功能:使用Cookie来存储

      • 比如记住用户名和密码,这些数据都是需要安全的,但是又由于Session只能短期存储,无法长期存储,所以只能用Cookie
    • 以登录用户的名称展示:使用Session来存储

      在这里插入图片描述

    • 验证码:使用session来存储

  • 结论

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

会话跟踪技术登录案例

  • 注意:该案例已上传到Gitee,可自行下载 :https://gitee.com/cgrs572/cookie-session-demo.git

需求说明

在这里插入图片描述

环境准备

  • 创建新的Web项目BrandDemo,引入坐标(在pom.xml文件中引入坐标依赖),并配置Tomcat(可详见Tomcat部分)

    • 需要的坐标依赖有mybatis、mysql驱动、servlet、jsp、jstl

    • 需要的插件有Tomcat插件

    • 完整pom.xml文件如下

      <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/maven-v4_0_0.xsd"><modelVersion>4.0.0</modelVersion><groupId>org.example</groupId><artifactId>BrandDemo</artifactId><packaging>war</packaging><version>1.0-SNAPSHOT</version><name>BrandDemo Maven Webapp</name><url>http://maven.apache.org</url><dependencies><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>3.8.1</version><scope>test</scope></dependency><!--MyBatis依赖--><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.5.16</version></dependency><!--mysql驱动--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.33</version></dependency><!--Servlet依赖--><dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>3.1.0</version><scope>provided</scope><!--依赖范围关键字provided:在编译环境和测试环境有效,但在真正运行时就不会在使用该jar包--></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><finalName>BrandDemo</finalName><plugins><!-- Tomcat插件 --><plugin><groupId>org.apache.tomcat.maven</groupId><artifactId>tomcat7-maven-plugin</artifactId><version>2.2</version></plugin></plugins></build>
      </project>
      
  • 创建三层架构包结构

    在这里插入图片描述

  • 创建数据库表tb_user并使IDEA与数据库建立连接,SQL代码如下

    DROP TABLE IF EXISTS tb_user;
    #创建表
    CREATE TABLE tb_user (id INT PRIMARY KEY AUTO_INCREMENT,username varchar(20),password varchar(20),gender char(1)L,addr varchar(30),
    );#向表中添加数据
    INSERT INTO `tb_user` VALUES (1, 'zhangsan', '123', '男', '北京');
    INSERT INTO `tb_user` VALUES (2, '李四', '234', '女', '天津');
    INSERT INTO `tb_user` VALUES (3, '王五', '11', '男', '西安');
    

    在这里插入图片描述

  • 创建实体类User此为Pojo类 :存放对数据库中数据封装的对象),代码如下

    package at.guigu.pojo;public class User {private Integer id;private String username;private String password;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;}@Overridepublic String toString() {return "User{" +"id=" + id +", username='" + username + '\'' +", password='" + password + '\'' +'}';}
    }
    
  • MyBrtis基础环境配置

    • 在mapper包下创建UserMapper接口

    • 在项目的源代码配置文件目录(即main包下的resources目录下)创建多层目录,多层目录对应Mapper接口所在的多层包,然后再该包中创建UserMapper.xmlSQL映射文件,基本代码如下

      <?xml version="1.0" encoding="UTF-8" ?>
      <!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><!--namespace:名称空间
      -->
      <mapper namespace="at.guigu.mapper.UserMapper"><!--由于数据库中的字段名与pojo包下User类中的属性名一致,所以不需结果映射--></mapper>
      
    • 在项目的源代码配置文件目录(即main包下的resources目录下)创建MyBatis核心配置文件mybatis-config.xml,代码如下

      <?xml version="1.0" encoding="UTF-8" ?>
      <!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""https://mybatis.org/dtd/mybatis-3-config.dtd">
      <configuration><!--设置别名--><typeAliases><package name="at.guigu.pojo"/></typeAliases><environments default="development"><environment id="development"><transactionManager type="JDBC"/><dataSource type="POOLED"><!--数据库 连接信息--><property name="driver" value="com.mysql.cj.jdbc.Driver"/><property name="url" value="jdbc:mysql://localhost:3306/mybatis?useUnicode=true&amp;characterEncoding=utf-8&amp;useSSL=false&amp;serverTimezone=Asia/Shanghai"/><property name="username" value="root"/><property name="password" value="123456"/></dataSource></environment></environments><mappers><!--加载SQL映射文件:传入sql映射文件的路径-->
      <!--        <mapper resource="at/guigu/mapper/BrandMapper.xml"/>--><package name="at.guigu.mapper"/></mappers>
      </configuration>
      

      在这里插入图片描述

  • 将JSP案例中的所有代码均移动到该Web项目下,使该登录案例登录成功后即为JSP案例中的页面,具体截图详见后续

用户登录

在这里插入图片描述

  • 解释:若登录成功则进入用户个人主页,若登录失败则显示出对应错误之处

  • 要完成该问则需满足三层架构,各层需要做的工作如下所示

    在这里插入图片描述

  • Dao层/Map层

    • Step1: UserMapper接口中写入查询所有数据的方法

      package at.guigu.mapper;import at.guigu.pojo.User;
      import org.apache.ibatis.annotations.Param;
      import org.apache.ibatis.annotations.Select;public interface UserMapper {/*** 根据用户名和密码查询用户对象* @param username* @param password* @return*///@Select("select * from tb_user where username=#{username} and password=#{password}")User select(@Param("username") String username, @Param("password") String password);
      }
      
    • Step2: 在对应的SQL映射文件UserMapper.xml中写入SQL语句

      <?xml version="1.0" encoding="UTF-8" ?>
      <!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><!--namespace:名称空间
      -->
      <mapper namespace="at.guigu.mapper.UserMapper"><!--由于数据库中的字段名与pojo包下User类中的属性名一致,所以不需结果映射--><select id="select" resultType="user">select * from tb_userwhere username=#{username} and password=#{password};</select>
      </mapper>
      
  • Service层

    • 由于Service层中的类均需要调用Mapper接口中的方法,那么该层中的类就都需要载核心配置文件,来获取SqlSessionFactorySQL连接工厂对象,所以就可以将SqlSessionFactory封装为一个工具类。所以在完善Service层之前需要先在util包下将工具类SqlSessionFactoryUtils创建好,代码如下:

      package at.guigu.util;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 {//静态代码快会随着类的加载而自动执行,且只执行一次try {//配置mybatis-config.xml文件路径。注意:若该文件直接在resources目录下,则直接写文件名即可String resource = "mybatis-config.xml";//利用Resources类中的静态方法将配置文件加载到内存InputStream inputStream = Resources.getResourceAsStream(resource);//获取SqlSessionFactory对象sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);} catch (IOException e) {e.printStackTrace();}}public static SqlSessionFactory getSqlSessionFactory() {return sqlSessionFactory;}
      }
      
    • Step1: 在service包下创建UserService类来调用mapper包下的UserMapper接口中的select方法,代码如下

      注意:获取SqlSessionFactory对象的代码放在了成员变量的位置,这样所有方法可共用该对象,并不需要重复获取

      package at.guigu.service;import at.guigu.mapper.UserMapper;
      import at.guigu.pojo.User;
      import at.guigu.util.SqlSessionFactoryUtils;
      import org.apache.ibatis.session.SqlSession;
      import org.apache.ibatis.session.SqlSessionFactory;import java.util.List;
      public class UserService {//1 获取SqlSessionFactory对象SqlSessionFactory sqlSessionFactory = SqlSessionFactoryUtils.getSqlSessionFactory();/***登录方法* @param username* @param password* @return*/public User login(String username, String password) {//2 获取SqlSession对象,执行SQL语句//2.1 获取SqlSession对象SqlSession sqlSession = sqlSessionFactory.openSession();//2.2 获取Mapper接口UserMapper的代理对象UserMapper userMapper = sqlSession.getMapper(UserMapper.class);//2.3 执行sql语句User user = userMapper.select(username, password);//释放资源sqlSession.close();return user;}
      }
      
  • Web层

    • Step1:由于Tmcat运行该项目后,直接是用户登录页面,所以应该有个login.jsp登录页面代码,如下所示

      • 注意:博主将无关修饰均已去除,只有个简单的静态登录页面
      <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
      <%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
      <html><head><title>Title</title></head><body><form action="/CookieSessionDemo/loginServlet" method="post"><h1>登录系统</h1>用户名:<input name="username" type="text"><br>密码:<input name="password" type="password"><br>记住账号:<input name="remember" type="checkbox"><br><input value="登录" type="submit"><!--这里提前写了一下注册的跳转链接,后面我们会把这个页面也写完--><a href="/CookieSessionDemo/register.jsp">没有账号?</a></form></body>
      </html>
      

      其运行截图如下

      在这里插入图片描述

    • Step2:创建LoginServlet类,且代码如下

      • 注意:创建一个私有的UserService对象应将其放在成员变量的位置,因为对于大工程来说可能会多次用到Service对象
      • 若登录成功,则跳转到查询所有品牌的页面,且该页面会欢迎登录的用户
      • 若登陆失败,则重新返回到用户登录页面即login.jsp,且该页面给出提示信息
      package at.guigu.web;import at.guigu.pojo.User;
      import at.guigu.service.BrandService;
      import at.guigu.service.UserService;import javax.servlet.*;
      import javax.servlet.http.*;
      import javax.servlet.annotation.*;
      import java.io.IOException;@WebServlet("/loginServlet")
      public class LoginServlet extends HttpServlet {//1 创建一个私有的UserService对象private UserService userService = new UserService();@Overrideprotected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {//2 获取用户名和密码String username = request.getParameter("username");String password = request.getParameter("password");//3 调用Service查询用户名和密码是否正确User user = userService.login(username, password);if (user != null) {//用户登陆成功//1 将登陆成功后的User对象,存储到Session中HttpSession session = request.getSession();session.setAttribute("user", user);//2 跳转到查询所有页面//注意:由于登录界面和查询所有页面的请求没有资源共享,所以使用重定向//动态获取虚拟目录String contextPath = request.getContextPath();response.sendRedirect(contextPath + "/selectAllServlet");} else {//用户登录失败//将错误信息存储到request域中request.setAttribute("login_msg", "用户名或密码错误");//跳回到用户登录页面即login.jsprequest.getRequestDispatcher("/login.jsp").forward(request, response);}}@Overrideprotected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {this.doGet(request, response);}
      }
      
      • 若用户登录成功,则跳转到查询所有商品页面,且该页面会欢迎登录的用户,所以brand.jsp代码如下

        <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
        <%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
        <html><head><title>Title</title></head><body><h1>${user.username},欢迎您</h1><input type="button" value="新增" id="add"><br><hr><table border="1" cellspacing="0"><tr><th>序号</th><th>品牌名称</th><th>企业名称</th><th>排序</th><th>品牌介绍</th><th>状态</th><th>操作</th></tr><c:forEach items="${brands}" var="brand" varStatus="xuhao"><tr align="center"><td>${xuhao.count}</td><td>${brand.brandName}</td><td>${brand.companyName}</td><td>${brand.ordered}</td><td>${brand.description}</td><c:choose><c:when test="${brand.status==1}"><td>启用</td></c:when><c:otherwise><td>禁用</td></c:otherwise></c:choose><td><a href="/CookieSessionDemo/selectByIdServlet?id=${brand.id}">修改</a> <a href="#">删除</a></td></tr></c:forEach></table><script>document.getElementById("add").onclick = function () {//addBrand.jsp的路径location.href = "/CookieSessionDemo/addBrand.jsp";}</script></body>
        </html>
        
      • 若登陆失败,则重新返回到用户登录页面即login.jsp,且该页面给出提示信息,所以login.jsp更改后的代码如下

        <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
        <%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
        <html><head><title>Login</title></head><body><form action="/CookieSessionDemo/loginServlet" method="post"><h1>登录系统</h1><%--提示错误信息代码--%><div>${login_msg}</div>用户名:<input name="username" type="text"><br>密码:<input name="password" type="password"><br>记住账号:<input name="remember" type="checkbox"><br><input value="登录" type="submit"><!--这里提前写了一下注册的跳转链接,后面我们会把这个页面也写完--><a href="/CookieSessionDemo/register.jsp">没有账号?</a></form></body>
        </html>
        
  • Tomcat运行该Web项目后截图如下所示

    • 登录成功界面

      在这里插入图片描述

    • 登陆失败界面

      在这里插入图片描述

记住用户——写Cookie

在这里插入图片描述

  • 解释:若用户勾选“记住账号”,则下次访问登录页面时会 自动填充 用户名和密码

  • 过程

    • 将用户名和密码写入到Cookie中,并且进行持久化存储Cookie,下次访问浏览器时会自动携带Cookie
    • 在页面获取到Cookie数据后,会将用户名和密码自动设置到用户名和密码框中
  • 写Cookie的条件

    • 用户登录成功
    • 用户勾选“记住账号”复选框
  • 需要对Web层进行更改,如下图所示

    在这里插入图片描述

  • Web层

    • Step1:login.jsp代码如下

      • 在复选框中加入属性value="1",这样只要复选框被勾选服务端就能通过获取到该复选框数据来判断是否用户“记住账号”
      <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
      <%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
      <html><head><title>Login</title></head><body><form action="/CookieSessionDemo/loginServlet" method="post"><h1>登录系统</h1><%--提示错误信息--%><div>${login_msg}</div>用户名:<input name="username" type="text"><br>密码:<input name="password" type="password"><br>记住账号:<input name="remember" type="checkbox" value="1"><br><input value="登录" type="submit"><!--这里提前写了一下注册的跳转链接,后面我们会把这个页面也写完--><a href="/CookieSessionDemo/register.jsp">没有账号?</a></form></body>
      </html>
      
    • Step2:LoginServlet类代码如下

      package at.guigu.web;import at.guigu.pojo.User;
      import at.guigu.service.BrandService;
      import at.guigu.service.UserService;import javax.servlet.*;
      import javax.servlet.http.*;
      import javax.servlet.annotation.*;
      import java.io.IOException;@WebServlet("/loginServlet")
      public class LoginServlet extends HttpServlet {//1 创建一个私有的UserService对象private UserService userService = new UserService();@Overrideprotected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {//2 获取用户名和密码以及获取复选框数据String username = request.getParameter("username");String password = request.getParameter("password");// 获取复选框数据String remember = request.getParameter("remember");//3 调用Service查询用户名和密码是否正确User user = userService.login(username, password);if (user != null) {//用户登陆成功//1 判断用户是否勾选复选框(即是否勾选记住账号)if ("1".equals(remember)) {//1.1 创建Cookie对象Cookie c_username = new Cookie("username", username);Cookie c_password = new Cookie("password", password);//1.2 设置Cookie存活时间——均设置为7天c_username.setMaxAge(60 * 60 * 24 * 7);c_password.setMaxAge(60 * 60 * 24 * 7);//1.3 将其发送到客户端response.addCookie(c_username);response.addCookie(c_password);}//2 将登陆成功后的User对象,存储到Session中HttpSession session = request.getSession();session.setAttribute("user", user);//3 跳转到查询所有页面//注意:由于登录界面和查询所有页面的请求没有资源共享,所以使用重定向//动态获取虚拟目录String contextPath = request.getContextPath();response.sendRedirect(contextPath + "/selectAllServlet");} else {//用户登录失败//将错误信息存储到request域中request.setAttribute("login_msg", "用户名或密码错误");//跳回到用户登录页面即login.jsprequest.getRequestDispatcher("/login.jsp").forward(request, response);}}@Overrideprotected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {this.doGet(request, response);}
      }
      
  • Tomcat运行成功后截图如下

    • Step1:输入用户名和密码并勾选“记住账号”后单击登录

      在这里插入图片描述

    • Step2:登录成功,会进入到查询所有商品的页面。由于是通过LoginServlet类向客户端服务器发送的响应,所以需要在开发者工具中找到loginServlet,打开后如图所示

      在这里插入图片描述

记住用户——获取Cookie

  • 解释:勾选“记住账号”后在下一次打开浏览器登录时会自动填充用户名和密码

    • 用户名和密码的获取是在登录界面login.jsp的代码中直接获取 (此处用到EL表达式) 并填写到对应的用户名和密码框中
  • EL表达式获取到Cookie的代码方式为

    EL代码解释
    ${cookie.key.value}通过键key获取到对应的键值。key指存储在Cookie中自己所设置的键的名称
  • login.jsp代码如下

    <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
    <%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
    <html><head><title>Login</title></head><body><form action="/CookieSessionDemo/loginServlet" method="post"><h1>登录系统</h1><%--提示错误信息--%><div>${login_msg}</div>用户名:<input name="username" type="text" value="${cookie.username.value}"><br>密码:<input name="password" type="password" value="${cookie.password.value}"><br>记住账号:<input name="remember" type="checkbox" value="1"><br><input value="登录" type="submit"><!--这里提前写了一下注册的跳转链接,后面我们会把这个页面也写完--><a href="/CookieSessionDemo/register.jsp">没有账号?</a></form></body>
    </html>
    
  • Tomcat运行该Web项目后,只要在输入用户名和密码之后勾选“记住账号”,则在下一次打开浏览器登录时会自动填充用户名和密码,如图所示

    在这里插入图片描述

用户注册

在这里插入图片描述

  • 解释:将用户信息保存到数据库,使用户下次能够登录

  • 注册涉及的功能

    • 注册功能:保存用户信息至数据库
    • 验证码功能:
      • 展示验证码:展示验证码图片,并且可以点击切换
      • 校验验证码:验证码填写不正确,则注册失败
  • 要完成该问则需满足三层架构,各层需要做的工作如下所示

    在这里插入图片描述

  • Dao层/Map层

    • Step1: UserMapper接口中写入对应的方法

      package at.guigu.mapper;import at.guigu.pojo.User;
      import org.apache.ibatis.annotations.Param;
      import org.apache.ibatis.annotations.Select;public interface UserMapper {/*** 根据用户名和密码查询用户对象* @param username* @param password* @return*///@Select("select * from tb_user where username=#{username} and password=#{password}")User select(@Param("username") String username, @Param("password") String password);/*** 通过用户名查询用户* @param username* @return*///@Select("select * from tb_user where username=#{username}")User selectByUsername(String username);/*** 注册用户* @param user*///@Select("insert into tb_user(username, password) values (#{username}, #{password})")void add(User user);
      }
      
    • Step2:在对应的SQL映射文件UserMapper.xml中写入SQL语句

      <?xml version="1.0" encoding="UTF-8" ?>
      <!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><!--namespace:名称空间
      -->
      <mapper namespace="at.guigu.mapper.UserMapper"><!--由于数据库中的字段名与pojo包下User类中的属性名一致,所以不需结果映射--><select id="select" resultType="user">select * from tb_userwhere username=#{username} and password=#{password};</select><select id="selectByUsername" resultType="at.guigu.pojo.User">select * from tb_user where username=#{username};</select><insert id="add">insert into tb_user(username, password) values (#{username}, #{password});</insert>
      </mapper>
      
  • Service层

    • 工具类SqlSessionFactoryUtils代码省略,详见用户登录中的SqlSessionFactoryUtils代码

    • Step1: 在service包下创建UserService类来调用mapper包下的UserMapper接口中的方法,代码如下

      package at.guigu.service;import at.guigu.mapper.UserMapper;
      import at.guigu.pojo.User;
      import at.guigu.util.SqlSessionFactoryUtils;
      import org.apache.ibatis.session.SqlSession;
      import org.apache.ibatis.session.SqlSessionFactory;import java.util.List;
      public class UserService {//1 获取SqlSessionFactory对象SqlSessionFactory sqlSessionFactory = SqlSessionFactoryUtils.getSqlSessionFactory();/***登录方法* @param username* @param password* @return*/public User login(String username, String password) {//2 获取SqlSession对象,执行SQL语句//2.1 获取SqlSession对象SqlSession sqlSession = sqlSessionFactory.openSession();//2.2 获取Mapper接口UserMapper的代理对象UserMapper userMapper = sqlSession.getMapper(UserMapper.class);//2.3 执行sql语句User user = userMapper.select(username, password);//释放资源sqlSession.close();return user;}/*** 注册功能* @param user* @return*/public boolean register(User user) {//2 获取SqlSession对象,执行SQL语句//2.1 获取SqlSession对象SqlSession sqlSession = sqlSessionFactory.openSession();//2.2 获取Mapper接口UserMapper的代理对象UserMapper userMapper = sqlSession.getMapper(UserMapper.class);//3 判断用户名是否存在,若不存在则可添加用户User u = userMapper.selectByUsername(user.getUsername());if (u == null) {//用户名不存在,注册//4 执行sql语句userMapper.add(user);//5 注意:增删改的SQL语句需要手动提交事务让其生效sqlSession.commit();}//释放资源sqlSession.close();//当u!=null时代表用户名存在,会返回false;反之会返回truereturn u==null;}
      }
      
  • Web层

    • Step1:在Web项目核心目录下创建register.jsp,且代码如下

      <%--Created by IntelliJ IDEA.User: 10195Date: 2024/7/2Time: 13:54To change this template use File | Settings | File Templates.
      --%>
      <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
      <%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
      <html><head><title>Title</title></head><body><form action="/CookieSessionDemo/registerServlet" method="post"><h1>欢迎注册</h1><!--已有账号的话就跳转至登录页面-->已有账号?<a href="login.jsp">点击登录</a><br><%--提示错误信息--%><div>${register_msg}</div>用户名:<input name="username" type="text"><br>密码:<input name="password" type="password"><br><input value="注册" type="submit"></form></body>
      </html>
      
    • Step2:创建RegisterServlet类且代码如下

      package at.guigu.web;import at.guigu.pojo.User;
      import at.guigu.service.UserService;import javax.servlet.*;
      import javax.servlet.http.*;
      import javax.servlet.annotation.*;
      import java.io.IOException;@WebServlet("/registerServlet")
      public class RegisterServlet extends HttpServlet {//1 创建一个私有的UserService对象private UserService userService = new UserService();@Overrideprotected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {//2 接收用户信息:获取用户名和密码以及获取复选框数据String username = request.getParameter("username");String password = request.getParameter("password");User user = new User();user.setUsername(username);user.setPassword(password);//3 调用Service注册用户Boolean flag = userService.register(user);//4 判断是否注册成功if (flag) {//注册成功request.setAttribute("register_msg", "注册成功,请登录");//跳回到用户登录页面即login.jsprequest.getRequestDispatcher("/login.jsp").forward(request, response);} else {//注册失败//将错误信息存储到request域中request.setAttribute("register_msg", "用户名重复");//跳回到用户登录页面即login.jsprequest.getRequestDispatcher("/register.jsp").forward(request, response);}}@Overrideprotected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {this.doGet(request, response);}
      }
      
  • Tomcat运行该Web项目后运行截图如下所示

    • 单击“没有账号”跳转到注册页面

      在这里插入图片描述

    • 注册失败:注册时若用户名已存在则会提示

      在这里插入图片描述

    • 注册城成功后会自动跳转到登录界面并提示注册成功

      在这里插入图片描述

验证码展示

  • 解释:展示验证码图片并可以通过单击切换验证码

  • 验证码原理:它其实就是Java代码生成的图片

  • 验证码作用:防止机器自动注册以此来攻击服务器

  • 实现流程

    • 前端发送请求给CheckCodeServlet
    • CheckCodeServlet接收到请求后,生成验证码图片,将图片用Reponse对象的输出流写回到前端
  • Service层

    • 验证码工具类CheckCodeUtil代码如下

      package at.guigu.util;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 返回验证码数据* @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 width* @param height* @param outputFile* @param verifySize* @return* @throws IOException*/public static String outputVerifyImage(int width, int height, File outputFile, int verifySize) throws IOException {String verifyCode = generateVerifyCode(verifySize);outputImage(width, height, outputFile, verifyCode);return verifyCode;}/*** 生成指定验证码图像文件** @param width* @param height* @param outputFile* @param code* @throws IOException*/public static void outputImage(int width, int height, 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(width, height, 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);}}}
      }
      
  • Web层

    • Step1:register.jsp文件代码更改如下:

      • 注意:
        • 验证码图片路径为CheckCodeServlet
        • 给图片绑定一个单击事件,只要单击图片就会重新生成验证码
        • 看不清?绑定一个单击事件,只要单击看不清?就会重新生成验证码
          • 看不清?绑定的单击事件对应的图片路径要加上一个时间戳,原因是:在单击看不清?之前所生成的验证码数据图片已经被浏览器缓存,所以若不加时间戳则单击看不清?后验证码数据不会改变,使用的是被缓存的验证码数据图片
      <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
      <%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
      <html><head><title>Title</title></head><body><form action="/CookieSessionDemo/registerServlet" method="post"><h1>欢迎注册</h1><!--已有账号的话就跳转至登录页面-->已有账号?<a href="login.jsp">点击登录</a><br><%--提示错误信息--%><div>${register_msg}</div>用户名:<input name="username" type="text"><br>密码:<input name="password" type="password"><br>验证码:<input name="checkCode" type="text"><img id="checkCodeImg" src="/CookieSessionDemo/checkCodeServlet"><a href="#" id="checkImg">看不清?</a><input value="注册" type="submit"></form><script>document.getElementById("checkCodeImg").onclick = function () {//路径后面加一个时间戳,能保证生成的图片永远不一样,避免浏览器缓存静态资源document.getElementById("checkCodeImg").src = "/CookieSessionDemo/checkCodeServlet?" + new Date().getMilliseconds();}document.getElementById("checkImg").onclick = function () {//路径后面加一个时间戳,能保证生成的图片永远不一样,避免浏览器缓存静态资源document.getElementById("checkCodeImg").src = "/CookieSessionDemo/checkCodeServlet?" + new Date().getMilliseconds();}</script></body>
      </html>
      
    • Step2:创建CheckCodeServlet类且代码如下

      package at.guigu.web;import at.guigu.util.CheckCodeUtil;import javax.servlet.*;
      import javax.servlet.http.*;
      import javax.servlet.annotation.*;
      import java.io.IOException;
      import java.io.OutputStream;@WebServlet("/checkCodeServlet")
      public class CheckCodeServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {//获取字节输出流ServletOutputStream sos = response.getOutputStream();//获取验证码字符串数据String checkCode = CheckCodeUtil.outputVerifyImage(100, 50, sos, 4);}@Overrideprotected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {this.doGet(request, response);}
      }
      
  • Tomcat运行该Web项目,运行截图如下

    在这里插入图片描述

验证码校验

  • 解释

    • 判断程序生成的验证码和用户输入的验证码是否一样,若不一样则阻止注册
    • 验证码图片和提交注册表单是两次请求,所以要将程序生成的验证码存入Session
  • Web层工作流程如下

    在这里插入图片描述

  • Web层

    • Step1:CheckCodeServlet文件代码更改如下:

      package at.guigu.web;import at.guigu.util.CheckCodeUtil;import javax.servlet.*;
      import javax.servlet.http.*;
      import javax.servlet.annotation.*;
      import java.io.IOException;
      import java.io.OutputStream;@WebServlet("/checkCodeServlet")
      public class CheckCodeServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {//1 生成验证码//获取字节输出流ServletOutputStream sos = response.getOutputStream();//调用工具类获取验证码字符串数据并生成验证码数据图片String checkCode = CheckCodeUtil.outputVerifyImage(100, 50, sos, 4);//2 将系统自动生成的验证码字符串存入SessionHttpSession session = request.getSession();session.setAttribute("checkCodeGen", checkCode);}@Overrideprotected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {this.doGet(request, response);}
      }
      
    • Step2:RegisterServlet文件代码更改如下:

      package at.guigu.web;import at.guigu.pojo.User;
      import at.guigu.service.UserService;import javax.servlet.*;
      import javax.servlet.http.*;
      import javax.servlet.annotation.*;
      import java.io.IOException;@WebServlet("/registerServlet")
      public class RegisterServlet extends HttpServlet {//1 创建一个私有的UserService对象private UserService userService = new UserService();@Overrideprotected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {//2 接收用户信息:获取用户名和密码以及获取复选框数据String username = request.getParameter("username");String password = request.getParameter("password");User user = new User();user.setUsername(username);user.setPassword(password);//获取用户输入的验证码String checkCode = request.getParameter("checkCode");//获取程序自动生成的验证码HttpSession session = request.getSession();String checkCodeGen = (String) session.getAttribute("checkCodeGen");//验证验证码是否正确:此处忽略大小写if (!checkCodeGen.equalsIgnoreCase(checkCode)) {//如果不一样则不允许注册,即注册失败//将错误信息存储到request域中request.setAttribute("register_msg", "验证码错误,请重新输入");//跳回到用户登录页面即login.jsprequest.getRequestDispatcher("/register.jsp").forward(request, response);return;//结束执行本java文件中的后续代码}//3 调用Service注册用户Boolean flag = userService.register(user);//4 判断是否注册成功if (flag) {//注册成功request.setAttribute("login_msg", "注册成功,请登录");//跳回到用户登录页面即login.jsprequest.getRequestDispatcher("/login.jsp").forward(request, response);} else {//注册失败//将错误信息存储到request域中request.setAttribute("register_msg", "用户名重复");//跳回到用户登录页面即login.jsprequest.getRequestDispatcher("/register.jsp").forward(request, response);}}@Overrideprotected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {this.doGet(request, response);}
      }
      
  • Tomcat运行该Web项目,运行截图如下

    • 验证码错误

      在这里插入图片描述

    • 验证码正确

      在这里插入图片描述

相关文章:

大数据------JavaWeb------会话跟踪技术(Cookie、Session)(完整知识点汇总)

会话跟踪技术&#xff08;Cookie&Session&#xff09; 注意&#xff1a; HTTP协议是无状态 的&#xff0c;即每次浏览器向服务器请求时&#xff0c;服务器都会将该请求视为新的请求&#xff0c;因此我们需要会话跟踪技术来实现会话内的数据共享 会话 当用户打开浏览器&am…...

crossJoin笛卡尔积

crossJoin笛卡尔积 在Spark中&#xff0c;crossJoin方法用于执行两个数据集之间的笛卡尔积操作。具体来说&#xff0c;如果有两个数据集&#xff08;DataFrame或Dataset&#xff09;&#xff0c;调用crossJoin方法将会生成一个新的数据集&#xff0c;其中包含两个原始数据集中所…...

Java客户端调用SOAP方式的WebService服务实现方式分析

简介 在多系统交互中&#xff0c;有时候需要以Java作为客户端来调用SOAP方式的WebService服务&#xff0c;本文通过分析不同的调用方式&#xff0c;以Demo的形式&#xff0c;帮助读者在生产实践中选择合适的调用方式。 本文JDK环境为JDK17。 结论 推荐使用Axis2或者Jaxws&#…...

华为机试真题--字符串序列判定

题目描述: 输入两个字符串S和L,都只包含英文小写字母,其中S长度<=100,L长度<=500000,请判定S是否是L的有效字串。 判定规则: S中的每个字符在L中都能找到(可以不连续),且S在L中字符的前后顺序与S中顺序要保持一致。(例如,S="ace"是L="abcd…...

Linux内核 -- 虚拟化之virtqueue结构

Linux Kernel中的Virtqueue Virtqueue是Linux Kernel中用于实现Virtio设备的一个关键数据结构。Virtio是一种虚拟I/O设备标准&#xff0c;旨在简化虚拟化环境中虚拟设备与虚拟机之间的通信。Virtqueue则是实现这种通信的核心机制。以下是Virtqueue的一些关键点&#xff1a; V…...

【pytorch18】Logistic Regression

回忆线性回归 for continuous:y xwbfor probability output:yσ(xwb) σ:sigmoid or logistic 线性回归是简单的线性模型&#xff0c;输入是x&#xff0c;网络参数是w和b&#xff0c;输出是连续的y的值 如何把它转化为分类问题?加了sigmoid函数&#xff0c;输出的值不再是…...

PostgreSQL的使用

PostgreSQL的使用 1.首先&#xff0c;使用docker进行安装pgvector数据库&#xff0c;具体的安装步骤可以查看我之前发的博文。 2.docker exec -it pgvector /bin/bash 进入docker容器内部&#xff0c;操作数据库&#xff0c;上述命令是以交互式命令进入了容器的内部&#xf…...

python 高级技巧 0706

python 33个高级用法技巧 列表推导式 简化了基于现有列表创建新列表的过程。 squares [x**2 for x in range(10)] print(squares)[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]字典推导式 用简洁的方式创建字典。 square_dict {x: x**2 for x in range(10)} print(square_dict){0…...

面试经典 106. 从中序与后序遍历序列构造二叉树

最近小胖开始找工作了&#xff0c;又来刷苦逼的算法了 555 废话不多说&#xff0c;看这一题&#xff0c;上链接&#xff1a;https://leetcode.cn/problems/construct-binary-tree-from-inorder-and-postorder-traversal/description/?envTypestudy-plan-v2&envIdtop-inte…...

如何解决群晖Docker注册表查询失败/无法拉取镜像等问题

文章目录 📖 介绍 📖🏡 演示环境 🏡📒 问题概述 📒📒 解决方案 📒🔖 方法一🔖 方法二🔖 方法三⚓️ 相关链接 🚓️📖 介绍 📖 在群晖(Synology)NAS设备上使用Docker时,我们可能会遇到查询Docker注册表失败,无法拉取Docker镜像的问题。这种情况…...

【Scrapy】 深入了解 Scrapy 中间件中的 process_spider_input 方法

准我快乐地重饰演某段美丽故事主人 饰演你旧年共寻梦的恋人 再去做没流着情泪的伊人 假装再有从前演过的戏份 重饰演某段美丽故事主人 饰演你旧年共寻梦的恋人 你纵是未明白仍夜深一人 穿起你那无言毛衣当跟你接近 &#x1f3b5; 陈慧娴《傻女》 Scrapy 是…...

数据库MySQL---基础篇

存储和管理数据的仓库 MySQL概述 数据库相关概念 数据库&#xff08;DataBase&#xff09;---数据存储的仓库&#xff0c;数据是有组织的进行存储 数据库管理系统&#xff08;DBMS&#xff09;-----操纵和管理数据库的大型软件 SQL----操作关系型数据库的编程语言&#xff…...

欧姆龙安全PLC及周边产品要点指南

电气安全、自动化设备作业安全&#xff0c;向来是非常非常之重要的&#xff01;越来越多的客户在规划新产线、改造既有产线的过程中&#xff0c;明确要求设计方和施工方将安全考虑进整体方案中进行考虑和报价&#xff01;作为一名自动化电气工程师&#xff0c;尤其是高级工程师…...

tableau气泡图与词云图绘制 - 8

气泡图及词云图绘制 1. 气泡图绘制1.1 选择相关属性字段1.2 选择气泡图1.3 设置颜色1.4 设置标签1.5 设置单位 2. 气泡图绘制 - 22.1 类别筛选2.2 页面年份获取2.3 行列获取2.4 历史轨迹显示 3. 词云图绘制3.1 筛选器3.2 选择相关属性3.3 选择气泡图3.4 设置类型颜色3.5 设置形…...

C语言 找出一个二维数组中的鞍点

找出一个二维数组中的鞍点,即该位置上的元素在该行上最大、在该列上最小。也可能没有鞍点。 #include <stdio.h>int main() {int matrix[4][4] {{10, 17, 13, 28},{21, 14, 16, 40},{30, 42, 23, 39},{24, 11, 19, 17}};int n 4, m 4;int found 0;for (int i 0; i …...

【笔记】在linux中设置错文件如何重置

以mysql的auto.cnf文件为例...

DNS中的CNAME与A记录:为什么无法共存A解析和C解析?

在互联网的世界中&#xff0c;DNS&#xff08;域名系统&#xff09;扮演着至关重要的角色&#xff0c;它将易于记忆的域名转换为计算机可识别的IP地址。在这个过程中&#xff0c;两种常见的DNS记录类型——CNAME记录和A记录——经常被提及。然而&#xff0c;它们之间存在着一些…...

线程和进程

文章目录 进程和线程进程线程案例 时间片概念调度方式线程的创建和启动第一种创建方式第二种创建方式&#xff08;匿名内部类&#xff09;第三种创建方式&#xff08;Runnable接口&#xff09;main线程和t线程之间的关系 线程的名字线程的优先级线程状态 进程和线程 进程 在计…...

【JavaEE】 简单认识CPU

&#x1f435;本篇文章将对cpu的相关知识进行讲解 一、认识CPU 下图是简略的冯诺依曼体系结构图 上图中&#xff0c;存储器用来存储数据&#xff0c;注意在存储器中都是以二进制的形式存储数据的&#xff0c;CPU就是中央处理器&#xff0c;其功能主要是进行各种算术运算和各种…...

《数字图像处理-OpenCV/Python》第17章:图像的特征描述

《数字图像处理-OpenCV/Python》第17章&#xff1a;图像的特征描述 本书京东 优惠购书链接 https://item.jd.com/14098452.html 本书CSDN 独家连载专栏 https://blog.csdn.net/youcans/category_12418787.html 第17章&#xff1a;图像的特征描述 特征检测与匹配是计算机视觉的…...

Java 语言特性(面试系列2)

一、SQL 基础 1. 复杂查询 &#xff08;1&#xff09;连接查询&#xff08;JOIN&#xff09; 内连接&#xff08;INNER JOIN&#xff09;&#xff1a;返回两表匹配的记录。 SELECT e.name, d.dept_name FROM employees e INNER JOIN departments d ON e.dept_id d.dept_id; 左…...

设计模式和设计原则回顾

设计模式和设计原则回顾 23种设计模式是设计原则的完美体现,设计原则设计原则是设计模式的理论基石, 设计模式 在经典的设计模式分类中(如《设计模式:可复用面向对象软件的基础》一书中),总共有23种设计模式,分为三大类: 一、创建型模式(5种) 1. 单例模式(Sing…...

智慧医疗能源事业线深度画像分析(上)

引言 医疗行业作为现代社会的关键基础设施,其能源消耗与环境影响正日益受到关注。随着全球"双碳"目标的推进和可持续发展理念的深入,智慧医疗能源事业线应运而生,致力于通过创新技术与管理方案,重构医疗领域的能源使用模式。这一事业线融合了能源管理、可持续发…...

前端倒计时误差!

提示:记录工作中遇到的需求及解决办法 文章目录 前言一、误差从何而来?二、五大解决方案1. 动态校准法(基础版)2. Web Worker 计时3. 服务器时间同步4. Performance API 高精度计时5. 页面可见性API优化三、生产环境最佳实践四、终极解决方案架构前言 前几天听说公司某个项…...

如何在看板中体现优先级变化

在看板中有效体现优先级变化的关键措施包括&#xff1a;采用颜色或标签标识优先级、设置任务排序规则、使用独立的优先级列或泳道、结合自动化规则同步优先级变化、建立定期的优先级审查流程。其中&#xff0c;设置任务排序规则尤其重要&#xff0c;因为它让看板视觉上直观地体…...

cf2117E

原题链接&#xff1a;https://codeforces.com/contest/2117/problem/E 题目背景&#xff1a; 给定两个数组a,b&#xff0c;可以执行多次以下操作&#xff1a;选择 i (1 < i < n - 1)&#xff0c;并设置 或&#xff0c;也可以在执行上述操作前执行一次删除任意 和 。求…...

《通信之道——从微积分到 5G》读书总结

第1章 绪 论 1.1 这是一本什么样的书 通信技术&#xff0c;说到底就是数学。 那些最基础、最本质的部分。 1.2 什么是通信 通信 发送方 接收方 承载信息的信号 解调出其中承载的信息 信息在发送方那里被加工成信号&#xff08;调制&#xff09; 把信息从信号中抽取出来&am…...

涂鸦T5AI手搓语音、emoji、otto机器人从入门到实战

“&#x1f916;手搓TuyaAI语音指令 &#x1f60d;秒变表情包大师&#xff0c;让萌系Otto机器人&#x1f525;玩出智能新花样&#xff01;开整&#xff01;” &#x1f916; Otto机器人 → 直接点明主体 手搓TuyaAI语音 → 强调 自主编程/自定义 语音控制&#xff08;TuyaAI…...

微信小程序云开发平台MySQL的连接方式

注&#xff1a;微信小程序云开发平台指的是腾讯云开发 先给结论&#xff1a;微信小程序云开发平台的MySQL&#xff0c;无法通过获取数据库连接信息的方式进行连接&#xff0c;连接只能通过云开发的SDK连接&#xff0c;具体要参考官方文档&#xff1a; 为什么&#xff1f; 因为…...

Maven 概述、安装、配置、仓库、私服详解

目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...