Просмотр исходного кода

Merge branch 'master' into 康年堂

csy 1 день назад
Родитель
Сommit
70809071f6
54 измененных файлов с 1083 добавлено и 199 удалено
  1. 1 1
      fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreAfterSalesScrmController.java
  2. 2 1
      fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreStatisticsScrmController.java
  3. 38 0
      fs-admin/src/main/java/com/fs/hisStore/task/MallStoreTask.java
  4. 2 0
      fs-common/src/main/java/com/fs/common/constant/LiveKeysConstant.java
  5. 89 5
      fs-company/src/main/java/com/fs/company/controller/company/IndexStatisticsController.java
  6. 6 0
      fs-company/src/main/java/com/fs/company/controller/qw/QwUserController.java
  7. 2 1
      fs-company/src/main/java/com/fs/hisStore/controller/FsStoreStatisticsScrmController.java
  8. 5 0
      fs-live-app/src/main/java/com/fs/framework/aspectj/LiveWatchUserAspect.java
  9. 77 9
      fs-live-app/src/main/java/com/fs/live/task/Task.java
  10. 5 1
      fs-live-app/src/main/java/com/fs/live/websocket/service/WebSocketServer.java
  11. 1 0
      fs-service/src/main/java/com/fs/company/mapper/CompanyUserMapper.java
  12. 2 0
      fs-service/src/main/java/com/fs/company/service/impl/CompanyServiceImpl.java
  13. 9 0
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCoursePeriodServiceImpl.java
  14. 22 21
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java
  15. 54 0
      fs-service/src/main/java/com/fs/enums/ExceptionCodeEnum.java
  16. 26 0
      fs-service/src/main/java/com/fs/fastGpt/service/impl/AiHookServiceImpl.java
  17. 2 0
      fs-service/src/main/java/com/fs/his/enums/FsUserIntegralLogTypeEnum.java
  18. 6 0
      fs-service/src/main/java/com/fs/hisStore/domain/FsStoreAfterSalesScrm.java
  19. 4 1
      fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreAfterSalesScrmMapper.java
  20. 7 0
      fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreOrderScrmMapper.java
  21. 1 1
      fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreProductScrmMapper.java
  22. 9 0
      fs-service/src/main/java/com/fs/hisStore/mapper/FsUserScrmMapper.java
  23. 3 0
      fs-service/src/main/java/com/fs/hisStore/param/FsMyStoreOrderQueryParam.java
  24. 3 0
      fs-service/src/main/java/com/fs/hisStore/param/FsStoreProductPackageQueryParam.java
  25. 3 3
      fs-service/src/main/java/com/fs/hisStore/service/IFsStoreProductScrmService.java
  26. 8 4
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreProductScrmServiceImpl.java
  27. 2 1
      fs-service/src/main/java/com/fs/hisStore/vo/FsStoreOrderStatisticsVO.java
  28. 6 0
      fs-service/src/main/java/com/fs/live/mapper/LiveCompletionPointsRecordMapper.java
  29. 143 113
      fs-service/src/main/java/com/fs/live/service/impl/LiveAfterSalesServiceImpl.java
  30. 2 2
      fs-service/src/main/java/com/fs/live/service/impl/LiveCompletionPointsRecordServiceImpl.java
  31. 10 8
      fs-service/src/main/java/com/fs/live/service/impl/LiveOrderServiceImpl.java
  32. 36 1
      fs-service/src/main/java/com/fs/live/service/impl/LiveRedConfServiceImpl.java
  33. 7 0
      fs-service/src/main/java/com/fs/live/service/impl/LiveServiceImpl.java
  34. 31 9
      fs-service/src/main/java/com/fs/live/service/impl/LiveWatchUserServiceImpl.java
  35. 2 1
      fs-service/src/main/java/com/fs/live/vo/LiveVo.java
  36. 3 0
      fs-service/src/main/java/com/fs/qw/domain/QwUser.java
  37. 1 0
      fs-service/src/main/java/com/fs/qw/service/IQwUserService.java
  38. 18 0
      fs-service/src/main/java/com/fs/qw/service/impl/QwUserServiceImpl.java
  39. 2 0
      fs-service/src/main/java/com/fs/qw/vo/QwUserVO.java
  40. 6 6
      fs-service/src/main/resources/application-config-druid-shdn.yml
  41. 6 0
      fs-service/src/main/resources/mapper/company/CompanyUserMapper.xml
  42. 2 0
      fs-service/src/main/resources/mapper/his/FsUserIntegralLogsMapper.xml
  43. 9 0
      fs-service/src/main/resources/mapper/hisStore/FsStoreProductScrmMapper.xml
  44. 3 3
      fs-service/src/main/resources/mapper/hisStore/MergedOrderMapper.xml
  45. 8 2
      fs-service/src/main/resources/mapper/live/LiveAfterSalesMapper.xml
  46. 9 0
      fs-service/src/main/resources/mapper/live/LiveCompletionPointsRecordMapper.xml
  47. 7 1
      fs-service/src/main/resources/mapper/qw/QwUserMapper.xml
  48. 7 1
      fs-user-app/src/main/java/com/fs/app/controller/course/CourseQwController.java
  49. 296 0
      fs-user-app/src/main/java/com/fs/app/controller/live/LiveCompletionPointsController.java
  50. 2 2
      fs-user-app/src/main/java/com/fs/app/controller/store/IndexScrmController.java
  51. 1 1
      fs-user-app/src/main/java/com/fs/app/controller/store/ProductScrmController.java
  52. 25 0
      fs-user-app/src/main/java/com/fs/app/vo/ReceivePointsVO.java
  53. 31 0
      fs-user-app/src/main/java/com/fs/app/vo/RemainingTimeVO.java
  54. 21 0
      fs-user-app/src/main/java/com/fs/app/vo/UpdateWatchDurationVO.java

+ 1 - 1
fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreAfterSalesScrmController.java

@@ -123,7 +123,7 @@ public class FsStoreAfterSalesScrmController extends BaseController
                             zmvo.setRealName(vo.getUserName());
                             zmvo.setUserPhone(vo.getUserPhone());
                             zmvo.setUserAddress(vo.getUserAddress());
-                            zmvo.setCreateTime(vo.getCreateTime());
+                            zmvo.setCreateTime(vo.getOrderCreateTime());
                             zmvo.setPayTime(vo.getOrderPayTime());
                             zmvo.setDeliverySn(vo.getOrderDeliverySn());
                             zmvo.setDeliveryName(vo.getOrderDeliveryName());

+ 2 - 1
fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreStatisticsScrmController.java

@@ -19,6 +19,7 @@ import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 
+import java.math.BigDecimal;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.stream.Collectors;
@@ -76,7 +77,7 @@ public class FsStoreStatisticsScrmController extends BaseController
             List<JSONObject> jsonObjectList = storeOrderService.selectFsStoreOrderCounts(timeEntity.toMap());
             List<String> dates = jsonObjectList.stream().map(jsonObject -> jsonObject.getString("type")).collect(Collectors.toList());
             List<Integer> orderCount = jsonObjectList.stream().map(jsonObject -> jsonObject.getInteger("orderCount")).collect(Collectors.toList());
-            List<Integer> payPrice = jsonObjectList.stream().map(jsonObject -> jsonObject.getInteger("payPrice")).collect(Collectors.toList());
+            List<BigDecimal> payPrice = jsonObjectList.stream().map(jsonObject -> jsonObject.getBigDecimal("payPrice")).collect(Collectors.toList());
             //表格数据
             List<FsStoreOrderCountsVO> tableData = storeOrderService.selectFsStoreOrderCountsByDept(timeEntity.toMap(),param.getDeptId());
             return R.ok().put("dates",dates).put("orderCount",orderCount).put("payPrice",payPrice).put("tableData",tableData);

+ 38 - 0
fs-admin/src/main/java/com/fs/hisStore/task/MallStoreTask.java

@@ -34,7 +34,11 @@ import com.fs.hisStore.mapper.FsStorePaymentScrmMapper;
 import com.fs.hisStore.mapper.FsStoreProductAttrValueScrmMapper;
 import com.fs.hisStore.param.*;
 import com.fs.hisStore.service.*;
+import com.fs.huifuPay.domain.HuiFuQueryOrderResult;
+import com.fs.huifuPay.sdk.opps.core.request.V2TradePaymentScanpayQueryRequest;
+import com.fs.huifuPay.service.HuiFuService;
 import com.fs.live.domain.LiveOrder;
+import com.fs.live.domain.LiveOrderPayment;
 import com.fs.pay.pay.dto.OrderQueryDTO;
 import com.fs.pay.service.IPayService;
 import com.fs.store.config.StoreConfig;
@@ -50,6 +54,7 @@ import org.springframework.stereotype.Component;
 
 import java.math.BigDecimal;
 import java.text.ParseException;
+import java.text.SimpleDateFormat;
 import java.time.LocalTime;
 import java.util.ArrayList;
 import java.util.Date;
@@ -160,6 +165,39 @@ public class MallStoreTask
 
     //@Autowired
     //private IFsUserOnlineStateService fsUserOnlineStateService;
