PmsSkuServiceImpl.java 6.7 KB
Newer Older
H
hxrui 已提交
1 2
package com.youlai.mall.pms.service.impl;

3
import cn.hutool.core.collection.CollectionUtil;
H
haoxr 已提交
4
import cn.hutool.core.convert.Convert;
H
haoxr 已提交
5
import cn.hutool.core.util.StrUtil;
6
import cn.hutool.json.JSONUtil;
H
haoxr 已提交
7
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
H
haoxr 已提交
8
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
H
hxrui 已提交
9 10
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.youlai.common.web.exception.BizException;
H
haoxr 已提交
11
import com.youlai.mall.pms.mapper.PmsSkuMapper;
H
haoxr 已提交
12
import com.youlai.mall.pms.pojo.entity.PmsSku;
H
haoxr 已提交
13
import com.youlai.mall.pms.pojo.dto.SkuDTO;
14
import com.youlai.mall.pms.pojo.dto.SkuLockDTO;
H
haoxr 已提交
15
import com.youlai.mall.pms.service.IPmsSkuService;
H
haoxr 已提交
16
import lombok.AllArgsConstructor;
H
hxrui 已提交
17
import lombok.extern.slf4j.Slf4j;
H
haoxr 已提交
18 19
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
20
import org.springframework.data.redis.core.StringRedisTemplate;
H
hxrui 已提交
21 22 23
import org.springframework.stereotype.Service;

import java.util.List;
24 25
import java.util.stream.Collectors;

26
import static com.youlai.mall.pms.common.constant.PmsConstants.LOCKED_STOCK_PREFIX;
H
haoxr 已提交
27
import static com.youlai.mall.pms.common.constant.PmsConstants.LOCK_SKU_PREFIX;
H
hxrui 已提交
28 29 30

