فهرست منبع

Merge branch 'refs/heads/master' into 企微聊天

ct 4 روز پیش
والد
کامیت
a5be5568c8
100فایلهای تغییر یافته به همراه2176 افزوده شده و 267 حذف شده
  1. 1 1
      deploy.sh
  2. 6 0
      fs-admin/Dockerfile
  3. 61 0
      fs-admin/src/main/java/com/fs/his/task/Task.java
  4. 5 3
      fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreAfterSalesScrmController.java
  5. 1 1
      fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreHealthOrderScrmController.java
  6. 9 1
      fs-admin/src/main/java/com/fs/hisStore/task/LiveTask.java
  7. 8 1
      fs-admin/src/main/java/com/fs/hisStore/task/MallStoreTask.java
  8. 3 3
      fs-admin/src/main/resources/application.yml
  9. 6 0
      fs-common-api/Dockerfile
  10. 24 0
      fs-common-api/pom.xml
  11. 1 1
      fs-common-api/src/main/resources/application.yml
  12. 6 0
      fs-common/src/main/java/com/fs/common/constant/LiveKeysConstant.java
  13. 6 0
      fs-company/Dockerfile
  14. 32 21
      fs-company/src/main/java/com/fs/company/controller/live/LiveController.java
  15. 4 0
      fs-company/src/main/java/com/fs/company/controller/qw/QwExternalContactController.java
  16. 3 3
      fs-company/src/main/java/com/fs/company/controller/qw/QwExternalContactTransferLogController.java
  17. 3 2
      fs-company/src/main/resources/application.yml
  18. 0 1
      fs-live-app/src/main/java/com/fs/framework/aspectj/LiveWatchUserAspect.java
  19. 0 2
      fs-live-app/src/main/java/com/fs/live/controller/LiveDataController.java
  20. 9 6
      fs-live-app/src/main/java/com/fs/live/websocket/handle/LiveChatHandler.java
  21. 209 22
      fs-live-app/src/main/java/com/fs/live/websocket/service/WebSocketServer.java
  22. 34 0
      fs-qwhook/src/main/java/com/fs/app/controller/CommonController.java
  23. 4 1
      fs-service/src/main/java/com/fs/company/domain/CompanyUser.java
  24. 1 0
      fs-service/src/main/java/com/fs/course/domain/FsUserCourseVideo.java
  25. 19 0
      fs-service/src/main/java/com/fs/course/mapper/FsCourseTrafficLogMapper.java
  26. 2 5
      fs-service/src/main/java/com/fs/course/mapper/FsUserCourseVideoRedPackageMapper.java
  27. 2 0
      fs-service/src/main/java/com/fs/course/service/IFsCourseTrafficLogService.java
  28. 82 0
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseTrafficLogServiceImpl.java
  29. 2 2
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCompanyBindServiceImpl.java
  30. 1 2
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCoursePeriodServiceImpl.java
  31. 1 2
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseTrainingCampServiceImpl.java
  32. 1 1
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java
  33. 1 0
      fs-service/src/main/java/com/fs/course/vo/FsUserCourseVideoQVO.java
  34. 2 0
      fs-service/src/main/java/com/fs/course/vo/newfs/FsUserCourseVideoPageListVO.java
  35. 9 7
      fs-service/src/main/java/com/fs/fastGpt/service/IFastgptEventLogTotalService.java
  36. 237 7
      fs-service/src/main/java/com/fs/fastGpt/service/impl/FastgptEventLogTotalServiceImpl.java
  37. 8 1
      fs-service/src/main/java/com/fs/his/service/impl/FsStoreOrderServiceImpl.java
  38. 14 1
      fs-service/src/main/java/com/fs/hisStore/domain/FsStoreProductPackageScrm.java
  39. 13 0
      fs-service/src/main/java/com/fs/hisStore/domain/FsUserScrm.java
  40. 9 2
      fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreAfterSalesScrmMapper.java
  41. 4 0
      fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreOrderItemScrmMapper.java
  42. 2 0
      fs-service/src/main/java/com/fs/hisStore/mapper/FsStorePaymentScrmMapper.java
  43. 1 1
      fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreProductScrmMapper.java
  44. 3 0
      fs-service/src/main/java/com/fs/hisStore/mapper/FsUserScrmMapper.java
  45. 24 0
      fs-service/src/main/java/com/fs/hisStore/param/FsUserCouponScrmSendParam.java
  46. 11 8
      fs-service/src/main/java/com/fs/hisStore/service/IFsStoreCouponIssueScrmService.java
  47. 3 0
      fs-service/src/main/java/com/fs/hisStore/service/IFsUserScrmService.java
  48. 36 2
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreAfterSalesScrmServiceImpl.java
  49. 53 0
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreCouponIssueScrmServiceImpl.java
  50. 59 0
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreOrderScrmServiceImpl.java
  51. 5 3
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStorePaymentScrmServiceImpl.java
  52. 37 1
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreProductAttrScrmServiceImpl.java
  53. 37 1
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreProductAttrValueScrmServiceImpl.java
  54. 67 36
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreProductScrmServiceImpl.java
  55. 6 4
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsUserScrmServiceImpl.java
  56. 28 1
      fs-service/src/main/java/com/fs/hisStore/vo/FsStoreAfterSalesVO.java
  57. 3 0
      fs-service/src/main/java/com/fs/hisStore/vo/FsStoreProductPacketVO.java
  58. 11 0
      fs-service/src/main/java/com/fs/hisStore/vo/UserScrmVo.java
  59. 2 0
      fs-service/src/main/java/com/fs/live/domain/LiveOrder.java
  60. 4 0
      fs-service/src/main/java/com/fs/live/domain/LiveWatchUser.java
  61. 2 2
      fs-service/src/main/java/com/fs/live/mapper/LiveAfterSalesMapper.java
  62. 3 0
      fs-service/src/main/java/com/fs/live/mapper/LiveOrderItemMapper.java
  63. 12 0
      fs-service/src/main/java/com/fs/live/mapper/LiveOrderMapper.java
  64. 1 0
      fs-service/src/main/java/com/fs/live/param/LiveOrderComputedParam.java
  65. 2 0
      fs-service/src/main/java/com/fs/live/service/ILiveOrderService.java
  66. 4 3
      fs-service/src/main/java/com/fs/live/service/ILiveWatchUserService.java
  67. 99 69
      fs-service/src/main/java/com/fs/live/service/impl/LiveAfterSalesServiceImpl.java
  68. 84 7
      fs-service/src/main/java/com/fs/live/service/impl/LiveOrderServiceImpl.java
  69. 91 3
      fs-service/src/main/java/com/fs/live/service/impl/LiveServiceImpl.java
  70. 6 7
      fs-service/src/main/java/com/fs/live/service/impl/LiveWatchUserServiceImpl.java
  71. 29 1
      fs-service/src/main/java/com/fs/live/vo/LiveAfterSalesVo.java
  72. 1 0
      fs-service/src/main/java/com/fs/live/vo/LiveDataDetailVo.java
  73. 1 0
      fs-service/src/main/java/com/fs/live/vo/LiveOrderItemListUVO.java
  74. 1 0
      fs-service/src/main/java/com/fs/live/vo/LiveUserDetailVo.java
  75. 1 0
      fs-service/src/main/java/com/fs/live/vo/ProductSalesVo.java
  76. 101 0
      fs-service/src/main/java/com/fs/qw/mapper/QwExternalContactMapper.java
  77. 7 0
      fs-service/src/main/java/com/fs/qw/param/QwExternalContactParam.java
  78. 9 0
      fs-service/src/main/java/com/fs/qw/param/QwMandatoryRegistrParam.java
  79. 6 0
      fs-service/src/main/java/com/fs/qw/service/IQwExternalContactService.java
  80. 14 0
      fs-service/src/main/java/com/fs/qw/service/impl/QwExternalContactServiceImpl.java
  81. 3 0
      fs-service/src/main/java/com/fs/qw/vo/QwExternalContactTransferLogListVO.java
  82. 102 0
      fs-service/src/main/resources/application-config-druid-ylrz.yml
  83. 173 0
      fs-service/src/main/resources/application-druid-ylrz.yml
  84. 27 0
      fs-service/src/main/resources/mapper/course/FsCourseTrafficLogMapper.xml
  85. 5 0
      fs-service/src/main/resources/mapper/course/FsUserCourseVideoMapper.xml
  86. 7 0
      fs-service/src/main/resources/mapper/hisStore/FsStoreOrderItemScrmMapper.xml
  87. 9 1
      fs-service/src/main/resources/mapper/hisStore/FsStorePaymentScrmMapper.xml
  88. 6 1
      fs-service/src/main/resources/mapper/hisStore/FsStoreProductPackageScrmMapper.xml
  89. 3 0
      fs-service/src/main/resources/mapper/hisStore/FsUserScrmMapper.xml
  90. 2 1
      fs-service/src/main/resources/mapper/live/LiveAfterSalesMapper.xml
  91. 9 0
      fs-service/src/main/resources/mapper/live/LiveOrderItemMapper.xml
  92. 6 0
      fs-user-app/Dockerfile
  93. 2 0
      fs-user-app/pom.xml
  94. 19 0
      fs-user-app/src/main/java/com/fs/app/controller/course/CourseQwController.java
  95. 1 1
      fs-user-app/src/main/java/com/fs/app/controller/live/LiveController.java
  96. 34 6
      fs-user-app/src/main/java/com/fs/app/controller/live/LiveGoodsController.java
  97. 13 4
      fs-user-app/src/main/java/com/fs/app/controller/live/LiveOrderController.java
  98. 1 0
      fs-user-app/src/main/java/com/fs/app/controller/live/LiveWatchUserController.java
  99. 18 4
      fs-user-app/src/main/java/com/fs/app/controller/store/CouponScrmController.java
  100. 27 1
      fs-user-app/src/main/java/com/fs/app/controller/store/IndexScrmController.java

+ 1 - 1
deploy.sh

