购物车
购物车
购物车分为用户登录购物车和未登录购物车操作,国内知名电商京东用户登录和不登录都可以操作购物车,如果用户不登录,操作购物车可以将数据存储到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);
//把购物车保存到redis
void saveCartToRedis(String username, Map<String, OrderItem> newCartMap);
//从购物车拿到redis
Map<String,OrderItem> getCartFromRedis(String username);
//合并cookie和redis
Map<String,OrderItem> merge(Map<String, OrderItem> cookieCartMap, Map<String, OrderItem> redisCartMap);
}
分为两块,添加购物车和查询购物车
天猫登录情况下实现
天猫是基于登录的情况下去实现购物车的添加,所以直接保存在redis中
添加
@Override
public 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();
//封装orderItem
orderItem = 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;
}
查询
//查询购物车列表数据
@Override
public 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 新购物车
*/
@Override
public 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
*/
@Override
public 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_";
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private SkuFeign skuFeign;
@Autowired
private SpuFeign spuFeign;
@Override
public 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();
//封装orderItem
orderItem = this.sku2OrderItem(sku,spu,num);
}
//3.将orderitem添加到redis中
redisTemplate.boundHashOps(CART+username).put(skuId,orderItem);
}
//查询购物车列表数据
@Override
public 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 新购物车
*/
@Override
public 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
*/
@Override
public void saveCartToRedis(String username, Map<String, OrderItem> newCartMap) {
redisTemplate.boundHashOps(Contatns.CART_REDIS_NAME+username).putAll(newCartMap);
}
/**
* 根据用户名 从redis中获取购物车
* @param username
* @return
*/
@Override
public 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
*/
@Override
public 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 {
@Autowired
private CartService cartService;
//
@Autowired
private 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){
//校验Jwt
Jwt 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";
@Autowired
private AuthService authService;
@Override
public 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();
}
@Override
public 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-web
main:
allow-bean-definition-overriding: true #当遇到同样名字的时候,是否允许覆盖注册
thymeleaf:
cache: false
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:6868/eureka
instance:
prefer-ip-address: true
feign:
hystrix:
enabled: true
client:
config:
default: #配置全局的feign的调用超时时间 如果 有指定的服务配置 默认的配置不会生效
connectTimeout: 60000 # 指定的是 消费者 连接服务提供者的连接超时时间 是否能连接 单位是毫秒
readTimeout: 80000 # 指定的是调用服务提供者的 服务 的超时时间() 单位是毫秒
#hystrix 配置
hystrix:
command:
default:
execution:
timeout:
#如果enabled设置为false,则请求超时交给ribbon控制
enabled: true
isolation:
strategy: SEMAPHORE
thread:
# 熔断器超时时间,默认: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 {
@Autowired
private CartFeign cartFeign;
/**
* 查询
*/
@GetMapping("/list")
public String list(Model model){
Map map = cartFeign.list();
model.addAttribute("items",map);
return "cart";
}
/**
* 添加购物车
*/
@GetMapping("/add")
@ResponseBody
public 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 购物车渲染服务、订单服务对接网关
- 修改微服务网关
changgou-gateway-web
的application.yml配置文件,添加order的路由过滤配置,配置如下:
#订单微服务
- id: changgou_order_route
uri: lb://order
predicates:
- 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_route
uri: lb://order-web
predicates:
- 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 {
@Override
public 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);
}
}
}
}
}
}
}
- 更改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去实现登录成功后要跳转的页面