PmsSkuServiceImpl.java 7.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
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
10
import com.youlai.common.result.Result;
H
hxrui 已提交
11
import com.youlai.common.web.exception.BizException;
12
import com.youlai.mall.pms.common.constant.PmsConstants;
H
haoxr 已提交
13
import com.youlai.mall.pms.mapper.PmsSkuMapper;
有来技术 已提交
14
import com.youlai.mall.pms.pojo.dto.app.LockStockDTO;
15 16
import com.youlai.mall.pms.pojo.dto.app.SkuDTO;
import com.youlai.mall.pms.pojo.entity.PmsSku;
H
haoxr 已提交
17
import com.youlai.mall.pms.service.IPmsSkuService;
18
import lombok.RequiredArgsConstructor;
H
hxrui 已提交
19
import lombok.extern.slf4j.Slf4j;
H
haoxr 已提交
20 21
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
22
import org.springframework.data.redis.core.StringRedisTemplate;
H
hxrui 已提交
23 24 25
import org.springframework.stereotype.Service;

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

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

33 34
    private final StringRedisTemplate redisTemplate;
    private final RedissonClient redissonClient;
9
99424 已提交
35 36


37

H
haoxr 已提交
38 39 40
    /**
     * 创建订单时锁定库存
     */
H
hxrui 已提交
41
    @Override
42
    public Result lockStock(List<LockStockDTO> skuLockList) {
43 44
        log.info("=======================创建订单,开始锁定商品库存=======================");
        log.info("锁定商品信息:{}", skuLockList.toString());
H
haoxr 已提交
45
        if (CollectionUtil.isEmpty(skuLockList)) {
46
            return Result.failed("锁定的商品列表为空");
47
        }
9
99424 已提交
48
        //prepareSkuLockList(null,  skuLockList);
49 50
        // 锁定商品
        skuLockList.forEach(item -> {
51
            RLock lock = redissonClient.getLock(PmsConstants.LOCK_SKU_PREFIX + item.getSkuId()); // 获取商品的分布式锁
H
haoxr 已提交
52
            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
        // 锁定失败的商品集合
有来技术 已提交
67
        List<LockStockDTO> unlockSkuList = skuLockList.stream().filter(item -> !item.getLocked()).collect(Collectors.toList());
H
haoxr 已提交
68
        if (CollectionUtil.isNotEmpty(unlockSkuList)) {
69
            // 恢复已被锁定的库存
有来技术 已提交
70
            List<LockStockDTO> lockSkuList = skuLockList.stream().filter(LockStockDTO::getLocked).collect(Collectors.toList());
H
haoxr 已提交
71 72 73 74 75 76
            lockSkuList.forEach(item ->
                    this.update(new LambdaUpdateWrapper<PmsSku>()
                            .eq(PmsSku::getId, item.getSkuId())
                            .setSql("locked_stock = locked_stock - " + item.getCount()))
            );
            // 提示订单哪些商品库存不足
77
            String ids= unlockSkuList.stream().map(sku -> sku.getSkuId().toString()).collect(Collectors.joining(","));
78
            return Result.failed("商品" + ids + "库存不足");
79 80 81 82
        }

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

9
99424 已提交
87

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

有来技术 已提交
100
        List<LockStockDTO> skuLockList = JSONUtil.toList(json, LockStockDTO.class);
H
haoxr 已提交
101 102 103 104 105 106 107 108

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

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

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

有来技术 已提交
125
        List<LockStockDTO> skuLockList = JSONUtil.toList(json, LockStockDTO.class);
H
haoxr 已提交
126 127

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

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


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

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

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

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

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

181 182 183 184 185 186 187 188 189 190 191 192

   /* private final SeataTccSkuService seataTccSkuService;

    @Override
    @GlobalTransactional
    public Boolean lockStockTcc(List<LockStockDTO> skuLockList) {
        seataTccSkuService.prepareSkuLockList(null, skuLockList);
        String orderToken = skuLockList.get(0).getOrderToken();
        redisTemplate.opsForValue().set(PmsConstants.LOCKED_STOCK_PREFIX + orderToken, JSONUtil.toJsonStr(skuLockList));
        return true;
    }*/

H
hxrui 已提交
193
}