|
|
@@ -0,0 +1,687 @@
|
|
|
+package com.fs.live.service.impl;
|
|
|
+
|
|
|
+import java.math.BigDecimal;
|
|
|
+import java.util.Date;
|
|
|
+import java.util.List;
|
|
|
+import java.util.Objects;
|
|
|
+import java.util.concurrent.TimeUnit;
|
|
|
+
|
|
|
+import cn.hutool.json.JSONUtil;
|
|
|
+import com.alibaba.fastjson.JSON;
|
|
|
+import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
|
|
|
+import com.fs.common.constant.FsConstants;
|
|
|
+import com.fs.common.core.domain.R;
|
|
|
+import com.fs.common.core.redis.RedisCache;
|
|
|
+import com.fs.common.utils.CloudHostUtils;
|
|
|
+import com.fs.common.utils.DateUtils;
|
|
|
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
|
|
+import com.fs.common.utils.StringUtils;
|
|
|
+import com.fs.company.domain.Company;
|
|
|
+import com.fs.company.domain.CompanyRedPacketBalanceLogs;
|
|
|
+import com.fs.company.mapper.CompanyMapper;
|
|
|
+import com.fs.company.mapper.CompanyRedPacketBalanceLogsMapper;
|
|
|
+import com.fs.company.service.ICompanyService;
|
|
|
+import com.fs.course.config.CourseConfig;
|
|
|
+import com.fs.course.domain.*;
|
|
|
+import com.fs.course.mapper.BalanceRollbackErrorMapper;
|
|
|
+import com.fs.course.param.FsCourseSendRewardUParam;
|
|
|
+import com.fs.course.service.impl.FsUserCourseVideoServiceImpl;
|
|
|
+import com.fs.his.domain.FsUser;
|
|
|
+import com.fs.his.domain.FsUserIntegralLogs;
|
|
|
+import com.fs.his.domain.FsUserWx;
|
|
|
+import com.fs.his.mapper.FsUserIntegralLogsMapper;
|
|
|
+import com.fs.his.mapper.FsUserMapper;
|
|
|
+import com.fs.his.param.WxSendRedPacketParam;
|
|
|
+import com.fs.his.service.IFsStorePaymentService;
|
|
|
+import com.fs.live.domain.Live;
|
|
|
+import com.fs.live.domain.LiveWatchConfig;
|
|
|
+import com.fs.live.domain.LiveWatchUser;
|
|
|
+import com.fs.live.mapper.LiveWatchUserMapper;
|
|
|
+import com.fs.live.param.LiveRedPacketParam;
|
|
|
+import com.fs.live.mapper.LiveMapper;
|
|
|
+import com.fs.live.service.ILiveMsgService;
|
|
|
+import com.fs.live.service.ILiveService;
|
|
|
+import com.fs.live.service.ILiveWatchUserService;
|
|
|
+import com.fs.voice.utils.StringUtil;
|
|
|
+import com.github.binarywang.wxpay.bean.transfer.TransferBillsResult;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.redisson.api.RLock;
|
|
|
+import org.redisson.api.RedissonClient;
|
|
|
+import org.slf4j.Logger;
|
|
|
+import org.slf4j.LoggerFactory;
|
|
|
+import org.springframework.beans.BeanUtils;
|
|
|
+import org.springframework.beans.factory.annotation.Autowired;
|
|
|
+import org.springframework.context.annotation.Lazy;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+import com.fs.live.mapper.LiveRedPacketLogMapper;
|
|
|
+import com.fs.live.domain.LiveRedPacketLog;
|
|
|
+import com.fs.live.service.ILiveRedPacketLogService;
|
|
|
+import org.springframework.transaction.annotation.Transactional;
|
|
|
+import org.springframework.transaction.support.TransactionSynchronization;
|
|
|
+import org.springframework.transaction.support.TransactionSynchronizationManager;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 直播红包 记录Service业务层处理
|
|
|
+ *
|
|
|
+ * @author fs
|
|
|
+ * @date 2026-06-08
|
|
|
+ */
|
|
|
+@Slf4j
|
|
|
+@Service
|
|
|
+public class LiveRedPacketLogServiceImpl extends ServiceImpl<LiveRedPacketLogMapper, LiveRedPacketLog> implements ILiveRedPacketLogService {
|
|
|
+
|
|
|
+ private static final Logger logger = LoggerFactory.getLogger(LiveRedPacketLogServiceImpl.class);
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private RedissonClient redissonClient;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private FsUserMapper fsUserMapper;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private LiveWatchUserMapper watchUserMapper;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ @Lazy
|
|
|
+ private ILiveService iLiveService;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private LiveRedPacketLogMapper redPacketLogMapper;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ RedisCache redisCache;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private IFsStorePaymentService paymentService;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private BalanceRollbackErrorMapper balanceRollbackErrorMapper;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ ICompanyService companyService;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private CompanyMapper companyMapper;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private FsUserIntegralLogsMapper fsUserIntegralLogsMapper;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private CompanyRedPacketBalanceLogsMapper companyRedPacketBalanceLogsMapper;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ @Lazy
|
|
|
+ private ILiveWatchUserService liveWatchUserService;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 查询直播红包 记录
|
|
|
+ *
|
|
|
+ * @param logId 直播红包 记录主键
|
|
|
+ * @return 直播红包 记录
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public LiveRedPacketLog selectLiveRedPacketLogByLogId(Long logId)
|
|
|
+ {
|
|
|
+ return baseMapper.selectLiveRedPacketLogByLogId(logId);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 查询直播红包 记录列表
|
|
|
+ *
|
|
|
+ * @param liveRedPacketLog 直播红包 记录
|
|
|
+ * @return 直播红包 记录
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public List<LiveRedPacketLog> selectLiveRedPacketLogList(LiveRedPacketLog liveRedPacketLog)
|
|
|
+ {
|
|
|
+ return baseMapper.selectLiveRedPacketLogList(liveRedPacketLog);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 新增直播红包 记录
|
|
|
+ *
|
|
|
+ * @param liveRedPacketLog 直播红包 记录
|
|
|
+ * @return 结果
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public int insertLiveRedPacketLog(LiveRedPacketLog liveRedPacketLog)
|
|
|
+ {
|
|
|
+ liveRedPacketLog.setCreateTime(DateUtils.getNowDate());
|
|
|
+ return baseMapper.insertLiveRedPacketLog(liveRedPacketLog);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 修改直播红包 记录
|
|
|
+ *
|
|
|
+ * @param liveRedPacketLog 直播红包 记录
|
|
|
+ * @return 结果
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public int updateLiveRedPacketLog(LiveRedPacketLog liveRedPacketLog)
|
|
|
+ {
|
|
|
+ liveRedPacketLog.setUpdateTime(DateUtils.getNowDate());
|
|
|
+ return baseMapper.updateLiveRedPacketLog(liveRedPacketLog);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 批量删除直播红包 记录
|
|
|
+ *
|
|
|
+ * @param logIds 需要删除的直播红包 记录主键
|
|
|
+ * @return 结果
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public int deleteLiveRedPacketLogByLogIds(Long[] logIds)
|
|
|
+ {
|
|
|
+ return baseMapper.deleteLiveRedPacketLogByLogIds(logIds);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 删除直播红包 记录信息
|
|
|
+ *
|
|
|
+ * @param logId 直播红包 记录主键
|
|
|
+ * @return 结果
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public int deleteLiveRedPacketLogByLogId(Long logId)
|
|
|
+ {
|
|
|
+ return baseMapper.deleteLiveRedPacketLogByLogId(logId);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ @Transactional
|
|
|
+ public R sendLiveReward(LiveRedPacketParam param) {
|
|
|
+
|
|
|
+ // 生成锁的key,基于用户ID和直播ID确保同一用户同一直播的请求被锁定
|
|
|
+ String lockKey = "liveReward_lock:user:" + param.getUserId() + ":live:" + param.getLiveId();
|
|
|
+ RLock lock = redissonClient.getLock(lockKey);
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 尝试获取锁,等待时间5秒,锁过期时间120秒
|
|
|
+ boolean isLocked = lock.tryLock(5, 120, TimeUnit.SECONDS);
|
|
|
+ if (!isLocked) {
|
|
|
+ logger.warn("直播获取锁失败,用户ID:{},直播ID:{}", param.getUserId(), param.getLiveId());
|
|
|
+ return R.error("操作频繁,请稍后再试!");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 从缓存里面更新一下时长
|
|
|
+ R cloneResult = liveWatchUserService.sendLiveRewardClone(param.getLiveId(), param.getUserId());
|
|
|
+
|
|
|
+ if (cloneResult.get("code") == null || !Integer.valueOf(200).equals(cloneResult.get("code"))){
|
|
|
+ return cloneResult.get("msg") != null ? cloneResult : R.error("直播观看校验时长失败");
|
|
|
+ }
|
|
|
+
|
|
|
+ //保证锁的释放 在事物完成之后
|
|
|
+ TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
|
|
|
+ @Override
|
|
|
+ public void afterCompletion(int status) {
|
|
|
+ try {
|
|
|
+ if (lock.isHeldByCurrentThread()) {
|
|
|
+ lock.unlock();
|
|
|
+ logger.info("直播事务完成后释放锁,用户ID:{},直播ID:{}", param.getUserId(), param.getLiveId());
|
|
|
+ }
|
|
|
+ } catch (IllegalMonitorStateException e) {
|
|
|
+ logger.warn("直播释放锁异常(可能已过期),用户ID:{},直播ID:{}", param.getUserId(), param.getLiveId());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+
|
|
|
+ logger.info("直播成功获取锁,开始处理奖励发放,用户ID:{},直播ID:{}", param.getUserId(), param.getLiveId());
|
|
|
+
|
|
|
+ // 获取用户信息
|
|
|
+ FsUser user = fsUserMapper.selectFsUserByUserId(param.getUserId());
|
|
|
+
|
|
|
+ log.info("直播查询会员信息:{}", user);
|
|
|
+ if (user.getStatus() == 0) {
|
|
|
+ return R.error("会员被停用,无权限,请联系客服!");
|
|
|
+ }
|
|
|
+
|
|
|
+ Live live = iLiveService.selectLiveByLiveId(param.getLiveId());
|
|
|
+ LiveWatchConfig config = JSON.parseObject(live.getConfigJson(), LiveWatchConfig.class);
|
|
|
+ log.info("直播配置:{}", config);
|
|
|
+ // 先查是否开启了启动 直播完课奖励
|
|
|
+ if (config.getEnabled() && 3 == config.getParticipateCondition()) {
|
|
|
+
|
|
|
+ //直播/录播
|
|
|
+ LiveWatchUser watchUser=null;
|
|
|
+ //录播
|
|
|
+ LiveWatchUser replayUser=null;
|
|
|
+
|
|
|
+ // 根据直播类型判断是否已发放奖励 先查直播
|
|
|
+ watchUser = watchUserMapper.getWatchLiveByLiveFlag(param.getUserId(), param.getLiveId());
|
|
|
+ log.info("直播看课记录:{}", watchUser);
|
|
|
+ if (watchUser == null) {
|
|
|
+ // 直播没有记录 查录播
|
|
|
+ watchUser = watchUserMapper.getWatchLiveByReplayFlag(param.getUserId(), param.getLiveId());
|
|
|
+
|
|
|
+ if (watchUser==null){
|
|
|
+ return R.error("您没有观看过该课程,无奖励发放!");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ //直播不空 那要存一下录播
|
|
|
+ else {
|
|
|
+ // 直播没有记录 查录播
|
|
|
+ replayUser = watchUserMapper.getWatchLiveByReplayFlag(param.getUserId(), param.getLiveId());
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ //是否以及领取了奖励 看直播或者录播 俩者只要有一个领取了奖励 就返回
|
|
|
+ if (watchUser.getRewardType() != null || (replayUser != null && replayUser.getRewardType() != null)) {
|
|
|
+
|
|
|
+ LiveRedPacketLog liveRedPacketLog = redPacketLogMapper.selectLiveRedPacketLogByTemporary(param.getLiveId(), param.getUserId());
|
|
|
+
|
|
|
+ log.info("直播课程红包:{}", liveRedPacketLog);
|
|
|
+ if (liveRedPacketLog != null && liveRedPacketLog.getStatus() == 1) {
|
|
|
+ return R.error("已领取该直播课程奖励,不可重复领取!");
|
|
|
+ }
|
|
|
+ if (liveRedPacketLog != null && liveRedPacketLog.getStatus() == 0) {
|
|
|
+ if (StringUtils.isNotEmpty(liveRedPacketLog.getResult())) {
|
|
|
+ R r = JSON.parseObject(liveRedPacketLog.getResult(), R.class);
|
|
|
+ return r;
|
|
|
+ } else {
|
|
|
+ return R.error("操作频繁,请稍后再试!");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (liveRedPacketLog != null && liveRedPacketLog.getStatus() == 2) {
|
|
|
+ return R.error("请联系客服补发");
|
|
|
+ }
|
|
|
+ return R.error("奖励已发放");
|
|
|
+ }
|
|
|
+
|
|
|
+ //判断直播数据 是否满足 完课/如果有录播数据 再判断录播是否满足完课
|
|
|
+ // 在线时长
|
|
|
+ long onlineSeconds = watchUser.getOnlineSeconds() != null ? watchUser.getOnlineSeconds() : 0L;
|
|
|
+ Long duration = live.getDuration();
|
|
|
+ Long completionRate = config.getCompletionRate();
|
|
|
+
|
|
|
+ if (duration == null || duration <= 0 || completionRate == null || completionRate <= 0) {
|
|
|
+ return R.error("直播完课配置异常,无法发放奖励");
|
|
|
+ }
|
|
|
+
|
|
|
+ //优先 发直播红包
|
|
|
+ if (watchUser.getLiveFlag()==1){
|
|
|
+
|
|
|
+ if (onlineSeconds * 100 < duration * completionRate) {
|
|
|
+ //直播时长不满足,看录播 如果录播的时长看课时长 大于了 设定的百分比则 可以领取积分
|
|
|
+ if (replayUser != null && replayUser.getOnlineSeconds() != null
|
|
|
+ && replayUser.getOnlineSeconds() * 100 >= duration * completionRate) {
|
|
|
+ // 如果是 回放完课 发积分
|
|
|
+ return sendLiveIntegralReward(param, user, watchUser, config);
|
|
|
+ }else {
|
|
|
+ return R.error("观看时长未达到完课要求,请看继续观看回放,之后再领取");
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ WxSendRedPacketParam packetParam = new WxSendRedPacketParam();
|
|
|
+
|
|
|
+ if (StringUtil.strIsNullOrEmpty(user.getAppOpenId())){
|
|
|
+ return R.error("请重新登录app");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 用app的 appOpenId
|
|
|
+ String openId = user.getAppOpenId();
|
|
|
+ packetParam.setOpenId(openId);
|
|
|
+ BeanUtils.copyProperties(param, packetParam);
|
|
|
+
|
|
|
+ return sendAppLiveRedPacketAuto(packetParam, watchUser, config, param);
|
|
|
+ }
|
|
|
+ // 其次 是 录播 再发 回放积分
|
|
|
+ else if (watchUser.getReplayFlag()==1){
|
|
|
+
|
|
|
+ if (onlineSeconds * 100 < duration * completionRate) {
|
|
|
+ return R.error("观看时长未达到完课要求,请继续观看");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果是 回放完课 发积分
|
|
|
+ return sendLiveIntegralReward(param, user, watchUser, config);
|
|
|
+ }else {
|
|
|
+ return R.error("未知 直播观看类型 ");
|
|
|
+ }
|
|
|
+ }else {
|
|
|
+ return R.error("当前直播 并未启动 直播完课奖励 ");
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ } catch (InterruptedException e) {
|
|
|
+ Thread.currentThread().interrupt();
|
|
|
+ logger.error("直播获取锁被中断,用户ID:{},直播ID:{}", param.getUserId(), param.getLiveId(), e);
|
|
|
+ return R.error("系统繁忙,请重试!");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public R syncLiveRedPacket(String outBatchNo, String batchId) {
|
|
|
+ LiveRedPacketLog log = redPacketLogMapper.selectLiveRedPacketLogByBatchNo(outBatchNo);
|
|
|
+ if (log!=null){
|
|
|
+ log.setStatus(1L);
|
|
|
+ log.setUpdateTime(new Date());
|
|
|
+ log.setBatchId(batchId);
|
|
|
+ redPacketLogMapper.updateLiveRedPacketLog(log);
|
|
|
+
|
|
|
+ // 更新扣减状态
|
|
|
+ CompanyRedPacketBalanceLogs redLogs = new CompanyRedPacketBalanceLogs();
|
|
|
+ redLogs.setRedPacketId(log.getLogId());
|
|
|
+ redLogs.setStatus(1L);
|
|
|
+ companyRedPacketBalanceLogsMapper.updateCompanyRedPacketBalanceLogsByRedPacketId(redLogs);
|
|
|
+
|
|
|
+ return R.ok();
|
|
|
+ }
|
|
|
+ return R.error("批次不存在");
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 直播 看课的的
|
|
|
+ */
|
|
|
+ private R sendAppLiveRedPacketAuto(WxSendRedPacketParam packetParam,LiveWatchUser watchUser,LiveWatchConfig config,LiveRedPacketParam param) {
|
|
|
+
|
|
|
+
|
|
|
+ // 确定红包金额
|
|
|
+ BigDecimal amount = config.getRedPacketAmount();
|
|
|
+
|
|
|
+ packetParam.setAmount(amount);
|
|
|
+
|
|
|
+ if (amount.compareTo(BigDecimal.ZERO) > 0) {
|
|
|
+
|
|
|
+ // 预设值异常对象
|
|
|
+
|
|
|
+ BalanceRollbackError balanceRollbackError = new BalanceRollbackError();
|
|
|
+ balanceRollbackError.setCompanyId(packetParam.getCompanyId());
|
|
|
+ balanceRollbackError.setUserId(watchUser.getUserId());
|
|
|
+ balanceRollbackError.setLogId(watchUser.getId());
|
|
|
+ balanceRollbackError.setVideoId(watchUser.getLiveId());
|
|
|
+ balanceRollbackError.setStatus(0);
|
|
|
+ balanceRollbackError.setMoney(amount);
|
|
|
+
|
|
|
+ // 打开红包扣减功能
|
|
|
+ if ("1".equals(config.getIsRedPackageBalanceDeduction())) {
|
|
|
+
|
|
|
+ if (packetParam.getCompanyId() == null) {
|
|
|
+ logger.error("直播发送红包参数错误,公司不能为空,异常请求参数{}", packetParam);
|
|
|
+ return R.error("发送红包失败,请联系管理员,公司不能为空");
|
|
|
+ }
|
|
|
+ String companyMoneyKey = FsConstants.COMPANY_MONEY_KEY + packetParam.getCompanyId();
|
|
|
+
|
|
|
+ // 第一次加锁:预扣减余额
|
|
|
+ RLock lock1 = redissonClient.getLock(FsConstants.COMPANY_MONEY_LOCK + packetParam.getCompanyId());
|
|
|
+ boolean lockAcquired = false;
|
|
|
+ BigDecimal newMoney;
|
|
|
+ try {
|
|
|
+ if (lock1.tryLock(3, 10, TimeUnit.SECONDS)) {
|
|
|
+ lockAcquired = true;
|
|
|
+ BigDecimal originalMoney;
|
|
|
+ // 获取当前余额
|
|
|
+ String moneyStr = redisCache.getCacheObject(companyMoneyKey);
|
|
|
+ if (StringUtils.isNotEmpty(moneyStr)) {
|
|
|
+ originalMoney = new BigDecimal(moneyStr);
|
|
|
+ } else {
|
|
|
+ // 缓存没有值,重启系统恢复redis数据 保证数据正确性
|
|
|
+ logger.error("直播发送红包获取redis余额缓存异常,异常请求参数{}", packetParam);
|
|
|
+ return R.error("系统异常,余额缓存异常,请稍后重试");
|
|
|
+ }
|
|
|
+
|
|
|
+ if (originalMoney.compareTo(BigDecimal.ZERO) < 0) {
|
|
|
+ logger.error("服务商余额不足,异常请求参数{}", packetParam);
|
|
|
+ return R.error("服务商余额不足,请联系群主服务器充值!");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 预扣减金额
|
|
|
+ newMoney = originalMoney.subtract(amount);
|
|
|
+ redisCache.setCacheObject(companyMoneyKey, newMoney.toString());
|
|
|
+ } else {
|
|
|
+ logger.error("获取redis锁失败,异常请求参数{}", packetParam);
|
|
|
+ return R.error("系统繁忙,请稍后重试");
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ logger.error("预扣减余额失败: 异常请求参数{},异常信息{}", packetParam, e.getMessage(), e);
|
|
|
+ return R.error("系统异常,请稍后重试");
|
|
|
+ } finally {
|
|
|
+ // 只有在成功获取锁的情况下才释放锁
|
|
|
+ if (lockAcquired && lock1.isHeldByCurrentThread()) {
|
|
|
+ try {
|
|
|
+ lock1.unlock();
|
|
|
+ } catch (IllegalMonitorStateException e) {
|
|
|
+ logger.warn("尝试释放非当前线程持有的锁: companyId={}", packetParam.getCompanyId());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ // 调用第三方接口(锁外操作)
|
|
|
+ R sendRedPacket;
|
|
|
+ try {
|
|
|
+ sendRedPacket = paymentService.sendAppLiveRedPacket(packetParam);
|
|
|
+ } catch (Exception e) {
|
|
|
+ logger.error("红包发送异常: 异常请求参数{}", packetParam, e);
|
|
|
+ // 异常时回滚余额
|
|
|
+
|
|
|
+ rollbackBalance(balanceRollbackError);
|
|
|
+ return R.error("奖励发送失败,请联系客服");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 红包发送成功处理
|
|
|
+ if (sendRedPacket.get("code").equals(200)) {
|
|
|
+
|
|
|
+ // 更新观看记录的奖励类型
|
|
|
+ watchUser.setRewardType(1);
|
|
|
+ watchUser.setSendType(1);
|
|
|
+ watchUserMapper.updateLiveWatchUser(watchUser);
|
|
|
+
|
|
|
+ LiveRedPacketLog liveRedPacketLog = new LiveRedPacketLog();
|
|
|
+ TransferBillsResult transferBillsResult;
|
|
|
+ if (sendRedPacket.get("isNew").equals(1)) {
|
|
|
+ transferBillsResult = (TransferBillsResult) sendRedPacket.get("data");
|
|
|
+ liveRedPacketLog.setResult(JSON.toJSONString(sendRedPacket));
|
|
|
+ liveRedPacketLog.setOutBatchNo(transferBillsResult.getOutBillNo());
|
|
|
+ liveRedPacketLog.setBatchId(transferBillsResult.getTransferBillNo());
|
|
|
+ } else {
|
|
|
+ liveRedPacketLog.setOutBatchNo(sendRedPacket.get("orderCode").toString());
|
|
|
+ liveRedPacketLog.setBatchId(sendRedPacket.get("batchId").toString());
|
|
|
+ }
|
|
|
+
|
|
|
+ liveRedPacketLog.setCompanyId(param.getCompanyId());
|
|
|
+ liveRedPacketLog.setUserId(watchUser.getUserId());
|
|
|
+ liveRedPacketLog.setLiveId(watchUser.getLiveId());
|
|
|
+ liveRedPacketLog.setStatus(0L);
|
|
|
+ liveRedPacketLog.setCompanyUserId(param.getCompanyUserId());
|
|
|
+ liveRedPacketLog.setCreateTime(new Date());
|
|
|
+ liveRedPacketLog.setAmount(amount);
|
|
|
+ liveRedPacketLog.setWatchLogId(watchUser.getId());
|
|
|
+ liveRedPacketLog.setAppId(packetParam.getAppId());
|
|
|
+ liveRedPacketLog.setWathcType(1L);
|
|
|
+
|
|
|
+ redPacketLogMapper.insertLiveRedPacketLog(liveRedPacketLog);
|
|
|
+
|
|
|
+ // 异步登记余额扣减日志
|
|
|
+ BigDecimal money = amount.multiply(BigDecimal.valueOf(-1));
|
|
|
+ companyService.asyncRecordBalanceLog(packetParam.getCompanyId(), money, 15, newMoney, "发放直播红包", liveRedPacketLog.getLogId());
|
|
|
+
|
|
|
+ return sendRedPacket;
|
|
|
+
|
|
|
+
|
|
|
+ } else {
|
|
|
+ // 发送失败,回滚余额
|
|
|
+ rollbackBalance(balanceRollbackError);
|
|
|
+ return R.error("奖励发送失败,请联系客服");
|
|
|
+ }
|
|
|
+
|
|
|
+ // ===================== 本次修改目的为了实时扣减公司余额=====================
|
|
|
+ } else {
|
|
|
+ Company company = companyMapper.selectCompanyById(packetParam.getCompanyId());
|
|
|
+ BigDecimal money = company.getMoney();
|
|
|
+ if (money.compareTo(BigDecimal.ZERO) <= 0) {
|
|
|
+ return R.error("服务商余额不足,请联系群主服务器充值!");
|
|
|
+ }
|
|
|
+
|
|
|
+ try{
|
|
|
+ // 发送红包
|
|
|
+ R sendRedPacket = paymentService.sendAppLiveRedPacket(packetParam);
|
|
|
+ if (sendRedPacket.get("code").equals(200)) {
|
|
|
+
|
|
|
+ // 更新观看记录的奖励类型
|
|
|
+ watchUser.setRewardType(1);
|
|
|
+ watchUser.setSendType(1);
|
|
|
+ watchUserMapper.updateLiveWatchUser(watchUser);
|
|
|
+
|
|
|
+ LiveRedPacketLog liveRedPacketLog = new LiveRedPacketLog();
|
|
|
+ TransferBillsResult transferBillsResult;
|
|
|
+ if (sendRedPacket.get("isNew").equals(1)) {
|
|
|
+ transferBillsResult = (TransferBillsResult) sendRedPacket.get("data");
|
|
|
+ liveRedPacketLog.setResult(JSON.toJSONString(sendRedPacket));
|
|
|
+ liveRedPacketLog.setOutBatchNo(transferBillsResult.getOutBillNo());
|
|
|
+ liveRedPacketLog.setBatchId(transferBillsResult.getTransferBillNo());
|
|
|
+ } else {
|
|
|
+ liveRedPacketLog.setOutBatchNo(sendRedPacket.get("orderCode").toString());
|
|
|
+ liveRedPacketLog.setBatchId(sendRedPacket.get("batchId").toString());
|
|
|
+ }
|
|
|
+ // 添加红包记录
|
|
|
+ liveRedPacketLog.setCompanyId(param.getCompanyId());
|
|
|
+ liveRedPacketLog.setUserId(watchUser.getUserId());
|
|
|
+ liveRedPacketLog.setLiveId(watchUser.getLiveId());
|
|
|
+ liveRedPacketLog.setStatus(0L);
|
|
|
+ liveRedPacketLog.setCompanyUserId(param.getCompanyUserId());
|
|
|
+ liveRedPacketLog.setCreateTime(new Date());
|
|
|
+ liveRedPacketLog.setAmount(amount);
|
|
|
+ liveRedPacketLog.setWatchLogId(watchUser.getId());
|
|
|
+ liveRedPacketLog.setAppId( packetParam.getAppId());
|
|
|
+
|
|
|
+ redPacketLogMapper.insertLiveRedPacketLog(liveRedPacketLog);
|
|
|
+
|
|
|
+ return sendRedPacket;
|
|
|
+ } else {
|
|
|
+ return R.error("奖励发送失败,请联系客服");
|
|
|
+ }
|
|
|
+ }catch (Exception e){
|
|
|
+ return R.error(e.getMessage());
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+
|
|
|
+ // 更新直播观看记录的奖励类型
|
|
|
+ watchUser.setRewardType(1);
|
|
|
+ watchUser.setSendType(1);
|
|
|
+ watchUserMapper.updateLiveWatchUser(watchUser);
|
|
|
+
|
|
|
+ LiveRedPacketLog liveRedPacketLog = new LiveRedPacketLog();
|
|
|
+ // 添加红包记录
|
|
|
+ liveRedPacketLog.setCompanyId(param.getCompanyId());
|
|
|
+ liveRedPacketLog.setUserId(watchUser.getUserId());
|
|
|
+ liveRedPacketLog.setLiveId(watchUser.getLiveId());
|
|
|
+ liveRedPacketLog.setStatus(1L);
|
|
|
+ liveRedPacketLog.setRemark("设置的直播红包金额为0");
|
|
|
+ liveRedPacketLog.setCompanyUserId(param.getCompanyUserId());
|
|
|
+ liveRedPacketLog.setCreateTime(new Date());
|
|
|
+ liveRedPacketLog.setAmount(BigDecimal.ZERO);
|
|
|
+ liveRedPacketLog.setWatchLogId(watchUser.getId());
|
|
|
+ liveRedPacketLog.setAppId( packetParam.getAppId());
|
|
|
+ redPacketLogMapper.insertLiveRedPacketLog(liveRedPacketLog);
|
|
|
+
|
|
|
+ return R.ok("答题成功!");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @Description: 回滚redis缓存中的余额 异常登记回滚异常表,定时重新回滚
|
|
|
+ * @Param:
|
|
|
+ * @Return:
|
|
|
+ * @Author
|
|
|
+ * @Date 2025/10/22 10:37
|
|
|
+ */
|
|
|
+ private void rollbackBalance(BalanceRollbackError balanceRollbackError) {
|
|
|
+ String companyMoneyKey = FsConstants.COMPANY_MONEY_KEY + balanceRollbackError.getCompanyId();
|
|
|
+ RLock lock2 = redissonClient.getLock(FsConstants.COMPANY_MONEY_LOCK + balanceRollbackError.getCompanyId());
|
|
|
+ boolean lockAcquired = false;
|
|
|
+ boolean backError = true;
|
|
|
+ try {
|
|
|
+ if (lock2.tryLock(3, 10, TimeUnit.SECONDS)) {
|
|
|
+ lockAcquired = true;
|
|
|
+ // 获取当前余额
|
|
|
+ String currentMoneyStr = redisCache.getCacheObject(companyMoneyKey);
|
|
|
+ if (StringUtils.isEmpty(currentMoneyStr)) {
|
|
|
+ throw new RuntimeException("回滚余额异常");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 回滚金额(加回之前扣减的金额)
|
|
|
+ BigDecimal rollbackMoney = new BigDecimal(currentMoneyStr).add(balanceRollbackError.getMoney());
|
|
|
+ redisCache.setCacheObject(companyMoneyKey, rollbackMoney.toString());
|
|
|
+ backError = false;
|
|
|
+ logger.info("余额回滚成功: companyId={}, amount={}", balanceRollbackError.getCompanyId(), balanceRollbackError.getMoney());
|
|
|
+
|
|
|
+ } else {
|
|
|
+ logger.warn("回滚余额时获取锁失败: companyId={}", balanceRollbackError.getCompanyId());
|
|
|
+ // 登记回滚余额异常表
|
|
|
+ balanceRollbackErrorMapper.insert(balanceRollbackError);
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ logger.error("回滚余额时发生异常: companyId={}", balanceRollbackError.getCompanyId(), e);
|
|
|
+ // 登记回滚余额异常表
|
|
|
+ if (backError) {
|
|
|
+ balanceRollbackErrorMapper.insert(balanceRollbackError);
|
|
|
+ }
|
|
|
+ } finally {
|
|
|
+ // 只有在成功获取锁的情况下才释放锁
|
|
|
+ if (lockAcquired && lock2.isHeldByCurrentThread()) {
|
|
|
+ try {
|
|
|
+ lock2.unlock();
|
|
|
+ } catch (IllegalMonitorStateException e) {
|
|
|
+ logger.warn("尝试释放非当前线程持有的锁: balanceRollbackError={}", balanceRollbackError);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 发放直播积分奖励
|
|
|
+ *
|
|
|
+ * @param user 用户信息
|
|
|
+ * @param watchUser 观看日志
|
|
|
+ * @param config 配置信息
|
|
|
+ * @return 处理结果
|
|
|
+ */
|
|
|
+ private R sendLiveIntegralReward(LiveRedPacketParam param, FsUser user, LiveWatchUser watchUser, LiveWatchConfig config) {
|
|
|
+ // 更新用户积分
|
|
|
+ FsUser userMap = new FsUser();
|
|
|
+ userMap.setUserId(user.getUserId());
|
|
|
+ userMap.setIntegral(user.getIntegral() + config.getScoreAmount());
|
|
|
+ fsUserMapper.updateFsUser(userMap);
|
|
|
+
|
|
|
+ // 记录积分日志
|
|
|
+ FsUserIntegralLogs integralLogs = new FsUserIntegralLogs();
|
|
|
+ integralLogs.setIntegral(config.getScoreAmount());
|
|
|
+ integralLogs.setUserId(user.getUserId());
|
|
|
+ integralLogs.setBalance(userMap.getIntegral());
|
|
|
+ integralLogs.setLogType(25);
|
|
|
+ integralLogs.setBusinessId(StringUtils.isNotEmpty(watchUser.getId().toString()) ? watchUser.getId().toString() : null);
|
|
|
+ integralLogs.setCreateTime(new Date());
|
|
|
+ fsUserIntegralLogsMapper.insertFsUserIntegralLogs(integralLogs);
|
|
|
+
|
|
|
+ //更新看课记录的奖励类型
|
|
|
+ watchUser.setRewardType(2);
|
|
|
+ watchUser.setSendType(1);
|
|
|
+ watchUserMapper.updateLiveWatchUser(watchUser);
|
|
|
+ logger.info("发放奖励====================》直播看课记录,{}", watchUser);
|
|
|
+
|
|
|
+ //积分转换红包
|
|
|
+ LiveRedPacketLog liveRedPacketLog = new LiveRedPacketLog();
|
|
|
+
|
|
|
+ liveRedPacketLog.setOutBatchNo(integralLogs.getId().toString());
|
|
|
+ liveRedPacketLog.setCompanyId(param.getCompanyId());
|
|
|
+ liveRedPacketLog.setUserId(param.getUserId());
|
|
|
+ liveRedPacketLog.setLiveId(param.getLiveId());
|
|
|
+ liveRedPacketLog.setStatus(1L);
|
|
|
+ liveRedPacketLog.setCompanyUserId(param.getCompanyUserId());
|
|
|
+ liveRedPacketLog.setCreateTime(new Date());
|
|
|
+ liveRedPacketLog.setAmount(BigDecimal.valueOf(config.getScoreAmount()).divide(BigDecimal.valueOf(1000)));
|
|
|
+ liveRedPacketLog.setRemark("直播答题领取积分转");
|
|
|
+ liveRedPacketLog.setWatchLogId(watchUser.getId() != null ? watchUser.getId() : null);
|
|
|
+
|
|
|
+ redPacketLogMapper.insertLiveRedPacketLog(liveRedPacketLog);
|
|
|
+ return R.ok("直播积分奖励发放成功").put("rewardType", 2);
|
|
|
+ }
|
|
|
+
|
|
|
+}
|