day11 Ajax&Axios&书城项目第六阶段
第一章 Ajax异步请求
1.1 服务器端渲染
1.2 Ajax渲染(局部更新)
1.3 前后端分离
真正的前后端分离是前端项目和后端项目分服务器部署,在我们这里我们先理解为彻底舍弃服务器端渲染,数据全部通过Ajax方式以JSON格式来传递
1.4 同步与异步
Ajax本身就是Asynchronous JavaScript And XML的缩写,直译为:异步的JavaScript和XML。在实际应用中Ajax指的是:不刷新浏览器窗口,不做页面跳转,局部更新页面内容的技术。
『同步』和『异步』是一对相对的概念,那么什么是同步,什么是异步呢?
定义:同步和异步关注的是消息通信机制 (synchronous communication/ asynchronous communication)。同步,就是调用某个东西是,调用方得等待这个调用返回结果才能继续往后执行。异步,和同步相反 调用方不会理解得到结果,而是在调用发出后调用者可用继续执行后续操作,被调用者通过状体来通知调用者,或者通过回掉函数来处理这个调用
比方说:你去商城买东西,你看上了一款手机,能和店家说你一个这款手机,他就去仓库拿货,你得在店里等着,不能离开,这叫做同步。现在你买手机赶时髦直接去京东下单,下单完成后你就可用做其他时间(追剧、打王者、lol)等货到了去签收就ok了.这就叫异步。
1.4.1 同步
多个操作按顺序执行,前面的操作没有完成,后面的操作就必须等待。
1.4.2 异步
多个操作相继开始并发执行,即使开始的先后顺序不同,但是由于它们各自是在自己独立的进程或线程中完成,所以互不干扰,谁也不用等谁。
1.4.3 原生态的Ajax异步请求
XMLHttpRequest 是 AJAX 的基础,现代的浏览器中都内置了该对象。
<script type="text/javascript">
function func(){
//创建ajax引擎对象
var xmlhttp = new XMLHttpRequest();
//ajax引擎对象绑定事件,监听服务器响应数据
xmlhttp.onreadystatechange=function(){
if(xmlhttp.readyState==4 && xmlhttp.status==200){
alert(xmlhttp.responseText);
}
}
xmlhttp.open("GET", "/ajaxServlet", true);
xmlhttp.send();
}
</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
2.3.1 数组转成Json格式字符串
String[] strs = {"abc","bcd","cde"};
String json = objectMapper.writeValueAsString(strs);
System.out.println("json = " + json);
2.3.2 JavaBean对象转成Json格式字符串
User user = new User(1,"张三","123");
String json = objectMapper.writeValueAsString(user);
System.out.println("json = " + json);
2.3.2 List集合转成Json格式字符串
User user1 = new User(1,"张三","123");
User user2 = new User(2,"李四","456");
List<User> userList = new ArrayList<User>();
userList.add(user1);
userList.add(user2);
//List集合转成Json格式字符串
String json = objectMapper.writeValueAsString(userList);
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/
使用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库
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 流程图
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创建购物车模型
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 实现思路
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弹层组件,优化用户体验
<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思路
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. 思路
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.思路
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.目标
2.思路
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.思路
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.问题现象
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;
}