第二阶段(day13)Cookie&Webstorage

(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和三层结构是两码事,三层结构是编码结构,没有包含页面。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值