ソースを参照

1、调整签到报错提示

yys 4 日 前
コミット
34a71bacf9

+ 3 - 0
fs-service/src/main/java/com/fs/live/domain/LiveAutoTask.java

@@ -3,6 +3,7 @@ package com.fs.live.domain;
 import java.util.Date;
 
 
+import com.fasterxml.jackson.annotation.JsonFormat;
 import com.fs.common.annotation.Excel;
 import lombok.Data;
 import com.fs.common.core.domain.BaseEntity;
@@ -38,10 +39,12 @@ public class LiveAutoTask extends BaseEntity{
     private Long triggerType;
 
     /** 触发值:绝对时间用yyyy-MM-dd HH:mm:ss,相对时间用分钟数 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     @Excel(name = "触发值:绝对时间用yyyy-MM-dd HH:mm:ss,相对时间用分钟数")
     private Date triggerValue;
 
     /** 触发值:绝对时间用yyyy-MM-dd HH:mm:ss,相对时间用分钟数 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     @Excel(name = "触发值:绝对时间用yyyy-MM-dd HH:mm:ss,相对时间用分钟数")
     private Date absValue;
 

+ 119 - 29
fs-service/src/main/java/com/fs/live/service/impl/LiveAutoTaskServiceImpl.java

@@ -198,31 +198,25 @@ public class LiveAutoTaskServiceImpl implements ILiveAutoTaskService {
             if (reqContent == null) {
                 return R.error("请配置签到奖励");
             }
-            R rewardsResult = buildSignRewards(liveAutoTask.getLiveId(), reqContent);
+            R rewardsResult = buildSignRewards(liveAutoTask.getLiveId(), reqContent, Collections.emptySet());
             if (!Integer.valueOf(200).equals(rewardsResult.get("code"))) {
                 return rewardsResult;
             }
             @SuppressWarnings("unchecked")
             List<JSONObject> rewardList = (List<JSONObject>) rewardsResult.get("data");
 
-            // 查询最新序号
+            // 查询最新序号,自动 +1(不限制次数)
             LiveAutoTask task = baseMapper.selectLastSignTaskByLiveId(liveAutoTask.getLiveId());
-            int nextSignNo = 1; // 默认第一次签到
+            int nextSignNo = 1;
 
-            if(task != null && StringUtils.isNotEmpty(task.getContent())){
+            if (task != null && StringUtils.isNotEmpty(task.getContent())) {
                 try {
-                    // 解析 content 获取 signNo
                     JSONObject contentObj = JSON.parseObject(task.getContent());
-                    String currentSignNo = contentObj.getString("signNo");
-
-                    if ("1".equals(currentSignNo)) {
-                        nextSignNo = 2;
-                    } else if ("2".equals(currentSignNo)) {
-                        nextSignNo = 3;
-                    } else if ("3".equals(currentSignNo)) {
-                        return R.error("最多只能设置三次签到任务");
-                    }else {
-                        return R.error("签到任务配置错误");
+                    if (contentObj != null && contentObj.get("signNo") != null) {
+                        int currentSignNo = contentObj.getIntValue("signNo");
+                        if (currentSignNo > 0) {
+                            nextSignNo = currentSignNo + 1;
+                        }
                     }
                 } catch (Exception e) {
                     log.error("解析签到任务content失败", e);
@@ -312,7 +306,30 @@ public class LiveAutoTaskServiceImpl implements ILiveAutoTaskService {
     public R updateLiveAutoTask(LiveAutoTask liveAutoTask)
     {
         LiveAutoTask existTask = baseMapper.selectLiveAutoTaskById(liveAutoTask.getId());
-        redisCache.redisTemplate.opsForZSet().removeRangeByScore("live:auto_task:" + existTask.getLiveId(), existTask.getAbsValue().getTime(), existTask.getAbsValue().getTime());
+        if (existTask == null) {
+            return R.error("任务不存在");
+        }
+        Live live = liveMapper.selectLiveByLiveId(existTask.getLiveId());
+        if (live == null) {
+            return R.error("直播间不存在");
+        }
+        // 从缓存移除旧触发点(absValue 为空时跳过,避免 NPE)
+        if (existTask.getAbsValue() != null) {
+            try {
+                redisCache.redisTemplate.opsForZSet().removeRangeByScore(
+                        "live:auto_task:" + existTask.getLiveId(),
+                        existTask.getAbsValue().getTime(),
+                        existTask.getAbsValue().getTime());
+            } catch (Exception e) {
+                log.warn("移除自动化任务缓存失败, taskId={}", existTask.getId(), e);
+            }
+        }
+        // 修改触发时间时同步重算 absValue
+        Date triggerValue = liveAutoTask.getTriggerValue();
+        if (triggerValue != null && live.getStartTime() != null) {
+            liveAutoTask.setAbsValue(getTriggerValue(triggerValue, live.getStartTime()));
+        }
+        liveAutoTask.setUpdateTime(new Date());
         if (liveAutoTask.getTaskType() == 1L) {
             // 商品
             LiveGoodsVo liveGoodsVo = goodsService.selectLiveGoodsVoByGoodsId(Long.valueOf(liveAutoTask.getContent()));
@@ -390,7 +407,9 @@ public class LiveAutoTaskServiceImpl implements ILiveAutoTaskService {
             if (reqContent == null) {
                 return R.error("请配置签到奖励");
             }
-            R rewardsResult = buildSignRewards(liveAutoTask.getLiveId(), reqContent);
+            // 任务触发后积分红包会变为「发放中」,更新时允许保留原配置中的红包
+            Set<Long> keepPointRedIds = collectExistingSignPointRedIds(existTask);
+            R rewardsResult = buildSignRewards(liveAutoTask.getLiveId(), reqContent, keepPointRedIds);
             if (!Integer.valueOf(200).equals(rewardsResult.get("code"))) {
                 return rewardsResult;
             }
@@ -398,7 +417,7 @@ public class LiveAutoTaskServiceImpl implements ILiveAutoTaskService {
             List<JSONObject> rewardList = (List<JSONObject>) rewardsResult.get("data");
 
             int signNo = 1;
-            if (existTask != null && StringUtils.isNotEmpty(existTask.getContent())) {
+            if (StringUtils.isNotEmpty(existTask.getContent())) {
                 try {
                     JSONObject oldContent = JSON.parseObject(existTask.getContent());
                     if (oldContent != null && oldContent.get("signNo") != null) {
@@ -417,6 +436,32 @@ public class LiveAutoTaskServiceImpl implements ILiveAutoTaskService {
         } else {
             return R.error("任务类型错误");
         }
+        // 直播中且启用、未执行完、触发点仍在未来时,写回缓存
+        Long finishStatus = liveAutoTask.getFinishStatus() != null
+                ? liveAutoTask.getFinishStatus()
+                : existTask.getFinishStatus();
+        Date absForCache = liveAutoTask.getAbsValue() != null
+                ? liveAutoTask.getAbsValue()
+                : existTask.getAbsValue();
+        if (live.getStatus() != null && live.getStatus() == 2
+                && liveAutoTask.getStatus() != null && liveAutoTask.getStatus() == 1L
+                && (finishStatus == null || finishStatus == 0L)
+                && absForCache != null && absForCache.after(new Date())) {
+            try {
+                LiveAutoTask cached = baseMapper.selectLiveAutoTaskById(liveAutoTask.getId());
+                if (cached != null && cached.getAbsValue() != null) {
+                    cached.setUpdateTime(null);
+                    cached.setCreateTime(null);
+                    redisCache.redisTemplate.opsForZSet().add(
+                            "live:auto_task:" + live.getLiveId(),
+                            JSON.toJSONString(cached),
+                            cached.getAbsValue().getTime());
+                    redisCache.redisTemplate.expire("live:auto_task:" + live.getLiveId(), 30, TimeUnit.MINUTES);
+                }
+            } catch (Exception e) {
+                log.warn("写回自动化任务缓存失败, taskId={}", liveAutoTask.getId(), e);
+            }
+        }
         return R.ok("更新成功");
     }
 
@@ -805,16 +850,58 @@ public class LiveAutoTaskServiceImpl implements ILiveAutoTaskService {
                 && (typeLabel.contains("核销") || typeLabel.contains("代金券"));
     }
 
+    /**
+     * 从已有签到任务 content 中收集积分红包 ID,供更新时放宽「未发放」校验。
+     */
+    private Set<Long> collectExistingSignPointRedIds(LiveAutoTask existTask) {
+        Set<Long> ids = new HashSet<>();
+        if (existTask == null || StringUtils.isEmpty(existTask.getContent())) {
+            return ids;
+        }
+        try {
+            JSONObject oldContent = JSON.parseObject(existTask.getContent());
+            if (oldContent == null) {
+                return ids;
+            }
+            com.alibaba.fastjson.JSONArray arr = oldContent.getJSONArray("rewards");
+            if (arr != null) {
+                for (int i = 0; i < arr.size(); i++) {
+                    JSONObject item = arr.getJSONObject(i);
+                    if (item == null) {
+                        continue;
+                    }
+                    Long rewardType = item.getLong("rewardType");
+                    if (rewardType == null || rewardType != 2L) {
+                        continue;
+                    }
+                    Long rewardId = item.getLong("rewardId");
+                    if (rewardId == null && item.getJSONObject("reward") != null) {
+                        rewardId = item.getJSONObject("reward").getLong("redId");
+                    }
+                    if (rewardId != null) {
+                        ids.add(rewardId);
+                    }
+                }
+            } else if (Long.valueOf(2L).equals(oldContent.getLong("rewardType"))) {
+                Long rewardId = oldContent.getLong("rewardId");
+                if (rewardId == null && oldContent.getJSONObject("reward") != null) {
+                    rewardId = oldContent.getJSONObject("reward").getLong("redId");
+                }
+                if (rewardId != null) {
+                    ids.add(rewardId);
+                }
+            }
+        } catch (Exception e) {
+            log.warn("解析原签到任务积分红包失败, taskId={}", existTask.getId(), e);
+        }
+        return ids;
+    }
+
     /**
      * 构建签到任务多奖励列表(兼容旧单奖励字段 rewardType/rewardId)。
-     * <ul>
-     *   <li>2=积分红包:选 LiveRedConf,整场任务最多配置 1 个</li>
-     *   <li>5=优惠券:选优惠券 + couponCount(领取张数)</li>
-     *   <li>6=金额红包:输入 amount(元,精确到 0.1,最低 0.1),最多配置 1 个;领取走微信转账</li>
-     * </ul>
-     * 积分红包与金额红包可同时存在,各自不能重复;已不支持抽奖(4)。
+     * @param keepPointRedIds 更新时允许保留的已激活积分红包 ID(任务触发后 redStatus 会变为发放中)
      */
-    private R buildSignRewards(Long liveId, JSONObject reqContent) {
+    private R buildSignRewards(Long liveId, JSONObject reqContent, Set<Long> keepPointRedIds) {
         List<JSONObject> inputList = new ArrayList<>();
         com.alibaba.fastjson.JSONArray rewardsArr = reqContent.getJSONArray("rewards");
         if (rewardsArr != null && !rewardsArr.isEmpty()) {
@@ -844,6 +931,7 @@ public class LiveAutoTaskServiceImpl implements ILiveAutoTaskService {
         Set<String> dedupe = new HashSet<>();
         boolean hasPointRed = false;
         boolean hasCashRed = false;
+        Set<Long> keepIds = keepPointRedIds != null ? keepPointRedIds : Collections.emptySet();
         for (JSONObject item : inputList) {
             Long rewardType = item.getLong("rewardType");
             if (rewardType == null) {
@@ -863,7 +951,7 @@ public class LiveAutoTaskServiceImpl implements ILiveAutoTaskService {
                 }
                 hasCashRed = true;
             }
-            R rewardResult = buildSignRewardItem(liveId, item);
+            R rewardResult = buildSignRewardItem(liveId, item, keepIds);
             if (!Integer.valueOf(200).equals(rewardResult.get("code"))) {
                 return rewardResult;
             }
@@ -884,12 +972,12 @@ public class LiveAutoTaskServiceImpl implements ILiveAutoTaskService {
     /**
      * 构建签到任务单条奖励内容并校验。
      * <ul>
-     *   <li>2:校验积分红包存在且未发放,写入 rewardId + reward</li>
+     *   <li>2:校验积分红包存在;新增须未发放,更新时允许保留原任务已激活的红包</li>
      *   <li>5:校验优惠券已关联直播间及库存,写入 rewardId + couponCount + reward</li>
      *   <li>6:校验金额精确到 0.1 元且 ≥0.1,写入 amount</li>
      * </ul>
      */
-    private R buildSignRewardItem(Long liveId, JSONObject item) {
+    private R buildSignRewardItem(Long liveId, JSONObject item, Set<Long> keepPointRedIds) {
         Long rewardType = item.getLong("rewardType");
         if (rewardType == 2L) {
             Long rewardId = item.getLong("rewardId");
@@ -900,7 +988,9 @@ public class LiveAutoTaskServiceImpl implements ILiveAutoTaskService {
             if (liveRedConf == null) {
                 return R.error("积分红包配置不存在");
             }
-            if (liveRedConf.getRedStatus() != 0L) {
+            boolean keepExisting = keepPointRedIds != null && keepPointRedIds.contains(rewardId);
+            // 签到任务触发后会把红包改为发放中(1);更新原配置时放行,新选红包仍须未发放
+            if (!keepExisting && liveRedConf.getRedStatus() != 0L) {
                 return R.error("积分红包状态应该为:未发放");
             }
             JSONObject stored = new JSONObject();

+ 2 - 1
fs-service/src/main/resources/mapper/his/FsUserMapper.xml

@@ -61,7 +61,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     </resultMap>
 
     <sql id="selectFsUserVo">
-        select user_id,qw_ext_id,sex,is_buy,`level`,course_ma_open_id,is_push,is_add_qw,source,login_device,bind_company_user_id,
+        select user_id,qw_ext_id,sex,is_buy,`level`,course_ma_open_id,is_push,is_add_qw,source,login_device,bind_company_user_id,invited_by_sales_id,
                is_individuation_push,store_open_id,password,jpush_id, is_vip,vip_start_date,vip_end_date,
                vip_level,vip_status,nick_name,integral_status, avatar, phone, integral,sign_num, status,
                tui_user_id, tui_time, tui_user_count, ma_open_id, mp_open_id, union_id, is_del, user_code,
@@ -712,6 +712,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="orderCount != null">order_count = #{orderCount},</if>
             <if test="companyUserId != null">company_user_id = #{companyUserId},</if>
             <if test="appId != null">app_id = #{appId},</if>
+            <if test="invitedBySalesId != null">invited_by_sales_id = #{invitedBySalesId},</if>
             <if test="appOpenId != null">app_open_id = #{appOpenId},</if>
             <if test="appleKey != null">apple_key = #{appleKey},</if>
             <if test="historyApp != null and historyApp != ''">history_app = #{historyApp},</if>

+ 2 - 1
fs-user-app/src/main/java/com/fs/app/controller/UserController.java

@@ -140,11 +140,12 @@ public class UserController extends  AppBaseController {
                user.setMayWithdraw(mayWithdraw.divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_DOWN));
             }
             CompanyUser companyUser =new CompanyUser();
-            if(user.getInvitedBySalesId()!=null){
+            if(user.getBindCompanyUserId()!=null){
                 companyUser = companyUserService.getInviteCodeByCompanyUserIdAndUserId(user.getInvitedBySalesId());
                 if(companyUser!=null){
                     companyUser.setInvitationName(StringUtils.isNotEmpty(companyUser.getNickName())?companyUser.getNickName():"");
                 }
+                user.setInvitedBySalesId(1L);
             }
 
             Map<String,Object> map=new HashMap<>();

+ 150 - 80
fs-user-app/src/main/java/com/fs/app/facade/impl/LiveFacadeServiceImpl.java

@@ -38,6 +38,7 @@ import com.fs.live.param.SignPO;
 import com.fs.live.service.*;
 import com.fs.live.vo.*;
 import com.fs.system.service.ISysConfigService;
+import com.github.binarywang.wxpay.exception.WxPayException;
 import com.github.pagehelper.PageInfo;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -373,7 +374,6 @@ public class LiveFacadeServiceImpl extends BaseController implements LiveFacadeS
             return R.error("签到任务尚未开启或奖励配置不存在");
         }
 
-        // 写入签到记录
         LiveSignRecord record = new LiveSignRecord();
         record.setLiveId(liveId);
         record.setUserId(userId);
@@ -381,84 +381,122 @@ public class LiveFacadeServiceImpl extends BaseController implements LiveFacadeS
         record.setCreateTime(new Date());
         liveSignRecordService.save(record);
 
-        List<Map<String, Object>> claimResults = new ArrayList<>();
+        List<Map<String, Object>> claimResults = new ArrayList<>(rewards.size());
         for (JSONObject item : rewards) {
-            Long rewardType = item.getLong("rewardType");
-            Long opLogId = item.getLong("opLogId");
-            Map<String, Object> one = new HashMap<>();
-            one.put("rewardType", rewardType);
-            one.put("opLogId", opLogId);
-            try {
-                if (rewardType != null && rewardType == 2L) {
-                    // 积分红包:走直播积分红包领取
-                    RedPO red = new RedPO();
-                    red.setLiveId(liveId);
-                    red.setUserId(userId);
-                    red.setRedId(item.getLong("rewardId"));
-                    red.setOpLogId(opLogId);
-                    R r = iLiveRedConfService.claimRedPacket(red);
-                    one.put("success", Integer.valueOf(200).equals(r.get("code")));
-                    one.put("msg", r.get("msg"));
-                } else if (rewardType != null && rewardType == 5L) {
-                    // 优惠券:按配置张数发放
-                    CouponPO coupon = new CouponPO();
-                    coupon.setLiveId(liveId);
-                    coupon.setUserId(userId);
-                    Long couponIssueId = item.getLong("couponIssueId");
-                    if (couponIssueId == null) {
-                        Long couponId = item.getLong("rewardId");
-                        LiveCouponIssue issue = liveCouponIssueService.selectIssueByLiveIdAndCouponId(liveId, couponId);
-                        couponIssueId = issue != null ? issue.getId() : null;
-                    }
-                    coupon.setCouponIssueId(couponIssueId);
-                    coupon.setGoodsId(item.getLong("goodsId"));
-                    coupon.setOpLogId(opLogId);
-                    Integer couponCount = item.getInteger("couponCount");
-                    coupon.setCouponCount(couponCount != null && couponCount > 0 ? couponCount : 1);
-                    R r = iLiveCouponService.claimCoupon(coupon);
-                    one.put("success", Integer.valueOf(200).equals(r.get("code")));
-                    one.put("msg", r.get("msg"));
-                    one.put("couponCount", coupon.getCouponCount());
-                    if (r.get("opLogId") != null) {
-                        one.put("opLogId", r.get("opLogId"));
-                    }
-                } else if (rewardType != null && rewardType == 6L) {
-                    // 金额红包:微信商家转账,App 用返回参数确认收款
-                    R r = claimSignCashRed(liveId, userId, item, sign);
-                    one.put("success", Integer.valueOf(200).equals(r.get("code")));
-                    one.put("msg", r.get("msg"));
-                    // App 调起微信确认收款所需字段
-                    if (Integer.valueOf(200).equals(r.get("code"))) {
-                        one.put("needWxConfirm", true);
-                        one.put("package", r.get("package"));
-                        one.put("mchId", r.get("mchId"));
-                        one.put("appId", r.get("appId"));
-                        one.put("orderCode", r.get("orderCode"));
-                        one.put("amount", item.getBigDecimal("amount"));
-                        if (opLogId != null) {
-                            liveConsoleOpLogService.bindOpLogUser(opLogId, liveId, userId);
-                        }
-                    }
-                } else {
-                    one.put("success", false);
-                    one.put("msg", "不支持的奖励类型");
-                }
-            } catch (Exception e) {
-                log.error("签到领取奖励失败, liveId={}, userId={}, rewardType={}", liveId, userId, rewardType, e);
+            claimResults.add(claimOneSignReward(liveId, userId, item, sign));
+        }
+        return buildSignClaimResponse(claimResults);
+    }
+
+    /**
+     * 领取单项签到奖励:2积分红包 / 5优惠券 / 6金额红包
+     */
+    private Map<String, Object> claimOneSignReward(Long liveId, Long userId, JSONObject item, SignPO sign) {
+        Long rewardType = item.getLong("rewardType");
+        Long opLogId = item.getLong("opLogId");
+        Map<String, Object> one = new HashMap<>();
+        one.put("rewardType", rewardType);
+        one.put("opLogId", opLogId);
+        try {
+            if (Objects.equals(rewardType, 2L)) {
+                claimSignPoints(one, liveId, userId, item, opLogId);
+            } else if (Objects.equals(rewardType, 5L)) {
+                claimSignCoupon(one, liveId, userId, item, opLogId);
+            } else if (Objects.equals(rewardType, 6L)) {
+                claimSignCash(one, liveId, userId, item, sign, opLogId);
+            } else {
                 one.put("success", false);
-                one.put("msg", "领取失败");
+                one.put("msg", "不支持的奖励类型");
             }
-            claimResults.add(one);
+        } catch (Exception e) {
+            log.error("签到领取奖励失败, liveId={}, userId={}, rewardType={}", liveId, userId, rewardType, e);
+            one.put("success", false);
+            one.put("msg", "领取失败");
         }
+        return one;
+    }
 
-        boolean anySuccess = claimResults.stream().anyMatch(m -> Boolean.TRUE.equals(m.get("success")));
-        // 有金额红包则返回其 one;否则只返回 msg
+    /** 积分红包 */
+    private void claimSignPoints(Map<String, Object> one, Long liveId, Long userId, JSONObject item, Long opLogId) {
+        RedPO red = new RedPO();
+        red.setLiveId(liveId);
+        red.setUserId(userId);
+        red.setRedId(item.getLong("rewardId"));
+        red.setOpLogId(opLogId);
+        fillClaimResult(one, iLiveRedConfService.claimRedPacket(red));
+    }
+
+    /** 优惠券(按配置张数) */
+    private void claimSignCoupon(Map<String, Object> one, Long liveId, Long userId, JSONObject item, Long opLogId) {
+        CouponPO coupon = new CouponPO();
+        coupon.setLiveId(liveId);
+        coupon.setUserId(userId);
+        Long couponIssueId = item.getLong("couponIssueId");
+        if (couponIssueId == null) {
+            LiveCouponIssue issue = liveCouponIssueService.selectIssueByLiveIdAndCouponId(liveId, item.getLong("rewardId"));
+            couponIssueId = issue != null ? issue.getId() : null;
+        }
+        coupon.setCouponIssueId(couponIssueId);
+        coupon.setGoodsId(item.getLong("goodsId"));
+        coupon.setOpLogId(opLogId);
+        Integer couponCount = item.getInteger("couponCount");
+        coupon.setCouponCount(couponCount != null && couponCount > 0 ? couponCount : 1);
+        R r = iLiveCouponService.claimCoupon(coupon);
+        fillClaimResult(one, r);
+        one.put("couponCount", coupon.getCouponCount());
+        if (r.get("opLogId") != null) {
+            one.put("opLogId", r.get("opLogId"));
+        }
+    }
+
+    /** 金额红包(微信转账,成功时写入确认收款参数) */
+    private void claimSignCash(Map<String, Object> one, Long liveId, Long userId,
+                               JSONObject item, SignPO sign, Long opLogId) {
+        R r = claimSignCashRed(liveId, userId, item, sign);
+        fillClaimResult(one, r);
+        if (!isROk(r)) {
+            return;
+        }
+        one.put("needWxConfirm", true);
+        one.put("package", r.get("package"));
+        one.put("mchId", r.get("mchId"));
+        one.put("appId", r.get("appId"));
+        one.put("orderCode", r.get("orderCode"));
+        one.put("amount", item.getBigDecimal("amount"));
+        if (opLogId != null) {
+            liveConsoleOpLogService.bindOpLogUser(opLogId, liveId, userId);
+        }
+    }
+
+    private void fillClaimResult(Map<String, Object> one, R r) {
+        one.put("success", isROk(r));
+        one.put("msg", r.get("msg"));
+        one.put("code", r.get("code"));
+    }
+
+    private boolean isROk(R r) {
+        return Integer.valueOf(200).equals(r.get("code"));
+    }
+
+    /**
+     * 有金额红包:成功返回 one,失败返回短提示(其他奖励已领:签到已到账,红包:xxx);
+     * 无金额红包:只返回 msg。
+     */
+    private R buildSignClaimResponse(List<Map<String, Object>> claimResults) {
         Map<String, Object> cashRed = claimResults.stream()
-                .filter(m -> Long.valueOf(6L).equals(m.get("rewardType")))
+                .filter(m -> Objects.equals(6L, m.get("rewardType")))
                 .findFirst().orElse(null);
         if (cashRed != null) {
-            return R.ok(cashRed);
+            if (Boolean.TRUE.equals(cashRed.get("success"))) {
+                return R.ok(cashRed);
+            }
+            String cashTip = cashRed.get("msg") != null ? String.valueOf(cashRed.get("msg")) : "领取失败";
+            boolean otherSuccess = claimResults.stream()
+                    .filter(m -> !Objects.equals(6L, m.get("rewardType")))
+                    .anyMatch(m -> Boolean.TRUE.equals(m.get("success")));
+            return R.error(otherSuccess ? "签到已到账,红包:" + cashTip : cashTip);
         }
+        boolean anySuccess = claimResults.stream().anyMatch(m -> Boolean.TRUE.equals(m.get("success")));
         return R.ok(anySuccess ? "签到成功" : "签到成功,但奖励领取失败");
     }
 
@@ -472,15 +510,10 @@ public class LiveFacadeServiceImpl extends BaseController implements LiveFacadeS
         if (amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) {
             return R.error("金额红包配置无效");
         }
-        Live live = liveService.selectLiveByLiveId(liveId);
-        if (live == null) {
-            return R.error("直播间不存在");
-        }
         FsUser user = fsUserService.selectFsUserById(userId);
         if (user == null) {
             return R.error("用户不存在");
         }
-        // 通过用户绑定销售ID查询销售所属公司,作为红包扣款公司
         Long bindCompanyUserId = user.getBindCompanyUserId();
         if (bindCompanyUserId == null) {
             return R.error("用户未绑定销售,无法发放金额红包");
@@ -490,7 +523,7 @@ public class LiveFacadeServiceImpl extends BaseController implements LiveFacadeS
             return R.error("绑定销售不存在或未归属公司,无法发放金额红包");
         }
         Long companyId = companyUser.getCompanyId();
-        // 复用课程发奖 openId 解析逻辑:按 source 取 H5/小程序/App openId
+
         FsCourseSendRewardUParam param = new FsCourseSendRewardUParam();
         param.setUserId(userId);
         param.setCompanyId(companyId);
@@ -502,10 +535,10 @@ public class LiveFacadeServiceImpl extends BaseController implements LiveFacadeS
         } catch (CustomException e) {
             return R.error(e.getMessage());
         }
-
         if (StringUtils.isEmpty(openId)) {
             return R.error("请使用微信登录后再领取金额红包");
         }
+
         WxSendRedPacketParam packetParam = new WxSendRedPacketParam();
         packetParam.setOpenId(openId);
         packetParam.setAmount(amount.setScale(1, RoundingMode.HALF_UP));
@@ -516,10 +549,9 @@ public class LiveFacadeServiceImpl extends BaseController implements LiveFacadeS
         packetParam.setUser(user);
         try {
             R sendResult = paymentService.sendAppRedPacket(packetParam);
-            if (!Integer.valueOf(200).equals(sendResult.get("code"))) {
+            if (!isROk(sendResult)) {
                 return sendResult;
             }
-            // 写入直播奖励流水(现金,来源类型 7=签到)
             try {
                 LiveRewardRecord rewardRecord = new LiveRewardRecord();
                 rewardRecord.setLiveId(liveId);
@@ -537,10 +569,48 @@ public class LiveFacadeServiceImpl extends BaseController implements LiveFacadeS
             return sendResult;
         } catch (Exception e) {
             log.error("签到金额红包发放失败, liveId={}, userId={}, amount={}", liveId, userId, amount, e);
-            return R.error("金额红包发放失败:" + e.getMessage());
+            return R.error(resolveCashRedTipByException(e));
         }
     }
 
+    /**
+     * 根据微信转账异常 errCode 转成短提示(如 NOT_ENOUGH → 资金不足,请联系销售!)
+     */
+    private String resolveCashRedTipByException(Throwable e) {
+        WxPayException wxEx = findWxPayException(e);
+        String errCode = null;
+        if (wxEx != null) {
+            errCode = wxEx.getErrCode();
+            // V3 有时 errCode 为空,从 customErrorMsg 兜底
+            if (StringUtils.isEmpty(errCode) && StringUtils.isNotEmpty(wxEx.getCustomErrorMsg())
+                    && wxEx.getCustomErrorMsg().contains("资金不足")) {
+                errCode = "NOT_ENOUGH";
+            }
+        }
+        if (StringUtils.isEmpty(errCode) && e.getMessage() != null && e.getMessage().contains("errCode=NOT_ENOUGH")) {
+            errCode = "NOT_ENOUGH";
+        }
+        return cashRedTipByWxErrCode(errCode);
+    }
+
+    private String cashRedTipByWxErrCode(String errCode) {
+        if ("NOT_ENOUGH".equalsIgnoreCase(errCode)) {
+            return "资金不足,请联系销售!";
+        }
+        return "领取失败";
+    }
+
+    private WxPayException findWxPayException(Throwable e) {
+        Throwable cur = e;
+        while (cur != null) {
+            if (cur instanceof WxPayException) {
+                return (WxPayException) cur;
+            }
+            cur = cur.getCause();
+        }
+        return null;
+    }
+
     /**
      * 按来源获取用户 openId:1=H5 2=小程序 3=App
      */