Browse Source

红德堂-实时扣款

Long 1 week ago
parent
commit
9a7f74e6e9
26 changed files with 1114 additions and 392 deletions
  1. 103 0
      fs-admin/src/main/java/com/fs/company/controller/CompanyRedPacketBalanceLogsController.java
  2. 20 3
      fs-admin/src/main/java/com/fs/his/controller/FsCompanyDeductController.java
  3. 25 6
      fs-admin/src/main/java/com/fs/his/controller/FsCompanyRechargeController.java
  4. 52 13
      fs-admin/src/main/java/com/fs/task/FsCompanyTask.java
  5. 7 0
      fs-common/src/main/java/com/fs/common/core/redis/RedisCache.java
  6. 21 2
      fs-company-app/src/main/java/com/fs/app/controller/UserController.java
  7. 52 0
      fs-service/src/main/java/com/fs/company/domain/CompanyRedPacketBalanceDeductionRecord.java
  8. 46 0
      fs-service/src/main/java/com/fs/company/domain/CompanyRedPacketBalanceLogs.java
  9. 8 2
      fs-service/src/main/java/com/fs/company/mapper/CompanyMapper.java
  10. 30 0
      fs-service/src/main/java/com/fs/company/mapper/CompanyRedPacketBalanceDeductionRecordMapper.java
  11. 61 0
      fs-service/src/main/java/com/fs/company/mapper/CompanyRedPacketBalanceLogsMapper.java
  12. 14 0
      fs-service/src/main/java/com/fs/company/service/ICompanyRedPacketBalanceDeductionRecordService.java
  13. 61 0
      fs-service/src/main/java/com/fs/company/service/ICompanyRedPacketBalanceLogsService.java
  14. 12 3
      fs-service/src/main/java/com/fs/company/service/ICompanyService.java
  15. 23 0
      fs-service/src/main/java/com/fs/company/service/impl/CompanyRedPacketBalanceDeductionRecordServiceImpl.java
  16. 93 0
      fs-service/src/main/java/com/fs/company/service/impl/CompanyRedPacketBalanceLogsServiceImpl.java
  17. 50 14
      fs-service/src/main/java/com/fs/company/service/impl/CompanyServiceImpl.java
  18. 248 0
      fs-service/src/main/java/com/fs/company/util/CompanyRedPacketBalanceUtil.java
  19. 2 0
      fs-service/src/main/java/com/fs/company/vo/CompanyDeductVO.java
  20. 0 3
      fs-service/src/main/java/com/fs/course/service/IFsUserCourseVideoService.java
  21. 6 1
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseRedPacketLogServiceImpl.java
  22. 84 343
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java
  23. 1 1
      fs-service/src/main/resources/application-config-dev.yml
  24. 7 0
      fs-service/src/main/resources/mapper/company/CompanyRedPacketBalanceDeductionRecordMapper.xml
  25. 87 0
      fs-service/src/main/resources/mapper/company/CompanyRedPacketBalanceLogsMapper.xml
  26. 1 1
      fs-service/src/main/resources/mapper/course/FsCourseRedPacketLogMapper.xml

+ 103 - 0
fs-admin/src/main/java/com/fs/company/controller/CompanyRedPacketBalanceLogsController.java

