Explorar el Código

添加上抽奖和同步用户信息

yuhongqi hace 5 días
padre
commit
8fecae40ea

+ 12 - 0
fs-admin/src/main/java/com/fs/live/controller/LiveController.java

@@ -178,4 +178,16 @@ public class LiveController extends BaseController {
         return liveService.clearLiveCache(liveId);
     }
 
+    /**
+     * 查询 his 平台大礼品奖励配置(rewardType=6)
+     */
+    @PreAuthorize("@ss.hasPermi('live:live:list')")
+    @GetMapping("/bigGiftReward/list")
+    public TableDataInfo listBigGiftReward(@RequestParam(value = "pageNum", defaultValue = "1") Integer pageNum,
+                                           @RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize,
+                                           @RequestParam(value = "name", required = false) String name,
+                                           @RequestParam(value = "status", required = false) Long status) {
+        return liveService.listBigGiftRewards(pageNum, pageSize, name, status);
+    }
+
 }

+ 47 - 0
fs-admin/src/main/java/com/fs/task/LiveTask.java

@@ -1,6 +1,7 @@
 package com.fs.task;
 
 import com.fs.common.annotation.QuartzRunnable;
+import com.fs.common.core.redis.RedisCache;
 import com.fs.erp.domain.ErpDeliverys;
 import com.fs.erp.domain.ErpOrderQuery;
 import com.fs.erp.dto.ErpOrderQueryRequert;
@@ -39,6 +40,7 @@ import org.springframework.stereotype.Component;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
 import java.util.List;
+import java.util.concurrent.TimeUnit;
 
 import static com.fs.live.utils.redis.RedisBatchHandler.CONSUME_INTERVAL;
 
