1. token刷新

用户登录后,返回了accessToken和refreshToken,其中refreshToken的过期时间比较长,用于续期操作,当用户登录过期之后,使用refreshToken来重新获取token

1.1 接口说明

接口url:/sso/members/refresh/{refreshToken}

请求方式:GET

请求参数:

参数名称参数类型说明
refreshTokenstringtoken,路径参数

返回数据:

{
 "success":true,
 "message":"success",
 "code":2000000000,
 "result":{
     "accessToken":"eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyQ29udGV4dCI6IntcImlkXCI6XCIxXCIsXCJpc1N1cGVyXCI6ZmFsc2UsXCJsb25nVGVybVwiOmZhbHNlLFwibmlja05hbWVcIjpcIm1zemx1XCIsXCJyb2xlXCI6XCJNRU1CRVJcIixcInVzZXJuYW1lXCI6XCJtc3psdVwifSIsInN1YiI6Im1zemx1IiwiZXhwIjoxNjI3MDQ1OTQ1fQ.5O9gTF6WmaDIziWc22dYHKVhcQ7tqRodb_Udtt1ZveY",
"refreshToken":"eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyQ29udGV4dCI6IntcImlkXCI6XCIxXCIsXCJpc1N1cGVyXCI6ZmFsc2UsXCJsb25nVGVybVwiOmZhbHNlLFwibmlja05hbWVcIjpcIm1zemx1XCIsXCJyb2xlXCI6XCJNRU1CRVJcIixcInVzZXJuYW1lXCI6XCJtc3psdVwifSIsInN1YiI6Im1zemx1IiwiZXhwIjoxNjI3NzM3MTQ1fQ.oqRjtBj4izPDu4hyVp3oXJ3YJqcTVq7URWvkbP942KU"
 }
}
1
2
3
4
5
6
7
8
9

返回的数据模型:

package com.mszlu.shop.common.security;

import lombok.Data;

/**
 * Token 实体类
 */
@Data
public class Token {
    /**
     * 访问token
     */
    private String accessToken;

    /**
     * 刷新token
     */
    private String refreshToken;

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

1.2 前端代码修改

接口路径修改,改为sso访问:

api/index.js

import request, {Method,ssoUrl} from '@/plugins/request.js'
/**
 * 刷新token
 */
export function handleRefreshToken (token) {
  return request({
    url: `${ssoUrl}/sso/members/refresh/${token}`,
    method: Method.GET,
    needToken: false
  })
}
1
2
3
4
5
6
7
8
9
10
11

plugin/request.js中

// respone拦截器
service.interceptors.response.use(
  async response => {
    await closeLoading(response);

    return response.data;
  },
  async error => {
    if (process.server) return Promise.reject(error);
    await closeLoading(error);
    const errorResponse = error.response || {};
    const errorData = errorResponse.data || {};

    if (errorResponse.status === 403) {
      isRefreshToken++;

      if (isRefreshToken === 1) {
          //先注释掉,接口没写完,会有问题
        // refresh(error)
        isRefreshToken = 0;
      }
    } else if (errorResponse.status === 404) {
      // 避免刷新token时也提示报错信息
    } else {
      if (error.message) {
        let _message =
          error.code === 'ECONNABORTED'
            ? '连接超时,请稍候再试!'
            : '网络错误,请稍后再试!';
        Message.error(errorData.message || _message);
      }
    }
    return Promise.reject(error);
  }
);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

1.3 Controller代码

