day11 Ajax&Axios&书城项目第六阶段

第一章 Ajax异步请求

1.1 服务器端渲染

img001.png

1.2 Ajax渲染(局部更新)

img002.png

1.3 前后端分离

真正的前后端分离是前端项目和后端项目分服务器部署,在我们这里我们先理解为彻底舍弃服务器端渲染,数据全部通过Ajax方式以JSON格式来传递

1.4 同步与异步

Ajax本身就是Asynchronous JavaScript And XML的缩写,直译为:异步的JavaScript和XML。在实际应用中Ajax指的是:不刷新浏览器窗口,不做页面跳转,局部更新页面内容的技术。

『同步』和『异步』是一对相对的概念,那么什么是同步,什么是异步呢?
定义:同步和异步关注的是消息通信机制 (synchronous communication/ asynchronous communication)。同步,就是调用某个东西是,调用方得等待这个调用返回结果才能继续往后执行。异步,和同步相反 调用方不会理解得到结果,而是在调用发出后调用者可用继续执行后续操作,被调用者通过状体来通知调用者,或者通过回掉函数来处理这个调用
比方说:你去商城买东西,你看上了一款手机,能和店家说你一个这款手机,他就去仓库拿货,你得在店里等着,不能离开,这叫做同步。现在你买手机赶时髦直接去京东下单,下单完成后你就可用做其他时间(追剧、打王者、lol)等货到了去签收就ok了.这就叫异步。

1.4.1 同步

多个操作按顺序执行,前面的操作没有完成,后面的操作就必须等待。
img003.png

1.4.2 异步

多个操作相继开始并发执行,即使开始的先后顺序不同,但是由于它们各自是在自己独立的进程或线程中完成,所以互不干扰,谁也不用等谁。
img004.png

1.4.3 原生态的Ajax异步请求

XMLHttpRequest 是 AJAX 的基础,现代的浏览器中都内置了该对象。

  1. <script type="text/javascript">
  2. function func(){
  3. //创建ajax引擎对象
  4. var xmlhttp = new XMLHttpRequest();
  5. //ajax引擎对象绑定事件,监听服务器响应数据
  6. xmlhttp.onreadystatechange=function(){
  7. if(xmlhttp.readyState==4 && xmlhttp.status==200){
  8. alert(xmlhttp.responseText);
  9. }
  10. }
  11. xmlhttp.open("GET", "/ajaxServlet", true);
  12. xmlhttp.send();
  13. }
  14. </script>

第二章 Json序列化

2.1 Json序列化和反序列化概述

实际项目开发中,经常会使用到Json数据格式进行交互。数据都是存在数据库中,由Java查询得到,需要将查询到的数据转成Json格式来响应客户端,因此就需要将Java对象序列化为Json格式的数据,同时也需要将Json格式的数据反序列化为Java的对象。

2.2 常用的Json序列化工具

  • Jsonlib:比较老的一款序列化工具,基本退出开发舞台
  • Google gson:Google出品的一款Json序列化工具,API应用最简单
  • JackSon:现在比较流行的Json序列化工具,SpringMVC内置应用
  • FastJson:阿里巴巴开发,是目前性能最好的Json序列化工具

2.3 Jackson工具的核心类:ObjectMapper

img022.png

2.3.1 数组转成Json格式字符串

  1. String[] strs = {"abc","bcd","cde"};
  2. String json = objectMapper.writeValueAsString(strs);
  3. System.out.println("json = " + json);

2.3.2 JavaBean对象转成Json格式字符串

  1. User user = new User(1,"张三","123");
  2. String json = objectMapper.writeValueAsString(user);
  3. System.out.println("json = " + json);

2.3.2 List集合转成Json格式字符串

  1. User user1 = new User(1,"张三","123");
  2. User user2 = new User(2,"李四","456");
  3. List<User> userList = new ArrayList<User>();
  4. userList.add(user1);
  5. userList.add(user2);
  6. //List集合转成Json格式字符串
  7. String json = objectMapper.writeValueAsString(userList);
  8. System.out.println("json = " + json);

2.3.3 Map集合转成Json格式字符串

 User user1 = new User(1,"张三","123");
 User user2 = new User(2,"李四","456");
 Map<String,User> userMap = new HashMap<String, User>();
 userMap.put("u1",user1);
 userMap.put("u2",user2);
 String json = objectMapper.writeValueAsString(userMap);
 System.out.println("json = " + json);

2.3.4 Json格式字符串转成JavaBean对象

 String json = "{\"id\":1,\"name\":\"张三\",\"password\":\"123\"}";
 User user = objectMapper.readValue(json,User.class);
 System.out.println("user = " + user);

2.3.5 Json格式字符串转成数组

String json = "[\"abc\",\"bcd\",\"cde\"]";
//转成数组,方法传递的二个参数是引用类型 TypeReference,类的泛型就是要转换后的类型
//TypeReference是抽象类,写法是匿名内部类的写法,抽象类没有抽象方法
String[] strs = objectMapper.readValue(json,new TypeReference<String[]>(){});
for(String s : strs){
System.out.println("s = " + s);
}

2.3.6 Json格式字符串转成List集合

String json = "[{\"id\":1,\"name\":\"张三\",\"password\":\"123\"},{\"id\":2,\"name\":\"李四\",\"password\":\"456\"}]";
List<User> userList = objectMapper.readValue(json,new TypeReference<List<User>>(){});
for(User user : userList){
System.out.println("user = " + user);
}

第三章 Axios

3.1 Axios简介

使用原生的JavaScript程序执行Ajax极其繁琐,所以一定要使用框架来完成。而Axios就是目前最流行的前端Ajax框架。

Axios官网:http://www.axios-js.com/
img005.png
使用Axios和使用Vue一样,导入对应的*.js文件即可。官方提供的script标签引入方式为:

