领券功能引入分布式锁

引入redisson依赖
This commit is contained in:
jieyuu 2024-08-06 00:51:54 +08:00
parent 78470e4279
commit 55024f78c0
6 changed files with 126 additions and 30 deletions

10
pom.xml
View File

@ -75,7 +75,6 @@
</dependency> </dependency>
<!--mybatis plus和springboot整合--> <!--mybatis plus和springboot整合-->
<dependency> <dependency>
<groupId>com.baomidou</groupId> <groupId>com.baomidou</groupId>
@ -93,7 +92,6 @@
</dependency> </dependency>
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 --> <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
<dependency> <dependency>
<groupId>org.apache.commons</groupId> <groupId>org.apache.commons</groupId>
@ -110,7 +108,6 @@
</dependency> </dependency>
<!--接口文档依赖--> <!--接口文档依赖-->
<dependency> <dependency>
<groupId>io.springfox</groupId> <groupId>io.springfox</groupId>
@ -138,6 +135,13 @@
<artifactId>jjwt</artifactId> <artifactId>jjwt</artifactId>
<version>0.7.0</version> <version>0.7.0</version>
</dependency> </dependency>
<!-- 分布式锁 -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.10.1</version>
</dependency>
</dependencies> </dependencies>
</dependencyManagement> </dependencyManagement>

View File

@ -102,6 +102,12 @@
<version>0.7.0</version> <version>0.7.0</version>
</dependency> </dependency>
<!-- redisson -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
</dependency>
</dependencies> </dependencies>
<properties> <properties>

View File

@ -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;
}
}

View File

@ -2,6 +2,7 @@ package net.jieyuu.mapper;
import net.jieyuu.model.CouponDO; import net.jieyuu.model.CouponDO;
import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.springframework.data.repository.query.Param;
/** /**
* <p> * <p>
@ -15,8 +16,9 @@ public interface CouponMapper extends BaseMapper<CouponDO> {
/** /**
* 扣减库存 * 扣减库存
*
* @param couponId * @param couponId
* @return * @return
*/ */
int reduceStock(long couponId); int reduceStock(@Param("couponId") long couponId);
} }

View File

@ -22,11 +22,16 @@ import net.jieyuu.utils.JsonData;
import net.jieyuu.vo.CouponVO; import net.jieyuu.vo.CouponVO;
import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired; 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 org.springframework.stereotype.Service;
import java.time.Duration;
import java.util.Arrays;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors; import java.util.stream.Collectors;
/** /**
@ -47,6 +52,9 @@ public class CouponServiceImpl implements CouponService {
@Autowired @Autowired
private CouponRecordMapper couponRecordMapper; private CouponRecordMapper couponRecordMapper;
@Autowired
private StringRedisTemplate redisTemplate;
@Override @Override
public Map<String, Object> pageCouponActivity(int page, int size) { public Map<String, Object> pageCouponActivity(int page, int size) {
Page<CouponDO> pageInfo = new Page<>(page, size); Page<CouponDO> pageInfo = new Page<>(page, size);
@ -80,6 +88,15 @@ public class CouponServiceImpl implements CouponService {
public JsonData addCoupon(long couponId, CouponCategoryEnum category) { public JsonData addCoupon(long couponId, CouponCategoryEnum category) {
LoginUser loginUser = LoginInterceptor.threadLocal.get(); LoginUser loginUser = LoginInterceptor.threadLocal.get();
String uuid = CommonUtil.generateUUID();
String lockKey = "lock:coupon:" + couponId;
//避免锁被误删
Boolean lockFlag = redisTemplate.opsForValue().setIfAbsent(lockKey, uuid, Duration.ofMinutes(10));
if (lockFlag) {
log.info("加锁成功:{}", couponId);
try {
// 执行业务逻辑
CouponDO couponDO = couponMapper.selectOne(new QueryWrapper<CouponDO>() CouponDO couponDO = couponMapper.selectOne(new QueryWrapper<CouponDO>()
.eq("id", couponId) .eq("id", couponId)
.eq("category", category.name())); .eq("category", category.name()));
@ -97,8 +114,7 @@ public class CouponServiceImpl implements CouponService {
couponRecordDO.setId(null); couponRecordDO.setId(null);
//扣减库存 todo //扣减库存 todo
int rows = 1;//couponMapper.reduceStock(couponId); int rows = couponMapper.reduceStock(couponId);
if (rows == 1) { if (rows == 1) {
//扣减库存成功才保存记录 //扣减库存成功才保存记录
couponRecordMapper.insert(couponRecordDO); couponRecordMapper.insert(couponRecordDO);
@ -106,7 +122,21 @@ public class CouponServiceImpl implements CouponService {
log.warn("发放优惠券错误:{},用户:{}", couponDO, loginUser); log.warn("发放优惠券错误:{},用户:{}", couponDO, loginUser);
throw new BizException(BizCodeEnum.COUPON_NO_STOCK); 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(); return JsonData.buildSuccess();
} }

View File

@ -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 id, category, publish, coupon_img, coupon_title, price, user_limit, start_time, end_time, publish_count, stock, create_time, condition_price
</sql> </sql>
<!--扣减库存-->
<update id="reduceStock">
update coupon set stock=stock-1 where id = #{couponId} and stock > 0
</update>
</mapper> </mapper>