Sfoglia il codice sorgente

Merge branch 'refs/heads/master_feat_redisson_20250916'

xdd 4 giorni fa
parent
commit
768f07195a

+ 94 - 0
fs-admin/src/main/java/com/fs/qw/FsCourseTask.java

@@ -1,11 +1,27 @@
 package com.fs.qw;
 
+import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
+import com.fs.common.core.domain.R;
+import com.fs.common.core.redis.RedisCache;
 import com.fs.course.service.IFsCourseWatchLogService;
 import com.fs.qw.service.IQwWorkTaskService;
+import com.fs.system.domain.SysConfig;
+import com.fs.system.mapper.SysConfigMapper;
+import io.jsonwebtoken.lang.Assert;
 import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang.exception.ExceptionUtils;
+import org.apache.http.util.Asserts;
+import org.redisson.api.RLock;
+import org.redisson.api.RedissonClient;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
 /**
  * 后台统计相关 定时任务
  */
@@ -16,7 +32,85 @@ public class FsCourseTask {
     private IFsCourseWatchLogService fsCourseWatchLogService;
     @Autowired
     private IQwWorkTaskService qwWorkTaskService;
+    @Autowired
+    private RedisCache redisCache;
+    private static final String REDPACKET_COMPANY_MONEY_CHANGE = "redpacket_money_CHANGE";
+    @Autowired
+    private SysConfigMapper sysConfigMapper;
+    private static final String REDPACKET_POOL_LOCK = "redpacket_pool_lock";
+    private static final String REDPACKET_COMPANY_MONEY = "redpacket_money";
+
+    @Autowired
+    private RedissonClient redissonClient;
+
+    /**
+     * 润天公司账户充值
+     * @param chargeMoney 充值金额
+     */
+    public void rechargeRedpacketMoney(String chargeMoney){
+        Assert.notNull(chargeMoney,"充值金额不能为空!");
+
+        log.info("润天公司账户充值 充值金额: {}",chargeMoney);
+
+        RLock lock = redissonClient.getLock(REDPACKET_POOL_LOCK);
+        try{
+            boolean locked = lock.tryLock(3, 10, TimeUnit.SECONDS);
+
+            if (!locked) {
+                log.error("获取锁失败...");
+                return;
+            }
+
+            BigDecimal redPacketCompanyMoney = redisCache.getCacheObject(REDPACKET_COMPANY_MONEY);
+            if(ObjectUtils.isNull(redPacketCompanyMoney)){
+                redPacketCompanyMoney = BigDecimal.ZERO;
+            }
+            BigDecimal value = redPacketCompanyMoney.add(new BigDecimal(chargeMoney));
+
+            log.info("润天公司账户充值成功 目前余额: {}",value);
 
+            redisCache.setCacheObject(REDPACKET_COMPANY_MONEY,value);
+
+
+            // 保存到数据库
+            SysConfig sysConfig = sysConfigMapper.selectConfigByConfigKey("company.money");
+
+            sysConfig.setConfigValue(value.setScale(4, RoundingMode.HALF_UP).toPlainString());
+            sysConfig.setConfigKey("company.money");
+            sysConfigMapper.updateConfig(sysConfig);
+
+        }catch (Exception e){
+            log.error("充值失败 原因:{}", ExceptionUtils.getFullStackTrace(e),e);
+            throw new RuntimeException(e);
+        }finally {
+            if (lock.isHeldByCurrentThread()) {
+                lock.unlock();
+            }
+        }
+    }
+    /**
+     * 公司红包金额变更
+     */
+    public void redpacketCompanyMoneyChange(){
+        SysConfig sysConfig = sysConfigMapper.selectConfigByConfigKey("company.money");
+        Asserts.notNull(sysConfig,"公司账户配置不能为空!");
+
+        String configValue = sysConfig.getConfigValue();
+        Asserts.notNull(configValue,"公司账户余额不能为空");
+
+        BigDecimal oldBigDecimal = new BigDecimal(configValue);
+
+        BigDecimal redPacketCompanyMoney = redisCache.getCacheObject(REDPACKET_COMPANY_MONEY);
+
+        log.info("公司账户红包余额变更 {} -> {}",oldBigDecimal,redPacketCompanyMoney);
+
+        // 如果两者不一致才同步到数据库
+        if(oldBigDecimal.compareTo(redPacketCompanyMoney) != 0){
+            sysConfig.setConfigValue(redPacketCompanyMoney.setScale(4, RoundingMode.HALF_UP).toPlainString());
+            sysConfig.setConfigKey("company.money");
+            sysConfigMapper.updateConfig(sysConfig);
+        }
+    }
     /**
      * 添加企微观看日志
      * @throws Exception

+ 2 - 0
fs-service/src/main/java/com/fs/company/service/ICompanyConfigService.java

@@ -67,4 +67,6 @@ public interface ICompanyConfigService
     String selectConfigByKey(String configKey);
 
     CompanyConfig selectCompanyConfigByServerKey(String key);
+
+    String selectRedPacketConfigByKey(Long companyId);
 }

+ 38 - 0
fs-service/src/main/java/com/fs/company/service/impl/CompanyConfigServiceImpl.java

@@ -8,10 +8,15 @@ import com.fs.company.domain.CompanyConfig;
 import com.fs.company.mapper.CompanyConfigMapper;
 import com.fs.company.service.ICompanyConfigService;
 import com.fs.system.domain.SysConfig;
+import org.apache.http.util.Asserts;
+import org.redisson.api.RedissonClient;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.stereotype.Service;
 
 import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
 
 import static com.fs.common.utils.DictUtils.getCacheKey;
 
@@ -28,6 +33,10 @@ public class CompanyConfigServiceImpl implements ICompanyConfigService
     private RedisCache redisCache;
     @Autowired
     private CompanyConfigMapper companyConfigMapper;
+    @Autowired
+    private RedisTemplate<String, String> redisTemplate; // 注入RedisTemplate
+    private static final String REDIS_KEY_PREFIX = "red_packet_config:";
+    private static final long CACHE_TIMEOUT = 24 * 60 * 60;
 
     /**
      * 查询参数配置
@@ -133,4 +142,33 @@ public class CompanyConfigServiceImpl implements ICompanyConfigService
     public CompanyConfig selectCompanyConfigByServerKey(String key) {
         return companyConfigMapper.selectCompanyConfigByServerKey(key);
     }
+
+    @Override
+    public String selectRedPacketConfigByKey(Long companyId) {
+        Asserts.notNull(companyId,"公司id不能为空!");
+        String redisKey = REDIS_KEY_PREFIX + companyId;
+        String cachedConfig = redisTemplate.opsForValue().get(redisKey);
+        if (cachedConfig != null) {
+            return cachedConfig;
+        }
+        synchronized (getSynchronizationObject(companyId)) {
+            cachedConfig = redisTemplate.opsForValue().get(redisKey);
+
+            if (cachedConfig != null) {
+                return cachedConfig;
+            }
+            String configFromDb = companyConfigMapper.selectRedPacketConfigByKey(companyId);
+            if (configFromDb != null) {
+                redisTemplate.opsForValue().set(redisKey, configFromDb, CACHE_TIMEOUT, TimeUnit.SECONDS);
+            } else {
+                redisTemplate.opsForValue().set(redisKey, "", 5 * 60, TimeUnit.SECONDS);
+            }
+            return configFromDb;
+        }
+    }
+
+    private static final ConcurrentHashMap<Long, Object> LOCKS = new ConcurrentHashMap<>();
+    private static Object getSynchronizationObject(Long companyId) {
+        return LOCKS.computeIfAbsent(companyId, k -> new Object());
+    }
 }

+ 15 - 24
fs-service/src/main/java/com/fs/core/config/WxMaConfiguration.java

@@ -33,8 +33,10 @@ import org.yaml.snakeyaml.events.Event;
 import javax.annotation.PostConstruct;
 import java.io.File;
 import java.util.ArrayList;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.stream.Collectors;
 
 @Slf4j
@@ -97,20 +99,7 @@ public class WxMaConfiguration {
     }
 
     public static WxMaService getMaService(String appid) {
-        // 从缓存获取
-        WxMaService wxService = maServices.get(appid);
-        if (wxService != null) {
-            return wxService;
-        }
-
-        // 缓存未命中,查询数据库
-        synchronized (WxMaConfiguration.class) {
-            // 双重检查
-            wxService = maServices.get(appid);
-            if (wxService != null) {
-                return wxService;
-            }
-
+        return maServices.computeIfAbsent(appid,e->{
             // 查询数据库
             FsCoursePlaySourceConfigMapper configMapper = SpringUtils.getBean(FsCoursePlaySourceConfigMapper.class);
             Wrapper<FsCoursePlaySourceConfig> queryWrapper = Wrappers.<FsCoursePlaySourceConfig>lambdaQuery()
@@ -121,11 +110,8 @@ public class WxMaConfiguration {
                 throw new IllegalArgumentException(String.format("未找到对应appid=[%s]的配置,请核实!", appid));
             }
 
-            WxMaService service = getWxMaService(config.getAppid(), config.getSecret(), config.getToken(), config.getAesKey(), config.getMsgDataFormat());
-            maServices.put(appid, service);
-            log.info("Initialized WxMaService for appid: {}", appid);
-            return service;
-        }
+            return getWxMaService(config.getAppid(), config.getSecret(), config.getToken(), config.getAesKey(), config.getMsgDataFormat());
+        });
     }
 
     /**
@@ -156,11 +142,16 @@ public class WxMaConfiguration {
         }
 
         maServices = configs.stream()
-            .map(a -> {
-                WxMaService service = getWxMaService(a.getAppid(), a.getSecret(), a.getToken(), a.getAesKey(), a.getMsgDataFormat());
-                routers.put(a.getAppid(), this.newRouter(service));
-                return service;
-            }).collect(Collectors.toMap(s -> s.getWxMaConfig().getAppid(), a -> a));
+                .map(a -> {
+                    WxMaService service = getWxMaService(a.getAppid(), a.getSecret(), a.getToken(), a.getAesKey(), a.getMsgDataFormat());
+                    routers.put(a.getAppid(), this.newRouter(service));
+                    return service;
+                }).collect(Collectors.toMap(
+                        s -> s.getWxMaConfig().getAppid(),
+                        a -> a,
+                        (existing, replacement) -> replacement,
+                        ConcurrentHashMap::new
+                ));
     }
 
     private WxMaMessageRouter newRouter(WxMaService service) {

+ 9 - 0
fs-service/src/main/java/com/fs/course/domain/FsCourseRedPacketLog.java

@@ -64,4 +64,13 @@ public class FsCourseRedPacketLog extends BaseEntity
 
     private String appId;//小程序appId
 
+    /**
+     * 账户余额扣减前
+     */
+    private BigDecimal accBalanceBefore;
+    /**
+     * 账户余额扣减后
+     */
+    private BigDecimal accBalanceAfter;
+
 }

