领券功能引入分布式锁
引入redisson依赖
This commit is contained in:
parent
78470e4279
commit
55024f78c0
12
pom.xml
12
pom.xml
@ -75,7 +75,6 @@
|
|||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<!--mybatis plus和springboot整合-->
|
<!--mybatis plus和springboot整合-->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.baomidou</groupId>
|
<groupId>com.baomidou</groupId>
|
||||||
@ -89,11 +88,10 @@
|
|||||||
<groupId>org.projectlombok</groupId>
|
<groupId>org.projectlombok</groupId>
|
||||||
<artifactId>lombok</artifactId>
|
<artifactId>lombok</artifactId>
|
||||||
<version>${lombok.version}</version>
|
<version>${lombok.version}</version>
|
||||||
<!-- <scope>provided</scope>-->
|
<!-- <scope>provided</scope>-->
|
||||||
</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>
|
||||||
|
@ -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>
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
<result column="start_time" property="startTime" />
|
<result column="start_time" property="startTime" />
|
||||||
<result column="end_time" property="endTime" />
|
<result column="end_time" property="endTime" />
|
||||||
<result column="publish_count" property="publishCount" />
|
<result column="publish_count" property="publishCount" />
|
||||||
<result column="stock" property="st ock" />
|
<result column="stock" property="stock" />
|
||||||
<result column="create_time" property="createTime" />
|
<result column="create_time" property="createTime" />
|
||||||
<result column="condition_price" property="conditionPrice" />
|
<result column="condition_price" property="conditionPrice" />
|
||||||
</resultMap>
|
</resultMap>
|
||||||
@ -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>
|
||||||
|
Loading…
Reference in New Issue
Block a user