@@ -0,0 +1,103 @@
+package com.fs.company.controller;
+
+import java.util.List;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.fs.common.annotation.Log;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.enums.BusinessType;
+import com.fs.company.domain.CompanyRedPacketBalanceLogs;
+import com.fs.company.service.ICompanyRedPacketBalanceLogsService;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.common.core.page.TableDataInfo;
+
+/**
+ * 企业红包余额记录Controller
+ * 
+ * @author fs
+ * @date 2025-11-19
+ */
+@RestController
+@RequestMapping("/company/companyRedPacketBalanceLogs")
+public class CompanyRedPacketBalanceLogsController extends BaseController
+{
+    @Autowired
+    private ICompanyRedPacketBalanceLogsService companyRedPacketBalanceLogsService;
+
+    /**
+     * 查询企业红包余额记录列表
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyRedPacketBalanceLogs:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(CompanyRedPacketBalanceLogs companyRedPacketBalanceLogs)
+    {
+        startPage();
+        List<CompanyRedPacketBalanceLogs> list = companyRedPacketBalanceLogsService.selectCompanyRedPacketBalanceLogsList(companyRedPacketBalanceLogs);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出企业红包余额记录列表
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyRedPacketBalanceLogs:export')")
+    @Log(title = "企业红包余额记录", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(CompanyRedPacketBalanceLogs companyRedPacketBalanceLogs)
+    {
+        List<CompanyRedPacketBalanceLogs> list = companyRedPacketBalanceLogsService.selectCompanyRedPacketBalanceLogsList(companyRedPacketBalanceLogs);
+        ExcelUtil<CompanyRedPacketBalanceLogs> util = new ExcelUtil<CompanyRedPacketBalanceLogs>(CompanyRedPacketBalanceLogs.class);
+        return util.exportExcel(list, "企业红包余额记录数据");
+    }
+
+    /**
+     * 获取企业红包余额记录详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyRedPacketBalanceLogs:query')")
+    @GetMapping(value = "/{logsId}")
+    public AjaxResult getInfo(@PathVariable("logsId") Long logsId)
+    {
+        return AjaxResult.success(companyRedPacketBalanceLogsService.selectCompanyRedPacketBalanceLogsByLogsId(logsId));
+    }
+
+    /**
+     * 新增企业红包余额记录
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyRedPacketBalanceLogs:add')")
+    @Log(title = "企业红包余额记录", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody CompanyRedPacketBalanceLogs companyRedPacketBalanceLogs)
+    {
+        return toAjax(companyRedPacketBalanceLogsService.insertCompanyRedPacketBalanceLogs(companyRedPacketBalanceLogs));
+    }
+
+    /**
+     * 修改企业红包余额记录
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyRedPacketBalanceLogs:edit')")
+    @Log(title = "企业红包余额记录", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody CompanyRedPacketBalanceLogs companyRedPacketBalanceLogs)
+    {
+        return toAjax(companyRedPacketBalanceLogsService.updateCompanyRedPacketBalanceLogs(companyRedPacketBalanceLogs));
+    }
+
+    /**
+     * 删除企业红包余额记录
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyRedPacketBalanceLogs:remove')")
+    @Log(title = "企业红包余额记录", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{logsIds}")
+    public AjaxResult remove(@PathVariable Long[] logsIds)
+    {
+        return toAjax(companyRedPacketBalanceLogsService.deleteCompanyRedPacketBalanceLogsByLogsIds(logsIds));
+    }
+}

+ 20 - 3
fs-admin/src/main/java/com/fs/his/controller/FsCompanyDeductController.java

@@ -12,9 +12,12 @@ import com.fs.common.utils.poi.ExcelUtil;
 import com.fs.company.domain.Company;
 import com.fs.company.domain.CompanyDeduct;
 import com.fs.company.domain.CompanyMoneyLogs;
+import com.fs.company.domain.CompanyRedPacketBalanceLogs;
 import com.fs.company.service.ICompanyDeductService;
 import com.fs.company.service.ICompanyMoneyLogsService;
+import com.fs.company.service.ICompanyRedPacketBalanceLogsService;
 import com.fs.company.service.ICompanyService;
+import com.fs.company.util.CompanyRedPacketBalanceUtil;
 import com.fs.company.vo.CompanyDeductVO;
 import com.fs.framework.web.service.TokenService;
 import lombok.Synchronized;
@@ -53,6 +56,10 @@ public class FsCompanyDeductController extends BaseController
     private ICompanyService companyService;
     @Autowired
     private ICompanyMoneyLogsService moneyLogsService;
+    @Autowired
+    private ICompanyRedPacketBalanceLogsService redPacketBalanceLogsService;
+    @Autowired
+    private CompanyRedPacketBalanceUtil companyRedPacketBalanceUtil;
 
     /**
      * 查询扣款管理列表
@@ -139,9 +146,19 @@ public class FsCompanyDeductController extends BaseController
         deduct.setRemark(param.getRemark());
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
         if(deduct.getIsAudit()==1){
-            if(1==param.getBusinessType()){// 红包充值
-                // 更新红包充值余额redis字段
-                companyService.redPacketTopUpCompany(deduct.getCompanyId(),deduct.getMoney(),"2");
+            if(1==param.getBusinessType()){// 红包扣减
+                BigDecimal newBalance = companyRedPacketBalanceUtil.subDbRedPacketBalance(deduct.getCompanyId(), deduct.getMoney());
+                deduct.setBalance(newBalance);
+
+                CompanyRedPacketBalanceLogs log = new CompanyRedPacketBalanceLogs();
+                log.setCompanyId(deduct.getCompanyId());
+                log.setMoney(deduct.getMoney().negate());
+                log.setBalance(newBalance);
+                log.setRemark(deduct.getRemark());
+                log.setCreateTime(new Date());
+                log.setStatus(1L);
+                log.setLogsType(2);
+                redPacketBalanceLogsService.insertCompanyRedPacketBalanceLogs(log);
             }else {
                 Company company=companyService.selectCompanyByIdForUpdate(deduct.getCompanyId());
                 company.setMoney(company.getMoney().subtract(deduct.getMoney()));

+ 25 - 6
fs-admin/src/main/java/com/fs/his/controller/FsCompanyRechargeController.java

@@ -1,5 +1,6 @@
 package com.fs.his.controller;
 
+import java.math.BigDecimal;
 import java.util.Date;
 import java.util.List;
 
@@ -10,9 +11,12 @@ import com.fs.common.utils.poi.ExcelUtil;
 import com.fs.company.domain.Company;
 import com.fs.company.domain.CompanyMoneyLogs;
 import com.fs.company.domain.CompanyRecharge;
+import com.fs.company.domain.CompanyRedPacketBalanceLogs;
 import com.fs.company.service.ICompanyMoneyLogsService;
 import com.fs.company.service.ICompanyRechargeService;
+import com.fs.company.service.ICompanyRedPacketBalanceLogsService;
 import com.fs.company.service.ICompanyService;
+import com.fs.company.util.CompanyRedPacketBalanceUtil;
 import com.fs.company.vo.CompanyRechargeVO;
 import com.fs.framework.web.service.TokenService;
 import lombok.Synchronized;
@@ -51,6 +55,10 @@ public class FsCompanyRechargeController extends BaseController
     private ICompanyService companyService;
     @Autowired
     private ICompanyMoneyLogsService moneyLogsService;
+    @Autowired
+    private ICompanyRedPacketBalanceLogsService redPacketBalanceLogsService;
+    @Autowired
+    private CompanyRedPacketBalanceUtil companyRedPacketBalanceUtil;
 
     /**
      * 查询充值管理列表
@@ -137,10 +145,23 @@ public class FsCompanyRechargeController extends BaseController
         companyRecharge.setRemark(param.getRemark());
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
         if(companyRecharge.getIsAudit()==1){
-            if(1==companyRecharge.getBusinessType()){// 红包充值
-                // 更新红包充值余额redis字段
-                companyService.redPacketTopUpCompany(companyRecharge.getCompanyId(),companyRecharge.getMoney(),"1");
-            }else {
+            // 红包充值
+            if(1 == companyRecharge.getBusinessType()){
+                BigDecimal newBalance = companyRedPacketBalanceUtil.addDbRedPacketBalance(companyRecharge.getCompanyId(), companyRecharge.getMoney());
+                companyRecharge.setBalance(newBalance);
+
+                CompanyRedPacketBalanceLogs log = new CompanyRedPacketBalanceLogs();
+                log.setCompanyId(companyRecharge.getCompanyId());
+                log.setMoney(companyRecharge.getMoney());
+                log.setBalance(newBalance);
+                log.setRemark(companyRecharge.getRemark());
+                log.setCreateTime(new Date());
+                log.setStatus(1L);
+                log.setLogsType(16);
+                redPacketBalanceLogsService.insertCompanyRedPacketBalanceLogs(log);
+            }
+            // 余额充值
+            else {
                 Company company=companyService.selectCompanyByIdForUpdate(companyRecharge.getCompanyId());
                 company.setMoney(company.getMoney().add(companyRecharge.getMoney()));
                 companyRecharge.setBalance(company.getMoney());
@@ -154,11 +175,9 @@ public class FsCompanyRechargeController extends BaseController
                 log.setBalance(company.getMoney());
                 log.setCreateTime(new Date());
                 moneyLogsService.insertCompanyMoneyLogs(log);
-
             }
             companyRecharge.setPayTime(new Date());
             companyRecharge.setStatus(1);
-
         }
 
         companyRecharge.setAuditTime(new Date());

+ 52 - 13
fs-admin/src/main/java/com/fs/task/FsCompanyTask.java

@@ -1,16 +1,20 @@
 package com.fs.task;
 
+import cn.hutool.json.JSONUtil;
 import com.fs.common.constant.FsConstants;
 import com.fs.common.core.redis.RedisCache;
 import com.fs.company.domain.Company;
 import com.fs.company.domain.CompanyMoneyLogs;
+import com.fs.company.domain.CompanyRedPacketBalanceDeductionRecord;
 import com.fs.company.mapper.CompanyMoneyLogsMapper;
+import com.fs.company.service.ICompanyRedPacketBalanceDeductionRecordService;
 import com.fs.company.service.ICompanyService;
 import com.fs.company.vo.RedPacketMoneyVO;
+import com.fs.course.config.CourseConfig;
 import com.fs.course.mapper.FsCourseRedPacketLogMapper;
+import com.fs.system.service.ISysConfigService;
 import lombok.AllArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.stereotype.Component;
 
@@ -24,37 +28,72 @@ import java.util.stream.Collectors;
 @Slf4j
 public class FsCompanyTask {
 
+    private final ICompanyService companyService;
+    private final ICompanyRedPacketBalanceDeductionRecordService deductionRecordService;
+    private final FsCourseRedPacketLogMapper fsCourseRedPacketLogMapper;
+    private final ISysConfigService configService;
     private final RedisCache redisCache;
-    private FsCourseRedPacketLogMapper fsCourseRedPacketLogMapper;
-    private ICompanyService companyService;
-
-    @Autowired
-    private RedisTemplate<String, Object> redisTemplate;
-
-    @Autowired
-    private CompanyMoneyLogsMapper moneyLogsMapper;
+    private final CompanyMoneyLogsMapper moneyLogsMapper;
+    private final RedisTemplate<String, Object> redisTemplate;
 
     public void refreshCompanyMoney() {
+        // 使用红包余额
+        if (isRedPackBalance()) {
+            return;
+        }
+
         LocalDateTime now = LocalDateTime.now();
         // 获取上一个小时的开始时间
         LocalDateTime startTime = now.minusHours(1)
                 .withMinute(0)
                 .withSecond(0);
         // 获取上一个小时的结束时间
-        LocalDateTime endTime = startTime
-                .withMinute(59)
-                .withSecond(59);
+        LocalDateTime endTime = now
+                .withMinute(0)
+                .withSecond(0);
         List<RedPacketMoneyVO> redPacketMoneyVOS = fsCourseRedPacketLogMapper.selectFsCourseRedPacketLogHourseByCompany(startTime, endTime);
         for (RedPacketMoneyVO redPacketMoneyVO : redPacketMoneyVOS) {
             companyService.subtractCompanyMoneyHourse(redPacketMoneyVO.getMoney(), redPacketMoneyVO.getCompanyId(), startTime.toLocalTime(), endTime.toLocalTime());
         }
     }
 
+    /**
+     * 是否使用红包余额
+     */
+    private boolean isRedPackBalance() {
+        // 获取配置信息
+        String json = configService.selectConfigByKey("course.config");
+        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
+
+        // 是否使用红包余额
+        return "1".equals(config.getIsRedPackageBalanceDeduction());
+    }
+
     /**
      * 自动关闭余额小于等于0的销售公司红包开关
      */
     public void autoCloseCompanyRedPacketSwitch() {
-        companyService.autoCloseCompanyRedPacketSwitch();
+        if (isRedPackBalance()) {
+            companyService.autoCloseCompanyRedPacketSwitchByRedPacketBalance();
+        } else {
+            companyService.autoCloseCompanyRedPacketSwitchByBalance();
+        }
+
+    }
+
+    /**
+     * 扣减已发红包更新
+     */
+    public void syncCompanyRedPacketBalance() {
+        List<CompanyRedPacketBalanceDeductionRecord>  records = deductionRecordService.selectAllCompleteDeductionRecord();
+        Map<Long, List<CompanyRedPacketBalanceDeductionRecord>> recordGroup = records.stream().collect(Collectors.groupingBy(CompanyRedPacketBalanceDeductionRecord::getCompanyId));
+        recordGroup.forEach((k, v) -> {
+           try {
+               companyService.subDbRedPacketBalance(k, v);
+           } catch (Exception e) {
+               log.error("定时扣减公司红包余额失败 err: {}", e.getMessage(), e);
+           }
+        });
     }
 
     /**

+ 7 - 0
fs-common/src/main/java/com/fs/common/core/redis/RedisCache.java

@@ -451,4 +451,11 @@ public class RedisCache
     public Long size(String key) {
         return redisTemplate.opsForHash().size(key);
     }
+
+    /**
+     * key是否已存在
+     */
+    public boolean containsKey(String key) {
+        return redisTemplate.hasKey(key);
+    }
 }

+ 21 - 2
fs-company-app/src/main/java/com/fs/app/controller/UserController.java

@@ -30,11 +30,14 @@ import com.fs.company.param.EditPwdParam;
 import com.fs.company.param.EditUserInfoParam;
 import com.fs.company.param.EditUserQrCodeParam;
 import com.fs.company.service.*;
