Browse Source

Merge branch 'refs/heads/master_red_banlance_20251103'

# Conflicts:
#	fs-service/src/main/java/com/fs/company/domain/Company.java
#	fs-service/src/main/java/com/fs/company/service/ICompanyService.java
#	fs-service/src/main/java/com/fs/company/service/impl/CompanyServiceImpl.java
xgb 1 week ago
parent
commit
2c506f3ed5
20 changed files with 546 additions and 198 deletions
  1. 35 0
      fs-admin/src/main/java/com/fs/his/controller/FsCompanyController.java
  2. 20 13
      fs-admin/src/main/java/com/fs/his/controller/FsCompanyRechargeController.java
  3. 16 4
      fs-admin/src/main/java/com/fs/his/task/CompanyBalanceTask.java
  4. 1 0
      fs-common/src/main/java/com/fs/common/constant/FsConstants.java
  5. 43 0
      fs-company/src/main/java/com/fs/company/controller/company/FsRedPacketController.java
  6. 2 0
      fs-service/src/main/java/com/fs/company/domain/Company.java
  7. 3 0
      fs-service/src/main/java/com/fs/company/domain/CompanyRecharge.java
  8. 3 0
      fs-service/src/main/java/com/fs/company/mapper/CompanyRechargeMapper.java
  9. 6 0
      fs-service/src/main/java/com/fs/company/service/ICompanyService.java
  10. 142 16
      fs-service/src/main/java/com/fs/company/service/impl/CompanyServiceImpl.java
  11. 3 0
      fs-service/src/main/java/com/fs/company/vo/CompanyRechargeVO.java
  12. 2 0
      fs-service/src/main/java/com/fs/company/vo/CompanyVO.java
  13. 5 0
      fs-service/src/main/java/com/fs/course/config/CourseConfig.java
  14. 2 3
      fs-service/src/main/java/com/fs/course/service/impl/BalanceRollbackErrorServiceImpl.java
  15. 168 154
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java
  16. 23 0
      fs-service/src/main/java/com/fs/his/domain/FsRedPacket.java
  17. 15 0
      fs-service/src/main/java/com/fs/his/service/IFsRedPacketService.java
  18. 45 0
      fs-service/src/main/java/com/fs/his/service/impl/FsRedPacketServiceImpl.java
  19. 1 0
      fs-service/src/main/resources/mapper/company/CompanyMapper.xml
  20. 11 8
      fs-service/src/main/resources/mapper/company/CompanyRechargeMapper.xml

+ 35 - 0
fs-admin/src/main/java/com/fs/his/controller/FsCompanyController.java

@@ -217,6 +217,41 @@ public class FsCompanyController extends BaseController {
 
     }
 
+
+    /**
+     * @Description: 红包充值功能 因公司余额的变动关联的地方太多,现在把充值红包的金额单独提出来 不合计到公司余额中
+     * @Param:
+     * @Return:
+     * @Author xgb
+     * @Date 2025/11/3 11:08
+     */
+    @PreAuthorize("@ss.hasPermi('his:company:redRecharge')")
+    @Log(title = "红包充值", businessType = BusinessType.INSERT)
+    @PostMapping(value = "/redRecharge")
+    @Transactional
+    @RepeatSubmit
+    public R redRecharge(@RequestBody CompanyRechargeParam param)
+    {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        CompanyRecharge redRecharge=new CompanyRecharge();
+        String orderSn =  OrderCodeUtils.getOrderSn();
+        if(StringUtils.isEmpty(orderSn)){
+            return R.error("订单生成失败,请重试");
+        }
+        redRecharge.setRechargeNo(orderSn);
+        redRecharge.setCompanyId(param.getCompanyId());
+        redRecharge.setMoney(param.getMoney());
+        redRecharge.setCreateUserId(loginUser.getUser().getUserId());
+        redRecharge.setIsAudit(0);
+        redRecharge.setStatus(0);
+        redRecharge.setRemark(param.getRemark());
+        redRecharge.setPayType(3);
+        redRecharge.setBusinessType(1);// 红包充值
+        rechargeService.insertCompanyRecharge(redRecharge);
+        return R.ok("提交成功,等待审核");
+
+    }
+
     @PreAuthorize("@ss.hasPermi('his:company:deduct')")
     @Log(title = "企业扣款", businessType = BusinessType.INSERT)
     @PostMapping(value = "/deduct")

+ 20 - 13
fs-admin/src/main/java/com/fs/his/controller/FsCompanyRechargeController.java