+ 137 - 83
fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java

@@ -4,6 +4,7 @@ import cn.hutool.json.JSONUtil;
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONObject;
 import com.baomidou.mybatisplus.core.conditions.Wrapper;
+import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.fs.common.BeanCopyUtils;
 import com.fs.common.core.domain.R;
@@ -60,13 +61,20 @@ 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;
@@ -82,6 +90,7 @@ import java.time.Instant;
 import java.time.LocalDateTime;
 import java.time.ZoneId;
 import java.util.*;
+import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicLong;
 import java.util.stream.Collectors;
@@ -104,6 +113,26 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
     private Boolean isNewWxMerchant;
     private static final Logger logger = LoggerFactory.getLogger(FsUserCourseVideoServiceImpl.class);
 
+    /**
+     * 红包账户锁
+     */
+    private static final String REDPACKET_POOL_LOCK = "redpacket_pool_lock";
+
+    /**
+     * 公司红包金额
+     */
+    private static final String REDPACKET_COMPANY_MONEY = "redpacket_money";
+
+    /**
+     * 红包改变记录
+     */
+    private static final String REDPACKET_COMPANY_MONEY_CHANGE = "redpacket_money_change";
+
+    /**
+     * 是否开启红包账户扣减
+     */
+    @Value("${enableRedPackAccount:0}")
+    private String ENABLE_RED_PACK_ACCOUNT;
 
     private static final String miniappRealLink = "/pages_course/video.html?course=";
     private static final String REAL_LINK_PREFIX = "/courseH5/pages/course/learning?course=";