+import com.fs.company.util.CompanyRedPacketBalanceUtil;
 import com.fs.company.vo.CompanyUserAppVO;
 import com.fs.company.vo.CompanyUserVO;
 import com.fs.core.security.SecurityUtils;
+import com.fs.course.config.CourseConfig;
 import com.fs.course.service.IFsCourseRedPacketLogService;
 import com.fs.his.service.IFsUserService;
+import com.fs.system.service.ISysConfigService;
 import com.fs.wx.miniapp.config.WxMaProperties;
 import com.github.pagehelper.PageHelper;
 import com.github.pagehelper.PageInfo;
@@ -84,6 +87,10 @@ public class UserController extends AppBaseController {
     private IFsUserService fsUserService;
     @Autowired
     private IFsCourseRedPacketLogService courseRedPacketLogService;
+    @Autowired
+    private CompanyRedPacketBalanceUtil companyRedPacketBalanceUtil;
+    @Autowired
+    private ISysConfigService configService;
 
     @Autowired
     private WxMaProperties properties;
@@ -263,9 +270,21 @@ public class UserController extends AppBaseController {
             companyUser.setCompanyName(company.getCompanyName());
             // 岗位
             companyUser.setPosts(postService.selectCompanyPostListByUserId(companyUser.getUserId()));
-            return R.ok().put("user", companyUser).put("balance", company.getMoney());
-        } catch (Exception e) {
 
+            String json = configService.selectConfigByKey("course.config");
+            CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
+
+            BigDecimal balance = company.getMoney();
+            // 使用红包余额
+            if ("1".equals(config.getIsRedPackageBalanceDeduction())) {
+                balance = companyRedPacketBalanceUtil.getCacheRedPacketBalance(companyUser.getCompanyId());
+            }
+
+            return R.ok()
+                    .put("user", companyUser)
+                    .put("balance", balance);
+        } catch (Exception e) {
+            log.error("获取销售公司信息异常 err: {}", e.getMessage(), e);
             return R.error("操作异常");
         }
     }

+ 52 - 0
fs-service/src/main/java/com/fs/company/domain/CompanyRedPacketBalanceDeductionRecord.java

@@ -0,0 +1,52 @@
+package com.fs.company.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+@Data
+@TableName("company_red_packet_balance_deduction_record")
+public class CompanyRedPacketBalanceDeductionRecord {
+
+    /**
+     * 主键ID
+     */
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    /**
+     * 公司ID
+     */
+    private Long companyId;
+
+    /**
+     * 红包记录ID
+     */
+    private Long redPacketId;
+
+    /**
+     * 扣减红包金额
+     */
+    private BigDecimal money;
+
+    /**
+     * 创建时间
+     */
+    private LocalDateTime createTime;
+
+    /**
+     * 修改时间
+     */
+    private LocalDateTime updateTime;
+
+    /**
+     * 状态 0初始化,1已扣减 2已作废
+     */
+    @TableField("`status`")
+    private Integer status;
+}

+ 46 - 0
fs-service/src/main/java/com/fs/company/domain/CompanyRedPacketBalanceLogs.java

@@ -0,0 +1,46 @@
+package com.fs.company.domain;
+
+import java.math.BigDecimal;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.fs.common.annotation.Excel;
+import lombok.Data;
+import com.fs.common.core.domain.BaseEntity;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 企业红包余额记录对象 company_red_packet_balance_logs
+ *
+ * @author fs
+ * @date 2025-11-19
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class CompanyRedPacketBalanceLogs extends BaseEntity{
+
+    /** ID */
+    private Long logsId;
+
+    /** 企业ID */
+    @Excel(name = "企业ID")
+    private Long companyId;
+
+    // 企业名称
+    private String companyName;
+
+    /** 金额 */
+    @Excel(name = "金额")
+    private BigDecimal money;
+
+    /** 余额 */
+    @Excel(name = "余额")
+    private BigDecimal balance;
+
+    /** 类型 字典字段 */
+    @Excel(name = "类型")
+    private Integer logsType;
+
+    /** 是否处理状态(0-初始化,1-已同步) */
+    private Long status;
+
+
+}

+ 8 - 2
fs-service/src/main/java/com/fs/company/mapper/CompanyMapper.java

@@ -211,10 +211,16 @@ public interface CompanyMapper
     List<OptionsVO> getCompanyListByCorpId(@Param("corpId") String corpId);
 
     /**
-     * 自动关闭余额小于等于0的销售公司红包开关
+     * 自动关闭余额小于等于0的销售公司红包开关(根据红包余额判断)
+     */
+    @Update("update company set open_red_packet = 0 where red_package_money <= 0 and open_red_packet = 1")
+    int autoCloseCompanyRedPacketSwitchByRedPacketBalance();
+
+    /**
+     * 自动关闭余额小于等于0的销售公司红包开关(根据余额判断)
      */
     @Update("update company set open_red_packet = 0 where money <= 0 and open_red_packet = 1")
-    int autoCloseCompanyRedPacketSwitch();
+    int autoCloseCompanyRedPacketSwitchByBalance();
 
     List<Company> selectCompanyMoneyAllList();
 

+ 30 - 0
fs-service/src/main/java/com/fs/company/mapper/CompanyRedPacketBalanceDeductionRecordMapper.java

@@ -0,0 +1,30 @@
+package com.fs.company.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.company.domain.CompanyRedPacketBalanceDeductionRecord;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+public interface CompanyRedPacketBalanceDeductionRecordMapper extends BaseMapper<CompanyRedPacketBalanceDeductionRecord> {
+
+    /**
+     * 查询公司待扣减金额
+     */
+    @Select("select ifnull(sum(money), 0) from company_red_packet_balance_deduction_record where company_id = #{companyId} and status in (0,1)")
+    BigDecimal getPendDeductionAmount(@Param("companyId") Long companyId);
+
+    /**
+     * 根据红包ID查询记录
+     */
+    @Select("select * from company_red_packet_balance_deduction_record where red_packet_id = #{redPacketId} and status = 0")
+    CompanyRedPacketBalanceDeductionRecord selectByRedPacketId(@Param("redPacketId") Long redPacketId);
+
+    /**
+     * 查询所有已完成扣款的数据
+     */
+    @Select("select * from company_red_packet_balance_deduction_record where status = 1 limit 5000")
+    List<CompanyRedPacketBalanceDeductionRecord> selectByStatus();
+}

+ 61 - 0
fs-service/src/main/java/com/fs/company/mapper/CompanyRedPacketBalanceLogsMapper.java

@@ -0,0 +1,61 @@
+package com.fs.company.mapper;
+
+import java.util.List;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.company.domain.CompanyRedPacketBalanceLogs;
+
+/**
+ * 企业红包余额记录Mapper接口
+ * 
+ * @author fs
+ * @date 2025-11-19
+ */
+public interface CompanyRedPacketBalanceLogsMapper extends BaseMapper<CompanyRedPacketBalanceLogs>{
+    /**
+     * 查询企业红包余额记录
+     * 
+     * @param logsId 企业红包余额记录主键
+     * @return 企业红包余额记录
+     */
+    CompanyRedPacketBalanceLogs selectCompanyRedPacketBalanceLogsByLogsId(Long logsId);
+
+    /**
+     * 查询企业红包余额记录列表
+     * 
+     * @param companyRedPacketBalanceLogs 企业红包余额记录
+     * @return 企业红包余额记录集合
+     */
+    List<CompanyRedPacketBalanceLogs> selectCompanyRedPacketBalanceLogsList(CompanyRedPacketBalanceLogs companyRedPacketBalanceLogs);
+
+    /**
+     * 新增企业红包余额记录
+     * 
+     * @param companyRedPacketBalanceLogs 企业红包余额记录
+     * @return 结果
+     */
+    int insertCompanyRedPacketBalanceLogs(CompanyRedPacketBalanceLogs companyRedPacketBalanceLogs);
+
+    /**
+     * 修改企业红包余额记录
+     * 
+     * @param companyRedPacketBalanceLogs 企业红包余额记录
+     * @return 结果
+     */
+    int updateCompanyRedPacketBalanceLogs(CompanyRedPacketBalanceLogs companyRedPacketBalanceLogs);
+
+    /**
+     * 删除企业红包余额记录
+     * 
+     * @param logsId 企业红包余额记录主键
+     * @return 结果
+     */
+    int deleteCompanyRedPacketBalanceLogsByLogsId(Long logsId);
+
+    /**
+     * 批量删除企业红包余额记录
+     * 
+     * @param logsIds 需要删除的数据主键集合
+     * @return 结果
+     */
+    int deleteCompanyRedPacketBalanceLogsByLogsIds(Long[] logsIds);
+}

+ 14 - 0
fs-service/src/main/java/com/fs/company/service/ICompanyRedPacketBalanceDeductionRecordService.java