<script src="https://unpkg.com/axios/dist/axios.min.js"></script>

我们可以把这个axios.min.js文件下载下来保存到本地来使用。

3.2 Axios基本用法

3.2.1 在前端页面引入开发环境

<script type="text/javascript" src="/demo/static/vue.js"></script>
<script type="text/javascript" src="/demo/static/axios.min.js"></script>

3.2.1 发送普通请求参数

HTML标签:

<div id="app">
    <button @click="commonParam">普通请求参数</button>
</div>

Vue+axios代码:

var vue = new Vue({
    "el":"#app",
    "data":{
        "message":""
    },
    "methods":{
        commonParam(){
            //使用axios发送异步请求
            axios({
                "method":"post",
                "url":"demo01",
                "params":{
                    "userName":"tom",
                    "userPwd":"123456"
                }
            }).then(response => {
                //then里面是处理请求成功的响应数据
                //response就是服务器端的响应数据,是json类型的
                //response里面的data就是响应体的数据
                this.message = response.data
            }).catch(error => {
                //error是请求失败的错误描述
                //error.response就是请求失败时候的响应信息
                console.log(error.response)
            })
        }
    }
})
</script>

效果:所有请求参数都被放到URL地址后面了,哪怕我们现在用的是POST请求方式。

package com.atguigu.servlet;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author Leevi
 * 日期2021-05-21  09:15
 */
public class ServletDemo01 extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        request.setCharacterEncoding("UTF-8");
        response.setContentType("text/html;charset=UTF-8");
        //1. 接收请求参数userName和userPwd
        String userName = request.getParameter("userName");
        String userPwd = request.getParameter("userPwd");
        System.out.println(userName + ":" + userPwd);

        //模拟出现异常
        //int num = 10/0;

        //2. 向浏览器响应数据
        response.getWriter().write("hello world!!!");
    }
}

3.2.2 axios程序接收到的响应对象结构

属性名 作用
config 调用axios(config对象)方法时传入的JSON对象
data 服务器端返回的响应体数据
headers 响应消息头
request 原生JavaScript执行Ajax操作时使用的XMLHttpRequest
status 响应状态码
statusText 响应状态码的说明文本

3.3 发送请求体JSON

HTML代码:

<button @click="sendJsonBody()">请求体JSON</button>

Vue+axios代码:

<script>
    var vue = new Vue({
        "el":"#app",
        "data":{
            "message":""
        },
        "methods":{
            sendJsonBody(){
                //使用axios发送异步请求,要携带Json请求体的参数
                axios({
                    "method":"post",
                    "url":"demo01",
                    //携带Json请求体参数
                    "data":{
                        "userName":"aobama",
                        "userPwd":"999999"
                    }
                }).then(response => {
                    this.message = response.data
                })
            }
        }
    })
</script>

3.3.2 后端代码

package com.atguigu.bean;

/**
 * 包名:com.atguigu.bean
 *
 * @author Leevi
 * 日期2021-06-16  10:11
 */
public class User {
    private String userName;
    private String userPwd;

    public User() {
    }

    public User(String userName, String userPwd) {
        this.userName = userName;
        this.userPwd = userPwd;
    }

    @Override
    public String toString() {
        return "User{" +
                "userName='" + userName + '\'' +
                ", userPwd='" + userPwd + '\'' +
                '}';
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getUserPwd() {
        return userPwd;
    }

    public void setUserPwd(String userPwd) {
        this.userPwd = userPwd;
    }
}

3.3.3 Servlet代码

package com.atguigu.servlet;

import com.atguigu.bean.User;
import com.google.gson.Gson;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;

/**
 * @author Leevi
 * 日期2021-06-16  09:12
 */
public class ServletDemo01 extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        try {
            response.setContentType("text/html;charset=UTF-8");
            //request.getParameter(name),request.getParameterValues(name),request.getParameterMap()这仨方法只能获取普通参数
            //什么是普通参数:1. 地址后面携带的参数  2. 表单提交的参数
            /*String userName = request.getParameter("userName");
            String userPwd = request.getParameter("userPwd");
            System.out.println("客户端传入的参数userName的值为:" + userName + ",传入的userPwd的值为:" + userPwd);*/

            //要获取Json请求体的参数,就必须得进行Json解析:可用来做Json解析的工具jar包有gson、fastjson、jackson(SpringMVC以及SpringBoot默认支持的)
            //做json解析其实就是:1. 将Java对象转成json字符串  2. 将json字符串转成Java对象

            //我们要获取json请求体的参数,其实就是将json请求体的参数封装到User对象中
            //1. 获取Json请求体的内容
            BufferedReader requestReader = request.getReader();
            //2. 从requestReader中循环读取拼接字符串
            StringBuilder stringBuilder = new StringBuilder();
            String buffer = "";
            while ((buffer = requestReader.readLine()) != null) {
                stringBuilder.append(buffer);
            }

            //3. 将stringBuilder转成字符串,这个字符串就是Json请求体
            String jsonBody = stringBuilder.toString();
            //4. 将jsonBody通过Json解析转成User对象
            ObjectMapper objectMapper = new ObjectMapper();
        User user = objectMapper.readValue(jsonBody, User.class);

            System.out.println("客户端传入的参数userName的值为:" + user.getUserName() + ",传入的userPwd的值为:" + user.getUserPwd());
            //模拟服务器出现异常
            //int num = 10/0;

