PmsSkuServiceImpl.java 6.3 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.common.constant.PmsConstants;
H
haoxr 已提交
12 13
import com.youlai.mall.pms.mapper.PmsSkuMapper;
import com.youlai.mall.pms.pojo.domain.PmsSku;
H
haoxr 已提交
14
import com.youlai.mall.pms.pojo.dto.SkuDTO;
15
import com.youlai.mall.pms.pojo.dto.SkuLockDTO;
H
haoxr 已提交
16
import com.youlai.mall.pms.service.IPmsSkuService;
H
haoxr 已提交
17
import lombok.AllArgsConstructor;
H
hxrui 已提交
18
import lombok.extern.slf4j.Slf4j;
H
haoxr 已提交
19 20
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
21
import org.springframework.data.redis.core.StringRedisTemplate;
H
hxrui 已提交
22 23 24
import org.springframework.stereotype.Service;

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

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

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

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

H
haoxr 已提交
37
    private RedissonClient redissonClient;
38

H
haoxr 已提交
39 40 41
    /**
     * 创建订单时锁定库存
     */
H
hxrui 已提交
42
    @Override
43
    public boolean lockStock(List<SkuLockDTO> skuLockList) {
H
haoxr 已提交
44

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();
H
haoxr 已提交
83
        redisTemplate.opsForValue().set(STOCK_LOCKED_PREFIX + orderToken, JSONUtil.toJsonStr(skuLockList));
H
hxrui 已提交
84 85 86
        return true;
    }

H
haoxr 已提交
87 88 89
    /**
     * 订单超时关单解锁库存
     */
H
hxrui 已提交
90
    @Override
H
haoxr 已提交
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106
    public boolean unlockStock(String orderToken) {
        String json = redisTemplate.opsForValue().get(STOCK_LOCKED_PREFIX + orderToken);
        if (StrUtil.isNotBlank(json)) {
            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中锁定的库存
        redisTemplate.opsForValue().decrement(STOCK_LOCKED_PREFIX + orderToken);
H
hxrui 已提交
107 108
        return true;
    }
H
haoxr 已提交
109

H
haoxr 已提交
110 111 112
    /**
     * 支付成功时扣减库存
     */
H
haoxr 已提交
113
    @Override
H
haoxr 已提交
114 115
    public boolean deductStock(String orderToken) {
        String json = redisTemplate.opsForValue().get(STOCK_LOCKED_PREFIX + orderToken);
H
haoxr 已提交
116
        if (StrUtil.isBlank(json)) {
H
haoxr 已提交
117 118 119 120 121 122
            return true;
        }

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

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

        // 删除redis中锁定的库存
        redisTemplate.opsForValue().decrement(STOCK_LOCKED_PREFIX + orderToken);
        return true;
H
haoxr 已提交
136 137 138
    }


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

        // 读->数据库
H
haoxr 已提交
158 159
        PmsSku pmsSku = this.getOne(new LambdaQueryWrapper<PmsSku>()
                .eq(PmsSku::getId, id)
160
                .select(PmsSku::getStock));
H
haoxr 已提交
161

H
haoxr 已提交
162
        if (pmsSku != null) {
163
            stock = pmsSku.getStock();
H
haoxr 已提交
164
            // 写->缓存
H
haoxr 已提交
165
            redisTemplate.opsForValue().set(PmsConstants.STOCK_LOCKED_PREFIX + id, String.valueOf(stock));
H
haoxr 已提交
166 167
        }

H
haoxr 已提交
168
        return stock;
H
haoxr 已提交
169 170

    }
H
haoxr 已提交
171 172

    @Override
H
haoxr 已提交
173 174
    public List<SkuDTO> listBySkuIds(List<Long> ids) {
        return this.baseMapper.listBySkuIds(ids);
H
haoxr 已提交
175
    }
H
haoxr 已提交
176 177


H
hxrui 已提交
178
}