@@ -137,21 +137,28 @@ public class FsCompanyRechargeController extends BaseController
         companyRecharge.setRemark(param.getRemark());
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
         if(companyRecharge.getIsAudit()==1){
-            Company company=companyService.selectCompanyByIdForUpdate(companyRecharge.getCompanyId());
-            company.setMoney(company.getMoney().add(companyRecharge.getMoney()));
-            companyRecharge.setBalance(company.getMoney());
-            companyService.updateCompany(company);
-            //生成流水
-            CompanyMoneyLogs log=new CompanyMoneyLogs();
-            log.setCompanyId(companyRecharge.getCompanyId());
-            log.setMoney(companyRecharge.getMoney());
-            log.setRemark(companyRecharge.getRemark());
-            log.setLogsType(1);
-            log.setBalance(company.getMoney());
-            log.setCreateTime(new Date());
-            moneyLogsService.insertCompanyMoneyLogs(log);
+            if(1==companyRecharge.getBusinessType()){// 红包充值
+                // 更新红包充值余额redis字段
+                companyService.redPacketTopUpCompany(companyRecharge.getCompanyId(),companyRecharge.getMoney());
+            }else {
+                Company company=companyService.selectCompanyByIdForUpdate(companyRecharge.getCompanyId());
+                company.setMoney(company.getMoney().add(companyRecharge.getMoney()));
+                companyRecharge.setBalance(company.getMoney());
+                companyService.updateCompany(company);
+                //生成流水
+                CompanyMoneyLogs log=new CompanyMoneyLogs();
+                log.setCompanyId(companyRecharge.getCompanyId());
+                log.setMoney(companyRecharge.getMoney());
+                log.setRemark(companyRecharge.getRemark());
+                log.setLogsType(1);
+                log.setBalance(company.getMoney());
+                log.setCreateTime(new Date());
+                moneyLogsService.insertCompanyMoneyLogs(log);
+
+            }
             companyRecharge.setPayTime(new Date());
             companyRecharge.setStatus(1);
+
         }
 
         companyRecharge.setAuditTime(new Date());

+ 16 - 4
fs-admin/src/main/java/com/fs/course/task/CompanyBalanceTask.java → fs-admin/src/main/java/com/fs/his/task/CompanyBalanceTask.java

@@ -1,14 +1,13 @@
-package com.fs.course.task;
+package com.fs.his.task;
 
 import com.fs.company.service.ICompanyService;
 import com.fs.course.service.BalanceRollbackErrorService;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.context.event.ApplicationReadyEvent;
-import org.springframework.context.event.EventListener;
+import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.stereotype.Component;
 
 /**
- * @description: 公司余额同步定时任务
+ * @description: 公司余额同步定时任务 (红包余额)
  * @author: Xgb
  * @createDate: 2025/10/22
  * @version: 1.0
@@ -59,6 +58,19 @@ public class CompanyBalanceTask {
 
     }
 
+    /**
+     * @Description: 每天晚上0点定时获取获取公司余额,记录到数据库中
+     * @Param:
+     * @Return:
+     * @Author xgb
+     * @Date 2025/11/4 10:43
+     */
+    public void recordRedPacketBalance() {
+        companyService.recordRedPacketBalance();
+    }
+
+
+
 
 
 }

+ 1 - 0
fs-common/src/main/java/com/fs/common/constant/FsConstants.java

@@ -14,6 +14,7 @@ public interface FsConstants {
 
     // 公司余额redis key "company:money:" + company.getCompanyId()
     String COMPANY_MONEY_KEY = "company:money:";
+    String COMPANY_MONEY_DATE_KEY = "company:money:date:";
     // 公司余额redis 锁
     String COMPANY_MONEY_LOCK = "company_money_lock:";
     // 看客统计  按公司分组 按TimeType 0-今天,1-昨天,2-本周,3-本月,4-上月;

+ 43 - 0
fs-company/src/main/java/com/fs/company/controller/company/FsRedPacketController.java

@@ -0,0 +1,43 @@
+package com.fs.company.controller.company;
+
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.R;
+import com.fs.common.utils.ServletUtils;
+import com.fs.company.domain.Company;
+import com.fs.framework.security.LoginUser;
+import com.fs.framework.service.TokenService;
+import com.fs.his.domain.FsRedPacket;
+import com.fs.his.service.IFsRedPacketService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * @description: 红包信息
+ * @author: Xgb
+ * @createDate: 2025/11/4
+ * @version: 1.0
+ */
+@RestController
+@RequestMapping("/his/redPacket")
+public class FsRedPacketController extends BaseController {
+
+    @Autowired
+    private IFsRedPacketService fsRedPacketService;
+
+    @Autowired
+    private TokenService tokenService;
+
+    @GetMapping("/info")
+    public R info() {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+
+        Company company = loginUser.getCompany();
+        FsRedPacket redPacket=fsRedPacketService.getInfo(company.getCompanyId());
+        return  R.ok().put("data",redPacket);
+    }
+
+
+
+}

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

@@ -136,6 +136,8 @@ public class Company extends BaseEntity
     /** 所属部门id */
     private Long deptId;
 
+    private BigDecimal redPackageMoney;
+
     @TableField(exist = false)
     private List<Long> ids;
 }

