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
+
+