+    @Autowired
+    private HuiFuService huiFuService;
+
+    // 订单银行回调数据丢失补偿
+    public void recoveryBankOrder() {
+        // 查询出来最近15分钟的订单 待支付 未退款
+        List<FsStoreOrderScrm> list = fsStoreOrderMapper.selectBankOrder();
+        if(list == null || list.isEmpty()) return;
+        for (FsStoreOrderScrm order : list) {
+            List<FsStorePaymentScrm> orderPayments = fsStorePaymentMapper.selectFsStorePaymentByOrderId(order.getId());
+            if(orderPayments == null || orderPayments.isEmpty()) continue;
+            for (FsStorePaymentScrm payment : orderPayments) {
+                V2TradePaymentScanpayQueryRequest request = new V2TradePaymentScanpayQueryRequest();
+                request.setOrgReqDate(new SimpleDateFormat("yyyyMMdd").format(payment.getCreateTime()));
+                request.setOrgHfSeqId(payment.getTradeNo());
+                request.setAppId(payment.getAppId());
+                HuiFuQueryOrderResult o = null;
+                try {
+                    o = huiFuService.queryOrder(request);
+                } catch (Exception e) {
+                    log.error("查询失败:"+e.getMessage());
+                    continue;
+                }
+                log.info("汇付返回"+o);
+                if ("00000000".equals(o.getResp_code()) && "S".equals(o.getTrans_stat())) {
+                    String[] orderSpilt=o.getOrg_req_seq_id().split("-");
+                    if ("store".equals(orderSpilt[0])) {
+                        orderService.payConfirm(1, null, orderSpilt[1], o.getOrg_hf_seq_id(), o.getOut_trans_id(), o.getParty_order_id());
+                    }
+                }
+            }
+        }
+    }
 
     public void PushErp() throws ParseException {
         List<Long> ids;

+ 2 - 0
fs-common/src/main/java/com/fs/common/constant/LiveKeysConstant.java

@@ -37,6 +37,8 @@ public class LiveKeysConstant {
     public static final Integer PRODUCT_DETAIL_CACHE_EXPIRE = 300; //商品详情缓存过期时间(秒)
 
     public static final String LIVE_TAG_MARK_CACHE = "live:tag:mark:%s"; //直播间打标签缓存,存储直播间ID、开始时间和视频时长
+    //记录用户观看直播间信息 直播间id、用户id、外部联系人id、qwUserId
+    public static final String LIVE_USER_WATCH_LOG_CACHE = "live:user:watch:log:%s:%s:%s:%s";
 
 
 }

+ 89 - 5
fs-company/src/main/java/com/fs/company/controller/company/IndexStatisticsController.java

@@ -3,6 +3,9 @@ package com.fs.company.controller.company;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.redis.RedisCache;
 import com.fs.common.utils.ServletUtils;
+import com.fs.company.domain.Company;
+import com.fs.company.service.ICompanyService;
+import com.fs.config.cloud.CloudHostProper;
 import com.fs.framework.security.LoginUser;
 import com.fs.framework.service.TokenService;
 import com.fs.statis.StatisticsRedisConstant;
@@ -12,7 +15,10 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
 
 import java.util.ArrayList;
+import java.util.Comparator;
 import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
 
 import static com.fs.statis.StatisticsRedisConstant.*;
 
@@ -31,6 +37,11 @@ public class IndexStatisticsController {
     @Autowired
     private IStatisticsService statisticsService;
 
+    @Autowired
+    private ICompanyService companyService;
+
+    @Autowired
+    CloudHostProper cloudHostProper;
     /**
      * 分析概览
      */
@@ -94,12 +105,85 @@ public class IndexStatisticsController {
             userType = 0;
         }
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
-        Long companyId = loginUser.getCompany().getCompanyId();
-        param.setCompanyId(companyId);
 
-        String key = String.format("%s:%d:%d:%d", DATA_OVERVIEW_DEALER_CHARTS, type,userType,param.getCompanyId());
-        List<DeaMemberTopTenDTO> deaMemberTopTenDTOS = redisCache.getCacheObject(key);
-        return R.ok().put("data", deaMemberTopTenDTOS);
+        if("四川致医".equals(cloudHostProper.getCompanyName())){
+            Long companyId1 = loginUser.getCompany().getCompanyId();
+            Company company = loginUser.getCompany();
+            param.setCompanyId(companyId1);
+            List<WatchEndPlayTrendDTO> watchEndPlayTrendDTOS;
+
+            // 参考watchCourseTopTen方法的处理逻辑
+            if ((param.getCompanyId() == null && param.getDeptId() == null) || (param.getCompanyId() == null && param.getDeptId() == 1)){
+                String key = String.format("%s:%d:%d", DATA_OVERVIEW_DEALER_CHARTS, type,userType);
+                watchEndPlayTrendDTOS = redisCache.getCacheObject(key);
+            }else if(param.getCompanyId() != null){
+                String key = String.format("%s:%d:%d:%d", DATA_OVERVIEW_DEALER_CHARTS, type,userType,param.getCompanyId());
+                watchEndPlayTrendDTOS = redisCache.getCacheObject(key);
+            }else{
+                Long[] companyIds = companyService.selectCompanyList(company).stream().map(Company::getCompanyId).toArray(Long[]::new);
+                List<WatchEndPlayTrendDTO> tempDTOS = new ArrayList<>();
+                for(Long companyId : companyIds){
+                    String key = String.format("%s:%d:%d:%d", DATA_OVERVIEW_DEALER_CHARTS, type,userType,companyId);
+                    List<WatchEndPlayTrendDTO> companyData = redisCache.getCacheObject(key);
+                    if (companyData != null) {
+                        tempDTOS.addAll(companyData);
+                    }
+                }
+                // 根据startDate 和 x 分组,合并watchUserCount和completedUserCount 限制最多返回10条记录
+                watchEndPlayTrendDTOS = tempDTOS.stream()
+                        .collect(Collectors.groupingBy(
+                                dto -> dto.getStartDate() + ":" + dto.getX(),  // 根据startDate和x分组
+                                Collectors.reducing(new WatchEndPlayTrendDTO(), (dto1, dto2) -> {
+                                    // 合并watchUserCount和completedUserCount
+                                    WatchEndPlayTrendDTO result = new WatchEndPlayTrendDTO();
+                                    // 复制分组标识字段
+                                    if (dto2 != null && dto2.getStartDate() != null) {
+                                        result.setStartDate(dto2.getStartDate());
+                                    } else if (dto1 != null) {
+                                        result.setStartDate(dto1.getStartDate());
+                                    }
+
+                                    if (dto2 != null && dto2.getX() != null) {
+                                        result.setX(dto2.getX());
+                                    } else if (dto1 != null) {
+                                        result.setX(dto1.getX());
+                                    }
+
+                                    // 合并数值字段
+                                    result.setWatchUserCount(
+                                            (dto1 == null || dto1.getWatchUserCount() == null ? 0 : dto1.getWatchUserCount()) +
+                                                    (dto2 == null || dto2.getWatchUserCount() == null ? 0 : dto2.getWatchUserCount())
+                                    );
+
+                                    result.setCompletedUserCount(
+                                            (dto1 == null || dto1.getCompletedUserCount() == null ? 0 : dto1.getCompletedUserCount()) +
+                                                    (dto2 == null || dto2.getCompletedUserCount() == null ? 0 : dto2.getCompletedUserCount())
+                                    );
+
+                                    return result;
+                                })
+                        ))
+                        .values()
+                        .stream()
+                        .filter(Objects::nonNull)  // 过滤掉null值
+                        .limit(10)
+                        .sorted(Comparator.comparing(WatchEndPlayTrendDTO::getX))
+                        .collect(Collectors.toList());
+            }
+
+            if(watchEndPlayTrendDTOS == null){
+                watchEndPlayTrendDTOS = new ArrayList<>();
+            }
+
+            return R.ok().put("data", watchEndPlayTrendDTOS);
+        }else{
+            Long companyId = loginUser.getCompany().getCompanyId();
+            param.setCompanyId(companyId);
+
+            String key = String.format("%s:%d:%d:%d", DATA_OVERVIEW_DEALER_CHARTS, type,userType,param.getCompanyId());
+            List<DeaMemberTopTenDTO> deaMemberTopTenDTOS = redisCache.getCacheObject(key);
+            return R.ok().put("data", deaMemberTopTenDTOS);
+        }
     }
 
     /**

+ 6 - 0
fs-company/src/main/java/com/fs/company/controller/qw/QwUserController.java

@@ -952,4 +952,10 @@ public class QwUserController extends BaseController
         List<QwUserVO> list = qwUserService.selectQwUserListByCompanyIdAndCorpIdAndNickName(companyId, corpId, nickName);
         return getDataTable(list);
     }
+
+    @GetMapping("/updateFastGptRoleStatusById/{id}")
+    public R updateFastGptRoleStatusById(@PathVariable Long id)
+    {
+        return qwUserService.updateQwUserFastGptRoleStatusById(id);
+    }
 }

+ 2 - 1
fs-company/src/main/java/com/fs/hisStore/controller/FsStoreStatisticsScrmController.java

@@ -21,6 +21,7 @@ import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 
+import java.math.BigDecimal;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.stream.Collectors;
@@ -80,7 +81,7 @@ public class FsStoreStatisticsScrmController extends BaseController
             List<JSONObject> jsonObjectList = storeOrderService.selectFsStoreOrderCounts(timeEntity.toMap());
             List<String> dates = jsonObjectList.stream().map(jsonObject -> jsonObject.getString("type")).collect(Collectors.toList());
             List<Integer> orderCount = jsonObjectList.stream().map(jsonObject -> jsonObject.getInteger("orderCount")).collect(Collectors.toList());
-            List<Integer> payPrice = jsonObjectList.stream().map(jsonObject -> jsonObject.getInteger("payPrice")).collect(Collectors.toList());
+            List<BigDecimal> payPrice = jsonObjectList.stream().map(jsonObject -> jsonObject.getBigDecimal("payPrice")).collect(Collectors.toList());
             return R.ok().put("list",list).put("dates",dates).put("orderCount",orderCount).put("payPrice",payPrice);
         }
         else {

+ 5 - 0
fs-live-app/src/main/java/com/fs/framework/aspectj/LiveWatchUserAspect.java

@@ -8,6 +8,9 @@ import org.aspectj.lang.JoinPoint;
 import org.aspectj.lang.annotation.AfterReturning;
 import org.aspectj.lang.annotation.Aspect;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.core.Ordered;
+import org.springframework.core.annotation.Order;
 import org.springframework.stereotype.Component;
 
 import java.util.Arrays;
@@ -17,11 +20,13 @@ import java.util.Set;
 @Aspect
 @Component
 @Slf4j
+@Order(Ordered.LOWEST_PRECEDENCE - 1)  // 调整切面优先级
 public class LiveWatchUserAspect {
 
 
 
     @Autowired
+    @Lazy
     private ILiveWatchUserService liveWatchUserService;
 
     @AfterReturning(pointcut = "execution(* com.fs.live.service.impl.LiveWatchUserServiceImpl.insertLiveWatchUser(..)) || " +

+ 77 - 9
fs-live-app/src/main/java/com/fs/live/task/Task.java

@@ -33,6 +33,7 @@ import javax.annotation.PostConstruct;
 import java.math.BigDecimal;
 import java.time.Instant;
 import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
 import java.util.*;
 import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
@@ -812,6 +813,7 @@ public class Task {
     @DistributeLock(key = "scanLiveWatchUserStatus", scene = "task")
     public void scanLiveWatchUserStatus() {
         try {
+            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
             // 查询所有正在直播的直播间
             List<Live> activeLives = liveService.selectNoEndLiveList();
             if (activeLives == null || activeLives.isEmpty()) {
@@ -864,7 +866,7 @@ public class Task {
                             if (onlineSeconds == null || onlineSeconds <= 0) {
                                 continue;
                             }
-                            
+
                             // 获取用户的 companyId 和 companyUserId
                             LiveUserFirstEntry liveUserFirstEntry =
                                     liveUserFirstEntryService.selectEntityByLiveIdUserIdWithCache(liveId, userId);
@@ -878,7 +880,10 @@ public class Task {
                             if (qwUserId == null || qwUserId <= 0 || externalContactId == null || externalContactId <= 0) {
                                 continue;
                             }
-
+                            //更新最新用户活跃时间
+                            String liveUserWatchLogKey = String.format(LIVE_USER_WATCH_LOG_CACHE, liveId, userId,externalContactId,qwUserId);
+                            LocalDateTime now = LocalDateTime.now();
+                            redisCache.setCacheObject(liveUserWatchLogKey,formatter.format(now),5,TimeUnit.MINUTES);
                             // 使用 updateLiveWatchLogTypeByDuration 的逻辑更新观看记录状态
                             updateLiveWatchLogTypeByDuration(liveId, userId, qwUserId, externalContactId,
                                     onlineSeconds, totalVideoDuration, updateLog);
@@ -942,13 +947,13 @@ public class Task {
                 boolean needUpdate = false;
                 Integer newLogType = log.getLogType();
 
-                // ① 如果在线时长 <= 3分钟,修改 logType 为 4(看课中断)
-                if (onlineSeconds <= 180) { // 3分钟 = 180秒
-                    newLogType = 4;
-                    needUpdate = true;
-                }
-                // ③ 如果直播视频 >= 40分钟,在线时长 >= 30分钟,logType 设置为 2(完课)
-                else if (totalVideoDuration >= 2400 && onlineSeconds >= 1800) { // 40分钟 = 2400秒,30分钟 = 1800秒
+                // ① 如果在线时长 <= 3分钟,修改 logType 为 4(看课中断) lmx-这个逻辑不合理,不能这样判定看课中断
+//                if (onlineSeconds <= 180) { // 3分钟 = 180秒
+//                    newLogType = 4;
+//                    needUpdate = true;
+//                } else
+                    // ③ 如果直播视频 >= 40分钟,在线时长 >= 30分钟,logType 设置为 2(完课)
+                if (totalVideoDuration >= 2400 && onlineSeconds >= 1800) { // 40分钟 = 2400秒,30分钟 = 1800秒
                     newLogType = 2;
                     log.setFinishTime(now);
                     needUpdate = true;
@@ -972,6 +977,69 @@ public class Task {
         }
     }
 
+    /**
+     * 每分钟扫描一次用户在线状态用于更新用户观看记录值
+     */
+    @Scheduled(cron = "0 0/1 * * * ?")
+    @DistributeLock(key = "updateLiveWatchUserStatus", scene = "task")
+    public void updateLiveWatchUserStatus() {
+        try {
+            Set<String> keys = redisCache.redisTemplate.keys("live:user:watch:log:*");
+            LocalDateTime now = LocalDateTime.now();
+            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+            List<LiveWatchLog> updateLog = new ArrayList<>();
+            if (keys != null && !keys.isEmpty()) {
+                for (String key : keys) {
+                    String[] split = key.split(":");
+                    String cacheTime = redisCache.getCacheObject(key);
+                    //判断缓存的值是否已经距离现在超过一分钟
+                    if (StringUtils.isNotBlank(cacheTime)) {
+                        try {
+                            LocalDateTime cachedDateTime = LocalDateTime.parse(cacheTime, formatter);
+                            // 比较时间,判断是否超过1分钟(60秒)
+                            long secondsBetween = java.time.Duration.between(cachedDateTime, now).getSeconds();
+                            if (secondsBetween >= 60) {
+                                // 距离上次记录已超过1分钟,更新状态为看课中断
+                                // 查询 LiveWatchLog
+                                LiveWatchLog queryLog = new LiveWatchLog();
+                                queryLog.setLiveId(Long.valueOf(split[4]));
+                                queryLog.setQwUserId(String.valueOf(split[7]));
+                                queryLog.setExternalContactId(Long.valueOf(split[6]));
+                                queryLog.setLogType(1);
+                                List<LiveWatchLog> logs = liveWatchLogService.selectLiveWatchLogList(queryLog);
+                                if (logs != null && !logs.isEmpty()) {
+                                    for (LiveWatchLog log : logs) {
+                                        if (log.getLogType() != null && log.getLogType() == 2) {
+                                            continue;
+                                        }
+                                        log.setLogType(4);
+                                        updateLog.add(log);
+                                    }
+                                }
+                            }
+                        } catch (Exception e) {
+                            log.error("解析缓存时间失败: cacheTime={}, error={}", cacheTime, e.getMessage());
+                        }
+                    }
+                }
+                // 批量插入回放用户数据
+                if (!updateLog.isEmpty()) {
+                    int batchSize = 500;
+                    for (int i = 0; i < updateLog.size(); i += batchSize) {
+                        int end = Math.min(i + batchSize, updateLog.size());
+                        List<LiveWatchLog> batch = updateLog.subList(i, end);
+                        liveWatchLogService.batchUpdateLiveWatchLog(batch);
+                    }
+                    for (LiveWatchLog liveWatchLog : updateLog) {
+                        redisCache.setCacheObject("live:watch:log:cache:" + liveWatchLog.getLogId(), liveWatchLog, 1, TimeUnit.HOURS);
+                    }
+                }
+            }
+        } catch (Exception ex) {
+            log.error("每分钟扫描一次用户在线状态用于更新用户观看记录值: error={}", ex.getMessage(), ex);
+        }
+    }
+
     /**
      * 批量同步Redis中的观看时长到数据库
      * 每2分钟执行一次,减少数据库压力

+ 5 - 1
fs-live-app/src/main/java/com/fs/live/websocket/service/WebSocketServer.java

@@ -39,6 +39,7 @@ import java.io.IOException;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
 import java.util.*;
 import java.util.concurrent.*;
 import java.util.concurrent.locks.Lock;
@@ -1191,7 +1192,7 @@ public class WebSocketServer {
             queryLog.setLiveId(liveId);
             queryLog.setQwUserId(String.valueOf(qwUserId));
             queryLog.setExternalContactId(externalContactId);
-
+            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
             List<LiveWatchLog> logs = liveWatchLogService.selectLiveWatchLogList(queryLog);
             if (logs != null && !logs.isEmpty()) {
                 for (LiveWatchLog log : logs) {
@@ -1199,6 +1200,9 @@ public class WebSocketServer {
                     if (log.getLogType() == null || log.getLogType() != 2) {
                         log.setLogType(1);
                         liveWatchLogService.updateLiveWatchLog(log);
+                        String liveUserWatchLogKey = String.format(LIVE_USER_WATCH_LOG_CACHE, liveId, userId,externalContactId,qwUserId);
+                        LocalDateTime now = LocalDateTime.now();
+                        redisCache.setCacheObject(liveUserWatchLogKey,formatter.format(now),5,TimeUnit.MINUTES);
                     }
                 }
             }

+ 1 - 0
fs-service/src/main/java/com/fs/company/mapper/CompanyUserMapper.java

@@ -351,4 +351,5 @@ public interface CompanyUserMapper
      */
     List<com.fs.hisStore.domain.FsUserScrm> selectBoundFsUsersByCompanyUserId(@Param("companyUserId") Long companyUserId);
 
+    CompanyUser selectCompanyUserByQwUserId(@Param("qwUserId") Long id);
 }

+ 2 - 0
fs-service/src/main/java/com/fs/company/service/impl/CompanyServiceImpl.java

@@ -50,6 +50,7 @@ import org.redisson.api.RedissonClient;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Lazy;
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
 import com.fs.company.service.ICompanyService;
@@ -120,6 +121,7 @@ public class CompanyServiceImpl implements ICompanyService
     private TransactionTemplate transactionTemplate;
 
     @Autowired
+    @Lazy
     private ILiveService liveService;
     @Autowired
     private LiveOrderMapper liveOrderMapper;

+ 9 - 0
fs-service/src/main/java/com/fs/course/service/impl/FsUserCoursePeriodServiceImpl.java

@@ -146,6 +146,15 @@ public class FsUserCoursePeriodServiceImpl implements IFsUserCoursePeriodService
             coursePeriodDays.setStartDateTime(startDateTime);
             coursePeriodDays.setEndDateTime(endDateTime);
             coursePeriodDays.setLastJoinTime(lastJsonTime);
+
+            if(LocalDate.now().isBefore(coursePeriodDays.getStartDateTime().toLocalDate())){
+                coursePeriodDays.setStatus(0);
+            } else if(LocalDate.now().isAfter(coursePeriodDays.getStartDateTime().toLocalDate())){
+                coursePeriodDays.setStatus(2);
+            } else{
+                coursePeriodDays.setStatus(1);
+            }
+
             fsUserCoursePeriodDaysMapper.updateFsUserCoursePeriodDays(coursePeriodDays); // 更新数据库中的课程日期
         }
 

+ 22 - 21
fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java

@@ -48,6 +48,7 @@ import com.fs.course.service.IFsUserCompanyUserService;
 import com.fs.course.service.IFsUserCourseVideoService;
 import com.fs.course.vo.*;
 import com.fs.course.vo.newfs.*;
+import com.fs.enums.ExceptionCodeEnum;
 import com.fs.his.config.AppConfig;
 import com.fs.his.domain.FsUser;
 import com.fs.his.domain.FsUserIntegralLogs;