+ 3 - 0
fs-service/src/main/java/com/fs/company/domain/CompanyRecharge.java

@@ -75,4 +75,7 @@ public class CompanyRecharge extends BaseEntity
 
     private String remark;
     private String imgs;
+
+    /** 业务类型 1普通 2-红包充值 */
+    private Integer businessType;
 }

+ 3 - 0
fs-service/src/main/java/com/fs/company/mapper/CompanyRechargeMapper.java

@@ -150,6 +150,9 @@ public interface CompanyRechargeMapper
             "<if test = 'maps.payType != null  '> " +
             "and r.pay_type = #{maps.payType}" +
             "</if>" +
+            "<if test = 'maps.businessType != null  '> " +
+            "and r.business_type = #{maps.businessType}" +
+            "</if>" +
 
             "<if test = 'maps.createTime != null  '> " +
             "and   DATE(r.create_time) = DATE(#{maps.createTime})" +

+ 6 - 0
fs-service/src/main/java/com/fs/company/service/ICompanyService.java

@@ -170,6 +170,12 @@ public interface ICompanyService
 
     void syncCompanyBalance();
 
+    void redPacketTopUpCompany(Long companyId, BigDecimal money);
+
+    void asyncRecordBalanceLog(Long companyId, BigDecimal money,Integer logType, BigDecimal balance, String remark);
+
+    void recordRedPacketBalance();
+
     /**
      * 批量更新公司余额
      * @param list 公司列表

+ 142 - 16
fs-service/src/main/java/com/fs/company/service/impl/CompanyServiceImpl.java

@@ -3,6 +3,7 @@ package com.fs.company.service.impl;
 import java.math.BigDecimal;
 import java.time.LocalTime;
 import java.util.*;
+import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 
 import cn.hutool.core.util.ObjectUtil;
@@ -42,6 +43,8 @@ import com.github.pagehelper.PageHelper;
 import com.google.gson.Gson;
 import org.apache.commons.collections4.CollectionUtils;
 import org.apache.commons.lang3.ObjectUtils;
+import org.redisson.api.RLock;
+import org.redisson.api.RedissonClient;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -104,6 +107,9 @@ public class CompanyServiceImpl implements ICompanyService
     @Autowired
     private RedisCache redisCache;
 
+    @Autowired
+    private RedissonClient redissonClient;
+
     @Autowired
     private TransactionTemplate transactionTemplate;
 
@@ -672,7 +678,16 @@ public class CompanyServiceImpl implements ICompanyService
 
     @Override
     public List<CompanyVO> selectCompanyListVO(Company company) {
-        return companyMapper.selectCompanyListVO(company);
+
+        List<CompanyVO> companyVOList=companyMapper.selectCompanyListVO(company);
+        companyVOList.forEach(companyVO -> {
+            String redPackageMoney = redisCache.getCacheObject(FsConstants.COMPANY_MONEY_KEY+companyVO.getCompanyId());
+            if(redPackageMoney!=null){
+                companyVO.setRedPackageMoney(new BigDecimal(redPackageMoney));
+
+            }
+        });
+        return companyVOList;
     }
 
     @Override
@@ -1318,12 +1333,12 @@ public class CompanyServiceImpl implements ICompanyService
                             String moneyStr = redisCache.getCacheObject(FsConstants.COMPANY_MONEY_KEY + company.getCompanyId());
                             if (StringUtils.isNotEmpty(moneyStr)) {
                                 BigDecimal redisMoney = new BigDecimal(moneyStr);
-                                BigDecimal dbMoney = company.getMoney();
+                                BigDecimal dbMoney = company.getRedPackageMoney();
                                 BigDecimal amount = redisMoney.subtract(dbMoney); // 计算差额
 
                                 // 只有当余额不一致时才更新
                                 if (amount.compareTo(BigDecimal.ZERO) != 0) {
-                                    company.setMoney(redisMoney);
+                                    company.setRedPackageMoney(redisMoney);
                                     int updateResult = companyMapper.updateCompany(company);
                                     if(updateResult != 1){
                                         logger.error("更新公司余额失败,公司ID: {}", company.getCompanyId());
@@ -1331,19 +1346,9 @@ public class CompanyServiceImpl implements ICompanyService
                                     }
 
                                     // 记录余额变更日志
-                                    CompanyMoneyLogs log = new CompanyMoneyLogs();
-                                    log.setCompanyId(company.getCompanyId());
-                                    log.setRemark("同步公司余额,差额: " + amount);
-                                    log.setMoney(amount);
-                                    log.setLogsType(15);
-                                    log.setBalance(redisMoney);
-                                    log.setCreateTime(new Date());
-
-                                    int logResult = moneyLogsMapper.insertCompanyMoneyLogs(log);
-                                    if(logResult != 1){
-                                        logger.error("添加公司余额日志失败,公司ID: {}", company.getCompanyId());
-                                        throw new RuntimeException("添加公司余额日志失败");
-                                    }
+                                    String remark = "同步公司余额,差额: " + amount+"(正数为增加,负数为扣减)";
+                                    // 实际不发生交易只是从缓存同步金额到数据库中 交易金额登记为0,备注清楚同步的金额
+                                    asyncRecordBalanceLog(company.getCompanyId(),new BigDecimal(0),17,redisMoney,remark);
                                 }
                             }
                             return null;
@@ -1354,6 +1359,127 @@ public class CompanyServiceImpl implements ICompanyService
                 }));
     }
 
+    /**
+     * @Description: 红包充值
+     * @Param:
+     * @Return:
+     * @Author xgb
+     * @Date 2025/11/3 14:01
+     */
+    @Override
+    public void redPacketTopUpCompany(Long companyId, BigDecimal money) {
+
+        String companyMoneyKey = FsConstants.COMPANY_MONEY_KEY + companyId;
+
+        // 加锁:增加余额
+        RLock lock1 = redissonClient.getLock(FsConstants.COMPANY_MONEY_LOCK + companyId);
+        boolean lockAcquired = false;
+        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余额缓存异常,异常请求参数companyId:{},money:{}",companyId,money);
+                    throw new RuntimeException("红包余额充值获取redis余额缓存异常");
+                }
+
+                // 增加余额
+                BigDecimal newMoney = originalMoney.add(money);
+                redisCache.setCacheObject(companyMoneyKey, newMoney.toString());
+
+                // 异步登记余额添加日志
+                asyncRecordBalanceLog(companyId,money,16,newMoney,"红包充值");
+
+            } else {
+                logger.error("获取redis锁失败,异常请求参数companyId:{},money:{}",companyId,money);
+                throw new RuntimeException("服务繁忙,请稍后重试");
+            }
+        } catch (Exception e) {
+            logger.error("增加余额失败: 异常请求参数companyId:{},money:{}",companyId,money, e);
+            throw new RuntimeException("系统异常,请稍后重试");
+        }finally {
+            // 只有在成功获取锁的情况下才释放锁
+            if (lockAcquired && lock1.isHeldByCurrentThread()) {
+                try {
+                    lock1.unlock();
+                } catch (IllegalMonitorStateException e) {
+                    logger.error("尝试释放非当前线程持有的锁: 异常请求参数companyId:{},money:{}",companyId,money);
+                }
+            }
+        }
+
+
+    }
+
+
+    /**
+     * 异步登记余额添加日志  xgb
+     * @param companyId 公司ID
+     * @param money 变更金额
+     * @param balance 当前余额
+     * @param remark 备注信息
+     * @param logType 16-红包余额充值 15-红包余额扣除 17-同步公司余额
+     */
+    @Async
+    @Override
+    public void asyncRecordBalanceLog(Long companyId, BigDecimal money,Integer logType, BigDecimal balance, String remark) {
+        try {
+            CompanyMoneyLogs log = new CompanyMoneyLogs();
+            log.setCompanyId(companyId);
+            log.setRemark(remark);
+            log.setMoney(money);
+            log.setLogsType(logType); // 同步余额
+            log.setBalance(balance);
+            log.setCreateTime(new Date());
+            moneyLogsMapper.insertCompanyMoneyLogs(log);
+            logger.info("异步登记余额日志成功 - 公司ID: {}, 金额: {}, 余额: {}, 备注: {}",
+                    companyId, money, balance, remark);
+        } catch (Exception e) {
+            logger.error("异步登记余额日志失败 - 公司ID: {}, 金额: {}, 余额: {}, 备注: {}",
+                    companyId, money, balance, remark, e);
+        }
+    }
+
+    /**
+     * @Description: 每天晚上0点定时获取获取公司余额,记录到数据库中
+     * @Param:
+     * @Return:
+     * @Author xgb
+     * @Date 2025/11/4 10:51
+     */
+    @Override
+    public void recordRedPacketBalance() {
+        Company query = new Company();
+        query.setIsDel(0);
+        // 查询公司列表 同步公司余额
+        List<Company> companyList = companyMapper.selectCompanyList(query);
+        Optional.ofNullable(companyList).ifPresent(list -> list.forEach(company -> {
+            transactionTemplate.execute(status -> {
+                String time=DateUtils.getTime();
+                String moneyStr = redisCache.getCacheObject(FsConstants.COMPANY_MONEY_KEY + company.getCompanyId());
+                if (StringUtils.isNotEmpty(moneyStr)) {
+                    // 存到缓存中,有效期25小时
+                    redisCache.setCacheObject(FsConstants.COMPANY_MONEY_DATE_KEY + company.getCompanyId()+":"+DateUtils.getDate(), moneyStr, 25, TimeUnit.HOURS);
+                    // 实际不发生交易只是从缓存获取当天余额报错25小时 交易金额登记为0,备注清楚同步的金额
+                    String remark = "时间:" + time +",当前公司余额,金额: " + moneyStr;
+                    BigDecimal money = new BigDecimal(moneyStr);
+                    asyncRecordBalanceLog(company.getCompanyId(),new BigDecimal(0),18,money,remark);
+                }
+                return null;
+            });
+
+
+        }));
+
+
+    }
+
+
     @Override
     public void batchUpdateCompany(List<Company> list) {
         companyMapper.batchUpdateCompany(list);

+ 3 - 0
fs-service/src/main/java/com/fs/company/vo/CompanyRechargeVO.java

@@ -85,6 +85,9 @@ public class CompanyRechargeVO implements Serializable {
     @Excel(name = "备注")
     private String remark;
 
+    /** 业务类型 0-普通 1-红包充值 */
+    private Integer businessType;
+
 
 
 }

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

@@ -95,4 +95,6 @@ public class CompanyVO implements Serializable
     private Integer usedNum;
     /** 所属部门id */
     private Long deptId;
+
+    private BigDecimal redPackageMoney;
 }

