|
@@ -1,26 +1,46 @@
|
|
|
package com.fs.live.service.impl;
|
|
|
|
|
|
+import java.util.Collections;
|
|
|
+import java.util.Date;
|
|
|
import java.util.List;
|
|
|
+import java.util.UUID;
|
|
|
+import java.util.concurrent.ThreadLocalRandom;
|
|
|
+import java.util.concurrent.TimeUnit;
|
|
|
+
|
|
|
+import com.fs.common.core.redis.RedisCache;
|
|
|
import com.fs.common.utils.DateUtils;
|
|
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
|
|
+import com.fs.live.domain.LiveUserRedRecord;
|
|
|
+import com.fs.live.mapper.LiveUserRedRecordMapper;
|
|
|
+import com.fs.live.param.RedPO;
|
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
|
+import org.springframework.data.redis.core.script.DefaultRedisScript;
|
|
|
import org.springframework.stereotype.Service;
|
|
|
import com.fs.live.mapper.LiveRedConfMapper;
|
|
|
import com.fs.live.domain.LiveRedConf;
|
|
|
import com.fs.live.service.ILiveRedConfService;
|
|
|
+import org.springframework.transaction.annotation.Transactional;
|
|
|
|
|
|
/**
|
|
|
* 直播红包记录配置Service业务层处理
|
|
|
- *
|
|
|
+ *
|
|
|
* @author fs
|
|
|
* @date 2025-07-17
|
|
|
*/
|
|
|
@Service
|
|
|
public class LiveRedConfServiceImpl extends ServiceImpl<LiveRedConfMapper, LiveRedConf> implements ILiveRedConfService {
|
|
|
|
|
|
+ @Autowired
|
|
|
+ private LiveUserRedRecordMapper userRedRecordMapper;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private RedisCache redisCache;
|
|
|
+
|
|
|
+ private static final String REDPACKET_REMAININGLOTS_KEY = "live:red:remainingLots:";
|
|
|
+ private static final String REDPACKET_REMAININGNUM_KEY = "live:red:remainingNum:";
|
|
|
/**
|
|
|
* 查询直播红包记录配置
|
|
|
- *
|
|
|
+ *
|
|
|
* @param redId 直播红包记录配置主键
|
|
|
* @return 直播红包记录配置
|
|
|
*/
|
|
@@ -32,7 +52,7 @@ public class LiveRedConfServiceImpl extends ServiceImpl<LiveRedConfMapper, LiveR
|
|
|
|
|
|
/**
|
|
|
* 查询直播红包记录配置列表
|
|
|
- *
|
|
|
+ *
|
|
|
* @param liveRedConf 直播红包记录配置
|
|
|
* @return 直播红包记录配置
|
|
|
*/
|
|
@@ -44,7 +64,7 @@ public class LiveRedConfServiceImpl extends ServiceImpl<LiveRedConfMapper, LiveR
|
|
|
|
|
|
/**
|
|
|
* 新增直播红包记录配置
|
|
|
- *
|
|
|
+ *
|
|
|
* @param liveRedConf 直播红包记录配置
|
|
|
* @return 结果
|
|
|
*/
|
|
@@ -57,7 +77,7 @@ public class LiveRedConfServiceImpl extends ServiceImpl<LiveRedConfMapper, LiveR
|
|
|
|
|
|
/**
|
|
|
* 修改直播红包记录配置
|
|
|
- *
|
|
|
+ *
|
|
|
* @param liveRedConf 直播红包记录配置
|
|
|
* @return 结果
|
|
|
*/
|
|
@@ -69,7 +89,7 @@ public class LiveRedConfServiceImpl extends ServiceImpl<LiveRedConfMapper, LiveR
|
|
|
|
|
|
/**
|
|
|
* 批量删除直播红包记录配置
|
|
|
- *
|
|
|
+ *
|
|
|
* @param redIds 需要删除的直播红包记录配置主键
|
|
|
* @return 结果
|
|
|
*/
|
|
@@ -81,7 +101,7 @@ public class LiveRedConfServiceImpl extends ServiceImpl<LiveRedConfMapper, LiveR
|
|
|
|
|
|
/**
|
|
|
* 删除直播红包记录配置信息
|
|
|
- *
|
|
|
+ *
|
|
|
* @param redId 直播红包记录配置主键
|
|
|
* @return 结果
|
|
|
*/
|
|
@@ -90,4 +110,172 @@ public class LiveRedConfServiceImpl extends ServiceImpl<LiveRedConfMapper, LiveR
|
|
|
{
|
|
|
return baseMapper.deleteLiveRedConfByRedId(redId);
|
|
|
}
|
|
|
+
|
|
|
+ @Override
|
|
|
+ @Transactional
|
|
|
+ public LiveRedConf getById(Long redId) {
|
|
|
+ return baseMapper.selectById(redId);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ @Transactional
|
|
|
+ public List<LiveRedConf> getByLiveId(Long liveId) {
|
|
|
+ return baseMapper.selectByLiveId(liveId);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ @Transactional
|
|
|
+ public void create(LiveRedConf conf) {
|
|
|
+ baseMapper.insert(conf);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ @Transactional
|
|
|
+ public void update(LiveRedConf conf) {
|
|
|
+ baseMapper.update(conf);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ @Transactional
|
|
|
+ public void delete(Long redId) {
|
|
|
+ baseMapper.deleteById(redId);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 用户领取红包
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ @Transactional
|
|
|
+ public boolean claimRedPacket(RedPO red) {
|
|
|
+ String lockKey = REDPACKET_REMAININGLOTS_KEY + red.getRedId();
|
|
|
+ List<String> keyList = Collections.emptyList();
|
|
|
+ keyList.add(lockKey);
|
|
|
+
|
|
|
+ try {
|
|
|
+ //获取红包锁
|
|
|
+ if (!tryLock(lockKey, red.getUserId().toString(), 5)) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ LiveRedConf conf = baseMapper.selectById(red.getRedId());
|
|
|
+ if (conf == null || conf.getRedStatus() != 1) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ //redis剩余红包数
|
|
|
+ Integer remaining = getRemaining(red.getRedId());
|
|
|
+ if (remaining <= 0) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ //剩余金额
|
|
|
+ //Integer remainingNum = getRemainingNum(red.getRedId());
|
|
|
+ conf.setRemaining(remaining);
|
|
|
+ Long integral = calculateIntegralAverage(conf);
|
|
|
+ conf.setTotalSend(conf.getTotalSend() + 1);
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ // 更新数据库和缓存
|
|
|
+ baseMapper.update(conf);
|
|
|
+ decreaseRemainingLotsIfPossible(red.getRedId());
|
|
|
+ //decreaseRemainingNumIfPossible(red.getRedId(),integral);
|
|
|
+
|
|
|
+ // 记录用户红包
|
|
|
+ LiveUserRedRecord record = new LiveUserRedRecord();
|
|
|
+ record.setRedId(red.getRedId());
|
|
|
+ record.setLiveId(red.getLiveId());
|
|
|
+ record.setUserId(red.getUserId());
|
|
|
+ record.setIntegral(integral);
|
|
|
+ userRedRecordMapper.insert(record);
|
|
|
+
|
|
|
+ // WebSocket 通知
|
|
|
+ //String msg = String.format("用户 %d 抢到了红包 %d,获得 %d 芳华币", userId, redId, integral);
|
|
|
+ //WebSocketServer.notifyUsers(msg);
|
|
|
+
|
|
|
+ return true;
|
|
|
+ } finally {
|
|
|
+ releaseLock(keyList, red.getUserId().toString());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public String start(String redId, Long userId) {
|
|
|
+ LiveRedConf conf = baseMapper.selectById(redId);
|
|
|
+ if (conf != null && conf.getRedStatus() == 0) {
|
|
|
+ conf.setRedStatus(1L);
|
|
|
+ conf.setUpdateTime(new Date());
|
|
|
+ conf.setUpdateBy(userId.toString());
|
|
|
+ baseMapper.update(conf);
|
|
|
+ return "红包发放成功";
|
|
|
+ }
|
|
|
+ return "红包发放失败";
|
|
|
+ }
|
|
|
+
|
|
|
+ // 初始化剩余数量
|
|
|
+ public void initRemainingLots(Long redId, Integer totalLots) {
|
|
|
+ String key = REDPACKET_REMAININGLOTS_KEY + redId;
|
|
|
+ redisCache.setCacheObject(key, totalLots.toString());
|
|
|
+ }
|
|
|
+ // 初始化剩余金额
|
|
|
+ public void initRemainingNum(Long redId, Long redNum) {
|
|
|
+ String key = REDPACKET_REMAININGNUM_KEY + redId;
|
|
|
+ redisCache.setCacheObject(key, redNum.toString());
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取剩余数量
|
|
|
+ public Integer getRemaining(Long redId) {
|
|
|
+ String key = REDPACKET_REMAININGLOTS_KEY + redId;
|
|
|
+ String value = redisCache.getCacheObject(key);
|
|
|
+ return value == null ? 0 : Integer.parseInt(value);
|
|
|
+ }
|
|
|
+ // 获取剩余数量
|
|
|
+ public Integer getRemainingNum(Long redId) {
|
|
|
+ String key = REDPACKET_REMAININGNUM_KEY + redId;
|
|
|
+ String value = redisCache.getCacheObject(key);
|
|
|
+ return value == null ? 0 : Integer.parseInt(value);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 减少剩余数量(原子操作)
|
|
|
+ public void decreaseRemainingLotsIfPossible(Long redId) {
|
|
|
+ String key = REDPACKET_REMAININGLOTS_KEY + redId;
|
|
|
+ redisCache.incrementCacheValue(key, -1);
|
|
|
+ }
|
|
|
+ // 减少剩余数量(原子操作)
|
|
|
+ public void decreaseRemainingNumIfPossible(Long redId, Long integral) {
|
|
|
+ String key = REDPACKET_REMAININGNUM_KEY + redId;
|
|
|
+ redisCache.incrementCacheValue(key, -integral);
|
|
|
+ }
|
|
|
+
|
|
|
+ public boolean tryLock(String lockKey, String clientId, long expireTime) {
|
|
|
+ Boolean success = redisCache.setIfAbsent(lockKey, clientId, expireTime, TimeUnit.SECONDS);
|
|
|
+ return Boolean.TRUE.equals(success);
|
|
|
+ }
|
|
|
+
|
|
|
+ public void releaseLock(List<String> lockKey, String clientId) {
|
|
|
+ String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
|
|
|
+ redisCache.redisTemplate.execute(new DefaultRedisScript<>(script, Long.class), lockKey, clientId);
|
|
|
+ }
|
|
|
+
|
|
|
+ private Long calculateIntegralRandom(LiveRedConf conf) {
|
|
|
+ if (conf.getTotalSend() >= conf.getTotalLots()) {
|
|
|
+ return 0L; // 已发完
|
|
|
+ }
|
|
|
+ //剩余红包个数
|
|
|
+ long remainingLots = conf.getTotalLots() - conf.getTotalSend();
|
|
|
+ //剩余红包金额
|
|
|
+ int remainingNum = getRemainingNum(conf.getRedId());
|
|
|
+ if (remainingLots == 1){
|
|
|
+ return (long) remainingNum;
|
|
|
+ }
|
|
|
+ long avgIntegral = remainingNum / remainingLots;
|
|
|
+ long minIntegral = avgIntegral / 3 == 0 ? 1 : avgIntegral / 3;
|
|
|
+ long maxIntegral = avgIntegral * 3 >= remainingNum ? remainingNum : avgIntegral * 3;
|
|
|
+ return ThreadLocalRandom.current().nextLong(minIntegral, maxIntegral + 1);
|
|
|
+ }
|
|
|
+
|
|
|
+ private Long calculateIntegralAverage(LiveRedConf conf) {
|
|
|
+ if (conf.getTotalSend() >= conf.getTotalLots()) {
|
|
|
+ return 0L; // 已发完
|
|
|
+ }
|
|
|
+ return conf.getRedNum() / conf.getTotalLots();
|
|
|
+ }
|
|
|
}
|