@@ -139,6 +168,8 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
     private AsyncIsAddKfXfkService xfkService;
 
 
+    @Autowired
+    private RedissonClient redissonClient;
     @Autowired
     private QwExternalContactMapper qwExternalContactMapper;
     @Autowired
@@ -412,7 +443,7 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
 
     @Override
     public R isAddKf(FsUserCourseVideoAddKfUParam param) {
-        logger.info("zyp \n【判断添加客服】:{}",param);
+        logger.info("【判断添加客服】:{}",param);
         //查询用户
         FsUser fsUser = fsUserMapper.selectFsUserByUserId(param.getUserId());
         //用户不存在唤起重新授权
@@ -583,7 +614,7 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
         log.setQwUserId(Long.valueOf(param.getQwUserId()));
         log.setCreateTime(new Date());
         log.setLogType(3);
-        logger.info("zyp \n【群聊生成看课记录】:{}",param);
+        logger.info("【群聊生成看课记录】:{}",param);
         courseWatchLogMapper.insertFsCourseWatchLog(log);
     }
 
@@ -778,7 +809,7 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
     public R getInternetTraffic(FsUserCourseVideoFinishUParam param) {
         try {
             if (param.getBufferRate()==null){
-                logger.error("zyp \n【缓冲值空】参数: {}",param);
+                logger.error("【缓冲值空】参数: {}",param);
                 return R.error("缓冲值空");
             }
             FsCourseTrafficLog trafficLog = new FsCourseTrafficLog();
@@ -805,13 +836,13 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
             // 处理 UUID 为空的情况
             if (StringUtils.isNotEmpty(trafficLog.getUuId())) {
                 // 直接插入或更新
-//                logger.error("zyp \n【插入或更新流量】:{}",trafficLog);
+//                logger.error("【插入或更新流量】:{}",trafficLog);
                 fsCourseTrafficLogMapper.insertOrUpdateTrafficLog(trafficLog);
             }
         } catch (Exception e) {
             e.printStackTrace();
             // 打印参数param和异常信息
-            logger.error("zyp \n【插入或更新流量失败】参数: {}, 错误信息:{}", param, e.getMessage(), e);
+            logger.error("【插入或更新流量失败】参数: {}, 错误信息:{}", param, e.getMessage(), e);
             return R.error();
         }
         return R.ok();
@@ -1047,13 +1078,12 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
                 try {
                     handleFsUserWx(user,param.getAppId());
                 }catch (Exception e){
-                    logger.error("zyp \n 【更新或插入用户与小程序的绑定关系失败】:{}", user.getUserId());
+                    logger.error("【更新或插入用户与小程序的绑定关系失败】:{}", user.getUserId());
                 }
             }else {
                 packetParam.setOpenId(fsUserWx.getOpenId());
             }
             //查出公司绑定openid并赋值
