购物车

  • 购物车
    • 一、购物车业务逻辑
      • 登录情况(天猫)
      • 不登录情况(京东)
    • 二、实现思路和代码实现
      • 天猫登录情况下实现
        • 添加
        • 查询
      • 京东不登录情况下实现
        • 添加购物车
        • 查询
    • 三、购物车渲染
      • 3.1 购物车渲染服务搭建
      • 3.2 购物车列表渲染
        • 3.2.1 Feign创建
        • 3.2.2 后台代码
        • 3.2.3 前端页面
        • 3.2.4 购物车渲染服务、订单服务对接网关
      • 3.3 商品数量变更
      • 3.4微服务间认证
        • 3.4.1feign拦截器实现微服务间认证
      • 3.5登录页面跳转

购物车

购物车分为用户登录购物车和未登录购物车操作,国内知名电商京东用户登录和不登录都可以操作购物车,如果用户不登录,操作购物车可以将数据存储到Cookie,用户登录后购物车数据可以存储到Redis中,再将之前未登录加入的购物车合并到Redis中即可。

淘宝天猫则采用了另外一种实现方案,用户要想将商品加入购物车,必须先登录才能操作购物车。

一、购物车业务逻辑

(1)需求分析

用户在商品详细页点击加入购物车,提交商品SKU编号和购买数量,添加到购物车。购物车展示页面如下:

(2)购物车实现思路

登录情况(天猫)

用户登录,记录都保存在服务端redis,可以进行一些数据分析和广告推送什么的,下次用户登录的时候。

cookie 保存格式
id orderItem
id orderItem
redis 保存格式
hash field value
用户id 商品id orderItem

不登录情况(京东)

用户不登录,操作购物车可以将数据存储到Cookie中,用户登录后把购物车数据存储到Redis中。用户在未登录的情况下有已添加购物车商品,在登录后需cookie和redis做个合并,最终保存在redis中,并清理对应的cookie

(3)表结构分析

用户登录后将商品加入购物车,需要存储商品详情以及购买数量,购物车详情表如下:

