Explorar o código

Merge remote-tracking branch 'origin/master' into matser

吴树波 hai 4 días
pai
achega
4003002d8d
Modificáronse 67 ficheiros con 1371 adicións e 246 borrados
  1. 4 0
      fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreHealthOrderScrmController.java
  2. 21 0
      fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreOrderScrmController.java
  3. 5 5
      fs-admin/src/main/java/com/fs/qw/qwTask/qwTask.java
  4. 2 2
      fs-admin/src/main/resources/application.yml
  5. 1 1
      fs-ai-call-task/src/main/resources/application.yml
  6. 4 4
      fs-cid-workflow/src/main/java/com/fs/app/task/CidTask.java
  7. 1 1
      fs-cid-workflow/src/main/resources/application.yml
  8. 19 0
      fs-company/src/main/java/com/fs/company/controller/crm/CrmCustomerController.java
  9. 4 0
      fs-company/src/main/java/com/fs/company/controller/live/LiveDataController.java
  10. 60 5
      fs-company/src/main/java/com/fs/company/controller/store/FsStoreOrderController.java
  11. 41 0
      fs-company/src/main/java/com/fs/hisStore/controller/FsStoreOrderScrmController.java
  12. 150 17
      fs-ipad-task/src/main/java/com/fs/app/task/SendSmsMsg.java
  13. 1 1
      fs-service/src/main/java/com/fs/common/service/ISmsService.java
  14. 3 1
      fs-service/src/main/java/com/fs/common/service/impl/SmsServiceImpl.java
  15. 6 0
      fs-service/src/main/java/com/fs/company/mapper/CompanySmsLogsMapper.java
  16. 1 1
      fs-service/src/main/java/com/fs/company/mapper/CompanyWxClientMapper.java
  17. 1 1
      fs-service/src/main/java/com/fs/company/service/ICompanyWxClientService.java
  18. 6 2
      fs-service/src/main/java/com/fs/company/service/impl/CompanyVoiceRoboticServiceImpl.java
  19. 2 2
      fs-service/src/main/java/com/fs/company/service/impl/CompanyWxClientServiceImpl.java
  20. 8 1
      fs-service/src/main/java/com/fs/company/service/impl/call/node/AiCallTaskNode.java
  21. 4 0
      fs-service/src/main/java/com/fs/company/vo/AiCallConfigVO.java
  22. 1 0
      fs-service/src/main/java/com/fs/course/config/CourseConfig.java
  23. 2 0
      fs-service/src/main/java/com/fs/course/mapper/FsCourseWatchLogMapper.java
  24. 27 0
      fs-service/src/main/java/com/fs/course/param/newfs/FsUserCourseVideoRemainTimeParam.java
  25. 2 0
      fs-service/src/main/java/com/fs/course/service/IFsCourseWatchLogService.java
  26. 5 0
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseWatchLogServiceImpl.java
  27. 6 1
      fs-service/src/main/java/com/fs/enums/ExecutionStatusEnum.java
  28. 14 0
      fs-service/src/main/java/com/fs/his/config/AppConfig.java
  29. 2 0
      fs-service/src/main/java/com/fs/his/enums/BusinessTypeEnum.java
  30. 2 0
      fs-service/src/main/java/com/fs/his/enums/PaymentMethodEnum.java
  31. 5 0
      fs-service/src/main/java/com/fs/his/param/FsIntegralOrderDoPayParam.java
  32. 23 0
      fs-service/src/main/java/com/fs/his/vo/GameVo.java
  33. 12 0
      fs-service/src/main/java/com/fs/hisStore/domain/FsStoreOrderScrm.java
  34. 8 1
      fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreCartScrmMapper.java
  35. 8 2
      fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreOrderItemScrmMapper.java
  36. 3 0
      fs-service/src/main/java/com/fs/hisStore/mapper/FsUserAddressScrmMapper.java
  37. 4 0
      fs-service/src/main/java/com/fs/hisStore/service/IFsStoreOrderScrmService.java
  38. 3 0
      fs-service/src/main/java/com/fs/hisStore/service/IFsStorePaymentScrmService.java
  39. 78 3
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreOrderScrmServiceImpl.java
  40. 363 0
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStorePaymentScrmServiceImpl.java
  41. 10 1
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreProductScrmServiceImpl.java
  42. 35 1
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreUserEndCategoryScrmServiceImpl.java
  43. 3 1
      fs-service/src/main/java/com/fs/hisStore/vo/FsStoreOrderItemExportVO.java
  44. 3 0
      fs-service/src/main/java/com/fs/hisStore/vo/FsStoreProductListQueryVO.java
  45. 7 7
      fs-service/src/main/java/com/fs/hisStore/vo/FsStoreUserEndCategoryProductVO.java
  46. 6 0
      fs-service/src/main/java/com/fs/hisStore/vo/OrderStatisticsVo.java
  47. 8 0
      fs-service/src/main/java/com/fs/qw/mapper/QwSopSmsLogsMapper.java
  48. 5 0
      fs-service/src/main/java/com/fs/qw/mapper/QwUserMapper.java
  49. 7 0
      fs-service/src/main/java/com/fs/qw/service/IQwSopSmsLogsService.java
  50. 5 0
      fs-service/src/main/java/com/fs/qw/service/impl/QwSopSmsLogsServiceImpl.java
  51. 21 1
      fs-service/src/main/java/com/fs/wxwork/service/WxWorkServiceImpl.java
  52. 5 1
      fs-service/src/main/resources/application-config-druid-jsbk.yml
  53. 4 4
      fs-service/src/main/resources/application-dev-xcsw.yml
  54. 3 4
      fs-service/src/main/resources/application-druid-jzzx.yml
  55. 4 4
      fs-service/src/main/resources/application-druid-xcsw.yml
  56. 13 0
      fs-service/src/main/resources/mapper/company/CompanySmsLogsMapper.xml
  57. 1 1
      fs-service/src/main/resources/mapper/company/CompanyWxClientMapper.xml
  58. 100 138
      fs-service/src/main/resources/mapper/hisStore/FsStoreOrderScrmMapper.xml
  59. 8 16
      fs-service/src/main/resources/mapper/hisStore/FsStoreProductScrmMapper.xml
  60. 4 4
      fs-service/src/main/resources/mapper/hisStore/FsStoreProductUserEndCategoryMapper.xml
  61. 6 3
      fs-service/src/main/resources/mapper/live/LiveDataMapper.xml
  62. 38 6
      fs-user-app/src/main/java/com/fs/app/controller/CompanyUserController.java
  63. 74 0
      fs-user-app/src/main/java/com/fs/app/controller/app/AppController.java
  64. 86 0
      fs-user-app/src/main/java/com/fs/app/controller/course/CourseFsUserController.java
  65. 1 1
      fs-wx-api/src/main/resources/application.yml
  66. 1 1
      fs-wx-task/src/main/java/com/fs/app/service/WxTaskService.java
  67. 1 1
      fs-wx-task/src/main/resources/application.yml

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