@@ -0,0 +1,14 @@
+package com.fs.company.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.fs.company.domain.CompanyRedPacketBalanceDeductionRecord;
+
+import java.util.List;
+
+public interface ICompanyRedPacketBalanceDeductionRecordService extends IService<CompanyRedPacketBalanceDeductionRecord> {
+
+    /**
+     * 查询所有已完成扣款的数据
+     */
+    List<CompanyRedPacketBalanceDeductionRecord> selectAllCompleteDeductionRecord();
+}

+ 61 - 0
fs-service/src/main/java/com/fs/company/service/ICompanyRedPacketBalanceLogsService.java

@@ -0,0 +1,61 @@
+package com.fs.company.service;
+
+import java.util.List;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.fs.company.domain.CompanyRedPacketBalanceLogs;
+
+/**
+ * 企业红包余额记录Service接口
+ * 
+ * @author fs
+ * @date 2025-11-19
+ */
+public interface ICompanyRedPacketBalanceLogsService extends IService<CompanyRedPacketBalanceLogs>{
+    /**
+     * 查询企业红包余额记录
+     * 
+     * @param logsId 企业红包余额记录主键
+     * @return 企业红包余额记录
+     */
+    CompanyRedPacketBalanceLogs selectCompanyRedPacketBalanceLogsByLogsId(Long logsId);
+
+    /**
+     * 查询企业红包余额记录列表
+     * 
+     * @param companyRedPacketBalanceLogs 企业红包余额记录
+     * @return 企业红包余额记录集合
+     */
+    List<CompanyRedPacketBalanceLogs> selectCompanyRedPacketBalanceLogsList(CompanyRedPacketBalanceLogs companyRedPacketBalanceLogs);
+
+    /**
+     * 新增企业红包余额记录
+     * 
+     * @param companyRedPacketBalanceLogs 企业红包余额记录
+     * @return 结果
+     */
+    int insertCompanyRedPacketBalanceLogs(CompanyRedPacketBalanceLogs companyRedPacketBalanceLogs);
+
+    /**
+     * 修改企业红包余额记录
+     * 
+     * @param companyRedPacketBalanceLogs 企业红包余额记录
+     * @return 结果
+     */
+    int updateCompanyRedPacketBalanceLogs(CompanyRedPacketBalanceLogs companyRedPacketBalanceLogs);
+
+    /**
+     * 批量删除企业红包余额记录
+     * 
+     * @param logsIds 需要删除的企业红包余额记录主键集合
+     * @return 结果
+     */
+    int deleteCompanyRedPacketBalanceLogsByLogsIds(Long[] logsIds);
+
+    /**
+     * 删除企业红包余额记录信息
+     * 
+     * @param logsId 企业红包余额记录主键
+     * @return 结果
+     */
+    int deleteCompanyRedPacketBalanceLogsByLogsId(Long logsId);
+}

+ 12 - 3
fs-service/src/main/java/com/fs/company/service/ICompanyService.java

@@ -6,6 +6,7 @@ import java.util.List;
 
 import com.fs.common.core.domain.R;
 import com.fs.company.domain.Company;
+import com.fs.company.domain.CompanyRedPacketBalanceDeductionRecord;
 import com.fs.company.domain.CompanyUser;
 import com.fs.company.param.CompanyLiveShowParam;
 import com.fs.company.param.CompanyParam;
@@ -22,8 +23,6 @@ import com.fs.hisStore.domain.FsStorePaymentScrm;
 import com.fs.live.domain.LiveOrder;
 import org.springframework.transaction.annotation.Transactional;
 
-import javax.validation.constraints.NotNull;
-
 
 /**
  * 企业Service接口
@@ -180,10 +179,15 @@ public interface ICompanyService
      */
     void changeRedPacketState(Long companyId, Integer state);
 
+    /**
+     * 自动关闭红包余额小于等于0的销售公司红包开关
+     */
+    void autoCloseCompanyRedPacketSwitchByRedPacketBalance();
+
     /**
      * 自动关闭余额小于等于0的销售公司红包开关
      */
-    void autoCloseCompanyRedPacketSwitch();
+    void autoCloseCompanyRedPacketSwitchByBalance();
 
     void syncCompanyBalance();
 
@@ -205,4 +209,9 @@ public interface ICompanyService
     List<CompanyVO> liveShowList(CompanyParam param);
 
     void batchUpdateLiveShow(CompanyLiveShowParam param);
+
+    /**
+     * 根据扣减记录扣减红包余额
+     */
+    void subDbRedPacketBalance(Long companyId, List<CompanyRedPacketBalanceDeductionRecord> records);
 }

+ 23 - 0
fs-service/src/main/java/com/fs/company/service/impl/CompanyRedPacketBalanceDeductionRecordServiceImpl.java

@@ -0,0 +1,23 @@
+package com.fs.company.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.fs.company.domain.CompanyRedPacketBalanceDeductionRecord;
+import com.fs.company.mapper.CompanyRedPacketBalanceDeductionRecordMapper;
+import com.fs.company.service.ICompanyRedPacketBalanceDeductionRecordService;
+import org.springframework.stereotype.Service;
+
+import java.util.Collections;
+import java.util.List;
+
+@Service
+public class CompanyRedPacketBalanceDeductionRecordServiceImpl extends ServiceImpl<CompanyRedPacketBalanceDeductionRecordMapper, CompanyRedPacketBalanceDeductionRecord>
+        implements ICompanyRedPacketBalanceDeductionRecordService {
+
+    /**
+     * 查询所有已完成扣款的数据
+     */
+    @Override
+    public List<CompanyRedPacketBalanceDeductionRecord> selectAllCompleteDeductionRecord() {
+        return baseMapper.selectByStatus();
+    }
+}

+ 93 - 0
fs-service/src/main/java/com/fs/company/service/impl/CompanyRedPacketBalanceLogsServiceImpl.java

@@ -0,0 +1,93 @@
+package com.fs.company.service.impl;
+
+import java.util.List;
+import com.fs.common.utils.DateUtils;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.fs.company.mapper.CompanyRedPacketBalanceLogsMapper;
+import com.fs.company.domain.CompanyRedPacketBalanceLogs;
+import com.fs.company.service.ICompanyRedPacketBalanceLogsService;
+
+/**
+ * 企业红包余额记录Service业务层处理
+ * 
+ * @author fs
+ * @date 2025-11-19
+ */
+@Service
+public class CompanyRedPacketBalanceLogsServiceImpl extends ServiceImpl<CompanyRedPacketBalanceLogsMapper, CompanyRedPacketBalanceLogs> implements ICompanyRedPacketBalanceLogsService {
+
+    /**
+     * 查询企业红包余额记录
+     * 
+     * @param logsId 企业红包余额记录主键
+     * @return 企业红包余额记录
+     */
+    @Override
+    public CompanyRedPacketBalanceLogs selectCompanyRedPacketBalanceLogsByLogsId(Long logsId)
+    {
+        return baseMapper.selectCompanyRedPacketBalanceLogsByLogsId(logsId);
+    }
+
+    /**
+     * 查询企业红包余额记录列表
+     * 
+     * @param companyRedPacketBalanceLogs 企业红包余额记录
+     * @return 企业红包余额记录
+     */
+    @Override
+    public List<CompanyRedPacketBalanceLogs> selectCompanyRedPacketBalanceLogsList(CompanyRedPacketBalanceLogs companyRedPacketBalanceLogs)
+    {
+        return baseMapper.selectCompanyRedPacketBalanceLogsList(companyRedPacketBalanceLogs);
+    }
+
+    /**
+     * 新增企业红包余额记录
+     * 
+     * @param companyRedPacketBalanceLogs 企业红包余额记录
+     * @return 结果
+     */
+    @Override
+    public int insertCompanyRedPacketBalanceLogs(CompanyRedPacketBalanceLogs companyRedPacketBalanceLogs)
+    {
+        companyRedPacketBalanceLogs.setCreateTime(DateUtils.getNowDate());
+        return baseMapper.insertCompanyRedPacketBalanceLogs(companyRedPacketBalanceLogs);
+    }
+
+    /**
+     * 修改企业红包余额记录
+     * 
+     * @param companyRedPacketBalanceLogs 企业红包余额记录
+     * @return 结果
+     */
+    @Override
+    public int updateCompanyRedPacketBalanceLogs(CompanyRedPacketBalanceLogs companyRedPacketBalanceLogs)
+    {
+        return baseMapper.updateCompanyRedPacketBalanceLogs(companyRedPacketBalanceLogs);
+    }
+
+    /**
+     * 批量删除企业红包余额记录
+     * 
+     * @param logsIds 需要删除的企业红包余额记录主键
+     * @return 结果
+     */
+    @Override
+    public int deleteCompanyRedPacketBalanceLogsByLogsIds(Long[] logsIds)
+    {
+        return baseMapper.deleteCompanyRedPacketBalanceLogsByLogsIds(logsIds);
+    }
+
+    /**
+     * 删除企业红包余额记录信息
+     * 
+     * @param logsId 企业红包余额记录主键
+     * @return 结果
+     */
+    @Override
+    public int deleteCompanyRedPacketBalanceLogsByLogsId(Long logsId)
+    {
+        return baseMapper.deleteCompanyRedPacketBalanceLogsByLogsId(logsId);
+    }
+}