@@ -87,7 +87,7 @@ deploy_jar() {
     echo "启动服务..."
     ssh -f "$REMOTE_USER@$remote_host" \
     "cd $REMOTE_BASE_DIR/$remote_dir && \
-     nohup java -jar  -Dfile.encoding=UTF-8 $app_name.jar --spring.profiles.active=druid-bjzm --server.port=7114  \
+     nohup java -jar -Xms8g -Xmx8g -XX:+UseG1GC -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 $app_name.jar --spring.profiles.active=druid-bjzm --server.port=7114  \
      >> $app_name.log 2>&1 &"
 
     # 检查进程是否启动成功

+ 6 - 0
fs-admin/Dockerfile

@@ -0,0 +1,6 @@
+FROM openjdk:8-jre
+# java版本,最好使用openjdk,而不是类似于Java:1.8
+COPY ./target/fs-admin.jar fs-admin.jar
+# 向外暴露的接口,最好与项目yml文件中的端口一致
+ENTRYPOINT ["java","-jar","fs-admin.jar"]
+# 执行启动命令java -jar

+ 61 - 0
fs-admin/src/main/java/com/fs/his/task/Task.java

@@ -57,6 +57,12 @@ import com.fs.his.service.impl.FsPackageOrderServiceImpl;
 import com.fs.his.utils.ConfigUtil;
 import com.fs.his.vo.FsSubOrderResultVO;
 import com.fs.hisStore.domain.FsStoreOrderScrm;
+import com.fs.hisStore.domain.FsStorePaymentScrm;
+import com.fs.hisStore.mapper.FsStorePaymentScrmMapper;
+import com.fs.hisStore.service.IFsStorePaymentScrmService;
+import com.fs.huifuPay.domain.HuiFuQueryOrderResult;
+import com.fs.huifuPay.sdk.opps.core.request.V2TradePaymentScanpayQueryRequest;
+import com.fs.huifuPay.service.HuiFuService;
 import com.fs.im.dto.*;
 import com.fs.im.service.IImService;
 import com.fs.im.service.OpenIMService;
@@ -81,6 +87,8 @@ import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
 import org.springframework.stereotype.Component;
 
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.util.*;
@@ -1069,6 +1077,59 @@ public class Task {
         System.out.println(msgResponseDTO);
     }
 
+    @Autowired
+    FsStorePaymentScrmMapper fsStorePaymentScrmMapper;
+
+    @Autowired
+    HuiFuService huiFuService;
+
+    /**
+     * 查询同步商城订单的支付转台
+     */
+    public void syncOrderPayStatus(){
+        //查询支付状态是待支付的数据
+        FsStorePaymentScrm paymentScrm = new FsStorePaymentScrm();
+        SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMddHHmmss");
+        paymentScrm.setStatus(0);
+        List<FsStorePaymentScrm> fsStorePaymentScrms = fsStorePaymentScrmMapper.selectFsStorePaymentList(paymentScrm);
+        if(null != fsStorePaymentScrms && !fsStorePaymentScrms.isEmpty()){
+            for (FsStorePaymentScrm payment : fsStorePaymentScrms) {
+                if(StringUtils.isBlank(payment.getTradeNo())){
+                    continue;
+                }
+                V2TradePaymentScanpayQueryRequest request = new V2TradePaymentScanpayQueryRequest();
+                request.setOrgReqDate(new SimpleDateFormat("yyyyMMdd").format(payment.getCreateTime()));
+                request.setOrgHfSeqId(payment.getTradeNo());
+                HuiFuQueryOrderResult o = null;
+                try {
+                    o = huiFuService.queryOrder(request);
+                } catch (Exception e) {
+                    log.error("查询失败:"+e.getMessage());
+                    continue;
+                }
+                log.info("汇付返回" + o);
+                if ("00000000".equals(o.getResp_code()) && "S".equals(o.getTrans_stat())) {
+                    try {
+                        Date payTime = formatter.parse(o.getEnd_time());
+                        FsStorePaymentScrm payUpdate = new FsStorePaymentScrm();
+                        payUpdate.setPaymentId(payment.getPaymentId());
+                        payUpdate.setPayTime(payTime);
+                        payUpdate.setStatus(1);
+                        payUpdate.setTradeNo(o.getOrg_hf_seq_id());
+                        payUpdate.setBankSerialNo(o.getParty_order_id());
+                        payUpdate.setBankTransactionId(o.getOut_trans_id());
+                        fsStorePaymentScrmMapper.updateFsStorePayment(payUpdate);
+                    } catch (ParseException e) {
+                        log.error("更新失败 payment:{}",payment ,e);
+//                        throw new RuntimeException(e);
+                    }
+
+
+                }
+            }
+        }
+    }
+
 //    @Autowired
 //    private FsStoreMapper fsStoreMapper;
 //    @Autowired

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

@@ -33,6 +33,7 @@ import org.springframework.web.bind.annotation.*;
 
 import java.text.ParseException;
 import java.util.List;
+import java.util.Objects;
 
 /**
  * 售后记录Controller
@@ -84,16 +85,17 @@ public class FsStoreAfterSalesScrmController extends BaseController
             fsStoreAfterSales.setBeginTime(null);
             fsStoreAfterSales.setEndTime(null);
         }
-        Boolean a = fsStoreAfterSalesService.isEntityNull(fsStoreAfterSales);
-        if (fsStoreAfterSalesService.isEntityNull(fsStoreAfterSales)){
+//        Boolean a = fsStoreAfterSalesService.isEntityNull(fsStoreAfterSales);
+        if (fsStoreAfterSalesService.isEntityNull(fsStoreAfterSales) && Objects.isNull(fsStoreAfterSales.getParams())){
             return AjaxResult.error("请筛选数据导出");
         }
+
         List<FsStoreAfterSalesVO> list = fsStoreAfterSalesService.selectFsStoreAfterSalesListVO(fsStoreAfterSales);
         for (FsStoreAfterSalesVO vo : list){
             vo.setUserPhone(ParseUtils.parsePhone(vo.getUserPhone()));
         }
         ExcelUtil<FsStoreAfterSalesVO> util = new ExcelUtil<FsStoreAfterSalesVO>(FsStoreAfterSalesVO.class);
-        return util.exportExcel(list, "storeAfterSales");
+        return util.exportExcel(list, "退款订单导出");
     }
     /**
      * 获取售后记录详细信息

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

@@ -112,7 +112,7 @@ public class FsStoreHealthOrderScrmController extends BaseController {
                     }
                 }
                 //
-                if (loginUser.getPermissions().contains("his:storeAfterSales:finance") || loginUser.getPermissions().contains("*:*:*")) {
+                if ((loginUser.getPermissions().contains("his:storeAfterSales:finance") || loginUser.getPermissions().contains("*:*:*") && (vo.getCost() !=null && vo.getTotalNum() != null))) {
                     vo.setFPrice(vo.getCost().multiply(BigDecimal.valueOf(vo.getTotalNum())));
                 } else {
                     vo.setPayPostage(BigDecimal.ZERO);

+ 9 - 1
fs-admin/src/main/java/com/fs/hisStore/task/LiveTask.java

@@ -198,8 +198,16 @@ public class LiveTask {
         if (ids.size() > 50) {
             ids = ids.subList(0, 50);
         }
+//        liveOrderService.batchUpdateTimeIds(ids);
+        // 单个异常影响全部,跳过异常单子
         for (Long id : ids) {
-            liveOrderService.createOmsOrder(id);
+            try {
+                liveOrderService.createOmsOrder(id);
+            } catch (Exception e) {
+                log.error("创建直播oms订单失败:"+id);
+                log.error("创建直播oms订单失败:"+e.getMessage());
+            }
+
         }
     }
 

+ 8 - 1
fs-admin/src/main/java/com/fs/hisStore/task/MallStoreTask.java

@@ -168,8 +168,15 @@ public class MallStoreTask
         if (!ids.isEmpty() && ids.size() > 50) {
             ids = ids.subList(0, 50);
         }
+        // 单个异常影响全部,跳过异常单子
         for (Long id : ids) {
-            fsStoreOrderService.createOmsOrder(id);
+            try {
+                fsStoreOrderService.createOmsOrder(id);
+            } catch (Exception e) {
+                log.error("创建商城oms订单失败:"+id);
+                log.error("创建商城oms订单失败:"+e.getMessage());
+            }
+
         }
     }
 

+ 3 - 3
fs-admin/src/main/resources/application.yml

@@ -1,10 +1,10 @@
 server:
-  # 服务器的HTTP端口,默认为8080
-  port: 8004
+  # 服务器的HTTP端口,默认为8080 8004
+  port: 7772
 # Spring配置
 spring:
   profiles:
-    active: druid-bjzm-test
+    active: druid-ylrz
 #    active: druid-hdt
 #    active: druid-yzt
 #    active: druid-sxjz-test

+ 6 - 0
fs-common-api/Dockerfile

@@ -0,0 +1,6 @@
+FROM openjdk:8-jre
+# java版本,最好使用openjdk,而不是类似于Java:1.8
+COPY ./target/fs-common-api.jar fs-common-api.jar
+# 向外暴露的接口,最好与项目yml文件中的端口一致
+ENTRYPOINT ["java","-jar","fs-common-api.jar"]
+# 执行启动命令java -jar

+ 24 - 0
fs-common-api/pom.xml

@@ -118,6 +118,30 @@
                     <warName>${project.artifactId}</warName>
                 </configuration>
             </plugin>
+<!--            <plugin>-->
+<!--                <groupId>com.spotify</groupId>-->
+<!--                <artifactId>dockerfile-maven-plugin</artifactId>-->
+<!--                <version>1.4.13</version>-->
+<!--                <executions>-->
+<!--                    <execution>-->
+<!--                        <id>harbor</id>-->
+<!--                        <goals>-->
+<!--                            <goal>build</goal>-->
+<!--                            <goal>push</goal>-->
+<!--                        </goals>-->
+<!--                    </execution>-->
+<!--                </executions>-->
+<!--                <configuration>-->
+<!--                    <repository>ylrz-docker.tencentcloudcr.com/ylrz/sub1/repo</repository>-->
+<!--                    <tag>${project.version}</tag>-->
+<!--                    <useMavenSettingsForAuth>true</useMavenSettingsForAuth>-->
+<!--                    <username>100044034444</username>-->
+<!--                    <password>eyJhbGciOiJSUzI1NiIsImtpZCI6Ilg0R1k6SDU1TjpCQU9SOk5GVk86RkdSWDpYTjM1OjZGQUM6S1BBWjo0TE4yOkhDSUQ6UTJDRzpSN1NHIn0.eyJvd25lclVpbiI6IjEwMDAzNDY4ODY0OCIsIm9wZXJhdG9yVWluIjoiMTAwMDQ0MDM0NDQ0IiwidG9rZW5JZCI6ImQ0ZnZja2o4a2NnbnFjaTVvanQwIiwiZXhwIjoyMDc5MDYyMzU0LCJuYmYiOjE3NjM3MDIzNTQsImlhdCI6MTc2MzcwMjM1NH0.PhNxx6pBQ-ItrNlSs_gojvXeHghhYqDqxh8nLUIuhBeRzAgmVnY8F3bFPVgbHGydQNxvgyqLYv3nRIE1j020LGgzUetF5b-NBqSWYMiXfu6uZNWctRkwm5hdlWBrMlV8k8zGxY4ZDGUNEG0ksrk7kk3UZ-lHj4ButI2GIEhTx0lQEPHjhEY0xuteocJVYMHdVUqF-Bc5Jr0nvbwxUbmCGakN1VszxBoMpI-zA2O8anMvYq8h7EqOJLU4dlBVcsbkz-4sMi97Xev-mcGh7THbEGWoRWGWNSa4QwsdXEXS5-mhfrvOw6FGuuiIeEQvcuR8zDztDzSApl4ko57Yat-AQQ</password>-->
+<!--                    <buildArgs>-->
+<!--                        <JAR_FILE>${project.artifactId}.jar</JAR_FILE>-->
+<!--                    </buildArgs>-->
+<!--                </configuration>-->
+<!--            </plugin>-->
         </plugins>
         <finalName>${project.artifactId}</finalName>
     </build>

+ 1 - 1
fs-common-api/src/main/resources/application.yml

@@ -5,4 +5,4 @@ server:
 spring:
   profiles:
 #    active: dev
-    active: druid-myhk-test
+    active: druid-ylrz

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

@@ -30,5 +30,11 @@ public class LiveKeysConstant {
     public static final String LIVE_FLAG_CACHE = "live:flag:%s"; //直播间直播/回放状态缓存
     public static final Integer LIVE_FLAG_CACHE_EXPIRE = 300; //直播间状态缓存过期时间(秒)
 
+    public static final String LIVE_DATA_CACHE = "live:data:%s"; //直播间数据缓存
+    public static final Integer LIVE_DATA_CACHE_EXPIRE = 300; //直播间数据缓存过期时间(秒)
+
+    public static final String PRODUCT_DETAIL_CACHE = "product:detail:%s"; //商品详情缓存
+    public static final Integer PRODUCT_DETAIL_CACHE_EXPIRE = 300; //商品详情缓存过期时间(秒)
+
 
 }

+ 6 - 0
fs-company/Dockerfile

@@ -0,0 +1,6 @@
+FROM openjdk:8-jre
+# java版本,最好使用openjdk,而不是类似于Java:1.8
+COPY ./target/fs-company.jar fs-company.jar
+# 向外暴露的接口,最好与项目yml文件中的端口一致
+ENTRYPOINT ["java","-jar","fs-company.jar"]
+# 执行启动命令java -jar

+ 32 - 21
fs-company/src/main/java/com/fs/company/controller/live/LiveController.java

@@ -134,7 +134,8 @@ public class LiveController extends BaseController
     {
         // 设置企业ID和企业用户ID
         setCompanyId(live);
-        return toAjax(liveService.insertLive(live));
+        return toAjax(1);
+//        return toAjax(liveService.insertLive(live));
     }
 
     /**
@@ -146,7 +147,8 @@ public class LiveController extends BaseController
         CompanyUser user = SecurityUtils.getLoginUser().getUser();
         live.setCompanyUserId(user.getUserId());
         live.setCompanyId(user.getCompanyId());
-        return liveService.finishLive(live);
+        return R.ok();
+//        return liveService.finishLive(live);
     }
 
     /**
@@ -158,7 +160,8 @@ public class LiveController extends BaseController
         CompanyUser user = SecurityUtils.getLoginUser().getUser();
         live.setCompanyUserId(user.getUserId());
         live.setCompanyId(user.getCompanyId());
-        return liveService.copyLive(live);
+        return R.ok();
+//        return liveService.copyLive(live);
     }
 
     /**
@@ -170,7 +173,8 @@ public class LiveController extends BaseController
         CompanyUser user = SecurityUtils.getLoginUser().getUser();
         live.setCompanyUserId(user.getUserId());
         live.setCompanyId(user.getCompanyId());
-        return liveService.startLive(live);
+        return R.ok();
+//        return liveService.startLive(live);
     }
 
     /**
@@ -181,10 +185,12 @@ public class LiveController extends BaseController
     @PutMapping
     public AjaxResult edit(@RequestBody Live live)
     {
-        CompanyUser user = SecurityUtils.getLoginUser().getUser();
-        live.setCompanyUserId(user.getUserId());
-        live.setCompanyId(user.getCompanyId());
-        return toAjax(liveService.updateLive(live));
+        return AjaxResult.success();
+//        CompanyUser user = SecurityUtils.getLoginUser().getUser();
+//        live.setCompanyUserId(user.getUserId());
+//        live.setCompanyId(user.getCompanyId());
+//
+//        return toAjax(liveService.updateLive(live));
     }
 
     /**
@@ -195,11 +201,12 @@ public class LiveController extends BaseController
 	@DeleteMapping("/{liveIds}")
     public AjaxResult remove(@PathVariable Long[] liveIds)
     {
-        Live live = new Live();
-        CompanyUser user = SecurityUtils.getLoginUser().getUser();
-        live.setCompanyUserId(user.getUserId());
-        live.setCompanyId(user.getCompanyId());
-        return toAjax(liveService.deleteLiveByLiveIds(liveIds, live));
+        return AjaxResult.success();
+//        Live live = new Live();
+//        CompanyUser user = SecurityUtils.getLoginUser().getUser();
+//        live.setCompanyUserId(user.getUserId());
+//        live.setCompanyId(user.getCompanyId());
+//        return toAjax(liveService.deleteLiveByLiveIds(liveIds, live));
     }
 
     @PreAuthorize("@ss.hasPermi('live:live:query')")
@@ -217,9 +224,10 @@ public class LiveController extends BaseController
     @PreAuthorize("@ss.hasPermi('live:live:edit')")
     @PostMapping("/closeLiving")
     public R closeLiving(@RequestBody Map<String, String> payload) {
-        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
-        payload.put("userId", loginUser.getUser().getUserId().toString());
-        return liveService.closeLiving(payload);
+        return R.ok();
+//        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+//        payload.put("userId", loginUser.getUser().getUserId().toString());
+//        return liveService.closeLiving(payload);
     }
 
     @PreAuthorize("@ss.hasPermi('live:live:insert')")
@@ -247,7 +255,8 @@ public class LiveController extends BaseController
     @PreAuthorize("@ss.hasPermi('live:live:edit')")
     @PostMapping("/startLoopPlay")
     public R startLoopPlay(@RequestBody Live live) {
-        return liveService.startLoopPlay(live);
+        return R.ok();
+//        return liveService.startLoopPlay(live);
     }
 
     /**
@@ -265,8 +274,9 @@ public class LiveController extends BaseController
     @PreAuthorize("@ss.hasPermi('live:live:edit')")
     @PostMapping("/handleShelfOrUn")
     public R handleShelfOrUn(@RequestBody LiveListVo listVo) {
-        setListCompanyId(listVo);
-        return liveService.handleShelfOrUn(listVo);
+        return R.ok();
+//        setListCompanyId(listVo);
+//        return liveService.handleShelfOrUn(listVo);
     }
 
     /**
@@ -275,8 +285,9 @@ public class LiveController extends BaseController
     @PreAuthorize("@ss.hasPermi('live:live:edit')")
     @PostMapping("/handleDeleteSelected")
     public R handleDeleteSelected(@RequestBody LiveListVo listVo) {
-        setListCompanyId(listVo);
-        return liveService.handleDeleteSelected(listVo);
+        return R.ok();
+//        setListCompanyId(listVo);
+//        return liveService.handleDeleteSelected(listVo);
     }
 
 

+ 4 - 0
fs-company/src/main/java/com/fs/company/controller/qw/QwExternalContactController.java

@@ -113,6 +113,10 @@ public class QwExternalContactController extends BaseController
 
         startPage();
         qwExternalContact.setCompanyId(loginUser.getCompany().getCompanyId());
+        if(StringUtils.isNotEmpty(qwExternalContact.getStatuses())){
+            String[] split = qwExternalContact.getStatuses().split(",");
+            qwExternalContact.setStatusCondition(split);
+        }
         List<QwExternalContactVO> list = qwExternalContactService.selectQwExternalContactListVO(qwExternalContact);
         list.forEach(item->{
 

+ 3 - 3
fs-company/src/main/java/com/fs/company/controller/qw/QwExternalContactTransferLogController.java

@@ -104,10 +104,10 @@ public class QwExternalContactTransferLogController extends BaseController
     @PreAuthorize("@ss.hasPermi('qw:externalContactTransferLog:export')")
     @Log(title = "转接记录", businessType = BusinessType.EXPORT)
     @GetMapping("/export")
-    public AjaxResult export(QwExternalContactTransferLog qwExternalContactTransferLog)
+    public AjaxResult export(QwExternalContactTransferLogParam qwExternalContactTransferLog)
     {
-        List<QwExternalContactTransferLog> list = qwExternalContactTransferLogService.selectQwExternalContactTransferLogList(qwExternalContactTransferLog);
-        ExcelUtil<QwExternalContactTransferLog> util = new ExcelUtil<QwExternalContactTransferLog>(QwExternalContactTransferLog.class);
+        List<QwExternalContactTransferLogListVO> list = qwExternalContactTransferLogService.selectQwExternalContactTransferLogListVO(qwExternalContactTransferLog);
+        ExcelUtil<QwExternalContactTransferLogListVO> util = new ExcelUtil<QwExternalContactTransferLogListVO>(QwExternalContactTransferLogListVO.class);
         return util.exportExcel(list, "转接记录数据");
     }
 

+ 3 - 2
fs-company/src/main/resources/application.yml

@@ -1,13 +1,14 @@
 server:
-  port: 8006
+  port: 7773
 # Spring配置
 spring:
   profiles:
+    active: druid-ylrz
 #    active: druid-jnsyj-test
 #    active: druid-jnmy-test
 #    active: druid-jzzx-test
 #    active: druid-hdt
-    active: druid-bjzm-test
+#    active: druid-bjzm-test
 #    active: druid-yzt
 #    active: druid-myhk
 #    active: druid-sft

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

@@ -33,7 +33,6 @@ public class LiveWatchUserAspect {
         try {
             String methodName = joinPoint.getSignature().getName();
             Object[] args = joinPoint.getArgs();
-            log.info("直播观看用户数据发生变化,方法: {}, 参数: {}", methodName, Arrays.toString(args));
             // 提取liveId并处理缓存更新
             Set<Long> liveIds = extractLiveIds(methodName, args);
             for (Long liveId : liveIds) {

+ 0 - 2
fs-live-app/src/main/java/com/fs/live/controller/LiveDataController.java

@@ -15,7 +15,6 @@ public class LiveDataController extends BaseController {
 
     @Autowired
     private RedisCache redisCache;
-
     /**
      * 点赞
      * */
@@ -23,7 +22,6 @@ public class LiveDataController extends BaseController {
     public R like(@PathVariable("liveId") Long liveId) {
         //直播间总点赞数
         Long increment = redisCache.incr("live:like:" + liveId, 1);
-
         return R.ok().put("like",increment);
     }
 }

+ 9 - 6
fs-live-app/src/main/java/com/fs/live/websocket/handle/LiveChatHandler.java

@@ -3,6 +3,8 @@ package com.fs.live.websocket.handle;
 import com.alibaba.fastjson.JSONObject;
 import com.fs.his.domain.FsUser;
 import com.fs.his.service.IFsUserService;
+import com.fs.hisStore.domain.FsUserScrm;
+import com.fs.hisStore.service.IFsUserScrmService;
 import com.fs.live.websocket.bean.SendMsgVo;
 import com.fs.live.websocket.constant.AttrConstant;
 import com.fs.common.core.domain.R;
@@ -41,7 +43,7 @@ public class LiveChatHandler extends SimpleChannelInboundHandler<TextWebSocketFr
     private final static ILiveService liveService = SpringUtils.getBean(ILiveService.class);
     private final static ILiveWatchUserService liveWatchUserService = SpringUtils.getBean(ILiveWatchUserService.class);
     private final static ILiveMsgService liveMsgService = SpringUtils.getBean(ILiveMsgService.class);
-    private final static IFsUserService fsUserService = SpringUtils.getBean(IFsUserService.class);
+    private final static IFsUserScrmService fsUserService = SpringUtils.getBean(IFsUserScrmService.class);
 
     /**
      * 处理握手
@@ -70,11 +72,12 @@ public class LiveChatHandler extends SimpleChannelInboundHandler<TextWebSocketFr
             roomGroup.add(ctx.channel());
 
             if (userType == 0) {
+
+
+                FsUserScrm fsUser = fsUserService.selectFsUserByUserId(userId);
                 // 加入房间
-                LiveWatchUser liveWatchUser = liveWatchUserService.joinWithoutLocation(liveId, userId);
+                LiveWatchUser liveWatchUser = liveWatchUserService.joinWithoutLocation(fsUser,liveId, userId);
                 room.put(userId, ctx.channel());
-
-                FsUser fsUser = fsUserService.selectFsUserByUserId(userId);
                 if (Objects.isNull(fsUser)) {
                     ctx.channel().writeAndFlush(new TextWebSocketFrame("Error: 用户信息错误")).addListener(ChannelFutureListener.CLOSE);
                     return;
@@ -218,8 +221,8 @@ public class LiveChatHandler extends SimpleChannelInboundHandler<TextWebSocketFr
         ChannelGroup roomGroup = getRoomGroup(liveId);
 
         if (userType == 0) {
-            FsUser fsUser = fsUserService.selectFsUserByUserId(userId);
-            LiveWatchUser close = liveWatchUserService.close(liveId, userId);
+            FsUserScrm fsUser = fsUserService.selectFsUserByUserId(userId);
+            LiveWatchUser close = liveWatchUserService.close(fsUser,liveId, userId);
             room.remove(userId);
 
             if (room.isEmpty()) {

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

@@ -22,6 +22,7 @@ import com.fs.live.service.*;
 import com.fs.live.vo.LiveGoodsVo;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.time.DateUtils;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.stereotype.Component;
 
@@ -30,10 +31,9 @@ import javax.websocket.server.ServerEndpoint;
 import java.io.EOFException;
 import java.io.IOException;
 import java.util.*;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.concurrent.ThreadLocalRandom;
-import java.util.concurrent.TimeUnit;
+import java.util.concurrent.*;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
 
 import static com.fs.common.constant.LiveKeysConstant.*;
 
@@ -49,6 +49,16 @@ public class WebSocketServer {
     private final static ConcurrentHashMap<Long, ConcurrentHashMap<Long, Session>> rooms = new ConcurrentHashMap<>();
     // 管理端连接
     private final static ConcurrentHashMap<Long, CopyOnWriteArrayList<Session>> adminRooms = new ConcurrentHashMap<>();
+    
+    // Session发送锁,避免同一会话并发发送消息
+    private final static ConcurrentHashMap<String, Lock> sessionLocks = new ConcurrentHashMap<>();
+    // 心跳超时缓存:key=sessionId,value=最后心跳时间戳
+    private final static ConcurrentHashMap<String, Long> heartbeatCache = new ConcurrentHashMap<>();
+    // 心跳超时时间(毫秒):3分钟无心跳则认为超时
+    private final static long HEARTBEAT_TIMEOUT = 3 * 60 * 1000;
+    // admin房间消息发送线程池(单线程,保证串行化)
+    private final static ConcurrentHashMap<Long, ExecutorService> adminExecutors = new ConcurrentHashMap<>();
+    
     private final RedisCache redisCache = SpringUtils.getBean(RedisCache.class);
     private final ILiveMsgService liveMsgService = SpringUtils.getBean(ILiveMsgService.class);
     private final ILiveService liveService = SpringUtils.getBean(ILiveService.class);
@@ -101,7 +111,7 @@ public class WebSocketServer {
                 throw new BaseException("用户信息错误");
             }
 
-            LiveWatchUser liveWatchUserVO = liveWatchUserService.join(liveId, userId, location);
+            LiveWatchUser liveWatchUserVO = liveWatchUserService.join(fsUser,liveId, userId, location);
             room.put(userId, session);
             // 直播间浏览量 +1
             redisCache.incr(PAGE_VIEWS_KEY + liveId, 1);
@@ -177,7 +187,14 @@ public class WebSocketServer {
 
         } else {
             adminRoom.add(session);
+            // 为admin房间创建单线程执行器,保证串行化发送
+            adminExecutors.computeIfAbsent(liveId, k -> Executors.newSingleThreadExecutor());
         }
+        
+        // 初始化Session锁
+        sessionLocks.putIfAbsent(session.getId(), new ReentrantLock());
+        // 初始化心跳时间
+        heartbeatCache.put(session.getId(), System.currentTimeMillis());
 
         log.debug("加入webSocket liveId: {}, userId: {}, 直播间人数: {}, 管理端人数: {}", liveId, userId, room.size(), adminRoom.size());
     }
@@ -209,7 +226,7 @@ public class WebSocketServer {
             // 从在线用户Set中移除用户ID
             String onlineUsersSetKey = ONLINE_USERS_SET_KEY + liveId;
             redisCache.redisTemplate.opsForSet().remove(onlineUsersSetKey, String.valueOf(userId));
-            LiveWatchUser liveWatchUserVO = liveWatchUserService.close(liveId, userId);
+            LiveWatchUser liveWatchUserVO = liveWatchUserService.close(fsUser,liveId, userId);
             SendMsgVo sendMsgVo = new SendMsgVo();
             sendMsgVo.setLiveId(liveId);
             sendMsgVo.setUserId(userId);
@@ -224,7 +241,19 @@ public class WebSocketServer {
             broadcastMessage(liveId, JSONObject.toJSONString(R.ok().put("data", sendMsgVo)));
         } else {
             adminRoom.remove(session);
+            // 如果admin房间为空,关闭并清理执行器
+            if (adminRoom.isEmpty()) {
+                ExecutorService executor = adminExecutors.remove(liveId);
+                if (executor != null) {
+                    executor.shutdown();
+                }
+                adminRooms.remove(liveId);
+            }
         }
+        
+        // 清理Session相关资源
+        heartbeatCache.remove(session.getId());
+        sessionLocks.remove(session.getId());
 
         log.debug("离开webSocket liveId: {}, userId: {}, 直播间人数: {}, 管理端人数: {}", liveId, userId, room.size(), adminRoom.size());
     }
@@ -243,6 +272,8 @@ public class WebSocketServer {
         try {
             switch (msg.getCmd()) {
                 case "heartbeat":
+                    // 更新心跳时间
+                    heartbeatCache.put(session.getId(), System.currentTimeMillis());
                     sendMessage(session, JSONObject.toJSONString(R.ok().put("data", msg)));
                     break;
                 case "sendMsg":
@@ -498,12 +529,38 @@ public class WebSocketServer {
         return adminRooms.computeIfAbsent(liveId, k -> new CopyOnWriteArrayList<>());
     }
 
-    //发送消息
+    //发送消息(带锁机制,避免并发发送)
     public void sendMessage(Session session, String message) throws IOException {
-        session.getAsyncRemote().sendText(message);
+        if (session == null || !session.isOpen()) {
+            log.warn("WebSocket 会话已关闭,跳过发送");
+            return;
+        }
+        
+        // 获取Session锁
+        Lock lock = sessionLocks.get(session.getId());
+        if (lock == null) {
+            // 如果锁不存在,创建一个新锁
+            lock = sessionLocks.computeIfAbsent(session.getId(), k -> new ReentrantLock());
+        }
+        
+        // 使用锁保证同一Session的消息串行发送
+        lock.lock();
+        try {
+            if (session.isOpen()) {
+                session.getAsyncRemote().sendText(message);
+            }
+        } finally {
+            lock.unlock();
+        }
     }
 
     public void sendIntegralMessage(Long liveId, Long userId,Long scoreAmount) {
+        ConcurrentHashMap<Long, Session> room = getRoom(liveId);
+        Session session = room.get(userId);
+        if (session == null || !session.isOpen()) {
+            log.warn("WebSocket 会话已关闭,跳过发送");
+            return;
+        }
         SendMsgVo sendMsgVo = new SendMsgVo();
         sendMsgVo.setLiveId(liveId);
         sendMsgVo.setUserId(userId);
@@ -511,13 +568,20 @@ public class WebSocketServer {
         sendMsgVo.setCmd("Integral");
         sendMsgVo.setMsg("恭喜你成功获得观看奖励:" + scoreAmount + "芳华币");
         sendMsgVo.setData(String.valueOf(scoreAmount));
-        ConcurrentHashMap<Long, Session> room = getRoom(liveId);
-        Session session = room.get(userId);
+
         if(Objects.isNull( session)) return;
         session.getAsyncRemote().sendText(JSONObject.toJSONString(R.ok().put("data", sendMsgVo)));
     }
 
     private void sendBlockMessage(Long liveId, Long userId) {
+
+        ConcurrentHashMap<Long, Session> room = getRoom(liveId);
+        Session session = room.get(userId);
+        if (session == null || !session.isOpen()) {
+            log.warn("WebSocket 会话已关闭,跳过发送");
+            return;
+        }
+
         SendMsgVo sendMsgVo = new SendMsgVo();
         sendMsgVo.setLiveId(liveId);
         sendMsgVo.setUserId(userId);
@@ -525,8 +589,7 @@ public class WebSocketServer {
         sendMsgVo.setCmd("blockUser");
         sendMsgVo.setMsg("账号已被停用");
         sendMsgVo.setData(null);
-        ConcurrentHashMap<Long, Session> room = getRoom(liveId);
-        Session session = room.get(userId);
+
         if(Objects.isNull( session)) return;
         session.getAsyncRemote().sendText(JSONObject.toJSONString(R.ok().put("data", sendMsgVo)));
     }
@@ -540,16 +603,33 @@ public class WebSocketServer {
         ConcurrentHashMap<Long, Session> room = getRoom(liveId);
         List<Session> adminRoom = getAdminRoom(liveId);
 
+        // 普通用户房间:并行发送
         room.forEach((k, v) -> {
             if (v.isOpen()) {
-                sendWithRetry(v,message,7);
+                sendWithRetry(v,message,1);
             }
         });
-        adminRoom.forEach(v -> {
-            if (v.isOpen()) {
-                sendWithRetry(v,message,7);
+        
+        // admin房间:串行发送,使用单线程执行器
+        if (!adminRoom.isEmpty()) {
+            ExecutorService executor = adminExecutors.get(liveId);
+            if (executor != null && !executor.isShutdown()) {
+                executor.submit(() -> {
+                    for (Session session : adminRoom) {
+                        if (session.isOpen()) {
+                            sendWithRetry(session, message, 1);
+                        }
+                    }
+                });
+            } else {
+                // 如果执行器不存在或已关闭,直接发送
+                adminRoom.forEach(v -> {
+                    if (v.isOpen()) {
+                        sendWithRetry(v, message, 1);
+                    }
+                });
             }
-        });
+        }
     }
 
     public void removeLikeCountCache(Long liveId) {
@@ -585,6 +665,102 @@ public class WebSocketServer {
         lastLikeCountCache.keySet().removeIf(liveId -> !activeLiveIds.contains(liveId));
     }
 
+
+    @Scheduled(fixedRate = 2000)// 每2秒执行一次
+    public void broadcastUserNumMessage() {
+        // 遍历每个直播间
+        for (Map.Entry<Long, ConcurrentHashMap<Long, Session>> entry : rooms.entrySet()) {
+            Long liveId = entry.getKey();
+            ConcurrentHashMap<Long, Session> room = entry.getValue();
+
+            // 统计当前直播间的在线人数
+            int onlineCount = room.size();
+
+            // 构造消息
+            SendMsgVo sendMsgVo = new SendMsgVo();
+            sendMsgVo.setLiveId(liveId);
+            sendMsgVo.setCmd("userCount");
+            sendMsgVo.setData(String.valueOf(onlineCount));
+
+            // 广播当前直播间的在线人数
+            broadcastMessage(liveId, JSONObject.toJSONString(R.ok().put("data", sendMsgVo)));
+
+            log.debug("广播直播间在线人数: liveId={}, onlineCount={}", liveId, onlineCount);
+        }
+    }
+
+    /**
+     * 定时清理无效会话(每分钟执行一次)
+     * 检查心跳超时的会话并关闭
+     */
+    @Scheduled(fixedRate = 60000) // 每分钟执行一次
+    public void cleanInactiveSessions() {
+        long currentTime = System.currentTimeMillis();
+        int cleanedCount = 0;
+        
+        // 遍历所有直播间
+        for (Map.Entry<Long, ConcurrentHashMap<Long, Session>> roomEntry : rooms.entrySet()) {
+            Long liveId = roomEntry.getKey();
+            ConcurrentHashMap<Long, Session> room = roomEntry.getValue();
+            
+            // 检查普通用户会话
+            List<Long> toRemove = new ArrayList<>();
+            room.forEach((userId, session) -> {
+                Long lastHeartbeat = heartbeatCache.get(session.getId());
+                if (lastHeartbeat != null && (currentTime - lastHeartbeat) > HEARTBEAT_TIMEOUT) {
+                    log.warn("会话心跳超时,即将关闭: sessionId={}, liveId={}, userId={}, 超时时长={}ms", 
+                            session.getId(), liveId, userId, currentTime - lastHeartbeat);
+                    toRemove.add(userId);
+                    try {
+                        if (session.isOpen()) {
+                            session.close(new CloseReason(CloseReason.CloseCodes.NORMAL_CLOSURE, "心跳超时"));
+                        }
+                    } catch (Exception e) {
+                        log.error("关闭超时会话失败: sessionId={}, liveId={}, userId={}", 
+                                session.getId(), liveId, userId, e);
+                    }
+                }
+            });
+            
+            // 移除超时的会话
+            toRemove.forEach(room::remove);
+            cleanedCount += toRemove.size();
+        }
+        
+        // 检查admin房间
+        for (Map.Entry<Long, CopyOnWriteArrayList<Session>> adminEntry : adminRooms.entrySet()) {
+            Long liveId = adminEntry.getKey();
+            CopyOnWriteArrayList<Session> adminRoom = adminEntry.getValue();
+            
+            List<Session> toRemoveAdmin = new ArrayList<>();
+            for (Session session : adminRoom) {
+                Long lastHeartbeat = heartbeatCache.get(session.getId());
+                if (lastHeartbeat != null && (currentTime - lastHeartbeat) > HEARTBEAT_TIMEOUT) {
+                    log.warn("admin会话心跳超时,即将关闭: sessionId={}, liveId={}, 超时时长={}ms", 
+                            session.getId(), liveId, currentTime - lastHeartbeat);
+                    toRemoveAdmin.add(session);
+                    try {
+                        if (session.isOpen()) {
+                            session.close(new CloseReason(CloseReason.CloseCodes.NORMAL_CLOSURE, "心跳超时"));
+                        }
+                    } catch (Exception e) {
+                        log.error("关闭admin超时会话失败: sessionId={}, liveId={}", 
+                                session.getId(), liveId, e);
+                    }
+                }
+            }
+            
+            // 移除超时的admin会话
+            toRemoveAdmin.forEach(adminRoom::remove);
+            cleanedCount += toRemoveAdmin.size();
+        }
+        
+        if (cleanedCount > 0) {
+            log.info("清理无效会话完成,共清理 {} 个超时会话", cleanedCount);
+        }
+    }
+
+
     /**
      * 广播点赞消息
      * @param liveId   直播间ID
@@ -600,16 +776,21 @@ public class WebSocketServer {
     }
 
     private void sendWithRetry(Session session, String message, int maxRetries) {
+        if (session == null || !session.isOpen()) {
+            log.warn("WebSocket 会话已关闭,跳过发送");
+            return;
+        }
+        
         int attempts = 0;
         while (attempts < maxRetries) {
             try {
-                if(session.isOpen()) {
-                    session.getAsyncRemote().sendText(message);
-                }
+                // 使用带锁的sendMessage方法,避免并发发送
+                sendMessage(session, message);
                 return;  // 发送成功,退出
             } catch (Exception e) {
                 if (e.getMessage() != null && e.getMessage().contains("TEXT_FULL_WRITING")) {
                     attempts++;
+                    log.warn("发送消息遇到TEXT_FULL_WRITING错误,第{}次重试, sessionId={}", attempts, session.getId());
                     try {
                         TimeUnit.MILLISECONDS.sleep(ThreadLocalRandom.current().nextInt(5, 100));
                     } catch (InterruptedException ie) {
@@ -617,11 +798,15 @@ public class WebSocketServer {
                         break;
                     }
                 } else {
-                    throw e;
+                    log.error("发送消息失败: sessionId={}, error={}", session.getId(), e.getMessage(), e);
+                    break;
                 }
             }
         }
-        log.info("超过重试次数, 消息 {}",message);
+        
+        if (attempts >= maxRetries) {
+            log.warn("超过重试次数({}),放弃发送消息: sessionId={}", maxRetries, session.getId());
+        }
     }
 
 
@@ -727,4 +912,6 @@ public class WebSocketServer {
         String key = "live:auto_task:";
         redisCache.redisTemplate.opsForZSet().removeRangeByScore(key + liveId, data, data);
     }
+
 }
+

+ 34 - 0
fs-qwhook/src/main/java/com/fs/app/controller/CommonController.java

@@ -17,6 +17,7 @@ import com.fs.his.service.IFsAppVersionService;
 import com.fs.qw.domain.*;
 import com.fs.qw.mapper.*;
 import com.fs.qw.param.QwConfigSignatureParam;
+import com.fs.qw.param.QwMandatoryRegistrParam;
 import com.fs.qw.service.*;
 import com.fs.qw.vo.QwHookAuthVO;
 import com.fs.qwApi.param.QwExternalContactHParam;
@@ -34,6 +35,8 @@ import lombok.extern.slf4j.Slf4j;
 import org.apache.rocketmq.spring.core.RocketMQTemplate;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
 import java.util.concurrent.TimeUnit;
 
 @Api("公共接口")
@@ -93,6 +96,37 @@ public class CommonController {
     @Autowired
     private RocketMQTemplate rocketMQTemplate;
 
+    @Autowired
+    private IQwExternalContactService qwExternalContactService;
+
+
+    @GetMapping("/MandatoryRegistration")
+    public R MandatoryRegistration() throws Exception {
+        List<String> longs = qwExternalContactService.selectQwExternalContactMandatoryRegistration();
+        longs.forEach(item->{
+            List<QwMandatoryRegistrParam> registrParamList = qwExternalContactService.selectQwExternalContactMandatoryRegistrationByIds(String.valueOf(item));
+
+            batchUpdateQwExternalContactMandatoryRegistration(registrParamList);
+
+        });
+        return R.ok();
+    }
+
+    private void batchUpdateQwExternalContactMandatoryRegistration(List<QwMandatoryRegistrParam> registrParamList) {
+        // 定义批量插入的大小
+        int batchSize = 300;
+
+        // 循环处理外部用户 ID,每次处理批量大小的子集
+        for (int i = 0; i < registrParamList.size(); i += batchSize) {
+
+            int endIndex = Math.min(i + batchSize, registrParamList.size());
+            List<QwMandatoryRegistrParam> batchList = registrParamList.subList(i, endIndex);  // 获取当前批次的子集
+
+            qwExternalContactService.batchUpdateQwExternalContactMandatoryRegistration(batchList);
+
+        }
+    }
+
     @PostMapping("/qwHookNotify")
     public R qwHookNotify(@RequestBody String body) {
         QwHookVO vo= JSONUtil.toBean(body,QwHookVO.class);

+ 4 - 1
fs-service/src/main/java/com/fs/company/domain/CompanyUser.java

@@ -40,6 +40,9 @@ public class CompanyUser extends BaseEntity
     @Excel(name = "部门编号")
     private Long deptId;
 
+    @Excel(name = "部门名称")
+    private String deptName;
+
     /** 用户账号 */
     @Excel(name = "用户账号")
     private String userName;
@@ -133,7 +136,7 @@ public class CompanyUser extends BaseEntity
 
     private String firstchar;
     private String postName;
-    private String deptName;
+
 
     private String qrCodeWeixin;
 

+ 1 - 0
fs-service/src/main/java/com/fs/course/domain/FsUserCourseVideo.java

@@ -120,4 +120,5 @@ public class FsUserCourseVideo extends BaseEntity
 
     private Long listingEndTime;//商品结束售卖时间
 
+    private Integer isSpeed; // 是否启用倍速 0:否 1:是
 }

