Переглянути джерело

Merge remote-tracking branch 'refs/remotes/origin/master' into Payment-Configuration

# Conflicts:
#	fs-service/src/main/resources/mapper/hisStore/FsStorePaymentScrmMapper.xml
yfh 2 днів тому
батько
коміт
f8c910a174
100 змінених файлів з 1509 додано та 417 видалено
  1. 139 0
      deploy - test.sh
  2. 1 1
      fs-ad-new-api/src/main/java/com/fs/app/controller/CallbackController.java
  3. 47 27
      fs-ad-new-api/src/main/java/com/fs/app/controller/WeChatController.java
  4. 1 0
      fs-ad-new-api/src/main/java/com/fs/app/facade/CallbackProcessingFacadeServiceImpl.java
  5. 1 0
      fs-ad-new-api/src/main/resources/application.yml
  6. 25 4
      fs-admin/src/main/java/com/fs/his/controller/FsStorePaymentController.java
  7. 84 0
      fs-admin/src/main/java/com/fs/hisStore/task/LiveTask.java
  8. 9 0
      fs-admin/src/main/java/com/fs/live/controller/LiveController.java
  9. 15 1
      fs-admin/src/main/java/com/fs/live/controller/LiveDataController.java
  10. 2 6
      fs-admin/src/main/java/com/fs/live/controller/LiveVideoController.java
  11. 6 4
      fs-admin/src/main/java/com/fs/live/controller/OrderController.java
  12. 10 0
      fs-admin/src/main/java/com/fs/qw/controller/QwFriendWelcomeController.java
  13. 5 4
      fs-company-app/src/main/java/com/fs/app/controller/FsUserController.java
  14. 9 0
      fs-company/src/main/java/com/fs/company/controller/course/FsUserCoursePeriodController.java
  15. 10 2
      fs-company/src/main/java/com/fs/company/controller/live/LiveController.java
  16. 12 3
      fs-company/src/main/java/com/fs/company/controller/live/LiveDataController.java
  17. 4 2
      fs-company/src/main/java/com/fs/company/controller/live/OrderController.java
  18. 13 0
      fs-company/src/main/java/com/fs/company/controller/qw/QwSopTempController.java
  19. 2 2
      fs-company/src/main/java/com/fs/company/controller/store/FsUserController.java
  20. 2 38
      fs-live-app/src/main/java/com/fs/live/task/LiveCompletionPointsTask.java
  21. 37 14
      fs-live-app/src/main/java/com/fs/live/task/Task.java
  22. 2 2
      fs-live-app/src/main/java/com/fs/live/websocket/auth/WebSocketConfigurator.java
  23. 82 5
      fs-live-app/src/main/java/com/fs/live/websocket/service/WebSocketServer.java
  24. 2 0
      fs-service/src/main/java/com/fs/common/param/BaseQueryParam.java
  25. 3 0
      fs-service/src/main/java/com/fs/company/domain/Company.java
  26. 3 0
      fs-service/src/main/java/com/fs/company/vo/CompanyVO.java
  27. 3 0
      fs-service/src/main/java/com/fs/course/domain/FsUserCoursePeriod.java
  28. 0 2
      fs-service/src/main/java/com/fs/course/service/IFsCourseRedPacketLogService.java
  29. 2 0
      fs-service/src/main/java/com/fs/course/service/IFsUserCoursePeriodService.java
  30. 47 37
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseRedPacketLogServiceImpl.java
  31. 12 0
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCoursePeriodServiceImpl.java
  32. 50 2
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java
  33. 3 0
      fs-service/src/main/java/com/fs/course/vo/FsUserCoursePeriodVO.java
  34. 1 1
      fs-service/src/main/java/com/fs/fastGpt/service/impl/AiHookServiceImpl.java
  35. 5 0
      fs-service/src/main/java/com/fs/his/domain/FsStorePaymentError.java
  36. 5 5
      fs-service/src/main/java/com/fs/his/domain/FsUser.java
  37. 7 0
      fs-service/src/main/java/com/fs/his/mapper/FsIntegralCartMapper.java
  38. 2 2
      fs-service/src/main/java/com/fs/his/mapper/FsUserMapper.java
  39. 9 0
      fs-service/src/main/java/com/fs/his/service/IFsStorePaymentErrorService.java
  40. 7 0
      fs-service/src/main/java/com/fs/his/service/impl/FsCouponServiceImpl.java
  41. 11 0
      fs-service/src/main/java/com/fs/his/service/impl/FsIntegralGoodsServiceImpl.java
  42. 51 19
      fs-service/src/main/java/com/fs/his/service/impl/FsIntegralOrderServiceImpl.java
  43. 21 0
      fs-service/src/main/java/com/fs/his/service/impl/FsStorePaymentErrorServiceImpl.java
  44. 45 6
      fs-service/src/main/java/com/fs/his/service/impl/FsStorePaymentServiceImpl.java
  45. 3 3
      fs-service/src/main/java/com/fs/his/service/impl/FsUserServiceImpl.java
  46. 4 0
      fs-service/src/main/java/com/fs/hisStore/domain/FsStoreProductScrm.java
  47. 6 2
      fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreProductScrmMapper.java
  48. 3 0
      fs-service/src/main/java/com/fs/hisStore/param/FsStoreProductAddEditParam.java
  49. 3 0
      fs-service/src/main/java/com/fs/hisStore/param/FsStoreProductQueryParam.java
  50. 2 1
      fs-service/src/main/java/com/fs/hisStore/service/IFsStoreProductScrmService.java
  51. 27 26
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreProductScrmServiceImpl.java
  52. 2 2
      fs-service/src/main/java/com/fs/hisStore/vo/FsStoreOrderItemExportRefundZMVO.java
  53. 1 1
      fs-service/src/main/java/com/fs/live/domain/LiveOrder.java
  54. 21 0
      fs-service/src/main/java/com/fs/live/mapper/LiveOrderMapper.java
  55. 7 0
      fs-service/src/main/java/com/fs/live/mapper/LiveWatchLogMapper.java
  56. 1 1
      fs-service/src/main/java/com/fs/live/service/ILiveAfterSalesService.java
  57. 7 0
      fs-service/src/main/java/com/fs/live/service/ILiveService.java
  58. 14 0
      fs-service/src/main/java/com/fs/live/service/ILiveWatchLogService.java
  59. 2 0
      fs-service/src/main/java/com/fs/live/service/ILiveWatchUserService.java
  60. 10 23
      fs-service/src/main/java/com/fs/live/service/impl/LiveAfterSalesServiceImpl.java
  61. 10 15
      fs-service/src/main/java/com/fs/live/service/impl/LiveCompletionPointsRecordServiceImpl.java
  62. 11 7
      fs-service/src/main/java/com/fs/live/service/impl/LiveDataServiceImpl.java
  63. 94 75
      fs-service/src/main/java/com/fs/live/service/impl/LiveOrderServiceImpl.java
  64. 37 17
      fs-service/src/main/java/com/fs/live/service/impl/LiveServiceImpl.java
  65. 17 8
      fs-service/src/main/java/com/fs/live/service/impl/LiveVideoServiceImpl.java
  66. 66 0
      fs-service/src/main/java/com/fs/live/service/impl/LiveWatchLogServiceImpl.java
  67. 8 1
      fs-service/src/main/java/com/fs/live/service/impl/LiveWatchUserServiceImpl.java
  68. 1 1
      fs-service/src/main/java/com/fs/live/vo/LiveUserDetailExportVO.java
  69. 20 11
      fs-service/src/main/java/com/fs/live/vo/MergedOrderExportVO.java
  70. 2 0
      fs-service/src/main/java/com/fs/live/vo/MergedOrderVO.java
  71. 18 0
      fs-service/src/main/java/com/fs/newAdv/controller/AdvMiniConfigController.java
  72. 58 0
      fs-service/src/main/java/com/fs/newAdv/domain/AdvMiniConfig.java
  73. 2 2
      fs-service/src/main/java/com/fs/newAdv/domain/PromotionAccount.java
  74. 2 2
      fs-service/src/main/java/com/fs/newAdv/integration/client/advertiser/BaiduApiClient.java
  75. 44 6
      fs-service/src/main/java/com/fs/newAdv/integration/client/advertiser/TencentApiClient.java
  76. 18 0
      fs-service/src/main/java/com/fs/newAdv/mapper/AdvMiniConfigMapper.java
  77. 16 0
      fs-service/src/main/java/com/fs/newAdv/service/IAdvMiniConfigService.java
  78. 1 1
      fs-service/src/main/java/com/fs/newAdv/service/ILeadService.java
  79. 20 0
      fs-service/src/main/java/com/fs/newAdv/service/impl/AdvMiniConfigServiceImpl.java
  80. 6 4
      fs-service/src/main/java/com/fs/newAdv/service/impl/LeadServiceImpl.java
  81. 1 1
      fs-service/src/main/java/com/fs/newAdv/vo/AccessTokenByAuthCodeVo.java
  82. 11 0
      fs-service/src/main/java/com/fs/qw/domain/QwCompany.java
  83. 4 0
      fs-service/src/main/java/com/fs/qw/service/impl/QwUserServiceImpl.java
  84. 6 0
      fs-service/src/main/java/com/fs/sop/mapper/QwSopTempRulesMapper.java
  85. 22 0
      fs-service/src/main/java/com/fs/sop/params/BatchOpenOrCloseOfficialParam.java
  86. 10 0
      fs-service/src/main/java/com/fs/sop/service/IQwSopTempService.java
  87. 30 0
      fs-service/src/main/java/com/fs/sop/service/impl/QwSopTempServiceImpl.java
  88. 7 0
      fs-service/src/main/java/com/fs/store/param/h5/FsUserPageListParam.java
  89. 1 1
      fs-service/src/main/resources/application-config-druid-cfryt.yml
  90. 2 2
      fs-service/src/main/resources/application-config-druid-heyantang.yml
  91. 2 2
      fs-service/src/main/resources/application-config-druid-nmgyt.yml
  92. 1 0
      fs-service/src/main/resources/application-config-druid-sczy.yml
  93. 1 1
      fs-service/src/main/resources/application-config-zkzh.yml
  94. 3 0
      fs-service/src/main/resources/mapper/company/CompanyMapper.xml
  95. 2 1
      fs-service/src/main/resources/mapper/course/FsCourseWatchLogMapper.xml
  96. 2 0
      fs-service/src/main/resources/mapper/course/FsUserCoursePeriodMapper.xml
  97. 5 0
      fs-service/src/main/resources/mapper/his/FsIntegralCartMapper.xml
  98. 11 1
      fs-service/src/main/resources/mapper/his/FsStorePaymentErrorMapper.xml
  99. 16 10
      fs-service/src/main/resources/mapper/his/FsUserMapper.xml
  100. 1 0
      fs-service/src/main/resources/mapper/hisStore/FsStorePaymentScrmMapper.xml

+ 139 - 0
deploy - test.sh

@@ -0,0 +1,139 @@
+#!/bin/bash
+
+# 各服务对应的远程服务器配置 公司本地
+declare -A SERVER_CONFIG=(
+    # 服务名:IP地址
+    ["fs-live-app"]="129.28.193.135"
+)
+
+# 通用配置(所有服务器相同)
+REMOTE_USER="root"
+REMOTE_BASE_DIR="/home/software"
+
+# 本地 JAR 包路径
+LOCAL_FS_LIVE_SOCKET_JAR="./fs-live-app/target/fs-live-app.jar"
+
+# 函数:检查本地文件是否存在
+check_local_file() {
+    if [ ! -f "$1" ]; then
+        echo "错误: 本地文件 $1 不存在。"
+        exit 1
+    fi
+}
+
+# 停止远程服务器上可能正在运行的旧版本
+stop_remote_app() {
+    local remote_user=$1
+    local remote_host=$2
+    local app_name=$3
+
+    echo "正在停止 $remote_host 上的 $app_name 服务..."
+    ssh "$remote_user@$remote_host" "pkill -f $app_name || echo '没有找到运行中的进程'"
+}
+
+# 检查服务器连通性
+check_server_connectivity() {
+    local remote_user=$1
+    local remote_host=$2
+
+    if ! ssh -o ConnectTimeout=5 "$remote_user@$remote_host" "echo '连接成功'" &>/dev/null; then
+        echo "错误: 无法连接到服务器 $remote_host"
+        return 1
+    fi
+    return 0
+}
+
+# 部署单个 JAR 包到指定服务器
+deploy_jar() {
+    local local_jar=$1
+    local service_name=$2  # 服务名,用于确定目标服务器
+    local remote_dir=$3     # 远程目录名
+
+    # 获取服务对应的服务器IP
+    local remote_host="${SERVER_CONFIG[$service_name]}"
+    if [ -z "$remote_host" ]; then
+        echo "错误: 未找到服务 $service_name 的服务器配置"
+        return 1
+    fi
+
+    local app_name=$(basename "$local_jar" .jar)
+
+    echo "========================================"
+    echo "开始部署 $service_name 到服务器 $remote_host"
+    echo "本地文件: $local_jar"
+    echo "远程目录: $REMOTE_BASE_DIR/$remote_dir"
+    echo "========================================"
+
+    # 检查本地文件
+    check_local_file "$local_jar"
+
+    # 检查服务器连通性
+    if ! check_server_connectivity "$REMOTE_USER" "$remote_host"; then
+        return 1
+    fi
+
+    # 创建远程目录(如果不存在)
+    echo "创建远程目录..."
+    ssh "$REMOTE_USER@$remote_host" "mkdir -p $REMOTE_BASE_DIR/$remote_dir"
+
+    # 停止旧版本
+    stop_remote_app "$REMOTE_USER" "$remote_host" "$app_name"
+
+    # 上传 JAR 包
+    echo "上传 JAR 包..."
+    scp "$local_jar" "$REMOTE_USER@$remote_host:$REMOTE_BASE_DIR/$remote_dir/"
+
+    # 在后台启动 JAR 包
+    echo "启动服务..."
+    ssh -f "$REMOTE_USER@$remote_host" \
+    "cd $REMOTE_BASE_DIR/$remote_dir && \
+     nohup java -jar -Xms1g -Xmx2g -XX:+UseG1GC -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 $app_name.jar --spring.profiles.active=druid-ylrz --server.port=7114  \
+     >> $app_name.log 2>&1 &"
+
+    # 检查进程是否启动成功
+    if ssh "$REMOTE_USER@$remote_host" "pgrep -f $app_name" &>/dev/null; then
+        echo "✓ $service_name 部署成功到 $remote_host"
+    else
+        echo "✗ $service_name 启动失败,请检查日志: $REMOTE_BASE_DIR/$remote_dir/$app_name.log"
+        return 1
+    fi
+
+    echo ""
+}
+
+# 主要部署流程
+echo "开始多服务器分布式部署..."
+echo "部署配置:"
+for service in "${!SERVER_CONFIG[@]}"; do
+    echo "  $service -> ${SERVER_CONFIG[$service]}"
+done
+echo ""
+
+# 部署 fs-live-app
+deploy_jar "$LOCAL_FS_LIVE_SOCKET_JAR" "fs-live-app" "fs-live-app"
+
+# 部署 fs-sync (注意:这里使用了不同的JAR命名方式)
+#deploy_jar "$LOCAL_FS_SYNC_APP_JAR" "fs-sync" "fs-sync"
+
+echo "========================================"
+echo "分布式部署完成!"
+echo "各服务部署状态:"
+for service in "${!SERVER_CONFIG[@]}"; do
+    remote_host="${SERVER_CONFIG[$service]}"
+    echo "  $service -> $remote_host"
+done
+echo "========================================"
+
+# 可选:显示各服务进程状态
+#echo "服务进程状态检查:"
+#for service in "${!SERVER_CONFIG[@]}"; do
+#    remote_host="${SERVER_CONFIG[$service]}"
+#    app_name="$service"  # 简化处理,实际可能需要根据JAR文件名调整
+#    if ssh "$REMOTE_USER@$remote_host" "pgrep -f $app_name" &>/dev/null; then
+#        echo "  ✓ $service 在 $remote_host 上运行正常"
+#    else
+#        echo "  ✗ $service 在 $remote_host 上未运行"
+#    fi
+#done
+
+# 251105 0953

+ 1 - 1
fs-ad-new-api/src/main/java/com/fs/app/controller/CallbackController.java

