From 55024f78c0034832f374ccb72dbb1ce8820ef857 Mon Sep 17 00:00:00 2001 From: jieyuu <645634619@qq.com> Date: Tue, 6 Aug 2024 00:51:54 +0800 Subject: [PATCH] =?UTF-8?q?=E9=A2=86=E5=88=B8=E5=8A=9F=E8=83=BD=E5=BC=95?= =?UTF-8?q?=E5=85=A5=E5=88=86=E5=B8=83=E5=BC=8F=E9=94=81=20=E5=BC=95?= =?UTF-8?q?=E5=85=A5redisson=E4=BE=9D=E8=B5=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 12 ++- xdclass-common/pom.xml | 6 ++ .../java/net/jieyuu/config/AppConfig.java | 49 ++++++++++++ .../java/net/jieyuu/mapper/CouponMapper.java | 6 +- .../service/impl/CouponServiceImpl.java | 76 +++++++++++++------ .../main/resources/mapper/CouponMapper.xml | 7 +- 6 files changed, 126 insertions(+), 30 deletions(-) create mode 100644 xdclass-common/src/main/java/net/jieyuu/config/AppConfig.java diff --git a/pom.xml b/pom.xml index 5f799da..6d56787 100644 --- a/pom.xml +++ b/pom.xml @@ -75,7 +75,6 @@ - com.baomidou @@ -89,11 +88,10 @@ org.projectlombok lombok ${lombok.version} - + - org.apache.commons @@ -110,7 +108,6 @@ - io.springfox @@ -138,6 +135,13 @@ jjwt 0.7.0 + + + org.redisson + redisson + 3.10.1 + + diff --git a/xdclass-common/pom.xml b/xdclass-common/pom.xml index ebd4fad..2f8bacf 100644 --- a/xdclass-common/pom.xml +++ b/xdclass-common/pom.xml @@ -102,6 +102,12 @@ 0.7.0 + + + org.redisson + redisson + + diff --git a/xdclass-common/src/main/java/net/jieyuu/config/AppConfig.java b/xdclass-common/src/main/java/net/jieyuu/config/AppConfig.java new file mode 100644 index 0000000..04fb302 --- /dev/null +++ b/xdclass-common/src/main/java/net/jieyuu/config/AppConfig.java @@ -0,0 +1,49 @@ +package net.jieyuu.config; + + +import lombok.Data; +import org.redisson.Redisson; +import org.redisson.api.RedissonClient; +import org.redisson.config.Config; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +@Data +public class AppConfig { + + @Value("${spring.redis.host}") + private String redisHost; + + @Value("${spring.redis.port}") + private String redisPort; + + @Value("${spring.redis.password}") + private String redisPwd; + + /** + * 配置分布式锁redisson + * + * @return + */ + @Bean + public RedissonClient redissonClient() { + Config config = new Config(); + + //单机模式 + config.useSingleServer() + .setPassword(redisPwd) + .setAddress("redis://" + redisHost + ":" + redisPort); + + //集群模式 + //config.useClusterServers() + //.setScanInterval(2000) + //.addNodeAddress("redis://10.0.29.30:6379", "redis://10.0.29.95:6379") + //.addNodeAddress("redis://127.0.0.1:6379"); + + RedissonClient redisson = Redisson.create(config); + return redisson; + } + +} diff --git a/xdclass-coupon-service/src/main/java/net/jieyuu/mapper/CouponMapper.java b/xdclass-coupon-service/src/main/java/net/jieyuu/mapper/CouponMapper.java index f6f0761..cd73bfa 100644 --- a/xdclass-coupon-service/src/main/java/net/jieyuu/mapper/CouponMapper.java +++ b/xdclass-coupon-service/src/main/java/net/jieyuu/mapper/CouponMapper.java @@ -2,10 +2,11 @@ package net.jieyuu.mapper; import net.jieyuu.model.CouponDO; import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.springframework.data.repository.query.Param; /** *

- * Mapper 接口 + * Mapper 接口 *

* * @author jieyuu @@ -15,8 +16,9 @@ public interface CouponMapper extends BaseMapper { /** * 扣减库存 + * * @param couponId * @return */ - int reduceStock(long couponId); + int reduceStock(@Param("couponId") long couponId); } diff --git a/xdclass-coupon-service/src/main/java/net/jieyuu/service/impl/CouponServiceImpl.java b/xdclass-coupon-service/src/main/java/net/jieyuu/service/impl/CouponServiceImpl.java index 3ad2582..fb304e0 100644 --- a/xdclass-coupon-service/src/main/java/net/jieyuu/service/impl/CouponServiceImpl.java +++ b/xdclass-coupon-service/src/main/java/net/jieyuu/service/impl/CouponServiceImpl.java @@ -22,11 +22,16 @@ import net.jieyuu.utils.JsonData; import net.jieyuu.vo.CouponVO; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.data.redis.core.script.DefaultRedisScript; import org.springframework.stereotype.Service; +import java.time.Duration; +import java.util.Arrays; import java.util.Date; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; /** @@ -47,6 +52,9 @@ public class CouponServiceImpl implements CouponService { @Autowired private CouponRecordMapper couponRecordMapper; + @Autowired + private StringRedisTemplate redisTemplate; + @Override public Map pageCouponActivity(int page, int size) { Page pageInfo = new Page<>(page, size); @@ -80,33 +88,55 @@ public class CouponServiceImpl implements CouponService { public JsonData addCoupon(long couponId, CouponCategoryEnum category) { LoginUser loginUser = LoginInterceptor.threadLocal.get(); - CouponDO couponDO = couponMapper.selectOne(new QueryWrapper() - .eq("id", couponId) - .eq("category", category.name())); - //优惠券是否可以领取 - this.couponCheck(couponDO, loginUser.getId()); + String uuid = CommonUtil.generateUUID(); + String lockKey = "lock:coupon:" + couponId; + //避免锁被误删 + Boolean lockFlag = redisTemplate.opsForValue().setIfAbsent(lockKey, uuid, Duration.ofMinutes(10)); - //构建领券记录 - CouponRecordDO couponRecordDO = new CouponRecordDO(); - BeanUtils.copyProperties(couponDO, couponRecordDO); - couponRecordDO.setCreateTime(new Date()); - couponRecordDO.setUseState(CouponStateEnum.NEW.name()); - couponRecordDO.setCouponId(couponId); - couponRecordDO.setUserName(loginUser.getName()); - couponRecordDO.setUserId(loginUser.getId()); - couponRecordDO.setId(null); + if (lockFlag) { + log.info("加锁成功:{}", couponId); + try { + // 执行业务逻辑 + CouponDO couponDO = couponMapper.selectOne(new QueryWrapper() + .eq("id", couponId) + .eq("category", category.name())); + //优惠券是否可以领取 + this.couponCheck(couponDO, loginUser.getId()); - //扣减库存 todo - int rows = 1;//couponMapper.reduceStock(couponId); + //构建领券记录 + CouponRecordDO couponRecordDO = new CouponRecordDO(); + BeanUtils.copyProperties(couponDO, couponRecordDO); + couponRecordDO.setCreateTime(new Date()); + couponRecordDO.setUseState(CouponStateEnum.NEW.name()); + couponRecordDO.setCouponId(couponId); + couponRecordDO.setUserName(loginUser.getName()); + couponRecordDO.setUserId(loginUser.getId()); + couponRecordDO.setId(null); - if (rows == 1) { - //扣减库存成功才保存记录 - couponRecordMapper.insert(couponRecordDO); - } else { - log.warn("发放优惠券错误:{},用户:{}", couponDO, loginUser); - throw new BizException(BizCodeEnum.COUPON_NO_STOCK); + //扣减库存 todo + int rows = couponMapper.reduceStock(couponId); + if (rows == 1) { + //扣减库存成功才保存记录 + couponRecordMapper.insert(couponRecordDO); + } else { + log.warn("发放优惠券错误:{},用户:{}", couponDO, loginUser); + throw new BizException(BizCodeEnum.COUPON_NO_STOCK); + } + } finally { + // 释放锁 + // 使用lua脚本保证 查询和删除的原子性 + String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"; + Integer result = redisTemplate.execute(new DefaultRedisScript<>(script, Integer.class), Arrays.asList(lockKey), uuid); + log.info("解锁{}", result); + } + } else {//加锁失败 + try { + TimeUnit.SECONDS.sleep(1); + } catch (InterruptedException e) { + log.error("自旋失败"); + } + addCoupon(couponId, category); } - return JsonData.buildSuccess(); } diff --git a/xdclass-coupon-service/src/main/resources/mapper/CouponMapper.xml b/xdclass-coupon-service/src/main/resources/mapper/CouponMapper.xml index 44a9b76..368be64 100644 --- a/xdclass-coupon-service/src/main/resources/mapper/CouponMapper.xml +++ b/xdclass-coupon-service/src/main/resources/mapper/CouponMapper.xml @@ -14,7 +14,7 @@ - + @@ -24,4 +24,9 @@ id, category, publish, coupon_img, coupon_title, price, user_limit, start_time, end_time, publish_count, stock, create_time, condition_price + + + update coupon set stock=stock-1 where id = #{couponId} and stock > 0 + +