Forráskód Böngészése

红包补发,仅老商户可使用

xw 2 napja
szülő
commit
7e71e1c397

+ 1 - 1
fs-company/src/main/java/com/fs/company/controller/course/FsCourseRedPacketLogController.java

@@ -249,7 +249,7 @@ public class FsCourseRedPacketLogController extends BaseController
      * 删除短链课程看课记录
      */
 
-    @Log(title = "短链课程看课记录", businessType = BusinessType.DELETE)
+    @Log(title = "补发红包", businessType = BusinessType.DELETE)
     @PutMapping("/retryCourseRedPacketLog/{logIds}")
     @RepeatSubmit
     public R retryCourseRedPacketLog(@PathVariable Long[] logIds)

+ 256 - 54
fs-service/src/main/java/com/fs/course/service/impl/FsCourseRedPacketLogServiceImpl.java

@@ -4,6 +4,7 @@ import java.math.BigDecimal;
 import java.time.LocalDateTime;
 import java.time.format.DateTimeFormatter;
 import java.util.*;
+import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 
 import cn.hutool.core.collection.CollectionUtil;
@@ -15,15 +16,19 @@ import com.fs.common.utils.StringUtils;
 import com.fs.company.cache.ICompanyDeptCacheService;
 import com.fs.company.domain.Company;
 import com.fs.company.domain.CompanyMoneyLogs;
+import com.fs.common.constant.FsConstants;
+import com.fs.common.core.redis.RedisCache;
 import com.fs.company.domain.CompanyRedPacketBalanceLogs;
 import com.fs.company.mapper.CompanyMapper;
 import com.fs.company.mapper.CompanyMoneyLogsMapper;
 import com.fs.company.mapper.CompanyRedPacketBalanceLogsMapper;
 import com.fs.company.service.ICompanyConfigService;
-import com.fs.company.service.ICompanyConfigService;
+import com.fs.company.service.ICompanyService;
 import com.fs.course.config.CourseConfig;
 import com.fs.course.config.RedPacketConfig;
+import com.fs.course.domain.BalanceRollbackError;
 import com.fs.course.domain.FsCourseWatchLog;
+import com.fs.course.mapper.BalanceRollbackErrorMapper;
 import com.fs.course.mapper.FsCourseRedPacketLogMapper;
 import com.fs.course.mapper.FsCourseWatchLogMapper;
 import com.fs.course.mapper.FsUserCoursePeriodMapper;
@@ -31,7 +36,9 @@ import com.fs.course.param.FsCourseRedPacketLogParam;
 import com.fs.course.vo.FsCourseRedPacketLogListPVO;
 import com.fs.course.vo.FsCourseRedPacketLogListVO;
 import com.fs.his.domain.FsUser;
+import com.fs.his.domain.FsUserWx;
 import com.fs.his.mapper.FsUserMapper;
+import com.fs.his.service.IFsUserWxService;
 import com.fs.his.param.WxSendRedPacketParam;
 import com.fs.his.service.IFsStorePaymentService;
 import com.fs.his.vo.OptionsVO;
@@ -45,12 +52,14 @@ import com.github.binarywang.wxpay.exception.WxPayException;
 import com.github.binarywang.wxpay.service.TransferService;
 import com.github.binarywang.wxpay.service.WxPayService;
 import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl;
+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.beans.factory.annotation.Value;
 import org.springframework.stereotype.Service;
-import com.fs.course.mapper.FsCourseRedPacketLogMapper;
 import com.fs.course.domain.FsCourseRedPacketLog;
 import com.fs.course.service.IFsCourseRedPacketLogService;
 import org.springframework.transaction.annotation.Transactional;
@@ -239,76 +248,269 @@ public class FsCourseRedPacketLogServiceImpl implements IFsCourseRedPacketLogSer
     @Autowired
     private FsUserMapper fsUserMapper;
     @Autowired
+    private IFsUserWxService fsUserWxService;
+    @Autowired
     private FsCourseWatchLogMapper courseWatchLogMapper;
     @Autowired
     private CompanyMoneyLogsMapper moneyLogsMapper;