@Service
@Slf4j
H
haoxr 已提交
31
@AllArgsConstructor
H
haoxr 已提交
32
public class PmsSkuServiceImpl extends ServiceImpl<PmsSkuMapper, PmsSku> implements IPmsSkuService {
H
hxrui 已提交
33

H
haoxr 已提交
34
    private StringRedisTemplate redisTemplate;
H
haoxr 已提交
35

H
haoxr 已提交
36
    private RedissonClient redissonClient;
37

H
haoxr 已提交
38 39 40
    /**
     * 创建订单时锁定库存
     */
H
hxrui 已提交
41
    @Override
42
    public boolean lockStock(List<SkuLockDTO> skuLockList) {
43 44
        log.info("=======================创建订单,开始锁定商品库存=======================");
        log.info("锁定商品信息:{}", skuLockList.toString());
H
haoxr 已提交
45
        if (CollectionUtil.isEmpty(skuLockList)) {
H
haoxr 已提交
46
            throw new BizException("锁定的商品列表为空");
47 48 49 50
        }

        // 锁定商品
        skuLockList.forEach(item -> {
H
haoxr 已提交
51 52
            RLock lock = redissonClient.getLock(LOCK_SKU_PREFIX + item.getSkuId()); // 获取商品的分布式锁
            lock.lock();
H
haoxr 已提交
53
            boolean result = this.update(new LambdaUpdateWrapper<PmsSku>()
54
                    .setSql("locked_stock = locked_stock + " + item.getCount())
H
haoxr 已提交
55
                    .eq(PmsSku::getId, item.getSkuId())
H
haoxr 已提交
56
                    .apply("stock - locked_stock >= {0}", item.getCount())
H
haoxr 已提交
57
            );
58 59 60 61
            if (result) {
                item.setLocked(true);
            } else {
                item.setLocked(false);
H
hxrui 已提交
62
            }
H
haoxr 已提交
63
            lock.unlock();
H
haoxr 已提交
64 65
        });

66
        // 锁定失败的商品集合
H
haoxr 已提交
67 68
        List<SkuLockDTO> unlockSkuList = skuLockList.stream().filter(item -> !item.getLocked()).collect(Collectors.toList());
        if (CollectionUtil.isNotEmpty(unlockSkuList)) {
69
            // 恢复已被锁定的库存
H
haoxr 已提交
70 71 72 73 74 75 76 77
            List<SkuLockDTO> lockSkuList = skuLockList.stream().filter(SkuLockDTO::getLocked).collect(Collectors.toList());
            lockSkuList.forEach(item ->
                    this.update(new LambdaUpdateWrapper<PmsSku>()
                            .eq(PmsSku::getId, item.getSkuId())
                            .setSql("locked_stock = locked_stock - " + item.getCount()))
            );
            // 提示订单哪些商品库存不足
            List<Long> ids = unlockSkuList.stream().map(SkuLockDTO::getSkuId).collect(Collectors.toList());
78 79 80 81 82
            throw new BizException("商品" + ids.toString() + "库存不足");
        }

        // 将锁定的商品保存至Redis中
        String orderToken = skuLockList.get(0).getOrderToken();
83
        redisTemplate.opsForValue().set(LOCKED_STOCK_PREFIX + orderToken, JSONUtil.toJsonStr(skuLockList));
H
hxrui 已提交
84 85 86
        return true;
    }

H
haoxr 已提交
87 88 89
    /**
     * 订单超时关单解锁库存
     */
H
hxrui 已提交
90
    @Override
H
haoxr 已提交
91
    public boolean unlockStock(String orderToken) {
92 93 94
        log.info("=======================订单超时未支付系统自动关单释放库存=======================");
        String json = redisTemplate.opsForValue().get(LOCKED_STOCK_PREFIX + orderToken);
        log.info("释放库存信息:{}", json);
P
PanPanda 已提交
95
        if (StrUtil.isBlank(json)) {
H
haoxr 已提交
96 97 98 99 100 101 102 103 104 105 106 107
            return true;
        }

        List<SkuLockDTO> skuLockList = JSONUtil.toList(json, SkuLockDTO.class);

        skuLockList.forEach(item ->
                this.update(new LambdaUpdateWrapper<PmsSku>()
                        .eq(PmsSku::getId, item.getSkuId())
                        .setSql("locked_stock = locked_stock - " + item.getCount()))
        );

        // 删除redis中锁定的库存
108
        redisTemplate.delete(LOCKED_STOCK_PREFIX + orderToken);
H
hxrui 已提交
109 110
        return true;
    }
H
haoxr 已提交
111

H
haoxr 已提交
112 113 114
    /**
     * 支付成功时扣减库存
     */
H
haoxr 已提交
115
    @Override
H
haoxr 已提交
116
    public boolean deductStock(String orderToken) {
117 118 119
        log.info("=======================支付成功扣减订单中商品库存=======================");
        String json = redisTemplate.opsForValue().get(LOCKED_STOCK_PREFIX + orderToken);
        log.info("订单商品信息:{}", json);
H
haoxr 已提交
120
        if (StrUtil.isBlank(json)) {
H
haoxr 已提交
121 122 123 124 125 126
            return true;
        }

        List<SkuLockDTO> skuLockList = JSONUtil.toList(json, SkuLockDTO.class);

        skuLockList.forEach(item -> {
H
haoxr 已提交
127 128
            boolean result = this.update(new LambdaUpdateWrapper<PmsSku>()
                    .eq(PmsSku::getId, item.getSkuId())
H
haoxr 已提交
129
                    .setSql("stock = stock - " + item.getCount())  // 扣减库存
130
                    .setSql("locked_stock = locked_stock - " + item.getCount())
H
haoxr 已提交
131 132
            );
            if (!result) {
H
haoxr 已提交
133
                throw new BizException("扣减库存失败,商品" + item.getSkuId() + "库存不足");
H
haoxr 已提交
134 135
            }
        });
H
haoxr 已提交
136 137

        // 删除redis中锁定的库存
138
        redisTemplate.delete(LOCKED_STOCK_PREFIX + orderToken);
H
haoxr 已提交
139
        return true;
H
haoxr 已提交
140 141 142
    }


H
haoxr 已提交
143 144 145 146
    /**
     * Cache-Aside pattern 缓存、数据库读写模式
     * 1. 读取数据,先读缓存,没有就去读数据库,然后将结果写入缓存
     * 2. 写入数据,先更新数据库,再删除缓存
H
haoxr 已提交
147
     *
H
haoxr 已提交
148
     * @param id 库存ID
H
haoxr 已提交
149 150 151
     * @return
     */
    @Override
H
haoxr 已提交
152 153
    public Integer getStockById(Long id) {
        Integer stock = 0;
H
haoxr 已提交
154
        // 读->缓存
155
        Object cacheVal = redisTemplate.opsForValue().get(LOCKED_STOCK_PREFIX + id);
H
haoxr 已提交
156
        if (cacheVal != null) {
H
haoxr 已提交
157 158
            stock = Convert.toInt(cacheVal);
            return stock;
H
haoxr 已提交
159 160 161
        }

        // 读->数据库
H
haoxr 已提交
162 163
        PmsSku pmsSku = this.getOne(new LambdaQueryWrapper<PmsSku>()
                .eq(PmsSku::getId, id)
164
                .select(PmsSku::getStock));
H
haoxr 已提交
165

H
haoxr 已提交
166
        if (pmsSku != null) {
167
            stock = pmsSku.getStock();
H
haoxr 已提交
168
            // 写->缓存
169
            redisTemplate.opsForValue().set(LOCKED_STOCK_PREFIX + id, String.valueOf(stock));
H
haoxr 已提交
170 171
        }

H
haoxr 已提交
172
        return stock;
H
haoxr 已提交
173
    }
H
haoxr 已提交
174 175

    @Override
176 177
    public SkuDTO getSkuById(Long id) {
        return this.baseMapper.getSkuById(id);
H
haoxr 已提交
178
    }
H
haoxr 已提交
179

H
hxrui 已提交
180
}