|
|
@@ -131,7 +131,7 @@ public class LiveRedConfServiceImpl implements ILiveRedConfService {
|
|
|
double score = localDateTime.atZone(java.time.ZoneId.systemDefault()).toInstant().toEpochMilli();
|
|
|
redisCache.redisTemplate.opsForZSet().add(cacheKey, String.valueOf(liveRedConf.getRedId()), score);
|
|
|
redisCache.redisTemplate.expire(cacheKey, 30, TimeUnit.MINUTES);
|
|
|
-
|
|
|
+
|
|
|
// 将红包配置缓存到 Redis(用于高并发查询)
|
|
|
String redConfCacheKey = REDPACKET_CONF_CACHE_KEY + liveRedConf.getRedId();
|
|
|
redisCache.setCacheObject(redConfCacheKey, JSONUtil.toJsonStr(liveRedConf), liveRedConf.getDuration().intValue() + 5, TimeUnit.MINUTES);
|
|
|
@@ -243,7 +243,7 @@ public class LiveRedConfServiceImpl implements ILiveRedConfService {
|
|
|
// * 4. 异步更新数据库
|
|
|
String redisKey = String.format(LiveKeysConstant.LIVE_HOME_PAGE_CONFIG_RED, red.getLiveId(), red.getRedId());
|
|
|
String userIdStr = String.valueOf(red.getUserId());
|
|
|
-
|
|
|
+
|
|
|
// 1. 使用 Redis HSETNX 原子操作保证幂等性(每个用户只能领取一次)
|
|
|
// 先尝试在 Redis 中标记用户已领取(原子操作,保证高并发安全)
|
|
|
LiveUserRedRecord record = new LiveUserRedRecord();
|
|
|
@@ -251,7 +251,7 @@ public class LiveRedConfServiceImpl implements ILiveRedConfService {
|
|
|
record.setLiveId(red.getLiveId());
|
|
|
record.setUserId(red.getUserId());
|
|
|
record.setCreateTime(new Date());
|
|
|
-
|
|
|
+
|
|
|
// 使用 HSETNX 原子操作:如果字段不存在则设置,返回 true;如果已存在则返回 false
|
|
|
Boolean claimed = redisCache.hashPutIfAbsent(redisKey, userIdStr, "claimed");
|
|
|
if (Boolean.FALSE.equals(claimed)) {
|
|
|
@@ -261,13 +261,13 @@ public class LiveRedConfServiceImpl implements ILiveRedConfService {
|
|
|
} else {
|
|
|
redisCache.expire(redisKey, 24, TimeUnit.HOURS);
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
try {
|
|
|
// 2. 从 Redis 读取红包配置(优先从缓存读取,提高响应速度)
|
|
|
String redConfCacheKey = REDPACKET_CONF_CACHE_KEY + red.getRedId();
|
|
|
Object confCache = redisCache.getCacheObject(redConfCacheKey);
|
|
|
LiveRedConf conf = null;
|
|
|
-
|
|
|
+
|
|
|
if (confCache != null) {
|
|
|
try {
|
|
|
conf = JSONUtil.toBean(confCache.toString(), LiveRedConf.class);
|
|
|
@@ -276,7 +276,7 @@ public class LiveRedConfServiceImpl implements ILiveRedConfService {
|
|
|
log.warn("从 Redis 缓存解析红包配置失败,从数据库读取,redId: {}", red.getRedId(), e);
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 如果 Redis 中没有配置,从数据库读取并缓存
|
|
|
if (conf == null) {
|
|
|
conf = baseMapper.selectLiveRedConfByRedId(red.getRedId());
|
|
|
@@ -286,14 +286,14 @@ public class LiveRedConfServiceImpl implements ILiveRedConfService {
|
|
|
log.debug("从数据库读取红包配置并缓存到 Redis,redId: {}", red.getRedId());
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 验证红包状态
|
|
|
if (conf == null || conf.getRedStatus() != 1) {
|
|
|
// 回滚:删除 Redis 中的标记
|
|
|
redisCache.hashDelete(redisKey, userIdStr);
|
|
|
return R.error("手慢了,红包已结束~");
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 3. 使用 Redis 原子操作减少剩余数量
|
|
|
if (getRemaining(red.getRedId()) <= 0 || !decreaseRemainingLotsIfPossible(red.getRedId())) {
|
|
|
// 回滚:删除 Redis 中的标记
|
|
|
@@ -307,7 +307,7 @@ public class LiveRedConfServiceImpl implements ILiveRedConfService {
|
|
|
redisCache.deleteObject(redConfCacheKey);
|
|
|
return R.error("手慢了,红包已被抢完~");
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 计算积分(平均分)
|
|
|
Long integral = calculateIntegralAverage(conf);
|
|
|
if (0L == integral) {
|
|
|
@@ -315,9 +315,9 @@ public class LiveRedConfServiceImpl implements ILiveRedConfService {
|
|
|
redisCache.hashDelete(redisKey, userIdStr);
|
|
|
return R.error("手慢了,红包被抢完了~");
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
record.setIntegral(integral);
|
|
|
-
|
|
|
+
|
|
|
// 4. 更新用户积分(同步操作,保证数据一致性)
|
|
|
BigDecimal balanceAmount = BigDecimal.valueOf(integral);
|
|
|
int updateResult = fsUserScrmMapper.incrIntegral(red.getUserId(), balanceAmount);
|
|
|
@@ -330,22 +330,22 @@ public class LiveRedConfServiceImpl implements ILiveRedConfService {
|
|
|
log.error("更新用户余额失败,userId: {}, balance: {}", red.getUserId(), balanceAmount);
|
|
|
return R.error("更新用户余额失败");
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 5. 更新 Redis 缓存中的记录(包含完整信息)
|
|
|
record.setCreateTime(new Date());
|
|
|
redisCache.hashPut(redisKey, userIdStr, JSONUtil.toJsonStr(record));
|
|
|
-
|
|
|
+
|
|
|
// 6. 异步更新数据库(提高响应速度,不阻塞用户)
|
|
|
// 查询用户当前余额(用于积分日志)
|
|
|
com.fs.hisStore.domain.FsUserScrm user = fsUserScrmMapper.selectFsUserById(red.getUserId());
|
|
|
Long currentIntegral = user.getIntegral() != null ? user.getIntegral() : 0L;
|
|
|
-
|
|
|
+
|
|
|
|
|
|
final LiveUserRedRecord finalRecord = record;
|
|
|
final LiveRedConf finalConf = conf;
|
|
|
final Long finalIntegral = integral;
|
|
|
final Long finalCurrentIntegral = currentIntegral;
|
|
|
-
|
|
|
+
|
|
|
try {
|
|
|
// 插入红包记录
|
|
|
userRedRecordMapper.insertLiveUserRedRecord(finalRecord);
|
|
|
@@ -368,11 +368,11 @@ public class LiveRedConfServiceImpl implements ILiveRedConfService {
|
|
|
log.error("异步更新数据库失败,userId: {}, redId: {}, integral: {}", red.getUserId(), red.getRedId(), finalIntegral, e);
|
|
|
}
|
|
|
|
|
|
-
|
|
|
|
|
|
-
|
|
|
- return R.ok("恭喜您成功抢到" + integral + "芳华币");
|
|
|
-
|
|
|
+
|
|
|
+
|
|
|
+ return R.ok("恭喜您成功抢到" + integral + "福币");
|
|
|
+
|
|
|
} catch (Exception e) {
|
|
|
// 发生异常,回滚 Redis 标记
|
|
|
redisCache.hashDelete(redisKey, userIdStr);
|
|
|
@@ -434,7 +434,7 @@ public class LiveRedConfServiceImpl implements ILiveRedConfService {
|
|
|
liveUserRedRecords = hashEntries.values().stream()
|
|
|
.map(value -> JSONUtil.toBean(JSONUtil.parseObj(value), LiveUserRedRecord.class))
|
|
|
.collect(Collectors.toList());
|
|
|
-
|
|
|
+
|
|
|
// 过滤掉已经存在于数据库中的记录(避免重复增加积分)
|
|
|
List<LiveUserRedRecord> newRecords = new ArrayList<>();
|
|
|
for (LiveUserRedRecord liveUserRedRecord : liveUserRedRecords) {
|
|
|
@@ -443,13 +443,13 @@ public class LiveRedConfServiceImpl implements ILiveRedConfService {
|
|
|
queryRecord.setUserId(liveUserRedRecord.getUserId());
|
|
|
queryRecord.setRedId(liveUserRedRecord.getRedId());
|
|
|
List<LiveUserRedRecord> existingRecords = userRedRecordMapper.selectLiveUserRedRecordList(queryRecord);
|
|
|
-
|
|
|
+
|
|
|
// 如果不存在,则添加到新记录列表
|
|
|
if (existingRecords == null || existingRecords.isEmpty()) {
|
|
|
newRecords.add(liveUserRedRecord);
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 只插入新记录(这些记录是从 Redis 同步过来的,但还没有插入数据库)
|
|
|
// 注意:积分增加逻辑已移除,现在只能通过 claimRedPacket 方法领取红包并增加积分
|
|
|
if (CollUtil.isNotEmpty(newRecords)) {
|