3-1 会话跟踪
会话的概念
客户端与服务器端的一次连接过程
从用户进入一个网站浏览到退出这个网站或者关闭浏览器称为一次会话
会话跟踪
会话跟踪是指在这个过程中浏览器与服务器的多次请求保持数据共享的状态的技术
![image-20220606162257369](https://i-blog.csdnimg.cn/blog_migrate/560413c6413e95d99649a181f9b67d07.png)
会话跟踪-两种方式
(1) 客户端cookie
最普遍用途是实现记住登录的用户
什么是Cookie
简单来说,Cookie就是服务器暂存放在你的电脑里的资料(.txt格式的文本文件),好让服务器用来辨认你的计算机。
目前Cookie最广泛的是记录用户登录信息,这样下次访问时可以不需要输入自己的用户名、密码了——当然这种方便也存在用户信息泄密的问题,尤其在多个用户共用一台电脑时很容易出现这样的问题
cookie应用-实现记住我
step1: 在登录表单中添加记住我复选框
<div class="form-group form-inline">
<label for="account">记住我</label>
<input type="checkbox" name="rememberMe" checked/>
</div>
step2: 在AdminServlet中处理登录login方法中,判断是否需要记住我,并设置cookie时效,发送cookie
/**登录*/
private void login(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
try {
String account = request.getParameter("account");
String password = request.getParameter("password");
//调用业务方法,获取当前登录的管理员对象
Admin loginAdmin = adminService.doLogin(account,password);
//判断是否需要记住我
String rememberMe = request.getParameter("rememberMe");
//创建cookie
Cookie ck1 = new Cookie("account",account);
Cookie ck2 = new Cookie("password",password);
if(rememberMe!= null && "on".equals(rememberMe)){ //记住我
//设置cookie时效性
// 两个特殊存活时间值: 0 表示立即删除cookie
// -1 表示当浏览器关闭时删除cookie
ck1.setMaxAge(60*60*24); //单位为秒
ck2.setMaxAge(60*60*24); //1天
}else{
ck1.setMaxAge(0); //单位为秒
ck2.setMaxAge(0);
}
//向客户端发送cookie
response.addCookie(ck1);
response.addCookie(ck2);
//登录成功,将当前登录的管理员用户存入会话作用域
request.getSession().setAttribute("loginAdmin",loginAdmin);
//进入管理首页
request.getRequestDispatcher("/manage/home.jsp").forward(request,response);
} catch (Exception e) {
if(e instanceof MyServiceException){
request.setAttribute("msg",e.getMessage());
}else {
e.printStackTrace();
}
request.getRequestDispatcher("/manage/login.jsp").forward(request, response);
}
}
step3: 在login.jsp页面中读取cookie
<%
//从请求对象中获取Cookie信息
String account = "";
String password="";
Cookie[] cks = request.getCookies();
if(cks != null){
for(Cookie ck : cks){
if(ck.getName().equals("account")){
account = ck.getValue();
}else if(ck.getName().equals("password")){
password = ck.getValue();
}
}
}
%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<base href="${pageContext.request.contextPath}/">
<title>蜗牛图书商城后台管理-登录</title>
<link href="bootstrap/css/bootstrap.css" rel="stylesheet">
<script type="text/javascript" src="js/jquery-3.5.1.js"></script>
<script type="text/javascript" src="bootstrap/js/bootstrap.js"></script>
</head>
<body>
<div style="width:20%;margin:0 auto; text-align: center; padding-top:5% ">
<form action="manage/admin" id="myform" method="post">
<input type="hidden" name="opr" value="login" />
<div class="form-group form-inline">
<label for="account">用户名:</label>
<input type="text" class="form-control" id="account" name="account"
value="<%=account%>"
placeholder="请输入管理员">
</div>
<div class="form-group form-inline">
<label for="account">密 码:</label>
<input type="password" class="form-control" id="password" name="password"
value="<%=password%>"
placeholder="请输入密码"/>
</div>
<div class="form-group form-inline">
<label for="account">记住我</label>
<input type="checkbox" name="rememberMe" checked/>
</div>
<div class="form-group">
<button class="btn btn-success btn-md" id="btnLogin" type="submit">登录</button>
<button class="btn btn-danger btn-md" type="reset">取消</button>
</div>
</form>
<p id="msg" style="color:red;text-align: center">${msg}</p>
</div>
另外: 在jsp页面中获取cookie可以直接使用EL
<input type="text" class="form-control" id="account" name="account"
value="${cookie.account.value}" />
...
(2) 服务端HttpSession对象
Cookie与session
1、数据存储位置:cookie数据存放在客户的浏览器上,session数据放在服务器上。
2、安全性:cookie不是很安全,别人可以分析存放在本地的cookie并进行cookie欺骗,考虑到安全应当使用session。
3、服务器性能:session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能,考虑到减轻服务器性能方面,应当使用cookie。
4、数据大小:单个cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个cookie。
5、信息重要程度:可以考虑将登陆信息等重要信息存放为session,其他信息如果需要保留,可以放在cookie中。
会话作用域:
|-登录用户信息
会话的创建
由于会话是基于客户端的请求而产生的,所有会话对象应当从request对象中去获取.
request.getSession() -表示从当前请求对象中获得相关联的会话对象,如果当前请求是第一次,则服务端会创建一个新session返回.
request.getSession(boolean create) - true 与不给参数效果相同
false 表示从当前请求对象中获得相关联的会话对象,如果获取不到,也不创建,而返回null
会话的结束
(1) 关闭浏览器
(2) 立即使用会话失效 session.invalidate() --会话作用域数据清空 --退出系统,注销帐号
(3) 会话非活动时间间隔 默认30分钟
实现注销/退出
<li role="presentation">
<a href="manage/admin?opr=logout" target="parent"><span class="glyphicon glyphicon-log-out"></span> 退出系统</a>
</li>
/**退出*/
private void logout(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.getSession().invalidate(); //使当前会话失效
//重定向
response.sendRedirect(request.getContextPath()+"/manage/login.jsp");
}
3-2 过滤器 (重点)
web应用三大组件:
Servlet 控制器,
Filter 过滤器,
Listener监听器 -[了解]
Filter过滤器简介
过滤器是一个驻留在服务器端的web组件,可以截取客户端和资源之间的请求与响应信息
![image-20220606191707989](https://i-blog.csdnimg.cn/blog_migrate/94274cb3ca151b49408b2ea82212f249.png)
过滤器应用-1
设置请求的字符集编码
实现步骤:
step1:定义类实现javax.servlet.Filter接口
step2:实现接口中的doFilter()方法
/**
* 编码字符集过滤器
*/
@WebFilter("/*")
public class EncodingFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
//执行过滤处理
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
throws IOException, ServletException {
request.setCharacterEncoding("UTF-8");
//放行
filterChain.doFilter(request, response);
}
@Override
public void destroy() {
}
}
step3:配置Filter
<filter>
<filter-name>EncodingFilter</filter-name>
<filter-class>com.woniu.mall.filter.EncodingFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>EncodingFilter</filter-name>
<url-pattern>/test</url-pattern>
</filter-mapping>
或使用注解
@WebFilter("/test")
public class MyFilter implements Filter {
}
@WebFilter("/test")
public class EncodingFilter implements Filter {
}
注:如果有多个过滤器都是同一个URL相关联,形成了一个过滤器链,执行的顺序取决配置方式,如果是web.xml配置,,则是先后顺序,如果注解,则是过滤器类的字母顺序
过滤 器应用-2
权限控制—必须登录的管理员才能访问后台
实现思路:
1.约定后台管理的请求操作统一 /manage/
2.请求/manage/ URL路径时,必须先进行管理员登录
3.通过过滤器实现判断,请求的会话作用域是否有loginAdmin对象
如果未登录,则返回登录页
已登录,则请求到达目标资源--> 放行
@WebFilter("/manage/*") //针对/manage/所有的请求
public class PrivilegeFilter implements Filter {
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest)req; //将req转换为HttpServletRequest类型
///manage/login.jsp /manage/admin?opr=gotoLogin 进入登录页面
//manage/admin?opr=login 执行登录请求
// System.out.println("URI:"+request.getRequestURI());
//System.out.println("URL:"+request.getRequestURL());
//System.out.println("opr:"+request.getParameter("opr"));
//例外情况
String uri = request.getRequestURI();
String opr = request.getParameter("opr");
if(uri.endsWith("login.jsp") || (uri.endsWith("/admin") && "login".equals(opr))){
filterChain.doFilter(req,resp);
return;
}
//针对请求的uri进行跳转
if(uri.endsWith("/manage")||uri.endsWith("/manage/gotoLogin")){
request.getRequestDispatcher("/manage/login.jsp").forward(req,resp);
return;
}
//获取会话作用域中的用户
HttpSession ses = request.getSession();
//判断会话作用域中是否存在登录的管理员
Admin loginAdmin = (Admin)ses.getAttribute("loginAdmin");
if(loginAdmin==null){
req.setAttribute("msg","对不起,未登录无权进行操作!");
request.getRequestDispatcher("/manage/login.jsp").forward(req,resp);
}else{
//放行
filterChain.doFilter(req,resp);
}
}
}
2.监听器-了解
监听器是Servlet规范中定义的一种特殊类
web服务端三大组件之一 --监听器 Listener
用于监听web服务端某个事件发生时的事件
(1) 用于监听ServletContext、HttpSession和ServletRequest等作用域对象的创建与销毁事件
(2) 用于监听作用域对象的属性发生修改的事件 setAttribute()
第一个监听器
/**
* 自定义监听器--用于监听全局作用域对象的创建与销毁
*/
@WebListener
public class MyServletContextListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("ServletContext全局对象创建....."+sce.getServletContext());
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("ServletContext全局对象销毁....."+sce.getServletContext());
}
}
监听器应用--统计当前系统中在线人数
package com.woniu.mall.listener;
import javax.servlet.ServletContext;
import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
/**
* 在线人数统计监听器
*/
@WebListener
public class OnlineNumsListener implements HttpSessionListener {
public void sessionCreated(HttpSessionEvent se) {
//获取全局作用域对象
ServletContext ctx = se.getSession().getServletContext();
Integer count = (Integer)ctx.getAttribute("count");
if(count==null){
//第一个会话
count = 1;
}else{
count++;
}
//将count存放全局作用域
ctx.setAttribute("count",count);
}
@Override
public void sessionDestroyed(HttpSessionEvent se) {
//获取全局作用域对象
ServletContext ctx = se.getSession().getServletContext();
Integer count = (Integer)ctx.getAttribute("count");
count--;
if(count<0){
count=0;
}
//将count存放全局作用域
ctx.setAttribute("count",count);
}
}
3.ajax - [重点]
DOM操作 -- DOM [文档对象模型Document Object Model DOM树]
|-添加元素
|-修改
|-删除
3-1 ajax简介
AJAX = Asynchronous JavaScript and XML(异步的 JavaScript 和 XML)。 --JSON取代xml
|-不是一种编程语言, 而是一种技术: 无刷新页面技术/局部更新页面技术
synchronous同步
Asynchronous 异步
同步与异步区别:
同步请求: 客户端请求服务端必须要等待响应后,才能再发送下一次请求
异步请求:客户端请求服务端不用等待响应后,就可以再次发送请求
没有ajax技术,就没有现今欣欣向荣的web开发网站
2005 年由 Google 推广开来的编程模式
使用了Ajax技术的网页通过在后台跟服务器进行少量的数据交换,网页就可以实现异步局部更新
3-2 原生JS实现ajax操作-[了解]
原生JS实现ajax-->核心引擎对象 XMLHttpRequest - XHR
XMLHttpRequest 是 AJAX 的基础。
所有现代浏览器均支持 XMLHttpRequest 对象
|-所有现代浏览器(IE7+、Firefox、Chrome、Safari 以及 Opera)均内建 XMLHttpRequest 对象
XMLHttpRequest 用于在后台与服务器交换数据。这意味着可以在不重新加载整个网页的情况下,对网页的某部分进行更新。
示例参考:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
<script>
//创建ajax的引擎对象
var xhr = new XMLHttpRequest();
function getResult(){
//建立连接 open("请求方式","请求URL",TRUE|FALSE是否是异步)
let code = document.getElementById("code").value;
xhr.open("GET","load?code="+code,true);
//设置回调callback --将事件注册到loadData函数上
xhr.onreadystatechange=loadData; //每当 readyState 属性改变时,就会调用该函数
//发送异步请求
xhr.send(null);
}
function loadData(){
//如果服务器处理完成,并没有发生错误
if(xhr.readyState==4){
if(xhr.status==200){
//alert("服务器处理完成!");
// alert(xhr.responseText);
let result = xhr.responseText; //获得字符串形式的响应数据
let arr = result.split(",");
document.getElementById("province").value=arr[0];
document.getElementById("city").value=arr[1];
}else{
alert("error!!!")
}
}
}
</script>
</head>
<body>
邮编:<input type="text" name="code" id="code" onblur="getResult()" /><br/>
省份:<input type="text" name="province" id="province" /><br/>
城市:<input type="text" name="city" id="city" />
</body>
</html>
LoadServlet.java
/**
* LoadServlet
*/
@WebServlet("/load")
public class LoadServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
System.out.println("请求到达LoadServlet....");
//获取请求参数code
String code = request.getParameter("code");
//访问数据库
String province="";
String city="";
if(code.equals("430000")){
province="湖北省";
city="武汉市";
}else if(code.equals("420000")){
province="湖南省";
city="长沙市";
}else{
province="其他";
city="其他";
}
//如果是ajax异步请求,服务端进行两种页面的跳转都无效!!!
//request.getRequestDispatcher("/ajax01.jsp").forward(request,response);
// response.sendRedirect("ajax01.jsp");
//输出ajax响应
response.setContentType("text/html;charset=utf-8");
PrintWriter out = response.getWriter();
out.println(province+","+city); //\r\n
// out.write(province+","+city); //不带换行\r\n
out.flush();
out.close();
}
}
3-3 axios库实现ajax操作 [重点]
axios是基于 Promise 的 HTTP 请求客户端,可同时在浏览器 和 Node.js 中使用
axios 功能特性:
● 从浏览器中创建 XMLHttpRequests
● 从 node.js 创建 http 请求
● 支持 Promise API
● 拦截请求和响应
● 转换请求数据和响应数据
● 自动转换 JSON 数据
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
<%--引入 axios.js库--%>
<script src="js/axios.min.js"></script>
<script>
//DOM2级 事件注册
window.addEventListener("load",function(){
//邮编 失去焦点事件
document.getElementById("code").addEventListener("blur",function(){
let code = document.getElementById("code").value;
//发送axios的异步请求 post与get不限
axios.post("load?code="+code).then(function(res){
//axios.get("load?code="+code).then(function(res){
//console.log(res.data);
let result = res.data; //获得字符串形式的响应数据
let arr = result.split(",");
document.getElementById("province").value=arr[0];
document.getElementById("city").value=arr[1];
}).catch(function(e){
console.log(e);
console.log("error");
});
});
});
</script>
</head>
<body>
邮编:<input type="text" name="code" id="code"/><br/>
省份:<input type="text" name="province" id="province" /><br/>
城市:<input type="text" name="city" id="city" />
</body>
</html>
关于axios请求的细节:
a. get 请求方式另一种传参形式:
//发送axios的异步请求
axios.get("load",{
params:{
code:code
}
}).then(function(res){
//console.log(res.data);
let result = res.data; //获得字符串形式的响应数据
let arr = result.split(",");
document.getElementById("province").value=arr[0];
document.getElementById("city").value=arr[1];
}).catch(function(e){
console.log(e);
console.log("error");
});
b.post方式也支持两种传参
//发送axios的异步请求
axios.post("load?code="+code).then(function(res){
//console.log(res.data);
let result = res.data; //获得字符串形式的响应数据
let arr = result.split(",");
document.getElementById("province").value=arr[0];
document.getElementById("city").value=arr[1];
}).catch(function(e){
console.log(e);
console.log("error");
});
----------------------------------------------------------------
//此时请求参数为js对象
axios.post("load",{code:code}).then(function(res){
//console.log(res.data);
let result = res.data; //获得字符串形式的响应数据
let arr = result.split(",");
document.getElementById("province").value=arr[0];
document.getElementById("city").value=arr[1];
}).catch(function(e){
console.log(e);
console.log("error");
});
3-4 经典案例
三级联动下拉列表
AreaCode实体类:
public class AreaCode {
private String code;
private String content;
private String codeLevel;
private String superior;
...
}
AreaCodeDao与XML
public interface AreaCodeDao {
List<AreaCode> queryAreaCodeListBySuperior(@Param("superior") String superior);
}
<mapper namespace="com.woniu.mall.dao.AreaCodeDao">
<select id="queryAreaCodeListBySuperior" resultType="AreaCode">
<if test="superior==null">
SELECT * FROM area_code WHERE superior=''
</if>
<if test="superior!=null and superior!=''">
SELECT * FROM area_code WHERE superior=#{superior}
</if>
</select>
</mapper>
AreaCodeServlet
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
//获取请求参数code
String superior = request.getParameter("superior");
//访问数据库
AreaCodeDao areaCodeDao = MyBatisUtil.getDao(AreaCodeDao.class);
List<AreaCode> areaCodeList = areaCodeDao.queryAreaCodeListBySuperior(superior);;
System.out.println(areaCodeList);
//JSON -- Js object Notation
//[{"code":"410000","content","湖北省"}, {"name":"value","name2",value}]
StringBuilder sb = new StringBuilder("[");
for (AreaCode areaCode : areaCodeList) {
sb.append("{\"code\":\""+areaCode.getCode()+"\",\"content\":"+"\""+ areaCode.getContent()+"\"},");
}
if(areaCodeList.size()>0) {
//去掉后面的,
sb.deleteCharAt(sb.length()-1);
}
//添加]
sb.append("]");
System.out.println(sb.toString());
String jsonStr = sb.toString();
//输出ajax响应
response.setContentType("application/json;charset=utf-8");
PrintWriter out = response.getWriter();
out.write(jsonStr); //不带换行\r\n
out.flush();
out.close();
}
area_code.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>三级联动下拉列表</title>
<!--引入 axios.js库-->
<script src="js/axios.min.js"></script>
<script>
//注册页面加载事件
window.addEventListener("load",function(){
//发送ajax请求,查询省份数据
axios.get("areaCode").then(function(res){
console.log(res.data);
//方式一:
//let prov = document.getElementById("province");
// let opt = document.createElement("option");
// opt.value="";
// opt.innerText = "-请选择-";
// prov.appendChild(opt)
//方式二:
let ops = document.getElementById("province").options;
ops.add(new Option("-请选择-",""));
for(let area of res.data){
// console.log(area.code,area.content);
// let opt = document.createElement("option");
// opt.value=area.code;
// opt.innerText = area.content;
// prov.appendChild(opt);
ops.add(new Option(area.content,area.code));
}
});
});
</script>
</head>
<body>
省:<select id="province"></select>
市:<select id="city"></select>
区:<select id="area"></select>
</body>
</html>