@@ -713,11 +714,11 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
         FsUser fsUser = fsUserMapper.selectFsUserByUserId(param.getUserId());
         //用户不存在唤起重新授权
         if (fsUser == null) {
-            return R.error(401, "未授权");
+            return R.error(ExceptionCodeEnum.USER_NOT_FOUND.getCode(), ExceptionCodeEnum.USER_NOT_FOUND.getDescription());
         }
 
         if (fsUser.getStatus() == 0) {
-            return R.error("会员被停用,无权限,请联系客服!");
+            return R.error(ExceptionCodeEnum.MEMBER_DISABLED.getCode(), ExceptionCodeEnum.MEMBER_DISABLED.getDescription());
         }
 //        if (param.getIsOpenCourse()!=null&&param.getIsOpenCourse()==1){
 //            FsCourseWatchLog log = courseWatchLogMapper.getWatchCourseVideoByUserId(param.getUserId(), param.getVideoId());
@@ -763,7 +764,7 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
         if (oneCompanyCourse && fsUser.getQwExtId() != null) {
             QwExternalContact qwExternalContact = qwExternalContactMapper.selectById(fsUser.getQwExtId());
             if (qwExternalContact.getCompanyUserId() != null && !qwExternalContact.getCompanyUserId().equals(param.getCompanyUserId())) {
-                return R.error(500, "该用户(" + fsUser.getUserId() + ")已成为其他销售会员");
+                return R.error(ExceptionCodeEnum.USER_ALREADY_OTHER_SALES_MEMBER.getCode(), ExceptionCodeEnum.USER_ALREADY_OTHER_SALES_MEMBER.getFormattedDescription(fsUser.getUserId()));
             }
         }
 
@@ -775,7 +776,7 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
             List<QwExternalContact> qwExternalContacts = qwExternalContactMapper.selectQwExternalContactList(query);
             if (qwExternalContacts != null && !qwExternalContacts.isEmpty() && !Objects.equals(qwExternalContacts.get(0).getCompanyUserId(), param.getCompanyUserId())) {
                 String msgInfo = "该用户(" + fsUser.getUserId() + ")已在公司" + param.getCompanyId() + "成为其他销售会员";
-                return R.error(500, msgInfo);
+                return R.error(ExceptionCodeEnum.USER_COMPANY_OTHER_SALES_MEMBER.getCode(), ExceptionCodeEnum.USER_COMPANY_OTHER_SALES_MEMBER.getFormattedDescription(fsUser.getUserId(),param.getCompanyId()));
             }
         }
 
@@ -794,7 +795,7 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
         } else {
             // 非法参数
             logger.warn("非法参数 isRoom: {}", isRoom);
-            return R.error("参数错误!");
+            return R.error(ExceptionCodeEnum.PARAM_ERROR.getCode(), ExceptionCodeEnum.PARAM_ERROR.getDescription());
         }
 
     }
@@ -804,13 +805,13 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
 //                "\t\t\t\t\t<div style=\"color: #999;font-size: 14px;font-weight: bold;\">添加伴学助手免费领取会员权限</div>";
         QwGroupChat qwGroupChat = qwGroupChatMapper.selectQwGroupChatByChatId(courseLink.getChatId());
         if (qwGroupChat == null) {
-            return R.error("群参数异常");
+            return R.error(ExceptionCodeEnum.GROUP_PARAM_ERROR.getCode(), ExceptionCodeEnum.GROUP_PARAM_ERROR.getDescription());
         }
         SopUserLogsInfo sopUserLogsInfo = new SopUserLogsInfo();
         sopUserLogsInfo.setChatId(courseLink.getChatId());
         List<QwGroupChatUser> qwGroupChatUsers = qwGroupChatUserMapper.selectByChatId(sopUserLogsInfo);
         if (qwGroupChatUsers == null || qwGroupChatUsers.isEmpty()) {
-            return R.error("群参数异常");
+            return R.error(ExceptionCodeEnum.GROUP_PARAM_ERROR.getCode(), ExceptionCodeEnum.GROUP_PARAM_ERROR.getDescription());
         }
         //群聊寻找用户新逻辑
         QwExternalContact qwExternalContact = null;
@@ -2529,10 +2530,10 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
         //查询用户
         FsUser fsUser = fsUserMapper.selectFsUserById(param.getUserId());
         if (fsUser == null) {
-            return ResponseResult.fail(401, "当前用户信息不存在");
+            return ResponseResult.fail(ExceptionCodeEnum.USER_NOT_FOUND.getCode(), ExceptionCodeEnum.USER_NOT_FOUND.getDescription());
         }
         if (fsUser.getStatus() == 0) {
-            return ResponseResult.fail(503, "会员被停用,无权限,请联系客服!");
+            return ResponseResult.fail(ExceptionCodeEnum.MEMBER_DISABLED.getCode(), ExceptionCodeEnum.MEMBER_DISABLED.getDescription());
         }
         //公开课
         if (param.getIsOpenCourse() != null && param.getIsOpenCourse() == 1) {
@@ -2560,14 +2561,14 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
         //判断该销售是否存在
         CompanyUser companyUser = companyUserMapper.selectCompanyUserById(param.getCompanyUserId());
         if (companyUser == null) {
-            return ResponseResult.fail(405, "当前销售不存在");
+            return ResponseResult.fail(ExceptionCodeEnum.SALES_NOT_FOUND.getCode(), ExceptionCodeEnum.SALES_NOT_FOUND.getDescription());
         }
 
         // 获取课程所属项目id
         FsUserCourse fsUserCourse = fsUserCourseMapper.selectFsUserCourseByCourseId(param.getCourseId());
         Long courseProject = fsUserCourse.getProject();
         if (Objects.isNull(courseProject)) {
-            return ResponseResult.fail(504, "课程配置错误,项目归属为空,课程ID: " + param.getCourseId());
+            return ResponseResult.fail(ExceptionCodeEnum.COURSE_CONFIG_ERROR.getCode(),  ExceptionCodeEnum.COURSE_CONFIG_ERROR.getFormattedDescription(param.getCourseId()));
         }
         FsUserCoursePeriod fsUserCoursePeriod = fsUserCoursePeriodMapper.selectFsUserCoursePeriodById(param.getPeriodId());
         FsUserCompanyUser userCompanyUser = userCompanyUserService.selectByUserIdAndProjectId(fsUser.getUserId(), courseProject);
@@ -2580,7 +2581,7 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
                 Company company = companyService.selectCompanyById(param.getCompanyId());
 
                 if ((companyUser.getIsAllowedAllRegister() != null && companyUser.getIsAllowedAllRegister() != 1)) {
-                    return ResponseResult.fail(504, "当前销售禁止绑定会员,请联系销售!");
+                    return ResponseResult.fail(ExceptionCodeEnum.SALES_FORBIDDEN_BIND.getCode(), ExceptionCodeEnum.SALES_FORBIDDEN_BIND.getDescription());
                 }
                 // 使用 Stream API 检查是否包含 companyId
                 // 修正类型转换问题
@@ -2590,7 +2591,7 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
                         && Arrays.stream(fsUserCoursePeriod.getIsNeedRegisterMember().split(","))
                         .map(String::trim)
                         .anyMatch(id -> id.equals(String.valueOf(company.getCompanyId()))))) {
-                    return ResponseResult.fail(504, "请联系销售发送邀请链接成为会员!");
+                    return ResponseResult.fail(ExceptionCodeEnum.CONTACT_SALES_FOR_INVITATION.getCode(), ExceptionCodeEnum.CONTACT_SALES_FOR_INVITATION.getDescription());
                 }
                 int defaultStatus = (company != null ? company.getFsUserIsDefaultBlack() : 0) == 1 ? 0 : 1;
                 userCompanyUser = userCompanyUserService.bindRelationship(param.getUserId(), courseProject, companyUser.getCompanyId(), companyUser.getUserId(), defaultStatus);
@@ -2601,7 +2602,7 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
         if (!param.getCompanyUserId().equals(userCompanyUser.getCompanyUserId())) {
             log.error("进入::isAddCompanyUser 该用户fsUser={},companyUser={},param={}", fsUser, companyUser, param);
 
-            return ResponseResult.fail(500, "该用户(" + fsUser.getUserId() + ")已成为其他销售会员");
+            return ResponseResult.fail(ExceptionCodeEnum.USER_ALREADY_OTHER_SALES_MEMBER.getCode(), ExceptionCodeEnum.USER_ALREADY_OTHER_SALES_MEMBER.getFormattedDescription(fsUser.getUserId()));
         }
 
         // 如果开启了黑名单审核,需要提示
@@ -2612,9 +2613,9 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
 
         if (userCompanyUser.getStatus() == 2) {
             if ("福本源".equals(signProjectName)) {
-                return ResponseResult.fail(504, "服务暂时不可用,请联系管理员");
+                return ResponseResult.fail(ExceptionCodeEnum.SERVICE_UNAVAILABLE.getCode(), ExceptionCodeEnum.SERVICE_UNAVAILABLE.getDescription());
             } else {
-                return ResponseResult.fail(504, "已被拉黑,请联系管理员");
+                return ResponseResult.fail(ExceptionCodeEnum.USER_BLACKLISTED.getCode(), ExceptionCodeEnum.USER_BLACKLISTED.getDescription());
             }
         }
 
@@ -2623,14 +2624,14 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
         FsCourseWatchLog watchCourseVideo = courseWatchLogMapper.getCourseWatchLogByUser(param.getUserId(), param.getVideoId(), param.getPeriodId());
 
         if (!isUserCoursePeriodValid(param)) {
-            return ResponseResult.fail(504, "请观看最新的课程项目");
+            return ResponseResult.fail(ExceptionCodeEnum.WATCH_LATEST_COURSE.getCode(), ExceptionCodeEnum.WATCH_LATEST_COURSE.getDescription());
         }
         // 项目看课数限制
         if (!EXCLUDE_PROJECTS.contains(signProjectName) && !CloudHostUtils.hasCloudHostName("弘德堂")) {
             log.error("进入了看课限制:传入参数:={},watchCourseVideo={}",param, watchCourseVideo);
             Integer logCount = fsUserCourseMapper.selectTodayCourseWatchLogCountByUserIdAndProjectId(param.getUserId(), courseProject);
             if (Objects.isNull(watchCourseVideo) && logCount > 0) {
-                return ResponseResult.fail(504, "超过项目看课数量限制");
+                return ResponseResult.fail(ExceptionCodeEnum.EXCEED_COURSE_LIMIT.getCode(), ExceptionCodeEnum.EXCEED_COURSE_LIMIT.getDescription());
             }
         }else {
             log.error("没有进入看课限制:传入参数:={},watchCourseVideo={}存在问题 ",param, watchCourseVideo);
@@ -2640,7 +2641,7 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
             if (!watchCourseVideo.getCompanyUserId().equals(param.getCompanyUserId())) {
                 //提示
                 log.error("数据库存在销售信息:{},分享得销售信息:{}", watchCourseVideo.getCompanyUserId(), param.getCompanyUserId());
-                return ResponseResult.fail(504, "已看过其他销售分享的此课程,不能重复观看");
+                return ResponseResult.fail(ExceptionCodeEnum.ALREADY_WATCHED_OTHER_SALES_COURSE.getCode(), ExceptionCodeEnum.ALREADY_WATCHED_OTHER_SALES_COURSE.getDescription());
             }
 
             FsCourseWatchLog updateLog = new FsCourseWatchLog();
@@ -3684,11 +3685,11 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
         FsUser fsUser = fsUserMapper.selectFsUserByUserId(param.getUserId());
         //用户不存在唤起重新授权
         if (fsUser == null) {
-            return R.error(504, "未授权");
+            return R.error(ExceptionCodeEnum.USER_NOT_FOUND.getCode(), ExceptionCodeEnum.USER_NOT_FOUND.getDescription());
         }
 
         if (fsUser.getStatus() == 0) {
-            return R.error("会员被停用,无权限,请联系客服!");
+            return R.error(ExceptionCodeEnum.MEMBER_DISABLED.getCode(), ExceptionCodeEnum.MEMBER_DISABLED.getDescription());
         }
         return createWatchIsOpen(param);
     }

+ 54 - 0
fs-service/src/main/java/com/fs/enums/ExceptionCodeEnum.java

@@ -0,0 +1,54 @@
+package com.fs.enums;
+
+import lombok.Getter;
+
+@Getter
+public enum ExceptionCodeEnum {
+
+    // ============ 用户相关错误 (400-419) ============
+    USER_NOT_FOUND(401, "当前用户信息不存在,请重新授权"),
+
+    // ============ 会员相关错误 (420-439) ============
+    USER_BLACKLISTED(421, "已被拉黑,请联系管理员"),
+
+    // ============ 销售相关错误 (440-459) ============
+    SALES_NOT_FOUND(441, "当前销售不存在"),
+    SALES_FORBIDDEN_BIND(442, "当前销售禁止绑定会员,请联系销售!"),
+    CONTACT_SALES_FOR_INVITATION(443, "请联系销售发送邀请链接成为会员!"),
+
+    // ============ 会员关系错误 (460-479) ============
+    MEMBER_DISABLED(461, "会员被停用,无权限,请联系客服!"),
+    USER_ALREADY_OTHER_SALES_MEMBER(462, "该用户(%s)已成为其他销售会员"),
+    USER_COMPANY_OTHER_SALES_MEMBER(463, "该用户(%s)已在公司%s成为其他销售会员"),
+
+    // ============ 课程相关错误 (480-499) ============
+    COURSE_CONFIG_ERROR(481, "课程配置错误,项目归属为空,课程ID: %s"),
+    WATCH_LATEST_COURSE(482, "请观看最新的课程项目"),
+    EXCEED_COURSE_LIMIT(483, "超过项目看课数量限制"),
+    ALREADY_WATCHED_OTHER_SALES_COURSE(484, "已看过其他销售分享的此课程,不能重复观看"),
+
+    // ============ 参数相关错误 (500-519) ============
+    PARAM_ERROR(501, "参数错误!"),
+    GROUP_PARAM_ERROR(502, "群参数异常"),
+    LIVE_PARAM_ERROR(503, "直播参数错误"),
+
+    // ============ 系统相关错误 (520-539) ============
+    SERVICE_UNAVAILABLE(521, "服务暂时不可用,请联系管理员");
+
+    private final Integer code;
+    private final String description;
+
+    ExceptionCodeEnum(Integer code, String description) {
+        this.code = code;
+        this.description = description;
+    }
+
+    /**
+     * 获取格式化后的描述信息
+     * @param args 格式化参数
+     * @return 格式化后的描述
+     */
+    public String getFormattedDescription(Object... args) {
+        return String.format(description, args);
+    }
+}

+ 26 - 0
fs-service/src/main/java/com/fs/fastGpt/service/impl/AiHookServiceImpl.java

@@ -9,8 +9,11 @@ import com.fs.common.config.FSConfig;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.redis.RedisCache;
 import com.fs.company.domain.CompanyConfig;
+import com.fs.company.domain.CompanyUser;
 import com.fs.company.mapper.CompanyConfigMapper;
+import com.fs.company.mapper.CompanyUserMapper;
 import com.fs.config.ai.AiHostProper;
+import com.fs.config.cloud.CloudHostProper;
 import com.fs.course.domain.FsUserCourseVideo;
 import com.fs.course.mapper.FsCourseWatchLogMapper;
 import com.fs.course.mapper.FsUserCourseVideoMapper;