 @ApiOperation(value = "刷新token")
    @GetMapping("/refresh/{refreshToken}")
    public Result<Object> refreshToken(@PathVariable String refreshToken) {
        return this.memberService.refreshToken(refreshToken);
    }
1
2
3
4
5

1.4 Service代码

public Result<Object> refreshToken(String refreshToken) {
        Claims claim = TokenUtils.parserToken(refreshToken);
        if (claim == null){
            return Result.fail(BusinessCodeEnum.HTTP_NO_LOGIN.getCode(),"已过期,请重新登录");
        }
        AuthUser authUser = JSON.parseObject(claim.get(SecurityKey.USER_CONTEXT).toString(), AuthUser.class);

        String value = redisTemplate.opsForValue().get(CachePrefix.REFRESH_TOKEN.name() + UserEnums.MEMBER.name() + refreshToken);
        if (StringUtils.isBlank(value)){
            return Result.fail(BusinessCodeEnum.HTTP_NO_LOGIN.getCode(),"已过期,请重新登录");
        }
        Token token = new Token();
        String accessToken = TokenUtils.createToken(authUser.getUsername(), authUser, 7 * 24 * 60L);
        //放入redis当中
        redisTemplate.opsForValue().set(CachePrefix.ACCESS_TOKEN.name() + UserEnums.MEMBER.name() + accessToken, "true",7, TimeUnit.DAYS);
        String newRefreshToken = TokenUtils.createToken(authUser.getUsername(), authUser, 15 * 24 * 60L);
        redisTemplate.opsForValue().set(CachePrefix.REFRESH_TOKEN.name() + UserEnums.MEMBER.name() + refreshToken, "true",15, TimeUnit.DAYS);
        token.setAccessToken(accessToken);
        token.setRefreshToken(newRefreshToken);
        return Result.success(token);
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

TokenUtils:

public static Claims parserToken(String refreshToken) {
        try {
            Claims claims = Jwts.parser()
                    .setSigningKey(SecretKeyUtil.generalKeyByDecoders())
                    .parseClaimsJws(refreshToken).getBody();
            return claims;
        } catch (ExpiredJwtException | UnsupportedJwtException | MalformedJwtException | SignatureException | IllegalArgumentException e) {
            //token 过期 认证失败等
            return null;
        }
    }
1
2
3
4
5
6
7
8
9
10
11

1.5 测试

2. 加入购物车

1.1 接口说明

接口url:/buyer/trade/carts

请求方式:POST

请求参数:

参数名称参数类型说明
skuIdstring产品id
numint购买数量
cartTypestring购物车类型
package com.mszlu.shop.model.buyer.eums;

/**
 * 购物车类型
 */
public enum CartTypeEnum {

    /**
     * 购物车
     */
    CART,
    /**
     * 立即购买
     */
    BUY_NOW,
    /**
     * 拼团
     */
    PINTUAN,
    /**
     * 积分
     */
    POINTS,
    /**
     * 虚拟商品
     */
    VIRTUAL;

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

返回数据:

{
 "success":true,
 "message":"success",
 "code":2000000000,
 "result":null
}
1
2
3
4
5
6

1.2 Controller

package com.mszlu.shop.buyer.controller.trade;

import com.mszlu.shop.buyer.service.trade.BuyerCartService;
import com.mszlu.shop.common.vo.Result;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@Api(tags = "买家端,购物车接口")
@RequestMapping("/trade/carts")
public class CartController {

    /**
     * 购物车
     */
    @Autowired
    private BuyerCartService buyerCartService;


    @ApiOperation(value = "向购物车中添加一个产品")
    @PostMapping
    @ApiImplicitParams({
            @ApiImplicitParam(name = "skuId", value = "产品ID", required = true, dataType = "Long", paramType = "query"),
            @ApiImplicitParam(name = "num", value = "此产品的购买数量", required = true, dataType = "int", paramType = "query"),
            @ApiImplicitParam(name = "cartType", value = "购物车类型,默认加入购物车", paramType = "query")
    })
    public Result<Object> add(String skuId,
                              Integer num,
                              String cartType) {
       //读取选中的列表
       return buyerCartService.add(skuId, num, cartType);
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

1.3 Service

package com.mszlu.shop.buyer.service.trade;

import com.mszlu.shop.buyer.handler.security.UserContext;
import com.mszlu.shop.buyer.service.CartService;
import com.mszlu.shop.buyer.service.GoodsSkuService;
import com.mszlu.shop.common.security.AuthUser;
import com.mszlu.shop.common.vo.Result;
import com.mszlu.shop.model.buyer.eums.CartTypeEnum;
import org.apache.commons.lang3.StringUtils;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.stereotype.Service;

@Service
public class BuyerCartService {

    @DubboReference(version = "1.0.0")
    private CartService cartService;

    public Result<Object> add(String skuId, Integer num, String cartType) {
        //获取购物车类型 默认为购物车
        CartTypeEnum cartTypeEnum = getCartType(cartType);
        AuthUser currentUser = UserContext.getCurrentUser();
        String id = currentUser.getId();
        return cartService.addCart(cartTypeEnum,skuId,num,id);
    }


    private CartTypeEnum getCartType(String cartType) {
        if (StringUtils.isBlank(cartType)){
            return CartTypeEnum.CART;
        }

        try {
            return CartTypeEnum.valueOf(cartType);
        }catch (Exception e){
            return CartTypeEnum.CART;
        }
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

1.4 Dubbo 购物车服务

package com.mszlu.shop.buyer.service.impl.trade;

import com.mszlu.shop.buyer.service.CartService;
import com.mszlu.shop.buyer.service.GoodsSkuService;
import com.mszlu.shop.buyer.service.impl.CacheService;
import com.mszlu.shop.common.eunms.BusinessCodeEnum;
import com.mszlu.shop.common.utils.CurrencyUtil;
import com.mszlu.shop.common.vo.Result;
import com.mszlu.shop.model.buyer.eums.CartTypeEnum;
import com.mszlu.shop.model.buyer.eums.goods.GoodsAuthEnum;
import com.mszlu.shop.model.buyer.eums.goods.GoodsStatusEnum;
import com.mszlu.shop.model.buyer.pojo.goods.GoodsSku;
import com.mszlu.shop.model.buyer.vo.trade.CartSkuVO;
import com.mszlu.shop.model.buyer.vo.trade.TradeVo;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.dubbo.config.annotation.DubboService;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.List;

@DubboService(version = "1.0.0")
public class CartServiceImpl implements CartService {

    @Autowired
    private GoodsSkuService goodsSkuService;

    private GoodsSku goodsSku;

    @Autowired
    private CacheService cacheService;

    @Override
    public Result<Object> addCart(CartTypeEnum cartTypeEnum, String skuId, Integer num, String userId) {
        if (userId == null){
            return Result.fail(BusinessCodeEnum.HTTP_NO_LOGIN.getCode(),"未登录");
        }
        //校验商品有效性,判定失效和库存
        Result<Object> checkGoodsResult = checkGoods(skuId,num);
        if (!checkGoodsResult.isSuccess()){
            return checkGoodsResult;
        }
        //只考虑购物车模式,需要有之前的购物车内容
        if (cartTypeEnum.equals(CartTypeEnum.CART)){
            //先从缓存中获取,获取不到 重新构建
            TradeVo tradeVo = createTradeVo(cartTypeEnum,userId);
            List<CartSkuVO> skuList = tradeVo.getSkuList();
            if (CollectionUtils.isEmpty(skuList)){
                //购物车中无商品
                addGoodsToCart(null,cartTypeEnum, num, skuList);
            }else{
                //购物车不为空
                //查找购物车中有无 此商品
                CartSkuVO cartSkuVO = skuList.stream().filter(i -> i.getGoodsSku().getId().equals(goodsSku.getId())).findFirst().orElse(null);
                if (cartSkuVO != null && cartSkuVO.getGoodsSku().getUpdateTime().equals(goodsSku.getUpdateTime())){
                    //证明此商品有效
                    //库存判断
                    Integer newNum = cartSkuVO.getNum() + num;
                    if (newNum > goodsSku.getQuantity()){
                        newNum = goodsSku.getQuantity();
                    }
                    addGoodsToCart(cartSkuVO,cartTypeEnum,newNum,skuList);
                }else{
                    //需要移除无效的商品
                    skuList.remove(cartSkuVO);
                    addGoodsToCart(null,cartTypeEnum,num,skuList);
                }
            }
            cacheService.set(cartTypeEnum.name() + "_" + userId,tradeVo,null);
        }
        return Result.success();
    }

    private void addGoodsToCart(CartSkuVO cartSkuVO,CartTypeEnum cartTypeEnum, Integer num, List<CartSkuVO> skuList) {
        if (cartSkuVO == null){
            cartSkuVO = new CartSkuVO(goodsSku);
        }
        cartSkuVO.setCartType(cartTypeEnum);
        cartSkuVO.setNum(num);
        //计算价格
        Double price = CurrencyUtil.mul(cartSkuVO.getPurchasePrice(), cartSkuVO.getNum());
        cartSkuVO.setSubTotal(price);
        //新加入的商品为选中状态
        cartSkuVO.setChecked(true);
        skuList.add(cartSkuVO);
    }

    private TradeVo createTradeVo(CartTypeEnum cartTypeEnum,String userId) {
        TradeVo tradeVo = cacheService.get(cartTypeEnum.name() + "_" + userId, TradeVo.class);
        if (tradeVo == null){
            tradeVo = new TradeVo();
            tradeVo.init(cartTypeEnum);
        }
        return tradeVo;
    }

    private Result<Object> checkGoods(String skuId, Integer num) {
        GoodsSku goodsSku = goodsSkuService.findGoodsSkuById(skuId);
        if (goodsSku == null){
            return Result.fail(-999,"商品不存在");
        }
        if (GoodsAuthEnum.PASS.getCode() != goodsSku.getIsAuth()){
            return Result.fail(-999,"商品未认证通过");
        }
        if (GoodsStatusEnum.UPPER.getCode() != goodsSku.getMarketEnable()){
            return Result.fail(-999,"商品已下架");
        }
        Integer quantity = goodsSku.getQuantity();
        if (quantity < num){
            return Result.fail(-999,"商品库存不足");
        }
        this.goodsSku = goodsSku;
        return Result.success();
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
@Override
    public GoodsSku findGoodsSkuById(String skuId) {
        return goodsSkuMapper.selectById(skuId);
    }
1
2
3
4

1.5 涉及到的实体类和工具类

金额计算工具:

package com.mszlu.shop.common.utils;

import java.math.BigDecimal;

/**
 * 金额计算工具
 */
public final class CurrencyUtil {
    /**
     * 默认除法运算精度
     */
    private static final int DEF_DIV_SCALE = 2;

    /**
     * 这个类不能实例化
     */
    private CurrencyUtil() {
    }

    /**
     * 提供精确的加法运算。
     *
     * @param v1 被加数
     * @param v2 加数
     * @return 两个参数的和
     */
    public static Double add(double v1, double v2) {
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        return b1.add(b2).setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
    }

    /**
     * 提供精确的减法运算。
     *
     * @param v1 被减数
     * @param v2 减数
     * @return 两个参数的差
     */
    public static double sub(double v1, double v2) {
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        return b1.subtract(b2).setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
    }

    /**
     * 提供精确的乘法运算。
     *
     * @param v1 被乘数
     * @param v2 乘数
     * @return 两个参数的积
     */
    public static Double mul(double v1, double v2) {
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        return b1.multiply(b2).setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
    }

    /**
     * 提供精确的乘法运算。
     *
     * @param v1 被乘数
     * @param v2 乘数
     * @param scale 表示表示需要精确到小数点以后几位。
     * @return 两个参数的积
     */
    public static Double mul(double v1, double v2, int scale) {
        if (scale < 0) {
            throw new IllegalArgumentException(
                    "The scale must be a positive integer or zero");
        }
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        return b1.multiply(b2).setScale(scale, BigDecimal.ROUND_HALF_UP).doubleValue();
    }

    /**
     * 提供(相对)精确的除法运算,当发生除不尽的情况时, 精确到小数点以后10位,以后的数字四舍五入。
     *
     * @param v1 被除数
     * @param v2 除数
     * @return 两个参数的商
     */
    public static double div(double v1, double v2) {
        return div(v1, v2, DEF_DIV_SCALE);
    }

    /**
     * 提供(相对)精确的除法运算。 当发生除不尽的情况时,由scale参数指定精度,以后的数字四舍五入。
     *
     * @param v1    被除数
     * @param v2    除数
     * @param scale 表示表示需要精确到小数点以后几位。
     * @return 两个参数的商
     */
    public static double div(double v1, double v2, int scale) {
        if (scale < 0) {
            throw new IllegalArgumentException(
                    "The scale must be a positive integer or zero");
        }
        //如果被除数等于0,则返回0
        if (v2 == 0) {
            return 0;
        }
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        return b1.divide(b2, scale, BigDecimal.ROUND_HALF_UP).doubleValue();
    }

    /**
     * 提供精确的小数位四舍五入处理。
     *
     * @param v     需要四舍五入的数字
     * @param scale 小数点后保留几位
     * @return 四舍五入后的结果
     */
    public static double round(double v, int scale) {
        if (scale < 0) {
            throw new IllegalArgumentException(
                    "The scale must be a positive integer or zero");
        }
        BigDecimal b = new BigDecimal(Double.toString(v));
        BigDecimal one = new BigDecimal("1");
        return b.divide(one, scale, BigDecimal.ROUND_HALF_UP).doubleValue();
    }

    /**
     * 金额转分
     *
     * @param money
     * @return
     */
    public static Integer fen(Double money) {
        double price = mul(money, 100);
        return (int) price;
    }

    /**
     * 金额转分
     *
     * @param money
     * @return
     */
    public static double reversalFen(Double money) {
        double price = div(money, 100);
        return price;
    }

    public static void main(String[] args) {
        System.out.println(fen(23.4324));
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152

缓存服务:

package com.mszlu.shop.buyer.service.impl;

import com.alibaba.fastjson.JSON;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

@Service
public class CacheService {

    @Autowired
    private StringRedisTemplate redisTemplate;

    public <T> T get(String key, Class<T> clazz){
        String redisValue = redisTemplate.opsForValue().get(key);
        if (StringUtils.isBlank(redisValue)){
            return null;
        }
        T parseObject = JSON.parseObject(redisValue, clazz);
        return parseObject;
    }

    public void set(String key, Object data, Long expire){
        if (expire == null){
            redisTemplate.opsForValue().set(key,JSON.toJSONString(data));
        }else {
            redisTemplate.opsForValue().set(key, JSON.toJSONString(data), expire, TimeUnit.SECONDS);
        }
    }

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

购物车视图:

package com.mszlu.shop.model.buyer.vo.trade;

import com.mszlu.shop.model.buyer.eums.CartTypeEnum;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

/**
 * 购物车视图
 */
@Data
public class TradeVo implements Serializable {

    @ApiModelProperty(value = "sn")
    private String sn;

    @ApiModelProperty(value = "是否为其他订单下的订单,如果是则为依赖订单的sn,否则为空")
    private String parentOrderSn;

    @ApiModelProperty(value = "购物车列表")
    private List<CartVO> cartList;

    @ApiModelProperty(value = "整笔交易中所有的规格商品")
    private List<CartSkuVO> skuList;

    @ApiModelProperty(value = "购物车车计算后的总价")
    private PriceDetailVO priceDetailVO;

    /**
     * 购物车类型
     */
    private CartTypeEnum cartTypeEnum;


    /**
     * 客户端类型
     */
    private String clientType;

    /**
     * 买家名称
     */
    private String memberName;

    /**
     * 买家id
     */
    private String memberId;

    /**
     * 分销商id
     */
    private String distributionId;

    public void init(CartTypeEnum cartTypeEnum) {
        this.skuList = new ArrayList<>();
        this.cartList = new ArrayList<>();
        this.cartTypeEnum = cartTypeEnum;
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65

购物车展示VO:

package com.mszlu.shop.model.buyer.vo.trade;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;


/**
 * 购物车展示VO
 *
 */
@Data
@ApiModel(description = "购物车")
@NoArgsConstructor
public class CartVO  implements Serializable {

    @ApiModelProperty(value = "购物车中的产品列表")
    private List<CartSkuVO> skuList;

    @ApiModelProperty(value = "sn")
    private String sn;

    @ApiModelProperty(value = "购物车页展示时,店铺内的商品是否全选状态.1为店铺商品全选状态,0位非全选")
    private Boolean checked;

    @ApiModelProperty(value = "重量")
    private Double weight;

    @ApiModelProperty(value = "购物车商品数量")
    private Integer goodsNum;

    @ApiModelProperty(value = "购物车商品数量")
    private String remark;

    @ApiModelProperty(value = "卖家id")
    private String storeId;

    @ApiModelProperty(value = "卖家姓名")
    private String storeName;

    @ApiModelProperty(value = "此商品价格展示")
    private PriceDetailVO priceDetailVO;

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

购物车产品:

package com.mszlu.shop.model.buyer.vo.trade;

import com.mszlu.shop.model.buyer.eums.CartTypeEnum;
import com.mszlu.shop.model.buyer.pojo.goods.GoodsSku;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

/**
 * 购物车中的产品
 *
 */
@Data
@NoArgsConstructor
public class CartSkuVO  implements Serializable {


    private String sn;
    /**
     * 对应的sku DO
     */
    private GoodsSku goodsSku;

    @ApiModelProperty(value = "购买数量")
    private Integer num;

    @ApiModelProperty(value = "购买时的成交价")
    private Double purchasePrice;

    @ApiModelProperty(value = "小记")
    private Double subTotal;
    /**
     * 是否选中,要去结算 0:未选中 1:已选中,默认
     */
    @ApiModelProperty(value = "是否选中,要去结算")
    private Boolean checked;

    @ApiModelProperty(value = "是否免运费")
    private Boolean isFreeFreight;

    @ApiModelProperty(value = "积分购买 积分数量")
    private Integer point;

    @ApiModelProperty(value = "是否失效 ")
    private Boolean invalid;

    @ApiModelProperty(value = "购物车商品错误消息")
    private String errorMessage;

    @ApiModelProperty(value = "是否可配送")
    private Boolean isShip;

    /**
     * @see com.mszlu.shop.model.buyer.eums.CartTypeEnum
     */
    @ApiModelProperty(value = "购物车类型")
    private CartTypeEnum cartType;

    @ApiModelProperty(value = "卖家id")
    private String storeId;

    @ApiModelProperty(value = "卖家姓名")
    private String storeName;

    @ApiModelProperty(value = "此商品价格展示")
    private PriceDetailVO priceDetailVO;

    public CartSkuVO(GoodsSku goodsSku) {
        this.goodsSku = goodsSku;
        this.checked = true;
        this.invalid = false;
        this.errorMessage = "";
        this.isShip = true;
        this.purchasePrice = goodsSku.getIsPromotion() != null && goodsSku.getIsPromotion() ? goodsSku.getPromotionPrice().doubleValue() : goodsSku.getPrice().doubleValue();
        this.isFreeFreight = false;
        this.setStoreId(goodsSku.getStoreId());
        this.setStoreName(goodsSku.getStoreName());
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82

3. 加入购物车修改代码

 //过期了,应该进行重新的添加
                    if (cartSkuVO.getGoodsSku().getUpdateTime().equals(goodsSku.getUpdateTime())){
                        Integer oldNum = cartSkuVO.getNum();
                        //需要做库存的判断
                        Integer newNum = oldNum + num;
                        if (newNum > goodsSku.getQuantity()){
                            return Result.fail(-999,"库存不足");
                        }
                        cartSkuVO.setNum(newNum);
                        cartSkuVO.setSubTotal(CurrencyUtil.mul(cartSkuVO.getPurchasePrice(),newNum));
                        //bug修正,需要先删 要不然重复了
                        skuList.remove(cartSkuVO);
                        skuList.add(cartSkuVO);
                    }else{
                        skuList.remove(cartSkuVO);
                        addGoodsToCart(num, goodsSku, skuList,cartTypeEnum);
                    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

在CartSkuVO中添加PriceDetailDTO属性,商品价格流水计算

用于购物车列表中 显示价格

@ApiModelProperty(value = "此商品价格流水计算")
    private PriceDetailDTO priceDetailDTO;

 public CartSkuVO(GoodsSku goodsSku) {
        this.goodsSku = goodsSku;
        this.checked = true;
        this.invalid = false;
        this.errorMessage = "";
        this.isShip = true;
        this.purchasePrice = goodsSku.getIsPromotion() != null && goodsSku.getIsPromotion() ? goodsSku.getPromotionPrice().doubleValue() : goodsSku.getPrice().doubleValue();
        this.isFreeFreight = false;
        this.setStoreId(goodsSku.getStoreId());
        this.setStoreName(goodsSku.getStoreName());
     //初始化 
        this.priceDetailDTO = new PriceDetailDTO();
        this.priceDetailDTO.addGoodsPrice(goodsSku.getPrice().doubleValue());
    }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.mszlu.shop.model.buyer.dtos.trade;


import com.mszlu.shop.common.utils.CurrencyUtil;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

/**
 * 商品流水
 */
@Data
public class PriceDetailDTO implements Serializable {


    @ApiModelProperty(value = "商品总金额(商品原价)")
    private Double goodsPrice;
    @ApiModelProperty(value = "优惠金额")
    private Double discountPrice;
    @ApiModelProperty(value = "优惠券金额")
    private Double couponPrice;
    @ApiModelProperty(value = "配送费")
    private Double freightPrice;
    @ApiModelProperty(value = "订单修改金额")
    private Double updatePrice;

    @ApiModelProperty(value = "单品分销返现支出")
    private Double distributionCommission;

    @ApiModelProperty(value = "平台收取交易佣金")
    private Double platFormCommission;

    @ApiModelProperty(value = "平台优惠券 使用金额")
    private Double siteCouponPrice;

    @ApiModelProperty(value = "站点优惠券佣金比例")
    private Double siteCouponPoint;

    @ApiModelProperty(value = "站点优惠券佣金")
    private Double siteCouponCommission;


    @ApiModelProperty(value = "流水金额(入账 出帐金额) = goodsPrice + freight - discountPrice - couponPrice + updatePrice")
    private Double flowPrice;

    @ApiModelProperty(value = "最终结算金额 = flowPrice - platFormCommission - distributionCommission")
    private Double billPrice;


    public PriceDetailDTO() {
        goodsPrice = 0d;
        freightPrice = 0d;

        discountPrice = 0d;
        couponPrice = 0d;

        distributionCommission = 0d;
        platFormCommission = 0d;

        updatePrice = 0d;

        flowPrice = 0d;
        billPrice = 0d;

        siteCouponPrice = 0d;
        siteCouponPoint = 0d;
        siteCouponCommission = 0d;

    }

    public static PriceDetailDTO accumulationPriceDTO(List<PriceDetailDTO> priceDetailDTOS) {

        PriceDetailDTO priceDetailDTO = new PriceDetailDTO();

        for (PriceDetailDTO priceDetail : priceDetailDTOS) {
            priceDetailDTO.addGoodsPrice(priceDetail.getGoodsPrice());
            priceDetailDTO.addFreightPrice(priceDetail.getFreightPrice());
            priceDetailDTO.addUpdatePrice(priceDetail.getUpdatePrice());
            priceDetailDTO.addDistributionCommission(priceDetail.getDistributionCommission());
            priceDetailDTO.addDiscountPrice(priceDetail.getDiscountPrice());
            priceDetailDTO.addPlatFormCommission(priceDetail.getPlatFormCommission());
            priceDetailDTO.addSiteCouponPrice(priceDetail.getSiteCouponPrice());
            priceDetailDTO.addSiteCouponPoint(priceDetail.getSiteCouponPoint());
            priceDetailDTO.addSiteCouponCommission(priceDetail.getSiteCouponCommission());
            priceDetailDTO.addFlowPrice(priceDetail.getFlowPrice());
            priceDetailDTO.addBillPrice(priceDetail.getBillPrice());
        }
        return priceDetailDTO;
    }

    public void addGoodsPrice(Double goodsPrice) {
        this.goodsPrice = CurrencyUtil.add(this.goodsPrice,goodsPrice);
    }
    public void addFreightPrice(Double freightPrice) {
        this.freightPrice = CurrencyUtil.add(this.freightPrice,freightPrice);
    }

    public void addDiscountPrice(Double discountPrice) {
        this.discountPrice = CurrencyUtil.add(this.discountPrice,discountPrice);
    }
    public void addUpdatePrice(Double updatePrice) {
        this.updatePrice = CurrencyUtil.add(this.updatePrice,updatePrice);
    }
    public void addDistributionCommission(Double distributionCommission) {
        this.distributionCommission = CurrencyUtil.add(this.distributionCommission,distributionCommission);
    }
    public void addPlatFormCommission(Double platFormCommission) {
        this.platFormCommission = CurrencyUtil.add(this.platFormCommission,platFormCommission);
    }
    public void addSiteCouponPrice(Double siteCouponPrice) {
        this.siteCouponPrice = CurrencyUtil.add(this.siteCouponPrice,siteCouponPrice);
    }
    public void addSiteCouponPoint(Double siteCouponPoint) {
        this.siteCouponPoint = CurrencyUtil.add(this.siteCouponPoint,siteCouponPoint);
    }
    public void addSiteCouponCommission(Double siteCouponCommission) {
        this.siteCouponCommission = CurrencyUtil.add(this.siteCouponCommission,siteCouponCommission);
    }
    public void addFlowPrice(Double flowPrice) {
        this.flowPrice = CurrencyUtil.add(this.flowPrice,flowPrice);
    }
    public void addBillPrice(Double billPrice) {
        this.billPrice = CurrencyUtil.add(this.billPrice,billPrice);
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129

将PriceDetailDTO也同时添加到TradeVo中,用于后续计算购物车的价格

 	//价格
    private PriceDetailDTO priceDetailDTO;

1
2
3

4. 我的购物车(难)

4.1 接口说明

接口url:/buyer/trade/all

请求方式:GET

请求参数:

参数名称参数类型说明

返回数据:

{
    "success":true,
    "message":"success",
    "code":2000000000,
    "result":{
        "sn":null,
        "parentOrderSn":null,
        "cartList":[
            {
                "skuList":[
                    {
                        "sn":null,
                        "goodsSku":{
                            "updateTime":"1970-01-19T18:39:31.928+08:00",
                            "createTime":"2021-04-30T11:56:26.757+08:00",
                            "deleteFlag":false,
                            "id":1387979130431078400,
                            "goodsId":1376848829046849536,
                            "simpleSpecs":" 绿色",
                            "freightTemplateId":"1376425599173656576",
                            "isPromotion":null,
                            "promotionPrice":null,
                            "goodsName":"朵唯(DOOV) X11Pro 安卓智能手机  绿色",
                            "sn":"32424",
                            "brandId":"1349563815406886916",
                            "categoryPath":"1348576427264204941,1348576427264204942,1348576427264204943",
                            "goodsUnit":"个",
                            "sellingPoint":"6+128G 翡翠绿 6.53英寸水滴全面屏 4G全网通双卡双待 超薄侧面指纹 八核游戏手机 学生拍照备用老人商务手机",
                            "weight":2,
                            "marketEnable":1,
                            "intro":"<p><img src=\"https://static.mszlu.com/mall/23aa6c57f1ed4eb69116d53e37dd15a1.jpg\" style=\"max-width:100%;\"/><br/></p>",
                            "price":4542,
                            "cost":4324,
                            "viewCount":null,
                            "buyCount":null,
                            "quantity":1000,
                            "grade":null,
                            "thumbnail":"https://static.mszlu.com/mall/47ee79c36c8c40c4ae9d61ead39f35ae.jpg?imageView2/1/w/400/h/400/q/75",
                            "big":null,
                            "small":null,
                            "original":null,
                            "storeCategoryPath":"1376372682651598848",
                            "commentNum":null,
                            "storeId":"1376369067769724928",
                            "storeName":"码神自营",
                            "templateId":null,
                            "isAuth":2,
                            "authMessage":null,
                            "underMessage":null,
                            "selfOperated":false,
                            "mobileIntro":"<p><img src=\"https://static.mszlu.com/mall/23aa6c57f1ed4eb69116d53e37dd15a1.jpg\"/><br/></p>",
                            "goodsVideo":null,
                            "recommend":false,
                            "salesModel":"RETAIL",
                            "goodsType":1
                        },
                        "num":2,
                        "purchasePrice":4542,
                        "subTotal":9084,
                        "checked":true,
                        "isFreeFreight":false,
                        "point":null,
                        "invalid":false,
                        "errorMessage":"",
                        "isShip":true,
                        "cartType":"CART",
                        "storeId":"1376369067769724928",
                        "storeName":"码神自营",
                        "priceDetailDTO":{
                            "goodsPrice":9084,
                            "discountPrice":0,
                            "couponPrice":0,
                            "freightPrice":0,
                            "updatePrice":0,
                            "distributionCommission":0,
                            "platFormCommission":454.2,
                            "siteCouponPrice":0,
                            "siteCouponPoint":0,
                            "siteCouponCommission":0,
                            "flowPrice":9084,
                            "billPrice":9084
                        },
                        "priceDetailVO":null
                    }
                ],
                "sn":null,
                "checked":true,
                "weight":0,
                "goodsNum":2,
                "remark":"",
                "storeId":"1376369067769724928",
                "storeName":"码神自营",
                "priceDetailVO":null,
                "priceDetailDTO":{
                    "goodsPrice":9084,
                    "discountPrice":0,
                    "couponPrice":0,
                    "freightPrice":0,
                    "updatePrice":0,
                    "distributionCommission":0,
                    "platFormCommission":454.2,
                    "siteCouponPrice":0,
                    "siteCouponPoint":0,
                    "siteCouponCommission":0,
                    "flowPrice":9084,
                    "billPrice":9084
                },
                "couponList":[

                ]
            }
        ],
        "skuList":[
            {
                "sn":null,
                "goodsSku":{
                    "updateTime":"1970-01-19T18:39:31.928+08:00",
                    "createTime":"2021-04-30T11:56:26.757+08:00",
                    "deleteFlag":false,
                    "id":1387979130431078400,
                    "goodsId":1376848829046849536,
                    "simpleSpecs":" 绿色",
                    "freightTemplateId":"1376425599173656576",
                    "isPromotion":null,
                    "promotionPrice":null,
                    "goodsName":"朵唯(DOOV) X11Pro 安卓智能手机  绿色",
                    "sn":"32424",
                    "brandId":"1349563815406886916",
                    "categoryPath":"1348576427264204941,1348576427264204942,1348576427264204943",
                    "goodsUnit":"个",
                    "sellingPoint":"6+128G 翡翠绿 6.53英寸水滴全面屏 4G全网通双卡双待 超薄侧面指纹 八核游戏手机 学生拍照备用老人商务手机",
                    "weight":2,
                    "marketEnable":1,
                    "intro":"<p><img src=\"https://static.mszlu.com/mall/23aa6c57f1ed4eb69116d53e37dd15a1.jpg\" style=\"max-width:100%;\"/><br/></p>",
                    "price":4542,
                    "cost":4324,
                    "viewCount":null,
                    "buyCount":null,
                    "quantity":1000,
                    "grade":null,
                    "thumbnail":"https://static.mszlu.com/mall/47ee79c36c8c40c4ae9d61ead39f35ae.jpg?imageView2/1/w/400/h/400/q/75",
                    "big":null,
                    "small":null,
                    "original":null,
                    "storeCategoryPath":"1376372682651598848",
                    "commentNum":null,
                    "storeId":"1376369067769724928",
                    "storeName":"码神自营",
                    "templateId":null,
                    "isAuth":2,
                    "authMessage":null,
                    "underMessage":null,
                    "selfOperated":false,
                    "mobileIntro":"<p><img src=\"https://static.mszlu.com/mall/23aa6c57f1ed4eb69116d53e37dd15a1.jpg\"/><br/></p>",
                    "goodsVideo":null,
                    "recommend":false,
                    "salesModel":"RETAIL",
                    "goodsType":1
                },
                "num":2,
                "purchasePrice":4542,
                "subTotal":9084,
                "checked":true,
                "isFreeFreight":false,
                "point":null,
                "invalid":false,
                "errorMessage":"",
                "isShip":true,
                "cartType":"CART",
                "storeId":"1376369067769724928",
                "storeName":"码神自营",
                "priceDetailDTO":{
                    "goodsPrice":9084,
                    "discountPrice":0,
                    "couponPrice":0,
                    "freightPrice":0,
                    "updatePrice":0,
                    "distributionCommission":0,
                    "platFormCommission":454.2,
                    "siteCouponPrice":0,
                    "siteCouponPoint":0,
                    "siteCouponCommission":0,
                    "flowPrice":9084,
                    "billPrice":9084
                },
                "priceDetailVO":null
            }
        ],
        "priceDetailVO":null,
        "cartTypeEnum":"CART",
        "clientType":null,
        "memberName":null,
        "memberId":null,
        "distributionId":null,
        "priceDetailDTO":{
            "goodsPrice":9084,
            "discountPrice":0,
            "couponPrice":0,
            "freightPrice":0,
            "updatePrice":0,
            "distributionCommission":0,
            "platFormCommission":454.2,
            "siteCouponPrice":0,
            "siteCouponPoint":0,
            "siteCouponCommission":0,
            "flowPrice":9084,
            "billPrice":9084
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210

4.2 Controller

 @ApiOperation(value = "获取购物车页面购物车详情")
    @GetMapping("/all")
    public Result<TradeVo> cartAll() {
        return this.buyerCartService.getAllTrade();
    }
1
2
3
4
5

4.2 Service

 public Result<TradeVo> getAllTrade() {
        AuthUser currentUser = UserContext.getCurrentUser();
        return cartService.buildAllTrade(CartTypeEnum.CART,currentUser.getId());
    }
1
2
3
4

4.3 Dubbo服务

@Autowired
    private TradeBuilder tradeBuilder;

 @Override
    public Result<TradeVo> buildAllTrade(CartTypeEnum cartTypeEnum, String userId) {
        TradeVo tradeVo = tradeBuilder.buildCart(createTradeVo(cartTypeEnum, userId));
        return Result.success(tradeVo);
    }

1
2
3
4
5
6
7
8
9

购物车列表中涉及到的功能比较多,比如需要展示商品,数量,价格,选中状态,优惠券,不同的选中会有不同的价格,从不同地方进入到购物车渲染流程也不一样,所以使用Builder来构建。

在Builder中,根据不同的步骤来进行渲染,此步骤分为:

0-> 校验商品 1-》 满优惠渲染 2->渲染优惠 3->优惠券渲染 4->计算运费 5->计算价格 6->分销渲染 7->其他渲染
1
package com.mszlu.shop.buyer.service.impl.trade;

import com.mszlu.shop.buyer.service.CartService;
import com.mszlu.shop.buyer.service.impl.trade.domain.CartRenderStep;
import com.mszlu.shop.model.buyer.eums.CartTypeEnum;
import com.mszlu.shop.model.buyer.vo.trade.TradeVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
@Slf4j
public class TradeBuilder {

    //购物车渲染步骤
    @Autowired
    private List<CartRenderStep> cartRenderSteps;

    /**
     * 购物车购物车渲染
     * 0-> 校验商品, 1-》 满优惠渲染, 2->渲染优惠,  5->计算价格
     */
//    int[] cartRender = {0, 1, 2, 5};
    /**
     * 购物车购物车渲染
     * 0-> 校验商品  5->计算价格
     */
    int[] cartRender = {0, 5};


    /**
     * 构造购物车
     * 购物车与结算信息不一致的地方主要是优惠券计算和运费计算,其他规则都是一致都
     *
     * @return 购物车展示信息
     */
    public TradeVo buildCart(TradeVo tradeVo) {

        //按照计划进行渲染
        for (int index : cartRender) {
            try {
                cartRenderSteps.get(index).render(tradeVo);
            } catch (Exception e) {
                log.error("购物车{}渲染异常:", cartRenderSteps.get(index).getClass(), e);
            }
        }
        return tradeVo;
    }

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
package com.mszlu.shop.buyer.service.impl.trade.domain;

import com.mszlu.shop.model.buyer.vo.trade.TradeVo;

/**
 * 购物车渲染
 */
public interface CartRenderStep {


    /**
     * 渲染一笔交易
     * 0-> 校验商品 1-》 满优惠渲染 2->渲染优惠 3->优惠券渲染 4->计算运费 5->计算价格 6->分销渲染 7->其他渲染
     *
     */
    void render(TradeVo tradeVo);
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

不同步骤渲染的实现类:

package com.mszlu.shop.buyer.service.impl.trade.domain.render;

import com.mszlu.shop.buyer.service.GoodsSkuService;
import com.mszlu.shop.buyer.service.impl.trade.domain.CartRenderStep;
import com.mszlu.shop.common.utils.CurrencyUtil;
import com.mszlu.shop.model.buyer.eums.goods.GoodsAuthEnum;
import com.mszlu.shop.model.buyer.eums.goods.GoodsStatusEnum;
import com.mszlu.shop.model.buyer.pojo.goods.GoodsSku;
import com.mszlu.shop.model.buyer.vo.trade.CartSkuVO;
import com.mszlu.shop.model.buyer.vo.trade.CartVO;
import com.mszlu.shop.model.buyer.vo.trade.TradeVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 商品有效性校验
 */
@Order(0)
@Service
public class CheckDataRender implements CartRenderStep {

    @Autowired
    private GoodsSkuService goodsSkuService;

    @Override
    public void render(TradeVo tradeVo) {
        //校验商品有效性
        checkData(tradeVo);
        //店铺分组数据初始化
        groupStore(tradeVo);
    }

    private void groupStore(TradeVo tradeVo) {
        //渲染的购物车
        List<CartVO> cartList = new ArrayList<>();

        //根据店铺分组
        Map<String, List<CartSkuVO>> storeCollect = tradeVo.getSkuList().parallelStream().collect(Collectors.groupingBy(CartSkuVO::getStoreId));
        for (Map.Entry<String, List<CartSkuVO>> storeCart : storeCollect.entrySet()) {
            if (!storeCart.getValue().isEmpty()) {
                CartVO cartVO = new CartVO(storeCart.getValue().get(0));
                cartVO.setSkuList(storeCart.getValue());
                storeCart.getValue().stream().filter(i -> Boolean.TRUE.equals(i.getChecked())).findFirst().ifPresent(cartSkuVO -> cartVO.setChecked(true));
                cartList.add(cartVO);
            }
        }
        tradeVo.setCartList(cartList);
    }

    private void checkData(TradeVo tradeVo) {
        //循环购物车中的商品
        for (CartSkuVO cartSkuVO : tradeVo.getSkuList()) {
            //商品信息
            GoodsSku dataSku = goodsSkuService.findGoodsSkuById(cartSkuVO.getGoodsSku().getId().toString());
            //商品有效性判定
            if (dataSku == null || dataSku.getUpdateTime().before(cartSkuVO.getGoodsSku().getUpdateTime())) {
                //设置购物车未选中
                cartSkuVO.setChecked(false);
                //设置购物车此sku商品已失效
                cartSkuVO.setInvalid(true);
                //设置失效消息
                cartSkuVO.setErrorMessage("商品信息发生变化,已失效");
                continue;
            }
            //商品上架状态判定
            if (GoodsAuthEnum.PASS.getCode() != dataSku.getIsAuth() || GoodsStatusEnum.UPPER.getCode() != dataSku.getMarketEnable()) {
                //设置购物车未选中
                cartSkuVO.setChecked(false);
                //设置购物车此sku商品已失效
                cartSkuVO.setInvalid(true);
                //设置失效消息
                cartSkuVO.setErrorMessage("商品已下架");
                continue;
            }
            //商品库存判定
            if (dataSku.getQuantity() < cartSkuVO.getNum()) {
                //设置购物车未选中
                cartSkuVO.setChecked(false);
                //设置失效消息
                cartSkuVO.setErrorMessage("商品库存不足,现有库存数量[" + dataSku.getQuantity() + "]");
            }
            //写入初始价格
            cartSkuVO.getPriceDetailDTO().setGoodsPrice(CurrencyUtil.mul(cartSkuVO.getPurchasePrice(), cartSkuVO.getNum()));
        }
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
package com.mszlu.shop.buyer.service.impl.trade.domain.render;

import com.mszlu.shop.buyer.service.impl.trade.domain.CartRenderStep;
import com.mszlu.shop.model.buyer.vo.trade.TradeVo;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Service;


/**
 * FullDiscountRender 
 */
@Service
@Order(1)
public class FullDiscountRender implements CartRenderStep {


    @Override
    public void render(TradeVo tradeVo) {

    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.mszlu.shop.buyer.service.impl.trade.domain.render;

import com.mszlu.shop.buyer.service.impl.trade.domain.CartRenderStep;
import com.mszlu.shop.model.buyer.vo.trade.TradeVo;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Service;

/**
 * 购物促销信息渲染实现
 */
@Service
@Order(2)
public class SkuPromotionRender implements CartRenderStep {
    @Override
    public void render(TradeVo tradeVo) {

    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.mszlu.shop.buyer.service.impl.trade.domain.render;

import com.mszlu.shop.buyer.service.impl.trade.domain.CartRenderStep;
import com.mszlu.shop.model.buyer.vo.trade.TradeVo;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Service;


/**
 * 购物促销信息渲染实现
 */
@Order(3)
@Service
public class CouponRender implements CartRenderStep {

    @Override
    public void render(TradeVo tradeVo) {

    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.mszlu.shop.buyer.service.impl.trade.domain.render;

import com.mszlu.shop.buyer.service.impl.trade.domain.CartRenderStep;
import com.mszlu.shop.model.buyer.vo.trade.TradeVo;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Service;


/**
 * sku 运费计算
 */
@Order(4)
@Service
public class SkuFreightRender implements CartRenderStep {


    @Override
    public void render(TradeVo tradeVo) {

    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.mszlu.shop.buyer.service.impl.trade.domain.render;

import com.mszlu.shop.buyer.service.CategoryService;
import com.mszlu.shop.buyer.service.impl.trade.domain.CartRenderStep;
import com.mszlu.shop.common.utils.CurrencyUtil;
import com.mszlu.shop.model.buyer.dtos.trade.PriceDetailDTO;
import com.mszlu.shop.model.buyer.vo.trade.CartSkuVO;
import com.mszlu.shop.model.buyer.vo.trade.CartVO;
import com.mszlu.shop.model.buyer.vo.trade.TradeVo;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 购物车渲染,将购物车中的各个商品,拆分到每个商家,形成购物车VO
 */
@Order(5)
@Service
public class CartPriceRender implements CartRenderStep {

    @Autowired
    private CategoryService categoryService;

    @Override
    public void render(TradeVo tradeVo) {
        //构造cartVO
        this.buildCart(tradeVo);
        //购物车价格计算
        this.buildCartPrice(tradeVo);
        //总计
        this.buildTradePrice(tradeVo);
    }

    private void buildTradePrice(TradeVo tradeVo) {
        //购物车列表
        List<CartVO> cartVOS = tradeVo.getCartList();

        List<PriceDetailDTO> priceDetailDTOS = new ArrayList<>();
        for (CartVO cart : cartVOS) {
            PriceDetailDTO priceDetailDTO = cart.getPriceDetailDTO();
            priceDetailDTOS.add(priceDetailDTO);
        }
        tradeVo.setPriceDetailDTO(PriceDetailDTO.accumulationPriceDTO(priceDetailDTOS));

    }

    /**
     * 购物车价格
     *
     * @param tradeVo 购物车展示信息
     */
    private void buildCartPrice(TradeVo tradeVo) {
        List<CartSkuVO> cartSkuVOList = tradeVo.getSkuList();
        //购物车列表
        List<CartVO> cartVOS = tradeVo.getCartList();

        //key store id
        //value 商品列表
        Map<String, List<CartSkuVO>> map = new HashMap<>();
        for (CartSkuVO cartSkuVO : cartSkuVOList) {
            //如果存在商家id
            if (map.containsKey(cartSkuVO.getGoodsSku().getStoreId())) {
                List<CartSkuVO> list = map.get(cartSkuVO.getGoodsSku().getStoreId());
                list.add(cartSkuVO);
            } else {
                List<CartSkuVO> list = new ArrayList<>();
                list.add(cartSkuVO);
                map.put(cartSkuVO.getGoodsSku().getStoreId(), list);
            }
        }

        //计算购物车价格
        for (CartVO cart : cartVOS) {
            List<CartSkuVO> cartSkuVOS = map.get(cart.getStoreId());
            List<PriceDetailDTO> priceDetailDTOS = new ArrayList<>();
            if (Boolean.TRUE.equals(cart.getChecked())) {
                //累加价格
                for (CartSkuVO cartSkuVO : cartSkuVOS) {
                    if (Boolean.TRUE.equals(cartSkuVO.getChecked())) {
                        PriceDetailDTO priceDetailDTO = cartSkuVO.getPriceDetailDTO();
                        //流水金额(入账 出帐金额) = goodsPrice + freight - discountPrice - couponPrice
                        double flowPrice = CurrencyUtil.sub(CurrencyUtil.add(priceDetailDTO.getGoodsPrice(), priceDetailDTO.getFreightPrice()), CurrencyUtil.add(priceDetailDTO.getDiscountPrice(), priceDetailDTO.getCouponPrice() != null ? priceDetailDTO.getCouponPrice() : 0));
                        priceDetailDTO.setFlowPrice(flowPrice);

                        //最终结算金额 = flowPrice - platFormCommission - distributionCommission
                        double billPrice = CurrencyUtil.sub(CurrencyUtil.sub(flowPrice, priceDetailDTO.getPlatFormCommission()), priceDetailDTO.getDistributionCommission());
                        priceDetailDTO.setBillPrice(billPrice);

                        //平台佣金
                        String categoryId = cartSkuVO.getGoodsSku().getCategoryPath().substring(
                                cartSkuVO.getGoodsSku().getCategoryPath().lastIndexOf(",") + 1
                        );
                        if (StringUtils.isNotEmpty(categoryId)) {
                            Double platFormCommission = CurrencyUtil.div(CurrencyUtil.mul(flowPrice, categoryService.findCategoryById(categoryId).getCommissionRate()), 100);
                            priceDetailDTO.setPlatFormCommission(platFormCommission);
                        }
                        priceDetailDTOS.add(priceDetailDTO);
                    }
                }
                cart.setPriceDetailDTO(PriceDetailDTO.accumulationPriceDTO(priceDetailDTOS));
            }
        }
    }

    /**
     * 购物车价格
     *
     * @param tradeVo 购物车展示信息
     */
    void buildCart(TradeVo tradeVo) {
        for (CartVO cart : tradeVo.getCartList()) {
            for (CartSkuVO sku : cart.getSkuList()) {
                if (Boolean.FALSE.equals(sku.getChecked())) {
                    continue;
                }
                cart.addGoodsNum(sku.getNum());
                if (cart.getStoreId().equals(sku.getStoreId()) && !cart.getSkuList().contains(sku)) {
                    cart.getSkuList().add(sku);
                }
            }
        }
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
package com.mszlu.shop.buyer.service.impl.trade.domain.render;

import com.mszlu.shop.buyer.service.impl.trade.domain.CartRenderStep;
import com.mszlu.shop.model.buyer.vo.trade.TradeVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Service;

/**
 * 购物促销信息渲染实现
 */
@Service
@Order(6)
public class DistributionPriceRender implements CartRenderStep {
    @Override
    public void render(TradeVo tradeVo) {

    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.mszlu.shop.buyer.service.impl.trade.domain.render;

import com.mszlu.shop.buyer.service.impl.trade.domain.CartRenderStep;
import com.mszlu.shop.model.buyer.vo.trade.TradeVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Service;

/**
 * 购物促销信息渲染实现
 */
@Service
@Order(6)
public class DistributionPriceRender implements CartRenderStep {
    @Override
    public void render(TradeVo tradeVo) {

    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.mszlu.shop.buyer.service.impl.trade.domain.render;

import com.mszlu.shop.buyer.service.impl.trade.domain.CartRenderStep;
import com.mszlu.shop.model.buyer.vo.trade.TradeVo;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Service;

/**
 * sn 生成
 */
@Order(7)
@Service
public class CartSnRender implements CartRenderStep {

    @Override
    public void render(TradeVo tradeVo) {

    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

4.4 测试

前端代码:Cart.vue中getCartList()方法中,先把优惠券相关的注释掉,要不影响展示

  for (let k = 0; k < this.cartList.length; k++) {
            let shop = this.cartList[k];
            //先注释掉
            // let list = await APIMember.couponList({ storeId: shop.storeId });
            // shop.couponList.push(...list.result.records);
          }
1
2
3
4
5
6