@@ -309,6 +309,8 @@ public class FsStoreHealthOrderScrmController extends BaseController {
                 for (FsStoreOrderItemExportZMVO vo : zmvoList) {
                     if("2".equals(vo.getOrderType())){
                         vo.setOrderTypeStr("直播订单" );
+                    }else if ("3".equals(vo.getOrderType())){
+                        vo.setOrderTypeStr("点播订单" );
                     }else{
                         vo.setOrderTypeStr("商城订单" );
                     }
@@ -418,6 +420,8 @@ public class FsStoreHealthOrderScrmController extends BaseController {
                     for (FsStoreOrderItemExportZMVO vo : zmvoList) {
                         if ("2".equals(vo.getOrderType())) {
                             vo.setOrderTypeStr("直播订单");
+                        }else if ("3".equals(vo.getOrderType())){
+                            vo.setOrderTypeStr("点播订单" );
                         }else {
                             vo.setOrderTypeStr("商城订单");
                         }

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

@@ -19,8 +19,12 @@ import com.fs.common.utils.ParseUtils;
 import com.fs.common.utils.ServletUtils;
 import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.company.domain.Company;
+import com.fs.company.domain.CompanyUser;
 import com.fs.company.param.CompanyStoreOrderMoneyLogsListParam;
 import com.fs.company.service.ICompanyMoneyLogsService;
+import com.fs.company.service.ICompanyService;
+import com.fs.company.service.ICompanyUserService;
 import com.fs.company.vo.CompanyStoreOrderMoneyLogsVO;
 import com.fs.config.cloud.CloudHostProper;
 import com.fs.erp.domain.ErpDeliverys;
@@ -150,6 +154,11 @@ public class FsStoreOrderScrmController extends BaseController {
     @Value("${cloud_host.company_name}")
     private String signProjectName;
 
+    @Autowired
+    private ICompanyUserService companyUserService;
+    @Autowired
+    private ICompanyService companyService;
+
     private IErpOrderService getErpService(){
         //判断是否开启erp
         IErpOrderService erpOrderService = null;
@@ -652,6 +661,18 @@ public class FsStoreOrderScrmController extends BaseController {
         if (user != null) {
             user.setPhone(ParseUtils.parsePhone(user.getPhone()));
         }
+
+        if (order.getCompanyUserId() != null) {
+            CompanyUser companyUser = companyUserService.selectCompanyUserByUserId(order.getCompanyUserId());
+            Company company = companyService.selectCompanyById(companyUser.getCompanyId());
+            order.setCompanyUserName(companyUser.getUserName());
+            order.setCompanyName(company.getCompanyName());
+        } else if (order.getCompanyId() != null) {
+            Company company = companyService.selectCompanyById(order.getCompanyId());
+            order.setCompanyName(company.getCompanyName());
+        }
+
+
         FsStoreOrderItemScrm itemMap = new FsStoreOrderItemScrm();
         itemMap.setOrderId(order.getId());
         List<FsStoreOrderItemScrm> items = orderItemService.selectFsStoreOrderItemList(itemMap);

+ 5 - 5
fs-admin/src/main/java/com/fs/qw/qwTask/qwTask.java

@@ -296,11 +296,11 @@ public class qwTask {
     }
 
     public void updateIpadStatus(QwUser qwUser,Long serverId){
-        QwUser u = new QwUser();
-        u.setId(qwUser.getId());
-        u.setServerId(null);
-        u.setServerStatus(0);
-        qwUserMapper.updateQwUser(u);
+//        QwUser u = new QwUser();
+//        u.setId(qwUser.getId());
+//        u.setVid(null);
+//        u.setServerStatus(0);
+        qwUserMapper.updateQwUserByUnbindIpad(qwUser.getId());
         ipadServerService.addServer(serverId);
         QwIpadServerLog qwIpadServerLog = new QwIpadServerLog();
         qwIpadServerLog.setType(2);

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

@@ -4,11 +4,11 @@ server:
 # Spring配置
 spring:
   profiles:
-#    active: druid-ylrz
+    active: druid-bjzm-test
 #    active: druid-hdt
 #    active: druid-yzt
 #    active: druid-sxjz-test
 #    active: druid-sft
 #    active: druid-fby
-    active: dev
+#    active: dev
 

+ 1 - 1
fs-ai-call-task/src/main/resources/application.yml

@@ -14,4 +14,4 @@ spring:
 #    active: druid-sxjz
 #    active: druid-hdt
 #    active: druid-myhk-test
-cid-group-no: 1
+cid-group-no: 3

+ 4 - 4
fs-cid-workflow/src/main/java/com/fs/app/task/CidTask.java

@@ -43,10 +43,10 @@ public class CidTask {
     /**
      * 扫描服务定时任务执行
      */
-    @Scheduled(cron = "0 0/1 * * * ?")
-    public void runContinueTask() {
-        cidWorkflowTaskService.runContinueTask();
-    }
+//    @Scheduled(cron = "0 0/1 * * * ?")
+//    public void runContinueTask() {
+//        cidWorkflowTaskService.runContinueTask();
+//    }
 
 
 

+ 1 - 1
fs-cid-workflow/src/main/resources/application.yml

@@ -14,4 +14,4 @@ spring:
 #    active: druid-sxjz
 #    active: druid-hdt
 #    active: druid-myhk-test
-cid-group-no: 1
+cid-group-no: 3

+ 19 - 0
fs-company/src/main/java/com/fs/company/controller/crm/CrmCustomerController.java

@@ -115,6 +115,25 @@ public class CrmCustomerController extends BaseController
         }
         return R.ok().put("rows", list);
     }
+    @PreAuthorize("@ss.hasPermi('crm:customer:list')")
+    @GetMapping("/listNoPage")
+    public R listNoPage(CrmCustomerListQueryParam crmCustomer){
+
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        crmCustomer.setCompanyId(loginUser.getCompany().getCompanyId());
+        if(!StringUtils.isEmpty(crmCustomer.getReceiveTimeRange())){
+            crmCustomer.setReceiveTimeList(crmCustomer.getReceiveTimeRange().split("--"));
+        }
+        List<CrmCustomerListVO> list = crmCustomerService.selectCrmCustomerListQueryParam(crmCustomer);
+        if (list != null) {
+            for (CrmCustomerListVO vo : list) {
+                if(vo.getMobile()!=null){
+                    vo.setMobile(vo.getMobile().replaceAll("(\\d{3})\\d*(\\d{4})", "$1****$2"));
+                }
+            }
+        }
+        return R.ok().put("rows", list);
+    }
 
     @ApiOperation("获取我的协作客户列表")
     @PreAuthorize("@ss.hasPermi('crm:customer:assistList')")

+ 4 - 0
fs-company/src/main/java/com/fs/company/controller/live/LiveDataController.java

@@ -1,6 +1,7 @@
 package com.fs.company.controller.live;
 
 import com.fs.common.annotation.Log;
+import com.fs.common.constant.HttpStatus;
 import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.core.domain.R;
@@ -12,9 +13,11 @@ import com.fs.framework.security.LoginUser;
 import com.fs.framework.security.SecurityUtils;
 import com.fs.framework.service.TokenService;
 import com.fs.live.domain.LiveData;
+import com.fs.live.param.LiveDataCompanyParam;
 import com.fs.live.param.LiveDataParam;
 import com.fs.live.service.ILiveDataService;
 import com.fs.live.vo.ColumnsConfigVo;
+import com.fs.live.vo.LiveDataCompanyVO;
 import com.fs.live.vo.LiveDataListVo;
 import com.fs.live.vo.LiveUserDetailExportVO;
 import com.github.pagehelper.PageHelper;
@@ -24,6 +27,7 @@ import org.springframework.web.bind.annotation.*;
 
 import javax.servlet.http.HttpServletRequest;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 

+ 60 - 5
fs-company/src/main/java/com/fs/company/controller/store/FsStoreOrderController.java

@@ -83,14 +83,18 @@ public class FsStoreOrderController extends BaseController
     private IFsStoreOrderItemScrmService orderItemScrmService;
 
     /**
-     * 查询直播订单列表(仅 fs_store_order_scrm 中 order_type=2)
-     * 分公司负责人(userType=00)可查公司下所有直播订单,否则仅能查自己的直播订单
+     * 查询直播/点播订单列表(fs_store_order_scrm 中 order_type=2 直播订单,order_type=3 点播订单)
+     * 如果前端传了 orderType,则按指定类型查询;如果没传(null),则查询所有直播和点播订单(orderType IN (2,3))
+     * 分公司负责人(userType=00)可查公司下所有订单,否则仅能查自己的订单
      */
     @PostMapping("/healthLiveList")
     public FsStoreOrderListAndStatisticsVo healthLiveList(@RequestBody com.fs.hisStore.param.FsStoreOrderParam param) {
         LoginUser loginUser = SecurityUtils.getLoginUser();
         param.setCompanyId(loginUser.getCompany().getCompanyId());
-        param.setOrderType(2);
+        // 如果前端传了 orderType,使用前端传的值;如果没传(null),设置 orderType = -1(特殊值,SQL 中会转换为查询 orderType IN (2,3))
+        if (param.getOrderType() == null) {
+            param.setOrderType(-1); // 特殊值,表示查询所有直播和点播订单
+        }
         if (!"00".equals(loginUser.getUser().getUserType())) {
             param.setCompanyUserId(loginUser.getUser().getUserId());
         } else {
@@ -147,11 +151,14 @@ public class FsStoreOrderController extends BaseController
         return vo;
     }
 
-    /** 直播订单导出:筛选条件与 healthLiveList 一致(orderType=2 + 公司/负责人权限) */
+    /** 直播/点播订单导出:筛选条件与 healthLiveList 一致(支持按 orderType 筛选,不传则查询所有 + 公司/负责人权限) */
     private void applyHealthLiveFilter(com.fs.hisStore.param.FsStoreOrderParam param) {
         LoginUser loginUser = SecurityUtils.getLoginUser();
         param.setCompanyId(loginUser.getCompany().getCompanyId());
-        param.setOrderType(2);
+        // 如果前端传了 orderType,使用前端传的值;如果没传(null),设置 orderType = -1(特殊值,SQL 中会转换为查询 orderType IN (2,3))
+        if (param.getOrderType() == null) {
+            param.setOrderType(-1); // 特殊值,表示查询所有直播和点播订单
+        }
         if (!"00".equals(loginUser.getUser().getUserType())) {
             param.setCompanyUserId(loginUser.getUser().getUserId());
         } else {
@@ -212,6 +219,17 @@ public class FsStoreOrderController extends BaseController
                 if (vo.getUserAddress() != null) {
                     vo.setUserAddress(ParseUtils.parseAddress(vo.getUserAddress()));
                 }
+                // 设置订单类型中文显示
+                if (vo.getOrderType() != null) {
+                    String orderTypeStr = vo.getOrderType().toString();
+                    if ("2".equals(orderTypeStr)) {
+                        vo.setOrderType("直播订单");
+                    } else if ("3".equals(orderTypeStr)) {
+                        vo.setOrderType("点播订单");
+                    } else {
+                        vo.setOrderType("商城订单");
+                    }
+                }
             }
         }
         String filter = param.getFilter();
@@ -243,6 +261,21 @@ public class FsStoreOrderController extends BaseController
             return AjaxResult.error("请筛选数据导出");
         }
         List<FsStoreOrderErpExportVO> list = fsStoreOrderScrmService.selectFsStoreOrderListVOByExport(param);
+        if (list != null) {
+            for (FsStoreOrderErpExportVO vo : list) {
+                // 设置订单类型中文显示
+                if (vo.getOrderType() != null) {
+                    String orderTypeStr = vo.getOrderType().toString();
+                    if ("2".equals(orderTypeStr)) {
+                        vo.setOrderType("直播订单");
+                    } else if ("3".equals(orderTypeStr)) {
+                        vo.setOrderType("点播订单");
+                    } else {
+                        vo.setOrderType("商城订单");
+                    }
+                }
+            }
+        }
         String filter = param.getFilter();
         ArrayList<String> filterList = new ArrayList<>();
         if (StringUtils.isNotBlank(filter)) {
@@ -296,6 +329,17 @@ public class FsStoreOrderController extends BaseController
                     vo.setCateName("");
                     vo.setBankTransactionId("");
                 }
+                // 设置订单类型中文显示
+                if (vo.getOrderType() != null) {
+                    String orderTypeStr = vo.getOrderType().toString();
+                    if ("2".equals(orderTypeStr)) {
+                        vo.setOrderType("直播订单");
+                    } else if ("3".equals(orderTypeStr)) {
+                        vo.setOrderType("点播订单");
+                    } else {
+                        vo.setOrderType("商城订单");
+                    }
+                }
             }
         }
         ExcelUtil<FsStoreOrderItemExportVO> util = new ExcelUtil<>(FsStoreOrderItemExportVO.class);
@@ -335,6 +379,17 @@ public class FsStoreOrderController extends BaseController
                     vo.setCateName("");
                     vo.setBankTransactionId("");
                 }
+                // 设置订单类型中文显示
+                if (vo.getOrderType() != null) {
+                    String orderTypeStr = vo.getOrderType().toString();
+                    if ("2".equals(orderTypeStr)) {
+                        vo.setOrderType("直播订单");
+                    } else if ("3".equals(orderTypeStr)) {
+                        vo.setOrderType("点播订单");
+                    } else {
+                        vo.setOrderType("商城订单");
+                    }
+                }
             }
         }
         ExcelUtil<FsStoreOrderItemExportVO> util = new ExcelUtil<>(FsStoreOrderItemExportVO.class);

+ 41 - 0
fs-company/src/main/java/com/fs/hisStore/controller/FsStoreOrderScrmController.java

@@ -18,9 +18,18 @@ import com.fs.common.utils.ParseUtils;
 import com.fs.common.utils.ServletUtils;
 import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.company.domain.Company;
 import com.fs.company.domain.CompanyUser;
+import com.fs.company.service.ICompanyService;
 import com.fs.company.service.ICompanyUserService;
 import com.fs.config.cloud.CloudHostProper;
+import com.fs.course.domain.FsCourseWatchLog;
+import com.fs.course.domain.FsUserCoursePeriod;
+import com.fs.course.domain.FsUserCourseVideo;
+import com.fs.course.param.FsCourseWatchLogParam;
+import com.fs.course.service.IFsCourseWatchLogService;
+import com.fs.course.service.IFsUserCoursePeriodService;
+import com.fs.course.service.IFsUserCourseVideoService;
 import com.fs.crm.domain.CrmCustomer;
 import com.fs.crm.service.ICrmCustomerService;
 import com.fs.framework.security.LoginUser;
@@ -88,6 +97,14 @@ public class FsStoreOrderScrmController extends BaseController
     private ICompanyUserService companyUserService;
     @Autowired
     private CloudHostProper cloudHostProper;
+    @Autowired
+    private ICompanyService companyService;
+    @Autowired
+    private IFsCourseWatchLogService fsCourseWatchLogService;
+    @Autowired
+    private IFsUserCoursePeriodService fsUserCoursePeriodService;
+    @Autowired
+    private IFsUserCourseVideoService fsUserCourseVideoService;
 
     /**
      * 查询订单列表
@@ -237,6 +254,30 @@ public class FsStoreOrderScrmController extends BaseController
         FsStoreOrderScrm order=fsStoreOrderService.selectFsStoreOrderById(id);
         order.setUserPhone(ParseUtils.parsePhone(order.getUserPhone()));
         order.setUserAddress(ParseUtils.parseAddress(order.getUserAddress()));
+
+        if (order.getCompanyUserId() != null) {
+            CompanyUser companyUser = companyUserService.selectCompanyUserByUserId(order.getCompanyUserId());
+            Company company = companyService.selectCompanyById(companyUser.getCompanyId());
+            order.setCompanyUserName(companyUser.getUserName());
+            order.setCompanyName(company.getCompanyName());
+        } else if (order.getCompanyId() != null) {
+            Company company = companyService.selectCompanyById(order.getCompanyId());
+            order.setCompanyName(company.getCompanyName());
+        }
+        if (order.getOrderType() != null && order.getOrderType() == 3) {
+            FsCourseWatchLogParam param = new FsCourseWatchLogParam();
+            param.setVideoId(Long.valueOf(order.getVideoId()));
+            FsCourseWatchLog log = fsCourseWatchLogService.selectFsCourseWatchLogWithUCCV(order.getUserId(), order.getCompanyUserId(), order.getCourseId(), order.getVideoId());
+            if (log != null) {
+                FsUserCoursePeriod fsUserCoursePeriod = fsUserCoursePeriodService.selectFsUserCoursePeriodById(log.getPeriodId());
+                order.setPeriodName(fsUserCoursePeriod.getPeriodName());
+            }
+            if (order.getVideoId() != null) {
+                FsUserCourseVideo fsUserCourseVideo = fsUserCourseVideoService.selectFsUserCourseVideoByVideoId(Long.valueOf(order.getVideoId()));
+                order.setVideoName(fsUserCourseVideo.getTitle());
+            }
+        }
+
         FsUser user=userService.selectFsUserById(order.getUserId());
         user.setPhone(ParseUtils.parsePhone(user.getPhone()));
         FsStoreOrderItemScrm itemMap=new FsStoreOrderItemScrm();

+ 150 - 17
fs-ipad-task/src/main/java/com/fs/app/task/SendSmsMsg.java

@@ -1,14 +1,13 @@
 package com.fs.app.task;
 
-import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONObject;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.redis.RedisCache;
 import com.fs.common.service.ISmsService;
-import com.fs.common.utils.date.DateUtil;
-import com.fs.course.domain.FsCourseWatchLog;
+import com.fs.company.domain.CompanySmsLogs;
+import com.fs.company.mapper.CompanySmsLogsMapper;
 import com.fs.course.service.IFsCourseWatchLogService;
 import com.fs.his.domain.FsUser;
 import com.fs.his.dto.SendResultDetailDTO;
@@ -16,14 +15,11 @@ import com.fs.his.service.IFsUserService;
 import com.fs.his.utils.PhoneUtil;
 import com.fs.qw.domain.QwIpadServer;
 import com.fs.qw.domain.QwSopSmsLogs;
-import com.fs.qw.domain.QwUser;
 import com.fs.qw.mapper.QwIpadServerMapper;
 import com.fs.qw.service.IQwSopSmsLogsService;
-import com.fs.qw.vo.QwSopCourseFinishTempSetting;
 import com.fs.sop.domain.QwSopLogs;
 import com.fs.sop.mapper.QwSopLogsMapper;
 import com.fs.sop.service.IQwSopLogsService;
-import com.fs.sop.service.impl.QwSopLogsServiceImpl;
 import com.google.common.cache.Cache;
 import com.google.common.cache.CacheBuilder;
 import com.google.common.util.concurrent.RateLimiter;
@@ -33,7 +29,6 @@ import org.springframework.context.annotation.Lazy;
 import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.stereotype.Component;
 import org.springframework.util.StringUtils;
-
 import javax.annotation.PreDestroy;
 import java.time.LocalDateTime;
 import java.time.ZoneId;
@@ -66,16 +61,18 @@ public class SendSmsMsg {
 
     private final IQwSopLogsService qwSopLogsService;
 
+    private final CompanySmsLogsMapper companySmsLogsMapper;
+
     private final IFsCourseWatchLogService watchLogService;
 
     // 线程池配置
     private static final int CORE_POOL_SIZE = 50;
-    private static final int MAX_POOL_SIZE = 200;
-    private static final int QUEUE_CAPACITY = 1000;
+    private static final int MAX_POOL_SIZE = 150;
+    private static final int QUEUE_CAPACITY = 3000;
     private static final long KEEP_ALIVE_TIME = 60L;
 
     // 分页大小
-    private static final int PAGE_SIZE = 5000;
+    private static final int PAGE_SIZE = 3000;
 
     // 手机号缓存
     private final Cache<Long, String> phoneCache = CacheBuilder.newBuilder()
@@ -84,7 +81,7 @@ public class SendSmsMsg {
             .build();
 
     // 限流器:控制全局发送速率
-    private final RateLimiter rateLimiter = RateLimiter.create(1000); // 每秒1000条
+    private final RateLimiter rateLimiter = RateLimiter.create(500); // 每秒500条
 
     // 短信发送线程池
     private final ThreadPoolExecutor smsExecutor = new ThreadPoolExecutor(
@@ -99,7 +96,7 @@ public class SendSmsMsg {
     private final ExecutorService statusExecutor = Executors.newSingleThreadExecutor();
 
     // 批量失败状态更新阈值
-    private static final int BATCH_UPDATE_SIZE = 500;
+    private static final int BATCH_UPDATE_SIZE = 1000;
 
     public SendSmsMsg(QwIpadServerMapper qwIpadServerMapper,
                       IQwSopSmsLogsService qwSopSmsLogsService,
@@ -107,7 +104,8 @@ public class SendSmsMsg {
                       IFsUserService fsUserService, RedisCache redisCache,
                       QwSopLogsMapper qwSopLogsMapper,
                       IQwSopLogsService qwSopLogsService,
-                      IFsCourseWatchLogService watchLogService) {
+                      IFsCourseWatchLogService watchLogService,
+                      CompanySmsLogsMapper companySmsLogsMapper) {
         this.qwIpadServerMapper = qwIpadServerMapper;
         this.qwSopSmsLogsService = qwSopSmsLogsService;
         this.smsService = smsService;
@@ -116,10 +114,30 @@ public class SendSmsMsg {
         this.qwSopLogsMapper = qwSopLogsMapper;
         this.qwSopLogsService = qwSopLogsService;
         this.watchLogService = watchLogService;
+        this.companySmsLogsMapper = companySmsLogsMapper;
     }
 
     @Scheduled(cron = "0 0 * * * ?") // 每小时执行一次
     public synchronized void sendSms() {
+        // 添加错峰延迟
+        try {
+            if (!StringUtils.isEmpty(groupNo)) {
+                int groupNoInt = Integer.parseInt(groupNo.trim());
+                int delaySeconds = groupNoInt % 10;
+
+                log.info("【错峰执行】窗口 {} 将延迟 {} 秒后执行", groupNo, delaySeconds);
+
+                if (delaySeconds > 0) {
+                    Thread.sleep(delaySeconds * 1000L);
+                }
+            }
+        } catch (InterruptedException e) {
+            log.error("【错峰执行】窗口 {} 等待被中断", groupNo, e);
+            Thread.currentThread().interrupt();
+            return;
+        } catch (NumberFormatException e) {
+            log.warn("【错峰执行】groupNo 格式错误,不执行延迟:{}", groupNo, e);
+        }
         sendSms(null);
     }
 
@@ -258,8 +276,11 @@ public class SendSmsMsg {
             //数据走数据校验
             if(!sopLogsMap.containsKey(logRecord.getSopLogId())){
                 log.error("处理sopLogId {} 失败,sopLogsMap 中不存在执行记录", logRecord.getSopLogId());
+                failReasonsList.add(new SendResultDetailDTO(false, "sopLogsMap 中不存在执行记录", logRecord.getSopLogId()));
+                failedIds.add(logRecord.getId());
                 continue;
             }
+
             QwSopLogs qwSopLogs = sopLogsMap.get(logRecord.getSopLogId());
 
             // 判断消息状态是否满足发送条件
@@ -273,7 +294,13 @@ public class SendSmsMsg {
 
             String redisKey = groupNo + ":" + logRecord.getId();
             try {
-                redisCache.setCacheObject(redisKey, logRecord.getId(), 2, TimeUnit.HOURS);
+                Boolean locked = redisCache.setIfAbsent(redisKey, logRecord.getId(), 20, TimeUnit.MINUTES);
+
+                if (locked == null || !locked) {
+                    log.warn("记录 id={} 已有锁,跳过发送(可能正在发送或重复提交)", logRecord.getId());
+                    continue;
+                }
+
                 SendResultDetailDTO detail = sendSingleSms(logRecord, userPhoneMap, redisKey);
                 if (detail.isSuccess()) {
                     success++;
@@ -283,10 +310,10 @@ public class SendSmsMsg {
                     failedIds.add(logRecord.getId());
                 }
             } catch (Exception e) {
+                redisCache.deleteObject(redisKey); // 异常立即释放锁
                 log.error("发送异常 id={}", logRecord.getId(), e);
                 failReasonsList.add(new SendResultDetailDTO(false, e.getMessage(), logRecord.getSopLogId()));
                 failedIds.add(logRecord.getId());
-                redisCache.deleteObject(redisKey);
             }
 
             //批量阈值
@@ -362,7 +389,9 @@ public class SendSmsMsg {
                     logRecord.getSmsTemplateCode(),
                     logRecord.getSopLogId(),
                     logRecord.getSmsIndex(),
-                    redisKey
+                    redisKey,
+                    logRecord.getCompanyId(),
+                    logRecord.getCompanyUserId()
             );
 
             if (r != null && "200".equals(String.valueOf(r.get("code")))) {
@@ -482,8 +511,112 @@ public class SendSmsMsg {
     }
 
 
-    //处理特定场景,电脑被重启后,发送短信任务会丢失
+    /**
+     * 处理执行中被中断的数据(如服务重启导致的发送中状态残留)
+     * 每10分钟执行一次,加入Redis锁检查,避免重复发送
+     */
+    @Scheduled(cron = "0 0/10 * * * ?")
+    public synchronized void  processSendLogsTask() {
+        this.processSendLogs(null);
+    }
+
+    public void  processSendLogsTest(String num) {
+        this.processSendLogs(num);
+    }
+
+    public void processSendLogs(String num) {
+        if(!StringUtils.isEmpty(num)){
+            groupNo = num;
+        }
+        log.info("处理执行中被中断的数据---->: 开始执行,groupNo={}", groupNo);
+
+        if (StringUtils.isEmpty(groupNo)) {
+            log.warn("processSendLogs: groupNo 为空,跳过执行");
+            return;
+        }
+
+        long groupOn;
+        try {
+            groupOn = Long.parseLong(groupNo.trim());
+        } catch (NumberFormatException e) {
+            log.warn("processSendLogs: groupNo 无法转为数字, groupNo={}", groupNo);
+            return;
+        }
+
+        // 获取server_ids
+        List<Long> serverIds = getServerIds(groupOn);
+        if (serverIds.isEmpty()) {
+            log.info("processSendLogs: 分组 groupNo={} 无 server,跳过", groupNo);
+            return;
+        }
+
+        List<QwSopSmsLogs> batch = qwSopSmsLogsService.getQwSopSmsLogsStateList(serverIds);
+        if (batch.isEmpty()) {
+            log.info("processSendLogs: 无待处理记录,groupNo={}", groupNo);
+            return;
+        }
+
+        List<Long> uuIds = batch.stream()
+                .map(QwSopSmsLogs::getSopLogId)
+                .collect(Collectors.toList());
+        List<CompanySmsLogs> companySmsLogsList = companySmsLogsMapper.getCompanySmsLogsByUuIdList(uuIds);
+        Map<String, CompanySmsLogs> checkMap = companySmsLogsList.stream()
+                .collect(Collectors.toMap(
+                        c -> c.getSopSmsLogId() + "-" + c.getSmsIndex(),
+                        v -> v,
+                        (v1, v2) -> v1
+                ));
+
+        List<QwSopSmsLogs> needCheckList = batch.stream()
+                .filter(e -> !checkMap.containsKey(e.getSopLogId() + "-" + e.getSmsIndex()))
+                .collect(Collectors.toList());
+
+        if (needCheckList.isEmpty()) {
+            log.info("processSendLogs: 所有记录在企业日志中已存在,无需处理,groupNo={}", groupNo);
+            return;
+        }
 
+        List<Long> sopLogIds = needCheckList.stream()
+                .map(QwSopSmsLogs::getSopLogId)
+                .collect(Collectors.toList());
+        List<QwSopLogs> sopLogs = qwSopLogsMapper.getQwSopInfoByUid(sopLogIds);
+        Map<Long, QwSopLogs> sopLogsMap = sopLogs.stream()
+                .collect(Collectors.toMap(QwSopLogs::getSmsLogsId, s -> s, (v1, v2) -> v1));
+
+        AtomicLong totalProcessed = new AtomicLong(0);
+        AtomicLong totalFailed = new AtomicLong(0);
+        List<CompletableFuture<Void>> futures = new ArrayList<>();
+
+        // 按server_id分组,提交异步任务
+        Map<Long, List<QwSopSmsLogs>> groupByServer = needCheckList.stream()
+                .collect(Collectors.groupingBy(QwSopSmsLogs::getServerId));
+
+        for (Map.Entry<Long, List<QwSopSmsLogs>> entry : groupByServer.entrySet()) {
+            List<QwSopSmsLogs> serverBatch = entry.getValue();
+            CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
+                try {
+                    SendResult result = processServerBatch(serverBatch, sopLogsMap);
+                    totalProcessed.addAndGet(result.success);
+                    totalFailed.addAndGet(result.failed);
+                } catch (Exception e) {
+                    log.error("处理server {} 批次失败", entry.getKey(), e);
+                }
+            }, smsExecutor);
+            futures.add(future);
+        }
+
+        if (!futures.isEmpty()) {
+            try {
+                CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
+                        .get(1, TimeUnit.HOURS);
+            } catch (Exception e) {
+                log.error("processSendLogs: 等待任务完成时发生异常", e);
+            }
+        }
+
+        log.info("processSendLogs: 处理完成,groupNo={}, 成功={}, 失败={}",
+                groupNo, totalProcessed.get(), totalFailed.get());
+    }
 
 
     public SendResultDetailDTO isSendLogs(QwSopLogs qwSopLogs,QwSopSmsLogs logRecord) {

+ 1 - 1
fs-service/src/main/java/com/fs/common/service/ISmsService.java

@@ -24,6 +24,6 @@ public interface ISmsService
 
     R sendCaptcha(String phone, String captcha, String code);
 
-    R sendUrl(String phone, String content, String code,Long uuid,Integer smsIndex,String deleteKey);
+    R sendUrl(String phone, String content, String code,Long uuid,Integer smsIndex,String deleteKey,Long companyId,Long companyUserId);
 
 }