@@ -169,6 +172,12 @@ public class AiHookServiceImpl implements AiHookService {
     @Autowired
     private ICrmMsgService crmMsgService;
 
+    @Autowired
+    private CompanyUserMapper companyUserMapper;
+
+    @Autowired
+    private CloudHostProper cloudHostProper;
+
     private static final String AI_REPLY = "AI_REPLY:";
     private static final String AI_REPLY_TAG = "AI_REPLY_TAG:";
 
@@ -392,6 +401,12 @@ public class AiHookServiceImpl implements AiHookService {
             log.error("未绑定角色");
             return userIsReply(sender, uid, user);
         }
+
+        if(user.getAiStatus() == 1){
+            log.error("ai已下线:{}",user);
+            return R.ok();
+        }
+
         Long serverId = user.getServerId();
         log.info("服务器id"+serverId);
         if (serverId == null) {
@@ -1469,6 +1484,17 @@ public class AiHookServiceImpl implements AiHookService {
             //添加看客记录
             addCourseWatchLog(qwExternalContactsId);
             String msgC = (String)redisCache.getCacheObject("msg:" + fastGptChatSession.getSessionId());
+
+            if (("今正科技".equals(cloudHostProper.getCompanyName()))) {
+                //处理名称替换
+                if (role.getReminderWords() != null && !role.getReminderWords().isEmpty() && role.getReminderWords().contains("#销售名称#")) {
+                    CompanyUser companyUser = companyUserMapper.selectCompanyUserByQwUserId(user.getId());
+                    if (companyUser != null) {
+                        role.setReminderWords(role.getReminderWords().replace("#销售名称#", companyUser.getNickName()));
+                    }
+                }
+            }
+
             //添加关键词
             addPromptWord(messageList,msgC,qwExternalContactsId,role.getReminderWords(), role.getContactInfo(),fastGptChatSession.getSessionId());
             R r = chatService.initiatingTakeChat(param, aiHostProper.getAiApi(), appKey);

+ 2 - 0
fs-service/src/main/java/com/fs/his/enums/FsUserIntegralLogTypeEnum.java

@@ -32,6 +32,8 @@ public enum FsUserIntegralLogTypeEnum {
     TYPE_22(22,"首次完成积分商城下单"),
     TYPE_23(23,"管理员添加"),
     TYPE_24(24, "付费课程订阅"),
+    TYPE_25(25, "直播完课积分"),
+    TYPE_26(26, "直播红包积分"),
     ;
 
 

+ 6 - 0
fs-service/src/main/java/com/fs/hisStore/domain/FsStoreAfterSalesScrm.java

@@ -150,4 +150,10 @@ public class FsStoreAfterSalesScrm extends BaseEntity
     @TableField(exist = false)
     private String hfOrderCode;
 
+    /**
+     * 用于查询银行交易流水
+     */
+    @TableField(exist = false)
+    private String bankTransactionId;
+
 }

+ 4 - 1
fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreAfterSalesScrmMapper.java

@@ -102,6 +102,9 @@ public interface FsStoreAfterSalesScrmMapper
             "<if test =\"maps.hfOrderCode != null and  maps.hfOrderCode!='' \"> " +
               "and fsps.pay_code = #{maps.hfOrderCode} " +
             "</if>" +
+            "<if test =\"maps.bankTransactionId != null and  maps.bankTransactionId!='' \"> " +
+              "and fsps.bank_transaction_id = #{maps.bankTransactionId} " +
+            "</if>" +
             "<if test = 'maps.status != null    '> " +
             "and s.status = #{maps.status} " +
             "</if>" +
@@ -255,7 +258,7 @@ public interface FsStoreAfterSalesScrmMapper
             " left join company c on c.company_id=s.company_id " +
             " left join company_user cu on cu.user_id=s.company_user_id " +
             " left join fs_store_payment_scrm fsps on fsps.business_order_id = o.id and fsps.status in (-1,1) " +
-            " where 1=1 and s.status = 4 and fsps.bank_transaction_id is not null " +
+            " where 1=1 and s.is_del = 0  and o.status = -2 and fsps.bank_transaction_id is not null " +
             "<if test =\"maps.hfOrderCode != null and  maps.hfOrderCode!='' \"> " +
             "and fsps.pay_code = #{maps.hfOrderCode} " +
             "</if>" +

+ 7 - 0
fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreOrderScrmMapper.java

@@ -478,6 +478,9 @@ public interface FsStoreOrderScrmMapper
             "<if test = 'maps.userId != null     '> " +
             "and o.user_id=#{maps.userId} " +
             "</if>" +
+            "<if test = 'maps.appId != null and  maps.appId !=\"\"    '> " +
+            "and o.app_id =#{maps.appId} " +
+            "</if>" +
             " order by o.id desc "+
             "</script>"})
     List<FsMyStoreOrderListQueryVO> selectFsMyStoreOrderListVO(@Param("maps")FsMyStoreOrderQueryParam param);
@@ -1416,4 +1419,8 @@ public interface FsStoreOrderScrmMapper
 
     @Update("update fs_store_order_scrm set `status`=-3 where order_code=#{orderCode}")
     int cancelOrderByCode(@Param("orderCode") String orderCode);
+
+
+    @Select("SELECT * FROM fs_store_order_scrm WHERE create_time >= DATE_SUB(NOW(), INTERVAL 30 MINUTE) and status = 0")
+    List<FsStoreOrderScrm> selectBankOrder();
 }

+ 1 - 1
fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreProductScrmMapper.java

@@ -274,7 +274,7 @@ public interface FsStoreProductScrmMapper
             "and  p.is_good=1 and p.is_display=1 order by p.sort desc limit #{count}")
     List<FsStoreProductListQueryVO> selectFsStoreProductGoodQuery(int count,@Param("config") MedicalMallConfig  config);
     List<FsStoreProductListQueryVO> selectFsStoreProductTuiListQuery(@Param("config") MedicalMallConfig  config, @Param("param") BaseQueryParam param);
-    List<FsStoreProductListQueryVO> selectFsStoreProductGoodListQuery(@Param("config") MedicalMallConfig  config);
+    List<FsStoreProductListQueryVO> selectFsStoreProductGoodListQuery(@Param("config") MedicalMallConfig  config, @Param("param") BaseQueryParam param);
     @Select({"<script> " +
             "select count(1) from fs_store_product_scrm  " +
             "where 1=1 " +

+ 9 - 0
fs-service/src/main/java/com/fs/hisStore/mapper/FsUserScrmMapper.java

@@ -124,6 +124,15 @@ public interface FsUserScrmMapper
     @Update("update fs_user set pay_count=pay_count+1" +
             " where user_id=#{userId}")
     int incPayCount(Long userId);
+    
+    /**
+     * 增加用户余额
+     * @param userId 用户ID
+     * @param integral 增加的积分
+     * @return 结果
+     */
+    @Update("update fs_user set integral = IFNULL(integral, 0) + #{integral} where user_id = #{userId}")
+    int incrIntegral(@Param("userId") Long userId, @Param("integral") BigDecimal integral);
     @Select("select * from fs_user where phone=#{phone}")
     FsUserScrm selectFsUserByPhone(String phone);
 

+ 3 - 0
fs-service/src/main/java/com/fs/hisStore/param/FsMyStoreOrderQueryParam.java

@@ -1,6 +1,7 @@
 package com.fs.hisStore.param;
 
 import com.fs.common.param.BaseQueryParam;
+import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
 
 import java.io.Serializable;
@@ -13,4 +14,6 @@ public class FsMyStoreOrderQueryParam extends BaseQueryParam implements Serializ
     private Long companyId;
     private Long companyUserId;
     private Integer deliveryStatus;
+    @ApiModelProperty(value = "当前的appid")
+    private String appId;
 }

+ 3 - 0
fs-service/src/main/java/com/fs/hisStore/param/FsStoreProductPackageQueryParam.java

@@ -1,6 +1,7 @@
 package com.fs.hisStore.param;
 
 import com.fs.common.param.BaseQueryParam;
+import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
 
 import java.io.Serializable;
@@ -12,4 +13,6 @@ public class FsStoreProductPackageQueryParam extends BaseQueryParam implements S
     private String title;
     private String cateId;
     private Integer status;
+    @ApiModelProperty(value = "当前的appid")
+    private String appId;
 }

+ 3 - 3
fs-service/src/main/java/com/fs/hisStore/service/IFsStoreProductScrmService.java

@@ -97,15 +97,15 @@ public interface IFsStoreProductScrmService
     void incProductStock(Long num, Long productId, Long productAttrValueId);
 
 
-    List<FsStoreProductListQueryVO> selectFsStoreProductNewQuery(int count);
+    List<FsStoreProductListQueryVO> selectFsStoreProductNewQuery(int count, String appId);
 
-    List<FsStoreProductListQueryVO> selectFsStoreProductHotQuery(int count);
+    List<FsStoreProductListQueryVO> selectFsStoreProductHotQuery(int count, String appId);
 
     List<FsStoreProductListQueryVO> selectFsStoreProductGoodQuery(int count);
 
     List<FsStoreProductListQueryVO> selectFsStoreProductTuiListQuery(BaseQueryParam param);
 
-    List<FsStoreProductListQueryVO> selectFsStoreProductGoodListQuery();
+    List<FsStoreProductListQueryVO> selectFsStoreProductGoodListQuery(BaseQueryParam param);
 
     Long selectFsStoreProductCount(int type);
     Long selectFsStoreProductCount(int type,Long companyId);

+ 8 - 4
fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreProductScrmServiceImpl.java

@@ -59,6 +59,7 @@ import org.apache.commons.lang3.StringUtils;
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.context.annotation.Lazy;
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
 import com.fs.hisStore.service.IFsStoreProductScrmService;
@@ -121,6 +122,7 @@ public class FsStoreProductScrmServiceImpl implements IFsStoreProductScrmService
     private FsStoreProductCategoryScrmMapper fsStoreProductCategoryScrmMapper;
 
     @Autowired
+    @Lazy
     private ILiveService liveService;
 
     @Autowired
@@ -1058,18 +1060,20 @@ public class FsStoreProductScrmServiceImpl implements IFsStoreProductScrmService
     }
 
     @Override