+    @Autowired
+    private RedissonClient redissonClient;
+    @Autowired
+    private RedisCache redisCache;
+    @Autowired
+    private ICompanyService companyService;
+    @Autowired
+    private BalanceRollbackErrorMapper balanceRollbackErrorMapper;
+
+    @Value("${isNewWxMerchant}")
+    private Boolean isNewWxMerchant;
     @Override
     @Transactional
     public R retryCourseRedPacketLog(Long[] logIds) {
-        int suc=0;
-        int err=0;
-        for (int i = 0; i < logIds.length; i++) {
-            Long id = logIds[i];
+        int suc = 0;
+        int err = 0;
+        CourseConfig courseConfig = JSONUtil.toBean(configService.selectConfigByKey("course.config"), CourseConfig.class);
+        int redPacketMode = courseConfig != null && courseConfig.getRedPacketMode() != null
+                ? courseConfig.getRedPacketMode() : 1;
+        boolean useRedisDeduction = courseConfig != null && "1".equals(courseConfig.getIsRedPackageBalanceDeduction());
+
+        for (Long id : logIds) {
             FsCourseRedPacketLog param = fsCourseRedPacketLogMapper.selectFsCourseRedPacketLogByLogId(id);
 
+            if (param == null || param.getStatus() != 0) {
+                err++;
+                continue;
+            }
+
+            Company company = companyMapper.selectCompanyById(param.getCompanyId());
+            if (company == null) {
+                err++;
+                continue;
+            }
+
+            BigDecimal amount = param.getAmount();
+            if (amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) {
+                err++;
+                continue;
+            }
+
+            FsUser user = fsUserMapper.selectFsUserByUserId(param.getUserId());
+            if (user == null) {
+                err++;
+                continue;
+            }
+
+            String appId = StringUtils.isNotBlank(param.getAppId()) ? param.getAppId() : company.getCourseMiniAppId();
+            if (StringUtils.isBlank(appId) && courseConfig != null && StringUtils.isNotBlank(courseConfig.getLoginMiniAppId())) {
+                appId = courseConfig.getLoginMiniAppId();
+            }
 
-            if (param!=null&&param.getStatus()==2){
-                Company company = companyMapper.selectCompanyByIdForUpdate(param.getCompanyId());
-                BigDecimal amount = param.getAmount();
-                BigDecimal money = company.getMoney();
-                BigDecimal subtract = money.subtract(amount);
-                if (subtract.compareTo(BigDecimal.ZERO)<0){
+            WxSendRedPacketParam packetParam = new WxSendRedPacketParam();
+            packetParam.setOpenId(user.getMpOpenId());
+            if (user.getMpOpenId() != null && !Boolean.TRUE.equals(isNewWxMerchant)) {
+                packetParam.setOpenId(user.getMpOpenId());
+            } else {
+                if (StringUtils.isBlank(appId)) {
+                    logger.warn("补发红包失败:无法解析小程序 appId,logId={}", id);
+                    err++;
+                    continue;
+                }
+                FsUserWx fsUserWx = fsUserWxService.selectByAppIdAndUserId(appId, param.getUserId(), 1);
+                if (fsUserWx == null || StringUtils.isBlank(fsUserWx.getOpenId())) {
+                    logger.warn("补发红包失败:fs_user_wx 缺少 openId,logId={} appId={} userId={}", id, appId, param.getUserId());
                     err++;
+                    continue;
                 }
-                WxSendRedPacketParam packetParam = new WxSendRedPacketParam();
-                FsUser user = fsUserMapper.selectFsUserByUserId(param.getUserId());
-                packetParam.setOpenId(user.getMaOpenId());
-                packetParam.setOpenId(user.getCourseMaOpenId());
-                packetParam.setAmount(param.getAmount());
-                packetParam.setSource(2);
-                packetParam.setRedPacketMode(1);
-                packetParam.setCompanyId(param.getCompanyId());
-                packetParam.setUser(user);
-                R sendRedPacket = paymentService.sendRedPacket(packetParam);
-                if (sendRedPacket.get("code").equals(200)) {
-                    FsCourseRedPacketLog redPacketLog = new FsCourseRedPacketLog();
-                    TransferBillsResult transferBillsResult;
-                    if (sendRedPacket.get("isNew").equals(1)){
-                        transferBillsResult = (TransferBillsResult)sendRedPacket.get("data");
-                        redPacketLog.setResult(JSON.toJSONString(sendRedPacket));
-                        redPacketLog.setOutBatchNo(transferBillsResult.getOutBillNo());
-                    }else {
-                        redPacketLog.setOutBatchNo(sendRedPacket.get("orderCode").toString());
+                packetParam.setOpenId(fsUserWx.getOpenId());
+            }
+
+            if (courseConfig != null && StringUtils.isNotEmpty(courseConfig.getMpAppId())) {
+                packetParam.setMpAppId(courseConfig.getMpAppId());
+            }
+            packetParam.setAmount(amount);
+            packetParam.setSource(2);
+            packetParam.setRedPacketMode(redPacketMode);
+            packetParam.setCompanyId(param.getCompanyId());
+            packetParam.setAppId(appId);
+            packetParam.setUser(user);
+
+            if (useRedisDeduction) {
+                if (packetParam.getCompanyId() == null) {
+                    err++;
+                    continue;
+                }
+
+                BalanceRollbackError balanceRollbackError = new BalanceRollbackError();
+                balanceRollbackError.setCompanyId(packetParam.getCompanyId());
+                balanceRollbackError.setUserId(param.getUserId());
+                balanceRollbackError.setLogId(param.getWatchLogId());
+                balanceRollbackError.setVideoId(param.getVideoId());
+                balanceRollbackError.setStatus(0);
+                balanceRollbackError.setMoney(amount);
+
+                String companyMoneyKey = FsConstants.COMPANY_MONEY_KEY + packetParam.getCompanyId();
+                RLock lock1 = redissonClient.getLock(FsConstants.COMPANY_MONEY_LOCK + packetParam.getCompanyId());
+                boolean lockAcquired = false;
+                BigDecimal newMoney = null;
+                try {
+                    if (lock1.tryLock(3, 10, TimeUnit.SECONDS)) {
+                        lockAcquired = true;
+                        String moneyStr = redisCache.getCacheObject(companyMoneyKey);
+                        if (StringUtils.isEmpty(moneyStr)) {
+                            logger.error("补发红包获取 redis 余额缓存异常,logId={} companyId={}", id, packetParam.getCompanyId());
+                            err++;
+                            continue;
+                        }
+                        BigDecimal originalMoney = new BigDecimal(moneyStr);
+                        if (originalMoney.compareTo(BigDecimal.ZERO) < 0.3) {
+                            logger.warn("补发红包 redis 余额不足,logId={} 当前余额={}", id, originalMoney);
+                            err++;
+                            continue;
+                        }
+                        newMoney = originalMoney.subtract(amount);
+                        redisCache.setCacheObject(companyMoneyKey, newMoney.toString());
+                    } else {
+                        logger.warn("补发红包获取 redis 锁失败,logId={}", id);
+                        err++;
+                        continue;
+                    }
+                } catch (Exception e) {
+                    logger.error("补发红包预扣 redis 余额失败 logId={}", id, e);
+                    err++;
+                    continue;
+                } finally {
+                    if (lockAcquired && lock1.isHeldByCurrentThread()) {
+                        try {
+                            lock1.unlock();
+                        } catch (IllegalMonitorStateException e) {
+                            logger.warn("补发红包释放 redis 锁异常 companyId={}", packetParam.getCompanyId());
+                        }
                     }
-                    FsCourseWatchLog log = new FsCourseWatchLog();
-                    log.setLogId(param.getWatchLogId());
-                    log.setRewardType(1);
-                    courseWatchLogMapper.updateFsCourseWatchLog(log);
-                    // 添加红包记录
-                    redPacketLog.setLogId(param.getLogId());
-                    redPacketLog.setStatus(0);
-                    fsCourseRedPacketLogMapper.updateFsCourseRedPacketLog(redPacketLog);
-                    // 更新观看记录的奖励类型
-                    company.setMoney(subtract);
-                    companyMapper.updateCompany(company);
-                    CompanyMoneyLogs logs=new CompanyMoneyLogs();
-                    logs.setCompanyId(company.getCompanyId());
-                    logs.setRemark("扣除红包金额");
-                    logs.setMoney(amount.multiply(new BigDecimal(-1)));
-                    logs.setLogsType(15);
-                    logs.setBalance(company.getMoney());
-                    logs.setCreateTime(new Date());
-                    moneyLogsMapper.insertCompanyMoneyLogs(logs);
+                }
+
+                R sendRedPacket;
+                try {
+                    sendRedPacket = paymentService.sendRedPacket(packetParam);
+                } catch (Exception e) {
+                    logger.error("补发红包接口异常 logId={}", id, e);
+                    rollbackBalance(balanceRollbackError);
+                    err++;
+                    continue;
+                }
+
+                if (sendRedPacket != null && sendRedPacket.get("code") != null && sendRedPacket.get("code").equals(200)) {
+                    updateRedPacketAndWatchLogAfterRetry(param, sendRedPacket, appId, courseConfig);
+                    BigDecimal moneyNeg = amount.multiply(BigDecimal.valueOf(-1));
+                    companyService.asyncRecordBalanceLog(param.getCompanyId(), moneyNeg, 15, newMoney, "发放红包", param.getLogId());
                     suc++;
-                }else {
+                } else {
+                    rollbackBalance(balanceRollbackError);
                     err++;
                 }
-            }else {
-                err++;
+            } else {
+                if (company.getMoney().compareTo(BigDecimal.ZERO) <= 0) {
+                    err++;
+                    continue;
+                }
+                R sendRedPacket;
+                try {
+                    sendRedPacket = paymentService.sendRedPacket(packetParam);
+                } catch (Exception e) {
+                    logger.error("补发红包接口异常 logId={}", id, e);
+                    err++;
+                    continue;
+                }
+
+                if (sendRedPacket != null && sendRedPacket.get("code") != null && sendRedPacket.get("code").equals(200)) {
+                    updateRedPacketAndWatchLogAfterRetry(param, sendRedPacket, appId, courseConfig);
+                    suc++;
+                } else {
+                    err++;
+                }
+            }
+        }
+        return R.ok("成功:" + suc + " 失败:" + err);
+    }
+
+
+    private void updateRedPacketAndWatchLogAfterRetry(FsCourseRedPacketLog param, R sendRedPacket, String appId,
+            CourseConfig courseConfig) {
+        FsCourseRedPacketLog redPacketLog = new FsCourseRedPacketLog();
+
+        if (sendRedPacket.get("isNew") != null && sendRedPacket.get("isNew").equals(1)) {
+            TransferBillsResult transferBillsResult = (TransferBillsResult) sendRedPacket.get("data");
+            redPacketLog.setResult(JSON.toJSONString(sendRedPacket));
+            redPacketLog.setOutBatchNo(transferBillsResult.getOutBillNo());
+            if (transferBillsResult.getTransferBillNo() != null) {
+                redPacketLog.setBatchId(transferBillsResult.getTransferBillNo());
+            }
+        } else {
+            Object orderCode = sendRedPacket.get("orderCode");
+            if (orderCode != null) {
+                redPacketLog.setOutBatchNo(orderCode.toString());
+            }
+            Object batchIdObj = sendRedPacket.get("batchId");
+            if (batchIdObj != null) {
+                redPacketLog.setBatchId(batchIdObj.toString());
             }
+            redPacketLog.setResult(JSON.toJSONString(sendRedPacket));
+        }
+        Object mchIdObj = sendRedPacket.get("mchId");
+        if (mchIdObj != null && StringUtils.isNotEmpty(String.valueOf(mchIdObj))) {
+            redPacketLog.setMchId(String.valueOf(mchIdObj));
+        }
 
+        if (param.getWatchLogId() != null) {
+            FsCourseWatchLog log = new FsCourseWatchLog();
+            log.setLogId(param.getWatchLogId());
+            int rewardType = courseConfig != null && courseConfig.getRewardType() != null ? courseConfig.getRewardType() : 1;
+            log.setRewardType(rewardType);
+            courseWatchLogMapper.updateFsCourseWatchLog(log);
+        }
+
+        redPacketLog.setLogId(param.getLogId());
+        redPacketLog.setStatus(0);
+        if (StringUtils.isBlank(param.getAppId()) && StringUtils.isNotBlank(appId)) {
+            redPacketLog.setAppId(appId);
+        }
+        fsCourseRedPacketLogMapper.updateFsCourseRedPacketLog(redPacketLog);
+    }
+
+    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);
+                }
+            }
         }
-        return R.ok("成功:"+suc+" 失败:"+err);
     }
 
     @Override