+ 5 - 0
fs-service/src/main/java/com/fs/course/config/CourseConfig.java

@@ -87,6 +87,11 @@ public class CourseConfig implements Serializable {
      */
     private Boolean isOpenIM;
 
+    /**
+     * 是否红包余额抵扣 1是 0否
+     */
+    private String isRedPackageBalanceDeduction;
+
 
     @Data
     public static class DisabledTimeVo{

+ 2 - 3
fs-service/src/main/java/com/fs/course/service/impl/BalanceRollbackErrorServiceImpl.java

@@ -89,8 +89,7 @@ public class BalanceRollbackErrorServiceImpl extends ServiceImpl<BalanceRollback
     }
 
     @Override
-    // todo
-//    @EventListener(ApplicationReadyEvent.class)
+    @EventListener(ApplicationReadyEvent.class)
     public void initCompanyBalance() {
 
         // 查询公司表 Company
@@ -104,7 +103,7 @@ public class BalanceRollbackErrorServiceImpl extends ServiceImpl<BalanceRollback
             String moneyStr = redisCache.getCacheObject(companyMoneyKey);
             if (StringUtils.isEmpty(moneyStr)) {
                 // 初始化余额
-                redisCache.setCacheObject(companyMoneyKey, company.getMoney().toString());
+                redisCache.setCacheObject(companyMoneyKey, company.getRedPackageMoney().toString());
             }
         }
     }