            response.getWriter().write("你好世界");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

P.S.:看着很麻烦是吧?别担心,将来我们有了SpringMVC之后,一个@RequestBody 注解就能够搞定,非常方便!

3.4 服务器端返回JSON数据

sendJsonBody(){
    //使用axios发送异步请求,要携带Json请求体的参数
    axios({
        "method":"post",
        "url":"demo01",
        //携带Json请求体参数
        "data":{
            "userName":"aobama",
            "userPwd":"999999"
        }
    }).then(response => {
        //目标是获取响应数据中的用户的用户名或者密码
        this.message = response.data.userName
    })
}
package com.atguigu.servlet;

import com.atguigu.bean.User;
import com.google.gson.Gson;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;

/**
 * @author Leevi
 * 日期2021-06-16  09:12
 */
public class ServletDemo01 extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        try {
           response.setContentType("text/html;charset=UTF-8");
            //在实际开发中服务器端向客户端响应的99%都会是Json字符串
            User responseUser = new User("周杰棍","ggggggg");
            //将responseUser转成json字符串
             ObjectMapper objectMapper = new ObjectMapper();
            String responseJson = objectMapper.writeValueAsString(responseUser)

            response.getWriter().write(responseJson);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

第四章 书城项目第五阶段

功能一 注册页面用户名唯一性检查优化

4.1 前端所需axios库

img014.png

4.2 后端所需jackson库

4.3. 封装CommonsResult

4.3.1 模型的作用

在整个项目中,凡是涉及到给Ajax请求返回响应,我们都封装到CommonsResult类型中。

4.3.2 模型的代码

package com.atguigu.bean;

/**
 * 包名:com.atguigu.bean
 *
 * @author Leevi
 * 日期2021-05-21  10:43
 */
public class CommonResult {
    /**
     * 服务器端处理请求的标示
     */
    private boolean flag;

    /**
     * 当服务器端处理请求成功的时候要显示给客户端的数据(主要针对于查询)
     */
    private Object resultData;

    /**
     * 当服务器端处理请求失败的时候要响应给客户端的错误信息
     */
    private String message;

    /**
     * 处理请求成功
     * @return
     */
    public static CommonResult ok(){
        return new CommonResult().setFlag(true);
    }

    /**
     * 处理请求失败
     * @return
     */
    public static CommonResult error(){
        return new CommonResult().setFlag(false);
    }

    public boolean isFlag() {
        return flag;
    }

    private CommonResult setFlag(boolean flag) {
        this.flag = flag;
        return this;
    }

    public Object getResultData() {
        return resultData;
    }

    public CommonResult setResultData(Object resultData) {
        this.resultData = resultData;
        return this;
    }

    public String getMessage() {
        return message;
    }

    public CommonResult setMessage(String message) {
        this.message = message;
        return this;
    }

    @Override
    public String toString() {
        return "CommonResult{" +
                "flag=" + flag +
                ", resultData=" + resultData +
                ", message='" + message + '\'' +
                '}';
    }
}

各个属性的含义:

属性名 含义
flag 服务器端处理请求的结果,取值为true或者false
message 服务器端处理请求失败之后,要响应给客户端的数据
resultData 服务器端处理请求成功之后,需要响应给客户端的数据

4.3.3 模型的好处

  • 作为整个团队开发过程中,前后端交互时使用的统一的数据格式
  • 有利于团队成员之间的协助,提高开发效率

4.4 功能实现

4.4.1 定位功能的位置

在用户输入用户名之后,立即检查这个用户名是否可用。

4.4.2 给用户名输入框绑定的事件类型

结论:不能在针对username设定的watch中发送Ajax请求。

原因:服务器端响应的速度跟不上用户输入的速度,而且服务器端异步返回响应数据,无法保证和用户输入的顺序完全一致。此时有下面几点问题:

  • 给服务器增加不必要的压力
  • 用户输入的数据在输入过程中是不断发生变化的
  • 响应数据和输入顺序不对应,会发生错乱

解决办法:绑定的事件类型使用失去焦点事件。

4.4.3 流程图

img017.png

4.4.4 代码实现

<script src="static/script/axios.js"></script>
<input type="text" placeholder="请输入用户名" name="username" v-model="username" @blur="checkUsername"/>

4.4.5 JavaScript代码

var vue = new Vue({
    "el":"#app",
    "data":{
        "user":{
            "username":"[[${param.userName}]]",
            "password":"",
            "passwordConfirm":"",
            "email":"[[${param.email}]]"
        },
        "usernameError":"",
        "passwordError":"",
        "passwordConfirmError":"",
        "emailError":"",
        "flag":true
    },
    "methods":{
        checkUsername(){
            //校验用户名是否符合要求
            //用户名应为3~12位数字和字母组成
            //声明一个正则表达式
            var reg = /^[0-9a-zA-Z]{3,12}$/
            //使用正则表达式校验用户名
            if (!reg.test(this.user.username)) {
                //用户名不符合规则
                this.usernameError = "用户名必须是3~12位数字和字母组成"
                this.flag = false
            }else {
                //用户名符合规则
                //校验用户名是否已存在
                axios({
                    "method":"post",
                    "url":"user",
                    "params":{
                        "method":"checkUsername",
                        "username":this.user.username
                    }
                }).then(response => {
                    if (response.data.flag) {
                        //表示用户名可用
                        this.usernameError = ""
                        this.flag = true
                    }else {
                        // 表示用户名不可用
                        this.usernameError = response.data.message
                        this.flag = false
                    }
                })
            }
        },
        checkPassword(){
            //密码是8-16位的数字、字母、_
            var reg = /^[0-9a-zA-Z_]{8,16}$/
            //使用正则表达式校验密码的规则
            if (!reg.test(this.user.password)) {
                this.passwordError = "密码必须是8-16位的数字、字母、_"
                this.flag = false
            }else {
                this.passwordError = ""
                this.flag = true
            }
        },
        checkPasswordConfirm(){
            //确认密码的校验就是校验确认密码和密码是否相同
            if (this.user.passwordConfirm != this.user.password) {
                this.passwordConfirmError = "两次输入的密码必须一致"
                this.flag = false
            }else {
                this.passwordConfirmError = ""
                this.flag = true
            }
        },
        checkEmail(){
            //编写一个正则表达式来校验邮箱格式
            var reg = /^[a-zA-Z0-9_\.-]+@([a-zA-Z0-9-]+[\.]{1})+[a-zA-Z]+$/
            //使用正则表达式校验邮箱
            if (!reg.test(this.user.email)) {
                this.emailError = "请输入正确的邮箱格式"
                this.flag = false
            }else {
                this.emailError = ""
                this.flag = true
            }
        },
        checkRegister(){
            //校验用户名、密码、确认密码、邮箱
            if (!this.flag) {
                //至少有一个校验未通过:则不能提交表单
                //阻止默认
                event.preventDefault()
            }
        },
        changeCodeImg(){
            //切换验证码图片:重新设置当前图片的src
            //event.target就表示获取当前事件所在的标签
            event.target.src = "kaptcha"
        }
    }
});
public void checkUsername(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    String username = request.getParameter("username");
    String result = userService.checkUsername(username);
    CommonResult commonResult ;
    if (result == null){
        commonResult = new CommonResult(true,"","用户名可以使用");
    }else {
        commonResult = new CommonResult(false,"","用户名被占用");
    }
    response.getWriter().write(objectMapper.writeValueAsString(commonResult));
    }

4.5 加入购物车

4.5.1创建购物车模型

img023.png

4.5.2 购物项详情类

package com.atguigu.bookstore.bean;

/**
 * 包名:com.atguigu.bookstore.bean
 *
 * @author Leevi
 * 日期2021-06-16  14:58
 */
public class CartItem {
    /**
     * 购物项存储的那本书的id
     */
    private Integer bookId;
    /**
     * 购物项存储的那本书的书名
     */
    private String bookName;
    /**
     * 购物项存储的那本书的图片路径
     */
    private String imgPath;
    /**
     * 购物项存储的那本书的单价
     */
    private Double price;
    /**
     * 购物项的书的数量
     */
    private Integer count = 0;
    /**
     * 购物项的金额
     */
    private Double amount = 0d;