-
         }
 
         //判断服务号配置是否存在
@@ -1067,104 +1097,126 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
         packetParam.setAppId(param.getAppId());
         packetParam.setUser(user);
 
-        System.out.println("红包金额"+amount);
-        System.out.println("红包商户号"+packetParam);
+        logger.info("红包金额 {},红包商户号 {}",amount,packetParam);
         //2025.6.19 红包金额为0的时候
         if (amount.compareTo(BigDecimal.ZERO)>0){
 
-//            Company company = companyMapper.selectCompanyByIdForUpdate(param.getCompanyId());
-           // BigDecimal money = company.getMoney();
-         //   BigDecimal subtract = money.subtract(amount);
-//            if (subtract.compareTo(BigDecimal.ZERO)<0){
-//                FsCourseRedPacketLog redPacketLog = new FsCourseRedPacketLog();
-//                redPacketLog.setCourseId(param.getCourseId());
-//                redPacketLog.setCompanyId(param.getCompanyId());
-//                redPacketLog.setUserId(param.getUserId());
-//                redPacketLog.setVideoId(param.getVideoId());
-//                redPacketLog.setStatus(2);
-//                redPacketLog.setQwUserId(param.getQwUserId() != null ? param.getQwUserId() : null);
-//                redPacketLog.setCompanyUserId(param.getCompanyUserId());
-//                redPacketLog.setCreateTime(new Date());
-//                redPacketLog.setAmount(amount);
-//                redPacketLog.setWatchLogId(log.getLogId() != null ? log.getLogId() : null);
-//                redPacketLog.setPeriodId(param.getPeriodId());
-//                redPacketLogMapper.insertFsCourseRedPacketLog(redPacketLog);
-//                return R.error("余额不足请稍等");
-//            }
-            // 发送红包
-            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());
+            //---------------发红包前先判断润天账户余额是否足够---------
+            RLock lock = redissonClient.getLock(REDPACKET_POOL_LOCK);
+            try{
+                boolean locked = lock.tryLock(3, 10, TimeUnit.SECONDS);
+
+                if (!locked) {
+                    logger.error("获取锁失败");
+                    return R.error("[红包领取] 系统繁忙,请重试!");
                 }
-                // 添加红包记录
-                redPacketLog.setCourseId(param.getCourseId());
-//            redPacketLog.setOutBatchNo(sendRedPacket.get("orderCode").toString());
-                redPacketLog.setCompanyId(param.getCompanyId());
-                redPacketLog.setUserId(param.getUserId());
-                redPacketLog.setVideoId(param.getVideoId());
-                redPacketLog.setStatus(0);
-                redPacketLog.setQwUserId(param.getQwUserId() != null ? param.getQwUserId() : null);
-                redPacketLog.setCompanyUserId(param.getCompanyUserId());
-                redPacketLog.setCreateTime(new Date());
-                redPacketLog.setAmount(amount);
-                redPacketLog.setWatchLogId(log.getLogId() != null ? log.getLogId() : null);
-                redPacketLog.setPeriodId(param.getPeriodId());
-                redPacketLogMapper.insertFsCourseRedPacketLog(redPacketLog);
 
-                // 更新观看记录的奖励类型
-                log.setRewardType(config.getRewardType());
-                courseWatchLogMapper.updateFsCourseWatchLog(log);
-//
-//                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);
+                // 发送红包
+                return sendRedPacketRewardToUser(param, log, config, packetParam, amount);
 
-                return sendRedPacket;
-            } else {
-                return R.error("奖励发送失败,请联系客服");
+            }catch (Exception e){
+                logger.error("领取红包失败原因:{}", ExceptionUtils.getFullStackTrace(e),e);
+                throw new RuntimeException(e);
+            }finally {
+                if (lock.isHeldByCurrentThread()) {
+                    lock.unlock();
+                }
             }
         } else {
             FsCourseRedPacketLog redPacketLog = new FsCourseRedPacketLog();
             // 添加红包记录
             redPacketLog.setCourseId(param.getCourseId());
-//            redPacketLog.setOutBatchNo(sendRedPacket.get("orderCode").toString());
+
             redPacketLog.setCompanyId(param.getCompanyId());
             redPacketLog.setUserId(param.getUserId());
             redPacketLog.setVideoId(param.getVideoId());
             redPacketLog.setStatus(0);
-            redPacketLog.setQwUserId(param.getQwUserId() != null ? param.getQwUserId() : null);
+            redPacketLog.setQwUserId(param.getQwUserId());
             redPacketLog.setCompanyUserId(param.getCompanyUserId());
             redPacketLog.setCreateTime(new Date());
             redPacketLog.setAmount(BigDecimal.ZERO);
-            redPacketLog.setWatchLogId(log.getLogId() != null ? log.getLogId() : null);
+            redPacketLog.setWatchLogId(log.getLogId());
             redPacketLog.setPeriodId(param.getPeriodId());
             redPacketLogMapper.insertFsCourseRedPacketLog(redPacketLog);
 
             // 更新观看记录的奖励类型
-//            if (param.getLinkType() == null || param.getLinkType() == 0) {
             log.setRewardType(config.getRewardType());
             courseWatchLogMapper.updateFsCourseWatchLog(log);
-//            }
             return R.ok("红包发送成功");
         }
 
     }
 