-    public List<FsStoreProductListQueryVO> selectFsStoreProductNewQuery(int count) {
+    public List<FsStoreProductListQueryVO> selectFsStoreProductNewQuery(int count, String appId) {
         HashMap<String, Object> map = new HashMap<>();
         map.put("count", count);
         map.put("config", medicalMallConfig);
+        map.put("appId", appId);
         return fsStoreProductMapper.selectFsStoreProductNewQuery(map);
     }
 
     @Override
-    public List<FsStoreProductListQueryVO> selectFsStoreProductHotQuery(int count) {
+    public List<FsStoreProductListQueryVO> selectFsStoreProductHotQuery(int count, String appId) {
         HashMap<String, Object> map = new HashMap<>();
         map.put("count", count);
         map.put("config", medicalMallConfig);
+        map.put("appId", appId);
         return fsStoreProductMapper.selectFsStoreProductHotQuery(map);
     }
 
@@ -1084,8 +1088,8 @@ public class FsStoreProductScrmServiceImpl implements IFsStoreProductScrmService
     }
 
     @Override
-    public List<FsStoreProductListQueryVO> selectFsStoreProductGoodListQuery() {
-        return fsStoreProductMapper.selectFsStoreProductGoodListQuery(medicalMallConfig);
+    public List<FsStoreProductListQueryVO> selectFsStoreProductGoodListQuery(BaseQueryParam param) {
+        return fsStoreProductMapper.selectFsStoreProductGoodListQuery(medicalMallConfig, param);
     }
 
     @Override

+ 2 - 1
fs-service/src/main/java/com/fs/hisStore/vo/FsStoreOrderStatisticsVO.java

@@ -4,6 +4,7 @@ import com.fs.common.annotation.Excel;
 import lombok.Data;
 
 import java.io.Serializable;
+import java.math.BigDecimal;
 
 @Data
 public class FsStoreOrderStatisticsVO implements Serializable
@@ -13,7 +14,7 @@ public class FsStoreOrderStatisticsVO implements Serializable
     @Excel(name = "订单数")
     Integer orderCount;
     @Excel(name = "订单金额")
-    Integer payPrice;
+    BigDecimal payPrice;
 
 
 

+ 6 - 0
fs-service/src/main/java/com/fs/live/mapper/LiveCompletionPointsRecordMapper.java

@@ -33,6 +33,12 @@ public interface LiveCompletionPointsRecordMapper {
      */
     LiveCompletionPointsRecord selectLatestByUser(@Param("userId") Long userId);
 
+    /**
+     * 查询用户在某直播间最近一次完课记录(不限制日期)
+     */
+    LiveCompletionPointsRecord selectLatestByUserAndLiveId(@Param("liveId") Long liveId, 
+                                                            @Param("userId") Long userId);
+
     /**
      * 查询用户未领取的完课记录列表
      */

+ 143 - 113
fs-service/src/main/java/com/fs/live/service/impl/LiveAfterSalesServiceImpl.java

@@ -85,6 +85,9 @@ import com.fs.live.service.ILiveAfterSalesService;
 import org.springframework.transaction.annotation.Propagation;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.transaction.interceptor.TransactionAspectSupport;
+import org.redisson.api.RLock;
+import org.redisson.api.RedissonClient;
+import java.util.concurrent.TimeUnit;
 
 /**
  * 售后记录Service业务层处理
@@ -162,6 +165,9 @@ public class LiveAfterSalesServiceImpl implements ILiveAfterSalesService {
 //    @Autowired
 //    private FsStoreDeliversMapper fsStoreDeliversMapper;
 
+    @Autowired
+    private RedissonClient redissonClient;
+
 
     /**
      * 查询售后记录
@@ -388,139 +394,163 @@ public class LiveAfterSalesServiceImpl implements ILiveAfterSalesService {
     @Transactional
     public R applyForAfterSales(String userId, LiveAfterSalesParam param) {
         log.info("申请退款请求信息:"+JSONUtil.toJsonStr(param));
-        LiveOrder order=liveOrderService.selectOrderIdByOrderCode(param.getOrderCode());
-        if(!order.getUserId().equals(userId)){
-            throw new CustomException("非法操作");
-        }
-        if(order.getStatus()==0){
-            return R.error("未支付订单不能申请售后");
-        }
-        if("1".equals(configUtil.generateConfigByKey(SysConfigEnum.HIS_CONFIG.getKey()).getString("erpOpen"))
-                && StringUtils.isEmpty(order.getExtendOrderId())
-                && !CloudHostUtils.hasCloudHostName("康年堂")){
-            log.info("erpOpen:{}",configUtil.generateConfigByKey(SysConfigEnum.HIS_CONFIG.getKey()).getString("erpOpen"));
-            return R.error("仓库未生成订单,暂时不能申请退款,请联系客服");
-        }
-        if(order.getStatus()== OrderInfoEnum.STATUS_NE3.getValue()){
-            return R.error("已取消订单不能申请售后");
-        }
-        if(order.getStatus()== OrderInfoEnum.STATUS_NE1.getValue()){
-            return R.error("已提交申请,等待处理");
-        }
+        
+        // 使用Redis分布式锁,确保同一订单只能有一个服务器处理申请售后 因为卓美有一个订单生成了三个售后订单,数据一摸一样,除了id
+        String lockKey = "live:afterSales:apply:" + param.getOrderCode();
+        RLock lock = redissonClient.getLock(lockKey);
+        
+        try {
+            // 尝试获取锁,等待时间3秒,锁过期时间30秒
+            boolean locked = lock.tryLock(3, 30, TimeUnit.SECONDS);
+            if (!locked) {
+                log.warn("申请售后订单锁获取失败,订单号:{}", param.getOrderCode());
+                return R.error("系统繁忙,请稍后重试");
+            }
+            
+            log.info("申请售后订单锁获取成功,订单号:{}", param.getOrderCode());
+            
+            LiveOrder order=liveOrderService.selectOrderIdByOrderCode(param.getOrderCode());
+            if(!order.getUserId().equals(userId)){
+                throw new CustomException("非法操作");
+            }
+            if(order.getStatus()==0){
+                return R.error("未支付订单不能申请售后");
+            }
+            if("1".equals(configUtil.generateConfigByKey(SysConfigEnum.HIS_CONFIG.getKey()).getString("erpOpen"))
+                    && StringUtils.isEmpty(order.getExtendOrderId())
+                    && !CloudHostUtils.hasCloudHostName("康年堂")){
+                log.info("erpOpen:{}",configUtil.generateConfigByKey(SysConfigEnum.HIS_CONFIG.getKey()).getString("erpOpen"));
+                return R.error("仓库未生成订单,暂时不能申请退款,请联系客服");
+            }
+            if(order.getStatus()== OrderInfoEnum.STATUS_NE3.getValue()){
+                return R.error("已取消订单不能申请售后");
+            }
+            if(order.getStatus()== OrderInfoEnum.STATUS_NE1.getValue()){
+                return R.error("已提交申请,等待处理");
+            }
 //        if(storeAfterSalesParam.getRefundAmount().compareTo(order.getPayPrice())==1){
 //            return R.error("退款金额不能大于支付金额");
 //        }
-        //已完成订单七天后不能申请退款
-        if(order.getStatus().equals(OrderInfoEnum.STATUS_3.getValue())) {
-            String json=configService.selectConfigByKey("store.config");
-            com.fs.hisStore.config.StoreConfig config=JSONUtil.toBean(json, com.fs.hisStore.config.StoreConfig.class);
-            //已完成订单
-            if (order.getFinishTime() != null) {
-                if (config.getStoreAfterSalesDay() != null && config.getStoreAfterSalesDay() > 0) {
-                    //判断完成时间是否超过指定时间
-                    Calendar calendarAfterSales = new GregorianCalendar();
-                    calendarAfterSales.setTime(order.getFinishTime());
-                    calendarAfterSales.add(calendarAfterSales.DATE, config.getStoreAfterSalesDay()); //把日期往后增加一天,整数  往后推,负数往前移动
-                    if (calendarAfterSales.getTime().getTime() < new Date().getTime()) {
-                        return R.error("此订单已超过售后时间,不能提交售后");
-                    }
+            //已完成订单七天后不能申请退款
+            if(order.getStatus().equals(OrderInfoEnum.STATUS_3.getValue())) {
+                String json=configService.selectConfigByKey("store.config");
+                com.fs.hisStore.config.StoreConfig config=JSONUtil.toBean(json, com.fs.hisStore.config.StoreConfig.class);
+                //已完成订单
+                if (order.getFinishTime() != null) {
+                    if (config.getStoreAfterSalesDay() != null && config.getStoreAfterSalesDay() > 0) {
+                        //判断完成时间是否超过指定时间
+                        Calendar calendarAfterSales = new GregorianCalendar();
+                        calendarAfterSales.setTime(order.getFinishTime());
+                        calendarAfterSales.add(calendarAfterSales.DATE, config.getStoreAfterSalesDay()); //把日期往后增加一天,整数  往后推,负数往前移动
+                        if (calendarAfterSales.getTime().getTime() < new Date().getTime()) {
+                            return R.error("此订单已超过售后时间,不能提交售后");
+                        }
 
+                    }
                 }
             }
-        }
-        //商品除去优惠后的总价格
-        //BigDecimal totalPrice = BigDecimal.ZERO;
-        //拿到所有的商品
-        List<LiveOrderItem> orderItems = liveOrderItemService.selectCheckedByOrderId(order.getOrderId());
-        for (LiveOrderItem item : orderItems) {
-            StoreOrderProductDTO cartInfo = JSONObject.parseObject(item.getJsonInfo(), StoreOrderProductDTO.class);
-            LiveAfterSalesProductParam prosuctParam = param.getProductList().stream().filter(p -> p.getProductId().equals(item.getProductId())).findFirst().orElse(new LiveAfterSalesProductParam());
-            if (prosuctParam.getProductId() != null) {
+            //商品除去优惠后的总价格
+            //BigDecimal totalPrice = BigDecimal.ZERO;
+            //拿到所有的商品
+            List<LiveOrderItem> orderItems = liveOrderItemService.selectCheckedByOrderId(order.getOrderId());
+            for (LiveOrderItem item : orderItems) {
+                StoreOrderProductDTO cartInfo = JSONObject.parseObject(item.getJsonInfo(), StoreOrderProductDTO.class);
+                LiveAfterSalesProductParam prosuctParam = param.getProductList().stream().filter(p -> p.getProductId().equals(item.getProductId())).findFirst().orElse(new LiveAfterSalesProductParam());
+                if (prosuctParam.getProductId() != null) {
 //                //商品优惠前总金额
 //                BigDecimal totalAmountOfGoods = NumberUtil.mul(cartInfo.getPrice(), item.getNum());
 //                //商品优惠总金额
 //                BigDecimal commodityDiscountAmount = NumberUtil.mul(NumberUtil.div(totalAmountOfGoods, NumberUtil.sub(order.getTotalPrice(), order.getPayPostage())), order.getCouponPrice());
 //                //商品优惠后总金额
 //                totalPrice = NumberUtil.add(totalPrice, NumberUtil.sub(totalAmountOfGoods, commodityDiscountAmount));
-                item.setIsAfterSales(1);
-                LiveOrderItem orderItem=new LiveOrderItem();
+                    item.setIsAfterSales(1);
+                    LiveOrderItem orderItem=new LiveOrderItem();
 //                BeanUtil.copyProperties(item, orderItem);
-                try {
-                    BeanUtils.copyProperties(orderItem,item);
-                } catch (IllegalAccessException e) {
-                    throw new RuntimeException(e);
-                } catch (InvocationTargetException e) {
-                    throw new RuntimeException(e);
-                }
-                liveOrderItemService.updateLiveOrderItem(orderItem);
+                    try {
+                        BeanUtils.copyProperties(orderItem,item);
+                    } catch (IllegalAccessException e) {
+                        throw new RuntimeException(e);
+                    } catch (InvocationTargetException e) {
+                        throw new RuntimeException(e);
+                    }
+                    liveOrderItemService.updateLiveOrderItem(orderItem);
 
+                }
             }
-        }
-        //更新订单状态
-        Integer orderStatus=order.getStatus();
-        order.setStatus(OrderInfoEnum.STATUS_NE1.getValue());
-        order.setRefundStatus(String.valueOf(OrderInfoEnum.REFUND_STATUS_1.getValue()));
-        order.setRefundExplain(param.getReasons());
-        order.setCancelReason(param.getExplains());
-        order.setIsAfterSales(1);
-        order.setRefundTime(new Date());
-        liveOrderService.updateLiveOrder(order);
-        //生成售后订单
-        LiveAfterSales storeAfterSales = new LiveAfterSales();
-        storeAfterSales.setOrderId(order.getOrderId());
-        storeAfterSales.setRefundAmount(param.getRefundAmount());
-        storeAfterSales.setRefundType(param.getServiceType());
-        storeAfterSales.setReasons(param.getReasons());
-        storeAfterSales.setExplains(param.getExplains());
-        storeAfterSales.setExplainImg(param.getExplainImg());
-        storeAfterSales.setStatus(AfterSalesStatusEnum.STATUS_0.getValue());
-        storeAfterSales.setSalesStatus(0);
-        storeAfterSales.setCreateTime(Timestamp.valueOf(LocalDateTime.now()));
-        storeAfterSales.setIsDel(0);
-        storeAfterSales.setOrderStatus(orderStatus);
-        storeAfterSales.setUserId(Long.valueOf(userId));
-        storeAfterSales.setCompanyId(order.getCompanyId());
-        storeAfterSales.setCompanyUserId(order.getCompanyUserId());
-        liveAfterSalesService.insertLiveAfterSales(storeAfterSales);
-        //售后商品详情
-        for (LiveAfterSalesProductParam productParam : param.getProductList()) {
-            LiveOrderItem item = orderItems.stream().filter(p -> p.getProductId().equals(productParam.getProductId())).findFirst().orElse(new LiveOrderItem());
-            LiveAfterSalesItem storeAfterSalesItem = new LiveAfterSalesItem();
-            storeAfterSalesItem.setAfterSalesId(storeAfterSales.getId());
-            storeAfterSalesItem.setProductId(item.getProductId());
-            storeAfterSalesItem.setJsonInfo(item.getJsonInfo());
-            storeAfterSalesItem.setIsDel(0);
-            liveAfterSalesItemMapper.insertLiveAfterSalesItem(storeAfterSalesItem);
-        }
-        //操作记录
-        LiveAfterSalesLogs storeAfterSalesStatus = new LiveAfterSalesLogs();
-        storeAfterSalesStatus.setStoreAfterSalesId(storeAfterSales.getId());
-        storeAfterSalesStatus.setChangeType(0);
-        storeAfterSalesStatus.setChangeMessage(AfterSalesStatusEnum.STATUS_0.getDesc());
-        storeAfterSalesStatus.setChangeTime(Timestamp.valueOf(LocalDateTime.now()));
-        FsUserScrm user=userService.selectFsUserById(Long.valueOf(userId));
-        storeAfterSalesStatus.setOperator(user.getNickname());
-        liveAfterSalesLogsMapper.insertLiveAfterSalesLogs(storeAfterSalesStatus);
+            //更新订单状态
+            Integer orderStatus=order.getStatus();
+            order.setStatus(OrderInfoEnum.STATUS_NE1.getValue());
+            order.setRefundStatus(String.valueOf(OrderInfoEnum.REFUND_STATUS_1.getValue()));
+            order.setRefundExplain(param.getReasons());
+            order.setCancelReason(param.getExplains());
+            order.setIsAfterSales(1);
+            order.setRefundTime(new Date());
+            liveOrderService.updateLiveOrder(order);
+            //生成售后订单
+            LiveAfterSales storeAfterSales = new LiveAfterSales();
+            storeAfterSales.setOrderId(order.getOrderId());
+            storeAfterSales.setRefundAmount(param.getRefundAmount());
+            storeAfterSales.setRefundType(param.getServiceType());
+            storeAfterSales.setReasons(param.getReasons());
+            storeAfterSales.setExplains(param.getExplains());
+            storeAfterSales.setExplainImg(param.getExplainImg());
+            storeAfterSales.setStatus(AfterSalesStatusEnum.STATUS_0.getValue());
+            storeAfterSales.setSalesStatus(0);
+            storeAfterSales.setCreateTime(Timestamp.valueOf(LocalDateTime.now()));
+            storeAfterSales.setIsDel(0);
+            storeAfterSales.setOrderStatus(orderStatus);
+            storeAfterSales.setUserId(Long.valueOf(userId));
+            storeAfterSales.setCompanyId(order.getCompanyId());
+            storeAfterSales.setCompanyUserId(order.getCompanyUserId());
+            liveAfterSalesService.insertLiveAfterSales(storeAfterSales);
+            //售后商品详情
+            for (LiveAfterSalesProductParam productParam : param.getProductList()) {
+                LiveOrderItem item = orderItems.stream().filter(p -> p.getProductId().equals(productParam.getProductId())).findFirst().orElse(new LiveOrderItem());
+                LiveAfterSalesItem storeAfterSalesItem = new LiveAfterSalesItem();
+                storeAfterSalesItem.setAfterSalesId(storeAfterSales.getId());
+                storeAfterSalesItem.setProductId(item.getProductId());
+                storeAfterSalesItem.setJsonInfo(item.getJsonInfo());
+                storeAfterSalesItem.setIsDel(0);
+                liveAfterSalesItemMapper.insertLiveAfterSalesItem(storeAfterSalesItem);
+            }
+            //操作记录
+            LiveAfterSalesLogs storeAfterSalesStatus = new LiveAfterSalesLogs();
+            storeAfterSalesStatus.setStoreAfterSalesId(storeAfterSales.getId());
+            storeAfterSalesStatus.setChangeType(0);
+            storeAfterSalesStatus.setChangeMessage(AfterSalesStatusEnum.STATUS_0.getDesc());
+            storeAfterSalesStatus.setChangeTime(Timestamp.valueOf(LocalDateTime.now()));
+            FsUserScrm user=userService.selectFsUserById(Long.valueOf(userId));
+            storeAfterSalesStatus.setOperator(user.getNickname());
+            liveAfterSalesLogsMapper.insertLiveAfterSalesLogs(storeAfterSalesStatus);
 
 //        //更新OMS
-        IErpOrderService erpOrderService = getErpService();
-        ErpRefundUpdateRequest request=new ErpRefundUpdateRequest();
-        request.setTid(order.getOrderCode());
-        request.setOid(order.getOrderCode());
-        request.setRefund_state(1);
-        request.setStoreAfterSalesId(storeAfterSales.getId());
-        request.setOrderStatus(orderStatus);
-        if (StringUtils.isNotBlank(order.getExtendOrderId())){
-            BaseResponse response=erpOrderService.refundUpdateLive(request);
-            if(response.getSuccess()){
-                return R.ok();
+            IErpOrderService erpOrderService = getErpService();
+            ErpRefundUpdateRequest request=new ErpRefundUpdateRequest();
+            request.setTid(order.getOrderCode());
+            request.setOid(order.getOrderCode());
+            request.setRefund_state(1);
+            request.setStoreAfterSalesId(storeAfterSales.getId());
+            request.setOrderStatus(orderStatus);
+            if (StringUtils.isNotBlank(order.getExtendOrderId())){
+                BaseResponse response=erpOrderService.refundUpdateLive(request);
+                if(response.getSuccess()){
+                    return R.ok();
+                }
+                else{
+                    TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
+                    return R.error(response.getErrorDesc());
+                }
             }
-            else{
-                TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
-                return R.error(response.getErrorDesc());
+            return R.ok();
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e);
+        } finally {
+            // 释放锁
+            if (lock.isHeldByCurrentThread()) {
+                lock.unlock();
+                log.info("申请售后订单锁释放成功,订单号:{}", param.getOrderCode());
             }
         }
-        return R.ok();
     }
 
     @Override

+ 2 - 2
fs-service/src/main/java/com/fs/live/service/impl/LiveCompletionPointsRecordServiceImpl.java

@@ -222,9 +222,9 @@ public class LiveCompletionPointsRecordServiceImpl implements ILiveCompletionPoi
         integralLog.setUserId(userId);
         integralLog.setIntegral(Long.valueOf(record.getPointsAwarded()));
         integralLog.setBalance(newIntegral);
-        integralLog.setLogType(5); // 5-直播完课积分
+        integralLog.setLogType(25); // 5-直播完课积分
         integralLog.setBusinessId("live_completion_" + recordId); // 业务ID:直播完课记录ID
-        integralLog.setBusinessType(5); // 5-直播完课
+        integralLog.setBusinessType(25); // 5-直播完课
         integralLog.setStatus(1);
         integralLog.setCreateTime(new Date());
         fsUserIntegralLogsMapper.insertFsUserIntegralLogs(integralLog);

+ 10 - 8
fs-service/src/main/java/com/fs/live/service/impl/LiveOrderServiceImpl.java

@@ -732,14 +732,16 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
             }
 
             // 佣金处理
-            if (order.getCompanyUserId() == -1L) {
-                companyService.addCompanyTuiLiveMoney(order);
-            } else {
-                // 目前是一级佣金
-                FsStoreProduct product = JSONUtil.toBean(order.getItemJson(), FsStoreProduct.class);
-                List<FsStoreProductAttrValueScrm> productAttrValues = fsStoreProductAttrValueMapper.selectFsStoreProductAttrValueByProductId(product.getProductId());
-                if (productAttrValues != null && !productAttrValues.isEmpty()) {
-                    userService.addTuiLiveMoney(order, productAttrValues);
+            if (order.getCompanyUserId() != null || order.getCompanyId() != null) {
+                if (order.getCompanyUserId() == -1L) {
+                    companyService.addCompanyTuiLiveMoney(order);
+                } else {
+                    // 目前是一级佣金
+                    FsStoreProduct product = JSONUtil.toBean(order.getItemJson(), FsStoreProduct.class);
+                    List<FsStoreProductAttrValueScrm> productAttrValues = fsStoreProductAttrValueMapper.selectFsStoreProductAttrValueByProductId(product.getProductId());
+                    if (productAttrValues != null && !productAttrValues.isEmpty()) {
+                        userService.addTuiLiveMoney(order, productAttrValues);
+                    }
                 }
             }
 

+ 36 - 1
fs-service/src/main/java/com/fs/live/service/impl/LiveRedConfServiceImpl.java

@@ -8,8 +8,13 @@ import com.fs.common.constant.LiveKeysConstant;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.redis.RedisCache;
 import com.fs.common.utils.DateUtils;
+import com.fs.his.domain.FsUserIntegralLogs;
+import com.fs.his.enums.FsUserIntegralLogTypeEnum;
+import com.fs.his.mapper.FsUserIntegralLogsMapper;
 import com.fs.his.mapper.FsUserMapper;
 import com.fs.his.service.IFsUserService;
+import com.fs.hisStore.mapper.FsUserIntegralLogsScrmMapper;
+import com.fs.hisStore.mapper.FsUserScrmMapper;
 import com.fs.live.domain.*;
 import com.fs.live.mapper.LiveMapper;
 import com.fs.live.mapper.LiveRedConfMapper;
@@ -57,6 +62,11 @@ public class LiveRedConfServiceImpl implements ILiveRedConfService {
     private LiveRewardRecordMapper liveRewardRecordMapper;
     @Autowired
     private ILiveAutoTaskService liveAutoTaskService;
+    @Autowired
+    private FsUserScrmMapper fsUserScrmMapper;
+    @Autowired
+    private FsUserIntegralLogsMapper fsUserIntegralLogsMapper;
+
 
     private static final String REDPACKET_REMAININGLOTS_KEY = "live:red:remainingLots:";
     private static final String REDPACKET_REMAININGNUM_KEY = "live:red:remainingNum:";
@@ -223,7 +233,7 @@ public class LiveRedConfServiceImpl implements ILiveRedConfService {
     @Transactional
     public R claimRedPacket(RedPO red) {
         // String claimKey = REDPACKET_CLAIM_KEY + red.getRedId();
-        Object o = redisCache.hashGet(String.format(LiveKeysConstant.LIVE_HOME_PAGE_CONFIG_RED, red.getLiveId(), red.getRedId()), String.valueOf(red.getUserId()));
+        Object o = redisCache.hashGet(String.format(LiveKeysConstant. LIVE_HOME_PAGE_CONFIG_RED, red.getLiveId(), red.getRedId()), String.valueOf(red.getUserId()));
         if (ObjectUtil.isNotEmpty(o)) {
             return R.error("您已经领取过红包了!");
         }
@@ -270,6 +280,31 @@ public class LiveRedConfServiceImpl implements ILiveRedConfService {
         record.setUserId(red.getUserId());
         record.setIntegral(integral);
         record.setCreateTime(new Date());
+        // 更新用户余额
+        BigDecimal balanceAmount = BigDecimal.valueOf(integral);
+        int updateResult = fsUserScrmMapper.incrIntegral(red.getUserId(), balanceAmount);
+        if (updateResult <= 0) {
+            log.error("更新用户余额失败,userId: {}, balance: {}", red.getUserId(), balanceAmount);
+            return R.error("更新用户余额失败");
+        }
+
+        // 查询用户当前余额
+        com.fs.hisStore.domain.FsUserScrm user = fsUserScrmMapper.selectFsUserById(red.getUserId());
+        Long currentIntegral = user.getIntegral() != null ? user.getIntegral() : 0L;
+        Long newIntegral = currentIntegral + integral;
+
+        // 添加余额变动记录
+        FsUserIntegralLogs integralLogs = new FsUserIntegralLogs();
+        integralLogs.setUserId(red.getUserId());
+        integralLogs.setIntegral(integral);
+        integralLogs.setBalance(newIntegral);
+        integralLogs.setLogType(FsUserIntegralLogTypeEnum.TYPE_26.getValue()); // 3表示分享获得积分,可根据实际情况调整
+        integralLogs.setBusinessId(String.valueOf(red.getRedId()));
+        integralLogs.setBusinessType(Math.toIntExact(conf.getRedId())); // 1表示直播红包
+        integralLogs.setStatus(0);
+        integralLogs.setCreateTime(new Date());
+        fsUserIntegralLogsMapper.insertFsUserIntegralLogs(integralLogs);
+
         // WebSocket 通知
         //String msg = String.format("用户 %d 抢到了红包 %d,获得 %d 芳华币", userId, redId, integral);
         //WebSocketServer.notifyUsers(msg);

+ 7 - 0
fs-service/src/main/java/com/fs/live/service/impl/LiveServiceImpl.java

@@ -57,6 +57,7 @@ import org.apache.http.impl.client.HttpClients;
 import org.apache.http.util.EntityUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import com.fs.common.utils.sign.Md5Utils;
@@ -129,6 +130,10 @@ public class LiveServiceImpl implements ILiveService
     @Autowired
     LiveTagConfigMapper liveTagConfigMapper;
 
+    @Autowired
+    @Lazy
+    private ILiveWatchUserService liveWatchUserService;
+
     private static String TOKEN_VALID_CODE = "40001";
 
     private static volatile Integer version = 0;
@@ -295,6 +300,8 @@ public class LiveServiceImpl implements ILiveService
 			long seconds = live.getStartTime().until(now, ChronoUnit.SECONDS);
 			liveVo.setNowDuration(seconds);
 		}
+        Map<String, Integer> liveFlagWithCache = liveWatchUserService.getLiveFlagWithCache(live.getLiveId());
+        liveVo.setLiveFlag(liveFlagWithCache.get("liveFlag"));
         ThreadUtil.execute(()->{
             redisCache.deleteObject(String.format(LiveKeysConstant.LIVE_HOME_PAGE_DETAIL, live.getLiveId()));
             redisCache.setCacheObject(String.format(LiveKeysConstant.LIVE_HOME_PAGE_DETAIL, live.getLiveId()), liveVo,LiveKeysConstant.LIVE_HOME_PAGE_DETAIL_EXPIRE, TimeUnit.SECONDS);

+ 31 - 9
fs-service/src/main/java/com/fs/live/service/impl/LiveWatchUserServiceImpl.java

@@ -13,6 +13,7 @@ import com.fs.common.utils.DateUtils;
 import com.fs.common.utils.StringUtils;
 import com.fs.course.domain.FsCourseLink;
 import com.fs.course.service.impl.FsUserCourseVideoServiceImpl;
+import com.fs.enums.ExceptionCodeEnum;
 import com.fs.his.domain.FsUser;
 import com.fs.his.mapper.FsUserMapper;
 import com.fs.his.service.IFsUserService;
@@ -41,6 +42,7 @@ import lombok.extern.slf4j.Slf4j;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Lazy;
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
 
@@ -399,7 +401,21 @@ public class LiveWatchUserServiceImpl implements ILiveWatchUserService {
         try {
             // 从 Redis 获取用户进入时间
             String entryTimeKey = String.format(USER_ENTRY_TIME_KEY, liveId, userId);
-            Long entryTime = redisCache.getCacheObject(entryTimeKey);
+            Object entryTimeObj = redisCache.getCacheObject(entryTimeKey);
+            Long entryTime = null;
+            if (entryTimeObj != null) {
+                if (entryTimeObj instanceof Long) {
+                    entryTime = (Long) entryTimeObj;
+                } else if (entryTimeObj instanceof String) {
+                    try {
+                        entryTime = Long.parseLong((String) entryTimeObj);
+                    } catch (NumberFormatException e) {
+                        log.warn("无法解析进入时间字符串为Long: {}", entryTimeObj);
+                    }
+                } else if (entryTimeObj instanceof Number) {
+                    entryTime = ((Number) entryTimeObj).longValue();
+                }
+            }
             // 获取当前直播/回放状态
             Map<String, Integer> flagMap = this.getLiveFlagWithCache(liveId);
             Integer currentLiveFlag = flagMap.get("liveFlag");
@@ -654,10 +670,10 @@ public class LiveWatchUserServiceImpl implements ILiveWatchUserService {
         FsUser fsUser = fsUserMapper.selectFsUserByUserId(param.getUserId());
         //用户不存在唤起重新授权
         if (fsUser == null) {
-            return R.error(401, "未授权");
+            return R.error(ExceptionCodeEnum.USER_NOT_FOUND.getCode(), ExceptionCodeEnum.USER_NOT_FOUND.getDescription());
         }
         if (fsUser.getStatus() == 0) {
-            return R.error("会员被停用,无权限,请联系客服!");
+            return R.error(ExceptionCodeEnum.MEMBER_DISABLED.getCode(), ExceptionCodeEnum.MEMBER_DISABLED.getDescription());
         }
         //未注册提示
         String noRegisterMsg = "由于您还未完成注册,请联系伴学助手完成注册即可观看!";
@@ -669,7 +685,7 @@ public class LiveWatchUserServiceImpl implements ILiveWatchUserService {
         } else if (null != param.getQwExternalId()) {
             return handleLivePerson(param,fsUser, noMemberMsg, noRegisterMsg);
         } else {
-            return R.error("直播参数错误");
+            return R.error(ExceptionCodeEnum.LIVE_PARAM_ERROR.getCode(), ExceptionCodeEnum.LIVE_PARAM_ERROR.getDescription());
         }
 
     }
@@ -859,6 +875,12 @@ public class LiveWatchUserServiceImpl implements ILiveWatchUserService {
     @Override
     @Async
     public void qwTagMarkByLiveWatchLog(Long liveId) {
+        //休眠2分钟 等待看课中断的所有看课记录状态被正确处理标记
+        try {
+            Thread.sleep(120000L);
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e);
+        }
         log.info("处理直播间打标签: liveId={}", liveId);
         //查询直播间的标签配置
         List<LiveTagItemVO> liveTagConfig = liveTagConfigMapper.getLiveTagListByliveId(liveId);
@@ -1072,7 +1094,7 @@ public class LiveWatchUserServiceImpl implements ILiveWatchUserService {
         try {
 
             LiveWatchUser liveWatchUser = baseMapper.selectByUniqueIndex(liveId, userId, liveFlag, replayFlag);
-            
+
             if (liveWatchUser != null) {
                 if (liveWatchUser.getOnlineSeconds() == null || duration > liveWatchUser.getOnlineSeconds()) {
                     liveWatchUser.setOnlineSeconds(duration);
@@ -1101,25 +1123,25 @@ public class LiveWatchUserServiceImpl implements ILiveWatchUserService {
     public Long getTotalWatchDuration(Long liveId, Long userId) {
         try {
             long totalDuration = 0L;
-            
+
             // 1. 查询直播观看记录(liveFlag=1, replayFlag=0)
             LiveWatchUser liveRecord = baseMapper.selectByUniqueIndex(liveId, userId, 1, 0);
             if (liveRecord != null && liveRecord.getOnlineSeconds() != null) {
                 totalDuration += liveRecord.getOnlineSeconds();
             }
-            
+
             // 2. 查询回放观看记录(liveFlag=0, replayFlag=1)
             LiveWatchUser replayRecord = baseMapper.selectByUniqueIndex(liveId, userId, 0, 1);
             if (replayRecord != null && replayRecord.getOnlineSeconds() != null) {
                 totalDuration += replayRecord.getOnlineSeconds();
             }
-            
+
             log.debug("查询总观看时长: liveId={}, userId={}, liveDuration={}, replayDuration={}, total={}",
                     liveId, userId,
                     liveRecord != null ? liveRecord.getOnlineSeconds() : 0,
                     replayRecord != null ? replayRecord.getOnlineSeconds() : 0,
                     totalDuration);
-            
+
             return totalDuration;
         } catch (Exception e) {
             log.error("查询总观看时长失败: liveId={}, userId={}", liveId, userId, e);

+ 2 - 1
fs-service/src/main/java/com/fs/live/vo/LiveVo.java

@@ -58,7 +58,8 @@ public class LiveVo {
     private Integer previewVideoType;
     private Long previewVideoId;
     private Integer globalVisible;
-    
+    private Integer liveFlag;
+
     /** 是否开启直播完课积分功能 */
     private Boolean completionPointsEnabled;
     

+ 3 - 0
fs-service/src/main/java/com/fs/qw/domain/QwUser.java

@@ -113,4 +113,7 @@ public class QwUser extends BaseEntity
 
     @TableField(exist = false)
     private Integer disableCompanyId;
+
+    //根据ai状态判断是否启用
+    private Integer aiStatus;
 }

+ 1 - 0
fs-service/src/main/java/com/fs/qw/service/IQwUserService.java

@@ -208,4 +208,5 @@ public interface IQwUserService
 
     R unbindQwUserByServerIds(List<String> serverIds);
 
+    R updateQwUserFastGptRoleStatusById(Long id);
 }

+ 18 - 0
fs-service/src/main/java/com/fs/qw/service/impl/QwUserServiceImpl.java

@@ -1625,6 +1625,24 @@ public class QwUserServiceImpl implements IQwUserService
         return R.ok();
     }
 