    public CartItem(Integer bookId, String bookName, String imgPath, Double price, Integer count, Double amount) {
        this.bookId = bookId;
        this.bookName = bookName;
        this.imgPath = imgPath;
        this.price = price;
        this.count = count;
        this.amount = amount;
    }

    public CartItem() {
    }

    public Integer getBookId() {
        return bookId;
    }

    public void setBookId(Integer bookId) {
        this.bookId = bookId;
    }

    public String getBookName() {
        return bookName;
    }

    public void setBookName(String bookName) {
        this.bookName = bookName;
    }

    public String getImgPath() {
        return imgPath;
    }

    public void setImgPath(String imgPath) {
        this.imgPath = imgPath;
    }

    public Double getPrice() {
        return price;
    }

    public void setPrice(Double price) {
        this.price = price;
    }

    public Integer getCount() {
        return count;
    }

    public void setCount(Integer count) {
        this.count = count;
    }

    /**
     * 获取当前购物项的金额
     * @return
     */
    public Double getAmount() {
        //我们自己计算金额
        this.amount = this.price * this.count;
        return this.amount;
    }

    public void setAmount(Double amount) {
        this.amount = amount;
    }

    @Override
    public String toString() {
        return "CartItem{" +
            "bookId=" + bookId +
            ", bookName='" + bookName + '\'' +
            ", imgPath='" + imgPath + '\'' +
            ", price=" + price +
            ", count=" + count +
            ", amount=" + amount +
            '}';
    }

    /**
     * 将count自增1
     */
    public void countIncrease(){
        this.count ++;
    }

    /**
     * 将当前购物项的数量进行 -1
     */
    public void countDecrease(){
        if (this.count > 0) {
            this.count --;
        }
    }
}

4.5.3 购物车类:Cart

package com.atguigu.bookstore.bean;

import com.atguigu.bookstore.entity.Book;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

/**
 * 包名:com.atguigu.bookstore.bean
 *
 * @author Leevi
 * 日期2021-06-16  15:04
 */
public class Cart {
    /**
     * 当前购物车的总金额
     */
    private Double totalAmount = 0d;

    /**
     * 当前购物车的商品总数
     */
    private Integer totalCount = 0;

    /**
     * 存储购物项的容器
     * 以购物项的bookId作为key,以购物项CartItem作为value
     */
    private Map<Integer,CartItem> cartItemMap = new HashMap<>();

    /**
     * 将某本书添加进购物车
     * @param book
     */
 public void addBookToCart(Book book){
        Integer bookId = book.getBookId();
        //判断当前商品是否已经存在购物车中
        if (cartItemMap.containsKey(bookId)){
            //取出购物车中的购物项
            CartItem cartItem = cartItemMap.get(bookId);
            //数量++
            cartItem.countIncrease();
        }else {
            //创建新的购物项
            CartItem cartItem = new CartItem();
            //设置购物项中的书信息
            cartItem.setBookId( book.getBookId());
            cartItem.setBookName( book.getBookName());
            cartItem.setPrice(book.getPrice());
            cartItem.setImgPath(book.getImgPath());
            cartItem.setCount(1);
            //购物项数据,添加到购物车
            cartItemMap.put(book.getBookId(),cartItem);
        }
         //计算购物车总价格
        this.totalAmount += book.getPrice();
    }