+ 50 - 14
fs-service/src/main/java/com/fs/company/service/impl/CompanyServiceImpl.java

@@ -1,12 +1,12 @@
 package com.fs.company.service.impl;
 
 import java.math.BigDecimal;
+import java.time.LocalDateTime;
 import java.time.LocalTime;
 import java.util.*;
 import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 
-import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.json.JSONUtil;
 import com.alibaba.fastjson.JSON;
 import com.fs.common.constant.FsConstants;
@@ -19,9 +19,8 @@ import com.fs.company.domain.*;
 import com.fs.company.mapper.*;
 import com.fs.company.param.CompanyLiveShowParam;
 import com.fs.company.param.CompanyParam;
-import com.fs.company.service.ICompanyMiniappService;
-import com.fs.company.service.ICompanyProfitService;
-import com.fs.company.service.ICompanyRoleService;
+import com.fs.company.service.*;
+import com.fs.company.util.CompanyRedPacketBalanceUtil;
 import com.fs.company.vo.*;
 import com.fs.course.mapper.FsCourseRedPacketLogMapper;
 import com.fs.his.config.StoreConfig;
@@ -51,7 +50,6 @@ import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
-import com.fs.company.service.ICompanyService;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.transaction.support.TransactionTemplate;
 
@@ -120,6 +118,12 @@ public class CompanyServiceImpl implements ICompanyService
 
     @Autowired
     private ILiveService liveService;
+    @Autowired
+    private ICompanyRedPacketBalanceLogsService companyRedPacketBalanceLogsService;
+    @Autowired
+    private ICompanyRedPacketBalanceDeductionRecordService deductionRecordService;
+    @Autowired
+    private CompanyRedPacketBalanceUtil companyRedPacketBalanceUtil;
 
 
     @Override
@@ -133,6 +137,34 @@ public class CompanyServiceImpl implements ICompanyService
         liveService.asyncToCache();
     }
 
+    /**
+     * 根据扣减记录扣减红包余额
+     */
+    @Transactional(rollbackFor = Exception.class)
+    @Override
+    public void subDbRedPacketBalance(Long companyId, List<CompanyRedPacketBalanceDeductionRecord> records) {
+        BigDecimal amount = records.stream().map(CompanyRedPacketBalanceDeductionRecord::getMoney).reduce(BigDecimal.ZERO, BigDecimal::add);
+        Company company = companyMapper.selectCompanyByIdForUpdate(companyId);
+        company.setRedPackageMoney(company.getRedPackageMoney().subtract(amount));
+        companyMapper.updateCompany(company);
+
+        CompanyRedPacketBalanceLogs logs = new CompanyRedPacketBalanceLogs();
+        logs.setCompanyId(companyId);
+        logs.setMoney(amount.negate());
+        logs.setRemark("扣减已发红包金额:" + amount);
+        logs.setCreateTime(DateUtils.getNowDate());
+        logs.setBalance(company.getRedPackageMoney());
+        logs.setStatus(1L);
+        logs.setLogsType(15);
+        companyRedPacketBalanceLogsService.insertCompanyRedPacketBalanceLogs(logs);
+
+        records.forEach(record -> {
+            record.setStatus(2);
+            record.setUpdateTime(LocalDateTime.now());
+        });
+        deductionRecordService.updateBatchById(records);
+    }
+
     @Override
     public List<OptionsVO> selectAllCompanyList(Long deptId) {
         return companyMapper.selectAllCompanyList(deptId);
@@ -724,12 +756,7 @@ public class CompanyServiceImpl implements ICompanyService
 
         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));
-
-            }
-
+            companyVO.setRedPackageMoney(companyRedPacketBalanceUtil.getCacheRedPacketBalance(companyVO.getCompanyId()));
             // 查询使用的小程序
             companyVO.setWatchMiniAppName(companyMiniappService.getFirstWatchMiniAppNameByCompanyId(companyVO.getCompanyId()));
         });
@@ -1369,13 +1396,22 @@ public class CompanyServiceImpl implements ICompanyService
         }
     }
 
+    /**
+     * 自动关闭红包余额小于等于0的销售公司红包开关
+     */
+    @Override
+    public void autoCloseCompanyRedPacketSwitchByRedPacketBalance() {
+        int affected = companyMapper.autoCloseCompanyRedPacketSwitchByRedPacketBalance();
+        log.debug("自动关闭销售公司红包开关-红包余额,共影响公司数: {}", affected);
+    }
+
     /**
      * 自动关闭余额小于等于0的销售公司红包开关
      */
     @Override