+ 3 - 1
fs-service/src/main/java/com/fs/common/service/impl/SmsServiceImpl.java

@@ -669,7 +669,7 @@ public class SmsServiceImpl implements ISmsService
 
 
     @Override
-    public R sendUrl(String phone, String content, String code,Long uuid,Integer smsIndex,String deleteKey) {
+    public R sendUrl(String phone, String content, String code,Long uuid,Integer smsIndex,String deleteKey,Long companyId,Long companyUserId) {
         log.info("发送短信:{},链接地址:{},短信模板:{}", phone, content, code);
         CompanySmsTemp temp = smsTempService.selectCompanySmsTempByCode(code);
         if (temp == null) {
@@ -702,6 +702,8 @@ public class SmsServiceImpl implements ISmsService
                         logs.setStatus(0);
                         logs.setType(sms.getType());
                         logs.setMid(itemVO.getMid());
+                        logs.setCompanyId(companyId);
+                        logs.setCompanyUserId(companyUserId);
                         if (uuid != null) {
                             logs.setSopSmsLogId(uuid);
                         }

+ 6 - 0
fs-service/src/main/java/com/fs/company/mapper/CompanySmsLogsMapper.java

@@ -194,4 +194,10 @@ public interface CompanySmsLogsMapper
     List<CompanySmsLogsStatisticsVO> selectSmsLogsStatisticsList(@Param("maps") CompanyStatisticsParam param);
     @Select("select * from company_sms_logs where phone=#{mobile} order by logs_id desc limit 1")
     CompanySmsLogs selectCompanySmsLogsByMobile(String mobile);
+
+    /**
+     * 获取相关短信记录
+     * @param uuIds
+     * **/
+    List<CompanySmsLogs> getCompanySmsLogsByUuIdList(@Param("uuIds") List<Long> uuIds);
 }

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

@@ -73,7 +73,7 @@ public interface CompanyWxClientMapper extends BaseMapper<CompanyWxClient> {
 
     List<CompanyWxClient> getAddWxList(@Param("accountIdList") List<Long> accountIdList, @Param("isWeCom") Integer isWeCom);
 
-    List<CompanyWxClient4WorkFlowVO> getAddWxList4Workflow(@Param("accountIdList") List<Long> accountIdList, @Param("execStatus") Integer execStatus, @Param("execNodeType") Integer execNodeType);
+    List<CompanyWxClient4WorkFlowVO> getAddWxList4Workflow(@Param("accountIdList") List<Long> accountIdList, @Param("execStatus") Integer execStatus, @Param("execNodeType") Integer execNodeType, @Param("cidGroupId") Integer cidGroupId);
 
     CompanyWxClient selectWx(@Param("accountId") Long accountId, @Param("v3") String v3);
 

+ 1 - 1
fs-service/src/main/java/com/fs/company/service/ICompanyWxClientService.java

@@ -71,7 +71,7 @@ public interface ICompanyWxClientService extends IService<CompanyWxClient> {
 
     List<CompanyWxClient> getAddWxList(List<Long> accountIdList,Integer isWeCom);
 
-    List<CompanyWxClient4WorkFlowVO> getAddWxList4Workflow(List<Long> accountIdList);
+    List<CompanyWxClient4WorkFlowVO> getAddWxList4Workflow(List<Long> accountIdList,Integer cidGroupId);
 
     List<CompanyWxClient> getQwAddWxList(List<Long> accountIdList,Integer isWeCom);
 

+ 6 - 2
fs-service/src/main/java/com/fs/company/service/impl/CompanyVoiceRoboticServiceImpl.java

@@ -28,6 +28,8 @@ import com.fs.crm.domain.CrmCustomer;
 import com.fs.crm.mapper.CrmCustomerMapper;
 import com.fs.crm.param.SmsSendBatchParam;
 import com.fs.crm.service.impl.CrmCustomerServiceImpl;
+import com.fs.enums.ExecutionStatusEnum;
+import com.fs.enums.NodeTypeEnum;
 import com.fs.qw.domain.QwUser;
 import com.fs.qw.mapper.QwUserMapper;
 import com.fs.qw.service.impl.QwExternalContactServiceImpl;
@@ -1241,11 +1243,13 @@ public class CompanyVoiceRoboticServiceImpl extends ServiceImpl<CompanyVoiceRobo
         for (WorkflowExecRecordVo record : records) {
             // 设置工作流状态名称
             if (record.getWorkflowStatus() != null) {
-                record.setWorkflowStatusName(getStatusName(record.getWorkflowStatus()));
+                ExecutionStatusEnum executionStatusEnum = ExecutionStatusEnum.fromValue(record.getWorkflowStatus());
+                record.setWorkflowStatusName( executionStatusEnum.getDescription());
             }
             // 设置节点类型名称
             if (record.getCurrentNodeType() != null) {
-                record.setCurrentNodeTypeName(getNodeTypeName(record.getCurrentNodeType()));
+                NodeTypeEnum nodeTypeEnum = NodeTypeEnum.fromValue(record.getCurrentNodeType());
+                record.setCurrentNodeTypeName(nodeTypeEnum.getDescription());
             }
             // 查询节点执行日志
             if (record.getWorkflowInstanceId() != null) {

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

@@ -238,8 +238,8 @@ public class CompanyWxClientServiceImpl extends ServiceImpl<CompanyWxClientMappe
      * @return
      */
     @Override
-    public  List<CompanyWxClient4WorkFlowVO> getAddWxList4Workflow(List<Long> accountIdList){
-        return baseMapper.getAddWxList4Workflow(accountIdList, ExecutionStatusEnum.PAUSED.getValue(), NodeTypeEnum.AI_ADD_WX_TASK.getValue());
+    public  List<CompanyWxClient4WorkFlowVO> getAddWxList4Workflow(List<Long> accountIdList,Integer cidGroupId){
+        return baseMapper.getAddWxList4Workflow(accountIdList, ExecutionStatusEnum.PAUSED.getValue(), NodeTypeEnum.AI_ADD_WX_TASK.getValue(),cidGroupId);
     }
 
     @Override

+ 8 - 1
fs-service/src/main/java/com/fs/company/service/impl/call/node/AiCallTaskNode.java

@@ -171,6 +171,13 @@ public class AiCallTaskNode extends AbstractWorkflowNode {
                 if (bus == null) {
                     return ExecutionResult.failure().errorMessage("未找到业务数据").build();
                 }
+                //手动外呼配置 1、人工 2、ai外呼
+                if(Integer.valueOf(1).equals(callConfigVo.getCallMode())){
+                    super.asyncWorkflowForBlockingNode(context.getWorkflowInstanceId(), context.getCurrentNodeKey(), context, ExecutionStatusEnum.WAITING_DO_CALL);
+                    return ExecutionResult.paused()
+                            .outputData(context.getVariables())
+                            .nextNodeKey("").build();
+                }
 //                companyVoiceRoboticService.workflowCallPhoneOne(bus.getRoboticId(), bus.getCalleeId(), context, callConfigVo);
                 // EasyCallCenter365 外呼
                  workflowCallPhoneOne4EasyCall(bus.getRoboticId(),bus.getCalleeId(), context, callConfigVo);
@@ -267,7 +274,7 @@ public class AiCallTaskNode extends AbstractWorkflowNode {
         // 音色来源(如未配置默认留空,由 EasyCallCenter365 使用默认值)
         createParam.setVoiceSource(callConfigVo.getVoiceSource());
         // 技能组(转人工客服分组,可选)
-        createParam.setGroupId(callConfigVo.getBusiGroupId());
+//        createParam.setGroupId(callConfigVo.getBusiGroupId());
 
         JSONObject runParam = (JSONObject) JSON.toJSON(createParam);
         runParam.put("companyId", robotic.getCompanyId());

+ 4 - 0
fs-service/src/main/java/com/fs/company/vo/AiCallConfigVO.java

@@ -60,4 +60,8 @@ public class AiCallConfigVO {
      */
     private String busiGroupId;
 
+    /**
+     * 外呼模式
+     */
+    private Integer callMode;
 }

+ 1 - 0
fs-service/src/main/java/com/fs/course/config/CourseConfig.java

@@ -47,6 +47,7 @@ public class CourseConfig implements Serializable {
     private Integer isNegative;//是否为负数 0、不允许,1、允许
 
     private Integer isOpen;
+    private Boolean completionCountdown;
 
     /**
      * 侧边栏是否仅展示当天课程

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

@@ -755,4 +755,6 @@ public interface FsCourseWatchLogMapper extends BaseMapper<FsCourseWatchLog> {
 
     List<FsSopMyCourseH5LinkVO> getSopCourseH5StudyListByQwExId(@Param("qwExternalId") Long qwExternalId);
 
+    @Select("select * from fs_course_watch_log where user_id=#{userId} and company_user_id=#{companyUserId} and course_id=#{courseId} and video_id=#{videoId} limit 1")
+    FsCourseWatchLog selectFsCourseWatchLogWithUCCV(@Param("userId") Long userId,@Param("companyUserId") Long companyUserId,@Param("courseId") Integer courseId,@Param("videoId") Integer videoId);
 }

+ 27 - 0
fs-service/src/main/java/com/fs/course/param/newfs/FsUserCourseVideoRemainTimeParam.java

@@ -0,0 +1,27 @@
+package com.fs.course.param.newfs;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+import java.io.Serializable;
+
+@Data
+public class FsUserCourseVideoRemainTimeParam implements Serializable {
+    @NotNull(message = "视频id不能为空")
+    @ApiModelProperty(value = "视频id")
+    private Integer videoId;
+
+    @NotNull(message = "用户id不能为空")
+    @ApiModelProperty(value = "用户id")
+    private Long fsUserId;
+
+    @NotNull(message = "课程id不能为空")
+    @ApiModelProperty(value = "课程id")
+    private Integer courseId;
+
+    @NotNull(message = "销售id不能为空")
+    @ApiModelProperty(value = "销售id")
+    private Long companyUserId;
+
+}

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

@@ -169,4 +169,6 @@ public interface IFsCourseWatchLogService extends IService<FsCourseWatchLog> {
     R decryptLink(String url);
 
     List<FsCourseWatchLog> selectFsUserWatchLogByExtId(QwExternalContact qwExternalContact);
+
+    FsCourseWatchLog selectFsCourseWatchLogWithUCCV(Long userId, Long companyUserId, Integer courseId, Integer videoId);
 }

+ 5 - 0
fs-service/src/main/java/com/fs/course/service/impl/FsCourseWatchLogServiceImpl.java

@@ -1724,4 +1724,9 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
         return fsCourseWatchLogMapper.selectFsUserWatchLogByExtId(qwExternalContact);
     }
 
+    @Override
+    public FsCourseWatchLog selectFsCourseWatchLogWithUCCV(Long userId, Long companyUserId, Integer courseId, Integer videoId) {
+        return fsCourseWatchLogMapper.selectFsCourseWatchLogWithUCCV(userId, companyUserId, courseId, videoId);
+    }
+
 }

+ 6 - 1
fs-service/src/main/java/com/fs/enums/ExecutionStatusEnum.java

@@ -53,7 +53,12 @@ public enum ExecutionStatusEnum {
     /**
      * 待处理
      */
-    PENDING("PENDING", "待处理", 10);
+    PENDING("PENDING", "待处理", 10),
+
+    /**
+     * 等待人工外呼
+     */
+    WAITING_DO_CALL("WAITINGDOCALL", "等待人工外呼", 11);
 
     private final String code;
     private final String description;

+ 14 - 0
fs-service/src/main/java/com/fs/his/config/AppConfig.java

@@ -2,6 +2,7 @@ package com.fs.his.config;
 
 import com.fs.course.vo.FsUserCourseVideoVO;
 import com.fs.his.domain.FsPackage;
+import com.fs.his.vo.GameVo;
 import lombok.Data;
 
 import java.util.List;
@@ -13,4 +14,17 @@ public class AppConfig {
     private Long courseId;
     private List<FsUserCourseVideoVO> fsCourse;
     private Integer unbindLimit;
+
+    private Long tongueFlag;
+    private String appId;
+
+    /**
+     * 游戏列表
+     */
+    private List<GameVo> gameList;
+
+    private String corpId; //APP客服配置 企业主体id
+    private String corpUrl; //APP客服配置 企业主体链接
+    private Integer addIntegral; //玩一局游戏加多少积分
+    private Integer defaultRewardGold; //看视频获取多少金币
 }

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

@@ -7,6 +7,8 @@ import lombok.Getter;
 @AllArgsConstructor
 public enum BusinessTypeEnum {
     INTEGRAL_ORDER("integral", 6, "积分商城订单支付"),
+    ORDER_ORDER("store", 8, "商城订单支付"),
+    LIVE_ORDER("live", 9, "直播订单支付"),
     ;
 
     private final String prefix;

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

@@ -8,6 +8,8 @@ import lombok.Getter;
 public enum PaymentMethodEnum {
     MINIAPP_WECHAT("weixin"), // 小程序微信支付
     H5_WECHAT("微信"),      // H5微信支付
+    WX_APP("wx_app"),      // H5微信支付
+    T_NATIVE("t_native"),      // H5微信支付
     ALIPAY("alipay"),         // 支付宝支付
     H5_ALIPAY("alipay");       // H5支付宝支付
 

+ 5 - 0
fs-service/src/main/java/com/fs/his/param/FsIntegralOrderDoPayParam.java

@@ -12,4 +12,9 @@ public class FsIntegralOrderDoPayParam {
     private Long userId;
 
     private String appId;
+
+    /**
+     * 商品类型
+     */
+    private String type;
 }

+ 23 - 0
fs-service/src/main/java/com/fs/his/vo/GameVo.java

@@ -0,0 +1,23 @@
+package com.fs.his.vo;
+
+import lombok.Data;
+
+@Data
+public class GameVo {
+    private Long id;
+
+    /**
+     * 游戏图片
+     */
+    private String image;
+
+    /**
+     * 游戏名称
+     */
+    private String name;
+
+    /**
+     * 游戏链接
+     */
+    private String url;
+}

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

@@ -355,6 +355,18 @@ public class FsStoreOrderScrm extends BaseEntity
     @TableField(exist = false)
     private String bankTransactionId;
 
+    @TableField(exist = false)
+    private Boolean isLive = false;
+
+    @TableField(exist = false)
+    private String companyUserName;
+    @TableField(exist = false)
+    private String companyName;
+    @TableField(exist = false)
+    private String periodName;
+    @TableField(exist = false)
+    private String videoName;
+
      // 是否审核,1-是,0-否
     private Integer isAudit;
 

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

@@ -93,8 +93,15 @@ public interface FsStoreCartScrmMapper
            "<foreach collection='array' item='id' open='(' separator=',' close=')'>#{id}</foreach>"+
             "</script>"})
     int delCart(Long[] ids);
-    @Select("select c.*,p.cate_id,p.product_name,p.image as product_image,p.temp_id,p.product_type,v.price,v.sku as product_attr_name,v.image as product_attr_image,v.stock,v.cost,v.integral,v.weight,v.volume,v.bar_code,v.group_bar_code,v.brokerage,v.brokerage_two,v.brokerage_three from fs_store_cart_scrm c left join fs_store_product_scrm p on p.product_id=c.product_id left join fs_store_product_attr_value_scrm v on v.id=c.product_attr_value_id where find_in_set(c.id,#{ids})")
+
+    @Select("select c.*,p.cate_id,p.product_name,p.image as product_image,p.temp_id,p.product_type,v.price,v.sku as product_attr_name,v.image as product_attr_image," +
+            "v.stock,v.cost,v.integral,v.weight,v.volume,v.bar_code,v.group_bar_code,v.brokerage,v.brokerage_two,v.brokerage_three " +
+            "from fs_store_cart_scrm c " +
+            "left join fs_store_product_scrm p on p.product_id=c.product_id " +
+            "left join fs_store_product_attr_value_scrm v on v.id=c.product_attr_value_id" +
+            " where find_in_set(c.id,#{ids})")
     List<FsStoreCartQueryVO> selectFsStoreCartListByIds(String ids);
+
     @Update("update  fs_store_cart_scrm set is_pay=1 where find_in_set(id,#{cartIds})")
     void updateIsPay(String cartIds);
 

+ 8 - 2
fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreOrderItemScrmMapper.java

@@ -140,9 +140,12 @@ public interface FsStoreOrderItemScrmMapper
             "<if test = 'maps.companyUserNickName != null and  maps.companyUserNickName !=  \"\" '> " +
             "and cu.nick_name like concat('%', #{maps.companyUserNickName}, '%') " +
             "</if>" +
-            "<if test = 'maps.orderType != null    '> " +
+            "<if test = 'maps.orderType != null and maps.orderType != -1    '> " +
             "and o.order_type =#{maps.orderType} " +
             "</if>" +
+            "<if test = 'maps.orderType != null and maps.orderType == -1    '> " +
+            "and o.order_type in (2, 3) " +
+            "</if>" +
             "<if test = 'maps.createTimeList != null    '> " +
             " AND date_format(o.create_time,'%y%m%d') &gt;= date_format(#{maps.createTimeList[0]},'%y%m%d') " +
             " AND date_format(o.create_time,'%y%m%d') &lt;= date_format(#{maps.createTimeList[1]},'%y%m%d') " +
@@ -251,9 +254,12 @@ public interface FsStoreOrderItemScrmMapper
             "<if test = 'maps.companyUserNickName != null and  maps.companyUserNickName !=  \"\" '> " +
             "and cu.nick_name like concat('%', #{maps.companyUserNickName}, '%') " +
             "</if>" +
-            "<if test = 'maps.orderType != null    '> " +
+            "<if test = 'maps.orderType != null and maps.orderType != -1    '> " +
             "and o.order_type =#{maps.orderType} " +
             "</if>" +
+            "<if test = 'maps.orderType != null and maps.orderType == -1    '> " +
+            "and o.order_type in (2, 3) " +
+            "</if>" +
             "<if test = 'maps.createTimeList != null    '> " +
             " AND date_format(o.create_time,'%y%m%d') &gt;= date_format(#{maps.createTimeList[0]},'%y%m%d') " +
             " AND date_format(o.create_time,'%y%m%d') &lt;= date_format(#{maps.createTimeList[1]},'%y%m%d') " +

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

@@ -66,10 +66,13 @@ public interface FsUserAddressScrmMapper
     public int deleteFsUserAddressByIds(Long[] ids);
 
     FsUserAddressScrm selectFsUserAddressByDefaultAddress(long uid);
+
     @Update("update fs_user_address set is_default=0 where user_id=#{userId}")
     int clearIsDefalut(long userId);
+
     @Select("select IFNULL(count(1),0) from fs_user_address where user_id=#{userId}  and is_del=0 " )
     Integer selectFsUserAddressCountsByUserId(long userId);
+
     @Update(" update fs_user_address set is_del=1 where user_id = #{userId}")
     Integer delAllAddress(Long userId);
 

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

@@ -13,6 +13,8 @@ import com.fs.company.domain.CompanyUser;
 import com.fs.course.dto.FsOrderDeliveryNoteDTO;
 import com.fs.erp.domain.ErpOrder;
 import com.fs.his.dto.FsStoreOrderAmountScrmStatsQueryDto;
+import com.fs.his.enums.PaymentMethodEnum;
+import com.fs.his.param.FsIntegralOrderDoPayParam;
 import com.fs.his.param.FsStoreOrderSalesParam;
 import com.fs.his.vo.FsStoreOrderAmountScrmStatsVo;
 import com.fs.his.vo.FsStoreOrderExcelVO;
@@ -387,4 +389,6 @@ public interface IFsStoreOrderScrmService
     R getOrderInfoBySidebar(String orderId,Long userId);
 
     R editOrderMoneyBySidebar(FsStoreOrderScrmSidebarVO param);
+
+    R payment(FsIntegralOrderDoPayParam param, PaymentMethodEnum paymentMethodEnum);
 }

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

@@ -5,6 +5,7 @@ import java.util.Map;
 
 import com.alibaba.fastjson.JSONObject;
 import com.fs.common.core.domain.R;
+import com.fs.his.param.PayOrderParam;
 import com.fs.hisStore.domain.FsStorePaymentScrm;
 import com.fs.hisStore.param.*;
 import com.fs.hisStore.vo.FsStorePaymentStatisticsVO;
@@ -122,4 +123,6 @@ public interface IFsStorePaymentScrmService
      * 批量导入更新微信订单发货状态
      * **/
     R oneClickShipping();
+
+    R processPaymentScrm(PayOrderParam payOrderParam);
 }

+ 78 - 3
fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreOrderScrmServiceImpl.java

@@ -58,12 +58,12 @@ import com.fs.his.dto.FsPrescribeUsageDTO;
 import com.fs.his.dto.FsProdItemDTO;
 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.enums.*;
 import com.fs.his.mapper.*;
+import com.fs.his.param.FsIntegralOrderDoPayParam;
 import com.fs.his.param.FsStoreOrderSalesParam;
 import com.fs.his.param.FsUserAddIntegralTemplateParam;
+import com.fs.his.param.PayOrderParam;
 import com.fs.his.service.IFsPrescribeService;
 import com.fs.his.service.IFsUserIntegralLogsService;
 import com.fs.his.service.IFsUserWatchService;
@@ -75,6 +75,7 @@ import com.fs.his.vo.FsPrescribeVO;
 import com.fs.hisStore.config.FsErpConfig;
 import com.fs.hisStore.constants.ErpTypeEnum;
 import com.fs.hisStore.dto.*;
+import com.fs.hisStore.enums.ShipperCodeEnum;
 import com.fs.hisStore.mapper.*;
 import com.fs.hisStore.param.*;
 import com.fs.hisStore.vo.*;
@@ -93,6 +94,8 @@ import com.fs.huifuPay.domain.HuifuCreateOrderResult;
 import com.fs.huifuPay.sdk.opps.core.request.V2TradePaymentScanpayRefundRequest;
 import com.fs.huifuPay.sdk.opps.core.utils.HuiFuUtils;
 import com.fs.huifuPay.service.HuiFuService;
+import com.fs.live.domain.LiveOrder;
+import com.fs.live.mapper.LiveOrderMapper;
 import com.fs.pay.pay.dto.OrderQueryDTO;
 import com.fs.pay.pay.dto.RefundDTO;
 import com.fs.pay.service.IPayService;
@@ -181,6 +184,12 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
     @Autowired
     private CompanyMoneyLogsMapper moneyLogsMapper;
 
+    @Autowired
+    private IFsStorePaymentScrmService storePaymentService;
+
+    @Autowired
+    private LiveOrderMapper liveOrderMapper;
+
     @Autowired
     private CompanyUserUserMapper companyUserUserMapper;
     @Autowired
@@ -5963,6 +5972,72 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
         }
     }
 
+    /**
+     * 预支付
+     */
+    @Transactional(rollbackFor = Exception.class)
+    @Override
+    public R payment(FsIntegralOrderDoPayParam param, PaymentMethodEnum paymentMethod) {
+        FsStoreOrderScrm order = buildPayment(param);
+        if (Objects.isNull(order) || !order.getStatus().equals(0)
+                || order.getPayMoney().compareTo(BigDecimal.ZERO) <= 0){
+            return R.error("非法操作");
+        }
+
+        PayOrderParam payOrderParam = buildPayOrderParam(paymentMethod, order);
+        return storePaymentService.processPaymentScrm(payOrderParam);
+    }
+
+
+    /**
+     * 综合参数
+     *
+     * @param param
+     * @return
+     */
+    public FsStoreOrderScrm buildPayment(FsIntegralOrderDoPayParam param){
+        if(param.getType().equals("live")){
+            LiveOrder liveOrder = liveOrderMapper.selectLiveOrderByOrderId(String.valueOf(param.getOrderId()));
+            if (ObjectUtil.isNotEmpty(liveOrder)){
+                FsStoreOrderScrm orderScrm = new FsStoreOrderScrm();
+                BeanUtils.copyProperties(liveOrder,orderScrm);
+                orderScrm.setId(liveOrder.getOrderId());
+                orderScrm.setUserId(Long.valueOf(liveOrder.getUserId()));
+                orderScrm.setIsLive(true);
+                return orderScrm;
+            }
+        }
+        if (param.getType().equals("store")){
+            FsStoreOrderScrm order = fsStoreOrderMapper.selectFsStoreOrderById(param.getOrderId());
+            if (ObjectUtil.isNotEmpty(order)){
+                return order;
+            }
+        }
+
+        return null;
+    }
+
+
+    /**
+     * 构建参数
+     */
+    private static PayOrderParam buildPayOrderParam(PaymentMethodEnum paymentMethod, FsStoreOrderScrm order) {
+        PayOrderParam payOrderParam = new PayOrderParam();
+        payOrderParam.setOrderId(order.getId());
+        payOrderParam.setOrderCode(order.getOrderCode());
+        payOrderParam.setAmount(order.getPayMoney());
+        payOrderParam.setUserId(order.getUserId());
+        payOrderParam.setCompanyId(order.getCompanyId());
+        payOrderParam.setCompanyUserId(order.getCompanyUserId());
+        payOrderParam.setPaymentMethod(paymentMethod);
+        if (order.getIsLive()){
+            payOrderParam.setBusinessType(BusinessTypeEnum.LIVE_ORDER);
+        }else {
+            payOrderParam.setBusinessType(BusinessTypeEnum.ORDER_ORDER);
+        }
+        return payOrderParam;
+    }
+
     private static final DateTimeFormatter CST_FORMATTER = DateTimeFormatter
             .ofPattern("EEE MMM dd HH:mm:ss zzz yyyy", Locale.US)
             .withZone(ZoneId.of("Asia/Shanghai"));

+ 363 - 0
fs-service/src/main/java/com/fs/hisStore/service/impl/FsStorePaymentScrmServiceImpl.java

@@ -41,9 +41,13 @@ import com.fs.course.domain.FsCourseRedPacketLog;
 import com.fs.course.mapper.FsCoursePlaySourceConfigMapper;
 import com.fs.course.mapper.FsCourseRedPacketLogMapper;
 import com.fs.course.service.IFsCourseRedPacketLogService;
+import com.fs.his.config.AppConfig;
 import com.fs.his.domain.*;
+import com.fs.his.dto.PayConfigDTO;
+import com.fs.his.enums.PaymentMethodEnum;
 import com.fs.his.mapper.FsUserWxMapper;
 import com.fs.his.mapper.MerchantAppConfigMapper;
+import com.fs.his.param.PayOrderParam;
 import com.fs.his.service.IFsUserService;
 import com.fs.his.service.IFsUserWxService;
 import com.fs.his.utils.ConfigUtil;
@@ -55,6 +59,8 @@ import com.fs.huifuPay.domain.HuiFuCreateOrder;
 import com.fs.huifuPay.domain.HuifuCreateOrderResult;
 import com.fs.huifuPay.sdk.opps.core.utils.HuiFuUtils;
 import com.fs.huifuPay.service.HuiFuService;
+import com.fs.live.domain.LiveOrderPayment;
+import com.fs.live.mapper.LiveOrderPaymentMapper;
 import com.fs.pay.pay.dto.WxJspayDTO;
 import com.fs.hisStore.vo.FsStorePaymentStatisticsVO;
 import com.fs.system.oss.CloudStorageService;
@@ -77,6 +83,7 @@ import com.github.binarywang.wxpay.exception.WxPayException;
 import com.github.binarywang.wxpay.service.TransferService;
 import com.github.binarywang.wxpay.service.WxPayService;
 import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl;
+import com.hc.openapi.tool.fastjson.JSON;
 import lombok.extern.slf4j.Slf4j;
 import me.chanjar.weixin.common.error.WxErrorException;
 import org.slf4j.Logger;
@@ -114,6 +121,9 @@ public class FsStorePaymentScrmServiceImpl implements IFsStorePaymentScrmService
     @Autowired
     private IFsUserScrmService userService;
 
+    @Autowired
+    private FsUserWxMapper userWxMapper;
+
     @Autowired
     private ICompanyUserService companyUserService;
     @Autowired
@@ -131,6 +141,9 @@ public class FsStorePaymentScrmServiceImpl implements IFsStorePaymentScrmService
     @Autowired
     private CompanyConfigMapper companyConfigMapper;
 
+    @Autowired
+    private LiveOrderPaymentMapper liveOrderPaymentMapper;
+
     @Autowired
     private HuiFuService huiFuService;
 
@@ -1067,6 +1080,8 @@ public class FsStorePaymentScrmServiceImpl implements IFsStorePaymentScrmService
         }
     }
 
+
+
     private boolean uploadShippingInfoToWechat(WxMaService wxService,
                                                FsStorePaymentUsetVo dto,
                                                String uploadTime) {
@@ -1102,4 +1117,352 @@ public class FsStorePaymentScrmServiceImpl implements IFsStorePaymentScrmService
             return false;
         }
     }
+
+    /**
+     * 发起支付
+     * @param payOrderParam 入参
+     * @return R
+     */
+    @Transactional(rollbackFor = Exception.class)
+    @Override
+    public R processPaymentScrm( PayOrderParam payOrderParam) {
+        logger.info("发起支付 payOrderParam: {}", JSON.toJSONString(payOrderParam));
+
+        if (payOrderParam.getAmount().compareTo(BigDecimal.ZERO) <= 0) {
+            throw new CustomException("支付金额不正确");
+        }
+
+        FsUserScrm user = userService.selectFsUserById(payOrderParam.getUserId());
+        if (Objects.isNull(user)) {
+            throw new CustomException("用户不存在");
+        }
+        String type = null;
+        FsPayConfig payConfig = new FsPayConfig();
+        if (PaymentMethodEnum.WX_APP == payOrderParam.getPaymentMethod()) {
+            String json = configService.selectConfigByKey("app.config");
+            AppConfig config = JSONUtil.toBean(json, AppConfig.class);
+            payOrderParam.setAppId(config.getAppId());
+            type = "wxApp";
+        }
+        //支付宝可以不需要appid(在没有appid的情况下)【ps:小程序的支付宝没传appid 就G】
+        if ((PaymentMethodEnum.ALIPAY == payOrderParam.getPaymentMethod())
+                && StringUtils.isBlank(payOrderParam.getAppId())) {
+            String json = configService.selectConfigByKey("his.pay");
+            PayConfigDTO payConfigDTO = JSONUtil.toBean(json, PayConfigDTO.class);
+            payConfig.setType(payConfigDTO.getType());
+        } else {
+            if (StringUtils.isBlank(payOrderParam.getAppId())) {
+                throw new IllegalArgumentException("appId不能为空");
+            }
+            FsCoursePlaySourceConfig fsCoursePlaySourceConfig = fsCoursePlaySourceConfigMapper.selectCoursePlaySourceConfigByAppId(payOrderParam.getAppId());
+            if (fsCoursePlaySourceConfig == null) {
+                throw new CustomException("未找到appId对应的小程序配置: " + payOrderParam.getAppId());
+            }
+            Long merchantConfigId = fsCoursePlaySourceConfig.getMerchantConfigId();
+            if (merchantConfigId == null || merchantConfigId <= 0) {
+                throw new CustomException("小程序没有配置商户信息");
+            }
+
+            MerchantAppConfig merchantAppConfig = merchantAppConfigMapper.selectMerchantAppConfigById(fsCoursePlaySourceConfig.getMerchantConfigId());
+            payConfig = JSON.parseObject(merchantAppConfig.getDataJson(), FsPayConfig.class);
+            if (StringUtils.isNotEmpty(type)) {
+                payConfig.setType(type);
+            } else {
+                payConfig.setType(merchantAppConfig.getMerchantType());
+            }
+            payConfig.setAppId(fsCoursePlaySourceConfig.getAppid());
+
+            logger.debug("支付配置 his.pay: {}", payConfig);
+        }
+
+
+//        FsPayConfig payConfig = JSONUtil.toBean(json, FsPayConfig.class);
+
+        if (isWechatPayment(payOrderParam.getPaymentMethod())) {
+            String openId = getOpenIdForPaymentMethod(user, payOrderParam.getPaymentMethod(), payConfig);
+            if (StringUtils.isBlank(openId)) {
+                throw new CustomException("用户OPENID不存在");
+            }
+        }
+
+        // 创建记录 TODO 根据type创建支付
+        FsStorePaymentScrm storePayment = new FsStorePaymentScrm();
+        if (payOrderParam.getBusinessType().getPrefix().equals("live")) {
+            LiveOrderPayment liveOrderPayment = createLiveStorePayment(payConfig, user, payOrderParam);
+            BeanUtils.copyProperties(liveOrderPayment, storePayment);
+        } else {
+            storePayment = createStorePaymentScrm(payConfig, user, payOrderParam);
+        }
+
+        // 根据配置类型创建第三方支付订单
+        return createThirdPartyPaymentScrm(payConfig, storePayment, user, payOrderParam);
+    }
+
+    /**
+     * 直播订单支付信息
+     *
+     * @param payConfig
+     * @param user
+     * @param payOrderParam
+     * @return
+     */
+    private LiveOrderPayment createLiveStorePayment(FsPayConfig payConfig, FsUserScrm user, PayOrderParam payOrderParam) {
+        String payCode = OrderCodeUtils.getOrderSn();
+        if (StringUtils.isEmpty(payCode)) {
+            throw new CustomException("订单生成失败,请重试");
+        }
+
+        LiveOrderPayment storePayment = new LiveOrderPayment();
+        storePayment.setStatus(0);
+        storePayment.setAppId(payConfig.getAppId());
+        storePayment.setPayMode(payConfig.getType());
+        storePayment.setBusinessCode(payOrderParam.getOrderCode());
+        storePayment.setPayCode(payCode);
+        storePayment.setPayMoney(payOrderParam.getAmount());
+        storePayment.setCreateTime(new Date());
+        storePayment.setPayTypeCode(payOrderParam.getPaymentMethod().getDesc());
+        storePayment.setBusinessType(payOrderParam.getBusinessType().getCode());
+        storePayment.setCompanyId(payOrderParam.getCompanyId());
+        storePayment.setCompanyUserId(payOrderParam.getCompanyUserId());
+        storePayment.setRemark(payOrderParam.getBusinessType().getDesc());
+        storePayment.setStoreId(payOrderParam.getStoreId());
+        storePayment.setUserId(user.getUserId());
+        storePayment.setBusinessId(payOrderParam.getOrderId().toString());
+
+        // 设置openId(如果是微信支付)
+        if (isWechatPayment(payOrderParam.getPaymentMethod())) {
+            storePayment.setOpenId(getOpenIdForPaymentMethod(user, payOrderParam.getPaymentMethod(), payConfig));
+        }
+
+        if (liveOrderPaymentMapper.insertLiveOrderPayment(storePayment) <= 0) {
+            throw new CustomException("支付订单创建失败");
+        }
+
+        return storePayment;
+    }
+
+    /**
+     * 判断是否微信支付
+     * @param method 支付类型
+     * @return boolean
+     */
+    private boolean isWechatPayment(PaymentMethodEnum method) {
+        return method == PaymentMethodEnum.MINIAPP_WECHAT || method == PaymentMethodEnum.H5_WECHAT;
+    }
+
+    /**
+     * 根据支付方式获取对应的openId
+     */
+    private String getOpenIdForPaymentMethod(FsUserScrm user, PaymentMethodEnum method, FsPayConfig payConfig) {
+        String openId;
+        switch (method) {
+            case MINIAPP_WECHAT:
+                openId = user.getMaOpenId();
+                break;
+            case H5_WECHAT:
+                openId = user.getMpOpenId();
+                break;
+            default:
+                openId = null;
+        }
+
+        if (StringUtils.isBlank(openId)) {
+            Wrapper<FsUserWx> queryWrapper = Wrappers.<FsUserWx>lambdaQuery()
+                    .eq(FsUserWx::getFsUserId, user.getUserId())
+                    .eq(FsUserWx::getAppId, payConfig.getAppId());
+            FsUserWx fsUserWx = userWxMapper.selectOne(queryWrapper);
+            if (Objects.nonNull(fsUserWx)) {
+                openId = fsUserWx.getOpenId();
+            }
+        }
+
+        return openId;
+    }
+
+    /**
+     * 发起预支付
+     */
+    private R createThirdPartyPaymentScrm(FsPayConfig payConfig, FsStorePaymentScrm storePayment, FsUserScrm user, PayOrderParam payOrderParam) {
+        switch (payConfig.getType()) {
+            case "wx":
+                return createWxPayment(storePayment, user, payOrderParam, payConfig);
+            case "wxApp":
+                return createWxAppPayment(storePayment, user, payOrderParam, payConfig);
+            case "hf":
+                return createHfPayment(storePayment, user, payOrderParam, payConfig);
+            default:
+                throw new CustomException("不支持的支付方式");
+        }
+    }
+
+    /**
+     * 汇付
+     */
+    private R createHfPayment(FsStorePaymentScrm storePayment, FsUserScrm user, PayOrderParam payOrderParam, FsPayConfig payConfig) {
+        logger.debug("创建汇付订单");
+
+        HuiFuCreateOrder order = new HuiFuCreateOrder();
+        order.setTradeType(getHfTradeType(payOrderParam.getPaymentMethod()));
+        order.setReqSeqId(payOrderParam.getBusinessType().getPrefix() + "-" + storePayment.getPayCode());
+        order.setTransAmt(storePayment.getPayMoney().toString());
+        order.setGoodsDesc(payOrderParam.getBusinessType().getDesc());
+
+        // 微信支付需要设置openid
+        if (isWechatPayment(payOrderParam.getPaymentMethod())) {
+            order.setOpenid(getOpenIdForPaymentMethod(user, payOrderParam.getPaymentMethod(), payConfig));
+        }
+
+        HuifuCreateOrderResult result = huiFuService.createOrder(order);
+        logger.debug("汇付支付创建结果: {}", result);
+
+        updateStorePaymentTradeNo(storePayment.getPaymentId(), result.getHf_seq_id());
+        return R.ok().put("isPay", 0).put("data", result).put("type", "hf");
+    }
+
+    /**
+     * 获取汇付交易类型
+     */
+    private String getHfTradeType(PaymentMethodEnum paymentMethod) {
+        switch (paymentMethod) {
+            case MINIAPP_WECHAT:
+                return "T_MINIAPP";
+            case H5_WECHAT:
+                return "T_JSAPI";
+            case ALIPAY:
+            case H5_ALIPAY:
+                return "A_NATIVE";
+            case T_NATIVE:
+                return "T_NATIVE";
+            default:
+                throw new CustomException("不支持的支付方式");
+        }
+    }
+
+    /**
+     * 更新支付订单交易号
+     */
+    private void updateStorePaymentTradeNo(Long paymentId, String tradeNo) {
+        FsStorePaymentScrm updatePayment = new FsStorePaymentScrm();
+        updatePayment.setPaymentId(paymentId);
+        updatePayment.setTradeNo(tradeNo);
+        fsStorePaymentMapper.updateFsStorePayment(updatePayment);
+    }
+
+
+    /**
+     * 微信支付
+     */
+    private R createWxPayment(FsStorePaymentScrm storePayment, FsUserScrm user, PayOrderParam payOrderParam, FsPayConfig payConfig) {
+        PaymentMethodEnum paymentMethod = payOrderParam.getPaymentMethod();
+        if (paymentMethod != PaymentMethodEnum.MINIAPP_WECHAT) {
+            logger.debug("微信支付 PaymentMethod: {}", paymentMethod.name());
+            throw new CustomException("不支持的支付方式");
+        }
+
+        WxPayConfig wxPayConfig = buildWxPayConfig(payConfig);
+        wxPayService.setConfig(wxPayConfig);
+
+        WxPayUnifiedOrderRequest orderRequest = new WxPayUnifiedOrderRequest();
+        orderRequest.setOpenid(getOpenIdForPaymentMethod(user, paymentMethod, payConfig));
+        orderRequest.setBody(payOrderParam.getBusinessType().getDesc());
+        orderRequest.setOutTradeNo(payOrderParam.getBusinessType().getPrefix() + "-" + storePayment.getPayCode());
+        orderRequest.setTotalFee(WxPayUnifiedOrderRequest.yuanToFen(storePayment.getPayMoney().toString()));
+        orderRequest.setTradeType("JSAPI");
+        orderRequest.setSpbillCreateIp(IpUtils.getIpAddr(ServletUtils.getRequest()));
+
+        try {
+            WxPayMpOrderResult orderResult = wxPayService.createOrder(orderRequest);
+            return R.ok().put("data", orderResult).put("type", "wx").put("isPay", 0);
+        } catch (WxPayException e) {
+            logger.error("微信支付发起失败: {}", e.getMessage(), e);
+            throw new CustomException("支付失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 微信App支付
+     */
+    private R createWxAppPayment(FsStorePaymentScrm storePayment, FsUserScrm user, PayOrderParam payOrderParam, FsPayConfig payConfig) {
+        //创建微信订单
+        WxPayConfig wxPayConfig = buildWxPayConfig(payConfig);
+
+        wxPayConfig.setAppId(wxPayConfig.getAppId());
+        wxPayConfig.setMchId(wxPayConfig.getMchId());
+        wxPayConfig.setMchKey(wxPayConfig.getMchKey());
+        wxPayConfig.setSubAppId(StringUtils.trimToNull(null));
+        wxPayConfig.setSubMchId(StringUtils.trimToNull(null));
+        wxPayConfig.setKeyPath(null);
+        wxPayConfig.setNotifyUrl(wxPayConfig.getNotifyUrl());
+        wxPayService.setConfig(wxPayConfig);
+        WxPayUnifiedOrderRequest orderRequest = new WxPayUnifiedOrderRequest();
+        orderRequest.setBody(payOrderParam.getBusinessType().getDesc());
+        orderRequest.setOutTradeNo(payOrderParam.getBusinessType().getPrefix() + "-" + storePayment.getPayCode());
+        orderRequest.setTotalFee(WxPayUnifiedOrderRequest.yuanToFen(storePayment.getPayMoney().toString()));
+        orderRequest.setTradeType("APP");
+        orderRequest.setSpbillCreateIp(IpUtils.getIpAddr(ServletUtils.getRequest()));
+        orderRequest.setNotifyUrl(wxPayConfig.getNotifyUrl());
+        //调用统一下单接口,获取"预支付交易会话标识"
+        try {
+            Object result = wxPayService.createOrder(orderRequest);
+            return R.ok().put("data",result).put("type","wxApp").put("isPay",0);
+        } catch (WxPayException e) {
+            e.printStackTrace();
+            throw new CustomException("支付失败"+e.getMessage());
+        }
+    }
+
+    /**
+     * 构建微信支付配置
+     */
+    private WxPayConfig buildWxPayConfig(FsPayConfig fsPayConfig) {
+        WxPayConfig payConfig = new WxPayConfig();
+        payConfig.setAppId(fsPayConfig.getAppId());
+        payConfig.setMchId(fsPayConfig.getWxMchId());
+        payConfig.setMchKey(fsPayConfig.getWxMchKey());
+        payConfig.setSubAppId(StringUtils.trimToNull(null));
+        payConfig.setSubMchId(StringUtils.trimToNull(null));
+        payConfig.setKeyPath(null);
+        payConfig.setNotifyUrl(fsPayConfig.getNotifyUrlScrm());
+        return payConfig;
+    }
+
+
+    /**
+     * 创建支付订单
+     */
+    private FsStorePaymentScrm createStorePaymentScrm(FsPayConfig payConfig, FsUserScrm user, PayOrderParam payOrderParam) {
+        String payCode = OrderCodeUtils.getOrderSn();
+        if (StringUtils.isEmpty(payCode)) {
+            throw new CustomException("订单生成失败,请重试");
+        }
+
+        FsStorePaymentScrm storePayment = new FsStorePaymentScrm();
+        storePayment.setStatus(0);
+        storePayment.setAppId(payConfig.getAppId());
+        storePayment.setOrderId(payOrderParam.getOrderId());
+        storePayment.setPayMode(payConfig.getType());
+        storePayment.setBusinessCode(payOrderParam.getOrderCode());
+        storePayment.setPayCode(payCode);
+        storePayment.setPayMoney(payOrderParam.getAmount());
+        storePayment.setCreateTime(new Date());
+        storePayment.setPayTypeCode(payOrderParam.getPaymentMethod().getDesc());
+        storePayment.setBusinessType(payOrderParam.getBusinessType().getCode());
+        storePayment.setCompanyId(payOrderParam.getCompanyId());
+        storePayment.setCompanyUserId(payOrderParam.getCompanyUserId());
+        storePayment.setRemark(payOrderParam.getBusinessType().getDesc());
+        storePayment.setStoreId(payOrderParam.getStoreId());
+        storePayment.setUserId(user.getUserId());
+        storePayment.setBusinessId(payOrderParam.getOrderId().toString());
+
+        // 设置openId(如果是微信支付)
+        if (isWechatPayment(payOrderParam.getPaymentMethod())) {
+            storePayment.setOpenId(getOpenIdForPaymentMethod(user, payOrderParam.getPaymentMethod(), payConfig));
+        }
+
+        if (fsStorePaymentMapper.insertFsStorePayment(storePayment) <= 0) {
+            throw new CustomException("支付订单创建失败");
+        }
+
+        return storePayment;
+    }
 }

+ 10 - 1
fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreProductScrmServiceImpl.java

@@ -681,6 +681,7 @@ public class FsStoreProductScrmServiceImpl implements IFsStoreProductScrmService
     @Transactional
     public R addOrEdit(FsStoreProductAddEditParam param) {
         ProductAttrCountDto countDto=computedProductCount(param.getValues());
+        Date nowDate = DateUtils.getNowDate();
         FsStoreProductScrm product=new FsStoreProductScrm();
         BeanUtils.copyProperties(param,product);
         product.setPrice(countDto.getMinPrice());
@@ -744,6 +745,7 @@ public class FsStoreProductScrmServiceImpl implements IFsStoreProductScrmService
                 //默认已审核
                 product.setIsAudit("1");
             }
+            product.setUpdateTime(nowDate);
             fsStoreProductMapper.updateFsStoreProduct(product);
             // 清除缓存
             clearProductDetailCache(product.getProductId());
@@ -761,6 +763,8 @@ public class FsStoreProductScrmServiceImpl implements IFsStoreProductScrmService
             }
         }
         else{
+            product.setCreateTime(nowDate);
+            product.setUpdateTime(nowDate);
             fsStoreProductMapper.insertFsStoreProduct(product);
         }
         storeAuditLogUtil.addOperLog(product.getProductId());
@@ -1248,6 +1252,7 @@ public class FsStoreProductScrmServiceImpl implements IFsStoreProductScrmService
         int failureNum = 0;
         StringBuilder successMsg = new StringBuilder();
         StringBuilder failureMsg = new StringBuilder();
+        Date nowDate = DateUtils.getNowDate();
         for (FsStoreProductExportVO productVO : list){
             try
             {
@@ -1255,6 +1260,8 @@ public class FsStoreProductScrmServiceImpl implements IFsStoreProductScrmService
                 if (product.getBarCode()==null || product.getBarCode()==""){
                     throw new CustomException("商品编号为空");
                 }
+                product.setCreateTime(nowDate);
+                product.setUpdateTime(nowDate);
                 this.insertFsStoreProduct(product);
                 ProductArrtDTO formatDetailDto = ProductArrtDTO.builder()
                         .value("规格")
@@ -1647,11 +1654,13 @@ public class FsStoreProductScrmServiceImpl implements IFsStoreProductScrmService
         // 查询原商品的规格属性与属性值
         List<FsStoreProductAttrScrm> attrList = fsStoreProductAttrMapper.selectFsStoreProductAttrByProductId(productId);
         List<FsStoreProductAttrValueScrm> attrValueList = fsStoreProductAttrValueMapper.selectFsStoreProductAttrValueByProductId(productId);
-
+        Date nowDate = DateUtils.getNowDate();
         FsStoreProductScrm copy = new FsStoreProductScrm();
         BeanUtils.copyProperties(fsStoreProductScrm, copy);
         copy.setProductId(null);
         copy.setIsAudit("0");
+        copy.setCreateTime(nowDate);
+        copy.setUpdateTime(nowDate);
         fsStoreProductMapper.insertFsStoreProduct(copy);
 
         // 复制规格属性

+ 35 - 1
fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreUserEndCategoryScrmServiceImpl.java

@@ -1,5 +1,6 @@
 package com.fs.hisStore.service.impl;
 
+import com.fs.common.core.redis.RedisCache;
 import com.fs.common.utils.DateUtils;
 import com.fs.hisStore.domain.FsStoreProductScrm;
 import com.fs.hisStore.domain.FsStoreUserEndCategoryScrm;
@@ -15,11 +16,14 @@ import com.github.pagehelper.PageHelper;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
+import java.math.BigDecimal;
+import java.math.RoundingMode;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.ThreadLocalRandom;
 import java.util.stream.Collectors;
 
 /**
@@ -40,6 +44,14 @@ public class FsStoreUserEndCategoryScrmServiceImpl implements IFsStoreUserEndCat
     @Autowired
     private FsStoreProductTagRelationScrmMapper productTagRelationMapper;
 
+    @Autowired
+    private RedisCache redisCache;
+
+    /** Redis key 用于存储固定的好评率随机值 */
+    private static final String POSITIVE_RATING_REDIS_KEY = "product:positiveRating:fixed:";
+    private static final double MIN_RATING = 95.0;
+    private static final double MAX_RATING = 99.9;
+
     @Override
     public List<FsStoreUserEndCategoryProductVO> listProductsByCategoryId(Long categoryId, String keyword) {
         if (categoryId == null) return new ArrayList<>();
@@ -121,7 +133,7 @@ public class FsStoreUserEndCategoryScrmServiceImpl implements IFsStoreUserEndCat
             vo.setOtPrice(p.getOtPrice());
             vo.setSales(p.getSales());
             vo.setTagList(tagMap.getOrDefault(pid, new ArrayList<>()));
-            vo.setPositiveRating();
+            vo.setPositiveRating(getFixedPositiveRating(p.getProductId()));
             result.add(vo);
         }
         out.put("list", result);
@@ -179,4 +191,26 @@ public class FsStoreUserEndCategoryScrmServiceImpl implements IFsStoreUserEndCat
         productUserEndCategoryMapper.deleteByCategoryIds(ids);
         return mapper.deleteByIds(ids);
     }
+
+    /**
+     * 获取固定的好评率随机值
+     * 先从 Redis 获取,如果不存在则生成随机值并保存到 Redis(永久保存)
+     * @return 固定的好评率值
+     */
+    private BigDecimal getFixedPositiveRating(Long productId) {
+        // 先从 Redis 获取
+        BigDecimal cachedRating = redisCache.getCacheObject(POSITIVE_RATING_REDIS_KEY + productId);
+        if (cachedRating != null) {
+            return cachedRating;
+        }
+        
+        // Redis 不存在,生成随机值
+        double rating = ThreadLocalRandom.current().nextDouble(MIN_RATING, MAX_RATING);
+        BigDecimal fixedRating = new BigDecimal(rating).setScale(1, RoundingMode.HALF_UP);
+        
+        // 保存到 Redis(永久保存,不设置过期时间)
+        redisCache.setCacheObject(POSITIVE_RATING_REDIS_KEY + productId, fixedRating);
+        
+        return fixedRating;
+    }
 }

+ 3 - 1
fs-service/src/main/java/com/fs/hisStore/vo/FsStoreOrderItemExportVO.java

@@ -14,6 +14,9 @@ public class FsStoreOrderItemExportVO implements Serializable
 
     private Long itemId;
 
+    @Excel(name = "订单类型")
+    private String orderType; // 订单类型 2.直播订单 3.点播订单
+
     /** 订单号 */
     @Excel(name = "订单号")
     private String orderCode;
@@ -121,7 +124,6 @@ public class FsStoreOrderItemExportVO implements Serializable
     private String isAudit;
 
 
-    private String orderType; // 订单类型 2.直播
     private BigDecimal payPrice;// 应付金额
     private BigDecimal deductionPrice;// 应付金额
     private BigDecimal payDelivery;// 应付邮费

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

@@ -21,6 +21,9 @@ public class FsStoreProductListQueryVO implements Serializable
     /** 商品图片 */
     private String image;
 
+    /** 轮播图 */
+    private String sliderImage;
+
     /** 商品名称 */
     private String productName;
 

+ 7 - 7
fs-service/src/main/java/com/fs/hisStore/vo/FsStoreUserEndCategoryProductVO.java

@@ -4,9 +4,7 @@ import lombok.Data;
 
 import java.io.Serializable;
 import java.math.BigDecimal;
-import java.math.RoundingMode;
 import java.util.List;
-import java.util.concurrent.ThreadLocalRandom;
 
 /**
  * 用户端分类下的商品项:商品ID、名称、售价、原价、销量、产品标签列表
@@ -15,8 +13,6 @@ import java.util.concurrent.ThreadLocalRandom;
 public class FsStoreUserEndCategoryProductVO implements Serializable {
 
     private static final long serialVersionUID = 1L;
-    private static final double min = 95.0;
-    private static final double max = 99.9;
 
     private Long productId;
     private String productName;
@@ -32,8 +28,12 @@ public class FsStoreUserEndCategoryProductVO implements Serializable {
     private Long sales;
     /** 产品标签名称列表 */
     private List<String> tagList;
-    public void setPositiveRating() {
-        double rating = ThreadLocalRandom.current().nextDouble(min, max);
-        this.positiveRating = new BigDecimal(rating).setScale(1, RoundingMode.HALF_UP);
+    
+    /**
+     * 设置好评率(从外部传入固定值)
+     * @param rating 好评率值
+     */
+    public void setPositiveRating(BigDecimal rating) {
+        this.positiveRating = rating;
     }
 }

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

@@ -165,4 +165,10 @@ public class OrderStatisticsVo implements Serializable {
      */
     @Excel(name = "在途订单金额", sort = 20, width = 20)
     private BigDecimal transitPrice;
+
+    /**
+     * 实际支付金额
+     **/
+    //@Excel(name = "实际支付金额", sort = 21, width = 20)
+    private BigDecimal actualPaymentAmount;
 }

+ 8 - 0
fs-service/src/main/java/com/fs/qw/mapper/QwSopSmsLogsMapper.java

@@ -128,4 +128,12 @@ public interface QwSopSmsLogsMapper extends BaseMapper<QwSopSmsLogs>{
 
     @DataSource(DataSourceType.SOP)
     void batchInsertQwSopSmsLogsOneTouch(@Param("qwSopSmsLogs") List<QwSopSmsLogs> logsToInsert);
+
+    /**
+     * 获取状态数据
+     * @param serverIds 服务器ID
+     * @return lsit 服务数据
+     * **/
+    @DataSource(DataSourceType.SOP)
+    List<QwSopSmsLogs> getQwSopSmsLogsStateList(@Param("serverIds") List<Long> serverIds);
 }

+ 5 - 0
fs-service/src/main/java/com/fs/qw/mapper/QwUserMapper.java

@@ -96,6 +96,11 @@ public interface QwUserMapper extends BaseMapper<QwUser>
      * @return 结果
      */
     public int updateQwUser(QwUser qwUser);
+
+    @Update("update qw_user set server_status = 0, server_id = null where id = #{id}")
+    public int updateQwUserByUnbindIpad(@Param("id") Long Id);
+
+
     public int updateQwUserByAppKey(QwUser qwUser);
 
     @Update(" update qw_user set login_status = 0, remark = '-'  where app_key = #{appKey}")

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

@@ -105,4 +105,11 @@ public interface IQwSopSmsLogsService extends IService<QwSopSmsLogs>{
 
 
     void batchInsertQwSopSmsLogsOneTouch(List<QwSopSmsLogs> logsToInsert);
+
+    /**
+     * 获取状态数据
+     * @param serverIds 服务器ID
+     * @return lsit 服务数据
+     * **/
+    List<QwSopSmsLogs> getQwSopSmsLogsStateList(List<Long> serverIds);
 }

+ 5 - 0
fs-service/src/main/java/com/fs/qw/service/impl/QwSopSmsLogsServiceImpl.java

@@ -140,4 +140,9 @@ public class QwSopSmsLogsServiceImpl extends ServiceImpl<QwSopSmsLogsMapper, QwS
     public void batchInsertQwSopSmsLogsOneTouch(List<QwSopSmsLogs> logsToInsert) {
          baseMapper.batchInsertQwSopSmsLogsOneTouch(logsToInsert);
     }
+
+    @Override
+    public List<QwSopSmsLogs> getQwSopSmsLogsStateList(List<Long> serverIds) {
+        return baseMapper.getQwSopSmsLogsStateList(serverIds);
+    }
 }

+ 21 - 1
fs-service/src/main/java/com/fs/wxwork/service/WxWorkServiceImpl.java

@@ -370,7 +370,27 @@ public class WxWorkServiceImpl implements WxWorkService {
 
     @Override
     public WxwSilkVoceDTO getSilkVoiceDoubao(String content, Long companyUserId, QwUser user) {
-        VcCompanyUser vcCompanyUser = companyUserMapper.selectVcCompanyUserByCompanyUserId(companyUserId);
+        String cacheKey = "voice:clone:company:user:id" + companyUserId;
+        VcCompanyUser vcCompanyUser = redisCache.getCacheObject(cacheKey);
+        if (vcCompanyUser == null) {
+            // 使用双重检查锁,防止缓存击穿
+            synchronized (this) {
+                // 再次检查缓存,防止在等待锁的过程中已经被其他线程加载
+                vcCompanyUser = redisCache.getCacheObject(cacheKey);
+                if (vcCompanyUser == null) {
+                    // 查询数据库
+                    vcCompanyUser = companyUserMapper.selectVcCompanyUserByCompanyUserId(companyUserId);
+                    if (vcCompanyUser == null) {
+                        // 缓存空对象,防止缓存穿透
+                        redisCache.setCacheObject(cacheKey, new VcCompanyUser(), 3600, TimeUnit.SECONDS);
+                        throw new RuntimeException("用户不存在");
+                    }
+                    // 设置缓存
+                    redisCache.setCacheObject(cacheKey, vcCompanyUser, 3600, TimeUnit.SECONDS);
+                }
+            }
+        }
+//        VcCompanyUser vcCompanyUser = companyUserMapper.selectVcCompanyUserByCompanyUserId(companyUserId);
         try {
             if (vcCompanyUser == null)throw new RuntimeException("用户不存在");
         }catch (Exception e){

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

@@ -46,6 +46,10 @@ wx:
         secret: 99fc38771e111e640c654626cbf7c5e9 # 公众号的appsecret
         token: PPKOdAlCoMO # 接口配置里的Token值
         aesKey: Eswa6VjwtVcw03qZy6Wllgrv5aytIA1SZPEU0kU2 # 接口配置里的EncodingAESKey值
+  # 开放平台app微信授权配置
+  open:
+    app-id: wx0472e2edacf036da
+    secret: 1edaa1b2c153a87bedce8dfb78adffce
 aifabu:  #爱链接
   appKey: 7b471be905ab17ef358c610dd117601d008
 watch:
@@ -89,7 +93,7 @@ headerImg:
   imgUrl: https://jsbk-1323137866.cos.ap-chongqing.myqcloud.com/app/jsbk.jpg
 ipad:
   ipadUrl: http://jsbkipad.sywktxd.cn
-  aiApi:
+  aiApi: http://49.232.181.28:3000/api
   wxIpadUrl:
   voiceApi:
   commonApi:

+ 4 - 4
fs-service/src/main/resources/application-dev-xcsw.yml

@@ -45,10 +45,10 @@ spring:
                 # 从库数据源
                 slave:
                     # 从数据源开关/默认关闭
-                    enabled: false
-                    url:
-                    username:
-                    password:
+                    enabled: true
+                    url: jdbc:mysql://1.95.187.223:3306/fs_his?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&allowMultiQueries=true
+                    username: root
+                    password: Ylrztek250218!3@.
                 # 初始连接数
                 initialSize: 5
                 # 最小连接池数量

+ 3 - 4
fs-service/src/main/resources/application-druid-jzzx.yml

@@ -47,10 +47,9 @@ spring:
                 # 从库数据源
                 slave:
                     # 从数据源开关/默认关闭
-                    enabled: false
-                    url:
-                    username:
-                    password:
+                    url: jdbc:mysql://192.168.0.137:3306/fs_his?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
+                    username: root
+                    password: Ylrztek250218!3@.
                 # 初始连接数
                 initialSize: 5
                 # 最小连接池数量

+ 4 - 4
fs-service/src/main/resources/application-druid-xcsw.yml

@@ -45,10 +45,10 @@ spring:
                 # 从库数据源
                 slave:
                     # 从数据源开关/默认关闭
-                    enabled: false
-                    url:
-                    username:
-                    password:
+                    enabled: true
+                    url: jdbc:mysql://192.168.0.43:3306/fs_his?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&allowMultiQueries=true
+                    username: root
+                    password: Ylrztek250218!3@.
                 # 初始连接数
                 initialSize: 5
                 # 最小连接池数量

+ 13 - 0
fs-service/src/main/resources/mapper/company/CompanySmsLogsMapper.xml

@@ -129,4 +129,17 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         ) t
         GROUP BY t.type
     </select>
+
+    <select id="getCompanySmsLogsByUuIdList" resultType="com.fs.company.domain.CompanySmsLogs">
+        SELECT
+        logs_id,
+        sop_sms_log_id,
+        sms_index
+        FROM
+        company_sms_logs
+        WHERE sop_sms_log_id IN
+        <foreach collection="uuIds" item="item" index="index" open="(" separator="," close=")">
+            #{item}
+        </foreach>
+    </select>
 </mapper>

+ 1 - 1
fs-service/src/main/resources/mapper/company/CompanyWxClientMapper.xml

@@ -182,7 +182,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
                              inner join company_voice_robotic_business t2 on t1.id = t2.wx_client_id and t1.robotic_id = t2.robotic_id
                              inner join company_ai_workflow_exec t3 on t3.business_key = t2.id
         where t1.is_add = 0 and t1.account_id is not null and t1.is_we_com = 1
-        and t3.current_node_type = #{execNodeType} And t3.status = #{execStatus}
+        and t3.current_node_type = #{execNodeType} And t3.status = #{execStatus} and t3.cid_group_no = #{cidGroupId}
         <if test="accountIdList != null and !accountIdList.isEmpty()">
             and t1.account_id in <foreach collection="accountIdList" open="(" separator="," close=")" item="item">#{item}</foreach>
         </if>

+ 100 - 138
fs-service/src/main/resources/mapper/hisStore/FsStoreOrderScrmMapper.xml

@@ -93,7 +93,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     </resultMap>
 
     <sql id="selectFsStoreOrderVo">
-        select id, order_code,service_fee, extend_order_id,pay_order_id,bank_order_id, user_id,order_visit, real_name, user_phone, user_address, cart_id, freight_price, total_num, total_price, total_postage, pay_price, pay_postage,pay_delivery,pay_money, deduction_price, coupon_id, coupon_price, paid, pay_time, pay_type, create_time, update_time, status, refund_status, refund_reason_wap_img, refund_reason_wap_explain, refund_reason_time, refund_reason_wap, refund_reason, refund_price, delivery_sn, delivery_name, delivery_type, delivery_id, gain_integral, use_integral, pay_integral, back_integral, mark, is_del, remark, cost, verify_code, store_id, shipping_type, is_channel, is_remind, is_sys_del,is_prescribe,prescribe_id ,company_id,company_user_id,is_package,package_json,item_json,order_type,package_id,finish_time,delivery_status,delivery_pay_status,delivery_time,delivery_pay_time,delivery_pay_money,tui_money,tui_money_status,delivery_import_time,tui_user_id,tui_user_money_status,order_create_type,store_house_code,dept_id,is_edit_money,customer_id,is_pay_remain,delivery_send_time,certificates,schedule_id,backend_edit_product_type from fs_store_order_scrm
+        select id, order_code,service_fee, extend_order_id,pay_order_id,bank_order_id, user_id,order_visit, real_name, user_phone, user_address, cart_id, freight_price, total_num, total_price, total_postage, pay_price, pay_postage,pay_delivery,pay_money, deduction_price, coupon_id, coupon_price, paid, pay_time, pay_type, create_time, update_time, status, refund_status, refund_reason_wap_img, refund_reason_wap_explain, refund_reason_time, refund_reason_wap, refund_reason, refund_price, delivery_sn, delivery_name, delivery_type, delivery_id, gain_integral, use_integral, pay_integral, back_integral, mark, is_del, remark, cost, verify_code, store_id, shipping_type, is_channel, is_remind, is_sys_del,is_prescribe,prescribe_id ,company_id,company_user_id,is_package,package_json,item_json,order_type,package_id,finish_time,delivery_status,delivery_pay_status,delivery_time,delivery_pay_time,delivery_pay_money,tui_money,tui_money_status,delivery_import_time,tui_user_id,tui_user_money_status,order_create_type,store_house_code,dept_id,is_edit_money,customer_id,is_pay_remain,delivery_send_time,certificates,schedule_id,backend_edit_product_type,video_id,course_id from fs_store_order_scrm
     </sql>
 
     <select id="selectFsStoreOrderList" parameterType="FsStoreOrderScrm" resultMap="FsStoreOrderResult">
@@ -660,153 +660,94 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     </select>
 
     <select id="selectOrderSaleStatisticsList" resultType="com.fs.hisStore.vo.OrderStatisticsVo">
-      SELECT * FROM (  SELECT
+        SELECT * FROM (
+        SELECT
         cu.user_id,
         cu.user_name,
         cu.nick_name,
         c.company_name,
         c.company_id,
-        SUM(fso.`status` != 0) AS totalNum,
-        SUM(
-        CASE
-        WHEN fso.`status` != 0 THEN
-        fso.total_price
-        ELSE
-        0
-        END
-        ) totalPrice,
-        SUM(fso.`status` = 5) AS dealNum,
-        SUM(
-        CASE
-        WHEN fso.`status` = 5 THEN
-        fso.total_price
-        ELSE
-        0
-        END
-        ) AS dealPrice,
-        SUM(
-        fso.`status` = 1
-        OR fso.`status` = -2
-        ) AS cancelNum,
-        SUM(
-        CASE
-        WHEN fso.`status` = 1
-        OR fso.`status` = -2 THEN
-        fso.total_price
-        ELSE
-        0
-        END
-        ) AS cancelPrice,
-        SUM(
-        fso.`status` = 2
-        AND (
-        fso.delivery_pay_status = 0
-        OR fso.delivery_pay_status IS NULL
-        )
-        ) AS pendingNum,
-        SUM(
-        CASE
-        WHEN fso.`status` = 2
-        AND (
-        fso.delivery_pay_status = 0
-        OR fso.delivery_pay_status IS NULL
-        ) THEN
-        fso.total_price
-        ELSE
-        0
-        END
-        )  AS pendingPrice,
-        SUM(
-        fso.`status` = 3
-        AND fso.delivery_pay_status > 0
-        ) AS invoiceNum,
-        SUM(
-        CASE
-        WHEN fso.`status` = 3
-        AND fso.delivery_pay_status > 0 THEN
-        fso.total_price
-        ELSE
-        0
-        END
-        ) AS invoicePrice,
-        IFNULL(
-        SUM(
-        fso.`status` > 3
-        AND fso.delivery_pay_status = 3
-        ),
-        0
-        ) AS signForNum,
-        SUM(
-        CASE
-        WHEN fso.`status` > 3
-        AND fso.delivery_pay_status = 3 THEN
-        fso.total_price
-        ELSE
-        0
-        END
-        ) AS signFPrice,
-        SUM(
-        fso.`status` = -1
-        AND fso.refund_status = 2
-        ) AS chargebackNum,
-        SUM(
-        CASE
-        WHEN fso.`status` = -1
-        AND fso.refund_status = 2 THEN
-        fso.total_price
-        ELSE
-        0
-        END
-        ) AS chargebackPrice
+        COUNT(DISTINCT fso.id) AS totalNum,
+        SUM(CASE WHEN fso.`status` != 0 THEN fso.total_price ELSE 0 END) AS totalPrice,
+        SUM(CASE WHEN fso.`status` != 0 THEN fso.pay_price ELSE 0 END) AS actualPaymentAmount,
+        SUM(CASE WHEN fso.`status` = 5 THEN 1 ELSE 0 END) AS dealNum,
+        SUM(CASE WHEN fso.`status` = 5 THEN fso.total_price ELSE 0 END) AS dealPrice,
+        SUM(CASE WHEN fso.`status` = 1 OR fso.`status` = -2 THEN 1 ELSE 0 END) AS cancelNum,
+        SUM(CASE WHEN fso.`status` = 1 OR fso.`status` = -2 THEN fso.total_price ELSE 0 END) AS cancelPrice,
+        SUM(CASE WHEN fso.`status` = 2 AND (fso.delivery_pay_status = 0 OR fso.delivery_pay_status IS NULL) THEN 1 ELSE 0 END) AS pendingNum,
+        SUM(CASE WHEN fso.`status` = 2 AND (fso.delivery_pay_status = 0 OR fso.delivery_pay_status IS NULL) THEN fso.total_price ELSE 0 END) AS pendingPrice,
+        SUM(CASE WHEN fso.`status` = 3 AND fso.delivery_pay_status > 0 THEN 1 ELSE 0 END) AS invoiceNum,
+        SUM(CASE WHEN fso.`status` = 3 AND fso.delivery_pay_status > 0 THEN fso.total_price ELSE 0 END) AS invoicePrice,
+        IFNULL(SUM(CASE WHEN fso.`status` > 3 AND fso.delivery_pay_status = 3 THEN 1 ELSE 0 END), 0) AS signForNum,
+        SUM(CASE WHEN fso.`status` > 3 AND fso.delivery_pay_status = 3 THEN fso.total_price ELSE 0 END) AS signFPrice,
+        SUM(CASE WHEN fso.`status` = -1 AND fso.refund_status = 2 THEN 1 ELSE 0 END) AS chargebackNum,
+        SUM(CASE WHEN fso.`status` = -1 AND fso.refund_status = 2 THEN fso.total_price ELSE 0 END) AS chargebackPrice
         FROM
         company_user cu
         INNER JOIN company c ON cu.company_id = c.company_id
         LEFT JOIN fs_store_order_scrm fso ON cu.user_id = fso.company_user_id
-        INNER JOIN fs_store_order_item_scrm fsoi ON fso.id = fsoi.order_id
-        INNER JOIN fs_store_product_scrm fsp ON fsoi.product_id = fsp.product_id
-        INNER JOIN fs_store_product_category_scrm fspc ON fspc.cate_id = fsp.cate_id
         WHERE
         fso.is_del = '0'
         AND fso.`status` != 0
+
+        <!-- 公司ID条件 -->
         <if test="param.companyId != null and param.companyId != ''">
-            AND  c.company_id = #{param.companyId}
+            AND c.company_id = #{param.companyId}
         </if>
 
+        <!-- 员工账号条件 -->
         <if test="param.userName != null and param.userName != ''">
-            AND cu.user_name Like CONCAT('%',#{param.userName},'%')
+            AND cu.user_name LIKE CONCAT('%', #{param.userName}, '%')
         </if>
 
+        <!-- 销售名称条件 -->
         <if test="param.companyUser != null and param.companyUser != ''">
-            AND cu.nick_name Like CONCAT('%',#{param.companyUser},'%')
+            AND cu.nick_name LIKE CONCAT('%', #{param.companyUser}, '%')
         </if>
 
-        <if test="param.productId != null and param.productId != ''">
-            AND fsp.product_id = #{param.productId}
+        <!-- 时间条件 -->
+        <if test="param.startDate != null">
+            AND DATE_FORMAT(fso.pay_time, '%Y-%m') >= DATE_FORMAT(#{param.startDate}, '%Y-%m')
         </if>
-
-        <if test="param.productName != null and param.productName != ''">
-            AND fsp.product_name Like CONCAT('%',#{param.productName},'%')
+        <if test="param.endDate != null">
+            AND DATE_FORMAT(fso.pay_time, '%Y-%m') &lt;= DATE_FORMAT(#{param.endDate}, '%Y-%m')
         </if>
 
-        <if test="param.cateName != null and param.cateName != ''">
-            AND fspc.cate_name Like CONCAT('%',#{param.cateName},'%')
-        </if>
+        <!-- 商品和标签筛选(使用EXISTS避免重复计算) -->
+        <if test="param.productId != null and param.productId != ''
+                  or param.productName != null and param.productName != ''
+                  or param.cateId != null and param.cateId != ''
+                  or param.cateName != null and param.cateName != ''">
+            AND EXISTS (
+            SELECT 1
+            FROM fs_store_order_item_scrm fsoi
+            INNER JOIN fs_store_product_scrm fsp ON fsoi.product_id = fsp.product_id
+            LEFT JOIN fs_store_product_category_scrm fspc ON fspc.cate_id = fsp.cate_id
+            WHERE fsoi.order_id = fso.id
 
-        <if test="param.cateId != null and param.cateId != ''">
-            AND fspc.cate_id = #{param.cateId}
-        </if>
+            <if test="param.productId != null and param.productId != ''">
+                AND fsp.product_id = #{param.productId}
+            </if>
 
-        <if test="param.startDate != null">
-            AND DATE_FORMAT(fso.pay_time,'%Y-%m') >= DATE_FORMAT(#{param.startDate},'%Y-%m')
-        </if>
+            <if test="param.productName != null and param.productName != ''">
+                AND fsp.product_name LIKE CONCAT('%', #{param.productName}, '%')
+            </if>
 
-        <if test="param.endDate != null">
-            AND DATE_FORMAT(fso.pay_time,'%Y-%m') &lt;= DATE_FORMAT(#{param.endDate},'%Y-%m')
+            <if test="param.cateId != null and param.cateId != ''">
+                AND fspc.cate_id = #{param.cateId}
+            </if>
+
+            <if test="param.cateName != null and param.cateName != ''">
+                AND fspc.cate_name LIKE CONCAT('%', #{param.cateName}, '%')
+            </if>
+            )
         </if>
+
         GROUP BY
-        cu.user_id
-        ) a ORDER BY
-        a.company_id DESC,a.totalNum DESC
+        cu.user_id, cu.user_name, cu.nick_name, c.company_name, c.company_id
+        ) a
+        ORDER BY
+        a.company_id DESC, a.totalNum DESC
     </select>
 
 
@@ -1213,10 +1154,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             </if>
             <if test="maps.isHealth != null and maps.isHealth !=  ''   ">
                 and (o.company_id is null
-                or o.order_type = 2)
+                or o.order_type = 2 or o.order_type = 3)
             </if>
             <if test="maps.notHealth != null  ">
-                and o.company_id is not null
+                and o.company_id is not null and o.order_type = 0
             </if>
             <if test="maps.companyUserId != null  ">
                 and o.company_user_id =#{maps.companyUserId}
@@ -1233,9 +1174,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="maps.productName != null and  maps.productName !=  '' ">
                 and fsp.product_name like concat('%', #{maps.productName}, '%')
             </if>
-            <if test="maps.orderType != null    ">
+            <if test="maps.orderType != null and maps.orderType != -1">
                 and o.order_type =#{maps.orderType}
             </if>
+            <if test="maps.orderType != null and maps.orderType == -1">
+                and o.order_type in (2, 3)
+            </if>
             <if test="maps.payType != null    ">
                 and o.pay_type =#{maps.payType}
             </if>
@@ -1389,10 +1333,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             </if>
             <if test="maps.isHealth != null and maps.isHealth !=  ''   ">
                 and (o.company_id is null
-                or o.order_type = 2)
+                or o.order_type = 2 or o.order_type = 3)
             </if>
             <if test="maps.notHealth != null  ">
-                and o.company_id is not null
+                and o.company_id is not null and o.order_type = 0
             </if>
             <if test="maps.companyUserId != null  ">
                 and o.company_user_id =#{maps.companyUserId}
@@ -1409,9 +1353,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="maps.productName != null and  maps.productName !=  '' ">
                 and fsp.product_name like concat('%', #{maps.productName}, '%')
             </if>
-            <if test="maps.orderType != null    ">
+            <if test="maps.orderType != null and maps.orderType != -1">
                 and o.order_type =#{maps.orderType}
             </if>
+            <if test="maps.orderType != null and maps.orderType == -1">
+                and o.order_type in (2, 3)
+            </if>
             <if test="maps.payType != null    ">
                 and o.pay_type =#{maps.payType}
             </if>
@@ -1542,10 +1489,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             </if>
             <if test="maps.isHealth != null and maps.isHealth !=  ''   ">
                 and (o.company_id is null
-                or o.order_type = 2)
+                or o.order_type = 2 or o.order_type = 3)
             </if>
             <if test="maps.notHealth != null  ">
-                and o.company_id is not null
+                and o.company_id is not null and o.order_type = 0
             </if>
             <if test="maps.companyUserId != null  ">
                 and o.company_user_id =#{maps.companyUserId}
@@ -1562,9 +1509,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="maps.productName != null and  maps.productName !=  '' ">
                 and fsp.product_name like concat('%', #{maps.productName}, '%')
             </if>
-            <if test="maps.orderType != null    ">
+            <if test="maps.orderType != null and maps.orderType != -1">
                 and o.order_type =#{maps.orderType}
             </if>
+            <if test="maps.orderType != null and maps.orderType == -1">
+                and o.order_type in (2, 3)
+            </if>
             <if test="maps.payType != null    ">
                 and o.pay_type =#{maps.payType}
             </if>
@@ -1699,10 +1649,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             </if>
             <if test="maps.isHealth != null and maps.isHealth !=  ''   ">
                 and (o.company_id is null
-                or o.order_type = 2)
+                or o.order_type = 2 or o.order_type = 3)
             </if>
             <if test="maps.notHealth != null  ">
-                and o.company_id is not null
+                and o.company_id is not null and o.order_type = 0
             </if>
             <if test="maps.companyUserId != null  ">
                 and o.company_user_id =#{maps.companyUserId}
@@ -1719,9 +1669,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="maps.productName != null and  maps.productName !=  '' ">
                 and fsp.product_name like concat('%', #{maps.productName}, '%')
             </if>
-            <if test="maps.orderType != null    ">
+            <if test="maps.orderType != null and maps.orderType != -1">
                 and o.order_type =#{maps.orderType}
             </if>
+            <if test="maps.orderType != null and maps.orderType == -1">
+                and o.order_type in (2, 3)
+            </if>
             <if test="maps.payType != null    ">
                 and o.pay_type =#{maps.payType}
             </if>
@@ -1852,10 +1805,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             </if>
             <if test="maps.isHealth != null and maps.isHealth !=  ''   ">
                 and (o.company_id is null
-                or o.order_type = 2)
+                or o.order_type = 2 or o.order_type = 3)
             </if>
             <if test="maps.notHealth != null  ">
-                and o.company_id is not null
+                and o.company_id is not null and o.order_type = 0
             </if>
             <if test="maps.companyUserId != null  ">
                 and o.company_user_id =#{maps.companyUserId}
@@ -1872,9 +1825,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="maps.productName != null and  maps.productName !=  '' ">
                 and fsp.product_name like concat('%', #{maps.productName}, '%')
             </if>
-            <if test="maps.orderType != null    ">
+            <if test="maps.orderType != null and maps.orderType != -1">
                 and o.order_type =#{maps.orderType}
             </if>
+            <if test="maps.orderType != null and maps.orderType == -1">
+                and o.order_type in (2, 3)
+            </if>
             <if test="maps.payType != null    ">
                 and o.pay_type =#{maps.payType}
             </if>
@@ -2048,10 +2004,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             </if>
             <if test="maps.isHealth != null and maps.isHealth !=  ''   ">
                 and (o.company_id is null
-                or o.order_type = 2)
+                or o.order_type = 2 or o.order_type = 3)
             </if>
             <if test="maps.notHealth != null  ">
-                and o.company_id is not null
+                and o.company_id is not null and o.order_type = 0
             </if>
             <if test="maps.companyUserId != null  ">
                 and o.company_user_id =#{maps.companyUserId}
@@ -2068,9 +2024,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="maps.productName != null and  maps.productName !=  '' ">
                 and fsp.product_name like concat('%', #{maps.productName}, '%')
             </if>
-            <if test="maps.orderType != null    ">
+            <if test="maps.orderType != null and maps.orderType != -1">
                 and o.order_type =#{maps.orderType}
             </if>
+            <if test="maps.orderType != null and maps.orderType == -1">
+                and o.order_type in (2, 3)
+            </if>
             <if test="maps.payType != null    ">
                 and o.pay_type =#{maps.payType}
             </if>
@@ -2242,9 +2201,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <if test="maps.companyUserNickName != null and maps.companyUserNickName != ''">
             AND cu.nick_name LIKE CONCAT('%', #{maps.companyUserNickName}, '%')
         </if>
-        <if test="maps.orderType != null">
+        <if test="maps.orderType != null and maps.orderType != -1">
             AND o.order_type = #{maps.orderType}
         </if>
+        <if test="maps.orderType != null and maps.orderType == -1">
+            AND o.order_type in (2, 3)
+        </if>
         <if test="maps.payType != null">
             AND o.pay_type = #{maps.payType}
         </if>

+ 8 - 16
fs-service/src/main/resources/mapper/hisStore/FsStoreProductScrmMapper.xml

@@ -514,13 +514,11 @@
             inner join fs_store_scrm fs on fs.store_id = p.store_id and fs.is_audit = 1
         </if>
         where p.is_del=0 and p.is_show=1
-        <if test='config.isAudit == "1"'>
-            and p.is_audit = '1'
-        </if>
+            and p.is_audit = 1
         <if test='appId != null and appId != "" '>
             and ((FIND_IN_SET(#{appId}, p.app_ids) > 0))
         </if>
-        and p.is_new=1 and p.is_display=1 order by p.sort desc limit #{count}
+        and p.is_new=1 and p.is_display=1 order by COALESCE(p.sort, 999999) asc, p.create_time desc limit #{count}
     </select>
     <select id="selectFsStoreProductNewQueryPage" resultType="com.fs.hisStore.vo.FsStoreProductListQueryVO">
         select p.* from fs_store_product_scrm p
@@ -528,16 +526,14 @@
             inner join fs_store_scrm fs on fs.store_id = p.store_id and fs.is_audit = 1
         </if>
         where p.is_del=0 and p.is_show=1
-        <if test='config.isAudit == "1"'>
-            and p.is_audit = '1'
-        </if>
+            and p.is_audit = 1
         <if test='appId != null and appId != ""'>
             and ((FIND_IN_SET(#{appId}, p.app_ids) > 0))
         </if>
         <if test='keyword != null and keyword != ""'>
             and p.product_name like CONCAT('%', #{keyword}, '%')
         </if>
-        and p.is_new=1 and p.is_display=1 order by p.sort desc
+        and p.is_new=1 and p.is_display=1 order by COALESCE(p.sort, 999999) asc, p.create_time desc
     </select>
     <select id="selectFsStoreProductHotQuery" resultType="com.fs.hisStore.vo.FsStoreProductListQueryVO">
         select p.* from fs_store_product_scrm p
@@ -545,13 +541,11 @@
             inner join fs_store_scrm fs on fs.store_id = p.store_id and fs.is_audit = 1
         </if>
         where p.is_del=0 and p.is_show=1
-        <if test='config.isAudit == "1" '>
-            and p.is_audit = '1'
-        </if>
+            and p.is_audit = 1
         <if test='appId != null and appId != "" '>
             and ((FIND_IN_SET(#{appId}, p.app_ids) > 0))
         </if>
-        and  p.is_hot=1 and p.is_display=1 order by p.sort desc limit #{count}
+        and  p.is_hot=1 and p.is_display=1 order by COALESCE(p.sort, 999999) asc, p.create_time desc limit #{count}
     </select>
     <select id="selectFsStoreProductHotQueryPage" resultType="com.fs.hisStore.vo.FsStoreProductListQueryVO">
         select p.* from fs_store_product_scrm p
@@ -559,16 +553,14 @@
             inner join fs_store_scrm fs on fs.store_id = p.store_id and fs.is_audit = 1
         </if>
         where p.is_del=0 and p.is_show=1
-        <if test='config.isAudit == "1" '>
-            and p.is_audit = '1'
-        </if>
+            and p.is_audit = 1
         <if test='appId != null and appId != ""'>
             and ((FIND_IN_SET(#{appId}, p.app_ids) > 0))
         </if>
         <if test='keyword != null and keyword != ""'>
             and p.product_name like CONCAT('%', #{keyword}, '%')
         </if>
-        and  p.is_hot=1 and p.is_display=1 order by p.sort desc
+        and  p.is_hot=1 and p.is_display=1 order by COALESCE(p.sort, 999999) asc, p.create_time desc
     </select>
     <select id="selectFsStoreProductGoodListQuery" resultType="com.fs.hisStore.vo.FsStoreProductListQueryVO">
         select p.* from fs_store_product_scrm p

+ 4 - 4
fs-service/src/main/resources/mapper/hisStore/FsStoreProductUserEndCategoryMapper.xml

@@ -22,19 +22,19 @@
 
     <select id="selectDistinctProductIdsByCategoryId" resultType="java.lang.Long">
         select distinct a.product_id from fs_store_product_user_end_category a left join fs_store_product_scrm c on a.product_id = c.product_id
-        where a.user_end_category_id = #{categoryId} and c.is_del = 0 and c.is_show = 1
+        where a.user_end_category_id = #{categoryId} and c.is_del = 0 and c.is_show = 1 and c.is_display = 1 and c.is_audit = 1
         <if test="keyword != null and keyword != ''">
             and c.product_name like CONCAT('%', #{keyword}, '%')
         </if>
-        order by c.sort desc, c.create_time desc, a.product_id
+        order by c.sort asc, c.create_time desc, a.product_id
     </select>
 
     <select id="selectDistinctProductIds" resultType="java.lang.Long">
         select distinct a.product_id from fs_store_product_user_end_category  a left join fs_store_product_scrm c on a.product_id = c.product_id
-        where c.is_del = 0 and c.is_show = 1
+        where c.is_del = 0 and c.is_show = 1 and c.is_display = 1 and c.is_audit = 1
         <if test="keyword != null and keyword != ''">
             and c.product_name like CONCAT('%', #{keyword}, '%')
         </if>
-        order by c.sort desc, c.create_time desc, a.product_id
+        order by c.sort asc, c.create_time desc, a.product_id
     </select>
 </mapper>

+ 6 - 3
fs-service/src/main/resources/mapper/live/LiveDataMapper.xml

@@ -629,7 +629,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             lufe.company_id AS companyId,
             COALESCE(c.company_name, '总台') AS companyName,
             COALESCE(COUNT(DISTINCT CASE
-                WHEN lwu.online_seconds >= COALESCE(vd.total_duration, 0)
+                WHEN lwu.online_seconds >= 1200
+<!--                     AND lwu.online_seconds >= COALESCE(vd.total_duration, 0)-->
                      AND COALESCE(vd.total_duration, 0) > 0
                 THEN lwu.user_id END), 0) AS totalCompleteCount
         FROM live_watch_user lwu
@@ -684,7 +685,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             COALESCE(COUNT(DISTINCT CASE
                 WHEN lwu.live_flag = 1
                      AND lwu.replay_flag = 0
-                     AND lwu.online_seconds >= COALESCE(vd.total_duration, 0)
+                     AND lwu.online_seconds >= 1200
+<!--                     AND lwu.online_seconds >= COALESCE(vd.total_duration, 0)-->
                      AND COALESCE(vd.total_duration, 0) > 0
                 THEN lwu.user_id END), 0) AS liveCompleteCount
         FROM live_watch_user lwu
@@ -739,7 +741,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             COALESCE(COUNT(DISTINCT CASE
                 WHEN lwu.live_flag = 0
                      AND lwu.replay_flag = 1
-                     AND lwu.online_seconds >= COALESCE(vd.total_duration, 0)
+                     AND lwu.online_seconds >= 1200
+<!--                     AND lwu.online_seconds >= COALESCE(vd.total_duration, 0)-->
                      AND COALESCE(vd.total_duration, 0) > 0
                 THEN lwu.user_id END), 0) AS replayCompleteCount
         FROM live_watch_user lwu

+ 38 - 6
fs-user-app/src/main/java/com/fs/app/controller/CompanyUserController.java

@@ -68,6 +68,7 @@ import org.apache.http.entity.StringEntity;
 import org.apache.http.impl.client.CloseableHttpClient;
 import org.apache.http.impl.client.HttpClients;
 import org.apache.http.util.EntityUtils;
+import org.redisson.api.RedissonClient;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.BeanUtils;
@@ -131,6 +132,8 @@ public class CompanyUserController extends AppBaseController {
     @Autowired
     private QwUserMapper qwUserMapper;
 
+    private static final String VOICE_CLONE_CACHE_KEY = "voice:clone:company:user:id";
+
     @PostMapping("/login")
     public R Login(@RequestBody CompanyUserLoginParam param, HttpServletRequest request) {
         try {
@@ -326,8 +329,9 @@ public class CompanyUserController extends AppBaseController {
             JSONObject vcConfig = configUtil.generateConfigByKey(SysConfigEnum.VS_CONFIG.getKey());
             if (vcConfig != null && !vcConfig.isEmpty() &&
                     "2".equals(vcConfig.getString("type"))) {
-                VcCompanyUser vcCompanyUser = companyUserMapper.selectVcCompanyUserByCompanyUserId(companyUserId);
-                if (vcCompanyUser == null) throw new RuntimeException("用户不存在");
+                VcCompanyUser vcCompanyUser = getVcCompanyUser(companyUserId);
+//                VcCompanyUser vcCompanyUser = companyUserMapper.selectVcCompanyUserByCompanyUserId(companyUserId);
+//                if (vcCompanyUser == null) throw new RuntimeException("用户不存在");
                 audioVO = ttsServiceImpl.textToSpeech(new TtsRequest(null, null, vcCompanyUser.getSpeakerId(), qwSopTempVoice.getVoiceTxt().replace(" ", "")));
                 QwUser qwUser = new QwUser();
                 qwUser.setCompanyUserId(companyUserId);
@@ -355,6 +359,33 @@ public class CompanyUserController extends AppBaseController {
         return R.ok().put("data", audioVO);
     }
 
+    private static final Integer CACHE_TTL = 3600; // 缓存过期时间,单位秒
+
+    public VcCompanyUser getVcCompanyUser(Long companyUserId) {
+        String cacheKey = VOICE_CLONE_CACHE_KEY + companyUserId;
+        VcCompanyUser vcCompanyUser = redisCache.getCacheObject(cacheKey);
+        if (vcCompanyUser != null) {
+            return vcCompanyUser;
+        }
+        // 使用双重检查锁,防止缓存击穿
+        synchronized (this) {
+            // 再次检查缓存,防止在等待锁的过程中已经被其他线程加载
+            vcCompanyUser = redisCache.getCacheObject(cacheKey);
+            if (vcCompanyUser != null) {
+                return vcCompanyUser;
+            }
+            // 查询数据库
+            vcCompanyUser = companyUserMapper.selectVcCompanyUserByCompanyUserId(companyUserId);
+            if (vcCompanyUser == null) {
+                // 缓存空对象,防止缓存穿透
+                redisCache.setCacheObject(cacheKey, new VcCompanyUser(), CACHE_TTL,TimeUnit.SECONDS);
+                throw new RuntimeException("用户不存在");
+            }
+            // 设置缓存
+            redisCache.setCacheObject(cacheKey, vcCompanyUser, CACHE_TTL,TimeUnit.SECONDS);
+            return vcCompanyUser;
+        }
+    }
     /**
      * 当只有user_voice_url时,生成表中对应条的voice_url
      *
@@ -380,8 +411,9 @@ public class CompanyUserController extends AppBaseController {
             JSONObject vcConfig = configUtil.generateConfigByKey(SysConfigEnum.VS_CONFIG.getKey());
             if (vcConfig != null && !vcConfig.isEmpty() &&
                     "2".equals(vcConfig.getString("type"))) {
-                VcCompanyUser vcCompanyUser = companyUserMapper.selectVcCompanyUserByCompanyUserId(companyUserId);
-                if (vcCompanyUser == null) throw new RuntimeException("用户不存在");
+                VcCompanyUser vcCompanyUser = getVcCompanyUser(companyUserId);
+//                VcCompanyUser vcCompanyUser = companyUserMapper.selectVcCompanyUserByCompanyUserId(companyUserId);
+//                if (vcCompanyUser == null) throw new RuntimeException("用户不存在");
                 audioVO = ttsServiceImpl.textToSpeech(new TtsRequest(null, null, vcCompanyUser.getSpeakerId(), qwSopTempVoice.getVoiceTxt().replace(" ", "")));
                 QwUser qwUser = new QwUser();
                 qwUser.setCompanyUserId(companyUserId);
@@ -816,8 +848,8 @@ public class CompanyUserController extends AppBaseController {
             HttpServletRequest httpRequest) throws Exception {
         if (companyUserId == null) companyUserId = 123L;
 //                getCompanyUserId();
-
-        VcCompanyUser vcCompanyUser = companyUserMapper.selectVcCompanyUserByCompanyUserId(companyUserId);
+        VcCompanyUser vcCompanyUser = getVcCompanyUser(companyUserId);
+//        VcCompanyUser vcCompanyUser = companyUserMapper.selectVcCompanyUserByCompanyUserId(companyUserId);
         if (vcCompanyUser == null) throw new RuntimeException("用户不存在");
         AudioVO audioVO = voiceCloneController.synthesizeSimple(text, vcCompanyUser.getSpeakerId(), "mp3", 1);
         vcCompanyUser.setLatestTextToSpeechUrl(audioVO.getUrl());

+ 74 - 0
fs-user-app/src/main/java/com/fs/app/controller/app/AppController.java

@@ -0,0 +1,74 @@
+package com.fs.app.controller.app;
+
+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.his.enums.PaymentMethodEnum;
+import com.fs.his.param.FsIntegralOrderDoPayParam;
+import com.fs.his.param.FsUserAddIntegralTemplateParam;
+import com.fs.his.service.IFsUserIntegralLogsService;
+import com.fs.hisStore.service.IFsStoreOrderScrmService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * @description: APP 相关接口
+ * @author: Xgb
+ * @createDate: 2026/2/3
+ * @version: 1.0
+ */
+@Api("app登录接口")
+@RestController
+@RequestMapping(value = "/app/api")
+@Slf4j
+public class AppController extends AppBaseController {
+
+    @Autowired
+    private IFsUserIntegralLogsService userIntegralLogsService;
+
+    @Autowired
+    private IFsStoreOrderScrmService fsStoreOrderScrmService;
+
+    /**
+     * @Description: APP 获取积分  28-下载
+     * @Param:
+     * @Return:
+     * @Author xgb
+     * @Date 2026/2/3 11:06
+     */
+    @ApiOperation("添加积分")
+    @Login
+    @PostMapping("/addIntegral")
+    public R addIntegral(@RequestBody FsUserAddIntegralTemplateParam param) {
+        param.setUserId(Long.parseLong(getUserId()));
+        return userIntegralLogsService.addIntegralTemplate(param);
+    }
+
+
+    @Login
+    @RepeatSubmit
+    @ApiOperation("支付宝支付")
+    @PostMapping("/aliPayment")
+    public R aliPayment(@Validated @RequestBody FsIntegralOrderDoPayParam param) {
+        param.setUserId(Long.parseLong(getUserId()));
+        return fsStoreOrderScrmService.payment(param, PaymentMethodEnum.ALIPAY);
+    }
+
+
+    @Login
+    @RepeatSubmit
+    @ApiOperation("微信支付")
+    @PostMapping("/wxPayment")
+    public R wxPayment(@Validated @RequestBody FsIntegralOrderDoPayParam param) {
+        param.setUserId(Long.parseLong(getUserId()));
+        return fsStoreOrderScrmService.payment(param, PaymentMethodEnum.WX_APP);
+    }
+}

+ 86 - 0
fs-user-app/src/main/java/com/fs/app/controller/course/CourseFsUserController.java

@@ -3,6 +3,7 @@ package com.fs.app.controller.course;
 
 
 import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.json.JSONUtil;
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fs.app.annotation.UserOperationLog;
 import com.fs.app.controller.AppBaseController;
@@ -16,10 +17,17 @@ import com.fs.common.core.domain.model.LoginUser;
 import com.fs.common.enums.BusinessType;
 import com.fs.common.utils.CloudHostUtils;
 import com.fs.common.utils.SecurityUtils;
+import com.fs.common.core.redis.RedisCache;
+import com.fs.course.config.CourseConfig;
+import com.fs.course.domain.FsCourseWatchLog;
+import com.fs.course.domain.FsUserCourseVideo;
 import com.fs.course.dto.BatchSendCourseDTO;
+import com.fs.course.mapper.FsCourseWatchLogMapper;
+import com.fs.course.mapper.FsUserCourseVideoMapper;
 import com.fs.course.param.*;
 import com.fs.course.param.newfs.FsUserCourseAddCompanyUserParam;
 import com.fs.course.param.newfs.FsUserCourseVideoLinkParam;
+import com.fs.course.param.newfs.FsUserCourseVideoRemainTimeParam;
 import com.fs.course.param.newfs.FsUserCourseVideoUParam;
 import com.fs.course.service.*;
 import com.fs.course.vo.FsUserCourseVideoH5VO;
@@ -28,6 +36,7 @@ import com.fs.his.domain.FsUser;
 import com.fs.his.enums.FsUserOperationEnum;
 import com.fs.im.dto.OpenImResponseDTO;
 import com.fs.im.service.OpenIMService;
+import com.fs.system.service.ISysConfigService;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import org.slf4j.Logger;
@@ -64,6 +73,14 @@ public class CourseFsUserController extends AppBaseController {
 
     @Autowired
     private RedisTemplate redisTemplate;
+    @Autowired
+    private ISysConfigService sysConfigService;
+    @Autowired
+    private FsCourseWatchLogMapper fsCourseWatchLogMapper;
+    @Autowired
+    private FsUserCourseVideoMapper fsUserCourseVideoMapper;
+    @Autowired
+    private RedisCache redisCache;
 
 
 
@@ -103,6 +120,75 @@ public class CourseFsUserController extends AppBaseController {
         return R.ok().put("data",course);
     }
 
+    @ApiOperation("h5课程完课倒计时")
+    @PostMapping("/getRemainTime")
+    public R getRemainTime(@RequestBody FsUserCourseVideoRemainTimeParam param)
+    {
+        String json = sysConfigService.selectConfigByKey("course.config");
+        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
+        Integer remainTime = 0;
+        if (config.getCompletionCountdown() != null && config.getCompletionCountdown()) {
+            FsCourseWatchLog fsCourseWatchLog = fsCourseWatchLogMapper.selectFsCourseWatchLogWithUCCV(param.getFsUserId(), param.getCompanyUserId(), param.getCourseId(), param.getVideoId());
+            if (fsCourseWatchLog == null) {
+                return R.error("未查询到用户的看课记录!");
+            }
+            
+            // 如果已经完课,剩余时间为0
+            if (fsCourseWatchLog != null && fsCourseWatchLog.getLogType() != null && fsCourseWatchLog.getLogType() == 2) {
+                remainTime = 0;
+            } else {
+                // 获取已观看时长(优先从Redis获取,因为可能还未同步到数据库)
+                Long watchedDuration = 0L;
+                String redisKey = "h5wxuser:watch:duration:" + param.getFsUserId() + ":" + param.getVideoId() + ":" + param.getCompanyUserId();
+                String durationStr = redisCache.getCacheObject(redisKey);
+                if (durationStr != null) {
+                    watchedDuration = Long.valueOf(durationStr);
+                } else if (fsCourseWatchLog != null && fsCourseWatchLog.getDuration() != null) {
+                    watchedDuration = fsCourseWatchLog.getDuration();
+                }
+                
+                // 获取视频总时长(参照scheduleUpdateDurationToDatabase中的getFsUserVideoDuration方法)
+                Long videoDuration = getFsUserVideoDuration(param.getVideoId().longValue());
+                
+                if (videoDuration != null && videoDuration > 0 && config.getAnswerRate() != null) {
+                    // 参照scheduleUpdateDurationToDatabase中的完课逻辑:percentage >= config.getAnswerRate()
+                    // 计算需要观看的时长(秒)
+                    long requiredDuration = videoDuration * config.getAnswerRate() / 100;
+                    
+                    // 计算剩余时间(秒)
+                    long remainTimeSeconds = requiredDuration - watchedDuration;
+                    remainTime = (int) Math.max(0, remainTimeSeconds);
+                }
+            }
+        }
+
+        return R.ok().put("remainTime",remainTime);
+    }
+
+    private Long getFsUserVideoDuration(Long videoId) {
+        //将视频时长也存到redis
+        String videoRedisKey = "h5wxuser:video:duration:" + videoId;
+        Long videoDuration = 0L;
+        try {
+            videoDuration = redisCache.getCacheObject(videoRedisKey);
+        } catch (Exception e) {
+            String string = redisCache.getCacheObject(videoRedisKey);
+            if (string != null) {
+                videoDuration = Long.parseLong(string);
+            }
+            logger.error("key中id为S:{}", videoDuration);
+        }
+
+        if (videoDuration == null || videoDuration == 0) {
+            FsUserCourseVideo video = fsUserCourseVideoMapper.selectFsUserCourseVideoByVideoId(videoId);
+            if (video != null && video.getDuration() != null) {
+                videoDuration = video.getDuration();
+                redisCache.setCacheObject(videoRedisKey, video.getDuration());
+            }
+        }
+        return videoDuration;
+    }
+
     @Login
     @ApiOperation("H5课程详情")
     @GetMapping("/videoDetails")

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

@@ -7,4 +7,4 @@ server:
 spring:
   profiles:
     active: dev
-group-no: 1
+group-no: 3

+ 1 - 1
fs-wx-task/src/main/java/com/fs/app/service/WxTaskService.java

@@ -252,7 +252,7 @@ public class WxTaskService {
         String json = sysConfigService.selectConfigByKey("wx.config");
         WxConfig config = JSONUtil.toBean(json, WxConfig.class);
         // 需要添加微信的列表
-        List<CompanyWxClient4WorkFlowVO> list = companyWxClientService.getAddWxList4Workflow(accountIdList);
+        List<CompanyWxClient4WorkFlowVO> list = companyWxClientService.getAddWxList4Workflow(accountIdList,cidGroupNo);
         log.info("需要添加微信的数量:{}", list.size());
         if (list.isEmpty()) return;
         List<CompanyWxClient> addList = new ArrayList<>();

+ 1 - 1
fs-wx-task/src/main/resources/application.yml

@@ -14,4 +14,4 @@ spring:
 #    active: druid-sxjz
 #    active: druid-hdt
 #    active: druid-myhk-test
-cid-group-no: 1
+cid-group-no: 3