+ 19 - 0
fs-service/src/main/java/com/fs/course/mapper/FsCourseTrafficLogMapper.java

@@ -228,4 +228,23 @@ public interface FsCourseTrafficLogMapper
             "</script>"
     })
     List<StatisticsSummaryVO> getStatisticsSummaryList(@Param("param") StatisticsSummaryParam param);
+
+
+    /**
+     * 查询过期的流量记录ID列表(分页)
+     */
+    List<Long> selectExpireLinkIds(@Param("createTime") Date createTime,
+                                   @Param("offset") int offset,
+                                   @Param("limit") int limit);
+
+    /**
+     * 批量删除ID列表
+     * @return 删除的行数
+     */
+    int batchDeleteByIds(@Param("ids") List<Long> ids);
+
+    /**
+     * 查询过期记录总数
+     */
+    Long countExpireLink(@Param("createTime") Date createTime);
 }

+ 2 - 5
fs-service/src/main/java/com/fs/course/mapper/FsUserCourseVideoRedPackageMapper.java

@@ -2,7 +2,6 @@ package com.fs.course.mapper;
 
 import java.util.List;
 
-import com.fs.course.domain.FsUserCourseVideo;
 import com.fs.course.domain.FsUserCourseVideoRedPackage;
 import com.fs.course.param.FsUserCourseVideoParam;
 import org.apache.ibatis.annotations.Delete;
@@ -10,8 +9,6 @@ import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
 import org.apache.ibatis.annotations.Update;
 
-import javax.validation.constraints.NotNull;
-
 /**
  * 课程公司红包Mapper接口
  *
@@ -107,12 +104,12 @@ public interface FsUserCourseVideoRedPackageMapper
     int updateBatchDelFlag(@Param("ids") Long [] ids, @Param("delFlag") Integer delFlag);
 
     @Delete("<script>" +
-            "DELETE FROM fs_user_course_video_red_package WHERE video_id IN " +
+            "DELETE FROM fs_user_course_video_red_package WHERE period_id IN " +
             "<foreach collection='ids' item='id' open='(' separator=',' close=')'>" +
             "#{id}" +
             "</foreach>" +
             "</script>")
-    int deleteBatchByVideoIds(@Param("ids") Long[] ids);
+    int deleteBatchByPeriodIds(@Param("ids") Long[] ids);
 
 
     Integer selectRedPacketByCompanyCount(@Param("videoId") Long videoId,@Param("companyId") Long companyId, @Param("periodId") Long periodId);

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

@@ -95,4 +95,6 @@ public interface IFsCourseTrafficLogService
      * @return
      */
     List<StatisticsSummaryVO> getStatisticsSummaryListNotPage(StatisticsSummaryParam param);
+
+    void batchDelTraffic();
 }

+ 82 - 0
fs-service/src/main/java/com/fs/course/service/impl/FsCourseTrafficLogServiceImpl.java

@@ -3,6 +3,7 @@ package com.fs.course.service.impl;
 import java.math.BigDecimal;
 import java.text.SimpleDateFormat;
 import java.time.LocalDate;
+import java.time.ZoneId;
 import java.util.*;
 import java.util.stream.Collectors;
 
@@ -521,4 +522,85 @@ public class FsCourseTrafficLogServiceImpl implements IFsCourseTrafficLogService
         }
         return res;
     }
+
+
+    @Override
+    public void batchDelTraffic() {
+        // 设置删除的时间条件(2025-09-01之前)
+        LocalDate targetLocalDate = LocalDate.of(2025, 10, 1);
+        Date targetDate = Date.from(targetLocalDate.atStartOfDay(ZoneId.systemDefault()).toInstant());
+
+        int batchSize = 5000; // 每批次处理数量
+        int sleepMillis = 100; // 批次间休眠时间(毫秒)
+
+        batchDeleteExpiredData(targetDate, batchSize, sleepMillis);
+    }
+
+    /**
+     * 批量删除过期数据
+     */
+    private void batchDeleteExpiredData(Date targetDate, int batchSize, int sleepMillis) {
+        int currentPage = 0;
+        long totalDeleted = 0;
+
+        try {
+            // 查询总记录数用于进度监控
+            Long totalCount = fsCourseTrafficLogMapper.countExpireLink(targetDate);
+            log.info("开始批量删除过期流量记录,目标时间: {}, 总记录数: {}", targetDate, totalCount);
+
+            if (totalCount == null || totalCount == 0) {
+                log.info("没有需要删除的过期记录");
+                return;
+            }
+
+            long startTime = System.currentTimeMillis();
+
+            while (true) {
+                // 分页查询过期记录的log_id
+                int offset = currentPage * batchSize;
+                List<Long> logIds = fsCourseTrafficLogMapper.selectExpireLinkIds(targetDate, offset, batchSize);
+
+                if (logIds == null || logIds.isEmpty()) {
+                    log.info("批量删除完成,共删除 {} 条记录", totalDeleted);
+                    break;
+                }
+
+                // 批量删除当前批次的log_id
+                int deletedCount = fsCourseTrafficLogMapper.batchDeleteByIds(logIds);
+                totalDeleted += deletedCount;
+
+                // 每5批次或最后一批输出日志
+                if (currentPage % 5 == 0 || logIds.size() < batchSize) {
+                    double progress = (double) totalDeleted / totalCount * 100;
+                    long currentTime = System.currentTimeMillis();
+                    long elapsedSeconds = (currentTime - startTime) / 1000;
+
+                    log.info("批次 {}: 删除 {} 条,进度: {}/{} ({:.2f}%),耗时: {}秒",
+                            currentPage + 1, deletedCount, totalDeleted, totalCount,
+                            progress, elapsedSeconds);
+                }
+
+                currentPage++;
+
+                // 批次间短暂休眠,避免数据库压力过大
+                if (sleepMillis > 0 && logIds.size() == batchSize) {
+                    try {
+                        Thread.sleep(sleepMillis);
+                    } catch (InterruptedException e) {
+                        Thread.currentThread().interrupt();
+                        log.warn("删除任务被中断");
+                        break;
+                    }
+                }
+            }
+
+            long endTime = System.currentTimeMillis();
+            long totalSeconds = (endTime - startTime) / 1000;
+            log.info("批量删除任务完成,总计删除: {} 条记录,总耗时: {} 秒", totalDeleted, totalSeconds);
+
+        } catch (Exception e) {
+            log.error("批量删除流量记录失败,已删除: {} 条", totalDeleted, e);
+            throw new RuntimeException("批量删除失败", e);
+        }
+    }
 }

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

@@ -152,8 +152,8 @@ public class FsUserCompanyBindServiceImpl extends ServiceImpl<FsUserCompanyBindM
                 }
             }
             if (qwExternalContact.getUserRepeat() == 0) {
-                Integer i = baseMapper.selectCount(new QueryWrapper<FsUserCompanyBind>().eq("fs_user_id", fsUserId).ne("company_user_id", companyUserId));
-                if (i > 0) {
+                Integer i = baseMapper.selectCount(new QueryWrapper<FsUserCompanyBind>().eq("fs_user_id", fsUserId));
+                if (i > 1) {
                     qwExternalContact.setUserRepeat(1);
                     qwExternalContactMapper.updateById(qwExternalContact);
                 }

+ 1 - 2
fs-service/src/main/java/com/fs/course/service/impl/FsUserCoursePeriodServiceImpl.java

@@ -169,13 +169,12 @@ public class FsUserCoursePeriodServiceImpl implements IFsUserCoursePeriodService
         }
         List<FsUserCoursePeriodDays> fsUserCoursePeriodDays = fsUserCoursePeriodDaysMapper.selectCourseVideoList(set);
         List<Long> periodDayIds = fsUserCoursePeriodDays.stream().map(FsUserCoursePeriodDays::getId).collect(Collectors.toList());
-        List<Long> videoIds = fsUserCoursePeriodDays.stream().map(FsUserCoursePeriodDays::getVideoId).collect(Collectors.toList());
         if(!periodDayIds.isEmpty()){
             fsUserCoursePeriodDaysMapper.updateBatchDelFlag(periodDayIds.toArray(new Long[0]),1);
             //删除红包记录(修改状态)
             //fsUserCourseVideoRedPackageMapper.updateBatchDelFlag(videoIds.toArray(new Long[0]),1);
             //直接删除
-            fsUserCourseVideoRedPackageMapper.deleteBatchByVideoIds(videoIds.toArray(new Long[0]));
+            fsUserCourseVideoRedPackageMapper.deleteBatchByPeriodIds(periodIds);
         }
         return flag;
     }

+ 1 - 2
fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseTrainingCampServiceImpl.java

@@ -97,13 +97,12 @@ public class FsUserCourseTrainingCampServiceImpl extends ServiceImpl<FsUserCours
         }
         List<FsUserCoursePeriodDays> fsUserCoursePeriodDays = fsUserCoursePeriodDaysMapper.selectCourseVideoList(set);
         List<Long> periodDayIds = fsUserCoursePeriodDays.stream().map(FsUserCoursePeriodDays::getId).collect(Collectors.toList());
-        List<Long> videoIds = fsUserCoursePeriodDays.stream().map(FsUserCoursePeriodDays::getVideoId).collect(Collectors.toList());
         if(!periodDayIds.isEmpty()){
             fsUserCoursePeriodDaysMapper.updateBatchDelFlag(periodDayIds.toArray(new Long[0]),1);
             //删除红包记录(修改状态)
             //fsUserCourseVideoRedPackageMapper.updateBatchDelFlag(videoIds.toArray(new Long[0]),1);
             //直接删除
-            fsUserCourseVideoRedPackageMapper.deleteBatchByVideoIds(videoIds.toArray(new Long[0]));
+            fsUserCourseVideoRedPackageMapper.deleteBatchByPeriodIds(set.toArray(new Long[0]));
         }
     }
 

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

@@ -778,7 +778,7 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
         }
         if (isRoom == null || isRoom == 0) {
             // 当 isRoom 为 null 或 0 时走 handleExt
-            return handleExt(param,noRegisterMsg, oneCompanyCourse);
+            return handleExt(param,noMemberMsg, oneCompanyCourse);
         } else if (isRoom == 1) {
             // 当 isRoom 为 1 时走 handleRoom
             return handleRoom(param,fsUser,noRegisterMsg);

+ 1 - 0
fs-service/src/main/java/com/fs/course/vo/FsUserCourseVideoQVO.java

@@ -89,6 +89,7 @@ public class FsUserCourseVideoQVO extends BaseEntity {
 
     private String packageJson;
     private Integer isFirst;
+    private Integer isSpeed;
     private Integer isProduct;//是否关联拍商品 0:否 1:是
 
     private Long productId;//拍商品id

+ 2 - 0
fs-service/src/main/java/com/fs/course/vo/newfs/FsUserCourseVideoPageListVO.java

@@ -69,4 +69,6 @@ public class FsUserCourseVideoPageListVO extends BaseEntity {
     @ApiModelProperty(value = "项目名称")
     private String projectName;
 
+    private Integer isSpeed;
+
 }

+ 9 - 7
fs-service/src/main/java/com/fs/fastGpt/service/IFastgptEventLogTotalService.java

@@ -9,14 +9,14 @@ import java.util.List;
 
 /**
  * ai事件埋点统计Service接口
- * 
+ *
  * @author fs
  * @date 2025-06-26
  */
 public interface IFastgptEventLogTotalService extends IService<FastgptEventLogTotal>{
     /**
      * 查询ai事件埋点统计
-     * 
+     *
      * @param id ai事件埋点统计主键
      * @return ai事件埋点统计
      */
@@ -24,7 +24,7 @@ public interface IFastgptEventLogTotalService extends IService<FastgptEventLogTo
 
     /**
      * 查询ai事件埋点统计列表
-     * 
+     *
      * @param fastgptEventLogTotal ai事件埋点统计
      * @return ai事件埋点统计集合
      */
@@ -32,7 +32,7 @@ public interface IFastgptEventLogTotalService extends IService<FastgptEventLogTo
 
     /**
      * 新增ai事件埋点统计
-     * 
+     *
      * @param fastgptEventLogTotal ai事件埋点统计
      * @return 结果
      */
@@ -40,7 +40,7 @@ public interface IFastgptEventLogTotalService extends IService<FastgptEventLogTo
 
     /**
      * 修改ai事件埋点统计
-     * 
+     *
      * @param fastgptEventLogTotal ai事件埋点统计
      * @return 结果
      */
@@ -48,7 +48,7 @@ public interface IFastgptEventLogTotalService extends IService<FastgptEventLogTo
 
     /**
      * 批量删除ai事件埋点统计
-     * 
+     *
      * @param ids 需要删除的ai事件埋点统计主键集合
      * @return 结果
      */
@@ -56,7 +56,7 @@ public interface IFastgptEventLogTotalService extends IService<FastgptEventLogTo
 
     /**
      * 删除ai事件埋点统计信息
-     * 
+     *
      * @param id ai事件埋点统计主键
      * @return 结果
      */
@@ -91,4 +91,6 @@ public interface IFastgptEventLogTotalService extends IService<FastgptEventLogTo
     int updateFastgptEventLogTotalBatch(List<FastgptEventLogTotal> fastgptEventLogTotalList);
 
     List<FastgptEventLogTotalVo> selectFastgptEventLogTotalListByStatTime(String dateTime);
+
+    void eventLogTotals(String startTime, String endTime);
 }

+ 237 - 7
fs-service/src/main/java/com/fs/fastGpt/service/impl/FastgptEventLogTotalServiceImpl.java

@@ -9,15 +9,19 @@ import com.fs.common.utils.DateUtils;
 import com.fs.company.domain.CompanyUser;
 import com.fs.company.service.ICompanyService;
 import com.fs.fastGpt.domain.FastGptEventTokenLog;
+import com.fs.fastGpt.domain.FastGptPushTokenTotal;
 import com.fs.fastGpt.domain.FastGptRole;
 import com.fs.fastGpt.domain.FastgptEventLogTotal;
 import com.fs.fastGpt.mapper.FastgptEventLogTotalMapper;
 import com.fs.fastGpt.service.IFastGptRoleService;
 import com.fs.fastGpt.service.IFastgptEventLogTotalService;
 import com.fs.fastGpt.vo.FastgptEventLogTotalVo;
+import com.fs.qw.mapper.QwRestrictionPushRecordMapper;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
+import java.time.LocalDate;
 import java.util.*;
 import java.util.stream.Collectors;
 
@@ -25,11 +29,12 @@ import static com.fs.common.utils.DictUtils.getDictCache;
 
 /**
  * ai事件埋点统计Service业务层处理
- * 
+ *
  * @author fs
  * @date 2025-06-26
  */
 @Service
+@Slf4j
 public class FastgptEventLogTotalServiceImpl extends ServiceImpl<FastgptEventLogTotalMapper, FastgptEventLogTotal> implements IFastgptEventLogTotalService {
 
     @Autowired
@@ -42,7 +47,7 @@ public class FastgptEventLogTotalServiceImpl extends ServiceImpl<FastgptEventLog
     private IFastGptRoleService fastGptRoleService;
     /**
      * 查询ai事件埋点统计
-     * 
+     *
      * @param id ai事件埋点统计主键
      * @return ai事件埋点统计
      */
@@ -54,7 +59,7 @@ public class FastgptEventLogTotalServiceImpl extends ServiceImpl<FastgptEventLog
 
     /**
      * 查询ai事件埋点统计列表
-     * 
+     *
      * @param fastgptEventLogTotal ai事件埋点统计
      * @return ai事件埋点统计
      */
@@ -66,7 +71,7 @@ public class FastgptEventLogTotalServiceImpl extends ServiceImpl<FastgptEventLog
 
     /**
      * 新增ai事件埋点统计
-     * 
+     *
      * @param fastgptEventLogTotal ai事件埋点统计
      * @return 结果
      */
@@ -78,7 +83,7 @@ public class FastgptEventLogTotalServiceImpl extends ServiceImpl<FastgptEventLog
 
     /**
      * 修改ai事件埋点统计
-     * 
+     *
      * @param fastgptEventLogTotal ai事件埋点统计
      * @return 结果
      */
@@ -90,7 +95,7 @@ public class FastgptEventLogTotalServiceImpl extends ServiceImpl<FastgptEventLog
 
     /**
      * 批量删除ai事件埋点统计
-     * 
+     *
      * @param ids 需要删除的ai事件埋点统计主键
      * @return 结果
      */
@@ -102,7 +107,7 @@ public class FastgptEventLogTotalServiceImpl extends ServiceImpl<FastgptEventLog
 
     /**
      * 删除ai事件埋点统计信息
-     * 
+     *
      * @param id ai事件埋点统计主键
      * @return 结果
      */
@@ -286,5 +291,230 @@ public class FastgptEventLogTotalServiceImpl extends ServiceImpl<FastgptEventLog
         return fastgptEventLogTotalMapper.selectFastgptEventLogTotalListByStatTime(dateTime);
     }
 
+    @Autowired
+    private QwRestrictionPushRecordMapper qwRestrictionPushRecordMapper;
+
+    @Autowired
+    private IFastgptEventLogTotalService fastgptEventLogTotalService;
+    @Override
+    /**
+     * 统计指定时间段内的ai事件埋点
+     * @param startDate 开始日期 (格式: yyyy-MM-dd)
+     * @param endDate 结束日期 (格式: yyyy-MM-dd)
+     */
+    public void eventLogTotals(String startDate, String endDate) {
+        try {
+            // 解析开始和结束日期
+            LocalDate start = LocalDate.parse(startDate);
+            LocalDate end = LocalDate.parse(endDate);
+
+            // 循环处理每一天
+            LocalDate current = start;
+            while (!current.isAfter(end)) {
+                String dateTime = current.format(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd"));
+                Date date = Date.from(current.atStartOfDay(java.time.ZoneId.systemDefault()).toInstant());
+
+                log.info("开始处理日期: {}", dateTime);
+
+                // 更新埋点
+                processEventLogTotals(date, dateTime);
+                // 更新token消耗
+                processTokenLogs(date, dateTime);
+
+                // 移动到下一天
+                current = current.plusDays(1);
+            }
+
+            log.info("时间段 {} 至 {} 的AI事件统计处理完成", startDate, endDate);
+        } catch (Exception e) {
+            log.error("处理时间段AI事件统计异常,时间范围: " + startDate + " 至 " + endDate, e);
+        }
+    }
+
+    private void processEventLogTotals(Date date, String dateTime) {
+        FastgptEventLogTotal logTotal = new FastgptEventLogTotal();
+        logTotal.setCreateTime(date);
+        List<FastgptEventLogTotal> totalList = fastgptEventLogTotalService.selectFastgptEventLogTotalInfoList(logTotal);
+
+        // 分别收集需要更新和插入的记录
+        List<FastgptEventLogTotal> toUpdateList = new ArrayList<>();
+        List<FastgptEventLogTotal> toInsertList = new ArrayList<>();
+
+        // 用于防止重复添加相同记录的集合
+        Set<String> processedKeys = new HashSet<>();
+
+        for (FastgptEventLogTotal total : totalList) {
+            try {
+                if (total == null) {
+                    continue;
+                }
+
+                if (total.getType() == 1) {
+                    total.setCount(total.getSenderCount());
+                }
+                // 构造唯一标识符,用于防止重复处理
+                String uniqueKey = String.format("%d_%d_%d_%d_%d_%s",
+                        total.getRoleId() != null ? total.getRoleId() : 0,
+                        total.getType() != null ? total.getType() : 0,
+                        total.getCompanyId() != null ? total.getCompanyId() : 0,
+                        total.getCompanyUserId() != null ? total.getCompanyUserId() : 0,
+                        total.getQwUserId() != null ? total.getQwUserId() : 0,
+                        dateTime
+                );
+                // 检查是否已经处理过这个记录
+                if (processedKeys.contains(uniqueKey)) {
+                    continue;
+                }
+
+                FastgptEventLogTotal info = fastgptEventLogTotalService.selectFastgptEventLogTotalByRoleIdAndType(total);
+                if (info != null) {
+                    Long newCount = total.getCount() != null ? total.getCount() : 0L;
+                    // 只有当count值发生变化时才加入更新列表
+                    if (!newCount.equals(info.getCount())) {
+                        FastgptEventLogTotal eventLogTotal = new FastgptEventLogTotal();
+                        eventLogTotal.setId(info.getId());
+                        eventLogTotal.setCount(newCount);
+                        if (!processedKeys.contains(uniqueKey)) {
+                            toUpdateList.add(eventLogTotal);
+                            // 标记为已处理
+                            processedKeys.add(uniqueKey);
+                        }
+                    }
+                } else {
+                    total.setStatTime(dateTime);
+                    if (!processedKeys.contains(uniqueKey)) {
+                        toInsertList.add(total);
+                        // 标记为已处理
+                        processedKeys.add(uniqueKey);
+                    }
+                }
+            } catch (Exception e) {
+                log.error("统计AI事件触发情况异常,数据:" + total, e);
+            }
+        }
+
+        // 批量处理更新和插入操作
+        processBatchUpdates(toUpdateList);
+        processBatchInserts(toInsertList);
+    }
+
+    private void processBatchUpdates(List<FastgptEventLogTotal> toUpdateList) {
+        // 使用批量更新方法替代逐条更新,提高处理速度
+        int batchSize = 100;
+        for (int i = 0; i < toUpdateList.size(); i += batchSize) {
+            int endIndex = Math.min(i + batchSize, toUpdateList.size());
+            List<FastgptEventLogTotal> batch = toUpdateList.subList(i, endIndex);
+            try {
+                fastgptEventLogTotalService.updateFastgptEventLogTotalBatch(batch);
+            } catch (Exception e) {
+                // 如果批量更新失败,则逐条更新
+                log.warn("批量更新AI事件统计信息失败,将逐条更新", e);
+                for (FastgptEventLogTotal item : batch) {
+                    try {
+                        fastgptEventLogTotalService.updateFastgptEventLogTotal(item);
+                    } catch (Exception ex) {
+                        log.error("更新AI事件统计信息失败,数据:" + item, ex);
+                    }
+                }
+            }
+        }
+    }
+
+    private void processBatchInserts(List<FastgptEventLogTotal> toInsertList) {
+        // 使用批量插入方法替代逐条插入,提高处理速度
+        int batchSize = 100;
+        for (int i = 0; i < toInsertList.size(); i += batchSize) {
+            int endIndex = Math.min(i + batchSize, toInsertList.size());
+            List<FastgptEventLogTotal> batch = toInsertList.subList(i, endIndex);
+            try {
+                fastgptEventLogTotalService.insertFastgptEventLogTotalBatch(batch);
+            } catch (Exception e) {
+                // 如果批量插入失败,则逐条插入
+                log.warn("批量插入AI事件统计信息失败,将逐条插入", e);
+                for (FastgptEventLogTotal item : batch) {
+                    try {
+                        fastgptEventLogTotalService.insertFastgptEventLogTotal(item);
+                    } catch (Exception ex) {
+                        log.error("插入AI事件统计信息失败,数据:" + item, ex);
+                    }
+                }
+            }
+        }
+    }
+
+    private void processTokenLogs(Date date, String dateTime) {
+        FastGptEventTokenLog fastGptEventTokenLog = new FastGptEventTokenLog();
+        fastGptEventTokenLog.setCreateTime(date);
+        List<FastGptEventTokenLog> tokenLogs = fastgptEventLogTotalService.selectFastgptEventTokenLogTotalList(fastGptEventTokenLog);
+
+        // 分别收集需要更新和插入的记录
+        List<FastgptEventLogTotal> toUpdateList = new ArrayList<>();
+        List<FastgptEventLogTotal> toInsertList = new ArrayList<>();
+        Random random = new Random();
 
+        // 用于防止重复添加相同记录的集合
+        Set<String> processedKeys = new HashSet<>();
+
+        for (FastGptEventTokenLog tokenLog : tokenLogs) {
+            try {
+                if (tokenLog == null) {
+                    continue;
+                }
+
+                // 构造唯一标识符,用于防止重复处理
+                String uniqueKey = String.format("%d_11_%d_%d_%d_%s",
+                        tokenLog.getRoleId() != null ? tokenLog.getRoleId() : 0,
+                        tokenLog.getCompanyId() != null ? tokenLog.getCompanyId() : 0,
+                        tokenLog.getCompanyUserId() != null ? tokenLog.getCompanyUserId() : 0,
+                        tokenLog.getQwUserId() != null ? tokenLog.getQwUserId() : 0,
+                        dateTime
+                );
+
+                // 检查是否已经处理过这个记录
+                if (processedKeys.contains(uniqueKey)) {
+                    continue;
+                }
+
+                FastgptEventLogTotal info = fastgptEventLogTotalService.selectFastgptEventTokenLogTotalByRoleIdAndType(tokenLog);
+                Long tokenCount = tokenLog.getTokenCount() != null ? tokenLog.getTokenCount() : 0L;
+                Long totalCount = (tokenCount * 8) + random.nextInt(21) - 10;
+
+                if (info != null) {
+                    // 只有当count值发生变化时才加入更新列表
+                    if (!totalCount.equals(info.getCount())) {
+                        FastgptEventLogTotal eventLogTotalNew = new FastgptEventLogTotal();
+                        eventLogTotalNew.setId(info.getId());
+                        eventLogTotalNew.setCount(totalCount);
+                        if (!processedKeys.contains(uniqueKey)) {
+                            toUpdateList.add(eventLogTotalNew);
+                            // 标记为已处理
+                            processedKeys.add(uniqueKey);
+                        }
+
+                    }
+                } else {
+                    FastgptEventLogTotal eventLogTotal = new FastgptEventLogTotal();
+                    eventLogTotal.setRoleId(tokenLog.getRoleId());
+                    eventLogTotal.setCount(totalCount);
+                    eventLogTotal.setType(11);
+                    eventLogTotal.setCompanyId(tokenLog.getCompanyId());
+                    eventLogTotal.setCompanyUserId(tokenLog.getCompanyUserId());
+                    eventLogTotal.setQwUserId(tokenLog.getQwUserId());
+                    eventLogTotal.setStatTime(dateTime);
+
+                    if (!processedKeys.contains(uniqueKey)) {
+                        toInsertList.add(eventLogTotal);
+                        // 标记为已处理
+                        processedKeys.add(uniqueKey);
+                    }
+                }
+            } catch (Exception e) {
+                log.error("统计AI消耗token触发情况异常,数据:" + tokenLog, e);
+            }
+        }
+
+        // 批量处理更新和插入操作
+        processBatchUpdates(toUpdateList);
+        processBatchInserts(toInsertList);
+    }
 }

+ 8 - 1
fs-service/src/main/java/com/fs/his/service/impl/FsStoreOrderServiceImpl.java

@@ -32,6 +32,7 @@ import com.fs.erp.dto.*;
 import com.fs.erp.dto.df.*;
 import com.fs.erp.service.IErpOrderService;
 import com.fs.event.*;
+import com.fs.gtPush.service.uniPush2Service;
 import com.fs.his.config.FsSysConfig;
 import com.fs.his.config.StoreConfig;
 import com.fs.his.domain.*;
@@ -855,7 +856,13 @@ public class FsStoreOrderServiceImpl implements IFsStoreOrderService {
         if (packageSubType == 3 && !CloudHostUtils.hasCloudHostName("金牛明医")) {
             inquiryOrderService.createOrderByPackageOrderStatus4(packageOrder);
         }
-        Long prescribeId = fsPrescribeService.insertFsPrescribeByPackageOrder(packageOrder);
+        // 九州,目前刷单不生成处方单
+        Long prescribeId = 0L;
+        if(CloudHostUtils.hasCloudHostName("九州在线")){
+            log.info("跳过生成处方单,处方单id:{}", prescribeId);
+        } else {
+            prescribeId = fsPrescribeService.insertFsPrescribeByPackageOrder(packageOrder);
+        }
 
         FsStoreOrder order = new FsStoreOrder();
         List<FsStoreOrderItem> items = new ArrayList<>();

+ 14 - 1
fs-service/src/main/java/com/fs/hisStore/domain/FsStoreProductPackageScrm.java

@@ -9,7 +9,7 @@ import com.fs.hisStore.dto.StoreOrderProductDTO;
 
 /**
  * 商品组合套餐对象 fs_store_product_package
- * 
+ *
  * @author fs
  * @date 2022-07-14
  */
@@ -66,6 +66,11 @@ public class FsStoreProductPackageScrm extends BaseEntity
 
     private Integer status;
 
+
+
+    //套餐赠送积分
+    private Integer integral;
+
     List<StoreOrderProductDTO> productList;
 
     public Integer getStatus() {
@@ -211,4 +216,12 @@ public class FsStoreProductPackageScrm extends BaseEntity
     public void setProducts(String products) {
         this.products = products;
     }
+
+    public Integer getIntegral() {
+        return integral;
+    }
+
+    public void setIntegral(Integer integral) {
+        this.integral = integral;
+    }
 }

+ 13 - 0
fs-service/src/main/java/com/fs/hisStore/domain/FsUserScrm.java

@@ -171,6 +171,10 @@ public class FsUserScrm extends BaseEntity
 
     private String courseMaOpenId; //看课小程序openid
 
+
+
+    private String historyApp; //app登录后不为null(表示是否下载app)
+
     private Long qwExtId;
 
     /**
@@ -839,4 +843,13 @@ public class FsUserScrm extends BaseEntity
     public void setOrderCount(Integer orderCount) {
         this.orderCount = orderCount;
     }
+
+
+    public String getHistoryApp() {
+        return historyApp;
+    }
+
+    public void setHistoryApp(String historyApp) {
+        this.historyApp = historyApp;
+    }
 }

+ 9 - 2
fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreAfterSalesScrmMapper.java

@@ -87,8 +87,15 @@ public interface FsStoreAfterSalesScrmMapper
     List<FsStoreAfterSalesQueryVO>  selectFsStoreAfterSalesListQuery(@Param("maps") FsStoreAfterSalesQueryParam storeAfterSalesParam);
 
     @Select({"<script> " +
-            "select s.*,o.delivery_status,o.delivery_id,u.phone as user_phone,c.company_name ,cu.nick_name as company_user_nick_name ,cu.phonenumber as company_usere_phonenumber  from fs_store_after_sales_scrm s INNER join fs_store_order_scrm o on o.order_code=s.order_code left join fs_user u on s.user_id=u.user_id left join company c on c.company_id=s.company_id left join company_user cu on cu.user_id=s.company_user_id " +
-            "where 1=1 " +
+            "select s.*,o.delivery_status,o.delivery_id,u.phone as user_phone,c.company_name ,cu.nick_name as company_user_nick_name ," +
+            "cu.phonenumber as company_usere_phonenumber,o.id as orderId,o.real_name as userName,o.item_json,o.user_address,o.pay_time as orderPayTime,o.pay_price,o.total_postage,fsps.bank_serial_no  " +
+            " from fs_store_after_sales_scrm s " +
+            " INNER join fs_store_order_scrm o on o.order_code=s.order_code " +
+            " left join fs_user u on s.user_id=u.user_id " +
+            " left join company c on c.company_id=s.company_id " +
+            " left join company_user cu on cu.user_id=s.company_user_id " +
+            " left join fs_store_payment_scrm fsps on fsps.business_order_id = s.id and fsps.status in (-1,1) " +
+            " where 1=1 " +
             "<if test = 'maps.status != null    '> " +
             "and s.status = #{maps.status} " +
             "</if>" +

+ 4 - 0
fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreOrderItemScrmMapper.java

@@ -274,4 +274,8 @@ public interface FsStoreOrderItemScrmMapper
 
     @Select("select product_id,json_info,num from fs_store_order_item_scrm where order_id=#{orderId}")
     List<FsStoreOrderItemListDVO> selectFsStoreOrderItemListDVOByOrderId(Long orderId);
+
+
+
+    List<FsStoreOrderItemVO> selectFsStoreOrderItemListByOrderIds(@Param("orderIds")List<Long> orderIds);
 }

+ 2 - 0
fs-service/src/main/java/com/fs/hisStore/mapper/FsStorePaymentScrmMapper.java

@@ -383,4 +383,6 @@ public interface FsStorePaymentScrmMapper
      * 批量更新发货状态
      * **/
     void batchUpadte(@Param("list") List<String> list);
+
+    void batchUpadteFailed(@Param("list") List<String> list);
 }

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

@@ -209,7 +209,7 @@ public interface FsStoreProductScrmMapper
             "        </foreach>\n" +
             "    </if>"+
             "<if test = 'maps.defaultOrder != null and maps.defaultOrder==\"desc\"  '> " +
-            "order by p.sort desc,product_id desc" +
+            "order by p.sort asc,product_id desc" +
             "</if>" +
             "<if test = 'maps.priceOrder != null and maps.priceOrder==\"desc\"   '> " +
             "order by p.price desc " +

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

@@ -3,6 +3,7 @@ package com.fs.hisStore.mapper;
 import com.fs.course.vo.newfs.FsCourseAnalysisCountVO;
 import com.fs.his.domain.FsUser;
 import com.fs.his.vo.OptionsVO;
+import com.fs.hisStore.vo.UserScrmVo;
 import com.fs.qw.param.QwFsUserParam;
 import com.fs.qw.vo.QwFsUserVO;
 import com.fs.hisStore.domain.FsUserScrm;
@@ -343,4 +344,6 @@ public interface FsUserScrmMapper
      */
     @Update("update fs_user set total_amount = IFNULL(total_amount, 0) + #{payMoney} where user_id = #{userId}")
     void incPayMoney(@Param("payMoney") BigDecimal payMoney, @Param("userId") Long userId);
+
+    List<UserScrmVo> selectUserVOList(@Param("phone") String phone);
 }

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

@@ -0,0 +1,24 @@
+package com.fs.hisStore.param;
+
+import com.fs.common.annotation.Excel;
+import lombok.Data;
+
+@Data
+public class FsUserCouponScrmSendParam {
+
+    /** 优惠劵id */
+    @Excel(name = "优惠劵id")
+    private Long id;
+
+    @Excel(name = "会员ID")
+    private Long userId;
+
+    // 发送人id
+    private Long setSendUserId;
+
+    //发送销售id
+    private Long companyUserId;
+
+    //发送销售公司id
+    private Long companyId;
+}

+ 11 - 8
fs-service/src/main/java/com/fs/hisStore/service/IFsStoreCouponIssueScrmService.java

@@ -6,19 +6,20 @@ import com.fs.common.core.domain.R;
 import com.fs.hisStore.domain.FsStoreCouponIssueScrm;
 import com.fs.hisStore.param.FsCouponIssueParam;
 import com.fs.hisStore.param.FsStoreCouponReceiveParam;
+import com.fs.hisStore.param.FsUserCouponScrmSendParam;
 import com.fs.hisStore.vo.FsStoreCouponIssueVO;
 
 /**
  * 优惠券领取Service接口
- * 
+ *
  * @author fs
  * @date 2022-03-15
  */
-public interface IFsStoreCouponIssueScrmService 
+public interface IFsStoreCouponIssueScrmService
 {
     /**
      * 查询优惠券领取
-     * 
+     *
      * @param id 优惠券领取ID
      * @return 优惠券领取
      */
@@ -26,7 +27,7 @@ public interface IFsStoreCouponIssueScrmService
 
     /**
      * 查询优惠券领取列表
-     * 
+     *
      * @param fsStoreCouponIssue 优惠券领取
      * @return 优惠券领取集合
      */
@@ -34,7 +35,7 @@ public interface IFsStoreCouponIssueScrmService
 
     /**
      * 新增优惠券领取
-     * 
+     *
      * @param fsStoreCouponIssue 优惠券领取
      * @return 结果
      */
@@ -42,7 +43,7 @@ public interface IFsStoreCouponIssueScrmService
 
     /**
      * 修改优惠券领取
-     * 
+     *
      * @param fsStoreCouponIssue 优惠券领取
      * @return 结果
      */
@@ -50,7 +51,7 @@ public interface IFsStoreCouponIssueScrmService
 
     /**
      * 批量删除优惠券领取
-     * 
+     *
      * @param ids 需要删除的优惠券领取ID
      * @return 结果
      */
@@ -58,7 +59,7 @@ public interface IFsStoreCouponIssueScrmService
 
     /**
      * 删除优惠券领取信息
-     * 
+     *
      * @param id 优惠券领取ID
      * @return 结果
      */
@@ -73,4 +74,6 @@ public interface IFsStoreCouponIssueScrmService
     FsStoreCouponIssueVO selectFsStoreCouponIssueVOById(Long id);
 
     List<FsStoreCouponIssueVO> selectFsStoreCouponIssueListVO(FsStoreCouponIssueScrm fsStoreCouponIssue);
+
+    R sendFsUserCoupon(FsUserCouponScrmSendParam param);
 }

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

@@ -11,6 +11,7 @@ import com.fs.his.domain.FsStoreProductAttrValue;
 import com.fs.his.domain.FsUser;
 import com.fs.his.vo.OptionsVO;
 import com.fs.hisStore.domain.FsStoreProductAttrValueScrm;
+import com.fs.hisStore.vo.UserScrmVo;
 import com.fs.live.domain.LiveOrder;
 import com.fs.qw.param.QwFsUserParam;
 import com.fs.qw.vo.QwFsUserVO;
@@ -294,4 +295,6 @@ public interface IFsUserScrmService
     void handleFsUserWx(FsUserScrm user, LoginMaWxParam param, WxMaJscode2SessionResult session);
 
     void incPayMoney(BigDecimal payMoney, Long userId);
+
+    List<UserScrmVo> selectUserVOList(String phone);
 }

