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

Merge remote-tracking branch 'origin/master'

zyp 1 неделя назад
Родитель
Сommit
50118d1f4d
100 измененных файлов с 2103 добавлено и 300 удалено
  1. 4 0
      fs-admin/src/main/java/com/fs/company/controller/CompanyUserAllController.java
  2. 35 0
      fs-admin/src/main/java/com/fs/course/controller/FsUserCourseCategoryController.java
  3. 42 0
      fs-admin/src/main/java/com/fs/his/controller/FsAiWorkflowController.java
  4. 97 1
      fs-admin/src/main/java/com/fs/his/controller/FsIntegralOrderController.java
  5. 4 1
      fs-admin/src/main/java/com/fs/his/controller/FsUserAddressController.java
  6. 13 8
      fs-admin/src/main/java/com/fs/his/controller/FsUserController.java
  7. 11 0
      fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreOrderItemScrmController.java
  8. 30 1
      fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreOrderScrmController.java
  9. 3 3
      fs-admin/src/main/java/com/fs/hisStore/task/LiveTask.java
  10. 28 0
      fs-admin/src/main/java/com/fs/live/controller/LiveMsgController.java
  11. 15 5
      fs-admin/src/main/java/com/fs/live/controller/OrderController.java
  12. 54 1
      fs-admin/src/main/java/com/fs/qw/controller/QwExternalContactController.java
  13. 155 0
      fs-admin/src/main/java/com/fs/qw/controller/QwGroupChatController.java
  14. 71 0
      fs-admin/src/main/java/com/fs/qw/controller/QwGroupChatUserController.java
  15. 16 0
      fs-common/src/main/java/com/fs/common/constant/RedisConstant.java
  16. 179 0
      fs-common/src/main/java/com/fs/common/core/redis/service/StockDeductService.java
  17. 39 18
      fs-company/src/main/java/com/fs/company/controller/company/CompanyAiWorkflowController.java
  18. 1 0
      fs-company/src/main/java/com/fs/company/controller/company/CompanyRedPacketBalanceLogsController.java
  19. 19 0
      fs-company/src/main/java/com/fs/company/controller/live/LiveMsgController.java
  20. 1 1
      fs-company/src/main/java/com/fs/company/controller/qw/QwExternalContactController.java
  21. 4 1
      fs-company/src/main/java/com/fs/company/controller/store/FsUserController.java
  22. 21 0
      fs-company/src/main/java/com/fs/hisStore/controller/FsStoreOrderScrmController.java
  23. 3 3
      fs-ipad-task/src/main/resources/logback.xml
  24. 43 9
      fs-live-app/src/main/java/com/fs/live/websocket/service/WebSocketServer.java
  25. 8 1
      fs-live-app/src/main/resources/application.yml
  26. 5 0
      fs-service/pom.xml
  27. 1 1
      fs-service/src/main/java/com/fs/aiSoundReplication/param/TtsRequest.java
  28. 1 0
      fs-service/src/main/java/com/fs/aiSoundReplication/service/impl/TtsServiceImpl.java
  29. 3 4
      fs-service/src/main/java/com/fs/aiSoundReplication/service/impl/VoiceCloneServiceImpl.java
  30. 2 0
      fs-service/src/main/java/com/fs/company/mapper/CompanyRechargeMapper.java
  31. 11 0
      fs-service/src/main/java/com/fs/company/param/CompanyUserAiWorkflowTtsVoiceParam.java
  32. 9 0
      fs-service/src/main/java/com/fs/company/service/ICompanyUserService.java
  33. 23 0
      fs-service/src/main/java/com/fs/company/service/impl/CompanyUserServiceImpl.java
  34. 1 1
      fs-service/src/main/java/com/fs/company/vo/CompanyProfitVO.java
  35. 3 0
      fs-service/src/main/java/com/fs/company/vo/CompanyRechargeVO.java
  36. 14 0
      fs-service/src/main/java/com/fs/course/dto/FsCourseCategoryImportDTO.java
  37. 3 0
      fs-service/src/main/java/com/fs/course/mapper/FsCoursePlaySourceConfigMapper.java
  38. 12 0
      fs-service/src/main/java/com/fs/course/mapper/FsUserCourseCategoryMapper.java
  39. 1 1
      fs-service/src/main/java/com/fs/course/mapper/FsUserVideoMapper.java
  40. 8 0
      fs-service/src/main/java/com/fs/course/service/IFsUserCourseCategoryService.java
  41. 89 1
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseCategoryServiceImpl.java
  42. 9 0
      fs-service/src/main/java/com/fs/erp/service/IErpOrderService.java
  43. 8 0
      fs-service/src/main/java/com/fs/erp/service/impl/DfOrderServiceImpl.java
  44. 11 0
      fs-service/src/main/java/com/fs/erp/service/impl/ErpOrderServiceImpl.java
  45. 9 0
      fs-service/src/main/java/com/fs/erp/service/impl/HzOMSErpOrderServiceImpl.java
  46. 186 31
      fs-service/src/main/java/com/fs/erp/service/impl/JSTErpOrderServiceImpl.java
  47. 9 0
      fs-service/src/main/java/com/fs/erp/service/impl/K9OrderScrmServiceImpl.java
  48. 8 0
      fs-service/src/main/java/com/fs/erp/service/impl/WdtErpOrderServiceImpl.java
  49. 4 0
      fs-service/src/main/java/com/fs/his/domain/FsUser.java
  50. 25 0
      fs-service/src/main/java/com/fs/his/mapper/FsAiWorkflowMapper.java
  51. 3 0
      fs-service/src/main/java/com/fs/his/mapper/MerchantAppConfigMapper.java
  52. 15 0
      fs-service/src/main/java/com/fs/his/param/FsAiWorkflowUpdateBindWCParam.java
  53. 17 0
      fs-service/src/main/java/com/fs/his/service/IFsAiWorkflowService.java
  54. 2 0
      fs-service/src/main/java/com/fs/his/service/IFsUserService.java
  55. 67 0
      fs-service/src/main/java/com/fs/his/service/impl/FsAiWorkflowServiceImpl.java
  56. 7 4
      fs-service/src/main/java/com/fs/his/service/impl/FsUserAddressServiceImpl.java
  57. 5 0
      fs-service/src/main/java/com/fs/his/service/impl/FsUserServiceImpl.java
  58. 5 0
      fs-service/src/main/java/com/fs/his/vo/FsAiWorkflowExportVO.java
  59. 34 0
      fs-service/src/main/java/com/fs/his/vo/FsAiWorkflowNodeVoiceVo.java
  60. 3 2
      fs-service/src/main/java/com/fs/his/vo/FsIntegralOrderListVO.java
  61. 2 2
      fs-service/src/main/java/com/fs/hisStore/domain/FsStoreOrderItemScrm.java
  62. 3 0
      fs-service/src/main/java/com/fs/hisStore/domain/FsStoreOrderScrm.java
  63. 2 0
      fs-service/src/main/java/com/fs/hisStore/domain/FsStoreProductScrm.java
  64. 4 0
      fs-service/src/main/java/com/fs/hisStore/mapper/FsShippingTemplatesRegionScrmMapper.java
  65. 4 0
      fs-service/src/main/java/com/fs/hisStore/mapper/FsShippingTemplatesScrmMapper.java
  66. 4 0
      fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreCouponUserScrmMapper.java
  67. 8 1
      fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreOrderScrmMapper.java
  68. 5 0
      fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreProductAttrValueScrmMapper.java
  69. 4 0
      fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreProductPurchaseLimitScrmMapper.java
  70. 15 1
      fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreProductScrmMapper.java
  71. 3 0
      fs-service/src/main/java/com/fs/hisStore/mapper/FsUserAddressScrmMapper.java
  72. 3 0
      fs-service/src/main/java/com/fs/hisStore/mapper/FsUserScrmMapper.java
  73. 3 0
      fs-service/src/main/java/com/fs/hisStore/param/FsStoreOrderParam.java
  74. 2 0
      fs-service/src/main/java/com/fs/hisStore/service/IFsStoreOrderItemScrmService.java
  75. 9 1
      fs-service/src/main/java/com/fs/hisStore/service/IFsStoreOrderScrmService.java
  76. 16 0
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreOrderItemScrmServiceImpl.java
  77. 40 0
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreOrderScrmServiceImpl.java
  78. 32 28
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreProductPurchaseLimitScrmServiceImpl.java
  79. 12 3
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreProductScrmServiceImpl.java
  80. 0 2
      fs-service/src/main/java/com/fs/hisStore/vo/FsStoreProductListVO.java
  81. 7 2
      fs-service/src/main/java/com/fs/live/domain/LiveGoods.java
  82. 3 0
      fs-service/src/main/java/com/fs/live/mapper/LiveCompletionPointsRecordMapper.java
  83. 5 0
      fs-service/src/main/java/com/fs/live/mapper/LiveCouponUserMapper.java
  84. 7 0
      fs-service/src/main/java/com/fs/live/mapper/LiveDataMapper.java
  85. 6 1
      fs-service/src/main/java/com/fs/live/mapper/LiveGoodsMapper.java
  86. 3 0
      fs-service/src/main/java/com/fs/live/mapper/LiveMapper.java
  87. 8 0
      fs-service/src/main/java/com/fs/live/mapper/LiveMsgMapper.java
  88. 1 0
      fs-service/src/main/java/com/fs/live/mapper/LiveOrderItemMapper.java
  89. 14 1
      fs-service/src/main/java/com/fs/live/mapper/LiveOrderMapper.java
  90. 3 0
      fs-service/src/main/java/com/fs/live/mapper/LiveOrderPaymentMapper.java
  91. 6 1
      fs-service/src/main/java/com/fs/live/mapper/LiveUserFirstEntryMapper.java
  92. 3 0
      fs-service/src/main/java/com/fs/live/mapper/LiveWatchUserMapper.java
  93. 3 0
      fs-service/src/main/java/com/fs/live/param/MergedOrderQueryParam.java
  94. 2 1
      fs-service/src/main/java/com/fs/live/service/ILiveGoodsService.java
  95. 9 0
      fs-service/src/main/java/com/fs/live/service/ILiveMsgService.java
  96. 5 0
      fs-service/src/main/java/com/fs/live/service/ILiveOrderService.java
  97. 9 1
      fs-service/src/main/java/com/fs/live/service/impl/LiveGoodsServiceImpl.java
  98. 77 0
      fs-service/src/main/java/com/fs/live/service/impl/LiveMsgServiceImpl.java
  99. 243 155
      fs-service/src/main/java/com/fs/live/service/impl/LiveOrderServiceImpl.java
  100. 9 2
      fs-service/src/main/java/com/fs/live/service/impl/LiveWatchUserServiceImpl.java

+ 4 - 0
fs-admin/src/main/java/com/fs/company/controller/CompanyUserAllController.java

