yuhongqi 2 viikkoa sitten
vanhempi
commit
f9410c5d0a

+ 28 - 0
fs-admin/src/main/java/com/fs/live/controller/LiveMsgController.java

@@ -3,11 +3,16 @@ package com.fs.live.controller;
 import com.fs.common.annotation.Log;
 import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.domain.R;
+import com.fs.common.core.domain.model.LoginUser;
 import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.ServletUtils;
 import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.framework.web.service.TokenService;
 import com.fs.live.domain.LiveMsg;
 import com.fs.live.service.ILiveMsgService;
+import com.fs.live.vo.LiveMsgExportVO;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
@@ -26,6 +31,9 @@ public class LiveMsgController extends BaseController
 {
     @Autowired
     private ILiveMsgService liveMsgService;
+    
+    @Autowired
+    private TokenService tokenService;
 
     /**
      * 查询直播讨论列表
@@ -102,4 +110,24 @@ public class LiveMsgController extends BaseController
     {
         return toAjax(liveMsgService.deleteLiveMsgByMsgIds(msgIds));
     }
+
+    /**
+     * 导出直播评论
+     */
+    @PreAuthorize("@ss.hasPermi('live:liveMsg:export')")
+    @Log(title = "直播评论导出", businessType = BusinessType.EXPORT)
+    @GetMapping("/exportComments/{liveId}")
+    public AjaxResult exportComments(@PathVariable("liveId") Long liveId)
+    {
+        try {
+            LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+            Long userId = loginUser.getUser().getUserId();
+            
+            List<LiveMsgExportVO> list = liveMsgService.exportLiveMsgComments(liveId, userId);
+            ExcelUtil<LiveMsgExportVO> util = new ExcelUtil<LiveMsgExportVO>(LiveMsgExportVO.class);
+            return util.exportExcel(list, "直播评论数据");
+        } catch (Exception e) {
+            return AjaxResult.error("导出失败:" + e.getMessage());
+        }
+    }
 }

+ 15 - 5
fs-admin/src/main/java/com/fs/live/controller/OrderController.java