    /**
     * 将某个购物项的数量+1
     * @param bookId
     */
    public void cartItemCountIncrease(Integer bookId){
        CartItem cartItem = cartItemMap.get(bookId);
        cartItem.countIncrease();
        this.totalAmount += cartItem.getPrice();
    }
    /**
     * 将某一个购物项的数量 -1
     * @param bookId
     */
   public void cartItemCountDecrease(Integer bookId){
        CartItem cartItem = cartItemMap.get(bookId);
        cartItem.countDecrease();
        if (cartItem.getCount()==0){
            //购物项为0,直接移除
            cartItemMap.remove(bookId);
        }
       //从新计算总结果
        this.totalAmount -= cartItem.getPrice();
    }

    /**
     * 根据bookId将购物项从购物车中移除
     * @param bookId
     */
    public void removeCartItem(Integer bookId){
        //移除商品
        CartItem cartItem = cartItemMap.remove(bookId);
        //计算总价格
        this.totalAmount -= cartItem.getAmount();
    }

    /**
     * 修改某个购物项的数量
     * @param bookId
     * @param newCount
     */
    public void updateCartItemCount(Integer bookId,Integer count){
      CartItem cartItem = cartItemMap.get(bookId);
      Integer originalCount = cartItem.getCount();
      cartItem.setCount(count);
        //计算总价格,必须计算差价
        this.totalAmount += (count-originalCount)*cartItem.getPrice();
    }

    public Double getTotalAmount() {
        return totalAmount;
    }

    public void setTotalAmount(Double totalAmount) {
        this.totalAmount = totalAmount;
    }

    /**
     * 计算商品总数量
     * @return
     */
    public Integer getTotalCount() {
        this.totalCount = 0;
        Collection<CartItem> values = cartItemMap.values();
        for(CartItem cartItem : values){
            this.totalCount += cartItem.getCount();
        }
        return totalCount;
    }


    public void setTotalCount(Integer totalCount) {
        this.totalCount = totalCount;
    }

    public Map<Integer, CartItem> getCartItemMap() {
        return cartItemMap;
    }

    public void setCartItemMap(Map<Integer, CartItem> cartItemMap) {
        this.cartItemMap = cartItemMap;
    }
}

4.5.4 实现思路

img018.png

4.5.6 客户端发送异步请求

<script src="static/script/vue.js" type="text/javascript" charset="utf-8"></script>
<script src="static/script/axios.js" type="text/javascript" charset="utf-8"></script>
<button @click="addBookToCart()" th:value="${book.bookId}">加入购物车</button>

给首页顶部绑定显示购物车中商品总数,由于要考虑是否登录的情况,所以登录和未登录的标签都要绑定数据模型

<!--登录前的风格-->
<div class="topbar-right" th:if="${session.loginUser == null}">
    <a href="user?method=toLoginPage" class="login">登录</a>
    <a href="user?method=toRegisterPage" class="register">注册</a>
    <a
       href="cart?method=toCartPage"
       class="cart iconfont icon-gouwuche
              "
       >
        购物车
        <div class="cart-num" v-text="totalCount">3</div>
    </a>
    <a href="admin?method=toManagerPage" class="admin">后台管理</a>
</div>
<!--登录后风格-->
<div class="topbar-right" th:unless="${session.loginUser == null}">
    <span>欢迎你<b th:text="${session.loginUser.userName}">张总</b></span>
    <a href="user?method=logout" class="register">注销</a>
    <a
       href="cart?method=toCartPage"
       class="cart iconfont icon-gouwuche
              ">
        购物车
        <div class="cart-num" v-text="totalCount">3</div>
    </a>
    <a href="pages/manager/book_manager.html" class="admin">后台管理</a>
</div>

4.5.7 加入layer弹层组件,优化用户体验

img019.png

<script type="text/javascript" src="static/script/jquery-1.7.2.js"></script>
<script type="text/javascript" src="static/layer/layer.js"></script>
var vue = new Vue({
    "el":"#app",
    "data":{
        "totalCount":0
    },
    "methods":{
        addBookToCart(){
            //获取bookId: bookId绑定在当前标签的value属性上
            //event.target就表示拿到当前标签
             var event = window.event || arguments.callee.caller.arguments[0];
             var bookId = event.target.value;

            //发送异步请求:添加书进购物车
            axios({
                "method":"post",
                "url":"cart",
                "params":{
                    "method":"addCartItem",
                    "id":bookId
                }
            }).then(response => {
                if (response.data.flag) {
                    //添加购物车成功
                    this.totalCount = response.data.resultData
                    layer.msg("添加购物车成功")
                }else {
                    //添加购物车失败
                    layer.msg("添加购物车失败")
                }
            })
        }
    }
});

4.5.8 后端代码

CartServlet

package com.atguigu.bookstore.servlet.model;

import com.atguigu.bookstore.bean.Cart;
import com.atguigu.bookstore.bean.CommonResult;
import com.atguigu.bookstore.entity.Book;
import com.atguigu.bookstore.service.BookService;
import com.atguigu.bookstore.service.impl.BookServiceImpl;
import com.atguigu.bookstore.servlet.base.ModelBaseServlet;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

/**
 * @author Leevi
 * 日期2021-06-16  16:03
 */
public class CartServlet extends ModelBaseServlet {
    private BookService bookService = new BookServiceImpl();
    /**
     * 将书添加进购物车
     * @param request
     * @param response
     */
    public void addCartItem(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //获取书id
        String id = request.getParameter("id");
        //查询书对象
        Book book = bookService.getBookById(id);
        //获取购物车对象
        Cart cart = getCart(request);
        cart.addBookToCart(book);
        CommonResult commonResult = new CommonResult(true,cart,"购物车添加成功");
        response.getWriter().write(objectMapper.writeValueAsString(commonResult));
    }