+ 168 - 154
fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java

@@ -1609,174 +1609,183 @@ 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("服务商余额不足,请联系群主服务器充值!");
-            }
-
-            // 发送红包
-            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());
-                    redPacketLog.setBatchId(transferBillsResult.getTransferBillNo());
-                }else {
-                    redPacketLog.setOutBatchNo(sendRedPacket.get("orderCode").toString());
-                    redPacketLog.setBatchId(sendRedPacket.get("batchId").toString());
+            // 打开红包扣减功能
+            if("1".equals(config.getIsRedPackageBalanceDeduction())){
+                // 先注释 20251024 redis 余额 充值没有考虑 其余扣减没有考虑
+                // ===================== 20251022 xgb 修改 本次修改目的为了实时扣减公司余额=====================
+                // 1 使用redis缓存加锁 预扣减余额 红包发送失败 恢复redis缓存余额,如果回滚失败登记异常记录表 定时任务重新回滚余额
+                // 2 另起定时任务 同步缓存余额到redis中
+                // 3 注意!!!!! 启动系统时查询公司账户余额(这个时候要保证余额正确)启动会自动保存到redis缓存中
+                // 注意!!!!! 打开这个开关前记得检测redis缓存余额是否正确 若不正确 修改数据库字段red_package_money,删除redis缓存,重启系统,
+
+
+                // 预设值异常对象
+
+                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);
+
+                if(packetParam.getCompanyId()== null){
+                    logger.error("发送红包参数错误,公司不能为空,异常请求参数{}",packetParam);
+                    return R.error("发送红包失败,请联系管理员");
                 }
-                // 添加红包记录
-                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());
-                redPacketLog.setAppId(param.getAppId());
-
-                redPacketLogMapper.insertFsCourseRedPacketLog(redPacketLog);
-
-                // 更新观看记录的奖励类型
-                log.setRewardType(config.getRewardType());
-                courseWatchLogMapper.updateFsCourseWatchLog(log);
-
-                return sendRedPacket;
-            } else {
-                return R.error("奖励发送失败,请联系客服");
-            }
-
-            // 先注释 20251024 redis 余额 充值没有考虑 其余扣减没有考虑
-            // ===================== 20251022 xgb 修改 本次修改目的为了实时扣减公司余额=====================
-            // 1 使用redis缓存加锁 预扣减余额 红包发送失败 恢复redis缓存余额,如果回滚失败登记异常记录表 定时任务重新回滚余额
-            // 2 另起定时任务 同步缓存余额到redis中
-            // 3 启动系统时查询公司账户余额(这个时候要保证余额正确)保存到redis缓存中
-
-
-            // 预设值异常对象
-            /*
-            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);
-
-            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;
-            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("系统异常,请稍后重试");
-                    }
+                String companyMoneyKey = FsConstants.COMPANY_MONEY_KEY + packetParam.getCompanyId();
 
-                    if (originalMoney.compareTo(BigDecimal.ZERO) < 0) {
-                        logger.error("服务商余额不足,异常请求参数{}",packetParam);
-                        return R.error("服务商余额不足,请联系群主服务器充值!");
+                // 第一次加锁:预扣减余额
+                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("系统繁忙,请稍后重试");
                     }
-
-                    // 预扣减金额
-                    BigDecimal 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());
+                } 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.sendRedPacket(packetParam);
-            } catch (Exception e) {
-                logger.error("红包发送异常: 异常请求参数{}",packetParam, e);
-                // 异常时回滚余额
+                // 调用第三方接口(锁外操作)
+                R sendRedPacket;
+                try {
+                    sendRedPacket= paymentService.sendRedPacket(packetParam);
+                } catch (Exception e) {
+                    logger.error("红包发送异常: 异常请求参数{}",packetParam, e);
+                    // 异常时回滚余额
 
-//                rollbackBalance(balanceRollbackError);
-                return R.error("奖励发送失败,请联系客服");
-            }
+                    rollbackBalance(balanceRollbackError);
+                    return R.error("奖励发送失败,请联系客服");
+                }
 
-            // 红包发送成功处理
-            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());
-                    redPacketLog.setBatchId(transferBillsResult.getTransferBillNo());
-                }else {
-                    redPacketLog.setOutBatchNo(sendRedPacket.get("orderCode").toString());
-                    redPacketLog.setBatchId(sendRedPacket.get("batchId").toString());
+                // 红包发送成功处理
+                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());
+                        redPacketLog.setBatchId(transferBillsResult.getTransferBillNo());
+                    }else {
+                        redPacketLog.setOutBatchNo(sendRedPacket.get("orderCode").toString());
+                        redPacketLog.setBatchId(sendRedPacket.get("batchId").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());
+                    redPacketLog.setAppId(param.getAppId());
+
+                    redPacketLogMapper.insertFsCourseRedPacketLog(redPacketLog);
+
+                    // 更新观看记录的奖励类型
+                    log.setRewardType(config.getRewardType());
+                    courseWatchLogMapper.updateFsCourseWatchLog(log);
+
+                    // 异步登记余额扣减日志
+                    BigDecimal money=amount.multiply(BigDecimal.valueOf(-1));
+                    companyService.asyncRecordBalanceLog(param.getCompanyId(), money, 15, newMoney, "发放红包");
+                    // 发送成功,记录日志等操作
+                    return sendRedPacket;
+
+
+                } else {
+                    // 发送失败,回滚余额
+                    rollbackBalance(balanceRollbackError);
+                    return R.error("奖励发送失败,请联系客服");
                 }
-                // 添加红包记录
-                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());
-                redPacketLog.setAppId(param.getAppId());
-
-                redPacketLogMapper.insertFsCourseRedPacketLog(redPacketLog);
-
-                // 更新观看记录的奖励类型
-                log.setRewardType(config.getRewardType());
-                courseWatchLogMapper.updateFsCourseWatchLog(log);
-                // 发送成功,记录日志等操作
-                return sendRedPacket;
 
+                // ===================== 本次修改目的为了实时扣减公司余额=====================
+            }else {
+                Company company = companyMapper.selectCompanyById(param.getCompanyId());
+                BigDecimal money = company.getMoney();
+                if (money.compareTo(BigDecimal.ZERO)<=0) {
+                    return R.error("服务商余额不足,请联系群主服务器充值!");
+                }
 
-            } else {
-                // 发送失败,回滚余额
-//                rollbackBalance(balanceRollbackError);
-                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());
+                        redPacketLog.setBatchId(transferBillsResult.getTransferBillNo());
+                    }else {
+                        redPacketLog.setOutBatchNo(sendRedPacket.get("orderCode").toString());
+                        redPacketLog.setBatchId(sendRedPacket.get("batchId").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());
+                    redPacketLog.setAppId(param.getAppId());
+
+                    redPacketLogMapper.insertFsCourseRedPacketLog(redPacketLog);
+
+                    // 更新观看记录的奖励类型
+                    log.setRewardType(config.getRewardType());
+                    courseWatchLogMapper.updateFsCourseWatchLog(log);
+
+                    return sendRedPacket;
+                } else {
+                    return R.error("奖励发送失败,请联系客服");
+                }
             }
-             */
-            // ===================== 本次修改目的为了实时扣减公司余额=====================
         } else {
             FsCourseRedPacketLog redPacketLog = new FsCourseRedPacketLog();
             // 添加红包记录
@@ -2663,6 +2672,11 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
 
     }
 