+ 36 - 2
fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreAfterSalesScrmServiceImpl.java

@@ -6,6 +6,7 @@ import cn.hutool.core.util.IdUtil;
 import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.json.JSONUtil;
 import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONObject;
 import com.fs.common.annotation.DataScope;
 import com.fs.common.annotation.RepeatSubmit;
@@ -42,6 +43,7 @@ import com.fs.hisStore.param.*;
 import com.fs.huifuPay.domain.HuiFuRefundResult;
 import com.fs.huifuPay.sdk.opps.core.request.V2TradePaymentScanpayRefundRequest;
 import com.fs.huifuPay.service.HuiFuService;
+import com.fs.live.vo.LiveOrderItemListUVO;
 import com.fs.pay.service.IPayService;
 import com.fs.hisStore.config.StoreConfig;
 import com.fs.hisStore.domain.*;
@@ -84,6 +86,7 @@ import java.text.ParseException;
 import java.text.SimpleDateFormat;
 import java.time.LocalDateTime;
 import java.util.*;
+import java.util.stream.Collectors;
 
 /**
  * 售后记录Service业务层处理
@@ -551,9 +554,40 @@ public class FsStoreAfterSalesScrmServiceImpl implements IFsStoreAfterSalesScrmS
     }
 
     @Override
-    @DataScope(deptAlias = "cu",userAlias = "cu")
+    @DataScope(deptAlias = "cu", userAlias = "cu")
     public List<FsStoreAfterSalesVO> selectFsStoreAfterSalesListVO(FsStoreAfterSalesScrm fsStoreAfterSales) {
-        return fsStoreAfterSalesMapper.selectFsStoreAfterSalesListVO(fsStoreAfterSales);
+        List<FsStoreAfterSalesVO> fsStoreAfterSalesVOS = fsStoreAfterSalesMapper.selectFsStoreAfterSalesListVO(fsStoreAfterSales);
+        List<Long> orderIds = new ArrayList<>();
+        Map<Long, List<FsStoreOrderItemVO>> orderItemMap = new HashMap<>();
+        if(null != fsStoreAfterSalesVOS && !fsStoreAfterSalesVOS.isEmpty()){
+            orderIds = fsStoreAfterSalesVOS.stream().map(e -> e.getOrderId()).collect(Collectors.toList());
+            if(null != orderIds && !orderIds.isEmpty()){
+                List<FsStoreOrderItemVO> fsStoreOrderItemVOS = fsStoreOrderItemMapper.selectFsStoreOrderItemListByOrderIds(orderIds);
+                orderItemMap = fsStoreOrderItemVOS.stream()
+                        .collect(Collectors.groupingBy(FsStoreOrderItemVO::getOrderId));
+            }
+        }
+        boolean mapEmpty = orderItemMap.isEmpty();
+        if (null != fsStoreAfterSalesVOS && !fsStoreAfterSalesVOS.isEmpty()) {
+            for (FsStoreAfterSalesVO item : fsStoreAfterSalesVOS) {
+                if(!mapEmpty && orderItemMap.containsKey(item.getOrderId())){
+                    List<FsStoreOrderItemVO> orderItems = orderItemMap.get(item.getOrderId());
+                    for (FsStoreOrderItemVO orderItem : orderItems) {
+                        try {
+                            JSONObject jsO = JSONObject.parseObject(orderItem.getJsonInfo());
+                            item.setProductName(StringUtils.isNotBlank(item.getProductName()) ? item.getProductName() + "," + jsO.getString("productName") : jsO.getString("productName"));
+                            item.setProductBarCode(StringUtils.isNotBlank(item.getProductBarCode()) ? item.getProductBarCode() + "," + jsO.getString("barCode") : jsO.getString("barCode"));
+                            item.setSku(StringUtils.isNotBlank(item.getSku()) ? item.getSku() + "," + jsO.getString("sku") : jsO.getString("sku"));
+                            item.setNum(StringUtils.isNotBlank(item.getNum()) ? item.getNum() + "," + jsO.getString("num") : jsO.getString("num"));
+                            item.setPrice(StringUtils.isNotBlank(item.getPrice()) ? item.getPrice() + "," + jsO.getString("price") : jsO.getString("price"));
+                        } catch (Exception ex) {
+                            logger.error("售后订单商品信息转换异常",ex);
+                        }
+                    }
+                }
+            }
+        }
+        return fsStoreAfterSalesVOS;
     }
 
 //    @Override

+ 53 - 0
fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreCouponIssueScrmServiceImpl.java

@@ -6,11 +6,13 @@ import java.util.List;
 
 import com.fs.common.core.domain.R;
 import com.fs.common.core.redis.RedisCache;
+import com.fs.common.exception.CustomException;
 import com.fs.common.utils.DateUtils;
 import com.fs.hisStore.domain.*;
 import com.fs.hisStore.mapper.*;
 import com.fs.hisStore.param.FsCouponIssueParam;
 import com.fs.hisStore.param.FsStoreCouponReceiveParam;
+import com.fs.hisStore.param.FsUserCouponScrmSendParam;
 import com.fs.hisStore.vo.FsStoreCouponIssueVO;
 import lombok.Synchronized;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -181,5 +183,56 @@ public class FsStoreCouponIssueScrmServiceImpl implements IFsStoreCouponIssueScr
         return fsStoreCouponIssueMapper.selectFsStoreCouponIssueListVO(fsStoreCouponIssue);
     }
 
+    @Override
+    @Transactional
+    public R sendFsUserCoupon(FsUserCouponScrmSendParam param) {
+        Long userId = param.getUserId();
+        if (userId ==null){
+            throw new CustomException("用户不能为空");
+        }
+        FsStoreCouponIssueScrm couponIssue=fsStoreCouponIssueMapper.selectFsStoreCouponIssueById(param.getId());
+        if(couponIssue.getStatus().equals(0)){
+            return R.error("此优惠券已停止领取");
+        }
+        if(couponIssue.getLimitTime().getTime()<new Date().getTime()){
+            return R.error("此优惠券已过期");
+        }
+        //判断用户是否领取过
+        FsUserScrm user= userMapper.selectFsUserById(userId);
+        if(user.getLevel().equals(0)){
+            if(fsStoreCouponIssueUserMapper.checkReceive(userId.toString(),param.getId())>0){
+                return R.error("已领取");
+            }
+        }
+        if(couponIssue.getTotalCount()<=couponIssue.getRemainCount()){
+            return R.error("此优惠券已领完");
+        }
+        FsStoreCouponScrm coupon=couponMapper.selectFsStoreCouponById(couponIssue.getCouponId());
+        //写入领取表
+        FsStoreCouponIssueUserScrm couponIssueUser=new FsStoreCouponIssueUserScrm();
+        couponIssueUser.setUserId(userId);
+        couponIssueUser.setIssueId(param.getId());
+        couponIssueUser.setIsDel(0);
+        couponIssueUser.setCreateTime(new Date());
+        fsStoreCouponIssueUserMapper.insertFsStoreCouponIssueUser(couponIssueUser);
+        FsStoreCouponUserScrm couponUser=new FsStoreCouponUserScrm();
+        couponUser.setCouponId(couponIssue.getCouponId());
+        couponUser.setUserId(userId);
+        couponUser.setCouponTitle(couponIssue.getCouponName());
+        couponUser.setCouponPrice(coupon.getCouponPrice());
+        couponUser.setUseMinPrice(coupon.getUseMinPrice());
+        Calendar calendar = Calendar.getInstance();
+        calendar.setTime(new Date());
+        calendar.add(Calendar.DATE,coupon.getCouponTime().intValue());
+        couponUser.setLimitTime(calendar.getTime());
+        couponUser.setCreateTime(new Date());
+        couponUser.setType("send");
+        couponUser.setStatus(0);
+        fsStoreCouponUserMapper.insertFsStoreCouponUser(couponUser);
+        //更新领取数量
+        fsStoreCouponIssueMapper.updateFsStoreCouponIssueCount(param.getId());
+        return R.ok("领取成功");
+    }
+
 
 }

+ 59 - 0
fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreOrderScrmServiceImpl.java

@@ -21,6 +21,7 @@ import com.fs.api.vo.ProductListVO;
 import com.fs.common.config.FSSysConfig;
 import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.core.domain.R;
+import com.fs.common.core.domain.entity.SysDictData;
 import com.fs.common.core.redis.RedisCache;
 import com.fs.common.event.TemplateBean;
 import com.fs.common.event.TemplateEvent;
@@ -62,9 +63,12 @@ import com.fs.his.dto.FsStoreOrderAmountScrmStatsQueryDto;
 import com.fs.his.dto.FsStoreOrderItemDTO;
 import com.fs.his.enums.FsStoreOrderLogEnum;
 import com.fs.his.enums.FsStoreOrderStatusEnum;
+import com.fs.his.enums.FsUserIntegralLogTypeEnum;
 import com.fs.his.mapper.*;
 import com.fs.his.param.FsStoreOrderSalesParam;
+import com.fs.his.param.FsUserAddIntegralTemplateParam;
 import com.fs.his.service.IFsPrescribeService;
+import com.fs.his.service.IFsUserIntegralLogsService;
 import com.fs.his.service.IFsUserWatchService;
 import com.fs.his.utils.ConfigUtil;
 import com.fs.his.vo.FsInquiryOrderVO;
@@ -104,6 +108,7 @@ import com.fs.hisStore.domain.*;
 import com.fs.hisStore.enums.*;
 import com.fs.hisStore.service.*;
 import com.fs.system.service.ISysConfigService;
+import com.fs.system.service.ISysDictTypeService;
 import com.fs.wx.miniapp.config.WxMaProperties;
 import com.fs.wx.order.domain.FsWxExpressTask;
 import com.fs.wx.order.dto.*;
@@ -379,6 +384,13 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
     @Autowired
     private IFsStoreCartScrmService fsStoreCartScrmService;
 
+    @Autowired
+    private ISysDictTypeService sysDictTypeService;
+    @Autowired
+    private IFsUserIntegralLogsService userIntegralLogsService;
+    @Autowired
+    private FsStoreProductPackageScrmMapper fsStoreProductPackageMapper;
+
     @PostConstruct
     public void initErpServiceMap() {
         erpServiceMap = new HashMap<>();
@@ -1820,6 +1832,13 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
                     companyService.addCompanyMoney(order);
                 }
             }
+            //套餐包赠送积分
+            try {
+                sendIntegral(order);
+            } catch (Exception e) {
+                log.error("赠送积分错误:{},订单号:{}", e.getMessage(), order.getOrderCode());
+            }
+
 
             FsErpConfig erpConfig = configUtil.getErpConfig();
             Integer erpType = erpConfig.getErpType();
@@ -1859,6 +1878,43 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
         }
     }
 
+    /**
+     * 套餐包赠送积分
+     * @param order
+     */
+    private void sendIntegral(FsStoreOrderScrm order) {
+        //如果是套餐包赠送积分
+        Integer orderCreateType = order.getOrderCreateType();
+        Long packageId = order.getPackageId();
+        if (orderCreateType != null && order.getUserId() != null && packageId !=null) {
+            List<SysDictData> sysDictData = sysDictTypeService.selectDictDataByType("store_order_create_type");
+            if (sysDictData != null && !sysDictData.isEmpty()){
+                String dictValue = "";
+                for (SysDictData dictData : sysDictData) {
+                    String dictLabel = dictData.getDictLabel();
+                    if (StringUtils.isNotBlank(dictLabel) && "套餐".equals(dictLabel)){
+                        dictValue = dictData.getDictValue();
+                        break;
+                    }
+                    if (StringUtils.isNotBlank(dictValue)){
+                        //查询套餐赠送积分
+                        FsStoreProductPackageScrm fsStoreProductPackage = fsStoreProductPackageMapper.selectFsStoreProductPackageById(packageId);
+                        if (fsStoreProductPackage != null && fsStoreProductPackage.getIntegral() != null && fsStoreProductPackage.getIntegral()>0){
+                            //赠送积分
+                            FsUserAddIntegralTemplateParam integralTemplateParam = new FsUserAddIntegralTemplateParam();
+                            integralTemplateParam.setUserId(order.getUserId());
+                            integralTemplateParam.setLogType(FsUserIntegralLogTypeEnum.TYPE_2.getValue()); //消费获取积分
+                            integralTemplateParam.setBusinessId(order.getId().toString());
+                            integralTemplateParam.setPoints(fsStoreProductPackage.getIntegral());
+                            userIntegralLogsService.addIntegralTemplate(integralTemplateParam);
+                        }
+
+                    }
+                }
+            }
+        }
+    }
+
     @Override
     public BigDecimal selectFsStoreOrderByPayPriceCount(FsStoreOrderStatisticsParam param) {
         return fsStoreOrderMapper.selectFsStoreOrderByPayPriceCount(param);
@@ -2007,6 +2063,7 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
 
 
     @Override
+    @Transactional
     public R createOmsOrder(Long orderId) throws ParseException {
         FsStoreOrderScrm order = fsStoreOrderMapper.selectFsStoreOrderById(orderId);
         FsSysConfig erpConfig = configUtil.generateStructConfigByKey(SysConfigEnum.HIS_CONFIG.getKey(), FsSysConfig.class);
@@ -2452,6 +2509,8 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
                 }
             }
         }
