|
@@ -8,6 +8,7 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
|
|
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
|
|
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
|
|
import com.fs.common.BeanCopyUtils;
|
|
|
+import com.fs.common.constant.FsConstants;
|
|
|
import com.fs.common.core.domain.R;
|
|
|
import com.fs.common.core.domain.ResponseResult;
|
|
|
import com.fs.common.core.domain.entity.SysDictData;
|
|
@@ -31,7 +32,10 @@ import com.fs.course.domain.*;
|
|
|
import com.fs.course.dto.CoursePackageDTO;
|
|
|
import com.fs.course.mapper.*;
|
|
|
import com.fs.course.param.*;
|
|
|
-import com.fs.course.param.newfs.*;
|
|
|
+import com.fs.course.param.newfs.FsUserCourseAddCompanyUserParam;
|
|
|
+import com.fs.course.param.newfs.FsUserCourseVideoLinkParam;
|
|
|
+import com.fs.course.param.newfs.FsUserCourseVideoUParam;
|
|
|
+import com.fs.course.param.newfs.UserCourseVideoPageParam;
|
|
|
import com.fs.course.service.IFsUserCompanyUserService;
|
|
|
import com.fs.course.service.IFsUserCourseVideoService;
|
|
|
import com.fs.course.service.IFsVideoResourceService;
|
|
@@ -62,38 +66,29 @@ import com.fs.qwApi.service.QwApiService;
|
|
|
import com.fs.sop.mapper.QwSopLogsMapper;
|
|
|
import com.fs.sop.mapper.SopUserLogsInfoMapper;
|
|
|
import com.fs.sop.service.ISopUserLogsInfoService;
|
|
|
-import com.fs.system.domain.SysConfig;
|
|
|
import com.fs.system.mapper.SysDictDataMapper;
|
|
|
import com.fs.system.service.ISysConfigService;
|
|
|
import com.fs.voice.utils.StringUtil;
|
|
|
import com.github.binarywang.wxpay.bean.transfer.TransferBillsResult;
|
|
|
-import com.google.common.collect.Sets;
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
import org.apache.commons.collections4.CollectionUtils;
|
|
|
-import org.apache.commons.lang.exception.ExceptionUtils;
|
|
|
import org.apache.rocketmq.spring.core.RocketMQTemplate;
|
|
|
-import org.jetbrains.annotations.NotNull;
|
|
|
import org.redisson.api.RLock;
|
|
|
import org.redisson.api.RedissonClient;
|
|
|
-import org.redisson.client.RedisClient;
|
|
|
import org.slf4j.Logger;
|
|
|
import org.slf4j.LoggerFactory;
|
|
|
import org.springframework.beans.BeanUtils;
|
|
|
import org.springframework.beans.BeansException;
|
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
|
import org.springframework.beans.factory.annotation.Value;
|
|
|
-import org.springframework.data.redis.core.RedisTemplate;
|
|
|
import org.springframework.stereotype.Service;
|
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
|
|
|
|
import java.math.BigDecimal;
|
|
|
import java.math.RoundingMode;
|
|
|
-import java.text.SimpleDateFormat;
|
|
|
import java.time.*;
|
|
|
import java.time.format.DateTimeFormatter;
|
|
|
-import java.time.temporal.ChronoUnit;
|
|
|
import java.util.*;
|
|
|
-import java.util.concurrent.CompletableFuture;
|
|
|
import java.util.concurrent.TimeUnit;
|
|
|
import java.util.concurrent.atomic.AtomicLong;
|
|
|
import java.util.stream.Collectors;
|
|
@@ -232,6 +227,9 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
|
|
|
@Autowired
|
|
|
ConfigUtil configUtil;
|
|
|
|
|
|
+ @Autowired
|
|
|
+ private BalanceRollbackErrorMapper balanceRollbackErrorMapper;
|
|
|
+
|
|
|
|
|
|
|
|
|
/**
|
|
@@ -1485,14 +1483,76 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
|
|
|
//2025.6.19 红包金额为0的时候
|
|
|
if (amount.compareTo(BigDecimal.ZERO)>0){
|
|
|
|
|
|
- Company company = companyMapper.selectCompanyById(param.getCompanyId());
|
|
|
- BigDecimal money = company.getMoney();
|
|
|
- if (money.compareTo(BigDecimal.ZERO)<=0) {
|
|
|
- return R.error("服务商余额不足,请联系群主服务器充值!");
|
|
|
+ // ===================== 20251022 xgb 修改 本次修改目的为了实时扣减公司余额=====================
|
|
|
+ // 1 使用redis缓存加锁 预扣减余额 红包发送失败 恢复redis缓存余额,如果回滚失败登记异常记录表 定时任务重新回滚余额
|
|
|
+ // 2 另起定时任务 同步缓存余额到redis中
|
|
|
+ // 3 启动系统时查询公司账户余额(这个时候要保证余额正确)保存到redis缓存中
|
|
|
+
|
|
|
+ if(packetParam.getCompanyId()== null){
|
|
|
+ logger.error("发送红包参数错误,公司不能为空,异常请求参数{}",packetParam);
|
|
|
+ return R.error("发送红包失败,请联系管理员");
|
|
|
}
|
|
|
+ String companyMoneyKey = FsConstants.COMPANY_MONEY_KEY + packetParam.getCompanyId();
|
|
|
|
|
|
- // 发送红包
|
|
|
- R sendRedPacket = paymentService.sendRedPacket(packetParam);
|
|
|
+ // 第一次加锁:预扣减余额
|
|
|
+ RLock lock1 = redissonClient.getLock(FsConstants.COMPANY_MONEY_LOCK + packetParam.getCompanyId());
|
|
|
+ try {
|
|
|
+ if (lock1.tryLock(3, 10, TimeUnit.SECONDS)) {
|
|
|
+ try {
|
|
|
+ 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("服务商余额不足,请联系群主服务器充值!");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 预扣减金额
|
|
|
+ BigDecimal newMoney = originalMoney.subtract(amount);
|
|
|
+ redisCache.setCacheObject(companyMoneyKey, newMoney.toString(), 2, TimeUnit.HOURS);
|
|
|
+
|
|
|
+ } finally {
|
|
|
+ lock1.unlock();
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ logger.error("获取redis锁失败,异常请求参数{}",packetParam);
|
|
|
+ return R.error("系统繁忙,请稍后重试");
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ logger.error("预扣减余额失败: 异常请求参数{},异常信息{}", packetParam, e.getMessage(), e);
|
|
|
+ return R.error("系统异常,请稍后重试");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 预设值异常对象
|
|
|
+ BalanceRollbackError balanceRollbackError = new BalanceRollbackError();
|
|
|
+ balanceRollbackError.setCompanyId(packetParam.getCompanyId());
|
|
|
+ balanceRollbackError.setUserId(user.getUserId());
|
|
|
+ balanceRollbackError.setLogId(log.getLogId());
|
|
|
+ balanceRollbackError.setVideoId(log.getVideoId());
|
|
|
+ balanceRollbackError.setStatus(0);
|
|
|
+ balanceRollbackError.setMoney(amount);
|
|
|
+
|
|
|
+ // 调用第三方接口(锁外操作)
|
|
|
+ R sendRedPacket;
|
|
|
+ try {
|
|
|
+ sendRedPacket= paymentService.sendRedPacket(packetParam);
|
|
|
+ } catch (Exception e) {
|
|
|
+ logger.error("红包发送异常: 异常请求参数{}",packetParam, e);
|
|
|
+ // 异常时回滚余额
|
|
|
+
|
|
|
+ rollbackBalance(balanceRollbackError);
|
|
|
+ return R.error("奖励发送失败,请联系客服");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 红包发送成功处理
|
|
|
if (sendRedPacket.get("code").equals(200)) {
|
|
|
FsCourseRedPacketLog redPacketLog = new FsCourseRedPacketLog();
|
|
|
TransferBillsResult transferBillsResult;
|
|
@@ -1524,11 +1584,15 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
|
|
|
// 更新观看记录的奖励类型
|
|
|
log.setRewardType(config.getRewardType());
|
|
|
courseWatchLogMapper.updateFsCourseWatchLog(log);
|
|
|
-
|
|
|
+ // 发送成功,记录日志等操作
|
|
|
return sendRedPacket;
|
|
|
} else {
|
|
|
+ // 发送失败,回滚余额
|
|
|
+ rollbackBalance(balanceRollbackError);
|
|
|
return R.error("奖励发送失败,请联系客服");
|
|
|
}
|
|
|
+
|
|
|
+ // ===================== 本次修改目的为了实时扣减公司余额=====================
|
|
|
} else {
|
|
|
FsCourseRedPacketLog redPacketLog = new FsCourseRedPacketLog();
|
|
|
// 添加红包记录
|
|
@@ -1555,6 +1619,48 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
|
|
|
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * @Description: 回滚redis缓存中的余额 异常登记回滚异常表,定时重新回滚
|
|
|
+ * @Param:
|
|
|
+ * @Return:
|
|
|
+ * @Author xgb
|
|
|
+ * @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());
|
|
|
+
|
|
|
+ try {
|
|
|
+ if (lock2.tryLock(3, 10, TimeUnit.SECONDS)) {
|
|
|
+ try {
|
|
|
+ // 获取当前余额
|
|
|
+ String currentMoneyStr = redisCache.getCacheObject(companyMoneyKey);
|
|
|
+ if (StringUtils.isNotEmpty(currentMoneyStr)) {
|
|
|
+ throw new RuntimeException("回滚余额异常");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 回滚金额(加回之前扣减的金额)
|
|
|
+ BigDecimal rollbackMoney = new BigDecimal(currentMoneyStr).add(balanceRollbackError.getMoney());
|
|
|
+ redisCache.setCacheObject(companyMoneyKey, rollbackMoney.toString(), 2, TimeUnit.HOURS);
|
|
|
+
|
|
|
+ logger.info("余额回滚成功: companyId={}, amount={}", balanceRollbackError.getCompanyId(), balanceRollbackError.getMoney());
|
|
|
+ } finally {
|
|
|
+ lock2.unlock();
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ logger.warn("回滚余额时获取锁失败: companyId={}", balanceRollbackError.getCompanyId());
|
|
|
+ // 登记回滚余额异常表
|
|
|
+ balanceRollbackErrorMapper.insert(balanceRollbackError);
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ logger.error("回滚余额时发生异常: companyId={}", balanceRollbackError.getCompanyId(), e);
|
|
|
+ // 登记回滚余额异常表
|
|
|
+ balanceRollbackErrorMapper.insert(balanceRollbackError);
|
|
|
+
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
/**
|
|
|
* 直接发送奖励
|
|
|
*
|