(jsp相关技术)
1.会话跟踪
1.1什么叫会话跟踪
浏览器和服务器访问的模式:浏览器发请求给服务器,服务器给浏览器返回响应。一次请求+一次响应作为一次完整的请求过程。
浏览器多次访问服务器,就要发送多次请求。每次访问都是独立的。我们需要一些手段知道用户访问过什么,用户想访问什么。这就是会话跟踪技术。
在无状态(一次请求响应就结束)的HTTP协议下,想要跟踪用户的路径(知道是谁,在干什么,要去哪)需要通过会话跟踪技术来辅助实现。
1.2会话跟踪实现方式
常用的会话跟踪技术有四种
1.URL方式:需要保存的信息直接追加到URL后,例如:http://127.0.0.1:8080/chapter03/viewList?pageNo=12
2.隐藏域方式:可以使用表单中的隐藏域保存相关信息, 例如: <input type="hidden" name=“status" value=“true">
(用户看不到,但可将参数偷偷传过去。和URL方式放参数本质上是一样的。URL方式对应get方式,该方式应对Post方式)
3.Session方式:将状态信息保存到服务器的会话对象中,通过唯一标记的ID值与客户端进行绑定使用;例如访问 控制功能就可以使用Session实现
(浏览器向服务器发送请求,服务器根据浏览器创建不同的session对象。之后同一个浏览器多次访问时可共享数据。某些流程像session域对象放入特定键值对;某些流程判断是否有该键值对,若有,则表示走过之前的某个服务。用它做过登录访问控制。故session就是一种会话跟踪技术。)
4.Cookie方式:将状态信息保存到客户端,服务器能够获得相关信息进行分析,从而生成对客户端的响应;例如简化登录功能就可以使用Cookie实现;
(数据存到浏览器里,并且根据不同的服务器存储,数据不会乱窜,比如:百度的存百度的,京东的存京东的。当再次访问服务器时,浏览器里存储的数据会自动发送给服务器。相比于1,2两种方式,不用自己去写,是浏览器自动发送的)
2.Cookie
2.1 Cookie介绍
1.Cookie译为小型文本文件或小甜饼,Web应用程序利用Cookie在客户端缓存服务器端文件。Cookie是以键值对形式存储在客户端主机硬盘中,由服务器端发送给客户端,客户端再下一次访问服务器端时,服务器端可以获取到客户端Cookie缓存文件。
(而且都是字符串格式,也只能传字符串格式。)
2.Cookie是由服务器端创建的,然后由服务器端发送给客户端,客户端以键值对形式存储Cookie,并标注Cookie的来源。客户端再次访问服务器端时,存储的Cookie会保存在请求协议中,服务器端可以获取上次存储的缓存文件内容。
(也可通过纯粹的js控制,在cookie里创建键值对)
2.2 Cookie特点
1.Cookie的用途:
电子商城中购物车功能(每买一样商品,保存一个Cookie)
用户自动登录功能(第一次登录时,将用户名和密码存储在Cookie)
2.Cookie的缺点:
多人共用一台计算机(例如导致用户名和密码不安全等问题)。
Cookie被删除时,利用Cookie统计用户数量出现偏差。
一人使用多台计算机(网站会将看成多个用户等问题)
Cookie会被附加在每次Http请求协议中,增加流量。
(每次发送请求,cookie都会自动发送。不管服务器读不读,数据都会出现在请求报文里。)
Cookie使用明文(未加密)传递的,安全性低。
(Cookie对中文的支持也比较差,传输数据时经常要转码。)
Cookie的大小限制在4KB左右,无法存储复杂需求。
3.Cookie规范
Http协议提供了有关Cookie的规范,现今市场上出现大量浏览器,一些浏览器对该Cookie规范进行了一些“扩展”,但Cookie缓存文件不会占满硬盘空间。
Cookie存储的大小上限为4KB。
一个服务器最多在客户端浏览器中可以保存20个Cookie。
一个浏览器最多可以保存300个Cookie。
浏览器的设置里有安全和隐私设置,专门有清除cookie的功能,也有阻止cookie(可阻止服务器往浏览器存ccokie)。
2.3 Cookie主要属性
name:cookie的名字,每个cookie都有一个名字;(key的部分)
content:cookie的值,与名字一起作为键值对形式存在;(value的部分)
domain:域,该cookie的域名,例如csdn.net,说明当前cookie来自csdn.net;(和域对象是两码事。)
path:路径,访问csdn.net下该路径时,当前cookie将被发送;
created:cookie被创建的时间;
Expired:cookie失效的时间;
最大生命时间:失效时间和创建时间的时间差,就是cookie的最大生命时间,超过该时间,cookie将失效,不再被发送到相应的域地址;
可通过浏览器看到cookie的属性。打开开发者工具,点击Application部分,在该界面找到Storage(储存),其有一项是Cookies,点开。可看到当前网站存的cookie。在这里可直接修改,删除cookie的相关数据。
cookie的发送在开发者工具的network里,在Headrers部分的Request Headers部分,可找到Cookie这一项。它将cookie里存的所有键值对都发到指定服务器。(发送时只有name和content。其他属性是告诉浏览器往哪发,当前访问路径要不要发,当前过期时间要不要发)
2.4Cookie操作(java部分)
1.cookie的创建
新建项目day4_cookie_storage
新建src.com.javasm.controller.CookieWriteDemo类
@WebServlet("/demo1") public class CookieWriteDemo extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //创建Cookie对象,构造传两个值(String name,String value) Cookie cookie = new Cookie("mykey", "myval"); //设置cookie的访问路径(什么情况下可以读到cookie,按照规范直接设置到根 cookie.setPath("/"); //设置有效时间,单位是秒。下面是一天内有效 cookie.setMaxAge(60*60*24); //通过响应对象将cookie添加进浏览器 resp.addCookie(cookie); } }
启动运行,显示
Error running ‘Tomcat 8.5.34‘: Address localhost:1099 is already in use
端口已被占用。
-
(1)win+R,打开命令提示符框,输入cmd,进入命令提示符
-
(2)输入netstat -aon | findstr 1099,找到占用1099端口的进程ID:PID
-
(3)输入taskkill -f -pid PID
-
(4)重启Tomcat
给页面加一些输出:
@WebServlet("/demo1") public class CookieWriteDemo extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { Cookie cookie = new Cookie("mykey", "myval"); cookie.setPath("/"); cookie.setMaxAge(60*60*24); //通过响应对象将cookie添加进浏览器 resp.addCookie(cookie); resp.setContentType("text/html;charset=utf-8"); PrintWriter writer = resp.getWriter(); writer.print("写入了cookie"); writer.flush(); writer.close(); } }
点右上角文本框,进入Run/Debug Configurations,在Deployment中将Application context设置为/day4
在Server将URL设置为http://localhost:8080/day4/demo1
重新部署,启动,页面输入http://localhost:8080/day4/demo1,页面出现写入了cookie。
打开开发者工具,点开Application,选择cookie。
2.修改cookie
保证同key和同path
@Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { Cookie cookie = new Cookie("mykey", "abc123"); cookie.setPath("/"); cookie.setMaxAge(60*60*24); resp.addCookie(cookie); resp.setContentType("text/html;charset=utf-8"); PrintWriter writer = resp.getWriter(); writer.print("写入了cookie"); writer.flush(); writer.close(); }
再次重新部署,运行,访问http://localhost:8080/day4/demo1后,打开开发者工具,会发现cookie的value变成了abc123。(还是同一条数据,并没有增加。)
有效时间的修改同上。但有两个特殊值,一个是负值
cookie.setMaxAge(-1);
此时cookie的MaxAge部分的值变成了session,表示关了浏览器,有效期就失效,这样的有效期就叫session。
效果就是,关闭浏览器,再打开,此条cookie消失。
还有一个特殊值:0
cookie.setMaxAge(0);
它的意思是删除cookie。(告诉浏览器此条cookie需要失效)
另一个需要注意的:
@Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { HttpSession session = req.getSession(); System.out.println(session.getId()); } }
对这个代码,浏览器不关闭,多次运行,控制台都会输出相同的seesion的id。同时在浏览器里也会多一条cookie,该条cookie的value就是session的id。(并没有主动创建cookie)
浏览器和cookie为什么能对应起来:
服务器里会生成session的集合,它是以ID为标记存储的,Map<sessionID,session对象>。浏览器访问到服务器,
服务器会创建session对象,并生成sessionID。并自动生成一条cookie,里面放的就是sessionID,此cookie会加在响应里。使用同一个浏览器访问时,会自动将cookie传过去,里面有sessionID,故可访问到同一个session对象。但此条cookie的有效期是session,即关闭浏览器,此cookie失效,此时再访问服务器,就会创建新的session。
笔试题:cookie和session有什么区别?
session是服务器内存里创建的对象,cookie是浏览器里存储键值对的方式。他俩唯一的关联就是Web服务器会自动创建一条JSESSIONID,并将有效期设置为session,存储的是session的编号。目的是让浏览器访问服务器时,在服务器里可找到指定的session对象,从而保证同一个浏览器多次访问时共享对象。
3.读cookie
新建CookieReadDemo类:
@WebServlet("/demo2") public class CookieReadDemo extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //读取cookie Cookie[] cookies = req.getCookies(); for(Cookie ck:cookies){ System.out.println(ck.getName()); System.out.println(ck.getValue()); } } }
只能读name和value属性,其他都为空。
若修改指定的key:
@WebServlet("/demo2") public class CookieReadDemo extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { Cookie[] cookies = req.getCookies(); for(Cookie ck:cookies){ if("mykey".equals(ck.getName())){ ck.setValue("abc123"); //还要写回去 resp.addCookie(ck); } } } }
结果是,新生成了一个cookie,并没有修改指定key的cookie。新生成的cookie的path是:/day4
如果想修改,而不是新建。就必须重新指定一下path
@Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { Cookie[] cookies = req.getCookies(); for(Cookie ck:cookies){ if("mykey".equals(ck.getName())){ ck.setValue("abc123"); ck.setPath("/"); resp.addCookie(ck); } } }
2.5 Cookie操作(js部分)
新建web.1cookieDemo.jsp
先输出一下cookie
<script> console.log(document.cookie); </script>
重新部署,运行后访问:http://localhost:8080/day4/1cookieDemo.jsp
打开开发者工具,在Console里,会出现:mykey=abc123
如果有多条cookie,会分号隔开,cookie和分号会有一个空格。(即一个分号一个空格分隔开)
也是只能读到key和value。读到的是纯字符串
1.Cookie写
document.cookie="jscook=jsval1;path=/;expires="+new Date("2021-11-11 11:11:11");
(需要写入的有键值对;访问路径;过期时间; 过期时间可通过日期对象传入。)
这里通过按钮写入cookie,并用bootstrap美化:
<html> <head> <title>Title</title> <link rel="stylesheet" type="text/css" href="${pageContext.request.contextPath}/css/bootstrap.css"/> <script src="${pageContext.request.contextPath}/js/jquery-3.6.0.js" type="text/javascript" charset="utf-8"></script> <script src="${pageContext.request.contextPath}/js/bootstrap.js" type="text/javascript" charset="utf-8"></script> </head> <body> <input id="addCKBtn" type="button" class="btn btn-default" value="添加cookie"> </body> <script> $("#addCKBtn").click(function () { document.cookie="jscook=jsval1;path=/;expires="+new Date("2050-11-11 11:11:11"); }) </script> </html>
2.Cookie读
<body> <input id="ckkey" type="text"> <input id="getCKBtn" type="button" class="btn btn-default" value="获取指定cookie的值"> </body> <script> /*读取指定的key*/ $("#getCKBtn").click(function(){ var kvArr = document.cookie.split("; "); /*先分成独立的几条cookie*/ $.each(kvArr,function (i,d) { var keyAndVal = d.split("="); /*每次读到的cookie,用=再分,就得到了键和值*/ if(keyAndVal[0]==$("#ckkey").val()){ /*用键的部分和输入框里输入的值去匹配*/ console.log(keyAndVal[1]); } }) }) </script> </html>
3.删除cookie
删除cookie主要是让cookie失效,故操作有效时间,给一个已经过期的时间(此刻时间之前的时间)。注意:仍然是同key,同path。值无所谓,但也得给一个。
<body> <input id="delCKBtn" type="button" class="btn btn-default" value="删除cookie"> </body> <script> $("#delCKBtn").click(function () { document.cookie="jskey=jsval;path=/;expires="+new Date("2000-11-11 11:11:11"); }) </script>
3.webStorage
3.1介绍
1.HTML5 提供了两种在客户端存储数据的新方法 统称Web storage:
localStorage , 没有时间限制的数据存储
(也是按给定的服务器去存,百度存百度,京东存京东。存的也是键值对)
sessionStorage , 针对一个 session 的数据存储,数据在浏览器关闭后自动删除
(有效期是session)
2.Web storage是一种设计由前端存储数据的技术,跟cookie有相似之处,但是因为设计给前端使用,所以操作上比cookie方便很多
浏览器打开开发者工具,Application部分的Storage里可看到Local Storage和Session Storage。点开这两套,他们只有key和value两个属性。
3.2 Web storage操作
localStorage.username = "admin";//设置值
localStorage.setItem("user", "user_name");//设置值
console.log(localStorage.username)//取值
console.log(localStorage.getItem("user"));//取值
//以上两种方式存值/取值都可以
localStorage.removeItem("user");//移除指定的key
localStorage.clear();//清空所有的数据
注意:sessionStorage用法同localStorage
Web storage是前端的技术,跟服务端的session对象无直接关系
Web storage没有默认随请求发送到服务器的机制
是纯前端的存储技术,通过js操作,不像cookie一样,每次发送数据时会自动发送,只有存储的功能,没有发送功能。
新建web.2storageDemo.jsp
1.添加storage
localStorage和sessionStorage的调用方法完全一样。
以sessionStorage为例,往里存数据,直接点一个不存在的key,直接赋值即可。
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> <link rel="stylesheet" type="text/css" href="${pageContext.request.contextPath}/css/bootstrap.css"/> <script src="${pageContext.request.contextPath}/js/jquery-3.6.0.js" type="text/javascript" charset="utf-8"></script> <script src="${pageContext.request.contextPath}/js/bootstrap.js" type="text/javascript" charset="utf-8"></script> </head> <body> <input id="addSGBtn" type="button" class="btn btn-default" value="添加storage"> </body> <script> $("#addSGBtn").click(function () { sessionStorage.mykey = "abc123"; }) </script> </html>
重新部署,运行,访问:http://localhost:8080/day4/2storageDemo.jsp
找到开发者工具的Session Storage,点击添加storage按钮,键值对出现。
若操作localStorage:
<body> <input id="addSGBtn" type="button" class="btn btn-default" value="添加storage"> </body> <script> $("#addSGBtn").click(function(){ localStorage.mykey = "abc123"; }) </script>
还可以通过方法的方式往里存数据
<body> <input id="addSGBtn" type="button" class="btn btn-default" value="添加storage"> </body> <script> $("#addSGBtn").click(function(){ localStorage.setItem("testyky","testval"); }) </script>
2.查找指定storage的值
<body> <input id="getSGBtn" type="button" class="btn btn-default" value="获取指定storage的值"> </body> <script> $("#getSGBtn").click(function () { console.log(localStorage.testyky); }) </script>
重新部署,运行,访问:http://localhost:8080/day4/2storageDemo.jsp
找到开发者工具的控制台Console,点击获取指定storage的值按钮,testval出现。
另一种方式:
<body> <input id="sgkey" type="text"> <input id="getSGBtn" type="button" class="btn btn-default" value="获取指定storage的值"> </body> <script> $("#getSGBtn").click(function () { localStorage.getItem($("#sgkey").val()); }) </script>
重新部署,运行,访问:http://localhost:8080/day4/2storageDemo.jsp
找到开发者工具的控制台Console,
文本框输入testyky,点击获取指定storage的值按钮,testval出现。
3.删除storage
1.删除指定的storage
<body> <input id="delSGBtn" type="button" class="btn btn-default" value="删除storage"> </body> <script> $("#delSGBtn").click(function(){ localStorage.removeItem("testyky"); }) </script>
2.清除所有的storage
localStorage.clear();//清空所有的数据
4.发送数据
storage通常配合URL方式或者隐藏域方式发送数据。
新建web.3sendStorage.jsp
1.使用url拼接
点一下按钮,让页面跳转,并让参数传到指定服务器,参数从storage里拿
<body> <input id="myBtn" type="button"> </body> <script> $("#myBtn").click(function () { location.href = "/day4/xxxx?mymsg=" + localStorage.getItem("testyky"); }) </script>
重新部署,运行,访问:http://localhost:8080/day4/3sendStorage.jsp
点击按钮,请求发出,变为http://localhost:8080/day4/xxxx?mymsg=testval
(storage不具备自动发送的功能,故通过会话跟踪的前两种方式手动发送)
2.使用隐藏框
<body> <input id="myBtn" type="button"> <form id="myForm" action="/day4/xxxxx" method="post"> <input type="text" name="userphone"><br> <input type="hidden" id="myhidden" name="myhidden"> <input id="mySubmitBtn" type="button" value="自己的提交按钮" > </form> </body> <script> $("#myBtn").click(function () { location.href = "/day4/xxxx?mymsg=" + localStorage.getItem("testyky"); }) $("#mySubmitBtn").click(function(){ $("#myhidden").val(sessionStorage.getItem("testyky")); $("#myForm").submit(); }) </script>
重新部署,运行,访问:http://localhost:8080/day4/3sendStorage.jsp
在文本框随便添点东西,比如123421,点击"自己的提交按钮"按钮
开发者工具看network,点击xxxx,在Payload里可看到userphone: 123421 myhidden:testval
输入栏是http://localhost:8080/day4/xxxx
4.cookie使用案例
①.新建web.img
存入三张图片:phone1.jpg phone2.jpg shoe,jpg
访问:http://localhost:8080/day4/img/phone1.jpg,网页可出现对应图片
若存入数据库,拼img标签时,一般用相对路径或相对根路径。相对路径编写麻烦,习惯用相对根路径。每个项目的名字不同,故数据库一般只存img/phone1.jpg。在拼接地址时,获取/day4,再和数据库存的地址拼起来。
②.数据库新建表tb_product
sku编码,作为商品编码,商品存储时,给商品单独编一个sku码。商品的编号,前几位是大类,中间几位是小分类,最后几位表示分类下商品的编号。
以这种方式作为商品主键,就不用自增了。
这两种id并不互斥,可同时存在于一张表。此时普通id作为物理主键,sku的id叫逻辑主键。
这里用sku编码:(有的是全数字,有的是数字加英文,但都是分大类,小类和商品编号)
③.展示商品列表
在day3_jsp_2的src.com.javasm.controller2新建ProdServlet类:
@WebServlet("/prod") public class ProdServlet extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //查询商品信息,然后跳转到展示商品的页面 } }
在bean层新建商品类:
@NoArgsConstructor @AllArgsConstructor @Getter @Setter @ToString public class Product { private String ProdId; private String ProdName; private Double ProdPrice; private String ProdImg; private String ProdDesc; }
在dao层新建ProdDao接口:
public interface ProdDao { List<Product> getAllProd(); }
在dao.impl层新建ProdDaoImpl类:
public class ProdDaoImpl implements ProdDao { @Override public List<Product> getAllProd() { Connection conn = DBHelper.getConn(); String sql = "select tp.prod_id,tp.prod_name,tp.prod_price,tp.prod_img,tp.prod_desc from tb_product tp "; PreparedStatement preparedStatement = null; ResultSet resultSet = null; List<Product> listProd = new ArrayList<Product>(); try { //防止注入攻击 preparedStatement = conn.prepareStatement(sql); resultSet = preparedStatement.executeQuery(); while(resultSet.next()){ String prodId = resultSet.getString("prod_id"); String prodName = resultSet.getString("prod_name"); Double prodPrice = resultSet.getDouble("prod_price"); String prodImg = resultSet.getString("prod_img"); String prodDesc = resultSet.getString("prod_desc"); Product prod = new Product(prodId,prodName,prodPrice,prodImg,prodDesc); listProd.add(prod); } } catch (SQLException e) { e.printStackTrace(); }finally { DBHelper.CloseConn(conn,null,preparedStatement,resultSet); } return listProd; } }
在MyTest类进行本地测试:
public class MyTest { public static void main(String[] args) { ProdDao pd = new ProdDaoImpl(); List<Product> allProd = pd.getAllProd(); System.out.println(allProd); } }
控制台可正确输出商品信息。
在service包下新建ProdService接口
public interface ProdService { List<Product> getAllProd(); }
在service.impl包下新建ProdServiceImpl类:
public class ProdServiceImpl implements ProdService { @Override public List<Product> getAllProd() { ProdDao pd = new ProdDaoImpl(); return pd.getAllProd(); } }
回到ProdServlet类:
@Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //查询商品信息 ProdService ps = new ProdServiceImpl(); List<Product> allProd = ps.getAllProd(); //跳转到展示商品的页面 }
新建web.pages.showProds.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <html> <head> <title>Title</title> <link rel="stylesheet" type="text/css" href="${pageContext.request.contextPath}/css/bootstrap.css"/> <script src="${pageContext.request.contextPath}/js/jquery-3.6.0.js" type="text/javascript" charset="utf-8"></script> <script src="${pageContext.request.contextPath}/js/bootstrap.js" type="text/javascript" charset="utf-8"></script> <style> .mydiv{ border:1px solid gray; padding:50px; width:70%; height:500px; margin:50px auto; } </style> </head> <body> <div class="mydiv"> <table class="table"> <tr> <th>商品编号</th> <th>商品信息</th> <th>单价</th> <th>描述信息</th> </tr> <tbody> <c:forEach items="${listProds}" var="myprod" > <tr> <td>${myprod.prodId}</td> <td>${myprod.prodName}<br/> <img src="${pageContext.request.contextPath}/${myprod.prodImg}" /></td> <td>${myprod.prodPrice}</td> <td>${myprod.prodDesc}</td> </tr> </c:forEach> </tbody> </table> </div> </body> </html>
回到ProdServlet类:
@Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //查询商品信息 ProdService ps = new ProdServiceImpl(); List<Product> allProd = ps.getAllProd(); //跳转到展示商品的页面 req.setAttribute("listProds",allProd); req.getRequestDispatcher("/pages/showProds.jsp").forward(req,resp); }
点击右上角文本框,进入Run/Debug Configurations界面,点开Deployment,右边的减号删掉项目4,加号加进现在用的项目3,同时可在Application context修改根目录名为:/day3_2
重新部署,运行,访问:http://localhost:8080/day3_2/prod
页面出现商品表页面。
图片太多大了,修改图片的大小。
在style里加一句:
.mydiv img{ width:80px ; height:80px; }
④.页面准备好,开始添加商品。
浏览完商品,开始真正下单时,才会往购物车里添加商品,也就是往数据库添加。临时过程中,想把数据储存,并且用户可以反复修改,就存在前端,考虑将商品相关信息存在cookie里。
cookie里主要存键值对,Name可以存商品编号,Value可以存商品数量。那么多Name如何区分是商品信息呢?可以加前缀,比如:prod_
在showProds.jsp中,先给商品显示页面的每一行加一个按钮:
<body> <div class="mydiv"> <table class="table"> <tr> <th>商品编号</th> <th>商品信息</th> <th>单价</th> <th>描述信息</th> <th>操作</th> </tr> <tbody> <c:forEach items="${listProds}" var="myprod" > <tr> <td>${myprod.prodId}</td> <td>${myprod.prodName}<br/> <img src="${pageContext.request.contextPath}/${myprod.prodImg}" /></td> <td>${myprod.prodPrice}</td> <td>${myprod.prodDesc}</td> <td><input type="button" class="btn btn-warning" value="添加到购物车" /></td> </tr> </c:forEach> </tbody> </table> </div> </body>
点击添加购物车后,用前缀加商品编号,放进cookie的Name部分,并且显示数量一(value部分为1)。
点击按钮要找到对应行的商品编号。可以通过层级结构去找。或者加一个自定义属性,在绑定属性时传值。
(如果有错误,在开发者工具的Source可以打断点)
<body> <div class="mydiv"> <table class="table"> <tr> <th>商品编号</th> <th>商品信息</th> <th>单价</th> <th>描述信息</th> <th>操作</th> </tr> <tbody> <c:forEach items="${listProds}" var="myprod" > <tr> <td>${myprod.prodId}</td> <td>${myprod.prodName}<br/> <img src="${pageContext.request.contextPath}/${myprod.prodImg}" /></td> <td>${myprod.prodPrice}</td> <td>${myprod.prodDesc}</td> <td><input type="button" prodid="${myprod.prodId}" class="btn btn-warning addCartBtn" value="添加到购物车" /></td> </tr> </c:forEach> </tbody> </table> </div> </body> <script> $(".addCartBtn").click(function () { //$(this).attr("prodid") 找到商品编号 var prodid = "prod_"+$(this).attr("prodid") //往cookie里写数据 document.cookie = prodid+"=1;path=/;expires="+new Date("2048-11-11 11:11:11") }) </script>
重新部署,运行,访问:http://localhost:8080/day3_2/prod
打开开发者工具,找到cookie,随便找一个商品点击添加到购物车,然后在cookie刷新一下,会出现符合要求的新的键值对。
现在每次点击,数量只能是一,没有增加。点一次加一个这个功能,有两种情况。若cookie里有这个key,数量加一(将当前值取出,再加一)。若没有key,直接给一。
先看是否有指定的key,用分号加空格分割,分成几条cookie。再用等号分割,分成key和value,将当前使用的key和分割出来的key比较,就能知道当前要使用的key在cookie里是否存在。
<script> $(".addCartBtn").click(function () { //$(this).attr("prodid") 找到商品编号 var prodid = "prod_"+$(this).attr("prodid"); var prodnum = 1; var kvArr = document.cookie.split("; "); $.each(kvArr,function (i,d) { var keyAndVal = d.split("="); if(keyAndVal[0]== prodid){ //key已经存在 //console.log(keyAndVal[1]); prodnum = parseInt(keyAndVal[1])+1; } }) document.cookie = prodid+"="+prodnum+";path=/;expires="+new Date("2048-11-11 11:11:11"); }) </script>
⑤.购物车页面
上面做的是商品页面,接下来做购物车页面。
在showProds.jsp里加一个去购物车结算按钮:加在div的代码下面:
<body> <div class="mydiv"> <a class="btn btn-danger" href="${pageContext.request.contextPath}/cart">去购物车结算</a> <table class="table"> <tr> <th>商品编号</th> <th>商品信息</th> <th>单价</th> <th>描述信息</th> <th>操作</th> </tr> <tbody> <c:forEach items="${listProds}" var="myprod" > <tr> <td>${myprod.prodId}</td> <td>${myprod.prodName}<br/> <img src="${pageContext.request.contextPath}/${myprod.prodImg}" /></td> <td>${myprod.prodPrice}</td> <td>${myprod.prodDesc}</td> <td><input type="button" prodid="${myprod.prodId}" class="btn btn-warning addCartBtn" value="添加到购物车" /></td> </tr> </c:forEach> </tbody> </table> </div> </body>
点击购物车结算以后会进入购物车的servlet,根据cookie里放的数据,把id还原成商品的商品编号,然后将商品信息展示到购物车页面:
新建src.com.javasm.controller2.CartServlet类:
先读取cookie,会有很多cookie,只将和商品有关的cookie拿出(key是以prod_开头)。拿到商品的cookie,将商品的编号摘出来。
最终在页面展示的时候要放商品列表,故要拿商品编号找商品对象。
@WebServlet("/cart") public class CartServlet extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { Cookie[] cookies = req.getCookies(); for(Cookie ck:cookies){ if (ck.getName().startsWith("prod_")){ //prod_SJ00150001,真正的编号在后半部分 String prodid = ck.getName().split("_")[1]; } } List<Product> lp = new ArrayList<Product>(); req.setAttribute("listProd",lp); } }
接下来做拿商品编号找商品对象的流程:
在ProdDao里:
public interface ProdDao { List<Product> getAllProd(); //通过商品编号找商品对象 Product getProdById(String prodid); }
在ProdDaoImpl里重写该方法:
@Override public Product getProdById(String prodid) { Connection conn = DBHelper.getConn(); String sql = "select tp.prod_id,tp.prod_desc,tp.prod_name,tp.prod_price,tp.prod_img from tb_product tp where tp.prod_id = ?"; PreparedStatement preparedStatement = null; ResultSet resultSet = null; Product returnProd = null; try { //防止注入攻击 preparedStatement = conn.prepareStatement(sql); preparedStatement.setString(1,prodid); resultSet = preparedStatement.executeQuery(); while (resultSet.next()) { String prodId = resultSet.getString("prod_id"); String prodName = resultSet.getString("prod_name"); Double prodPrice = resultSet.getDouble("prod_price"); String prodImg = resultSet.getString("prod_img"); String prodDesc = resultSet.getString("prod_desc"); returnProd = new Product(prodId, prodName, prodPrice, prodImg, prodDesc); } } catch (SQLException e) { e.printStackTrace(); } finally { DBHelper.CloseConn(conn, null, preparedStatement, resultSet); } return returnProd; }
在MyTest里测试一条cookie
public class MyTest { public static void main(String[] args) { ProdDao pd = new ProdDaoImpl(); Product prodById = pd.getProdById("SJ00110013"); System.out.println(prodById); } }
测试结果:Product(ProdId=SJ00110013, ProdName=便宜手机, ProdPrice=95.3, ProdImg=img/phone2.jpg, ProdDesc=便宜手机)
在ProdService接口:
public interface ProdService { List<Product> getAllProd(); Product getProdById(String prodid); }
在ProdServiceImpl类重写该方法:
@Override public Product getProdById(String prodid) { ProdDao pd = new ProdDaoImpl(); return pd.getProdById(prodid); }
回到CartServlet类:
@WebServlet("/cart") public class CartServlet extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { Cookie[] cookies = req.getCookies(); ProdService ps = new ProdServiceImpl(); List<Product> lp = new ArrayList<Product>(); for(Cookie ck:cookies){ if (ck.getName().startsWith("prod_")){ //prod_SJ00150001,真正的编号在后半部分 String prodid = ck.getName().split("_")[1]; Product prodById = ps.getProdById(prodid); lp.add(prodById); } } req.setAttribute("listProd",lp); } }
问题是数量没用上。而且商品实体类里也没有数量。
商品实体是和数据库一一对应的,商品实体加数量,最多表示库存,并不会表示购物车的商品数量,故数据库里数量的字段不会加。
比较老的规范里:查出来的数据对应数据库的数据模型,要显示的数据模型和数据库的数据模型在字段上可能有一定区别。此时和数据库对应的实体类会在类名后加DTO,和显示相关的实体类会在类后面加VO。但实际处理起来很麻烦。(国企一般这么做)
现在的问题是少一个字段,这个字段不往数据库建。在实体里多建一个字段,和数据库交互时不让该字段交互,只有和数据库对应的字段才能交互。但加上这种字段必须加上注释。
在Product类:
@NoArgsConstructor @AllArgsConstructor @Getter @Setter @ToString public class Product { private String ProdId; private String ProdName; private Double ProdPrice; private String ProdImg; private String ProdDesc; private Integer prodNum; //显示使用 购物车商品数量 public Product(String prodId, String prodName, Double prodPrice, String prodImg, String prodDesc) { ProdId = prodId; ProdName = prodName; ProdPrice = prodPrice; ProdImg = prodImg; ProdDesc = prodDesc; } }
回到CartServlet类:
@Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { Cookie[] cookies = req.getCookies(); ProdService ps = new ProdServiceImpl(); List<Product> lp = new ArrayList<Product>(); for(Cookie ck:cookies){ if (ck.getName().startsWith("prod_")){ //prod_SJ00150001,真正的编号在后半部分 String prodid = ck.getName().split("_")[1]; Product prodById = ps.getProdById(prodid); //在要显示的列表里添加商品数量字段 prodById.setProdNum(Integer.parseInt(ck.getValue())); //getValue()只能拿到字符串 lp.add(prodById); } } req.setAttribute("listProd",lp); //传页面数据 req.getRequestDispatcher("/pages/showCarts.jsp").forward(req,resp); }
接下来展示购物车页面:
新建web.pages.showCarts.jsp:
数量里要放数字加减框
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <html> <head> <title>Title</title> <link rel="stylesheet" type="text/css" href="${pageContext.request.contextPath}/css/bootstrap.css"/> <script src="${pageContext.request.contextPath}/js/jquery-3.6.0.js" type="text/javascript" charset="utf-8"></script> <script src="${pageContext.request.contextPath}/js/bootstrap.js" type="text/javascript" charset="utf-8"></script> <style> .mydiv{ border: 1px solid gray; padding: 50px; width: 80%; height: 500px; margin: 50px auto; border-radius: 10px; } .mydiv img{ width:80px ; height:80px ; } </style> </head> <body> <div class="mydiv"> <h3>购物车页面</h3> <table class="table"> <tr> <th>商品编号</th> <th>商品信息</th> <th>单价</th> <th>数量</th> <th>小计</th> <th>操作</th> </tr> <tbody> <c:forEach items="${listProd}" var="myprod" > <tr> <td>${myprod.prodId}</td> <td>${myprod.prodName}<br/> <img src="${pageContext.request.contextPath}/${myprod.prodImg}" /> </td> <td>${myprod.prodPrice}</td> <td> <input class="diffBtn" type="button" value="-" /> <input type="text" value="${myprod.prodNum}" /> <input class="addBtn" type="button" value="+" /><br /> </td> <td> ${myprod.prodPrice*myprod.prodNum} </td> <td><input type="button" prodid="${myprod.prodId}" class="btn btn-warning" value="删除"/> </td> </tr> </c:forEach> </tbody> </table> </div> </body> <script> $(".addCartBtn").click(function(){ var prodid = "prod_"+$(this).attr("prodid") console.log(document.cookie); var prodnum = 1; var kvArr = document.cookie.split("; "); $.each(kvArr,function (i,d) { var keyAndVal = d.split("="); if(keyAndVal[0]==prodid){ prodnum = parseInt(keyAndVal[1])+1; } }) document.cookie = prodid+"="+prodnum+";path=/;expires="+new Date("2048-11-11 11:11:11"); }) </script> </html>
重新部署,运行,访问:http://localhost:8080/day3_2/prod
点击去购物车结算按钮,会跳转到:http://localhost:8080/day3_2/prod
页面如下:
因为前面只操作了这一个商品,故只有这一项。
⑥.作业:
用js完善加减框
同时动加减框的时候,小计要跟着动(获取前面的单价,根据数量计算)。
注:加减时cookie也要跟着动。
删除时整行删除,对应的cookie也要删掉。
5.总结
现在使用的结构是三层结构:控制层(controller),业务逻辑层(service),dao层。并且多加了一个页面。、
整体的关系是使用了后台代码结构里的mvc思想。
mvc思想:model(数据层),view(视图层),control(控制层)。严格的和代码对应的话,model对应两层:dao和service,都用来处理数据模型。view就是这些页面,control就是这些servlet。
请求处理过程就是先进入控制层,控制层调用数据如何处理,根据处理的结果,选择对应的视图来显示。
mv思想:控制层接收请求,通过service加dao处理数据模型。
之后会将数据模型传给前端,让前端处理。
注:mvc和三层结构是两码事,三层结构是编码结构,没有包含页面。