-    public void autoCloseCompanyRedPacketSwitch() {
-        int affected = companyMapper.autoCloseCompanyRedPacketSwitch();
-        log.debug("自动关闭销售公司红包开关,共影响公司数: {}", affected);
+    public void autoCloseCompanyRedPacketSwitchByBalance() {
+        int affected = companyMapper.autoCloseCompanyRedPacketSwitchByBalance();
+        log.debug("自动关闭销售公司红包开关-余额,共影响公司数: {}", affected);
     }
 
     /**

+ 248 - 0
fs-service/src/main/java/com/fs/company/util/CompanyRedPacketBalanceUtil.java

@@ -0,0 +1,248 @@
+package com.fs.company.util;
+
+import com.fs.common.core.redis.RedisCache;
+import com.fs.common.exception.ServiceException;
+import com.fs.company.domain.Company;
+import com.fs.company.domain.CompanyRedPacketBalanceDeductionRecord;
+import com.fs.company.mapper.CompanyMapper;
+import com.fs.company.mapper.CompanyRedPacketBalanceDeductionRecordMapper;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.redisson.api.RLock;
+import org.redisson.api.RedissonClient;
+import org.springframework.stereotype.Component;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.concurrent.TimeUnit;
+
+@Slf4j
+@Component
+@AllArgsConstructor
+public class CompanyRedPacketBalanceUtil {
+
+    private final static String COMPANY_RED_PACKET_BALANCE_LOCK = "CompanyRedPacketBalance:lock:";
+    private final static String COMPANY_RED_PACKET_BALANCE_CACHE = "CompanyRedPacketBalance:cache:";
+
+    private final RedissonClient redissonClient;
+    private final RedisCache redisCache;
+    private final CompanyMapper companyMapper;
+    private final CompanyRedPacketBalanceDeductionRecordMapper deductionRecordMapper;
+
+    /**
+     * 减少公司红包余额
+     */
+    public CompanyRedPacketBalanceDeductionRecord subCacheRedPacketBalance(Long companyId, BigDecimal money) {
+        RLock lock = getLock(companyId);
+
+        if (money.compareTo(BigDecimal.ZERO) <= 0) {
+            throw new ServiceException("奖励金额不能小于0");
+        }
+
+        try {
+            if (!lock.tryLock(5, 60, TimeUnit.SECONDS)) {
+                throw new ServiceException("系统繁忙,请稍后再试!");
+            }
+
+            BigDecimal redPacketBalance = getCacheRedPacketBalance(companyId);
+
+            if (redPacketBalance.compareTo(money) < 0) {
+                throw new ServiceException("服务商余额不足,请联系群主服务器充值!");
+            }
+
+            setCacheRedPacketBalance(companyId, redPacketBalance.subtract(money));
+
+            // 因为有时差需要中间表记录
+            CompanyRedPacketBalanceDeductionRecord record = new CompanyRedPacketBalanceDeductionRecord();
+            record.setCompanyId(companyId);
+            record.setMoney(money);
+            record.setStatus(0);
+            record.setCreateTime(LocalDateTime.now());
+            deductionRecordMapper.insert(record);
+
+            return record;
+        } catch (InterruptedException e) {
+            log.error("公司红包余额扣减异常 err: {}", e.getMessage(), e);
+            throw new ServiceException("系统繁忙,请稍后再试!");
+        } finally {
+            if (lock.isHeldByCurrentThread()) {
+                lock.unlock();
+            }
+        }
+    }
+
+    /**
+     * 公司红包余额充值
+     */
+    public BigDecimal addDbRedPacketBalance(Long companyId, BigDecimal money) {
+        RLock lock = getLock(companyId);
+        if (money.compareTo(BigDecimal.ZERO) <= 0) {
+            throw new ServiceException("充值金额不能为0");
+        }
+
+        try {
+            if (!lock.tryLock(5, 60, TimeUnit.SECONDS)) {
+                throw new ServiceException("系统繁忙,请稍后再试!");
+            }
+
+            Company company = companyMapper.selectCompanyByIdForUpdate(companyId);
+            BigDecimal redPacketBalance = company.getRedPackageMoney().add(money);
+            company.setRedPackageMoney(redPacketBalance);
+            companyMapper.updateCompany(company);
+
+            BigDecimal pendDeductionAmount = deductionRecordMapper.getPendDeductionAmount(companyId);
+            BigDecimal newRedPacketBalance = redPacketBalance.subtract(pendDeductionAmount);
+            setCacheRedPacketBalance(companyId, newRedPacketBalance);
+            return redPacketBalance;
+        } catch (InterruptedException e) {
+            log.error("公司红包余额充值异常 err: {}", e.getMessage(), e);
+            throw new ServiceException("系统繁忙,请稍后再试!");
+        } finally {
+            if (lock.isHeldByCurrentThread()) {
+                lock.unlock();
+            }
+        }
+    }
+
+    /**
+     * 公司红包余额扣减
+     */
+    public BigDecimal subDbRedPacketBalance(Long companyId, BigDecimal money) {
+        RLock lock = getLock(companyId);
+        if (money.compareTo(BigDecimal.ZERO) <= 0) {
+            throw new ServiceException("充值金额不能为0");
+        }
+
+        try {
+            if (!lock.tryLock(5, 60, TimeUnit.SECONDS)) {
+                throw new ServiceException("系统繁忙,请稍后再试!");
+            }
+
+            Company company = companyMapper.selectCompanyByIdForUpdate(companyId);
+            BigDecimal redPacketBalance = company.getRedPackageMoney().subtract(money);
+            company.setRedPackageMoney(redPacketBalance);
+            companyMapper.updateCompany(company);
+
+            BigDecimal pendDeductionAmount = deductionRecordMapper.getPendDeductionAmount(companyId);
+            BigDecimal newRedPacketBalance = redPacketBalance.subtract(pendDeductionAmount);
+            setCacheRedPacketBalance(companyId, newRedPacketBalance);
+            return redPacketBalance;
+        } catch (InterruptedException e) {
+            log.error("公司红包余额充值异常 err: {}", e.getMessage(), e);
+            throw new ServiceException("系统繁忙,请稍后再试!");
+        } finally {
+            if (lock.isHeldByCurrentThread()) {
+                lock.unlock();
+            }
+        }
+    }
+
+    /**
+     * 同步最新公司红包余额
+     */
+    public BigDecimal syncDbRedPacketBalance(Long companyId) {
+        RLock lock = getLock(companyId);
+        try {
+            if (!lock.tryLock(5, 60, TimeUnit.SECONDS)) {
+                throw new ServiceException("系统繁忙,请稍后再试!");
+            }
+
+            Company company = companyMapper.selectCompanyByIdForUpdate(companyId);
+            BigDecimal redPacketBalance = company.getRedPackageMoney();
+            BigDecimal pendDeductionAmount = deductionRecordMapper.getPendDeductionAmount(companyId);
+            BigDecimal newRedPacketBalance = redPacketBalance.subtract(pendDeductionAmount);
+            setCacheRedPacketBalance(companyId, newRedPacketBalance);
+            return newRedPacketBalance;
+        } catch (InterruptedException e) {
+            log.error("同步公司红包余额异常 err: {}", e.getMessage(), e);
+            throw new ServiceException("系统繁忙,请稍后再试!");
+        } finally {
+            if (lock.isHeldByCurrentThread()) {
+                lock.unlock();
+            }
+        }
+    }
+
+    /**
+     * 获取公司锁
+     */
+    private RLock getLock(Long companyId) {
+        return redissonClient.getLock(COMPANY_RED_PACKET_BALANCE_LOCK + companyId);
+    }
+
+    /**
+     * 设置公司余额
+     */
+    private void setCacheRedPacketBalance(Long companyId, BigDecimal redPacketBalance) {
+        String key = COMPANY_RED_PACKET_BALANCE_CACHE + companyId;
+        redisCache.setCacheObject(key, redPacketBalance);
+    }
+
+    /**
+     * 获取公司红包余额
+     */
+    public BigDecimal getCacheRedPacketBalance(Long companyId) {
+        String key = COMPANY_RED_PACKET_BALANCE_CACHE + companyId;
+        if (!redisCache.containsKey(key)) {
+            return syncDbRedPacketBalance(companyId);
+        }
+        return redisCache.getCacheObject(key);
+    }
+
+    /**
+     * 作废记录
+     */
+    public void invalidRecord(CompanyRedPacketBalanceDeductionRecord record) {
+        if (record == null || record.getStatus() != 0) {
+            return;
+        }
+        record.setStatus(2);
+        record.setUpdateTime(LocalDateTime.now());
+        deductionRecordMapper.updateById(record);
+
+        Long companyId = record.getCompanyId();
+        RLock lock = getLock(companyId);
+        try {
+            if (!lock.tryLock(5, 10, TimeUnit.SECONDS)) {
+                return;
+            }
+            String key = COMPANY_RED_PACKET_BALANCE_CACHE + companyId;
+            if (!redisCache.containsKey(key)) {
+                return;
+            }
+            BigDecimal cacheRedPacketBalance = redisCache.getCacheObject(key);
+            redisCache.setCacheObject(key, cacheRedPacketBalance.add(record.getMoney()));
+        } catch (Exception ignore) {
+            // nothing to do
+        } finally {
+            if (lock.isHeldByCurrentThread()) {
+                lock.unlock();
+            }
+        }
+    }
+
+    /**
+     * 更新记录(用于更新红包记录ID)
+     */
+    public void updateRecordRedPacketId(CompanyRedPacketBalanceDeductionRecord record, Long redPacketId) {
+        if (record == null) {
+            return;
+        }
+        record.setRedPacketId(redPacketId);
+        deductionRecordMapper.updateById(record);
+    }
+
+    /**
+     * 更新记录(扣款完成)
+     */
+    public void completeRecord(Long redPacketId) {
+        CompanyRedPacketBalanceDeductionRecord record = deductionRecordMapper.selectByRedPacketId(redPacketId);
+        if (record == null) {
+            return;
+        }
+        record.setStatus(1);
+        record.setUpdateTime(LocalDateTime.now());
+        deductionRecordMapper.updateById(record);
+    }
+
+}

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