@@ -85,6 +87,13 @@ public class LiveTask {
     public RedisBatchHandler redisBatchHandler;
     @Autowired
     private IFsUserService fsUserService;
+    @Autowired
+    private RedisCache redisCache;
+
+    private static final String APP_USER_SYNC_TASK_LOCK_KEY = "task:live:app_user_sync:lock";
+    private static final long APP_USER_SYNC_TASK_LOCK_SECONDS = 600L;
+    /** 两个 APP 用户同步任务错峰启动间隔(秒) */
+    private static final long APP_USER_SYNC_TASK_STAGGER_SECONDS = 15L;
 
     // 订单银行回调数据丢失补偿
     public void recoveryBankOrder() {
@@ -213,13 +222,51 @@ public class LiveTask {
      */
     @QuartzRunnable(name = "同步APP用户数据")
     public void syncAppUser() {
+        if (!tryAcquireAppUserSyncLock()) {
+            logger.debug("同步APP用户数据任务跳过,其他任务正在执行");
+            return;
+        }
         try {
             fsUserService.syncAppUsersForRecentLiveOrders(3);
         } catch (Exception e) {
             logger.error("同步APP用户数据失败", e);
+        } finally {
+            releaseAppUserSyncLock();
+        }
+    }
+
+    /**
+     * 从 Redis 拉取下单时缓存的待同步 APP 用户并同步
+     */
+    @QuartzRunnable(name = "同步APP用户数据(Redis)")
+    public void syncAppUserFromRedis() {
+        try {
+            Thread.sleep(APP_USER_SYNC_TASK_STAGGER_SECONDS * 1000);
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            return;
+        }
+        if (!tryAcquireAppUserSyncLock()) {
+            logger.debug("Redis同步APP用户数据任务跳过,其他任务正在执行");
+            return;
+        }
+        try {
+            fsUserService.syncPendingAppUsersFromRedis();
+        } catch (Exception e) {
+            logger.error("Redis同步APP用户数据失败", e);
+        } finally {
+            releaseAppUserSyncLock();
         }
     }
 
+    private boolean tryAcquireAppUserSyncLock() {
+        return redisCache.setIfAbsent(APP_USER_SYNC_TASK_LOCK_KEY, "1", APP_USER_SYNC_TASK_LOCK_SECONDS, TimeUnit.SECONDS);
+    }
+
+    private void releaseAppUserSyncLock() {
+        redisCache.deleteObject(APP_USER_SYNC_TASK_LOCK_KEY);
+    }
+
         /**
          * 退款自动处理 24小时未审核自动审核通过 每小时执行一次
          */

+ 13 - 1
fs-company/src/main/java/com/fs/company/controller/live/LiveController.java

@@ -323,7 +323,7 @@ public class LiveController extends BaseController
         if (config == null || config.getAppid() == null || config.getSecret() == null) {
             return R.error("微信小程序appid或secret未配置");
         }
-        
+
         String url="https://api.weixin.qq.com/cgi-bin/stable_token";
         HashMap<String, String> map = new HashMap<>();
         map.put("grant_type","client_credential");
@@ -370,4 +370,16 @@ public class LiveController extends BaseController
 
         return R.ok().put("data", exist);
     }
+
+    /**
+     * 查询 his 平台大礼品奖励配置(rewardType=6)
+     */
+    @PreAuthorize("@ss.hasPermi('live:live:list')")
+    @GetMapping("/bigGiftReward/list")
+    public TableDataInfo listBigGiftReward(@RequestParam(value = "pageNum", defaultValue = "1") Integer pageNum,
+                                           @RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize,
+                                           @RequestParam(value = "name", required = false) String name,
+                                           @RequestParam(value = "status", required = false) Long status) {
+        return liveService.listBigGiftRewards(pageNum, pageSize, name, status);
+    }
 }

+ 5 - 2
fs-service-system/src/main/java/com/fs/live/domain/LiveWatchConfig.java

@@ -34,10 +34,13 @@ public class LiveWatchConfig extends BaseEntity{
     @Excel(name = "观看时长")
     private Long watchDuration;
 
-    /** 实施动作 1现金红包 2积分红包 */
-    @Excel(name = "实施动作 1现金红包 2积分红包")
+    /** 实施动作 1现金红包 2积分红包 3抽奖 */
+    @Excel(name = "实施动作 1现金红包 2积分红包 3抽奖")
     private Long action;
 
+    /** 大礼品抽奖奖励ID(action=3时使用,仅保存ID) */
+    private Long bigGiftRewardId;
+
     /** 领取提示语 */
     @Excel(name = "领取提示语")
     private String receivePrompt;

+ 6 - 0
fs-service-system/src/main/java/com/fs/live/service/ILiveService.java

@@ -3,6 +3,7 @@ package com.fs.live.service;
 
 import com.fs.common.vo.LiveVo;
 import com.fs.common.core.domain.R;
+import com.fs.common.core.page.TableDataInfo;
 import com.fs.live.domain.Live;
 import com.fs.live.vo.LiveConfigVo;
 import com.fs.live.vo.LiveListVo;
@@ -206,4 +207,9 @@ public interface ILiveService
     Integer getLiveCountByMap(Map<String, Object> params);
 
     R clearLiveCache(Long liveId);
+
+    /**
+     * 查询 his 平台大礼品奖励配置(rewardType=6)
+     */
+    TableDataInfo listBigGiftRewards(Integer pageNum, Integer pageSize, String name, Long status);
 }

+ 11 - 0
fs-service-system/src/main/java/com/fs/live/service/impl/LiveOrderServiceImpl.java

@@ -3394,6 +3394,7 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
                 liveOrderItem.setNetPrice(netPrice);
                 liveOrderItemMapper.insertLiveOrderItem(liveOrderItem);
                 redisCache.deleteObject("orderKey:" + liveOrder.getOrderKey());
+                cachePendingAppUserSync(liveOrder);
                 return R.ok("下单成功").put("order", liveOrder);
             } else {
                 return R.error("订单创建失败");
@@ -3404,6 +3405,16 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
         }
     }
 