+        //退积分 todo
+
         orderStatusService.create(order.getId(), OrderLogEnum.REFUND_ORDER_SUCCESS.getValue(),
                 OrderLogEnum.REFUND_ORDER_SUCCESS.getDesc());
         if (order.getTuiUserId() != null && order.getTuiUserId() > 0) {

+ 5 - 3
fs-service/src/main/java/com/fs/hisStore/service/impl/FsStorePaymentScrmServiceImpl.java

@@ -979,6 +979,7 @@ public class FsStorePaymentScrmServiceImpl implements IFsStorePaymentScrmService
                 Map<String, List<FsStorePaymentUsetVo>> paymentUsetVoMap = paymentList.stream().collect(Collectors.groupingBy(FsStorePaymentUsetVo::getAppId));
                 for (Map.Entry<String, List<FsStorePaymentUsetVo>> entry : paymentUsetVoMap.entrySet()) {
                     List<String> successList = new ArrayList<>();
+                    List<String> failedList = new ArrayList<>();
                     String appId = entry.getKey();
                     List<FsStorePaymentUsetVo> userPayments = entry.getValue();
                     final WxMaService wxService = WxMaConfiguration.getMaService(appId);
@@ -988,18 +989,19 @@ public class FsStorePaymentScrmServiceImpl implements IFsStorePaymentScrmService
                             if (uploadShippingInfoToWechat(wxService, v, uploadTime)) {
                                 successList.add(v.getBankTransactionId());
                             }else {
-                                successList.add(v.getBankTransactionId());
+                                failedList.add(v.getBankTransactionId());
                             }
                         }
                         //批量更新数据
                         if (!successList.isEmpty()) {
                             fsStorePaymentMapper.batchUpadte(successList);
                         }
+                        if(!failedList.isEmpty()){
+                            fsStorePaymentMapper.batchUpadteFailed(failedList);
+                        }
                     }
                 }
             }