@@ -35,6 +35,8 @@ public class CompanyDeductVO implements Serializable {
 
     @Excel(name = "金额")
     private BigDecimal money;
+    @Excel(name = "余额")
+    private BigDecimal balance;
 
 
     @Excel(name = "创建时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")

+ 0 - 3
fs-service/src/main/java/com/fs/course/service/IFsUserCourseVideoService.java

@@ -139,9 +139,6 @@ public interface IFsUserCourseVideoService
      */
     ResponseResult<FsUserCourseVideoLinkDetailsVO> getLinkCourseVideoDetails(FsUserCourseVideoLinkParam param);
 
-
-    R addWatchLogByLink(FsUserCourseAddCompanyUserParam param);
-
     /**
      * 更新看课时长
      * @param param 入参

+ 6 - 1
fs-service/src/main/java/com/fs/course/service/impl/FsCourseRedPacketLogServiceImpl.java

@@ -17,6 +17,7 @@ import com.fs.company.domain.Company;
 import com.fs.company.domain.CompanyMoneyLogs;
 import com.fs.company.mapper.CompanyMapper;
 import com.fs.company.mapper.CompanyMoneyLogsMapper;
+import com.fs.company.util.CompanyRedPacketBalanceUtil;
 import com.fs.course.config.CourseConfig;
 import com.fs.course.config.RedPacketConfig;
 import com.fs.course.domain.FsCourseWatchLog;
@@ -59,7 +60,8 @@ public class FsCourseRedPacketLogServiceImpl implements IFsCourseRedPacketLogSer
     private FsCourseRedPacketLogMapper fsCourseRedPacketLogMapper;
     @Autowired
     private CompanyMapper companyMapper;
-
+    @Autowired
+    private CompanyRedPacketBalanceUtil companyRedPacketBalanceUtil;
     @Autowired
     private ISysConfigService configService;
     /**
@@ -155,6 +157,9 @@ public class FsCourseRedPacketLogServiceImpl implements IFsCourseRedPacketLogSer
             log.setUpdateTime(new Date());
             log.setBatchId(batchId);
             fsCourseRedPacketLogMapper.updateFsCourseRedPacketLog(log);
+
+            companyRedPacketBalanceUtil.completeRecord(log.getLogId());
+
             return R.ok();
         }
         return R.error("批次不存在");

+ 84 - 343
fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java

@@ -25,12 +25,11 @@ import com.fs.common.utils.date.DateUtil;
 import com.fs.company.constant.CompanyTrafficConstants;
 import com.fs.company.domain.Company;
 import com.fs.company.domain.CompanyCompanyFsuser;
+import com.fs.company.domain.CompanyRedPacketBalanceDeductionRecord;
 import com.fs.company.domain.CompanyUser;
-import com.fs.company.mapper.CompanyCompanyFsuserMapper;
-import com.fs.company.mapper.CompanyMapper;
-import com.fs.company.mapper.CompanyMoneyLogsMapper;
-import com.fs.company.mapper.CompanyUserMapper;
+import com.fs.company.mapper.*;
 import com.fs.company.service.ICompanyService;
+import com.fs.company.util.CompanyRedPacketBalanceUtil;
 import com.fs.config.cloud.CloudHostProper;
 import com.fs.course.config.CourseConfig;
 import com.fs.course.domain.*;
@@ -183,8 +182,6 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
     private FsCourseSopLogsMapper courseSopLogsMapper;
     @Autowired
     private QwApiService qwApiService;
-    //    @Autowired
-//    private IAdHtmlClickLogService adHtmlClickLogService;
     @Autowired
     private QwUserMapper qwUserMapper;
     @Autowired
@@ -259,16 +256,10 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
 
     @Autowired
     private IFsUserCompanyBindService fsUserCompanyBindService;
-
-    @Autowired
-    private BalanceRollbackErrorMapper balanceRollbackErrorMapper;
-
     @Autowired
     private IFsUserIntegralLogsService iFsUserIntegralLogsService;
     @Autowired
-    private QwTagGroupMapper qwTagGroupMapper;
-    @Autowired
-    private QwTagMapper qwTagMapper;
+    private CompanyRedPacketBalanceUtil companyRedPacketBalanceUtil;
 
 
 
@@ -1478,16 +1469,34 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
             // 更新观看记录的奖励类型
             log.setRewardType(config.getRewardType());
             courseWatchLogMapper.updateFsCourseWatchLog(log);
-            return R.ok("红包发送成功");
+            return R.ok("答题成功,请联系群主");
         }
 
     }
 
     private R sendRedPacketRewardToUser(FsCourseSendRewardUParam param, FsCourseWatchLog log, CourseConfig config, WxSendRedPacketParam packetParam, BigDecimal amount) {
-
+        // 打开红包扣减功能
+        CompanyRedPacketBalanceDeductionRecord record = null;
+        if("1".equals(config.getIsRedPackageBalanceDeduction())){
+            try {
+                record = companyRedPacketBalanceUtil.subCacheRedPacketBalance(packetParam.getCompanyId(), amount);
+            } catch (ServiceException e) {
+                return R.error(e.getMessage());
+            } catch (Exception e) {
+                logger.error("公司红包余额扣减失败 err: {}", e.getMessage(), e);
+                return R.error("奖励发送失败,请联系客服");
+            }
+        }
 
         // 发送红包
-        R sendRedPacket = paymentService.sendRedPacket(packetParam);
+        R sendRedPacket;
+        try {
+            sendRedPacket = paymentService.sendRedPacket(packetParam);
+        } catch (Exception e) {
+            companyRedPacketBalanceUtil.invalidRecord(record);
+            return R.error("奖励发送失败,请联系客服!");
+        }
+
         if (sendRedPacket.get("code").equals(200)) {
             FsCourseRedPacketLog redPacketLog = new FsCourseRedPacketLog();
             TransferBillsResult transferBillsResult;
@@ -1516,31 +1525,15 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
             log.setRewardType(config.getRewardType());
             courseWatchLogMapper.updateFsCourseWatchLog(log);
 
-//            redisCache.setCacheObject("h5user:redPacket:"+param.getUserId(),LocalDateTime.now().toString());
+            companyRedPacketBalanceUtil.updateRecordRedPacketId(record, redPacketLog.getLogId());
 
             return sendRedPacket;
         } else {
+            companyRedPacketBalanceUtil.invalidRecord(record);
             return R.error("奖励发送失败,请联系客服");
         }
     }
 
-
-
-
-    private void handleFsUserWx(FsUser user, String appId) {
-        FsUserWx fsUserWx = new FsUserWx();
-        fsUserWx.setType(1);
-        fsUserWx.setFsUserId(user.getUserId());
-        fsUserWx.setAppId(appId);
-        fsUserWx.setOpenId(user.getCourseMaOpenId());
-        fsUserWx.setUnionId(user.getUnionId());
-        fsUserWx.setCreateTime(new Date());
-        fsUserWx.setUpdateTime(new Date());
-        fsUserWxService.saveOrUpdateByUniqueKey(fsUserWx);
-
-        logger.info("【更新或插入用户与小程序{}的绑定关系】:{}", appId, user.getUserId());
-    }
-
     /**
      * 发放红包奖励
      *
@@ -1619,181 +1612,73 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
         if (openRedPacket && amount.compareTo(BigDecimal.ZERO)>0){
 
             // 打开红包扣减功能
-            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("发送红包失败,请联系管理员");
-                }
-                String companyMoneyKey = FsConstants.COMPANY_MONEY_KEY + packetParam.getCompanyId();
-
-                // 第一次加锁:预扣减余额
-                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("系统繁忙,请稍后重试");
-                    }
-                } 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;
+            CompanyRedPacketBalanceDeductionRecord record = null;
+            if ("1".equals(config.getIsRedPackageBalanceDeduction())) {
                 try {
-                    sendRedPacket= paymentService.sendRedPacket(packetParam);
+                    record = companyRedPacketBalanceUtil.subCacheRedPacketBalance(packetParam.getCompanyId(), amount);
+                } catch (ServiceException e) {
+                    return R.error(e.getMessage());
                 } catch (Exception e) {
-                    logger.error("红包发送异常: 异常请求参数{}",packetParam, e);
-                    // 异常时回滚余额
-
-                    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());
-                    }
-                    // 添加红包记录
-                    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);
+                    logger.error("公司红包余额扣减失败 err: {}", e.getMessage(), e);
                     return R.error("奖励发送失败,请联系客服");
                 }
-
-                // ===================== 本次修改目的为了实时扣减公司余额=====================
-            }else {
+            } else {
                 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());
-                    }
-                    // 添加红包记录
-                    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("奖励发送失败,请联系客服");
+            // 发送红包
+            R sendRedPacket;
+            try {
+                sendRedPacket = paymentService.sendRedPacket(packetParam);
+            } catch (Exception e) {
+                companyRedPacketBalanceUtil.invalidRecord(record);
+                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());
                 }
+                // 添加红包记录
+                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);
+
+                companyRedPacketBalanceUtil.updateRecordRedPacketId(record, redPacketLog.getLogId());
+
+                // 发送成功,记录日志等操作
+                return sendRedPacket;
+            } else {
+                companyRedPacketBalanceUtil.invalidRecord(record);
+                return R.error("奖励发送失败,请联系客服");
             }
         } else {
             FsCourseRedPacketLog redPacketLog = new FsCourseRedPacketLog();
@@ -1816,7 +1701,7 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
             // 更新观看记录的奖励类
             log.setRewardType(config.getRewardType());
             courseWatchLogMapper.updateFsCourseWatchLog(log);
-            return R.ok("红包发送成功");
+            return R.ok("答题成功,请联系群主");
         }
 
     }
@@ -1844,144 +1729,6 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
         return company.getOpenRedPacket() == 1 && periodRedPacketState;
     }
 
-    /**
-     * @Description: 回滚redis缓存中的余额 异常登记回滚异常表,定时重新回滚
-     * @Param:
-     * @Return:
-     * @Author xgb
-     * @Date 2025/10/22 10:37
-     */
-    private void rollbackBalance(BalanceRollbackError balanceRollbackError) {
-        String companyMoneyKey = FsConstants.COMPANY_MONEY_KEY + balanceRollbackError.getCompanyId();
-        RLock lock2 = redissonClient.getLock(FsConstants.COMPANY_MONEY_LOCK + balanceRollbackError.getCompanyId());
-        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);
-                }
-            }
-        }
-    }
-
-
-    /**
-     * 直接发送奖励
-     *
-     * @param config
-     * @param packetParam
-     * @param param
-     * @param amount
-     * @param log
-     * @return
-     */
-    private R processRedPacket(CourseConfig config, WxSendRedPacketParam packetParam, FsCourseSendRewardUParam param, BigDecimal amount, FsCourseWatchLog log) {
-        R sendRedPacket = paymentService.sendRedPacket(packetParam);
-
-        if (!sendRedPacket.get("code").equals(200)) {
-            return R.error("奖励发送失败,请联系客服");
-        }
-
-        createRedPacketLog(sendRedPacket, param, amount, log);
-        updateWatchLogRewardType(log, config);
-
-        return sendRedPacket;
-    }
-
-    private void createRedPacketLog(R sendRedPacket, FsCourseSendRewardUParam param, BigDecimal amount, FsCourseWatchLog log) {
-        FsCourseRedPacketLog redPacketLog = new FsCourseRedPacketLog();
-
-        // Set common fields
-        redPacketLog.setCourseId(param.getCourseId());
-        redPacketLog.setCompanyId(param.getCompanyId());
-        redPacketLog.setUserId(param.getUserId());
-        redPacketLog.setVideoId(param.getVideoId());
-        redPacketLog.setStatus(0);
-        redPacketLog.setQwUserId(param.getQwUserId());
-        redPacketLog.setCompanyUserId(param.getCompanyUserId());
-        redPacketLog.setCreateTime(new Date());
-        redPacketLog.setAmount(amount);
-        redPacketLog.setWatchLogId(log != null ? log.getLogId() : null);
-        redPacketLog.setPeriodId(param.getPeriodId());
-        redPacketLog.setAppId(param.getAppId());
-
-        // Set batch number based on isNew flag
-        if (sendRedPacket.get("isNew").equals(1)) {
-            TransferBillsResult transferBillsResult = (TransferBillsResult) sendRedPacket.get("data");
-            redPacketLog.setResult(JSON.toJSONString(sendRedPacket));
-            redPacketLog.setOutBatchNo(transferBillsResult.getOutBillNo());
-        } else {
-            redPacketLog.setOutBatchNo(sendRedPacket.get("orderCode").toString());
-        }
-
-        redPacketLogMapper.insertFsCourseRedPacketLog(redPacketLog);
-    }
-
-    private void updateWatchLogRewardType(FsCourseWatchLog log, CourseConfig config) {
-        if (log != null) {
-            log.setRewardType(config.getRewardType());
-            courseWatchLogMapper.updateFsCourseWatchLog(log);
-        }
-    }
-    /**
-     * 获取用户openId
-     *
-     * @param userId    用户ID
-     * @param companyId 公司ID
-     * @param source    来源 1公众号 2小程序
-     * @return openId
-     */
-    private String getOpenId(Long userId, Long companyId, Integer source) {
-        Company company = companyMapper.selectCompanyById(companyId);
-        String appId = source == 1 ? company.getCourseMaAppId() : company.getCourseMiniAppId();
-
-        // 公司配置为空时获取默认配置
-        if (StringUtils.isBlank(appId)) {
-            String json = configService.selectConfigByKey("course.config");
-            CourseConfig config = JSON.parseObject(json, CourseConfig.class);
-            appId = source == 1 ? config.getMpAppId() : config.getMiniprogramAppid();
-        }
-
-        // 查询openId
-        Wrapper<FsUserWx> queryWrapper = Wrappers.<FsUserWx>lambdaQuery().eq(FsUserWx::getFsUserId, userId).eq(FsUserWx::getAppId, appId);
-        FsUserWx fsUserWx = fsUserWxService.getOne(queryWrapper);
-        if (Objects.isNull(fsUserWx)) {
-            throw new CustomException("获取openId失败");
-        }
-
-        return fsUserWx.getOpenId();
-    }
-
     /**
      * 发放积分奖励
      *
@@ -2496,12 +2243,6 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
         return ResponseResult.ok(vo);
     }
 
-    @Override
-    public R addWatchLogByLink(FsUserCourseAddCompanyUserParam param) {
-
-        return null;
-    }
-
     @Override
     public R updateWatchDurationWx(FsUserCourseVideoUParam param) {
         //临时短链不做记录

+ 1 - 1
fs-service/src/main/resources/application-config-dev.yml

@@ -75,7 +75,7 @@ watch:
   password3: v9xsKuqn_$d2y
 
 fs :
-  commonApi: http://172.16.0.16:8010
+  commonApi: http://127.0.0.1:7771
   h5CommonApi: http://119.29.195.254:8010
   jwt:
     # 加密秘钥

+ 7 - 0
fs-service/src/main/resources/mapper/company/CompanyRedPacketBalanceDeductionRecordMapper.xml

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.fs.company.mapper.CompanyRedPacketBalanceDeductionRecordMapper">
+    
+</mapper>

+ 87 - 0
fs-service/src/main/resources/mapper/company/CompanyRedPacketBalanceLogsMapper.xml

@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.fs.company.mapper.CompanyRedPacketBalanceLogsMapper">
+
+    <resultMap type="CompanyRedPacketBalanceLogs" id="CompanyRedPacketBalanceLogsResult">
+        <result property="logsId"    column="logs_id"    />
+        <result property="companyId"    column="company_id"    />
+        <result property="money"    column="money"    />
+        <result property="remark"    column="remark"    />
+        <result property="createTime"    column="create_time"    />
+        <result property="balance"    column="balance"    />
+        <result property="logsType"    column="logs_type"    />
+        <result property="status"    column="status"    />
+    </resultMap>
+
+    <sql id="selectCompanyRedPacketBalanceLogsVo">
+        select logs_id, company_id, money, remark, create_time, balance, logs_type, status from company_red_packet_balance_logs
+    </sql>
+
+    <select id="selectCompanyRedPacketBalanceLogsList" parameterType="CompanyRedPacketBalanceLogs" resultMap="CompanyRedPacketBalanceLogsResult">
+        select l.logs_id, l.company_id, l.money, l.remark, l.create_time, l.balance, l.logs_type, l.status,c.company_name
+        from
+        company_red_packet_balance_logs l
+        left join company c on c.company_id = l.company_id
+        <where>
+            <if test="logsId != null "> and l.logs_id = #{logsId}</if>
+            <if test="companyId != null "> and l.company_id = #{companyId}</if>
+            <if test="params.beginCreateTime != null and params.beginCreateTime != '' and params.endCreateTime != null and params.endCreateTime != ''"> and l.create_time between #{params.beginCreateTime} and #{params.endCreateTime}</if>
+            <if test="logsType != null "> and l.logs_type = #{logsType}</if>
+        </where>
+        order by l.create_time desc
+    </select>
+
+    <select id="selectCompanyRedPacketBalanceLogsByLogsId" parameterType="Long" resultMap="CompanyRedPacketBalanceLogsResult">
+        <include refid="selectCompanyRedPacketBalanceLogsVo"/>
+        where logs_id = #{logsId}
+    </select>
+
+    <insert id="insertCompanyRedPacketBalanceLogs" parameterType="CompanyRedPacketBalanceLogs" useGeneratedKeys="true" keyProperty="logsId">
+        insert into company_red_packet_balance_logs
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="companyId != null">company_id,</if>
+            <if test="money != null">money,</if>
+            <if test="remark != null">remark,</if>
+            <if test="createTime != null">create_time,</if>
+            <if test="balance != null">balance,</if>
+            <if test="logsType != null">logs_type,</if>
+            <if test="status != null">status,</if>
+         </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="companyId != null">#{companyId},</if>
+            <if test="money != null">#{money},</if>
+            <if test="remark != null">#{remark},</if>
+            <if test="createTime != null">#{createTime},</if>
+            <if test="balance != null">#{balance},</if>
+            <if test="logsType != null">#{logsType},</if>
+            <if test="status != null">#{status},</if>
+         </trim>
+    </insert>
+
+    <update id="updateCompanyRedPacketBalanceLogs" parameterType="CompanyRedPacketBalanceLogs">
+        update company_red_packet_balance_logs
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="companyId != null">company_id = #{companyId},</if>
+            <if test="money != null">money = #{money},</if>
+            <if test="remark != null">remark = #{remark},</if>
+            <if test="createTime != null">create_time = #{createTime},</if>
+            <if test="balance != null">balance = #{balance},</if>
+            <if test="logsType != null">logs_type = #{logsType},</if>
+            <if test="status != null">status = #{status},</if>
+        </trim>
+        where logs_id = #{logsId}
+    </update>
+
+    <delete id="deleteCompanyRedPacketBalanceLogsByLogsId" parameterType="Long">
+        delete from company_red_packet_balance_logs where logs_id = #{logsId}
+    </delete>
+
+    <delete id="deleteCompanyRedPacketBalanceLogsByLogsIds" parameterType="String">
+        delete from company_red_packet_balance_logs where logs_id in
+        <foreach item="logsId" collection="array" open="(" separator="," close=")">
+            #{logsId}
+        </foreach>
+    </delete>
+</mapper>

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

@@ -180,7 +180,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     </select>
 
     <select id="selectFsCourseRedPacketLogHourseByCompany" resultType="com.fs.company.vo.RedPacketMoneyVO">
-        SELECT a.company_id, SUM(amount) as money  FROM fs_course_red_packet_log a WHERE a.create_time &gt;= #{startTime} AND a.create_time &lt;= #{endTime} GROUP BY a.company_id
+        SELECT a.company_id, ifnull(sum(amount),0) as money  FROM fs_course_red_packet_log a WHERE a.create_time >= #{startTime} AND a.create_time &lt; #{endTime} and `status` = 1 GROUP BY a.company_id
     </select>
     <!-- 看客红包统计   -->
     <select id="statistics" resultType="com.fs.course.dto.CourseRedPacketStatisticsDTO">