1.购物车环境搭建
1.介绍
2.操作
1).创建cart购物车项目
gulimall-cart
2).配置购物车的域名
3).配置gateway网关
- id: gulimall_cart_route
uri: lb://gulimall-cart
predicates:
- Host=cart.gulimall.com
4).配置资源到nginx
a.动态资源放入项目
b.静态资源放入nginx
放入nginx的/html/static/cart下
c.配置nginx,已经配置好,gulimall.conf为*.gulimall.com
d.配置动态资源的路径
5).配置页面跳转
a.购物车页面,点击商城首页、商城图片,跳转到product的首页
<a href="http://gulimall.com/">谷粒商城首页</a>
<a href="http://gulimall.com/"><img src="/static/cart/img/logo1.jpg" style="height: 60px;width: 180px;" /></a>
b.商城首页页面,点击我的购物车,跳转到购物车首页
<span><a href="http://cart.gulimall.com/">我的购物车</a></span>
3.购物车需求
1).需求描述:
- 用户可以在登录状态下将商品添加到购物车【用户购物车/在线购物车】
- 放入数据库
- mongodb
- 放入 redis(采用)
登录以后,会将临时购物车的数据全部合并过来,并清空临时购物车;
- 用户可以在未登录状态下将商品添加到购物车【游客购物车/离线购物车/临时购物车】
- 放入 localstorage(客户端存储,后台不存)
- cookie
- WebSQL
- 放入 redis(采用)
浏览器即使关闭,下次进入,临时购物车数据都在
- 用户可以使用购物车一起结算下单
- 给购物车添加商品
- 用户可以查询自己的购物车
- 用户可以在购物车中修改购买商品的数量。
- 用户可以在购物车中删除商品。
- 选中不选中商品
- 在购物车中展示商品优惠信
- 提示购物车商品价格变化
2).需求描述:
因此每一个购物项信息,都是一个对象,基本字段包括:
{
skuId: 2131241,
check: true,
title: “Apple iphone…”,
defaultImage: “…”,
price: 4999,
count: 1,
totalPrice: 4999,
skuSaleVO: {…}
}
另外,购物车中不止一条数据,因此最终会是对象的数组。即:
[
{…},{…},{…}
]
Redis 有 5 种不同数据结构,这里选择哪一种比较合适呢?Map<String, List>>
- 首先不同用户应该有独立的购物车,因此购物车应该以用户的作为 key 来存储,Value 是 用户的所有购物车信息。这样看来基本的
k-v
结构就可以了。- 但是,我们对购物车中的商品进行增、删、改操作,基本都需要根据商品 id 进行判断, 为了方便后期处理,我们的购物车也应该是
k-v
结构,key 是商品 id,value 才是这个商品的 购物车信息。
综上所述,我们的购物车结构是一个双层 Map:Map<String,Map<String,String>>
即, Map<String id,Map<String id,cartItemInfo>>- 第一层 Map,Key 是用户 id
- 第二层 Map,Key 是购物车中商品 id,值是购物项数据
3.购物车的数据结构
2.数据模型分析
2.1.购物车 数据模型分析
1).购物车 实体类–注意数据计算
//整个购物车
//整个购物车存放的商品信息。需要计算的属性需要重写get方法,保证每次获取属性都会进行计算
public class Cart {
//购物车的购物项
private List<CartItem> items;
//商品数量
private Integer countNum;
//商品类型数量,几种不同的商品
private Integer countType;
//商品总价
private BigDecimal totalAmount;
//商品减免价格,优惠多少
private BigDecimal reduce = new BigDecimal("0.00");
public List<CartItem> getItems() {
return items;
}
public void setItems(List<CartItem> items) {
this.items = items;
}
public Integer getCountNum() {
int count = 0;
if (items != null && items.size() > 0) {
for (CartItem item : items) {
count += item.getCount();
}
}
return count;
}
public Integer getCountType() {
int count = 0;
if (items != null && items.size() > 0) {
for (CartItem item : items) {
count += 1;
}
}
return count;
}
public BigDecimal getTotalAmount() {
BigDecimal amount = new BigDecimal("0");
// 1.计算购物项总价
if (!CollectionUtils.isEmpty(items)) {
for (CartItem item : items) {
if (item.getCheck()) {
amount = amount.add(item.getTotalPrice());
}
}
}
// 2.计算优惠后的价格
return amount.subtract(getReduce());
}
public void setTotalAmount(BigDecimal totalAmount) {
this.totalAmount = totalAmount;
}
public BigDecimal getReduce() {
return reduce;
}
public void setReduce(BigDecimal reduce) {
this.reduce = reduce;
}
}
2).购物车的购物项–注意数据计算
//购物车的购物项内容
public class CartItem {
//商品Id
private Long skuId;
//是否选中商品
private Boolean check = true;
//商品名称
private String title;
//商品图片
private String image;
//商品属性
private List<String> skuAttr;
//价格
private BigDecimal price;
//商品数量
private Integer count;
//总价格
private BigDecimal totalPrice;
//商品销售属性
public Long getSkuId() {
return skuId;
}
public void setSkuId(Long skuId) {
this.skuId = skuId;
}
public Boolean getCheck() {
return check;
}
public void setCheck(Boolean check) {
this.check = check;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getImage() {
return image;
}
public void setImage(String image) {
this.image = image;
}
public List<String> getSkuAttr() {
return skuAttr;
}
public void setSkuAttr(List<String> skuAttr) {
this.skuAttr = skuAttr;
}
public BigDecimal getPrice() {
return price;
}
public void setPrice(BigDecimal price) {
this.price = price;
}
public Integer getCount() {
return count;
}
public void setCount(Integer count) {
this.count = count;
}
/**
* 计算总价格:价格*数量
*/
public BigDecimal getTotalPrice() {
return this.price.multiply(new BigDecimal(""+this.count));
}
public void setTotalPrice(BigDecimal totalPrice) {
this.totalPrice = totalPrice;
}
}
3).用户信息的实体类
//用户信息
@Data
@ToString
public class UserInfoTo {
private Long userId;//如果没有用户id,说明为登录,一定封装临时用户
//临时用户
private String userKey;//一定封装
//是否为临时用户
private Boolean tempUser=false;
}
2.2 请求前,先拦截(拦截器)
0.说明:拦截请求时,拦截器使用threadLocal设置set数据,可以保证统一线程数据共享
请求拦截前,把数据放入threadLocal。方法执行时,直接获得数据。
1).购物车常量CartConstant–放入common
/**
* @Description: 购物车常量
**/
public class CartConstant {
//临时用户的cookie名称
public final static String TEMP_USER_COOKIE_NAME = "user-key";
//临时用户cookie过期时间,一个月
public final static int TEMP_USER_COOKIE_TIMEOUT = 60*60*24*30;
public final static String CART_PREFIX = "gulimall:cart:";
}
2).拦截器–实现登录、未登录的拦截
a.请求执行前,拦截所有请求,确定登录状态
- 判断用户是否登录。
- 未登录,一定分配临时用户user-key。
如果没有临时用户,一定分配一个临时用户。
目标方法执行之前,把用户信息放入threadLocal(为了同一个线程共享数据)
返回true
b.目标方法执行后:分配临时用户,让浏览器保存
获得用户信息
判断是否有临时用户。如果没有临时用户,一定保存一个临时用户
//在目标方法执行之前,判断用户的登陆状态。并封包传递给controller的目标请求
//@Component
public class CartInterceptor implements HandlerInterceptor {
//threadLocal,保证拦截器-->controller-->service-->dao是一个线程执行(同一个线程共享数据)
public static ThreadLocal<UserInfoTo> threadLocal = new ThreadLocal();
//目标方法执行前。判断是否成功
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HttpSession session = request.getSession();
MemberRespVo member = (MemberRespVo) session.getAttribute(AuthServerConstant.LOGIN_USER);
UserInfoTo userInfo = new UserInfoTo();
if (member != null) {
//用户已登录
userInfo.setUserId(member.getId());
}
//未登录,一定分配临时用户user-key
Cookie[] cookies = request.getCookies();
if (cookies != null && cookies.length > 0) {
for (Cookie cookie : cookies) {
//user-key,判断cookie名字是否相同,相同就是登录成功
String name = cookie.getName();
if (name.equals(CartConstant.TEMP_USER_COOKIE_NAME)) {
userInfo.setUserKey(cookie.getValue());
//是临时用户
userInfo.setTempUser(true);
}
}
}
//如果没有临时用户,一定分配一个临时用户
if (StringUtils.isEmpty(userInfo.getUserKey())) {
String uuid = UUID.randomUUID().toString();
userInfo.setUserKey(uuid);
}
//目标方法执行之前,数据放入threadLocal。
threadLocal.set(userInfo);
return true;
}
//目标方法执行后:分配临时用户,让浏览器保存
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
UserInfoTo userInfo = threadLocal.get();
//判断是否有临时用户。如果没有临时用户,一定保存一个临时用户
if (!userInfo.getTempUser()) {
//持续延长临时用户的过期时间
Cookie cookie = new Cookie(CartConstant.TEMP_USER_COOKIE_NAME, userInfo.getUserKey());
cookie.setDomain("gulimall.com");//cook域名
cookie.setMaxAge(CartConstant.TEMP_USER_COOKIE_TIMEOUT);//cookie过期时间,一个月
response.addCookie(cookie);
}
}
}
3).GulimallWebConfig,添加拦截器,拦截所有请求
@Configuration
public class GulimallWebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
//拦截所有请求
registry.addInterceptor(new CartInterceptor()).addPathPatterns("/**");
}
}
4).请求经过拦截器后,执行方法,get获取threadLocal用户的信息
@GetMapping("/cart.html")
public String cartPage(){
//1.快速得到用户信息。id,user-key
//threadLocal,保证拦截器-->controller-->service-->dao是一个线程执行(同一个线程共享数据)
UserInfoTo userInfoTo = CartInterceptor.threadLocal.get();
System.out.println("userInfoTo"+userInfoTo);
return "cartList";
}