Browse Source

feat: 更新业务逻辑

master
xc-yjs 8 months ago
parent
commit
6fbb66df99
  1. 6
      nla-common/pom.xml
  2. 35
      nla-common/src/main/java/cn/nla/common/config/RedissonConfig.java
  3. 9
      nla-common/src/main/java/cn/nla/common/util/CommonUtil.java
  4. 146
      nla-coupon-service/src/main/java/cn/nla/coupon/service/impl/CouponServiceImpl.java
  5. 5
      nla-coupon-service/src/main/resources/application.yml

6
nla-common/pom.xml

@ -72,6 +72,12 @@
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<!--分布式锁-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.10.1</version>
</dependency>
<!-- JWT相关 -->
<dependency>
<groupId>io.jsonwebtoken</groupId>

35
nla-common/src/main/java/cn/nla/common/config/RedissonConfig.java

@ -0,0 +1,35 @@
package cn.nla.common.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;
/**
* Redisson配置
*/
@Configuration
@Data
public class RedissonConfig {
@Value("${spring.redis.host}")
private String redisHost;
@Value("${spring.redis.port}")
private String redisPort;
@Value("${spring.redis.password}")
private String redisPwd;
/**
* 配置分布式锁的redisson
*/
@Bean
public RedissonClient redissonClient() {
Config config = new Config();
//单机方式
config.useSingleServer().setPassword(redisPwd).setAddress("redis://" + redisHost + ":" + redisPort);
//集群
// config.useClusterServers().addNodeAddress("redis://127.0.0.1:6379","redis://127.0.0.2:6379")
return Redisson.create(config);
}
}

9
nla-common/src/main/java/cn/nla/common/util/CommonUtil.java

@ -11,6 +11,7 @@ import java.net.InetAddress;
import java.net.UnknownHostException;
import java.security.MessageDigest;
import java.util.Random;
import java.util.UUID;
/**
* 工具类
@ -119,6 +120,14 @@ public class CommonUtil {
return saltString.toString();
}
/**
* 生成uuid
*/
public static String generateUUID() {
return UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32);
}
/**
* 响应json数据给前端
*/

146
nla-coupon-service/src/main/java/cn/nla/coupon/service/impl/CouponServiceImpl.java