@@ -111,7 +111,7 @@ public class CallbackController {
         IAccessTokenClient tokenClient = (IAccessTokenClient) apiClient;
         AccessTokenVo accessToken = tokenClient.getAccessTokenByAuthCode(AccessTokenByAuthCodeVo
                 .builder()
-                .userId(byId.getAdAccountId())
+                .adAccountId(byId.getAdAccountId())
                 .appId(byId.getAppId())
                 .authCode(authCode)
                 .appSecret(byId.getAppSecret())

+ 47 - 27
fs-ad-new-api/src/main/java/com/fs/app/controller/WeChatController.java

@@ -4,9 +4,12 @@ import cn.hutool.http.HttpRequest;
 import cn.hutool.http.HttpResponse;
 import cn.hutool.json.JSONUtil;
 import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.fs.app.facade.CallbackProcessingFacadeService;
 import com.fs.common.constant.SystemConstant;
 import com.fs.common.result.Result;
+import com.fs.newAdv.domain.AdvMiniConfig;
+import com.fs.newAdv.service.IAdvMiniConfigService;
 import com.fs.wx.miniapp.config.WxMaProperties;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -15,7 +18,9 @@ import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.RestController;
 
+import java.time.LocalDateTime;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 /**
@@ -34,34 +39,49 @@ public class WeChatController {
     @Autowired
     private WxMaProperties properties;
 
+    @Autowired
+    private IAdvMiniConfigService advMiniConfigService;
+
     @GetMapping("/getSchemeUrl")
     public Result<String> getSchemeUrl(@RequestParam(value = "traceId") String traceId) {
-        String appId = "wx0447a16ef6199f03";
-        String secret = "f063fcd818e31d4c89013a67f5123990";
-        HttpResponse execute2 = HttpRequest.get("https://api.weixin.qq.com/cgi-bin/token")
-                .form("grant_type", "client_credential")
-                .form("appid", appId)
-                .form("secret", secret)
-                .timeout(SystemConstant.API_TIMEOUT)
-                .execute();
-        JSONObject obj = JSONObject.parseObject(execute2.body());
-        String access_token = obj.getString("access_token");
-
-        Map<String, Object> map = new HashMap<>();
-        Map<String, Object> map2 = new HashMap<>();
-        map2.put("path", "/pages/shopping/productDetails");
-        map2.put("query", "traceId=" + traceId);
-        //map2.put("env_version", "trial");
-        map.put("jump_wxa", map2);
-        map.put("is_expire", false);
-        HttpResponse execute = HttpRequest.post("https://api.weixin.qq.com/wxa/generatescheme?access_token=" + access_token)
-                .header("Content-Type", "application/json")
-                .body(JSONUtil.toJsonStr(map))
-                .timeout(SystemConstant.API_TIMEOUT)
-                .execute();
-        log.info("getSchemeUrl:{}", execute.body());
-        obj = JSONObject.parseObject(execute.body());
-        //response.addHeader("Access-Control-Allow-Origin", "*");
-        return Result.success(obj.getString("openlink"));
+        List<AdvMiniConfig> list = advMiniConfigService.list(new LambdaQueryWrapper<AdvMiniConfig>()
+                .eq(AdvMiniConfig::getStatus, 1));
+        for (AdvMiniConfig advMiniConfig : list) {
+            try {
+                String access_token = advMiniConfig.getAccessToken();
+                // 判断token是否过期
+                if (advMiniConfig.getExpiresIn().isBefore(LocalDateTime.now().plusMinutes(10))) {
+                    HttpResponse execute2 = HttpRequest.get("https://api.weixin.qq.com/cgi-bin/token")
+                            .form("grant_type", "client_credential")
+                            .form("appid", advMiniConfig.getAppId())
+                            .form("secret", advMiniConfig.getAppSecret())
+                            .timeout(SystemConstant.API_TIMEOUT)
+                            .execute();
+                    JSONObject obj = JSONObject.parseObject(execute2.body());
+                    access_token = obj.getString("access_token");
+                    advMiniConfig.setAccessToken(access_token);
+                    advMiniConfigService.updateById(advMiniConfig);
+                }
+                Map<String, Object> map = new HashMap<>();
+                Map<String, Object> map2 = new HashMap<>();
+                map2.put("path", "pages/home/productList");
+                map2.put("query", "traceId=" + traceId);
+                map2.put("env_version", "trial");
+                map.put("jump_wxa", map2);
+                map.put("is_expire", false);
+                HttpResponse execute = HttpRequest.post("https://api.weixin.qq.com/wxa/generatescheme?access_token=" + access_token)
+                        .header("Content-Type", "application/json")
+                        .body(JSONUtil.toJsonStr(map))
+                        .timeout(SystemConstant.API_TIMEOUT)
+                        .execute();
+                log.info("getSchemeUrl:{}", execute.body());
+                JSONObject jsonObject = JSONObject.parseObject(execute.body());
+                //response.addHeader("Access-Control-Allow-Origin", "*");
+                return Result.success(jsonObject.getString("openlink"));
+            }catch (Exception e){
+                log.error("getSchemeUrl error:{}",advMiniConfig.getAppId(), e);
+            }
+        }
+        return Result.success("");
     }
 }

+ 1 - 0
fs-ad-new-api/src/main/java/com/fs/app/facade/CallbackProcessingFacadeServiceImpl.java

@@ -221,6 +221,7 @@ public class CallbackProcessingFacadeServiceImpl implements CallbackProcessingFa
                                                Integer allocationRule,
                                                Long allocationRuleId,
                                                Lead byTraceId) {
+
         // 二维码
         String qrCode = "";
         if (allocationRule == 1) {

+ 1 - 0
fs-ad-new-api/src/main/resources/application.yml

@@ -7,3 +7,4 @@ spring:
     active: dev
 #    active: druid-ylrz
 
+#

+ 25 - 4
fs-admin/src/main/java/com/fs/his/controller/FsStorePaymentController.java

@@ -5,10 +5,10 @@ import java.util.List;
 
 import com.fs.common.core.domain.R;
 import com.fs.common.utils.SecurityUtils;
-import com.fs.his.domain.FsExportTask;
+import com.fs.his.domain.*;
 import com.fs.his.mapper.FsPrescribeMapper;
 import com.fs.his.param.FsStorePaymentParam;
-import com.fs.his.service.IFsExportTaskService;
+import com.fs.his.service.*;
 import com.fs.his.vo.FsStorePaymentExcelVO;
 import com.fs.his.vo.FsStorePaymentVO;
 import lombok.extern.slf4j.Slf4j;
@@ -26,8 +26,6 @@ import com.fs.common.annotation.Log;
 import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.enums.BusinessType;
-import com.fs.his.domain.FsStorePayment;
-import com.fs.his.service.IFsStorePaymentService;
 import com.fs.common.utils.poi.ExcelUtil;
 import com.fs.common.core.page.TableDataInfo;
 
@@ -44,11 +42,17 @@ public class FsStorePaymentController extends BaseController
 {
     @Autowired
     private IFsStorePaymentService fsStorePaymentService;
+
+    @Autowired
+    private IFsStorePaymentErrorService fsStorePaymentErrorService;
     @Autowired
     FsPrescribeMapper fsPrescribeMapper;
 
     @Autowired
     private IFsExportTaskService exportTaskService;
+    @Autowired
+    private IFsPackageOrderService fsPackageOrderService;
+
     /**
      * 查询支付明细列表
      */
@@ -169,4 +173,21 @@ public class FsStorePaymentController extends BaseController
     {
         return toAjax(fsStorePaymentService.deleteFsStorePaymentByPaymentIds(paymentIds));
     }
+
+    /**
+     * 查询支付错误明细
+     */
+    @GetMapping("/error/list")
+    public TableDataInfo list(FsStorePaymentError fsStorePaymentError)
+    {
+        startPage();
+        List<FsStorePaymentError> list = fsStorePaymentErrorService.selectFsStorePaymentErrorList(fsStorePaymentError);
+        for (FsStorePaymentError vo : list){
+            if (vo.getBusinessType() != null && vo.getBusinessType()==3 &&  vo.getOrderId() != null) {
+                FsPackageOrder fsPackageOrder = fsPackageOrderService.selectFsPackageOrderByOrderId(vo.getOrderId());
+                vo.setOrderNo(fsPackageOrder.getOrderSn());
+            }
+        }
+        return getDataTable(list);
+    }
 }

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

@@ -164,6 +164,90 @@ public class LiveTask {
     @Autowired
     private FsJstAftersalePushScrmService fsJstAftersalePushScrmService;
 
+    /**
+     * 查询被拆分的订单,然后查询拆分订单的物流信息
+     */
+    public void querySplitOrderDelivery() {
+        try {
+            // 查询状态为6(被拆分)的订单
+            List<LiveOrder> splitOrders = liveOrderMapper.selectSplitOrders();
+            if (splitOrders == null || splitOrders.isEmpty()) {
+                log.debug("没有找到被拆分的订单");
+                return;
+            }
+
+            log.info("找到 {} 个被拆分的订单,开始查询拆分订单的物流信息", splitOrders.size());
+
+            IErpOrderService erpOrderService = getErpOrderService();
+            if (erpOrderService == null) {
+                log.warn("ERP服务未配置,无法查询拆分订单物流信息");
+                return;
+            }
+
+            for (LiveOrder splitOrder : splitOrders) {
+                try {
+                    // 查询该订单的所有拆分订单(通过原订单号查询)
+                    List<LiveOrder> childOrders = liveOrderMapper.selectChildOrdersByParentOrderCode(splitOrder.getOrderCode());
+                    if (childOrders == null || childOrders.isEmpty()) {
+                        log.debug("订单 {} 没有找到拆分订单", splitOrder.getOrderCode());
+                        continue;
+                    }
+
+                    // 遍历拆分订单,查询物流信息
+                    for (LiveOrder childOrder : childOrders) {
+                        if (StringUtils.isEmpty(childOrder.getExtendOrderId())) {
+                            log.debug("拆分订单 {} 没有扩展订单ID,跳过", childOrder.getOrderCode());
+                            continue;
+                        }
+
+                        // 查询ERP订单信息
+                        ErpOrderQueryRequert request = new ErpOrderQueryRequert();
+                        request.setCode(childOrder.getExtendOrderId());
+                        ErpOrderQueryResponse response = erpOrderService.getLiveOrder(request);
+
+                        if (!response.getSuccess()) {
+                            if ("429".equals(response.getCode())) {
+                                log.warn("ERP接口限流,停止查询");
+                                break;
+                            }
+                            log.warn("查询拆分订单物流信息失败, orderCode={}, error={}", childOrder.getOrderCode(), response.getCode());
+                            continue;
+                        }
+
+                        // 更新物流信息
+                        if (response.getOrders() != null && !response.getOrders().isEmpty()) {
+                            for (ErpOrderQuery orderQuery : response.getOrders()) {
+                                if (orderQuery.getDeliverys() != null && !orderQuery.getDeliverys().isEmpty()) {
+                                    for (ErpDeliverys delivery : orderQuery.getDeliverys()) {
+                                        if (delivery.getDelivery() && StringUtils.isNotEmpty(delivery.getMail_no())) {
+                                            // 更新订单物流信息
+                                            childOrder.setDeliverySn(delivery.getMail_no());
+                                            childOrder.setDeliveryCode(delivery.getExpress_code());
+                                            childOrder.setDeliveryName(delivery.getExpress_name());
+                                            if (childOrder.getStatus() == 2) { // 待发货状态
+                                                childOrder.setStatus(3); // 更新为待收货
+                                            }
+                                            childOrder.setUpdateTime(new Date());
+                                            liveOrderMapper.updateLiveOrder(childOrder);
+                                            log.info("拆分订单物流信息已更新, orderCode={}, deliverySn={}, expressName={}",
+                                                    childOrder.getOrderCode(), delivery.getMail_no(), delivery.getExpress_name());
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    }
+                } catch (Exception e) {
+                    log.error("处理拆分订单物流信息异常, orderCode={}", splitOrder.getOrderCode(), e);
+                }
+            }
+
+            log.info("拆分订单物流信息查询完成");
+        } catch (Exception e) {
+            log.error("查询拆分订单物流信息任务异常", e);
+        }
+    }
+
     // 聚水潭 推送售后信息
     public void pushJst(){
         fsJstAftersalePushScrmService.pushJst();

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

@@ -221,4 +221,13 @@ public class LiveController extends BaseController {
         return getDataTable(list);
     }
 
+    /**
+     * 清除直播间缓存
+     */
+    @Log(title = "直播", businessType = BusinessType.UPDATE)
+    @PostMapping("/clearCache/{liveId}")
+    public R clearCache(@PathVariable("liveId") Long liveId) {
+        return liveService.clearLiveCache(liveId);
+    }
+
 }

+ 15 - 1
fs-admin/src/main/java/com/fs/live/controller/LiveDataController.java

@@ -8,6 +8,8 @@ import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.enums.BusinessType;
 import com.fs.common.utils.SecurityUtils;
 import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.company.domain.CompanyUser;
+import com.fs.framework.web.service.TokenService;
 import com.fs.live.domain.LiveData;
 import com.fs.live.param.LiveDataParam;
 import com.fs.live.service.ILiveDataService;
@@ -29,6 +31,8 @@ public class LiveDataController extends BaseController {
 
     @Autowired
     private ILiveDataService liveDataService;
+    @Autowired
+    private TokenService tokenService;
 
     /**
      * 直播数据页面卡片数据
@@ -128,11 +132,21 @@ public class LiveDataController extends BaseController {
     /**
      * 查询直播间用户详情列表(SQL方式)
      * @param liveId 直播间ID
+     * @param pageNum 页码
+     * @param pageSize 每页大小
      * @return 用户详情列表
      */
     @PreAuthorize("@ss.hasPermi('liveData:liveData:query')")
     @GetMapping("/getLiveUserDetailListBySql")
-    public R getLiveUserDetailListBySql(@RequestParam Long liveId) {
+    public R getLiveUserDetailListBySql(@RequestParam Long liveId,
+                                        @RequestParam(defaultValue = "1") Integer pageNum,
+                                        @RequestParam(defaultValue = "100") Integer pageSize) {
+        // 限制最大每页查询条数为1000
+        if (pageSize > 1000) {
+            pageSize = 1000;
+        }
+
+        PageHelper.startPage(pageNum, pageSize);
         return liveDataService.getLiveUserDetailListBySql(liveId,null,null);
     }
 

+ 2 - 6
fs-admin/src/main/java/com/fs/live/controller/LiveVideoController.java

@@ -29,8 +29,7 @@ public class LiveVideoController extends BaseController
 {
     @Autowired
     private ILiveVideoService liveVideoService;
-    @Autowired
-    private CloudHostProper cloudHostProper;
+
     /**
      * 查询直播视频列表
      */
@@ -93,10 +92,7 @@ public class LiveVideoController extends BaseController
     @PostMapping
     public AjaxResult add(@RequestBody LiveVideo liveVideo)
     {
-        if (LiveEnum.contains(cloudHostProper.getCompanyName())) {
-            liveVideo.setVideoUrl(liveVideo.getLineOne());
-            liveVideo.setFinishStatus(1);
-        }
+
         return toAjax(liveVideoService.insertLiveVideo(liveVideo));
     }
 

+ 6 - 4
fs-admin/src/main/java/com/fs/live/controller/OrderController.java

@@ -235,20 +235,21 @@ public class OrderController extends BaseController
             MergedOrderExportVO exportVO = new MergedOrderExportVO();
             
             // 订单基本信息(参考 FsStoreOrderItemExportVO 的顺序)
+            exportVO.setOrderTypeName(vo.getOrderTypeName());
             exportVO.setOrderCode(vo.getOrderCode());
             exportVO.setStatus(vo.getStatus() != null ? String.valueOf(vo.getStatus()) : null);
             exportVO.setUserId(vo.getUserId());
             
             // 产品信息
-            exportVO.setProductName(vo.getProductName());
+            exportVO.setProductName(StringUtils.isEmpty(vo.getProductName()) ? "产品被删除" : vo.getProductName());
             exportVO.setBarCode(vo.getBarCode());
             exportVO.setProductSpec(StringUtils.isEmpty(vo.getProductSpec()) ? "默认" : vo.getProductSpec());
             exportVO.setTotalNum(vo.getTotalNum());
             exportVO.setPrice(vo.getTotalPrice()); // 产品价格使用订单总价
-            exportVO.setCost(vo.getCost());
+            exportVO.setCost(vo.getCost() != null ? vo.getCost() : BigDecimal.ZERO);
             exportVO.setFPrice(vo.getCost() != null ? vo.getCost().multiply(BigDecimal.valueOf(vo.getTotalNum())) : BigDecimal.ZERO); // 结算价,合并订单暂无此字段
             exportVO.setPayPostage(vo.getPayDelivery());
-            exportVO.setCateName(vo.getCateName());
+            exportVO.setCateName(StringUtils.isEmpty(vo.getCateName()) ? "产品被删除" : vo.getCateName());
             // 收货信息
             exportVO.setRealName(vo.getRealName());
             if (isPlainText) {
@@ -262,7 +263,8 @@ public class OrderController extends BaseController
             // 时间信息
             exportVO.setCreateTime(vo.getCreateTime());
             exportVO.setPayTime(vo.getPayTime());
-            
+            exportVO.setHfshh(vo.getHfshh());
+
             // 物流信息
             exportVO.setDeliverySn(vo.getDeliveryCode()); // 快递公司编号,合并订单暂无此字段
             exportVO.setDeliveryName(vo.getDeliveryName()); // 快递公司,合并订单暂无此字段

+ 10 - 0
fs-admin/src/main/java/com/fs/qw/controller/QwFriendWelcomeController.java

@@ -1,6 +1,7 @@
 package com.fs.qw.controller;
 
 import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.R;
 import com.fs.common.core.page.TableDataInfo;
 import com.fs.qw.param.QwFriendWelcomeParam;
 import com.fs.qw.service.IQwFriendWelcomeService;
@@ -8,6 +9,7 @@ import com.fs.qw.vo.QwFriendWelcomeVO;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 
@@ -30,4 +32,12 @@ public class QwFriendWelcomeController extends BaseController {
         List<QwFriendWelcomeVO> list = qwFriendWelcomeService.selectQwFriendWelcomeList(qwFriendWelcomeParam);
         return getDataTable(list);
     }
+
+    /**
+     * 获取好友欢迎语详细信息
+     */
+    @GetMapping(value = "/{id}")
+    public R getInfo(@PathVariable("id") Long id) {
+        return R.ok().put("row", qwFriendWelcomeService.selectQwFriendWelcomeById(id));
+    }
 }

+ 5 - 4
fs-company-app/src/main/java/com/fs/app/controller/FsUserController.java

@@ -45,10 +45,7 @@ import java.io.InputStream;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.time.format.DateTimeFormatter;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
+import java.util.*;
 
 import static com.fs.his.utils.PhoneUtil.encryptPhone;
 
@@ -216,6 +213,8 @@ public class FsUserController extends AppBaseController {
             @ApiParam(value = "类型,1-按完播率,2-按正确率", required = true) @RequestParam Integer type
     ) {
         long userId = Long.parseLong(getUserId());
+        // 中康的数据太多太卡不要这个
+//        return ResponseResult.ok(Collections.emptyList());
         return ResponseResult.ok(fsUserService.userRanking(userId, startTime, endTime, periodId, videoId, order, type));
     }
 
@@ -231,6 +230,8 @@ public class FsUserController extends AppBaseController {
             @ApiParam(value = "类型,1-按完播率,2-按正确率", required = true) @RequestParam Integer type
     ) {
         long userId = Long.parseLong(getUserId());
+        // 中康的数据太多太卡不要这个
+//        return ResponseResult.ok(Collections.emptyList());
         return ResponseResult.ok(fsUserService.courseRanking(userId, startTime, endTime, courseId, videoId, order, type));
     }
 

+ 9 - 0
fs-company/src/main/java/com/fs/company/controller/course/FsUserCoursePeriodController.java

@@ -195,6 +195,15 @@ public class FsUserCoursePeriodController extends BaseController {
 
     }
 
+    @PreAuthorize("@ss.hasPermi('course:period:edit')")
+    @Log(title = "会员营期", businessType = BusinessType.UPDATE)
+    @PutMapping("/updatePeriodIsOpenRestReminder")
+    public AjaxResult updatePeriodIsOpenRestReminder(@RequestBody FsUserCoursePeriod fsUserCoursePeriod)
+    {
+        return toAjax(fsUserCoursePeriodService.updatePeriod(fsUserCoursePeriod));
+
+    }
+
     /**
      * 删除会员营期
      */

+ 10 - 2
fs-company/src/main/java/com/fs/company/controller/live/LiveController.java

@@ -15,6 +15,7 @@ import com.fs.company.domain.CompanyUser;
 import com.fs.framework.security.LoginUser;
 import com.fs.framework.security.SecurityUtils;
 import com.fs.framework.service.TokenService;
+import com.fs.his.domain.FsPayConfig;
 import com.fs.huifuPay.domain.HuiFuQueryOrderResult;
 import com.fs.huifuPay.sdk.opps.core.request.V2TradePaymentScanpayQueryRequest;
 import com.fs.huifuPay.service.HuiFuService;
@@ -28,7 +29,10 @@ import com.fs.live.service.ILiveCompanyCodeService;
 import com.fs.live.service.ILiveOrderService;
 import com.fs.live.service.ILiveService;
 import com.fs.live.vo.LiveListVo;
+import com.fs.system.domain.SysConfig;
+import com.fs.system.mapper.SysConfigMapper;
 import com.fs.system.oss.OSSFactory;
+import com.fs.wx.miniapp.config.WxMaProperties;
 import com.google.common.reflect.TypeToken;
 import com.google.gson.Gson;
 import io.swagger.annotations.ApiOperation;
@@ -343,6 +347,9 @@ public class LiveController extends BaseController
         }
     }
 
+    @Autowired
+    private WxMaProperties properties;
+
     @ApiOperation("生成微信小程序码")
     @GetMapping("/getWxaCodeUnLimit")
     @PreAuthorize("@ss.hasPermi('live:live:edit')")
@@ -350,9 +357,10 @@ public class LiveController extends BaseController
         String url="https://api.weixin.qq.com/cgi-bin/stable_token";
         HashMap<String, String> map = new HashMap<>();
         map.put("grant_type","client_credential");
+
         // 百域承品
-        map.put("appid","wx44beed5640bcb1ba");
-        map.put("secret","1bfcfa420f741801575a74d94752d014");
+        map.put("appid",properties.getConfigs().get(0).getAppid());
+        map.put("secret",properties.getConfigs().get(0).getSecret());
         String accessToken = HttpUtils.endApi(url, null, map);
         // 创建Gson对象
         Gson gson = new Gson();

+ 12 - 3
fs-company/src/main/java/com/fs/company/controller/live/LiveDataController.java

@@ -55,17 +55,26 @@ public class LiveDataController extends BaseController
     /**
      * 查询直播间用户详情列表(SQL方式)
      * @param liveId 直播间ID
+     * @param pageNum 页码
+     * @param pageSize 每页大小
      * @return 用户详情列表
      */
     @PreAuthorize("@ss.hasPermi('liveData:liveData:query')")
     @GetMapping("/getLiveUserDetailListBySql")
-    public R getLiveUserDetailListBySql(@RequestParam Long liveId, HttpServletRequest request) {
+    public R getLiveUserDetailListBySql(@RequestParam Long liveId, 
+                                        @RequestParam(defaultValue = "1") Integer pageNum,
+                                        @RequestParam(defaultValue = "100") Integer pageSize,
+                                        HttpServletRequest request) {
+        // 限制最大每页查询条数为1000
+        if (pageSize > 1000) {
+            pageSize = 1000;
+        }
+        PageHelper.startPage(pageNum, pageSize);
         CompanyUser user = tokenService.getLoginUser(request).getUser();
         if ("00".equals(user.getUserType())) {
             return liveDataService.getLiveUserDetailListBySql(liveId,user.getCompanyId(),null);
         }
-        return liveDataService.getLiveUserDetailListBySql(liveId,user.getCompanyId(),user.getUserId());
-    }
+        return liveDataService.getLiveUserDetailListBySql(liveId,user.getCompanyId(),user.getUserId());    }
 
     /**
      * 查询直播间详情数据(查询数据服务器处理方式)

+ 4 - 2
fs-company/src/main/java/com/fs/company/controller/live/OrderController.java

@@ -239,12 +239,13 @@ public class OrderController extends BaseController
             MergedOrderExportVO exportVO = new MergedOrderExportVO();
 
             // 订单基本信息(参考 FsStoreOrderItemExportVO 的顺序)
+            exportVO.setOrderTypeName(vo.getOrderTypeName());
             exportVO.setOrderCode(vo.getOrderCode());
             exportVO.setStatus(vo.getStatus() != null ? String.valueOf(vo.getStatus()) : null);
             exportVO.setUserId(vo.getUserId());
 
             // 产品信息
-            exportVO.setProductName(vo.getProductName());
+            exportVO.setProductName(StringUtils.isEmpty(vo.getProductName()) ? "产品被删除" : vo.getProductName());
             exportVO.setBarCode(vo.getBarCode());
             exportVO.setProductSpec(StringUtils.isEmpty(vo.getProductSpec()) ? "默认" : vo.getProductSpec());
             exportVO.setTotalNum(vo.getTotalNum());
@@ -252,7 +253,7 @@ public class OrderController extends BaseController
             exportVO.setCost(BigDecimal.ZERO);
             exportVO.setFPrice(BigDecimal.ZERO); // 结算价,合并订单暂无此字段
             exportVO.setPayPostage(vo.getPayDelivery());
-            exportVO.setCateName(vo.getCateName());
+            exportVO.setCateName(StringUtils.isEmpty(vo.getCateName()) ? "产品被删除" : vo.getCateName());
 
             // 收货信息
             exportVO.setRealName(vo.getRealName());
@@ -270,6 +271,7 @@ public class OrderController extends BaseController
             // 公司和销售信息
             exportVO.setCompanyName(vo.getCompanyName());
             exportVO.setCompanyUserNickName(vo.getCompanyUserNickName());
+            exportVO.setHfshh(vo.getHfshh());
 
             // 套餐信息
             exportVO.setPackageName(null); // 套餐名称,合并订单暂无此字段

+ 13 - 0
fs-company/src/main/java/com/fs/company/controller/qw/QwSopTempController.java

@@ -20,6 +20,7 @@ import com.fs.qw.vo.SortDayVo;
 import com.fs.sop.domain.QwSop;
 import com.fs.sop.domain.QwSopTemp;
 import com.fs.sop.domain.QwSopTempDay;
+import com.fs.sop.params.BatchOpenOrCloseOfficialParam;
 import com.fs.sop.params.QwSopShareTempParam;
 import com.fs.sop.service.IQwSopTempService;
 import com.fs.sop.vo.UpdateRedVo;
@@ -362,4 +363,16 @@ public class QwSopTempController extends BaseController
     public R getSelectableRange(){
         return R.ok().put("data", qwSopTempService.getSelectableRange());
     }
+
+    /**
+     * sop模板update一键开关官方群发
+     * @param param
+     * @return
+     */
+    @PreAuthorize("@ss.hasPermi('qw:sopTemp:edit') or @ss.hasPermi('qw:sopTemp:myEdit') or @ss.hasPermi('qw:sopTemp:deptEdit')")
+    @Log(title = "sop模板update一键开关官方群发", businessType = BusinessType.UPDATE)
+    @PostMapping("/batchOpenOrCloseOfficial")
+    public R batchOpenOrCloseOfficial(@RequestBody BatchOpenOrCloseOfficialParam param){
+        return qwSopTempService.batchOpenOrCloseOfficial(param);
+    }
 }

+ 2 - 2
fs-company/src/main/java/com/fs/company/controller/store/FsUserController.java

@@ -84,8 +84,8 @@ public class FsUserController extends BaseController
         List<FsUser> list=fsUserService.selectFsUserList(fsUser);
         if(list.isEmpty()){
             //如果是加密的电话,需要加密后查询
-            if(StringUtils.isFullNumber(fsUser.getKeywords()) && fsUser.getKeywords().length() == 11){
-                fsUser.setKeywords(encryptPhone(fsUser.getKeywords()));
+            if(StringUtils.isFullNumber(fsUser.getPhone()) && fsUser.getPhone().length() == 11){
+                fsUser.setPhone(encryptPhone(fsUser.getPhone()));
             }
             list = fsUserService.selectFsUserList(fsUser);
         }

+ 2 - 38
fs-live-app/src/main/java/com/fs/live/task/LiveCompletionPointsTask.java

@@ -39,7 +39,7 @@ public class LiveCompletionPointsTask {
     private ILiveService liveService;
 
     /**
-     * 定时检查观看时长并创建完课记录
+     * 定时检查观看时长并创建完课记录(兜底机制)
      * 每分钟执行一次
      * 优化:防重复推送 + 只查询开启了完课积分的直播间
      */
@@ -53,8 +53,6 @@ public class LiveCompletionPointsTask {
                 log.debug("当前没有开启完课积分的直播间");
                 return;
             }
-            
-            log.info("开始检查完课状态, 开启完课积分的直播间数量: {}", activeLives.size());
 
             for (Live live : activeLives) {
                 try {
@@ -65,14 +63,9 @@ public class LiveCompletionPointsTask {
                     Map<Object, Object> userDurations = redisCache.hashEntries(hashKey);
                     
                     if (userDurations == null || userDurations.isEmpty()) {
-                        log.warn("直播间没有观看时长数据, liveId={}, liveName={}, Redis Key: {}, userDurations={}", 
-                                liveId, live.getLiveName(), hashKey, userDurations);
+
                         continue;
                     }
-                    
-                    log.info("直播间有观看数据, liveId={}, liveName={}, 用户数: {}", 
-                            liveId, live.getLiveName(), userDurations.size());
-                    
                     // 3. 逐个用户处理
                     for (Map.Entry<Object, Object> entry : userDurations.entrySet()) {
                         try {
@@ -81,9 +74,6 @@ public class LiveCompletionPointsTask {
                             
                             completionPointsRecordService.checkAndCreateCompletionRecord(liveId, userId, duration);
 
-                            // 5. 检查是否有新的完课记录待领取,推送弹窗消息(防重复)
-                            sendCompletionNotificationOnce(liveId, userId);
-
                         } catch (Exception e) {
                             log.error("处理用户完课状态失败, liveId={}, userId={}", liveId, entry.getKey(), e);
                         }
@@ -98,30 +88,4 @@ public class LiveCompletionPointsTask {
             log.error("检查完课状态定时任务执行失败", e);
         }
     }
-
-   private void sendCompletionNotificationOnce(Long liveId, Long userId) {
-    try {
-        // 查询未领取的完课记录
-        List<LiveCompletionPointsRecord> unreceivedRecords = 
-            completionPointsRecordService.getUserUnreceivedRecords(liveId, userId);
-        
-        if (unreceivedRecords == null || unreceivedRecords.isEmpty()) {
-            return;
-        }
-
-        SendMsgVo sendMsgVo = new SendMsgVo();
-        sendMsgVo.setLiveId(liveId);
-        sendMsgVo.setUserId(userId);
-        sendMsgVo.setCmd("completionPoints");
-        sendMsgVo.setMsg("完成任务!");
-        sendMsgVo.setData(JSONObject.toJSONString(unreceivedRecords.get(0)));
-        
-        webSocketServer.sendCompletionPointsMessage(liveId, userId, sendMsgVo);
-        
-        log.info("发送完课积分弹窗通知, liveId={}, userId={}, points={}", 
-                liveId, userId, unreceivedRecords.get(0).getPointsAwarded());
-        } catch (Exception e) {
-        log.error("发送完课通知失败", e);
-        }
-    }
 }

+ 37 - 14
fs-live-app/src/main/java/com/fs/live/task/Task.java

@@ -169,7 +169,10 @@ public class Task {
                         redisCache.expire(key+live.getLiveId(), 1, TimeUnit.DAYS);
                     });
                 }
-                
+                // 清理小程序缓存 和 直播标签缓存
+                String cacheKey = String.format(LiveKeysConstant.LIVE_DATA_CACHE, live.getLiveId());
+                redisCache.deleteObject(cacheKey);
+                liveWatchUserService.clearLiveFlagCache(live.getLiveId());
                 // 将开启的直播间信息写入Redis缓存,用于打标签定时任务
                 try {
                     // 获取视频时长
@@ -216,8 +219,10 @@ public class Task {
                         redisCache.redisTemplate.opsForZSet().remove(key + live.getLiveId(), JSON.toJSONString(liveAutoTask),liveAutoTask.getAbsValue().getTime());
                     });
                 }
+                String cacheKey = String.format(LiveKeysConstant.LIVE_DATA_CACHE, live.getLiveId());
+                redisCache.deleteObject(cacheKey);
                 webSocketServer.removeLikeCountCache(live.getLiveId());
-                
+
                 // 删除打标签缓存
                 try {
                     String tagMarkKey = String.format(LiveKeysConstant.LIVE_TAG_MARK_CACHE, live.getLiveId());
@@ -410,9 +415,13 @@ public class Task {
         for (Live openRewardLive : openRewardLives) {
             String configJson = openRewardLive.getConfigJson();
             LiveWatchConfig config = JSON.parseObject(configJson, LiveWatchConfig.class);
-            if (config.getEnabled()) {
+            if (config.getEnabled() && 1 == config.getParticipateCondition()) {
+                List<LiveWatchUser> liveWatchUsers = liveWatchUserService.checkOnlineNoRewardUser(openRewardLive.getLiveId(), now);
+                if (liveWatchUsers == null || liveWatchUsers.isEmpty()) {
+                    continue;
+                }
                 // 3.检查当前直播间的在线用户(可以传入一个时间,然后查出来当天没领取奖励的用户)
-                List<LiveWatchUser> onlineUser = liveWatchUserService.checkOnlineNoRewardUser(openRewardLive.getLiveId(), now)
+                List<LiveWatchUser> onlineUser = liveWatchUsers
                         .stream().filter(user -> (now.getTime() - user.getUpdateTime().getTime() + ( user.getOnlineSeconds() == null ? 0L : user.getOnlineSeconds())) > config.getWatchDuration() * 60 * 1000)
                         .collect(Collectors.toList());
                 if(onlineUser.isEmpty()) continue;
@@ -624,7 +633,7 @@ public class Task {
     }
 
     /**
-     * 定时扫描开启的直播间,检查是否到了打标签的时间
+     * 定时扫描开启的直播间,检查是否到了打标签的时间,然后把正在看直播的用户拆分为 直播用户和回放用户
      * 每10秒执行一次
      */
     @Scheduled(cron = "0/10 * * * * ?")
@@ -679,7 +688,8 @@ public class Task {
                         queryUser.setLiveId(liveId);
                         queryUser.setLiveFlag(1);
                         queryUser.setReplayFlag(0);
-                        List<LiveWatchUser> liveUsers = liveWatchUserService.selectLiveWatchUserList(queryUser);
+                        queryUser.setOnline(0);
+                        List<LiveWatchUser> liveUsers = liveWatchUserService.selectAllWatchUser(queryUser);
 
                         if (liveUsers != null && !liveUsers.isEmpty()) {
 
@@ -725,6 +735,7 @@ public class Task {
                                 // 更新直播用户的在线时长
                                 liveUser.setOnlineSeconds(totalOnlineSeconds);
                                 liveUser.setUpdateTime(nowDate);
+                                liveUser.setOnline(1);
                                 updateLiveUsers.add(liveUser);
 
                                 // 2. 生成回放用户数据(liveFlag = 0, replayFlag = 1),在线时长从0开始
@@ -732,7 +743,7 @@ public class Task {
                                 replayUser.setLiveId(liveUser.getLiveId());
                                 replayUser.setUserId(liveUser.getUserId());
                                 replayUser.setMsgStatus(liveUser.getMsgStatus());
-                                replayUser.setOnline(liveUser.getOnline());
+                                replayUser.setOnline(0);
                                 replayUser.setOnlineSeconds(0L); // 回放观看时长从0开始,重新计时
                                 replayUser.setGlobalVisible(liveUser.getGlobalVisible());
                                 replayUser.setSingleVisible(liveUser.getSingleVisible());
@@ -742,6 +753,7 @@ public class Task {
                                 replayUser.setCreateTime(nowDate);
                                 replayUser.setUpdateTime(nowDate);
                                 replayUsers.add(replayUser);
+                                redisCache.setCacheObject(entryTimeKey,now);
                             }
 
                             // 批量更新直播用户的在线时长
@@ -824,8 +836,7 @@ public class Task {
                     queryUser.setLiveId(liveId);
                     queryUser.setLiveFlag(1);
                     queryUser.setReplayFlag(0);
-                    queryUser.setOnline(0); // 在线用户
-                    List<LiveWatchUser> onlineUsers = liveWatchUserService.selectLiveWatchUserList(queryUser);
+                    List<LiveWatchUser> onlineUsers = liveWatchUserService.selectAllWatchUser(queryUser);
                     if (onlineUsers == null || onlineUsers.isEmpty()) {
                         continue;
                     }
@@ -840,6 +851,7 @@ public class Task {
                     }
 
                     // 处理每个在线用户
+                    List<LiveWatchLog> updateLog = new ArrayList<>();
                     for (LiveWatchUser user : onlineUsers) {
                         try {
                             Long userId = user.getUserId();
@@ -869,13 +881,25 @@ public class Task {
 
                             // 使用 updateLiveWatchLogTypeByDuration 的逻辑更新观看记录状态
                             updateLiveWatchLogTypeByDuration(liveId, userId, qwUserId, externalContactId,
-                                    onlineSeconds, totalVideoDuration);
+                                    onlineSeconds, totalVideoDuration, updateLog);
                             
                         } catch (Exception e) {
                             log.error("处理用户观看记录状态异常: liveId={}, userId={}, error={}",
                                     liveId, user.getUserId(), e.getMessage(), e);
                         }
                     }
+                    // 批量插入回放用户数据
+                    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 e) {
                     log.error("处理直播间观看记录状态异常: liveId={}, error={}",
@@ -897,16 +921,15 @@ public class Task {
      * @param totalVideoDuration 视频总时长(秒)
      */
     private void updateLiveWatchLogTypeByDuration(Long liveId, Long userId, Long qwUserId,
-                                                   Long exId, Long onlineSeconds, long totalVideoDuration) {
+                                                   Long exId, Long onlineSeconds, long totalVideoDuration, List<LiveWatchLog> updateLog) {
         try {
             // 查询 LiveWatchLog
             LiveWatchLog queryLog = new LiveWatchLog();
             queryLog.setLiveId(liveId);
-            queryLog.setUserId(userId);
             queryLog.setQwUserId(String.valueOf(qwUserId));
             queryLog.setExternalContactId(exId);
 
-            List<LiveWatchLog> logs = liveWatchLogService.selectLiveWatchLogList(queryLog);
+            List<LiveWatchLog> logs = liveWatchLogService.selectLiveWatchLogByLogIdWithCache(queryLog);
             if (logs == null || logs.isEmpty()) {
                 return;
             }
@@ -940,7 +963,7 @@ public class Task {
                 // 如果 logType 已经是 2(完课),不再更新
                 if (needUpdate) {
                     log.setLogType(newLogType);
-                    liveWatchLogService.updateLiveWatchLog(log);
+                    updateLog.add(log);
                 }
             }
         } catch (Exception e) {

+ 2 - 2
fs-live-app/src/main/java/com/fs/live/websocket/auth/WebSocketConfigurator.java

@@ -56,10 +56,10 @@ public class WebSocketConfigurator extends ServerEndpointConfig.Configurator {
             userProperties.put(AttrConstant.LOCATION, parameterMap.get(AttrConstant.LOCATION).get(0));
         }
         if (parameterMap.containsKey(AttrConstant.QW_USER_ID)) {
-            userProperties.put(AttrConstant.QW_USER_ID, parameterMap.get(AttrConstant.QW_USER_ID).get(0));
+            userProperties.put(AttrConstant.QW_USER_ID, Long.valueOf(parameterMap.get(AttrConstant.QW_USER_ID).get(0)));
         }
         if (parameterMap.containsKey(AttrConstant.EXTERNAL_CONTACT_ID)) {
-            userProperties.put(AttrConstant.EXTERNAL_CONTACT_ID, parameterMap.get(AttrConstant.EXTERNAL_CONTACT_ID).get(0));
+            userProperties.put(AttrConstant.EXTERNAL_CONTACT_ID, Long.valueOf(parameterMap.get(AttrConstant.EXTERNAL_CONTACT_ID).get(0)));
         }
 
         // 验证token

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

@@ -36,6 +36,7 @@ import javax.websocket.*;
 import javax.websocket.server.ServerEndpoint;
 import java.io.EOFException;
 import java.io.IOException;
+import java.time.LocalDateTime;
 import java.util.*;
 import java.util.concurrent.*;
 import java.util.concurrent.locks.Lock;
@@ -80,6 +81,7 @@ public class WebSocketServer {
     private final LiveCouponMapper liveCouponMapper = SpringUtils.getBean(LiveCouponMapper.class);
     private final ILiveWatchLogService liveWatchLogService = SpringUtils.getBean(ILiveWatchLogService.class);
     private final ILiveVideoService liveVideoService = SpringUtils.getBean(ILiveVideoService.class);
+    private final ILiveCompletionPointsRecordService completionPointsRecordService = SpringUtils.getBean(ILiveCompletionPointsRecordService.class);
     private static Random random = new Random();
     
     // Redis key 前缀:用户进入直播间时间
@@ -126,7 +128,7 @@ public class WebSocketServer {
 
         // 记录连接信息 管理员不记录
         if (userType == 0) {
-            FsUserScrm fsUser = fsUserService.selectFsUserByUserId(userId);
+            FsUserScrm fsUser = fsUserService.selectFsUserById(userId);
             if (Objects.isNull(fsUser)) {
                 throw new BaseException("用户信息错误");
             }
@@ -135,8 +137,14 @@ public class WebSocketServer {
             room.put(userId, session);
             
             // 存储用户进入直播间的时间到 Redis(用于计算在线时长)
+            // 如果已经存在进入时间,说明是重连,不应该覆盖,保持原来的进入时间
             String entryTimeKey = String.format(USER_ENTRY_TIME_KEY, liveId, userId);
-            redisCache.setCacheObject(entryTimeKey, System.currentTimeMillis(), 24, TimeUnit.HOURS);
+            Long existingEntryTime = redisCache.getCacheObject(entryTimeKey);
+            if (existingEntryTime == null) {
+                // 首次连接,记录进入时间
+                redisCache.setCacheObject(entryTimeKey, System.currentTimeMillis(), 24, TimeUnit.HOURS);
+            }
+            // 如果是重连,不覆盖进入时间,保持原来的进入时间以便正确计算总时长
             
             // 直播间浏览量 +1
             redisCache.incr(PAGE_VIEWS_KEY + liveId, 1);
@@ -273,7 +281,7 @@ public class WebSocketServer {
         ConcurrentHashMap<Long, Session> room = getRoom(liveId);
         List<Session> adminRoom = getAdminRoom(liveId);
         if (userType == 0) {
-            FsUserScrm fsUser = fsUserService.selectFsUserByUserId(userId);
+            FsUserScrm fsUser = fsUserService.selectFsUserById(userId);
             if (Objects.isNull(fsUser)) {
                 throw new BaseException("用户信息错误");
             }
@@ -349,6 +357,32 @@ public class WebSocketServer {
                     if (msg.getData() != null && !msg.getData().isEmpty()) {
                         try {
                             Long currentDuration = Long.parseLong(msg.getData());
+
+                            Live currentLive = liveService.selectLiveByLiveId(liveId);
+                            if (currentLive == null) {
+                                break;
+                            }
+                            
+                            // 判断直播是否已开始:status=2(直播中) 或 当前时间 >= 开播时间
+                            boolean isLiveStarted = false;
+                            if (currentLive.getStatus() != null && currentLive.getStatus() == 2) {
+                                // status=2 表示直播中
+                                isLiveStarted = true;
+                            } else if (currentLive.getStartTime() != null) {
+                                // 判断当前时间是否已超过开播时间
+                                LocalDateTime now = java.time.LocalDateTime.now();
+                                isLiveStarted = now.isAfter(currentLive.getStartTime()) || now.isEqual(currentLive.getStartTime());
+                            }
+                            
+                            if (!isLiveStarted) {
+                                log.debug("[心跳-观看时长] 直播未开始(开播倒计时中),不统计观看时长, liveId={}, status={}, startTime={}", 
+                                        liveId, currentLive.getStatus(), currentLive.getStartTime());
+                                break;
+                            }
+                            
+                            log.debug("[心跳-观看时长] 直播已开始,统计观看时长, liveId={}, userId={}, duration={}秒", 
+                                    liveId, watchUserId, currentDuration);
+                            
                             // 使用Hash结构存储:一个直播间一个Hash,包含所有用户的时长
                             String hashKey = "live:watch:duration:hash:" + liveId;
                             String userIdField = String.valueOf(watchUserId);
@@ -361,6 +395,8 @@ public class WebSocketServer {
                                 // 设置过期时间(2小时)
                                 redisCache.expire(hashKey, 2, TimeUnit.HOURS);
 
+                                checkAndSendCompletionPointsInRealTime(liveId, watchUserId, currentDuration);
+
                             }
                         } catch (Exception e) {
                             log.error("[心跳-观看时长] 更新失败, liveId={}, userId={}, data={}", 
@@ -1148,7 +1184,6 @@ public class WebSocketServer {
         try {
             LiveWatchLog queryLog = new LiveWatchLog();
             queryLog.setLiveId(liveId);
-            queryLog.setUserId(userId);
             queryLog.setQwUserId(String.valueOf(qwUserId));
             queryLog.setExternalContactId(externalContactId);
             
@@ -1272,7 +1307,6 @@ public class WebSocketServer {
             // 查询 LiveWatchLog
             LiveWatchLog queryLog = new LiveWatchLog();
             queryLog.setLiveId(liveId);
-            queryLog.setUserId(userId);
             queryLog.setCompanyId(companyId);
             queryLog.setCompanyUserId(companyUserId);
             
@@ -1315,5 +1349,48 @@ public class WebSocketServer {
         }
     }
 
+    /**
+     * 实时检查并推送完课积分
+     * 在用户观看时长更新时立即检查是否达到完课条件,达到则立即推送
+     * @param liveId 直播间ID
+     * @param userId 用户ID
+     * @param duration 当前观看时长(秒)
+     */
+    private void checkAndSendCompletionPointsInRealTime(long liveId, long userId, Long duration) {
+        try {
+            log.debug("[实时完课检查] liveId={}, userId={}, duration={}秒", liveId, userId, duration);
+
+            // 1. 调用完课记录服务检查并创建完课记录
+            completionPointsRecordService.checkAndCreateCompletionRecord(liveId, userId, duration);
+
+            // 2. 查询是否有新的未领取完课记录
+            List<LiveCompletionPointsRecord> unreceivedRecords =
+                completionPointsRecordService.getUserUnreceivedRecords(liveId, userId);
+
+            if (unreceivedRecords == null || unreceivedRecords.isEmpty()) {
+                // 没有待领取的完课记录
+                return;
+            }
+
+            // 3. 构建推送消息
+            SendMsgVo sendMsgVo = new SendMsgVo();
+            sendMsgVo.setLiveId(liveId);
+            sendMsgVo.setUserId(userId);
+            sendMsgVo.setCmd("completionPoints");
+            sendMsgVo.setMsg("完成任务!");
+            sendMsgVo.setData(JSONObject.toJSONString(unreceivedRecords.get(0)));
+
+            // 4. 实时推送完课积分弹窗
+            sendCompletionPointsMessage(liveId, userId, sendMsgVo);
+
+            log.info("[实时完课推送] 发送完课积分弹窗通知, liveId={}, userId={}, points={}, duration={}秒",
+                    liveId, userId, unreceivedRecords.get(0).getPointsAwarded(), duration);
+
+        } catch (Exception e) {
+            log.error("[实时完课推送] 实时检查完课积分失败, liveId={}, userId={}, duration={}",
+                    liveId, userId, duration, e);
+        }
+    }
+
 }
 

+ 2 - 0
fs-service/src/main/java/com/fs/common/param/BaseQueryParam.java

@@ -18,5 +18,7 @@ public class BaseQueryParam extends BaseEntity implements Serializable {
     private Integer limit;
     @ApiModelProperty(value = "搜索字符串")
     private String keyword;
+    @ApiModelProperty(value = "当前的appid")
+    private String appId;
 
 }

+ 3 - 0
fs-service/src/main/java/com/fs/company/domain/Company.java

@@ -138,6 +138,9 @@ public class Company extends BaseEntity
 
     private BigDecimal redPackageMoney;
 
+    // 控制休息提示是否打开要暂停  0-关闭 1-打开 null-默认打开
+    private Integer isOpenRestReminder;
+
     @TableField(exist = false)
     private List<Long> ids;
 }

+ 3 - 0
fs-service/src/main/java/com/fs/company/vo/CompanyVO.java

@@ -104,4 +104,7 @@ public class CompanyVO implements Serializable
      * 新增展示商户号(分公司配置时有值)
      */
     private String mchId;
+
+    // 控制休息提示是否打开要暂停  0-关闭 1-打开 null-默认打开
+    private Integer isOpenRestReminder;
 }

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

@@ -119,4 +119,7 @@ public class FsUserCoursePeriod
     private Date periodLine;
     /** 是否需要单独注册会员,1-是,0-否(用于个微销售分享看课) */
     private String isNeedRegisterMember;
+
+    // 控制休息提示是否打开要暂停  0-关闭 1-打开 null-默认打开
+    private Integer IsOpenRestReminder;
 }

+ 0 - 2
fs-service/src/main/java/com/fs/course/service/IFsCourseRedPacketLogService.java

@@ -89,6 +89,4 @@ public interface IFsCourseRedPacketLogService
     void queryRedPacketResult(String startTime, String endTime);
 
     R getBillsByTransferBillNo(String batchId);
-
-    R transferBatchesBatchId(String batchId);
 }

+ 2 - 0
fs-service/src/main/java/com/fs/course/service/IFsUserCoursePeriodService.java

@@ -105,4 +105,6 @@ public interface IFsUserCoursePeriodService
     int editIsNeedRegisterMember(FsUserCoursePeriod fsUserCoursePeriod,String s);
 
     List<SysDictData> selectFsUserCoursePeriodListLabel(FsUserCoursePeriod fsUserCoursePeriod);
+
+    int updatePeriod(FsUserCoursePeriod fsUserCoursePeriod);
 }

+ 47 - 37
fs-service/src/main/java/com/fs/course/service/impl/FsCourseRedPacketLogServiceImpl.java

@@ -17,6 +17,7 @@ import com.fs.company.domain.Company;
 import com.fs.company.domain.CompanyMoneyLogs;
 import com.fs.company.mapper.CompanyMapper;
 import com.fs.company.mapper.CompanyMoneyLogsMapper;
+import com.fs.company.service.ICompanyConfigService;
 import com.fs.course.config.CourseConfig;
 import com.fs.course.config.RedPacketConfig;
 import com.fs.course.domain.FsCourseWatchLog;
@@ -478,33 +479,8 @@ public class FsCourseRedPacketLogServiceImpl implements IFsCourseRedPacketLogSer
                 company.getCompanyId(), moneyLog.getMoney());
     }
 
-    @Override
-    public R transferBatchesBatchId(String batchId) {
-        FsCourseRedPacketLog log = fsCourseRedPacketLogMapper.selectFsCourseRedPacketLogByBatchId(batchId);
-        if (log ==null){
-            return R.error("未查询到红包记录!");
-        }
-        String json = configService.selectConfigByKey("redPacket.config");
-        RedPacketConfig config = JSONUtil.toBean(json, RedPacketConfig.class);
-        //创建微信订单
-        WxPayConfig payConfig = new WxPayConfig();
-        BeanUtils.copyProperties(config,payConfig);
-        WxPayService wxPayService = new WxPayServiceImpl();
-        wxPayService.setConfig(payConfig);
-        TransferService transferService=wxPayService.getTransferService();
-
-        QueryTransferBatchesRequest request = new QueryTransferBatchesRequest();
-        request.setBatchId(batchId);
-
-        try {
-            QueryTransferBatchesResult queryTransferBatchesResult = transferService.transferBatchesBatchId(request);
-            logger.info("FsCourseRedPacketLog-log_id:{},【红包处理】查询批次结果:{}",log.getLogId(),queryTransferBatchesResult);
-            return R.ok(queryTransferBatchesResult.toString());
-        } catch (WxPayException e) {
-            logger.error(e.getMessage());
-            return R.error(e.getMessage());
-        }
-    }
+    @Autowired
+    private ICompanyConfigService companyConfigService;
 
     @Override
     public R getBillsByTransferBillNo(String batchId) {
@@ -512,8 +488,29 @@ public class FsCourseRedPacketLogServiceImpl implements IFsCourseRedPacketLogSer
         if (log ==null){
             return R.error("未查询到红包记录!");
         }
-        String json = configService.selectConfigByKey("redPacket.config");
-        RedPacketConfig config = JSONUtil.toBean(json, RedPacketConfig.class);
+        // 获取配置信息
+        CourseConfig courseConfig = JSONUtil.toBean(configService.selectConfigByKey("course.config"), CourseConfig.class);
+
+        String json;
+        RedPacketConfig config = new RedPacketConfig();
+        // 根据红包模式获取配置
+        switch (courseConfig.getRedPacketMode()){
+            case 1:
+                json = configService.selectConfigByKey("redPacket.config");
+                config = JSONUtil.toBean(json, RedPacketConfig.class);
+                break;
+            case 2:
+                json = companyConfigService.selectRedPacketConfigByKey(log.getCompanyId());
+                //如果分公司配置为空就走总后台的配置
+                if (StringUtils.isEmpty(json)){
+                    json = configService.selectConfigByKey("redPacket.config");
+                }
+                config = JSONUtil.toBean(json, RedPacketConfig.class);
+                break;
+            default:
+                throw new UnsupportedOperationException("当前红包模式不支持!");
+        }
+
         //创建微信订单
         WxPayConfig payConfig = new WxPayConfig();
         BeanUtils.copyProperties(config,payConfig);
@@ -521,15 +518,28 @@ public class FsCourseRedPacketLogServiceImpl implements IFsCourseRedPacketLogSer
         wxPayService.setConfig(payConfig);
         TransferService transferService=wxPayService.getTransferService();
 
-        try {
-            TransferBillsGetResult queryRedPacketResult = transferService.getBillsByTransferBillNo(batchId);
-            logger.info("FsCourseRedPacketLog-log_id:{},【红包处理】查询批次结果:{}",log.getLogId(),queryRedPacketResult);
-            return R.ok(queryRedPacketResult.toString());
-        } catch (WxPayException e) {
-            logger.error(e.getMessage());
-            return R.error(e.getMessage());
+        if (config.getIsNew() != null && config.getIsNew() == 1) {
+            try {
+                TransferBillsGetResult queryRedPacketResult = transferService.getBillsByTransferBillNo(batchId);
+                logger.info("FsCourseRedPacketLog-log_id:{},【红包处理】查询批次结果:{}",log.getLogId(),queryRedPacketResult);
+                return R.ok(queryRedPacketResult.toString());
+            } catch (WxPayException e) {
+                logger.error(e.getMessage());
+                return R.error(e.getMessage());
+            }
+        } else {
+            QueryTransferBatchesRequest request = new QueryTransferBatchesRequest();
+            request.setBatchId(batchId);
+
+            try {
+                QueryTransferBatchesResult queryTransferBatchesResult = transferService.transferBatchesBatchId(request);
+                logger.info("FsCourseRedPacketLog-log_id:{},【红包处理】查询批次结果:{}",log.getLogId(),queryTransferBatchesResult);
+                return R.ok(queryTransferBatchesResult.toString());
+            } catch (WxPayException e) {
+                logger.error(e.getMessage());
+                return R.error(e.getMessage());
+            }
         }
 
-
     }
 }

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

@@ -343,4 +343,16 @@ public class FsUserCoursePeriodServiceImpl implements IFsUserCoursePeriodService
     public List<SysDictData> selectFsUserCoursePeriodListLabel(FsUserCoursePeriod fsUserCoursePeriod) {
         return fsUserCoursePeriodMapper.selectFsUserCoursePeriodListLabel(fsUserCoursePeriod);
     }
+
+    /**
+     * @Description: 更新营期信息
+     * @Param:
+     * @Return:
+     * @Author xgb
+     * @Date 2025/12/19 10:52
+     */
+    @Override
+    public int updatePeriod(FsUserCoursePeriod fsUserCoursePeriod) {
+        return fsUserCoursePeriodMapper.updateFsUserCoursePeriod(fsUserCoursePeriod);
+    }
 }

+ 50 - 2
fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java

@@ -143,7 +143,7 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
     private static final String SHORT_LINK_PREFIX = "/courseH5/pages/course/learning?s=";
     // 排除看课数量限制的公司集合
     private static final Set<String> EXCLUDE_PROJECTS = new HashSet<>(Arrays.asList(
-            "福本源", "宽益堂", "叮当国医"
+            "福本源", "宽益堂", "叮当国医", "易行健"
     ));
     @Autowired
     ICompanyService companyService;
@@ -1002,6 +1002,9 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
                     log.info("用户:" + param.getVideoId());
                     log.info("企微用户:" + param.getQwUserId());
                     param.setQwExternalId(UnionEXt.getId());
+//                    param.setQwUserId(String.valueOf(UnionEXt.getQwUserId()));
+//                    param.setCompanyUserId(UnionEXt.getCompanyUserId());
+//                    param.setCompanyId(UnionEXt.getCompanyId());
                     FsCourseWatchLog log = courseWatchLogMapper.getWatchCourseVideoByExt(UnionEXt.getId(), param.getVideoId(), param.getQwUserId());
                     if (log == null) {
                         param.setUserId(user.getUserId());
@@ -2829,7 +2832,7 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
         }
 
         // 2025-11-16 鹤颜堂 xgb 添加配置控制休息提示是否打开要暂停 默认打开 0-关闭 1-打开
-        if (config.getIsOpenRestReminder() == null || config.getIsOpenRestReminder() == 1) {
+        if(isOpenRestReminder(config,watchLog)){
             if (courseVideoDetails != null && courseVideoDetails.getDuration() != null) {
                 // 查询视频是否设置了红包,没有就不提示
                 Integer fsUserCourseVideoRedPackage = fsUserCourseVideoRedPackageMapper.selectRedPacketByCompanyCount(param.getVideoId(), null, param.getPeriodId());
@@ -2840,6 +2843,7 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
             }
         }
 
+
         vo.setTipsTime(tipsTime);
         vo.setTipsTime2(tipsTime2);
         //判断是否完课
@@ -2909,6 +2913,50 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
         return ResponseResult.ok(vo);
     }
 
+    /**
+     * @Description: 是否看课中断
+     * @Param:
+     * @Return:
+     * @Author xgb 默认 打开看课中断
+     * @Date 2025/12/18 17:33
+     */
+
+    private boolean isOpenRestReminder(CourseConfig config ,FsCourseWatchLog watchLog){
+        // 查询公司看课中断是否打开
+        boolean result= true;
+
+        if (config.getIsOpenRestReminder() != null && config.getIsOpenRestReminder() == 0) {
+            result=false;
+        }
+
+        if(watchLog!=null){
+            if(watchLog.getCompanyId()!=null){
+                Company company=companyMapper.selectCompanyById(watchLog.getCompanyId());
+                if(company!=null && company.getIsOpenRestReminder()!=null){
+                    if(company.getIsOpenRestReminder()==0){
+                        result=false;
+                    }else {
+                        result=true;
+                    }
+                }
+            }
+
+            if(watchLog.getPeriodId()!=null){
+                FsUserCoursePeriod period= fsUserCoursePeriodMapper.selectFsUserCoursePeriodById(watchLog.getPeriodId());
+                if(period!=null && period.getIsOpenRestReminder()!=null){
+                    if(period.getIsOpenRestReminder()==0){
+                        result=false;
+                    }else {
+                        result=true;
+                    }
+                }
+            }
+        }
+
+
+        return result;
+    }
+
     @Override
     public R addWatchLogByLink(FsUserCourseAddCompanyUserParam param) {
 

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

@@ -90,4 +90,7 @@ public class FsUserCoursePeriodVO implements Serializable {
     private Date periodLine;
     /** 是否需要单独注册会员,1-是,0-否(用于个微销售分享看课) */
     private String isNeedRegisterMember;
+
+    // 控制休息提示是否打开要暂停  0-关闭 1-打开 null-默认打开
+    private Integer IsOpenRestReminder;
 }

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

@@ -819,7 +819,7 @@ public class AiHookServiceImpl implements AiHookService {
                                         .append("\uD83C\uDF39\uD83C\uDF39\uD83C\uDF39");
                             case 3:
                                 ExpressInfoDTO expressInfo = getExpress(fsStoreOrder.getOrderId());
-                                sBuilder.append("您购买的有一个包裹 ");
+                                sBuilder.append("您有一个包裹 ");
                                 sBuilder.append(" 已经查询到了,正在配送中了。\n");
                                 if(expressInfo != null && expressInfo.getTraces() != null && !expressInfo.getTraces().isEmpty()){
                                     List<TracesDTO> traces = expressInfo.getTraces();

+ 5 - 0
fs-service/src/main/java/com/fs/his/domain/FsStorePaymentError.java

@@ -35,4 +35,9 @@ public class FsStorePaymentError extends BaseEntity
     @Excel(name = "0未处理 1已处理")
     private Integer status;
 
+
+    private Long orderId;
+
+    private Integer businessType;
+
 }

+ 5 - 5
fs-service/src/main/java/com/fs/his/domain/FsUser.java

@@ -232,11 +232,11 @@ public class FsUser extends BaseEntity
     @TableField(exist = false)
     private String nicknameExact;
 
-    /**
-     * 搜索关键词-电话号码/会员id/会员昵称
-     * **/
-    @TableField(exist = false)
-    private String keywords;
+//    /**
+//     * 搜索关键词-电话号码/会员id/会员昵称
+//     * **/
+//    @TableField(exist = false)
+//    private String keywords;
 
     public String getNickname() {
         return nickname;

+ 7 - 0
fs-service/src/main/java/com/fs/his/mapper/FsIntegralCartMapper.java

@@ -28,4 +28,11 @@ public interface FsIntegralCartMapper extends BaseMapper<FsIntegralCart> {
      * @return  list
      */
     List<FsIntegralCartVO> getCartsByMap(@Param("params") Map<String, Object> params);
+
+    /**
+     * 根据商品ID删除购物车记录
+     * @param goodsId   商品ID
+     * @return  删除数量
+     */
+    int deleteCartByGoodsId(@Param("goodsId") Long goodsId);
 }

+ 2 - 2
fs-service/src/main/java/com/fs/his/mapper/FsUserMapper.java

@@ -312,9 +312,9 @@ public interface FsUserMapper
 
     UserDetailsVO getCountWatchCourse (@Param("userId") Long userId, @Param("fsUserId") Long fsUserId, @Param("dateTag") String dateTag,@Param("userCompanyId")  Long userCompanyId);
 
-    UserDetailsVO getCountAnswer(@Param("userId") Long userId, @Param("fsUserId") Long fsUserId, @Param("dateTag") String dateTag);
+    UserDetailsVO getCountAnswer(@Param("userCompanyId") Long userCompanyId, @Param("fsUserId") Long fsUserId, @Param("dateTag") String dateTag);
 
-    UserDetailsVO getCountRedPacket(@Param("userId") Long userId, @Param("fsUserId") Long fsUserId, @Param("dateTag") String dateTag);
+    UserDetailsVO getCountRedPacket(@Param("userCompanyId") Long userCompanyId, @Param("fsUserId") Long fsUserId, @Param("dateTag") String dateTag);
 
     FsUserSummaryCountVO countUserSummary(@Param("userId") Long userId, @Param("companyId") Long companyId);
 

+ 9 - 0
fs-service/src/main/java/com/fs/his/service/IFsStorePaymentErrorService.java

@@ -0,0 +1,9 @@
+package com.fs.his.service;
+
+import com.fs.his.domain.FsStorePaymentError;
+
+import java.util.List;
+
+public interface IFsStorePaymentErrorService {
+    List<FsStorePaymentError> selectFsStorePaymentErrorList(FsStorePaymentError fsStorePaymentError);
+}

+ 7 - 0
fs-service/src/main/java/com/fs/his/service/impl/FsCouponServiceImpl.java

@@ -141,6 +141,13 @@ public class FsCouponServiceImpl implements IFsCouponService
         if(fsUserCouponMapper.checkReceive(param.getUserId(),coupon.getCouponId())>0){
             return R.error("您已领取此券");
         }
+        //判断是否是私域优惠券
+        Integer couponType = coupon.getCouponType();
+        if(couponType!=null && couponType==5){
+            if (param.getCompanyId() == null || param.getCompanyUserId() == null) {
+                return R.error("您无法领取该优惠券");
+            }
+        }
         FsUserCoupon userCoupon=new FsUserCoupon();
         userCoupon.setUserId(param.getUserId());
         userCoupon.setStatus(0);

+ 11 - 0
fs-service/src/main/java/com/fs/his/service/impl/FsIntegralGoodsServiceImpl.java

@@ -9,6 +9,7 @@ import com.fs.course.domain.FsCourseAnswerReward;
 import com.fs.his.domain.FsChineseMedicine;
 import com.fs.his.domain.FsIntegralGoods;
 import com.fs.his.domain.FsUser;
+import com.fs.his.mapper.FsIntegralCartMapper;
 import com.fs.his.mapper.FsIntegralGoodsMapper;
 import com.fs.his.mapper.FsUserMapper;
 import com.fs.his.param.FsIntegralGoodsListUParam;
@@ -37,6 +38,8 @@ public class FsIntegralGoodsServiceImpl implements IFsIntegralGoodsService
     private FsIntegralGoodsMapper fsIntegralGoodsMapper;
     @Autowired
     private FsUserMapper fsUserMapper;
+    @Autowired
+    private FsIntegralCartMapper fsIntegralCartMapper;
 
     @Autowired
     private ISysConfigService configService;
@@ -99,6 +102,11 @@ public class FsIntegralGoodsServiceImpl implements IFsIntegralGoodsService
     @Override
     public int deleteFsIntegralGoodsByGoodsIds(Long[] goodsIds)
     {
+        // 先删除购物车中的相关记录
+        for (Long goodsId : goodsIds) {
+            fsIntegralCartMapper.deleteCartByGoodsId(goodsId);
+        }
+        // 再删除商品
         return fsIntegralGoodsMapper.deleteFsIntegralGoodsByGoodsIds(goodsIds);
     }
 
@@ -111,6 +119,9 @@ public class FsIntegralGoodsServiceImpl implements IFsIntegralGoodsService
     @Override
     public int deleteFsIntegralGoodsByGoodsId(Long goodsId)
     {
+        // 先删除购物车中的相关记录
+        fsIntegralCartMapper.deleteCartByGoodsId(goodsId);
+        // 再删除商品
         return fsIntegralGoodsMapper.deleteFsIntegralGoodsByGoodsId(goodsId);
     }
 

+ 51 - 19
fs-service/src/main/java/com/fs/his/service/impl/FsIntegralOrderServiceImpl.java

@@ -43,6 +43,7 @@ import com.fs.his.param.*;
 import com.fs.his.service.*;
 import com.fs.his.utils.ConfigUtil;
 import com.fs.his.utils.PhoneUtil;
+import com.fs.his.utils.RedisCacheUtil;
 import com.fs.his.vo.*;
 import com.fs.qw.domain.QwUser;
 import com.fs.qw.mapper.QwUserMapper;
@@ -161,6 +162,9 @@ public class FsIntegralOrderServiceImpl implements IFsIntegralOrderService
 
     @Autowired
     private IFsUserAddressService fsUserAddressService;
+
+    @Autowired
+    private RedisCacheUtil redisCacheUtil;
     /**
      * 查询积分商品订单
      *
@@ -312,6 +316,10 @@ public class FsIntegralOrderServiceImpl implements IFsIntegralOrderService
             throw new CustomException("库存不足");
         }
 
+        // 清除商品缓存
+        redisCacheUtil.delSpringCacheKey("getIntegralGoodsById", integralGoods.getGoodsId());
+        redisCacheUtil.delRedisKey("getIntegralGoodsList");
+
         integralGoods.setNum(1);
         List<FsIntegralGoods> goodsItem = new ArrayList<>();
         goodsItem.add(integralGoods);
@@ -467,6 +475,9 @@ public class FsIntegralOrderServiceImpl implements IFsIntegralOrderService
                 throw new CustomException("库存不足");
             }
 
+            // 清除商品缓存
+            redisCacheUtil.delSpringCacheKey("getIntegralGoodsById", integralGoods.getGoodsId());
+
             totalIntegral += integralGoods.getIntegral() * cart.getCartNum();
             totalCash = totalCash.add(integralGoods.getCash().multiply(BigDecimal.valueOf(cart.getCartNum())));
 
@@ -474,7 +485,17 @@ public class FsIntegralOrderServiceImpl implements IFsIntegralOrderService
             goodsItem.add(integralGoods);
         }
 
-        return createOrder(user, address, totalIntegral, totalCash, goodsItem, null);
+        // 清除商品列表缓存
+        redisCacheUtil.delRedisKey("getIntegralGoodsList");
+
+        // 创建订单
+        R result = createOrder(user, address, totalIntegral, totalCash, goodsItem, null);
+
+        // 订单创建成功后,删除购物车中已下单的商品
+        if (result.get("code").equals(200)) {
+            cartService.delCartByIds(user.getUserId(), param.getIds());
+        }
+        return result;
     }
 
     @Override
@@ -566,8 +587,9 @@ public class FsIntegralOrderServiceImpl implements IFsIntegralOrderService
     public R cannelOrder(Long orderId) {
         // 取消订单
         FsIntegralOrder order = fsIntegralOrderMapper.selectFsIntegralOrderByOrderId(orderId);
-        if (!order.getStatus().equals(4)){
-            return R.error("非法操作");
+        // 允许取消待支付(4)和待发货(1)的订单
+        if (!order.getStatus().equals(4) && !order.getStatus().equals(1)){
+            return R.error("非法操作,只能取消待支付或待发货的订单");
         }
 
         order.setStatus(5);
@@ -576,26 +598,36 @@ public class FsIntegralOrderServiceImpl implements IFsIntegralOrderService
         // 还原库存
         if (order.getItemJson().startsWith("[") && order.getItemJson().endsWith("]")){
             List<FsIntegralGoods> goodsItem = JSONUtil.toBean(order.getItemJson(), new TypeReference<List<FsIntegralGoods>>(){}, true);
-            goodsItem.forEach(goods -> fsIntegralGoodsMapper.addStock(goods.getGoodsId(), Objects.isNull(goods.getNum()) ? 1 : goods.getNum()));
+            goodsItem.forEach(goods -> {
+                fsIntegralGoodsMapper.addStock(goods.getGoodsId(), Objects.isNull(goods.getNum()) ? 1 : goods.getNum());
+                // 清除商品缓存
+                redisCacheUtil.delSpringCacheKey("getIntegralGoodsById", goods.getGoodsId());
+            });
         } else {
             FsIntegralGoods integralGoods = JSONUtil.toBean(order.getItemJson(), FsIntegralGoods.class);
             fsIntegralGoodsMapper.addStock(integralGoods.getGoodsId(), Objects.isNull(integralGoods.getNum()) ? 1 : integralGoods.getNum());
+            // 清除商品缓存
+            redisCacheUtil.delSpringCacheKey("getIntegralGoodsById", integralGoods.getGoodsId());
+        }
+        // 清除商品列表缓存
+        redisCacheUtil.delRedisKey("getIntegralGoodsList");
+
+        // 还原积分(只有当订单消耗了积分时才退还)
+        if (StringUtils.isNotEmpty(order.getIntegral()) && Long.parseLong(order.getIntegral()) > 0) {
+            FsUser user=fsUserMapper.selectFsUserByUserId(order.getUserId());
+            FsUser userMap=new FsUser();
+            userMap.setUserId(user.getUserId());
+            userMap.setIntegral(user.getIntegral() + Long.parseLong(order.getIntegral()));
+            fsUserMapper.updateFsUser(userMap);
+            FsUserIntegralLogs logs = new FsUserIntegralLogs();
+            logs.setIntegral(Long.parseLong(order.getIntegral()));
+            logs.setUserId(order.getUserId());
+            logs.setBalance(userMap.getIntegral());
+            logs.setLogType(5);
+            logs.setBusinessId(order.getOrderId().toString());
+            logs.setCreateTime(new Date());
+            fsUserIntegralLogsMapper.insertFsUserIntegralLogs(logs);
         }
-
-        // 还原积分
-        FsUser user=fsUserMapper.selectFsUserByUserId(order.getUserId());
-        FsUser userMap=new FsUser();
-        userMap.setUserId(user.getUserId());
-        userMap.setIntegral(user.getIntegral() + Long.parseLong(order.getIntegral()));
-        fsUserMapper.updateFsUser(userMap);
-        FsUserIntegralLogs logs = new FsUserIntegralLogs();
-        logs.setIntegral(Long.parseLong(order.getIntegral()));
-        logs.setUserId(order.getUserId());
-        logs.setBalance(userMap.getIntegral());
-        logs.setLogType(5);
-        logs.setBusinessId(order.getOrderId().toString());
-        logs.setCreateTime(new Date());
-        fsUserIntegralLogsMapper.insertFsUserIntegralLogs(logs);
 
         return R.ok();
     }

+ 21 - 0
fs-service/src/main/java/com/fs/his/service/impl/FsStorePaymentErrorServiceImpl.java

@@ -0,0 +1,21 @@
+package com.fs.his.service.impl;
+
+import com.fs.his.domain.FsStorePaymentError;
+import com.fs.his.mapper.FsStorePaymentErrorMapper;
+import com.fs.his.service.IFsStorePaymentErrorService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+@Service
+public class FsStorePaymentErrorServiceImpl implements IFsStorePaymentErrorService {
+    @Autowired
+    private FsStorePaymentErrorMapper fsStorePaymentErrorMapper;
+
+    public List<FsStorePaymentError> selectFsStorePaymentErrorList(FsStorePaymentError fsStorePaymentError){
+        return fsStorePaymentErrorMapper.selectFsStorePaymentErrorList(fsStorePaymentError);
+    }
+
+
+}

+ 45 - 6
fs-service/src/main/java/com/fs/his/service/impl/FsStorePaymentServiceImpl.java

@@ -48,9 +48,7 @@ import com.fs.course.service.IFsUserVipOrderService;
 import com.fs.his.domain.*;
 import com.fs.his.enums.PaymentMethodEnum;
 import com.fs.his.mapper.*;
-import com.fs.his.param.FsStorePaymentParam;
-import com.fs.his.param.PayOrderParam;
-import com.fs.his.param.WxSendRedPacketParam;
+import com.fs.his.param.*;
 import com.fs.his.service.IFsInquiryOrderService;
 
 import com.fs.his.service.IFsPackageOrderService;
@@ -220,6 +218,9 @@ public class FsStorePaymentServiceImpl implements IFsStorePaymentService {
     @Value("${cloud_host.company_name}")
     private String signProjectName;
 
+    @Autowired
+    private FsStorePaymentErrorMapper fsStorePaymentErrorMapper;
+
     /**
      * 红包账户锁
      */
@@ -1155,7 +1156,7 @@ public class FsStorePaymentServiceImpl implements IFsStorePaymentService {
         logger.info("zyp \n【收到转账回调V3::分公司】:{}",notifyData);
         try {
 //            String json = configService.selectConfigByKey("redPacket.config");
-            String json = companyConfigMapper.selectRedPacketConfigByKey(companyId);
+            String json = companyConfigService.selectRedPacketConfigByKey(companyId);
             RedPacketConfig config = JSONUtil.toBean(json, RedPacketConfig.class);
 
             //创建微信订单
@@ -1676,18 +1677,56 @@ public class FsStorePaymentServiceImpl implements IFsStorePaymentService {
         return "SUCCESS";
     }
 
+    long TWENTY_DAYS_IN_MILLIS = 1728000000L;// 使用毫秒判断,20天 = 20 * 24 * 60 * 60 * 1000 毫秒
     @Override
     public void synchronizePayStatus() {
         FsStorePayment queryParam = new FsStorePayment();
         queryParam.setStatus(0);//未支付
-        queryParam.setBeginTime(DateUtils.addDateDays(-1));
+//        queryParam.setBeginTime(DateUtils.addDateDays(-1));
         queryParam.setEndTime(DateUtils.getDate());
         List<FsStorePayment> list = selectFsStorePaymentList(queryParam);
         if (list != null && !list.isEmpty()) {
             List<CompletableFuture<Void>> futures = new ArrayList<>();
             for (FsStorePayment fsStorePayment : list) {
                 CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
-                    updateFsStorePaymentByDecryptForm(fsStorePayment.getPaymentId());
+                    try {
+                        updateFsStorePaymentByDecryptForm(fsStorePayment.getPaymentId());
+                        //查询是否改为已支付
+                        FsStorePayment finalPayment = fsStorePaymentMapper.selectFsStorePaymentByPaymentId(fsStorePayment.getPaymentId());
+                        try {
+                            Date createTime = finalPayment.getCreateTime();
+                            Date now = new Date();
+                            long value = now.getTime() - createTime.getTime();
+                            if(finalPayment.getStatus() == 0
+                                    && finalPayment.getBusinessType() == 3
+                                    && (value > TWENTY_DAYS_IN_MILLIS)
+                                    && finalPayment.getBusinessId() != null){
+                                //套餐包超过20天取消订单
+                                FsPackageOrderCancelParam param = new FsPackageOrderCancelParam();
+                                param.setOrderId(Long.valueOf(finalPayment.getBusinessId()));
+                                packageOrderService.cancel(param);
+                            }
+                        } catch (NumberFormatException e) {
+                            logger.info("定时任务:同步支付状态超时取消订单失败,payment_id:{}",fsStorePayment.getPaymentId());
+                        }
+                    } catch (Exception e) {
+                        //添加失败记录
+                        FsStorePaymentError fsStorePaymentError = new FsStorePaymentError();
+                        fsStorePaymentError.setOrderFlowNo(fsStorePayment.getTradeNo());
+                        String businessId = fsStorePayment.getBusinessId();
+                        fsStorePaymentError.setBusinessType(fsStorePayment.getBusinessType());
+                        fsStorePaymentError.setMsg(e.getMessage());
+                        fsStorePaymentError.setStatus(0);
+                        fsStorePaymentError.setCreateTime(new Date());
+                        if (businessId != null && fsStorePayment.getBusinessType() == 3) {
+                            fsStorePaymentError.setOrderId(Long.valueOf(businessId));
+                            FsPackageOrder fsPackageOrder = packageOrderService.selectFsPackageOrderByOrderId(Long.valueOf(businessId));
+                            if (fsPackageOrder != null) {
+                                fsStorePaymentError.setOrderNo(fsPackageOrder.getOrderSn());
+                            }
+                        }
+                        fsStorePaymentErrorMapper.insertFsStorePaymentError(fsStorePaymentError);
+                    }
                     logger.info("定时任务:同步支付状态,payment_id:{}",fsStorePayment.getPaymentId());
                 });
                 futures.add(future);

+ 3 - 3
fs-service/src/main/java/com/fs/his/service/impl/FsUserServiceImpl.java

@@ -835,10 +835,10 @@ public class FsUserServiceImpl implements IFsUserService {
     @Override
     public UserDetailsVO getUserDetails(Long userId, Long fsUserId, String dateTag, Long userCompanyId) {
         UserDetailsVO countWatchCourse = fsUserMapper.getCountWatchCourse(userId, fsUserId, dateTag, userCompanyId);
-        FsUserCompanyUser fsUserCompanyUser = userCompanyUserService.selectFsUserCompanyUserById(userCompanyId);
+//        FsUserCompanyUser fsUserCompanyUser = userCompanyUserService.selectFsUserCompanyUserById(userCompanyId);
 
-        UserDetailsVO countAnswer = fsUserMapper.getCountAnswer(fsUserCompanyUser.getCompanyUserId(), fsUserId, dateTag);
-        UserDetailsVO countRedPacket = fsUserMapper.getCountRedPacket(fsUserCompanyUser.getCompanyUserId(), fsUserId, dateTag);
+        UserDetailsVO countAnswer = fsUserMapper.getCountAnswer(userCompanyId, fsUserId, dateTag);
+        UserDetailsVO countRedPacket = fsUserMapper.getCountRedPacket(userCompanyId, fsUserId, dateTag);
         UserDetailsVO vo = new UserDetailsVO();
         if (countWatchCourse != null) {
             BeanUtils.copyProperties(countWatchCourse, vo);

+ 4 - 0
fs-service/src/main/java/com/fs/hisStore/domain/FsStoreProductScrm.java

@@ -339,4 +339,8 @@ public class FsStoreProductScrm extends BaseEntity
     @Excel(name = "国产或进口")
     private String domesticImported;
 
+    /** 所属小程序app_id,多个用逗号隔开 */
+    @Excel(name = "所属小程序app_id")
+    private String appIds;
+
 }

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

@@ -3,6 +3,7 @@ package com.fs.hisStore.mapper;
 import java.util.List;
 import java.util.Map;
 
+import com.fs.common.param.BaseQueryParam;
 import com.fs.his.domain.FsStoreProduct;
 import com.fs.his.param.FsStoreProductListSParam;
 import com.fs.his.vo.FsStoreProductListSVO;
@@ -207,7 +208,10 @@ public interface FsStoreProductScrmMapper
             "        <foreach collection='maps.cateIds' item='cateId' open='(' separator=',' close=')'>\n" +
             "            #{cateId}\n" +
             "        </foreach>\n" +
-            "    </if>"+
+            "    </if>" +
+            "<if test = 'maps.appId != null and maps.appId != \" \" '> " +
+            " and ((FIND_IN_SET(#{maps.appId}, p.app_ids) > 0)) " +
+            "</if>"+
             "<if test = 'maps.defaultOrder != null and maps.defaultOrder==\"desc\"  '> " +
             "order by p.sort asc,product_id desc" +
             "</if>" +
@@ -269,7 +273,7 @@ public interface FsStoreProductScrmMapper
             "</if>" +
             "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);
+    List<FsStoreProductListQueryVO> selectFsStoreProductTuiListQuery(@Param("config") MedicalMallConfig  config, @Param("param") BaseQueryParam param);
     List<FsStoreProductListQueryVO> selectFsStoreProductGoodListQuery(@Param("config") MedicalMallConfig  config);
     @Select({"<script> " +
             "select count(1) from fs_store_product_scrm  " +

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

@@ -296,4 +296,7 @@ public class FsStoreProductAddEditParam implements Serializable
     @Excel(name = "国产或进口")
     private String domesticImported;
 
+    /** 所属小程序app_id,多个用逗号隔开 */
+    private String appIds;
+
 }

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

@@ -40,4 +40,7 @@ public class FsStoreProductQueryParam extends BaseQueryParam implements Serializ
     private Integer isStores;
 
     List<Long> cateIds;
+
+    @ApiModelProperty(value = "当前的appid")
+    private String appId;
 }

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

@@ -4,6 +4,7 @@ import java.util.List;
 import java.util.Map;
 
 import com.fs.common.core.domain.R;
+import com.fs.common.param.BaseQueryParam;
 import com.fs.his.domain.FsStoreProduct;
 import com.fs.hisStore.domain.FsStoreProductScrm;
 import com.fs.hisStore.domain.FsStoreProductRuleScrm;
@@ -102,7 +103,7 @@ public interface IFsStoreProductScrmService
 
     List<FsStoreProductListQueryVO> selectFsStoreProductGoodQuery(int count);
 
-    List<FsStoreProductListQueryVO> selectFsStoreProductTuiListQuery();
+    List<FsStoreProductListQueryVO> selectFsStoreProductTuiListQuery(BaseQueryParam param);
 
     List<FsStoreProductListQueryVO> selectFsStoreProductGoodListQuery();
 

+ 27 - 26
fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreProductScrmServiceImpl.java

@@ -17,6 +17,7 @@ import com.fs.common.core.domain.R;
 import com.fs.common.core.redis.RedisCache;
 import com.fs.common.exception.CustomException;
 import com.fs.common.exception.ServiceException;
+import com.fs.common.param.BaseQueryParam;
 import com.fs.common.utils.DateUtils;
 import com.fs.company.cache.ICompanyCacheService;
 import com.fs.config.cloud.CloudHostProper;
@@ -236,15 +237,15 @@ public class FsStoreProductScrmServiceImpl implements IFsStoreProductScrmService
         storeAuditLogUtil.addBatchAuditArray(productIds, "", "");
         log.info("批量删除商品:{}", productIds);
         int result = fsStoreProductMapper.deleteFsStoreProductByIds(productIds);
-        
+
         // 清除缓存
         clearProductDetailCache(productIds);
-        
+
         // 异步处理商品删除联动逻辑
         if (result > 0) {
             handleProductDeleteAsync(productIds);
         }
-        
+
         return result;
     }
 
@@ -258,7 +259,7 @@ public class FsStoreProductScrmServiceImpl implements IFsStoreProductScrmService
     public void handleProductDeleteAsync(Long[] productIds) {
         try {
             log.info("开始异步处理商品删除联动,商品IDs: {}", Arrays.toString(productIds));
-            
+
             // 查询所有未直播(1)、直播中(2)和直播回放(4)的直播间
             // 使用 LiveMapper 查询状态为1,2,4的直播间(包括所有类型)
             List<Live> allLiveList = liveMapper.liveListAll();
@@ -283,19 +284,19 @@ public class FsStoreProductScrmServiceImpl implements IFsStoreProductScrmService
                     }
                 }
             }
-            
+
             if (targetLiveList.isEmpty()) {
                 log.info("没有找到需要处理的直播间");
                 return;
             }
-            
+
             log.info("找到 {} 个需要处理的直播间", targetLiveList.size());
-            
+
             // 遍历每个被删除的商品
             for (Long productId : productIds) {
                 processProductDeleteForLives(productId, targetLiveList);
             }
-            
+
             log.info("商品删除联动处理完成");
         } catch (Exception e) {
             log.error("异步处理商品删除联动失败", e);
@@ -315,19 +316,19 @@ public class FsStoreProductScrmServiceImpl implements IFsStoreProductScrmService
                 if (liveId == null) {
                     continue;
                 }
-                
+
                 // 1. 删除直播商品
                 deleteLiveGoodsByProductId(productId, liveId);
-                
+
                 // 2. 删除直播定时任务(直播上下架、直播卡片)
                 deleteLiveAutoTasksByProductId(productId, liveId);
-                
+
                 // 3. 删除直播抽奖(产品关联被删除的商品)
                 deleteLiveLotteryProductConfByProductId(productId, liveId);
-                
+
                 // 4. 删除直播定时任务(抽奖,里面关联了这个商品的)
                 deleteLiveAutoTasksByLotteryProductId(productId, liveId);
-                
+
             } catch (Exception e) {
                 log.error("处理直播间 {} 的商品 {} 删除联动失败", live.getLiveId(), productId, e);
             }
@@ -346,7 +347,7 @@ public class FsStoreProductScrmServiceImpl implements IFsStoreProductScrmService
             queryGoods.setProductId(productId);
             queryGoods.setLiveId(liveId);
             List<LiveGoods> goodsList = liveGoodsService.selectLiveGoodsList(queryGoods);
-            
+
             if (!goodsList.isEmpty()) {
                 Long[] goodsIds = goodsList.stream()
                         .map(LiveGoods::getGoodsId)
@@ -370,9 +371,9 @@ public class FsStoreProductScrmServiceImpl implements IFsStoreProductScrmService
             LiveAutoTask queryTask = new LiveAutoTask();
             queryTask.setLiveId(liveId);
             List<LiveAutoTask> taskList = liveAutoTaskService.selectLiveAutoTaskList(queryTask);
-            
+
             List<Long> taskIdsToDelete = new ArrayList<>();
-            
+
             for (LiveAutoTask task : taskList) {
                 // 任务类型:1-定时推送卡片商品 6-自动上下架
                 if (task.getTaskType() != null && (task.getTaskType() == 1L || task.getTaskType() == 6L)) {
@@ -396,7 +397,7 @@ public class FsStoreProductScrmServiceImpl implements IFsStoreProductScrmService
                     }
                 }
             }
-            
+
             if (!taskIdsToDelete.isEmpty()) {
                 Long[] ids = taskIdsToDelete.toArray(new Long[0]);
                 liveAutoTaskService.deleteLiveAutoTaskByIds(ids);
@@ -419,7 +420,7 @@ public class FsStoreProductScrmServiceImpl implements IFsStoreProductScrmService
             queryConf.setProductId(productId);
             queryConf.setLiveId(liveId);
             List<LiveLotteryProductConf> confList = liveLotteryProductConfMapper.selectLiveLotteryProductConfList(queryConf);
-            
+
             if (!confList.isEmpty()) {
                 Long[] ids = confList.stream()
                         .map(LiveLotteryProductConf::getId)
@@ -445,23 +446,23 @@ public class FsStoreProductScrmServiceImpl implements IFsStoreProductScrmService
             queryConf.setProductId(productId);
             queryConf.setLiveId(liveId);
             List<LiveLotteryProductConf> confList = liveLotteryProductConfMapper.selectLiveLotteryProductConfList(queryConf);
-            
+
             if (confList.isEmpty()) {
                 return;
             }
-            
+
             // 获取所有相关的抽奖ID
             Set<Long> lotteryIds = confList.stream()
                     .map(LiveLotteryProductConf::getLotteryId)
                     .collect(Collectors.toSet());
-            
+
             // 查询该直播间的所有定时任务
             LiveAutoTask queryTask = new LiveAutoTask();
             queryTask.setLiveId(liveId);
             List<LiveAutoTask> taskList = liveAutoTaskService.selectLiveAutoTaskList(queryTask);
-            
+
             List<Long> taskIdsToDelete = new ArrayList<>();
-            
+
             for (LiveAutoTask task : taskList) {
                 // 任务类型:4-抽奖
                 if (task.getTaskType() != null && task.getTaskType() == 4L) {
@@ -475,7 +476,7 @@ public class FsStoreProductScrmServiceImpl implements IFsStoreProductScrmService
                     }
                 }
             }
-            
+
             if (!taskIdsToDelete.isEmpty()) {
                 Long[] ids = taskIdsToDelete.toArray(new Long[0]);
                 liveAutoTaskService.deleteLiveAutoTaskByIds(ids);
@@ -1078,8 +1079,8 @@ public class FsStoreProductScrmServiceImpl implements IFsStoreProductScrmService
     }
 
     @Override
-    public List<FsStoreProductListQueryVO> selectFsStoreProductTuiListQuery() {
-        return fsStoreProductMapper.selectFsStoreProductTuiListQuery(medicalMallConfig);
+    public List<FsStoreProductListQueryVO> selectFsStoreProductTuiListQuery(BaseQueryParam param) {
+        return fsStoreProductMapper.selectFsStoreProductTuiListQuery(medicalMallConfig, param);
     }
 
     @Override

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

@@ -123,8 +123,8 @@ public class FsStoreOrderItemExportRefundZMVO implements Serializable  {
     @Excel(name = "银行交易流水号",sort = 240)
     private String bankTransactionId;
 
-    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
-    @Excel(name = "退款时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss",sort = 220)
+//    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+//    @Excel(name = "退款时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss",sort = 220)
     private Date refundTime;
 
     @Excel(name = "退款数量" ,sort = 230)

+ 1 - 1
fs-service/src/main/java/com/fs/live/domain/LiveOrder.java

@@ -96,7 +96,7 @@ public class LiveOrder extends BaseEntity {
     private String payType;
 
     /** 订单状态(-1 : 申请退款 -2 : 退货成功 0:已取消 1:待支付 2:待发货;3:待收货;4:待评价;5:已完成) */
-    @Excel(name = "订单状态", readConverterExp = "-=1,:=,申=请退款,-=2,:=,退=货成功,1=:待支付,2=:待发货;3:待收货;4:待评价;5:已完成")
+    @Excel(name = "订单状态", readConverterExp = "-=1,:=,申=请退款,-=2,:=,退=货成功,1=:待支付,2=:待发货;3:待收货;4:待评价;5:已完成;6:被拆分")
     private Integer status;
 
     /** 0 未退款 1 申请中 2 已退款 */

+ 21 - 0
fs-service/src/main/java/com/fs/live/mapper/LiveOrderMapper.java

@@ -126,6 +126,27 @@ public interface LiveOrderMapper {
     @Select("select * from live_order where order_code=#{orderCode} limit 1")
     LiveOrder selectLiveOrderByOrderCode(@Param("orderCode") String orderCode);
 
+    /**
+     * 查询状态为6(被拆分)的订单
+     */
+    @Select("select * from live_order where status = 6")
+    List<LiveOrder> selectSplitOrders();
+
+    /**
+     * 根据父订单号查询子订单(拆分订单)
+     * 通过用户ID、直播ID、公司ID等关联查找可能的拆分订单
+     */
+    @Select({"<script> " +
+            "select * from live_order " +
+            "where status != 6 " +
+            "and user_id = (select user_id from live_order where order_code = #{parentOrderCode} limit 1) " +
+            "and live_id = (select live_id from live_order where order_code = #{parentOrderCode} limit 1) " +
+            "and create_time >= (select create_time from live_order where order_code = #{parentOrderCode} limit 1) " +
+            "and order_code != #{parentOrderCode} " +
+            "order by create_time asc " +
+            "</script>"})
+    List<LiveOrder> selectChildOrdersByParentOrderCode(@Param("parentOrderCode") String parentOrderCode);
+
     List<LiveOrder> selectFsOutDateOrder();
 
 

+ 7 - 0
fs-service/src/main/java/com/fs/live/mapper/LiveWatchLogMapper.java

@@ -74,4 +74,11 @@ public interface LiveWatchLogMapper extends BaseMapper<LiveWatchLog> {
 
     List<LiveWatchLogListVO> selectLiveWatchLogListInfo(LiveWatchLog liveWatchLog);
 
+    /**
+     * 批量更新直播看课记录
+     * @param liveWatchLogs 需要更新的直播看课记录列表
+     * @return 更新的记录数
+     */
+    int batchUpdateLiveWatchLog(@Param("list") List<LiveWatchLog> liveWatchLogs);
+
 }

+ 1 - 1
fs-service/src/main/java/com/fs/live/service/ILiveAfterSalesService.java

@@ -84,7 +84,7 @@ public interface ILiveAfterSalesService {
 
     R refundMoney(LiveAfterSalesRefundParam param);
 
-    R cancel(LiveAfterSalesCancelParam param);
+    R cancel(LiveAfterSalesCancelParam param)  throws ParseException;
 
     R applyForAfterSales(String userId, LiveAfterSalesParam param);
 

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

@@ -187,6 +187,13 @@ public interface ILiveService
      */
     LiveConfigVo asyncToCacheLiveConfig(Long liveId);
 
+    /**
+     * 清除直播间数据缓存
+     * @param liveId 直播间ID
+     * @return 结果
+     */
+    R clearLiveCache(Long liveId);
+
     List<Live> liveCompanyList(Long companyId);
 
     R subNotifyLive(LiveNotifyParam liveNotifyParam);

+ 14 - 0
fs-service/src/main/java/com/fs/live/service/ILiveWatchLogService.java

@@ -66,4 +66,18 @@ public interface ILiveWatchLogService extends IService<LiveWatchLog>{
      * @return
      */
     List<LiveWatchLogListVO> selectLiveWatchLogListInfo(LiveWatchLog liveWatchLog);
+
+    /**
+     * 批量更新直播看课记录
+     * @param liveWatchLogs 需要更新的直播看课记录列表
+     * @return 更新的记录数
+     */
+    int batchUpdateLiveWatchLog(List<LiveWatchLog> liveWatchLogs);
+
+    /**
+     * 查询直播看课记录并缓存1小时
+     * @param logId 直播看课记录主键
+     * @return 直播看课记录
+     */
+    List<LiveWatchLog> selectLiveWatchLogByLogIdWithCache(LiveWatchLog logId);
 }

+ 2 - 0
fs-service/src/main/java/com/fs/live/service/ILiveWatchUserService.java

@@ -177,4 +177,6 @@ public interface ILiveWatchUserService {
      * @param liveId 直播间ID
      */
     void clearLiveFlagCache(Long liveId);
+
+    List<LiveWatchUser> selectAllWatchUser(LiveWatchUser queryUser);
 }

+ 10 - 23
fs-service/src/main/java/com/fs/live/service/impl/LiveAfterSalesServiceImpl.java

@@ -280,8 +280,8 @@ public class LiveAfterSalesServiceImpl implements ILiveAfterSalesService {
     }
 
     @Override
-    @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
-    public R cancel(LiveAfterSalesCancelParam param) {
+    @Transactional
+    public R cancel(LiveAfterSalesCancelParam param)  throws ParseException{
         LiveAfterSales storeAfterSales = baseMapper.selectLiveAfterSalesById(param.getSalesId());
         if (storeAfterSales == null) {
             throw new CustomException("未查询到售后订单信息");
@@ -322,13 +322,8 @@ public class LiveAfterSalesServiceImpl implements ILiveAfterSalesService {
                     orderMap.setStatus(order.getStatus());
                     liveOrderService.updateLiveOrder(orderMap);
                     liveOrderItemMapper.updateFsStoreOrderCode(order.getOrderId(),orderSn);
-                    try {
-                        //生成新的订单
-                        liveOrderService.createOmsOrder(order.getOrderId());
-                    } catch (Exception e) {
-                        log.error("撤销售后,生成oms订单异常",e);
-                    }
-
+                    //生成新的订单
+                    liveOrderService.createOmsOrder(order.getOrderId());
                 }
             }
         }
@@ -431,7 +426,6 @@ public class LiveAfterSalesServiceImpl implements ILiveAfterSalesService {
         storeAfterSales.setIsDel(0);
         storeAfterSales.setOrderStatus(orderStatus);
         storeAfterSales.setUserId(Long.valueOf(userId));
-        storeAfterSales.setOrderStatus(orderStatus);
         storeAfterSales.setCompanyId(order.getCompanyId());
         storeAfterSales.setCompanyUserId(order.getCompanyUserId());
         liveAfterSalesService.insertLiveAfterSales(storeAfterSales);
@@ -557,6 +551,10 @@ public class LiveAfterSalesServiceImpl implements ILiveAfterSalesService {
         if(ObjectUtil.isNull(liveAfterSales)) {
             throw new IllegalArgumentException("售后单不存在!");
         }
+        if (!liveAfterSales.getStatus().equals(AfterStatusEnum.STATUS_3.getValue())) {
+            throw new CustomException("非法操作");
+        }
+        liveAfterSales.setRefundAmount(param.getRefundAmount());
         return liveOrderService.refundOrderMoney(liveAfterSales.getOrderId(),liveAfterSales);
     }
 
@@ -638,6 +636,7 @@ public class LiveAfterSalesServiceImpl implements ILiveAfterSalesService {
         if (!liveAfterSales.getStatus().equals(AfterStatusEnum.STATUS_0.getValue())) {
             throw new CustomException("非法操作");
         }
+        //仅退款
         if(liveAfterSales.getRefundType().equals(0)){
             //仅退款未发货处理
             if(liveAfterSales.getOrderStatus().equals(OrderInfoEnum.STATUS_1.getValue())){
@@ -645,14 +644,11 @@ public class LiveAfterSalesServiceImpl implements ILiveAfterSalesService {
                 liveAfterSales.setStatus(3);
                 baseMapper.updateLiveAfterSales(liveAfterSales);
             }
-            //待收货直接退款
+            //仅退款待收货处理
             else if(liveAfterSales.getOrderStatus().equals(OrderInfoEnum.STATUS_2.getValue())){
                 liveAfterSales.setStatus(2);
                 baseMapper.updateLiveAfterSales(liveAfterSales);
             //已完成 退货退款
-            } else if(liveAfterSales.getOrderStatus().equals(4)) {
-                liveAfterSales.setStatus(1);
-                baseMapper.updateLiveAfterSales(liveAfterSales);
             }
             LiveAfterSalesLogs salesLogs = new LiveAfterSalesLogs();
             salesLogs.setStoreAfterSalesId(liveAfterSales.getId());
@@ -897,7 +893,6 @@ public class LiveAfterSalesServiceImpl implements ILiveAfterSalesService {
         LiveOrder order = liveOrderService.selectLiveOrderByOrderId(String.valueOf(storeAfterSales.getOrderId()));
         order.setStatus(storeAfterSales.getOrderStatus());
         order.setRefundStatus(OrderInfoEnum.REFUND_STATUS_0.getValue().toString());
-        order.setIsAfterSales(0);
         liveOrderService.updateLiveOrder(order);
         //操作记录
         LiveAfterSalesLogs logs = new LiveAfterSalesLogs();
@@ -921,14 +916,6 @@ public class LiveAfterSalesServiceImpl implements ILiveAfterSalesService {
                 orderMap.setStatus(order.getStatus());
                 liveOrderService.updateLiveOrder(orderMap);
                 liveOrderItemService.updateFsStoreOrderCode(order.getOrderId(), orderSn);
-                //生成新的订单
-                List<LiveOrderPayment> payments = liveOrderPaymentMapper.selectLiveOrderPaymentByPay(5, order.getOrderId());
-                for (LiveOrderPayment payment : payments) {
-                    LiveOrderPayment livePayment = new LiveOrderPayment();
-                    livePayment.setPaymentId(payment.getPaymentId());
-                    livePayment.setBusinessCode(orderSn);
-                    liveOrderPaymentMapper.updateLiveOrderPayment(livePayment);
-                }
                 liveOrderService.createOmsOrder(order.getOrderId());
             }
         }

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

@@ -62,7 +62,7 @@ public class LiveCompletionPointsRecordServiceImpl implements ILiveCompletionPoi
             // 1. 获取直播信息和配置
             Live live = liveService.selectLiveByLiveId(liveId);
             if (live == null) {
-                log.warn("直播间不存在, liveId={}", liveId);
+
                 return;
             }
 
@@ -71,7 +71,7 @@ public class LiveCompletionPointsRecordServiceImpl implements ILiveCompletionPoi
             
             // 检查是否开启完课积分功能
             if (!config.isEnabled()) {
-                log.debug("直播间未开启完课积分功能, liveId={}", liveId);
+
                 return;
             }
             
@@ -80,8 +80,7 @@ public class LiveCompletionPointsRecordServiceImpl implements ILiveCompletionPoi
             int[] pointsConfig = config.getPointsConfig();
             
             if (completionRate == null || pointsConfig == null || pointsConfig.length == 0) {
-                log.warn("完课积分配置不完整, liveId={}, completionRate={}, pointsConfig={}", 
-                        liveId, completionRate, pointsConfig);
+
                 return;
             }
 
@@ -90,19 +89,18 @@ public class LiveCompletionPointsRecordServiceImpl implements ILiveCompletionPoi
             if (actualWatchDuration == null) {
                 // 自动累加直播和回放的观看时长
                 actualWatchDuration = liveWatchUserService.getTotalWatchDuration(liveId, userId);
-                log.debug("自动累计观看时长: liveId={}, userId={}, totalDuration={}",
-                        liveId, userId, actualWatchDuration);
+
             }
 
             if (actualWatchDuration == null || actualWatchDuration <= 0) {
-                log.debug("观看时长为0, liveId={}, userId={}", liveId, userId);
+
                 return;
             }
 
             // 4. 获取视频总时长(秒)
             Long videoDuration = live.getDuration();
             if (videoDuration == null || videoDuration <= 0) {
-                log.warn("直播间视频时长无效, liveId={}, duration={}", liveId, videoDuration);
+
                 return;
             }
 
@@ -118,8 +116,7 @@ public class LiveCompletionPointsRecordServiceImpl implements ILiveCompletionPoi
 
             // 6. 判断是否达到完课标准
             if (watchRate.compareTo(BigDecimal.valueOf(completionRate)) < 0) {
-                log.debug("观看时长未达到完课标准, liveId={}, userId={}, watchDuration={}, videoDuration={}, watchRate={}%, required={}%",
-                        liveId, userId, actualWatchDuration, videoDuration, watchRate, completionRate);
+
                 return;
             }
 
@@ -129,7 +126,7 @@ public class LiveCompletionPointsRecordServiceImpl implements ILiveCompletionPoi
 
             LiveCompletionPointsRecord todayRecord = recordMapper.selectByUserAndDate(liveId, userId, currentDate);
             if (todayRecord != null) {
-                log.debug("今天已有完课记录, liveId={}, userId={}", liveId, userId);
+
                 return;
             }
 
@@ -145,8 +142,7 @@ public class LiveCompletionPointsRecordServiceImpl implements ILiveCompletionPoi
 
                 if (daysBetween == 0) {
                     continuousDays = latestRecord.getContinuousDays();
-                    log.debug("今天已有其他直播间完课记录,继承连续天数, liveId={}, userId={}, continuousDays={}", 
-                            liveId, userId, continuousDays);
+
                 } else if (daysBetween == 1) {
                     // 昨天完课了,连续天数+1
                     continuousDays = latestRecord.getContinuousDays() + 1;
@@ -177,8 +173,7 @@ public class LiveCompletionPointsRecordServiceImpl implements ILiveCompletionPoi
 
             recordMapper.insertRecord(record);
 
-            log.info("创建完课记录成功, liveId={}, userId={}, watchDuration={}, videoDuration={}, watchRate={}%, continuousDays={}, points={}",
-                    liveId, userId, actualWatchDuration, videoDuration, watchRate, continuousDays, points);
+
 
         } catch (Exception e) {
             log.error("检查并创建完课记录失败, liveId={}, userId={}", liveId, userId, e);

+ 11 - 7
fs-service/src/main/java/com/fs/live/service/impl/LiveDataServiceImpl.java

@@ -26,6 +26,8 @@ import com.fs.his.mapper.FsUserMapper;
 import com.fs.hisStore.domain.FsStoreProductScrm;
 import com.fs.hisStore.mapper.FsStoreProductScrmMapper;
 import java.util.stream.Collectors;
+
+import com.github.pagehelper.PageInfo;
 import io.swagger.models.auth.In;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -697,7 +699,13 @@ public class LiveDataServiceImpl implements ILiveDataService {
     @Override
     public R getLiveUserDetailListBySql(Long liveId, Long companyId, Long companyUserId ) {
         List<LiveUserDetailVo> userDetailList = liveDataMapper.selectLiveUserDetailListBySql(liveId, companyId, companyUserId);
-        return R.ok().put("data", userDetailList);
+        // 使用 PageInfo 获取分页信息
+        PageInfo<LiveUserDetailVo> pageInfo = new PageInfo<>(userDetailList);
+        R data = R.ok().put("data", userDetailList);
+        if (pageInfo != null) {
+            data.put("total", pageInfo.getTotal());
+        }
+        return data;
     }
 
     @Override
@@ -1088,6 +1096,8 @@ public class LiveDataServiceImpl implements ILiveDataService {
             return new ArrayList<>();
         }
 
+
+
         // 转换为导出VO列表
         List<LiveUserDetailExportVO> exportList = new ArrayList<>();
         for (LiveUserDetailVo userDetail : userDetailList) {
@@ -1114,12 +1124,6 @@ public class LiveDataServiceImpl implements ILiveDataService {
             exportVO.setCompanyName(userDetail.getCompanyName());
             exportVO.setSalesName(userDetail.getSalesName());
 
-            // 是否完课(根据观看时长判断,假设30分钟以上为完课)
-            if (totalSeconds >= 1800) {
-                exportVO.setIsCompleted("是");
-            } else {
-                exportVO.setIsCompleted("否");
-            }
 
             exportList.add(exportVO);
         }

+ 94 - 75
fs-service/src/main/java/com/fs/live/service/impl/LiveOrderServiceImpl.java

@@ -3,6 +3,7 @@ package com.fs.live.service.impl;
 import java.lang.reflect.Field;
 import java.math.BigDecimal;
 import java.sql.Timestamp;
+import java.text.DecimalFormat;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
 import java.time.LocalDateTime;
@@ -689,7 +690,7 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
     }
 
     @Override
-    @Transactional(rollbackFor = Throwable.class,propagation = Propagation.REQUIRED)
+    @Transactional
     public String payConfirm(Integer type,Long orderId,String payCode,String tradeNo,String bankTransactionId,String bankSerialNo) {
         Object savePoint = TransactionAspectSupport.currentTransactionStatus().createSavepoint();
         try {
@@ -782,21 +783,23 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
             Map<String, Integer> liveFlagWithCache = liveWatchUserService.getLiveFlagWithCache(order.getLiveId());
             if (liveFlagWithCache != null && liveFlagWithCache.containsKey("liveFlag") && 1 == liveFlagWithCache.get("liveFlag")) {
                 try {
-                    LiveWatchLog queryLog = new LiveWatchLog();
-                    queryLog.setLiveId(order.getLiveId());
-                    queryLog.setUserId(Long.valueOf(order.getUserId()));
-                    queryLog.setCompanyId(order.getCompanyId());
-                    queryLog.setCompanyUserId(order.getCompanyUserId());
-
-                    List<LiveWatchLog> logs = liveWatchLogService.selectLiveWatchLogList(queryLog);
-                    if (logs != null && !logs.isEmpty()) {
-                        for (LiveWatchLog log : logs) {
-                            if (log.getLogType() == null || log.getLogType() != 2) {
-                                log.setLiveBuy(1);
-                                liveWatchLogService.updateLiveWatchLog(log);
+                    LiveUserFirstEntry liveUserFirstEntry = liveUserFirstEntryService.selectEntityByLiveIdUserId(order.getLiveId(), Long.parseLong(order.getUserId()));
+                    if (liveUserFirstEntry != null && liveUserFirstEntry.getExternalContactId()!=null && liveUserFirstEntry.getExternalContactId() > 0 &&  liveUserFirstEntry.getQwUserId()!=null && liveUserFirstEntry.getQwUserId() > 0) {
+                        LiveWatchLog queryLog = new LiveWatchLog();
+                        queryLog.setLiveId(order.getLiveId());
+                        queryLog.setQwUserId(String.valueOf(liveUserFirstEntry.getQwUserId()));
+                        queryLog.setExternalContactId(liveUserFirstEntry.getExternalContactId());
+                        List<LiveWatchLog> logs = liveWatchLogService.selectLiveWatchLogList(queryLog);
+                        if (logs != null && !logs.isEmpty()) {
+                            for (LiveWatchLog log : logs) {
+                                if (log.getLogType() == null || log.getLogType() != 2) {
+                                    log.setLiveBuy(1);
+                                    liveWatchLogService.updateLiveWatchLog(log);
+                                }
                             }
                         }
                     }
+
                 } catch (Exception e) {
                     log.error("更新 updateLiveWatchLog LiveWatchLog logType 异常(连接时):liveId={}, userId={}, error={}",
                             order.getLiveId(), order.getUserId(), e.getMessage(), e);
@@ -1389,6 +1392,7 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
     @Override
     @Transactional(rollbackFor = Exception.class)
     public R refundOrderMoney(Long orderId, LiveAfterSales liveAfterSales) {
+        BigDecimal refundAmount = liveAfterSales.getRefundAmount();
         IErpOrderService erpOrderService = getErpService();
         FsErpConfig erpConfig = configUtil.generateStructConfigByKey("his.config", FsErpConfig.class);
         LiveOrder order = baseMapper.selectLiveOrderByOrderId(String.valueOf(orderId));
@@ -1407,43 +1411,49 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
                 && !CloudHostUtils.hasCloudHostName("康年堂")) {
             return R.error("暂未推送至erp,请稍后再试!");
         }
-        liveAfterSales.setRefundAmount(order.getPayPrice());
+        if (Objects.isNull(refundAmount)) {
+            throw new CustomException("退款金额不能为空");
+        }
+        if (order.getPayMoney().compareTo(refundAmount) < 0) {
+            throw new CustomException("退款金额不能大于支付金额");
+        }
+        liveAfterSales.setRefundAmount(liveAfterSales.getRefundAmount());
         liveAfterSales.setStatus(4);
         liveAfterSales.setSalesStatus(3);
         liveAfterSalesService.updateLiveAfterSales(liveAfterSales);
-        if (StringUtils.isNotEmpty(order.getExtendOrderId())) {
-            ErpRefundUpdateRequest request = new ErpRefundUpdateRequest();
-            request.setTid(order.getOrderCode());
-            request.setOid(order.getOrderCode());
-            request.setRefund_state(1);
-            request.setOrderStatus(order.getStatus());
-            if (ObjectUtils.equals(order.getStatus(), 2)) {
-                LiveAfterSalesParam param = new LiveAfterSalesParam();
-                param.setOrderCode(order.getOrderCode());
-                param.setRefundAmount(order.getPayMoney());
-                param.setServiceType(1);
-                param.setReasons("后台手动退款流程");
-                param.setExplainImg(null);
-                List<FsStoreAfterSalesProductParam> productParams = new ArrayList<>();
-                List <LiveOrderItem> items = liveOrderItemMapper.selectLiveOrderItemByOrderId(order.getOrderId());
-                for (LiveOrderItem item : items){
-                    FsStoreAfterSalesProductParam param1 = new FsStoreAfterSalesProductParam();
-                    param1.setProductId(item.getProductId());
-                    param1.setNum(Math.toIntExact(item.getNum()));
-                    productParams.add(param1);
-                }
-                return liveAfterSalesService.applyForAfterSales(order.getUserId(), param);
-            } else {
-                ErpOrderQueryRequert queryRequest = new ErpOrderQueryRequert();
-                queryRequest.setCode(order.getExtendOrderId());
-                ErpOrderQueryResponse response = erpOrderService.getLiveOrder(queryRequest);
-                if (response.getOrders() != null && response.getOrders().size() > 0) {
-                    if (response.getOrders().get(0).getCancle() != null && !response.getOrders().get(0).getCancle()) {
-                        jSTOrderService.refundUpdateLive(request);
-                    }
-                }
-            }
-        }
+//        if (StringUtils.isNotEmpty(order.getExtendOrderId())) {
+//            ErpRefundUpdateRequest request = new ErpRefundUpdateRequest();
+//            request.setTid(order.getOrderCode());
+//            request.setOid(order.getOrderCode());
+//            request.setRefund_state(1);
+//            request.setOrderStatus(order.getStatus());
+//            if (ObjectUtils.equals(order.getStatus(), 2)) {
+//                LiveAfterSalesParam param = new LiveAfterSalesParam();
+//                param.setOrderCode(order.getOrderCode());
+//                param.setRefundAmount(order.getPayMoney());
+//                param.setServiceType(1);
+//                param.setReasons("后台手动退款流程");
+//                param.setExplainImg(null);
+//                List<FsStoreAfterSalesProductParam> productParams = new ArrayList<>();
+//                List <LiveOrderItem> items = liveOrderItemMapper.selectLiveOrderItemByOrderId(order.getOrderId());
+//                for (LiveOrderItem item : items){
+//                    FsStoreAfterSalesProductParam param1 = new FsStoreAfterSalesProductParam();
+//                    param1.setProductId(item.getProductId());
+//                    param1.setNum(Math.toIntExact(item.getNum()));
+//                    productParams.add(param1);
+//                }
+//                return liveAfterSalesService.applyForAfterSales(order.getUserId(), param);
+//            } else {
+//                ErpOrderQueryRequert queryRequest = new ErpOrderQueryRequert();
+//                queryRequest.setCode(order.getExtendOrderId());
+//                ErpOrderQueryResponse response = erpOrderService.getLiveOrder(queryRequest);
+//                if (response.getOrders() != null && response.getOrders().size() > 0) {
+//                    if (response.getOrders().get(0).getCancle() != null && !response.getOrders().get(0).getCancle()) {
+//                        jSTOrderService.refundUpdateLive(request);
+//                    }
+//                }
+//            }
+//        }
         order.setStatus(OrderInfoEnum.STATUS_NE2.getValue());
         order.setRefundMoney(order.getPayMoney());
         order.setRefundStatus(String.valueOf(OrderInfoEnum.REFUND_STATUS_2.getValue()));
@@ -1494,7 +1504,7 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
                         refundRequest.setOutTradeNo("live-" + payment.getPayCode());
                         refundRequest.setOutRefundNo("live-" + payment.getPayCode());
                         refundRequest.setTotalFee(WxPayUnifiedOrderRequest.yuanToFen(payment.getPayMoney().toString()));
-                        refundRequest.setRefundFee(WxPayUnifiedOrderRequest.yuanToFen(payment.getPayMoney().toString()));
+                        refundRequest.setRefundFee(WxPayUnifiedOrderRequest.yuanToFen(refundAmount.toString()));
                         try {
                             WxPayRefundResult refundResult = wxPayService.refund(refundRequest);
                             WxPayRefundQueryResult refundQueryResult = wxPayService.refundQuery("", refundResult.getOutTradeNo(), refundResult.getOutRefundNo(), refundResult.getRefundId());
@@ -1503,14 +1513,12 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
                                 paymentMap.setPaymentId(payment.getPaymentId());
                                 paymentMap.setStatus(-1);
                                 paymentMap.setRefundTime(DateUtils.getNowDate());
-                                paymentMap.setRefundMoney(payment.getPayMoney());
+                                paymentMap.setRefundMoney(refundAmount);
                                 liveOrderPaymentMapper.updateLiveOrderPayment(paymentMap);
                             } else {
-                                TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
                                 return R.error("退款请求失败" + (refundQueryResult != null ? refundQueryResult.getErrCodeDes() : ""));
                             }
                         } catch (WxPayException e) {
-                            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
                             return R.error("退款请求失败" + e.getErrCodeDes());
                         }
                     } else if (payment.getPayMode() != null && "hf".equals(payment.getPayMode())) {
@@ -1518,25 +1526,26 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
                         FsHfpayConfigMapper fsHfpayConfigMapper = SpringUtils.getBean(FsHfpayConfigMapper.class);
                         if (payment.getAppId() != null) {
                             FsHfpayConfig fsHfpayConfig = fsHfpayConfigMapper.selectByAppId(payment.getAppId());
-                            if (fsHfpayConfig != null) {
+                            if (fsHfpayConfig == null){
+                                huifuId = fsPayConfig.getHuifuId();
+                            }else {
                                 huifuId = fsHfpayConfig.getHuifuId();
                             }
                         } else {
                             CloudHostProper cloudHostProper = SpringUtils.getBean(CloudHostProper.class);
                             if ("益善缘".equals(cloudHostProper.getCompanyName())) {
                                 FsHfpayConfig fsPayConfig2 = fsHfpayConfigMapper.selectByAppId("wx0d1a3dd485268521");
-                                if (fsPayConfig2 != null) {
-                                    huifuId = fsPayConfig2.getHuifuId();
-                                }
+                                huifuId = fsPayConfig2.getHuifuId();
                             } else {
                                 huifuId = fsPayConfig.getHuifuId();
                             }
                         }
 
                         V2TradePaymentScanpayRefundRequest request = new V2TradePaymentScanpayRefundRequest();
+                        DecimalFormat df = new DecimalFormat("0.00");
                         request.setOrgHfSeqId(payment.getTradeNo());
                         request.setHuifuId(huifuId);
-                        request.setOrdAmt(payment.getPayMoney().toString());
+                        request.setOrdAmt(df.format(refundAmount));
                         request.setOrgReqDate(new SimpleDateFormat("yyyyMMdd").format(payment.getCreateTime()));
                         request.setReqSeqId("refund-" + payment.getPayCode());
                         Map<String, Object> extendInfoMap = new HashMap<>();
@@ -1547,7 +1556,7 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
                         log.info("退款:" + refund);
                         if (refund != null && ("00000000".equals(refund.getResp_code()) || "00000100".equals(refund.getResp_code()))
                                 && ("S".equals(refund.getTrans_stat()) || "P".equals(refund.getTrans_stat()))) {
-                            payment.setRefundMoney(payment.getPayMoney());
+                            payment.setRefundMoney(refundAmount);
                             payment.setStatus(-1);
                             payment.setRefundTime(new Date());
                             liveOrderPaymentMapper.updateLiveOrderPayment(payment);
@@ -3122,6 +3131,17 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
                     if (amount != null){
                         payMoney=amount;
                     }
+                    //运费
+                    BigDecimal payPostage = order.getPayPostage();
+                    if (payPostage == null || payPostage.compareTo(BigDecimal.ZERO) <= 0){
+                        payPostage = storeConfig.getPayPostage();
+                        if (payPostage == null){
+                            payPostage = BigDecimal.ZERO;
+                        }
+                        order.setPayPrice(order.getPayPrice().add(payPostage));
+                    }
+                    order.setPayPostage(payPostage);
+                    payMoney = payMoney.add(payPostage);
                     order.setPayMoney(payMoney);
                     order.setPayDelivery(order.getPayPrice().subtract(payMoney) );
 //                    order.setPayMoney(BigDecimal.ZERO);
@@ -3135,8 +3155,8 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
             String payCode = OrderCodeUtils.getOrderSn();
 //            order.setOrderCode(orderCode);
 //            if(order.getPayType().equals("1")||order.getPayType().equals("2")){
-            if((order.getPayType().equals("1")||order.getPayType().equals("2")||order.getPayType().equals("3")) && order.getPayMoney().compareTo(new BigDecimal(0))>0){
-                LiveOrderPayment storePayment=new LiveOrderPayment();
+            if ((order.getPayType().equals("1") || order.getPayType().equals("2") || order.getPayType().equals("3")) && order.getPayMoney().compareTo(new BigDecimal(0)) > 0) {
+                LiveOrderPayment storePayment = new LiveOrderPayment();
                 storePayment.setCompanyId(order.getCompanyId());
                 storePayment.setCompanyUserId(order.getCompanyUserId());
                 storePayment.setPayMode(fsPayConfig.getType());
@@ -3153,35 +3173,35 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
                 storePayment.setAppId(param.getAppId());
                 liveOrderPaymentMapper.insertLiveOrderPayment(storePayment);
 
-                if (fsPayConfig.getType().equals("hf")){
+                if (fsPayConfig.getType().equals("hf")) {
                     HuiFuCreateOrder o = new HuiFuCreateOrder();
                     o.setTradeType("T_MINIAPP");
                     o.setOpenid(storePayment.getOpenId());
-                    o.setReqSeqId("live-"+storePayment.getPayCode());
+                    o.setReqSeqId("live-" + storePayment.getPayCode());
                     o.setTransAmt(storePayment.getPayMoney().toString());
                     o.setGoodsDesc("直播订单支付");
                     if (StringUtils.isNotBlank(param.getAppId())) {
                         o.setAppId(param.getAppId());
                     }
                     HuifuCreateOrderResult result = huiFuService.createOrder(o);
-                    if(result.getResp_code()!=null&&(result.getResp_code().equals("00000000")||result.getResp_code().equals("00000100"))){
-                        LiveOrderPayment mt=new LiveOrderPayment();
+                    if (result.getResp_code() != null && (result.getResp_code().equals("00000000") || result.getResp_code().equals("00000100"))) {
+                        LiveOrderPayment mt = new LiveOrderPayment();
                         mt.setPaymentId(storePayment.getPaymentId());
                         mt.setTradeNo(result.getHf_seq_id());
                         mt.setAppId(param.getAppId());
                         mt.setBusinessCode(order.getOrderCode());
                         liveOrderPaymentMapper.updateLiveOrderPayment(mt);
-                        redisCache.setCacheObject("isPaying:"+order.getOrderId(),order.getOrderId().toString(),1, TimeUnit.MINUTES);
+                        redisCache.setCacheObject("isPaying:" + order.getOrderId(), order.getOrderId().toString(), 1, TimeUnit.MINUTES);
                         log.info("汇付支付");
-                        Map<String, Object> resultMap = JSON.parseObject(result.getPay_info(), new TypeReference<Map<String, Object>>() {});
+                        Map<String, Object> resultMap = JSON.parseObject(result.getPay_info(), new TypeReference<Map<String, Object>>() {
+                        });
                         String s = (String) resultMap.get("package");
-                        resultMap.put("packageValue",s);
-                        return R.ok().put("payType",param.getPayType()).put("result",resultMap);
-                    }
-                    else{
+                        resultMap.put("packageValue", s);
+                        return R.ok().put("payType", param.getPayType()).put("result", resultMap);
+                    } else {
                         return R.error(result.getResp_desc());
                     }
-                }else  if (fsPayConfig.getType().equals("wx")){
+                } else if (fsPayConfig.getType().equals("wx")) {
                     WxPayConfig payConfig = new WxPayConfig();
                     payConfig.setAppId(fsPayConfig.getAppId());
                     payConfig.setMchId(fsPayConfig.getWxMchId());
@@ -3202,7 +3222,7 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
                     //调用统一下单接口,获取"预支付交易会话标识"
                     try {
                         WxPayMpOrderResult orderResult = wxPayService.createOrder(orderRequest);
-                        return R.ok().put("result", orderResult).put("type", "wx").put("isPay", 0).put("payType",param.getPayType());
+                        return R.ok().put("result", orderResult).put("type", "wx").put("isPay", 0).put("payType", param.getPayType());
                     } catch (WxPayException e) {
                         e.printStackTrace();
                         throw new CustomException("支付失败" + e.getMessage());
@@ -3210,10 +3230,10 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
                 }
             }
 //            else if(order.getPayType().equals("3")){
-            else if(order.getPayType().equals("3") && order.getPayMoney().compareTo(new BigDecimal(0))<=0){
+            else if (order.getPayType().equals("3") && order.getPayMoney().compareTo(new BigDecimal(0)) <= 0) {
                 //货到付款
-                this.payConfirm(2,order.getOrderId(),null,null,null,null);
-                return R.ok().put("payType",param.getPayType());
+                this.payConfirm(2, order.getOrderId(), null, null, null, null);
+                return R.ok().put("payType", param.getPayType());
             }
             return R.error();
         }
@@ -3665,7 +3685,6 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
         liveOrder.setStatus(OrderInfoEnum.STATUS_0.getValue());
         liveOrder.setPayType("1");
         liveOrder.setTotalPrice(payPrice);
-        liveOrder.setPayMoney(BigDecimal.ZERO);
         liveOrder.setPayPrice(payPrice.subtract(liveOrder.getDiscountMoney()));
         try {
             if (baseMapper.insertLiveOrder(liveOrder) > 0) {

+ 37 - 17
fs-service/src/main/java/com/fs/live/service/impl/LiveServiceImpl.java

@@ -320,7 +320,7 @@ public class LiveServiceImpl implements ILiveService
     @Override
     public R subNotifyLive(LiveNotifyParam param) {
         LiveMiniprogramSubNotifyTask notifyTask = new LiveMiniprogramSubNotifyTask();
-        notifyTask.setPage("/pages_course/living.html?liveId=" + param.getLiveId());
+        notifyTask.setPage("pages_course/living?liveId=" + param.getLiveId());
         notifyTask.setTaskName("直播间预约提醒");
         notifyTask.setTemplateId(param.getTemplateId());
         Long userId = param.getUserId();
@@ -341,7 +341,6 @@ public class LiveServiceImpl implements ILiveService
         }
 
         notifyTask.setTouser(maOpenId);
-        notifyTask.setPage(String.valueOf(1));
 
         notifyTask.setCreateTime(LocalDateTime.now());
         // 状态等待执行
@@ -940,9 +939,18 @@ public class LiveServiceImpl implements ILiveService
         }
 
         SysConfig sysConfig = sysConfigService.selectConfigByConfigKey("living.config");
+        if (sysConfig == null || StringUtils.isEmpty(sysConfig.getConfigValue())) {
+            log.error("直播配置不存在或为空, liveId: {}", live.getLiveId());
+            return R.error("直播配置不存在,请联系管理员配置");
+        }
         Map<String, String> livingConfigMap = JSON.parseObject(sysConfig.getConfigValue(), Map.class);
         if (livingConfigMap == null || livingConfigMap.isEmpty()) {
-            return R.error("缺失直播配置");
+            log.error("直播配置解析失败, liveId: {}, configValue: {}", live.getLiveId(), sysConfig.getConfigValue());
+            return R.error("直播配置解析失败");
+        }
+        if (StringUtils.isEmpty(livingConfigMap.get("domain")) || StringUtils.isEmpty(livingConfigMap.get("app"))) {
+            log.error("直播配置缺少必要参数, liveId: {}, configMap: {}", live.getLiveId(), livingConfigMap);
+            return R.error("直播配置缺少必要参数(domain/app)");
         }
         String rtmpPushUrl = generateRtmpPushUrl(livingConfigMap.get("domain"), livingConfigMap.get("app"), exist.getLiveId().toString());
         String hlvPlayUrl = generateHlvPlayUrl(livingConfigMap.get("http"), livingConfigMap.get("app"), exist.getLiveId().toString());
@@ -1215,9 +1223,9 @@ public class LiveServiceImpl implements ILiveService
         Map<Long, LiveAutoTask> goodsTaskMap = goodsTasks.stream()
                 .collect(Collectors.toMap(task -> parseIdFromContent(task.getContent(), "goodsId"),
                         Function.identity(), (existing, replacement) -> existing));
-        Map<Long, LiveAutoTask> shelfTaskMap = shelfTasks.stream()
-                .collect(Collectors.toMap(task -> parseIdFromContent(task.getContent(), "goodsId"),
-                        Function.identity(), (existing, replacement) -> existing));
+        // 使用 groupingBy 支持同一个商品有多个上下架任务(不同时间点的上架/下架操作)
+        Map<Long, List<LiveAutoTask>> shelfTaskMap = shelfTasks.stream()
+                .collect(Collectors.groupingBy(task -> parseIdFromContent(task.getContent(), "goodsId")));
 
         LiveGoods queryParam = new LiveGoods();
         queryParam.setLiveId(existLiveId);
@@ -1257,14 +1265,16 @@ public class LiveServiceImpl implements ILiveService
                     liveAutoTaskService.directInsertLiveAutoTask(newTask);
                 }
 
-                // 复制上下架任务(taskType=6)
-                LiveAutoTask shelfTask = shelfTaskMap.get(liveGoods.getGoodsId());
-                if (shelfTask != null) {
-                    JSONObject contentJson = JSON.parseObject(shelfTask.getContent());
-                    contentJson.put("goodsId", newGoods.getGoodsId());
-                    LiveAutoTask newTask = createAutoTaskEntity(shelfTask, newLiveId, now,
-                            contentJson.toJSONString());
-                    liveAutoTaskService.directInsertLiveAutoTask(newTask);
+                // 复制上下架任务(taskType=6)- 支持同一个商品有多个上下架任务
+                List<LiveAutoTask> shelfTaskList = shelfTaskMap.get(liveGoods.getGoodsId());
+                if (shelfTaskList != null && !shelfTaskList.isEmpty()) {
+                    for (LiveAutoTask shelfTask : shelfTaskList) {
+                        JSONObject contentJson = JSON.parseObject(shelfTask.getContent());
+                        contentJson.put("goodsId", newGoods.getGoodsId());
+                        LiveAutoTask newTask = createAutoTaskEntity(shelfTask, newLiveId, now,
+                                contentJson.toJSONString());
+                        liveAutoTaskService.directInsertLiveAutoTask(newTask);
+                    }
                 }
             }
         }
@@ -1339,15 +1349,25 @@ public class LiveServiceImpl implements ILiveService
         return jsonObject.getLong(key);
     }
 
+
     /**
-     * 清除直播间数据缓存
+     * 清除直播间数据缓存(公开方法)
      * @param liveId 直播间ID
+     * @return 结果
      */
-    private void clearLiveCache(Long liveId) {
-        if (liveId != null) {
+    @Override
+    public R clearLiveCache(Long liveId) {
+        if (liveId == null) {
+            return R.error("直播间ID不能为空");
+        }
+        try {
             String cacheKey = String.format(LiveKeysConstant.LIVE_DATA_CACHE, liveId);
             redisCache.deleteObject(cacheKey);
             log.debug("清除直播间缓存: liveId={}", liveId);
+            return R.ok("缓存清理成功");
+        } catch (Exception e) {
+            log.error("清除直播间缓存失败: liveId={}", liveId, e);
+            return R.error("缓存清理失败: " + e.getMessage());
         }
     }
 

+ 17 - 8
fs-service/src/main/java/com/fs/live/service/impl/LiveVideoServiceImpl.java

@@ -3,6 +3,8 @@ package com.fs.live.service.impl;
 import com.fs.common.core.redis.RedisCache;
 import com.fs.common.utils.DateUtils;
 import com.fs.common.utils.StringUtils;
+import com.fs.config.cloud.CloudHostProper;
+import com.fs.hisStore.enums.LiveEnum;
 import com.fs.live.domain.LiveVideo;
 import com.fs.live.mapper.LiveVideoMapper;
 import com.fs.live.service.ILiveVideoService;
@@ -31,10 +33,11 @@ public class LiveVideoServiceImpl implements ILiveVideoService
 {
     @Autowired
     private LiveVideoMapper liveVideoMapper;
-    
+
     @Autowired
     private RedisCache redisCache;
-
+    @Autowired
+    private CloudHostProper cloudHostProper;
     /**
      * 查询直播视频
      *
@@ -81,8 +84,14 @@ public class LiveVideoServiceImpl implements ILiveVideoService
     @Override
     public int insertLiveVideo(LiveVideo liveVideo)
     {
-        // 直播ID为-1,则新增 直播视频库
-        liveVideo.setFinishStatus(0);
+//        if (LiveEnum.contains(cloudHostProper.getCompanyName())) {
+//            liveVideo.setVideoUrl(liveVideo.getLineOne());
+//            liveVideo.setFinishStatus(1);
+//        }else {
+            // 直播ID为-1,则新增 直播视频库
+            liveVideo.setFinishStatus(0);
+//        }
+
         if (liveVideo.getLiveId() == -1) {
             liveVideo.setCreateTime(DateUtils.getNowDate());
             return liveVideoMapper.insertLiveVideo(liveVideo);
@@ -189,21 +198,21 @@ public class LiveVideoServiceImpl implements ILiveVideoService
     public List<LiveVideo> listByLiveIdWithCache(Long liveId, Integer type) {
         // Redis缓存键
         String cacheKey = "live:video:list:" + liveId + ":" + type;
-        
+
         // 先从缓存中获取
         List<LiveVideo> cachedVideos = redisCache.getCacheObject(cacheKey);
         if (cachedVideos != null && !cachedVideos.isEmpty()) {
             return cachedVideos;
         }
-        
+
         // 缓存未命中,从数据库查询
         List<LiveVideo> videos = liveVideoMapper.selectByIdAndType(liveId, type);
-        
+
         // 将查询结果存入缓存,缓存时间1小时
         if (videos != null) {
             redisCache.setCacheObject(cacheKey, videos, 1, TimeUnit.HOURS);
         }
-        
+
         return videos;
     }
 

+ 66 - 0
fs-service/src/main/java/com/fs/live/service/impl/LiveWatchLogServiceImpl.java

@@ -1,7 +1,11 @@
 package com.fs.live.service.impl;
 
+import java.util.Date;
 import java.util.List;
+import java.util.concurrent.TimeUnit;
+import com.fs.common.core.redis.RedisCache;
 import com.fs.common.utils.DateUtils;
+import com.fs.common.utils.spring.SpringUtils;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.fs.live.vo.LiveWatchLogListVO;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -21,6 +25,10 @@ public class LiveWatchLogServiceImpl extends ServiceImpl<LiveWatchLogMapper, Liv
 
     @Autowired
     private LiveWatchLogMapper liveWatchLogMapper;
+    
+    private final RedisCache redisCache = SpringUtils.getBean(RedisCache.class);
+    
+    private static final String LIVE_WATCH_LOG_CACHE_KEY = "live:watch:log:cache:%s"; // logId
 
     /**
      * 查询直播看课记录
@@ -101,4 +109,62 @@ public class LiveWatchLogServiceImpl extends ServiceImpl<LiveWatchLogMapper, Liv
     {
         return baseMapper.deleteLiveWatchLogByLogId(logId);
     }
+
+    /**
+     * 批量更新直播看课记录
+     * @param liveWatchLogs 需要更新的直播看课记录列表
+     * @return 更新的记录数
+     */
+    @Override
+    public int batchUpdateLiveWatchLog(List<LiveWatchLog> liveWatchLogs) {
+        if (liveWatchLogs == null || liveWatchLogs.isEmpty()) {
+            return 0;
+        }
+        Date now = DateUtils.getNowDate();
+        // 设置统一的更新时间
+        for (LiveWatchLog log : liveWatchLogs) {
+            if (log.getUpdateTime() == null) {
+                log.setUpdateTime(now);
+            }
+        }
+        int result = baseMapper.batchUpdateLiveWatchLog(liveWatchLogs);
+        
+        // 更新后清除相关缓存
+        for (LiveWatchLog log : liveWatchLogs) {
+            if (log.getLogId() != null) {
+                String cacheKey = String.format(LIVE_WATCH_LOG_CACHE_KEY, log.getLogId());
+                redisCache.deleteObject(cacheKey);
+            }
+        }
+        
+        return result;
+    }
+
+    /**
+     * 查询直播看课记录并缓存1小时
+     * @param logId 直播看课记录主键
+     * @return 直播看课记录
+     */
+    @Override
+    public List<LiveWatchLog> selectLiveWatchLogByLogIdWithCache(LiveWatchLog logId) {
+        if (logId == null) {
+            return null;
+        }
+        
+        // 先从缓存中获取
+        String cacheKey = String.format(LIVE_WATCH_LOG_CACHE_KEY, logId);
+        List<LiveWatchLog> cachedLog = redisCache.getCacheObject(cacheKey);
+        if (cachedLog != null) {
+            return cachedLog;
+        }
+        
+        // 缓存中没有,从数据库查询
+        List<LiveWatchLog> log = baseMapper.selectLiveWatchLogList(logId);
+        if (log != null) {
+            // 将查询结果缓存1小时
+            redisCache.setCacheObject(cacheKey, log, 1, TimeUnit.HOURS);
+        }
+        
+        return log;
+    }
 }

+ 8 - 1
fs-service/src/main/java/com/fs/live/service/impl/LiveWatchUserServiceImpl.java

@@ -220,6 +220,11 @@ public class LiveWatchUserServiceImpl implements ILiveWatchUserService {
         redisCache.deleteObject(cacheKey);
     }
 
+    @Override
+    public List<LiveWatchUser> selectAllWatchUser(LiveWatchUser queryUser) {
+        return baseMapper.selectLiveWatchUserList(queryUser);
+    }
+
     /**
      * 批量删除直播间观看用户
      *
@@ -854,9 +859,10 @@ public class LiveWatchUserServiceImpl implements ILiveWatchUserService {
     @Override
     @Async
     public void qwTagMarkByLiveWatchLog(Long liveId) {
+        log.info("处理直播间打标签: liveId={}", liveId);
         //查询直播间的标签配置
         List<LiveTagItemVO> liveTagConfig = liveTagConfigMapper.getLiveTagListByliveId(liveId);
-
+        log.info("处理直播间打标签: liveTagConfig={}", liveTagConfig);
         /**
          * 8	回放已下单
          * 7	直播已下单
@@ -921,6 +927,7 @@ public class LiveWatchUserServiceImpl implements ILiveWatchUserService {
                 default:
                     break;
             }
+            addItem.setTags(tags);
             if (null != liveLog.getLiveBuy() && liveLog.getLiveBuy().equals(1)) {
                 liveTagItemVO = liveTagMp.get(7);
                 if (null != liveTagItemVO) {

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

@@ -49,7 +49,7 @@ public class LiveUserDetailExportVO {
     private String salesName;
 
     /** 是否完课 */
-    @Excel(name = "是否完课")
+//    @Excel(name = "是否完课")
     private String isCompleted;
 
 }

+ 20 - 11
fs-service/src/main/java/com/fs/live/vo/MergedOrderExportVO.java

@@ -19,6 +19,10 @@ public class MergedOrderExportVO implements Serializable
 {
     private static final long serialVersionUID = 1L;
 
+    /** 订单类型 */
+    @Excel(name = "订单类型")
+    private String orderTypeName;
+
     /** 订单号 */
     @Excel(name = "订单号")
     private String orderCode;
@@ -48,13 +52,25 @@ public class MergedOrderExportVO implements Serializable
     private Integer totalNum;
 
     /** 产品价格 */
-    @Excel(name = "产品价格")
+//    @Excel(name = "产品价格")
     private BigDecimal price;
 
     /** 成本价 */
     @Excel(name = "成本价")
     private BigDecimal cost;
 
+    /** 商品金额 */
+    @Excel(name = "商品金额")
+    private BigDecimal totalPrice;
+
+    /** 应付金额 */
+    @Excel(name = "应付金额")
+    private BigDecimal payPrice;
+
+    /** 实付金额 */
+    @Excel(name = "实付金额")
+    private BigDecimal payMoney;
+
     /** 结算价 */
     @Excel(name = "结算价")
     private BigDecimal FPrice;
@@ -137,18 +153,11 @@ public class MergedOrderExportVO implements Serializable
     /** 银行交易流水号 */
     @Excel(name = "银行交易流水号")
     private String bankTransactionId;
+    /** 汇付商户订单号 */
+    @Excel(name = "汇付商户订单号")
+    private String hfshh;
 
-    /** 商品金额 */
-    @Excel(name = "商品金额")
-    private BigDecimal totalPrice;
 
-    /** 应付金额 */
-    @Excel(name = "应付金额")
-    private BigDecimal payPrice;
-
-    /** 实付金额 */
-    @Excel(name = "实付金额")
-    private BigDecimal payMoney;
 
 
 }

+ 2 - 0
fs-service/src/main/java/com/fs/live/vo/MergedOrderVO.java

@@ -109,6 +109,8 @@ public class MergedOrderVO implements Serializable
 
     /** 订单类型:1-销售订单,2-商城订单,3-直播订单 */
     private Integer orderType;
+//    汇付商户订单号
+    private String hfshh;
 
     /** 订单类型名称 */
     private String orderTypeName;

+ 18 - 0
fs-service/src/main/java/com/fs/newAdv/controller/AdvMiniConfigController.java

@@ -0,0 +1,18 @@
+package com.fs.newAdv.controller;
+
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * <p>
+ * 广告微信小程序配置 前端控制器
+ * </p>
+ *
+ * @author zhangqin
+ * @since 2025-12-19
+ */
+@RestController
+@RequestMapping("/advMiniConfig")
+public class AdvMiniConfigController {
+
+}

+ 58 - 0
fs-service/src/main/java/com/fs/newAdv/domain/AdvMiniConfig.java

@@ -0,0 +1,58 @@
+package com.fs.newAdv.domain;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.extension.activerecord.Model;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+/**
+ * <p>
+ * 广告微信小程序配置
+ * </p>
+ *
+ * @author zhangqin
+ * @since 2025-12-19
+ */
+@Getter
+@Setter
+@TableName("adv_mini_config")
+@ApiModel(value = "AdvMiniConfig对象", description = "广告微信小程序配置")
+public class AdvMiniConfig extends Model<AdvMiniConfig> {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId("id")
+    private Integer id;
+
+    @TableField("app_id")
+    private String appId;
+
+    @TableField("app_secret")
+    private String appSecret;
+
+    @TableField("access_token")
+    private String accessToken;
+
+    @ApiModelProperty("小程序状态1启用 0停用")
+    @TableField("status")
+    private Integer status;
+
+    @ApiModelProperty("过期时间")
+    @TableField("expires_in")
+    private LocalDateTime expiresIn;
+
+    @TableField("create_time")
+    private LocalDateTime createTime;
+
+    @Override
+    public Serializable pkVal() {
+        return this.id;
+    }
+}

+ 2 - 2
fs-service/src/main/java/com/fs/newAdv/domain/PromotionAccount.java

@@ -71,9 +71,9 @@ public class PromotionAccount implements Serializable {
     private Integer apiSwitch;
 
     /**
-     * 推广账户密码(加密存储)
+     * 推拓展字段
      */
-    private String accountPassword;
+    private String extendedField;
     /**
      * 1线上 2线下
      */

+ 2 - 2
fs-service/src/main/java/com/fs/newAdv/integration/client/advertiser/BaiduApiClient.java

@@ -123,7 +123,7 @@ public class BaiduApiClient extends AbstractApiClient implements IAccessTokenCli
         Map<String, Object> map = new HashMap<>();
         Map<String, Object> header = new HashMap<>();
         header.put("accessToken", account.getAccessToken());
-        header.put("userName", account.getAccountShortName());
+        header.put("userName", account.getExtendedField());
         map.put("header", header);
         Map<String, Object> body = new HashMap<>();
         // 基础信息
@@ -197,7 +197,7 @@ public class BaiduApiClient extends AbstractApiClient implements IAccessTokenCli
             requestMap.put("secretKey", codeVo.getAppSecret());
             requestMap.put("authCode", codeVo.getAuthCode());
             requestMap.put("grantType", "access_token");
-            requestMap.put("userId", codeVo.getUserId());
+            requestMap.put("userId", codeVo.getAdAccountId());
             // 发送HTTP请求
             return HttpRequest.post(ACCESS_TOKEN_URL)
                     .header("Content-Type", "application/json")

+ 44 - 6
fs-service/src/main/java/com/fs/newAdv/integration/client/advertiser/TencentApiClient.java

@@ -1,6 +1,8 @@
 package com.fs.newAdv.integration.client.advertiser;
 
 import cn.hutool.http.HttpRequest;
+import cn.hutool.http.HttpResponse;
+import cn.hutool.json.JSONArray;
 import cn.hutool.json.JSONObject;
 import cn.hutool.json.JSONUtil;
 import com.fs.common.constant.SystemConstant;
@@ -15,11 +17,9 @@ import com.fs.newAdv.vo.AccessTokenVo;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Component;
 
+import java.math.BigDecimal;
 import java.time.LocalDateTime;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
 
 /**
  * 腾讯广点通API客户端
@@ -34,6 +34,7 @@ public class TencentApiClient extends AbstractApiClient implements IAccessTokenC
 
     private static final String CONVERSION_API_URL = "https://api.e.qq.com/user_actions/add";
     private static final String TOKEN_API_URL = "https://api.e.qq.com/oauth/token";
+    private static final String REPORTS_API_URL = "https://api.e.qq.com/v3.0/daily_reports/get?access_token=%s&timestamp=%s&nonce=%s";
 
     /**
      * 回传转化数据
@@ -84,7 +85,44 @@ public class TencentApiClient extends AbstractApiClient implements IAccessTokenC
 
     @Override
     public SiteStatistics getDataReport(PromotionAccount account, String ideaId, String startDate, String endDate) {
-        return null;
+        // 构建请求参数
+        Map<String, Object> body = new HashMap<>();
+        // 基础信息
+        //报告名称: 信息流整体账户报告
+        //reportType: 2172649
+        //支持的时间单位: HOUR DAY WEEK MONTH SUMMARY
+        //支持的最大时间区间: 824天
+        body.put("account_id", account.getAdAccountId());
+        body.put("level", "REPORT_LEVEL_ADGROUP");
+        Map<String, Object> date_range = new HashMap<>();
+        date_range.put("start_date", startDate);
+        date_range.put("end_date", endDate);
+        body.put("date_range", date_range);
+
+        Map<String, Object> filtering = new HashMap<>();
+        filtering.put("field", "dynamic_creative_id");
+        filtering.put("operator", "IN");
+        filtering.put("values", Arrays.asList(ideaId));
+        body.put("filtering", date_range);
+
+        log.info("腾讯数据请求参数:{}", JSONUtil.toJsonStr(body));
+        HttpResponse execute = HttpRequest.get(String.format(REPORTS_API_URL, account.getAccessToken(), new Date().getTime(), SnowflakeUtil.randomUUID()))
+                .form(body)
+                .timeout(SystemConstant.API_TIMEOUT)
+                .execute();
+        JSONObject jsonObject = JSONUtil.parseObj(execute.body());
+        log.info("腾讯数据返回结果:{}", execute.body());
+        JSONObject data = jsonObject.getJSONObject("body").getJSONObject("data");
+        JSONArray rows = data.getJSONArray("list");
+        JSONObject jsonObject1 = rows.getJSONObject(0);
+        SiteStatistics siteStatistics = new SiteStatistics();
+        siteStatistics.setImpressionCount(Integer.valueOf(jsonObject1.getStr("view_count")));
+        siteStatistics.setClickCount(Integer.valueOf(jsonObject1.getStr("valid_click_count")));
+        siteStatistics.setActualCost(new BigDecimal(jsonObject1.getStr("cost")));
+        siteStatistics.setAccountCost(new BigDecimal(jsonObject1.getStr("cost")));
+        siteStatistics.setClickRate(new BigDecimal(jsonObject1.getStr("ctr")));
+        siteStatistics.setAvgClickPrice(new BigDecimal(jsonObject1.getStr("cpc")));
+        return siteStatistics;
     }
 
 
@@ -110,7 +148,7 @@ public class TencentApiClient extends AbstractApiClient implements IAccessTokenC
                     .form("client_secret", codeVo.getAppSecret())
                     .form("grant_type", "authorization_code")
                     .form("authorization_code", codeVo.getAuthCode())
-                    .form("redirect_uri", "authorization_code")
+                    // .form("redirect_uri", "authorization_code")
                     .timeout(SystemConstant.API_TIMEOUT)
                     .execute();
         });

+ 18 - 0
fs-service/src/main/java/com/fs/newAdv/mapper/AdvMiniConfigMapper.java

@@ -0,0 +1,18 @@
+package com.fs.newAdv.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.newAdv.domain.AdvMiniConfig;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * <p>
+ * 广告微信小程序配置 Mapper 接口
+ * </p>
+ *
+ * @author zhangqin
+ * @since 2025-12-19
+ */
+@Mapper
+public interface AdvMiniConfigMapper extends BaseMapper<AdvMiniConfig> {
+
+}

+ 16 - 0
fs-service/src/main/java/com/fs/newAdv/service/IAdvMiniConfigService.java

@@ -0,0 +1,16 @@
+package com.fs.newAdv.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.fs.newAdv.domain.AdvMiniConfig;
+
+/**
+ * <p>
+ * 广告微信小程序配置 服务类
+ * </p>
+ *
+ * @author zhangqin
+ * @since 2025-12-19
+ */
+public interface IAdvMiniConfigService extends IService<AdvMiniConfig> {
+
+}

+ 1 - 1
fs-service/src/main/java/com/fs/newAdv/service/ILeadService.java

@@ -41,7 +41,7 @@ public interface ILeadService extends IService<Lead> {
      * @param mpOpenId
      * @param phone
      */
-    void weChatAuthorizationLead(String traceId, String unionId, String mpOpenId, String phone);
+    void weChatAuthorizationLead(String traceId, String unionId, String maOpenId, String phone);
 
     /**
      * 小程序授权落地页访问线索处理

+ 20 - 0
fs-service/src/main/java/com/fs/newAdv/service/impl/AdvMiniConfigServiceImpl.java

@@ -0,0 +1,20 @@
+package com.fs.newAdv.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.fs.newAdv.domain.AdvMiniConfig;
+import com.fs.newAdv.mapper.AdvMiniConfigMapper;
+import com.fs.newAdv.service.IAdvMiniConfigService;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ * 广告微信小程序配置 服务实现类
+ * </p>
+ *
+ * @author zhangqin
+ * @since 2025-12-19
+ */
+@Service
+public class AdvMiniConfigServiceImpl extends ServiceImpl<AdvMiniConfigMapper, AdvMiniConfig> implements IAdvMiniConfigService {
+
+}

+ 6 - 4
fs-service/src/main/java/com/fs/newAdv/service/impl/LeadServiceImpl.java

@@ -118,20 +118,22 @@ public class LeadServiceImpl extends ServiceImpl<LeadMapper, Lead> implements IL
 
     @Override
     @Async
-    public void weChatAuthorizationLead(String traceId, String unionId, String mpOpenId, String phone) {
+    public void weChatAuthorizationLead(String traceId, String unionId, String maOpenId, String phone) {
        try{
+           log.info("用户微信授权线索信息:{}", traceId);
            Lead byTraceId = this.getByTraceId(traceId);
            if (byTraceId == null) {
                return;
            }
            this.update(new LambdaUpdateWrapper<Lead>()
                    .eq(Lead::getTraceId, traceId)
-                   .set(Lead::getUnionid, unionId)
-                   .set(Lead::getPhone, phone)
-                   .set(Lead::getOpenid, mpOpenId)
+                   .set(ObjectUtil.isNotEmpty(unionId),Lead::getUnionid, unionId)
+                   .set(ObjectUtil.isNotEmpty(phone),Lead::getPhone, phone)
+                   .set(ObjectUtil.isNotEmpty(maOpenId),Lead::getOpenid, maOpenId)
                    .set(Lead::getMiniAuth, 1));
            if (ObjectUtil.isNotEmpty(byTraceId.getLandingPageTs()) && byTraceId.getLandingPageTs().toLocalDate().isEqual(LocalDate.now())) {
                // 微信授权且当日创建事件
+               log.info("用户微信授权线索事件回传:{}", traceId);
                conversionEventPublisher.publishConversionEvent(traceId, SystemEventTypeEnum.AUTH_TODAY_CREATE);
            }
        }catch (Exception e){

+ 1 - 1
fs-service/src/main/java/com/fs/newAdv/vo/AccessTokenByAuthCodeVo.java

@@ -11,7 +11,7 @@ public class AccessTokenByAuthCodeVo implements Serializable {
     private String appId;
     private String appSecret;
     private String authCode;
-    private String userId;
+    private String adAccountId;
 
 
 

+ 11 - 0
fs-service/src/main/java/com/fs/qw/domain/QwCompany.java

@@ -85,4 +85,15 @@ public class QwCompany extends BaseEntity
     private Long createUserId;
     // 创建部门
     private Long createDeptId;
+
+    /**
+     * 御君方云医小程序原始id
+     */
+    private String yjfyyAppId;
+    /**
+     * 御君方云医应用id
+     */
+    private String yjfyyAgentId;
+
+    private String yjfyySchema;
 }

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

@@ -1309,6 +1309,10 @@ public class QwUserServiceImpl implements IQwUserService
         WxWorkGetQrCodeDTO wxWorkGetQrCodeDTO = new WxWorkGetQrCodeDTO();
         wxWorkGetQrCodeDTO.setUuid(qwUser.getUid());
         try {
+            redisCache.deleteObject("qrCode:uuid:"+qwUser.getUid());
+            redisCache.deleteObject("qrCodeUid:"+qwUser.getUid());
+            redisCache.deleteObject("qrCode:qwUserId:"+qwUser.getId());
+            redisCache.deleteObject("qrCodeUid:qwUserId:"+qwUser.getId());
             wxWorkService.LoginOut(wxWorkGetQrCodeDTO,qwUser.getServerId());
         }catch (Exception e){
 

+ 6 - 0
fs-service/src/main/java/com/fs/sop/mapper/QwSopTempRulesMapper.java

@@ -99,4 +99,10 @@ public interface QwSopTempRulesMapper extends BaseMapper<QwSopTempRules> {
     List<QwSopTempRules> listByTempIdAndNameAndDayNum(@Param("id") String id, @Param("dayNum") Integer dayNum);
 
     void deleteByIdList(@Param("ids") List<String> ids);
+
+    List<Long> getTempOfficialIdsForOpen(@Param("tempId")String tempId);
+
+    List<Long> getTempOfficialIdsForClose(@Param("tempId") String tempId);
+
+    int updateTempRulesOfficialBatch(@Param("ids") List<Long> ids,@Param("official") Integer official);
 }

+ 22 - 0
fs-service/src/main/java/com/fs/sop/params/BatchOpenOrCloseOfficialParam.java

@@ -0,0 +1,22 @@
+package com.fs.sop.params;
+
+import lombok.Data;
+
+/**
+ * @author MixLiu
+ * @date 2025/12/22 上午10:30)
+ */
+@Data
+public class BatchOpenOrCloseOfficialParam {
+
+    /**
+     * 模板id
+     */
+    private String tempId;
+
+    /**
+     * 是否官方 0 关闭 1 开启
+     */
+    private Integer isOfficial;
+
+}

+ 10 - 0
fs-service/src/main/java/com/fs/sop/service/IQwSopTempService.java

@@ -1,8 +1,10 @@
 package com.fs.sop.service;
 
+import com.fs.common.core.domain.R;
 import com.fs.qw.vo.SortDayVo;
 import com.fs.sop.domain.QwSopTemp;
 import com.fs.sop.domain.QwSopTempDay;
+import com.fs.sop.params.BatchOpenOrCloseOfficialParam;
 import com.fs.sop.params.QwSopShareTempParam;
 import com.fs.sop.vo.QwSopTempRedPackageVo;
 
@@ -101,4 +103,12 @@ public interface IQwSopTempService {
     List<String> getSelectableRange();
 
     void syncTemplate(Long courseId);
+
+    /**
+     * sop模板update一键开关官方群发
+     * @param param
+     * @return
+     */
+    R batchOpenOrCloseOfficial(BatchOpenOrCloseOfficialParam param);
+
 }

+ 30 - 0
fs-service/src/main/java/com/fs/sop/service/impl/QwSopTempServiceImpl.java

@@ -8,9 +8,11 @@ import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.fs.common.BeanCopyUtils;
 import com.fs.common.annotation.DataSource;
+import com.fs.common.core.domain.R;
 import com.fs.common.enums.DataSourceType;
 import com.fs.common.exception.base.BaseException;
 import com.fs.common.utils.PubFun;
+import com.fs.common.utils.StringUtils;
 import com.fs.company.domain.Company;
 import com.fs.company.service.ICompanyService;
 import com.fs.config.cloud.CloudHostProper;
@@ -30,6 +32,8 @@ import com.fs.qw.vo.QwUserVO;
 import com.fs.qw.vo.SortDayVo;
 import com.fs.sop.domain.*;
 import com.fs.sop.mapper.QwSopTempMapper;
+import com.fs.sop.mapper.QwSopTempRulesMapper;
+import com.fs.sop.params.BatchOpenOrCloseOfficialParam;
 import com.fs.sop.params.QwSopShareTempParam;
 import com.fs.sop.service.*;
 import com.fs.sop.vo.QwSopTempRedPackageVo;
@@ -84,6 +88,8 @@ public class QwSopTempServiceImpl implements IQwSopTempService {
     private final ICompanyService companyService;
     private final ThreadPoolTaskExecutor threadPoolTaskExecutor;
 
+    @Autowired
+    QwSopTempRulesMapper qwSopTempRulesMapper;
     /**
      * 查询sop模板
      *
@@ -788,4 +794,28 @@ public class QwSopTempServiceImpl implements IQwSopTempService {
 //        }
     }
 
+    /**
+     * sop模板update一键开关官方群发
+     * @param param
+     * @return
+     */
+    public R batchOpenOrCloseOfficial(BatchOpenOrCloseOfficialParam param){
+        List<Long> updateIds = new ArrayList<>();
+        if(StringUtils.isBlank(param.getTempId())){
+            return R.error("参数错误,请确认模板id是否正确");
+        }
+        if(Integer.valueOf(1).equals(param.getIsOfficial())){
+            updateIds = qwSopTempRulesMapper.getTempOfficialIdsForOpen(param.getTempId());
+        }else if(Integer.valueOf(0).equals(param.getIsOfficial())){
+            updateIds = qwSopTempRulesMapper.getTempOfficialIdsForClose(param.getTempId());
+        }else{
+            return R.error("参数错误,请确认选择类型是否正确");
+        }
+
+        if(null != updateIds && !updateIds.isEmpty()){
+            qwSopTempRulesMapper.updateTempRulesOfficialBatch(updateIds, param.getIsOfficial());
+        }
+        return  R.ok("操作成功");
+    }
+
 }

+ 7 - 0
fs-service/src/main/java/com/fs/store/param/h5/FsUserPageListParam.java

@@ -6,6 +6,7 @@ import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
 
 import java.io.Serializable;
+import java.util.Date;
 import java.util.Set;
 
 
@@ -89,6 +90,12 @@ public class FsUserPageListParam implements Serializable {
      */
     private Boolean isHidePhoneMiddle = Boolean.TRUE;
 
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date eTime;
+
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date sTime;
+
 
 }
 

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

@@ -89,7 +89,7 @@ headerImg:
   imgUrl: https://ysy-1329817240.cos.ap-guangzhou.myqcloud.com/ysy/20250820/2c47e4f105b641b4a49df50a77338e32.png
 ipad:
   ipadUrl: http://ipad.cfrytjkzx.cn
-  aiApi:
+  aiApi: http://49.232.181.28:3000/api
   voiceApi:
   commonApi:
 wx_miniapp_temp:

+ 2 - 2
fs-service/src/main/resources/application-config-druid-heyantang.yml

@@ -80,8 +80,8 @@ tencent_cloud_config:
 cloud_host:
   company_name: 鹤颜堂
   projectCode: CDHYT
-  spaceName:
-  volcengineUrl:
+  spaceName: cdhyt-2114522511
+  volcengineUrl: https://cdhytvolcengine.ylrztop.com
 headerImg:
   imgUrl: https
 ipad:

+ 2 - 2
fs-service/src/main/resources/application-config-druid-nmgyt.yml

@@ -86,8 +86,8 @@ tencent_cloud_config:
 cloud_host:
   company_name: 内蒙古一贴
   projectCode: NMGYT
-  spaceName:
-  volcengineUrl:
+  spaceName: nmgmyt-2114522511
+  volcengineUrl: https://nmgmytvolcengine.ylrztop.com
 headerImg:
   imgUrl: https
 ipad:

+ 1 - 0
fs-service/src/main/resources/application-config-druid-sczy.yml

@@ -95,6 +95,7 @@ cloud_host:
   company_name: 四川致医
   projectCode: SCZY
   spaceName:
+  volcengineUrl:
 headerImg:
   imgUrl: https://jiuzhouzaixian.obs.cn-southwest-2.myhuaweicloud.com/fs/20250623/1750665141214.png
 ipad:

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

@@ -116,7 +116,7 @@ cloud_host:
   company_name: 中康
   projectCode: ZKZH
   spaceName: sxzk-2114522511
-  volcengineUrl:
+  volcengineUrl: https://sxzkvolcengine.ylrztop.com
 headerImg:
   imgUrl: https://zkzh-2025.oss-cn-beijing.aliyuncs.com/fs/20250619/e31b5e051a474a7a9b4ad02575b46196.png
 

+ 3 - 0
fs-service/src/main/resources/mapper/company/CompanyMapper.xml

@@ -122,6 +122,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="groupName != null">group_name,</if>
             <if test="maxPadNum != null">max_pad_num,</if>
             <if test="deptId != null">dept_id,</if>
+            <if test="isOpenRestReminder != null">is_open_rest_reminder,</if>
         </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="companyName != null">#{companyName},</if>
@@ -158,6 +159,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="groupName != null">#{groupName},</if>
             <if test="maxPadNum != null">#{maxPadNum},</if>
             <if test="deptId != null">#{deptId},</if>
+            <if test="isOpenRestReminder != null">is_open_rest_reminder = #{isOpenRestReminder},</if>
          </trim>
     </insert>
 
@@ -201,6 +203,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="maxPadNum != null">max_pad_num = #{maxPadNum},</if>
             <if test="deptId != null">dept_id = #{deptId},</if>
             <if test="redPackageMoney != null">red_package_money = #{redPackageMoney},</if>
+            <if test="isOpenRestReminder != null">is_open_rest_reminder = #{isOpenRestReminder},</if>
         </trim>
         where company_id = #{companyId}
     </update>

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

@@ -489,7 +489,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
                     sop_id,
                     finish_time,
                     send_finish_msg,
-                    camp_period_time
+                    camp_period_time,
+                    period_id
                 FROM
                     fs_course_watch_log
                 WHERE

+ 2 - 0
fs-service/src/main/resources/mapper/course/FsUserCoursePeriodMapper.xml

@@ -56,6 +56,7 @@
         fs_user_course_period.update_time,
         fs_user_course_period.period_status,
         fs_user_course_period.max_view_num,
+        fs_user_course_period.is_open_rest_reminder,
         course_style,
         live_room_style,
         red_packet_grant_method,
@@ -166,6 +167,7 @@
             <if test="courseLogo != null and courseLogo !=''">course_logo = #{courseLogo},</if>
             <if test="openCommentStatus != null">open_comment_status = #{openCommentStatus},</if>
             <if test="periodLine != null">period_line = #{periodLine},</if>
+            <if test="isOpenRestReminder != null">is_open_rest_reminder = #{isOpenRestReminder},</if>
         </trim>
         where period_id = #{periodId}
     </update>

+ 5 - 0
fs-service/src/main/resources/mapper/his/FsIntegralCartMapper.xml

@@ -41,6 +41,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         select
             ifnull(sum(ic.cart_num), 0)
         from fs_integral_cart ic
+        inner join fs_integral_goods ig on ig.goods_id = ic.goods_id
         <where>
             <if test="params.userId != null">
                 ic.user_id = #{params.userId}
@@ -79,4 +80,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             </if>
         </where>
     </select>
+
+    <delete id="deleteCartByGoodsId">
+        delete from fs_integral_cart where goods_id = #{goodsId}
+    </delete>
 </mapper>

+ 11 - 1
fs-service/src/main/resources/mapper/his/FsStorePaymentErrorMapper.xml

@@ -11,10 +11,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="msg"    column="msg"    />
         <result property="status"    column="status"    />
         <result property="createTime"    column="create_time"    />
+        <result property="orderId"    column="order_id"    />
+        <result property="businessType"    column="business_type"    />
     </resultMap>
 
     <sql id="selectFsStorePaymentErrorVo">
-        select id, order_flow_no, order_no, msg, status, create_time from fs_store_payment_error
+        select id, order_flow_no, order_no, msg, status, create_time,order_id,business_type from fs_store_payment_error
     </sql>
 
     <select id="selectFsStorePaymentErrorList" parameterType="FsStorePaymentError" resultMap="FsStorePaymentErrorResult">
@@ -23,6 +25,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="orderFlowNo != null  and orderFlowNo != ''"> and order_flow_no = #{orderFlowNo}</if>
             <if test="orderNo != null  and orderNo != ''"> and order_no = #{orderNo}</if>
             <if test="msg != null  and msg != ''"> and msg = #{msg}</if>
+            <if test="orderId != null "> and order_id = #{orderId}</if>
+            <if test="businessType != null "> and business_type = #{businessType}</if>
             <if test="status != null "> and status = #{status}</if>
         </where>
     </select>
@@ -41,6 +45,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="msg != null">msg,</if>
             <if test="status != null">status,</if>
             <if test="createTime != null">create_time,</if>
+            <if test="orderId != null">order_id,</if>
+            <if test="businessType != null">business_type,</if>
          </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="id != null">#{id},</if>
@@ -49,6 +55,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="msg != null">#{msg},</if>
             <if test="status != null">#{status},</if>
             <if test="createTime != null">#{createTime},</if>
+            <if test="orderId != null">#{orderId},</if>
+            <if test="businessType != null">#{businessType},</if>
          </trim>
     </insert>
 
@@ -60,6 +68,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="msg != null">msg = #{msg},</if>
             <if test="status != null">status = #{status},</if>
             <if test="createTime != null">create_time = #{createTime},</if>
+            <if test="orderId != null">order_id = #{orderId},</if>
+            <if test="businessType != null">business_type = #{businessType},</if>
         </trim>
         where id = #{id}
     </update>

+ 16 - 10
fs-service/src/main/resources/mapper/his/FsUserMapper.xml

@@ -92,12 +92,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="registerCode != null   and registerCode != '' ">and register_code = #{registerCode}</if>
             <if test="source != null  and source != '' ">and source = #{source}</if>
             <if test="isShow != null  ">and is_show = #{isShow}</if>
-            <if test="keywords != null  and keywords != ''">
-                AND (fs_user.nick_name LIKE concat( #{keywords},'%')
-                or  fs_user.phone = #{keywords}
-                or fs_user.user_id = #{keywords}
-                )
-            </if>
             <!--<if test="qwRepeat != null  ">and qw_repeat = #{qwRepeat}</if>
             <if test="userRepeat != null  ">and user_repeat = #{userRepeat}</if>-->
 <!--            <if test="payOrder != null  ">and pay_order = #{payOrder}</if>-->
@@ -407,6 +401,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="projectId != null">
                 AND ucu.project_id = #{projectId}
             </if>
+            <if test="eTime != null and sTime !=null">
+                AND fs_user.create_time &gt;= #{sTime} AND fs_user.create_time &lt;= #{eTime}
+            </if>
         </where>
         limit ${(pageNum-1)*pageSize},${pageSize}
     </select>
@@ -867,7 +864,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
                     </when>
                 </choose>
             </if>
-            and fs_user.user_id = #{fsUserId} and fucu.company_user_id =#{userId}
+            and fs_user.user_id = #{fsUserId}
+            <if test="userCompanyId!=null">
+                and fucu.id = #{userCompanyId}
+            </if>
         </where>
         GROUP BY
         fs_user.user_id
@@ -898,7 +898,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
                     </when>
                 </choose>
             </if>
-            and fs_user.user_id = #{fsUserId} and fucu.company_user_id =#{userId}
+            and fs_user.user_id = #{fsUserId}
+            <if test="userCompanyId!=null">
+                and fucu.id = #{userCompanyId}
+            </if>
         </where>
         GROUP BY
         fs_user.user_id
@@ -933,7 +936,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
                     </when>
                 </choose>
             </if>
-            and  fs_user.user_id = #{fsUserId} and fucu.company_user_id =#{userId}
+            and  fs_user.user_id = #{fsUserId}
+            <if test="userCompanyId!=null">
+                and fucu.id = #{userCompanyId}
+            </if>
         </where>
         GROUP BY
         fs_user.user_id
@@ -1997,7 +2003,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     <select id="selectFsUserVOListByProjectNew" resultType="com.fs.his.vo.FsUserVO">
         SELECT
             u.user_id, u.nick_name, u.avatar, u.phone, u.integral, u.now_money,
-            ucu.project_id,ucu.company_user_id as companyUserId,ucu.create_time as bindTime,ucu.status,
+            ucu.project_id,ucu.id as companyUserId,ucu.create_time as bindTime,ucu.status,
             company.company_name,
             cu.nick_name   companyUserNickName
         FROM

+ 1 - 0
fs-service/src/main/resources/mapper/hisStore/FsStorePaymentScrmMapper.xml

@@ -28,6 +28,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="orderId"    column="order_id"    />
         <result property="isPayRemain"    column="is_pay_remain"    />
         <result property="payMode"    column="pay_mode"    />
+        <result property="appId"    column="app_id"    />
         <result property="businessCode"    column="business_code"    />
     </resultMap>
 

Деякі файли не було показано, через те що забагато файлів було змінено