    private Cart getCart(HttpServletRequest request){
        HttpSession session = request.getSession();
        Cart cart =(Cart) session.getAttribute("cart");
        if(cart == null){
            cart = new Cart();
            session.setAttribute("cart",cart);
        }
        return cart;
    }

4.5.9 解决刷新首页后无法显示购物车商品总数的问题

在首页的created钩子函数中发送异步请求获取购物车商品总数

     mounted:function () {
          axios({
            method:"GET",
            url:"cart",
            params:{
              method:"getTotalCount"
            }
          }).then(response=>{
             this.cartCount = response.data.resultData;
          });
        }

在CartServlet中添加getTotalCount方法

/**
     * 获取购物车的商品总数
     * @param request
     * @param response
     */
public void getTotalCount(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //获取购物车
        Cart cart = getCart(request);
        Integer totalCount = 0;
        if (cart != null)
        //取出购物项目
            totalCount =cart.getTotalCount();
   response.getWriter().write(objectMapper.writeValueAsString(new CommonResult(true,totalCount,"")));
    }
}

第五章 显示购物车页面

5.1目标

把购物车信息在专门的页面显示出来

5.2思路

img001.png

5.3 购物车超链接

登录状态和未登录状态

<div class="topbar-right" th:if="${session.loginUser == null}">
    <a href="user?method=toLoginPage" class="login">登录</a>
    <a href="user?method=toRegisterPage" class="register">注册</a>
    <a
       href="cart?method=toCartPage"
       class="cart iconfont icon-gouwuche
              "
       >
        购物车
        <div class="cart-num" th:if="${session.cart != null}" th:text="${session.cart.totalCount}">3</div>
    </a>
    <a href="admin?method=toManagerPage" class="admin">后台管理</a>
</div>
<!--登录后风格-->
<div class="topbar-right" th:unless="${session.loginUser == null}">
    <span>欢迎你<b th:text="${session.loginUser.username}">张总</b></span>
    <a href="user?method=logout" class="register">注销</a>
    <a
       href="cart?method=toCartPage"
       class="cart iconfont icon-gouwuche
              ">
        购物车
        <div class="cart-num" th:if="${session.cart != null}" th:text="${session.cart.totalCount}">3</div>
    </a>
    <a href="admin?method=toManagerPage" class="admin">后台管理</a>
</div>

5.4 CartServlet跳转到cart.html页面的代码

/**
     * 跳转到显示购物车列表的页面
     * @param request
     * @param response
     */
public void toCartPage(HttpServletRequest request,HttpServletResponse response) throws IOException {
    processTemplate("cart/cart",request,response);
}

5.5 cart.html

<div class="list" id="app">
  <div class="w">
    <table>
      <thead>
        <tr>
          <th>图片</th>
          <th>商品名称</th>

          <th>数量</th>
          <th>单价</th>
          <th>金额</th>
          <th>操作</th>
        </tr>
      </thead>
      <tbody v-if="cart==0">
      <tr colspan="">
        <td>购物车还没有商品</td>
      </tr>
      </tbody>
      <tbody>
        <tr v-for="cartItem in cart.cartItemMap">
          <td>
            <img :src="cartItem.imgPath" alt="" />
          </td>
          <td>{{cartItem.bookName}}</td>
          <td>
            <span class="count">-</span>
            <input class="count-num" type="text" value="1" v-model="cartItem.count" />
            <span class="count">+</span>
          </td>
          <td>{{cartItem.price}}</td>
          <td>{{cartItem.amount}}</td>
          <td><a href="">删除</a></td>
        </tr>

      </tbody>
    </table>
    <div class="footer">
      <div class="footer-left">
        <a href="#" class="clear-cart">清空购物车</a>
        <a href="#">继续购物</a>
      </div>
      <div class="footer-right">
        <div>共<span v-text="cart.totalCount">3</span>件商品</div>
        <div class="total-price">总金额<span v-text="cart.totalAmount">99.9</span>元</div>
        <a class="pay" href="checkout.html">去结账</a>
      </div>
    </div>
  </div>
</div>


<!--vue代码-->
<script>
    <script type="text/javascript">
       new Vue({
         el:"#app",
         data:{
           cart:0
         },
         methods:{
           showCart:function () {
              axios({
                method:"POST",
                url:"cart",
                params:{
                  method:"getCart"
                }
              }).then(response=>{
                if(response.data.flag){
                  this.cart = response.data.resultData;
                  console.log(this.cart);
                }
              });
           }
         },
         mounted:function () {
            this.showCart();
         }
       });
</script>

5.6 CartServlet中添加getCart方法

  public void getCart(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //获取购物车
        Cart cart = getCart(request);
        if (cart.getCartItemMap().size() == 0){
            response.getWriter().write(objectMapper.writeValueAsString(
                    new CommonResult(false,"","购物车是空的")));
        }else {
            response.getWriter().write(objectMapper.writeValueAsString(
                    new CommonResult(true,cart,"购物车!!")));
        }
    }

第六章 清空购物车

1 目标

当用户确定点击清空购物车,将Session域中的Cart对象移除。

2 思路

cart.html→清空购物车超链接→点击事件→confirm()确认→确定→CartServlet.clearCart()→从Session域移除Cart对象→跳转回到cart.html页面

3 代码实现

3.1 前端页面代码

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
        <base th:href="@{/}"/>
        <link rel="stylesheet" href="static/css/minireset.css" />
        <link rel="stylesheet" href="static/css/common.css" />
        <link rel="stylesheet" href="static/css/cart.css" />
        <script src="static/script/vue.js"></script>
        <script src="static/script/axios.min.js"></script>
    </head>
    <body>
        <div class="header">
            <div class="w">
                <div class="header-left">
                    <a href="index.html">
                        <img src="static/img/logo.gif" alt=""
                             /></a>
                    <h1>我的购物车</h1>
                </div>
                <div class="header-right">
                    <h3>欢迎<span th:text="${session.loginUser.userName}">张总</span>光临尚硅谷书城</h3>
                    <div class="order"><a href="../order/order.html">我的订单</a></div>
                    <div class="destory"><a href="user?method=logout">注销</a></div>
                    <div class="gohome">
                        <a href="index.html">返回</a>
                    </div>
                </div>
            </div>
        </div>
        <div class="list" id="app">
            <div class="w">
                <table>
                    <thead>
                        <tr>
                            <th>图片</th>
                            <th>商品名称</th>

