|
|
@@ -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&¶m.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
|