+    private R sendRedPacketRewardToUser(FsCourseSendRewardUParam param, FsCourseWatchLog log, CourseConfig config, WxSendRedPacketParam packetParam, BigDecimal amount) {
+
+        BigDecimal companyMoney = null;
+        if(StringUtils.equals(ENABLE_RED_PACK_ACCOUNT,"1")) {
+            companyMoney = redisCache.getCacheObject(REDPACKET_COMPANY_MONEY);
+            if(ObjectUtils.isNull(companyMoney)){
+                SysConfig sysConfig = sysConfigService.selectConfigByConfigKey("company.money");
+                if(ObjectUtils.isNull(sysConfig)){
+                    throw new IllegalArgumentException("润天公司账户余额不能为空!请检查配置!");
+                }
+                String configValue = sysConfig.getConfigValue();
+                companyMoney = new BigDecimal(configValue);
+                logger.info("缓存公司余额为空,从数据库读取 companyMoney: {}",companyMoney);
+            }
+
+            if (companyMoney.compareTo(BigDecimal.ZERO) <= 0) {
+                logger.info("润天账户余额: {} 不足!", companyMoney);
+                return R.error("[红包领取] 账户余额不足,请联系管理员!");
+            }
+        }
+
+        // 发送红包
+        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());
+            }
+            // 添加红包记录
+            redPacketLog.setCourseId(param.getCourseId());
+            redPacketLog.setCompanyId(param.getCompanyId());
+            redPacketLog.setUserId(param.getUserId());
+            redPacketLog.setVideoId(param.getVideoId());
+            redPacketLog.setStatus(0);
+            redPacketLog.setQwUserId(param.getQwUserId() != null ? param.getQwUserId() : null);
+            redPacketLog.setCompanyUserId(param.getCompanyUserId());
+            redPacketLog.setCreateTime(new Date());
+            redPacketLog.setAmount(amount);
+            redPacketLog.setWatchLogId(log.getLogId() != null ? log.getLogId() : null);
+            redPacketLog.setPeriodId(param.getPeriodId());
+            if(StringUtils.equals(ENABLE_RED_PACK_ACCOUNT,"1")) {
+                redPacketLog.setAccBalanceBefore(companyMoney);
+                redPacketLog.setAccBalanceAfter(companyMoney.subtract(amount));
+            }
+
+            CompletableFuture.runAsync(() -> {
+                redPacketLogMapper.insertFsCourseRedPacketLog(redPacketLog);
+                // 更新观看记录的奖励类型
+                log.setRewardType(config.getRewardType());
+                courseWatchLogMapper.updateFsCourseWatchLog(log);
+            });
+
+            if(StringUtils.equals(ENABLE_RED_PACK_ACCOUNT,"1")) {
+                // 更新账户余额
+                logger.info("[更新账户余额] 当前余额{} 更新后余额{}",companyMoney.toPlainString(),companyMoney.subtract(amount).toPlainString());
+
+                companyMoney = companyMoney.subtract(amount);
+                redisCache.setCacheObject(REDPACKET_COMPANY_MONEY,companyMoney);
+            }
+            return sendRedPacket;
+        } else {
+            return R.error("奖励发送失败,请联系客服");
+        }
+    }
+
 
     private void handleFsUserWx(FsUser user, String appId) {
         FsUserWx fsUserWx = new FsUserWx();
@@ -1177,7 +1229,7 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
         fsUserWx.setUpdateTime(new Date());
         fsUserWxService.saveOrUpdateByUniqueKey(fsUserWx);
 
-        logger.info("zyp \n 【更新或插入用户与小程序{}的绑定关系】:{}", appId, user.getUserId());
+        logger.info("【更新或插入用户与小程序{}的绑定关系】:{}", appId, user.getUserId());
     }
 
     /**
@@ -1232,14 +1284,14 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
                 FsUserWx fsUserWx = fsUserWxService.selectByAppIdAndUserId(param.getAppId(),user.getUserId(),1);
                 if (fsUserWx ==null){
                     if (user.getCourseMaOpenId()==null){
-                        logger.error("zyp \n 【转账openId参数错误】:{}", user.getUserId());
+                        logger.error(" 【转账openId参数错误】:{}", user.getUserId());
                         return R.error("openId参数错误,请清理缓存后重新授权!");
                     }
                     packetParam.setOpenId(user.getCourseMaOpenId());
                     try {
                         handleFsUserWx(user,param.getAppId());
                     }catch (Exception e){
-                        logger.error("zyp \n 【更新或插入用户与小程序的绑定关系失败】:{}", user.getUserId(),e);
+                        logger.error(" 【更新或插入用户与小程序的绑定关系失败】:{}", user.getUserId(),e);
                     }
 
                 }else {
@@ -2163,6 +2215,8 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
         }
         link.setProjectCode(cloudHostProper.getProjectCode());
 
+        link.setProjectCode(cloudHostProper.getProjectCode());
+
         String randomString = generateRandomStringWithLock();
         if (StringUtil.strIsNullOrEmpty(randomString)){
             link.setLink(UUID.randomUUID().toString().replace("-", ""));
@@ -2608,7 +2662,7 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
     public R getInternetTrafficIsOpen(FsUserCourseVideoFinishUParam param) {
         try {
             if (param.getBufferRate()==null){
-                logger.error("zyp \n【缓冲值空】参数: {}",param);
+                logger.error("【缓冲值空】参数: {}",param);
                 return R.error("缓冲值空");
             }
             FsCourseTrafficLog trafficLog = new FsCourseTrafficLog();
@@ -2629,13 +2683,13 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
             // 处理 UUID 为空的情况
             if (StringUtils.isNotEmpty(trafficLog.getUuId())) {
                 // 直接插入或更新
-//                logger.error("zyp \n【插入或更新流量】:{}",trafficLog);
+//                logger.error("【插入或更新流量】:{}",trafficLog);
                 fsCourseTrafficLogMapper.insertOrUpdateTrafficLog(trafficLog);
             }
         } catch (Exception e) {
             e.printStackTrace();
             // 打印参数param和异常信息
-            logger.error("zyp \n【插入或更新流量失败】参数: {}, 错误信息:{}", param, e.getMessage(), e);
+            logger.error("【插入或更新流量失败】参数: {}, 错误信息:{}", param, e.getMessage(), e);
             return R.error();
         }
         return R.ok();
@@ -2651,7 +2705,7 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
             log.setDuration(0L);
             log.setCreateTime(new Date());
             log.setLogType(3);
-            logger.info("zyp \n【群聊生成看课记录】:{}",param);
+            logger.info("【群聊生成看课记录】:{}",param);
             courseWatchLogMapper.insertFsCourseWatchLog(log);
         } catch (BeansException e) {
             return R.error("群聊生成看课记录失败!");

+ 8 - 4
fs-service/src/main/java/com/fs/his/service/impl/FsStorePaymentServiceImpl.java

@@ -26,6 +26,7 @@ import com.fs.company.domain.Company;
 import com.fs.company.domain.CompanyUser;
 import com.fs.company.mapper.CompanyConfigMapper;
 import com.fs.company.param.FsStoreStatisticsParam;
+import com.fs.company.service.ICompanyConfigService;
 import com.fs.company.service.ICompanyService;
 import com.fs.company.service.ICompanyUserService;
 import com.fs.company.vo.FsStorePaymentStatisticsVO;
@@ -115,6 +116,7 @@ import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Propagation;
 import org.springframework.transaction.annotation.Transactional;
 
 import javax.servlet.http.HttpServletRequest;
@@ -475,9 +477,11 @@ public class FsStorePaymentServiceImpl implements IFsStorePaymentService {
         return fsStorePaymentMapper.selectFsStorePaymentByPaymentCode(payCode);
     }
 
+    @Autowired
+    private ICompanyConfigService companyConfigService;
 
     @Override
-    @Transactional
+    @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
     public R sendRedPacket(WxSendRedPacketParam param) {
 
         String json;
@@ -489,7 +493,7 @@ public class FsStorePaymentServiceImpl implements IFsStorePaymentService {
                 config = JSONUtil.toBean(json, RedPacketConfig.class);
                 break;
             case 2:
-                json = companyConfigMapper.selectRedPacketConfigByKey(param.getCompanyId());
+                json = companyConfigService.selectRedPacketConfigByKey(param.getCompanyId());
                 config = JSONUtil.toBean(json, RedPacketConfig.class);
                 break;
         }
@@ -499,7 +503,7 @@ public class FsStorePaymentServiceImpl implements IFsStorePaymentService {
             String appId = StringUtils.isBlank(param.getAppId()) ? config.getMiniappId() : param.getAppId();
             config.setAppId(appId);
         }
-        System.out.println("最终传参"+config);
+        logger.info("最终传参 {}",config);
         //组合返回参数
         R result = new R();
         // 根据 isNew 判断使用哪种发红包方式
@@ -510,7 +514,7 @@ public class FsStorePaymentServiceImpl implements IFsStorePaymentService {
         }
         result.put("mchId",config.getMchId()+"");
         result.put("isNew",config.getIsNew());
-        System.out.println("红包返回:"+result);
+        logger.info("红包返回:{}",result);
         return result;
     }
 

+ 22 - 8
fs-service/src/main/java/com/fs/system/service/impl/SysConfigServiceImpl.java

@@ -14,8 +14,10 @@ import com.fs.system.service.ISysConfigService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import javax.annotation.PostConstruct;
+import javax.annotation.concurrent.ThreadSafe;
 import java.util.Collection;
 import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
 
 /**
  * 参数配置 服务层实现
@@ -23,6 +25,7 @@ import java.util.List;
 
  */
 @Service