+    private static final String LIVE_APP_USER_SYNC_PENDING_KEY_PREFIX = "live:app_user_sync:pending:";
+
+    private void cachePendingAppUserSync(LiveOrder liveOrder) {
+        if (liveOrder.getOrderId() == null || liveOrder.getAppUserId() == null) {
+            return;
+        }
+        String key = LIVE_APP_USER_SYNC_PENDING_KEY_PREFIX + liveOrder.getOrderId();
+        redisCache.setCacheObject(key, String.valueOf(liveOrder.getAppUserId()), 15, TimeUnit.DAYS);
+    }
+
     private BigDecimal handleDeliveryMoney(LiveOrder liveOrder) {
         BigDecimal storePostage = BigDecimal.ZERO;
         List<Long> citys = new ArrayList<>();

+ 76 - 2
fs-service-system/src/main/java/com/fs/live/service/impl/LiveServiceImpl.java

@@ -9,6 +9,7 @@ import com.fs.common.core.redis.service.StockDeductService;
 import com.fs.common.vo.LiveVo;
 import com.fs.common.constant.LiveKeysConstant;
 import com.fs.common.core.domain.R;
+import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.core.redis.RedisCache;
 import com.fs.common.core.redis.RedisUtil;
 import com.fs.common.exception.BaseException;
@@ -21,6 +22,8 @@ import com.fs.live.mapper.*;
 import com.fs.live.param.LiveLotteryProduct;
 import com.fs.live.param.LiveReplayParam;
 import com.fs.live.service.*;
+import com.fs.live.utils.CrossServiceMd5Util;
+import com.fs.live.utils.CrossServiceRsaUtil;
 import com.fs.live.utils.ProcessManager;
 import com.fs.live.vo.*;
 import com.fs.store.domain.FsMiniprogramSubNotifyTask;
@@ -42,8 +45,12 @@ import okhttp3.Request;
 import okhttp3.Response;
 import org.apache.ibatis.javassist.tools.web.Webserver;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.client.RestTemplate;
 import com.fs.common.utils.sign.Md5Utils;
 
 import java.io.IOException;
@@ -67,6 +74,8 @@ import java.util.stream.Collectors;
 public class LiveServiceImpl implements ILiveService
 {
 
+    private static final String BIG_GIFT_REWARD_LIST_URL = "http://42.194.245.189:8010/app/common/listBigGiftRewards";
+
     private final ILiveVideoService liveVideoService;
     private final ILiveDataService liveDataService;
 
@@ -155,7 +164,7 @@ public class LiveServiceImpl implements ILiveService
         LiveGoodsVo liveGoodsVo = liveGoodsService.showGoods(liveId);
         List<LiveRedConf> liveRedConfs = liveRedConfService.selectActivedRed(liveId);
         List<LiveLotteryConfVo> liveLotteryConfs = liveLotteryConfService.selectActivedLottery(liveId);
-        
+
         // 优化:使用传统for循环替代Stream操作,减少对象创建
         List<Long> lotteryIds = new ArrayList<>();
         if (liveLotteryConfs != null && !liveLotteryConfs.isEmpty()) {
@@ -165,7 +174,7 @@ public class LiveServiceImpl implements ILiveService
                 }
             }
         }