+    @Override
+    public R updateQwUserFastGptRoleStatusById(Long id) {
+        QwUser qwUser = qwUserMapper.selectQwUserById(id);
+        if(qwUser != null){
+            Integer aiStatus = qwUser.getAiStatus() == 0 ? 1 : 0;
+            QwUser qwUserNew = new QwUser();
+            qwUserNew.setId(id);
+            qwUserNew.setAiStatus(aiStatus);
+            int i = qwUserMapper.updateQwUser(qwUserNew);
+            if (i > 0) {
+                return R.ok("修改成功");
+            }else {
+                return R.error("修改失败");
+            }
+        }
+        return R.error("修改失败,未找到企微!");
+    }
+
     /**
      * 根据销售公司和企微ID查询企微用户
      */

+ 2 - 0
fs-service/src/main/java/com/fs/qw/vo/QwUserVO.java

@@ -118,4 +118,6 @@ public class QwUserVO {
     private Long doctorId;
 
     private Integer videoGetStatus;
+
+    private Integer aiStatus;
 }

+ 6 - 6
fs-service/src/main/resources/application-config-druid-shdn.yml

@@ -73,10 +73,10 @@ nuonuo:
 tencent_cloud_config:
   secret_id: AKIDiMq9lDf2EOM9lIfqqfKo7FNgM5meD0sT
   secret_key: u5SuS80342xzx8FRBukza9lVNHKNMSaB