+    public static void main(String[] args) {
+        BigDecimal a=new BigDecimal(22.01);
+        System.out.println(a.multiply(new BigDecimal(-1)));
+    }
+
 
     //插入观看记录
     private void addWatchLogIfNeeded(Long videoId, Long courseId,

+ 23 - 0
fs-service/src/main/java/com/fs/his/domain/FsRedPacket.java

@@ -0,0 +1,23 @@
+package com.fs.his.domain;
+
+import com.fs.common.core.domain.BaseEntity;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+/**
+ * @description: 红包
+ * @author: Xgb
+ * @createDate: 2025/11/4
+ * @version: 1.0
+ */
+@Data
+public class FsRedPacket extends BaseEntity {
+    private static final long serialVersionUID = 1L;
+
+    // 红包余额
+    private BigDecimal redBalance;
+    // 今日消耗
+    private BigDecimal redTodayComsumption;
+
+}

+ 15 - 0
fs-service/src/main/java/com/fs/his/service/IFsRedPacketService.java

@@ -0,0 +1,15 @@
+package com.fs.his.service;
+
+import com.fs.his.domain.FsRedPacket;
+
+/**
+ * 红包Service接口
+ *
+ * @author fs
+ * @date 2023-10-23
+ */
+public interface IFsRedPacketService
+{
+
+    FsRedPacket getInfo(Long companyId);
+}

+ 45 - 0
fs-service/src/main/java/com/fs/his/service/impl/FsRedPacketServiceImpl.java

@@ -0,0 +1,45 @@
+package com.fs.his.service.impl;
+
+import com.fs.common.constant.FsConstants;
+import com.fs.common.core.redis.RedisCache;
+import com.fs.common.utils.DateUtils;
+import com.fs.his.domain.FsRedPacket;
+import com.fs.his.service.IFsRedPacketService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.math.BigDecimal;
+
+/**
+ * 红包Service业务层处理
+ *
+ * @author fs
+ * @date 2023-10-23
+ */
+@Service
+public class FsRedPacketServiceImpl implements IFsRedPacketService
+{
+
+    @Autowired
+    private RedisCache redisCache;
+    /**
+     * @Description: 获取红包余额相关信息
+     * @Param:
+     * @Return:
+     * @Author xgb
+     * @Date 2025/11/4 11:52
+     */
+    @Override
+    public FsRedPacket getInfo(Long companyId) {
+       String yesterdayBalance =redisCache.getCacheObject(FsConstants.COMPANY_MONEY_DATE_KEY+companyId+":"+ DateUtils.getDate());
+       String balance = redisCache.getCacheObject(FsConstants.COMPANY_MONEY_KEY+companyId);
+       FsRedPacket fsRedPacket = new FsRedPacket();
+       if(balance!=null){
+           fsRedPacket.setRedBalance(new BigDecimal( balance));
+           if(yesterdayBalance!=null){
+               fsRedPacket.setRedTodayComsumption(fsRedPacket.getRedBalance().subtract(new BigDecimal(yesterdayBalance)));
+           }
+       }
+       return fsRedPacket;
+    }
+}

+ 1 - 0
fs-service/src/main/resources/mapper/company/CompanyMapper.xml

@@ -200,6 +200,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="groupName != null">group_name = #{groupName},</if>
             <if test="maxPadNum != null">max_pad_num = #{maxPadNum},</if>
             <if test="deptId != null">dept_id = #{deptId},</if>
+            <if test="redPackageMoney != null">red_package_money = #{redPackageMoney},</if>
         </trim>
         where company_id = #{companyId}
     </update>

+ 11 - 8
fs-service/src/main/resources/mapper/company/CompanyRechargeMapper.xml

@@ -3,7 +3,7 @@
 PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 <mapper namespace="com.fs.company.mapper.CompanyRechargeMapper">
-    
+
     <resultMap type="CompanyRecharge" id="CompanyRechargeResult">
         <result property="rechargeId"    column="recharge_id"    />
         <result property="companyId"    column="company_id"    />
@@ -20,15 +20,16 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="auditUserId"    column="audit_user_id"    />
         <result property="auditTime"    column="audit_time"    />
         <result property="remark"    column="remark"    />
+        <result property="businessType"    column="business_type"    />
     </resultMap>
 
     <sql id="selectCompanyRechargeVo">
-        select recharge_id, company_id, recharge_no, money, create_time, pay_time, status, pay_type, trade_no, balance, create_user_id, is_audit, audit_user_id, audit_time,remark from company_recharge
+        select recharge_id, company_id, recharge_no, money, create_time, pay_time, status, pay_type, trade_no, balance, create_user_id, is_audit, audit_user_id, audit_time,remark, business_type from company_recharge
     </sql>
 
     <select id="selectCompanyRechargeList" parameterType="CompanyRecharge" resultMap="CompanyRechargeResult">
         <include refid="selectCompanyRechargeVo"/>
-        <where>  
+        <where>
             <if test="companyId != null "> and company_id = #{companyId}</if>
             <if test="rechargeNo != null  and rechargeNo != ''"> and recharge_no = #{rechargeNo}</if>
             <if test="money != null "> and money = #{money}</if>
@@ -43,12 +44,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="auditTime != null "> and audit_time = #{auditTime}</if>
         </where>
     </select>
-    
+
     <select id="selectCompanyRechargeById" parameterType="Long" resultMap="CompanyRechargeResult">
         <include refid="selectCompanyRechargeVo"/>
         where recharge_id = #{rechargeId}
     </select>
-        
+
     <insert id="insertCompanyRecharge" parameterType="CompanyRecharge" useGeneratedKeys="true" keyProperty="rechargeId">
         insert into company_recharge
         <trim prefix="(" suffix=")" suffixOverrides=",">
@@ -66,6 +67,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="auditUserId != null">audit_user_id,</if>
             <if test="auditTime != null">audit_time,</if>
             <if test="remark != null">remark,</if>
+            <if test="businessType != null">business_type,</if>
          </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="companyId != null">#{companyId},</if>
@@ -82,6 +84,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="auditUserId != null">#{auditUserId},</if>
             <if test="auditTime != null">#{auditTime},</if>
             <if test="remark != null">#{remark},</if>
+            <if test="businessType != null">#{businessType},</if>
          </trim>
     </insert>
 
@@ -111,10 +114,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     </delete>
 
     <delete id="deleteCompanyRechargeByIds" parameterType="String">
-        delete from company_recharge where recharge_id in 
+        delete from company_recharge where recharge_id in
         <foreach item="rechargeId" collection="array" open="(" separator="," close=")">
             #{rechargeId}
         </foreach>
     </delete>
-    
-</mapper>
+
+</mapper>