-        
+
         if (!lotteryIds.isEmpty()) {
             List<LiveLotteryProductListVo> products = liveLotteryProductConfMapper.selectLiveLotteryProductConfByLotteryIds(lotteryIds);
             // 优化:使用传统for循环替代Stream操作,减少对象创建
@@ -1369,4 +1378,69 @@ public class LiveServiceImpl implements ILiveService
     public Integer getLiveCountByMap(Map<String, Object> params) {
         return baseMapper.getLiveCountByMap(params);
     }
+
+    @Override
+    public TableDataInfo listBigGiftRewards(Integer pageNum, Integer pageSize, String name, Long status) {
+        TableDataInfo tableDataInfo = new TableDataInfo();
+        tableDataInfo.setCode(200);
+        tableDataInfo.setMsg("查询成功");
+        tableDataInfo.setRows(Collections.emptyList());
+        tableDataInfo.setTotal(0L);
+
+        try {
+            String encryptedStr = CrossServiceRsaUtil.encryptForRequest("reward" + System.currentTimeMillis());
+            Map<String, Object> paramMap = new HashMap<>();
+            paramMap.put("encryptedStr", encryptedStr);
+            paramMap.put("pageNum", pageNum == null || pageNum < 1 ? 1 : pageNum);
+            paramMap.put("pageSize", pageSize == null || pageSize < 1 ? 50 : pageSize);
+            if (StringUtils.isNotBlank(name)) {
+                paramMap.put("name", name);
+            }
+            if (status != null) {
+                paramMap.put("status", status);
+            }
+
+            RestTemplate restTemplate = new RestTemplate();
+            HttpHeaders headers = new HttpHeaders();
+            headers.setContentType(MediaType.APPLICATION_JSON);
+            HttpEntity<Map<String, Object>> requestEntity = new HttpEntity<>(paramMap, headers);
+            String responseBody = restTemplate.postForObject(BIG_GIFT_REWARD_LIST_URL, requestEntity, String.class);
+            if (StringUtils.isBlank(responseBody)) {
+                log.warn("查询大礼品奖励配置接口无响应");
+                return tableDataInfo;
+            }
+
+            JSONObject respObj = JSON.parseObject(responseBody);
+            if (respObj == null || !Integer.valueOf(200).equals(respObj.getInteger("code"))) {
+                log.warn("查询大礼品奖励配置接口失败: {}", responseBody);
+                tableDataInfo.setMsg(respObj != null ? respObj.getString("msg") : "查询失败");
+                return tableDataInfo;
+            }
+
+            String encryptedData = respObj.getString("data");
+            if (StringUtils.isBlank(encryptedData)) {
+                return tableDataInfo;
+            }
+
+            String decryptedJson = CrossServiceMd5Util.decrypt(encryptedData);
+            if (StringUtils.isBlank(decryptedJson)) {
+                log.warn("查询大礼品奖励配置解密失败");
+                return tableDataInfo;
+            }
+
+            JSONObject dataObj = JSON.parseObject(decryptedJson);
+            if (dataObj == null) {
+                return tableDataInfo;
+            }
+            tableDataInfo.setTotal(dataObj.getLongValue("total"));
+            if (dataObj.getJSONArray("rows") != null) {
+                tableDataInfo.setRows(dataObj.getJSONArray("rows"));
+            }
+            return tableDataInfo;
+        } catch (Exception e) {
+            log.error("查询大礼品奖励配置异常, pageNum={}, pageSize={}", pageNum, pageSize, e);
+            tableDataInfo.setMsg("查询失败");
+            return tableDataInfo;
+        }
+    }
 }

+ 5 - 0
fs-service-system/src/main/java/com/fs/store/service/IFsUserService.java

@@ -148,6 +148,11 @@ public interface IFsUserService
      */
     void syncAppUsersForRecentLiveOrders(int days);
 
+    /**
+     * 从 Redis 拉取待同步的直播订单用户并批量同步 APP 用户数据
+     */
+    void syncPendingAppUsersFromRedis();
+
     /**
      * 查询已同步的APP用户(app_sync_flag=1)
      */

+ 152 - 1
fs-service-system/src/main/java/com/fs/store/service/impl/FsUserServiceImpl.java

@@ -16,6 +16,7 @@ import cn.hutool.json.JSONUtil;
 import com.alibaba.fastjson.JSONObject;
 import com.alibaba.fastjson.TypeReference;
 import com.fs.common.core.domain.R;
+import com.fs.common.core.redis.RedisCache;
 import com.fs.common.utils.DateUtils;
 import com.fs.common.utils.IpUtil;
 import com.fs.live.domain.LiveOrder;