-
-
             return R.ok(builder.toString().equals("") ? "操作成功!" : builder.toString());
         } catch (Exception e) {
             log.error("导入发货单快递信息失败", e);

+ 37 - 1
fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreProductAttrScrmServiceImpl.java

@@ -3,9 +3,12 @@ package com.fs.hisStore.service.impl;
 import java.util.List;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
+import com.fs.common.constant.LiveKeysConstant;
+import com.fs.common.core.redis.RedisCache;
 import com.fs.hisStore.mapper.FsStoreProductAttrScrmMapper;
 import com.fs.hisStore.domain.FsStoreProductAttrScrm;
 import com.fs.hisStore.service.IFsStoreProductAttrScrmService;
+import lombok.extern.slf4j.Slf4j;
 
 /**
  * 商品属性Service业务层处理
@@ -14,11 +17,27 @@ import com.fs.hisStore.service.IFsStoreProductAttrScrmService;
  * @date 2022-03-15
  */
 @Service
+@Slf4j
 public class FsStoreProductAttrScrmServiceImpl implements IFsStoreProductAttrScrmService
 {
     @Autowired
     private FsStoreProductAttrScrmMapper fsStoreProductAttrMapper;
 
+    @Autowired
+    private RedisCache redisCache;
+
+    /**
+     * 清除商品详情缓存
+     * @param productId 商品ID
+     */
+    private void clearProductDetailCache(Long productId) {
+        if (productId != null) {
+            String cacheKey = String.format(LiveKeysConstant.PRODUCT_DETAIL_CACHE, productId);
+            redisCache.deleteObject(cacheKey);
+            log.debug("清除商品详情缓存(属性): productId={}", productId);
+        }
+    }
+
     /**
      * 查询商品属性
      * 
@@ -64,7 +83,10 @@ public class FsStoreProductAttrScrmServiceImpl implements IFsStoreProductAttrScr
     @Override
     public int updateFsStoreProductAttr(FsStoreProductAttrScrm fsStoreProductAttr)
     {
-        return fsStoreProductAttrMapper.updateFsStoreProductAttr(fsStoreProductAttr);
+        int result = fsStoreProductAttrMapper.updateFsStoreProductAttr(fsStoreProductAttr);
+        // 清除对应商品的缓存
+        clearProductDetailCache(fsStoreProductAttr.getProductId());
+        return result;
     }
 
     /**
@@ -76,6 +98,15 @@ public class FsStoreProductAttrScrmServiceImpl implements IFsStoreProductAttrScr
     @Override
     public int deleteFsStoreProductAttrByIds(Long[] ids)
     {
+        // 先查询出对应的商品ID,然后清除缓存
+        if (ids != null && ids.length > 0) {
+            for (Long id : ids) {
+                FsStoreProductAttrScrm attr = fsStoreProductAttrMapper.selectFsStoreProductAttrById(id);
+                if (attr != null) {
+                    clearProductDetailCache(attr.getProductId());
+                }
+            }
+        }
         return fsStoreProductAttrMapper.deleteFsStoreProductAttrByIds(ids);
     }
 
@@ -88,6 +119,11 @@ public class FsStoreProductAttrScrmServiceImpl implements IFsStoreProductAttrScr
     @Override
     public int deleteFsStoreProductAttrById(Long id)
     {
+        // 先查询出对应的商品ID,然后清除缓存
+        FsStoreProductAttrScrm attr = fsStoreProductAttrMapper.selectFsStoreProductAttrById(id);
+        if (attr != null) {
+            clearProductDetailCache(attr.getProductId());
+        }
         return fsStoreProductAttrMapper.deleteFsStoreProductAttrById(id);
     }
 

+ 37 - 1
fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreProductAttrValueScrmServiceImpl.java

@@ -3,6 +3,8 @@ package com.fs.hisStore.service.impl;
 import java.util.Collections;
 import java.util.List;
 
+import com.fs.common.constant.LiveKeysConstant;
+import com.fs.common.core.redis.RedisCache;
 import com.fs.hisStore.config.MedicalMallConfig;
 import com.fs.hisStore.param.FsProductAttrValueParam;
 import com.fs.hisStore.param.FsStoreProductAttrValueQueryParam;
@@ -10,6 +12,7 @@ import com.fs.hisStore.param.FsStoreTuiProductAttrValueParam;
 import com.fs.hisStore.vo.FsStoreProductAttrValueQueryVO;
 import com.fs.hisStore.vo.FsStoreProductAttrValueVO;
 import com.fs.hisStore.vo.FsStoreTuiProductAttrValueVO;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import com.fs.hisStore.mapper.FsStoreProductAttrValueScrmMapper;
@@ -23,6 +26,7 @@ import com.fs.hisStore.service.IFsStoreProductAttrValueScrmService;
  * @date 2022-03-15
  */
 @Service
+@Slf4j
 public class FsStoreProductAttrValueScrmServiceImpl implements IFsStoreProductAttrValueScrmService
 {
     @Autowired
@@ -31,6 +35,21 @@ public class FsStoreProductAttrValueScrmServiceImpl implements IFsStoreProductAt
     @Autowired
     private MedicalMallConfig config;
 
+    @Autowired
+    private RedisCache redisCache;
+
+    /**
+     * 清除商品详情缓存
+     * @param productId 商品ID
+     */
+    private void clearProductDetailCache(Long productId) {
+        if (productId != null) {
+            String cacheKey = String.format(LiveKeysConstant.PRODUCT_DETAIL_CACHE, productId);
+            redisCache.deleteObject(cacheKey);
+            log.debug("清除商品详情缓存(属性值): productId={}", productId);
+        }
+    }
+
     /**
      * 查询商品属性值
      *
@@ -76,7 +95,10 @@ public class FsStoreProductAttrValueScrmServiceImpl implements IFsStoreProductAt
     @Override
     public int updateFsStoreProductAttrValue(FsStoreProductAttrValueScrm fsStoreProductAttrValue)
     {
-        return fsStoreProductAttrValueMapper.updateFsStoreProductAttrValue(fsStoreProductAttrValue);
+        int result = fsStoreProductAttrValueMapper.updateFsStoreProductAttrValue(fsStoreProductAttrValue);
+        // 清除对应商品的缓存
+        clearProductDetailCache(fsStoreProductAttrValue.getProductId());
+        return result;
     }
 
     /**
@@ -88,6 +110,15 @@ public class FsStoreProductAttrValueScrmServiceImpl implements IFsStoreProductAt
     @Override
     public int deleteFsStoreProductAttrValueByIds(Long[] ids)
     {
+        // 先查询出对应的商品ID,然后清除缓存
+        if (ids != null && ids.length > 0) {
+            for (Long id : ids) {
+                FsStoreProductAttrValueScrm attrValue = fsStoreProductAttrValueMapper.selectFsStoreProductAttrValueById(id);
+                if (attrValue != null) {
+                    clearProductDetailCache(attrValue.getProductId());
+                }
+            }
+        }
         return fsStoreProductAttrValueMapper.deleteFsStoreProductAttrValueByIds(ids);
     }
 
@@ -100,6 +131,11 @@ public class FsStoreProductAttrValueScrmServiceImpl implements IFsStoreProductAt
     @Override
     public int deleteFsStoreProductAttrValueById(Long id)
     {
+        // 先查询出对应的商品ID,然后清除缓存
+        FsStoreProductAttrValueScrm attrValue = fsStoreProductAttrValueMapper.selectFsStoreProductAttrValueById(id);
+        if (attrValue != null) {
+            clearProductDetailCache(attrValue.getProductId());
+        }
         return fsStoreProductAttrValueMapper.deleteFsStoreProductAttrValueById(id);
     }
 

+ 67 - 36
fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreProductScrmServiceImpl.java

@@ -12,7 +12,9 @@ import cn.hutool.core.util.StrUtil;
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONObject;
 import com.fs.common.BeanCopyUtils;
+import com.fs.common.constant.LiveKeysConstant;
 import com.fs.common.core.domain.R;
+import com.fs.common.core.redis.RedisCache;
 import com.fs.common.exception.CustomException;
 import com.fs.common.exception.ServiceException;
 import com.fs.common.utils.DateUtils;
@@ -135,6 +137,33 @@ public class FsStoreProductScrmServiceImpl implements IFsStoreProductScrmService
     @Autowired
     private LiveLotteryProductConfMapper liveLotteryProductConfMapper;
 
+    @Autowired
+    private RedisCache redisCache;
+
+    /**
+     * 清除商品详情缓存
+     * @param productId 商品ID
+     */
+    private void clearProductDetailCache(Long productId) {
+        if (productId != null) {
+            String cacheKey = String.format(LiveKeysConstant.PRODUCT_DETAIL_CACHE, productId);
+            redisCache.deleteObject(cacheKey);
+            log.debug("清除商品详情缓存: productId={}", productId);
+        }
+    }
+
+    /**
+     * 批量清除商品详情缓存
+     * @param productIds 商品ID数组
+     */
+    private void clearProductDetailCache(Long[] productIds) {
+        if (productIds != null && productIds.length > 0) {
+            for (Long productId : productIds) {
+                clearProductDetailCache(productId);
+            }
+        }
+    }
+
     /**
      * 查询商品
      *
@@ -189,7 +218,10 @@ public class FsStoreProductScrmServiceImpl implements IFsStoreProductScrmService
         if(1 == fsStoreProduct.getIsShow() && "1".equals(fsStoreProduct.getIsAudit())){
             fsStoreProduct.setIsAudit("0");
         }
-        return fsStoreProductMapper.updateFsStoreProduct(fsStoreProduct);
+        int result = fsStoreProductMapper.updateFsStoreProduct(fsStoreProduct);
+        // 清除缓存
+        clearProductDetailCache(fsStoreProduct.getProductId());
+        return result;
     }
 
     /**
@@ -202,19 +234,17 @@ public class FsStoreProductScrmServiceImpl implements IFsStoreProductScrmService
     public int deleteFsStoreProductByIds(Long[] productIds)
     {
         storeAuditLogUtil.addBatchAuditArray(productIds, "", "");
-
+        log.info("批量删除商品:{}", productIds);
         int result = fsStoreProductMapper.deleteFsStoreProductByIds(productIds);
-
+        
+        // 清除缓存
+        clearProductDetailCache(productIds);
+        
         // 异步处理商品删除联动逻辑
         if (result > 0) {
-            try {
-                log.info("批量删除商品:{}", productIds);
-                handleProductDeleteAsync(productIds);
-            } catch (Exception e) {
-                log.error("商品删除异步处理失败:{}", e.getMessage());
-            }
+            handleProductDeleteAsync(productIds);
         }
-
+        
         return result;
     }
 
@@ -228,7 +258,7 @@ public class FsStoreProductScrmServiceImpl implements IFsStoreProductScrmService
     public void handleProductDeleteAsync(Long[] productIds) {
         try {
             log.info("开始异步处理商品删除联动,商品IDs: {}", Arrays.toString(productIds));
-
+            
             // 查询所有未直播(1)、直播中(2)和直播回放(4)的直播间
             // 使用 LiveMapper 查询状态为1,2,4的直播间(包括所有类型)
             List<Live> allLiveList = liveMapper.liveListAll();
@@ -253,19 +283,19 @@ public class FsStoreProductScrmServiceImpl implements IFsStoreProductScrmService
                     }
                 }
             }
-
+            
             if (targetLiveList.isEmpty()) {
                 log.info("没有找到需要处理的直播间");
                 return;
             }
-
+            
             log.info("找到 {} 个需要处理的直播间", targetLiveList.size());
-
+            
             // 遍历每个被删除的商品
             for (Long productId : productIds) {
                 processProductDeleteForLives(productId, targetLiveList);
             }
-
+            
             log.info("商品删除联动处理完成");
         } catch (Exception e) {
             log.error("异步处理商品删除联动失败", e);
@@ -285,19 +315,19 @@ public class FsStoreProductScrmServiceImpl implements IFsStoreProductScrmService
                 if (liveId == null) {
                     continue;
                 }
-
+                
                 // 1. 删除直播商品
                 deleteLiveGoodsByProductId(productId, liveId);
-
+                
                 // 2. 删除直播定时任务(直播上下架、直播卡片)
                 deleteLiveAutoTasksByProductId(productId, liveId);
-
+                
                 // 3. 删除直播抽奖(产品关联被删除的商品)
                 deleteLiveLotteryProductConfByProductId(productId, liveId);
-
+                
                 // 4. 删除直播定时任务(抽奖,里面关联了这个商品的)
                 deleteLiveAutoTasksByLotteryProductId(productId, liveId);
-
+                
             } catch (Exception e) {
                 log.error("处理直播间 {} 的商品 {} 删除联动失败", live.getLiveId(), productId, e);
             }
@@ -316,7 +346,7 @@ public class FsStoreProductScrmServiceImpl implements IFsStoreProductScrmService
             queryGoods.setProductId(productId);
             queryGoods.setLiveId(liveId);
             List<LiveGoods> goodsList = liveGoodsService.selectLiveGoodsList(queryGoods);
-
+            
             if (!goodsList.isEmpty()) {
                 Long[] goodsIds = goodsList.stream()
                         .map(LiveGoods::getGoodsId)
@@ -340,9 +370,9 @@ public class FsStoreProductScrmServiceImpl implements IFsStoreProductScrmService
             LiveAutoTask queryTask = new LiveAutoTask();
             queryTask.setLiveId(liveId);
             List<LiveAutoTask> taskList = liveAutoTaskService.selectLiveAutoTaskList(queryTask);
-
+            
             List<Long> taskIdsToDelete = new ArrayList<>();
-
+            
             for (LiveAutoTask task : taskList) {
                 // 任务类型:1-定时推送卡片商品 6-自动上下架
                 if (task.getTaskType() != null && (task.getTaskType() == 1L || task.getTaskType() == 6L)) {
@@ -366,7 +396,7 @@ public class FsStoreProductScrmServiceImpl implements IFsStoreProductScrmService
                     }
                 }
             }
-
+            
             if (!taskIdsToDelete.isEmpty()) {
                 Long[] ids = taskIdsToDelete.toArray(new Long[0]);
                 liveAutoTaskService.deleteLiveAutoTaskByIds(ids);
@@ -389,7 +419,7 @@ public class FsStoreProductScrmServiceImpl implements IFsStoreProductScrmService
             queryConf.setProductId(productId);
             queryConf.setLiveId(liveId);
             List<LiveLotteryProductConf> confList = liveLotteryProductConfMapper.selectLiveLotteryProductConfList(queryConf);
-
+            
             if (!confList.isEmpty()) {
                 Long[] ids = confList.stream()
                         .map(LiveLotteryProductConf::getId)
@@ -415,23 +445,23 @@ public class FsStoreProductScrmServiceImpl implements IFsStoreProductScrmService
             queryConf.setProductId(productId);
             queryConf.setLiveId(liveId);
             List<LiveLotteryProductConf> confList = liveLotteryProductConfMapper.selectLiveLotteryProductConfList(queryConf);
-
+            
             if (confList.isEmpty()) {
                 return;
             }
-
+            
             // 获取所有相关的抽奖ID
             Set<Long> lotteryIds = confList.stream()
                     .map(LiveLotteryProductConf::getLotteryId)
                     .collect(Collectors.toSet());
-
+            
             // 查询该直播间的所有定时任务
             LiveAutoTask queryTask = new LiveAutoTask();
             queryTask.setLiveId(liveId);
             List<LiveAutoTask> taskList = liveAutoTaskService.selectLiveAutoTaskList(queryTask);
-
+            
             List<Long> taskIdsToDelete = new ArrayList<>();
-
+            
             for (LiveAutoTask task : taskList) {
                 // 任务类型:4-抽奖
                 if (task.getTaskType() != null && task.getTaskType() == 4L) {
@@ -445,7 +475,7 @@ public class FsStoreProductScrmServiceImpl implements IFsStoreProductScrmService
                     }
                 }
             }
-
+            
             if (!taskIdsToDelete.isEmpty()) {
                 Long[] ids = taskIdsToDelete.toArray(new Long[0]);
                 liveAutoTaskService.deleteLiveAutoTaskByIds(ids);
@@ -465,7 +495,10 @@ public class FsStoreProductScrmServiceImpl implements IFsStoreProductScrmService
     @Override
     public int deleteFsStoreProductById(Long productId)
     {
-        return fsStoreProductMapper.deleteFsStoreProductById(productId);
+        int result = fsStoreProductMapper.deleteFsStoreProductById(productId);
+        // 清除缓存
+        clearProductDetailCache(productId);
+        return result;
     }
 
     @Override
@@ -672,6 +705,8 @@ public class FsStoreProductScrmServiceImpl implements IFsStoreProductScrmService
                 product.setIsAudit("1");
             }
             fsStoreProductMapper.updateFsStoreProduct(product);
+            // 清除缓存
+            clearProductDetailCache(product.getProductId());
             if (param.getSpecType().equals(0)) {
                 ProductArrtDTO fromatDetailDto = ProductArrtDTO.builder()
                         .value("规格")
@@ -1404,10 +1439,6 @@ public class FsStoreProductScrmServiceImpl implements IFsStoreProductScrmService
                         }
                     }
                 }
-                //重命名
-                product.setProductName(product.getProductName() + " - 副本");
-                product.setCreateTime(new Date());
-                product.setUpdateTime(new Date());
 
                 fsStoreProductMapper.insertFsStoreProduct(product);
                 Long fsStoreProductId = product.getProductId();

+ 6 - 4
fs-service/src/main/java/com/fs/hisStore/service/impl/FsUserScrmServiceImpl.java

@@ -37,6 +37,7 @@ import com.fs.his.domain.FsUserBill;
 import com.fs.his.domain.FsUserWx;
 import com.fs.his.service.IFsUserWxService;
 import com.fs.his.vo.OptionsVO;
+import com.fs.hisStore.vo.*;
 import com.fs.live.domain.LiveOrder;
 import com.fs.qw.param.QwFsUserParam;
 import com.fs.qw.vo.QwFsUserVO;
@@ -57,10 +58,6 @@ import com.fs.hisStore.service.IFsUserCompanyUserScrmService;
 import com.fs.hisStore.service.IFsUserProjectTagScrmService;
 import com.fs.hisStore.service.IFsUserScrmService;
 import com.fs.hisStore.service.cache.IFsUserCourseCountCacheService;
-import com.fs.hisStore.vo.FSUserVO;
-import com.fs.hisStore.vo.FsCompanyUserListQueryVO;
-import com.fs.hisStore.vo.FsUserLastCount;
-import com.fs.hisStore.vo.FsUserTuiVO;
 import com.fs.hisStore.vo.h5.*;
 import com.fs.system.mapper.SysDictDataMapper;
 import com.github.pagehelper.PageHelper;
@@ -1162,4 +1159,9 @@ public class FsUserScrmServiceImpl implements IFsUserScrmService
     public void incPayMoney(BigDecimal payMoney, Long userId) {
         fsUserMapper.incPayMoney(payMoney, userId);
     }
+
+    @Override
+    public List<UserScrmVo> selectUserVOList(String phone) {
+        return fsUserMapper.selectUserVOList(phone);
+    }
 }

+ 28 - 1
fs-service/src/main/java/com/fs/hisStore/vo/FsStoreAfterSalesVO.java

@@ -100,7 +100,34 @@ public class FsStoreAfterSalesVO implements Serializable
     @Excel(name = "提交时间",width =30, dateFormat = "yyyy-MM-dd HH:mm:ss")
     private Date createTime;
 
-
+    private String itemJson;
+
+    @Excel(name = "用户名称")
+    private String userName;
+    @Excel(name ="产品名称")
+    private String productName;
+    @Excel(name ="产品编码")
+    private String productBarCode;
+    @Excel(name ="规格")
+    private String sku;
+    @Excel(name ="产品数量")
+    private String num;
+    @Excel(name ="产品价格")
+    private String price;
+    @Excel(name="额外运费")
+    private BigDecimal totalPostage;
+    @Excel(name="实付金额")
+    private BigDecimal payPrice;
+    //    @Excel(name="收货人姓名")
+//    private String realName;
+    @Excel(name ="详细地址")
+    private  String userAddress;
+    @Excel(name ="支付时间" ,dateFormat = "yyyy-MM-dd HH:mm:ss")
+    private Date orderPayTime;
+    @Excel(name ="银行交易流水号")
+    private String bankSerialNo;
+
+    private Long orderId;
 
 
 }

+ 3 - 0
fs-service/src/main/java/com/fs/hisStore/vo/FsStoreProductPacketVO.java

@@ -71,6 +71,9 @@ public class FsStoreProductPacketVO implements Serializable
     @Excel(name = "状态", dictType = "common_status")
     private Integer status;
 
+    @Excel(name = "赠送积分")
+    private Integer integral;
+
     List<StoreOrderProductDTO> productList;
 
 }

+ 11 - 0
fs-service/src/main/java/com/fs/hisStore/vo/UserScrmVo.java

@@ -0,0 +1,11 @@
+package com.fs.hisStore.vo;
+
+import lombok.Data;
+
+@Data
+public class UserScrmVo {
+    Long id;
+    String name;
+    String position;
+    String certificateCode;
+}

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

@@ -349,5 +349,7 @@ public class LiveOrder extends BaseEntity {
     private Integer cost;
     @TableField(exist = false)
     private String bankTransactionId;
+    @TableField(exist = false)
+    private Long attrValueId;
 
 }

+ 4 - 0
fs-service/src/main/java/com/fs/live/domain/LiveWatchUser.java

@@ -62,4 +62,8 @@ public class LiveWatchUser extends BaseEntity {
     @Excel(name = "用户所在位置")
     private String location;
 
+
+    private Integer pageNum;
+    private Integer pageSize;
+
 }

+ 2 - 2
fs-service/src/main/java/com/fs/live/mapper/LiveAfterSalesMapper.java

@@ -119,9 +119,9 @@ public interface LiveAfterSalesMapper {
             "and sales_status = 3 " +
             "</if>" +
             "<if test = 'maps.userId != null    '> " +
-            "and user_id = #{maps.userId} " +
+            "and las.user_id = #{maps.userId} " +
             "</if>" +
-            "order by create_time desc "+
+            "order by las.create_time desc "+
             "</script>"})
     List<LiveAfterSalesQueryVO> selectLiveAfterSalesListQuery(@Param("maps") LiveAfterSalesQueryParam param);
 

+ 3 - 0
fs-service/src/main/java/com/fs/live/mapper/LiveOrderItemMapper.java

@@ -166,4 +166,7 @@ public interface LiveOrderItemMapper {
             " order by o.id desc limit 50000"+
             "</script>"})
     List<LiveOrderItemExportVO> selectFsStoreOrderItemListExportVO(@Param("maps") LiveOrderParam fsStoreOrder);
+
+
+    List<LiveOrderItemListUVO> selectLiveOrderItemListUVOByOrderIds(@Param("orderIds")List<Long> orderIds);
 }

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

@@ -459,4 +459,16 @@ public interface LiveOrderMapper {
 
 
     void batchUpdateTime(@Param("list") List<LiveOrder> list);
+
+    @Update({
+            "<script>",
+            "UPDATE live_order ",
+            "SET update_time = NOW() ",
+            "WHERE id IN ",
+            "<foreach collection='ids' item='id' open='(' separator=',' close=')'>",
+            "#{id}",
+            "</foreach>",
+            "</script>"
+    })
+    void batchUpdateTimeIds(@Param("ids") List<Long> ids);
 }

+ 1 - 0
fs-service/src/main/java/com/fs/live/param/LiveOrderComputedParam.java

@@ -17,6 +17,7 @@ public class LiveOrderComputedParam implements Serializable
     private Long productId;
     private String totalNum;
     private Long cityId;
+    private Long attrValueId;
 
 
     @ApiModelProperty(value = "使用积分 1使用")

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

@@ -261,4 +261,6 @@ public interface ILiveOrderService {
     List<LiveOrder> selectBankOrder();
 
     void batchUpdateTime(List<LiveOrder> list);
+
+    void batchUpdateTimeIds(List<Long> ids);
 }

+ 4 - 3
fs-service/src/main/java/com/fs/live/service/ILiveWatchUserService.java

@@ -2,6 +2,7 @@ package com.fs.live.service;
 
 
 import com.fs.common.core.domain.R;
+import com.fs.hisStore.domain.FsUserScrm;
 import com.fs.live.domain.LiveWatchUser;
 import com.fs.live.vo.LiveWatchUserVO;
 
@@ -77,9 +78,9 @@ public interface ILiveWatchUserService {
 
     Map<String, Integer> getLiveFlagWithCache(Long liveId);
 
-    LiveWatchUser join(long liveId, long userId, String location);
-    LiveWatchUser joinWithoutLocation(long liveId, long userId);
-    LiveWatchUser close(long liveId, long userId);
+    LiveWatchUser join(FsUserScrm fsUser,long liveId, long userId, String location);
+    LiveWatchUser joinWithoutLocation(FsUserScrm fsUser,long liveId, long userId);
+    LiveWatchUser close(FsUserScrm fsUser,long liveId, long userId);
 
     /**
      * 查询直播间在线用户列表

+ 99 - 69
fs-service/src/main/java/com/fs/live/service/impl/LiveAfterSalesServiceImpl.java

@@ -5,6 +5,7 @@ import java.sql.Timestamp;
 import java.text.SimpleDateFormat;
 import java.time.LocalDateTime;
 import java.util.*;
+import java.util.stream.Collectors;
 
 import cn.hutool.core.bean.BeanUtil;
 import cn.hutool.core.date.DateTime;
@@ -12,6 +13,7 @@ import cn.hutool.core.util.IdUtil;
 import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.json.JSONUtil;
 import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONObject;
 import com.fs.common.core.domain.R;
 import com.fs.common.exception.CustomException;
@@ -56,10 +58,7 @@ import com.fs.live.param.*;
 import com.fs.live.service.ILiveOrderItemService;
 import com.fs.live.service.ILiveOrderLogsService;
 import com.fs.live.service.ILiveOrderService;
-import com.fs.live.vo.LiveAfterSalesListUVO;
-import com.fs.live.vo.LiveAfterSalesQueryVO;
-import com.fs.live.vo.LiveAfterSalesVo;
-import com.fs.live.vo.LiveOrderItemVO1;
+import com.fs.live.vo.*;
 import com.fs.store.config.StoreConfig;
 import com.fs.store.domain.*;
 import com.fs.store.service.cache.IFsUserCacheService;
@@ -201,6 +200,17 @@ public class LiveAfterSalesServiceImpl implements ILiveAfterSalesService {
     public List<LiveAfterSalesVo> selectLiveAfterSalesVoList(LiveAfterSalesVo liveAfterSales)
     {
         List<LiveAfterSalesVo> liveAfterSalesVos = baseMapper.selectLiveAfterSalesVoList(liveAfterSales);
+        List<Long> orderIds = new ArrayList<>();
+        Map<Long, List<LiveOrderItemListUVO>> orderItemMap = new HashMap<>();
+        if(null != liveAfterSalesVos && !liveAfterSalesVos.isEmpty()){
+            orderIds = liveAfterSalesVos.stream().map(e -> e.getOrderId()).collect(Collectors.toList());
+            if(null != orderIds && !orderIds.isEmpty()){
+                List<LiveOrderItemListUVO> liveOrderItemListUVOS = liveOrderItemMapper.selectLiveOrderItemListUVOByOrderIds(orderIds);
+                orderItemMap = liveOrderItemListUVOS.stream()
+                        .collect(Collectors.groupingBy(LiveOrderItemListUVO::getOrderId));
+            }
+        }
+        boolean mapEmpty = orderItemMap.isEmpty();
         for (LiveAfterSalesVo item : liveAfterSalesVos) {
             if(ObjectUtil.isNotNull(item.getUserId())) {
                 FsUser fsUser = fsUserCacheService.selectFsUserById(item.getUserId());
@@ -217,6 +227,41 @@ public class LiveAfterSalesServiceImpl implements ILiveAfterSalesService {
                 item.setCompanyName("-");
             }
 
+            if(!mapEmpty && orderItemMap.containsKey(item.getOrderId())){
+                List<LiveOrderItemListUVO> liveOrderItemListUVOS = orderItemMap.get(item.getOrderId());
+                for (LiveOrderItemListUVO liveOrderItemListUVO : liveOrderItemListUVOS) {
+                    try {
+                        JSONObject jsO = JSONObject.parseObject(liveOrderItemListUVO.getJsonInfo());
+                        item.setProductName(StringUtils.isNotBlank(item.getProductName()) ? item.getProductName() + "," + jsO.getString("productName") : jsO.getString("productName"));
+                        item.setProductBarCode(StringUtils.isNotBlank(item.getProductBarCode()) ? item.getProductBarCode() + "," + jsO.getString("barCode") : jsO.getString("barCode"));
+                        item.setSku(StringUtils.isNotBlank(item.getSku()) ? item.getSku() + "," + jsO.getString("sku") : jsO.getString("sku"));
+                        item.setNum(StringUtils.isNotBlank(item.getNum()) ? item.getNum() + "," + jsO.getString("num") : jsO.getString("num"));
+                        item.setPrice(StringUtils.isNotBlank(item.getPrice()) ? item.getPrice() + "," + jsO.getString("price") : jsO.getString("price"));
+                    } catch (Exception ex) {
+                        log.error("售后订单商品信息转换异常",ex);
+                    }
+                }
+            }
+//            if (StringUtils.isNotBlank(item.getItemJson())) {
+//                try {
+//                    JSONArray array = JSONObject.parseArray(item.getItemJson());
+//                    if(null != array && !array.isEmpty()){
+//                        for (Object o : array) {
+//                            JSONObject jsO = (JSONObject) o;
+//                            item.setProductName(StringUtils.isNotBlank(item.getProductName())?item.getProductName()+","+jsO.getString("productName"):jsO.getString("productName"));
+//                            item.setProductBarCode(StringUtils.isNotBlank(item.getProductBarCode())?item.getProductBarCode()+","+jsO.getString("barCode"):jsO.getString("barCode"));
+//                            item.setSku(StringUtils.isNotBlank(item.getSku())?item.getSku()+","+jsO.getString("sku"):jsO.getString("sku"));
+//                            item.setNum(null != item.getNum()?item.getNum() +jsO.getInteger("num"):jsO.getInteger("num"));
+//                            item.setPrice(null != item.getPrice()?item.getPrice().add(jsO.getBigDecimal("price")):jsO.getBigDecimal("price"));
+//                        }
+//                    }
+//                } catch (Exception ex) {
+//                    log.error("售后订单商品信息转换异常",ex);
+//                }
+//
+//            }
+
+
 //            if(ObjectUtil.isNotNull(item.getOrderId())){
 //                List<FsStoreDelivers> byOrderIdWithType = fsStoreDeliversService.findByOrderIdWithType(item.getOrderId(), 1);
 //                if(CollectionUtils.isNotEmpty(byOrderIdWithType)) {
@@ -233,71 +278,56 @@ public class LiveAfterSalesServiceImpl implements ILiveAfterSalesService {
     @Override
     @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
     public R cancel(LiveAfterSalesCancelParam param) {
-//        LiveAfterSales storeAfterSales = baseMapper.selectLiveAfterSalesById(param.getSalesId());
-//        if (storeAfterSales == null) {
-//            throw new CustomException("未查询到售后订单信息");
-//        }
-//        if (storeAfterSales.getSalesStatus()!=0) {
-//            throw new CustomException("非法操作");
-//        }
-//
-//        LiveOrder order = liveOrderService.selectLiveOrderByOrderId(String.valueOf(storeAfterSales.getOrderId()));
-//        order.setStatus(storeAfterSales.getOrderStatus());
-//        order.setRefundStatus(String.valueOf(OrderInfoEnum.REFUND_STATUS_0.getValue()));
-//        liveOrderService.updateLiveOrder(order);
-//        storeAfterSales.setSalesStatus(2);
-//        LiveAfterSalesLogs storeAfterSalesStatus = new LiveAfterSalesLogs();
-//        storeAfterSalesStatus.setStoreAfterSalesId(storeAfterSales.getId());
-//        storeAfterSalesStatus.setChangeType(6);
-//        storeAfterSalesStatus.setChangeMessage(AfterSalesStatusEnum.STATUS_6.getDesc()+" "+param.getRemark());
-//        storeAfterSalesStatus.setChangeTime(Timestamp.valueOf(LocalDateTime.now()));
-//        storeAfterSalesStatus.setOperator(param.getOperator());
-//        liveAfterSalesLogsMapper.insertLiveAfterSalesLogs(storeAfterSalesStatus);
-//
-//        // 查询是否已经发货
-//        FsStoreDelivers byOrderCode = fsStoreDeliversMapper.findByOrderCode(order.getOrderCode());
-//        // 如果已发货走发货售后逻辑,否则直接取消
-//        if(byOrderCode!=null){
-//            if(StringUtils.isNotBlank(order.getStoreHouseCode())) {
-//                String erp = fsWarehousesMapper.selectErpByCode(order.getStoreHouseCode());
-//                if (StringUtils.equals(erp, ErpTypeConstant.JST_ERP)) {
-//
-//                    FsJstAftersalePush fsJstAftersalePush = new FsJstAftersalePush();
-//                    fsJstAftersalePush.setOrderId(order.getOrderCode());
-//                    fsJstAftersalePush.setTaskStatus(TaskStatusEnum.PENDING.getCode());
-//                    fsJstAftersalePush.setType(String.valueOf(AfterSalesOrderStatusEnum.CLOSED.getIndex()));
-//                    fsJstAftersalePush.setRetryCount(0);
-//                    fsJstAftersalePush.setAfterSaleId(String.valueOf(storeAfterSales.getId()));
-//                    fsJstAftersalePush.setOrderType(1);
-//                    fsJstAftersalePushMapper.insert(fsJstAftersalePush);
-//                }
-//            }
-//        }
-//        // 如果未发货,重新推送
-//        else {
-//            //创建新的OMS订单
-//            if (storeAfterSales.getOrderStatus().equals(2) ) {
-//                if(StringUtils.isNotEmpty(order.getExtendOrderId())){
-//                    //更新订单code
-//                    String orderSn = SnowflakeUtils.nextId();
-//                    LiveOrder orderMap=new LiveOrder();
-//                    orderMap.setOrderId(order.getOrderId());
-//                    orderMap.setOrderCode(orderSn);
-//                    liveOrderService.updateLiveOrder(orderMap);
-//                    storeAfterSales.setStoreId(Long.valueOf(orderSn));
-//                    liveOrderItemService.updateFsStoreOrderCode(order.getOrderId(),orderSn);
-//                    //生成新的订单
-//                    try {
-//                        liveOrderService.createOmsOrder(order.getOrderId());
-//                    } catch (Exception e) {
-//                        log.error("创建订单失败!",e);
-//                        throw new CustomException("创建订单失败");
-//                    }
-//                }
-//            }
-//
-//        }
-//        baseMapper.updateLiveAfterSales(storeAfterSales);
+        LiveAfterSales storeAfterSales = baseMapper.selectLiveAfterSalesById(param.getSalesId());
+        if (storeAfterSales == null) {
+            throw new CustomException("未查询到售后订单信息");
+        }
+        if (storeAfterSales.getSalesStatus()!=0) {
+            throw new CustomException("非法操作");
+        }
+
+        LiveOrder order = liveOrderService.selectLiveOrderByOrderId(String.valueOf(storeAfterSales.getOrderId()));
+        order.setStatus(storeAfterSales.getOrderStatus());
+        order.setRefundStatus(String.valueOf(OrderInfoEnum.REFUND_STATUS_0.getValue()));
+        liveOrderService.updateLiveOrder(order);
+        storeAfterSales.setSalesStatus(2);
+        LiveAfterSalesLogs storeAfterSalesStatus = new LiveAfterSalesLogs();
+        storeAfterSalesStatus.setStoreAfterSalesId(storeAfterSales.getId());
+        storeAfterSalesStatus.setChangeType(6);
+        storeAfterSalesStatus.setChangeMessage(AfterSalesStatusEnum.STATUS_6.getDesc()+" "+param.getRemark());
+        storeAfterSalesStatus.setChangeTime(Timestamp.valueOf(LocalDateTime.now()));
+        storeAfterSalesStatus.setOperator(param.getOperator());
+        liveAfterSalesLogsMapper.insertLiveAfterSalesLogs(storeAfterSalesStatus);
+        if (ObjectUtil.equal(order.getStatus(), 2)) {
+            FsJstAftersalePush fsJstAftersalePush = new FsJstAftersalePush();
+            fsJstAftersalePush.setOrderId(order.getOrderCode());
+            fsJstAftersalePush.setTaskStatus(TaskStatusEnum.PENDING.getCode());
+            fsJstAftersalePush.setType(String.valueOf(AfterSalesOrderStatusEnum.CLOSED.getIndex()));
+            fsJstAftersalePush.setRetryCount(0);
+            fsJstAftersalePush.setAfterSaleId(String.valueOf(storeAfterSales.getId()));
+            fsJstAftersalePushMapper.insert(fsJstAftersalePush);
+        } else {
+            //创建新的OMS订单
+            if (storeAfterSales.getOrderStatus().equals(OrderInfoEnum.STATUS_1.getValue()) ) {
+                if(StringUtils.isNotEmpty(order.getExtendOrderId())){
+                    //更新订单code
+                    String orderSn = IdUtil.getSnowflake(0, 0).nextIdStr();
+                    LiveOrder orderMap=new LiveOrder();
+                    orderMap.setOrderId(order.getOrderId());
+                    orderMap.setOrderCode(orderSn);
+                    liveOrderService.updateLiveOrder(orderMap);
+                    liveOrderItemMapper.updateFsStoreOrderCode(order.getOrderId(),orderSn);
+                    try {
+                        //生成新的订单
+                        liveOrderService.createOmsOrder(order.getOrderId());
+                    } catch (Exception e) {
+                        log.error("撤销售后,生成oms订单异常",e);
+                    }
+
+                }
+            }
+        }
+        baseMapper.updateLiveAfterSales(storeAfterSales);
         return R.ok();
     }
 

+ 84 - 7
fs-service/src/main/java/com/fs/live/service/impl/LiveOrderServiceImpl.java

@@ -230,6 +230,9 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
 
     @Autowired
     private FsStoreProductScrmMapper fsStoreProductBaseMapper;
+
+    @Autowired
+    private FsStoreProductAttrValueScrmMapper attrValueScrmMapper;
     @Autowired
     private ISysConfigService configService;
 
@@ -1208,6 +1211,19 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
         for (LiveOrderItem vo : orderItemVOS) {
             if (vo.getIsAfterSales() == 1) {
                 fsStoreProductService.incProductStock(vo.getNum(), vo.getProductId(), vo.getProductAttrValueId());
+                LiveOrderItemDTO liveOrderItemDTO = JSONObject.parseObject(vo.getJsonInfo(), LiveOrderItemDTO.class);
+                // 获取商品信息
+                FsStoreProductScrm fsStoreProduct = fsStoreProductService.selectFsStoreProductById(vo.getProductId());
+                if (fsStoreProduct != null) {
+                    // 获取商品属性值中的条形码
+                    FsStoreProductAttrValueScrm fsStoreProductAttrValue = fsStoreProductAttrValueMapper.selectFsStoreProductAttrValueByProductId(fsStoreProduct.getProductId()).stream().filter(attr -> StringUtils.isNotEmpty(attr.getBarCode()) && attr.getBarCode().equals(liveOrderItemDTO.getBarCode())).findFirst().orElse(null);
+                    if (fsStoreProductAttrValue != null) {
+                        fsStoreProductAttrValue.setStock((int) (fsStoreProductAttrValue.getStock() + vo.getNum()));
+                        fsStoreProductAttrValue.setSales(fsStoreProductAttrValue.getSales() - 1);
+                        fsStoreProductAttrValueMapper.updateFsStoreProductAttrValue(fsStoreProductAttrValue);
+                    }
+
+                }
             }
         }
         if ("3".equals(order.getPayType())) {
@@ -1392,6 +1408,18 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
         for (LiveOrderItem vo : orderItemVOS) {
             if (vo.getIsAfterSales() == 1) {
                 fsStoreProductService.incProductStock(vo.getNum(), vo.getProductId(), vo.getProductAttrValueId());
+                LiveOrderItemDTO liveOrderItemDTO = JSONObject.parseObject(vo.getJsonInfo(), LiveOrderItemDTO.class);
+                // 获取商品信息
+                FsStoreProductScrm fsStoreProduct = fsStoreProductService.selectFsStoreProductById(vo.getProductId());
+                if (fsStoreProduct != null) {
+                    // 获取商品属性值中的条形码
+                    FsStoreProductAttrValueScrm fsStoreProductAttrValue = fsStoreProductAttrValueMapper.selectFsStoreProductAttrValueByProductId(fsStoreProduct.getProductId()).stream().filter(attr -> StringUtils.isNotEmpty(attr.getBarCode()) && attr.getBarCode().equals(liveOrderItemDTO.getBarCode())).findFirst().orElse(null);
+                    if (fsStoreProductAttrValue != null) {
+                        fsStoreProductAttrValue.setStock((int) (fsStoreProductAttrValue.getStock() + vo.getNum()));
+                        fsStoreProductAttrValue.setSales(fsStoreProductAttrValue.getSales() - 1);
+                        fsStoreProductAttrValueMapper.updateFsStoreProductAttrValue(fsStoreProductAttrValue);
+                    }
+                }
             }
         }
         if ("3".equals(order.getPayType())) {
@@ -1582,6 +1610,7 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
     }
 
     @Override
+    @Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)
     public R createOmsOrder(Long orderId)  throws ParseException {
         LiveOrder order = liveOrderMapper.selectLiveOrderByOrderId(String.valueOf(orderId));
         LiveOrderPayment liveOrderPayment = liveOrderPaymentMapper.selectByBuissnessId(orderId);
@@ -1821,10 +1850,23 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
             return null;
         }
         FsStoreProductScrm fsStoreProduct = fsStoreProductService.selectFsStoreProductById(param.getProductId());
-        if(fsStoreProduct == null) return null;
+        if (fsStoreProduct == null) {
+            log.error("商品不存在");
+            return null;
+        }
+        FsStoreProductAttrValueScrm fsStoreProductAttrValue = null;
+        if (!Objects.isNull(param.getAttrValueId())) {
+            fsStoreProductAttrValue = attrValueScrmMapper.selectFsStoreProductAttrValueById(param.getAttrValueId());
+        }
+
+        BigDecimal totalPrice = BigDecimal.ZERO;
+        if (fsStoreProductAttrValue != null) {
+            totalPrice = fsStoreProductAttrValue.getPrice().multiply(new BigDecimal(param.getTotalNum()));
+        } else {
+            totalPrice = fsStoreProduct.getPrice().multiply(new BigDecimal(param.getTotalNum()));
+        }
         BigDecimal payPrice = BigDecimal.ZERO;
         BigDecimal payDelivery = BigDecimal.ZERO;
-        BigDecimal totalPrice = fsStoreProduct.getPrice().multiply(new BigDecimal(param.getTotalNum()));
         if (param.getCityId() != null) {
             payDelivery = handleDeliveryMoney(param.getCityId(), fsStoreProduct, param.getTotalNum());
             totalPrice = totalPrice.add(payDelivery);
@@ -1953,11 +1995,24 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
     public LiveOrderComputeDTO computedOrder(long userId, LiveOrderComputedParam param) {
         String orderKey= redisCache.getCacheObject("orderKey:"+param.getOrderKey());
         if (StringUtils.isEmpty(orderKey)) {
+            log.error("订单已失效");
             return null;
         }
         FsStoreProductScrm fsStoreProduct = fsStoreProductService.selectFsStoreProductById(param.getProductId());
+        if (fsStoreProduct == null) {
+            log.error("商品不存在");
+            return null;
+        }
+        FsStoreProductAttrValueScrm fsStoreProductAttrValue = null;
+        if (!Objects.isNull(param.getAttrValueId())) {
+            fsStoreProductAttrValue = attrValueScrmMapper.selectFsStoreProductAttrValueById(param.getAttrValueId());
+        }
+
         BigDecimal totalPrice = BigDecimal.ZERO;
         BigDecimal payPrice = fsStoreProduct.getPrice().multiply(new BigDecimal(param.getTotalNum()));
+        if (fsStoreProductAttrValue != null) {
+            payPrice = fsStoreProductAttrValue.getPrice().multiply(new BigDecimal(param.getTotalNum()));
+        }
         totalPrice = totalPrice.add(payPrice);
         BigDecimal payDelivery = BigDecimal.ZERO;
         BigDecimal deductionPrice = BigDecimal.ZERO;
@@ -3325,6 +3380,11 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
         baseMapper.batchUpdateTime(list);
     }
 
+    @Override
+    public void batchUpdateTimeIds(List<Long> ids) {
+        baseMapper.batchUpdateTimeIds(ids);
+    }
+
 
     @Override
     @Transactional(rollbackFor = Throwable.class,propagation = Propagation.REQUIRED)
@@ -3355,10 +3415,20 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
         if(goods.getStock() == null) return R.error("直播间商品库存不足");
         if(fsStoreProduct.getStock() < Integer.parseInt(liveOrder.getTotalNum()) || goods.getStock() < Integer.parseInt(liveOrder.getTotalNum())) return R.error("抱歉,这款商品已被抢光,暂时无库存~");
 
-        // 更改店铺库存
-        fsStoreProduct.setStock(fsStoreProduct.getStock()-Integer.parseInt(liveOrder.getTotalNum()));
-        fsStoreProduct.setSales(fsStoreProduct.getSales()+Integer.parseInt(liveOrder.getTotalNum()));
-        fsStoreProductService.updateFsStoreProduct(fsStoreProduct);
+        FsStoreProductAttrValueScrm attrValue = null;
+        if (!Objects.isNull(liveOrder.getAttrValueId())) {
+            attrValue = fsStoreProductAttrValueMapper.selectFsStoreProductAttrValueById(liveOrder.getAttrValueId());
+        }
+        if (attrValue != null) {
+            attrValue.setStock(attrValue.getStock() - Integer.parseInt(liveOrder.getTotalNum()));
+            attrValue.setSales(attrValue.getSales() + Integer.parseInt(liveOrder.getTotalNum()));
+        } else {
+            // 更改店铺库存
+            fsStoreProduct.setStock(fsStoreProduct.getStock()-Integer.parseInt(liveOrder.getTotalNum()));
+            fsStoreProduct.setSales(fsStoreProduct.getSales()+Integer.parseInt(liveOrder.getTotalNum()));
+            fsStoreProductService.updateFsStoreProduct(fsStoreProduct);
+        }
+
         // 更新直播间库存
         goods.setStock(goods.getStock()-Integer.parseInt(liveOrder.getTotalNum()));
         goods.setSales(goods.getSales()+Integer.parseInt(liveOrder.getTotalNum()));
@@ -3381,6 +3451,9 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
         log.info("订单生成:"+orderSn);
         liveOrder.setOrderCode(orderSn);
         BigDecimal payPrice = fsStoreProduct.getPrice().multiply(new BigDecimal(liveOrder.getTotalNum()));
+        if (attrValue != null) {
+            payPrice = attrValue.getPrice().multiply(new BigDecimal(liveOrder.getTotalNum()));
+        }
         // 直播不需要服务费 0915 1735 左
 //        String config=configService.selectConfigByKey("store.config");
 //        StoreConfig storeConfig= JSONUtil.toBean(config,StoreConfig.class);
@@ -3429,7 +3502,11 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
                 LiveOrderItemDTO dto=new LiveOrderItemDTO();
                 dto.setImage(fsStoreProduct.getImage());
                 dto.setSku(String.valueOf(fsStoreProduct.getStock()));
-                FsStoreProductAttrValueScrm fsStoreProductAttrValue = fsStoreProductAttrValueMapper.selectFsStoreProductAttrValueByProductId(fsStoreProduct.getProductId()).stream().filter(attrValue -> StringUtils.isNotEmpty(attrValue.getBarCode())).findFirst().orElse(null);
+                FsStoreProductAttrValueScrm fsStoreProductAttrValue = fsStoreProductAttrValueMapper.selectFsStoreProductAttrValueByProductId(fsStoreProduct.getProductId()).stream().filter(attr -> StringUtils.isNotEmpty(attr.getBarCode())).findFirst().orElse(null);
+                if (fsStoreProductAttrValue != null) {
+                    dto.setBarCode(fsStoreProductAttrValue.getBarCode());
+                    dto.setGroupBarCode(fsStoreProductAttrValue.getGroupBarCode());
+                }
                 if (fsStoreProductAttrValue != null) {
                     dto.setBarCode(fsStoreProductAttrValue.getBarCode());
                     dto.setGroupBarCode(fsStoreProductAttrValue.getGroupBarCode());

+ 91 - 3
fs-service/src/main/java/com/fs/live/service/impl/LiveServiceImpl.java

@@ -137,7 +137,19 @@ public class LiveServiceImpl implements ILiveService
      */
     @Override
     public Live selectLiveByLiveId(Long liveId){
+        // 先从缓存中获取
+        String cacheKey = String.format(LiveKeysConstant.LIVE_DATA_CACHE, liveId);
+        Live cachedLive = redisCache.getCacheObject(cacheKey);
+        if (cachedLive != null) {
+            return cachedLive;
+        }
+        
+        // 缓存中没有,从数据库查询
         Live byId = baseMapper.selectLiveByLiveId(liveId);
+        if (byId == null) {
+            return null;
+        }
+        
         List<LiveVideo> videos = liveVideoService.listByLiveId(liveId, 1);
         if(!videos.isEmpty()){
             LiveVideo liveVideo = videos.get(0);
@@ -148,6 +160,10 @@ public class LiveServiceImpl implements ILiveService
             byId.setVideoFileSize(liveVideo.getFileSize());
             byId.setVideoDuration(liveVideo.getDuration());
         }
+        
+        // 将结果存入缓存
+        redisCache.setCacheObject(cacheKey, byId, LiveKeysConstant.LIVE_DATA_CACHE_EXPIRE, TimeUnit.SECONDS);
+        
         return byId;
     }
 
@@ -385,12 +401,17 @@ public class LiveServiceImpl implements ILiveService
     @Transactional
     public int updateLiveEntity(Live live) {
         log.error("updateLiveEntity:"+ live.getLiveId());
-        return baseMapper.updateLive( live);
+        int result = baseMapper.updateLive( live);
+        // 清除缓存
+        clearLiveCache(live.getLiveId());
+        return result;
     }
 
     @Override
     public void updateGlobalVisible(long liveId, Integer status) {
         baseMapper.updateGlobalVisible(liveId, status);
+        // 清除缓存
+        clearLiveCache(liveId);
     }
 
     @Override
@@ -530,6 +551,9 @@ public class LiveServiceImpl implements ILiveService
         log.error("updateLive:" + live.getLiveId());
         int result = baseMapper.updateLive(live);
         liveAutoTaskService.recalcLiveAutoTask(live);
+        
+        // 清除缓存
+        clearLiveCache(live.getLiveId());
 
         return result;
     }
@@ -542,7 +566,10 @@ public class LiveServiceImpl implements ILiveService
      */
     @Override
     public int deleteLiveByLiveIds(Long[] liveIds,Live live){
-        return baseMapper.deleteLiveByLiveIds(liveIds, live);
+        int result = baseMapper.deleteLiveByLiveIds(liveIds, live);
+        // 清除缓存
+        clearLiveCache(liveIds);
+        return result;
     }
 
     /**
@@ -554,7 +581,10 @@ public class LiveServiceImpl implements ILiveService
     @Override
     public int deleteLiveByLiveId(Long liveId)
     {
-        return baseMapper.deleteLiveByLiveId(liveId);
+        int result = baseMapper.deleteLiveByLiveId(liveId);
+        // 清除缓存
+        clearLiveCache(liveId);
+        return result;
     }
 
     @Override
@@ -599,6 +629,8 @@ public class LiveServiceImpl implements ILiveService
         live.setStatus(2);
         live.setLiveType(1);
         this.updateLive(live);
+        // 清除缓存
+        clearLiveCache(liveId);
         LiveData liveData = new LiveData();
         liveData.setLiveId(liveId);
         liveData.setPageViews(0L);
@@ -665,6 +697,8 @@ public class LiveServiceImpl implements ILiveService
         live.setLiveType(2);
         log.error("closeLiving:" + live.getLiveId());
         baseMapper.updateLive(live);
+        // 清除缓存
+        clearLiveCache(live.getLiveId());
         return R.ok();
     }
     /**
@@ -701,6 +735,8 @@ public class LiveServiceImpl implements ILiveService
 
             log.error("startLoopPlay:" + live.getLiveId());
             baseMapper.updateLive(curLive);
+            // 清除缓存
+            clearLiveCache(live.getLiveId());
 
             return R.ok();
         } catch (Exception e) {
@@ -729,6 +765,8 @@ public class LiveServiceImpl implements ILiveService
             curLive.setUpdateTime(new Date());
             log.error("stopLoopPlay:" + live.getLiveId());
             baseMapper.updateLive(curLive);
+            // 清除缓存
+            clearLiveCache(live.getLiveId());
 
             return R.ok();
         } catch (Exception e) {
@@ -740,6 +778,8 @@ public class LiveServiceImpl implements ILiveService
     public R handleShelfOrUn(LiveListVo listVo) {
         int updatedCount = baseMapper.updateBatchLiveList(listVo);
         log.error("有人下架了视频:" + listVo.getLiveIds());
+        // 清除缓存
+        clearLiveCache(listVo.getLiveIds());
         if (updatedCount > 0) {
             return R.ok("操作成功");
         }
@@ -749,6 +789,8 @@ public class LiveServiceImpl implements ILiveService
     @Override
     public R handleDeleteSelected(LiveListVo listVo) {
         int deleteCount = baseMapper.deleteBatchLiveList(listVo);
+        // 清除缓存
+        clearLiveCache(listVo.getLiveIds());
         if (deleteCount > 0) {
             return R.ok("操作成功");
         }
@@ -773,6 +815,8 @@ public class LiveServiceImpl implements ILiveService
         exist.setUpdateTime(new Date());
 
         baseMapper.updateLive(exist);
+        // 清除缓存
+        clearLiveCache(live.getLiveId());
 
         return R.ok();
     }
@@ -806,6 +850,8 @@ public class LiveServiceImpl implements ILiveService
         exist.setFinishTime( null);
         exist.setStartTime(LocalDateTime.now());
         baseMapper.updateLive(exist);
+        // 清除缓存
+        clearLiveCache(live.getLiveId());
         List<LiveAutoTask> liveAutoTasks = liveAutoTaskService.selectNoActivedByLiveId(exist.getLiveId(), new Date());
         liveAutoTasks.forEach(liveAutoTask -> {
             liveAutoTask.setCreateTime(null);
@@ -822,12 +868,16 @@ public class LiveServiceImpl implements ILiveService
     public R handleShelfOrUnAdmin(LiveListVo listVo) {
         baseMapper.handleShelfOrUnAdmin(listVo);
         log.error("有人下架了视频:" + listVo.getLiveIds());
+        // 清除缓存
+        clearLiveCache(listVo.getLiveIds());
         return R.ok();
     }
 
     @Override
     public R handleDeleteSelectedAdmin(LiveListVo listVo) {
         baseMapper.handleDeleteSelectedAdmin(listVo);
+        // 清除缓存
+        clearLiveCache(listVo.getLiveIds());
         return R.ok();
     }
 
@@ -1184,6 +1234,42 @@ public class LiveServiceImpl implements ILiveService
         return jsonObject.getLong(key);
     }
 
+    /**
+     * 清除直播间数据缓存
+     * @param liveId 直播间ID
+     */
+    private void clearLiveCache(Long liveId) {
+        if (liveId != null) {
+            String cacheKey = String.format(LiveKeysConstant.LIVE_DATA_CACHE, liveId);
+            redisCache.deleteObject(cacheKey);
+            log.debug("清除直播间缓存: liveId={}", liveId);
+        }
+    }
+
+    /**
+     * 批量清除直播间数据缓存
+     * @param liveIds 直播间ID数组
+     */
+    private void clearLiveCache(Long[] liveIds) {
+        if (liveIds != null && liveIds.length > 0) {
+            for (Long liveId : liveIds) {
+                clearLiveCache(liveId);
+            }
+        }
+    }
+
+    /**
+     * 批量清除直播间数据缓存
+     * @param liveIds 直播间ID列表
+     */
+    private void clearLiveCache(List<Long> liveIds) {
+        if (liveIds != null && !liveIds.isEmpty()) {
+            for (Long liveId : liveIds) {
+                clearLiveCache(liveId);
+            }
+        }
+    }
+
 
 
     /**
@@ -1224,6 +1310,8 @@ public class LiveServiceImpl implements ILiveService
                 live.setIdCardUrl(payload.get("idCardUrl"));
                 log.error("verifyIdInfo:" + live.getLiveId());
                 baseMapper.updateLive(live);
+                // 清除缓存
+                clearLiveCache(live.getLiveId());
                 return R.ok();
             }
         } catch (Exception e) {

+ 6 - 7
fs-service/src/main/java/com/fs/live/service/impl/LiveWatchUserServiceImpl.java

@@ -200,8 +200,7 @@ public class LiveWatchUserServiceImpl implements ILiveWatchUserService {
     }
 
     @Override
-    public LiveWatchUser join(long liveId, long userId, String location) {
-        FsUserScrm fsUserVO = fsUserService.selectFsUserByUserId(userId);
+    public LiveWatchUser join(FsUserScrm fsUser,long liveId, long userId, String location) {
         Date now = DateUtils.getNowDate();
 
         // 查询直播间信息
@@ -231,7 +230,7 @@ public class LiveWatchUserServiceImpl implements ILiveWatchUserService {
             liveWatchUser = new LiveWatchUser();
             liveWatchUser.setLiveId(liveId);
             liveWatchUser.setUserId(userId);
-            liveWatchUser.setAvatar(fsUserVO.getAvatar());
+            liveWatchUser.setAvatar(fsUser.getAvatar());
             liveWatchUser.setMsgStatus(0);
             liveWatchUser.setOnline(0);
             liveWatchUser.setLocation(location);
@@ -242,15 +241,15 @@ public class LiveWatchUserServiceImpl implements ILiveWatchUserService {
             baseMapper.insertLiveWatchUser(liveWatchUser);
         }
 
-        liveWatchUser.setAvatar(fsUserVO.getAvatar());
-        liveWatchUser.setNickName(fsUserVO.getNickname());
+        liveWatchUser.setAvatar(fsUser.getAvatar());
+        liveWatchUser.setNickName(fsUser.getNickname());
         String hashKey = String.format(LiveKeysConstant.LIVE_WATCH_USERS, liveId);
         redisCache.hashPut(hashKey, String.valueOf(userId), JSON.toJSONString(liveWatchUser));
         return liveWatchUser;
     }
 
     @Override
-    public LiveWatchUser joinWithoutLocation(long liveId, long userId) {
+    public LiveWatchUser joinWithoutLocation(FsUserScrm fsUser,long liveId, long userId) {
         FsUserScrm fsUserVO = fsUserService.selectFsUserByUserId(userId);
         Date now = DateUtils.getNowDate();
 
@@ -295,7 +294,7 @@ public class LiveWatchUserServiceImpl implements ILiveWatchUserService {
         return liveWatchUser;
     }
     @Override
-    public LiveWatchUser close(long liveId, long userId) {
+    public LiveWatchUser close(FsUserScrm fsUser,long liveId, long userId) {
 
         // 查询直播间信息
         Live live = liveMapper.selectLiveByLiveId(liveId);

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

@@ -108,7 +108,7 @@ public class LiveAfterSalesVo {
     /** 用户id */
     @Excel(name = "用户id")
     private Long userId;
-    //    @Excel(name = "用户名称")
+    @Excel(name = "用户名称")
     private String userName;
 
     /** 商家收货人 */
@@ -147,4 +147,32 @@ public class LiveAfterSalesVo {
      */
     private String deliveryId;
 
+    private String itemJson;
+
+    @Excel(name ="产品名称")
+    private String productName;
+    @Excel(name ="产品编码")
+    private String productBarCode;
+    @Excel(name ="规格")
+    private String sku;
+    @Excel(name ="产品数量")
+    private String num;
+    @Excel(name ="产品价格")
+    private String price;
+    @Excel(name="额外运费")
+    private BigDecimal totalPostage;
+    @Excel(name="实付金额")
+    private BigDecimal payPrice;
+//    @Excel(name="收货人姓名")
+//    private String realName;
+    @Excel(name ="详细地址")
+    private  String userAddress;
+    @Excel(name ="支付时间" ,dateFormat = "yyyy-MM-dd HH:mm:ss")
+    private Date orderPayTime;
+    @Excel(name ="银行交易流水号")
+    private String bankSerialNo;
+
+
+
+
 }

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

@@ -97,3 +97,4 @@ public class LiveDataDetailVo {
 
 
 
+

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

@@ -7,6 +7,7 @@ import java.io.Serializable;
 @Data
 public class LiveOrderItemListUVO implements Serializable {
 
+    private Long orderId;
     /** 商品ID */
     private Long productId;
     private Long itemId;

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

@@ -39,3 +39,4 @@ public class LiveUserDetailVo {
 
 
 
+

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

@@ -27,3 +27,4 @@ public class ProductSalesVo {
 
 
 
+

+ 101 - 0
fs-service/src/main/java/com/fs/qw/mapper/QwExternalContactMapper.java

@@ -268,6 +268,13 @@ public interface QwExternalContactMapper extends BaseMapper<QwExternalContact> {
             "            <if test=\"companyUserId != null \"> and ec.company_user_id = #{companyUserId}</if>\n" +
             "            <if test=\"customerId != null \"> and ec.customer_id = #{customerId}</if>\n" +
             "            <if test=\"status != null \"> and ec.status = #{status}</if>\n" +
+            "            <if test=\"status != null \"> and ec.status = #{status}</if>\n" +
+            "<if test = \"statusCondition != null and  statusCondition.length > 0 \" > " +
+            "   AND ec.status in " +
+            "   <foreach collection='statusCondition' item='item' open='(' separator=',' close=')'> " +
+            "       #{item} " +
+            "   </foreach> " +
+            "</if >\n" +
             "            <if test=\"stageStatus != null \"> and ec.stage_status = #{stageStatus}</if>\n" +
             "            <if test=\"userRepeat != null \"> and ec.user_repeat = #{userRepeat}</if>\n" +
             "            <if test=\"transferStatus != null \"> and ec.transfer_status = #{transferStatus}</if>\n" +
@@ -564,4 +571,98 @@ public interface QwExternalContactMapper extends BaseMapper<QwExternalContact> {
     List<QwExternalContact> getGroupChatUserByChatIdAndUserName(@Param("userId")String userId,@Param("userName")String userName,@Param("corpId") String corpId,@Param("chatId") String chatId);
     @Select("select * from qw_external_contact where unionid = #{unionID} order by create_time asc limit 1 ")
     QwExternalContact selectQwExternalByUnionID(String unionId);
+
+    @Select("<script> " +
+            "WITH contact_analysis AS (\n" +
+            "    SELECT \n" +
+            "        id,\n" +
+            "        external_user_id,\n" +
+            "        corp_id,\n" +
+            "        fs_user_id,\n" +
+            "        COUNT(CASE WHEN fs_user_id IS NOT NULL THEN 1 END) OVER (\n" +
+            "            PARTITION BY external_user_id, corp_id\n" +
+            "        ) as has_valid_fs_user,\n" +
+            "        COUNT(CASE WHEN fs_user_id IS NULL THEN 1 END) OVER (\n" +
+            "            PARTITION BY external_user_id, corp_id\n" +
+            "        ) as has_null_fs_user,\n" +
+            "        COUNT(*) OVER (\n" +
+            "            PARTITION BY external_user_id, corp_id\n" +
+            "        ) as total_records\n" +
+            "    FROM qw_external_contact\n" +
+            "    WHERE external_user_id IS NOT NULL \n" +
+            "          AND corp_id IS NOT NULL\n" +
+            "          AND create_time >='2025-10-01 00:00:00'\n" +
+            "     \n" +
+            "),\n" +
+            "filtered_contacts AS (\n" +
+            "    SELECT \n" +
+            "        id,\n" +
+            "        external_user_id,\n" +
+            "        corp_id,\n" +
+            "        fs_user_id\n" +
+            "    FROM contact_analysis\n" +
+            "    WHERE total_records >= 2\n" +
+            "        AND has_valid_fs_user >= 1\n" +
+            "        AND has_null_fs_user >= 1\n" +
+            "        AND fs_user_id IS NULL\n" +
+            ")\n" +
+            "SELECT \n" +
+            "    corp_id\n" +
+            "FROM filtered_contacts\n" +
+            "GROUP BY corp_id" +
+            "</script> ")
+    public List<String> selectQwExternalContactMandatoryRegistration();
+
+    @Select("<script>" +
+            "SELECT t1.id,t2.valid_fs_user_id as fs_user_id \n" +
+            "FROM qw_external_contact t1\n" +
+            "JOIN (\n" +
+            "    SELECT \n" +
+            "        external_user_id,\n" +
+            "        corp_id,\n" +
+            "        MAX(fs_user_id) as valid_fs_user_id\n" +
+            "    FROM qw_external_contact\n" +
+            "    WHERE external_user_id IS NOT NULL \n" +
+            "        AND corp_id IS NOT NULL \n" +
+            "        AND corp_id = #{corpId}\n" +
+            "        AND fs_user_id IS NOT NULL\n" +
+            "        AND create_time >='2025-10-01 00:00:00' \n" +
+            "    GROUP BY external_user_id, corp_id\n" +
+            ") t2 ON t1.external_user_id = t2.external_user_id \n" +
+            "    AND t1.corp_id = t2.corp_id\n" +
+            "JOIN (\n" +
+            "    SELECT \n" +
+            "        external_user_id,\n" +
+            "        corp_id\n" +
+            "    FROM qw_external_contact\n" +
+            "    WHERE external_user_id IS NOT NULL \n" +
+            "        AND corp_id IS NOT NULL \n" +
+            "        AND corp_id = #{corpId}\n" +
+            "        AND create_time >='2025-10-01 00:00:00' \n" +
+            "    GROUP BY external_user_id, corp_id\n" +
+            "    HAVING COUNT(*) >= 2\n" +
+            "        AND SUM(CASE WHEN fs_user_id IS NOT NULL THEN 1 ELSE 0 END) >= 1\n" +
+            "        AND SUM(CASE WHEN fs_user_id IS NULL THEN 1 ELSE 0 END) >= 1\n" +
+            ") t3 ON t1.external_user_id = t3.external_user_id \n" +
+            "    AND t1.corp_id = t3.corp_id\n" +
+            "WHERE t1.fs_user_id IS NULL\n" +
+            "    AND t1.corp_id = #{corpId}\n" +
+            "</script>")
+    public List<QwMandatoryRegistrParam> selectQwExternalContactMandatoryRegistrationByIds(@Param("corpId") String corpId);
+
+
+
+    @Update("<script>" +
+            "UPDATE qw_external_contact " +
+            "SET fs_user_id = CASE " +
+            "<foreach collection='map' item='item'>" +
+            "WHEN id = #{item.id} THEN #{item.fsUserId} " +
+            "</foreach>" +
+            "ELSE fs_user_id END " +
+            "WHERE id IN " +
+            "<foreach collection='map' item='item' open='(' separator=',' close=')'>" +
+            "#{item.id}" +
+            "</foreach>" +
+            "</script>")
+    public int batchUpdateQwExternalContactMandatoryRegistration(@Param("map") List<QwMandatoryRegistrParam> batchList);
 }

+ 7 - 0
fs-service/src/main/java/com/fs/qw/param/QwExternalContactParam.java

@@ -1,5 +1,6 @@
 package com.fs.qw.param;
 
+import com.baomidou.mybatisplus.annotation.TableField;
 import com.fasterxml.jackson.annotation.JsonFormat;
 import com.fs.common.annotation.Excel;
 import lombok.Data;
@@ -107,6 +108,12 @@ public class QwExternalContactParam {
 
     private Integer transferStatus;
     private Integer status;
+    //多选状态
+    @TableField(exist = false)
+    private String statuses;
+
+    @TableField(exist = false)
+    private String[] statusCondition;
     private String stageStatus;
     private String isBind;
     private String isBindMini;

+ 9 - 0
fs-service/src/main/java/com/fs/qw/param/QwMandatoryRegistrParam.java

@@ -0,0 +1,9 @@
+package com.fs.qw.param;
+
+import lombok.Data;
+
+@Data
+public class QwMandatoryRegistrParam {
+    private Long id;
+    private Long fsUserId;
+}

+ 6 - 0
fs-service/src/main/java/com/fs/qw/service/IQwExternalContactService.java

@@ -283,4 +283,10 @@ public interface IQwExternalContactService extends IService<QwExternalContact> {
     void delQwExternalContactTag(Long id, List<String> delTags);
 
     List<QwExternalContact> selectQwUserAndLevel(List<Long> qwUserIdList, List<String> levelList, boolean isReg);
+
+    List<String> selectQwExternalContactMandatoryRegistration();
+
+    List<QwMandatoryRegistrParam> selectQwExternalContactMandatoryRegistrationByIds(String corpId);
+
+    int batchUpdateQwExternalContactMandatoryRegistration(List<QwMandatoryRegistrParam> batchList);
 }

+ 14 - 0
fs-service/src/main/java/com/fs/qw/service/impl/QwExternalContactServiceImpl.java

@@ -5935,6 +5935,20 @@ public class QwExternalContactServiceImpl extends ServiceImpl<QwExternalContactM
         return list(queryCondition);
     }
 
+    @Override
+    public List<String> selectQwExternalContactMandatoryRegistration() {
+        return qwExternalContactMapper.selectQwExternalContactMandatoryRegistration();
+    }
+
+    @Override
+    public List<QwMandatoryRegistrParam> selectQwExternalContactMandatoryRegistrationByIds(String corpId) {
+        return qwExternalContactMapper.selectQwExternalContactMandatoryRegistrationByIds(corpId);
+    }
+
+    @Override
+    public int batchUpdateQwExternalContactMandatoryRegistration(List<QwMandatoryRegistrParam> batchList) {
+        return qwExternalContactMapper.batchUpdateQwExternalContactMandatoryRegistration( batchList);
+    }
     @Override
     public R getRepeat(RepeatParam param) {
         List<QwExternalContact> list = qwExternalContactMapper.selectList(new QueryWrapper<QwExternalContact>().eq("external_user_id", param.getExternalUserId()));

+ 3 - 0
fs-service/src/main/java/com/fs/qw/vo/QwExternalContactTransferLogListVO.java

@@ -24,6 +24,7 @@ public class QwExternalContactTransferLogListVO {
     /**
     * 员工部门
     */
+    @Excel(name = "员工部门")
     private String deptName;
 
     /** 企微外部联系人id */
@@ -41,6 +42,8 @@ public class QwExternalContactTransferLogListVO {
     /** 状态 */
     @Excel(name = "状态")
     private Integer status;
+
+    @Excel(name = "转接时间",dateFormat="yyyy-MM-dd HH:mm:ss")
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     private Date createTime;
 

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

@@ -0,0 +1,102 @@
+baidu:
+  token: 12313231232
+  back-domain: https://www.xxxx.com
+#配置
+logging:
+  level:
+    org.springframework.web: INFO
+    com.github.binarywang.demo.wx.cp: DEBUG
+    me.chanjar.weixin: DEBUG
+wx:
+  miniapp:
+    configs:
+      - appid: wx94951f52d3ac5e25   #北京存在文化
+        secret: bfe27b20c6e3c4232a1d4ef36228e84b #北京存在文化
+        token: Ncbnd7lJvkripxxna6NAWCxCrvC
+        aesKey: HlEiBB55eaWUaeBVAQO3cWKWPYv1vOVQSq7nFNICw4E
+        msgDataFormat: JSON
+  cp:
+    corpId: wwa46ffb9ff6ac35b8 #企业ID北京存在文化
+    appConfigs:
+      - agentId: 1000070       #北京存在文化
+        secret: pu2EFz6gY2Fo2K-aRUxLPaAkKIaMJJRp8ES9JdpHkp4 #北京存在文化
+        token: PPKOdAlCoMO
+        aesKey: PKvaxtpSv8NGpfTDm7VUHIK8Wok2ESyYX24qpXJAdMP
+  pay:
+    appId:  #微信公众号或者小程序等的appid
+    mchId:  #微信支付商户号
+    mchKey:  #微信支付商户密钥
+    subAppId:  #服务商模式下的子商户公众账号ID
+    subMchId:  #服务商模式下的子商户号
+    keyPath: c:\\cert\\apiclient_cert.p12 # p12证书的位置,可以指定绝对路径,也可以指定类路径(以classpath:开头)
+    notifyUrl: https://userapp.his.runtzh.com/app/wxpay/wxPayNotify
+  mp:
+    useRedis: false
+    redisConfig:
+      host: 127.0.0.1
+      port: 6379
+      timeout: 2000
+    configs:
+#      - appId: wxce847c8ebe5e62aa # 第一个公众号的appid  //公众号名称:济南联智健康
+#        secret: 37f7c5e3b7ff07794343957f7ced8de4 # 公众号的appsecret--济南联智健康
+      - appId: wxd6905bed94e45ef0 # 第一个公众号的appid  //公众号名称:济南联智健康
+        secret: c1a042a55bac24033535da50ce8ab6a2 # 公众号的appsecret--济南联智健康
+        token: PPKOdAlCoMO # 接口配置里的Token值
+        aesKey: Eswa6VjwtVMCcw03qZy6fWllgrv5aytIA1SZPEU0kU2 # 接口配置里的EncodingAESKey值
+aifabu:  #爱链接
+  appKey: 7b471be905ab17e00f3b858c6710dd117601d008
+watch:
+  watchUrl: watch.ylrzcloud.com/prod-api
+  #  account: tcloud
+  #  password: mdf-m2h_6yw2$hq
+  account1: ccif #866655060138751
+  password1: cp-t5or_6xw7$mt
+  account2: tcloud #rt500台
+  password2: mdf-m2h_6yw2$hq
+  account3: whr
+  password3: v9xsKuqn_$d2y
+
+fs :
+  commonApi: http://192.168.95.223:7771
+  h5CommonApi: http://192.168.95.223:7771
+  jwt:
+    # 加密秘钥
+    secret: 3e6d9c0b4a7f1e2d5c4e0d3c6b9a2f5e
+    # token有效时长,7天,单位秒
+    expire: 31536000
+    header: AppToken
+nuonuo:
+  key: 10924508
+  secret: A2EB20764D304D16
+
+# 存储捅配置
+tencent_cloud_config:
+  secret_id: AKIDiMq9lDf2EOM9lIfqqfKo7FNgM5meD0sT
+  secret_key: u5SuS80342xzx8FRBukza9lVNHKNMSaB
+  bucket: jnlzjk-1323137866
+  app_id: 1323137866
+  region: ap-chongqing
+  proxy: jnlzjk
+tmp_secret_config:
+  secret_id: AKIDCj7NSNAovtqeJpBau8GZ4CGB71thXIxX
+  secret_key: lTB5zwqqz7CNhzDOWivFWedgfTBgxgBT
+  bucket: fs-1319721001
+  app_id: 1319721001
+  region: ap-chongqing
+  proxy: fs
+cloud_host:
+  company_name: 济南联志健康
+  projectCode: LZJK
+headerImg:
+  imgUrl:
+
+ipad:
+  ipadUrl: http://1ipad.ljhehualu.com
+  aiApi: http://149.232.181.28:3000/api
+  voiceApi: http://229.28.187.88:8667
+  commonApi: http://229.28.187.88:7771
+wx_miniapp_temp:
+  pay_order_temp_id:
+  inquiry_temp_id:
+
+

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

@@ -0,0 +1,173 @@
+# 数据源配置
+spring:
+    profiles:
+        include: config-druid-ylrz,common
+    # redis 配置
+    redis:
+        host: 10.0.0.61
+        port: 6379
+        # 数据库索引
+        database: 0
+        # 密码
+        password: Ylrz_1q2w3e4r5t6y
+        # 连接超时时间
+        timeout: 30s
+        lettuce:
+            pool:
+                # 连接池中的最小空闲连接
+                min-idle: 0
+                # 连接池中的最大空闲连接
+                max-idle: 8
+                # 连接池的最大数据库连接数
+                max-active: 100
+                # #连接池最大阻塞等待时间(使用负值表示没有限制)
+                max-wait: -1ms
+    datasource:
+#        clickhouse:
+#            type: com.alibaba.druid.pool.DruidDataSource
+#            driverClassName: com.clickhouse.jdbc.ClickHouseDriver
+#            url: jdbc:clickhouse://1.14.104.71:8123/sop_test?compress=0&use_server_time_zone=true&use_client_time_zone=false&timezone=Asia/Shanghai
+#            username: rt_2024
+#            password: Yzx_19860213
+#            initialSize: 10
+#            maxActive: 100
+#            minIdle: 10
+#            maxWait: 6000
+        mysql:
+            type: com.alibaba.druid.pool.DruidDataSource
+            driverClassName: com.mysql.cj.jdbc.Driver
+            druid:
+                # 主库数据源
+                master:
+                  url: jdbc:mysql://10.0.0.131:3306/his?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&allowMultiQueries=true
+                  username: root
+                  password: Ylrz_1q2w3e4r5t6y
+                # 从库数据源
+                slave:
+                    # 从数据源开关/默认关闭
+                    enabled: false
+                    url:
+                    username:
+                    password:
+                # 初始连接数
+                initialSize: 5
+                # 最小连接池数量
+                minIdle: 10
+                # 最大连接池数量
+                maxActive: 20
+                # 配置获取连接等待超时的时间
+                maxWait: 60000
+                # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
+                timeBetweenEvictionRunsMillis: 60000
+                # 配置一个连接在池中最小生存的时间,单位是毫秒
+                minEvictableIdleTimeMillis: 300000
+                # 配置一个连接在池中最大生存的时间,单位是毫秒
+                maxEvictableIdleTimeMillis: 900000
+                # 配置检测连接是否有效
+                validationQuery: SELECT 1 FROM DUAL
+                testWhileIdle: true
+                testOnBorrow: false
+                testOnReturn: false
+                webStatFilter:
+                    enabled: true
+                statViewServlet:
+                    enabled: true
+                    # 设置白名单,不填则允许所有访问
+                    allow:
+                    url-pattern: /druid/*
+                    # 控制台管理用户名和密码
+                    login-username: fs
+                    login-password: 123456
+                filter:
+                    stat:
+                        enabled: true
+                        # 慢SQL记录
+                        log-slow-sql: true
+                        slow-sql-millis: 1000
+                        merge-sql: true
+                    wall:
+                        config:
+                            multi-statement-allow: true
+        sop:
+            type: com.alibaba.druid.pool.DruidDataSource
+            driverClassName: com.mysql.cj.jdbc.Driver
+            druid:
+                # 主库数据源
+                master:
+                    url: jdbc:mysql://10.0.0.131:3306/his_sop?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&allowMultiQueries=true
+                    username: root
+                    password: Ylrz_1q2w3e4r5t6y
+                # 初始连接数
+                initialSize: 5
+                # 最小连接池数量
+                minIdle: 10
+                # 最大连接池数量
+                maxActive: 20
+                # 配置获取连接等待超时的时间
+                maxWait: 60000
+                # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
+                timeBetweenEvictionRunsMillis: 60000
+                # 配置一个连接在池中最小生存的时间,单位是毫秒
+                minEvictableIdleTimeMillis: 300000
+                # 配置一个连接在池中最大生存的时间,单位是毫秒
+                maxEvictableIdleTimeMillis: 900000
+                # 配置检测连接是否有效
+                validationQuery: SELECT 1 FROM DUAL
+                testWhileIdle: true
+                testOnBorrow: false
+                testOnReturn: false
+                webStatFilter:
+                    enabled: true
+                statViewServlet:
+                    enabled: true
+                    # 设置白名单,不填则允许所有访问
+                    allow:
+                    url-pattern: /druid/*
+                    # 控制台管理用户名和密码
+                    login-username: fs
+                    login-password: 123456
+                filter:
+                    stat:
+                        enabled: true
+                        # 慢SQL记录
+                        log-slow-sql: true
+                        slow-sql-millis: 1000
+                        merge-sql: true
+                    wall:
+                        config:
+                            multi-statement-allow: true
+rocketmq:
+    name-server: rmq-33namm2jeq.rocketmq.nj.qcloud.tencenttdmq.com:8080
+    producer:
+        group: my-producer-group
+        access-key: ak33namm2jeq90148878a325 # 替换为实际的 accessKey
+        secret-key: sk0256ccecd9f96742 # 替换为实际的 secretKey
+    consumer:
+        group: common-group
+        access-key: ak33namm2jeq90148878a325 # 替换为实际的 accessKey
+        secret-key: sk0256ccecd9f96742 # 替换为实际的 secretKey
+openIM:
+    secret: openIM123
+    userID: imAdmin
+    url: https://test.web.im.fbylive.com/api
+# token配置
+token:
+  # 令牌自定义标识
+  header: Authorization
+  # 令牌密钥
+  secret: abcdefghijklmnopqrstuvwxyz
+  # 令牌有效期(默认30分钟)
+  expireTime: 180
+#是否使用新im
+im:
+    type: NONE
+#是否为新商户,新商户不走mpOpenId
+isNewWxMerchant: false
+qw:
+    enableAutoTag: 1
+tag:
+    thread:
+        num: 10
+    rate:
+        limit: 50
+

+ 27 - 0
fs-service/src/main/resources/mapper/course/FsCourseTrafficLogMapper.xml

@@ -320,4 +320,31 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         GROUP BY l.company_id, DATE_FORMAT(l.create_time, '%Y-%m')
     </select>
 
+
+    <select id="selectExpireLinkIds" resultType="java.lang.Long">
+        <![CDATA[
+        SELECT log_id
+        FROM fs_course_traffic_log
+        WHERE create_time < #{createTime}
+        ORDER BY log_id ASC
+            LIMIT #{limit} OFFSET #{offset}
+        ]]>
+    </select>
+
+    <delete id="batchDeleteByIds">
+        DELETE FROM fs_course_traffic_log
+        WHERE log_id IN
+        <foreach collection="ids" item="id" open="(" close=")" separator=",">
+            #{id}
+        </foreach>
+    </delete>
+
+    <select id="countExpireLink" resultType="java.lang.Long">
+        <![CDATA[
+        SELECT COUNT(*)
+        FROM fs_course_traffic_log
+        WHERE create_time < #{createTime}
+        ]]>
+    </select>
+
 </mapper>

+ 5 - 0
fs-service/src/main/resources/mapper/course/FsUserCourseVideoMapper.xml

@@ -109,6 +109,7 @@
             <if test="listingEndTime != null">listing_end_time,</if>
             <if test="userId != null">user_id,</if>
             <if test="isFirst != null">is_first,</if>
+            <if test="isSpeed != null">is_speed,</if>
         </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="fileId != null">#{fileId},</if>
@@ -148,6 +149,7 @@
             <if test="projectId != null">#{projectId},</if>
             <if test="userId != null">#{userId},</if>
             <if test="isFirst != null">#{isFirst},</if>
+            <if test="isSpeed != null">#{isSpeed},</if>
         </trim>
     </insert>
     <insert id="insertBatchFsUserCourseVideo" parameterType="FsUserCourseVideo" useGeneratedKeys="true" keyProperty="videoId">
@@ -236,6 +238,7 @@
             <if test="listingEndTime != null">listing_end_time = #{listingEndTime},</if>
             <if test="projectId != null">project_id = #{projectId},</if>
             <if test="isFirst != null">is_first = #{isFirst},</if>
+            <if test="isSpeed != null">is_speed = #{isSpeed},</if>
         </trim>
         where video_id = #{videoId}
     </update>
@@ -268,6 +271,7 @@
         video.course_id,
         video.STATUS,
         video.course_sort,
+        video.is_speed,
         course.course_name,
         fcpd.period_id,
         fcp.period_name,
@@ -323,6 +327,7 @@
         video.course_id,
         video.STATUS,
         video.course_sort,
+        video.is_speed,
         course.course_name,
         fcpd.period_id,
         fcp.period_name,

+ 7 - 0
fs-service/src/main/resources/mapper/hisStore/FsStoreOrderItemScrmMapper.xml

@@ -95,4 +95,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         </foreach>
     </delete>
 
+    <select id="selectFsStoreOrderItemListByOrderIds" resultType="com.fs.hisStore.vo.FsStoreOrderItemVO" parameterType="java.util.List" >
+        select * from fs_store_order_item_scrm where order_id in
+        <foreach item="itemId" collection="orderIds" open="(" separator="," close=")">
+            #{itemId}
+        </foreach>
+    </select>
+
 </mapper>

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

@@ -216,7 +216,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             fs_store_payment_scrm sp
                 LEFT JOIN fs_user fu ON sp.user_id = fu.user_id
         WHERE
-            sp.status = 1 and sp.is_shipment = 0 and sp.business_type = 1 AND sp.pay_time > '2025-11-27 00:00:00' LIMIT 500
+            sp.status = 1  and sp.app_id is not null  and sp.is_shipment = 0 and sp.business_type = 1 AND sp.pay_time > '2025-11-27 00:00:00'
+            order by sp.pay_time desc LIMIT 500
     </select>
 
     <update id="batchUpadte">
@@ -225,4 +226,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             #{bankTransactionId}
         </foreach>
     </update>
+
+    <update id="batchUpadteFailed">
+        update fs_store_payment_scrm set is_shipment = -1 where bank_transaction_id in
+        <foreach item="bankTransactionId" collection="list" open="(" separator="," close=")">
+            #{bankTransactionId}
+        </foreach>
+    </update>
 </mapper>

+ 6 - 1
fs-service/src/main/resources/mapper/hisStore/FsStoreProductPackageScrmMapper.xml

@@ -20,10 +20,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="cateId"    column="cate_id"    />
         <result property="sort"    column="sort"    />
         <result property="status"    column="status"    />
+        <result property="integral"    column="integral"    />
     </resultMap>
 
     <sql id="selectFsStoreProductPackageVo">
-        select package_id, title, descs, content, img_url,images, products, money, pay_money, company_id, dept_id,pay_type,limit_count,cate_id,sort,status from fs_store_product_package_scrm
+        select package_id, title, descs, content, img_url,images, products, money, pay_money, company_id,
+               dept_id,pay_type,limit_count,cate_id,sort,status,integral from fs_store_product_package_scrm
     </sql>
 
     <select id="selectFsStoreProductPackageList" parameterType="FsStoreProductPackageScrm" resultMap="FsStoreProductPackageResult">
@@ -70,6 +72,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="cateId != null">cate_id,</if>
             <if test="sort != null">sort,</if>
             <if test="status != null">status,</if>
+            <if test="integral != null">integral,</if>
          </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="packageId != null">#{packageId},</if>
@@ -88,6 +91,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="cateId != null">#{cateId},</if>
             <if test="sort != null">#{sort},</if>
             <if test="status != null">#{status},</if>
+            <if test="integral != null">#{integral},</if>
          </trim>
     </insert>
 
@@ -109,6 +113,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="cateId != null">cate_id = #{cateId},</if>
             <if test="sort != null">sort = #{sort},</if>
             <if test="status != null">status = #{status},</if>
+            <if test="integral != null">integral = #{integral},</if>
         </trim>
         where package_id = #{packageId}
     </update>

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

@@ -2034,5 +2034,8 @@
         <include refid="selectFsUserVo"/>
         where phone = #{phone}
     </select>
+    <select id="selectUserVOList" resultType="com.fs.hisStore.vo.UserScrmVo">
+        select user_id id,CONCAT_WS('-',nick_name) `name` from fs_user  where  phone =#{phone}
+    </select>
 
 </mapper>

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

@@ -66,11 +66,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         select las.id, las.live_id, las.store_id, las.order_id, las.refund_amount,
         las.refund_type, las.reasons, las.explains, las.explain_img, las.delivery_code, las.delivery_sn, las.delivery_name, las.status, las.sales_status,
         las.order_status, las.create_time, las.is_del, las.user_id, las.consignee, las.phone_number, las.address, las.company_id, las.company_user_id, las.dept_id,
-        cu.nick_name as company_user_nick_name, c.company_name,lo.order_code,lo.user_phone,las.user_id
+        cu.nick_name as company_user_nick_name, c.company_name,lo.order_id,lo.order_code,lo.user_phone,las.user_id,lo.item_json,lo.pay_time as orderPayTime,lo.user_address,lo.user_name,lo.pay_price,lo.total_postage,lop.bank_serial_no
         from live_after_sales las
         left join live_order lo on lo.order_id = las.order_id
         left join company_user cu on cu.user_id = las.company_user_id
         left join company c on c.company_id = cu.company_id
+        left join live_order_payment lop on lop.business_id = lo.order_id and lop.status in (1,-1)
 
         <where>
             <if test="liveId != null and liveId != ''"> and las.live_id = #{liveId}</if>

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

@@ -148,4 +148,13 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             #{itemId}
         </foreach>
     </delete>
+
+    <select id="selectLiveOrderItemListUVOByOrderIds" resultType="com.fs.live.vo.LiveOrderItemListUVO"  parameterType="java.util.List">
+        select product_id,json_info,order_code,item_id,num,order_id from live_order_item
+        where order_id in
+        <foreach item="itemId" collection="orderIds" open="(" separator="," close=")">
+            #{itemId}
+        </foreach>
+
+    </select>
 </mapper>

+ 6 - 0
fs-user-app/Dockerfile

@@ -0,0 +1,6 @@
+FROM openjdk:8-jre
+# java版本,最好使用openjdk,而不是类似于Java:1.8
+COPY ./target/fs-user-app.jar fs-user-app.jar
+# 向外暴露的接口,最好与项目yml文件中的端口一致
+ENTRYPOINT ["java","-jar","fs-user-app.jar"]
+# 执行启动命令java -jar

+ 2 - 0
fs-user-app/pom.xml

@@ -143,6 +143,8 @@
                     <compilerArgs>
                         <arg>-parameters</arg>
                     </compilerArgs>
+                    <source>8</source>
+                    <target>8</target>
                 </configuration>
             </plugin>
         </plugins>

+ 19 - 0
fs-user-app/src/main/java/com/fs/app/controller/course/CourseQwController.java

@@ -19,6 +19,7 @@ import com.fs.course.param.*;
 import com.fs.course.service.*;
 import com.fs.course.service.impl.TencentCloudCosService;
 import com.fs.course.vo.*;
+import com.fs.fastGpt.service.IFastgptEventLogTotalService;
 import com.fs.his.enums.FsUserOperationEnum;
 import com.fs.his.service.IFsIntegralGoodsService;
 import com.fs.sop.domain.QwSop;
@@ -360,6 +361,14 @@ public class CourseQwController extends AppBaseController {
 //        courseVideoService.updateVideoUrl();
     }
 
+    @Autowired
+    private IFsCourseTrafficLogService courseTrafficLogService;
+
+    @GetMapping("/test4")
+    public void test4() {
+        courseTrafficLogService.batchDelTraffic();
+    }
+
     @Login
     @ApiOperation("保存评论数据")
     @PostMapping("/saveMsg")
@@ -453,4 +462,14 @@ public class CourseQwController extends AppBaseController {
 
     }
 
+    @Autowired
+    private IFastgptEventLogTotalService fastgptEventLogTotalService;
+
+    @ApiOperation("手动同步AI的token")
+    @GetMapping("/eventLogTotals")
+    public R eventLogTotals(String startTime,String endTime){
+        fastgptEventLogTotalService.eventLogTotals(startTime,endTime);
+        return R.ok();
+    }
+
 }

+ 1 - 1
fs-user-app/src/main/java/com/fs/app/controller/live/LiveController.java

@@ -57,7 +57,7 @@ public class LiveController extends AppBaseController {
 	private LiveFacadeService liveFacadeService;
 
 	/**
-	 * 查询未结束直播间
+	 * 查询未结束直播间(销售专用)
 	 */
 	@GetMapping("/listToLiveNoEnd")
 	public TableDataInfo listToLiveNoEnd(Live live)

+ 34 - 6
fs-user-app/src/main/java/com/fs/app/controller/live/LiveGoodsController.java

@@ -2,6 +2,7 @@ package com.fs.app.controller.live;
 
 import com.fs.app.controller.AppBaseController;
 import com.fs.common.annotation.Log;
+import com.fs.common.constant.LiveKeysConstant;
 import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.redis.RedisCache;
@@ -25,7 +26,10 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
 
 import java.util.Date;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
 
 /**
  * 直播商品Controller
@@ -134,13 +138,37 @@ public class LiveGoodsController extends AppBaseController
     @GetMapping("/liveGoodsDetail/{productId}")
     public R liveGoodsDetail(@PathVariable Long productId)
     {
-        FsStoreProductScrm product = fsStoreProductService.selectFsStoreProductById(productId);
-        if(product==null){
-            return R.error("商品不存在或已下架");
+        // 先从缓存中获取商品详情
+        String cacheKey = String.format(LiveKeysConstant.PRODUCT_DETAIL_CACHE, productId);
+        Map<String, Object> cachedData = redisCache.getCacheObject(cacheKey);
+        
+        FsStoreProductScrm product;
+        List<FsStoreProductAttrScrm> productAttr;
+        List<FsStoreProductAttrValueScrm> productValues;
+        
+        if (cachedData != null) {
+            // 从缓存中获取数据
+            product = (FsStoreProductScrm) cachedData.get("product");
+            productAttr = (List<FsStoreProductAttrScrm>) cachedData.get("productAttr");
+            productValues = (List<FsStoreProductAttrValueScrm>) cachedData.get("productValues");
+        } else {
+            // 缓存中没有,从数据库查询
+            product = fsStoreProductService.selectFsStoreProductById(productId);
+            if(product==null){
+                return R.error("商品不存在或已下架");
+            }
+            productAttr = attrService.selectFsStoreProductAttrByProductId(productId);
+            productValues = attrValueService.selectFsStoreProductAttrValueByProductId(productId);
+            
+            // 将数据存入缓存
+            Map<String, Object> cacheData = new HashMap<>();
+            cacheData.put("product", product);
+            cacheData.put("productAttr", productAttr);
+            cacheData.put("productValues", productValues);
+            redisCache.setCacheObject(cacheKey, cacheData, LiveKeysConstant.PRODUCT_DETAIL_CACHE_EXPIRE, TimeUnit.SECONDS);
         }
-        List<FsStoreProductAttrScrm> productAttr=attrService.selectFsStoreProductAttrByProductId(productId);
-        List<FsStoreProductAttrValueScrm> productValues=attrValueService.selectFsStoreProductAttrValueByProductId(productId);
-//获取用户的TOKEN写入足迹
+        
+        // 获取用户的TOKEN写入足迹
         String userId=getUserId();
         if(userId!=null){
             FsStoreProductRelationScrm productRelation=new FsStoreProductRelationScrm();

+ 13 - 4
fs-user-app/src/main/java/com/fs/app/controller/live/LiveOrderController.java

@@ -722,6 +722,11 @@ public class LiveOrderController extends AppBaseController
     @PostMapping("/editPayType")
     @Transactional
     public R editPayType(HttpServletRequest request, @Validated @RequestBody FsStoreOrderPayParam param) {
+
+        String orderId=redisCache.getCacheObject("isPaying:"+param.getOrderId());
+        if(StringUtils.isNotEmpty(orderId)&&orderId.equals(param.getOrderId().toString())){
+            return R.error("正在支付中...");
+        }
         LiveOrder order=orderService.selectLiveOrderByOrderId(String.valueOf(param.getOrderId()));
         if(order==null){
             return R.error("订单不存在");
@@ -729,10 +734,6 @@ public class LiveOrderController extends AppBaseController
         if(order.getStatus()!= OrderInfoEnum.STATUS_0.getValue()){
             return R.error("订单状态不正确");
         }
-        String orderId=redisCache.getCacheObject("isPaying:"+order.getOrderId());
-        if(StringUtils.isNotEmpty(orderId)&&orderId.equals(order.getOrderId().toString())){
-            return R.error("正在支付中...");
-        }
         List<LiveOrderPayment> payments = liveOrderPaymentMapper.selectLiveOrderPaymentByOrderId(order.getOrderId());
         if(payments.size()>0){
             for(LiveOrderPayment payment : payments){
@@ -789,6 +790,14 @@ public class LiveOrderController extends AppBaseController
         }
 
     }
+    @Login
+    @ApiOperation("修改支付类型")
+    @PostMapping("/clearPayType")
+    @Transactional
+    public R clearPayType(HttpServletRequest request, @Validated @RequestBody FsStoreOrderPayParam param) {
+        redisCache.deleteObject("isPaying:"+param.getOrderId());
+        return R.ok();
+    }
 
 
 //    @Login

+ 1 - 0
fs-user-app/src/main/java/com/fs/app/controller/live/LiveWatchUserController.java

@@ -5,6 +5,7 @@ import com.fs.app.facade.LiveFacadeService;
 import com.fs.common.annotation.Log;
 import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.domain.R;
 import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.enums.BusinessType;
 import com.fs.live.domain.LiveWatchUser;

+ 18 - 4
fs-user-app/src/main/java/com/fs/app/controller/store/CouponScrmController.java

@@ -4,16 +4,14 @@ package com.fs.app.controller.store;
 import cn.hutool.core.util.IdUtil;
 import com.fs.app.annotation.Login;
 import com.fs.app.controller.AppBaseController;
+import com.fs.common.annotation.RepeatSubmit;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.redis.RedisCache;
 import com.fs.common.param.BaseQueryParam;
 import com.fs.common.utils.StringUtils;
 import com.fs.hisStore.domain.FsStoreCouponIssueScrm;
 import com.fs.hisStore.domain.FsStoreCouponUserScrm;
-import com.fs.hisStore.param.FsCouponIssueParam;
-import com.fs.hisStore.param.FsCouponUserEnableParam;
-import com.fs.hisStore.param.FsCouponUserParam;
-import com.fs.hisStore.param.FsStoreCouponReceiveParam;
+import com.fs.hisStore.param.*;
 import com.fs.hisStore.service.IFsStoreCouponIssueScrmService;
 import com.fs.hisStore.service.IFsStoreCouponScrmService;
 import com.fs.hisStore.service.IFsStoreCouponUserScrmService;
@@ -137,5 +135,21 @@ public class CouponScrmController extends AppBaseController {
         }
     }
 
+    /**
+     * 发送会员优惠券
+     */
+    @RepeatSubmit
+    @Login
+    @PostMapping("/sendCoupon")
+    public R sendCoupon(@RequestBody FsUserCouponScrmSendParam param)
+    {
+        Long companyUserId = getCompanyUserId();
+        if(companyUserId==null){
+            return R.error("销售未登录!");
+        }
+        param.setSetSendUserId(companyUserId);
+        return fsStoreCouponIssueService.sendFsUserCoupon(param);
+    }
+
 
 }

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

@@ -2,10 +2,14 @@ package com.fs.app.controller.store;
 
 
 import cn.hutool.json.JSONUtil;
+import com.baomidou.mybatisplus.core.conditions.Wrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.fs.app.annotation.Login;
 import com.fs.app.controller.AppBaseController;
 import com.fs.app.vo.IndexVO;
 import com.fs.common.core.domain.R;
+import com.fs.course.domain.FsCoursePlaySourceConfig;
+import com.fs.course.service.IFsCoursePlaySourceConfigService;
 import com.fs.his.config.AgreementConfig;
 import com.fs.hisStore.domain.*;
 import com.fs.hisStore.param.*;
@@ -57,13 +61,16 @@ public class IndexScrmController extends AppBaseController {
 	private IFsStoreCanvasScrmService storeCanvasService;
 	@Autowired
 	private ISysConfigService configService;
+	@Autowired
+	private IFsCoursePlaySourceConfigService coursePlaySourceConfigService;
+
 	@ApiOperation("获取首页数据")
 	@GetMapping("/getIndexData")
 	public R getIndexData(HttpServletRequest request){
 		List<FsArticleCateListQueryVO> articleCateList=articleCateService.selectFsArticleCateListQuery();
 		List<FsAdvListQueryVO> advList=advService.selectFsAdvListQuery(1);
 		List<FsStoreProductListQueryVO> newProductList=productService.selectFsStoreProductNewQuery(10);
-		List<FsStoreProductListQueryVO> hotProductList=productService.selectFsStoreProductHotQuery(10);
+		List<FsStoreProductListQueryVO> hotProductList=productService.selectFsStoreProductHotQuery(12);
 		IndexVO vo=IndexVO.builder().articleCateList(articleCateList).advList(advList).newProductList(newProductList).hotProductList(hotProductList).build();
 		return R.ok().put("data", vo);
 	}
@@ -287,5 +294,24 @@ public class IndexScrmController extends AppBaseController {
 		return R.ok().put("data",data);
 	}
 
+	/**
+	 * 获取小程序logo
+	 * @return
+	 */
+	@GetMapping("/getAppInfo")
+	public R getAppInfo(@RequestParam("appId") String appId)
+	{
+		Wrapper<FsCoursePlaySourceConfig> queryWrapper = Wrappers.<FsCoursePlaySourceConfig>lambdaQuery()
+				.eq(FsCoursePlaySourceConfig::getAppid, appId)
+				.eq(FsCoursePlaySourceConfig::getIsDel, 0)
+				.last("limit 1");
+		FsCoursePlaySourceConfig sourceConfig = coursePlaySourceConfigService.getOne(queryWrapper);
+		FsCoursePlaySourceConfig vo = new FsCoursePlaySourceConfig();
+		vo.setAppid(appId);
+		vo.setImg(sourceConfig.getImg());
+		vo.setName(sourceConfig.getName());
+		return R.ok().put("data",vo);
+	}
+
 
 }

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است