@@ -415,6 +415,8 @@ public class CompanyUserAllController extends BaseController {
     /**
      * 批量修改 销售的所属区域(临时的)
      */
+    @PreAuthorize("@ss.hasPermi('company:user:updateCompanyUserAreaList')")
+    @Log(title = "用户管理", businessType = BusinessType.UPDATE)
     @PostMapping("/updateCompanyUserAreaList")
     public R updateCompanyUserAreaList(@RequestBody CompanyUserAreaParam param)
     {
@@ -432,6 +434,7 @@ public class CompanyUserAllController extends BaseController {
         return  R.ok().put("data",subDomain);
     }
 
+    @PreAuthorize("@ss.hasPermi('company:user:setRegister')")
     @Log(title = "设置是否需要单独注册会员", businessType = BusinessType.UPDATE)
     @PutMapping("/setRegister")
     public AjaxResult setIsRegisterMember(@RequestParam Boolean status, @RequestBody List<Long> userIds) {
@@ -443,6 +446,7 @@ public class CompanyUserAllController extends BaseController {
         }
     }
 
+    @PreAuthorize("@ss.hasPermi('company:user:allowedAllRegister')")
     @Log(title = "是否允许所有方式注册会员", businessType = BusinessType.UPDATE)
     @PutMapping("/allowedAllRegister")
     public AjaxResult isAllowedAllRegister(@RequestParam Boolean status, @RequestBody List<Long> userIds) {

+ 35 - 0
fs-admin/src/main/java/com/fs/course/controller/FsUserCourseCategoryController.java

@@ -5,6 +5,7 @@ import java.util.List;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.domain.model.LoginUser;
 import com.fs.common.utils.ServletUtils;
+import com.fs.course.dto.FsCourseCategoryImportDTO;
 import com.fs.framework.web.service.TokenService;
 import com.fs.his.domain.FsStoreProductCategory;
 import com.fs.his.vo.FsStoreProductCategoryVO;
@@ -33,6 +34,7 @@ import com.fs.course.domain.FsUserCourseCategory;
 import com.fs.course.service.IFsUserCourseCategoryService;
 import com.fs.common.utils.poi.ExcelUtil;
 import com.fs.common.core.page.TableDataInfo;
+import org.springframework.web.multipart.MultipartFile;
 
 /**
  * 课堂分类Controller
@@ -174,4 +176,37 @@ public class FsUserCourseCategoryController extends BaseController
         List<OptionsVO> list = fsUserCourseCategoryService.selectCateListByPid(pid);
         return R.ok().put("data", list);
     }
+
+    // 下载模板
+    @GetMapping("/importTemplate")
+    public AjaxResult importTemplate() {
+        ExcelUtil<FsCourseCategoryImportDTO> util = new ExcelUtil<>(FsCourseCategoryImportDTO.class);
+        return util.importTemplateExcel("课堂分类导入模板");
+    }
+
+    @Log(title = "导入", businessType = BusinessType.IMPORT)
+    @PreAuthorize("@ss.hasPermi('course:userCourseCategory:importData')")
+    @PostMapping("/importData")
+    public AjaxResult importData(MultipartFile file) throws Exception {
+        ExcelUtil<FsCourseCategoryImportDTO> util = new ExcelUtil<>(FsCourseCategoryImportDTO.class);
+        List<FsCourseCategoryImportDTO> list = util.importExcel(file.getInputStream());
+
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        Long userId = loginUser.getUser().getUserId();
+        String json = configService.selectConfigByKey("course.config");
+        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
+        if (ObjectUtil.isEmpty(config.getIsBound()) || !config.getIsBound()){
+            userId = null;
+        }
+
+        return AjaxResult.success(fsUserCourseCategoryService.importData(list, userId));
+    }
+
+    @PreAuthorize("@ss.hasPermi('course:userCourseCategory:exportFail')")
+    @Log(title = "课堂分类", businessType = BusinessType.EXPORT)
+    @PostMapping("/exportFail")
+    public AjaxResult exportFail(@RequestBody List<FsCourseCategoryImportDTO> list) {
+        ExcelUtil<FsCourseCategoryImportDTO> util = new ExcelUtil<>(FsCourseCategoryImportDTO.class);
+        return util.exportExcel(list, "课堂分类错误数据");
+    }
 }

+ 42 - 0
fs-admin/src/main/java/com/fs/his/controller/FsAiWorkflowController.java

@@ -9,6 +9,7 @@ import com.fs.common.utils.poi.ExcelUtil;
 import com.fs.his.domain.FsAiWorkflow;
 import com.fs.his.domain.FsAiWorkflowNodeType;
 import com.fs.his.param.FsAiWorkflowSaveParam;
+import com.fs.his.param.FsAiWorkflowUpdateBindWCParam;
 import com.fs.his.service.IFsAiWorkflowService;
 import com.fs.his.vo.FsAiWorkflowVO;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -16,6 +17,7 @@ import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
 
 import java.util.List;
+import java.util.Map;
 
 /**
  * AI工作流Controller
@@ -118,4 +120,44 @@ public class FsAiWorkflowController extends BaseController {
     public AjaxResult exportJson(@PathVariable("workflowId") Long workflowId) {
         return AjaxResult.success(fsAiWorkflowService.exportWorkflowJson(workflowId));
     }
+    /**
+     * 分页销售
+     */
+    @GetMapping("/listCompanyUser")
+    public AjaxResult listCompanyUser() {
+        return AjaxResult.success(fsAiWorkflowService.listCompanyUser());
+    }
+
+    /**
+     * 查销售
+     */
+    @GetMapping("/getCompanyUserById/{companyUserId}")
+    public AjaxResult getCompanyUserById(@PathVariable("companyUserId") Long companyUserId) {
+        return AjaxResult.success(fsAiWorkflowService.getCompanyUserById(companyUserId));
+    }
+
+//    /**
+//     * 查销售是否已经被绑定
+//     */
+//    @GetMapping("/checkCompanyUserBeUsed/{companyUserId}")
+//    public AjaxResult checkCompanyUserBeUsed(@PathVariable("companyUserId") Long companyUserId) {
+//        return AjaxResult.success(fsAiWorkflowService.checkCompanyUserBeUsed(companyUserId));
+//    }
+
+    /**
+     * 查工作流已绑定的销售
+     */
+    @GetMapping("/getBindCompanyUserByWorkflowId/{workflowId}")
+    public AjaxResult getBindCompanyUserByWorkflowId(@PathVariable("workflowId") Long workflowId) {
+        return AjaxResult.success(fsAiWorkflowService.getBindCompanyUserByWorkflowId(workflowId));
+    }
+
+    /**
+     * 修改工作流绑定的销售
+     */
+    @PostMapping("/updateWorkflowBindCompanyUser")
+    public AjaxResult updateWorkflowBindCompanyUser(@RequestBody FsAiWorkflowUpdateBindWCParam param) {
+        return fsAiWorkflowService.updateWorkflowBindCompanyUser(param);
+    }
+
 }

+ 97 - 1
fs-admin/src/main/java/com/fs/his/controller/FsIntegralOrderController.java

@@ -148,6 +148,7 @@ public class FsIntegralOrderController extends BaseController
     @PreAuthorize("@ss.hasPermi('his:integralOrder:export')")
     @Log(title = "积分商品订单", businessType = BusinessType.EXPORT)
     @GetMapping("/export")
+
     public AjaxResult export(FsIntegralOrderParam fsIntegralOrder) {
         List<FsIntegralOrderListVO> fsIntegralOrderListVOS = new ArrayList<>();
         if (CloudHostUtils.hasCloudHostName("金牛明医")){
@@ -162,6 +163,101 @@ public class FsIntegralOrderController extends BaseController
         }
         SysRole sysRole = isCheckPermission();
         // 处理商品名称:解析item_json并设置goodsName
+        for (FsIntegralOrderListVO vo : fsIntegralOrderListVOS) {
+            if (StringUtils.isNotEmpty(vo.getItemJson())) {
+                try {
+                    // 尝试解析JSON格式的商品信息
+                    if (vo.getItemJson().startsWith("[")) {
+                        // 数组格式,遍历所有商品,用换行符分隔
+                        com.alibaba.fastjson.JSONArray jsonArray = com.alibaba.fastjson.JSONArray.parseArray(vo.getItemJson());
+                        if (jsonArray != null && !jsonArray.isEmpty()) {
+                            StringBuilder goodsNameBuilder = new StringBuilder();
+                            StringBuilder barCodeBuilder = new StringBuilder();
+
+                            for (int i = 0; i < jsonArray.size(); i++) {
+                                com.alibaba.fastjson.JSONObject goods = jsonArray.getJSONObject(i);
+
+                                // 处理商品名称和数量
+                                if (goods != null && goods.getString("goodsName") != null) {
+                                    String name = goods.getString("goodsName");
+                                    String num = goods.getString("num");
+                                    if (StringUtils.isEmpty(num)) {
+                                        num = "1"; // 默认数量为1
+                                    }
+                                    if (goodsNameBuilder.length() > 0) {
+                                        goodsNameBuilder.append("\r\n");
+                                    }
+                                    goodsNameBuilder.append(name).append(",数量:").append(num).append("个");
+                                }
+
+                                // 处理商品编码
+                                if (goods != null && goods.getString("barCode") != null) {
+                                    String barCode = goods.getString("barCode");
+                                    if (barCodeBuilder.length() > 0) {
+                                        barCodeBuilder.append("\r\n");
+                                    }
+                                    barCodeBuilder.append(barCode);
+                                }
+                            }
+
+                            // 设置商品名称
+                            if (goodsNameBuilder.length() > 0) {
+                                vo.setGoodsName(goodsNameBuilder.toString());
+                            }
+
+                            // 设置商品编码
+                            if (barCodeBuilder.length() > 0) {
+                                vo.setBarCode(barCodeBuilder.toString());
+                            }
+                        }
+                    } else if (vo.getItemJson().startsWith("{")) {
+                        // 对象格式
+                        com.alibaba.fastjson.JSONObject goods = com.alibaba.fastjson.JSONObject.parseObject(vo.getItemJson());
+
+                        // 处理商品名称和数量
+                        if (goods != null && goods.getString("goodsName") != null) {
+                            String name = goods.getString("goodsName");
+                            String num = goods.getString("num");
+                            if (StringUtils.isEmpty(num)) {
+                                num = "1"; // 默认数量为1
+                            }
+                            vo.setGoodsName(name + ",数量:" + num + "个");
+                        }
+
+                        // 处理商品编码
+                        if (goods != null && goods.getString("barCode") != null) {
+                            vo.setBarCode(goods.getString("barCode"));
+                        }
+                    }
+                } catch (Exception e) {
+                    // 解析失败时保持goodsName为空,避免导出出错
+                    log.warn("解析商品信息失败,订单编号:{}, 商品信息:{}", vo.getOrderCode(), vo.getItemJson());
+                }
+            }
+            if (!(sysRole.getIsCheckPhone()==1)){
+                // 加密手机号
+                vo.setUserPhone(decryptAutoPhoneMk(vo.getUserPhone()));
+            }
+
+        }
+        ExcelUtil<FsIntegralOrderListVO> util = new ExcelUtil<>(FsIntegralOrderListVO.class);
+        return util.exportExcel(new ArrayList<>(fsIntegralOrderListVOS), "积分商品订单数据");
+    }
+
+    /*public AjaxResult export(FsIntegralOrderParam fsIntegralOrder) {
+        List<FsIntegralOrderListVO> fsIntegralOrderListVOS = new ArrayList<>();
+        if (CloudHostUtils.hasCloudHostName("金牛明医")){
+            *//*目前只有金牛有状态为6的查询,其他项目避免使用6状态码*//*
+            if (fsIntegralOrder.getStatus() != null && fsIntegralOrder.getStatus().equals("6")) {
+                fsIntegralOrder.setStatus("1");
+                fsIntegralOrder.setIsPush(0);
+            }
+            fsIntegralOrderListVOS = fsIntegralOrderService.selectFsIntegralOrderListByJn(fsIntegralOrder);
+        } else {
+            fsIntegralOrderListVOS = fsIntegralOrderService.selectFsIntegralOrderListVO(fsIntegralOrder);
+        }
+        SysRole sysRole = isCheckPermission();
+        // 处理商品名称:解析item_json并设置goodsName
         for (FsIntegralOrderListVO vo : fsIntegralOrderListVOS) {
             if (StringUtils.isNotEmpty(vo.getItemJson())) {
                 try {
@@ -201,7 +297,7 @@ public class FsIntegralOrderController extends BaseController
         }
         ExcelUtil<FsIntegralOrderListVO> util = new ExcelUtil<>(FsIntegralOrderListVO.class);
         return util.exportExcel(new ArrayList<>(fsIntegralOrderListVOS), "积分商品订单数据");
-    }
+    }*/
     /**
      * 发货
      */

+ 4 - 1
fs-admin/src/main/java/com/fs/his/controller/FsUserAddressController.java

@@ -6,6 +6,7 @@ import com.alibaba.fastjson.JSON;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.domain.entity.SysRole;
 import com.fs.common.core.domain.entity.SysUser;
+import com.fs.common.utils.CloudHostUtils;
 import com.fs.common.utils.ParseUtils;
 import com.fs.common.utils.SecurityUtils;
 import com.fs.his.dto.AddressInfoDTO;
@@ -118,7 +119,9 @@ public class FsUserAddressController extends BaseController
     public AjaxResult getInfo(@PathVariable("addressId") Long addressId)
     {
         FsUserAddress fsUserAddress = fsUserAddressService.selectFsUserAddressByAddressId(addressId);
-        fsUserAddress.setPhone(ParseUtils.parsePhone(fsUserAddress.getPhone()));
+        if(!CloudHostUtils.hasCloudHostName("广州郑多燕")) {
+            fsUserAddress.setPhone(ParseUtils.parsePhone(fsUserAddress.getPhone()));
+        }
         fsUserAddress.setDetail(ParseUtils.parseAddress(fsUserAddress.getDetail()));
         return AjaxResult.success(fsUserAddress);
     }

+ 13 - 8
fs-admin/src/main/java/com/fs/his/controller/FsUserController.java

@@ -10,6 +10,7 @@ import com.fs.common.core.domain.ResponseResult;
 import com.fs.common.core.domain.entity.SysRole;
 import com.fs.common.core.domain.entity.SysUser;
 import com.fs.common.exception.CustomException;
+import com.fs.common.utils.CloudHostUtils;
 import com.fs.common.utils.ParseUtils;
 import com.fs.common.utils.SecurityUtils;
 import com.fs.common.utils.StringUtils;
@@ -253,7 +254,9 @@ public class FsUserController extends BaseController
     public AjaxResult getInfo(@PathVariable("userId") Long userId)
     {
         FsUser fsUser = fsUserService.selectFsUserByUserId(userId);
-        fsUser.setPhone(decryptAutoPhoneMk(fsUser.getPhone()));
+        if(!CloudHostUtils.hasCloudHostName("广州郑多燕")) {
+            fsUser.setPhone(decryptAutoPhoneMk(fsUser.getPhone()));
+        }
         return AjaxResult.success(fsUser);
     }
 
@@ -262,16 +265,17 @@ public class FsUserController extends BaseController
     {
 
         List<FsUserAddress> fsUserAddresses = fsUserService.selectFsUserAddressByUserId(userId);
-        for (FsUserAddress fsUserAddress : fsUserAddresses) {
-            if (fsUserAddress.getPhone()!=null&&fsUserAddress.getPhone()!=""){
-                if (fsUserAddress.getPhone().length()>11){
-                    fsUserAddress.setPhone(decryptPhoneMk(fsUserAddress.getPhone()));
-                }else {
-                    fsUserAddress.setPhone(fsUserAddress.getPhone().replaceAll("(\\d{3})\\d*(\\d{4})", "$1****$2"));
+        if(!CloudHostUtils.hasCloudHostName("广州郑多燕")) {
+            for (FsUserAddress fsUserAddress : fsUserAddresses) {
+                if (fsUserAddress.getPhone() != null && fsUserAddress.getPhone() != "") {
+                    if (fsUserAddress.getPhone().length() > 11) {
+                        fsUserAddress.setPhone(decryptPhoneMk(fsUserAddress.getPhone()));
+                    } else {
+                        fsUserAddress.setPhone(fsUserAddress.getPhone().replaceAll("(\\d{3})\\d*(\\d{4})", "$1****$2"));
+                    }
                 }
             }
         }
-
         return AjaxResult.success(fsUserAddresses);
     }
 
@@ -338,6 +342,7 @@ public class FsUserController extends BaseController
     public R listBySearch(FsUser user)
     {
         if (Objects.nonNull(user) && StringUtils.isNotBlank(user.getPhone())){
+            user.setPhoneUnencrypted(user.getPhone());
             user.setPhone(PhoneUtil.encryptPhone(user.getPhone()));
         }
         List<FsUser> list = fsUserService.selectFsUserList(user);

+ 11 - 0
fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreOrderItemScrmController.java

@@ -94,4 +94,15 @@ public class FsStoreOrderItemScrmController extends BaseController
     {
         return toAjax(fsStoreOrderItemService.deleteFsStoreOrderItemByIds(itemIds));
     }
+
+    /**
+     * 修改订单数量及总价
+     */
+    @PreAuthorize("@ss.hasPermi('store:storeOrderItem:updateNum')")
+    @Log(title = "订单详情修改订单数量", businessType = BusinessType.UPDATE)
+    @PutMapping("/updateNum")
+    public AjaxResult updateNum(@RequestBody FsStoreOrderItemScrm fsStoreOrderItem)
+    {
+        return toAjax(fsStoreOrderItemService.updateFsStoreOrderItemNum(fsStoreOrderItem));
+    }
 }

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

@@ -22,6 +22,7 @@ import com.fs.common.utils.poi.ExcelUtil;
 import com.fs.company.param.CompanyStoreOrderMoneyLogsListParam;
 import com.fs.company.service.ICompanyMoneyLogsService;
 import com.fs.company.vo.CompanyStoreOrderMoneyLogsVO;
+import com.fs.config.cloud.CloudHostProper;
 import com.fs.erp.domain.ErpDeliverys;
 import com.fs.erp.domain.ErpOrderQuery;
 import com.fs.erp.dto.ErpOrderQueryRequert;
@@ -101,6 +102,9 @@ public class FsStoreOrderScrmController extends BaseController {
     @Autowired
     IFsStorePaymentScrmService paymentService;
 
+    @Autowired
+    private CloudHostProper cloudHostProper;
+
     @Autowired
     private ICompanyMoneyLogsService moneyLogsService;
     @Autowired
@@ -723,7 +727,14 @@ public class FsStoreOrderScrmController extends BaseController {
     public AjaxResult edit(@RequestBody FsStoreOrderScrm fsStoreOrder) {
         return toAjax(fsStoreOrderService.updateFsStoreOrder(fsStoreOrder));
     }
-
+    /**
+     * 修改订单itemJson
+     */
+    @Log(title = "修改订单itemJson", businessType = BusinessType.UPDATE)
+    @GetMapping("/updateStoreOrderItemJson/{orderId}/{backendEditProductType}")
+    public AjaxResult updateStoreOrderItemJson(@PathVariable("orderId") Long orderId,@PathVariable("backendEditProductType") Integer backendEditProductType) {
+        return toAjax(fsStoreOrderService.updateStoreOrderItemJson(orderId,backendEditProductType));
+    }
     /**
      * 修改物流
      * @param fsStoreOrder
@@ -902,6 +913,10 @@ public class FsStoreOrderScrmController extends BaseController {
      * **/
     @GetMapping("/orderDimensionStatisticsList")
     public TableDataInfo orderDimensionStatisticsList(OrderStatisticsParam param){
+        // 郑多燕需求
+        if("广州郑多燕".equals(cloudHostProper.getCompanyName())){
+            return getDataTable(fsStoreOrderService.selectZDYOrderSaleStatisticsList(param));
+        }
         return getDataTable(fsStoreOrderService.selectOrderDimensionStatisticsList(param));
     }
 
@@ -1137,6 +1152,20 @@ public class FsStoreOrderScrmController extends BaseController {
         return R.ok("成功审核 " + count + " 条订单");
     }
 
+    @ApiOperation("订单备注")
+    @Log(title = "订单管理", businessType = BusinessType.UPDATE)
+    @PreAuthorize("@ss.hasPermi('store:storeOrder:remark')")
+    @PostMapping("/remark")
+    public R remark(@Validated @RequestBody FsStoreOrderScrm param) {
+        if (param.getId() == null || param.getId() < 1) {
+            return R.error("订单ID错误");
+        }
+        if (StringUtils.isEmpty(param.getOrderRemark())) {
+            return R.error("订单备注不能为空");
+        }
+        return fsStoreOrderService.orderRemark(param);
+    }
+
     private FsStoreOrderDf getDFInfo(String loginAccount) {
         //查询订单账户 判断是否存在该订单账户
         List<FsDfAccount> erpAccounts = fsDfAccountService.selectFsDfAccountList(null);

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

@@ -331,10 +331,10 @@ public class LiveTask {
     public void deliveryOp() {
         List<LiveOrder> list = liveOrderService.selectUpdateExpress();
         if(list == null || list.isEmpty()) return;
-
+        Date now = new Date();
         for (LiveOrder order : list) {
-            order.setUpdateTime(new Date());
-            liveOrderService.updateLiveOrder(order);
+            order.setUpdateTime(now);
+            liveOrderService.updateTime(order);
             ErpOrderQueryRequert request = new ErpOrderQueryRequert();
             request.setCode(order.getExtendOrderId());
             IErpOrderService erpOrderService = getErpOrderService();

+ 28 - 0
fs-admin/src/main/java/com/fs/live/controller/LiveMsgController.java

@@ -3,11 +3,16 @@ package com.fs.live.controller;
 import com.fs.common.annotation.Log;
 import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.domain.R;
+import com.fs.common.core.domain.model.LoginUser;
 import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.ServletUtils;
 import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.framework.web.service.TokenService;
 import com.fs.live.domain.LiveMsg;
 import com.fs.live.service.ILiveMsgService;
+import com.fs.live.vo.LiveMsgExportVO;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
@@ -26,6 +31,9 @@ public class LiveMsgController extends BaseController
 {
     @Autowired
     private ILiveMsgService liveMsgService;
+    
+    @Autowired
+    private TokenService tokenService;
 
     /**
      * 查询直播讨论列表
@@ -102,4 +110,24 @@ public class LiveMsgController extends BaseController
     {
         return toAjax(liveMsgService.deleteLiveMsgByMsgIds(msgIds));
     }
+
+    /**
+     * 导出直播评论
+     */
+    @PreAuthorize("@ss.hasPermi('live:liveMsg:export')")
+    @Log(title = "直播评论导出", businessType = BusinessType.EXPORT)
+    @GetMapping("/exportComments/{liveId}")
+    public AjaxResult exportComments(@PathVariable("liveId") Long liveId)
+    {
+        try {
+            LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+            Long userId = loginUser.getUser().getUserId();
+            
+            List<LiveMsgExportVO> list = liveMsgService.exportLiveMsgComments(liveId, userId);
+            ExcelUtil<LiveMsgExportVO> util = new ExcelUtil<LiveMsgExportVO>(LiveMsgExportVO.class);
+            return util.exportExcel(list, "直播评论数据");
+        } catch (Exception e) {
+            return AjaxResult.error("导出失败:" + e.getMessage());
+        }
+    }
 }

+ 15 - 5
fs-admin/src/main/java/com/fs/live/controller/OrderController.java

@@ -121,7 +121,9 @@ public class OrderController extends BaseController
     public AjaxResult export(MergedOrderQueryParam param)
     {
         // 先查询数据,限制查询20001条,用于判断是否超过限制
-        PageHelper.startPage(1, maxExportCount + 1);
+        param.setExportFlag(1);
+        param.setPageNum(1);
+        param.setPageSize(maxExportCount + 1);
         List<MergedOrderVO> list = mergedOrderService.selectMergedOrderList(param);
         // 如果查询结果超过20000条,返回错误提示
         if (list != null && list.size() > maxExportCount) {
@@ -160,7 +162,9 @@ public class OrderController extends BaseController
     public AjaxResult exportDetails(MergedOrderQueryParam param)
     {
         // 先查询数据,限制查询20001条,用于判断是否超过限制
-        PageHelper.startPage(1, maxExportCount + 1);
+        param.setExportFlag(1);
+        param.setPageNum(1);
+        param.setPageSize(maxExportCount + 1);
         List<MergedOrderVO> list = mergedOrderService.selectMergedOrderList(param);
         // 如果查询结果超过20000条,返回错误提示
         if (list != null && list.size() > maxExportCount) {
@@ -200,7 +204,9 @@ public class OrderController extends BaseController
     public AjaxResult exportItems(MergedOrderQueryParam param)
     {
         // 先查询数据,限制查询20001条,用于判断是否超过限制
-        PageHelper.startPage(1, maxExportCount + 1);
+        param.setExportFlag(1);
+        param.setPageNum(1);
+        param.setPageSize(maxExportCount + 1);
         List<MergedOrderVO> list = mergedOrderService.selectMergedOrderList(param);
 
         // 如果查询结果超过20000条,返回错误提示
@@ -225,7 +231,9 @@ public class OrderController extends BaseController
     public AjaxResult exportItemsDetails(MergedOrderQueryParam param)
     {
         // 先查询数据,限制查询20001条,用于判断是否超过限制
-        PageHelper.startPage(1, maxExportCount + 1);
+        param.setExportFlag(1);
+        param.setPageNum(1);
+        param.setPageSize(maxExportCount + 1);
         List<MergedOrderVO> list = mergedOrderService.selectMergedOrderList(param);
 
         // 如果查询结果超过20000条,返回错误提示
@@ -246,7 +254,9 @@ public class OrderController extends BaseController
     public AjaxResult exportShipping(MergedOrderQueryParam param)
     {
         // 先查询数据,限制查询20001条,用于判断是否超过限制
-        PageHelper.startPage(1, maxExportCount + 1);
+        param.setExportFlag(1);
+        param.setPageNum(1);
+        param.setPageSize(maxExportCount + 1);
         List<MergedOrderVO> list = mergedOrderService.selectMergedOrderList(param);
         // 如果查询结果超过20000条,返回错误提示
         if (list != null && list.size() > maxExportCount) {

+ 54 - 1
fs-admin/src/main/java/com/fs/qw/controller/QwExternalContactController.java

@@ -2,19 +2,24 @@ package com.fs.qw.controller;
 
 import java.util.List;
 import java.util.Objects;
+import java.util.stream.Collectors;
 
 import cn.hutool.core.collection.CollectionUtil;
 import cn.hutool.core.util.ObjectUtil;
 import com.fs.common.core.domain.R;
 import com.fs.common.exception.ServiceException;
+import com.fs.common.utils.ServletUtils;
 import com.fs.qw.param.QwExternalContactParam;
 import com.fs.qw.param.QwTagSearchParam;
+import com.fs.qw.param.ResignedTransferParam;
+import com.fs.qw.param.TransferParam;
 import com.fs.qw.service.IQwExternalContactInfoService;
 import com.fs.qw.service.IQwTagService;
 import com.fs.qw.vo.QwExternalContactUnionIdExportVO;
 import com.fs.qw.vo.QwExternalContactVO;
 import com.google.gson.Gson;
 import com.google.gson.reflect.TypeToken;
+import org.apache.commons.collections.CollectionUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.GetMapping;
@@ -170,11 +175,59 @@ public class QwExternalContactController extends BaseController
     {
         return toAjax(qwExternalContactService.deleteQwExternalContactByIds(ids));
     }
-
+    @PreAuthorize("@ss.hasPermi('qw:externalContact:getUserInfo')")
     @GetMapping(value = "getUserInfo/{id}")
     public R getUserInfo(@PathVariable("id") Long id)
     {
         return R.ok().put("data",qwExternalContactInfoService.selectQwExternalContactInfoByExternalContactId(id));
     }
 
+    @PreAuthorize("@ss.hasPermi('qw:externalContact:addUnassigned')")
+    @Log(title = "同步待转接", businessType = BusinessType.INSERT)
+    @PostMapping("/addUnassigned")
+    public R addUnassigned(@RequestBody QwExternalContact qwExternalContact)
+    {
+        return qwExternalContactService.syncQwExternalContactUnassigned(qwExternalContact.getCorpId());
+    }
+    @PreAuthorize("@ss.hasPermi('qw:externalContact:transfer')")
+    @Log(title = "企业微信客户", businessType = BusinessType.UPDATE)
+    @PutMapping("/resignedTransfer")
+    public R resignedTransfer(@RequestBody ResignedTransferParam param)
+    {
+        if (ObjectUtil.isNotEmpty(param.getQwUserName())&&ObjectUtil.isNotEmpty(param.getType())&&param.getType().equals("1")){
+            QwExternalContactParam qwExternalContact =new QwExternalContactParam();
+            qwExternalContact.setQwUserName(param.getQwUserName());
+
+//            LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+//            qwExternalContact.setCompanyId(loginUser.getCompany().getCompanyId());
+            List<QwExternalContactVO> list = qwExternalContactService.selectQwExternalContactListVO(qwExternalContact);
+            if (!CollectionUtils.isEmpty(list)){
+                List<Long> ids = list.stream().map(QwExternalContactVO::getId).collect(Collectors.toList());
+                param.setIds(ids);
+            }
+        }
+
+        return qwExternalContactService.resignedTransfer(param);
+    }
+    /**
+     * 在职转接 分配客户
+     */
+    @PreAuthorize("@ss.hasPermi('qw:externalContact:transfer')")
+    @Log(title = "企业微信客户", businessType = BusinessType.UPDATE)
+    @PutMapping("/transfer")
+    public R transfer(@RequestBody TransferParam param)
+    {
+        if (ObjectUtil.isNotEmpty(param.getQwUserName())&&ObjectUtil.isNotEmpty(param.getType())&&param.getType().equals("1")){
+            QwExternalContactParam qwExternalContact =new QwExternalContactParam();
+            qwExternalContact.setQwUserName(param.getQwUserName());
+//            LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+//            qwExternalContact.setCompanyId(loginUser.getCompany().getCompanyId());
+            List<QwExternalContactVO> list = qwExternalContactService.selectQwExternalContactListVO(qwExternalContact);
+            if (!CollectionUtils.isEmpty(list)){
+                List<Long> ids = list.stream().map(QwExternalContactVO::getId).collect(Collectors.toList());
+                param.setIds(ids);
+            }
+        }
+        return qwExternalContactService.transfer(param);
+    }
 }

+ 155 - 0
fs-admin/src/main/java/com/fs/qw/controller/QwGroupChatController.java

@@ -0,0 +1,155 @@
+package com.fs.qw.controller;
+
+import com.fs.common.core.controller.BaseController;
+//import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.domain.R;
+import com.fs.common.core.page.TableDataInfo;
+//import com.fs.common.utils.ServletUtils;
+//import com.fs.company.service.impl.CompanyDeptServiceImpl;
+//import com.fs.framework.security.LoginUser;
+//import com.fs.framework.service.TokenService;
+import com.fs.qw.param.QwGroupChatParam;
+import com.fs.qw.service.IQwGroupChatService;
+//import com.fs.qw.service.IQwUserService;
+//import com.fs.qw.vo.QwGroupChatOptionsVO;
+import com.fs.qw.vo.QwGroupChatVO;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 客户群详情Controller
+ *
+ * @author fs
+ * @date 2024-06-20
+ */
+@RestController
+@RequestMapping("/qw/groupChat")
+public class QwGroupChatController extends BaseController
+{
+    @Autowired
+    private IQwGroupChatService qwGroupChatService;
+
+//    @Autowired
+//    private TokenService tokenService;
+
+//    @Autowired
+//    private CompanyDeptServiceImpl companyDeptService;
+//
+//
+//    @Autowired
+//    private IQwUserService iQwUserService;
+
+    /**
+     * 查询客户群详情列表总后台查看
+     */
+    @PreAuthorize("@ss.hasPermi('qw:groupChat:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(QwGroupChatParam qwGroupChat)
+    {
+        startPage();
+        List<QwGroupChatVO> list = qwGroupChatService.selectQwGroupChatListAdmin(qwGroupChat);
+        return getDataTable(list);
+    }
+
+//    /**
+//     * 查询我的部门客户群详情列表
+//     */
+//    @PreAuthorize("@ss.hasPermi('qw:groupChat:deptList')")
+//    @GetMapping("/deptList")
+//    public TableDataInfo deptList(QwGroupChatParam qwGroupChat)
+//    {
+//        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+//
+//        List<Long> combinedList = new ArrayList<>();
+//        //本部门
+//        Long deptId = loginUser.getUser().getDeptId();
+//        if (deptId!=null){
+//            combinedList.add(deptId);
+//        }
+//        //本部门的下级部门
+//        List<Long> deptList = companyDeptService.selectCompanyDeptByParentId(deptId);
+//        if (!deptList.isEmpty()){
+//            combinedList.addAll(deptList);
+//        }
+//        qwGroupChat.setCuDeptIdList(combinedList);
+//        qwGroupChat.setUserType(loginUser.getUser().getUserType());
+//
+//
+//        startPage();
+//        List<QwGroupChatVO> list = qwGroupChatService.selectQwGroupChatList(qwGroupChat);
+//        return getDataTable(list);
+//    }
+
+
+//    /**
+//     * 我的-查询客户群详情列表
+//     */
+//    @PreAuthorize("@ss.hasPermi('qw:groupChat:myList')")
+//    @GetMapping("/myList")
+//    public TableDataInfo myList(QwGroupChatParam qwGroupChat)
+//    {
+//        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+//        qwGroupChat.setCompanyId(loginUser.getCompany().getCompanyId());
+//        qwGroupChat.setCompanyUserId(loginUser.getUser().getUserId());
+//        startPage();
+//        List<QwGroupChatVO> list = qwGroupChatService.selectQwGroupChatList(qwGroupChat);
+//        return getDataTable(list);
+//    }
+
+
+//    /**
+//     * 获取客户群详情详细信息
+//     */
+////    @PreAuthorize("@ss.hasPermi('qw:groupChat:query')")
+//    @GetMapping(value = "/{chatId}")
+//    public AjaxResult getInfo(@PathVariable("chatId") String chatId)
+//    {
+//        return AjaxResult.success(qwGroupChatService.selectQwGroupChatByChatId(chatId));
+//    }
+
+    /**
+     *  同步 插入客户群信息
+     */
+    @GetMapping("/cogradientGroupChat/{corpId}")
+    public R cogradientGroupChat(@PathVariable("corpId") String corpId) throws Exception {
+
+//        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+//        Long companyId = loginUser.getCompany().getCompanyId();
+        List<String> qwUserIdList=new ArrayList<>();
+        return qwGroupChatService.cogradientGroupChat(corpId,qwUserIdList);
+    }
+
+//    /**
+//     *  同步 我的客户群信息
+//     */
+//    @GetMapping("/cogradientMyGroupChat/{corpId}")
+//    public R cogradientMyGroupChat(@PathVariable("corpId") String corpId) throws Exception {
+//
+//        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+//
+//        List<String> qwUserIdList = iQwUserService.selectQwUserListByCompanyUserId(loginUser.getUser().getUserId(), corpId);
+//
+//        return qwGroupChatService.cogradientGroupChat(corpId,qwUserIdList);
+//    }
+
+//    @GetMapping("/allList/{corpId}")
+//    public AjaxResult allList(@PathVariable("corpId") String corpId)
+//    {
+////        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+////        Long companyId = loginUser.getCompany().getCompanyId();
+//        List<QwGroupChatOptionsVO> list = qwGroupChatService.selectGroupChatOptionsVOList(corpId);
+//        return AjaxResult.success(list);
+//    }
+//    @GetMapping("/listAll")
+//    public AjaxResult listAll(String qwUserIds, String corpId, String sopId){
+//        List<QwGroupChatOptionsVO> list = qwGroupChatService.listAllByQwUserList(qwUserIds, corpId, sopId);
+//        return AjaxResult.success(list);
+//    }
+}

+ 71 - 0
fs-admin/src/main/java/com/fs/qw/controller/QwGroupChatUserController.java

@@ -0,0 +1,71 @@
+package com.fs.qw.controller;
+
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.domain.R;
+import com.fs.common.core.page.TableDataInfo;
+//import com.fs.framework.service.TokenService;
+import com.fs.qw.domain.QwGroupChatUser;
+import com.fs.qw.param.QwGroupChatUserDataType;
+import com.fs.qw.param.QwInsertGroupChatUserParam;
+import com.fs.qw.service.IQwGroupChatUserService;
+import com.fs.qw.vo.QwGroupChatUserVO;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 客户群成员列Controller
+ *
+ * @author fs
+ * @date 2024-06-20
+ */
+@RestController
+@RequestMapping("/qw/group_chat_user")
+public class QwGroupChatUserController extends BaseController
+{
+    @Autowired
+    private IQwGroupChatUserService qwGroupChatUserService;
+
+//    @Autowired
+//    private TokenService tokenService;
+
+    /**
+     * 查询客户群成员列列表
+     */
+    @GetMapping("/list")
+    public TableDataInfo list(QwGroupChatUser qwGroupChatUser)
+    {
+        startPage();
+        List<QwGroupChatUserVO> list = qwGroupChatUserService.selectQwGroupChatUserList(qwGroupChatUser);
+        return getDataTable(list);
+    }
+
+
+    /**
+     * 获取客户群成员列详细信息
+     */
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(qwGroupChatUserService.selectQwGroupChatUserById(id));
+    }
+
+
+    /** 获取日星月的入群退群统计 */
+    @GetMapping("/getDataWeekMonthCount")
+    public R getDataWeekMonthCount(QwGroupChatUserDataType qwGroupChatUserDataType){
+
+        return qwGroupChatUserService.getDataWeekMonthCount(qwGroupChatUserDataType);
+    }
+
+    /**
+     *  同步 插入某个客户群信息
+     */
+    @PutMapping("/cogradientGroupChatUser")
+    public R cogradientGroupChatUser(@RequestBody QwInsertGroupChatUserParam param) {
+
+        return qwGroupChatUserService.cogradientGroupChatUser(param.getCorpId(), param.getChatId());
+    }
+}

+ 16 - 0
fs-common/src/main/java/com/fs/common/constant/RedisConstant.java

@@ -0,0 +1,16 @@
+package com.fs.common.constant;
+/**
+ * 库存与锁相关常量(Java 8 静态常量优化)
+ */
+public class RedisConstant {
+    // 库存Key前缀
+    public static final String STOCK_KEY_PREFIX = "product:stock:";
+    // 分布式锁Key前缀
+    public static final String LOCK_KEY_PREFIX = "product:lock:";
+    // 锁过期时间(30秒,避免死锁,大于业务执行时间)
+    public static final long LOCK_EXPIRE_SECONDS = 3L;
+    // 锁重试间隔(50毫秒,非阻塞重试,避免线程阻塞)
+    public static final long LOCK_RETRY_INTERVAL = 100L;
+    // 锁最大重试次数(3次,避免无限重试)
+    public static final int LOCK_MAX_RETRY = 20;
+}

+ 179 - 0
fs-common/src/main/java/com/fs/common/core/redis/service/StockDeductService.java

@@ -0,0 +1,179 @@
+package com.fs.common.core.redis.service;
+
+import com.fs.common.constant.RedisConstant;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.core.script.DefaultRedisScript;
+import org.springframework.stereotype.Service;
+
+import java.util.Collections;
+import java.util.Optional;
+import java.util.Random;
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.IntStream;
+
+/**
+ * 高并发库存扣减服务(Java 8 + Redis分布式锁)
+ */
+@Slf4j
+@Service
+public class StockDeductService {
+
+    // 注入RedisTemplate
+    public final RedisTemplate<String, Object> redisTemplate;
+
+    // 构造器注入(Spring 推荐,Java 8 支持)
+    public StockDeductService(RedisTemplate<String, Object> redisTemplate) {
+        this.redisTemplate = redisTemplate;
+    }
+
+    // 库存扣减Lua脚本(预编译,提升高并发性能)
+    private static final DefaultRedisScript<Long> STOCK_DEDUCT_SCRIPT;
+    // 锁释放Lua脚本(预编译)
+    private static final DefaultRedisScript<Long> LOCK_RELEASE_SCRIPT;
+
+    // 库存扣减Lua脚本(优化后,增强健壮性)
+    static {
+        // 初始化库存扣减脚本
+        STOCK_DEDUCT_SCRIPT = new DefaultRedisScript<>();
+        STOCK_DEDUCT_SCRIPT.setScriptText("if redis.call('exists', KEYS[1]) ~= 1 then " + "return -2; " + "end " + "local stock_str = redis.call('get', KEYS[1]); " + "local stock = tonumber(stock_str); " + "if stock == nil then " + "return -3; " + "end " + "local deductNum_str = ARGV[1]; " + "local deductNum = tonumber(deductNum_str); " + "if deductNum == nil or deductNum <= 0 then " + "return -4; " + "end " + "if stock >= deductNum then " + "return redis.call('decrby', KEYS[1], deductNum); " + "else " + "return -1; " + "end");
+        STOCK_DEDUCT_SCRIPT.setResultType(Long.class);
+
+        // 锁释放脚本保持不变
+        LOCK_RELEASE_SCRIPT = new DefaultRedisScript<>();
+        LOCK_RELEASE_SCRIPT.setScriptText("if redis.call('get', KEYS[1]) == ARGV[1] then " + "return redis.call('del', KEYS[1]) " + "else return 0 end");
+        LOCK_RELEASE_SCRIPT.setResultType(Long.class);
+    }
+
+    /**
+     * 初始化商品库存(Redis)
+     *
+     * @param productId 商品ID
+     * @param initStock 初始库存
+     */
+    public void initStock(Long productId, Long liveId, Integer initStock) {
+        String stockKey = RedisConstant.STOCK_KEY_PREFIX + liveId + ":" + productId;
+        redisTemplate.opsForValue().set(stockKey, initStock, 24 * 60 * 60, TimeUnit.SECONDS);
+        log.info("商品" + productId + "库存初始化完成,初始库存:" + initStock);
+    }
+
+    /**
+     * 同步获取商品库存(基础版,适用于低并发/实时性要求高的场景)
+     *
+     * @param productId 商品ID
+     * @param liveId    直播间ID(贴合原有库存Key的拆分粒度)
+     * @return 库存数量(库存不存在/非数字时返回0)
+     */
+    public Integer getStock(Long productId, Long liveId) {
+        // 1. 参数合法性校验(避免空ID导致Redis Key异常)
+        if (productId == null || liveId == null) {
+            log.warn("获取库存失败:商品ID/直播间ID为空");
+            return 0;
+        }
+
+        // 2. 拼接库存Key(与初始化/扣减逻辑保持一致)
+        String stockKey = RedisConstant.STOCK_KEY_PREFIX + liveId + ":" + productId;
+
+        try {
+            // 3. 读取Redis库存值(Java 8 Optional处理空值)
+            Object stockObj = redisTemplate.opsForValue().get(stockKey);
+            Integer stock = Optional.ofNullable(stockObj)
+                    // 处理Redis值非数字的情况(如脏数据)
+                    .map(val -> {
+                        if (val instanceof Integer) {
+                            return (Integer) val;
+                        } else if (val instanceof String) {
+                            try {
+                                return Integer.parseInt((String) val);
+                            } catch (NumberFormatException e) {
+                                log.error("库存值格式异常,Key:{},值:{}", stockKey, val, e);
+                                return 0;
+                            }
+                        } else {
+                            log.error("库存值类型不支持,Key:{},类型:{}", stockKey, val.getClass().getName());
+                            return 0;
+                        }
+                    })
+                    // 库存Key不存在时返回0
+                    .orElse(0);
+
+            log.info("获取商品{}库存成功,直播间{},当前库存:{}", productId, liveId, stock);
+            return stock;
+        } catch (Exception e) {
+            // 4. 异常兜底(Redis连接异常/超时等场景)
+            log.error("获取商品{}库存异常,直播间{}", productId, liveId, e);
+            return 0;
+        }
+    }
+
+    /**
+     * 异步获取商品库存(高并发版,适配原有异步扣减逻辑)
+     *
+     * @param productId 商品ID
+     * @param liveId    直播间ID
+     * @return 异步结果:库存数量(异常/空值返回0)
+     */
+    public CompletableFuture<Integer> getStockAsync(Long productId, Long liveId) {
+        // 复用CompletableFuture异步化,避免主线程阻塞(Java 8特性)
+        return CompletableFuture.supplyAsync(() -> getStock(productId, liveId));
+    }
+
+    /**
+     * 高并发库存扣减(核心方法,落地Java 8特性)
+     *
+     * @param productId 商品ID
+     * @param deductNum 扣减数量(默认1)
+     * @return 扣减结果:true=成功,false=失败
+     */
+    public CompletableFuture<Boolean> deductStockAsync(Long productId, Long liveId, Integer deductNum, Long userId) {
+        // Java 8 CompletableFuture 异步处理,提升高并发吞吐量
+        return CompletableFuture.supplyAsync(() -> {
+            // 1. 参数校验(Java 8 Optional 空值处理)
+            Integer num = Optional.ofNullable(deductNum).orElse(1);
+            String stockKey = RedisConstant.STOCK_KEY_PREFIX + liveId + ":" + productId;
+            try {
+                // 5. 执行库存扣减Lua脚本(原子操作,防超卖)
+                // 新增日志:打印当前库存值和扣减数量
+                Integer currentStockStr = (Integer) redisTemplate.opsForValue().get(stockKey);
+                log.info("拿到锁成功 → 库存Key:{},当前库存值:{},扣减数量:{}", stockKey, currentStockStr, num);
+
+                // 执行库存扣减Lua脚本
+                Long remainingStock = redisTemplate.execute(STOCK_DEDUCT_SCRIPT, Collections.singletonList(stockKey), 1);
+
+                // 新增日志:打印Lua返回结果
+                log.info("Lua脚本返回值:{}", remainingStock);
+
+                // 6. 判断扣减结果
+                if (remainingStock >= 0) {
+                    log.info("商品{}库存扣减成功,剩余库存:{}", productId, remainingStock);
+                    return true;
+                } else {
+                    String errorMsg = "";
+                    switch (remainingStock.intValue()) {
+                        case -1:
+                            errorMsg = "库存不足";
+                            break;
+                        case -2:
+                            errorMsg = "库存Key不存在";
+                            break;
+                        case -3:
+                            errorMsg = "库存值非数字";
+                            break;
+                        case -4:
+                            errorMsg = "扣减数量无效";
+                            break;
+                        default:
+                            errorMsg = "未知错误,错误码:" + remainingStock;
+                    }
+                    log.info("商品{}扣减失败:{}", productId, errorMsg);
+                    return false;
+                }
+            }catch (Exception e) {
+                log.error("支付失败获取失败", e);
+                return false;
+            }
+        });
+    }
+}

+ 39 - 18
fs-company/src/main/java/com/fs/company/controller/company/CompanyAiWorkflowController.java

@@ -4,6 +4,7 @@ import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.utils.ServletUtils;
+import com.fs.company.param.CompanyUserAiWorkflowTtsVoiceParam;
 import com.fs.framework.security.LoginUser;
 import com.fs.framework.service.TokenService;
 import com.fs.his.domain.FsAiWorkflow;
@@ -57,8 +58,8 @@ public class CompanyAiWorkflowController extends BaseController {
      * 获取当前登录企业用户的工作流关键节点
      * 节点类型: start(开始), end(结束), ai_chat(AI对话)
      */
-    @GetMapping("/myNodes")
-    public R getMyWorkflowNodes() {
+//    @GetMapping("/myNodes")
+    public R getMyWorkflowNode() {
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
         if (loginUser == null || loginUser.getUser() == null) {
             return R.error("用户信息错误");
@@ -70,27 +71,47 @@ public class CompanyAiWorkflowController extends BaseController {
     }
 
     /**
-     * 更新节点的语音URL
-     * @param nodeId 节点ID
-     * @param voiceUrl 语音URL
+     * 获取当前登录企业用户的工作流及需要录音的节点
      */
-    @PutMapping("/node/voiceUrl")
-    public R updateNodeVoiceUrl(@RequestParam("nodeId") Long nodeId,
-                                @RequestParam("voiceUrl") String voiceUrl) {
+    @GetMapping("/myNodes")
+    public R getMyWorkflowNodes() {
+        startPage();
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
         if (loginUser == null || loginUser.getUser() == null) {
             return R.error("用户信息错误");
         }
         Long companyUserId = loginUser.getUser().getUserId();
-
-        int result = fsAiWorkflowService.updateNodeVoiceUrl(nodeId, voiceUrl, companyUserId);
-        if (result == -1) {
-            return R.error("节点不存在");
-        } else if (result == -2) {
-            return R.error("无权限修改该节点");
-        } else if (result > 0) {
-            return R.ok("更新成功");
-        }
-        return R.error("更新失败");
+        return R.ok().put("data", fsAiWorkflowService.getMyWorkflowNodes(companyUserId));
     }
+
+//    @PostMapping("/ttsVoice")
+//    public R ttsVoice(@RequestBody CompanyUserAiWorkflowTtsVoiceParam param) {
+//        return R.ok().put("data", fsAiWorkflowService.ttsVoice(param));
+//    }
+
+//    /**
+//     * 更新节点的语音URL
+//     * @param nodeKey 节点ID
+//     * @param voiceUrl 语音URL
+//     */
+//    @PutMapping("/node/voiceUrl")
+//    public R updateNodeVoiceUrl(@RequestParam("nodeKey") String nodeKey,
+//                                @RequestParam("voiceUrl") String voiceUrl,
+//                                @RequestParam("workflowId") Long workflowId) {
+//        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+//        if (loginUser == null || loginUser.getUser() == null) {
+//            return R.error("用户信息错误");
+//        }
+//        Long companyUserId = loginUser.getUser().getUserId();
+//
+//        int result = fsAiWorkflowService.updateNodeVoiceUrl(nodeKey, voiceUrl, companyUserId,workflowId);
+//        if (result == -1) {
+//            return R.error("节点不存在");
+//        } else if (result == -2) {
+//            return R.error("无权限修改该节点");
+//        } else if (result > 0) {
+//            return R.ok("更新成功");
+//        }
+//        return R.error("更新失败");
+//    }
 }

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

@@ -108,6 +108,7 @@ public class CompanyRedPacketBalanceLogsController extends BaseController {
         redRecharge.setRemark(param.getRemark());
         redRecharge.setPayType(3);
         redRecharge.setBusinessType(1);// 红包充值
+        redRecharge.setImgs(param.getImgs());// 红包充值
         rechargeService.insertCompanyRecharge(redRecharge);
         return R.ok("提交成功,等待审核");
 

+ 19 - 0
fs-company/src/main/java/com/fs/company/controller/live/LiveMsgController.java

@@ -10,6 +10,7 @@ import com.fs.company.domain.CompanyUser;
 import com.fs.framework.security.SecurityUtils;
 import com.fs.live.domain.LiveMsg;
 import com.fs.live.service.ILiveMsgService;
+import com.fs.live.vo.LiveMsgExportVO;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
 
@@ -105,4 +106,22 @@ public class LiveMsgController extends BaseController
         return toAjax(liveMsgService.deleteLiveMsgByMsgIds(msgIds));
     }
 
+    /**
+     * 导出直播评论
+     */
+    @Log(title = "直播评论导出", businessType = BusinessType.EXPORT)
+    @GetMapping("/exportComments/{liveId}")
+    public AjaxResult exportComments(@PathVariable("liveId") Long liveId)
+    {
+        try {
+            CompanyUser user = SecurityUtils.getLoginUser().getUser();
+            Long userId = user.getUserId();
+            
+            List<LiveMsgExportVO> list = liveMsgService.exportLiveMsgComments(liveId, userId);
+            ExcelUtil<LiveMsgExportVO> util = new ExcelUtil<LiveMsgExportVO>(LiveMsgExportVO.class);
+            return util.exportExcel(list, "直播评论数据");
+        } catch (Exception e) {
+            return AjaxResult.error("导出失败:" + e.getMessage());
+        }
+    }
 }

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

@@ -722,7 +722,7 @@ public class QwExternalContactController extends BaseController
 
         return  qwExternalContactService.setCustomerCourseSopList(param);
     }
-    @PreAuthorize("@ss.hasPermi('qw:externalContact:edit')")
+    @PreAuthorize("@ss.hasPermi('qw:externalContact:edit') or @ss.hasPermi('qw:externalContact:deptEdit') or @ss.hasPermi('qw:externalContact:myEdit')")
     @Log(title = "批量修改备注", businessType = BusinessType.UPDATE)
     @PostMapping("/batchUpdateExternalContactNotes")
     public R batchUpdateExternalContactNotes(@RequestBody QwExternalContactUpdateNoteParam param) throws JSONException {

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

@@ -8,6 +8,7 @@ import com.fs.common.core.domain.R;
 import com.fs.common.core.domain.ResponseResult;
 import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.CloudHostUtils;
 import com.fs.common.utils.ServletUtils;
 import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.poi.ExcelUtil;
@@ -187,7 +188,9 @@ public class FsUserController extends BaseController
     public AjaxResult getInfo(@PathVariable("userId") Long userId)
     {
         FsUser fsUser = fsUserService.selectFsUserByUserId(userId);
-        fsUser.setPhone(decryptAutoPhoneMk(fsUser.getPhone()));
+        if(!CloudHostUtils.hasCloudHostName("广州郑多燕")) {
+            fsUser.setPhone(decryptAutoPhoneMk(fsUser.getPhone()));
+        }
         return AjaxResult.success(fsUser);
     }
 

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

@@ -15,6 +15,9 @@ 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.CompanyUser;
+import com.fs.company.service.ICompanyUserService;
+import com.fs.config.cloud.CloudHostProper;
 import com.fs.crm.domain.CrmCustomer;
 import com.fs.crm.service.ICrmCustomerService;
 import com.fs.framework.security.LoginUser;
@@ -46,6 +49,7 @@ import java.text.ParseException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.stream.Collectors;
 
 /**
  * 订单Controller
@@ -80,6 +84,10 @@ public class FsStoreOrderScrmController extends BaseController
     private IFsStoreOrderAuditLogScrmService orderAuditLogService;
     @Autowired
     private ISysConfigService configService;
+    @Autowired
+    private ICompanyUserService companyUserService;
+    @Autowired
+    private CloudHostProper cloudHostProper;
 
     /**
      * 查询订单列表
@@ -90,6 +98,19 @@ public class FsStoreOrderScrmController extends BaseController
     {
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
         param.setCompanyId(loginUser.getCompany().getCompanyId());
+
+        // 郑多燕需求
+        if("广州郑多燕".equals(cloudHostProper.getCompanyName())){
+            // 根据当前销售所属的数据权限过滤订单;
+            List<CompanyUser> companyUsers = companyUserService.getDataScopeCompanyUser(loginUser.getUser().getUserId());
+            if(!companyUsers.isEmpty()){
+                List<Long> companyUserIds = companyUsers.stream().map(CompanyUser::getUserId).collect(Collectors.toList());
+                param.setCompanyUserIds(companyUserIds);
+            } else {
+                // 表示数据权限是本人
+                param.setCompanyUserId(loginUser.getUser().getUserId());
+            }
+        }
         startPage();
         if(!StringUtils.isEmpty(param.getCreateTimeRange())){
             param.setCreateTimeList(param.getCreateTimeRange().split("--"));

+ 3 - 3
fs-ipad-task/src/main/resources/logback.xml

@@ -21,7 +21,7 @@
             <!-- 日志文件名格式 -->
 			<fileNamePattern>${log.path}/sys-info.%d{yyyy-MM-dd}.log</fileNamePattern>
 			<!-- 日志最大的历史 60天 -->
-			<maxHistory>60</maxHistory>
+			<maxHistory>6</maxHistory>
 		</rollingPolicy>
 		<encoder>
 			<pattern>${log.pattern}</pattern>
@@ -43,7 +43,7 @@
             <!-- 日志文件名格式 -->
             <fileNamePattern>${log.path}/sys-error.%d{yyyy-MM-dd}.log</fileNamePattern>
 			<!-- 日志最大的历史 60天 -->
-			<maxHistory>60</maxHistory>
+			<maxHistory>6</maxHistory>
         </rollingPolicy>
         <encoder>
             <pattern>${log.pattern}</pattern>
@@ -65,7 +65,7 @@
             <!-- 按天回滚 daily -->
             <fileNamePattern>${log.path}/sys-user.%d{yyyy-MM-dd}.log</fileNamePattern>
             <!-- 日志最大的历史 60天 -->
-            <maxHistory>60</maxHistory>
+            <maxHistory>6</maxHistory>
         </rollingPolicy>
         <encoder>
             <pattern>${log.pattern}</pattern>

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

@@ -110,7 +110,7 @@ public class WebSocketServer {
         if (live == null) {
             throw new BaseException("未找到直播间");
         }
-        long companyId = live.getCompanyId() == null ? -1L : live.getCompanyId();
+        long companyId = -1L;
         long companyUserId = -1L;
         if (!Objects.isNull(userProperties.get("companyId"))) {
             companyId = (long) userProperties.get("companyId");
@@ -131,7 +131,15 @@ public class WebSocketServer {
 
         // 记录连接信息 管理员不记录
         if (userType == 0) {
-            FsUserScrm fsUser = fsUserService.selectFsUserById(userId);
+            // 缓存用户信息,过期时间4小时
+            String userCacheKey = "fs:user:" + userId;
+            FsUserScrm fsUser = redisCache.getCacheObject(userCacheKey);
+            if (fsUser == null) {
+                fsUser = fsUserService.selectFsUserById(userId);
+                if (fsUser != null) {
+                    redisCache.setCacheObject(userCacheKey, fsUser, 4, TimeUnit.HOURS);
+                }
+            }
             if (Objects.isNull(fsUser)) {
                 throw new BaseException("用户信息错误");
             }
@@ -195,7 +203,15 @@ public class WebSocketServer {
                 broadcastWebMessage(liveId, JSONObject.toJSONString(R.ok().put("data", sendMsgVo)));
             }
 
-            LiveUserFirstEntry liveUserFirstEntry = liveUserFirstEntryService.selectEntityByLiveIdUserId(liveId, userId);
+            // 缓存用户首次进入记录,过期时间4小时
+            String liveUserFirstEntryCacheKey = "live:userFirstEntry:" + liveId + ":" + userId;
+            LiveUserFirstEntry liveUserFirstEntry = redisCache.getCacheObject(liveUserFirstEntryCacheKey);
+            if (liveUserFirstEntry == null) {
+                liveUserFirstEntry = liveUserFirstEntryService.selectEntityByLiveIdUserId(liveId, userId);
+                if (liveUserFirstEntry != null) {
+                    redisCache.setCacheObject(liveUserFirstEntryCacheKey, liveUserFirstEntry, 4, TimeUnit.HOURS);
+                }
+            }
             // 如果用户连上了 socket,并且公司ID和销售ID大于0,更新 LiveWatchLog 的 logType
 
             if ((qwUserId > 0 && externalContactId > 0) || (liveUserFirstEntry != null && liveUserFirstEntry.getCompanyId() > 0 && liveUserFirstEntry.getCompanyUserId() > 0 )) {
@@ -226,9 +242,19 @@ public class WebSocketServer {
             } else {
                 // 这个用户A邀请用户b,b的业绩算a的销售的
                 if (companyId == -2L) {
-                    LiveUserFirstEntry clientB = liveUserFirstEntryService.selectEntityByLiveIdUserId(liveId, companyUserId);
-                    companyId = clientB.getCompanyId();
-                    companyUserId = clientB.getCompanyUserId();
+                    // 缓存用户首次进入记录,过期时间4小时
+                    String clientBCacheKey = "live:userFirstEntry:" + liveId + ":" + companyUserId;
+                    LiveUserFirstEntry clientB = redisCache.getCacheObject(clientBCacheKey);
+                    if (clientB == null) {
+                        clientB = liveUserFirstEntryService.selectEntityByLiveIdUserId(liveId, companyUserId);
+                        if (clientB != null) {
+                            redisCache.setCacheObject(clientBCacheKey, clientB, 4, TimeUnit.HOURS);
+                        }
+                    }
+                    if (clientB != null) {
+                        companyId = clientB.getCompanyId();
+                        companyUserId = clientB.getCompanyUserId();
+                    }
                 }
                 Date date = new Date();
                 liveUserFirstEntry = new LiveUserFirstEntry();
@@ -247,10 +273,10 @@ public class WebSocketServer {
                 }
                 liveUserFirstEntryService.insertLiveUserFirstEntry(liveUserFirstEntry);
             }
-            redisCache.setCacheObject( "live:user:first:entry:" + liveId + ":" + userId, liveUserFirstEntry,1, TimeUnit.HOURS);
+            redisCache.setCacheObject( "live:user:first:entry:" + liveId + ":" + userId, liveUserFirstEntry, 4, TimeUnit.HOURS);
 
             // 推送完课积分倒计时配置信息给前端
-            sendCompletionPointsConfigToUser(session, liveId, userId, live);
+//            sendCompletionPointsConfigToUser(session, liveId, userId, live);
 
 
         } else {
@@ -287,7 +313,15 @@ public class WebSocketServer {
         ConcurrentHashMap<Long, Session> room = getRoom(liveId);
         List<Session> adminRoom = getAdminRoom(liveId);
         if (userType == 0) {
-            FsUserScrm fsUser = fsUserService.selectFsUserById(userId);
+            // 缓存用户信息,过期时间4小时
+            String userCacheKey = "fs:user:" + userId;
+            FsUserScrm fsUser = redisCache.getCacheObject(userCacheKey);
+            if (fsUser == null) {
+                fsUser = fsUserService.selectFsUserById(userId);
+                if (fsUser != null) {
+                    redisCache.setCacheObject(userCacheKey, fsUser, 4, TimeUnit.HOURS);
+                }
+            }
             if (Objects.isNull(fsUser)) {
                 throw new BaseException("用户信息错误");
             }

+ 8 - 1
fs-live-app/src/main/resources/application.yml

@@ -2,8 +2,15 @@
 server:
   # 服务器的HTTP端口,默认为
   port: 7114
+  tomcat:
+    max-connections: 1000000
+    max-threads: 32
+    connection-timeout: 600000
+    accept-count: 10000
+  compression:
+    enabled: true
 
 # Spring配置
 spring:
   profiles:
-    active: dev-test
+    active: druid-bjzm-test

+ 5 - 0
fs-service/pom.xml

@@ -297,6 +297,11 @@
             <artifactId>volc-sdk-java</artifactId>
             <version>1.0.250</version>
         </dependency>
+        <dependency>
+            <groupId>org.springframework.retry</groupId>
+            <artifactId>spring-retry</artifactId>
+            <version>1.3.1</version>
+        </dependency>
 
 
     </dependencies>

+ 1 - 1
fs-service/src/main/java/com/fs/aiSoundReplication/param/TtsRequest.java

@@ -16,7 +16,7 @@ public class TtsRequest {
     private String token;
 
     @JsonProperty("cluster")
-    private String cluster = "volcano_icl"; // 声音复刻必须使用此cluster
+    private String cluster = "volcano_icl"; // 声音复刻必须使用此cluster 2.0 volc_voice_clone_v2
 
     @JsonProperty("voice_type")
     private String voiceType; // 训练好的speaker_id

+ 1 - 0
fs-service/src/main/java/com/fs/aiSoundReplication/service/impl/TtsServiceImpl.java

@@ -316,6 +316,7 @@ public class TtsServiceImpl implements TtsService {
             put("reqid", request.getReqId());
             put("text", request.getText());
             put("operation","query");
+            put("split_sentence", 1);//处理声音复刻语速过快
         }};
 
         requestBody.put("app",app);

+ 3 - 4
fs-service/src/main/java/com/fs/aiSoundReplication/service/impl/VoiceCloneServiceImpl.java

@@ -41,7 +41,7 @@ public class VoiceCloneServiceImpl implements VoiceCloneService {
         try {
             // 1. 参数校验
             if (!validateUploadParams(speakerId, audioFile, modelType))
-                return R.error("参数错误");
+                throw new VoiceCloneException(ErrorCodeEnum.BAD_REQUEST_ERROR.getCode(),ErrorCodeEnum.BAD_REQUEST_ERROR.getMessage());
 
             // 2. 构建请求
             VoiceCloneRequest request = buildUploadRequest(speakerId, audioFile, modelType, language);
@@ -62,12 +62,11 @@ public class VoiceCloneServiceImpl implements VoiceCloneService {
 
         } catch (JsonProcessingException e) {
             log.error("JSON序列化失败,声音复刻发送豆包失败", e);
-            R.error("JSON序列化失败");
+            return R.error("JSON序列化失败");
         } catch (IOException e) {
             log.error("文件处理失败,声音复刻发送豆包失败", e);
-            R.error("文件处理失败");
+            return R.error("文件处理失败");
         }
-        return R.ok();
     }
 
     @Override

+ 2 - 0
fs-service/src/main/java/com/fs/company/mapper/CompanyRechargeMapper.java

@@ -98,8 +98,10 @@ public interface CompanyRechargeMapper
             "order by r.recharge_id desc " +
             "</script>"})
     List<CompanyRechargeVO> selectCompanyRechargeVOList(@Param("maps")CompanyRecharge companyRecharge);
+
     @Select("select * from company_recharge where recharge_no=#{no}")
     CompanyRecharge selectCompanyRechargeByNo(String no);
+
     @Select("select sum(money) from company_recharge where status=1")
     BigDecimal selectCompanyRechargeMoney();
 

+ 11 - 0
fs-service/src/main/java/com/fs/company/param/CompanyUserAiWorkflowTtsVoiceParam.java

@@ -0,0 +1,11 @@
+package com.fs.company.param;
+
+import lombok.Data;
+
+/**
+ * ai工作流销售tts参数
+ */
+@Data
+public class CompanyUserAiWorkflowTtsVoiceParam {
+
+}

+ 9 - 0
fs-service/src/main/java/com/fs/company/service/ICompanyUserService.java

@@ -264,4 +264,13 @@ public interface ICompanyUserService {
      * @return 绑定的用户列表
      */
     List<com.fs.hisStore.domain.FsUserScrm> selectBoundFsUsersByCompanyUserId(Long companyUserId);
+
+
+    /**
+     * 根据当前销售所属的数据权限过滤订单;
+     * @auth Caoliqin
+     * @param companyUserId
+     * @return
+     */
+    List<CompanyUser> getDataScopeCompanyUser(Long companyUserId);
 }

+ 23 - 0
fs-service/src/main/java/com/fs/company/service/impl/CompanyUserServiceImpl.java

@@ -128,6 +128,9 @@ public class CompanyUserServiceImpl implements ICompanyUserService
 //    @Autowired
 //    private ICompanyUserRoleService userRoleService;
 
+    @Autowired
+    private CompanyRoleMapper companyRoleMapper;
+
 
     /**
      * 查询物业公司管理员信息
@@ -1096,4 +1099,24 @@ public class CompanyUserServiceImpl implements ICompanyUserService
     public List<com.fs.hisStore.domain.FsUserScrm> selectBoundFsUsersByCompanyUserId(Long companyUserId) {
         return companyUserMapper.selectBoundFsUsersByCompanyUserId(companyUserId);
     }
+
+    @Override
+    public List<CompanyUser> getDataScopeCompanyUser(Long companyUserId) {
+        CompanyUser companyUser = companyUserService.selectCompanyUserById(companyUserId);
+        List<CompanyRole> companyRoles = companyRoleMapper.selectRolePermissionByUserId(companyUserId);
+        List<CompanyUser> companyUsers;
+        // 是否是管理员或者包含 1:全部数据权限
+        if (companyUser.isAdmin() || companyRoles.stream().anyMatch(r -> "1".equals(r.getDataScope()))) {
+            companyUsers = companyUserService.getCompanyUserListByCompanyIdAndDeptId(companyUser.getCompanyId(), null);
+        }
+        // 是否包含 3:本部门数据权限 4:本部门及以下数据权限
+        else if (companyRoles.stream().anyMatch(r -> "3".equals(r.getDataScope()) || "4".equals(r.getDataScope()))) {
+            companyUsers = companyUserService.getCompanyUserListByCompanyIdAndDeptId(companyUser.getCompanyId(), companyUser.getDeptId());
+        }
+        // 默认空,判断是否包含 5:仅可查看本人
+        else {
+            companyUsers = new ArrayList<>();
+        }
+        return companyUsers;
+    }
 }

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

@@ -49,7 +49,7 @@ public class CompanyProfitVO implements Serializable
     private BigDecimal money;
 
     /** 余额 */
-    @Excel(name = "余额")
+    //@Excel(name = "余额")
     private BigDecimal balances;
 
     /** 提现状态(1确认中,2成功,3失败) */

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

@@ -85,6 +85,9 @@ public class CompanyRechargeVO implements Serializable {
     @Excel(name = "备注")
     private String remark;
 
+    @Excel(name = "凭证")
+    private String imgs;
+
     /** 业务类型 0-普通 1-红包充值 */
     private Integer businessType;
 

+ 14 - 0
fs-service/src/main/java/com/fs/course/dto/FsCourseCategoryImportDTO.java

@@ -0,0 +1,14 @@
+package com.fs.course.dto;
+
+import com.fs.common.annotation.Excel;
+import lombok.Data;
+
+@Data
+public class FsCourseCategoryImportDTO {
+
+    @Excel(name = "父分类")
+    private String rootCate;
+
+    @Excel(name = "子分类")
+    private String subCate;
+}

+ 3 - 0
fs-service/src/main/java/com/fs/course/mapper/FsCoursePlaySourceConfigMapper.java

@@ -1,6 +1,8 @@
 package com.fs.course.mapper;
 
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.common.annotation.DataSource;
+import com.fs.common.enums.DataSourceType;
 import com.fs.course.domain.FsCoursePlaySourceConfig;
 import com.fs.course.vo.FsCoursePlaySourceConfigVO;
 import org.apache.ibatis.annotations.Param;
@@ -15,5 +17,6 @@ public interface FsCoursePlaySourceConfigMapper extends BaseMapper<FsCoursePlayS
      */
     List<FsCoursePlaySourceConfigVO> selectCoursePlaySourceConfigVOListByMap(@Param("params") Map<String, Object> params);
 
+    @DataSource(DataSourceType.SLAVE)
     FsCoursePlaySourceConfig selectCoursePlaySourceConfigByAppId(String appId);
 }

+ 12 - 0
fs-service/src/main/java/com/fs/course/mapper/FsUserCourseCategoryMapper.java

@@ -101,4 +101,16 @@ public interface FsUserCourseCategoryMapper
 
     @Select("select cate_id dict_value, cate_name dict_label  from fs_user_course_category WHERE pid = 0 and is_del=0 ")
     List<OptionsVO> selectAllFsUserCourseCategoryPidList();
+
+    /**
+     * 根据名称和上级ID查询分类
+     */
+    @Select("select * from fs_user_course_category where cate_name = #{name} and pid = #{parentId} and is_del = 0")
+    FsUserCourseCategory selectFsUserCourseCategoryByNameAndParentId(@Param("name") String name, @Param("parentId") Long parentId);
+
+    /**
+     * 获取序号
+     */
+    @Select("select ifnull(max(sort), 0) + 1 from fs_user_course_category where pid = #{parentId}")
+    Long getSortByParentId(@Param("parentId") Long parentId);
 }

+ 1 - 1
fs-service/src/main/java/com/fs/course/mapper/FsUserVideoMapper.java

@@ -254,7 +254,7 @@ public interface FsUserVideoMapper
     @Select({"<script> " +
             "select v.video_id as id,v.title,v.description as msg,t.nick_name as username,t.avatar as headImg, " +
             "v.thumbnail as cover,v.url as src,v.likes as likeNum,v.comments as smsNum,v.favorite_num," +
-            "v.create_time,v.views as playNumber,v.product_id,p.img_url,p.package_name,v.upload_type,v.shares,v.add_num,v.is_audit,v.fail_reason,v.status from fs_user_video v " +
+            "v.create_time,v.views as playNumber,v.product_id,p.img_url,p.package_name,v.upload_type,v.shares,v.add_num,v.is_audit,v.status from fs_user_video v " +
             "left join fs_user_talent t on t.talent_id = v.talent_id " +
             " left join fs_package p on p.package_id = v.product_id " +
             "where v.is_del = 0 and (" +

+ 8 - 0
fs-service/src/main/java/com/fs/course/service/IFsUserCourseCategoryService.java

@@ -1,7 +1,10 @@
 package com.fs.course.service;
 
 import java.util.List;
+import java.util.Map;
+
 import com.fs.course.domain.FsUserCourseCategory;
+import com.fs.course.dto.FsCourseCategoryImportDTO;
 import com.fs.his.vo.OptionsVO;
 
 /**
@@ -68,4 +71,9 @@ public interface IFsUserCourseCategoryService
     List<OptionsVO> selectFsUserCourseCategoryPidList(Long userId);
 
     List<OptionsVO> selectCateListByPid(Long pid);
+
+    /**
+     * 课堂分类导入
+     */
+    Map<String, Object> importData(List<FsCourseCategoryImportDTO> list, Long userId);
 }

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

@@ -1,8 +1,14 @@
 package com.fs.course.service.impl;
 
-import java.util.List;
+import java.util.*;
+import java.util.stream.Collectors;
+
+import com.fs.common.exception.CustomException;
 import com.fs.common.utils.DateUtils;
+import com.fs.common.utils.StringUtils;
+import com.fs.course.dto.FsCourseCategoryImportDTO;
 import com.fs.his.vo.OptionsVO;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import com.fs.course.mapper.FsUserCourseCategoryMapper;
@@ -15,6 +21,7 @@ import com.fs.course.service.IFsUserCourseCategoryService;
  * @author fs
  * @date 2024-05-15
  */
+@Slf4j
 @Service
 public class FsUserCourseCategoryServiceImpl implements IFsUserCourseCategoryService
 {
@@ -118,4 +125,85 @@ public class FsUserCourseCategoryServiceImpl implements IFsUserCourseCategorySer
     public List<OptionsVO> selectCateListByPid(Long pid) {
         return fsUserCourseCategoryMapper.selectCateListByPid(pid);
     }
+
+    /**
+     * 课堂分类导入
+     */
+    @Override
+    public Map<String, Object> importData(List<FsCourseCategoryImportDTO> list, Long userId) {
+        int suCnt = 0;
+        int filCnt = 0;
+        List<FsCourseCategoryImportDTO> failedList = new ArrayList<>();
+
+        if (list == null || list.isEmpty()) {
+            throw new CustomException("导入数据不能为空");
+        }
+
+        Map<String, List<FsCourseCategoryImportDTO>> rootMap = list.stream()
+                .filter(dto -> StringUtils.isNotBlank(dto.getRootCate()))
+                .collect(Collectors.groupingBy(FsCourseCategoryImportDTO::getRootCate));
+        for (Map.Entry<String, List<FsCourseCategoryImportDTO>> entry : rootMap.entrySet()) {
+            if (StringUtils.isBlank(entry.getKey())) {
+                failedList.addAll(entry.getValue());
+                filCnt += entry.getValue().size();
+                continue;
+            }
+
+            String rootCate = entry.getKey().trim();
+            FsUserCourseCategory root = fsUserCourseCategoryMapper.selectFsUserCourseCategoryByNameAndParentId(rootCate, 0L);
+            if (Objects.isNull(root)) {
+                root = new FsUserCourseCategory();
+                root.setPid(0L);
+                root.setCateName(rootCate);
+                root.setSort(fsUserCourseCategoryMapper.getSortByParentId(0L));
+                root.setIsShow(1);
+                root.setCreateTime(new Date());
+                root.setIsDel(0);
+                root.setUserId(userId);
+                fsUserCourseCategoryMapper.insertFsUserCourseCategory(root);
+            }
+
+            Long parentId = root.getCateId();
+            long curSort = fsUserCourseCategoryMapper.getSortByParentId(root.getCateId());
+            List<FsCourseCategoryImportDTO> subList = entry.getValue();
+            for (FsCourseCategoryImportDTO child : subList) {
+                try {
+                    if (StringUtils.isBlank(child.getSubCate())) {
+                        failedList.add(child);
+                        filCnt++;
+                        continue;
+                    }
+
+                    String cateName = child.getSubCate().trim();
+                    FsUserCourseCategory sub = fsUserCourseCategoryMapper.selectFsUserCourseCategoryByNameAndParentId(cateName, parentId);
+                    if (Objects.nonNull(sub)) {
+                        failedList.add(child);
+                        filCnt++;
+                        continue;
+                    }
+
+                    sub = new FsUserCourseCategory();
+                    sub.setPid(parentId);
+                    sub.setCateName(cateName);
+                    sub.setSort(curSort++);
+                    sub.setIsShow(1);
+                    sub.setCreateTime(new Date());
+                    sub.setIsDel(0);
+                    sub.setUserId(userId);
+                    fsUserCourseCategoryMapper.insertFsUserCourseCategory(sub);
+
+                    suCnt++;
+                } catch (Exception e) {
+                    filCnt++;
+                    log.error("导入子分类失败: {}", child, e);
+                }
+            }
+        }
+
+        String message = "导入完成!成功" + suCnt + "条,失败" + filCnt + "条。";
+        Map<String, Object> resp = new HashMap<>();
+        resp.put("message", message);
+        resp.put("failList", failedList);
+        return resp;
+    }
 }

+ 9 - 0
fs-service/src/main/java/com/fs/erp/service/IErpOrderService.java

@@ -7,6 +7,8 @@ import com.fs.his.domain.FsStoreOrder;
 import com.fs.hisStore.domain.FsStoreOrderScrm;
 import com.fs.live.domain.LiveOrder;
 
+import java.util.List;
+
 public interface IErpOrderService
 {
 
@@ -34,5 +36,12 @@ public interface IErpOrderService
 
     void getOrderScrmDeliveryStatus(FsStoreOrderScrm order);
     void getOrderLiveDeliveryStatus(LiveOrder order);
+    
+    /**
+     * 批量查询直播订单
+     * @param orderList 订单列表
+     * @return 查询结果
+     */
+    ErpOrderQueryResponse batchGetLiveOrder(List<LiveOrder> orderList);
 }
 

+ 8 - 0
fs-service/src/main/java/com/fs/erp/service/impl/DfOrderServiceImpl.java

@@ -1020,6 +1020,14 @@ public class DfOrderServiceImpl implements IErpOrderService {
         }
     }
 
+    @Override
+    public ErpOrderQueryResponse batchGetLiveOrder(List<LiveOrder> orderList) {
+        ErpOrderQueryResponse response = new ErpOrderQueryResponse();
+        response.setOrders(Collections.emptyList());
+        response.setSuccess(true);
+        return response;
+    }
+
     private void cancelOrderLive(LiveOrder order) {
         Integer deliveryStatus = order.getDeliveryStatus();
         if (deliveryStatus == null || deliveryStatus == 0) {

+ 11 - 0
fs-service/src/main/java/com/fs/erp/service/impl/ErpOrderServiceImpl.java

@@ -15,6 +15,9 @@ import com.fs.hisStore.domain.FsStoreOrderScrm;
 import com.fs.live.domain.LiveOrder;
 import com.fs.live.mapper.LiveOrderMapper;
 import org.slf4j.Logger;
+
+import java.util.Collections;
+import java.util.List;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
@@ -193,4 +196,12 @@ public class ErpOrderServiceImpl implements IErpOrderService
     public void getOrderLiveDeliveryStatus(LiveOrder order) {
 
     }
+
+    @Override
+    public ErpOrderQueryResponse batchGetLiveOrder(List<LiveOrder> orderList) {
+        ErpOrderQueryResponse response = new ErpOrderQueryResponse();
+        response.setOrders(Collections.emptyList());
+        response.setSuccess(true);
+        return response;
+    }
 }

+ 9 - 0
fs-service/src/main/java/com/fs/erp/service/impl/HzOMSErpOrderServiceImpl.java

@@ -27,6 +27,7 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
 import java.math.BigDecimal;
+import java.util.Collections;
 import java.util.List;
 
 /**
@@ -345,6 +346,14 @@ public class HzOMSErpOrderServiceImpl implements IErpOrderService {
 
     }
 
+    @Override
+    public ErpOrderQueryResponse batchGetLiveOrder(List<LiveOrder> orderList) {
+        ErpOrderQueryResponse response = new ErpOrderQueryResponse();
+        response.setOrders(Collections.emptyList());
+        response.setSuccess(true);
+        return response;
+    }
+
     /**
      * 构建瀚智创建订单参数
      *

+ 186 - 31
fs-service/src/main/java/com/fs/erp/service/impl/JSTErpOrderServiceImpl.java

@@ -524,25 +524,8 @@ public class JSTErpOrderServiceImpl implements IErpOrderService {
         OrderQueryRequestDTO requestDTO = new OrderQueryRequestDTO();
         requestDTO.setOIds(Collections.singletonList(Long.valueOf(param.getCode())));
 
-        // 限流检查:每分钟最多100次请求,超过95次返回429
-        String rateLimitKey = RATE_LIMIT_KEY_PREFIX + System.currentTimeMillis() / 60000; // 每分钟一个key
-
-        // 使用原子操作增加计数,并获取增加后的值
-        Long currentCount = redisCache.incr(rateLimitKey, 1L);
-
-        // 如果是第一次请求,设置过期时间为1分钟
-        if (currentCount == 1) {
-            redisCache.expire(rateLimitKey, 1, TimeUnit.MINUTES);
-        }
         // 3. 构建响应对象
         ErpOrderQueryResponse response = new ErpOrderQueryResponse();
-        // 如果当前分钟内请求次数超过95次,直接返回429错误
-        if (currentCount >= RATE_LIMIT_THRESHOLD) {
-            response.setCode("429");
-            response.setSuccess(false);
-            return response;
-        }
-
 
         // 2. 调用ERP服务查询订单
         OrderQueryResponseDTO query = jstErpHttpService.query(requestDTO);
@@ -565,24 +548,10 @@ public class JSTErpOrderServiceImpl implements IErpOrderService {
 
     @Override
     public ErpOrderQueryResponse getLiveOrder(ErpOrderQueryRequert param) {
-        // 限流检查:每分钟最多100次请求,超过95次返回429
-        String rateLimitKey = RATE_LIMIT_KEY_PREFIX + System.currentTimeMillis() / 60000; // 每分钟一个key
 
-        // 使用原子操作增加计数,并获取增加后的值
-        Long currentCount = redisCache.incr(rateLimitKey, 1L);
 
-        // 如果是第一次请求,设置过期时间为1分钟
-        if (currentCount == 1) {
-            redisCache.expire(rateLimitKey, 1, TimeUnit.MINUTES);
-        }
         // 3. 构建响应对象
         ErpOrderQueryResponse response = new ErpOrderQueryResponse();
-        // 如果当前分钟内请求次数超过95次,直接返回429错误
-        if (currentCount >= RATE_LIMIT_THRESHOLD) {
-            response.setCode("429");
-            response.setSuccess(false);
-            return response;
-        }
 
         // 1. 构建查询请求DTO
         OrderQueryRequestDTO requestDTO = new OrderQueryRequestDTO();
@@ -1166,5 +1135,191 @@ public class JSTErpOrderServiceImpl implements IErpOrderService {
     public void getOrderLiveDeliveryStatus(LiveOrder order) {
 
     }
+
+    /**
+     * 批量查询直播订单
+     * @param orderList 订单列表
+     * @return 查询结果
+     */
+    @Override
+    public ErpOrderQueryResponse batchGetLiveOrder(List<LiveOrder> orderList) {
+        if (CollectionUtils.isEmpty(orderList)) {
+            ErpOrderQueryResponse response = new ErpOrderQueryResponse();
+            response.setOrders(Collections.emptyList());
+            response.setSuccess(true);
+            return response;
+        }
+
+        // 提取所有 extendOrderId
+        List<Long> extendOrderIds = orderList.stream()
+                .filter(order -> StringUtils.isNotEmpty(order.getExtendOrderId()))
+                .map(order -> Long.valueOf(order.getExtendOrderId()))
+                .collect(Collectors.toList());
+
+        if (CollectionUtils.isEmpty(extendOrderIds)) {
+            ErpOrderQueryResponse response = new ErpOrderQueryResponse();
+            response.setOrders(Collections.emptyList());
+            response.setSuccess(true);
+            return response;
+        }
+
+        // 构建批量查询请求
+        OrderQueryRequestDTO requestDTO = new OrderQueryRequestDTO();
+        requestDTO.setOIds(extendOrderIds);
+        requestDTO.setOrderItemFlds(Arrays.asList("status"));
+
+        // 调用ERP服务查询订单
+        OrderQueryResponseDTO query = jstErpHttpService.query(requestDTO);
+
+        // 构建响应对象
+        ErpOrderQueryResponse response = new ErpOrderQueryResponse();
+        List<Map<String, Object>> channelUpdateList = new ArrayList<>();
+        List<ErpOrderQuery> erpOrders = new ArrayList<>();
+
+        if (query.getOrders() != null && !query.getOrders().isEmpty()) {
+            // 创建订单映射,方便查找
+            Map<String, LiveOrder> orderMap = orderList.stream()
+                    .collect(Collectors.toMap(
+                            LiveOrder::getExtendOrderId,
+                            order -> order,
+                            (existing, replacement) -> existing
+                    ));
+
+            List<LiveOrder> splitOrders = new ArrayList<>();
+            // 收集需要更新channel字段的订单(其他状态)
+
+
+            // 处理查询结果
+            for (OrderQueryResponseDTO.Order order : query.getOrders()) {
+                String status = order.getStatus();
+                LiveOrder liveOrder = orderMap.get(String.valueOf(order.getOId()));
+
+                if (liveOrder == null) {
+                    continue;
+                }
+
+                // 如果是 sent 状态,正常处理
+                if (ErpQueryOrderStatusEnum.SENT.getCode().equals(status)) {
+                    ErpOrderQuery erpOrder = convertToErpOrderQueryLive(order);
+                    erpOrders.add(erpOrder);
+                }
+                // 如果是 split 状态,收集起来后续处理
+                else if ("Split".equals(status)) {
+                    splitOrders.add(liveOrder);
+                }
+                // 其他状态,收集起来最后统一批量更新
+                else {
+                    Map<String, Object> updateMap = new HashMap<>();
+                    updateMap.put("orderId", liveOrder.getOrderId());
+                    updateMap.put("channel", status);
+                    channelUpdateList.add(updateMap);
+                    log.info("订单状态待写入channel字段,orderCode: {}, status: {}", liveOrder.getOrderCode(), status);
+                }
+            }
+
+            // 处理 split 状态的订单,按照 getLiveOrder 的逻辑处理
+            if (!splitOrders.isEmpty()) {
+                // 对每个拆分订单,使用 soId 重新查询聚水潭,获取所有相关订单
+                for (LiveOrder splitLiveOrder : splitOrders) {
+                    try {
+                        // 使用订单号(soId)重新查询聚水潭,获取所有相关订单
+                        OrderQueryRequestDTO splitRequestDTO = new OrderQueryRequestDTO();
+                        splitRequestDTO.setSoIds(Collections.singletonList(splitLiveOrder.getOrderCode()));
+                        splitRequestDTO.setOrderItemFlds(Arrays.asList("status"));
+
+                        // 重新查询聚水潭
+                        OrderQueryResponseDTO splitQuery = jstErpHttpService.query(splitRequestDTO);
+
+                        if (splitQuery != null && splitQuery.getOrders() != null && !splitQuery.getOrders().isEmpty()) {
+                            // 查找状态不为拆分并且已发货的订单
+                            OrderQueryResponseDTO.Order sentOrder = splitQuery.getOrders().stream()
+                                    .filter(order -> !"Split".equals(order.getStatus()) 
+                                            && ErpQueryOrderStatusEnum.SENT.getCode().equals(order.getStatus()))
+                                    .findFirst()
+                                    .orElse(null);
+
+                            if (sentOrder != null) {
+                                // 找到符合条件的订单,同步物流状态并转换
+                                // 同步物流状态 - 更新订单的物流信息
+                                LiveOrder updateOrder = new LiveOrder();
+                                updateOrder.setOrderId(splitLiveOrder.getOrderId());
+                                if (StringUtils.isNotEmpty(sentOrder.getLogisticsCompany())) {
+                                    updateOrder.setDeliveryName(sentOrder.getLogisticsCompany());
+                                }
+                                if (StringUtils.isNotEmpty(sentOrder.getLId())) {
+                                    updateOrder.setDeliverySn(sentOrder.getLId());
+                                }
+                                if (StringUtils.isNotEmpty(sentOrder.getLcId())) {
+                                    updateOrder.setDeliveryCode(sentOrder.getLcId());
+                                }
+                                if (StringUtils.isNotEmpty(sentOrder.getSendDate())) {
+                                    try {
+                                        SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+                                        Date sendDate = formatter.parse(sentOrder.getSendDate());
+                                        updateOrder.setDeliverySendTime(sendDate);
+                                        updateOrder.setDeliveryTime(sentOrder.getSendDate());
+                                    } catch (Exception e) {
+                                        log.error("解析发货时间失败: {}", sentOrder.getSendDate(), e);
+                                    }
+                                }
+                                liveOrderMapper.updateLiveOrder(updateOrder);
+                                log.info("拆分订单查询:找到已发货订单并同步物流状态,orderCode: {}, status: {}, logisticsCompany: {}, deliverySn: {}", 
+                                        sentOrder.getSoId(), sentOrder.getStatus(), sentOrder.getLogisticsCompany(), sentOrder.getLId());
+
+                                // 转换并添加到结果中
+                                ErpOrderQuery erpOrder = convertToErpOrderQueryLive(sentOrder);
+                                erpOrders.add(erpOrder);
+                            } else {
+                                // 如果没有找到已发货的订单,记录日志
+                                log.warn("拆分订单查询:未找到已发货的非拆分订单,orderCode: {}", splitLiveOrder.getOrderCode());
+                            }
+                        } else {
+                            // 重新查询失败,记录日志
+                            log.error("拆分订单查询:重新查询聚水潭失败,orderCode: {}", splitLiveOrder.getOrderCode());
+                        }
+                    } catch (Exception e) {
+                        log.error("处理拆分订单异常,orderCode: {}", splitLiveOrder.getOrderCode(), e);
+                    }
+                }
+            }
+
+            response.setOrders(erpOrders);
+        } else {
+            response.setOrders(Collections.emptyList());
+        }
+
+        // 统一批量更新其他状态的订单channel字段
+        if (!channelUpdateList.isEmpty()) {
+            batchUpdateChannel(channelUpdateList);
+        }
+
+        response.setSuccess(true);
+        return response;
+    }
+
+    /**
+     * 批量更新订单channel字段
+     * @param channelUpdateList 订单ID和channel的映射列表
+     */
+    private void batchUpdateChannel(List<Map<String, Object>> channelUpdateList) {
+        try {
+            liveOrderMapper.batchUpdateChannelByOrderIds(channelUpdateList);
+            log.info("批量更新订单channel字段完成,共更新{}条记录", channelUpdateList.size());
+        } catch (Exception e) {
+            log.error("批量更新订单channel字段失败", e);
+            // 如果批量更新失败,可以降级为单个更新
+            for (Map<String, Object> updateMap : channelUpdateList) {
+                try {
+                    LiveOrder updateOrder = new LiveOrder();
+                    updateOrder.setOrderId((Long) updateMap.get("orderId"));
+                    updateOrder.setChannel((String) updateMap.get("channel"));
+                    liveOrderMapper.updateLiveOrder(updateOrder);
+                } catch (Exception ex) {
+                    log.error("单个更新订单channel字段失败,orderId: {}, channel: {}", 
+                            updateMap.get("orderId"), updateMap.get("channel"), ex);
+                }
+            }
+        }
+    }
 }
 

+ 9 - 0
fs-service/src/main/java/com/fs/erp/service/impl/K9OrderScrmServiceImpl.java

@@ -36,6 +36,7 @@ import java.math.BigDecimal;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 import java.util.stream.Collectors;
 
@@ -636,4 +637,12 @@ public class K9OrderScrmServiceImpl implements IErpOrderService {
     public void getOrderLiveDeliveryStatus(LiveOrder order) {
 
     }
+
+    @Override
+    public ErpOrderQueryResponse batchGetLiveOrder(List<LiveOrder> orderList) {
+        ErpOrderQueryResponse response = new ErpOrderQueryResponse();
+        response.setOrders(Collections.emptyList());
+        response.setSuccess(true);
+        return response;
+    }
 }

+ 8 - 0
fs-service/src/main/java/com/fs/erp/service/impl/WdtErpOrderServiceImpl.java

@@ -1309,6 +1309,14 @@ public class WdtErpOrderServiceImpl implements IErpOrderService {
 
     }
 
+    @Override
+    public ErpOrderQueryResponse batchGetLiveOrder(List<LiveOrder> orderList) {
+        ErpOrderQueryResponse response = new ErpOrderQueryResponse();
+        response.setOrders(Collections.emptyList());
+        response.setSuccess(true);
+        return response;
+    }
+
     public static String convertToSnakeCase(Object obj) {
         SerializeConfig config = new SerializeConfig();
         config.propertyNamingStrategy = PropertyNamingStrategy.SnakeCase;

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

@@ -43,6 +43,10 @@ public class FsUser extends BaseEntity
     /** 手机号码 */
     @Excel(name = "手机号码")
     private String phone;
+
+    /** 不加密手机号 */
+    private String phoneUnencrypted;
+
     private BigDecimal nowMoney;
     private BigDecimal brokeragePrice;
 

+ 25 - 0
fs-service/src/main/java/com/fs/his/mapper/FsAiWorkflowMapper.java

@@ -1,6 +1,11 @@
 package com.fs.his.mapper;
 
+import com.fs.company.domain.CompanyUser;
 import com.fs.his.domain.FsAiWorkflow;
+import com.fs.his.domain.FsAiWorkflowNode;
+import com.fs.his.vo.FsAiWorkflowNodeVoiceVo;
+import org.apache.ibatis.annotations.Param;
+
 import java.util.List;
 
 /**
@@ -27,4 +32,24 @@ public interface FsAiWorkflowMapper {
     int deleteFsAiWorkflowById(Long workflowId);
 
     int deleteFsAiWorkflowByIds(Long[] workflowIds);
+
+    int selectFsAiWorkflowCountByCompanyUserId(Long companyUserId);
+
+    int updateWorkflowBindCompanyUser(@Param("workflowId") Long workflowId, @Param("companyUserId") Long companyUserId);
+
+    List<CompanyUser> getBindCompanyUserByWorkflowId(Long workflowId);
+
+    int insertFsAiWorkflowCompanyUser(@Param("workflowId")Long workflowId,@Param("companyUserIds") List<Long> companyUserIds);
+
+    int insertAiWorkflowCompanyUserVoice(@Param("workflowId") Long workflowId, @Param("companyUserIds") List<Long> companyUserIds, @Param("fsAiWorkflowNodes") List<FsAiWorkflowNode> fsAiWorkflowNodes);
+
+    List<String> selectFsAiWorkflowNodeByWorkflowId(@Param("workflowId") Long workflowId);
+
+    List<Long> selectWorkflowCompanyUserByWfId(@Param("workflowId") Long workflowId);
+
+    void deleteAiWorkflowCompanyUserVoice(@Param("workflowId") Long workflowId,@Param("nodeKeyDel") List<String> nodeKeyDel);
+
+    void addAiWorkflowCompanyUserVoice(@Param("workflowId") Long workflowId, @Param("companyUserIds") List<Long> companyUserIds, @Param("nodeKeyAdd") List<String> nodeKeyAdd);
+
+    List<FsAiWorkflowNodeVoiceVo> getMyWorkflowNodes(@Param("companyUserId") Long companyUserId);
 }

+ 3 - 0
fs-service/src/main/java/com/fs/his/mapper/MerchantAppConfigMapper.java

@@ -2,6 +2,8 @@ package com.fs.his.mapper;
 
 import java.util.List;
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.common.annotation.DataSource;
+import com.fs.common.enums.DataSourceType;
 import com.fs.his.domain.MerchantAppConfig;
 
 /**
@@ -30,6 +32,7 @@ public interface MerchantAppConfigMapper extends BaseMapper<MerchantAppConfig>{
      * @param id 商户应用配置主键
      * @return 商户应用配置
      */
+    @DataSource(DataSourceType.SLAVE)
     MerchantAppConfig selectMerchantAppConfigById(Long id);
 
     /**

+ 15 - 0
fs-service/src/main/java/com/fs/his/param/FsAiWorkflowUpdateBindWCParam.java

@@ -0,0 +1,15 @@
+package com.fs.his.param;
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * AI工作流-工作流与销售id绑定关系
+ */
+@Data
+public class FsAiWorkflowUpdateBindWCParam {
+    private static final long serialVersionUID = 1L;
+    private Long workflowId;
+    private List<Long> companyUserIds;
+}

+ 17 - 0
fs-service/src/main/java/com/fs/his/service/IFsAiWorkflowService.java

@@ -1,13 +1,18 @@
 package com.fs.his.service;
 
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.company.domain.CompanyUser;
 import com.fs.his.domain.FsAiWorkflow;
 import com.fs.his.domain.FsAiWorkflowNode;
 import com.fs.his.domain.FsAiWorkflowNodeType;
 import com.fs.his.param.FsAiWorkflowSaveParam;
+import com.fs.his.param.FsAiWorkflowUpdateBindWCParam;
 import com.fs.his.vo.FsAiWorkflowExportVO;
+import com.fs.his.vo.FsAiWorkflowNodeVoiceVo;
 import com.fs.his.vo.FsAiWorkflowVO;
 
 import java.util.List;
+import java.util.Map;
 
 /**
  * AI工作流Service接口
@@ -56,6 +61,8 @@ public interface IFsAiWorkflowService {
      */
     FsAiWorkflowExportVO exportWorkflowJson(Long workflowId);
 
+    List<CompanyUser> listCompanyUser();
+
     /**
      * 根据企业用户ID查询工作流的指定类型节点
      * @param companyUserId 企业用户ID
@@ -72,4 +79,14 @@ public interface IFsAiWorkflowService {
      * @return 1成功 0失败 -1节点不存在 -2无权限
      */
     int updateNodeVoiceUrl(Long nodeId, String voiceUrl, Long companyUserId);
+
+    CompanyUser getCompanyUserById(Long id);
+
+    Boolean checkCompanyUserBeUsed(Long companyUserId);
+
+    AjaxResult updateWorkflowBindCompanyUser(FsAiWorkflowUpdateBindWCParam param);
+
+    AjaxResult getBindCompanyUserByWorkflowId(Long workflowId);
+
+    List<FsAiWorkflowNodeVoiceVo> getMyWorkflowNodes(Long companyUserId);
 }

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

@@ -98,6 +98,8 @@ public interface IFsUserService
      */
     public int deleteFsUserByUserId(Long userId);
 
+    int realDeleteFsUserByUserId(Long userId);
+
     List<FsUserVO> selectFsUserListVO(FsUserParam fsUser);
 
     FsUser selectFsUserByOpenId(String openId);

+ 67 - 0
fs-service/src/main/java/com/fs/his/service/impl/FsAiWorkflowServiceImpl.java

@@ -1,6 +1,10 @@
 package com.fs.his.service.impl;
 
+import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.utils.DateUtils;
+import com.fs.common.utils.spring.SpringUtils;
+import com.fs.company.domain.CompanyUser;
+import com.fs.company.mapper.CompanyUserMapper;
 import com.fs.his.domain.FsAiWorkflow;
 import com.fs.his.domain.FsAiWorkflowEdge;
 import com.fs.his.domain.FsAiWorkflowNode;
@@ -10,8 +14,10 @@ import com.fs.his.mapper.FsAiWorkflowMapper;
 import com.fs.his.mapper.FsAiWorkflowNodeMapper;
 import com.fs.his.mapper.FsAiWorkflowNodeTypeMapper;
 import com.fs.his.param.FsAiWorkflowSaveParam;
+import com.fs.his.param.FsAiWorkflowUpdateBindWCParam;
 import com.fs.his.service.IFsAiWorkflowService;
 import com.fs.his.vo.FsAiWorkflowExportVO;
+import com.fs.his.vo.FsAiWorkflowNodeVoiceVo;
 import com.fs.his.vo.FsAiWorkflowVO;
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -39,6 +45,7 @@ public class FsAiWorkflowServiceImpl implements IFsAiWorkflowService {
     @Autowired
     private FsAiWorkflowNodeTypeMapper fsAiWorkflowNodeTypeMapper;
 
+    private static List<String> aiCallNodeTypes = Arrays.asList("param", "ai_chat","end");
     @Override
     public FsAiWorkflowVO selectFsAiWorkflowById(Long workflowId) {
         FsAiWorkflow workflow = fsAiWorkflowMapper.selectFsAiWorkflowById(workflowId);
@@ -110,6 +117,28 @@ public class FsAiWorkflowServiceImpl implements IFsAiWorkflowService {
             fsAiWorkflowEdgeMapper.batchInsertFsAiWorkflowEdge(edges);
         }
 
+        //更新工作流后,需要删除或新增录音节点对应的id
+
+        List<String> nodeKeyOlds = fsAiWorkflowMapper.selectFsAiWorkflowNodeByWorkflowId(workflowId);
+        List<Long> companyUserIds = fsAiWorkflowMapper.selectWorkflowCompanyUserByWfId(workflowId);
+
+        // 提取新节点的nodeKey
+        List<String> newNodeKeys = nodes.stream()
+                .filter(node -> aiCallNodeTypes.contains(node.getNodeType()))
+                .map(FsAiWorkflowNode::getNodeKey)
+                .collect(Collectors.toList());
+
+        // 找出新增的nodeKey(新节点中有,旧节点中没有)
+        List<String> nodeKeyAdd = newNodeKeys.stream()
+                .filter(key -> !nodeKeyOlds.contains(key))
+                .collect(Collectors.toList());
+
+        // 找出需要删除的nodeKey(旧节点中有,新节点中没有)
+        List<String> nodeKeyDel = nodeKeyOlds.stream()
+                .filter(key -> !newNodeKeys.contains(key))
+                .collect(Collectors.toList());
+        if (!nodeKeyDel.isEmpty())fsAiWorkflowMapper.deleteAiWorkflowCompanyUserVoice(workflowId, nodeKeyDel);
+        if (!nodeKeyAdd.isEmpty())fsAiWorkflowMapper.addAiWorkflowCompanyUserVoice(workflowId,companyUserIds, nodeKeyAdd);
         return workflowId;
     }
 
@@ -244,6 +273,11 @@ public class FsAiWorkflowServiceImpl implements IFsAiWorkflowService {
         return exportVO;
     }
 
+    @Override
+    public List<CompanyUser> listCompanyUser() {
+        return SpringUtils.getBean(CompanyUserMapper.class).selectAllCompanyUserList();
+    }
+
     private String getWorkflowTypeName(Integer workflowType) {
         if (workflowType == null) return "";
         switch (workflowType) {
@@ -299,4 +333,37 @@ public class FsAiWorkflowServiceImpl implements IFsAiWorkflowService {
         updateNode.setVoiceUrl(voiceUrl);
         return fsAiWorkflowNodeMapper.updateFsAiWorkflowNode(updateNode);
     }
+
+    @Override
+    public CompanyUser getCompanyUserById(Long id) {
+        return SpringUtils.getBean(CompanyUserMapper.class).selectCompanyUserById( id);
+    }
+
+    @Override
+    public Boolean checkCompanyUserBeUsed(Long companyUserId) {
+        return fsAiWorkflowMapper.selectFsAiWorkflowCountByCompanyUserId(companyUserId) > 0;
+    }
+
+    @Override
+    public AjaxResult updateWorkflowBindCompanyUser(FsAiWorkflowUpdateBindWCParam param) {
+        if (param == null ||param.getWorkflowId() == null || param.getCompanyUserIds() == null || param.getCompanyUserIds().size() == 0) return AjaxResult.error("传参异常");
+        FsAiWorkflow fsAiWorkflowResult = fsAiWorkflowMapper.selectFsAiWorkflowById(Long.valueOf(param.getWorkflowId().toString()));
+        if (fsAiWorkflowResult == null)return AjaxResult.error("未查询到对应工作流,请刷新后重试");
+        int i = fsAiWorkflowMapper.insertFsAiWorkflowCompanyUser(param.getWorkflowId(),param.getCompanyUserIds());
+        List<FsAiWorkflowNode> fsAiWorkflowNodes = fsAiWorkflowNodeMapper.selectNodesByWorkflowIdAndTypes(param.getWorkflowId(), aiCallNodeTypes);
+        fsAiWorkflowMapper.insertAiWorkflowCompanyUserVoice(param.getWorkflowId(), param.getCompanyUserIds(), fsAiWorkflowNodes);
+        return AjaxResult.success("绑定成功");
+    }
+
+    @Override
+    public AjaxResult getBindCompanyUserByWorkflowId(Long workflowId) {
+        FsAiWorkflow fsAiWorkflow = fsAiWorkflowMapper.selectFsAiWorkflowById(workflowId);
+        if (fsAiWorkflow == null) return AjaxResult.error("工作流不存在");
+        return AjaxResult.success(fsAiWorkflowMapper.getBindCompanyUserByWorkflowId(workflowId));
+    }
+
+    @Override
+    public List<FsAiWorkflowNodeVoiceVo> getMyWorkflowNodes(Long companyUserId) {
+        return fsAiWorkflowMapper.getMyWorkflowNodes(companyUserId);
+    }
 }

+ 7 - 4
fs-service/src/main/java/com/fs/his/service/impl/FsUserAddressServiceImpl.java

@@ -9,6 +9,7 @@ import java.util.Map;
 
 import cn.hutool.http.HttpUtil;
 import com.fs.common.exception.CustomException;
+import com.fs.common.utils.CloudHostUtils;
 import com.fs.common.utils.DateUtils;
 import com.fs.his.config.FsSysConfig;
 import com.fs.his.param.FsStoreOrderParam;
@@ -84,10 +85,12 @@ public class FsUserAddressServiceImpl implements IFsUserAddressService
     public int updateFsUserAddress(FsUserAddress fsUserAddress)
     {
         fsUserAddress.setUpdateTime(DateUtils.getNowDate());
-        if (fsUserAddress.getPhone()!=null&&fsUserAddress.getPhone().length()==11&&fsUserAddress.getPhone().matches("\\d+")){
-            fsUserAddress.setPhone(encryptPhone(fsUserAddress.getPhone()));
-        }else {
-            fsUserAddress.setPhone(null);
+        if(!CloudHostUtils.hasCloudHostName("广州郑多燕")) {
+            if (fsUserAddress.getPhone() != null && fsUserAddress.getPhone().length() == 11 && fsUserAddress.getPhone().matches("\\d+")) {
+                fsUserAddress.setPhone(encryptPhone(fsUserAddress.getPhone()));
+            } else {
+                fsUserAddress.setPhone(null);
+            }
         }
         return fsUserAddressMapper.updateFsUserAddress(fsUserAddress);
     }

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

@@ -286,6 +286,11 @@ public class FsUserServiceImpl implements IFsUserService {
         return fsUserMapper.updateFsUserByUserId(userId);
     }
 
+    @Override
+    public int realDeleteFsUserByUserId(Long userId) {
+        return fsUserMapper.deleteFsUserByUserId(userId);
+    }
+
     /**
      * 列表查询
      * @param fsUser

+ 5 - 0
fs-service/src/main/java/com/fs/his/vo/FsAiWorkflowExportVO.java

@@ -67,6 +67,11 @@ public class FsAiWorkflowExportVO implements Serializable {
 
         /** 排序 */
         private Integer sortOrder;
+
+        /**
+         * 语音URL
+         */
+        private String voiceUrl;
     }
 
     /** 连接信息 */

+ 34 - 0
fs-service/src/main/java/com/fs/his/vo/FsAiWorkflowNodeVoiceVo.java

@@ -0,0 +1,34 @@
+package com.fs.his.vo;
+
+import com.fs.common.annotation.Excel;
+import lombok.Data;
+
+/**
+ *
+ */
+@Data
+public class FsAiWorkflowNodeVoiceVo {
+    /** 节点ID */
+    private Long nodeId;
+    /** 销售用户ID */
+    private Long companyUserId;
+
+    /** 工作流ID */
+    private Long workflowId;
+    /** 工作流名称 */
+    private String workflowName;
+
+    /** 节点唯一标识 */
+    private String nodeKey;
+
+    /** 节点名称 */
+    private String nodeName;
+
+    /** 节点类型 start/end/condition/action/ai/delay/http */
+    private String nodeType;
+
+    /**
+     * 语音URL
+     */
+    private String voiceUrl;
+}

+ 3 - 2
fs-service/src/main/java/com/fs/his/vo/FsIntegralOrderListVO.java

@@ -34,9 +34,10 @@ public class FsIntegralOrderListVO {
     private String userAddress;
 
     /** 商品信息 */
-    @Excel(name = "商品信息")
+    //@Excel(name = "商品信息")
     private String itemJson;
-    @Excel(name = "商品名称")
+    //@Excel(name = "商品名称")
+    @Excel(name = "商品信息")//卓美 导出时候把商品名称 数据拼接在一起
     private String goodsName;
 
     /** 商品条码 */

+ 2 - 2
fs-service/src/main/java/com/fs/hisStore/domain/FsStoreOrderItemScrm.java

@@ -58,6 +58,6 @@ public class FsStoreOrderItemScrm extends BaseEntity
     private Integer isGift;
 
     private Integer isDrug;
-
-
+    /** 修改后的总数量 */
+    private Integer newNum;
 }

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

@@ -360,4 +360,7 @@ public class FsStoreOrderScrm extends BaseEntity
     // 订单总后台备注
     private String orderRemark;
 
+    // 后台修改商品类型,0-未修改过;1-总后台;2-销售后台
+    private Integer backendEditProductType;
+
 }

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

@@ -347,4 +347,6 @@ public class FsStoreProductScrm extends BaseEntity
     @Excel(name = "限购数量")
     private Integer purchaseLimit;
 
+    /** 过滤商品id */
+    private Long[] excludeProductIds;
 }

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

@@ -1,6 +1,9 @@
 package com.fs.hisStore.mapper;
 
 import java.util.List;
+
+import com.fs.common.annotation.DataSource;
+import com.fs.common.enums.DataSourceType;
 import com.fs.hisStore.domain.FsShippingTemplatesRegionScrm;
 import org.apache.ibatis.annotations.Delete;
 import org.apache.ibatis.annotations.Param;
@@ -65,5 +68,6 @@ public interface FsShippingTemplatesRegionScrmMapper
     @Delete("delete from fs_shipping_templates_region where temp_id=#{tempId}")
     int deleteFsShippingTemplatesRegionByTempId(Long tempId);
     @Select("select * from fs_shipping_templates_region where find_in_set(temp_id,#{tempIds}) and find_in_set(city_id,#{cityIds})")
+    @DataSource(DataSourceType.SLAVE)
     List<FsShippingTemplatesRegionScrm> selectFsShippingTemplatesRegionListByTempIdsAndCityIds(@Param("tempIds") String tempIds, @Param("cityIds") String cityIds);
 }

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

@@ -1,6 +1,9 @@
 package com.fs.hisStore.mapper;
 
 import java.util.List;
+
+import com.fs.common.annotation.DataSource;
+import com.fs.common.enums.DataSourceType;
 import com.fs.hisStore.domain.FsShippingTemplatesScrm;
 import org.apache.ibatis.annotations.Select;
 
@@ -59,6 +62,7 @@ public interface FsShippingTemplatesScrmMapper
      * @return 结果
      */
     public int deleteFsShippingTemplatesByIds(Long[] id);
+    @DataSource(DataSourceType.SLAVE)
     @Select("select * from fs_shipping_templates where find_in_set(id,#{ids})")
     List<FsShippingTemplatesScrm> selectFsShippingTemplatesByIds(String ids);
 }

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

@@ -1,6 +1,9 @@
 package com.fs.hisStore.mapper;
 
 import java.util.List;
+
+import com.fs.common.annotation.DataSource;
+import com.fs.common.enums.DataSourceType;
 import com.fs.hisStore.domain.FsStoreCouponUserScrm;
 import com.fs.hisStore.param.FsCouponUserEnableParam;
 import com.fs.hisStore.vo.FsStoreCouponUserVO;
@@ -22,6 +25,7 @@ public interface FsStoreCouponUserScrmMapper
      * @param id 优惠券发放记录ID
      * @return 优惠券发放记录
      */
+    @DataSource(DataSourceType.SLAVE)
     public FsStoreCouponUserScrm selectFsStoreCouponUserById(Long id);
 
     /**

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

@@ -999,7 +999,7 @@ public interface FsStoreOrderScrmMapper
     @Select("select id from fs_store_order_scrm where status = 2  and DATE(pay_time)>='2023-11-1' and DATE(create_time)<='2024-2-7' and company_id is null ")
     List<Long> selectSyncExpressIds();
 
-    @Select("select id from fs_store_order_scrm where status = 2  and DATE(pay_time)>='2023-11-1' and company_id is null ")
+    @Select("select id from fs_store_order_scrm where status = 2  and DATE(pay_time)>='2025-11-1' and delivery_id is not null and delivery_sn is not null")
     List<Long> selectSyncExpressIdsNoDate();
 
 
@@ -1088,6 +1088,13 @@ public interface FsStoreOrderScrmMapper
      * **/
     List<OrderStatisticsVo> selectOrderSaleStatisticsList(@Param("param") OrderStatisticsParam param);
 
+    /**
+     * 郑多燕订单销售维度分页查询接口
+     * @param param 查询条件
+     * @return List<OrderStatisticsVo>
+     * **/
+    List<OrderStatisticsVo> selectZDYOrderSaleStatisticsList(@Param("param") OrderStatisticsParam param);
+
     List<FsStoreOrderCountsVO> selectFsStoreOrderCountsByDept(Map<String, Object> map);
 
     List<FsStoreOrderScrm> computePayMoney(@Param("customerIds")List<Long> customerIds);

+ 5 - 0
fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreProductAttrValueScrmMapper.java

@@ -2,6 +2,8 @@ package com.fs.hisStore.mapper;
 
 import java.util.List;
 
+import com.fs.common.annotation.DataSource;
+import com.fs.common.enums.DataSourceType;
 import com.fs.hisStore.config.MedicalMallConfig;
 import com.fs.hisStore.domain.FsStoreProductAttrValueScrm;
 import com.fs.hisStore.param.FsProductAttrValueParam;
@@ -29,6 +31,7 @@ public interface FsStoreProductAttrValueScrmMapper
      * @param id 商品属性值ID
      * @return 商品属性值
      */
+    @DataSource(DataSourceType.SLAVE)
         public FsStoreProductAttrValueScrm selectFsStoreProductAttrValueById(Long id);
 
     /**
@@ -73,6 +76,7 @@ public interface FsStoreProductAttrValueScrmMapper
     @Delete("delete from fs_store_product_attr_value_scrm where product_id=#{productId}")
     int deleteFsStoreProductAttrValueByProductId(Long productId);
     @Select("select * from fs_store_product_attr_value_scrm where  product_id=#{productId}")
+    @DataSource(DataSourceType.SLAVE)
     List<FsStoreProductAttrValueScrm> selectFsStoreProductAttrValueByProductId(Long productId);
     @Select("select ifnull(stock,0) from fs_store_product_attr_value_scrm where  id=#{productAttrValueId}")
     int selectFsStoreProductStockById(Long productAttrValueId);
@@ -81,6 +85,7 @@ public interface FsStoreProductAttrValueScrmMapper
      * 使用行锁查询规格库存
      */
     @Select("select ifnull(stock,0) from fs_store_product_attr_value_scrm where id=#{productAttrValueId} for update")
+    @DataSource(DataSourceType.SLAVE)
     Integer selectProductAttrStockForUpdate(@Param("productAttrValueId") Long productAttrValueId);
 
     @Update("update fs_store_product_attr_value_scrm set stock=stock-#{num},sales=sales+#{num}" +

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

@@ -1,6 +1,9 @@
 package com.fs.hisStore.mapper;
 
 import java.util.List;
+
+import com.fs.common.annotation.DataSource;
+import com.fs.common.enums.DataSourceType;
 import com.fs.hisStore.domain.FsStoreProductPurchaseLimitScrm;
 import org.apache.ibatis.annotations.Param;
 
@@ -35,6 +38,7 @@ public interface FsStoreProductPurchaseLimitScrmMapper
      * @param userId 用户ID
      * @return 商品限购
      */
+    @DataSource(DataSourceType.SLAVE)
     public FsStoreProductPurchaseLimitScrm selectByProductIdAndUserId(@Param("productId") Long productId, @Param("userId") Long userId);
 
     /**

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

@@ -3,6 +3,8 @@ package com.fs.hisStore.mapper;
 import java.util.List;
 import java.util.Map;
 
+import com.fs.common.annotation.DataSource;
+import com.fs.common.enums.DataSourceType;
 import com.fs.common.param.BaseQueryParam;
 import com.fs.his.domain.FsStoreProduct;
 import com.fs.his.param.FsStoreProductListSParam;
@@ -34,6 +36,7 @@ public interface FsStoreProductScrmMapper
      * @param productId 商品ID
      * @return 商品
      */
+    @DataSource(DataSourceType.SLAVE)
     public FsStoreProductScrm selectFsStoreProductById(Long productId);
 
     /**
@@ -146,6 +149,11 @@ public interface FsStoreProductScrmMapper
             " <if test='maps.precautions != null and maps.precautions != \"\"'>" +
             "     AND p.precautions LIKE CONCAT('%', #{maps.precautions}, '%')" +
             " </if>"+
+            "<if test = 'maps.excludeProductIds != null '>" +
+            "and p.product_id not in " +
+            "<foreach collection='maps.excludeProductIds'  item='item' index='index'  open='(' separator=',' close=')'> " +
+            " #{item} </foreach>" +
+            "</if>" +
             " order by p.product_id desc "+
             "</script>"})
     List<FsStoreProductListVO> selectFsStoreProductListVO(@Param("maps") FsStoreProductScrm fsStoreProduct);
@@ -173,6 +181,11 @@ public interface FsStoreProductScrmMapper
             "<if test = 'maps.isShow != null    '> " +
             "and p.is_show =#{maps.isShow} " +
             "</if>" +
+            "<if test = 'maps.excludeProductIds != null '>" +
+            "and p.product_id not in " +
+            "<foreach collection='maps.excludeProductIds'  item='item' index='index'  open='(' separator=',' close=')'> " +
+            " #{item} </foreach>" +
+            "</if>" +
             " order by p.product_id desc "+
             "</script>"})
     List<FsStoreProductListVO> selectFsStoreProductBarCodeListVO(@Param("maps") FsStoreProductScrm fsStoreProduct);
@@ -255,11 +268,12 @@ public interface FsStoreProductScrmMapper
     @Update("update fs_store_product_scrm set stock=stock-#{num}, sales=sales+#{num}" +
             " where product_id=#{productId} and stock >= #{num}")
     int decProductAttrStock(@Param("productId")Long productId, @Param("num")Integer cartNum);
-    
+
     /**
      * 使用行锁查询商品库存
      */
     @Select("select stock from fs_store_product_scrm where product_id=#{productId} for update")
+    @DataSource(DataSourceType.SLAVE)
     Integer selectProductStockForUpdate(@Param("productId") Long productId);
     @Update("update fs_store_product_scrm set stock=stock+#{num}, sales=sales-#{num}" +
             " where product_id=#{productId}")

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

@@ -2,6 +2,8 @@ package com.fs.hisStore.mapper;
 
 import java.util.List;
 
+import com.fs.common.annotation.DataSource;
+import com.fs.common.enums.DataSourceType;
 import com.fs.his.domain.FsUserAddress;
 import com.fs.hisStore.domain.FsUserAddressScrm;
 import org.apache.ibatis.annotations.Select;
@@ -72,5 +74,6 @@ public interface FsUserAddressScrmMapper
     Integer delAllAddress(Long userId);
 
     @Select("select * from fs_user_address where user_id=#{uid} and is_default=1 and is_del=0 limit 1")
+    @DataSource(DataSourceType.SLAVE)
     FsUserAddress selectFsUserAddressByDefault(long userId);
 }

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

@@ -1,5 +1,7 @@
 package com.fs.hisStore.mapper;
 
+import com.fs.common.annotation.DataSource;
+import com.fs.common.enums.DataSourceType;
 import com.fs.course.vo.newfs.FsCourseAnalysisCountVO;
 import com.fs.his.domain.FsUser;
 import com.fs.his.vo.OptionsVO;
@@ -41,6 +43,7 @@ public interface FsUserScrmMapper
      * @param userId 用户ID
      * @return 用户
      */
+    @DataSource(DataSourceType.SLAVE)
     public FsUserScrm selectFsUserById(Long userId);
 
     /**

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

@@ -121,4 +121,7 @@ public class FsStoreOrderParam extends BaseEntity implements Serializable
 
     // 是否审核,1-是,0-否
     private Integer isAudit;
+
+    // 多个销售id
+    private List<Long> companyUserIds;
 }

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

@@ -79,4 +79,6 @@ public interface IFsStoreOrderItemScrmService
     String selectFsStoreOrderItemByOrderId(Long orderId);
 
     Long selectProductIdByOrderCode(String orderCode);
+
+    int updateFsStoreOrderItemNum(FsStoreOrderItemScrm fsStoreOrderItem);
 }

+ 9 - 1
fs-service/src/main/java/com/fs/hisStore/service/IFsStoreOrderScrmService.java

@@ -12,7 +12,6 @@ import com.fs.common.core.domain.R;
 import com.fs.company.domain.CompanyUser;
 import com.fs.course.dto.FsOrderDeliveryNoteDTO;
 import com.fs.erp.domain.ErpOrder;
-import com.fs.his.domain.FsStorePayment;
 import com.fs.his.dto.FsStoreOrderAmountScrmStatsQueryDto;
 import com.fs.his.param.FsStoreOrderSalesParam;
 import com.fs.his.vo.FsStoreOrderAmountScrmStatsVo;
@@ -251,6 +250,8 @@ public interface IFsStoreOrderScrmService
      * **/
     List<OrderStatisticsVo> selectOrderDimensionStatisticsList(OrderStatisticsParam param);
 
+    List<OrderStatisticsVo> selectZDYOrderSaleStatisticsList(OrderStatisticsParam param);
+
     List<FsStoreOrderCountsVO> selectFsStoreOrderCountsByDept(Map<String, Object> map,Long deptId);
 
     R updateSalesOrderMoneyByProduct(FsStoreOrderMoneyByProductParam param);
@@ -357,4 +358,11 @@ public interface IFsStoreOrderScrmService
      * @return 更新条数
      */
     int batchAuditOrder(FsStoreOrderBatchAuditParam param);
+
+    /**
+     * 订单备注
+     */
+    R orderRemark(FsStoreOrderScrm orderScrm);
+
+    int updateStoreOrderItemJson(Long orderId,Integer backendEditProductType);
 }

+ 16 - 0
fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreOrderItemScrmServiceImpl.java

@@ -3,7 +3,10 @@ package com.fs.hisStore.service.impl;
 import java.util.Collections;
 import java.util.List;
 
+import cn.hutool.json.JSONUtil;
+import com.alibaba.fastjson.JSONObject;
 import com.fs.his.vo.FsStoreOrderItemListDVO;
+import com.fs.hisStore.dto.FsStoreCartDTO;
 import com.fs.hisStore.param.FsStoreOrderParam;
 import com.fs.hisStore.vo.FsStoreOrderItemExportVO;
 import com.fs.hisStore.vo.FsStoreOrderItemVO;
@@ -141,4 +144,17 @@ public class FsStoreOrderItemScrmServiceImpl implements IFsStoreOrderItemScrmSer
     public Long selectProductIdByOrderCode(String orderCode) {
         return fsStoreOrderItemMapper.selectProductIdByOrderCode(orderCode);
     }
+
+    @Override
+    public int updateFsStoreOrderItemNum(FsStoreOrderItemScrm fsStoreOrderItem) {
+        FsStoreOrderItemScrm databaseItem = fsStoreOrderItemMapper.selectFsStoreOrderItemById(fsStoreOrderItem.getItemId());
+        if(databaseItem != null){
+            fsStoreOrderItem.setNum(fsStoreOrderItem.getNewNum());
+            FsStoreCartDTO fsStoreCartDTO = JSONObject.parseObject(databaseItem.getJsonInfo(), FsStoreCartDTO.class);
+            fsStoreCartDTO.setNum(fsStoreOrderItem.getNewNum());
+            fsStoreOrderItem.setJsonInfo(JSONUtil.toJsonStr(fsStoreCartDTO));
+            return fsStoreOrderItemMapper.updateFsStoreOrderItem(fsStoreOrderItem);
+        }
+        return 0;
+    }
 }

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

@@ -3,6 +3,7 @@ package com.fs.hisStore.service.impl;
 import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
 import cn.binarywang.wx.miniapp.bean.shop.request.shipping.*;
 import cn.binarywang.wx.miniapp.bean.shop.response.WxMaOrderShippingInfoGetResponse;
+import cn.hutool.core.collection.CollectionUtil;
 import cn.hutool.core.date.DateTime;
 import cn.binarywang.wx.miniapp.api.WxMaService;
 import cn.hutool.core.net.URLDecoder;
@@ -2747,6 +2748,9 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
             }
         }
         ExpressInfoDTO dto = expressService.getExpressInfo(order.getOrderCode(), order.getDeliverySn(), order.getDeliveryId(), lastFourNumber);
+        if (dto == null || dto.getState() == null || dto.getStateEx() == null) {
+            return null;
+        }
         FsStoreOrderScrm map = new FsStoreOrderScrm();
         map.setDeliveryStatus(Integer.parseInt(dto.getState()));
         map.setId(order.getId());
@@ -3618,6 +3622,11 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
 //        }
     }
 
+    @Override
+    public List<OrderStatisticsVo> selectZDYOrderSaleStatisticsList(OrderStatisticsParam param) {
+        return fsStoreOrderMapper.selectZDYOrderSaleStatisticsList(param);
+    }
+
     @Override
     public List<FsStoreOrderCountsVO> selectFsStoreOrderCountsByDept(Map<String, Object> map, Long deptId) {
         List<FsStoreOrderCountsVO> vos = fsStoreOrderMapper.selectFsStoreOrderCountsByDept(map);
@@ -5583,6 +5592,37 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
         return fsStoreOrderMapper.batchUpdateAuditStatus(param.getOrderIds(), param.getIsAudit());
     }
 
+    @Override
+    public R orderRemark(FsStoreOrderScrm orderScrm) {
+        FsStoreOrderScrm order = fsStoreOrderMapper.selectFsStoreOrderById(orderScrm.getId());
+        if (order != null) {
+            FsStoreOrderScrm map = new FsStoreOrderScrm();
+            map.setId(orderScrm.getId());
+            map.setOrderRemark(orderScrm.getOrderRemark());
+
+            if (fsStoreOrderMapper.updateFsStoreOrder(map) > 0) {
+                return R.ok();
+            } else {
+                return R.error("备注失败");
+            }
+        }
+        return R.error("未找到订单");
+    }
+
+    @Override
+    public int updateStoreOrderItemJson(Long orderId,Integer backendEditProductType) {
+        FsStoreOrderScrm order = new FsStoreOrderScrm();
+        order.setId(orderId);
+        List<FsStoreOrderItemVO> orderItemVOS = fsStoreOrderItemMapper.selectFsStoreOrderItemListByOrderId(orderId);
+        if(CollectionUtil.isNotEmpty(orderItemVOS)){
+            order.setItemJson(JSON.toJSONString(orderItemVOS));
+        }else{
+            order.setItemJson("");
+        }
+        order.setBackendEditProductType(backendEditProductType);
+        return fsStoreOrderMapper.updateFsStoreOrder(order);
+    }
+
     private static final DateTimeFormatter CST_FORMATTER = DateTimeFormatter
             .ofPattern("EEE MMM dd HH:mm:ss zzz yyyy", Locale.US)
             .withZone(ZoneId.of("Asia/Shanghai"));

+ 32 - 28
fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreProductPurchaseLimitScrmServiceImpl.java

@@ -8,6 +8,7 @@ import java.util.concurrent.TimeUnit;
 
 import com.fs.common.constant.LiveKeysConstant;
 import com.fs.common.core.redis.RedisCache;
+import com.fs.common.core.redis.RedisCacheT;
 import com.fs.common.utils.DateUtils;
 import com.fs.hisStore.domain.FsStoreProductAttrScrm;
 import com.fs.hisStore.domain.FsStoreProductAttrValueScrm;
@@ -28,14 +29,15 @@ import org.springframework.stereotype.Service;
  * @date 2024-01-01
  */
 @Service
-public class FsStoreProductPurchaseLimitScrmServiceImpl implements IFsStoreProductPurchaseLimitScrmService
-{
+public class FsStoreProductPurchaseLimitScrmServiceImpl implements IFsStoreProductPurchaseLimitScrmService {
     @Autowired
     private FsStoreProductPurchaseLimitScrmMapper fsStoreProductPurchaseLimitMapper;
 
     @Autowired
     private RedisCache redisCache;
     @Autowired
+    private RedisCacheT<FsStoreProductPurchaseLimitScrm> redisCacheT;
+    @Autowired
     private FsStoreProductScrmMapper fsStoreProductScrmMapper;
     @Autowired
     private FsStoreProductAttrScrmMapper fsStoreProductAttrScrmMapper;
@@ -50,8 +52,7 @@ public class FsStoreProductPurchaseLimitScrmServiceImpl implements IFsStoreProdu
      * @return 商品限购
      */
     @Override
-    public FsStoreProductPurchaseLimitScrm selectFsStoreProductPurchaseLimitById(Long id)
-    {
+    public FsStoreProductPurchaseLimitScrm selectFsStoreProductPurchaseLimitById(Long id) {
         return fsStoreProductPurchaseLimitMapper.selectFsStoreProductPurchaseLimitById(id);
     }
 
@@ -62,8 +63,7 @@ public class FsStoreProductPurchaseLimitScrmServiceImpl implements IFsStoreProdu
      * @return 商品限购
      */
     @Override
-    public List<FsStoreProductPurchaseLimitScrm> selectFsStoreProductPurchaseLimitList(FsStoreProductPurchaseLimitScrm fsStoreProductPurchaseLimit)
-    {
+    public List<FsStoreProductPurchaseLimitScrm> selectFsStoreProductPurchaseLimitList(FsStoreProductPurchaseLimitScrm fsStoreProductPurchaseLimit) {
         return fsStoreProductPurchaseLimitMapper.selectFsStoreProductPurchaseLimitList(fsStoreProductPurchaseLimit);
     }
 
@@ -71,13 +71,19 @@ public class FsStoreProductPurchaseLimitScrmServiceImpl implements IFsStoreProdu
      * 根据商品ID和用户ID查询限购记录
      *
      * @param productId 商品ID
-     * @param userId 用户ID
+     * @param userId    用户ID
      * @return 商品限购
      */
     @Override
-    public FsStoreProductPurchaseLimitScrm selectByProductIdAndUserId(Long productId, Long userId)
-    {
-        return fsStoreProductPurchaseLimitMapper.selectByProductIdAndUserId(productId, userId);
+    public FsStoreProductPurchaseLimitScrm selectByProductIdAndUserId(Long productId, Long userId) {
+        String key = "live:order:limit:" + productId + ":" + userId;
+        FsStoreProductPurchaseLimitScrm scrm = redisCacheT.getCacheObject(key);
+        if(scrm != null){
+            return scrm;
+        }
+        scrm = fsStoreProductPurchaseLimitMapper.selectByProductIdAndUserId(productId, userId);
+        redisCacheT.setCacheObject(key, scrm);
+        return scrm;
     }
 
     /**
@@ -87,8 +93,7 @@ public class FsStoreProductPurchaseLimitScrmServiceImpl implements IFsStoreProdu
      * @return 结果
      */
     @Override
-    public int insertFsStoreProductPurchaseLimit(FsStoreProductPurchaseLimitScrm fsStoreProductPurchaseLimit)
-    {
+    public int insertFsStoreProductPurchaseLimit(FsStoreProductPurchaseLimitScrm fsStoreProductPurchaseLimit) {
         fsStoreProductPurchaseLimit.setCreateTime(DateUtils.getNowDate());
         return fsStoreProductPurchaseLimitMapper.insertFsStoreProductPurchaseLimit(fsStoreProductPurchaseLimit);
     }
@@ -100,8 +105,7 @@ public class FsStoreProductPurchaseLimitScrmServiceImpl implements IFsStoreProdu
      * @return 结果
      */
     @Override
-    public int updateFsStoreProductPurchaseLimit(FsStoreProductPurchaseLimitScrm fsStoreProductPurchaseLimit)
-    {
+    public int updateFsStoreProductPurchaseLimit(FsStoreProductPurchaseLimitScrm fsStoreProductPurchaseLimit) {
         fsStoreProductPurchaseLimit.setUpdateTime(DateUtils.getNowDate());
         return fsStoreProductPurchaseLimitMapper.updateFsStoreProductPurchaseLimit(fsStoreProductPurchaseLimit);
     }
@@ -113,8 +117,7 @@ public class FsStoreProductPurchaseLimitScrmServiceImpl implements IFsStoreProdu
      * @return 结果
      */
     @Override
-    public int deleteFsStoreProductPurchaseLimitByIds(Long[] ids)
-    {
+    public int deleteFsStoreProductPurchaseLimitByIds(Long[] ids) {
         return fsStoreProductPurchaseLimitMapper.deleteFsStoreProductPurchaseLimitByIds(ids);
     }
 
@@ -125,8 +128,7 @@ public class FsStoreProductPurchaseLimitScrmServiceImpl implements IFsStoreProdu
      * @return 结果
      */
     @Override
-    public int deleteFsStoreProductPurchaseLimitById(Long id)
-    {
+    public int deleteFsStoreProductPurchaseLimitById(Long id) {
         return fsStoreProductPurchaseLimitMapper.deleteFsStoreProductPurchaseLimitById(id);
     }
 
@@ -134,13 +136,12 @@ public class FsStoreProductPurchaseLimitScrmServiceImpl implements IFsStoreProdu
      * 增加用户限购数量
      *
      * @param productId 商品ID
-     * @param userId 用户ID
-     * @param num 增加的数量
+     * @param userId    用户ID
+     * @param num       增加的数量
      * @return 结果
      */
     @Override
-    public int increasePurchaseLimit(Long productId, Long userId, Integer num)
-    {
+    public int increasePurchaseLimit(Long productId, Long userId, Integer num) {
         String cacheKey = String.format(LiveKeysConstant.PRODUCT_DETAIL_CACHE, productId);
         Map<String, Object> cachedData = redisCache.getCacheObject(cacheKey);
 
@@ -154,7 +155,7 @@ public class FsStoreProductPurchaseLimitScrmServiceImpl implements IFsStoreProdu
         } else {
             // 缓存中没有,从数据库查询
             product = fsStoreProductScrmMapper.selectFsStoreProductById(productId);
-            if(product==null){
+            if (product == null) {
                 return -1;
             }
             productAttr = fsStoreProductAttrScrmMapper.selectFsStoreProductAttrByProductId(productId);
@@ -167,18 +168,22 @@ public class FsStoreProductPurchaseLimitScrmServiceImpl implements IFsStoreProdu
             cacheData.put("productValues", productValues);
             redisCache.setCacheObject(cacheKey, cacheData, LiveKeysConstant.PRODUCT_DETAIL_CACHE_EXPIRE, TimeUnit.SECONDS);
         }
+
         if (product != null && product.getPurchaseLimit() != null && product.getPurchaseLimit() > 0) {
             FsStoreProductPurchaseLimitScrm limit = selectByProductIdAndUserId(productId, userId);
+            String key = "live:order:limit:" + productId + ":" + userId;
             if (limit == null) {
                 // 创建新记录
                 limit = new FsStoreProductPurchaseLimitScrm();
                 limit.setProductId(productId);
                 limit.setUserId(userId);
                 limit.setNum(num);
+                redisCacheT.setCacheObject(key, limit);
                 return insertFsStoreProductPurchaseLimit(limit);
             } else {
                 // 更新现有记录
                 limit.setNum(limit.getNum() + num);
+                redisCacheT.setCacheObject(key, limit);
                 return updateFsStoreProductPurchaseLimit(limit);
             }
         }
@@ -189,13 +194,12 @@ public class FsStoreProductPurchaseLimitScrmServiceImpl implements IFsStoreProdu
      * 减少用户限购数量
      *
      * @param productId 商品ID
-     * @param userId 用户ID
-     * @param num 减少的数量
+     * @param userId    用户ID
+     * @param num       减少的数量
      * @return 结果
      */
     @Override
-    public int decreasePurchaseLimit(Long productId, Long userId, Integer num)
-    {
+    public int decreasePurchaseLimit(Long productId, Long userId, Integer num) {
         String cacheKey = String.format(LiveKeysConstant.PRODUCT_DETAIL_CACHE, productId);
         Map<String, Object> cachedData = redisCache.getCacheObject(cacheKey);
 
@@ -209,7 +213,7 @@ public class FsStoreProductPurchaseLimitScrmServiceImpl implements IFsStoreProdu
         } else {
             // 缓存中没有,从数据库查询
             product = fsStoreProductScrmMapper.selectFsStoreProductById(productId);
-            if(product==null){
+            if (product == null) {
                 return -1;
             }
             productAttr = fsStoreProductAttrScrmMapper.selectFsStoreProductAttrByProductId(productId);

+ 12 - 3
fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreProductScrmServiceImpl.java

@@ -15,6 +15,7 @@ import com.fs.common.BeanCopyUtils;
 import com.fs.common.constant.LiveKeysConstant;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.redis.RedisCache;
+import com.fs.common.core.redis.RedisCacheT;
 import com.fs.common.exception.CustomException;
 import com.fs.common.exception.ServiceException;
 import com.fs.common.param.BaseQueryParam;
@@ -145,6 +146,8 @@ public class FsStoreProductScrmServiceImpl implements IFsStoreProductScrmService
 
     @Autowired
     private RedisCache redisCache;
+    @Autowired
+    private RedisCacheT<FsStoreProductScrm> redisCacheT;
 
     /**
      * 清除商品详情缓存
@@ -177,9 +180,15 @@ public class FsStoreProductScrmServiceImpl implements IFsStoreProductScrmService
      * @return 商品
      */
     @Override
-    public FsStoreProductScrm selectFsStoreProductById(Long productId)
-    {
-        return fsStoreProductMapper.selectFsStoreProductById(productId);
+    public FsStoreProductScrm selectFsStoreProductById(Long productId){
+        String key = "fs:product:id:" + productId;
+        FsStoreProductScrm scrm = redisCacheT.getCacheObject(key);
+        if(scrm != null){
+            return scrm;
+        }
+        scrm = fsStoreProductMapper.selectFsStoreProductById(productId);
+        redisCacheT.setCacheObject(key, scrm);
+        return scrm;
     }
 
     /**

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

@@ -35,7 +35,6 @@ public class FsStoreProductListVO  implements Serializable
 
     /** 产品条码(一维码) */
     private String barCode;
-
     /** 分类id */
     private Long cateId;
 
@@ -116,5 +115,4 @@ public class FsStoreProductListVO  implements Serializable
 
     private String storeId;
     private String storeName;
-
 }

+ 7 - 2
fs-service/src/main/java/com/fs/live/domain/LiveGoods.java

@@ -1,8 +1,12 @@
 package com.fs.live.domain;
 
 
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
 import com.fs.common.annotation.Excel;
 import com.fs.common.core.domain.BaseEntity;
+import com.fs.common.core.domain.BaseEntityTow;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
 
@@ -13,10 +17,10 @@ import lombok.EqualsAndHashCode;
  * @date 2025-07-08
  */
 @Data
-@EqualsAndHashCode(callSuper = true)
 public class LiveGoods extends BaseEntity{
 
     /** ID */
+    @TableId(type = IdType.AUTO)
     private Long goodsId;
 
     /** 直播ID */
@@ -60,8 +64,9 @@ public class LiveGoods extends BaseEntity{
     private Long sort;
 
     /** 商品名称搜索关键字*/
-
+    @TableField(exist = false)
     private String keywords;
+    @TableField(exist = false)
     private String productName;
 
 

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

@@ -1,5 +1,7 @@
 package com.fs.live.mapper;
 
+import com.fs.common.annotation.DataSource;
+import com.fs.common.enums.DataSourceType;
 import com.fs.live.domain.LiveCompletionPointsRecord;
 import org.apache.ibatis.annotations.Param;
 
@@ -36,6 +38,7 @@ public interface LiveCompletionPointsRecordMapper {
     /**
      * 查询用户在某直播间最近一次完课记录(不限制日期)
      */
+    @DataSource(DataSourceType.SLAVE)
     LiveCompletionPointsRecord selectLatestByUserAndLiveId(@Param("liveId") Long liveId, 
                                                             @Param("userId") Long userId);
 

+ 5 - 0
fs-service/src/main/java/com/fs/live/mapper/LiveCouponUserMapper.java

@@ -1,6 +1,9 @@
 package com.fs.live.mapper;
 
 import java.util.List;
+
+import com.fs.common.annotation.DataSource;
+import com.fs.common.enums.DataSourceType;
 import com.fs.live.domain.LiveCouponUser;
 import com.fs.live.param.CouponPO;
 import org.apache.ibatis.annotations.Param;
@@ -20,6 +23,7 @@ public interface LiveCouponUserMapper
      * @param id 优惠券发放记录ID
      * @return 优惠券发放记录
      */
+    @DataSource(DataSourceType.SLAVE)
     public LiveCouponUser selectLiveCouponUserById(Long id);
 
     /**
@@ -71,5 +75,6 @@ public interface LiveCouponUserMapper
             " and (lcu.goods_id= #{coupon.goodsId} or lcu.goods_id is null or lcu.goods_id = 0)" +
             " </if>" +
             "</script>")
+    @DataSource(DataSourceType.SLAVE)
     List<LiveCouponUser> curCoupon(@Param("coupon") CouponPO coupon);
 }

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

@@ -1,6 +1,8 @@
 package com.fs.live.mapper;
 
 
+import com.fs.common.annotation.DataSource;
+import com.fs.common.enums.DataSourceType;
 import com.fs.live.domain.LiveData;
 import com.fs.live.vo.LiveDashBoardDataVo;
 import com.fs.live.vo.LiveDataDetailVo;
@@ -140,6 +142,7 @@ public interface LiveDataMapper {
             "FROM " +
             "    live_data ld " +
             "where ld.live_id=#{liveId}")
+    @DataSource(DataSourceType.SLAVE)
     Map<String, Integer> selectDashboardCount(@Param("liveId") Long liveId);
 
     /**
@@ -147,6 +150,7 @@ public interface LiveDataMapper {
      * @param liveIds 直播间ID列表
      * @return 统计数据
      */
+    @DataSource(DataSourceType.SLAVE)
     LiveDataStatisticsVo selectLiveDataStatistics(@Param("liveIds") List<Long> liveIds);
 
     /**
@@ -154,6 +158,7 @@ public interface LiveDataMapper {
      * @param liveIds 直播间ID列表
      * @return 列表数据
      */
+    @DataSource(DataSourceType.SLAVE)
     List<LiveDataListVo> selectLiveDataListByLiveIds(@Param("liveIds") List<Long> liveIds);
 
     /**
@@ -161,6 +166,7 @@ public interface LiveDataMapper {
      * @param liveId 直播间ID
      * @return 详情数据
      */
+    @DataSource(DataSourceType.SLAVE)
     LiveDataDetailVo selectLiveDataDetailBySql(@Param("liveId") Long liveId);
 
     /**
@@ -168,5 +174,6 @@ public interface LiveDataMapper {
      * @param liveId 直播间ID
      * @return 用户详情列表
      */
+    @DataSource(DataSourceType.SLAVE)
     List<LiveUserDetailVo> selectLiveUserDetailListBySql(@Param("liveId") Long liveId,@Param("companyId") Long companyId,@Param("companyUserId") Long companyUserId);
 }

+ 6 - 1
fs-service/src/main/java/com/fs/live/mapper/LiveGoodsMapper.java

@@ -1,5 +1,8 @@
 package com.fs.live.mapper;
 
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.common.annotation.DataSource;
+import com.fs.common.enums.DataSourceType;
 import com.fs.live.domain.LiveGoods;
 import com.fs.live.domain.LiveOrder;
 import com.fs.live.vo.LiveGoodsListVo;
@@ -17,7 +20,7 @@ import java.util.Map;
  * @author fs
  * @date 2025-07-08
  */
-public interface LiveGoodsMapper {
+public interface LiveGoodsMapper extends BaseMapper<LiveGoods> {
     /**
      * 查询直播商品
      *
@@ -90,6 +93,7 @@ public interface LiveGoodsMapper {
      * @param liveGoods 直播商品
      * @return 商品信息集合
      */
+    @DataSource(DataSourceType.SLAVE)
     List<LiveGoodsVo> selectProductListByLiveId(LiveGoods liveGoods);
 
     /**
@@ -112,6 +116,7 @@ public interface LiveGoodsMapper {
     List<LiveGoodsVo> selectProductListByOrder(LiveOrder liveOrder);
 
     @Select("select * from live_goods where live_id = #{liveId} and product_id = #{productId}")
+    @DataSource(DataSourceType.SLAVE)
     LiveGoods selectLiveGoodsByProductId(@Param("liveId") Long liveId,@Param("productId") Long productId);
 
     /**

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

@@ -1,6 +1,8 @@
 package com.fs.live.mapper;
 
 
+import com.fs.common.annotation.DataSource;
+import com.fs.common.enums.DataSourceType;
 import com.fs.live.domain.Live;
 import com.fs.live.param.LiveDataParam;
 import com.fs.live.vo.LiveListVo;
@@ -24,6 +26,7 @@ public interface LiveMapper
      * @param liveId 直播主键
      * @return 直播
      */
+    @DataSource(DataSourceType.SLAVE)
     public Live selectLiveByLiveId(Long liveId);
 
     /**

+ 8 - 0
fs-service/src/main/java/com/fs/live/mapper/LiveMsgMapper.java

@@ -84,4 +84,12 @@ public interface LiveMsgMapper
     Map<String, BigDecimal> selectDashboardCount(@Param("liveId") Long liveId);
 
     List<LiveMsg> selectLiveMsgSingleList(LiveMsg liveMsg);
+
+    /**
+     * 查询直播评论用于导出
+     *
+     * @param liveId 直播ID
+     * @return 评论列表
+     */
+    List<com.fs.live.vo.LiveMsgExportVO> selectLiveMsgForExport(@Param("liveId") Long liveId);
 }

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

@@ -41,6 +41,7 @@ public interface LiveOrderItemMapper {
      * @return 结果
      */
     int insertLiveOrderItem(LiveOrderItem liveOrderItem);
+    int insertLiveOrderItemTest(LiveOrderItem liveOrderItem);
 
     /**
      * 修改订单详情

+ 14 - 1
fs-service/src/main/java/com/fs/live/mapper/LiveOrderMapper.java

@@ -1,6 +1,8 @@
 package com.fs.live.mapper;
 
 
+import com.fs.common.annotation.DataSource;
+import com.fs.common.enums.DataSourceType;
 import com.fs.hisStore.vo.FsStoreOrderItemExportZMVO;
 import com.fs.live.domain.LiveOrder;
 import com.fs.live.dto.LiveOrderDeliveryNoteDTO;
@@ -41,6 +43,7 @@ public interface LiveOrderMapper {
      * @param liveOrder 订单
      * @return 订单集合
      */
+    @DataSource(DataSourceType.SLAVE)
     List<LiveOrder> selectLiveOrderList(LiveOrder liveOrder);
 
     /**
@@ -50,6 +53,7 @@ public interface LiveOrderMapper {
      * @return 结果
      */
     int insertLiveOrder(LiveOrder liveOrder);
+    int insertLiveOrderTest(LiveOrder liveOrder);
 
     /**
      * 修改订单
@@ -103,14 +107,17 @@ public interface LiveOrderMapper {
             "</where> " +
             "order by create_time desc" +
             "</script>"})
+    @DataSource(DataSourceType.SLAVE)
     List<LiveOrderListVo> selectLiveOrderListVo(@Param("userId") String userId,@Param("status") Integer status);
 
     @Select("select * from live_order where `status` = 3 AND TIMESTAMPDIFF(HOUR, start_time, NOW()) >= 48  ")
     List<LiveOrder> selectLiveOrderByFinish();
 
     @Select("select * from live_order where `status` = 1 and extend_order_id is not null and (delivery_sn is null or delivery_code = '') and refund_status = 0 and is_pay = 1 order by update_time ")
+    @DataSource(DataSourceType.SLAVE)
     List<LiveOrder> selectUpdateExpress();
 
+
     @Select("select order_id from live_order where `status` = 2 and delivery_code is not null and delivery_sn is not null and is_pay = 1")
     List<Long> selectSyncExpressIds();
 
@@ -480,6 +487,12 @@ public interface LiveOrderMapper {
      */
     void batchUpdateInOrderCode(@Param("list") List<LiveOrderDeliveryNoteDTO> dtoList);
 
+    /**
+     * 批量更新订单channel字段
+     * @param maps 订单ID和channel的映射列表,每个map包含orderId和channel
+     */
+    void batchUpdateChannelByOrderIds(@Param("maps") List<Map<String, Object>> maps);
+
     @Select("SELECT * FROM live_order WHERE user_id=#{userId} LIMIT 1")
     LiveOrder selectOrderByUserIdLimit1(@Param("userId") Long userId);
 
@@ -489,7 +502,7 @@ public interface LiveOrderMapper {
     /*
     * 查询订单创建时间为最近30分钟的订单
     * */
-    @Select("SELECT * FROM live_order WHERE create_time >= DATE_SUB(NOW(), INTERVAL 15 MINUTE) and status = 0 and refund_status = 0")
+    @Select("SELECT * FROM live_order WHERE create_time >= '2026-01-15 19:00:00' and status = 0 and refund_status = 0")
     List<LiveOrder> selectBankOrder();
 
 

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

@@ -1,6 +1,8 @@
 package com.fs.live.mapper;
 
 
+import com.fs.common.annotation.DataSource;
+import com.fs.common.enums.DataSourceType;
 import com.fs.live.domain.LiveOrderPayment;
 import com.fs.live.vo.LiveOrderPaymentVo;
 import org.apache.ibatis.annotations.Param;
@@ -22,6 +24,7 @@ public interface LiveOrderPaymentMapper {
      * @param paymentId 支付明细主键
      * @return 支付明细
      */
+    @DataSource(DataSourceType.SLAVE)
     LiveOrderPayment selectLiveOrderPaymentByPaymentId(Long paymentId);
 
     /**

+ 6 - 1
fs-service/src/main/java/com/fs/live/mapper/LiveUserFirstEntryMapper.java

@@ -1,6 +1,8 @@
 package com.fs.live.mapper;
 
 
+import com.fs.common.annotation.DataSource;
+import com.fs.common.enums.DataSourceType;
 import com.fs.live.domain.LiveUserFirstEntry;
 import com.fs.live.vo.LiveDashBoardDataVo;
 import com.fs.live.vo.LiveUserFirstProfit;
@@ -71,10 +73,11 @@ public interface LiveUserFirstEntryMapper {
 
     @Select("select count(*) from live_user_first_entry where user_id=#{userId} and DATE(entry_date)=DATE(#{now})")
     int selectTodayEntry(@Param("userId") long userId,@Param("now") Date now);
-
+    @DataSource(DataSourceType.SLAVE)
     List<LiveUserFirstProfit> selectLiveProfitList();
 
     @Select("select * from live_user_first_entry where live_id=#{liveId} and user_id=#{userId}")
+    @DataSource(DataSourceType.SLAVE)
     LiveUserFirstEntry selectEntityByLiveIdUserId(@Param("liveId") long liveId,@Param("userId") long userId);
 
     @Select("SELECT  " +
@@ -90,6 +93,7 @@ public interface LiveUserFirstEntryMapper {
             "ORDER BY  " +
             "  invite_num DESC   " +
             "  LIMIT 10")
+    @DataSource(DataSourceType.SLAVE)
     List<LiveUserFirstVo> selectDashboardInviteCount(@Param("liveId") Long liveId);
 
     @Select("SELECT  " +
@@ -104,6 +108,7 @@ public interface LiveUserFirstEntryMapper {
             "  lufe.company_user_id   " +
             "ORDER BY  " +
             "  invite_num DESC   ")
+    @DataSource(DataSourceType.SLAVE)
     List<LiveUserFirstVo> inviteList(@Param("liveId") Long liveId);
 
     @Select("SELECT  sum(case when company_user_id > 0 then 1 else 0 end ) as shareUrlNum, " +

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

@@ -1,6 +1,8 @@
 package com.fs.live.mapper;
 
 
+import com.fs.common.annotation.DataSource;
+import com.fs.common.enums.DataSourceType;
 import com.fs.live.domain.LiveWatchUser;
 import com.fs.live.vo.LiveDashBoardDataVo;
 import com.fs.live.vo.LiveWatchUserEntry;
@@ -148,6 +150,7 @@ public interface LiveWatchUserMapper {
     @Select("select lufe.company_id,lufe.company_user_id,lwu.* from live_watch_user lwu" +
             " left join live_user_first_entry lufe on lwu.live_id = lufe.live_id and lwu.user_id = lufe.user_id" +
             " where lwu.live_id = #{liveId} and lwu.user_id = #{userId} and lwu.live_flag = #{liveFlag} and lwu.replay_flag = #{replayFlag} limit 1 ")
+    @DataSource(DataSourceType.SLAVE)
     LiveWatchUserEntry selectLiveWatchAndCompanyUserByFlag(@Param("liveId") Long liveId,@Param("userId") Long userId,@Param("liveFlag") Integer liveFlag,@Param("replayFlag") Integer replayFlag);
 
     /**

+ 3 - 0
fs-service/src/main/java/com/fs/live/param/MergedOrderQueryParam.java

@@ -123,5 +123,8 @@ public class MergedOrderQueryParam extends BaseQueryParam implements Serializabl
     
     /** 分页偏移量(在外部计算后传入,不在SQL中计算) */
     private Integer offset;
+
+    /** 分页偏移量(在外部计算后传入,不在SQL中计算) */
+    private Integer exportFlag;
 }
 

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

@@ -1,6 +1,7 @@
 package com.fs.live.service;
 
 
+import com.baomidou.mybatisplus.extension.service.IService;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.domain.entity.SysUser;
 import com.fs.company.domain.CompanyUser;
@@ -17,7 +18,7 @@ import java.util.Map;
  * @author fs
  * @date 2025-07-08
  */
-public interface ILiveGoodsService {
+public interface ILiveGoodsService extends IService<LiveGoods> {
     /**
      * 查询直播商品
      *

+ 9 - 0
fs-service/src/main/java/com/fs/live/service/ILiveMsgService.java

@@ -69,4 +69,13 @@ public interface ILiveMsgService
     List<LiveMsg> listRecentMsg(Long id);
 
     List<LiveMsg> selectLiveMsgSingleList(LiveMsg liveMsg);
+
+    /**
+     * 导出直播评论
+     *
+     * @param liveId 直播ID
+     * @param userId 用户ID(用于Redis加锁)
+     * @return 评论列表
+     */
+    List<com.fs.live.vo.LiveMsgExportVO> exportLiveMsgComments(Long liveId, Long userId);
 }

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

@@ -125,6 +125,7 @@ public interface ILiveOrderService {
      * @return
      */
     R createLiveOrder(LiveOrder liveOrder);
+    R createLiveOrderTest(LiveOrder liveOrder);
 
     /**
      * 订单确认
@@ -270,4 +271,8 @@ public interface ILiveOrderService {
     Long isExistPayedRecord(Long orderId);
 
     void payConfirmPayment(Long existPayedRecordId);
+
+    void updateTime(LiveOrder order);
+
+    void initStock();
 }

+ 9 - 1
fs-service/src/main/java/com/fs/live/service/impl/LiveGoodsServiceImpl.java

@@ -1,8 +1,10 @@
 package com.fs.live.service.impl;
 
 
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.domain.entity.SysUser;
+import com.fs.common.core.redis.service.StockDeductService;
 import com.fs.common.exception.ServiceException;
 import com.fs.common.utils.DateUtils;
 import com.fs.company.domain.CompanyUser;
@@ -33,10 +35,12 @@ import java.util.stream.Collectors;
  * @date 2025-07-08
  */
 @Service
-public class LiveGoodsServiceImpl  implements ILiveGoodsService {
+public class LiveGoodsServiceImpl extends ServiceImpl<LiveGoodsMapper, LiveGoods> implements ILiveGoodsService {
 
     @Autowired
     private FsStoreProductScrmMapper fsStoreProductMapper;
+    @Autowired
+    private StockDeductService stockDeductService;
 
     @Autowired
     private LiveGoodsMapper baseMapper;
@@ -98,6 +102,7 @@ public class LiveGoodsServiceImpl  implements ILiveGoodsService {
             if(fsStoreProduct == null) return R.error("商品不存在");
             if(fsStoreProduct.getIsShow() == 0 || existGoods.getStatus() == 0) return R.error("商品已下架");
             if(fsStoreProduct.getStock() < liveGoods.getStock()) return R.error("商品库存不足");
+            stockDeductService.initStock(existGoods.getProductId(), existGoods.getLiveId(), liveGoods.getStock().intValue());
         }
         baseMapper.updateLiveGoods(liveGoods);
         return R.ok();
@@ -377,6 +382,9 @@ public class LiveGoodsServiceImpl  implements ILiveGoodsService {
                 .collect(Collectors.toList());
 //
 //        // 批量插入
+        liveGoodsList.forEach(e -> {
+            stockDeductService.initStock(e.getProductId(), liveId, e.getStock().intValue());
+        });
         return baseMapper.insertLiveGoodsList(liveGoodsList);
     }
 

+ 77 - 0
fs-service/src/main/java/com/fs/live/service/impl/LiveMsgServiceImpl.java

@@ -1,15 +1,23 @@
 package com.fs.live.service.impl;
 
 
+import com.fs.common.core.domain.R;
+import com.fs.common.exception.CustomException;
 import com.fs.common.utils.DateUtils;
 import com.fs.live.domain.LiveMsg;
 import com.fs.live.mapper.LiveMsgMapper;
 import com.fs.live.service.ILiveMsgService;
+import com.fs.live.vo.LiveMsgExportVO;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.core.script.DefaultRedisScript;
 import org.springframework.stereotype.Service;
 
 import java.util.Collections;
 import java.util.List;
+import java.util.concurrent.TimeUnit;
 
 /**
  * 直播讨论Service业务层处理
@@ -20,10 +28,20 @@ import java.util.List;
 @Service
 public class LiveMsgServiceImpl implements ILiveMsgService
 {
+    private static final Logger log = LoggerFactory.getLogger(LiveMsgServiceImpl.class);
+    
     @Autowired
     private LiveMsgMapper liveMsgMapper;
     @Autowired
     private LiveDataServiceImpl liveDataService;
+    
+    @Autowired(required = false)
+    private RedisTemplate<String, Object> redisTemplate;
+    
+    /** Redis锁前缀 */
+    private static final String LOCK_PREFIX = "live:msg:export:lock:";
+    /** 锁过期时间(秒) */
+    private static final long LOCK_EXPIRE_TIME = 300; // 5分钟
 
     /**
      * 查询直播讨论
@@ -108,4 +126,63 @@ public class LiveMsgServiceImpl implements ILiveMsgService
     public List<LiveMsg> selectLiveMsgSingleList(LiveMsg liveMsg) {
         return liveMsgMapper.selectLiveMsgSingleList(liveMsg);
     }
+
+    @Override
+    public List<LiveMsgExportVO> exportLiveMsgComments(Long liveId, Long userId) {
+        if (liveId == null) {
+            throw new CustomException("直播ID不能为空");
+        }
+        if (userId == null) {
+            throw new CustomException("用户ID不能为空");
+        }
+
+        // Redis锁的key:用户ID + 直播间ID
+        String lockKey = LOCK_PREFIX + userId + ":" + liveId;
+        String lockValue = String.valueOf(System.currentTimeMillis());
+
+        try {
+            // 尝试获取锁
+            Boolean lockAcquired = false;
+            if (redisTemplate != null) {
+                lockAcquired = redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, LOCK_EXPIRE_TIME, TimeUnit.SECONDS);
+            }
+
+            if (redisTemplate != null && !lockAcquired) {
+                log.warn("用户{}正在导出直播间{}的评论,请勿重复操作", userId, liveId);
+                throw new CustomException("正在导出中,请勿重复操作");
+            }
+
+            try {
+                // 查询评论数据
+                List<LiveMsgExportVO> list = liveMsgMapper.selectLiveMsgForExport(liveId);
+                log.info("用户{}导出直播间{}的评论,共{}条", userId, liveId, list != null ? list.size() : 0);
+                return list != null ? list : Collections.emptyList();
+            } finally {
+                // 释放锁
+                if (redisTemplate != null && lockAcquired) {
+                    // 使用Lua脚本确保只删除自己的锁
+                    String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
+                            "return redis.call('del', KEYS[1]) " +
+                            "else return 0 end";
+                    DefaultRedisScript<Long> script = new DefaultRedisScript<>();
+                    script.setScriptText(luaScript);
+                    script.setResultType(Long.class);
+                    redisTemplate.execute(script, Collections.singletonList(lockKey), lockValue);
+                }
+            }
+        } catch (CustomException e) {
+            throw e;
+        } catch (Exception e) {
+            log.error("导出直播评论失败,liveId: {}, userId: {}", liveId, userId, e);
+            // 确保异常时也释放锁
+            if (redisTemplate != null) {
+                try {
+                    redisTemplate.delete(lockKey);
+                } catch (Exception ex) {
+                    log.error("释放Redis锁失败", ex);
+                }
+            }
+            throw new CustomException("导出失败:" + e.getMessage());
+        }
+    }
 }

Разница между файлами не показана из-за своего большого размера
+ 243 - 155
fs-service/src/main/java/com/fs/live/service/impl/LiveOrderServiceImpl.java


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

@@ -304,8 +304,15 @@ public class LiveWatchUserServiceImpl implements ILiveWatchUserService {
     @Override
     public LiveWatchUser join(FsUserScrm fsUser,long liveId, long userId, String location) {
 
-        // 查询直播间信息
-        Live live = liveMapper.selectLiveByLiveId(liveId);
+        // 缓存直播间信息,过期时间4小时
+        String liveCacheKey = "live:" + liveId;
+        Live live = redisCache.getCacheObject(liveCacheKey);
+        if (live == null) {
+            live = liveMapper.selectLiveByLiveId(liveId);
+            if (live != null) {
+                redisCache.setCacheObject(liveCacheKey, live, 4, TimeUnit.HOURS);
+            }
+        }
         if (live == null) {
             throw new RuntimeException("直播间不存在");
         }

Некоторые файлы не были показаны из-за большого количества измененных файлов