@@ -110,7 +111,8 @@ public class FsUserServiceImpl implements IFsUserService
     @Autowired
     LiveRewardCompensationMapper liveRewardCompensationMapper;
 
-
+    @Autowired
+    private RedisCache redisCache;
 
     Logger logger =  LoggerFactory.getLogger(FsUserServiceImpl.class);
 
@@ -744,6 +746,8 @@ public class FsUserServiceImpl implements IFsUserService
 //    }
 
     private static final String APP_USER_SYNC_URL = "http://42.194.245.189:8010/app/common/syncAppUsers";
+    private static final String APP_USER_SYNC_BY_USER_IDS_URL = "http://42.194.245.189:8010/app/common/syncAppUsersByUserIds";
+    private static final String LIVE_APP_USER_SYNC_PENDING_KEY_PREFIX = "live:app_user_sync:pending:";
     private static final int APP_USER_SYNC_BATCH_SIZE = 50;
 
     @Override
@@ -828,6 +832,153 @@ public class FsUserServiceImpl implements IFsUserService
         fsUserMapper.batchUpdateAppSyncFields(updateList);
     }
 
+    @Override
+    public void syncPendingAppUsersFromRedis() {
+        Collection<String> keys = redisCache.keys(LIVE_APP_USER_SYNC_PENDING_KEY_PREFIX + "*");
+        if (keys == null || keys.isEmpty()) {
+            return;
+        }
+
+        List<String> pendingKeys = new ArrayList<>();
+        List<Long> userIds = new ArrayList<>();
+        for (String key : keys) {
+            String userIdStr = redisCache.getCacheObject(key);
+            if (StringUtils.isBlank(userIdStr)) {
+                redisCache.deleteObject(key);
+                continue;
+            }
+            try {
+                userIds.add(Long.parseLong(userIdStr.trim()));
+                pendingKeys.add(key);
+            } catch (NumberFormatException e) {
+                redisCache.deleteObject(key);
+            }
+        }
+        if (userIds.isEmpty()) {
+            return;
+        }
+
+        List<Long> distinctUserIds = userIds.stream().distinct().collect(Collectors.toList());
+        List<FsUser> usersToSync = fsUserMapper.selectUnsyncedAppUsersByUserIds(distinctUserIds);
+        Set<Long> unsyncedUserIds = new HashSet<>();
+        if (usersToSync != null) {
+            for (FsUser user : usersToSync) {
+                if (user != null && user.getUserId() != null) {
+                    unsyncedUserIds.add(user.getUserId());
+                }
+            }
+        }
+
+        Set<Long> completedUserIds = distinctUserIds.stream()
+                .filter(id -> !unsyncedUserIds.contains(id))
+                .collect(Collectors.toSet());
+
+        if (usersToSync != null && !usersToSync.isEmpty()) {
+            Map<Long, FsUser> userByUserId = usersToSync.stream()
+                    .filter(u -> u.getUserId() != null)
+                    .collect(Collectors.toMap(FsUser::getUserId, Function.identity(), (a, b) -> a));
+
+            List<Long> syncUserIds = new ArrayList<>(userByUserId.keySet());
+            for (int i = 0; i < syncUserIds.size(); i += APP_USER_SYNC_BATCH_SIZE) {
+                int end = Math.min(i + APP_USER_SYNC_BATCH_SIZE, syncUserIds.size());
+                List<Long> batch = syncUserIds.subList(i, end);
+                try {
+                    Map<String, AppUserSyncVo> syncMap = requestAppUserSyncByUserIds(batch);
+                    if (syncMap == null || syncMap.isEmpty()) {
+                        continue;
+                    }
+                    List<FsUser> updateList = new ArrayList<>();
+                    for (Long userId : batch) {
+                        FsUser localUser = userByUserId.get(userId);
+                        if (localUser == null) {
+                            continue;
+                        }
+                        AppUserSyncVo vo = findAppUserSyncVo(syncMap, localUser);
+                        if (vo == null || vo.getAppUserId() == null) {
+                            continue;
+                        }
+                        FsUser update = new FsUser();
+                        update.setUserId(localUser.getUserId());
+                        update.setAppUserId(vo.getAppUserId());
+                        update.setAppDeptName(vo.getAppDeptName());
+                        update.setAppCompanyUserId(vo.getAppCompanyUserId());
+                        update.setAppCompanyUserName(vo.getAppCompanyUserName());
+                        update.setAppSyncFlag(1);
+                        updateList.add(update);
+                        completedUserIds.add(userId);
+                    }
+                    batchUpdateAppSyncUsers(updateList);
+                } catch (Exception e) {
+                    logger.error("Redis待同步APP用户批量同步失败, batchSize={}", batch.size(), e);
+                }
+            }
+        }
+
+        for (String key : pendingKeys) {
+            String userIdStr = redisCache.getCacheObject(key);
+            if (StringUtils.isBlank(userIdStr)) {
+                redisCache.deleteObject(key);
+                continue;
+            }
+            try {
+                Long userId = Long.parseLong(userIdStr.trim());
+                if (completedUserIds.contains(userId)) {
+                    redisCache.deleteObject(key);
+                }
+            } catch (NumberFormatException e) {
+                redisCache.deleteObject(key);
+            }
+        }
+    }
+
+    private AppUserSyncVo findAppUserSyncVo(Map<String, AppUserSyncVo> syncMap, FsUser localUser) {
+        if (localUser.getAppUserId() != null) {
+            AppUserSyncVo vo = syncMap.get(String.valueOf(localUser.getAppUserId()));
+            if (vo != null) {
+                return vo;
+            }
+        }
+        if (StringUtils.isNotBlank(localUser.getUnionId())) {
+            for (AppUserSyncVo vo : syncMap.values()) {
+                if (vo != null && localUser.getUnionId().equals(vo.getUnionId())) {
+                    return vo;
+                }
+            }
+        }
+        return null;
+    }
+
+    private Map<String, AppUserSyncVo> requestAppUserSyncByUserIds(List<Long> userIds) {
+        String encryptedStr = CrossServiceRsaUtil.encryptForRequest("user" + System.currentTimeMillis());
+        Map<String, Object> paramMap = new HashMap<>();
+        paramMap.put("encryptedStr", encryptedStr);
+        paramMap.put("userIds", userIds);
+
+        RestTemplate restTemplate = new RestTemplate();
+        HttpHeaders headers = new HttpHeaders();
+        headers.setContentType(MediaType.APPLICATION_JSON);
+        HttpEntity<Map<String, Object>> requestEntity = new HttpEntity<>(paramMap, headers);
+        String responseBody = restTemplate.postForObject(APP_USER_SYNC_BY_USER_IDS_URL, requestEntity, String.class);
+        if (StringUtils.isBlank(responseBody)) {
+            logger.warn("按userId同步APP用户接口无响应");
+            return Collections.emptyMap();
+        }
+        JSONObject respObj = JSONObject.parseObject(responseBody);
+        if (respObj == null || !"200".equals(respObj.getString("code"))) {
+            logger.warn("按userId同步APP用户接口失败: {}", responseBody);
+            return Collections.emptyMap();
+        }
+        String encryptedData = respObj.getString("data");
+        if (StringUtils.isBlank(encryptedData)) {
+            return Collections.emptyMap();
+        }
+        String decryptedJson = CrossServiceMd5Util.decrypt(encryptedData);
+        if (StringUtils.isBlank(decryptedJson)) {
+            return Collections.emptyMap();
+        }
+        return JSONObject.parseObject(decryptedJson, new TypeReference<Map<String, AppUserSyncVo>>() {});
+    }
+
     private Map<String, AppUserSyncVo> requestAppUserSync(List<String> maOpenIds) {
         String encryptedStr = CrossServiceRsaUtil.encryptForRequest("user" + System.currentTimeMillis());
         Map<String, Object> paramMap = new HashMap<>();