소스 검색

Merge branch 'master' into 康年堂

# Conflicts:
#	fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreOrderScrmMapper.java
#	fs-service/src/main/java/com/fs/hisStore/service/IFsStoreOrderScrmService.java
#	fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreOrderScrmServiceImpl.java
#	fs-service/src/main/resources/mapper/hisStore/FsStoreOrderScrmMapper.xml
yh 3 주 전
부모
커밋
b94a7b4892
100개의 변경된 파일2184개의 추가작업 그리고 489개의 파일을 삭제
  1. 1 1
      fs-ad-new-api/Dockerfile
  2. 3 4
      fs-ad-new-api/src/main/java/com/fs/app/controller/LandingPageController.java
  3. 14 10
      fs-ad-new-api/src/main/java/com/fs/app/controller/TestController.java
  4. 26 2
      fs-ad-new-api/src/main/java/com/fs/app/controller/TrackingController.java
  5. 6 4
      fs-ad-new-api/src/main/java/com/fs/app/controller/WeChatController.java
  6. 7 3
      fs-ad-new-api/src/main/java/com/fs/app/facade/CallbackProcessingFacadeService.java
  7. 54 41
      fs-ad-new-api/src/main/java/com/fs/app/facade/CallbackProcessingFacadeServiceImpl.java
  8. 3 1
      fs-ad-new-api/src/main/java/com/fs/app/facade/ConversionServiceImpl.java
  9. 2 14
      fs-ad-new-api/src/main/java/com/fs/app/mq/consumer/ConversionTrackingMessageConsumer.java
  10. 54 0
      fs-ad-new-api/src/main/java/com/fs/framework/aspectj/RocketMQTraceIdAspect.java
  11. 5 5
      fs-admin/src/main/java/com/fs/company/controller/CompanyController.java
  12. 19 1
      fs-admin/src/main/java/com/fs/course/controller/FsUserCourseVideoController.java
  13. 20 0
      fs-admin/src/main/java/com/fs/fastGpt/GptRoleController.java
  14. 15 0
      fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreOrderScrmController.java
  15. 4 4
      fs-admin/src/main/java/com/fs/qw/controller/QwCompanyController.java
  16. 688 84
      fs-admin/src/main/java/com/fs/qw/controller/QwUserController.java
  17. 1 1
      fs-company/src/main/java/com/fs/company/controller/company/CompanyUserController.java
  18. 6 5
      fs-company/src/main/java/com/fs/company/controller/live/LiveDataController.java
  19. 2 1
      fs-company/src/main/java/com/fs/company/controller/live/OrderController.java
  20. 18 4
      fs-company/src/main/java/com/fs/hisStore/controller/FsStoreOrderScrmController.java
  21. 8 4
      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/service/WebSocketServer.java
  23. 1 1
      fs-qw-api/Dockerfile
  24. 2 19
      fs-qw-api/src/main/java/com/fs/app/service/QwDataCallbackService.java
  25. 33 9
      fs-service/src/main/java/com/fs/company/service/impl/CompanyServiceImpl.java
  26. 11 4
      fs-service/src/main/java/com/fs/course/mapper/FsUserCourseVideoMapper.java
  27. 21 0
      fs-service/src/main/java/com/fs/course/param/BatchEditCoverParam.java
  28. 10 1
      fs-service/src/main/java/com/fs/course/service/IFsUserCourseVideoService.java
  29. 27 8
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java
  30. 2 2
      fs-service/src/main/java/com/fs/his/domain/FsIntegralOrder.java
  31. 1 0
      fs-service/src/main/java/com/fs/his/enums/FsUserIntegralLogTypeEnum.java
  32. 4 0
      fs-service/src/main/java/com/fs/his/mapper/FsIntegralCartMapper.java
  33. 5 0
      fs-service/src/main/java/com/fs/his/service/impl/FsIntegralCartServiceImpl.java
  34. 8 0
      fs-service/src/main/java/com/fs/his/service/impl/FsIntegralGoodsServiceImpl.java
  35. 11 1
      fs-service/src/main/java/com/fs/his/service/impl/FsIntegralOrderServiceImpl.java
  36. 24 0
      fs-service/src/main/java/com/fs/his/service/impl/FsStoreAfterSalesServiceImpl.java
  37. 4 2
      fs-service/src/main/java/com/fs/his/service/impl/FsStorePaymentServiceImpl.java
  38. 1 1
      fs-service/src/main/java/com/fs/his/vo/FsIntegralOrderExcelVO.java
  39. 7 0
      fs-service/src/main/java/com/fs/hisStore/domain/FsStoreOrderScrm.java
  40. 5 1
      fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreOrderItemScrmMapper.java
  41. 15 3
      fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreOrderScrmMapper.java
  42. 24 0
      fs-service/src/main/java/com/fs/hisStore/param/FsStoreOrderBatchAuditParam.java
  43. 2 0
      fs-service/src/main/java/com/fs/hisStore/param/FsStoreOrderParam.java
  44. 8 0
      fs-service/src/main/java/com/fs/hisStore/service/IFsStoreOrderScrmService.java
  45. 25 0
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreAfterSalesScrmServiceImpl.java
  46. 12 1
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreOrderScrmServiceImpl.java
  47. 6 0
      fs-service/src/main/java/com/fs/hisStore/vo/FsStoreOrderExportVO.java
  48. 6 0
      fs-service/src/main/java/com/fs/hisStore/vo/FsStoreOrderItemExportVO.java
  49. 5 0
      fs-service/src/main/java/com/fs/hisStore/vo/FsStoreOrderVO.java
  50. 2 0
      fs-service/src/main/java/com/fs/live/service/ILiveCompletionPointsRecordService.java
  51. 2 0
      fs-service/src/main/java/com/fs/live/service/ILiveDataService.java
  52. 26 0
      fs-service/src/main/java/com/fs/live/service/impl/LiveAfterSalesServiceImpl.java
  53. 114 0
      fs-service/src/main/java/com/fs/live/service/impl/LiveCompletionPointsRecordServiceImpl.java
  54. 30 0
      fs-service/src/main/java/com/fs/live/service/impl/LiveDataServiceImpl.java
  55. 2 4
      fs-service/src/main/java/com/fs/live/service/impl/LiveOrderServiceImpl.java
  56. 95 12
      fs-service/src/main/java/com/fs/live/service/impl/LiveRedConfServiceImpl.java
  57. 8 0
      fs-service/src/main/java/com/fs/live/vo/LiveAfterSalesVo.java
  58. 19 0
      fs-service/src/main/java/com/fs/live/vo/LiveDataListVo.java
  59. 1 0
      fs-service/src/main/java/com/fs/newAdv/constant/MqTopicConstant.java
  60. 16 7
      fs-service/src/main/java/com/fs/newAdv/domain/Lead.java
  61. 12 0
      fs-service/src/main/java/com/fs/newAdv/dto/req/QwExternalIdBindTrackReq.java
  62. 10 0
      fs-service/src/main/java/com/fs/newAdv/dto/req/TraceIdDto.java
  63. 8 8
      fs-service/src/main/java/com/fs/newAdv/enums/SystemEventTypeEnum.java
  64. 2 1
      fs-service/src/main/java/com/fs/newAdv/event/ConversionEventListener.java
  65. 2 3
      fs-service/src/main/java/com/fs/newAdv/integration/adapter/BaiduAdapter.java
  66. 4 5
      fs-service/src/main/java/com/fs/newAdv/integration/adapter/OPPOAdapter.java
  67. 2 3
      fs-service/src/main/java/com/fs/newAdv/integration/adapter/OceanEngineAdapter.java
  68. 1 2
      fs-service/src/main/java/com/fs/newAdv/integration/adapter/TencentAdapter.java
  69. 1 2
      fs-service/src/main/java/com/fs/newAdv/integration/adapter/VIVOAdapter.java
  70. 16 9
      fs-service/src/main/java/com/fs/newAdv/integration/client/AbstractApiClient.java
  71. 1 1
      fs-service/src/main/java/com/fs/newAdv/integration/client/advertiser/BaiduApiClient.java
  72. 13 11
      fs-service/src/main/java/com/fs/newAdv/integration/client/advertiser/OPPOApiClient.java
  73. 21 2
      fs-service/src/main/java/com/fs/newAdv/integration/client/advertiser/OceanEngineApiClient.java
  74. 1 1
      fs-service/src/main/java/com/fs/newAdv/integration/client/advertiser/VIVOApiClient.java
  75. 13 5
      fs-service/src/main/java/com/fs/newAdv/service/ILeadService.java
  76. 95 59
      fs-service/src/main/java/com/fs/newAdv/service/impl/LeadServiceImpl.java
  77. 1 1
      fs-service/src/main/java/com/fs/newAdv/service/impl/SiteServiceImpl.java
  78. 12 0
      fs-service/src/main/java/com/fs/newAdv/vo/ConversionParmVo.java
  79. 1 1
      fs-service/src/main/java/com/fs/qw/service/impl/AsyncChatSopService.java
  80. 5 0
      fs-service/src/main/java/com/fs/qw/vo/QwExternalContactVO.java
  81. 1 1
      fs-service/src/main/java/com/fs/sop/service/impl/SopUserLogsServiceImpl.java
  82. 2 2
      fs-service/src/main/resources/application-config-druid-ddgy.yml
  83. 2 2
      fs-service/src/main/resources/application-config-druid-fby.yml
  84. 2 2
      fs-service/src/main/resources/application-config-druid-gzzdy.yml
  85. 2 2
      fs-service/src/main/resources/application-config-druid-hst.yml
  86. 2 1
      fs-service/src/main/resources/application-config-druid-jnmy.yml
  87. 2 2
      fs-service/src/main/resources/application-config-druid-kyt.yml
  88. 3 5
      fs-service/src/main/resources/application-config-druid-ylrz.yml
  89. 1 1
      fs-service/src/main/resources/application-druid-shdn.yml
  90. 3 0
      fs-service/src/main/resources/application-druid-ylrz.yml
  91. 15 1
      fs-service/src/main/resources/mapper/course/FsUserCourseVideoMapper.xml
  92. 9 0
      fs-service/src/main/resources/mapper/his/FsUserMapper.xml
  93. 20 3
      fs-service/src/main/resources/mapper/hisStore/FsStoreOrderScrmMapper.xml
  94. 104 64
      fs-user-app/src/main/java/com/fs/app/controller/live/LiveCompletionPointsController.java
  95. 195 0
      fs-user-app/src/main/java/com/fs/app/controller/live/LiveOrderController.java
  96. 4 0
      fs-user-app/src/main/java/com/fs/app/controller/store/WxUserScrmController.java
  97. 2 2
      fs-user-app/src/main/resources/application.yml
  98. 7 1
      fs-websocket/src/main/java/com/fs/websocket/FsWebSocketServiceApplication.java
  99. 4 4
      fs-websocket/src/main/java/com/fs/websocket/config/WebSocketConfig.java
  100. 30 15
      fs-websocket/src/main/java/com/fs/websocket/service/WebSocketServer.java

+ 1 - 1
fs-ad-new-api/Dockerfile

@@ -1,4 +1,4 @@
-FROM openjdk:8-jre
+FROM anolis-registry.cn-zhangjiakou.cr.aliyuncs.com/openanolis/openjdk:8-8.6
 # java版本,最好使用openjdk,而不是类似于Java:1.8
 COPY ./target/fs-ad-new-api.jar fs-ad-new-api.jar
 # 向外暴露的接口,最好与项目yml文件中的端口一致

+ 3 - 4
fs-ad-new-api/src/main/java/com/fs/app/controller/LandingPageController.java

