会话跟踪技术(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);
- Step1: 创建Cookie对象并设置数据
-
代码示例
-
客户端访问A
Servlet
,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 { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //发送Cookie //1 创建Cookie对象并设置数据 Cookie cookie = new Cookie("username", "zhangsan"); //2 发送Cookie response.addCookie(cookie); } @Override protected 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
信息
- 注意:由于在实际项目中浏览器会保存很多
- Step1: 使用Request对象获取客户端携带的所有
-
代码示例
客户端访问A
Servlet
后(此时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 { @Override protected 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; } } } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }
Tomcat运行该Web项目后运行截图如下
Cookie
原理
-
Cookie的实现是基于HTTP协议的
- 响应头:
set-cookie
- 发送
Cookie
用到响应头
- 发送
- 请求头:
cookie
- 获取
Cookie
用到请求头
- 获取
- 响应头:
-
工作流程解释
- A
Servlet
发送Cookie
: AServlet
中创建了Cookie
数据,Tomcat会将该Cookie
对象响应给客户端浏览器,由于Tomcat是基于Http协议做出响应的,所以发送给客户端的是Http协议的响应数据,当Tomcat发现服务端要发送Cookie
给客户端浏览器时,会在响应数据中给Cookie加上一个响应头即set-cookie
,然后将其发送给客户端浏览器,客户端接收到该响应数据后通过响应头set-cookie
知道服务端发送的是Cookie
,则会对其进行解析,解析后则会拿到username=zhangsan
这个数据并将其存储到客户端的内存中 - B
Servlet
接收Cookie :当客户端携带Cookie数据来访问BServlet
时,浏览器会自动为Cookie
设置一个请求头cookie
,然后将其发送给BServlet
,然后BServlet
即可通过对应的方法来获取到客户端发送到服务器的所有Cookie
- A
-
代码示例
-
代码示例即为发送
Cookie
和获取Cookie
的代码示例,运行截图如下
-
Cookie
存活时间
Cookie 类中的方法 | 解释 |
---|---|
public void setMaxAge(int seconds) | 设置 Cookie 的最大存活时间。seconds 为秒数 |
-
默认情况下 Cookie存储在浏览器内存中,当浏览器被关闭时内存释放,则Cookie被销毁
- 即B
Servlet
能访问到AServlet
发送给浏览器的Cookie
是因为浏览器在发送请求给AServlet
后并没有关闭浏览器,如果浏览器在发送请求给AServlet
后关闭了浏览器,此时浏览器发送请求给BServlet
则BServlet
接收不到AServlet
发送给浏览器的Cookie
,除非AServlet
设置Cookie存活时间。
- 即B
-
可利用
setMaxAge(int seconds)
方法来设置Cookie存活时间- 正数: 将
Cooke
写入浏览器所在电脑的硬盘,持久化存储,但到时间会自动删除 - 负数: 默认值,
Cookie
在当前浏览器内存中,当浏览器关闭时Cookie会被立即销毁 - 零: 删除对应
Cookie
- 正数: 将
-
代码示例
-
A
Servlet
代码如下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 { @Override protected 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 发送Cookie response.addCookie(cookie); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }
-
B
Servlet
代码详见获取Cookie
,Tomcat运行该Web项目后运行步骤截图如下所示-
Step1:访问A
Servlet
后关闭浏览器 -
Step2:重新打开一个浏览器,访问B
Servlet
,访问后服务端显示出Cookie
-
-
Cookie
存储中文
-
Cookie默认情况下不能存储中文,若存储则会报错
-
A
Servlet
的Cookie
存储中文方法:利用URL编码进行转码-
Step1:单独设置Cookie构造器的第二个参数value的值
-
Step2:将value进行转码:
value = URLEncoder.encode(value,"UTF-8");
-
Step3:创建Cookie对象:
Cookie cookie = new Cookie("key", value)
-
A
Servlet
代码示例如下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 { @Override protected 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 发送Cookie response.addCookie(cookie); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }
-
-
B
Servlet
获取Cookie
中的中文的方法:利用URL编码进行解码-
Step1:首先获取到对应的
value
值 -
Step2:将
value
值利用URLDecoder.decode(value, "UTF-8");
方法进行解码 -
B
Servlet
代码示例如下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 { @Override protected 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; } } } @Override protected 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
对象,我们可以通过AServlet
向Session
对象中存入数据;然后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对象相关方法执行对应的功能
- Step1:通过
-
代码示例
-
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 { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //向Session中存储数据 //1 获取Session对象 HttpSession session = request.getSession(); //2 存储数据 session.setAttribute("username", "zhangsan"); } @Override protected 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 { @Override protected 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); } @Override protected 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
中
- 钝化:在服务器正常关闭后,Tomcat会自动将
-
若浏览器处于不同次会话,则重新访问服务器时,服务器会重新创建一次
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 { @Override protected 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); } @Override protected 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 { @Override protected 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); } @Override protected 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来存储
-
验证码:使用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_brand并使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; } @Override public String toString() { return "User{" + "id=" + id + ", username='" + username + '\'' + ", password='" + password + '\'' + '}'; } }
-
MyBrtis基础环境配置
-
在mapper包下创建
UserMapper
接口 -
在项目的源代码配置文件目录(即main包下的resources目录下)创建多层目录,多层目录对应Mapper接口所在的多层包,然后再该包中创建
UserMapper.xml
SQL映射文件,基本代码如下<?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"> <!-- namespace:名称空间 --> <mapper namespace="at.guigu.mapper.UserMapper"> <!--由于数据库中的字段名与pojo包下User类中的属性名一致,所以不需结果映射--> </mapper>
-
在项目的源代码配置文件目录(即main包下的resources目录下)创建MyBatis核心配置文件
mybatis-config.xml
,代码如下<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//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&characterEncoding=utf-8&useSSL=false&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 mapper PUBLIC "-//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_user where username=#{username} and password=#{password}; </select> </mapper>
-
-
Service层
-
由于Service层中的类均需要调用Mapper接口中的方法,那么该层中的类就都需要载核心配置文件,来获取
SqlSessionFactory
SQL连接工厂对象,所以就可以将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(); @Override protected 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.jsp request.getRequestDispatcher("/login.jsp").forward(request, response); } } @Override protected 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(); @Override protected 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.jsp request.getRequestDispatcher("/login.jsp").forward(request, response); } } @Override protected 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 mapper PUBLIC "-//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_user where 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;反之会返回true return u==null; } }
-
-
Web层
-
Step1:在Web项目核心目录下创建
register.jsp
,且代码如下<%-- Created by IntelliJ IDEA. User: 10195 Date: 2024/7/2 Time: 13:54 To 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(); @Override protected 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.jsp request.getRequestDispatcher("/login.jsp").forward(request, response); } else {//注册失败 //将错误信息存储到request域中 request.setAttribute("register_msg", "用户名重复"); //跳回到用户登录页面即login.jsp request.getRequestDispatcher("/register.jsp").forward(request, response); } } @Override protected 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 { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //获取字节输出流 ServletOutputStream sos = response.getOutputStream(); //获取验证码字符串数据 String checkCode = CheckCodeUtil.outputVerifyImage(100, 50, sos, 4); } @Override protected 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 { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //1 生成验证码 //获取字节输出流 ServletOutputStream sos = response.getOutputStream(); //调用工具类获取验证码字符串数据并生成验证码数据图片 String checkCode = CheckCodeUtil.outputVerifyImage(100, 50, sos, 4); //2 将系统自动生成的验证码字符串存入Session HttpSession session = request.getSession(); session.setAttribute("checkCodeGen", checkCode); } @Override protected 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(); @Override protected 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.jsp request.getRequestDispatcher("/register.jsp").forward(request, response); return;//结束执行本java文件中的后续代码 } //3 调用Service注册用户 Boolean flag = userService.register(user); //4 判断是否注册成功 if (flag) {//注册成功 request.setAttribute("login_msg", "注册成功,请登录"); //跳回到用户登录页面即login.jsp request.getRequestDispatcher("/login.jsp").forward(request, response); } else {//注册失败 //将错误信息存储到request域中 request.setAttribute("register_msg", "用户名重复"); //跳回到用户登录页面即login.jsp request.getRequestDispatcher("/register.jsp").forward(request, response); } } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }
-
-
Tomcat运行该Web项目,运行截图如下
-
验证码错误
-
验证码正确
-