+@ThreadSafe
 public class SysConfigServiceImpl implements ISysConfigService
 {
     @Autowired
@@ -31,6 +34,7 @@ public class SysConfigServiceImpl implements ISysConfigService
     @Autowired
     private RedisCache redisCache;
 
+    private static final ConcurrentHashMap<String, Object> KEY_LOCKS = new ConcurrentHashMap<>();
     /**
      * 项目启动时,初始化参数到缓存
      */
@@ -69,15 +73,25 @@ public class SysConfigServiceImpl implements ISysConfigService
         {
             return configValue;
         }
-        SysConfig config = new SysConfig();
-        config.setConfigKey(configKey);
-        SysConfig retConfig = configMapper.selectConfig(config);
-        if (StringUtils.isNotNull(retConfig))
-        {
-            redisCache.setCacheObject(getCacheKey(configKey), retConfig.getConfigValue());
-            return retConfig.getConfigValue();
+        Object keyLock = KEY_LOCKS.computeIfAbsent(configKey, k -> new Object());
+
+        synchronized (keyLock){
+            configValue = Convert.toStr(redisCache.getCacheObject(getCacheKey(configKey)));
+            if (StringUtils.isNotEmpty(configValue))
+            {
+                return configValue;
+            }
+
+            SysConfig config = new SysConfig();
+            config.setConfigKey(configKey);
+            SysConfig retConfig = configMapper.selectConfig(config);
+            if (StringUtils.isNotNull(retConfig))
+            {
+                redisCache.setCacheObject(getCacheKey(configKey), retConfig.getConfigValue());
+                return retConfig.getConfigValue();
+            }
+            return StringUtils.EMPTY;
         }
-        return StringUtils.EMPTY;
     }
 
     /**

+ 2 - 0
fs-service/src/main/resources/application-config-druid-qdtst.yml

@@ -93,3 +93,5 @@ ipad:
 wx_miniapp_temp:
   pay_order_temp_id:
   inquiry_temp_id:
+
+enableRedPackAccount: 1

+ 1 - 1
fs-service/src/main/resources/application-druid-qdtst.yml

@@ -167,4 +167,4 @@ openIM:
     secret:
     userID:
 #是否为新商户,新商户不走mpOpenId
-isNewWxMerchant: true
+isNewWxMerchant: false

+ 6 - 0
fs-service/src/main/resources/mapper/course/FsCourseRedPacketLogMapper.xml

@@ -110,6 +110,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="result != null">result,</if>
             <if test="batchId != null">batch_id,</if>
             <if test="appId != null">app_id,</if>
+            <if test="appId != null">app_id,</if>
+            <if test="appId != null">app_id,</if>
+            <if test="accBalanceBefore != null">acc_balance_before,</if>
+            <if test="accBalanceAfter != null">acc_balance_after,</if>
          </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="courseId != null">#{courseId},</if>
@@ -129,6 +133,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="result != null">#{result},</if>
             <if test="batchId != null">#{batchId},</if>
             <if test="appId != null">#{appId},</if>
+            <if test="accBalanceBefore != null">#{accBalanceBefore},</if>
+            <if test="accBalanceAfter != null">#{accBalanceAfter},</if>
         </trim>
     </insert>
 

+ 1 - 1
fs-user-app/src/main/java/com/fs/app/controller/course/CourseQwController.java

@@ -222,7 +222,7 @@ public class CourseQwController extends AppBaseController {
     public R sendReward(@RequestBody FsCourseSendRewardUParam param)
     {
         param.setUserId(Long.parseLong(getUserId()));
-        logger.info("zyp \n【发放奖励】:{}",param);
+        logger.info("【发放奖励】:{}",param);
         return courseVideoService.sendReward(param);
     }
 

+ 4 - 4
fs-user-app/src/main/java/com/fs/app/controller/store/CourseH5ScrmController.java

@@ -146,9 +146,9 @@ public class CourseH5ScrmController extends AppBaseController {
     public R courseAnswer(@RequestBody FsCourseQuestionAnswerUParam param)
     {
         param.setUserId(Long.parseLong(getUserId()));
-        logger.info("zyp \n【答题】:{}",param.getQuestions());
+        logger.info("【答题】:{}",param.getQuestions());
         if (param.getDuration()==null){
-            logger.info("zyp \n【未识别到时长】:{}",param.getUserId());
+            logger.info("【未识别到时长】:{}",param.getUserId());
         }
         return questionBankService.courseAnswer(param, false);
     }
@@ -159,13 +159,13 @@ public class CourseH5ScrmController extends AppBaseController {
     public R sendReward(@RequestBody FsCourseSendRewardUParam param)
     {
         param.setUserId(Long.parseLong(getUserId()));
-        logger.info("zyp \n【发放奖励】:{}",param);
+        logger.info("【发放奖励】:{}",param);
         return courseVideoService.sendReward(param);
     }
 
     @PostMapping("/getErrMsg")
     public void getErrMsg(@RequestParam("msg") String msg) {
-        logger.error("zyp \n【h5看课中途报错】:{}",msg);
+        logger.error("【h5看课中途报错】:{}",msg);
     }
 
 }

+ 2 - 2
fs-user-app/src/main/java/com/fs/app/controller/store/CourseWxH5ScrmController.java

@@ -121,14 +121,14 @@ public class CourseWxH5ScrmController extends AppBaseController {
     public R sendReward(@RequestBody FsCourseSendRewardUParam param)
     {
         param.setUserId(Long.parseLong(getUserId()));
-        logger.info("zyp \n【发放奖励】:{}",param);
+        logger.info("【发放奖励】:{}",param);
         return courseVideoService.sendRewardByFsUser(param);
     }
 
 
     @PostMapping("/getErrMsg")
     public void getErrMsg(@RequestParam("msg") String msg) {
-        logger.error("zyp \n【h5看课中途报错】:{}",msg);
+        logger.error("【h5看课中途报错】:{}",msg);
     }
 
 

+ 2 - 1
fs-user-app/src/main/resources/application.yml

@@ -11,5 +11,6 @@ spring:
 #    active: druid-yzt
 #    active: druid-hdt
 #    active: druid-sxjz
+    active: druid-qdtst
 #    active: druid-yzt
-    active: druid-jnmy-test
+#    active: druid-jnmy-test