                            <th>数量</th>
                            <th>单价</th>
                            <th>金额</th>
                            <th>操作</th>
                        </tr>
                    </thead>
                    <tbody v-if="cart.cartItemList == null">
                        <tr>
                            <td colspan="6">
                                购物车空空如也,请添加购物车信息
                            </td>
                        </tr>
                    </tbody>
                    <tbody v-if="cart.cartItemList != null">
                        <tr v-for="(cartItem,index) in cart.cartItemList">
                            <td>
                                <img :src="cartItem.imgPath" alt="" />
                                <input type="hidden" name="bookId" v-model="cartItem.bookId"/>
                            </td>
                            <td v-text="cartItem.bookName"></td>
                            <td>
                                <span class="count">-</span>
                                <input class="count-num" type="text" v-model="cartItem.count" />
                                <span class="count">+</span>
                            </td>
                            <td v-text="cartItem.price"></td>
                            <td v-text="cartItem.amount"></td>
                            <td><a href="">删除</a></td>
                        </tr>
                    </tbody>
                </table>
                <div class="footer">
                    <div class="footer-left">
                        <a href="javascript:;" @click="cleanCart()" class="clear-cart">清空购物车</a>
                        <a href="#">继续购物</a>
                    </div>
                    <div class="footer-right" v-if="cart.cartItemList != null">
                        <div>共<span v-text="cart.totalCount"></span>件商品</div>
                        <div class="total-price">总金额<span v-text="cart.totalAmount"></span>元</div>
                        <a class="pay" href="checkout.html">去结账</a>
                    </div>
                </div>
            </div>
        </div>
        <div class="bottom">
            <div class="w">
                <div class="top">
                    <ul>
                        <li>
                            <a href="">
                                <img src="static/img/bottom1.png" alt="" />
                                <span>大咖级讲师亲自授课</span>
                            </a>
                        </li>
                        <li>
                            <a href="">
                                <img src="static/img/bottom.png" alt="" />
                                <span>课程为学员成长持续赋能</span>
                            </a>
                        </li>
                        <li>
                            <a href="">
                                <img src="static/img/bottom2.png" alt="" />
                                <span>学员真是情况大公开</span>
                            </a>
                        </li>
                    </ul>
                </div>
                <div class="content">
                    <dl>
                        <dt>关于尚硅谷</dt>
                        <dd>教育理念</dd>
                        <!-- <dd>名师团队</dd>
<dd>学员心声</dd> -->
                    </dl>
                    <dl>
                        <dt>资源下载</dt>
                        <dd>视频下载</dd>
                        <!-- <dd>资料下载</dd>
<dd>工具下载</dd> -->
                    </dl>
                    <dl>
                        <dt>加入我们</dt>
                        <dd>招聘岗位</dd>
                        <!-- <dd>岗位介绍</dd>
<dd>招贤纳师</dd> -->
                    </dl>
                    <dl>
                        <dt>联系我们</dt>
                        <dd>http://www.atguigu.com</dd>
                        <dd></dd>
                    </dl>
                </div>
            </div>
            <div class="down">
                尚硅谷书城.Copyright ©2015
            </div>
        </div>
        <script>
            var vue = new Vue({
                "el":"#app",
                "data":{
                    "cart":{}
                },
                "methods":{
                    showCart(){
                        //发送异步请求获取购物车的信息
                        axios({
                            "method":"post",
                            "url":"cart",
                            "params":{
                                "method":"getCartJSON"
                            }
                        }).then(response => {
                            this.cart = response.data.resultData
                        } )
                    },
                    cleanCart(){
                       //弹出确认框,问是否真的要清空购物车
                      if(confirm("确定要清空购物车吗?")){
                          location.href="cart?method=clearCart";
                         }
                    }
                },
                mounted(){
                    //钩子函数,在这个钩子函数中就能使用数据模型
                    this.showCart()
                }
            });
        </script>
    </body>
</html>

3.2 CartServlet.cleanCart()

/**
     * 清空购物车
     * @param request
     * @param response
     */
 public void clearCart(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        Cart cart = getCart(request);
        cart.getCartItemMap().clear();
         cart.setTotalAmount(0D);
        processTemplate("cart/cart",request,response);
    }

第七章 减号

1.目标

● 在大于1的数值基础上-1:执行-1的逻辑
● 在1的基础上-1:执行删除item的逻辑

2. 思路

img002.png

3.前端代码

3.1 给减号绑定点击事件

<a @click="cartItemCountDecrease(cartItem.bookId,cartItem.count)" class="count" href="javascript:;">-</a>

3.2 Vue代码

cartItemCountDecrease:function(id,count){
    if(count == 1){
        if(!confirm("确定删除此商品吗")){
            return;
        }
    }
    axios({
        method:"post",
        url:"cart",
        params:{
            method:"cartItemCountDecrease",
            bookId:id
        }
    }).then(response=>{
        if (response.data.flag){
            //清空原有购物车数据
            this.cart=0;
            //重新加载购物车
            this.showCart();
        }
    });
}

4 后端代码

CartServlet.cartItemCountDecrease()方法

/**
     * 购物项数量-1
     * @param request
     * @param response
     */
 public void cartItemCountDecrease(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        Integer bookId = Integer.parseInt(request.getParameter("bookId"));
        Cart cart = getCart(request);
        cart.cartItemCountDecrease(bookId);
       response.getWriter().write(objectMapper.writeValueAsString(new CommonResult(true,"","减少商品成功") ));
        System.out.println("cart = " + cart);
    }

第八章 加号

1.目标

告诉Servlet将Session域中Cart对象里面对应的CartItem执行count+1操作

2.思路

img003.png

3.代码实现

3.1 前端代码

给加号绑定点击事件

<span class="count" @click="cartItemCountIncrease(cartItem.bookId)">+</span>

vue代码

cartItemCountIncrease:function(id){
    axios({
        method:"post",
        url:"cart",
        params:{
            method:"cartItemCountIncrease",
            bookId:id
        }
    }).then(response=>{
        if (response.data.flag){
            this.cart=0;
            this.showCart();
        }
    });
}

3.2 后端代码

CartServlet.cartItemCountIncrease()

/**
     * 购物项数量加一
     * @param request
     * @param response
     */
 public void cartItemCountIncrease(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        Integer bookId = Integer.parseInt(request.getParameter("bookId"));
        System.out.println("bookId = " + bookId);
        Cart cart = getCart(request);
        cart.cartItemCountIncrease(bookId);
        response.getWriter().write(objectMapper.writeValueAsString(new CommonResult(true,"","商品增加成功") ));
    }
}

第九章 删除

1.目标

点击删除超链接后,把对应的CartItem从Cart中删除

2.思路

img004.png

3.代码实现

3.1 前端代码

给删除按钮绑定点击事件

<a href="#" @click.prevent="removeCartItem(cartItem.bookName,cartItem.bookId,index)">删除</a>

vue和axios代码