@ -20,13 +20,23 @@ import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
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 org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
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;
/**
@ -41,9 +51,14 @@ import java.util.stream.Collectors;
@Service
public class CouponServiceImpl extends ServiceImpl<CouponMapper, CouponEntity> implements CouponService {
@Resource
private RedissonClient redissonClient;
@Resource
private CouponRecordMapper couponRecordMapper;
@Autowired
private StringRedisTemplate redisTemplate;
@Override
public Map<String, Object> pageCouponActivity(int page, int size) {
Page<CouponEntity> pageInfo = new Page<>(page, size);
@ -60,38 +75,109 @@ public class CouponServiceImpl extends ServiceImpl<CouponMapper, CouponEntity> i
return pageMap;
}
@Override
public JsonData addCoupon(Long couponId, CouponCategoryEnum category) {
LoginUser loginUser = LoginInterceptor.threadLocal.get();
CouponEntity coupon = baseMapper.selectOne(
Wrappers.<CouponEntity>lambdaQuery().eq(CouponEntity::getId, couponId)
.eq(CouponEntity::getCategory, category.name()));
//优惠券是否可以领取
this.checkCoupon(coupon, loginUser.getId());
//构建领劵记录
CouponRecordEntity couponRecord = new CouponRecordEntity();
BeanUtils.copyProperties(coupon, couponRecord);
couponRecord.setCreateTime(new Date());
couponRecord.setUseState(CouponStateEnum.NEW.name());
couponRecord.setUserId(loginUser.getId());
couponRecord.setUserName(loginUser.getName());
couponRecord.setCouponId(couponId);
couponRecord.setId(null);
//扣减库存
// int rows = baseMapper.reduceStock(couponId);
//⾼并发下扣减劵库存,采⽤乐观锁,当前stock做版本号,延伸多种防⽌超卖的问题,⼀次只能领取1张
// 数据库添加字段: version INT DEFAULT 1, 根据当前的版本号更新
int rows = baseMapper.reduceStockOpt(couponId, coupon.getVersion());
if (rows == 1) {
//库存扣减成功才保存
couponRecordMapper.insert(couponRecord);
} else {
log.warn("发放优惠券失败:{},⽤ 户:{}", coupon, loginUser);
throw new BizException(BizCodeEnum.COUPON_NO_STOCK);
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
@Override
public JsonData addCoupon(Long couponId, CouponCategoryEnum category) {
LoginUser loginUser = LoginInterceptor.threadLocal.get();
String lockKey = "lock:coupon:" + couponId;
RLock rLock = redissonClient.getLock(lockKey);
//多个线程进入,会阻塞等待释放锁
rLock.lock();
log.info("领劵接口加锁成功:{}", Thread.currentThread().getId());
try {
CouponEntity coupon = baseMapper.selectOne(
Wrappers.<CouponEntity>lambdaQuery()
.eq(CouponEntity::getId, couponId).eq(CouponEntity::getCategory, category.name()));
//优惠券是否可以领取
this.checkCoupon(coupon, loginUser.getId());
//构建领劵记录
CouponRecordEntity couponRecord = new CouponRecordEntity();
BeanUtils.copyProperties(coupon, couponRecord);
couponRecord.setCreateTime(new Date());
couponRecord.setUseState(CouponStateEnum.NEW.name());
couponRecord.setUserId(loginUser.getId());
couponRecord.setUserName(loginUser.getName());
couponRecord.setCouponId(couponId);
couponRecord.setId(null);
//扣减库存
int rows = baseMapper.reduceStock(couponId);
if (rows == 1) {
//库存扣减成功才保存
couponRecordMapper.insert(couponRecord);
} else {
log.warn("发放优惠券失败:{},⽤户:{}", coupon, loginUser);
throw new BizException(BizCodeEnum.COUPON_NO_STOCK);
}
} finally {
rLock.unlock();
log.info("解锁成功");
}
return JsonData.buildSuccess();
}
return JsonData.buildSuccess();
}
public JsonData addCouponOld(Long couponId, CouponCategoryEnum category) {
// synchronized (this) {
// String key = "coupon:"+couponId;
// setnx
// if(redisTemplate.opsForValue().setIfAbsent(key,"1")){
// redisTemplate.expire(key,30,TimeUnit.SECONDS);
// }
// setnx setex
// if(redisTemplate.opsForValue().setIfAbsent(key,"1",30,TimeUnit.SECONDS)){
// }else{
// }
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 {
CouponEntity coupon = baseMapper.selectOne(
Wrappers.<CouponEntity>lambdaQuery().eq(CouponEntity::getId, couponId)
.eq(CouponEntity::getCategory, category.name()));
//优惠券是否可以领取
this.checkCoupon(coupon, loginUser.getId());
//构建领劵记录
CouponRecordEntity couponRecord = new CouponRecordEntity();
BeanUtils.copyProperties(coupon, couponRecord);
couponRecord.setCreateTime(new Date());
couponRecord.setUseState(CouponStateEnum.NEW.name());
couponRecord.setUserId(loginUser.getId());
couponRecord.setUserName(loginUser.getName());
couponRecord.setCouponId(couponId);
couponRecord.setId(null);
//扣减库存
int rows = baseMapper.reduceStock(couponId);
//⾼并发下扣减劵库存,采⽤乐观锁,当前stock做版本号,延伸多种防⽌超卖的问题,⼀次只能领取1张
// 数据库添加字段: version INT DEFAULT 1, 根据当前的版本号更新
// int rows = baseMapper.reduceStockOpt(couponId, coupon.getVersion());
if (rows == 1) {
//库存扣减成功才保存
couponRecordMapper.insert(couponRecord);
} else {
log.warn("发放优惠券失败:{},⽤ 户:{}", coupon, loginUser);
throw new BizException(BizCodeEnum.COUPON_NO_STOCK);
}
} finally {
//解锁
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();
}
private CouponVO beanProcess(CouponEntity entity) {
CouponVO couponVO = new CouponVO();

5
nla-coupon-service/src/main/resources/application.yml

@ -3,6 +3,11 @@ server:
spring:
application:
name: nla-coupon-service
redis:
host: 127.0.0.1
port: 6379
database: 0
password: yuan123456
#数据库配置
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver

Loading…
Cancel
Save