@@ -31,18 +31,17 @@ public class LandingPageController {
      * 落地页访问
      */
     @PostMapping("/h5/home")
-    public Result<LandingIndexRes> track(
+    public Result<LandingIndexRes> h5Home(
             @RequestBody LandingIndexReq req) {
-        log.info("落地页访问追踪:req={}", req);
         // 查询落地页模板
-        return Result.success(facadeService.getLandingIndexBySiteId(req.getAllParams()));
+        return Result.success(facadeService.getLandingIndexBySiteId(req.getViewUrl(),req.getAllParams()));
     }
 
     /**
      * 小程序访问
      */
     @PostMapping("/mini/home")
-    public Result<LandingIndexRes> track(@RequestBody WeChatLandingIndexReq req) {
+    public Result<LandingIndexRes> miniHome(@RequestBody WeChatLandingIndexReq req) {
         return Result.success(facadeService.getWxLandingIndexBySiteId(req));
     }
 

+ 14 - 10
fs-ad-new-api/src/main/java/com/fs/app/controller/TestController.java

@@ -1,15 +1,13 @@
 package com.fs.app.controller;
 
+import com.fs.newAdv.dto.req.TraceIdDto;
 import com.fs.newAdv.integration.client.advertiser.BaiduApiClient;
 import com.fs.newAdv.enums.SystemEventTypeEnum;
 import com.fs.newAdv.event.ConversionEventPublisher;
 import com.fs.newAdv.service.IPromotionAccountService;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
-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;
+import org.springframework.web.bind.annotation.*;
 
 /**
  * 广告商监测链接
@@ -28,15 +26,21 @@ public class TestController {
     @Autowired
     private IPromotionAccountService promotionAccountService;
 
-    @GetMapping("/test1/{traceId}")
-    public void test1(@PathVariable("traceId") String traceId) {
+    @PostMapping("/test1")
+    public void test1(@RequestBody TraceIdDto traceId) {
         log.info("模拟 当日加群 事件完成");
-        conversionEventPublisher.publishConversionEvent(traceId, SystemEventTypeEnum.GROUP_TODAY);
+        conversionEventPublisher.publishConversionEvent(traceId.getTraceId(), SystemEventTypeEnum.GROUP_TODAY);
     }
 
-    @GetMapping("/test2/{traceId}")
-    public void test2(@PathVariable("traceId") String traceId) {
+    @PostMapping("/test2")
+    public void test2(@RequestBody TraceIdDto traceId) {
         log.info("模拟 当日加微 事件完成");
-        conversionEventPublisher.publishConversionEvent(traceId, SystemEventTypeEnum.WEI_CHAT_TODAY);
+        conversionEventPublisher.publishConversionEvent(traceId.getTraceId(), SystemEventTypeEnum.WEI_CHAT_TODAY);
+    }
+
+    @PostMapping("/test3")
+    public void test3(@RequestBody TraceIdDto traceId) {
+        log.info("模拟 微信授权 事件完成");
+        conversionEventPublisher.publishConversionEvent(traceId.getTraceId(), SystemEventTypeEnum.AUTH_TODAY_CREATE);
     }
 }

+ 26 - 2
fs-ad-new-api/src/main/java/com/fs/app/controller/TrackingController.java

@@ -1,6 +1,10 @@
 package com.fs.app.controller;
 
 import com.fs.app.facade.CallbackProcessingFacadeService;
+import com.fs.app.facade.IConversionService;
+import com.fs.common.result.Result;
+import com.fs.newAdv.dto.req.QwExternalIdBindTrackReq;
+import com.fs.newAdv.enums.SystemEventTypeEnum;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
@@ -22,6 +26,8 @@ public class TrackingController {
     private CallbackProcessingFacadeService facadeService;
 
 
+    @Autowired
+    private IConversionService conversionService;
     /**
      * 监测链接端口
      *
@@ -29,14 +35,14 @@ public class TrackingController {
      * @param response  HTTP响应
      */
     @GetMapping("/click/{advertiserCode}")
-    public void trackBaidu(
+    public void track(
             @RequestParam Map<String, String> allParams,
             @PathVariable("advertiserCode") Long advertiserCode,
             HttpServletResponse response) {
         log.info("接收监测请求 | params={}", allParams);
         try {
             // 2. 保存点击追踪记录
-            facadeService.saveClickTrace(advertiserCode,allParams);
+            facadeService.saveClickTrace(advertiserCode, allParams);
             // 3. 返回 200 OK
             response.setStatus(HttpServletResponse.SC_OK);
         } catch (Exception e) {
@@ -45,4 +51,22 @@ public class TrackingController {
         }
     }
 
+    /**
+     * 绑定企微用户与线索
+     */
+    @PostMapping("/bind/qwTrack")
+    public Result<String> qwExternalIdBindTrack(@RequestBody QwExternalIdBindTrackReq req) {
+        facadeService.qwExternalIdBindTrack(req);
+        return Result.success();
+    }
+
+    /**
+     * 绑定企微用户与线索
+     */
+    @PostMapping("/bind/qwTrack/test/{id1}/{id2}")
+    public Result<String> qwExternalIdBindTrack1(@PathVariable String id1, @PathVariable String id2) {
+        boolean success = conversionService.reportTrackingToAdvertiser(SystemEventTypeEnum.getByCode(id1), id2);
+        return Result.success();
+    }
+
 }

+ 6 - 4
fs-ad-new-api/src/main/java/com/fs/app/controller/WeChatController.java

@@ -50,7 +50,8 @@ public class WeChatController {
             try {
                 String access_token = advMiniConfig.getAccessToken();
                 // 判断token是否过期
-                if (advMiniConfig.getExpiresIn().isBefore(LocalDateTime.now().plusMinutes(10))) {
+                if (LocalDateTime.now().plusMinutes(10).isAfter(advMiniConfig.getExpiresIn())) {
+                    // 提前10分钟刷新Token
                     HttpResponse execute2 = HttpRequest.get("https://api.weixin.qq.com/cgi-bin/token")
                             .form("grant_type", "client_credential")
                             .form("appid", advMiniConfig.getAppId())
@@ -59,15 +60,16 @@ public class WeChatController {
                             .execute();
                     JSONObject obj = JSONObject.parseObject(execute2.body());
                     access_token = obj.getString("access_token");
+                    log.info("getSchemeUrl:{}", obj);
                     advMiniConfig.setAccessToken(access_token);
-                    advMiniConfig.setExpiresIn(LocalDateTime.now().plusSeconds(obj.getLong("expires_in")));
+                    advMiniConfig.setExpiresIn(LocalDateTime.now().plusSeconds(obj.getInteger("expires_in")));
                     advMiniConfigService.updateById(advMiniConfig);
                 }
                 Map<String, Object> map = new HashMap<>();
                 Map<String, Object> map2 = new HashMap<>();
-                map2.put("path", "/pages/shopping/productDetails");
+                map2.put("path", "pages/home/productList");
                 map2.put("query", "traceId=" + traceId);
-                map2.put("env_version", "trial");
+                    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)

+ 7 - 3
fs-ad-new-api/src/main/java/com/fs/app/facade/CallbackProcessingFacadeService.java

@@ -1,5 +1,6 @@
 package com.fs.app.facade;
 
+import com.fs.newAdv.dto.req.QwExternalIdBindTrackReq;
 import com.fs.newAdv.dto.req.WeChatLandingIndexReq;
 import com.fs.newAdv.dto.res.LandingIndexRes;
 
@@ -10,20 +11,23 @@ public interface CallbackProcessingFacadeService {
 
     /**
      * 链化追踪点击
+     *
      * @param allParams
      */
-    void saveClickTrace(Long advertiserCode,Map<String,String> allParams);
+    void saveClickTrace(Long advertiserCode, Map<String, String> allParams);
+
     /**
      * 根据站点ID获取落地页信息
      *
-     * @param siteId
      * @return
      */
-    LandingIndexRes getLandingIndexBySiteId(Map<String, String> allParams);
+    LandingIndexRes getLandingIndexBySiteId(String viewUrl,Map<String, String> allParams);
 
     //----------------------code回调---------------------------------
     void gdtGetAuthCode(Integer code, Long state);
 
 
     LandingIndexRes getWxLandingIndexBySiteId(WeChatLandingIndexReq req);
+
+    void qwExternalIdBindTrack(QwExternalIdBindTrackReq req);
 }

+ 54 - 41
fs-ad-new-api/src/main/java/com/fs/app/facade/CallbackProcessingFacadeServiceImpl.java

@@ -1,22 +1,24 @@
 package com.fs.app.facade;
 
+import cn.hutool.core.util.IdUtil;
 import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.core.util.StrUtil;
 import cn.hutool.json.JSONArray;
 import cn.hutool.json.JSONObject;
 import cn.hutool.json.JSONUtil;
 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
-import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
-import com.fs.newAdv.integration.adapter.IAdvertiserAdapter;
-import com.fs.newAdv.integration.factory.AdvertiserHandlerFactory;
 import com.fs.common.exception.base.BusinessException;
 import com.fs.common.utils.RedisUtil;
+import com.fs.common.utils.SnowflakeUtil;
 import com.fs.newAdv.domain.LandingPageTemplate;
 import com.fs.newAdv.domain.Lead;
 import com.fs.newAdv.domain.Site;
+import com.fs.newAdv.dto.req.QwExternalIdBindTrackReq;
 import com.fs.newAdv.dto.req.WeChatLandingIndexReq;
 import com.fs.newAdv.dto.res.LandingIndexRes;
 import com.fs.newAdv.enums.AdvertiserTypeEnum;
+import com.fs.newAdv.integration.adapter.IAdvertiserAdapter;
+import com.fs.newAdv.integration.factory.AdvertiserHandlerFactory;
 import com.fs.newAdv.service.ILandingPageTemplateService;
 import com.fs.newAdv.service.ILeadService;
 import com.fs.newAdv.service.ISiteService;
@@ -72,50 +74,53 @@ public class CallbackProcessingFacadeServiceImpl implements CallbackProcessingFa
     public void saveClickTrace(Long advertiserCode, Map<String, String> allParams) {
         IAdvertiserAdapter advertiserAdapter = handlerFactory.getAdapter(AdvertiserTypeEnum.getByCode(advertiserCode));
         Lead lead = advertiserAdapter.adaptCallbackData(allParams);
-        Lead byTraceId = leadService.getByTraceId(lead.getTraceId());
-        if (ObjectUtil.isNotEmpty(byTraceId)) {
-            throw new BusinessException("监测信息已存在: " + lead.getTraceId());
+        Lead byClickId = leadService.getByClickId(lead.getTraceId());
+        if (ObjectUtil.isNotEmpty(byClickId)) {
+            log.info("线索已存在:{}", lead.getTraceId());
+            return;
         }
         lead.setStatus(0);
         lead.setClickTrigger(1);
         lead.setTraceRawParams(JSONUtil.toJsonStr(allParams));
+        lead.setTraceId(SnowflakeUtil.randomUUID());
         boolean saved = leadService.save(lead);
         if (!saved) {
             log.error("线索保存失败:{}", lead);
-            throw new RuntimeException("线索保存失败");
         }
     }
 
     private String getTraceIdByAdvertiser(AdvertiserTypeEnum byCode, Map<String, String> allParams) {
-        String traceId;
+        String clickId;
         switch (byCode) {
             case OCEANENGINE:
             case TENCENT:
+                clickId = allParams.get("clickid");
+                break;
             case OPPO:
-                traceId = allParams.get("click_id");
+                clickId = allParams.get("tid");
                 break;
             case BAIDU:
-                traceId = allParams.get("bd_vid");
+                clickId = allParams.get("bd_vid");
                 break;
             case VIVO:
-                traceId = allParams.get("requestId");
+                clickId = allParams.get("requestId");
                 break;
             case IQIYI:
-                traceId = allParams.get("traceId");
+                clickId = allParams.get("traceId");
                 break;
             default:
-                traceId = "ylrz_test";
+                clickId = "ylrz_test";
         }
-        if (StrUtil.isEmpty(traceId)) {
-            traceId = "ylrz_test";
+        if (StrUtil.isEmpty(clickId)) {
+            clickId = "ylrz_test"+ IdUtil.randomUUID();
         }
 
-        return traceId;
+        return clickId;
     }
 
     @Override
     @Transactional(rollbackFor = Exception.class)
-    public LandingIndexRes getLandingIndexBySiteId(Map<String, String> allParams) {
+    public LandingIndexRes getLandingIndexBySiteId(String viewUrl,Map<String, String> allParams) {
         // 站点信息
         String paramsSiteId = allParams.get("siteId");
         if (ObjectUtil.isEmpty(paramsSiteId)) {
@@ -131,25 +136,25 @@ public class CallbackProcessingFacadeServiceImpl implements CallbackProcessingFa
         // 广告商信息
         Long advertiserId = byId.getAdvertiserId();
         // 访问链路id
-        String traceId = getTraceIdByAdvertiser(Objects.requireNonNull(AdvertiserTypeEnum.getByCode(advertiserId)), allParams);
+        String clickId = getTraceIdByAdvertiser(Objects.requireNonNull(AdvertiserTypeEnum.getByCode(advertiserId)), allParams);
         // 线索信息
-        Lead byTraceId = leadService.getByTraceId(traceId);
-        boolean isNewLead = ObjectUtil.isEmpty(byTraceId);
+        Lead lead = leadService.getByClickId(clickId);
+        boolean isNewLead = ObjectUtil.isEmpty(lead);
         if (isNewLead) {
             IAdvertiserAdapter advertiserAdapter = handlerFactory.getAdapter(AdvertiserTypeEnum.getByCode(advertiserId));
-            byTraceId = advertiserAdapter.adaptCallbackData(allParams);
-            byTraceId.setAdvertiserId(advertiserId);
-            byTraceId.setSiteId(siteId);
-            byTraceId.setTraceId(traceId);
+            lead = advertiserAdapter.adaptCallbackData(allParams);
+            lead.setAdvertiserId(advertiserId);
+            lead.setSiteId(siteId);
+            lead.setClickId(clickId);
             // 设置站点和落地页的关联
-            setSiteByIdeaId(siteId, byTraceId.getIdeaId());
+            setSiteByIdeaId(siteId, lead.getIdeaId());
         } else {
             // 检查站点和广告商信息是否异常
-            if (!Objects.equals(byTraceId.getSiteId(), siteId)) {
-                log.info("落地页站点信息异常:{}---{}", byTraceId.getSiteId(), siteId);
+            if (!Objects.equals(lead.getSiteId(), siteId)) {
+                log.info("落地页站点信息异常:{}---{}", lead.getSiteId(), siteId);
             }
-            if (!Objects.equals(byTraceId.getAdvertiserId(), advertiserId)) {
-                log.info("落地页广告商信息异常:{}---{}", byTraceId.getAdvertiserId(), advertiserId);
+            if (!Objects.equals(lead.getAdvertiserId(), advertiserId)) {
+                log.info("落地页广告商信息异常:{}---{}", lead.getAdvertiserId(), advertiserId);
             }
         }
         // 模板缓存
@@ -170,26 +175,28 @@ public class CallbackProcessingFacadeServiceImpl implements CallbackProcessingFa
         LandingPageTemplate landingPageTemplate = landingPageTemplateService.getById(byId.getLaunchPageId());
         JSONObject jsonObject = JSONUtil.parseObj(landingPageTemplate.getTemplateData());
         // 替换二维码链接
-        updateQrCodeInTemplate(jsonObject, traceId, byId, byTraceId);
+        updateQrCodeInTemplate(jsonObject, byId, lead);
         String templateData = JSONUtil.toJsonStr(jsonObject);
 
         // 保存或更新 线索信息
         LocalDateTime now = LocalDateTime.now();
-        byTraceId.setLandingPageRawParams(JSONUtil.toJsonStr(allParams));
-        byTraceId.setLandingPageTrigger(1);
-        byTraceId.setLandingPageTs(now);
-        byTraceId.setUpdateTime(now);
+        lead.setLandingPageRawParams(JSONUtil.toJsonStr(allParams));
+        lead.setLandingPageTrigger(1);
+        lead.setLandingPageTs(now);
+        lead.setUpdateTime(now);
+        lead.setViewUrl(viewUrl);
         if (isNewLead) {
-            leadService.save(byTraceId);
+            lead.setTraceId(SnowflakeUtil.randomUUID());
+            leadService.save(lead);
         } else {
-            leadService.updateById(byTraceId);
+            leadService.updateById(lead);
         }
 
         // 封装返回结果
         LandingIndexRes res = new LandingIndexRes();
-        redisUtil.set(TEMPLATE_DATA + traceId, templateData, 24, TimeUnit.HOURS);
+        redisUtil.set(TEMPLATE_DATA + lead.getTraceId(), templateData, 24, TimeUnit.HOURS);
         res.setTemplateData(templateData);
-        res.setTraceId(byTraceId.getTraceId());
+        res.setTraceId(lead.getTraceId());
         return res;
     }
 
@@ -202,7 +209,7 @@ public class CallbackProcessingFacadeServiceImpl implements CallbackProcessingFa
     /**
      * 更新模板中的二维码信息
      */
-    private void updateQrCodeInTemplate(JSONObject templateData, String traceId, Site site, Lead lead) {
+    private void updateQrCodeInTemplate(JSONObject templateData, Site site, Lead lead) {
         JSONArray configList = templateData.getJSONArray("configList");
         if (configList == null || configList.isEmpty()) {
             return;
@@ -227,7 +234,7 @@ public class CallbackProcessingFacadeServiceImpl implements CallbackProcessingFa
                                                Integer allocationRule,
                                                Long allocationRuleId,
                                                Lead byTraceId) {
-        log.info("开始获取广告二维码: {} {} {} {}", launchType, allocationRule, allocationRuleId ,byTraceId);
+        log.info("开始获取广告二维码: {} {} {} {}", launchType, allocationRule, allocationRuleId, byTraceId);
         // 二维码
         String qrCode = "";
         if (allocationRule == 1) {
@@ -285,10 +292,16 @@ public class CallbackProcessingFacadeServiceImpl implements CallbackProcessingFa
     @Override
     public LandingIndexRes getWxLandingIndexBySiteId(WeChatLandingIndexReq req) {
         // 更新授权页访问信息记录
-        leadService.updateAuthIndex(req.getTraceId(),req.getType());
+        leadService.updateAuthIndex(req.getTraceId(), req.getType());
         String templateData = String.valueOf(redisUtil.get(TEMPLATE_DATA + req.getTraceId()));
         LandingIndexRes landingIndexRes = new LandingIndexRes();
         landingIndexRes.setTemplateData(templateData);
         return landingIndexRes;
     }
+
+    @Override
+    public void qwExternalIdBindTrack(QwExternalIdBindTrackReq req) {
+        // 广告线索处理
+        leadService.updateAddMemberLead(req.getQwExternalId(), req.getUnionid());
+    }
 }

+ 3 - 1
fs-ad-new-api/src/main/java/com/fs/app/facade/ConversionServiceImpl.java

@@ -96,7 +96,7 @@ public class ConversionServiceImpl implements IConversionService {
             return false;
         }
 
-        Map<String, Object> params = JSONUtil.toBean(lead.getTraceRawParams(), Map.class);
+        Map<String, Object> params = JSONUtil.toBean(lead.getLandingPageRawParams(), Map.class);
         // 构建回传参数
         Map<String, Object> conversionData = new HashMap<>();
         // ------------------------------通用参数----------
@@ -109,6 +109,8 @@ public class ConversionServiceImpl implements IConversionService {
         conversionData.put("traceId", traceId);
         conversionData.put("eventType", advertiserEventType.getAdvertiserEventType());
         conversionData.put("timestamp", System.currentTimeMillis() / 1000);
+        conversionData.put("viewUrl", lead.getViewUrl());
+
         // 添加平台适应参数
         IAdvertiserAdapter adapter = advertiserHandlerFactory.getAdapter(AdvertiserTypeEnum.getByCode(callbackAccount.getAdvertiserId()));
         adapter.uploadConversionData(conversionData, params);

+ 2 - 14
fs-ad-new-api/src/main/java/com/fs/app/mq/consumer/ConversionTrackingMessageConsumer.java

@@ -2,17 +2,13 @@ package com.fs.app.mq.consumer;
 
 import com.fs.app.facade.IConversionService;
 import com.fs.common.annotation.DistributeLock;
-import com.fs.common.utils.RedisUtil;
-import com.fs.common.utils.TraceIdUtil;
 import com.fs.newAdv.constant.ConversionTrackingMessage;
 import com.fs.newAdv.constant.MqTopicConstant;
 import com.fs.newAdv.enums.SystemEventTypeEnum;
-import com.fs.newAdv.mapper.ConversionLogMapper;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.rocketmq.spring.annotation.ConsumeMode;
 import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
 import org.apache.rocketmq.spring.core.RocketMQListener;
-import org.jboss.logging.MDC;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
@@ -25,23 +21,17 @@ import org.springframework.stereotype.Component;
 @Component
 @RocketMQMessageListener(
         topic = MqTopicConstant.CONVERSION_TRACKING_TOPIC,
-        consumerGroup = MqTopicConstant.CONVERSION_TRACKING_TOPIC_CONSUMER_GROUP,
+        consumerGroup = MqTopicConstant.CONVERSION_TRACKING_GROUP,
         // 并发消费模式(多线程并发消费,线程数由RocketMQ自动管理)
         consumeMode = ConsumeMode.CONCURRENTLY,
         // 最大重试次数(RocketMQ默认16次)
-        maxReconsumeTimes = 16
+        maxReconsumeTimes = 3
 )
 public class ConversionTrackingMessageConsumer implements RocketMQListener<ConversionTrackingMessage> {
 
     @Autowired
     private IConversionService conversionService;
 
-    @Autowired
-    private RedisUtil redisUtil;
-
-    @Autowired
-    private ConversionLogMapper conversionLogMapper;
-
     /**
      * 消费转化消息
      *
@@ -50,8 +40,6 @@ public class ConversionTrackingMessageConsumer implements RocketMQListener<Conve
     @Override
     @DistributeLock(scene = "mq", keyExpression = "#message.traceId", waitTime = 0, errorMsg = "重复消费")
     public void onMessage(ConversionTrackingMessage message) {
-        TraceIdUtil.put(message.getTrackId());
-
         String traceId = message.getTraceId();
         SystemEventTypeEnum eventType = message.getEventType();
 

+ 54 - 0
fs-ad-new-api/src/main/java/com/fs/framework/aspectj/RocketMQTraceIdAspect.java

@@ -0,0 +1,54 @@
+package com.fs.framework.aspectj;
+
+import com.fs.common.utils.TraceIdUtil;
+import com.fs.newAdv.constant.ConversionTrackingMessage;
+import lombok.extern.slf4j.Slf4j;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Pointcut;
+import org.springframework.core.annotation.Order;
+import org.springframework.stereotype.Component;
+
+/**
+ * RocketMQ消费者链路ID切面
+ * 在消费消息前设置链路ID到MDC,保证整个消费链路的日志可追踪
+ *
+ * @author zhangqin
+ */
+@Aspect
+@Component
+@Slf4j
+@Order(1) // 优先于 DistributeLockAspect 执行
+public class RocketMQTraceIdAspect {
+
+    @Pointcut("execution(* com.fs.app.mq.consumer.*.onMessage(..))")
+    public void mqConsumerPointcut() {
+    }
+
+    @Around("mqConsumerPointcut()")
+    public Object around(ProceedingJoinPoint pjp) throws Throwable {
+        String trackId = null;
+        try {
+            // 从消息参数中提取 trackId
+            Object[] args = pjp.getArgs();
+            if (args != null && args.length > 0) {
+                Object message = args[0];
+                if (message instanceof ConversionTrackingMessage) {
+                    trackId = ((ConversionTrackingMessage) message).getTrackId();
+                }
+            }
+            // 设置链路ID到MDC
+            if (trackId != null && !trackId.isEmpty()) {
+                TraceIdUtil.put(trackId);
+            } else {
+                // 如果没有 trackId,则生成一个新的
+                TraceIdUtil.init();
+            }
+            return pjp.proceed();
+        } finally {
+            // 清理MDC,避免线程复用时链路ID污染
+            TraceIdUtil.clear();
+        }
+    }
+}

+ 5 - 5
fs-admin/src/main/java/com/fs/company/controller/CompanyController.java

@@ -134,7 +134,7 @@ public class CompanyController extends BaseController
      * 新增企业
      */
     @PreAuthorize("@ss.hasPermi('company:company:add')")
-    @Log(title = "企业", businessType = BusinessType.INSERT)
+    @Log(title = "企业", businessType = BusinessType.INSERT, isStoreLog = true)
     @PostMapping
     public R add(@RequestBody Company company)
     {
@@ -153,7 +153,7 @@ public class CompanyController extends BaseController
      * 修改企业
      */
     @PreAuthorize("@ss.hasPermi('company:company:edit')")
-    @Log(title = "企业", businessType = BusinessType.UPDATE)
+    @Log(title = "企业", businessType = BusinessType.UPDATE, isStoreLog = true)
     @PutMapping
     public AjaxResult edit(@RequestBody Company company)
     {
@@ -186,7 +186,7 @@ public class CompanyController extends BaseController
      * 删除企业
      */
     @PreAuthorize("@ss.hasPermi('company:company:remove')")
-    @Log(title = "企业", businessType = BusinessType.DELETE)
+    @Log(title = "企业", businessType = BusinessType.DELETE, isStoreLog = true)
 	@DeleteMapping("/{companyIds}")
     public AjaxResult remove(@PathVariable Long[] companyIds)
     {
@@ -244,7 +244,7 @@ public class CompanyController extends BaseController
 
 
     @PreAuthorize("@ss.hasPermi('company:company:recharge')")
-    @Log(title = "企业转账", businessType = BusinessType.INSERT)
+    @Log(title = "企业转账", businessType = BusinessType.INSERT, isStoreLog = true)
     @PostMapping(value = "/recharge")
     @Transactional
     @RepeatSubmit
@@ -270,7 +270,7 @@ public class CompanyController extends BaseController
     }
 
     @PreAuthorize("@ss.hasPermi('company:company:deduct')")
-    @Log(title = "企业扣款", businessType = BusinessType.INSERT)
+    @Log(title = "企业扣款", businessType = BusinessType.INSERT, isStoreLog = true)
     @PostMapping(value = "/deduct")
     @Transactional
     @RepeatSubmit

+ 19 - 1
fs-admin/src/main/java/com/fs/course/controller/FsUserCourseVideoController.java

@@ -15,6 +15,7 @@ import com.fs.course.config.CourseConfig;
 import com.fs.course.domain.FsUserCourse;
 import com.fs.course.domain.FsUserCourseVideo;
 import com.fs.course.mapper.FsUserCourseVideoMapper;
+import com.fs.course.param.BatchEditCoverParam;
 import com.fs.course.param.BatchRedUpdate;
 import com.fs.course.param.BatchVideoSvae;
 import com.fs.course.param.CourseVideoUpdates;
@@ -23,12 +24,13 @@ import com.fs.course.service.IFsUserCourseVideoService;
 import com.fs.course.vo.FsUserCourseVideoChooseVO;
 import com.fs.framework.web.service.TokenService;
 import com.fs.his.vo.OptionsVO;
-import com.fs.qw.vo.SortDayVo;
 import com.fs.system.service.ISysConfigService;
 import com.github.pagehelper.PageHelper;
 import com.github.pagehelper.PageInfo;
+import io.swagger.annotations.ApiOperation;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
 import java.util.HashMap;
@@ -262,4 +264,20 @@ public class FsUserCourseVideoController extends BaseController
         List<FsUserCourseVideoChooseVO> list = fsUserCourseVideoService.getChooseCourseVideoListByMap(params);
         return R.ok().put("data", new PageInfo<>(list));
     }
+
+    @ApiOperation("视频下架")
+    @PreAuthorize("@ss.hasPermi('course:userCourseVideo:batchDown')")
+    @Log(title = "课堂视频", businessType = BusinessType.UPDATE)
+    @PostMapping("/batchDown/{videoIds}")
+    public AjaxResult batchDown(@PathVariable String[] videoIds) {
+        return toAjax(fsUserCourseVideoService.batchDown(videoIds));
+    }
+
+    @ApiOperation("批量修改视频封面图")
+    @PreAuthorize("@ss.hasPermi('course:userCourseVideo:batchEditCover')")
+    @Log(title = "课堂视频", businessType = BusinessType.UPDATE)
+    @PostMapping("/batchEditCover")
+    public AjaxResult batchEditCover(@Validated @RequestBody BatchEditCoverParam param) {
+        return toAjax(fsUserCourseVideoService.batchEditCover(param));
+    }
 }

+ 20 - 0
fs-admin/src/main/java/com/fs/fastGpt/GptRoleController.java

@@ -6,6 +6,7 @@ import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.ServletUtils;
 import com.fs.common.utils.poi.ExcelUtil;
 import com.fs.fastGpt.domain.FastGptRole;
 import com.fs.fastGpt.service.IFastGptRoleService;
@@ -49,6 +50,25 @@ public class GptRoleController extends BaseController
         return getDataTable(list);
     }
 
+    /**
+     * 查询应用列表
+     */
+    @PreAuthorize("@ss.hasPermi('fastGpt:fastGptRole:newList')")
+    @GetMapping("/newList")
+    public TableDataInfo newList(FastGptRole fastGptRole)
+    {
+        startPage();
+        fastGptRole.setCompanyId(fastGptRole.getCompanyId());
+        List<FastGptRoleVO> list = fastGptRoleService.selectFastGptRoleListVONew(fastGptRole);
+        for (FastGptRoleVO fastGptRoleVO : list) {
+            String reminderWords = fastGptRoleVO.getReminderWords();
+            if (reminderWords!=null && reminderWords.length()>110) {
+                fastGptRoleVO.setReminderWords(reminderWords.substring(0,110)+"...");
+            }
+        }
+        return getDataTable(list);
+    }
+
     /**
      * 导出应用列表
      */

+ 15 - 0
fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreOrderScrmController.java

@@ -1154,6 +1154,21 @@ public class FsStoreOrderScrmController extends BaseController {
         return R.ok();
     }
 
+    @ApiOperation("批量审核订单")
+    @Log(title = "订单管理", businessType = BusinessType.UPDATE)
+    @PreAuthorize("@ss.hasPermi('store:storeOrder:batchAudit')")
+    @PostMapping("/batchAudit")
+    public R batchAuditOrder(@Validated @RequestBody FsStoreOrderBatchAuditParam param) {
+        if (param.getOrderIds() == null || param.getOrderIds().isEmpty()) {
+            return R.error("订单ID列表不能为空");
+        }
+        if (param.getIsAudit() == null) {
+            return R.error("审核状态不能为空");
+        }
+        int count = fsStoreOrderService.batchAuditOrder(param);
+        return R.ok("成功审核 " + count + " 条订单");
+    }
+
     private FsStoreOrderDf getDFInfo(String loginAccount) {
         //查询订单账户 判断是否存在该订单账户
         List<FsDfAccount> erpAccounts = fsDfAccountService.selectFsDfAccountList(null);

+ 4 - 4
fs-admin/src/main/java/com/fs/qw/controller/QwCompanyController.java

@@ -87,7 +87,7 @@ public class QwCompanyController extends BaseController
      * 导出企微主体列表
      */
     @PreAuthorize("@ss.hasPermi('qw:qwCompany:export')")
-    @Log(title = "企微主体", businessType = BusinessType.EXPORT)
+    @Log(title = "企微主体", businessType = BusinessType.EXPORT, isStoreLog = true)
     @GetMapping("/export")
     public AjaxResult export(QwCompany qwCompany)
     {
@@ -110,7 +110,7 @@ public class QwCompanyController extends BaseController
      * 新增企微主体
      */
     @PreAuthorize("@ss.hasPermi('qw:qwCompany:add')")
-    @Log(title = "企微主体", businessType = BusinessType.INSERT)
+    @Log(title = "企微主体", businessType = BusinessType.INSERT, isStoreLog = true)
     @PostMapping
     public AjaxResult add(@RequestBody QwCompany qwCompany)
     {
@@ -125,7 +125,7 @@ public class QwCompanyController extends BaseController
      * 修改企微主体
      */
     @PreAuthorize("@ss.hasPermi('qw:qwCompany:edit')")
-    @Log(title = "企微主体", businessType = BusinessType.UPDATE)
+    @Log(title = "企微主体", businessType = BusinessType.UPDATE, isStoreLog = true)
     @PutMapping
     public AjaxResult edit(@RequestBody QwCompany qwCompany)
     {
@@ -136,7 +136,7 @@ public class QwCompanyController extends BaseController
      * 删除企微主体
      */
     @PreAuthorize("@ss.hasPermi('qw:qwCompany:remove')")
-    @Log(title = "企微主体", businessType = BusinessType.DELETE)
+    @Log(title = "企微主体", businessType = BusinessType.DELETE, isStoreLog = true)
 	@DeleteMapping("/{ids}")
     public AjaxResult remove(@PathVariable Long[] ids)
     {

+ 688 - 84
fs-admin/src/main/java/com/fs/qw/controller/QwUserController.java

@@ -1,32 +1,39 @@
 package com.fs.qw.controller;
 
+import cn.hutool.core.util.ObjectUtil;
 import com.alibaba.fastjson.JSON;
 import com.fs.common.annotation.Log;
 import com.fs.common.annotation.RepeatSubmit;
+import com.fs.common.constant.Constants;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.core.domain.model.LoginUser;
 import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.enums.BusinessType;
-import com.fs.common.utils.ServletUtils;
-import com.fs.company.domain.Company;
+import com.fs.common.exception.ServiceException;
+import com.fs.common.exception.user.UserPasswordNotMatchException;
+import com.fs.common.utils.MessageUtils;
+import com.fs.common.utils.poi.ExcelUtil;
 import com.fs.company.domain.CompanyUser;
 import com.fs.company.mapper.CompanyUserMapper;
 import com.fs.company.service.ICompanyUserService;
+import com.fs.company.service.impl.CompanyDeptServiceImpl;
+import com.fs.fastGpt.domain.FastGptRole;
+import com.fs.fastGpt.mapper.FastGptRoleMapper;
+import com.fs.framework.manager.AsyncManager;
+import com.fs.framework.manager.factory.AsyncFactory;
 import com.fs.qw.domain.QwExternalContact;
-import com.fs.qw.domain.QwExternalContactTransferCompanyAudit;
 import com.fs.qw.domain.QwUser;
 import com.fs.qw.mapper.QwCompanyMapper;
 import com.fs.qw.mapper.QwExternalContactMapper;
-import com.fs.qw.param.QwFsUserParam;
-import com.fs.qw.param.QwUserBingParam;
-import com.fs.qw.param.QwUserListParam;
+import com.fs.qw.param.*;
 import com.fs.qw.service.IQwDeptService;
 import com.fs.qw.service.IQwExternalContactTransferCompanyAuditService;
 import com.fs.qw.service.IQwUserService;
 import com.fs.qw.vo.QwOptionsVO;
 import com.fs.qw.vo.QwUserVO;
+import com.fs.qw.vo.UpdateSendTypeVo;
 import com.fs.qwApi.domain.QwExternalContactAllListResult;
 import com.fs.qwApi.domain.inner.ExternalContact;
 import com.fs.qwApi.domain.inner.ExternalContactInfo;
@@ -34,14 +41,18 @@ import com.fs.qwApi.domain.inner.FollowInfo;
 import com.fs.qwApi.param.QwExternalListParam;
 import com.fs.qwApi.service.QwApiService;
 import com.fs.voice.utils.StringUtil;
+import com.github.pagehelper.PageHelper;
+import com.github.pagehelper.PageInfo;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
 import org.springframework.web.bind.annotation.*;
 
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
+import javax.annotation.Resource;
+import java.util.*;
 import java.util.stream.Collectors;
 
 /**
@@ -77,25 +88,612 @@ public class QwUserController extends BaseController {
     @Autowired
     private QwExternalContactMapper qwExternalContactMapper;
 
-    @GetMapping("/getQwUserAll")
-    public AjaxResult getQwUserAll(){
-        return AjaxResult.success(qwUserService.getQwUserAll());
+    @Autowired
+    private CompanyDeptServiceImpl companyDeptService;
+
+    @Resource
+    private AuthenticationManager authenticationManager;
+    @Autowired
+    private FastGptRoleMapper fastGptRoleMapper;
+
+    /**
+     * 查询企微员工列表
+     */
+    @PreAuthorize("@ss.hasPermi('qw:user:staffList')")
+    @GetMapping("/staffList")
+    public TableDataInfo staffList(QwUserListParam qwUser) {
+
+        // 添加企微部门查询条件
+        Long deptId = qwUser.getDeptId();
+        if(deptId!=null && qwUser.getCorpId()!=null){
+            List<Long> qwDeptIdList = new ArrayList<>();
+            if (deptId!=null){
+                qwDeptIdList.add(deptId);
+            }
+            // 本部门的下级部门
+            List<Long> deptList = qwUserService.selectDeptByParentId(deptId,qwUser.getCorpId());
+            if (!deptList.isEmpty()){
+                qwDeptIdList.addAll(deptList);
+            }
+            qwUser.setQwDeptIdList(qwDeptIdList);
+        }
+        startPage();
+        List<QwUserVO> list = qwUserService.selectQwUserListStaffVO(qwUser);
+        return getDataTable(list);
     }
 
     /**
-     * 获取企微信息
-     * **/
-    @GetMapping("/getQwUserInfo")
-    public R getQwUserInfo(QwFsUserParam param){
-        return R.ok().put("data",qwUserService.getQwUserInfo(param));
+     * 查询企微员工列表
+     */
+    @PreAuthorize("@ss.hasPermi('qw:user:staffListPost')")
+    @PostMapping("/staffListPost")
+    public TableDataInfo staffListPost(@RequestBody QwUserListParam qwUser) {
+        // 添加企微部门查询条件
+        Long deptId = qwUser.getDeptId();
+        if(deptId!=null && qwUser.getCorpId()!=null){
+            List<Long> qwDeptIdList = new ArrayList<>();
+            qwDeptIdList.add(deptId);
+            // 本部门的下级部门
+            List<Long> deptList = qwUserService.selectDeptByParentId(deptId,qwUser.getCorpId());
+            if (!deptList.isEmpty()){
+                qwDeptIdList.addAll(deptList);
+            }
+            qwUser.setQwDeptIdList(qwDeptIdList);
+        }
+        startPage();
+        List<QwUserVO> list = qwUserService.selectQwUserListStaffVO(qwUser);
+        return getDataTable(list);
+    }
+
+    /**
+     * 查询我的企微员工列表
+     */
+    @PreAuthorize("@ss.hasPermi('qw:user:myStaffList')")
+    @GetMapping("/myStaffList")
+    public TableDataInfo myStaffList(QwUserListParam qwUser) {
+        startPage();
+        qwUser.setCompanyId(qwUser.getCompanyId());
+        List<QwUserVO> list = qwUserService.selectQwUserListStaffVO(qwUser);
+        return getDataTable(list);
+    }
+
+
+    /**
+     * 导出企微员工列表
+     * @param qwUser
+     * @return AjaxResult
+     */
+    @PreAuthorize("@ss.hasPermi('qw:user:export')")
+    @Log(title = "企微员工", businessType = BusinessType.EXPORT)
+    @GetMapping("/exportStaff")
+    public AjaxResult export(QwUserListParam qwUser) {
+        qwUser.setCompanyId(qwUser.getCompanyId());
+        List<QwUserVO> list = qwUserService.selectQwUserListStaffVO(qwUser);
+        ExcelUtil<QwUserVO> util = new ExcelUtil<QwUserVO>(QwUserVO.class);
+        return util.exportExcel(list, "企微员工数据");
+    }
+
+    /**
+     * 导出企微用户列表
+     */
+    @PreAuthorize("@ss.hasPermi('qw:user:export')")
+    @Log(title = "企微用户", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(QwUser qwUser) {
+        qwUser.setCompanyId(qwUser.getCompanyId());
+        List<QwUser> list = qwUserService.selectQwUserList(qwUser);
+        ExcelUtil<QwUser> util = new ExcelUtil<QwUser>(QwUser.class);
+        return util.exportExcel(list, "企微用户数据");
+    }
+
+
+    /**
+     * 查询我的部门 企业微信员工列表
+     */
+    @PreAuthorize("@ss.hasPermi('qw:user:myDepartList')")
+    @GetMapping("/myDepartList")
+    public TableDataInfo myDepartList(QwUserListParam qwUser)
+    {
+
+        qwUser.setCompanyId(qwUser.getCompanyId());
+//        qwUser.setUserType(loginUser.getUser().getUserType());
+        List<Long> combinedList = new ArrayList<>();
+        //本部门
+        Long deptId = getLoginUser().getUser().getDeptId();
+        if (deptId!=null){
+            combinedList.add(deptId);
+        }
+        //本部门的下级部门
+        List<Long> deptList = companyDeptService.selectCompanyDeptByParentId(deptId);
+        if (!deptList.isEmpty()){
+            combinedList.addAll(deptList);
+        }
+
+        // 添加企微部门查询条件
+        Long qwDeptId = qwUser.getDeptId();
+        if(qwDeptId!=null && qwUser.getCorpId()!=null){
+            List<Long> qwDeptIdList = new ArrayList<>();
+            if (qwDeptId!=null){
+                qwDeptIdList.add(qwDeptId);
+            }
+            // 本部门的下级部门
+            List<Long> qwDeptList = qwUserService.selectDeptByParentId(qwDeptId,qwUser.getCorpId());
+            if (!qwDeptList.isEmpty()){
+                qwDeptIdList.addAll(qwDeptList);
+            }
+            qwUser.setQwDeptIdList(qwDeptIdList);
+        }
+
+
+        qwUser.setCuDeptIdList(combinedList);
+//        qwUser.setUserType(loginUser.getUser().getUserType());
+
+        startPage();
+        List<QwUserVO> list = qwUserService.selectQwUserListStaffVO(qwUser);
+        return getDataTable(list);
+    }
+
+    @PreAuthorize("@ss.hasPermi('qw:user:login')")
+    @PostMapping("/loginQwIpad")
+    public R loginQwIpad(@RequestBody QwLoginHookParam loginParam){
+        return qwUserService.loginQwIpad(loginParam);
+    }
+
+
+    /**
+     * 查询部门下的 企业微信账号
+     */
+    @PostMapping("/getQwUserByDept")
+    public R getQwUserByDept(@RequestBody QwUserByDeptParam deptParam){
+        deptParam.setCompanyId(deptParam.getCompanyId());
+        return R.ok().put("data",qwUserService.getQwUserByDept(deptParam)) ;
+    }
+
+
+
+
+    @PreAuthorize("@ss.hasPermi('qw:user:login')")
+    @PostMapping("/getQwIpad")
+    @RepeatSubmit
+    public R getQwIpad(@RequestBody QwLoginHookParam loginParam){
+        return qwUserService.getQwIpad(loginParam);
+    }
+
+    @PreAuthorize("@ss.hasPermi('qw:user:login')")
+    @PostMapping("/delQwIpad")
+    @RepeatSubmit
+    public R delQwIpad(@RequestBody QwLoginHookParam loginParam){
+        return qwUserService.delQwIpad(loginParam);
+    }
+
+    @PreAuthorize("@ss.hasPermi('qw:user:login')")
+    @PostMapping("/qrCodeStatus")
+    public R qrCodeStatus(@RequestBody QwLoginHookParam loginParam){
+        return qwUserService.qrCodeStatus(loginParam);
+    }
+    //输入验证码
+    @PostMapping("/qrCodeVerify")
+    public R qrCodeVerify(@RequestBody QwLoginHookParam loginParam){
+        return qwUserService.qrCodeVerify(loginParam);
+    }
+
+    @PostMapping("/outLoginQwIpad")
+    public R outLoginQwIpad(@RequestBody QwLoginHookParam loginParam){
+        return qwUserService.outLoginQwIpad(loginParam);
+    }
+
+    @PostMapping("/twoCode")
+    public R twoCode(@RequestBody QwLoginHookParam loginParam){
+        return qwUserService.getTwoCode(loginParam);
+    }
+    @PostMapping("/twoCodeStatus")
+    public R TwoCodeStatus(@RequestBody QwLoginHookParam loginParam){
+        return qwUserService.getTwoCodeStatus(loginParam);
+    }
+
+    @PostMapping("/getQwIpadStatus")
+    public R getQwIpadStatus(@RequestBody QwLoginHookParam loginParam){
+        return qwUserService.getLoginQwIpadStatus(loginParam);
+    }
+    /**
+     * 直接授权key
+     */
+    @PreAuthorize("@ss.hasPermi('qw:user:authAppKey')")
+    @PostMapping("/authAppKey")
+    public R authAppKey(@RequestBody QwUser param){
+        return qwUserService.authAppKey(param);
+    }
+
+    /**
+     * 输入授权key
+     */
+    @PreAuthorize("@ss.hasPermi('qw:user:authAppKey')")
+    @PostMapping("/handleInputAuthAppKey")
+    public R handleInputAuthAppKey(@RequestBody QwUser param){
+        return qwUserService.handleInputAuthAppKey(param);
     }
 
-   @GetMapping("/getMyQwCompanyList")
-    public R getMyQwCompanyList()
+
+    /**
+     * 登录企业微信(发起登录)
+     */
+    @PreAuthorize("@ss.hasPermi('qw:user:login')")
+    @PostMapping("/loginQwCode")
+    public R loginQwCode(@RequestBody QwLoginParam loginParam){
+        return qwUserService.loginQwCode(loginParam);
+    }
+
+    /**
+     * 登录请求-刷新获取二维码
+     */
+    @PreAuthorize("@ss.hasPermi('qw:user:login')")
+    @PostMapping("/loginQwCodeUrl")
+    public R loginQwCodeUrl(@RequestBody QwLoginParam loginParam){
+        return qwUserService.loginQwCodeUrl(loginParam);
+    }
+    /**
+     * 取redis里的登录二维码
+     */
+    @PreAuthorize("@ss.hasPermi('qw:user:login')")
+    @PostMapping("/getQwCodeUrl")
+    public R getQwCodeUrl(@RequestBody QwLoginParam loginParam) throws InterruptedException {
+        return qwUserService.getQwCodeUrl(loginParam);
+    }
+
+    /**
+     * 登录企业微信(传输验证信息)
+     */
+    @PreAuthorize("@ss.hasPermi('qw:user:login')")
+    @PostMapping("/loginQwCodeMsg")
+    public R loginQwCodeMsg(@RequestBody QwLoginParam loginParam){
+        return qwUserService.loginQwCodeMsg(loginParam);
+    }
+
+    /**
+     * 退出企业微信(退出插件)
+     */
+    @PreAuthorize("@ss.hasPermi('qw:user:login')")
+    @PostMapping("/logoutQwLogout")
+    public R logoutQwLogout(@RequestBody QwLoginParam loginParam){
+        return qwUserService.logoutQwLogout(loginParam);
+    }
+
+//    /**
+//     * 企业微信(修改登录状态)
+//     */
+//    @PreAuthorize("@ss.hasPermi('qw:user:login')")
+//    @PostMapping("/modifyLoginQwStatus")
+//    public R modifyLoginQwStatus(@RequestBody QwLoginParam loginParam){
+//        return qwUserService.modifyLoginQwStatus(loginParam);
+//    }
+//
+    /**
+     * 查询企业微信登录状态
+     */
+    @PreAuthorize("@ss.hasPermi('qw:user:login')")
+    @PostMapping("/getLoginQwStatus")
+    public R getLoginQwStatus(@RequestBody QwLoginParam loginParam){
+        return qwUserService.getLoginQwStatus(loginParam);
+    }
+
+    @PutMapping
+    public AjaxResult updateUser(@RequestBody QwUser qwUser){
+        return toAjax(qwUserService.updateQwUser(qwUser));
+    }
+
+    /**
+     * 自动发课启用禁用
+     * @param qwUser
+     * @return
+     */
+    @PostMapping("/updateIsAuto")
+    @PreAuthorize("@ss.hasPermi('qw:user:isauto')")
+    public AjaxResult updateIsAuto(@RequestBody QwUser qwUser){
+        return toAjax(qwUserService.updateQwUser(qwUser));
+    }
+
+    /**
+     * 企业微信员工账号 绑定 云主机
+     */
+    @PreAuthorize("@ss.hasPermi('qw:user:loginIp')")
+    @Log(title = "绑定 云主机", businessType = BusinessType.INSERT)
+    @GetMapping("/qwBindCloudHost/{appKey}")
+    public R qwBindCloudHost(@PathVariable("appKey") String appKey){
+        return qwUserService.qwBindCloudHost(appKey);
+    }
+
+    /**
+     * 获取云主机的账密
+     *
+     */
+    @PreAuthorize("@ss.hasPermi('qw:user:cloudAP')")
+    @PostMapping("/selectCloudAP")
+    public R selectCloudAP(@RequestBody QwCloudAPParam param) throws Exception {
+        return qwUserService.selectCloudAP(param);
+    }
+
+    /**
+     * 根据销售账号密码 获取 他的所有企业微信账号以及云主机和账号密码
+     */
+    @PostMapping("/selectCloudByCompany")
+    public R selectCloudByCompany(@RequestBody QwCloudIPByCompanyParam param) throws Exception {
+
+        // 用户验证
+        Authentication authentication = null;
+        try
+        {
+            // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
+            authentication = authenticationManager
+                    .authenticate(new UsernamePasswordAuthenticationToken(param.getCompanyAdmin(), param.getCompanyPassWord()));
+        }
+        catch (Exception e)
+        {
+            if (e instanceof BadCredentialsException)
+            {
+                AsyncManager.me().execute(AsyncFactory.recordLogininfor(param.getCompanyAdmin(), Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
+                throw new UserPasswordNotMatchException();
+            }
+            else
+            {
+                AsyncManager.me().execute(AsyncFactory.recordLogininfor(param.getCompanyAdmin(), Constants.LOGIN_FAIL, e.getMessage()));
+                throw new ServiceException(e.getMessage());
+            }
+        }
+        LoginUser loginUser=(LoginUser) authentication.getPrincipal();
+
+        return qwUserService.selectCloudByCompany(loginUser.getUser().getCompanyId(),loginUser.getUser().getUserId());
+    }
+
+
+    /**
+     * 企业微信员工账号 绑定 云主机
+     */
+    @PreAuthorize("@ss.hasPermi('qw:user:bindIp')")
+    @GetMapping("/qwBindCloudHostByIp/{appKey}/{IP}")
+    public R qwBindCloudHostByIp(@PathVariable("appKey") String appKey,@PathVariable("IP") String IP){
+        return qwUserService.qwBindCloudHostByIp(appKey,IP);
+    }
+
+    /**
+     * 企业微信员工账号 解除绑定 云主机
+     */
+    @PreAuthorize("@ss.hasPermi('qw:user:loginIpOut')")
+    @Log(title = "解除绑定 云主机", businessType = BusinessType.UPDATE)
+    @GetMapping("/qwUnbindCloudHost/{appKey}")
+    public R qwUnbindCloudHost(@PathVariable("appKey") String appKey){
+        return qwUserService.qwUnbindCloudHost(appKey);
+    }
+
+    /**
+     * 根据销售名称模糊查询
+     * @param qwUserName  名称
+     * @return  list
+     */
+    @GetMapping("/getQwUserListLikeName")
+    public R getQwUserListLikeName(@RequestParam(required = false) String qwUserName,
+                                   @RequestParam(required = false, defaultValue = "1") Integer pageNum,
+                                   @RequestParam(required = false, defaultValue = "10") Integer pageSize) {
+        Map<String,Object> params = new HashMap<>();
+        params.put("qwUserName", qwUserName);
+
+        PageHelper.startPage(pageNum, pageSize);
+        List<QwOptionsVO> qwUserList = companyUserService.selectQwUserListLikeName(params);
+        return R.ok().put("data", new PageInfo<>(qwUserList));
+    }
+
+    /**
+     * 查询企微用户列表
+     */
+//    @PreAuthorize("@ss.hasPermi('qw:user:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(QwUserListParam qwUser)
     {
-        List<QwOptionsVO> list = qwUserService.selectQwCompanyListOptionsVOAll();
+        startPage();
+        qwUser.setCompanyId(qwUser.getCompanyId());
+        if (ObjectUtil.isNotEmpty(qwUser.getIsRemark())&&qwUser.getIsRemark().equals("1")){
+            qwUser.setCompanyUserId(getLoginUser().getUser().getUserId());
+        }else if (ObjectUtil.isNotEmpty(qwUser.getIsRemark())&&qwUser.getIsRemark().equals("2")){
+            qwUser.setDeptId(getLoginUser().getDeptId());
+            qwUser.setCorpId(null);
+        }
+
+        List<QwUserVO> list = qwUserService.selectQwUserListVO(qwUser);
+        return getDataTable(list);
+    }
+
+    /**
+     * 查询企微用户列表
+     */
+
+    @GetMapping("/userList")
+    public TableDataInfo userList(QwUserListParam qwUser)
+    {
+        startPage();
+//        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+//        qwUser.setCompanyId(loginUser.getCompany().getCompanyId());
+
+        List<QwUserVO> list = qwUserService.selectAllQwUserListVO(qwUser);
+        return getDataTable(list);
+    }
+    //    /**
+//     * 查询我的企微用户列表
+//     */
+//    @PreAuthorize("@ss.hasPermi('qw:user:myList')")
+//    @GetMapping("/myList")
+//    public TableDataInfo myList(QwUserParam qwUser)
+//    {
+//        startPage();
+//        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+//        qwUser.setCompanyId(loginUser.getCompany().getCompanyId());
+//        qwUser.setCompanyUserId(loginUser.getCompany().getUserId());
+//        List<QwUserVO> list = qwUserService.selectQwUserListVO(qwUser);
+//        return getDataTable(list);
+//    }
+    @GetMapping("/getQwAllUserList")
+    public R getQwAllUserList(@RequestParam String corpId, @RequestParam Long companyId)
+    {
+        List<QwUserVO> list = companyUserService.selectCompanyQwUserList(corpId, companyId);
         return  R.ok().put("data",list);
     }
+    /**
+     * 查询企微用户列表-下拉框
+     */
+    @GetMapping("/qwList")
+    public TableDataInfo getQwList(QwUser qwUser)
+    {
+        startPage();
+
+        if(ObjectUtil.notEqual(qwUser.getDisableCompanyId(),1)){
+            qwUser.setCompanyId(qwUser.getCompanyId());
+        }
+        List<QwUser> list = qwUserService.selectQwUserList(qwUser);
+        return getDataTable(list);
+    }
+    /**
+     * 查询企微员工列表-用于员工管理绑定
+     */
+    @GetMapping("/getQwUserList")
+    public R getQwUserList()
+    {
+        QwUserParam qwUser = new QwUserParam();
+        List<String> strings = qwCompanyMapper.selectQwCompanyCorpIdListByCompanyId(getLoginUser().getUser().getCompanyId());
+        qwUser.setCorpId(strings);
+        if (strings==null||strings.size()==0){
+            return  R.ok().put("data",null);
+        }
+        qwUser.setIsDel(0);
+        List<QwUserVO> list = qwUserService.selectQwUserListBindVO(qwUser);
+        return  R.ok().put("data",list);
+    }
+
+    @GetMapping("/getMyQwUserList")
+    public R getMyQwUserList()
+    {
+        List<QwOptionsVO> list = qwUserService.selectQwUserListOptionsVOByCompanyUserId(getLoginUser().getUser().getUserId());
+        return  R.ok().put("data",list);
+    }
+    @GetMapping("/getMyQwCompanyList/{companyId}")
+    public R getMyQwCompanyList(@PathVariable Long companyId)
+    {
+        List<QwOptionsVO> list = qwUserService.selectQwCompanyListOptionsVOByCompanyId(companyId);
+        return  R.ok().put("data",list);
+    }
+    /**
+     * 获取企微用户详细信息
+     */
+
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(qwUserService.selectQwUserVOById(id));
+    }
+    /**
+     * 批量查询 企微用户详细信息
+     */
+    @GetMapping(value = "/getInfo/{ids}")
+    public AjaxResult getInfoByIds(@PathVariable("ids") Long[] ids)
+    {
+        return AjaxResult.success(qwUserService.selectQwUserVOByIds(ids));
+    }
+
+
+//    /**
+//     * 新增企微用户
+//     */
+//    @PreAuthorize("@ss.hasPermi('qw:user:add')")
+//    @Log(title = "企微用户", businessType = BusinessType.INSERT)
+//    @PostMapping
+//    public R add()
+//    {
+//        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+//
+//        return R.ok(qwUserService.syncQwUser(loginUser.getCompany().getCompanyId()));
+//    }
+
+
+
+    /**
+     * 同步企微用户
+     */
+    @RepeatSubmit
+    @PreAuthorize("@ss.hasPermi('qw:user:sync')")
+    @Log(title = "企微用户", businessType = BusinessType.INSERT)
+    @PostMapping("sync/{corpId}")
+    public R sync(@PathVariable String corpId)
+    {
+        List<String> strings = qwCompanyMapper.selectQwCompanyCorpIdListByCompanyId(getLoginUser().getUser().getCompanyId());
+        for (String string : strings) {
+
+            if (string.equals(corpId)){
+                qwUserService.syncQwUser(string);
+                qwDeptService.insertOrUpdateQwDept(string);
+                logger.info("同步完成");
+            }
+        }
+        return R.ok();
+    }
+    @RepeatSubmit
+    @PreAuthorize("@ss.hasPermi('qw:user:sync')")
+    @Log(title = "同步企微用户名称", businessType = BusinessType.INSERT)
+    @PostMapping("syncName/{corpId}")
+    public R syncName(@PathVariable String corpId)
+    {
+        List<String> strings = qwCompanyMapper.selectQwCompanyCorpIdListByCompanyId(getLoginUser().getUser().getCompanyId());
+        for (String string : strings) {
+            if (string.equals(corpId)){
+                qwUserService.syncQwUserName(string);
+            }
+        }
+        return R.ok();
+    }
+    /**
+     * 绑定AI客服
+     */
+//    @PreAuthorize("@ss.hasPermi('qw:user:bindAi')")
+    @Log(title = "企微用户", businessType = BusinessType.UPDATE)
+    @PutMapping("/bindAi")
+    @RepeatSubmit
+    public R edit(@RequestBody QwUserBindAi param)
+    {
+        QwUser qwUser=new QwUser();
+        qwUser.setId(param.getId());
+        qwUser.setFastGptRoleId(param.getFastGptRoleId());
+        FastGptRole role = fastGptRoleMapper.selectFastGptRoleByRoleId(param.getFastGptRoleId());
+
+        if (role.getBindCorpId()!=null){
+            if (role.getBindCorpId().equals(param.getCorpId())){
+                qwUserService.updateQwUser(qwUser);
+            }else {
+                return R.error("该角色已绑定其他企业");
+            }
+        }else {
+            int i = qwUserService.updateQwUser(qwUser);
+
+            FastGptRole fastGptRole=new FastGptRole();
+            fastGptRole.setRoleId(param.getFastGptRoleId());
+            fastGptRole.setBindCorpId(param.getCorpId());
+            fastGptRoleMapper.updateFastGptRole(fastGptRole);
+
+            if (i>0){
+                return R.ok();
+            }else {
+                return R.error("绑定失败");
+            }
+        }
+
+        return R.ok();
+    }
+
+    /**
+     * 解除应用绑定
+     */
+//    @PreAuthorize("@ss.hasPermi('fastGpt:fastGptRole:relieve')")
+    @Log(title = "解除应用", businessType = BusinessType.UPDATE)
+    @GetMapping("/relieveFastGptRoleById/{id}")
+    public R relieveFastGptRoleById(@PathVariable("id") Long id)
+    {
+        return qwUserService.relieveFastGptRoleById(id);
+    }
 
     /**
      * 绑定企微用户
@@ -188,6 +786,7 @@ public class QwUserController extends BaseController {
         }
         return R.error("绑定失败");
 
+
     }
 
 
@@ -201,6 +800,52 @@ public class QwUserController extends BaseController {
         syncMyQwExternalContact(qu,null);
     }
 
+    /** 修改企微用户的欢迎语 */
+    @PostMapping("/weclomeQwUser")
+    public R weclomeQwUser(@RequestBody QwUser qwUser) throws Exception {
+
+
+        return  qwUserService.weclomeQwUser(qwUser);
+    }
+
+    /**
+     * 删除企微用户
+     */
+    @PreAuthorize("@ss.hasPermi('qw:user:remove')")
+    @Log(title = "企微用户", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(qwUserService.deleteQwUserByIds(ids));
+    }
+
+
+    /**
+     * 获取企业微信用户列表
+     */
+    @GetMapping("/qwUserList/{corpId}")
+    public TableDataInfo qwUserList(@PathVariable String corpId)
+    {
+
+//        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+//        Long companyId = loginUser.getCompany().getCompanyId();
+
+        List<QwOptionsVO> list = qwUserService.selectQwUserListOptionsVO(corpId);
+        return getDataTable(list);
+    }
+    @GetMapping("/getQwUserAll")
+    public AjaxResult getQwUserAll(){
+        return AjaxResult.success(qwUserService.getQwUserAll());
+    }
+
+    /**
+     * 获取企微信息
+     * **/
+    @GetMapping("/getQwUserInfo")
+    public R getQwUserInfo(QwFsUserParam param){
+        return R.ok().put("data",qwUserService.getQwUserInfo(param));
+    }
+
     public R  syncMyQwExternalContact(QwUser qwUser,String getNextCursor )  {
 
         String qwUserId = qwUser.getQwUserId();
@@ -286,79 +931,38 @@ public class QwUserController extends BaseController {
         return R.ok();
     }
 
-
-    /**
-     * 同步企微用户
-     */
-    @RepeatSubmit
-    @PreAuthorize("@ss.hasPermi('qw:user:sync')")
-    @Log(title = "企微用户", businessType = BusinessType.INSERT)
-    @PostMapping("sync/{corpId}")
-    public R sync(@PathVariable("corpId") String corpId)
-    {
-
-        List<String> strings = qwCompanyMapper.selectQwCompanyCorpIdListByAll();
-        for (String string : strings) {
-
-            if (string.equals(corpId)){
-                qwUserService.syncQwUser(string);
-                qwDeptService.insertOrUpdateQwDept(string);
-                logger.info("同步完成");
-            }
-        }
-        return R.ok();
-    }
-
-    /**
-     * 获取企微用户详细信息
-     */
-
-    @GetMapping(value = "/{id}")
-    public AjaxResult getInfo(@PathVariable("id") Long id)
-    {
-        return AjaxResult.success(qwUserService.selectQwUserVOById(id));
-    }
-    /**
-     * 批量查询 企微用户详细信息
-     */
-    @GetMapping(value = "/getInfo/{ids}")
-    public AjaxResult getInfoByIds(@PathVariable("ids") Long[] ids)
-    {
-        return AjaxResult.success(qwUserService.selectQwUserVOByIds(ids));
+    //    /**
+//     * 重启云主机
+//     * @return
+//     */
+//    @PutMapping("/restartHost")
+//    public R restartCloudHost(@RequestParam String serverIp) {
+//        return qwUserService.restartCloudHost(serverIp);
+//    }
+    @PostMapping("/updateSendType")
+    public R updateSendType(@RequestBody UpdateSendTypeVo vo) {
+        return qwUserService.updateSendType(vo);
     }
 
-    @RepeatSubmit
-    @PreAuthorize("@ss.hasPermi('qw:user:sync')")
-    @Log(title = "同步企微用户名称", businessType = BusinessType.INSERT)
-    @PostMapping("syncName/{corpId}")
-    public R syncName(@PathVariable("corpId") String corpId)
-    {
-        List<String> strings = qwCompanyMapper.selectQwCompanyCorpIdListByAll();
-        for (String string : strings) {
-            if (string.equals(corpId)){
-                qwUserService.syncQwUserName(string);
-            }
-        }
+    @GetMapping("/changeVideoStatus")
+    public R changeVideoStatus(Long id) {
+        qwUserService.changeVideoStatus(id);
         return R.ok();
     }
 
-
-    /**
-     * 查询企微用户列表
-     */
-
-    @GetMapping("/userList")
-    public TableDataInfo userList(QwUserListParam qwUser)
+    @GetMapping("/companyQwUserlist")
+    public TableDataInfo companyQwUserlist(@RequestParam Long companyId,
+                                           @RequestParam String corpId,
+                                           @RequestParam(required = false) String nickName)
     {
         startPage();
-        List<QwUserVO> list = qwUserService.selectAllQwUserListVO(qwUser);
+        List<QwUserVO> list = qwUserService.selectQwUserListByCompanyIdAndCorpIdAndNickName(companyId, corpId, nickName);
         return getDataTable(list);
     }
 
-    @GetMapping("/getQwAllUserList")
-    public R getQwAllUserList(@RequestParam String corpId, @RequestParam Long companyId)
+    @GetMapping("/updateFastGptRoleStatusById/{id}")
+    public R updateFastGptRoleStatusById(@PathVariable Long id)
     {
-        List<QwUserVO> list = companyUserService.selectCompanyQwUserList(corpId, companyId);
-        return  R.ok().put("data",list);
+        return qwUserService.updateQwUserFastGptRoleStatusById(id);
     }
 }

+ 1 - 1
fs-company/src/main/java/com/fs/company/controller/company/CompanyUserController.java

@@ -199,7 +199,7 @@ public class CompanyUserController extends BaseController {
                     }
                     //是否绑定
                     if(QwStatusEnum.BOUND.getCode() == companyUserQwListVO.getQwStatus()){
-                        if(!companyUserQwListVO.getQwUserId().isEmpty()){
+                        if(!StringUtil.strIsNullOrEmpty(companyUserQwListVO.getQwUserId())){
                             Long[] ids = Arrays.stream(companyUserQwListVO.getQwUserId().split(","))
                                     .map(Long::parseLong)
                                     .toArray(Long[]::new);

+ 6 - 5
fs-company/src/main/java/com/fs/company/controller/live/LiveDataController.java

@@ -15,6 +15,7 @@ import com.fs.live.domain.LiveData;
 import com.fs.live.param.LiveDataParam;
 import com.fs.live.service.ILiveDataService;
 import com.fs.live.vo.ColumnsConfigVo;
+import com.fs.live.vo.LiveDataListVo;
 import com.fs.live.vo.LiveUserDetailExportVO;
 import com.github.pagehelper.PageHelper;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -61,7 +62,7 @@ public class LiveDataController extends BaseController
      */
     @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,
                                         HttpServletRequest request) {
@@ -163,11 +164,11 @@ public class LiveDataController extends BaseController
     @PreAuthorize("@ss.hasPermi('liveData:liveData:export')")
     @Log(title = "直播数据", businessType = BusinessType.EXPORT)
     @GetMapping("/export")
-    public AjaxResult export(LiveData liveData)
+    public AjaxResult export(LiveDataParam param)
     {
-        List<LiveData> list = liveDataService.selectLiveDataList(liveData);
-        ExcelUtil<LiveData> util = new ExcelUtil<LiveData>(LiveData.class);
-        return util.exportExcel(list, "直播数据数据");
+        List<LiveDataListVo> liveDataListVos = liveDataService.exportLiveData(param);
+        ExcelUtil<LiveDataListVo> util = new ExcelUtil<LiveDataListVo>(LiveDataListVo.class);
+        return util.exportExcel(liveDataListVos, "直播数据数据");
     }
 
     /**

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

@@ -66,7 +66,8 @@ public class OrderController extends BaseController
         if(param.getOrderTypeFilter() == null || param.getOrderTypeFilter().equals("2")){
             return getDataTable(new ArrayList<>());
         }
-
+        CompanyUser user = SecurityUtils.getLoginUser().getUser();
+        param.setCompanyId(user.getCompanyId());
         startPage();
         List<MergedOrderVO> list = mergedOrderService.selectMergedOrderList(param);
         for (MergedOrderVO vo : list) {

+ 18 - 4
fs-company/src/main/java/com/fs/hisStore/controller/FsStoreOrderScrmController.java

@@ -30,13 +30,11 @@ import com.fs.hisStore.dto.ExpressInfoDTO;
 import com.fs.hisStore.dto.StoreOrderProductDTO;
 import com.fs.hisStore.enums.OrderLogEnum;
 import com.fs.hisStore.enums.ShipperCodeEnum;
-import com.fs.hisStore.param.FsStoreOrderBindCustomerParam;
-import com.fs.hisStore.param.FsStoreOrderCreateUserParam;
-import com.fs.hisStore.param.FsStoreOrderFinishParam;
-import com.fs.hisStore.param.FsStoreOrderParam;
+import com.fs.hisStore.param.*;
 import com.fs.hisStore.service.*;
 import com.fs.hisStore.vo.*;
 import com.fs.system.service.ISysConfigService;
+import io.swagger.annotations.ApiOperation;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.validation.annotation.Validated;
@@ -473,4 +471,20 @@ public class FsStoreOrderScrmController extends BaseController
         Integer createSalesOrderType = config.getCreateSalesOrderType();
         return R.ok().put("createSalesOrderType",createSalesOrderType);
     }
+
+    @ApiOperation("批量审核订单")
+    @Log(title = "订单管理", businessType = BusinessType.UPDATE)
+    @PreAuthorize("@ss.hasPermi('store:storeOrder:batchAudit')")
+    @PostMapping("/batchAudit")
+    public R batchAuditOrder(@Validated @RequestBody FsStoreOrderBatchAuditParam param) {
+        if (param.getOrderIds() == null || param.getOrderIds().isEmpty()) {
+            return R.error("订单ID列表不能为空");
+        }
+        if (param.getIsAudit() == null) {
+            return R.error("审核状态不能为空");
+        }
+        int count = fsStoreOrderService.batchAuditOrder(param);
+        return R.ok("成功审核 " + count + " 条订单");
+    }
+
 }

+ 8 - 4
fs-live-app/src/main/java/com/fs/live/task/Task.java

@@ -40,6 +40,7 @@ import java.util.stream.Collectors;
 
 import static com.fs.common.constant.LiveKeysConstant.*;
 import static com.fs.common.constant.LiveKeysConstant.LIVE_COUPON_NUM;
+import static com.fs.live.websocket.service.WebSocketServer.USER_ENTRY_TIME_KEY;
 
 @Component
 @AllArgsConstructor
@@ -819,7 +820,6 @@ public class Task {
             if (activeLives == null || activeLives.isEmpty()) {
                 return;
             }
-
             for (Live live : activeLives) {
                 try {
                     Long liveId = live.getLiveId();
@@ -862,18 +862,22 @@ public class Task {
                             }
 
                             // 获取用户的在线观看时长
-                            Long onlineSeconds = user.getOnlineSeconds();
+                            String entryTimeKey = String.format(USER_ENTRY_TIME_KEY, liveId, userId);
+                            Long existingEntryTime = redisCache.getCacheObject(entryTimeKey);
+                            Long onlineSeconds = user.getOnlineSeconds() ==null ? 0L : user.getOnlineSeconds();
+                            if(null != existingEntryTime){
+                                onlineSeconds = onlineSeconds + ((System.currentTimeMillis() - existingEntryTime)/1000);
+                            }
                             if (onlineSeconds == null || onlineSeconds <= 0) {
                                 continue;
                             }
-
                             // 获取用户的 companyId 和 companyUserId
                             LiveUserFirstEntry liveUserFirstEntry =
                                     liveUserFirstEntryService.selectEntityByLiveIdUserIdWithCache(liveId, userId);
                             if (liveUserFirstEntry == null) {
                                 continue;
                             }
-                            
+
                             Long qwUserId = liveUserFirstEntry.getQwUserId();
                             Long externalContactId = liveUserFirstEntry.getExternalContactId();
 

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

@@ -88,7 +88,7 @@ public class WebSocketServer {
     private static Random random = new Random();
 
     // Redis key 前缀:用户进入直播间时间
-    private static final String USER_ENTRY_TIME_KEY = "live:user:entry:time:%s:%s"; // liveId:userId
+    public static final String USER_ENTRY_TIME_KEY = "live:user:entry:time:%s:%s"; // liveId:userId
 
     // 直播间在线用户缓存
 //    private static final ConcurrentHashMap<Long, Integer> liveOnlineUsers = new ConcurrentHashMap<>();
@@ -225,7 +225,7 @@ public class WebSocketServer {
                 }
             } else {
                 // 这个用户A邀请用户b,b的业绩算a的销售的
-                if (companyUserId == -2L) {
+                if (companyId == -2L) {
                     LiveUserFirstEntry clientB = liveUserFirstEntryService.selectEntityByLiveIdUserId(liveId, companyUserId);
                     companyId = clientB.getCompanyId();
                     companyUserId = clientB.getCompanyUserId();

+ 1 - 1
fs-qw-api/Dockerfile

@@ -1,4 +1,4 @@
-FROM openjdk:8-jre
+FROM anolis-registry.cn-zhangjiakou.cr.aliyuncs.com/openanolis/openjdk:8-8.6
 # java版本,最好使用openjdk,而不是类似于Java:1.8
 COPY ./target/fs-qw-api.jar fs-qw-api.jar
 # 向外暴露的接口,最好与项目yml文件中的端口一致

+ 2 - 19
fs-qw-api/src/main/java/com/fs/app/service/QwDataCallbackService.java

@@ -236,24 +236,7 @@ public class QwDataCallbackService {
                                             redisCache.setCacheObject(cacheKey, "1", 10, TimeUnit.MINUTES);
 
                                             // 广告线索处理
-                                            leadService.updateAddMemberLead(externalUserID,userID,corpId,State)
-                                                    .thenAccept(result -> {
-                                                        if (StrUtil.isNotEmpty(result)) {
-                                                            QwExternalContact qwExternalContact = qwExternalContactMapper.selectQwExternalByExternalIdAndCompanyIdToIdAndFs(externalUserID, userID, corpId);
-                                                            if (qwExternalContact == null){
-                                                                try {
-                                                                    Thread.sleep(2000);
-                                                                } catch (InterruptedException e) {
-                                                                    throw new RuntimeException(e);
-                                                                }
-                                                                qwExternalContact = qwExternalContactMapper.selectQwExternalByExternalIdAndCompanyIdToIdAndFs(externalUserID, userID, corpId);
-                                                            }
-                                                            QwExternalContact temp = new QwExternalContact();
-                                                            temp.setId(qwExternalContact.getId());
-                                                            temp.setTraceId(result);
-                                                            qwExternalContactMapper.updateById(temp);
-                                                        }
-                                                    });
+                                            leadService.updateAddMemberLead(externalUserID,userID,corpId,State);
                                         } catch (Exception e) {
                                             // 7. 业务逻辑失败时,删除缓存
                                             redisCache.deleteObject(cacheKey);
@@ -431,7 +414,7 @@ public class QwDataCallbackService {
                                     if (qwGroupChatUserOld==null) {
                                         qwGroupChatUserService.insertQwGroupChatUser(qwGroupChatUser);
                                         // 群员入群 广告判断记录
-                                        leadService.updateGroupAddMemberLead(qwGroupChatUser.getUserId(),qwGroupChatUser.getChatId(),qwGroupChatUser.getCorpId(),qwGroupChatUser.getUnionid());
+                                        leadService.updateGroupAddMemberLead(qwGroupChatUser.getName(),qwGroupChatUser.getChatId(),qwGroupChatUser.getCorpId(),qwGroupChatUser.getUnionid());
                                     }else {
                                         qwGroupChatUserOld.setIsOut(1L);
                                         qwGroupChatUserOld.setCorpId(corpId);

+ 33 - 9
fs-service/src/main/java/com/fs/company/service/impl/CompanyServiceImpl.java

@@ -522,13 +522,23 @@ public class CompanyServiceImpl implements ICompanyService
                 orderMap.setTuiMoneyStatus(1);
                 storeOrderMapper.updateFsStoreOrder(orderMap);
                 // order.getPayRemain() 数据库实际没有这个字段了 直接使用 应付金额
-                BigDecimal money = order.getPayPrice();
-                company.setMoney(company.getMoney().add(money));
+                // 卓美,按照润天进行百分比进行分佣
+                String json =configService.selectConfigByKey("store.config");
+                com.fs.store.config.StoreConfig config= JSONUtil.toBean(json, com.fs.store.config.StoreConfig.class);
+                //支付金额-(订单金额*rate%)
+                BigDecimal tuiMoney = BigDecimal.ZERO;
+                if (config != null && config.getTuiMoneyRate() != null) {
+                    Double rate = config.getTuiMoneyRate() / 100d;
+                    tuiMoney = order.getPayPrice().subtract(order.getTotalPrice().multiply(new BigDecimal(rate)));
+                } else {
+                    tuiMoney = order.getPayPrice();
+                }
+                company.setMoney(company.getMoney().add(tuiMoney));
                 companyMapper.updateCompany(company);
                 CompanyMoneyLogs log=new CompanyMoneyLogs();
                 log.setCompanyId(company.getCompanyId());
                 log.setRemark("佣金入账");
-                log.setMoney(money);
+                log.setMoney(tuiMoney);
                 log.setLogsType(3);
                 log.setBalance(company.getMoney());
                 log.setCreateTime(new Date());
@@ -547,20 +557,34 @@ public class CompanyServiceImpl implements ICompanyService
         if(order.getCompanyId()>0){
             Company company=companyMapper.selectCompanyByIdForUpdate(order.getCompanyId());
             if(company!=null){
-                company.setMoney(company.getMoney().subtract(order.getTuiMoney()));
-                company.setTuiMoney(company.getTuiMoney().subtract(order.getTuiMoney()));
+                // 卓美,按照润天进行百分比进行分佣
+                String json =configService.selectConfigByKey("store.config");
+                com.fs.store.config.StoreConfig config= JSONUtil.toBean(json, com.fs.store.config.StoreConfig.class);
+                //支付金额-(订单金额*rate%)
+                BigDecimal tuiMoney = BigDecimal.ZERO;
+                if (config != null && config.getTuiMoneyRate() != null) {
+                    Double rate = config.getTuiMoneyRate() / 100d;
+                    tuiMoney = order.getPayPrice().subtract(order.getTotalPrice().multiply(new BigDecimal(rate)));
+                } else {
+                    tuiMoney = order.getPayPrice();
+                }
+                company.setMoney(company.getMoney().add(tuiMoney));
                 companyMapper.updateCompany(company);
                 //写入日志
                 CompanyMoneyLogs log=new CompanyMoneyLogs();
                 log.setCompanyId(order.getCompanyId());
-                log.setRemark("订单佣金退款");
-                log.setMoney(order.getTuiMoney().multiply(new BigDecimal(-1)));
-                log.setLogsType(4);
+                log.setRemark("佣金入账");
+                log.setMoney(tuiMoney);
+                log.setLogsType(3);
                 log.setBalance(company.getMoney());
                 log.setCreateTime(new Date());
                 log.setBusinessId(order.getOrderId().toString());
                 moneyLogsMapper.insertCompanyMoneyLogs(log);
-
+                LiveOrder liveOrder = new LiveOrder();
+                liveOrder.setOrderId(order.getOrderId());
+                liveOrder.setTuiMoneyStatus(1);
+                liveOrder.setTuiMoney(tuiMoney);
+                liveOrderMapper.updateLiveOrder(liveOrder);
             }
         }
     }

+ 11 - 4
fs-service/src/main/java/com/fs/course/mapper/FsUserCourseVideoMapper.java

@@ -3,10 +3,7 @@ package com.fs.course.mapper;
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.fs.course.domain.FsUserCourseVideo;
 import com.fs.course.domain.FsVideoResource;
-import com.fs.course.param.CourseVideoUpdates;
-import com.fs.course.param.FsCourseListBySidebarParam;
-import com.fs.course.param.FsUserCourseVideoListUParam;
-import com.fs.course.param.FsUserCourseVideoParam;
+import com.fs.course.param.*;
 import com.fs.course.param.newfs.UserCourseVideoPageParam;
 import com.fs.course.vo.*;
 import com.fs.course.vo.newfs.FsUserCourseVideoPageListVO;
@@ -282,4 +279,14 @@ public interface FsUserCourseVideoMapper extends BaseMapper<FsUserCourseVideo> {
 
     @Select("select * from fs_video_resource where hsy_vid is not null")
     List<FsVideoResource> selectVideoByVid();
+
+    /**
+     * 下架
+     */
+    int batchDown(String[] videoIds);
+
+    /**
+     * 批量修改视频封面
+     */
+    int batchEditCover(BatchEditCoverParam param);
 }

+ 21 - 0
fs-service/src/main/java/com/fs/course/param/BatchEditCoverParam.java

@@ -0,0 +1,21 @@
+package com.fs.course.param;
+
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotEmpty;
+import java.util.List;
+
+@Data
+public class BatchEditCoverParam {
+    /**
+     * 封面图
+     */
+    @NotBlank(message = "封面图不能为空")
+    private String thumbnail;
+    /**
+     * 视频小节
+     */
+    @NotEmpty(message = "小节ID不能为空")
+    private List<Long> videoIds;
+}

+ 10 - 1
fs-service/src/main/java/com/fs/course/service/IFsUserCourseVideoService.java

@@ -17,7 +17,6 @@ import com.fs.course.vo.newfs.FsUserVideoListVO;
 import com.fs.his.domain.FsUser;
 import com.fs.his.vo.OptionsVO;
 import com.fs.qw.param.FsUserCourseRedPageParam;
-import org.springframework.web.multipart.MultipartFile;
 
 import java.util.List;
 import java.util.Map;
@@ -245,4 +244,14 @@ public interface IFsUserCourseVideoService extends IService<FsUserCourseVideo> {
     R createRoomMiniLinkByCourse(FsCourseLinkRoomNewParam param);
 
     void updateMediaPublishStatus(String vid);
+
+    /**
+     * 视频下架
+     */
+    int batchDown(String[] videoIds);
+
+    /**
+     * 批量修改视频封面
+     */
+    int batchEditCover(BatchEditCoverParam param);
 }

+ 27 - 8
fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java

@@ -27,7 +27,6 @@ import com.fs.company.domain.CompanyCompanyFsuser;
 import com.fs.company.domain.CompanyUser;
 import com.fs.company.mapper.CompanyCompanyFsuserMapper;
 import com.fs.company.mapper.CompanyMapper;
-import com.fs.company.mapper.CompanyMoneyLogsMapper;
 import com.fs.company.mapper.CompanyUserMapper;
 import com.fs.company.service.ICompanyService;
 import com.fs.config.cloud.CloudHostProper;
@@ -84,8 +83,6 @@ import com.fs.system.mapper.SysDictDataMapper;
 import com.fs.system.service.ISysConfigService;
 import com.fs.voice.utils.StringUtil;
 import com.github.binarywang.wxpay.bean.transfer.TransferBillsResult;
-import com.volcengine.helper.VodUploadProgressListener;
-import com.volcengine.model.beans.Functions;
 import com.volcengine.service.vod.IVodService;
 import com.volcengine.service.vod.model.business.VodUrlUploadURLSet;
 import com.volcengine.service.vod.model.request.*;
@@ -103,12 +100,9 @@ import org.springframework.beans.factory.annotation.Value;
 import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
-import org.springframework.web.multipart.MultipartFile;
 
-import java.io.File;
 import java.math.BigDecimal;
 import java.math.RoundingMode;
-import java.text.SimpleDateFormat;
 import java.time.*;
 import java.time.format.DateTimeFormatter;
 import java.util.*;
@@ -2205,7 +2199,7 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
                      return R.error("奖励发送失败,请联系客服");
                  }
              }catch (Exception e){
-                 return R.error("发放奖励失败,请联系客服");
+                 return R.error(e.getMessage());
              }
 
             }
@@ -2455,8 +2449,17 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
         FsUserCourseVideoDetailsVO fsUserCourseVideoDetailsVO = new FsUserCourseVideoDetailsVO();
         BeanUtils.copyProperties(fsUserCourseVideo, fsUserCourseVideoDetailsVO);
 
+        //从配置中读取默认线路
+        String json = configService.selectConfigByKey("course.config");
+        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
+        if (Integer.valueOf(1).equals(config.getDefaultLine())) {
+            fsUserCourseVideoDetailsVO.setVideoUrl(fsUserCourseVideo.getLineTwo());
+        } else {
+            fsUserCourseVideoDetailsVO.setVideoUrl(fsUserCourseVideo.getLineOne());
+        }
         //这里 改成取线路一值,返回给前端。VideoUrl 是原视频(用来算流量的),不要去改,lineOne是转码后的视频
-        fsUserCourseVideoDetailsVO.setVideoUrl(fsUserCourseVideo.getLineOne());
+//        fsUserCourseVideoDetailsVO.setVideoUrl(fsUserCourseVideo.getLineOne());
+
 
         // 获取课程相关的题库
         String questionBankId = fsUserCourseVideo.getQuestionBankId();
@@ -4449,5 +4452,21 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
             }
         }
     }
+
+    /**
+     * 视频下架
+     */
+    @Override
+    public int batchDown(String[] videoIds) {
+        return baseMapper.batchDown(videoIds);
+    }
+
+    /**
+     * 批量修改视频封面
+     */
+    @Override
+    public int batchEditCover(BatchEditCoverParam param) {
+        return baseMapper.batchEditCover(param);
+    }
 }
 

+ 2 - 2
fs-service/src/main/java/com/fs/his/domain/FsIntegralOrder.java

@@ -126,8 +126,8 @@ public class FsIntegralOrder
     private Date createTime;
     @TableField(exist = false)
     private Date updateTime;
-
+    @TableField(exist = false)
     private Integer deliveryStatus;
-
+    @TableField(exist = false)
     private String deliveryType;
 }

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

@@ -34,6 +34,7 @@ public enum FsUserIntegralLogTypeEnum {
     TYPE_24(24, "付费课程订阅"),
     TYPE_25(25, "直播完课积分"),
     TYPE_26(26, "直播红包积分"),
+    TYPE_27(27, "积分订单取消退回积分"),
     ;
 
 

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

@@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.fs.his.domain.FsIntegralCart;
 import com.fs.his.vo.FsIntegralCartVO;
 import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Update;
 
 import java.util.List;
 import java.util.Map;
@@ -35,4 +36,7 @@ public interface FsIntegralCartMapper extends BaseMapper<FsIntegralCart> {
      * @return  删除数量
      */
     int deleteCartByGoodsId(@Param("goodsId") Long goodsId);
+
+    @Update("update fs_integral_cart set integral = #{integral} where goods_id = #{goodsId}")
+    void updateIntegralByGoodsId(@Param("goodsId")Long goodsId,@Param("integral") Long integral);
 }

+ 5 - 0
fs-service/src/main/java/com/fs/his/service/impl/FsIntegralCartServiceImpl.java

@@ -3,6 +3,7 @@ package com.fs.his.service.impl;
 import com.baomidou.mybatisplus.core.conditions.Wrapper;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.fs.common.core.domain.R;
 import com.fs.common.exception.CustomException;
 import com.fs.his.domain.FsIntegralCart;
 import com.fs.his.domain.FsIntegralGoods;
@@ -61,6 +62,10 @@ public class FsIntegralCartServiceImpl extends ServiceImpl<FsIntegralCartMapper,
             log.warn("addOrUpdateCart goods not enough cartNum: {}", goodsId);
             throw new CustomException("库存不足");
         }
+        if (fsIntegralGoods.getStatus() == 0) {
+            log.warn("addOrUpdateCart goods not enable cartNum: {}", goodsId);
+            throw new CustomException("商品停售");
+        }
 
         FsIntegralCart cart = new FsIntegralCart();
         cart.setUserId(userId);

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

@@ -90,6 +90,14 @@ public class FsIntegralGoodsServiceImpl implements IFsIntegralGoodsService
     @Override
     public int updateFsIntegralGoods(FsIntegralGoods fsIntegralGoods)
     {
+        FsIntegralGoods existGoods = fsIntegralGoodsMapper.selectFsIntegralGoodsByGoodsId(fsIntegralGoods.getGoodsId());
+        if (existGoods == null) {
+            return -1;
+        }
+        // 存在的积分和现在的积分不相等,更新积分购物车的积分
+        if(existGoods.getIntegral() != fsIntegralGoods.getIntegral()) {
+            fsIntegralCartMapper.updateIntegralByGoodsId(fsIntegralGoods.getGoodsId(), fsIntegralGoods.getIntegral());
+        }
         return fsIntegralGoodsMapper.updateFsIntegralGoods(fsIntegralGoods);
     }
 

+ 11 - 1
fs-service/src/main/java/com/fs/his/service/impl/FsIntegralOrderServiceImpl.java

@@ -295,6 +295,7 @@ public class FsIntegralOrderServiceImpl implements IFsIntegralOrderService
         FsUser user=fsUserMapper.selectFsUserByUserId(param.getUserId());
         FsUserAddress address=fsUserAddressMapper.selectFsUserAddressByAddressId(param.getAddressId());
         FsIntegralGoods integralGoods=fsIntegralGoodsMapper.selectFsIntegralGoodsByGoodsId(param.getGoodsId());
+
         if (Objects.isNull(user)) {
             return R.error("用户不存在");
         }
@@ -310,6 +311,9 @@ public class FsIntegralOrderServiceImpl implements IFsIntegralOrderService
         if(user.getIntegral()<integralGoods.getIntegral()){
             return R.error("积分不足");
         }
+        if (integralGoods.getStatus() == 0) {
+            return R.error("商品已经停售,请联系客户进行处理!");
+        }
 
         // 减库存
         if (fsIntegralGoodsMapper.subStock(integralGoods.getGoodsId(), 1) <= 0) {
@@ -469,6 +473,9 @@ public class FsIntegralOrderServiceImpl implements IFsIntegralOrderService
             if(integralGoods.getStock() < cart.getCartNum()){
                 throw new CustomException("库存不足");
             }
+            if (integralGoods.getStatus() == 0) {
+                return R.error(integralGoods.getGoodsName() + "商品已经停售,请联系客户进行处理!");
+            }
 
             // 减库存
             if (fsIntegralGoodsMapper.subStock(integralGoods.getGoodsId(), cart.getCartNum()) <= 0) {
@@ -811,15 +818,18 @@ public class FsIntegralOrderServiceImpl implements IFsIntegralOrderService
             FsUser fsUser = fsUserMapper.selectFsUserByUserId(fsIntegralOrder.getUserId());
             fsUser.setIntegral(fsUser.getIntegral()+Long.parseLong(fsIntegralOrder.getIntegral()));
             i = fsUserMapper.updateFsUser(fsUser);
+            Date now = new Date();
             //新增积分记录
             FsUserIntegralLogs fsUserIntegralLogs = new FsUserIntegralLogs();
             fsUserIntegralLogs.setBalance(fsUser.getIntegral());
             fsUserIntegralLogs.setBusinessId(fsIntegralOrder.getOrderId().toString());
             fsUserIntegralLogs.setUserId(fsIntegralOrder.getUserId());
-            fsUserIntegralLogs.setLogType(20);
+            fsUserIntegralLogs.setLogType(27);
             fsUserIntegralLogs.setIntegral(Long.parseLong(fsIntegralOrder.getIntegral()));
             fsUserIntegralLogs.setBusinessType(2);
             fsUserIntegralLogs.setStatus(0);
+            fsUserIntegralLogs.setCreateTime(now);
+            fsUserIntegralLogs.setUpdateTime(now);
             i = fsUserIntegralLogsMapper.insertFsUserIntegralLogs(fsUserIntegralLogs);
         }
         return i;

+ 24 - 0
fs-service/src/main/java/com/fs/his/service/impl/FsStoreAfterSalesServiceImpl.java

@@ -646,6 +646,30 @@ public class FsStoreAfterSalesServiceImpl implements IFsStoreAfterSalesService {
     @Override
     @Transactional
     public R applyAfterSales(long uesrId, FsStoreAfterSalesApplyParam param) {
+        // 查询配置:是否删除历史售后数据
+        try {
+            String deleteAfterSalesConfig = configService.selectConfigByKey("delete_after_sales");
+            if (StringUtils.isNotEmpty(deleteAfterSalesConfig) && "true".equalsIgnoreCase(deleteAfterSalesConfig.trim())) {
+                // 查询历史售后数据,将 is_del 设置为 1
+                FsStoreAfterSales queryAfterSales = new FsStoreAfterSales();
+                queryAfterSales.setOrderId(param.getOrderId());
+                List<FsStoreAfterSales> historyAfterSalesList = fsStoreAfterSalesMapper.selectFsStoreAfterSalesList(queryAfterSales);
+                if (historyAfterSalesList != null && !historyAfterSalesList.isEmpty()) {
+                    for (FsStoreAfterSales historyAfterSales : historyAfterSalesList) {
+                        if (historyAfterSales.getIsDel() != null && historyAfterSales.getIsDel() == 0) {
+                            FsStoreAfterSales updateAfterSales = new FsStoreAfterSales();
+                            updateAfterSales.setId(historyAfterSales.getId());
+                            updateAfterSales.setIsDel(1);
+                            fsStoreAfterSalesMapper.updateFsStoreAfterSales(updateAfterSales);
+                            logger.info("删除历史售后数据,售后ID:{},订单ID:{}", historyAfterSales.getId(), param.getOrderId());
+                        }
+                    }
+                }
+            }
+        } catch (Exception e) {
+            logger.error("查询或更新历史售后数据失败", e);
+        }
+        
         FsStoreOrder order = fsStoreOrderMapper.selectFsStoreOrderByOrderId(param.getOrderId());
         if (!order.getUserId().equals(uesrId)) {
             throw new CustomException("非法操作");

+ 4 - 2
fs-service/src/main/java/com/fs/his/service/impl/FsStorePaymentServiceImpl.java

@@ -659,7 +659,8 @@ public class FsStorePaymentServiceImpl implements IFsStorePaymentService {
             return result;
         }catch (Exception e){
             logger.error("领取红包失败原因:{}", ExceptionUtils.getMessage(e),e);
-            if (e instanceof WxPayException && "济南联志健康".equals(signProjectName)) {
+//            if (e instanceof WxPayException && "济南联志健康".equals(signProjectName)) {
+            if (e instanceof WxPayException) {
                 WxPayException wxPayException = (WxPayException) e;
                 String customErrorMsg = wxPayException.getCustomErrorMsg();
                 if (null != customErrorMsg && customErrorMsg.startsWith("商户运营账户资金不足")) {
@@ -923,7 +924,8 @@ public class FsStorePaymentServiceImpl implements IFsStorePaymentService {
             return R.ok("发送红包成功").put("orderCode", transferBatchesResult.getOutBatchNo()).put("batchId", transferBatchesResult.getBatchId()).put("mchId", config.getMchId());
         } catch (Exception e) {
             logger.error("商家转账支付失败:参数: {} :原因: {}",JSON.toJSONString(param), e.getMessage(),e);
-            if (e instanceof WxPayException && "济南联志健康".equals(signProjectName)) {
+            if (e instanceof WxPayException) {
+//            if (e instanceof WxPayException && "济南联志健康".equals(signProjectName)) {
                 WxPayException wxPayException = (WxPayException) e;
                 String customErrorMsg = wxPayException.getCustomErrorMsg();
                 if (null != customErrorMsg && customErrorMsg.startsWith("商户运营账户资金不足")) {

+ 1 - 1
fs-service/src/main/java/com/fs/his/vo/FsIntegralOrderExcelVO.java

@@ -5,7 +5,7 @@ import lombok.Data;
 
 @Data
 public class FsIntegralOrderExcelVO {
-    @Excel(name = "药品订单号",required = true)
+    @Excel(name = "订单号",required = true)
     private String orderCode;
 
     @Excel(name = "订单状态",dictType = "sys_integral_order_status")

+ 7 - 0
fs-service/src/main/java/com/fs/hisStore/domain/FsStoreOrderScrm.java

@@ -353,4 +353,11 @@ public class FsStoreOrderScrm extends BaseEntity
 
     @TableField(exist = false)
     private String bankTransactionId;
+
+     // 是否审核,1-是,0-否
+    private Integer isAudit;
+
+    // 订单总后台备注
+    private String orderRemark;
+
 }

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

@@ -76,7 +76,8 @@ public interface FsStoreOrderItemScrmMapper
 
     @Select({"<script> " +
             "select i.*,o.user_id,psps.cost,o.pay_postage,o.total_num,o.status,fspcs.cate_name, o.real_name,o.user_phone,o.user_address,o.create_time,o.pay_time,o.delivery_sn,o.delivery_name,o.delivery_id, c.company_name ,cu.nick_name as company_user_nick_name ,cu.phonenumber as company_usere_phonenumber,o.upload_time ,CASE WHEN o.certificates IS NULL OR o.certificates = '' THEN 0 ELSE 1 END AS is_upload   " +
-            " ,p.title as package_name,cts.name as scheduleName,os.pay_money, os.bank_transaction_id as bankTransactionId " +
+            " ,p.title as package_name,cts.name as scheduleName,os.pay_money, os.bank_transaction_id as bankTransactionId, o.delivery_send_time " +
+            ", CASE o.is_audit WHEN 1 THEN '是' ELSE '否' END AS isAudit " +
             " from fs_store_order_item_scrm i " +
             " left join fs_store_order_scrm o on o.id=i.order_id" +
             " left join fs_store_payment_scrm os on os.business_order_id = o.id " +
@@ -159,6 +160,9 @@ public interface FsStoreOrderItemScrmMapper
             "<if test = 'maps.scheduleId != null    '> " +
             "and o.schedule_id =#{maps.scheduleId} " +
             "</if>" +
+            "<if test = 'maps.isAudit != null'> " +
+            "and o.is_audit = #{maps.isAudit} " +
+            "</if>" +
             " order by o.id desc limit 50000"+
             "</script>"})
     List<FsStoreOrderItemExportVO> selectFsStoreOrderItemListExportVO(@Param("maps")FsStoreOrderParam fsStoreOrder);

+ 15 - 3
fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreOrderScrmMapper.java

@@ -652,6 +652,7 @@ public interface FsStoreOrderScrmMapper
 
     @Select({"<script> " +
             "select o.*,cts.name as scheduleName,u.nickname,u.phone,cc.push_code,cc.create_time as customer_create_time,cc.source,cc.customer_code, c.company_name ,cu.nick_name as company_user_nick_name ,cu.phonenumber as company_usere_phonenumber ,p.title as package_title ,CASE WHEN o.certificates IS NULL OR o.certificates = '' THEN 0 ELSE 1 END AS is_upload  " +
+            ", CASE o.is_audit WHEN 1 THEN '是' ELSE '否' END AS isAudit " +
             " from fs_store_order_scrm o  " +
             " left JOIN fs_store_product_package_scrm p on o.package_id=p.package_id " +
             " left join fs_user u on o.user_id=u.user_id  " +
@@ -749,6 +750,9 @@ public interface FsStoreOrderScrmMapper
             "<if test = 'maps.isUpload != null and maps.isUpload == 1    '> " +
             "and o.certificates is not null " +
             "</if>" +
+            "<if test = 'maps.isAudit != null'> " +
+            "and o.is_audit = #{maps.isAudit} " +
+            "</if>" +
             " ${maps.params.dataScope} "+
             " order by o.id desc limit 50000"+
             "</script>"})
@@ -1197,9 +1201,8 @@ public interface FsStoreOrderScrmMapper
             " update fs_store_order_scrm "+
             "<trim prefix=\"SET\" suffixOverrides=\",\">\n" +
             "            <if test=\"storeId != null\">store_id = #{storeId},</if>\n" +
-            "            <if test=\"extendOrderId != null\">extend_order_id = #{extendOrderId},</if>\n" +
             "            <if test=\"userId != null and userId != ''\">user_id = #{userId},</if>\n" +
-            "            <if test=\"realName != null and realName != ''\">real_name = #{realName},</if>\n" +
+            "            <if test=\"userName != null and userName != ''\">user_name = #{userName},</if>\n" +
             "            <if test=\"userPhone != null and userPhone != ''\">user_phone = #{userPhone},</if>\n" +
             "            <if test=\"userAddress != null and userAddress != ''\">user_address = #{userAddress},</if>\n" +
             "            <if test=\"cartId != null and cartId != ''\">cart_id = #{cartId},</if>\n" +
@@ -1222,7 +1225,6 @@ public interface FsStoreOrderScrmMapper
             "            <if test=\"deliveryCode != null\">delivery_code = #{deliveryCode},</if>\n" +
             "            <if test=\"deliveryName != null\">delivery_name = #{deliveryName},</if>\n" +
             "            <if test=\"deliverySn != null\">delivery_sn = #{deliverySn},</if>\n" +
-            "            <if test=\"deliveryId != null\">delivery_id = #{deliveryId},</if>\n" +
             "            <if test=\"remark != null\">remark = #{remark},</if>\n" +
             "            <if test=\"isDel != null\">is_del = #{isDel},</if>\n" +
             "            <if test=\"costPrice != null\">cost_price = #{costPrice},</if>\n" +
@@ -1425,6 +1427,16 @@ public interface FsStoreOrderScrmMapper
     @Select("SELECT * FROM fs_store_order_scrm WHERE create_time >= DATE_SUB(NOW(), INTERVAL 30 MINUTE) and status = 0")
     List<FsStoreOrderScrm> selectBankOrder();
 
+    /**
+     * 批量更新订单审核状态
+     *
+     * @param orderIds 订单ID列表
+     * @param isAudit  审核状态 1-是,0-否
+     * @return 更新条数
+     */
+    int batchUpdateAuditStatus(@Param("orderIds") List<Long> orderIds, @Param("isAudit") Integer isAudit);
+
+
     List<FsStoreOrderScrm> getEligibleOrderCoupons(@Param("userId")long userId, @Param("packageId")long packageId);
 
     List<FsStorePayment> getFsStorePayMentScrm(@Param("orderCodes")List<Long> orderCodes);

+ 24 - 0
fs-service/src/main/java/com/fs/hisStore/param/FsStoreOrderBatchAuditParam.java

@@ -0,0 +1,24 @@
+package com.fs.hisStore.param;
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 批量审核订单参数
+ *
+ * @author fs
+ * @date 2024
+ */
+@Data
+public class FsStoreOrderBatchAuditParam {
+    /**
+     * 订单ID列表
+     */
+    private List<Long> orderIds;
+
+    /**
+     * 审核状态 1-是,0-否
+     */
+    private Integer isAudit;
+}

+ 2 - 0
fs-service/src/main/java/com/fs/hisStore/param/FsStoreOrderParam.java

@@ -119,4 +119,6 @@ public class FsStoreOrderParam extends BaseEntity implements Serializable
     private Integer pageNum;
     private Integer pageSize;
 
+    // 是否审核,1-是,0-否
+    private Integer isAudit;
 }

+ 8 - 0
fs-service/src/main/java/com/fs/hisStore/service/IFsStoreOrderScrmService.java

@@ -352,6 +352,14 @@ public interface IFsStoreOrderScrmService
 
     void updateFsStoreOrderDb(FsStoreOrderScrm order);
 
+    /**
+     * 批量审核订单
+     *
+     * @param param 批量审核参数
+     * @return 更新条数
+     */
+    int batchAuditOrder(FsStoreOrderBatchAuditParam param);
+
     ExpressInfoDTO getDfExpressInfoDTO(FsStoreOrderScrm order);
 
     String importOrderStatusData(List<FsStoreOrderStateVo> list);

+ 25 - 0
fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreAfterSalesScrmServiceImpl.java

@@ -277,6 +277,31 @@ public class FsStoreAfterSalesScrmServiceImpl implements IFsStoreAfterSalesScrmS
     @Transactional
     public R applyForAfterSales(long userId, FsStoreAfterSalesParam storeAfterSalesParam) {
         logger.info("申请退款请求信息:"+JSONUtil.toJsonStr(storeAfterSalesParam));
+        
+        // 查询配置:是否删除历史售后数据
+        try {
+            String deleteAfterSalesConfig = configService.selectConfigByKey("delete_after_sales");
+            if (StringUtils.isNotEmpty(deleteAfterSalesConfig) && "true".equalsIgnoreCase(deleteAfterSalesConfig.trim())) {
+                // 查询历史售后数据,将 is_del 设置为 1
+                FsStoreAfterSalesScrm queryAfterSales = new FsStoreAfterSalesScrm();
+                queryAfterSales.setOrderCode(storeAfterSalesParam.getOrderCode());
+                List<FsStoreAfterSalesScrm> historyAfterSalesList = fsStoreAfterSalesMapper.selectFsStoreAfterSalesList(queryAfterSales);
+                if (historyAfterSalesList != null && !historyAfterSalesList.isEmpty()) {
+                    for (FsStoreAfterSalesScrm historyAfterSales : historyAfterSalesList) {
+                        if (historyAfterSales.getIsDel() != null && historyAfterSales.getIsDel() == 0) {
+                            FsStoreAfterSalesScrm updateAfterSales = new FsStoreAfterSalesScrm();
+                            updateAfterSales.setId(historyAfterSales.getId());
+                            updateAfterSales.setIsDel(1);
+                            fsStoreAfterSalesMapper.updateFsStoreAfterSales(updateAfterSales);
+                            logger.info("删除历史售后数据,售后ID:{},订单号:{}", historyAfterSales.getId(), storeAfterSalesParam.getOrderCode());
+                        }
+                    }
+                }
+            }
+        } catch (Exception e) {
+            logger.error("查询或更新历史售后数据失败", e);
+        }
+        
         FsStoreOrderScrm order=orderService.selectFsStoreOrderByOrderCode(storeAfterSalesParam.getOrderCode());
         Integer orderStatus = order.getStatus();
         if(!order.getUserId().equals(userId)){

+ 12 - 1
fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreOrderScrmServiceImpl.java

@@ -363,6 +363,12 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
     @Autowired
     private FsStoreAfterSalesItemScrmMapper fsStoreAfterSalesItemMapper;
 
+    @Autowired
+    private FsStoreAfterSalesScrmMapper fsStoreAfterSalesScrmMapper;
+
+    @Autowired
+    private FsStoreAfterSalesStatusScrmMapper fsStoreAfterSalesStatusScrmMapper;
+
     @Autowired
     private FsPackageOrderMapper fsPackageOrderMapper;
 
@@ -1656,7 +1662,6 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
 
     @Override
     public R confirmPackageOrder(long uid, FsStoreConfirmPackageIdOrderParam param) {
-        this.releaseCoupon(uid,param.getPackageId());
         FsUserAddressScrm address = userAddressMapper.selectFsUserAddressByDefaultAddress(uid);
         FsStoreProductPackageScrm storeProductPackage = productPackageService.selectFsStoreProductPackageById(param.getPackageId());
         // 由于套餐制单前面有生成oderkey,并且要取修改的价格,所以这里判断,如果有传就用传的orderkey,如果没有就生成(代表走的是直接购买)
@@ -5443,6 +5448,12 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
         fsStoreOrderMapper.updateFsStoreOrder(order);
     }
 
+    @Override
+    @Transactional
+    public int batchAuditOrder(FsStoreOrderBatchAuditParam param) {
+        return fsStoreOrderMapper.batchUpdateAuditStatus(param.getOrderIds(), param.getIsAudit());
+    }
+
     @Override
     public com.fs.his.dto.ExpressInfoDTO getDfExpressInfoDTO(FsStoreOrderScrm order) {
         com.fs.his.dto.ExpressInfoDTO expressInfoDTO = new com.fs.his.dto.ExpressInfoDTO();

+ 6 - 0
fs-service/src/main/java/com/fs/hisStore/vo/FsStoreOrderExportVO.java

@@ -252,6 +252,9 @@ public class FsStoreOrderExportVO implements Serializable
     @Excel(name = "物流跟踪状态" , dictType = "store_order_delivery_type")
     private String deliveryType;
 
+    @Excel(name = "发货时间")
+    private String deliverySendTime;
+
     @Excel(name = "客户编码")
     private String customerCode;
 
@@ -267,4 +270,7 @@ public class FsStoreOrderExportVO implements Serializable
 
     @Excel(name = "归属档期")
     private String scheduleName;
+
+    @Excel(name = "是否审核")
+    private String isAudit;
 }

+ 6 - 0
fs-service/src/main/java/com/fs/hisStore/vo/FsStoreOrderItemExportVO.java

@@ -89,6 +89,9 @@ public class FsStoreOrderItemExportVO implements Serializable
     @Excel(name = "快递单号")
     private String deliveryId;
 
+    @Excel(name = "发货时间")
+    private String deliverySendTime;
+
     @Excel(name = "所属公司")
     private String companyName;
     @Excel(name = "所属销售")
@@ -114,4 +117,7 @@ public class FsStoreOrderItemExportVO implements Serializable
     @Excel(name = "银行交易流水号")
     private String bankTransactionId;
 
+    @Excel(name = "是否审核")
+    private String isAudit;
+
 }

+ 5 - 0
fs-service/src/main/java/com/fs/hisStore/vo/FsStoreOrderVO.java

@@ -251,6 +251,9 @@ public class FsStoreOrderVO implements Serializable
     @Excel(name = "物流代收金额")
     private BigDecimal deliveryPayMoney;
 
+    @Excel(name = "发货时间")
+    private String deliverySendTime;
+
     private String itemJson;
 
     private String orderVisit;
@@ -271,5 +274,7 @@ public class FsStoreOrderVO implements Serializable
     @Excel(name = "银行交易流水号")
     private String bankTransactionId;
 
+    // 是否审核,1-是,0-否
+    private Boolean isAudit;
 
 }

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

@@ -50,4 +50,6 @@ public interface ILiveCompletionPointsRecordService {
      * @return 完课记录
      */
     LiveCompletionPointsRecord selectByUserAndDate(Long liveId, Long userId, Date date);
+
+    LiveCompletionPointsRecord createCompletionRecord(Long liveId, Long userId);
 }

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

@@ -167,4 +167,6 @@ public interface ILiveDataService {
      * @return 导出VO列表
      */
     List<LiveUserDetailExportVO> exportLiveUserDetail(Long liveId, Long companyId, Long companyUserId);
+
+    List<LiveDataListVo> exportLiveData(LiveDataParam param);
 }

+ 26 - 0
fs-service/src/main/java/com/fs/live/service/impl/LiveAfterSalesServiceImpl.java

@@ -410,6 +410,32 @@ public class LiveAfterSalesServiceImpl implements ILiveAfterSalesService {
             log.info("申请售后订单锁获取成功,订单号:{}", param.getOrderCode());
             
             LiveOrder order=liveOrderService.selectOrderIdByOrderCode(param.getOrderCode());
+            
+            // 查询配置:是否删除历史售后数据
+            try {
+                String deleteAfterSalesConfig = configService.selectConfigByKey("delete_after_sales");
+                if (StringUtils.isNotEmpty(deleteAfterSalesConfig) && "true".equalsIgnoreCase(deleteAfterSalesConfig.trim())) {
+                    // 查询历史售后数据,将 is_del 设置为 1
+                    if (order != null && order.getOrderId() != null) {
+                        LiveAfterSales queryAfterSales = new LiveAfterSales();
+                        queryAfterSales.setOrderId(order.getOrderId());
+                        List<LiveAfterSales> historyAfterSalesList = baseMapper.selectLiveAfterSalesList(queryAfterSales);
+                        if (historyAfterSalesList != null && !historyAfterSalesList.isEmpty()) {
+                            for (LiveAfterSales historyAfterSales : historyAfterSalesList) {
+                                if (historyAfterSales.getIsDel() != null && historyAfterSales.getIsDel() == 0) {
+                                    LiveAfterSales updateAfterSales = new LiveAfterSales();
+                                    updateAfterSales.setId(historyAfterSales.getId());
+                                    updateAfterSales.setIsDel(1);
+                                    baseMapper.updateLiveAfterSales(updateAfterSales);
+                                    log.info("删除历史售后数据,售后ID:{},订单ID:{}", historyAfterSales.getId(), order.getOrderId());
+                                }
+                            }
+                        }
+                    }
+                }
+            } catch (Exception e) {
+                log.error("查询或更新历史售后数据失败", e);
+            }
             if(!order.getUserId().equals(userId)){
                 throw new CustomException("非法操作");
             }

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

@@ -269,6 +269,120 @@ public class LiveCompletionPointsRecordServiceImpl implements ILiveCompletionPoi
         return recordMapper.selectByUserAndDate(liveId, userId, date);
     }
 
+    @Override
+    public LiveCompletionPointsRecord createCompletionRecord(Long liveId, Long userId) {
+        try {
+            // 1. 获取直播信息和配置
+            Live live = liveService.selectLiveByLiveId(liveId);
+            if (live == null) {
+
+                return null;
+            }
+
+            // 2. 从数据库获取完课积分配置
+            CompletionPointsConfig config = getCompletionPointsConfig(live);
+
+            // 检查是否开启完课积分功能
+            if (!config.isEnabled()) {
+
+                return null;
+            }
+
+            // 检查配置完整性
+            Integer completionRate = config.getCompletionRate();
+            int[] pointsConfig = config.getPointsConfig();
+
+            if (completionRate == null || pointsConfig == null || pointsConfig.length == 0) {
+
+                return null;
+            }
+
+            // 3. 获取观看时长(如果为null,则从数据库累计直播+回放时长)
+            // 4.这个时间和liveWatchUser分开算
+            long actualWatchDuration = 0L;
+
+            // 4. 获取视频总时长(秒)
+            Long videoDuration = live.getDuration();
+            if (videoDuration == null || videoDuration <= 0) {
+
+                return null;
+            }
+
+            // 5. 计算完课比例
+            BigDecimal watchRate = BigDecimal.valueOf(actualWatchDuration)
+                    .multiply(BigDecimal.valueOf(100))
+                    .divide(BigDecimal.valueOf(videoDuration), 2, RoundingMode.HALF_UP);
+
+            // 限制完课比例最大值为100.00%(防止数据库字段溢出)
+            if (watchRate.compareTo(BigDecimal.valueOf(100)) > 0) {
+                watchRate = BigDecimal.valueOf(100);
+            }
+
+//            // 6. 判断是否达到完课标准
+//            if (watchRate.compareTo(BigDecimal.valueOf(completionRate)) < 0) {
+//
+//                return null;
+//            }
+
+            // 7. 检查今天是否已有完课记录
+            LocalDate today = LocalDate.now();
+            Date currentDate = Date.from(today.atStartOfDay(ZoneId.systemDefault()).toInstant());
+
+            LiveCompletionPointsRecord todayRecord = recordMapper.selectByUserAndDate(liveId, userId, currentDate);
+            if (todayRecord != null) {
+
+                return todayRecord;
+            }
+
+            // 7. 查询最近一次完课记录(不限直播间),计算连续天数
+            LiveCompletionPointsRecord latestRecord = recordMapper.selectLatestByUser(userId);
+            int continuousDays = 1;
+
+            if (latestRecord != null) {
+                LocalDate lastDate = latestRecord.getCurrentCompletionDate()
+                        .toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
+
+                long daysBetween = ChronoUnit.DAYS.between(lastDate, today);
+
+                if (daysBetween == 0) {
+                    continuousDays = latestRecord.getContinuousDays();
+
+                } else if (daysBetween == 1) {
+                    // 昨天完课了,连续天数+1
+                    continuousDays = latestRecord.getContinuousDays() + 1;
+                } else {
+                    // 中断了,重新开始
+                    continuousDays = 1;
+                }
+            }
+
+            // 8. 计算积分
+            int points = calculatePoints(continuousDays, pointsConfig);
+
+            // 9. 创建完课记录
+            LiveCompletionPointsRecord record = new LiveCompletionPointsRecord();
+            record.setLiveId(liveId);
+            record.setUserId(userId);
+            record.setWatchDuration(actualWatchDuration);
+            record.setVideoDuration(videoDuration);
+            record.setCompletionRate(watchRate);
+            record.setContinuousDays(continuousDays);
+            record.setPointsAwarded(points);
+            record.setCurrentCompletionDate(currentDate);
+            record.setReceiveStatus(0); // 未领取
+
+            if (latestRecord != null) {
+                record.setLastCompletionDate(latestRecord.getCurrentCompletionDate());
+            }
+
+            recordMapper.insertRecord(record);
+            return record;
+        } catch (Exception e) {
+            log.error("检查并创建完课记录失败, liveId={}, userId={}", liveId, userId, e);
+            throw e;
+        }
+    }
+
     /**
      * 从直播配置中获取完课积分配置
      */

+ 30 - 0
fs-service/src/main/java/com/fs/live/service/impl/LiveDataServiceImpl.java

@@ -192,6 +192,36 @@ public class LiveDataServiceImpl implements ILiveDataService {
         return R.ok().put("list", liveDataList).put("data", statistics).put("total", total);
     }
 
+    @Override
+    public List<LiveDataListVo> exportLiveData(LiveDataParam param){
+        List<Live> lives = liveMapper.listLiveData(param);
+        int total = liveMapper.listLiveDataCount(param);
+
+        if (lives == null || lives.isEmpty()) {
+            LiveDataStatisticsVo statistics = new LiveDataStatisticsVo();
+            return Collections.emptyList();
+        }
+
+        // 获取直播间ID列表
+        List<Long> liveIds = lives.stream()
+                .map(Live::getLiveId)
+                .collect(Collectors.toList());
+
+        // 查询统计数据(根据live_watch_user表查询用户的在线时长,计算平均时长
+        // 根据live_video的文件时长,判断用户的完课情况
+        // 根据live_order查询直播间的销量额和订单数)
+        LiveDataStatisticsVo statistics = baseMapper.selectLiveDataStatistics(liveIds);
+        if (statistics == null) {
+            statistics = new LiveDataStatisticsVo();
+        }
+
+        // 查询列表数据(每个直播间的详细统计数据)
+        List<LiveDataListVo> liveDataList = baseMapper.selectLiveDataListByLiveIds(liveIds);
+        if (liveDataList == null) {
+            liveDataList = Collections.emptyList();
+        }
+        return liveDataList;
+    }
     /**
      * 查询直播数据
      *

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

@@ -794,10 +794,8 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
                         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);
-                                }
+                                log.setLiveBuy(1);
+                                liveWatchLogService.updateLiveWatchLog(log);
                             }
                         }
                     }

+ 95 - 12
fs-service/src/main/java/com/fs/live/service/impl/LiveRedConfServiceImpl.java

@@ -270,7 +270,8 @@ public class LiveRedConfServiceImpl implements ILiveRedConfService {
             liveRedConf.setRedStatus(2L);
             baseMapper.updateLiveRedConf(liveRedConf);
             Set<String> range = CollUtil.newHashSet(String.valueOf(red.getRedId()));
-            finishRedStatusBySetIds(range);
+//            finishRedStatusBySetIds(range);
+            updateDbByRed(liveRedConf);
             return R.error("手慢了,红包已被抢完~");
         }
         // 记录用户红包
@@ -280,14 +281,44 @@ public class LiveRedConfServiceImpl implements ILiveRedConfService {
         record.setUserId(red.getUserId());
         record.setIntegral(integral);
         record.setCreateTime(new Date());
-        // 更新用户余额
-        BigDecimal balanceAmount = BigDecimal.valueOf(integral);
-        int updateResult = fsUserScrmMapper.incrIntegral(red.getUserId(), balanceAmount);
-        if (updateResult <= 0) {
-            log.error("更新用户余额失败,userId: {}, balance: {}", red.getUserId(), balanceAmount);
-            return R.error("更新用户余额失败");
+        
+        // 双重检查:先检查 Redis(已有),再检查数据库(防止重复领取)
+        String redisKey = String.format(LiveKeysConstant.LIVE_HOME_PAGE_CONFIG_RED, red.getLiveId(), red.getRedId());
+        Object redisRecord = redisCache.hashGet(redisKey, String.valueOf(red.getUserId()));
+        if (ObjectUtil.isNotEmpty(redisRecord)) {
+            log.warn("用户 {} 在 Redis 中已存在红包记录 redId: {},跳过重复处理", red.getUserId(), red.getRedId());
+            return R.error("您已经领取过红包了!");
+        }
+        
+        LiveUserRedRecord queryRecord = new LiveUserRedRecord();
+        queryRecord.setUserId(red.getUserId());
+        queryRecord.setRedId(red.getRedId());
+        List<LiveUserRedRecord> existingRecords = userRedRecordMapper.selectLiveUserRedRecordList(queryRecord);
+        if (existingRecords != null && !existingRecords.isEmpty()) {
+            log.warn("用户 {} 在数据库中已存在红包记录 redId: {},跳过重复处理", red.getUserId(), red.getRedId());
+            // 如果数据库已有记录但 Redis 没有,同步到 Redis
+            redisCache.hashPut(redisKey, String.valueOf(red.getUserId()), JSONUtil.toJsonStr(existingRecords.get(0)));
+            return R.error("您已经领取过红包了!");
+        }
+        
+        // 先插入数据库记录(使用数据库约束防止重复)
+        int insertResult = userRedRecordMapper.insertLiveUserRedRecord(record);
+        if (insertResult <= 0) {
+            log.error("插入红包记录失败,userId: {}, redId: {}", red.getUserId(), red.getRedId());
+            return R.error("领取红包失败,请稍后重试");
+        }
+        
+        // 插入后再次验证,防止并发插入导致重复
+        List<LiveUserRedRecord> verifyRecords = userRedRecordMapper.selectLiveUserRedRecordList(queryRecord);
+        if (verifyRecords != null && verifyRecords.size() > 1) {
+            // 发现重复记录,删除刚插入的记录并回滚
+            log.error("检测到重复红包记录,userId: {}, redId: {},记录数: {}", red.getUserId(), red.getRedId(), verifyRecords.size());
+            // 删除最后插入的记录(通常是当前请求插入的)
+            userRedRecordMapper.deleteLiveUserRedRecordById(record.getId());
+            return R.error("您已经领取过红包了!");
         }
 
+
         // 查询用户当前余额
         com.fs.hisStore.domain.FsUserScrm user = fsUserScrmMapper.selectFsUserById(red.getUserId());
         Long currentIntegral = user.getIntegral() != null ? user.getIntegral() : 0L;
@@ -304,11 +335,22 @@ public class LiveRedConfServiceImpl implements ILiveRedConfService {
         integralLogs.setStatus(0);
         integralLogs.setCreateTime(new Date());
         fsUserIntegralLogsMapper.insertFsUserIntegralLogs(integralLogs);
+        // 更新用户余额
+        BigDecimal balanceAmount = BigDecimal.valueOf(integral);
+        int updateResult = fsUserScrmMapper.incrIntegral(red.getUserId(), balanceAmount);
+        if (updateResult <= 0) {
+            log.error("更新用户余额失败,userId: {}, balance: {}", red.getUserId(), balanceAmount);
+            // 回滚:删除已插入的记录
+            userRedRecordMapper.deleteLiveUserRedRecordById(record.getId());
+            return R.error("更新用户余额失败");
+        }
+
+        // 最后更新 Redis 缓存(确保原子性:先插入数据库,再更新 Redis)
+        redisCache.hashPut(redisKey, String.valueOf(red.getUserId()), JSONUtil.toJsonStr(record));
 
         // WebSocket 通知
         //String msg = String.format("用户 %d 抢到了红包 %d,获得 %d 芳华币", userId, redId, integral);
         //WebSocketServer.notifyUsers(msg);
-        redisCache.hashPut(String.format(LiveKeysConstant.LIVE_HOME_PAGE_CONFIG_RED, red.getLiveId(), red.getRedId()), String.valueOf(red.getUserId()), JSONUtil.toJsonStr(record));
         return R.ok("恭喜您成功抢到" + integral + "芳华币");
 /*        } catch (Exception e) {
             e.printStackTrace();
@@ -358,6 +400,9 @@ public class LiveRedConfServiceImpl implements ILiveRedConfService {
         // 插入抽奖记录
         for (Long id : redIds) {
             LiveRedConf liveRedConf = baseMapper.selectLiveRedConfByRedId(id);
+            if (liveRedConf == null) {
+                continue;
+            }
             // 更新数据库
             updateDbByRed(liveRedConf);
             String hashKey = String.format(LiveKeysConstant.LIVE_HOME_PAGE_CONFIG_RED, liveRedConf.getLiveId(), liveRedConf.getRedId());
@@ -367,11 +412,49 @@ public class LiveRedConfServiceImpl implements ILiveRedConfService {
                 liveUserRedRecords = hashEntries.values().stream()
                         .map(value -> JSONUtil.toBean(JSONUtil.parseObj(value), LiveUserRedRecord.class))
                         .collect(Collectors.toList());
-                userRedRecordMapper.insertLiveUserRedRecordBatch(liveUserRedRecords);
+                
+                // 过滤掉已经存在于数据库中的记录(避免重复增加积分)
+                List<LiveUserRedRecord> newRecords = new ArrayList<>();
                 for (LiveUserRedRecord liveUserRedRecord : liveUserRedRecords) {
-                    userService.incrIntegral(Collections.singletonList(liveUserRedRecord.getUserId()), liveUserRedRecord.getIntegral());
-                    // 保存用户领取芳华币记录 方便统计计算
-                    saveUserRewardRecord(liveUserRedRecord);
+                    // 检查数据库中是否已存在该记录(使用 redId 和 userId 组合查询)
+                    LiveUserRedRecord queryRecord = new LiveUserRedRecord();
+                    queryRecord.setUserId(liveUserRedRecord.getUserId());
+                    queryRecord.setRedId(liveUserRedRecord.getRedId());
+                    List<LiveUserRedRecord> existingRecords = userRedRecordMapper.selectLiveUserRedRecordList(queryRecord);
+                    
+                    // 如果不存在,则添加到新记录列表
+                    if (existingRecords == null || existingRecords.isEmpty()) {
+                        newRecords.add(liveUserRedRecord);
+                    }
+                }
+                
+                // 只插入新记录(这些记录可能是从 Redis 同步过来的,但还没有插入数据库)
+                if (CollUtil.isNotEmpty(newRecords)) {
+                    try {
+                        userRedRecordMapper.insertLiveUserRedRecordBatch(newRecords);
+                        
+                        // 只为新记录增加积分(避免重复增加)
+                        // 注意:如果 claimRedPacket 已经处理过,这里不应该再增加积分
+                        // 所以这里只处理那些在 claimRedPacket 中还没有处理过的记录
+                        for (LiveUserRedRecord liveUserRedRecord : newRecords) {
+                            // 再次检查:如果 claimRedPacket 已经处理过,数据库中应该有记录
+                            // 如果这里还能插入,说明 claimRedPacket 没有处理过,需要增加积分
+                            LiveUserRedRecord verifyRecord = new LiveUserRedRecord();
+                            verifyRecord.setUserId(liveUserRedRecord.getUserId());
+                            verifyRecord.setRedId(liveUserRedRecord.getRedId());
+                            List<LiveUserRedRecord> verifyRecords = userRedRecordMapper.selectLiveUserRedRecordList(verifyRecord);
+                            // 确保只有一条记录,且积分还没有增加过
+                            if (verifyRecords != null && verifyRecords.size() == 1) {
+                                // 检查积分日志,如果已经增加过积分,则不再增加
+                                // 这里简化处理:如果记录存在且只有一条,说明是新的,需要增加积分
+                                userService.incrIntegral(Collections.singletonList(liveUserRedRecord.getUserId()), liveUserRedRecord.getIntegral());
+                                // 保存用户领取芳华币记录 方便统计计算
+                                saveUserRewardRecord(liveUserRedRecord);
+                            }
+                        }
+                    } catch (Exception e) {
+                        log.error("批量插入红包记录失败,redId: {}", id, e);
+                    }
                 }
             }
             redisCache.deleteObject(hashKey);

+ 8 - 0
fs-service/src/main/java/com/fs/live/vo/LiveAfterSalesVo.java

@@ -140,6 +140,14 @@ public class LiveAfterSalesVo {
     @Excel(name = "下单时间",dateFormat = "yyyy-MM-dd HH:mm:ss")
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     private Date createTime;
+    /** 创建时间 */
+    @Excel(name = "下单开始时间",dateFormat = "yyyy-MM-dd HH:mm:ss")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date createTimeBegin;
+    /** 创建时间 */
+    @Excel(name = "下单结束时间",dateFormat = "yyyy-MM-dd HH:mm:ss")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date createTimeEnd;
 
 
 

+ 19 - 0
fs-service/src/main/java/com/fs/live/vo/LiveDataListVo.java

@@ -1,6 +1,7 @@
 package com.fs.live.vo;
 
 import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fs.common.annotation.Excel;
 import lombok.Data;
 
 import java.math.BigDecimal;
@@ -15,59 +16,77 @@ import java.util.Date;
 @Data
 public class LiveDataListVo {
     /** 直播ID */
+    @Excel(name = "直播ID")
     private Long liveId;
 
     /** 直播名称 */
+    @Excel(name = "直播名称")
     private String liveName;
 
     /** 直播类型 1直播,2录播,3直播回放 */
+    @Excel(name = "直播类型 1直播,2录播,3直播回放")
     private Integer liveType;
 
     /** 直播状态 1未开播 2直播中 3已结束 4直播回放中 */
+    @Excel(name = "直播状态 1未开播 2直播中 3已结束 4直播回放中")
     private Integer status;
 
     /** 开始时间 */
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @Excel(name = "开始时间", width = 30, dateFormat = "yyyy-MM-dd")
     private Date startTime;
 
     /** 结束时间 */
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @Excel(name = "结束时间", width = 30, dateFormat = "yyyy-MM-dd")
     private Date finishTime;
 
     /** 累计观看人数 */
+    @Excel(name = "累计观看人数")
     private Long totalViewers = 0L;
 
     /** 直播观看人数 */
+    @Excel(name = "直播观看人数")
     private Long liveViewers = 0L;
 
     /** 回放观看人数 */
+    @Excel(name = "回放观看人数")
     private Long playbackViewers = 0L;
 
     /** 直播平均时长(秒) */
+    @Excel(name = "直播平均时长")
     private Long liveAvgDuration = 0L;
 
     /** 回放平均时长(秒) */
+    @Excel(name = "回放平均时长")
     private Long playbackAvgDuration = 0L;
 
     /** 累计完课人数 */
+    @Excel(name = "累计完课人数")
     private Long totalCompletedCourses = 0L;
 
     /** 直播完课人数 */
+    @Excel(name = "直播完课人数")
     private Long liveCompletedCourses = 0L;
 
     /** 回放完课人数 */
+    @Excel(name = "回放完课人数")
     private Long playbackCompletedCourses = 0L;
 
     /** GMV(总销售额) */
+    @Excel(name = "总销售额")
     private BigDecimal gmv = BigDecimal.ZERO;
 
     /** 付费人数 */
+    @Excel(name = "付费人数")
     private Long paidUsers = 0L;
 
     /** 付费单数 */
+    @Excel(name = "付费单数")
     private Long paidOrders = 0L;
 
     /** 销量统计 */
+    @Excel(name = "销量统计")
     private Long salesCount = 0L;
 }
 

+ 1 - 0
fs-service/src/main/java/com/fs/newAdv/constant/MqTopicConstant.java

@@ -24,4 +24,5 @@ public class MqTopicConstant {
      * 转化追踪consumer
      */
     public static final String CONVERSION_TRACKING_TOPIC_CONSUMER_GROUP = "conversion-topic-tracking-consumer-group";
+    public static final String CONVERSION_TRACKING_GROUP = "conversion-tracking-group";
 }

+ 16 - 7
fs-service/src/main/java/com/fs/newAdv/domain/Lead.java

@@ -31,10 +31,12 @@ public class Lead implements Serializable {
     private Long siteId;
 
     /**
-     * 链路id唯一(clickId)
+     * 链路id唯一
      */
     private String traceId;
 
+    private String clickId;
+
     /**
      * 来源平台
      */
@@ -76,16 +78,15 @@ public class Lead implements Serializable {
     /**
      * 外部联系人id 添加人id
      */
-    private String externalUserId;
-    /**
-     * 属于用户id 被添加的id
-     */
-    private String userId;
-    private String corpId;
+    private Long externalId;
     /**
      * 群Id
      */
     private String chatId;
+    /**
+     * 企微主题Id
+     */
+    private String corpId;
     /**
      * 是否添加企微 1是 0否
      */
@@ -104,6 +105,10 @@ public class Lead implements Serializable {
      */
     private String openid;
     private String unionid;
+    /**
+     * 微信名称 unionid不存在时关联
+     */
+    private String weiChatName;
     /**
      * 小程序授权1是 0否
      */
@@ -179,5 +184,9 @@ public class Lead implements Serializable {
      * 落地页访问时间
      */
     private LocalDateTime landingPageTs;
+    /**
+     * 落地页原始Url
+     */
+    private String viewUrl;
 }
 

+ 12 - 0
fs-service/src/main/java/com/fs/newAdv/dto/req/QwExternalIdBindTrackReq.java

@@ -0,0 +1,12 @@
+package com.fs.newAdv.dto.req;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+@Data
+public class QwExternalIdBindTrackReq implements Serializable {
+    private static final long serialVersionUID = 1L;
+    private Long qwExternalId;
+    private String unionid;
+}

+ 10 - 0
fs-service/src/main/java/com/fs/newAdv/dto/req/TraceIdDto.java

@@ -0,0 +1,10 @@
+package com.fs.newAdv.dto.req;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+@Data
+public class TraceIdDto implements Serializable {
+    private String traceId;
+}

+ 8 - 8
fs-service/src/main/java/com/fs/newAdv/enums/SystemEventTypeEnum.java

@@ -5,14 +5,14 @@ import lombok.Getter;
 @Getter
 public enum SystemEventTypeEnum {
 
-    GROUP_TODAY("1", "当日加群"),
-    WEI_CHAT_TODAY("2", "当日加微"),
-    TO_CLASS_AND_GROUP_TODAY("3", "直播到课且当日加群"),
-    TO_CLASS_AND_WEI_CHAT_TODAY("4", "直播到课且当日加微"),
-    BUY_ORDER("5", "商品购买订单"),
-    AUTH_TODAY_CREATE("6", "微信授权且当日创建"),
-    COMPLETE_CLASS_AND_GROUP_TODAY("7", "直播完课且当日加群"),
-    COMPLETE_CLASS_AND_WEI_CHAT_TODAY("8", "直播完课且当日加微");
+    GROUP_TODAY("event1", "当日加群"),
+    WEI_CHAT_TODAY("event2", "当日加微"),
+    TO_CLASS_AND_GROUP_TODAY("event3", "直播到课且当日加群"),
+    TO_CLASS_AND_WEI_CHAT_TODAY("event4", "直播到课且当日加微"),
+    BUY_ORDER("event5", "商品购买订单"),
+    AUTH_TODAY_CREATE("event6", "微信授权且当日创建"),
+    COMPLETE_CLASS_AND_GROUP_TODAY("event7", "直播完课且当日加群"),
+    COMPLETE_CLASS_AND_WEI_CHAT_TODAY("event8", "直播完课且当日加微");
 
     private final String code;
     private final String description;

+ 2 - 1
fs-service/src/main/java/com/fs/newAdv/event/ConversionEventListener.java

@@ -58,11 +58,12 @@ public class ConversionEventListener {
                 new SendCallback() {
                     @Override
                     public void onSuccess(SendResult sendResult) {
+                        log.error("转化消息发送成功:{}", trackId);
                     }
 
                     @Override
                     public void onException(Throwable e) {
-                        log.error("转化消息发送失败:{}", trackId);
+                        log.error("转化消息发送失败:{}", trackId,e);
                         // TODO: 记录到数据库,后续补偿
                     }
                 }

+ 2 - 3
fs-service/src/main/java/com/fs/newAdv/integration/adapter/BaiduAdapter.java

@@ -28,9 +28,8 @@ public class BaiduAdapter implements IAdvertiserAdapter {
      */
     @Override
     public Lead adaptCallbackData(Map<String, String> rawData) {
-        log.info("百度数据适配:{}", rawData);
         Lead lead = new Lead();
-        lead.setTraceId(rawData.get("bd_vid"));
+        lead.setClickId(rawData.get("bd_vid"));
         lead.setAdvertiserId(AdvertiserTypeEnum.BAIDU.getCode());
         lead.setIp(rawData.get("ip"));
         lead.setIdeaId(rawData.get("aid"));
@@ -54,7 +53,7 @@ public class BaiduAdapter implements IAdvertiserAdapter {
 
     @Override
     public void uploadConversionData(Map<String, Object> conversionData, Map<String, Object> params) {
-        conversionData.put("logidUrl",params.get("logidUrl"));
+
     }
 }
 

+ 4 - 5
fs-service/src/main/java/com/fs/newAdv/integration/adapter/OPPOAdapter.java

@@ -29,10 +29,9 @@ public class OPPOAdapter implements IAdvertiserAdapter {
      */
     @Override
     public Lead adaptCallbackData(Map<String, String> rawData) {
-        log.info("腾讯数据适配:{}", rawData);
         Lead lead = new Lead();
-        lead.setTraceId(rawData.get("request_id"));
-        lead.setAdvertiserId(AdvertiserTypeEnum.TENCENT.getCode());
+        lead.setClickId(rawData.get("request_id"));
+        lead.setAdvertiserId(AdvertiserTypeEnum.OPPO.getCode());
         lead.setIp(rawData.get("ip"));
         if (StrUtil.isNotEmpty(rawData.get("dynamic_creative_id"))){
             lead.setIdeaId(rawData.get("dynamic_creative_id"));
@@ -50,7 +49,7 @@ public class OPPOAdapter implements IAdvertiserAdapter {
                     Instant.ofEpochMilli(Long.parseLong(rawData.get("click_time"))),
                     ZoneId.systemDefault()));
         } catch (Exception e) {
-            log.error("时间转换异常", e);
+            log.error("时间转换异常");
         }
         return lead;
     }
@@ -63,8 +62,8 @@ public class OPPOAdapter implements IAdvertiserAdapter {
     @Override
     public void uploadConversionData(Map<String, Object> conversionData, Map<String, Object> params) {
         // ------------------------------oppo参数----------
-        conversionData.put("tid", params.get("tid"));
         conversionData.put("lbid", params.get("lbid"));
+        conversionData.put("pageId", params.get("pageId"));
     }
 }
 

+ 2 - 3
fs-service/src/main/java/com/fs/newAdv/integration/adapter/OceanEngineAdapter.java

@@ -29,9 +29,8 @@ public class OceanEngineAdapter implements IAdvertiserAdapter {
      */
     @Override
     public Lead adaptCallbackData(Map<String, String> rawData) {
-        log.info("巨量引擎数据适配:{}", rawData);
         Lead lead = new Lead();
-        lead.setTraceId(rawData.get("track_id"));
+        lead.setClickId(rawData.get("track_id"));
         lead.setAdvertiserId(AdvertiserTypeEnum.OCEANENGINE.getCode());
         if (StrUtil.isNotEmpty(rawData.get("cid"))) {
             lead.setIdeaId(rawData.get("cid"));
@@ -52,7 +51,7 @@ public class OceanEngineAdapter implements IAdvertiserAdapter {
                     Instant.ofEpochMilli(Long.parseLong(rawData.get("ts"))),
                     ZoneId.systemDefault()));
         } catch (Exception e) {
-            log.error("时间转换异常", e);
+            log.error("时间转换异常");
         }
         return lead;
     }

+ 1 - 2
fs-service/src/main/java/com/fs/newAdv/integration/adapter/TencentAdapter.java

@@ -28,9 +28,8 @@ public class TencentAdapter implements IAdvertiserAdapter {
      */
     @Override
     public Lead adaptCallbackData(Map<String, String> rawData) {
-        log.info("腾讯数据适配:{}", rawData);
         Lead lead = new Lead();
-        lead.setTraceId(rawData.get("request_id"));
+        lead.setClickId(rawData.get("request_id"));
         lead.setAdvertiserId(AdvertiserTypeEnum.TENCENT.getCode());
         lead.setIp(rawData.get("ip"));
         if (StrUtil.isNotEmpty(rawData.get("dynamic_creative_id"))){

+ 1 - 2
fs-service/src/main/java/com/fs/newAdv/integration/adapter/VIVOAdapter.java

@@ -28,9 +28,8 @@ public class VIVOAdapter implements IAdvertiserAdapter {
      */
     @Override
     public Lead adaptCallbackData(Map<String, String> rawData) {
-        log.info("VIVO适配:{}", rawData);
         Lead lead = new Lead();
-        lead.setTraceId(rawData.get("requestId"));
+        lead.setClickId(rawData.get("requestId"));
         lead.setAdvertiserId(AdvertiserTypeEnum.VIVO.getCode());
         lead.setIp(rawData.get("ip"));
         lead.setIdeaId(rawData.get("creativeId"));

+ 16 - 9
fs-service/src/main/java/com/fs/newAdv/integration/client/AbstractApiClient.java

@@ -11,7 +11,6 @@ import com.fs.newAdv.service.IApiCallLogService;
 import com.fs.newAdv.service.IPromotionAccountService;
 import com.fs.newAdv.vo.AccessTokenVo;
 import lombok.extern.slf4j.Slf4j;
-import org.apache.poi.ss.formula.functions.T;
 import org.springframework.beans.factory.annotation.Autowired;
 
 import java.time.LocalDateTime;
@@ -39,21 +38,29 @@ public abstract class AbstractApiClient implements IApiClient {
     protected boolean executeWithLog(AdvertiserTypeEnum advertiserType, String apiUrl, Map<String, Object> params, ApiCall action) {
         long start = System.currentTimeMillis();
         boolean callStatus = false;
-        String responseBody = "";
+        JSONObject jsonObject = null;
         try {
             log.info("[{}] 调用开始, 参数: {}", apiUrl, params);
             HttpResponse result = action.call();
-            JSONObject jsonObject = JSONUtil.parseObj(result.body());
-            Integer code = (Integer) jsonObject.get("code");
-            if (ObjectUtil.isNotEmpty(code) && (code == 0 || code == 200)) {
+            jsonObject = JSONUtil.parseObj(result.body());
+            // 通用状态
+            Integer code = jsonObject.getInt("code");
+            // 百度状态
+            Integer status = -1;
+            JSONObject header = jsonObject.getJSONObject("header");
+            if (ObjectUtil.isNotEmpty(header)) {
+                status = header.getInt("status");
+            }
+            if ((ObjectUtil.isNotEmpty(code) && (code == 0 || code == 200 ))
+            || (ObjectUtil.isNotEmpty(status) && status == 0)
+            ) {
                 callStatus = true;
             }
-            responseBody = JSONUtil.toJsonStr(jsonObject);
-            log.info("[{}] 调用成功, 耗时: {} ms, 返回结果: {}", apiUrl, System.currentTimeMillis() - start, result);
+            log.info("[{}] 调用成功, 耗时: {} ms, 返回结果: {}", apiUrl, System.currentTimeMillis() - start, jsonObject);
         } catch (Exception e) {
             log.error("[{}] 调用失败, 耗时: {} ms, 错误信息: {}", apiUrl, System.currentTimeMillis() - start, e.getMessage(), e);
         }
-        saveApiCallLog(advertiserType, apiUrl, params, responseBody, callStatus, start);
+        saveApiCallLog(advertiserType, apiUrl, params, jsonObject != null ? JSONUtil.toJsonStr(jsonObject) : null, callStatus, start);
         return callStatus;
     }
 
@@ -74,7 +81,7 @@ public abstract class AbstractApiClient implements IApiClient {
     protected String getAccessToken(Long promotionAccountId) {
         PromotionAccount byId = promotionAccountService.getById(promotionAccountId);
         // 判断token是否过期 提前1小时刷新
-        if (byId.getExpireTime().isBefore(LocalDateTime.now().plusHours(1))) {
+        if (ObjectUtil.isEmpty(byId.getExpireTime()) || byId.getExpireTime().isBefore(LocalDateTime.now().plusHours(1))) {
             // 获取请求参数
             IApiClient apiClient = advertiserHandlerFactory.getApiClient(AdvertiserTypeEnum.getByCode(byId.getAdvertiserId()));
             IAccessTokenClient tokenClient = (IAccessTokenClient) apiClient;

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

@@ -85,7 +85,7 @@ public class BaiduApiClient extends AbstractApiClient implements IAccessTokenCli
         Map<String, Object> conversion = new HashMap<>();
 
         // 点击ID(必填)
-        String logidUrl = (String) conversionData.get("logidUrl");
+        String logidUrl = (String) conversionData.get("viewUrl");
         if (StrUtil.isBlank(logidUrl)) {
             throw new ThirdPartyException("落地页不能为空");
         }

+ 13 - 11
fs-service/src/main/java/com/fs/newAdv/integration/client/advertiser/OPPOApiClient.java

@@ -8,10 +8,13 @@ import com.fs.newAdv.domain.SiteStatistics;
 import com.fs.newAdv.enums.AdvertiserTypeEnum;
 import com.fs.newAdv.integration.client.AbstractApiClient;
 import com.fs.common.constant.SystemConstant;
+import com.google.common.hash.Hashing;
+import com.google.common.io.BaseEncoding;
 import lombok.extern.slf4j.Slf4j;
 import me.chanjar.weixin.common.util.crypto.SHA1;
 import org.springframework.stereotype.Component;
 
+import java.nio.charset.Charset;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -45,15 +48,15 @@ public class OPPOApiClient extends AbstractApiClient {
             // 构建请求参数
             Map<String, Object> requestBody = buildConversionParams(conversionData);
             Long timestamp = (Long) conversionData.get("timestamp");
-            String ownerId = (String) conversionData.get("ownerId");
+            String ownerId = (String) conversionData.get("adAccountId");
             String appId = (String) conversionData.get("appId");
-            String appKey = (String) conversionData.get("appKey");
-            String sign = SHA1.gen(appId + appKey + timestamp);
-            String token = Base64.encode(ownerId + "," + appId + "," + timestamp + "," + sign);
+            String appKey = (String) conversionData.get("appSecret");
+            String sign = Hashing.sha1().hashString(appId + appKey + timestamp, Charset.defaultCharset()).toString();
+            String token = BaseEncoding.base64().encode((ownerId + "," + appId + "," + timestamp + "," + sign).getBytes());
             // 发送HTTP请求
             return HttpRequest.post(CONVERSION_API_URL)
                     .header("Content-Type", "application/json")
-                    .header("Authorization", "Bearer  " + token)
+                    .header("Authorization", "Bearer " + token)
                     .body(JSONUtil.toJsonStr(requestBody))
                     .timeout(SystemConstant.API_TIMEOUT)
                     .execute();
@@ -70,18 +73,17 @@ public class OPPOApiClient extends AbstractApiClient {
         Map<String, Object> params = new HashMap<>();
 
         // 落地页Id:投放广告到投放
-        params.put("pageId", conversionData.get("traceId"));
+        params.put("pageId", conversionData.get("pageId"));
         // 广告主id:对应广告主自提供
-        params.put("adAccountId", conversionData.get("adAccountId"));
+        params.put("ownerId", conversionData.get("adAccountId"));
         // 用户IP:广告主收集
-        // params.put("ip", conversionData.get("ip"));
-        params.put("ip", "192.168.1.1");
+        params.put("ip", "127.0.0.1");
         // traceId:播放时追加在url上
-        params.put("tid", conversionData.get("tid"));
+        params.put("tid", conversionData.get("traceId"));
         // 流量号:播放时追加在URL上
         params.put("lbid ", conversionData.get("lbid"));
         // 事件
-        params.put("transformType ", conversionData.get("eventType"));
+        params.put("transformType", conversionData.get("eventType"));
         return params;
     }
 

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

@@ -18,8 +18,11 @@ import com.fs.newAdv.vo.AccessTokenVo;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.http.client.utils.URIBuilder;
 import org.springframework.stereotype.Component;
+import org.springframework.web.util.UriComponentsBuilder;
 
 import java.math.BigDecimal;
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
 import java.time.LocalDateTime;
 import java.util.Arrays;
 import java.util.Collections;
@@ -63,9 +66,16 @@ public class OceanEngineApiClient extends AbstractApiClient implements IAccessTo
         return executeWithLog(AdvertiserTypeEnum.OCEANENGINE, CONVERSION_API_URL, conversionData, () -> {
             // 构建请求参数
             Map<String, Object> requestBody = buildConversionParams(conversionData);
+/*            UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(CONVERSION_API_URL)
+                    .queryParam("callback", requestBody.get("traceId")) // 核心参数,监测链接透传的内容
+                    .queryParam("event_type", requestBody.get("event_type"))
+                    .queryParam("conv_time", requestBody.get("conv_time"));
+            URI uri = builder.build().encode(StandardCharsets.UTF_8).toUri();
+            log.info("准备调用巨量引擎回传接口, URL: {}", uri);*/
+
             // 发送HTTP请求
             return HttpRequest.get(CONVERSION_API_URL)
-                    .form(JSONUtil.toJsonStr(requestBody))
+                    .form(requestBody)
                     .timeout(SystemConstant.API_TIMEOUT)
                     .execute();
         });
@@ -94,6 +104,7 @@ public class OceanEngineApiClient extends AbstractApiClient implements IAccessTo
 
     @Override
     public SiteStatistics getDataReport(PromotionAccount account, String ideaId, String startDate, String endDate) {
+        log.info("开始获取头条报表数据:{} {} {} {}",account.getId(), ideaId,startDate,endDate);
         // 构建请求参数
         Map<String, Object> map = new HashMap<>();
         map.put("advertiser_id", Long.valueOf(account.getAdAccountId()));
@@ -142,8 +153,15 @@ public class OceanEngineApiClient extends AbstractApiClient implements IAccessTo
                 .header("Access-Token", getAccessToken(account.getId()))
                 .timeout(SystemConstant.API_TIMEOUT)
                 .execute();
-        JSONObject jsonObject = JSONUtil.parseObj(execute.body());
+        String body = execute.body();
+        log.info("头条报表数据返回:{} ",body);
+        JSONObject jsonObject = JSONUtil.parseObj(body);
         JSONObject data = jsonObject.getJSONObject("data");
+        int code = jsonObject.getInt("code");
+        if (code != 0){
+            log.error("头条报表数据返回异常:{} ",body);
+            return new SiteStatistics();
+        }
         JSONArray rows = data.getJSONArray("rows");
         JSONObject jsonObject1 = rows.getJSONObject(0);
         JSONObject jsonObject2 = jsonObject1.getJSONObject("metrics");
@@ -200,5 +218,6 @@ public class OceanEngineApiClient extends AbstractApiClient implements IAccessTo
                 .expireTime(LocalDateTime.now().plusSeconds(data.getLong("refresh_token_expires_in")))
                 .build();
     }
+
 }
 

+ 1 - 1
fs-service/src/main/java/com/fs/newAdv/integration/client/advertiser/VIVOApiClient.java

@@ -82,7 +82,7 @@ public class VIVOApiClient extends AbstractApiClient {
 
     @Override
     public AdvertiserTypeEnum getAdvertiserType() {
-        return AdvertiserTypeEnum.OPPO;
+        return AdvertiserTypeEnum.VIVO;
     }
 
     @Override

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

@@ -16,6 +16,7 @@ import java.util.concurrent.CompletableFuture;
 public interface ILeadService extends IService<Lead> {
 
     Lead getByTraceId(String traceId);
+    Lead getByClickId(String clickId);
 
     /**
      * 用户加企业群线索处理
@@ -28,12 +29,12 @@ public interface ILeadService extends IService<Lead> {
 
     /**
      * 用户添加企业微信线索处理
-     * @param externalUserID
-     * @param userID
-     * @param corpId
-     * @param state
      */
-    CompletableFuture<String> updateAddMemberLead(String externalUserID, String userID, String corpId, String state);
+    void updateAddMemberLead(Long externalId, String unionId);
+    /**
+     * 用户添加企业微信线索处理
+     */
+    void updateAddMemberLead(String externalUserID,String userID,String corpId,String State);
     /**
      * 小程序授权线索处理
      * @param traceId
@@ -43,6 +44,13 @@ public interface ILeadService extends IService<Lead> {
      */
     void weChatAuthorizationLead(String traceId, String unionId, String maOpenId, String phone);
 
+    /**
+     * 小程序获取头像昵称线索处理
+     * @param traceId
+     * @param weiChatName
+     */
+    void weChatNameLead(String traceId, String weiChatName);
+
     /**
      * 小程序授权落地页访问线索处理
      * @param traceId

+ 95 - 59
fs-service/src/main/java/com/fs/newAdv/service/impl/LeadServiceImpl.java

@@ -1,6 +1,7 @@
 package com.fs.newAdv.service.impl;
 
 import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
@@ -11,17 +12,14 @@ import com.fs.newAdv.mapper.LeadMapper;
 import com.fs.newAdv.service.ILeadService;
 import com.fs.qw.domain.QwExternalContact;
 import com.fs.qw.mapper.QwExternalContactMapper;
-import com.fs.qwApi.domain.QwExternalContactResult;
-import com.fs.qwApi.service.QwApiService;
+import com.fs.qw.service.IQwExternalContactService;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
 
 import java.time.LocalDate;
-import java.util.Collections;
 import java.util.List;
-import java.util.concurrent.CompletableFuture;
 
 /**
  * 域名管理Service实现类
@@ -36,9 +34,9 @@ public class LeadServiceImpl extends ServiceImpl<LeadMapper, Lead> implements IL
 
     @Autowired
     private ConversionEventPublisher conversionEventPublisher;
-
     @Autowired
-    private QwApiService qwApiService;
+    private IQwExternalContactService qwExternalContactService;
+
     @Autowired
     private QwExternalContactMapper qwExternalContactMapper;
 
@@ -52,27 +50,40 @@ public class LeadServiceImpl extends ServiceImpl<LeadMapper, Lead> implements IL
         return list.isEmpty() ? null : list.get(0);
     }
 
+    @Override
+    public Lead getByClickId(String clickId) {
+        List<Lead> list = this.list(new LambdaQueryWrapper<Lead>()
+                .eq(Lead::getClickId, clickId));
+        if (list.size() > 1) {
+            log.error("查询投流点击信息不唯一:{}", clickId);
+        }
+        return list.isEmpty() ? null : list.get(0);
+    }
+
     @Override
     @Async
-    public void updateGroupAddMemberLead(String userId, String chatId, String corpId, String unionid) {
+    public void updateGroupAddMemberLead(String name, String chatId, String corpId, String unionid) {
+        log.info("用户加群线索信息:{} {} {} {}", name, chatId, corpId, unionid);
+        LambdaQueryWrapper<Lead> last = new LambdaQueryWrapper<Lead>();
+        if (StrUtil.isNotEmpty(unionid)) {
+            last.eq(Lead::getUnionid, unionid);
+        } else if (StrUtil.isNotEmpty(name)) {
+            last.eq(Lead::getWeiChatName, name);
+        }
+        last.eq(Lead::getAddContactQw, 0).last("LIMIT 1");
         // 末次归因逻辑
-        Lead lead = this.getOne(new LambdaQueryWrapper<Lead>()
-                .eq(Lead::getUnionid, unionid)
-                .eq(Lead::getAddContactQw, 0)
-                .last("LIMIT 1"));
+        Lead lead = this.getOne(last);
         if (lead != null) {
-            lead.setCorpId(corpId);
             lead.setChatId(chatId);
-            lead.setUserId(userId);
             lead.setAddContactQw(1);
+            lead.setCorpId(corpId);
             this.updateById(lead);
-
             if (ObjectUtil.isNotEmpty(lead.getLandingPageTs()) && lead.getLandingPageTs().toLocalDate().isEqual(LocalDate.now())) {
                 // 当日加群事件回调
                 conversionEventPublisher.publishConversionEvent(lead.getTraceId(), SystemEventTypeEnum.GROUP_TODAY);
             }
         } else {
-            log.info("用户加群线索信息不存在:{}", userId);
+            log.info("用户加群线索信息不存在:{}", name);
         }
 
 
@@ -80,65 +91,90 @@ public class LeadServiceImpl extends ServiceImpl<LeadMapper, Lead> implements IL
 
     @Override
     @Async
-    public CompletableFuture<String> updateAddMemberLead(String externalUserID, String userID, String corpId, String state) {
-        QwExternalContactResult externalContactResult = qwApiService.getExternalcontact(externalUserID, corpId);
-        String unionid = externalContactResult.getExternal_contact().getUnionid();
+    public void updateAddMemberLead(Long externalId, String unionid) {
+        QwExternalContact qwExternalContact = qwExternalContactService.selectQwExternalContactById(externalId);
+        if (qwExternalContact == null) {
+            log.info("外部联系人信息不存在:{}", externalId);
+            return;
+        }
+        qwExternalContact.setUnionid(unionid);
+        this.updateAddMemberLead(qwExternalContact);
+    }
+
+    @Override
+    public void updateAddMemberLead(String externalUserID, String userID, String corpId, String State) {
+        QwExternalContact qwExternalContact = qwExternalContactMapper.selectQwExternalByExternalIdAndCompanyIdToIdAndFs(externalUserID, userID, corpId);
+        if (qwExternalContact == null) {
+            log.info("外部联系人信息不存在:{} {} {}", externalUserID, userID, corpId);
+            return;
+        }
+        this.updateAddMemberLead(qwExternalContact);
+    }
+
+    private void updateAddMemberLead(QwExternalContact qwExternalContact) {
+        log.info("用户加微线索信息:{}", qwExternalContact);
+        LambdaQueryWrapper<Lead> last = new LambdaQueryWrapper<Lead>();
+        if (StrUtil.isNotEmpty(qwExternalContact.getUnionid())) {
+            last.eq(Lead::getUnionid, qwExternalContact.getUnionid());
+        } else if (StrUtil.isNotEmpty(qwExternalContact.getName())) {
+            last.eq(Lead::getWeiChatName, qwExternalContact.getName());
+        }
+        last.eq(Lead::getAddContactQwGroup, 0).last("LIMIT 1");
         // 末次归因逻辑
-        Lead lead = this.getOne(new LambdaQueryWrapper<Lead>()
-                .eq(Lead::getUnionid, unionid)
-                .eq(Lead::getAddContactQwGroup, 0)
-                .last("LIMIT 1"));
+        Lead lead = this.getOne(last);
         if (lead != null) {
-            lead.setExternalUserId(externalUserID);
-            lead.setCorpId(corpId);
-            lead.setUserId(userID);
+            lead.setExternalId(qwExternalContact.getId());
             lead.setAddContactQwGroup(1);
             this.updateById(lead);
-
             // 绑定企微用户线索关系
-            QwExternalContact qwExternalContact = qwExternalContactMapper.selectQwExternalByExternalIdAndCompanyIdToIdAndFs(externalUserID, userID, corpId);
-            if (qwExternalContact != null){
-                QwExternalContact temp = new QwExternalContact();
-                temp.setId(qwExternalContact.getId());
-                temp.setTraceId(lead.getTraceId());
-                qwExternalContactMapper.updateById(temp);
-            }else {
-                log.info("广告归因企微用户信息不存在:{} {} {} ", externalUserID, userID,corpId);
-            }
+            QwExternalContact temp = new QwExternalContact();
+            temp.setId(qwExternalContact.getId());
+            temp.setTraceId(lead.getTraceId());
+            qwExternalContactMapper.updateById(temp);
             if (ObjectUtil.isNotEmpty(lead.getLandingPageTs()) && lead.getLandingPageTs().toLocalDate().isEqual(LocalDate.now())) {
                 // 当日加微事件回调
                 conversionEventPublisher.publishConversionEvent(lead.getTraceId(), SystemEventTypeEnum.WEI_CHAT_TODAY);
             }
-            return CompletableFuture.completedFuture(lead.getTraceId());
+
         } else {
-            log.info("广告归因线索不存在:{} {} {}", externalUserID, userID,corpId);
+            log.info("广告归因线索不存在:{}", qwExternalContact);
         }
-        return CompletableFuture.completedFuture(null);
     }
 
     @Override
     @Async
     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(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){
-           e.printStackTrace();
-       }
+        log.info("用户微信授权线索信息:{} {} {} {}", traceId, unionId, phone, maOpenId);
+        Lead byTraceId = this.getByTraceId(traceId);
+        if (byTraceId == null) {
+            return;
+        }
+        this.update(new LambdaUpdateWrapper<Lead>()
+                .eq(Lead::getTraceId, traceId)
+                .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);
+        }
+    }
+
+    @Override
+    @Async
+    public void weChatNameLead(String traceId, String weiChatName) {
+        log.info("用户微信昵称线索信息:{} {}", traceId, weiChatName);
+        Lead byTraceId = this.getByTraceId(traceId);
+        if (byTraceId == null || StrUtil.isEmpty(weiChatName)) {
+            return;
+        }
+        this.update(new LambdaUpdateWrapper<Lead>()
+                .eq(Lead::getTraceId, traceId)
+                .isNull(Lead::getWeiChatName)
+                .set(Lead::getWeiChatName, weiChatName)
+        );
     }
 
     @Override
@@ -161,7 +197,7 @@ public class LeadServiceImpl extends ServiceImpl<LeadMapper, Lead> implements IL
         boolean update = this.update(new LambdaUpdateWrapper<Lead>()
                 .eq(Lead::getTraceId, traceId)
                 .set(Lead::getMiniLaunchIndexCount, 1));
-        if (!update){
+        if (!update) {
             log.error("发起进入小程序失败:{}", traceId);
         }
     }

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

@@ -25,7 +25,7 @@ public class SiteServiceImpl extends ServiceImpl<SiteMapper, Site> implements IS
     @Transactional(rollbackFor = Exception.class)
     public void createSite(Site site) {
         this.save(site);
-        site.setSiteUrl("https://" + site.getLaunchDomain() + "/#/pages/index/index?siteId=" + site.getId());
+        site.setSiteUrl("https://" + site.getLaunchDomain() + "/pages/index/index?siteId=" + site.getId());
         this.updateById(site);
     }
 

+ 12 - 0
fs-service/src/main/java/com/fs/newAdv/vo/ConversionParmVo.java

@@ -0,0 +1,12 @@
+package com.fs.newAdv.vo;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 回传通用参数类
+ */
+@Data
+public class ConversionParmVo implements Serializable {
+}

+ 1 - 1
fs-service/src/main/java/com/fs/qw/service/impl/AsyncChatSopService.java

@@ -71,7 +71,7 @@ public class AsyncChatSopService {
 
     private void processInternal(ChatSopRuleTimeVO timeVO, QwSopTemp temp, Map<String, QwUserVO> qwUserMap, Map<String, QwGroupChat> groupChatMap) {
         if(StringUtils.isNotEmpty(timeVO.getChatId())){
-            List<SopUserLogs> list = Arrays.stream(timeVO.getChatId().split(",")).map(e -> {
+            List<SopUserLogs> list = Arrays.stream(timeVO.getChatId().split(",")).filter(StringUtils::isNotEmpty).map(e -> {
                 SopUserLogs sopUserLogs = new SopUserLogs();
                 sopUserLogs.setSopId(timeVO.getId());
                 sopUserLogs.setSopTempId(temp.getId());

+ 5 - 0
fs-service/src/main/java/com/fs/qw/vo/QwExternalContactVO.java

@@ -143,4 +143,9 @@ public class QwExternalContactVO {
      * 联系我分组名称
      */
     private String wayGroupName;
+
+    /**
+     * 广告链路id
+     */
+    private String traceId;
 }

+ 1 - 1
fs-service/src/main/java/com/fs/sop/service/impl/SopUserLogsServiceImpl.java

@@ -1049,7 +1049,7 @@ public class SopUserLogsServiceImpl implements ISopUserLogsService {
         List<QwGroupChat> qwGroupChatList = qwGroupChatService.selectQwGroupChatByChatIds(vo.getChatIds().split(","));
         Map<String, QwGroupChat> groupChatMap = PubFun.listToMapByGroupObject(qwGroupChatList, QwGroupChat::getChatId);
 
-        List<SopUserLogs> list = Arrays.stream(vo.getChatIds().split(",")).map(e -> {
+        List<SopUserLogs> list = Arrays.stream(vo.getChatIds().split(",")).filter(StringUtils::isNotEmpty).distinct().map(e -> {
             SopUserLogs sopUserLogs = new SopUserLogs();
             sopUserLogs.setSopId(qwSop.getId());
             sopUserLogs.setSopTempId(qwSop.getTempId());

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

@@ -87,8 +87,8 @@ tencent_cloud_config:
 cloud_host:
   company_name: 叮当国医
   projectCode: DDGY
-  spaceName:
-  volcengineUrl:
+  spaceName: ddgy-2114522511
+  volcengineUrl: https://ddgyvolcengine.ylrztop.com
 #看课授权时显示的头像
 headerImg:
   imgUrl: https://ddgy-1323137866.cos.ap-chongqing.myqcloud.com/fs/20251010/ddgy.jpg

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

@@ -106,8 +106,8 @@ tmp_secret_config:
 cloud_host:
   company_name: 福本源
   projectCode: FBY
-  spaceName:
-  volcengineUrl:
+  spaceName: fby-2114522511
+  volcengineUrl: https://fbyvolcengine.ylrztop.com
 headerImg:
   imgUrl: https://fbylive.obs.cn-southwest-2.myhuaweicloud.com/fs/20250730/1753840024082.png
 ipad:

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

@@ -83,8 +83,8 @@ tencent_cloud_config:
 cloud_host:
   company_name: 广州郑多燕
   projectCode: GZZDY
-  spaceName:
-  volcengineUrl:
+  spaceName: gzzdy-2114522511
+  volcengineUrl: https://gzzdyvolcengine.ylrztop.com
 #看课授权时显示的头像
 headerImg:
   imgUrl:

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

@@ -83,8 +83,8 @@ tencent_cloud_config:
 cloud_host:
   company_name: 鸿森堂
   projectCode: HST
-  spaceName:
-  volcengineUrl:
+  spaceName: hst-2114522
+  volcengineUrl: https://hstvolcengine.ylrztop.com
 #看课授权时显示的头像
 headerImg:
   imgUrl:

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

@@ -88,7 +88,8 @@ tmp_secret_config:
 cloud_host:
   company_name: 金牛明医
   projectCode: JNMY
-  spaceName:
+  spaceName: cdjnmy-2114522511
+  volcengineUrl: https://cdjnmyvolcengine.ylrztop.com
 headerImg:
   imgUrl: https
 ipad:

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

@@ -83,8 +83,8 @@ tencent_cloud_config:
 cloud_host:
   company_name: 宽益堂
   projectCode: KYT
-  spaceName:
-  volcengineUrl:
+  spaceName: kyt-2114522511
+  volcengineUrl: https://kytvolcengine.ylrztop.com
 #看课授权时显示的头像
 headerImg:
   imgUrl: https://kuanyitang-1317640934.cos.ap-shanghai.myqcloud.com/kuanyitang/20250813/6b3b62e01672407c98f0561b73e35f6a.jpg

+ 3 - 5
fs-service/src/main/resources/application-config-druid-ylrz.yml

@@ -37,10 +37,8 @@ wx:
       port: 6379
       timeout: 2000
     configs:
-#      - appId: wxce847c8ebe5e62aa # 第一个公众号的appid  //公众号名称:济南联智健康
-#        secret: 37f7c5e3b7ff07794343957f7ced8de4 # 公众号的appsecret--济南联智健康
-      - appId: wxd6905bed94e45ef0 # 第一个公众号的appid  //公众号名称:济南联智健康
-        secret: c1a042a55bac24033535da50ce8ab6a2 # 公众号的appsecret--济南联智健康
+      - appId: wx93ce67750e3cfba3 # 第一个公众号的appid  //公众号名称:云联融智
+        secret: c172884087264160563bfe5775ca0f6f # 公众号的appsecret
         token: PPKOdAlCoMO # 接口配置里的Token值
         aesKey: Eswa6VjwtVMCcw03qZy6fWllgrv5aytIA1SZPEU0kU2 # 接口配置里的EncodingAESKey值
 aifabu:  #爱链接
@@ -86,7 +84,7 @@ headerImg:
   imgUrl:
 
 ipad:
-  ipadUrl: http://1ipad.ljhehualu.com
+  ipadUrl: http://qwipad.scrm.ylrzcloud.com
   aiApi: http://149.232.181.28:3000/api
   voiceApi: http://229.28.187.88:8667
   commonApi: http://229.28.187.88:7771

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

@@ -9,7 +9,7 @@ spring:
         # 数据库索引
         database: 0
         # 密码
-        password: !@#123QWe
+        password: QWE123qwe
         # 连接超时时间
         timeout: 30s
         lettuce:

+ 3 - 0
fs-service/src/main/resources/application-druid-ylrz.yml

@@ -142,6 +142,9 @@ rocketmq:
         group: conversion-tracking-group
         access-key: ak19op47padq83a6882e2edb # 替换为实际的 accessKey
         secret-key: sk4ca8a0dbdac7ed75 # 替换为实际的 secretKey
+    consumer:
+        access-key: ak19op47padq83a6882e2edb # 替换为实际的 accessKey
+        secret-key: sk4ca8a0dbdac7ed75 # 替换为实际的 secretKey
 openIM:
     secret: openIM123
     userID: imAdmin

+ 15 - 1
fs-service/src/main/resources/mapper/course/FsUserCourseVideoMapper.xml

@@ -305,7 +305,7 @@
         </if>
         <!-- 营销提前查看天数逻辑 -->
         AND DATE_SUB(fcpd.day_date, INTERVAL fcp.max_view_num DAY) &lt;= now()
-        order by video.course_sort
+        order by fcpd.start_date_time, ccut.start_date_time, video.course_sort
     </select>
 
     <select id="selectVideoListByMap" resultType="com.fs.his.vo.OptionsVO">
@@ -503,4 +503,18 @@
            and video.is_del = 0  and video.course_id= #{courseId}
             limit 1
     </select>
+
+    <update id="batchDown" parameterType="String">
+        update fs_user_course_video set is_on_put = 1 where video_id in
+        <foreach item="videoId" collection="array" open="(" separator="," close=")">
+            #{videoId}
+        </foreach>
+    </update>
+
+    <update id="batchEditCover" parameterType="com.fs.course.param.BatchEditCoverParam">
+        update fs_user_course_video set thumbnail = #{thumbnail} where video_id in
+        <foreach item="videoId" collection="videoIds" open="(" separator="," close=")">
+            #{videoId}
+        </foreach>
+    </update>
 </mapper>

+ 9 - 0
fs-service/src/main/resources/mapper/his/FsUserMapper.xml

@@ -2433,5 +2433,14 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         select * from fs_user where phone=#{phone}
     </select>
 
+    <!-- 批量更新用户积分(增加积分) -->
+    <update id="batchUpdateUserIntegral">
+        update fs_user
+        set integral = integral + #{addIntegral}
+        where user_id in
+        <foreach collection="userIds" item="userId" open="(" separator="," close=")">
+            #{userId}
+        </foreach>
+    </update>
 
 </mapper>

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

@@ -957,8 +957,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
                     WHEN order_code = #{item.orderNumber} THEN #{item.deliveryPayTime}
                 </foreach>
                 ELSE delivery_pay_time
-                END
+                END,
             </if>
+            delivery_send_time = NOW()
         </set>
         WHERE order_code IN
         <foreach collection="list" item="item" open="(" separator="," close=")">
@@ -1594,7 +1595,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     <select id="selectFsStoreOrderListVO" resultType="com.fs.hisStore.vo.FsStoreOrderVO">
         select DISTINCT o.id,o.order_code,o.extend_order_id,o.pay_order_id,o.bank_order_id,o.user_id,o.real_name,o.user_phone,o.user_address,o.cart_id,o.freight_price,o.total_num,o.total_price,o.total_postage,o.pay_price,o.pay_postage,o.pay_delivery,o.pay_money,o.deduction_price,o.coupon_id,o.coupon_price,o.paid,o.pay_time,o.pay_type,o.create_time,o.update_time,o.status,o.refund_status,o.refund_reason_wap_img,o.refund_reason_wap_explain,o.refund_reason_time,o.refund_reason_wap,o.refund_reason,o.refund_price,o.delivery_sn,o.delivery_name,o.delivery_type,o.delivery_id,o.gain_integral,o.use_integral,o.pay_integral,o.back_integral,o.mark,o.is_del,o.remark,o.verify_code,o.store_id,o.shipping_type,o.is_channel,o.is_remind,o.is_sys_del,o.is_prescribe,o.prescribe_id,o.company_id,o.company_user_id,o.is_package,o.package_json,o.order_type,o.package_id,o.finish_time,o.delivery_status,o.delivery_pay_status,o.delivery_time,o.delivery_pay_time,o.delivery_pay_money,o.tui_money,o.tui_money_status,o.delivery_import_time,o.tui_user_id,o.tui_user_money_status,o.order_create_type,o.store_house_code,o.dept_id,o.is_edit_money,o.customer_id,o.is_pay_remain,o.delivery_send_time,o.certificates,o.upload_time,o.item_json,o.schedule_id,o.delivery_pay_type,o.order_visit,o.service_fee,o.cycle,o.prescribe_price,o.follow_doctor_id,o.follow_time,o.user_coupon_id,o.order_medium,o.erp_phone
         ,u.phone,u.register_code,u.register_date,u.source, c.company_name ,cu.nick_name as company_user_nick_name ,cu.phonenumber as company_usere_phonenumber
-        , csc.name miniProgramName,fsp.cost, fspc.cate_name,spavs.bar_code, sp_latest.bank_transaction_id as bankTransactionId
+        , csc.name miniProgramName,fsp.cost, fspc.cate_name,spavs.bar_code, sp_latest.bank_transaction_id as bankTransactionId, o.is_audit
         from fs_store_order_scrm o
         left join fs_user u on o.user_id=u.user_id
         left join company c on c.company_id=o.company_id
@@ -1732,6 +1733,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="maps.erpAccount == '未分拣'">
                 and ( df.login_account is null or df.login_account like '')
             </if>
+            <if test="maps.isAudit != null   ">
+                and o.is_audit = #{maps.isAudit}
+            </if>
         </where>
         ${maps.params.dataScope}
 <!--        <if test="maps.productName != null and  maps.productName !=  ''   ">-->
@@ -1837,7 +1841,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
                 and o.status = #{maps.status}
             </if>
             <if test="maps.status == 6">
-                and o.`status`= 1
+                and o.`status`= 2
                 and  ( o.extend_order_id is null or  o.extend_order_id like '')
             </if>
             <if test="maps.isUpload != null and maps.isUpload == 0    ">
@@ -1916,6 +1920,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="maps.erpAccount == '未分拣'">
                 and ( df.login_account is null or df.login_account like '')
             </if>
+            <if test="maps.isAudit != null ">
+                and o.is_audit = #{maps.isAudit}
+            </if>
         </where>
         ${maps.params.dataScope}
 <!--        <if test="maps.productName != null and  maps.productName !=  ''   ">-->
@@ -2129,6 +2136,16 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             #{item.id}
         </foreach>
     </update>
+
+    <!-- 批量更新订单审核状态 -->
+    <update id="batchUpdateAuditStatus">
+        UPDATE fs_store_order_scrm
+        SET is_audit = #{isAudit}
+        WHERE id IN
+        <foreach collection="orderIds" item="orderId" open="(" separator="," close=")">
+            #{orderId}
+        </foreach>
+    </update>
     <select id="getEligibleOrderCoupons" resultType="com.fs.hisStore.domain.FsStoreOrderScrm">
         SELECT * FROM fs_store_order_scrm
         WHERE user_id = #{userId}

+ 104 - 64
fs-user-app/src/main/java/com/fs/app/controller/live/LiveCompletionPointsController.java

@@ -16,7 +16,12 @@ import com.fs.live.domain.LiveCompletionPointsRecord;
 import com.fs.live.mapper.LiveCompletionPointsRecordMapper;
 import com.fs.live.service.ILiveCompletionPointsRecordService;
 import com.fs.live.service.ILiveService;
+import org.redisson.api.RLock;
+import org.redisson.api.RedissonClient;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.transaction.annotation.Transactional;
 import org.springframework.web.bind.annotation.*;
 
 import java.math.BigDecimal;
@@ -27,6 +32,7 @@ import java.util.Date;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.TimeUnit;
 
 /**
  * 直播完课积分Controller
@@ -35,6 +41,8 @@ import java.util.Map;
 @RequestMapping("/app/live/completion")
 public class LiveCompletionPointsController extends AppBaseController {
 
+    private static final Logger logger = LoggerFactory.getLogger(LiveCompletionPointsController.class);
+
     @Autowired
     private ILiveCompletionPointsRecordService completionPointsRecordService;
 
@@ -50,6 +58,9 @@ public class LiveCompletionPointsController extends AppBaseController {
     @Autowired
     private LiveCompletionPointsRecordMapper completionPointsRecordMapper;
 
+    @Autowired
+    private RedissonClient redissonClient;
+
     /**
      * 领取完课积分
      */
@@ -152,9 +163,7 @@ public class LiveCompletionPointsController extends AppBaseController {
             
             // 3. 如果没有记录,查询直播间配置并生成记录
             if (record == null) {
-                completionPointsRecordService.checkAndCreateCompletionRecord(liveId, userId, null);
-                // 重新查询
-                record = completionPointsRecordMapper.selectLatestByUserAndLiveId(liveId, userId);
+                record = completionPointsRecordService.createCompletionRecord(liveId, userId);
             }
 
             // 4. 计算剩余时长
@@ -183,6 +192,7 @@ public class LiveCompletionPointsController extends AppBaseController {
      * 更新用户看课completionPointsRecordService看课记录里面的时长
      */
     @PostMapping("/update-watch-duration")
+    @Transactional
     public R updateWatchDuration(@RequestParam Long liveId, @RequestParam Long watchDuration) {
         Long userId = Long.parseLong(getUserId());
         
@@ -252,8 +262,9 @@ public class LiveCompletionPointsController extends AppBaseController {
             // 6. 更新完课记录中的看课时长
             if (record == null) {
                 // 如果没有记录,先创建记录
-                completionPointsRecordService.checkAndCreateCompletionRecord(liveId, userId, finalWatchDuration);
-                record = completionPointsRecordMapper.selectLatestByUserAndLiveId(liveId, userId);
+                record = completionPointsRecordService.createCompletionRecord(liveId, userId);
+                record.setWatchDuration(finalWatchDuration);
+                completionPointsRecordMapper.updateRecord(record);
             } else {
                 // 更新现有记录的看课时长
                 Long currentWatchDuration = record.getWatchDuration() != null 
@@ -272,7 +283,10 @@ public class LiveCompletionPointsController extends AppBaseController {
                     record.setCompletionRate(completionRate);
                 }
                 
-                completionPointsRecordMapper.updateRecord(record);
+                int updateResult = completionPointsRecordMapper.updateRecord(record);
+                if (updateResult <= 0) {
+                    return R.error("更新看课时间失败");
+                }
             }
 
             UpdateWatchDurationVO vo = new UpdateWatchDurationVO();
@@ -294,81 +308,107 @@ public class LiveCompletionPointsController extends AppBaseController {
      * 没达到,返回报错
      */
     @PostMapping("/receive-points")
-    @RepeatSubmit
     public R receivePoints(@RequestParam Long liveId) {
         Long userId = Long.parseLong(getUserId());
         
+        // 创建唯一锁,确保同一个 liveId 和 userId 只能有一个线程在执行
+        String lockKey = String.format("receivePoints:liveId:%d:userId:%d", liveId, userId);
+        RLock lock = redissonClient.getLock(lockKey);
+        
         try {
-            // 1. 查询当前用户和当前直播间的最近一次完课记录(不限制日期)
-            LiveCompletionPointsRecord record = completionPointsRecordMapper.selectLatestByUserAndLiveId(liveId, userId);
-            
-            if (record == null) {
-                return R.error("您还没有看课记录,无法领取积分");
+            // 尝试获取锁,等待时间0秒,锁持有时间15秒
+            boolean locked = lock.tryLock(0, 15, TimeUnit.SECONDS);
+            if (!locked) {
+                logger.warn("获取领取积分锁失败,liveId: {}, userId: {}", liveId, userId);
+                return R.error("系统繁忙,请稍后重试");
             }
 
-            // 2. 获取直播间信息和配置
-            Live live = liveService.selectLiveByLiveId(liveId);
-            if (live == null) {
-                return R.error("直播间不存在");
-            }
+            try {
+                // 1. 查询当前用户和当前直播间的最近一次完课记录(不限制日期)
+                LiveCompletionPointsRecord record = completionPointsRecordMapper.selectLatestByUserAndLiveId(liveId, userId);
+                
+                if (record == null) {
+                    return R.error("您还没有看课记录,无法领取积分");
+                }
 
-            // 3. 检查看课记录里面的时长是否达到完课标准
-            Long watchDuration = record.getWatchDuration();
-            if (watchDuration == null || watchDuration <= 0) {
-                return R.error("您的看课时长不足,无法领取积分");
-            }
+                // 2. 获取直播间信息和配置
+                Live live = liveService.selectLiveByLiveId(liveId);
+                if (live == null) {
+                    return R.error("直播间不存在");
+                }
 
-            // 4. 检查完课比例是否达到标准
-            BigDecimal completionRate = record.getCompletionRate();
-            if (completionRate == null) {
-                // 重新计算完课比例
-                Long videoDuration = live.getDuration();
-                if (videoDuration == null || videoDuration <= 0) {
-                    return R.error("直播间视频时长配置错误");
+                // 3. 检查看课记录里面的时长是否达到完课标准
+                Long watchDuration = record.getWatchDuration();
+                if (watchDuration == null || watchDuration <= 0) {
+                    return R.error("您的看课时长不足,无法领取积分");
                 }
-                completionRate = BigDecimal.valueOf(watchDuration)
-                        .multiply(BigDecimal.valueOf(100))
-                        .divide(BigDecimal.valueOf(videoDuration), 2, java.math.RoundingMode.HALF_UP);
-                if (completionRate.compareTo(BigDecimal.valueOf(100)) > 0) {
-                    completionRate = BigDecimal.valueOf(100);
+
+                // 4. 检查完课比例是否达到标准
+                BigDecimal completionRate = record.getCompletionRate();
+                if (completionRate == null) {
+                    // 重新计算完课比例
+                    Long videoDuration = live.getDuration();
+                    if (videoDuration == null || videoDuration <= 0) {
+                        return R.error("直播间视频时长配置错误");
+                    }
+                    completionRate = BigDecimal.valueOf(watchDuration)
+                            .multiply(BigDecimal.valueOf(100))
+                            .divide(BigDecimal.valueOf(videoDuration), 2, java.math.RoundingMode.HALF_UP);
+                    if (completionRate.compareTo(BigDecimal.valueOf(100)) > 0) {
+                        completionRate = BigDecimal.valueOf(100);
+                    }
+                    record.setCompletionRate(completionRate);
                 }
-                record.setCompletionRate(completionRate);
-            }
 
-            // 5. 从直播间配置获取完课标准
-            String configJson = live.getConfigJson();
-            Integer requiredCompletionRate = null;
-            if (configJson != null && !configJson.isEmpty()) {
-                try {
-                    com.alibaba.fastjson.JSONObject jsonConfig = com.alibaba.fastjson.JSON.parseObject(configJson);
-                    requiredCompletionRate = jsonConfig.getInteger("completionRate");
-                } catch (Exception e) {
-                    // 解析失败,忽略
+                // 5. 从直播间配置获取完课标准
+                String configJson = live.getConfigJson();
+                Integer requiredCompletionRate = null;
+                if (configJson != null && !configJson.isEmpty()) {
+                    try {
+                        com.alibaba.fastjson.JSONObject jsonConfig = com.alibaba.fastjson.JSON.parseObject(configJson);
+                        requiredCompletionRate = jsonConfig.getInteger("completionRate");
+                    } catch (Exception e) {
+                        // 解析失败,忽略
+                    }
                 }
-            }
 
-            // 6. 判断是否达到完课标准
-            if (requiredCompletionRate != null && completionRate.compareTo(BigDecimal.valueOf(requiredCompletionRate)) < 0) {
-                return R.error("您的完课比例未达到标准(" + requiredCompletionRate + "%),当前完课比例:" + completionRate + "%");
-            }
+                // 6. 判断是否达到完课标准
+                if (requiredCompletionRate != null && completionRate.compareTo(BigDecimal.valueOf(requiredCompletionRate)) < 0) {
+                    return R.error("您的完课比例未达到标准(" + requiredCompletionRate + "%),当前完课比例:" + completionRate + "%");
+                }
 
-            // 7. 检查是否已领取
-            if (record.getReceiveStatus() != null && record.getReceiveStatus() == 1) {
-                return R.error("该完课积分已领取");
-            }
+                // 7. 检查是否已领取
+                if (record.getReceiveStatus() != null && record.getReceiveStatus() == 1) {
+                    return R.error("该完课积分已领取");
+                }
 
-            // 8. 领取积分(更新看课记录的领取状态,给用户加积分)
-            LiveCompletionPointsRecord receivedRecord = completionPointsRecordService.receiveCompletionPoints(record.getId(), userId);
+                // 8. 领取积分(更新看课记录的领取状态,给用户加积分)
+                LiveCompletionPointsRecord receivedRecord = completionPointsRecordService.receiveCompletionPoints(record.getId(), userId);
 
-            ReceivePointsVO vo = new ReceivePointsVO();
-            vo.setRecord(receivedRecord);
-            vo.setPoints(receivedRecord.getPointsAwarded());
-            vo.setContinuousDays(receivedRecord.getContinuousDays());
-            
-            return R.ok().put("data", vo);
-        } catch (BaseException e) {
-            return R.error(e.getMessage());
+                ReceivePointsVO vo = new ReceivePointsVO();
+                vo.setRecord(receivedRecord);
+                vo.setPoints(receivedRecord.getPointsAwarded());
+                vo.setContinuousDays(receivedRecord.getContinuousDays());
+                
+                return R.ok().put("data", vo);
+            } catch (BaseException e) {
+                return R.error(e.getMessage());
+            } catch (Exception e) {
+                logger.error("领取积分失败,liveId: {}, userId: {}", liveId, userId, e);
+                return R.error("领取失败: " + e.getMessage());
+            } finally {
+                // 释放锁
+                if (lock.isHeldByCurrentThread()) {
+                    lock.unlock();
+                    logger.debug("领取积分锁已释放,liveId: {}, userId: {}", liveId, userId);
+                }
+            }
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            logger.error("获取领取积分锁被中断,liveId: {}, userId: {}", liveId, userId, e);
+            return R.error("系统繁忙,请稍后重试");
         } catch (Exception e) {
+            logger.error("领取积分异常,liveId: {}, userId: {}", liveId, userId, e);
             return R.error("领取失败: " + e.getMessage());
         }
     }

+ 195 - 0
fs-user-app/src/main/java/com/fs/app/controller/live/LiveOrderController.java

@@ -32,8 +32,10 @@ import com.fs.his.service.IFsExpressService;
 import com.fs.his.utils.ConfigUtil;
 import com.fs.hisStore.domain.*;
 import com.fs.hisStore.dto.FsStoreOrderComputeDTO;
+import com.fs.hisStore.enums.AfterSalesStatusEnum;
 import com.fs.hisStore.enums.OrderInfoEnum;
 import com.fs.hisStore.mapper.FsStorePaymentScrmMapper;
+import com.fs.hisStore.service.IFsStoreAfterSalesScrmService;
 import com.fs.hisStore.param.*;
 import com.fs.hisStore.service.IFsStoreOrderScrmService;
 import com.fs.hisStore.service.IFsUserScrmService;
@@ -43,9 +45,11 @@ import com.fs.huifuPay.domain.HuiFuQueryOrderResult;
 import com.fs.huifuPay.domain.HuifuCreateOrderResult;
 import com.fs.huifuPay.sdk.opps.core.request.V2TradePaymentScanpayQueryRequest;
 import com.fs.huifuPay.service.HuiFuService;
+import com.fs.live.domain.LiveAfterSales;
 import com.fs.live.domain.LiveOrder;
 import com.fs.live.domain.LiveOrderPayment;
 import com.fs.live.dto.LiveOrderComputeDTO;
+import com.fs.live.enums.LiveAfterSalesStatusEnum;
 import com.fs.live.enums.LiveOrderCancleReason;
 import com.fs.live.mapper.LiveOrderPaymentMapper;
 import com.fs.live.param.*;
@@ -83,7 +87,9 @@ import org.springframework.web.bind.annotation.*;
 
 import javax.servlet.http.HttpServletRequest;
 import java.math.BigDecimal;
+import java.sql.Timestamp;
 import java.text.SimpleDateFormat;
+import java.time.LocalDateTime;
 import java.util.*;
 import java.util.concurrent.TimeUnit;
 
@@ -130,6 +136,12 @@ public class LiveOrderController extends AppBaseController
     IPayService ybPayService;
     @Autowired
     private HuiFuService huiFuService;
+    @Autowired
+    private IFsStoreOrderScrmService fsStoreOrderScrmService;
+    @Autowired
+    private IFsStoreAfterSalesScrmService fsStoreAfterSalesScrmService;
+    @Autowired
+    private FsStorePaymentScrmMapper fsStorePaymentScrmMapper;
 
 
 
@@ -914,5 +926,188 @@ public class LiveOrderController extends AppBaseController
     }
 
 
+    @ApiOperation("批量生成售后订单并退款")
+//    @PostMapping("/batchCreateAfterSalesAndRefund")
+    public R batchCreateAfterSalesAndRefund(@RequestBody List<String> payCodes) {
+        if (payCodes == null || payCodes.isEmpty()) {
+            return R.error("支付订单号列表不能为空");
+        }
+
+        List<Map<String, Object>> results = new ArrayList<>();
+        
+        for (String payCode : payCodes) {
+            Map<String, Object> result = new HashMap<>();
+            result.put("payCode", payCode);
+            
+            try {
+                // 先查询支付记录
+                // 先尝试查询商城订单支付记录
+                FsStorePaymentScrm storePayment = fsStorePaymentScrmMapper.selectFsStorePaymentByCode(payCode.split("-")[1]);
+                
+                if (storePayment != null) {
+                    // 商城订单支付记录
+                    result.put("paymentType", "store");
+                    result.put("paymentId", storePayment.getPaymentId());
+                    
+                    // 根据支付记录查询订单
+                    FsStoreOrderScrm storeOrder = null;
+                    if (storePayment.getOrderId() != null) {
+                        storeOrder = fsStoreOrderScrmService.selectFsStoreOrderById(storePayment.getOrderId());
+                    } else if (StringUtils.isNotEmpty(storePayment.getBusinessOrderId())) {
+                        // 如果没有orderId,尝试通过businessOrderId查询
+                        storeOrder = fsStoreOrderScrmService.selectFsStoreOrderByOrderCode(storePayment.getBusinessOrderId());
+                    }
+                    
+                    if (storeOrder == null) {
+                        result.put("success", false);
+                        result.put("message", "根据支付记录未找到对应的订单");
+                        results.add(result);
+                        continue;
+                    }
+                    
+                    result.put("orderCode", storeOrder.getOrderCode());
+                    result.put("orderId", storeOrder.getId());
+                    
+                    // 查询售后记录
+                    FsStoreAfterSalesScrm queryAfterSales = new FsStoreAfterSalesScrm();
+                    queryAfterSales.setOrderCode(storeOrder.getOrderCode());
+                    List<FsStoreAfterSalesScrm> existingAfterSales = fsStoreAfterSalesScrmService.selectFsStoreAfterSalesList(queryAfterSales);
+                    
+                    FsStoreAfterSalesScrm afterSales = null;
+                    if (existingAfterSales == null || existingAfterSales.isEmpty()) {
+                        // 创建售后记录
+                        afterSales = new FsStoreAfterSalesScrm();
+                        afterSales.setOrderCode(storeOrder.getOrderCode());
+                        afterSales.setRefundAmount(storeOrder.getPayMoney());
+                        afterSales.setServiceType(0); // 0仅退款
+                        afterSales.setReasons("系统自动生成售后订单");
+                        afterSales.setExplains("系统自动生成售后订单");
+                        afterSales.setExplainImg(null);
+                        afterSales.setStatus(AfterSalesStatusEnum.STATUS_4.getValue()); // 4财务已审核(退款成功)
+                        afterSales.setSalesStatus(3); // 3已完成
+                        afterSales.setCreateTime(Timestamp.valueOf(LocalDateTime.now()));
+                        afterSales.setIsDel(0);
+                        afterSales.setUserId(storeOrder.getUserId());
+                        afterSales.setOrderStatus(storeOrder.getStatus());
+                        afterSales.setCompanyId(storeOrder.getCompanyId());
+                        afterSales.setCompanyUserId(storeOrder.getCompanyUserId());
+                        if (storeOrder.getPackageJson() != null) {
+                            afterSales.setPackageJson(storeOrder.getPackageJson());
+                        }
+                        if (storeOrder.getIsPackage() != null) {
+                            afterSales.setIsPackage(storeOrder.getIsPackage());
+                        }
+                        fsStoreAfterSalesScrmService.insertFsStoreAfterSales(afterSales);
+                        result.put("afterSalesCreated", true);
+                    } else {
+                        afterSales = existingAfterSales.get(0);
+                        result.put("afterSalesCreated", false);
+                        result.put("afterSalesId", afterSales.getId());
+                    }
+                    
+                    // 修改订单状态为退款成功
+                    storeOrder.setStatus(OrderInfoEnum.STATUS_NE2.getValue()); // -2退款成功
+                    storeOrder.setRefundStatus(OrderInfoEnum.REFUND_STATUS_2.getValue()); // 2已退款
+                    storeOrder.setRefundPrice(storeOrder.getPayMoney());
+                    fsStoreOrderScrmService.updateFsStoreOrder(storeOrder);
+                    
+                    result.put("success", true);
+                    result.put("message", "处理成功");
+                    result.put("afterSalesId", afterSales != null ? afterSales.getId() : null);
+                    
+                } else {
+                    // 尝试查询直播订单支付记录
+                    LiveOrderPayment livePayment = liveOrderPaymentMapper.selectLiveOrderPaymentByPaymentCode(payCode.split("-")[1]);
+                    
+                    if (livePayment != null) {
+                        // 直播订单支付记录
+                        result.put("paymentType", "live");
+                        result.put("paymentId", livePayment.getPaymentId());
+                        
+                        // 根据支付记录查询订单
+                        LiveOrder liveOrder = null;
+                        if (StringUtils.isNotEmpty(livePayment.getBusinessId())) {
+                            // businessId 是订单ID(Long类型转String)
+                            try {
+                                Long orderId = Long.parseLong(livePayment.getBusinessId());
+                                liveOrder = orderService.selectLiveOrderByOrderId(String.valueOf(orderId));
+                            } catch (NumberFormatException e) {
+                                // 如果不是数字,可能是订单号
+                                liveOrder = orderService.selectLiveOrderByOrderId(livePayment.getBusinessId());
+                            }
+                        } else if (StringUtils.isNotEmpty(livePayment.getBusinessCode())) {
+                            // 通过businessCode(订单号)查询
+                            liveOrder = orderService.selectLiveOrderByOrderId(livePayment.getBusinessCode());
+                        }
+                        
+                        if (liveOrder == null) {
+                            result.put("success", false);
+                            result.put("message", "根据支付记录未找到对应的订单");
+                            results.add(result);
+                            continue;
+                        }
+                        
+                        result.put("orderCode", liveOrder.getOrderCode());
+                        result.put("orderId", liveOrder.getOrderId());
+                        
+                        // 查询售后记录
+                        LiveAfterSales queryAfterSales = new LiveAfterSales();
+                        queryAfterSales.setOrderId(liveOrder.getOrderId());
+                        List<LiveAfterSales> existingAfterSales = liveAfterSalesService.selectLiveAfterSalesList(queryAfterSales);
+                        
+                        LiveAfterSales afterSales = null;
+                        if (existingAfterSales == null || existingAfterSales.isEmpty()) {
+                            // 创建售后记录
+                            afterSales = new LiveAfterSales();
+                            afterSales.setOrderId(liveOrder.getOrderId());
+                            afterSales.setRefundAmount(liveOrder.getPayMoney());
+                            afterSales.setRefundType(0); // 0仅退款
+                            afterSales.setReasons("系统自动生成售后订单");
+                            afterSales.setExplains("系统自动生成售后订单");
+                            afterSales.setExplainImg(null);
+                            afterSales.setStatus(LiveAfterSalesStatusEnum.STATUS_4.getValue()); // 4财务已审核(退款成功)
+                            afterSales.setSalesStatus(3); // 3已完成
+                            afterSales.setCreateTime(Timestamp.valueOf(LocalDateTime.now()));
+                            afterSales.setIsDel(0);
+                            afterSales.setUserId(Long.valueOf(liveOrder.getUserId()));
+                            afterSales.setOrderStatus(liveOrder.getStatus());
+                            afterSales.setCompanyId(liveOrder.getCompanyId());
+                            afterSales.setCompanyUserId(liveOrder.getCompanyUserId());
+                            liveAfterSalesService.insertLiveAfterSales(afterSales);
+                            result.put("afterSalesCreated", true);
+                        } else {
+                            afterSales = existingAfterSales.get(0);
+                            result.put("afterSalesCreated", false);
+                            result.put("afterSalesId", afterSales.getId());
+                        }
+                        
+                        // 修改订单状态为退款成功
+                        liveOrder.setStatus(OrderInfoEnum.STATUS_NE2.getValue()); // -2退款成功
+                        liveOrder.setRefundStatus(String.valueOf(OrderInfoEnum.REFUND_STATUS_2.getValue())); // 2已退款
+                        liveOrder.setRefundMoney(liveOrder.getPayMoney());
+                        liveOrder.setRefundTime(new Date());
+                        orderService.updateLiveOrder(liveOrder);
+                        
+                        result.put("success", true);
+                        result.put("message", "处理成功");
+                        result.put("afterSalesId", afterSales != null ? afterSales.getId() : null);
+                        
+                    } else {
+                        result.put("success", false);
+                        result.put("message", "未找到对应的支付记录");
+                    }
+                }
+                
+            } catch (Exception e) {
+                logger.error("处理支付订单失败: " + payCode, e);
+                result.put("success", false);
+                result.put("message", "处理失败: " + e.getMessage());
+            }
+            
+            results.add(result);
+        }
+        
+        return R.ok().put("results", results);
+    }
 
 }

+ 4 - 0
fs-user-app/src/main/java/com/fs/app/controller/store/WxUserScrmController.java

@@ -244,6 +244,7 @@ public class WxUserScrmController extends AppBaseController {
             if (!wxService.getUserService().checkUserInfo(session.getSessionKey(), param.getRawData(), param.getSignature())) {
                 return R.error("user check failed");
             }
+
             // 解密用户信息
             WxMaUserInfo userInfo = wxService.getUserService().getUserInfo(session.getSessionKey(), param.getEncryptedData(), param.getIv());
             FsUserScrm user=userService.selectFsUserById(Long.parseLong(getUserId()));
@@ -251,6 +252,9 @@ public class WxUserScrmController extends AppBaseController {
             user.setAvatar(userInfo.getAvatarUrl());
             user.setIsWeixinAuth(1);
             userService.updateFsUser(user);
+
+            // 广告线索
+            leadService.weChatNameLead(param.getTraceId(), userInfo.getNickName());
             return R.ok();
         } catch (WxErrorException e) {
             e.printStackTrace();

+ 2 - 2
fs-user-app/src/main/resources/application.yml

@@ -7,8 +7,8 @@ server:
 # Spring配置
 spring:
   profiles:
-    active: druid-myhk-test
-#    active: dev
+#    active: druid-myhk-test
+    active: dev
 #    active: druid-jzzx
 #    active: druid-yzt
 #    active: druid-hdt

+ 7 - 1
fs-websocket/src/main/java/com/fs/websocket/FsWebSocketServiceApplication.java

@@ -4,13 +4,19 @@ import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
 import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
 import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.FilterType;
 
 /**
  * WebSocket服务启动程序
  *
  * @author fs
  */
-@SpringBootApplication(scanBasePackages = {"com.fs"}, exclude= {DataSourceAutoConfiguration.class})
+@SpringBootApplication(exclude= {DataSourceAutoConfiguration.class})
+@ComponentScan(basePackages = {"com.fs"}, excludeFilters = {
+        @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {com.fs.common.utils.RedisUtil.class,
+                com.fs.common.core.redis.RedisCache.class, com.fs.common.core.redis.RedisCacheT.class})
+        })
 public class FsWebSocketServiceApplication extends SpringBootServletInitializer {
 
     public static void main(String[] args) {

+ 4 - 4
fs-websocket/src/main/java/com/fs/websocket/config/WebSocketConfig.java

@@ -27,13 +27,13 @@ public class WebSocketConfig {
     public ServletServerContainerFactoryBean createWebSocketContainer() {
         ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();
         // 设置文本消息缓冲区大小
-        container.setMaxTextMessageBufferSize(10240000);
+        container.setMaxTextMessageBufferSize(1048576);
         // 设置二进制消息缓冲区大小
-        container.setMaxBinaryMessageBufferSize(10240000);
+        container.setMaxBinaryMessageBufferSize(1048576);
         // 设置最大会话空闲超时时间(单位:毫秒)
-        container.setMaxSessionIdleTimeout(20 * 60000L); // 15分钟
+        container.setMaxSessionIdleTimeout(60000L); // 15分钟
         // 设置异步发送超时时间(单位:毫秒)
-        container.setAsyncSendTimeout(300 * 1000L);
+        container.setAsyncSendTimeout(30 * 1000L);
         return container;
     }
 

+ 30 - 15
fs-websocket/src/main/java/com/fs/websocket/service/WebSocketServer.java

@@ -6,15 +6,12 @@ import com.fs.common.utils.StringUtils;
 import com.fs.websocket.bean.SendMsgVO;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Component;
-import org.springframework.util.CollectionUtils;
-
 import javax.websocket.*;
 import javax.websocket.server.PathParam;
 import javax.websocket.server.ServerEndpoint;
 import java.io.IOException;
-import java.util.Collection;
 import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.*;
 import java.util.stream.Collectors;
 
 @ServerEndpoint("/app/webSocket/{userId}")
@@ -24,25 +21,41 @@ public class WebSocketServer {
 
     //concurrent包的线程安全,用来存放每个客户端对应的WebSocketServer的会话对象
     private static final ConcurrentHashMap<Long, Session> sessionPools = new ConcurrentHashMap<>();
-//    private final RedisCache redisCache = SpringUtils.getBean(RedisCache.class);
+
 
     //分发消息
     public void sendMessageToAll(String message) throws IOException {
-        Collection<Session> sessions = sessionPools.values();
-        if(!CollectionUtils.isEmpty(sessions)){
-            for (Session session : sessions) {
-                System.out.println("发送数据:" + message);
-                session.getBasicRemote().sendText(message);
-                log.info("分发消息结束,人数,{},消息内容,{}",  sessionPools.size(), message);
-            }
-        }
+//        Collection<Session> sessions = sessionPools.values();
+//        if(!CollectionUtils.isEmpty(sessions)){
+            sessionPools.forEach((userId, session) -> {
+                if (session.isOpen()) {
+                    try {
+                        // 异步发送,设置超时
+                        Future<Void> future = session.getAsyncRemote().sendText(message);
+                        System.out.println("分发消息,数据内容:" + message);
+                        try {
+                            future.get(10, TimeUnit.SECONDS);
+                        } catch (TimeoutException e) {
+                            // 超时关闭连接
+                            session.close();
+                            log.error("超时关闭连接,并移除用户:{}", userId, e);
+                            sessionPools.remove(userId);
+                        }
+                    } catch (Exception e) {
+                        log.error("分发消息失败,并移除用户: {}", userId, e);
+                        sessionPools.remove(userId);
+                    }
+                }
+            });
+        log.info("分发消息结束,人数,{}",  sessionPools.size());
+//        }
     }
 
     //指定用户发送消息
     public static void sendMessage(Session session, String message) throws IOException {
         if(session != null){
             synchronized (session) {
-                log.info("发送数据:{}", message);
+                log.info("发送心跳数据:{}", message);
                 session.getBasicRemote().sendText(message);
             }
         }
@@ -95,7 +108,9 @@ public class WebSocketServer {
     //错误时调用
     @OnError
     public void onError(Session session, Throwable throwable) {
-        log.error("webSocket连接错误,{}", throwable.getMessage());
+        Map<String, String> params = getParams(session);
+        long userId = Long.parseLong(params.get("userId"));
+        log.error("webSocket连接错误,{},移除用户,{}", throwable.getMessage(), userId);
         throwable.printStackTrace();
     }