 removeCartItem:function(id){
     axios({
         method:"post",
         url:"cart",
         params:{
             method:"removeCartItem",
             bookId:id
         }

     }).then(response=>{
         if (response.data.flag){
             this.cart=0;
             this.showCart();
         }
     });
 }

3.2 后端代码

CartServlet.removeCartItem()

 //移除购物车商品
    public void removeCartItem(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        Integer bookId = Integer.parseInt(request.getParameter("bookId"));
        Cart cart = getCart(request);
        cart.removeCartItem(bookId);
        response.getWriter().write(objectMapper.writeValueAsString(new CommonResult(true,"","移除商品成功") ));
    }

第十章 文本框修改

1.目标

用户在文本框输入新数据后,根据用户输入在Session中的Cart中修改CartItem中的count

2.思路

img005.png

3.代码实现

3.1 前端代码

绑定失去change事件

<input class="count-num" type="text" v-model="cartItem.count" @change="updateCartItemCount(cartItem.count,cartItem.bookId"/>

vue和axios代码

updateCartItemCount:function(count,id){
    let reg=/^[1-9][0-9]*$/;
    if(reg.test(count)){
        axios({
            method:"post",
            url:"cart",
            params:{
                method:"updateCartItemCount",
                bookId:id,
                count:count
            }
        }).then(response=>{
            if (response.data.flag){
                this.cart=0;
                this.showCart();
            }
        });
    }else {
        layer.msg("输入有误");
    }
}

3.2后端代码

CartServlet.updateCartItemCount()

/**
     * 修改某个购物项的count
     * @param request
     * @param response
     */
public void updateCartItemCount(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        Integer bookId = Integer.parseInt(request.getParameter("bookId"));
        Integer count = Integer.parseInt(request.getParameter("count"));
        Cart cart = getCart(request);
        cart.updateCartItemCount(bookId,count);
        response.getWriter().write(objectMapper.writeValueAsString(new CommonResult(true,"","修改商品数量成功") ));
    }
}

第十一章 Double数据运算过程中精度调整

1.问题现象

img11.png

2.解决方案

● 使用BigDecimal类型来进行Double类型数据运算
● 创建BigDecimal类型对象时将Double类型的数据转换为字符串

Cart类:

//修改商品数量
public void updateCartItemCount(Integer bookId,Integer count){
    CartItem cartItem = cartItemMap.get(bookId);
    Integer originalCount = cartItem.getCount();
    cartItem.setCount(count);
    totalAmountBig = totalAmountBig.add(new BigDecimal((count-originalCount)*cartItem.getPrice()+""));
    this.totalAmount = totalAmountBig.doubleValue();
}

//移除购物车商品
public void removeCartItem(Integer bookId){
    //移除商品
    CartItem cartItem = cartItemMap.remove(bookId);
    //计算总价格
    totalAmountBig = totalAmountBig.subtract(new BigDecimal(cartItem.getAmount()+""));
    this.totalAmount = totalAmountBig.doubleValue();
}

//++
public void cartItemCountIncrease(Integer bookId){
    CartItem cartItem = cartItemMap.get(bookId);
    cartItem.countIncrease();
    totalAmountBig = totalAmountBig.add(new BigDecimal(cartItem.getPrice()+""));
    this.totalAmount=totalAmountBig.doubleValue();
}

//--
public void cartItemCountDecrease(Integer bookId){
    CartItem cartItem = cartItemMap.get(bookId);
    cartItem.countDecrease();
    if (cartItem.getCount()==0){
        cartItemMap.remove(bookId);
    }
    totalAmountBig = totalAmountBig.subtract(new BigDecimal(cartItem.getPrice()+""));
    this.totalAmount =totalAmountBig.doubleValue();;
}

CartItem类:

    //小计=购买数量*单价
public Double getAmount() {
    BigDecimal bigDecimalPrice = new BigDecimal(this.price+"");
    BigDecimal bigDecimalCount = new BigDecimal(this.count+"");
    this.amount = bigDecimalPrice.multiply(bigDecimalCount).doubleValue();
    return this.amount;
}