-  bucket: jnlzjk-1323137866
+  bucket: shdn-1323137866
   app_id: 1323137866
   region: ap-chongqing
-  proxy: jnlzjk
+  proxy: shdn
 tmp_secret_config:
   secret_id: AKIDCj7NSNAovtqeJpBau8GZ4CGB71thXIxX
   secret_key: lTB5zwqqz7CNhzDOWivFWedgfTBgxgBT
@@ -85,10 +85,10 @@ tmp_secret_config:
   region: ap-chongqing
   proxy: fs
 cloud_host:
-  company_name: 济南联志健康
-  projectCode: LZJK
-  spaceName:
-  volcengineUrl: https://jnlzvolcengine.ylrztop.com
+  company_name: 上海德宁
+  projectCode: SHDN
+  spaceName: shdn-20251226
+  volcengineUrl: https://shdnjbvolcengine.ylrztop.com
 headerImg:
   imgUrl:
 

+ 6 - 0
fs-service/src/main/resources/mapper/company/CompanyUserMapper.xml

@@ -732,5 +732,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         WHERE bind_company_user_id = #{companyUserId}
         AND is_del = 0
     </select>
+    <select id="selectCompanyUserByQwUserId" resultType="com.fs.company.domain.CompanyUser">
+        SELECT * FROM company_user
+        WHERE FIND_IN_SET(#{qwUserId}, qw_user_id) > 0
+        order by create_time desc
+            limit 1
+    </select>
 
 </mapper>

+ 2 - 0
fs-service/src/main/resources/mapper/his/FsUserIntegralLogsMapper.xml

@@ -58,6 +58,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="createTime != null">create_time,</if>
             <if test="businessType != null">business_type,</if>
             <if test="status != null">status,</if>
+            <if test="remark != null">remark,</if>
         </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="userId != null">#{userId},</if>
@@ -68,6 +69,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="createTime != null">#{createTime},</if>
             <if test="businessType != null">#{businessType},</if>
             <if test="status != null">#{status},</if>
+            <if test="remark != null">#{remark},</if>
          </trim>
     </insert>
 

+ 9 - 0
fs-service/src/main/resources/mapper/hisStore/FsStoreProductScrmMapper.xml

@@ -513,6 +513,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <if test='config.isAudit == "1"'>
             and p.is_audit = '1'
         </if>
+        <if test='appId != null and appId = "" '>
+            and ((FIND_IN_SET(#{appId}, p.app_ids) > 0))
+        </if>
         and p.is_new=1 and p.is_display=1 order by p.sort desc limit #{count}
     </select>
     <select id="selectFsStoreProductHotQuery" resultType="com.fs.hisStore.vo.FsStoreProductListQueryVO">
@@ -524,6 +527,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <if test='config.isAudit == "1" '>
         and p.is_audit = '1'
         </if>
+        <if test='appId != null and appId = "" '>
+            and ((FIND_IN_SET(#{appId}, p.app_ids) > 0))
+        </if>
         and  p.is_hot=1 and p.is_display=1 order by p.sort desc limit #{count}
     </select>
     <select id="selectFsStoreProductGoodListQuery" resultType="com.fs.hisStore.vo.FsStoreProductListQueryVO">
@@ -535,6 +541,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <if test='config.isAudit == "1" '>
         and p.is_audit = '1'
         </if>
+        <if test = 'param.appId != null and param.appId != ""'>
+        and ((FIND_IN_SET(#{param.appId}, p.app_ids) > 0))
+        </if>
         and  p.is_good=1 and p.is_display=1 order by p.sort desc
     </select>
 

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

@@ -146,7 +146,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             AND csc.appid = #{maps.appId}
           </if>
         <if test="maps.hfshh != null and maps.hfshh != ''">
-            AND hfshh = #{maps.hfshh}
+            AND CONCAT('store-',sp_latest.pay_code) = #{maps.hfshh}
         </if>
           group by o.id
           UNION ALL
@@ -291,7 +291,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             AND csc.appid = #{maps.appId}
           </if>
         <if test="maps.hfshh != null and maps.hfshh != ''">
-            AND hfshh = #{maps.hfshh}
+            AND CONCAT('store-',sp_latest.pay_code) = #{maps.hfshh}
         </if>
         group by o.id
           UNION ALL
@@ -433,7 +433,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             AND DATE(o.delivery_send_time) BETWEEN SUBSTRING_INDEX(#{maps.deliverySendTimeRange}, '--', 1) AND SUBSTRING_INDEX(#{maps.deliverySendTimeRange}, '--', -1)
           </if>
         <if test="maps.hfshh != null and maps.hfshh != ''">
-            AND hfshh = #{maps.hfshh}
+            AND CONCAT('store-',sp_latest.pay_code) = #{maps.hfshh}
         </if>
         group by o.order_id
         ) AS merged_orders

+ 8 - 2
fs-service/src/main/resources/mapper/live/LiveAfterSalesMapper.xml

@@ -80,6 +80,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 
         <where>
             <if test="hfOrderCode != null and hfOrderCode != ''"> and lop.pay_code = #{hfOrderCode}</if>
+            <if test="bankTransactionId != null and bankTransactionId != ''"> and lop.bank_transaction_id = #{bankTransactionId}</if>
             <if test="liveId != null and liveId != ''"> and las.live_id = #{liveId}</if>
             <if test="companyUserNickName != null and companyUserNickName != ''"> and cu.nick_name like concat(#{companyUserNickName},'%')</if>
             <if test="storeId != null and storeId != ''"> and las.store_id = #{storeId}</if>
@@ -109,6 +110,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="companyUserId != null "> and las.company_user_id = #{companyUserId}</if>
             <if test="deptId != null "> and cu.dept_id = #{deptId}</if>
             <if test="userPhone != null "> and lo.user_phone like concat(#{userPhone},'%')</if>
+            <if test="createTimeBegin != null and createTimeBegin != ''"> and date_format(las.create_time,'%y%m%d') &gt;= date_format(#{createTimeBegin},'%y%m%d')</if>
+            <if test="createTimeEnd != null and createTimeEnd != ''"> and date_format(las.create_time,'%y%m%d') &lt;= date_format(#{createTimeEnd},'%y%m%d')</if>
         </where>
         <if test="productName != null and productName != ''">
         group by las.id
@@ -118,7 +121,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     <select id="selectLiveAfterSalesVoListExport" parameterType="com.fs.live.vo.LiveAfterSalesVo" resultType="com.fs.live.vo.LiveAfterSalesVo">
         select las.id, las.live_id, las.store_id, las.refund_amount,
         las.refund_type, las.reasons, las.explains, las.explain_img, las.delivery_code, las.delivery_sn, las.delivery_name, las.status, las.sales_status,
-        las.order_status, las.create_time, las.is_del, las.user_id, las.consignee, las.phone_number, las.address, las.company_id, las.company_user_id, las.dept_id,
+        las.order_status, lo.create_time, las.is_del, las.user_id, las.consignee, las.phone_number, las.address, las.company_id, las.company_user_id, las.dept_id,
         cu.nick_name as company_user_nick_name, c.company_name,lo.order_id,lo.order_code,lo.user_phone,lo.item_json,lo.pay_time as orderPayTime,
         lo.user_address,lo.user_name,lo.pay_price,lo.total_postage,lop.bank_serial_no,lo.delivery_sn as orderDeliveryId,lo.delivery_name as orderDeliveryName,
         lo.delivery_code as orderDeliverySn,lo.status as orderStatus,lop.bank_transaction_id,lo.pay_money,lop.pay_code as payCode
@@ -131,8 +134,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         left join live_order_item loi on loi.order_id = lo.order_id
         </if>
 
-        where 1=1 and las.status =4 and lop.bank_transaction_id is not null
+        where 1=1 and las.is_del = 0 and lo.status = -2 and lop.bank_transaction_id is not null
             <if test="hfOrderCode != null and hfOrderCode != ''"> and lop.pay_code = #{hfOrderCode}</if>
+            <if test="bankTransactionId != null and bankTransactionId != ''"> and lop.bank_transaction_id = #{bankTransactionId}</if>
             <if test="liveId != null and liveId != ''"> and las.live_id = #{liveId}</if>
             <if test="companyUserNickName != null and companyUserNickName != ''"> and cu.nick_name like concat(#{companyUserNickName},'%')</if>
             <if test="storeId != null and storeId != ''"> and las.store_id = #{storeId}</if>
@@ -162,6 +166,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="companyUserId != null "> and las.company_user_id = #{companyUserId}</if>
             <if test="deptId != null "> and cu.dept_id = #{deptId}</if>
             <if test="userPhone != null "> and lo.user_phone like concat(#{userPhone},'%')</if>
+            <if test="createTimeBegin != null and createTimeBegin != ''"> and date_format(las.create_time,'%y%m%d') &gt;= date_format(#{createTimeBegin},'%y%m%d')</if>
+            <if test="createTimeEnd != null and createTimeEnd != ''"> and date_format(las.create_time,'%y%m%d') &lt;= date_format(#{createTimeEnd},'%y%m%d')</if>
 
         <if test="productName != null and productName != ''">
         group by las.id

+ 9 - 0
fs-service/src/main/resources/mapper/live/LiveCompletionPointsRecordMapper.xml

@@ -89,6 +89,15 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         LIMIT 1
     </select>
 
+    <!-- 查询用户在某直播间最近一次完课记录(不限制日期) -->
+    <select id="selectLatestByUserAndLiveId" resultMap="LiveCompletionPointsRecordResult">
+        SELECT * FROM live_completion_points_record
+        WHERE live_id = #{liveId}
+          AND user_id = #{userId}
+        ORDER BY current_completion_date DESC, id DESC
+        LIMIT 1
+    </select>
+
     <!-- 查询用户未领取的完课记录列表 -->
     <select id="selectUnreceivedByUser" resultMap="LiveCompletionPointsRecordResult">
         SELECT * FROM live_completion_points_record

+ 7 - 1
fs-service/src/main/resources/mapper/qw/QwUserMapper.xml

@@ -35,10 +35,15 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="isAuto"    column="is_auto"    />
         <result property="videoGetStatus"    column="video_get_status"    />
         <result property="updateTime"    column="update_time"    />
+        <result property="aiStatus"    column="ai_status"    />
     </resultMap>
 
     <sql id="selectQwUserVo">
-        select id,is_auto, video_get_status, qw_user_id,server_id,server_status,ipad_status,config_id,vid,uid,contact_way,app_key, qw_user_name, department, openid, company_id, company_user_id, corp_id, status, is_del, welcome_text, welcome_image, is_send_msg,app_key,qw_hook_id,fastGpt_role_id,login_status,tool_status,login_code_url,version,update_time from qw_user
+        select id,is_auto, video_get_status, qw_user_id,server_id,server_status,
+               ipad_status,config_id,vid,uid,contact_way,app_key, qw_user_name, department,
+               openid, company_id, company_user_id, corp_id, status, is_del, welcome_text,
+               welcome_image, is_send_msg,app_key,qw_hook_id,fastGpt_role_id,login_status,
+               tool_status,login_code_url,version,update_time,ai_status from qw_user
         </sql>
 
     <select id="selectQwUserList" parameterType="QwUser" resultMap="QwUserResult">
@@ -178,6 +183,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="serverStatus != null">server_status = #{serverStatus},</if>
             <if test="isAuto != null">is_auto = #{isAuto},</if>
             <if test="videoGetStatus != null">video_get_status = #{videoGetStatus},</if>
+            <if test="aiStatus != null">ai_status = #{aiStatus},</if>
         </trim>
         where id = #{id}
     </update>

+ 7 - 1
fs-user-app/src/main/java/com/fs/app/controller/course/CourseQwController.java

@@ -25,6 +25,7 @@ import com.fs.his.service.IFsIntegralGoodsService;
 import com.fs.sop.domain.QwSop;
 import com.fs.sop.service.IQwSopService;
 import com.fs.system.service.ISysConfigService;
+import com.fs.voice.utils.StringUtil;
 import com.github.pagehelper.PageHelper;
 import com.github.pagehelper.PageInfo;
 import com.hc.openapi.tool.fastjson.JSON;
@@ -237,10 +238,15 @@ public class CourseQwController extends AppBaseController {
     @PostMapping("/sendReward")
     @RepeatSubmit
     @UserOperationLog(operationType = FsUserOperationEnum.SENDREWARD)
-    public R sendReward(@RequestBody @Valid FsCourseSendRewardUParam param)
+    public R sendReward(@RequestBody FsCourseSendRewardUParam param)
     {
         param.setUserId(Long.parseLong(getUserId()));
         logger.info("【发放奖励】3:{}",param);
+
+        if ( param.getSource()!=3  && StringUtil.strIsNullOrEmpty(param.getAppId())){
+            return R.error("appId不能为空");
+        }
+
         return courseVideoService.sendReward(param);
     }
 

+ 296 - 0
fs-user-app/src/main/java/com/fs/app/controller/live/LiveCompletionPointsController.java

@@ -1,15 +1,29 @@
 package com.fs.app.controller.live;
 
 import com.fs.app.controller.AppBaseController;
+import com.fs.app.vo.ReceivePointsVO;
+import com.fs.app.vo.RemainingTimeVO;
+import com.fs.app.vo.UpdateWatchDurationVO;
 import com.fs.common.annotation.RepeatSubmit;
 import com.fs.common.core.domain.R;
+import com.fs.common.exception.base.BaseException;
 import com.fs.his.domain.FsUser;
+import com.fs.his.domain.FsUserIntegralLogs;
+import com.fs.his.service.IFsUserIntegralLogsService;
 import com.fs.his.service.IFsUserService;
+import com.fs.live.domain.Live;
 import com.fs.live.domain.LiveCompletionPointsRecord;
+import com.fs.live.mapper.LiveCompletionPointsRecordMapper;
 import com.fs.live.service.ILiveCompletionPointsRecordService;
+import com.fs.live.service.ILiveService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
 
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.util.Date;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -27,6 +41,15 @@ public class LiveCompletionPointsController extends AppBaseController {
     @Autowired
     private IFsUserService fsUserService;
 
+    @Autowired
+    private ILiveService liveService;
+
+    @Autowired
+    private IFsUserIntegralLogsService fsUserIntegralLogsService;
+
+    @Autowired
+    private LiveCompletionPointsRecordMapper completionPointsRecordMapper;
+
     /**
      * 领取完课积分
      */
@@ -107,4 +130,277 @@ public class LiveCompletionPointsController extends AppBaseController {
             return R.error("创建失败: " + e.getMessage());
         }
     }
+
+    /**
+     * 第一个接口:查询当前用户当前直播间领取积分的剩余时长
+     * GET请求,传入直播间id
+     * 查询当前用户和当前直播间的积分记录(不限制日期),如果不存在就生成看课记录
+     */
+    @GetMapping("/remaining-time")
+    public R getRemainingTime(@RequestParam Long liveId) {
+        Long userId = Long.parseLong(getUserId());
+        
+        try {
+            // 1. 获取直播间信息
+            Live live = liveService.selectLiveByLiveId(liveId);
+            if (live == null) {
+                return R.error("直播间不存在");
+            }
+
+            // 2. 查询当前用户和当前直播间的最近一次完课记录(不限制日期)
+            LiveCompletionPointsRecord record = completionPointsRecordMapper.selectLatestByUserAndLiveId(liveId, userId);
+            
+            // 3. 如果没有记录,查询直播间配置并生成记录
+            if (record == null) {
+                completionPointsRecordService.checkAndCreateCompletionRecord(liveId, userId, null);
+                // 重新查询
+                record = completionPointsRecordMapper.selectLatestByUserAndLiveId(liveId, userId);
+            }
+
+            // 4. 计算剩余时长
+            RemainingTimeVO vo = new RemainingTimeVO();
+            Long videoDuration = live.getDuration() != null ? live.getDuration() : 0L;
+            Long watchDuration = record != null && record.getWatchDuration() != null 
+                    ? record.getWatchDuration() : 0L;
+            
+            vo.setVideoDuration(videoDuration);
+            if (record != null) {
+                vo.setCompletionRate(record.getCompletionRate());
+            }
+            vo.setWatchDuration(watchDuration);
+            vo.setRemainingTime(Math.max(0, videoDuration - watchDuration));
+            vo.setHasReceived(record != null && record.getReceiveStatus() != null && record.getReceiveStatus() == 1);
+            
+            return R.ok().put("data", vo);
+        } catch (Exception e) {
+            return R.error("查询失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 第二个接口:更新用户的看课时长
+     * POST请求,传入直播间id和看课时长
+     * 更新用户看课completionPointsRecordService看课记录里面的时长
+     */
+    @PostMapping("/update-watch-duration")
+    public R updateWatchDuration(@RequestParam Long liveId, @RequestParam Long watchDuration) {
+        Long userId = Long.parseLong(getUserId());
+        
+        try {
+            // 1. 获取直播间信息
+            Live live = liveService.selectLiveByLiveId(liveId);
+            if (live == null) {
+                return R.error("直播间不存在");
+            }
+
+            // 2. 判断当前时间是否在直播期间(状态为2,直播中)
+            boolean isLiveInProgress = false;
+            LocalDateTime now = LocalDateTime.now();
+            
+            if (live.getStatus() != null && live.getStatus() == 2) {
+                // status=2 表示直播中
+                isLiveInProgress = true;
+            } else if (live.getStartTime() != null && live.getFinishTime() != null) {
+                // 判断当前时间是否在开播时间和结束时间之间
+                isLiveInProgress = (now.isAfter(live.getStartTime()) || now.isEqual(live.getStartTime()))
+                        && (now.isBefore(live.getFinishTime()) || now.isEqual(live.getFinishTime()));
+            }
+
+            if (!isLiveInProgress) {
+                return R.error("当前不在直播期间,无法更新看课时长");
+            }
+
+            // 3. 查询当前直播间的完课记录(不限制日期)
+            LiveCompletionPointsRecord record = completionPointsRecordMapper.selectLatestByUserAndLiveId(liveId, userId);
+            
+            // 4. 计算看课时长
+            Date updateTime = null;
+            if (record != null && record.getUpdateTime() != null) {
+                updateTime = record.getUpdateTime();
+            }
+            
+            // 判断更新时间与直播间开始时间的关系
+            Date startTime = live.getStartTime() != null 
+                    ? java.sql.Timestamp.valueOf(live.getStartTime()) : null;
+            
+            Date currentTime = new Date();
+            long timeDiff = 0L;
+            
+            if (updateTime != null && startTime != null) {
+                if (updateTime.before(startTime)) {
+                    // 更新时间小于直播间开始时间,使用直播间开始时间进行计算
+                    timeDiff = (currentTime.getTime() - startTime.getTime()) / 1000; // 转换为秒
+                } else {
+                    // 更新时间大于等于开播时间,按照更新时间进行计算
+                    timeDiff = (currentTime.getTime() - updateTime.getTime()) / 1000; // 转换为秒
+                }
+            } else if (startTime != null) {
+                // 没有更新记录,使用直播间开始时间计算
+                timeDiff = (currentTime.getTime() - startTime.getTime()) / 1000; // 转换为秒
+            }
+            
+            // 5. 如果请求传入的时间大于这个时间差,就使用计算出的看课时长,否则使用请求传入的时长
+            Long finalWatchDuration;
+            if (watchDuration > timeDiff) {
+                // 请求传入的时间大于时间差,使用计算出的看课时长
+                finalWatchDuration = timeDiff;
+            } else {
+                // 否则使用请求传入的时长
+                finalWatchDuration = watchDuration;
+            }
+            
+            // 6. 更新完课记录中的看课时长
+            if (record == null) {
+                // 如果没有记录,先创建记录
+                completionPointsRecordService.checkAndCreateCompletionRecord(liveId, userId, finalWatchDuration);
+                record = completionPointsRecordMapper.selectLatestByUserAndLiveId(liveId, userId);
+            } else {
+                // 更新现有记录的看课时长
+                Long currentWatchDuration = record.getWatchDuration() != null 
+                        ? record.getWatchDuration() : 0L;
+                record.setWatchDuration(currentWatchDuration + finalWatchDuration);
+                
+                // 重新计算完课比例
+                Long videoDuration = live.getDuration();
+                if (videoDuration != null && videoDuration > 0) {
+                    BigDecimal completionRate = BigDecimal.valueOf(record.getWatchDuration())
+                            .multiply(BigDecimal.valueOf(100))
+                            .divide(BigDecimal.valueOf(videoDuration), 2, java.math.RoundingMode.HALF_UP);
+                    if (completionRate.compareTo(BigDecimal.valueOf(100)) > 0) {
+                        completionRate = BigDecimal.valueOf(100);
+                    }
+                    record.setCompletionRate(completionRate);
+                }
+                
+                completionPointsRecordMapper.updateRecord(record);
+            }
+
+            UpdateWatchDurationVO vo = new UpdateWatchDurationVO();
+            vo.setWatchDuration(finalWatchDuration);
+            vo.setTotalWatchDuration(record != null && record.getWatchDuration() != null 
+                    ? record.getWatchDuration() : finalWatchDuration);
+            
+            return R.ok().put("data", vo);
+        } catch (Exception e) {
+            return R.error("更新失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 第三个接口:用户领取看课积分
+     * POST请求,传入直播间id
+     * 只查询这个直播间的看课记录(不限制日期),检查是否达到了完课标准
+     * 达到了更新了看课记录里面的领取条件,给用户发积分
+     * 没达到,返回报错
+     */
+    @PostMapping("/receive-points")
+    @RepeatSubmit
+    public R receivePoints(@RequestParam Long liveId) {
+        Long userId = Long.parseLong(getUserId());
+        
+        try {
+            // 1. 查询当前用户和当前直播间的最近一次完课记录(不限制日期)
+            LiveCompletionPointsRecord record = completionPointsRecordMapper.selectLatestByUserAndLiveId(liveId, userId);
+            
+            if (record == null) {
+                return R.error("您还没有看课记录,无法领取积分");
+            }
+
+            // 2. 获取直播间信息和配置
+            Live live = liveService.selectLiveByLiveId(liveId);
+            if (live == null) {
+                return R.error("直播间不存在");
+            }
+
+            // 3. 检查看课记录里面的时长是否达到完课标准
+            Long watchDuration = record.getWatchDuration();
+            if (watchDuration == null || watchDuration <= 0) {
+                return R.error("您的看课时长不足,无法领取积分");
+            }
+
+            // 4. 检查完课比例是否达到标准
+            BigDecimal completionRate = record.getCompletionRate();
+            if (completionRate == null) {
+                // 重新计算完课比例
+                Long videoDuration = live.getDuration();
+                if (videoDuration == null || videoDuration <= 0) {
+                    return R.error("直播间视频时长配置错误");
+                }
+                completionRate = BigDecimal.valueOf(watchDuration)
+                        .multiply(BigDecimal.valueOf(100))
+                        .divide(BigDecimal.valueOf(videoDuration), 2, java.math.RoundingMode.HALF_UP);
+                if (completionRate.compareTo(BigDecimal.valueOf(100)) > 0) {
+                    completionRate = BigDecimal.valueOf(100);
+                }
+                record.setCompletionRate(completionRate);
+            }
+
+            // 5. 从直播间配置获取完课标准
+            String configJson = live.getConfigJson();
+            Integer requiredCompletionRate = null;
+            if (configJson != null && !configJson.isEmpty()) {
+                try {
+                    com.alibaba.fastjson.JSONObject jsonConfig = com.alibaba.fastjson.JSON.parseObject(configJson);
+                    requiredCompletionRate = jsonConfig.getInteger("completionRate");
+                } catch (Exception e) {
+                    // 解析失败,忽略
+                }
+            }
+
+            // 6. 判断是否达到完课标准
+            if (requiredCompletionRate != null && completionRate.compareTo(BigDecimal.valueOf(requiredCompletionRate)) < 0) {
+                return R.error("您的完课比例未达到标准(" + requiredCompletionRate + "%),当前完课比例:" + completionRate + "%");
+            }
+
+            // 7. 检查是否已领取
+            if (record.getReceiveStatus() != null && record.getReceiveStatus() == 1) {
+                return R.error("该完课积分已领取");
+            }
+
+            // 8. 领取积分(更新看课记录的领取状态,给用户加积分)
+            LiveCompletionPointsRecord receivedRecord = completionPointsRecordService.receiveCompletionPoints(record.getId(), userId);
+
+            ReceivePointsVO vo = new ReceivePointsVO();
+            vo.setRecord(receivedRecord);
+            vo.setPoints(receivedRecord.getPointsAwarded());
+            vo.setContinuousDays(receivedRecord.getContinuousDays());
+            
+            return R.ok().put("data", vo);
+        } catch (BaseException e) {
+            return R.error(e.getMessage());
+        } catch (Exception e) {
+            return R.error("领取失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 第四个接口:查询用户自己的积分记录
+     * GET请求
+     */
+    @GetMapping("/integral-logs")
+    public R getIntegralLogs(@RequestParam(required = false) Integer type) {
+        Long userId = Long.parseLong(getUserId());
+        
+        try {
+            FsUserIntegralLogs query = new FsUserIntegralLogs();
+            query.setUserId(userId);
+            
+            List<FsUserIntegralLogs> logs = fsUserIntegralLogsService.selectFsUserIntegralLogsList(query);
+            
+            // 如果指定了类型,进行过滤
+            if (type != null) {
+                if (type == 1) {
+                    // 获得积分(积分大于0)
+                    logs.removeIf(log -> log.getIntegral() == null || log.getIntegral() <= 0);
+                } else if (type == 2) {
+                    // 消耗积分(积分小于0)
+                    logs.removeIf(log -> log.getIntegral() == null || log.getIntegral() >= 0);
+                }
+            }
+            
+            return R.ok().put("data", logs);
+        } catch (Exception e) {
+            return R.error("查询失败: " + e.getMessage());
+        }
+    }
 }

+ 2 - 2
fs-user-app/src/main/java/com/fs/app/controller/store/IndexScrmController.java

@@ -69,8 +69,8 @@ public class IndexScrmController extends AppBaseController {
 	public R getIndexData(HttpServletRequest request){
 		List<FsArticleCateListQueryVO> articleCateList=articleCateService.selectFsArticleCateListQuery();
 		List<FsAdvListQueryVO> advList=advService.selectFsAdvListQuery(1);
-		List<FsStoreProductListQueryVO> newProductList=productService.selectFsStoreProductNewQuery(10);
-		List<FsStoreProductListQueryVO> hotProductList=productService.selectFsStoreProductHotQuery(12);
+		List<FsStoreProductListQueryVO> newProductList=productService.selectFsStoreProductNewQuery(10, request.getParameter("appId"));
+		List<FsStoreProductListQueryVO> hotProductList=productService.selectFsStoreProductHotQuery(12, request.getParameter("appId"));
 		IndexVO vo=IndexVO.builder().articleCateList(articleCateList).advList(advList).newProductList(newProductList).hotProductList(hotProductList).build();
 		return R.ok().put("data", vo);
 	}

+ 1 - 1
fs-user-app/src/main/java/com/fs/app/controller/store/ProductScrmController.java

@@ -268,7 +268,7 @@ public class ProductScrmController extends AppBaseController {
     @GetMapping("/getGoodsProducts")
     public R getGoodsProducts(BaseQueryParam param, HttpServletRequest request){
         PageHelper.startPage(param.getPage(), param.getPageSize());
-        List<FsStoreProductListQueryVO> list=productService.selectFsStoreProductGoodListQuery();
+        List<FsStoreProductListQueryVO> list=productService.selectFsStoreProductGoodListQuery(param);
         PageInfo<FsStoreProductListQueryVO> listPageInfo=new PageInfo<>(list);
         return R.ok().put("data",listPageInfo);
 

+ 25 - 0
fs-user-app/src/main/java/com/fs/app/vo/ReceivePointsVO.java

@@ -0,0 +1,25 @@
+package com.fs.app.vo;
+
+import com.fs.live.domain.LiveCompletionPointsRecord;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 领取积分返回VO
+ */
+@Data
+public class ReceivePointsVO implements Serializable {
+    
+    private static final long serialVersionUID = 1L;
+    
+    /** 完课记录 */
+    private LiveCompletionPointsRecord record;
+    
+    /** 获得的积分 */
+    private Integer points;
+    
+    /** 连续天数 */
+    private Integer continuousDays;
+}
+

+ 31 - 0
fs-user-app/src/main/java/com/fs/app/vo/RemainingTimeVO.java

@@ -0,0 +1,31 @@
+package com.fs.app.vo;
+
+import lombok.Data;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+/**
+ * 剩余时长返回VO
+ */
+@Data
+public class RemainingTimeVO implements Serializable {
+    
+    private static final long serialVersionUID = 1L;
+    
+    /** 剩余时长(秒) */
+    private Long remainingTime;
+    
+    /** 已观看时长(秒) */
+    private Long watchDuration;
+    
+    /** 视频总时长(秒) */
+    private Long videoDuration;
+    
+    /** 是否领取过 */
+    private Boolean hasReceived;
+
+    /** 完课比例(%) */
+    private BigDecimal completionRate;
+}
+

+ 21 - 0
fs-user-app/src/main/java/com/fs/app/vo/UpdateWatchDurationVO.java

@@ -0,0 +1,21 @@
+package com.fs.app.vo;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 更新看课时长返回VO
+ */
+@Data
+public class UpdateWatchDurationVO implements Serializable {
+    
+    private static final long serialVersionUID = 1L;
+    
+    /** 本次更新的看课时长(秒) */
+    private Long watchDuration;
+    
+    /** 总看课时长(秒) */
+    private Long totalWatchDuration;
+}
+