@@ -121,7 +121,9 @@ public class OrderController extends BaseController
     public AjaxResult export(MergedOrderQueryParam param)
     {
         // 先查询数据,限制查询20001条,用于判断是否超过限制
-        PageHelper.startPage(1, maxExportCount + 1);
+        param.setExportFlag(1);
+        param.setPageNum(1);
+        param.setPageSize(maxExportCount + 1);
         List<MergedOrderVO> list = mergedOrderService.selectMergedOrderList(param);
         // 如果查询结果超过20000条,返回错误提示
         if (list != null && list.size() > maxExportCount) {
@@ -160,7 +162,9 @@ public class OrderController extends BaseController
     public AjaxResult exportDetails(MergedOrderQueryParam param)
     {
         // 先查询数据,限制查询20001条,用于判断是否超过限制
-        PageHelper.startPage(1, maxExportCount + 1);
+        param.setExportFlag(1);
+        param.setPageNum(1);
+        param.setPageSize(maxExportCount + 1);
         List<MergedOrderVO> list = mergedOrderService.selectMergedOrderList(param);
         // 如果查询结果超过20000条,返回错误提示
         if (list != null && list.size() > maxExportCount) {
@@ -200,7 +204,9 @@ public class OrderController extends BaseController
     public AjaxResult exportItems(MergedOrderQueryParam param)
     {
         // 先查询数据,限制查询20001条,用于判断是否超过限制
-        PageHelper.startPage(1, maxExportCount + 1);
+        param.setExportFlag(1);
+        param.setPageNum(1);
+        param.setPageSize(maxExportCount + 1);
         List<MergedOrderVO> list = mergedOrderService.selectMergedOrderList(param);
 
         // 如果查询结果超过20000条,返回错误提示
@@ -225,7 +231,9 @@ public class OrderController extends BaseController
     public AjaxResult exportItemsDetails(MergedOrderQueryParam param)
     {
         // 先查询数据,限制查询20001条,用于判断是否超过限制
-        PageHelper.startPage(1, maxExportCount + 1);
+        param.setExportFlag(1);
+        param.setPageNum(1);
+        param.setPageSize(maxExportCount + 1);
         List<MergedOrderVO> list = mergedOrderService.selectMergedOrderList(param);
 
         // 如果查询结果超过20000条,返回错误提示
@@ -246,7 +254,9 @@ public class OrderController extends BaseController
     public AjaxResult exportShipping(MergedOrderQueryParam param)
     {
         // 先查询数据,限制查询20001条,用于判断是否超过限制
-        PageHelper.startPage(1, maxExportCount + 1);
+        param.setExportFlag(1);
+        param.setPageNum(1);
+        param.setPageSize(maxExportCount + 1);
         List<MergedOrderVO> list = mergedOrderService.selectMergedOrderList(param);
         // 如果查询结果超过20000条,返回错误提示
         if (list != null && list.size() > maxExportCount) {

+ 19 - 0
fs-company/src/main/java/com/fs/company/controller/live/LiveMsgController.java

@@ -10,6 +10,7 @@ import com.fs.company.domain.CompanyUser;
 import com.fs.framework.security.SecurityUtils;
 import com.fs.live.domain.LiveMsg;
 import com.fs.live.service.ILiveMsgService;
+import com.fs.live.vo.LiveMsgExportVO;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
 
@@ -105,4 +106,22 @@ public class LiveMsgController extends BaseController
         return toAjax(liveMsgService.deleteLiveMsgByMsgIds(msgIds));
     }
 
+    /**
+     * 导出直播评论
+     */
+    @Log(title = "直播评论导出", businessType = BusinessType.EXPORT)
+    @GetMapping("/exportComments/{liveId}")
+    public AjaxResult exportComments(@PathVariable("liveId") Long liveId)
+    {
+        try {
+            CompanyUser user = SecurityUtils.getLoginUser().getUser();
+            Long userId = user.getUserId();
+            
+            List<LiveMsgExportVO> list = liveMsgService.exportLiveMsgComments(liveId, userId);
+            ExcelUtil<LiveMsgExportVO> util = new ExcelUtil<LiveMsgExportVO>(LiveMsgExportVO.class);
+            return util.exportExcel(list, "直播评论数据");
+        } catch (Exception e) {
+            return AjaxResult.error("导出失败:" + e.getMessage());
+        }
+    }
 }

+ 0 - 31
fs-service/src/main/java/com/fs/erp/service/impl/JSTErpOrderServiceImpl.java

@@ -524,25 +524,8 @@ public class JSTErpOrderServiceImpl implements IErpOrderService {
         OrderQueryRequestDTO requestDTO = new OrderQueryRequestDTO();
         requestDTO.setOIds(Collections.singletonList(Long.valueOf(param.getCode())));
 
-        // 限流检查:每分钟最多100次请求,超过95次返回429
-        String rateLimitKey = RATE_LIMIT_KEY_PREFIX + System.currentTimeMillis() / 60000; // 每分钟一个key
-
-        // 使用原子操作增加计数,并获取增加后的值
-        Long currentCount = redisCache.incr(rateLimitKey, 1L);
-
-        // 如果是第一次请求,设置过期时间为1分钟
-        if (currentCount == 1) {
-            redisCache.expire(rateLimitKey, 1, TimeUnit.MINUTES);
-        }
         // 3. 构建响应对象
         ErpOrderQueryResponse response = new ErpOrderQueryResponse();
-        // 如果当前分钟内请求次数超过95次,直接返回429错误
-        if (currentCount >= RATE_LIMIT_THRESHOLD) {
-            response.setCode("429");
-            response.setSuccess(false);
-            return response;
-        }
-
 
         // 2. 调用ERP服务查询订单
         OrderQueryResponseDTO query = jstErpHttpService.query(requestDTO);
@@ -565,24 +548,10 @@ public class JSTErpOrderServiceImpl implements IErpOrderService {
 
     @Override
     public ErpOrderQueryResponse getLiveOrder(ErpOrderQueryRequert param) {
-        // 限流检查:每分钟最多100次请求,超过95次返回429
-        String rateLimitKey = RATE_LIMIT_KEY_PREFIX + System.currentTimeMillis() / 60000; // 每分钟一个key
 
-        // 使用原子操作增加计数,并获取增加后的值
-        Long currentCount = redisCache.incr(rateLimitKey, 1L);
 
-        // 如果是第一次请求,设置过期时间为1分钟
-        if (currentCount == 1) {
-            redisCache.expire(rateLimitKey, 1, TimeUnit.MINUTES);
-        }
         // 3. 构建响应对象
         ErpOrderQueryResponse response = new ErpOrderQueryResponse();
-        // 如果当前分钟内请求次数超过95次,直接返回429错误
-        if (currentCount >= RATE_LIMIT_THRESHOLD) {
-            response.setCode("429");
-            response.setSuccess(false);
-            return response;
-        }
 
         // 1. 构建查询请求DTO
         OrderQueryRequestDTO requestDTO = new OrderQueryRequestDTO();

+ 8 - 0
fs-service/src/main/java/com/fs/live/mapper/LiveMsgMapper.java

@@ -84,4 +84,12 @@ public interface LiveMsgMapper
     Map<String, BigDecimal> selectDashboardCount(@Param("liveId") Long liveId);
 
     List<LiveMsg> selectLiveMsgSingleList(LiveMsg liveMsg);
+
+    /**
+     * 查询直播评论用于导出
+     *
+     * @param liveId 直播ID
+     * @return 评论列表
+     */
+    List<com.fs.live.vo.LiveMsgExportVO> selectLiveMsgForExport(@Param("liveId") Long liveId);
 }

+ 3 - 0
fs-service/src/main/java/com/fs/live/param/MergedOrderQueryParam.java

@@ -123,5 +123,8 @@ public class MergedOrderQueryParam extends BaseQueryParam implements Serializabl
     
     /** 分页偏移量(在外部计算后传入,不在SQL中计算) */
     private Integer offset;
+
+    /** 分页偏移量(在外部计算后传入,不在SQL中计算) */
+    private Integer exportFlag;
 }
 

+ 9 - 0
fs-service/src/main/java/com/fs/live/service/ILiveMsgService.java

@@ -69,4 +69,13 @@ public interface ILiveMsgService
     List<LiveMsg> listRecentMsg(Long id);
 
     List<LiveMsg> selectLiveMsgSingleList(LiveMsg liveMsg);
+
+    /**
+     * 导出直播评论
+     *
+     * @param liveId 直播ID
+     * @param userId 用户ID(用于Redis加锁)
+     * @return 评论列表
+     */
+    List<com.fs.live.vo.LiveMsgExportVO> exportLiveMsgComments(Long liveId, Long userId);
 }

+ 77 - 0
fs-service/src/main/java/com/fs/live/service/impl/LiveMsgServiceImpl.java

@@ -1,15 +1,23 @@
 package com.fs.live.service.impl;
 
 
+import com.fs.common.core.domain.R;
+import com.fs.common.exception.CustomException;
 import com.fs.common.utils.DateUtils;
 import com.fs.live.domain.LiveMsg;
 import com.fs.live.mapper.LiveMsgMapper;
 import com.fs.live.service.ILiveMsgService;
+import com.fs.live.vo.LiveMsgExportVO;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.core.script.DefaultRedisScript;
 import org.springframework.stereotype.Service;
 
 import java.util.Collections;
 import java.util.List;
+import java.util.concurrent.TimeUnit;
 
 /**
  * 直播讨论Service业务层处理
@@ -20,10 +28,20 @@ import java.util.List;
 @Service
 public class LiveMsgServiceImpl implements ILiveMsgService
 {
+    private static final Logger log = LoggerFactory.getLogger(LiveMsgServiceImpl.class);
+    
     @Autowired
     private LiveMsgMapper liveMsgMapper;
     @Autowired
     private LiveDataServiceImpl liveDataService;
+    
+    @Autowired(required = false)
+    private RedisTemplate<String, Object> redisTemplate;
+    
+    /** Redis锁前缀 */
+    private static final String LOCK_PREFIX = "live:msg:export:lock:";
+    /** 锁过期时间(秒) */
+    private static final long LOCK_EXPIRE_TIME = 300; // 5分钟
 
     /**
      * 查询直播讨论
@@ -108,4 +126,63 @@ public class LiveMsgServiceImpl implements ILiveMsgService
     public List<LiveMsg> selectLiveMsgSingleList(LiveMsg liveMsg) {
         return liveMsgMapper.selectLiveMsgSingleList(liveMsg);
     }
+
+    @Override
+    public List<LiveMsgExportVO> exportLiveMsgComments(Long liveId, Long userId) {
+        if (liveId == null) {
+            throw new CustomException("直播ID不能为空");
+        }
+        if (userId == null) {
+            throw new CustomException("用户ID不能为空");
+        }
+
+        // Redis锁的key:用户ID + 直播间ID
+        String lockKey = LOCK_PREFIX + userId + ":" + liveId;
+        String lockValue = String.valueOf(System.currentTimeMillis());
+
+        try {
+            // 尝试获取锁
+            Boolean lockAcquired = false;
+            if (redisTemplate != null) {
+                lockAcquired = redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, LOCK_EXPIRE_TIME, TimeUnit.SECONDS);
+            }
+
+            if (redisTemplate != null && !lockAcquired) {
+                log.warn("用户{}正在导出直播间{}的评论,请勿重复操作", userId, liveId);
+                throw new CustomException("正在导出中,请勿重复操作");
+            }
+
+            try {
+                // 查询评论数据
+                List<LiveMsgExportVO> list = liveMsgMapper.selectLiveMsgForExport(liveId);
+                log.info("用户{}导出直播间{}的评论,共{}条", userId, liveId, list != null ? list.size() : 0);
+                return list != null ? list : Collections.emptyList();
+            } finally {
+                // 释放锁
+                if (redisTemplate != null && lockAcquired) {
+                    // 使用Lua脚本确保只删除自己的锁
+                    String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
+                            "return redis.call('del', KEYS[1]) " +
+                            "else return 0 end";
+                    DefaultRedisScript<Long> script = new DefaultRedisScript<>();
+                    script.setScriptText(luaScript);
+                    script.setResultType(Long.class);
+                    redisTemplate.execute(script, Collections.singletonList(lockKey), lockValue);
+                }
+            }
+        } catch (CustomException e) {
+            throw e;
+        } catch (Exception e) {
+            log.error("导出直播评论失败,liveId: {}, userId: {}", liveId, userId, e);
+            // 确保异常时也释放锁
+            if (redisTemplate != null) {
+                try {
+                    redisTemplate.delete(lockKey);
+                } catch (Exception ex) {
+                    log.error("释放Redis锁失败", ex);
+                }
+            }
+            throw new CustomException("导出失败:" + e.getMessage());
+        }
+    }
 }

+ 30 - 0
fs-service/src/main/java/com/fs/live/vo/LiveMsgExportVO.java

@@ -0,0 +1,30 @@
+package com.fs.live.vo;
+
+import com.fs.common.annotation.Excel;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 直播评论导出VO
+ *
+ * @author fs
+ * @date 2026-01-13
+ */
+@Data
+public class LiveMsgExportVO implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    /** 用户昵称 */
+    @Excel(name = "用户昵称")
+    private String nickName;
+
+    /** 评论内容 */
+    @Excel(name = "评论内容")
+    private String msg;
+
+    /** 发送时间 */
+    @Excel(name = "发送时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
+    private Date createTime;
+}

+ 1 - 1
fs-service/src/main/java/com/fs/wx/order/mapper/FsWxExpressTaskMapper.java

@@ -16,7 +16,7 @@ public interface FsWxExpressTaskMapper {
      * @param id 任务ID
      * @return FsWxExpressTask 任务实体
      */
-    @Select("SELECT * FROM f s_wx_express_task WHERE id = #{id}")
+    @Select("SELECT * FROM fs_wx_express_task WHERE id = #{id}")
     FsWxExpressTask selectById(@Param("id") Long id);
 
     /**

+ 1 - 0
fs-service/src/main/java/com/fs/wx/order/service/ShippingService.java

@@ -83,6 +83,7 @@ public class ShippingService {
                 if (!weChatApiResponse.isSuccess()) {
                     log.warn("微信接口返回业务错误: code={}, message={}", weChatApiResponse.getErrcode(), weChatApiResponse.getErrmsg());
                     if(ObjectUtil.equal(weChatApiResponse.getErrcode(),40001)) {
+                        accessToken = weChatAuthService.getAccessToken(true);
                         log.info("token缓存失效,清除token,等待下次执行...");
                     }
                 }

+ 9 - 2
fs-service/src/main/resources/mapper/hisStore/MergedOrderMapper.xml

@@ -119,7 +119,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             AND DATE(delivery_import_time) BETWEEN SUBSTRING_INDEX(#{maps.deliveryImportTimeRange}, '--', 1) AND SUBSTRING_INDEX(#{maps.deliveryImportTimeRange}, '--', -1)
           </if>
         ORDER BY create_time DESC
+      <if test="maps.exportFlag == null or maps.exportFlag == ''">
         limit 1000
+      </if>
+
       ) o
       left join ( SELECT fsois.*, ROW_NUMBER() OVER ( PARTITION BY fsois.order_id ORDER BY fsois.item_id ) AS rn FROM fs_store_order_item_scrm fsois ) item_latest ON item_latest.order_id = o.id and item_latest.rn = 1
       LEFT JOIN fs_user u ON o.user_id = u.user_id
@@ -273,7 +276,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             AND DATE(delivery_import_time) BETWEEN SUBSTRING_INDEX(#{maps.deliveryImportTimeRange}, '--', 1) AND SUBSTRING_INDEX(#{maps.deliveryImportTimeRange}, '--', -1)
           </if>
         ORDER BY create_time DESC
-      limit 1000
+      <if test="maps.exportFlag == null or maps.exportFlag == ''">
+        limit 1000
+      </if>
       ) o
         left join ( SELECT fsois.*, ROW_NUMBER() OVER ( PARTITION BY fsois.order_id ORDER BY fsois.item_id ) AS rn FROM fs_store_order_item_scrm fsois ) item_latest ON item_latest.order_id = o.id and item_latest.rn = 1
       LEFT JOIN fs_user u ON o.user_id = u.user_id
@@ -424,7 +429,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             AND DATE(delivery_send_time) BETWEEN SUBSTRING_INDEX(#{maps.deliverySendTimeRange}, '--', 1) AND SUBSTRING_INDEX(#{maps.deliverySendTimeRange}, '--', -1)
           </if>
         ORDER BY create_time DESC
-      limit 1000
+      <if test="maps.exportFlag == null or maps.exportFlag == ''">
+        limit 1000
+      </if>
       ) o
       left join live_order_item loi on loi.order_id = o.order_id
       LEFT JOIN fs_user u ON o.user_id = u.user_id

+ 12 - 0
fs-service/src/main/resources/mapper/live/LiveMsgMapper.xml

@@ -109,4 +109,16 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             #{msgId}
         </foreach>
     </delete>
+
+    <!-- 导出直播评论数据 -->
+    <select id="selectLiveMsgForExport" resultType="com.fs.live.vo.LiveMsgExportVO">
+        SELECT 
+            nick_name as nickName,
+            msg,
+            create_time as createTime
+        FROM live_msg
+        WHERE live_id = #{liveId}
+        ORDER BY create_time ASC
+        LIMIT 30000
+    </select>
 </mapper>

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

@@ -175,7 +175,7 @@ public class LiveFacadeServiceImpl extends BaseController implements LiveFacadeS
             liveVo.setTodayRewardReceived(false);
         }
         
-        return R.ok().put("data", liveVo);
+        return R.ok().put("serviceTime", System.currentTimeMillis()).put("data", liveVo);
     }
     
     /**