CREATE TABLE `tb_order_item` (`id` varchar(20) COLLATE utf8_bin NOT NULL COMMENT 'ID',`category_id1` int(11) DEFAULT NULL COMMENT '1级分类',`category_id2` int(11) DEFAULT NULL COMMENT '2级分类',`category_id3` int(11) DEFAULT NULL COMMENT '3级分类',`spu_id` varchar(20) COLLATE utf8_bin DEFAULT NULL COMMENT 'SPU_ID',`sku_id` bigint(20) NOT NULL COMMENT 'SKU_ID',`order_id` bigint(20) NOT NULL COMMENT '订单ID',`name` varchar(200) COLLATE utf8_bin DEFAULT NULL COMMENT '商品名称',`price` int(20) DEFAULT NULL COMMENT '单价',`num` int(10) DEFAULT NULL COMMENT '数量',`money` int(20) DEFAULT NULL COMMENT '总金额',`pay_money` int(11) DEFAULT NULL COMMENT '实付金额',`image` varchar(200) COLLATE utf8_bin DEFAULT NULL COMMENT '图片地址',`weight` int(11) DEFAULT NULL COMMENT '重量',`post_fee` int(11) DEFAULT NULL COMMENT '运费',`is_return` char(1) COLLATE utf8_bin DEFAULT NULL COMMENT '是否退货',PRIMARY KEY (`id`),KEY `item_id` (`sku_id`),KEY `order_id` (`order_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;

二、实现思路和代码实现

先定义一个类 统一cookie和redis的名字

public class Contatns {public static final String CART_COOKIE_NAME="cart";public static final String CART_REDIS_NAME="cart_";
}

总的接口信息 不登录和登录都有了


public interface CartService {//添加购物车void addCart(String skuId,Integer num,String username);//查询购物车数据Map list(String username);//Map<String,OrderItem> addCart2(String skuId,Integer num,Map<String,OrderItem> oldCart);//把购物车保存到redisvoid saveCartToRedis(String username, Map<String, OrderItem> newCartMap);//从购物车拿到redisMap<String,OrderItem> getCartFromRedis(String username);//合并cookie和redisMap<String,OrderItem> merge(Map<String, OrderItem> cookieCartMap, Map<String, OrderItem> redisCartMap);
}

分为两块,添加购物车和查询购物车

天猫登录情况下实现

天猫是基于登录的情况下去实现购物车的添加,所以直接保存在redis中

添加

 @Overridepublic void addCart(String skuId, Integer num, String username) {//1.查询redis中相对应的商品信息OrderItem orderItem = (OrderItem) redisTemplate.boundHashOps(CART+username).get(skuId);if (orderItem != null){//2.如果当前商品在redis中的存在,则更新商品的数量与价钱orderItem.setNum(orderItem.getNum()+num);//也有可能是减的情况,小于0要删除不然会出负数if (orderItem.getNum()<=0){//删除该商品redisTemplate.boundHashOps(CART+username).delete(skuId);return;}orderItem.setMoney(orderItem.getNum()*orderItem.getPrice());orderItem.setPayMoney(orderItem.getNum()*orderItem.getPrice());}else {//3.如果当前商品在redis中不存在,将商品添加到redis中//需要注入喔Sku sku = skuFeign.findById(skuId).getData();Spu spu = spuFeign.findSpuById(sku.getSpuId()).getData();//封装orderItemorderItem = this.sku2OrderItem(sku,spu,num);}//3.将orderitem添加到redis中redisTemplate.boundHashOps(CART+username).put(skuId,orderItem);}

sku2OrderItem抽取出来使用

 private OrderItem sku2OrderItem(Sku sku, Spu spu, Integer num) {OrderItem orderItem = new OrderItem();orderItem.setSpuId(sku.getSpuId());orderItem.setSkuId(sku.getId());orderItem.setName(sku.getName());orderItem.setPrice(sku.getPrice());orderItem.setNum(num);orderItem.setMoney(orderItem.getPrice()*num);orderItem.setPayMoney(orderItem.getPrice()*num);orderItem.setImage(sku.getImage());orderItem.setWeight(sku.getWeight()*num);//分类信息orderItem.setCategoryId1(spu.getCategory1Id());orderItem.setCategoryId2(spu.getCategory2Id());orderItem.setCategoryId3(spu.getCategory3Id());return orderItem;}

查询

//查询购物车列表数据@Overridepublic Map list(String username) {Map map = new HashMap();List<OrderItem> orderItemList = redisTemplate.boundHashOps(CART + username).values();map.put("orderItemList",orderItemList);//商品的总数量与总价格Integer totalNum = 0;Integer totalMoney = 0;for (OrderItem orderItem : orderItemList) {totalNum+=orderItem.getNum();totalMoney+=orderItem.getMoney();}map.put("totalNum",totalNum);map.put("totalMoney",totalMoney);return map;}

京东不登录情况下实现

因为没有登录就可以实现购物车的添加,所以先保存在cookie中,登录后再把cookie和redis数据进行合并,并清除对应的cookie

添加购物车

    /*** 添加购物车,不关系购物车存储位置* @param skuId* @param num* @param oldCartMap 老购物车* @return 新购物车*/@Overridepublic Map<String, OrderItem> addCart2(String skuId, Integer num, Map<String, OrderItem> oldCartMap) {//购物车是否存在 现在购物项 根据商品名去找OrderItem orderItem = oldCartMap.get(skuId);if(orderItem!=null){//存在,数量,金额orderItem.setNum(orderItem.getNum()+num);if(orderItem.getNum()<=0){//数量小于等于0,删除此购物项,从购物车中oldCartMap.remove(skuId);}orderItem.setMoney(orderItem.getNum()*orderItem.getPrice());orderItem.setPayMoney(orderItem.getNum()*orderItem.getPrice());//p补充:money-优惠金额=payMoney}else{新购物项添加到购物车//3.如果当前商品在redis中不存在,将商品添加到redis中Sku sku = skuFeign.findById(skuId).getData();Spu spu = spuFeign.findSpuById(sku.getSpuId()).getData();//封装orderItem:新的购物项,orderItem = this.sku2OrderItem(sku,spu,num);//放到购物车oldCartMap.put(skuId,orderItem);}return oldCartMap;}

查询

当点击查询,需要做合并了

  /*** 合并购物车* @param oldCart* @param newCart* @return*/@Overridepublic Map<String, OrderItem> merge(Map<String, OrderItem> oldCart, Map<String, OrderItem> newCart) {for (String key : newCart.keySet()) {OrderItem newOrderItem = newCart.get("key");//新的购物项//判断老的购物车是否存在 新的购物项,商品OrderItem oldOrderItem = oldCart.get(newOrderItem.getSkuId());if(oldOrderItem!=null){//如果存在 ,数量改变,价格,oldOrderItem.setNum(oldOrderItem.getNum()+newOrderItem.getNum());//改变数量oldOrderItem.setMoney(oldOrderItem.getNum()*oldOrderItem.getPrice());//改变小计oldOrderItem.setPayMoney(oldOrderItem.getNum()*oldOrderItem.getPrice());//改变小计}else{//如果不存在,直接追加newCart.put(newOrderItem.getId(),newOrderItem);}}return oldCart;//返回合并后的购物车,如果newCart不存在,直接返回oldCart,如果newCart存在,正常进循环,向oldCart中添加 ,  返回合并后的oldCart}

总的ServiceImpl

@Service
public class CartServiceImpl implements CartService {private static final String CART="cart_";@Autowiredprivate RedisTemplate redisTemplate;@Autowiredprivate SkuFeign skuFeign;@Autowiredprivate SpuFeign spuFeign;@Overridepublic void addCart(String skuId, Integer num, String username) {//1.查询redis中相对应的商品信息OrderItem orderItem = (OrderItem) redisTemplate.boundHashOps(CART+username).get(skuId);if (orderItem != null){//2.如果当前商品在redis中的存在,则更新商品的数量与价钱orderItem.setNum(orderItem.getNum()+num);//也有可能是减的情况,小于0要删除不然会出负数if (orderItem.getNum()<=0){//删除该商品redisTemplate.boundHashOps(CART+username).delete(skuId);return;}orderItem.setMoney(orderItem.getNum()*orderItem.getPrice());orderItem.setPayMoney(orderItem.getNum()*orderItem.getPrice());}else {//3.如果当前商品在redis中不存在,将商品添加到redis中Sku sku = skuFeign.findById(skuId).getData();Spu spu = spuFeign.findSpuById(sku.getSpuId()).getData();//封装orderItemorderItem = this.sku2OrderItem(sku,spu,num);}//3.将orderitem添加到redis中redisTemplate.boundHashOps(CART+username).put(skuId,orderItem);}//查询购物车列表数据@Overridepublic Map list(String username) {Map map = new HashMap();List<OrderItem> orderItemList = redisTemplate.boundHashOps(CART + username).values();map.put("orderItemList",orderItemList);//商品的总数量与总价格Integer totalNum = 0;Integer totalMoney = 0;for (OrderItem orderItem : orderItemList) {totalNum+=orderItem.getNum();totalMoney+=orderItem.getMoney();}map.put("totalNum",totalNum);map.put("totalMoney",totalMoney);return map;}private OrderItem sku2OrderItem(Sku sku, Spu spu, Integer num) {OrderItem orderItem = new OrderItem();orderItem.setSpuId(sku.getSpuId());orderItem.setSkuId(sku.getId());orderItem.setName(sku.getName());orderItem.setPrice(sku.getPrice());orderItem.setNum(num);orderItem.setMoney(orderItem.getPrice()*num);orderItem.setPayMoney(orderItem.getPrice()*num);orderItem.setImage(sku.getImage());orderItem.setWeight(sku.getWeight()*num);//分类信息orderItem.setCategoryId1(spu.getCategory1Id());orderItem.setCategoryId2(spu.getCategory2Id());orderItem.setCategoryId3(spu.getCategory3Id());return orderItem;}/*** 添加购物车,不关系购物车存储位置* @param skuId* @param num* @param oldCartMap 老购物车* @return 新购物车*/@Overridepublic Map<String, OrderItem> addCart2(String skuId, Integer num, Map<String, OrderItem> oldCartMap) {//购物车是否存在 现在购物项 根据商品名去找OrderItem orderItem = oldCartMap.get(skuId);if(orderItem!=null){//存在,数量,金额orderItem.setNum(orderItem.getNum()+num);if(orderItem.getNum()<=0){//数量小于等于0,删除此购物项,从购物车中oldCartMap.remove(skuId);}orderItem.setMoney(orderItem.getNum()*orderItem.getPrice());orderItem.setPayMoney(orderItem.getNum()*orderItem.getPrice());//p补充:money-优惠金额=payMoney}else{新购物项添加到购物车//3.如果当前商品在redis中不存在,将商品添加到redis中Sku sku = skuFeign.findById(skuId).getData();Spu spu = spuFeign.findSpuById(sku.getSpuId()).getData();//封装orderItem:新的购物项,orderItem = this.sku2OrderItem(sku,spu,num);//放到购物车oldCartMap.put(skuId,orderItem);}return oldCartMap;}/*** 把购物车存储到redis中* @param username* @param newCartMap*/@Overridepublic void saveCartToRedis(String username, Map<String, OrderItem> newCartMap) {redisTemplate.boundHashOps(Contatns.CART_REDIS_NAME+username).putAll(newCartMap);}/*** 根据用户名 从redis中获取购物车* @param username* @return*/@Overridepublic Map<String, OrderItem> getCartFromRedis(String username) {Map<String, OrderItem>  cart = redisTemplate.boundHashOps(Contatns.CART_REDIS_NAME + username).entries();if(cart==null || cart.size()<=0){cart=new HashMap<>();}return cart;}/*** 合并购物车* @param oldCart* @param newCart* @return*/@Overridepublic Map<String, OrderItem> merge(Map<String, OrderItem> oldCart, Map<String, OrderItem> newCart) {for (String key : newCart.keySet()) {OrderItem newOrderItem = newCart.get("key");//新的购物项//判断老的购物车是否存在 新的购物项,商品OrderItem oldOrderItem = oldCart.get(newOrderItem.getSkuId());if(oldOrderItem!=null){//如果存在 ,数量改变,价格,oldOrderItem.setNum(oldOrderItem.getNum()+newOrderItem.getNum());//改变数量oldOrderItem.setMoney(oldOrderItem.getNum()*oldOrderItem.getPrice());//改变小计oldOrderItem.setPayMoney(oldOrderItem.getNum()*oldOrderItem.getPrice());//改变小计}else{//如果不存在,直接追加newCart.put(newOrderItem.getId(),newOrderItem);}}return oldCart;//返回合并后的购物车,如果newCart不存在,直接返回oldCart,如果newCart存在,正常进循环,向oldCart中添加 ,  返回合并后的oldCart}
}

接下来是controller层了

@RestController
@RequestMapping("/cart")
public class CartController {@Autowiredprivate CartService cartService;
//@Autowiredprivate TokenDecode tokenDecode;@GetMapping("/addCart")public Result addCart(@RequestParam("skuId") String skuId, @RequestParam("num") Integer num){//动态获取当前人信息,暂时静态
//        String username = "itcast";String username = tokenDecode.getUserInfo().get("username");cartService.addCart(skuId,num,username);return new Result(true, StatusCode.OK,"加入购物车成功");}@GetMapping("/list")public Map list(){//动态获取当前人信息,暂时静态
//        String username = "itcast";String username = tokenDecode.getUserInfo().get("username");Map map = cartService.list(username);return map;}/*** 添加购物车* @param skuId* @param num* @return*/@GetMapping("addCart2")public Result addCart2(@RequestParam("skuId") String skuId, @RequestParam("num") Integer num,HttpServletRequest request,HttpServletResponse response){String username = tokenDecode.getUserInfo().get("username");//获取购物车Map<String, OrderItem> oldCartMap = this.getCart(request,response);//添加购物车业务Map<String, OrderItem> newCartMap = cartService.addCart2(skuId, num, oldCartMap);if("anonymousUser".equals(username)){//未登录,存cookie//新购物车转转json,String newCartJson = JSON.toJSONString(newCartMap);//写到cookie中CookieUtil.setCookie(request,response, Contatns.CART_COOKIE_NAME,newCartJson,60*60*24*7,"utf-8");}else{//登录,存redis中cartService.saveCartToRedis(username,newCartMap);}return  new Result(true, StatusCode.OK,"加入购物车成功",newCartMap);}/*** 获取老的购物车* @return*/private  Map<String, OrderItem> getCart(HttpServletRequest request,HttpServletResponse response){String username = tokenDecode.getUserInfo().get("username");//从cookie获取购物车String cartValue = CookieUtil.getCookieValue(request, Contatns.CART_COOKIE_NAME, "utf-8");if(StringUtils.isEmpty(cartValue)){cartValue="{}";}//转换为map结构  这里因为value是自定义对象map.class会转化异常Map<String, OrderItem>  cookieCartMap = JSON.parseObject(cartValue, new TypeReference<Map<String,OrderItem>>(){});//判断是否登录,登录不成功if("anonymousUser".equals(username)){System.out.println("从cookie中获取购物车,并返回");return cookieCartMap;}else{//登录从redis中获取Map<String, OrderItem>  redisCartMap=cartService.getCartFromRedis(username);if(cookieCartMap!=null && cookieCartMap.size()>0){//合并购物车redisCartMap= cartService.merge(cookieCartMap,redisCartMap);//合并后的购物车存到redis中cartService.saveCartToRedis(username,redisCartMap);//清理cookie中购物车CookieUtil.deleteCookie(request,response,Contatns.CART_COOKIE_NAME);}return redisCartMap;}}/*** 查询购物车* @param request* @return*/@GetMapping("list2")public Result list2(HttpServletRequest request,HttpServletResponse response){Map<String, OrderItem> cart = this.getCart(request,response);return new Result(true, StatusCode.OK,"查询购物车成功",cart);}
}

这里的anonymousUser是token鉴权的时候,如果类型是游客,就会放username和anonymousUser放进去

public class TokenDecode {//公钥private static final String PUBLIC_KEY = "public.key";private static String publickey="";/**** 获取用户信息* @return*/public Map<String,String> getUserInfo(){Authentication authentication = SecurityContextHolder.getContext().getAuthentication();if(authentication instanceof AnonymousAuthenticationToken){Object principal = authentication.getPrincipal();Map<String,String> map=new HashMap<>();map.put("username",principal.toString());return map;}//获取授权信息OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) SecurityContextHolder.getContext().getAuthentication().getDetails();//令牌解码return dcodeToken(details.getTokenValue());}/**** 读取令牌数据*/public Map<String,String> dcodeToken(String token){//校验JwtJwt jwt = JwtHelper.decodeAndVerify(token, new RsaVerifier(getPubKey()));//获取Jwt原始内容String claims = jwt.getClaims();return JSON.parseObject(claims,Map.class);}/*** 获取非对称加密公钥 Key* @return 公钥 Key*/public String getPubKey() {if(!StringUtils.isEmpty(publickey)){return publickey;}Resource resource = new ClassPathResource(PUBLIC_KEY);try {InputStreamReader inputStreamReader = new InputStreamReader(resource.getInputStream());BufferedReader br = new BufferedReader(inputStreamReader);publickey = br.lines().collect(Collectors.joining("\n"));return publickey;} catch (IOException ioe) {return null;}}
}

网关这一块记得放行,在昨天的代码中AuthFilter需要放行这一块的内容

@Component
public class AuthFilter implements GlobalFilter, Ordered {private static final String LOGIN_URL="http://localhost:8001/api/oauth/toLogin";@Autowiredprivate AuthService authService;@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {ServerHttpRequest request = exchange.getRequest();ServerHttpResponse response = exchange.getResponse();//1.判断当前请求路径是否为登录请求,如果是,则直接放行String path = request.getURI().getPath();if ("/api/oauth/login".equals(path) || !UrlFilter.hasAuthorize(path) ){//直接放行return chain.filter(exchange);}else if("/api/cart/list2".equals(path) || "/api/cart/addCart2".equals(path)){//2.从cookie中获取jti的值,如果该值不存在,拒绝本次访问String jti = authService.getJtiFromCookie(request);if(StringUtils.isNotEmpty(jti)){//3.从redis中获取jwt的值,如果该值不存在,拒绝本次访问String jwt = authService.getJwtFromRedis(jti);if(StringUtils.isNotEmpty(jwt)){//4.对当前的请求对象进行增强,让它会携带令牌的信息request.mutate().header("Authorization","Bearer "+jwt);}}return chain.filter(exchange);//不登录放行}//2.从cookie中获取jti的值,如果该值不存在,拒绝本次访问String jti = authService.getJtiFromCookie(request);if (StringUtils.isEmpty(jti)){//拒绝访问/*response.setStatusCode(HttpStatus.UNAUTHORIZED);return response.setComplete();*///跳转登录页面return this.toLoginPage(LOGIN_URL+"?FROM="+request.getURI().getPath(),exchange);}//3.从redis中获取jwt的值,如果该值不存在,拒绝本次访问String jwt = authService.getJwtFromRedis(jti);if (StringUtils.isEmpty(jwt)){//拒绝访问/*response.setStatusCode(HttpStatus.UNAUTHORIZED);return response.setComplete();*/return  this.toLoginPage(LOGIN_URL,exchange);}//4.对当前的请求对象进行增强,让它会携带令牌的信息request.mutate().header("Authorization","Bearer "+jwt);return chain.filter(exchange);}//跳转登录页面private Mono<Void> toLoginPage(String loginUrl, ServerWebExchange exchange) {ServerHttpResponse response = exchange.getResponse();response.setStatusCode(HttpStatus.SEE_OTHER);response.getHeaders().set("Location",loginUrl);return response.setComplete();}@Overridepublic int getOrder() {return 0;}
}

三、购物车渲染

3.1 购物车渲染服务搭建

    <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency>

application.yml配置

server:port: 9011
spring:application:name: order-webmain:allow-bean-definition-overriding: true   #当遇到同样名字的时候,是否允许覆盖注册thymeleaf:cache: false
eureka:client:service-url:defaultZone: http://127.0.0.1:6868/eurekainstance:prefer-ip-address: true
feign:hystrix:enabled: trueclient:config:default:   #配置全局的feign的调用超时时间  如果 有指定的服务配置 默认的配置不会生效connectTimeout: 60000 # 指定的是 消费者 连接服务提供者的连接超时时间 是否能连接  单位是毫秒readTimeout: 80000  # 指定的是调用服务提供者的 服务 的超时时间()  单位是毫秒
#hystrix 配置
hystrix:command:default:execution:timeout:#如果enabled设置为false,则请求超时交给ribbon控制enabled: trueisolation:strategy: SEMAPHOREthread:# 熔断器超时时间,默认:1000/毫秒timeoutInMilliseconds: 80000
#请求处理的超时时间
ribbon:ReadTimeout: 4000#请求连接的超时时间ConnectTimeout: 3000

启动类

@SpringBootApplication
@EnableEurekaClient
public class OrderWebApplication {public static void main(String[] args) {SpringApplication.run(OrderWebApplication.class,args);}
}

3.2 购物车列表渲染

3.2.1 Feign创建

在changgou_service_order_api中添加CartFeign接口,并在接口中创建添加购物车和查询购物车列表,代码如下:

@FeignClient(name="order")
public interface CartFeign {/*** 添加购物车* @param skuId* @param num* @return*/@GetMapping("/cart/add")public Result add(@RequestParam("skuId") String skuId, @RequestParam("num") Integer num);/**** 查询用户购物车列表* @return*/@GetMapping(value = "/cart/list")public Map list();
}

3.2.2 后台代码

在changgou_web_order中创建com.changgou.order.controller.CartController,并添加查询购物车集合方法和添加购物车方法,代码如下:

@Controller
@RequestMapping("/wcart")
public class CartController {@Autowiredprivate CartFeign cartFeign;/*** 查询*/@GetMapping("/list")public String list(Model model){Map map = cartFeign.list();model.addAttribute("items",map);return "cart";}/*** 添加购物车*/@GetMapping("/add")@ResponseBodypublic Result<Map> add(String id, Integer num){cartFeign.add(id, num);Map map = cartFeign.list();return new Result<>(true, StatusCode.OK,"添加购物车成功",map);}
}

3.2.3 前端页面

(1)加载列表数据

我们可以先在网页下面添加一个js脚本,用于加载购物车数据,并用一个对象存储,代码如下:

<!-- vue loadlist --><div class="cart-list" v-for="item in items.orderItemList" :key="item.index"><ul class="goods-list yui3-g"><li class="yui3-u-1-24"><input type="checkbox" name="chk_list" id="" value="" /></li><li class="yui3-u-6-24"><div class="good-item"><div class="item-img"><img :src="item.image" /></div><div class="item-msg"></div></div></li><li class="yui3-u-5-24"><div class="item-txt">{{item.name}}</div></li><li class="yui3-u-1-8"><span class="price">{{item.price}}</span></li><li class="yui3-u-1-8"><a href="javascript:void(0)" @click="add(item.skuId,-1)" class="increment mins">-</a><input autocomplete="off" type="text" v-model="item.num" @blur="add(item.skuId,item.num)" value="1" minnum="1" class="itxt" /><a href="javascript:void(0)" @click="add(item.skuId,1)" class="increment plus">+</a></li><li class="yui3-u-1-8"><span class="sum">{{item.num*item.price}}</span></li><li class="yui3-u-1-8"><a href="#none">删除</a><br /><a href="#none">移到收藏</a></li></ul></div></div></div></div><div class="cart-tool"><div class="select-all"><input class="chooseAll" type="checkbox" name="" id="" value="" /><span>全选</span></div><div class="option"><a href="#none">删除选中的商品</a><a href="#none">移到我的关注</a><a href="#none">清除下柜商品</a></div><div class="money-box"><div class="chosed">已选择<span>{{items.totalNum}}</span>件商品</div><div class="sumprice"><span><em>总价(不含运费) :</em><i class="summoney">¥{{items.totalMoney}}</i></span><span><em>已节省:</em><i>-¥20.00</i></span></div><div class="sumbtn"><a class="sum-btn" href="getOrderInfo.html" target="_blank">结算</a></div></div></div>
<script th:inline="javascript">var app = new Vue({el: '#app',data() {return {items: [[${items}]]}},methods:{add:function (skuId, num) {axios.get("/wcart/add?skuId="+skuId+"&num="+num).then(function (response) {if (response.data.flag){app.items=response.data.data}})}}})</script>

3.2.4 购物车渲染服务、订单服务对接网关

  1. 修改微服务网关changgou-gateway-web的application.yml配置文件,添加order的路由过滤配置,配置如下:
            #订单微服务- id: changgou_order_routeuri: lb://orderpredicates:- Path=/api/cart/**,/api/categoryReport/**,/api/orderConfig/**,/api/order/**,/api/orderItem/**,/api/orderLog/**,/api/preferential/**,/api/returnCause/**,/api/returnOrder/**,/api/returnOrderItem/**filters:- StripPrefix=1#购物车订单渲染微服务- id: changgou_order_web_routeuri: lb://order-webpredicates:- Path=/api/wcart/**,/api/worder/**filters:- StripPrefix=1

3.3 商品数量变更

​ 用户可以点击+号或者-号,或者手动输入一个数字,然后更新购物车列表,我们可以给-+号一个点击事件,给数字框一个失去焦点事件,然后调用后台,实现购物车的更新。

请求后台方法:

在js里面创建一个请求后台的方法,代码如下:

添加事件:

在±号和数字框那里添加点击事件和失去焦点事件,然后调用上面的add方法,代码如下:

3.4微服务间认证


如上图:因为微服务之间并没有传递头文件,所以我们可以定义一个拦截器,每次微服务调用之前都先检查下头文件,将请求的头文件中的令牌数据再放入到header中,再调用其他微服务即可。

3.4.1feign拦截器实现微服务间认证

(1)创建拦截器

在changgou_common服务中创建一个com.changgou.interceptor.FeignInterceptor拦截器,并将所有头文件数据再次加入到Feign请求的微服务头文件中,代码如下:

@Component
public class FeignInterceptor implements RequestInterceptor {@Overridepublic void apply(RequestTemplate requestTemplate) {RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();if (requestAttributes!=null){HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();if (request!=null){Enumeration<String> headerNames = request.getHeaderNames();if (headerNames!=null){while (headerNames.hasMoreElements()){String headerName = headerNames.nextElement();if (headerName.equals("authorization")){String headerValue = request.getHeader(headerName);requestTemplate.header(headerName,headerValue);}}}}}}
}
  1. 更改changgou_order_web启动类,添加拦截器声明
@Bean
public FeignInterceptor feignInterceptor(){return new FeignInterceptor();
}

3配置包扫描

@EnableFeignClients(basePackages = {"com.changgou.user.feign"})

3.5登录页面跳转

直接访问购物车,先进入网关,网关一看还没登录就定向到Oauth服务去登录,登录的时候拼接一下url,比如在url加入key value 这样就能携带过去了,前端通过指定的key去实现登录成功后要跳转的页面

购物车模块实现(类比京东和天猫)相关推荐

  1. HTML期末学生大作业(9套)html+css+javascript仿京东、天猫、服装、各大电商模板(大学毕业设计)

    web前端html+css+js仿京东商城/天猫/电商平台❤功能齐全(大学毕业设计论文) 仿京东/天猫/苏苏/鞋服商城/服装商城/古装商城/商城首页/商城html/商城模板/商城购物/中文电商/商城h ...

  2. mmall商城购物车模块总结

    购物车模块的设计思想 购物车的实现方式有很多,但是最常见的就三种:Cookie,Session,数据库.三种方法各有优劣,适合的场景各不相同.Cookie方法:通过把购物车中的商品数据写入Cookie ...

  3. 在京东、天猫、淘宝都存在情况下,为什么聚美还能崛起?

    咖友提问:在京东,天猫,淘宝都存在情况下,为什么聚美还能起来啊?能崛起根本原因是啥? ▍刘百万 烘焙家 CEO 电商的精髓在于长尾,供应链.如果你是做o2o那就是玩刚需.高频次,最后1km.话说回来, ...

  4. python购物车模块

    # 代码实现: 购物车# 功能要求: 1.用户输入总资产,例如:2000.# 2.显示商品列表,让用户根据序号选择商品,加入购物车购买# 3.如果商品总额大于总资产,提示账户余额不足,否则,购买成功# ...

  5. 购物车模块如何进行测试?

    目录 一. 验证购物车界面设计 二. 购物车功能测试 三. 购物车非功能 测试工作中遇到有商品购买类的项目时,对于购物车模块的测试是无法绕开的.鉴于购物车模块在项目业务中的复杂性,想要对购物车功能模块 ...

  6. Mvp快速搭建商城购物车模块

    代码地址如下: http://www.demodashi.com/demo/12834.html 前言: 说到MVP的时候其实大家都不陌生,但是涉及到实际项目中使用,还是有些无从下手.因此这里小编带着 ...

  7. jQuery实现PC端商城购物车模块基本功能(每个商品的小计和合计都会根据添加和删除的操作来动态计算)

    jQuery实现PC端商城购物车模块基本功能 先上效果图: 因为主要是想练习jQuery的使用,所以页面CSS部分比较简陋,有需要的话,大家在参考代码时,可以自己再完善下CSS部分的代码,让购物车页面 ...

  8. “入圈”高端大获成功!小米10至尊版上市首月霸榜京东、天猫5000元以上档销量第一...

    9月17日上午,小米公司产品总监王腾通过微博分享了小米10至尊纪念版最新的销售情况.他表示,小米10至尊纪念版刚刚上市满月,已经在京东.天猫双平台5000元以上高端手机的销量排行中排名第一. 小米10 ...

  9. Android仿京东、天猫商品详情页

    前言 前面在介绍控件TabLayout控件和CoordinatorLayout使用的时候说了下实现京东.天猫详情页面的效果,今天要说的是优化版,是我们线上实现的效果,首先看一下效果: 项目结构分析 首 ...

最新文章

  1. SPRING 事务管理说明
  2. 熬夜族又一噩耗:“早死”风险更高!
  3. linux – syslog,rsyslog和syslog-ng之间有什么区别?
  4. elasticsearch 安装( 阿里云ECS )、远程访问、启动报错处理
  5. electron增加导航按钮_Electron发布6.0 Released版本
  6. wepack中loader的分类
  7. python基础之迭代器、生成器、装饰器
  8. SPSS问卷或量表调查研究需要多少份或要求多大的样本量?【SPSS 062期】
  9. android自动点击开红包,自动点击连点器
  10. 主析取范式与主合取范式原理探究
  11. 基于c#的winform中图片放大后不清晰问题
  12. 大学生学C语言用什么笔记本电脑,有哪些适合大学生用的笔记本电脑
  13. java partial class_easymock教程-partial class mocking
  14. 【计科快速入门】五、算术逻辑单元
  15. PHP isset()和empty()区别
  16. 中国微团·国京酒业:白酒进入人体后
  17. Dad34 Java对象的内存形式
  18. 小米10pro卡刷教程 卡刷升级官方系统方法
  19. 接入华为联运 / 小米联运 怎么测?
  20. 为什么游戏更新不了服务器维护,自走棋手游更新不了怎么办 更新失败解决方法介绍...

热门文章

  1. python抢票代码_Python自动化xpath实现自动抢票抢货
  2. ResultSet的升级RowSet、离线的CachedRowSet、离线分页查询
  3. lol现在哪个服务器有无限火力,《LOL》国服自选无限火力什么时候出 2020自选无限火力上线时间...
  4. Java期末项目--KFC点餐小界面
  5. 关于 C#使用Console.WriteLine调试没有命令行输出 的解决方法
  6. 2021年中国文化产业发展现状分析:营业收入达119064亿元,占GDP的10.41%[图]
  7. office2007软件下载,WPS2007免费下载
  8. 阿里云RocketMQ:No route info of this topic, com.aliyun.openservices.ons.api.exception
  9. 关于JS出现的Cannot read property 'val' of null错误
  10. java 显示多行歌词_Java Swing制作多行滚动歌词显示控件 | 学步园