浏览代码

Merge branch 'master' of http://1.14.104.71:10880/root/ylrz_his_scrm_java

caoliqin 3 周之前
父节点
当前提交
b58aea020e
共有 42 个文件被更改,包括 1179 次插入118 次删除
  1. 3 1
      fs-admin/src/main/java/com/fs/course/controller/FsUserCourseCategoryController.java
  2. 13 1
      fs-admin/src/main/java/com/fs/course/controller/FsUserCourseVideoController.java
  3. 3 1
      fs-admin/src/main/java/com/fs/course/controller/FsVideoResourceController.java
  4. 9 1
      fs-admin/src/main/java/com/fs/his/controller/FsIntegralOrderController.java
  5. 46 5
      fs-company/src/main/java/com/fs/company/controller/company/CompanyInboundCallManageController.java
  6. 1 1
      fs-company/src/main/java/com/fs/company/controller/live/LiveController.java
  7. 11 5
      fs-company/src/main/java/com/fs/hisStore/controller/FsStoreOrderScrmController.java
  8. 461 0
      fs-qw-task/src/main/java/com/fs/app/task/EasyCallCallbackTask.java
  9. 22 1
      fs-qw-task/src/main/java/com/fs/framework/config/DataSourceConfig.java
  10. 4 0
      fs-service/src/main/java/com/fs/company/domain/CompanyVoiceRoboticCallLogCallphone.java
  11. 1 1
      fs-service/src/main/java/com/fs/company/mapper/CompanyConfigMapper.java
  12. 10 0
      fs-service/src/main/java/com/fs/company/mapper/CompanyVoiceRoboticCallLogCallphoneMapper.java
  13. 3 0
      fs-service/src/main/java/com/fs/company/mapper/EasyCallMapper.java
  14. 16 1
      fs-service/src/main/java/com/fs/company/vo/easycall/EasyCallCallPhoneVO.java
  15. 3 3
      fs-service/src/main/java/com/fs/company/vo/easycall/EasyCallVoiceCodeVO.java
  16. 8 0
      fs-service/src/main/java/com/fs/config/cloud/CloudHostProper.java
  17. 50 0
      fs-service/src/main/java/com/fs/course/config/CourseConfig.java
  18. 8 0
      fs-service/src/main/java/com/fs/course/domain/FsUserCourseComment.java
  19. 3 1
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java
  20. 2 0
      fs-service/src/main/java/com/fs/his/param/FsIntegralOrderDoPayParam.java
  21. 11 0
      fs-service/src/main/java/com/fs/his/vo/FsIntegralOrderListUVO.java
  22. 30 0
      fs-service/src/main/java/com/fs/hisStore/config/StoreConfig.java
  23. 1 1
      fs-service/src/main/java/com/fs/hisStore/domain/FsStorePaymentScrm.java
  24. 1 1
      fs-service/src/main/java/com/fs/hisStore/mapper/FsStorePaymentScrmMapper.java
  25. 21 11
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreAfterSalesScrmServiceImpl.java
  26. 59 40
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreOrderScrmServiceImpl.java
  27. 5 5
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStorePaymentScrmServiceImpl.java
  28. 53 18
      fs-service/src/main/java/com/fs/live/service/impl/LiveOrderServiceImpl.java
  29. 2 0
      fs-service/src/main/resources/application-config-druid-bjzm-test.yml
  30. 2 0
      fs-service/src/main/resources/application-config-druid-bjzm.yml
  31. 23 0
      fs-service/src/main/resources/mapper/company/CompanyVoiceRoboticCallLogCallphoneMapper.xml
  32. 4 3
      fs-service/src/main/resources/mapper/company/EasyCallInboundLlmMapper.xml
  33. 4 0
      fs-service/src/main/resources/mapper/company/EasyCallMapper.xml
  34. 8 1
      fs-service/src/main/resources/mapper/course/FsUserCourseCategoryMapper.xml
  35. 8 9
      fs-service/src/main/resources/mapper/course/FsUserCourseMapper.xml
  36. 8 1
      fs-service/src/main/resources/mapper/his/FsIntegralOrderMapper.xml
  37. 129 1
      fs-user-app/src/main/java/com/fs/app/controller/CourseCommentController.java
  38. 1 0
      fs-user-app/src/main/java/com/fs/app/controller/app/AppController.java
  39. 2 2
      fs-user-app/src/main/java/com/fs/app/controller/course/CourseQwController.java
  40. 1 1
      fs-user-app/src/main/java/com/fs/app/controller/live/LiveOrderController.java
  41. 128 1
      fs-user-app/src/main/java/com/fs/app/controller/store/CourseCommentScrmController.java
  42. 1 1
      fs-user-app/src/main/java/com/fs/app/controller/store/PayScrmController.java

+ 3 - 1
fs-admin/src/main/java/com/fs/course/controller/FsUserCourseCategoryController.java

@@ -183,7 +183,9 @@ public class FsUserCourseCategoryController extends BaseController
 	@DeleteMapping("/{cateIds}")
     public AjaxResult remove(@PathVariable Long[] cateIds)
     {
-        if ("北京卓美".equals(cloudHostProper.getCompanyName())) {
+        String configJson = configService.selectConfigByKey("course.config");
+        CourseConfig courseConfig = JSONUtil.toBean(configJson, CourseConfig.class);
+        if (courseConfig != null && Boolean.TRUE.equals(courseConfig.getEnableCategoryDeleteCheck())) {
             for (Long cateId : cateIds) {
                 if (cateId == null) {
                     continue;

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

@@ -120,6 +120,16 @@ public class FsUserCourseVideoController extends BaseController
         return AjaxResult.success(fsUserCourseVideoService.selectFsUserCourseVideoByVideoIdVO(videoId,null));
     }
 
+    /**
+     * 获取课堂视频详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('course:userCourseVideo:query')")
+    @GetMapping(value = "/public/{videoId}")
+    public AjaxResult getPublicInfo(@PathVariable("videoId") Long videoId)
+    {
+        return AjaxResult.success(fsUserCourseVideoService.selectFsUserCourseVideoByVideoIdVO(videoId,null));
+    }
+
     /**
      * 新增课堂视频
      */
@@ -223,7 +233,9 @@ public class FsUserCourseVideoController extends BaseController
 	@DeleteMapping("/{videoIds}")
     public AjaxResult remove(@PathVariable String[] videoIds)
     {
-        if ("北京卓美".equals(companyName) && videoIds != null && videoIds.length > 0) {
+        String configJson = configService.selectConfigByKey("course.config");
+        CourseConfig courseConfig = JSONUtil.toBean(configJson, CourseConfig.class);
+        if (courseConfig != null && Boolean.TRUE.equals(courseConfig.getEnableVideoDeleteOnShelfCheck()) && videoIds != null && videoIds.length > 0) {
             int onShelf = fsUserCourseVideoMapper.countOnShelfCourseByVideoIds(videoIds);
             if (onShelf > 0) {
                 return AjaxResult.error("内容有上架状态,请先修改启用状态");

+ 3 - 1
fs-admin/src/main/java/com/fs/course/controller/FsVideoResourceController.java

@@ -190,7 +190,9 @@ public class FsVideoResourceController extends BaseController {
     @Log(title = "视频素材库", businessType = BusinessType.DELETE)
     @DeleteMapping("/{ids}")
     public AjaxResult remove(@PathVariable Long[] ids) {
-        if ("北京卓美".equals(cloudHostProper.getCompanyName()) && ids != null && ids.length > 0) {
+        String configJson = configService.selectConfigByKey("course.config");
+        CourseConfig courseConfig = JSONUtil.toBean(configJson, CourseConfig.class);
+        if (courseConfig != null && Boolean.TRUE.equals(courseConfig.getEnableResourceDeleteCourseCheck()) && ids != null && ids.length > 0) {
             int linked = fsUserCourseVideoMapper.countCourseVideoByVideoResourceIds(ids);
             if (linked > 0) {
                 return AjaxResult.error("素材有关联课程,不能删除");

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

@@ -17,6 +17,9 @@ import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.poi.ExcelUtil;
 import com.fs.common.utils.uuid.IdUtils;
 import com.fs.config.cloud.CloudHostProper;
+import cn.hutool.json.JSONUtil;
+import com.fs.hisStore.config.StoreConfig;
+import com.fs.system.service.ISysConfigService;
 import com.fs.his.domain.*;
 import com.fs.his.dto.ExpressInfoDTO;
 import com.fs.his.enums.ShipperCodeEnum;
@@ -77,6 +80,9 @@ public class FsIntegralOrderController extends BaseController
     @Autowired
     private FsIntegralOrderMapper fsIntegralOrderMapper;
 
+    @Autowired
+    private ISysConfigService configService;
+
     /**
      * 查询积分商品订单列表
      */
@@ -174,7 +180,9 @@ public class FsIntegralOrderController extends BaseController
                 // 加密手机号
 
                 // 260506 积分订单没有接入erp 暂时这么处理 卓美 需要对订单进行发货处理
-                if (!"北京卓美".equals(cloudHostProper.getCompanyName())) {
+                String hisStoreJson = configService.selectConfigByKey("his.store");
+                StoreConfig storeConfig = JSONUtil.toBean(hisStoreJson, StoreConfig.class);
+                if (storeConfig == null || !Boolean.TRUE.equals(storeConfig.getEnableIntegralOrderPhoneDecrypt())) {
                     vo.setUserPhone(decryptAutoPhoneMk(vo.getUserPhone()));
                 }
             }

+ 46 - 5
fs-company/src/main/java/com/fs/company/controller/company/CompanyInboundCallManageController.java

@@ -1,5 +1,6 @@
 package com.fs.company.controller.company;
 
+import com.alibaba.fastjson.JSONObject;
 import com.fs.aicall.domain.CcLlmAgentAccount;
 import com.fs.aicall.service.ICompanyBindAiModelService;
 import com.fs.common.annotation.Log;
@@ -12,6 +13,8 @@ import com.fs.common.utils.StringUtils;
 import com.fs.company.domain.CompanyInboundBind;
 import com.fs.company.domain.EasyCallInboundCdrVO;
 import com.fs.company.mapper.CompanyInboundBindMapper;
+import com.fs.company.mapper.CompanyMapper;
+import com.fs.company.mapper.CompanyVoiceCloneRefMapper;
 import com.fs.company.mapper.EasyCallInboundLlmMapper;
 import com.fs.company.service.ICompanyInboundCallManageService;
 import com.fs.company.vo.easycall.EasyCallBizGroupVO;
@@ -22,14 +25,12 @@ import com.fs.company.vo.easycall.EasyCallLlmAccountVO;
 import com.fs.company.vo.easycall.EasyCallVoiceCodeVO;
 import com.fs.framework.security.LoginUser;
 import com.fs.framework.service.TokenService;
+import com.fs.system.service.ISysConfigService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
 
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
 import java.util.stream.Collectors;
 
 /**
@@ -55,6 +56,13 @@ public class CompanyInboundCallManageController extends BaseController {
 
     @Autowired
     private ICompanyBindAiModelService companyBindAiModelService;
+    @Autowired
+    private CompanyVoiceCloneRefMapper companyVoiceCloneRefMapper;
+    @Autowired
+    private ISysConfigService configService;
+
+    @Autowired
+    CompanyMapper companyMapper;
 
     /**
      * 查询呼入大模型配置列表
@@ -213,8 +221,23 @@ public class CompanyInboundCallManageController extends BaseController {
      */
     @GetMapping("/voiceList")
     public AjaxResult getVoiceList(@RequestParam("voiceSource") String voiceSource) {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        Long companyId = loginUser.getUser().getCompanyId();
+        List<Long> ttsIds = companyVoiceCloneRefMapper.selectByCompanyIdAndCompanyUserId(companyId, loginUser.getCompany().getUserId());
         List<EasyCallVoiceCodeVO> list = inboundLlmMapper.selectVoiceListBySource(voiceSource);
-        return AjaxResult.success(list);
+        List<EasyCallVoiceCodeVO> result = list.stream()
+                .filter(item ->
+                        item.getPriority() == 1 || (item.getPriority() == 0 && ttsIds.contains(item.getId()))
+                )
+                .map(item -> {
+                    EasyCallVoiceCodeVO vo = new EasyCallVoiceCodeVO();
+                    vo.setVoiceCode(item.getVoiceCode());
+                    vo.setVoiceName(item.getVoiceName());
+                    vo.setVoiceSource(item.getVoiceSource());
+                    return vo;
+                })
+                .collect(Collectors.toList());
+        return AjaxResult.success(result);
     }
 
     /**
@@ -231,7 +254,25 @@ public class CompanyInboundCallManageController extends BaseController {
      */
     @GetMapping("/gatewayList")
     public AjaxResult getGatewayList() {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        Long companyId = loginUser.getUser().getCompanyId();
+        String gateWayList = companyMapper.getGateWayList(companyId);
+        String json = configService.selectConfigByKey("cId.config");
         List<EasyCallGatewayVO> list = inboundLlmMapper.selectOutboundGatewayList();
+        if (null != companyId && null != list && !list.isEmpty()) {
+            if(StringUtils.isNotBlank(gateWayList)){
+                List<Long> collect = Arrays.stream(gateWayList.split(",")).map(item -> Long.valueOf(item.trim())).collect(Collectors.toList());
+                list = list.stream().filter(item -> collect.contains(item.getId())).collect(Collectors.toList());
+            }else{
+                if (StringUtils.isNotBlank(json)) {
+                    JSONObject obj = JSONObject.parseObject(json);
+                    if(null != obj && obj.containsKey("showGatewayIds")){
+                        List<Long> showGatewayIds = obj.getJSONArray("showGatewayIds").stream().map(item -> Long.valueOf(item.toString())).collect(Collectors.toList());
+                        list = list.stream().filter(item -> showGatewayIds.contains(item.getId())).collect(Collectors.toList());
+                    }
+                }
+            }
+        }
         return AjaxResult.success(list);
     }
 

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

@@ -71,7 +71,7 @@ public class LiveController extends BaseController
     {
         startPage();
         List<Live> list = new ArrayList<>();
-        if (CloudHostUtils.hasCloudHostName("济世百康","蒙牛") ) {
+        if (CloudHostUtils.hasCloudHostName("济世百康") ) {
             //直播也发
             list = liveService.listToLiveNoEndNew(live);
         }else{

+ 11 - 5
fs-company/src/main/java/com/fs/hisStore/controller/FsStoreOrderScrmController.java

@@ -133,14 +133,16 @@ public class FsStoreOrderScrmController extends BaseController
         }
         startPage();
 
-        if("北京卓美".equals(cloudHostProper.getCompanyName())){
+        String configJson = configService.selectConfigByKey("his.store");
+        StoreConfig storeConfig = JSONUtil.toBean(configJson, StoreConfig.class);
+        if(storeConfig != null && Boolean.TRUE.equals(storeConfig.getEnableCompanyOrderMode())){
             if(!"00".equals(loginUser.getUser().getUserType())){//非管理员看见自己数据
                 param.setCompanyUserId(loginUser.getUser().getUserId());
             }
             param.setIsCompanyOrder(1);//是否销售订单
         }
-
-
+        
+        
         if(!StringUtils.isEmpty(param.getCreateTimeRange())){
             param.setCreateTimeList(param.getCreateTimeRange().split("--"));
         }
@@ -218,7 +220,9 @@ public class FsStoreOrderScrmController extends BaseController
         if(!StringUtils.isEmpty(param.getDeliveryImportTimeRange())){
             param.setDeliveryImportTimeList(param.getDeliveryImportTimeRange().split("--"));
         }
-        if("北京卓美".equals(cloudHostProper.getCompanyName())){
+        String configJson = configService.selectConfigByKey("his.store");
+        StoreConfig storeConfig = JSONUtil.toBean(configJson, StoreConfig.class);
+        if(storeConfig != null && Boolean.TRUE.equals(storeConfig.getEnableCompanyOrderMode())){
             if(!"00".equals(loginUser.getUser().getUserType())){//非管理员看见自己数据
                 param.setCompanyUserId(loginUser.getUser().getUserId());
             }
@@ -537,7 +541,9 @@ public class FsStoreOrderScrmController extends BaseController
         if(!StringUtils.isEmpty(param.getDeliveryImportTimeRange())){
             param.setDeliveryImportTimeList(param.getDeliveryImportTimeRange().split("--"));
         }
-        if("北京卓美".equals(cloudHostProper.getCompanyName())){
+        String configJson = configService.selectConfigByKey("his.store");
+        StoreConfig storeConfig = JSONUtil.toBean(configJson, StoreConfig.class);
+        if(storeConfig != null && Boolean.TRUE.equals(storeConfig.getEnableCompanyOrderMode())){
             if(!"00".equals(loginUser.getUser().getUserType())){//非管理员看见自己数据
                 param.setCompanyUserId(loginUser.getUser().getUserId());
             }

+ 461 - 0
fs-qw-task/src/main/java/com/fs/app/task/EasyCallCallbackTask.java

@@ -0,0 +1,461 @@
+package com.fs.app.task;
+
+import cn.hutool.json.JSONUtil;
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.fs.common.core.domain.entity.SysDictData;
+import com.fs.common.utils.StringUtils;
+import com.fs.company.domain.*;
+import com.fs.company.mapper.CompanyVoiceRoboticCallLogCallphoneMapper;
+import com.fs.company.mapper.CompanyWxAccountMapper;
+import com.fs.company.mapper.EasyCallMapper;
+import com.fs.company.service.ICompanyVoiceRoboticCalleesService;
+import com.fs.company.service.impl.CompanyVoiceRoboticCallLogCallphoneServiceImpl;
+import com.fs.company.service.impl.CompanyVoiceRoboticWxServiceImpl;
+import com.fs.company.service.impl.CompanyWxClientServiceImpl;
+import com.fs.company.vo.CidConfigVO;
+import com.fs.company.vo.easycall.EasyCallCallPhoneVO;
+import com.fs.crm.service.ICrmCustomerAnalyzeService;
+import com.fs.crm.service.ICrmCustomerPropertyService;
+import com.fs.qw.domain.QwUser;
+import com.fs.qw.mapper.QwUserMapper;
+import com.fs.system.service.ISysConfigService;
+import com.fs.system.service.impl.SysDictTypeServiceImpl;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.List;
+import java.util.Optional;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * EasyCall外呼回调定时任务
+ * 定期查询待回调的记录,通过callback_uuid去easycallcenter365库查询回调信息
+ *
+ * @author system
+ * @date 2026-04-30
+ */
+@Component
+@Slf4j
+public class EasyCallCallbackTask {
+
+    @Autowired
+    private CompanyVoiceRoboticCallLogCallphoneMapper callLogMapper;
+
+    @Autowired
+    private EasyCallMapper easyCallMapper;
+
+    @Autowired
+    private CompanyVoiceRoboticCallLogCallphoneServiceImpl callLogService;
+
+    @Autowired
+    private ICrmCustomerAnalyzeService crmCustomerAnalyzeService;
+
+    @Autowired
+    private SysDictTypeServiceImpl sysDictTypeService;
+
+    @Autowired
+    private ISysConfigService configService;
+
+    @Autowired
+    private CompanyWxClientServiceImpl companyWxClientService;
+
+    @Autowired
+    private CompanyVoiceRoboticWxServiceImpl companyVoiceRoboticWxService;
+
+    @Autowired
+    private CompanyWxAccountMapper companyWxAccountMapper;
+
+    @Autowired
+    private QwUserMapper qwUserMapper;
+
+    @Autowired
+    private ICrmCustomerPropertyService crmCustomerPropertyService;
+
+    @Autowired
+    private ICompanyVoiceRoboticCalleesService companyVoiceRoboticCalleesService;
+
+    private final AtomicBoolean isRunning = new AtomicBoolean(false);
+
+    /** 最大重试次数 */
+    private static final int MAX_RETRY_COUNT = 3;
+
+    /** 默认通话费用(元/分钟) */
+    private static final BigDecimal DEFAULT_CALL_CHARGE = new BigDecimal("0.12");
+
+    /** 一分钟的秒数 */
+    private static final BigDecimal ONE_MINUTES_SECOND = new BigDecimal("60");
+
+    /**
+     * 定时任务:处理EasyCall外呼回调
+     * 执行时间:每5分钟执行一次
+     * 功能:查询待回调记录,通过callback_uuid查询回调信息并更新
+     */
+    @Scheduled(cron = "0 0/5 * * * ?")
+    public void processEasyCallCallback() {
+        // 尝试设置标志为 true,表示任务开始执行
+        if (!isRunning.compareAndSet(false, true)) {
+            log.info("【EasyCall回调任务】上一个任务尚未完成,跳过此次执行");
+            return;
+        }
+
+        try {
+            long startTime = System.currentTimeMillis();
+            log.info("【EasyCall回调任务】开始执行");
+
+            // 查询待回调的记录(status=1且重试次数小于3)
+            List<CompanyVoiceRoboticCallLogCallphone> pendingRecords = callLogMapper.selectPendingCallbackRecords();
+
+            if (pendingRecords == null || pendingRecords.isEmpty()) {
+                log.info("【EasyCall回调任务】暂无待处理记录");
+                return;
+            }
+
+            log.info("【EasyCall回调任务】查询到待处理记录数量: {}", pendingRecords.size());
+
+            int successCount = 0;
+            int failCount = 0;
+            int maxRetryCount = 0;
+
+            for (CompanyVoiceRoboticCallLogCallphone record : pendingRecords) {
+                try {
+                    boolean success = processCallbackRecord(record);
+                    if (success) {
+                        successCount++;
+                    } else {
+                        failCount++;
+                    }
+                } catch (Exception e) {
+                    log.error("【EasyCall回调任务】处理记录失败,logId={}, callbackUuid={}",
+                            record.getLogId(), record.getCallbackUuid(), e);
+                    failCount++;
+                    // 更新重试次数
+                    updateRetryStatus(record, false);
+                }
+            }
+
+            long endTime = System.currentTimeMillis();
+            log.info("【EasyCall回调任务】执行完成,总数={}, 成功={}, 失败={}, 达到最大重试={}, 耗时={}ms",
+                    pendingRecords.size(), successCount, failCount, maxRetryCount, (endTime - startTime));
+
+        } catch (Exception e) {
+            log.error("【EasyCall回调任务】执行异常", e);
+        } finally {
+            // 重置标志为 false,表示任务已完成
+            isRunning.set(false);
+        }
+    }
+
+    /**
+     * 处理单条回调记录
+     *
+     * @param record 待处理记录
+     * @return true-处理成功,false-处理失败
+     */
+    private boolean processCallbackRecord(CompanyVoiceRoboticCallLogCallphone record) {
+        String callbackUuid = record.getCallbackUuid();
+        Long logId = record.getLogId();
+        Integer currentRetryCount = record.getRetryCount() == null ? 0 : record.getRetryCount();
+
+        log.info("【EasyCall回调任务】开始处理记录,logId={}, callbackUuid={}, 当前重试次数={}",
+                logId, callbackUuid, currentRetryCount);
+
+        // 通过callback_uuid去easycallcenter365库的cc_call_phone表查询
+        EasyCallCallPhoneVO callPhoneRes = easyCallMapper.getCallPhoneInfoByCallBackUuid(callbackUuid);
+
+        if (callPhoneRes == null) {
+            log.warn("【EasyCall回调任务】未查询到回调信息,logId={}, callbackUuid={}", logId, callbackUuid);
+            return updateRetryStatus(record, false);
+        }
+
+        log.info("【EasyCall回调任务】查询到回调信息,logId={}, uuid={}, telephone={}",
+                logId, callPhoneRes.getUuid(), callPhoneRes.getTelephone());
+
+        try {
+            // 更新回调数据到company_voice_robotic_call_log_callphone表
+            updateCallbackData(record, callPhoneRes);
+
+            // 回调成功,状态改为2(回调字段须走完整 update,仅 updateRetryCountAndStatus 不会落库 result 等)
+            record.setStatus(2);
+            record.setRetryCount(currentRetryCount + 1);
+            callLogMapper.updateCompanyVoiceRoboticCallLogCallphone(record);
+
+            // 更新用户标签
+            try {
+                crmCustomerPropertyService.addPropertyByCallLog(record);
+                log.info("【EasyCall回调任务】【isSendMsg=标签更新】用户标签更新成功,logId={}", logId);
+            } catch (Exception e) {
+                log.error("【EasyCall回调任务】更新用户标签失败,logId={}", logId, e);
+            }
+
+            log.info("【EasyCall回调任务】【isSendMsg=回调成功】回调数据更新成功,logId={}, callbackUuid={}, 状态=2",
+                    logId, callbackUuid);
+
+            return true;
+        } catch (Exception e) {
+            log.error("【EasyCall回调任务】更新回调数据失败,logId={}, callbackUuid={}", logId, callbackUuid, e);
+            return updateRetryStatus(record, false);
+        }
+    }
+
+    /**
+     * 更新重试状态
+     *
+     * @param record 记录
+     * @param success 是否成功
+     * @return true-更新成功,false-更新失败
+     */
+    private boolean updateRetryStatus(CompanyVoiceRoboticCallLogCallphone record, boolean success) {
+        Integer currentRetryCount = record.getRetryCount() == null ? 0 : record.getRetryCount();
+        int newRetryCount = currentRetryCount + 1;
+
+        record.setRetryCount(newRetryCount);
+
+        // 判断是否达到最大重试次数
+        if (newRetryCount >= MAX_RETRY_COUNT) {
+            // 达到最大重试次数,状态改为3
+            record.setStatus(3);
+            log.warn("【EasyCall回调任务】【isSendMsg=3】达到最大重试次数,logId={}, callbackUuid={}, 重试次数={}, 状态=3",
+                    record.getLogId(), record.getCallbackUuid(), newRetryCount);
+        } else {
+            log.info("【EasyCall回调任务】【isSendMsg=1】更新重试次数,logId={}, callbackUuid={}, 重试次数={}, 状态=1",
+                    record.getLogId(), record.getCallbackUuid(), newRetryCount);
+        }
+
+        try {
+            callLogMapper.updateRetryCountAndStatus(record);
+            return true;
+        } catch (Exception e) {
+            log.error("【EasyCall回调任务】更新重试状态失败,logId={}", record.getLogId(), e);
+            return false;
+        }
+    }
+
+    /**
+     * 更新回调数据到company_voice_robotic_call_log_callphone表
+     *
+     * @param record 原记录
+     * @param callPhoneRes 回调数据
+     */
+    private void updateCallbackData(CompanyVoiceRoboticCallLogCallphone record, EasyCallCallPhoneVO callPhoneRes) {
+        log.info("【EasyCall回调任务】开始更新回调数据,logId={}, uuid={}", record.getLogId(), callPhoneRes.getUuid());
+
+        // 设置回调结果
+        record.setResult(JSON.toJSONString(callPhoneRes));
+
+        // 设置录音地址
+        record.setRecordPath(callPhoneRes.getWavfile());
+
+        // 设置对话内容
+        record.setContentList(callPhoneRes.getDialogue());
+
+        // 设置主叫号码
+        record.setCallerNum(callPhoneRes.getTelephone());
+
+        // 设置被叫号码
+        record.setCalleeNum(callPhoneRes.getCallerNumber());
+
+        // 设置uuid
+        record.setUuid(callPhoneRes.getUuid());
+
+        // 设置外呼时间
+        record.setCallCreateTime(callPhoneRes.getCalloutTime());
+
+        // 设置呼叫结束时间
+        record.setCallAnswerTime(callPhoneRes.getCallEndTime());
+
+        // 设置通话时长(毫秒转秒)
+        if (callPhoneRes.getTimeLen() != null) {
+            record.setCallTime(Long.valueOf(callPhoneRes.getTimeLen() / 1000));
+        }
+
+        // 意向度:优先使用 EasyCall cc_call_phone 的 intention/intent;无则再走 AI(对话)
+        String intentRaw = resolveIntentFromCcCallPhone(callPhoneRes);
+        String intentf = convertIntention(intentRaw);
+        if (StringUtils.isBlank(intentf)) {
+            intentf = convertIntention(calculateIntention(record, callPhoneRes));
+        }
+        if (StringUtils.isBlank(intentf)) {
+            intentf = "0";
+        }
+        record.setIntention(intentf);
+
+        // 计算通话费用
+        BigDecimal cost = calculateCallCost(record.getCallTime());
+        record.setCost(cost);
+
+        Long companyUserId = resolveCompanyUserId(record, callPhoneRes);
+        if (companyUserId != null) {
+            record.setCompanyUserId(companyUserId);
+        }
+
+        log.info("【EasyCall回调任务】【isSendMsg=数据更新】回调数据准备完成,logId={}, intention={}, callTime={}, cost={}, companyUserId={}, recordPath={}",
+                record.getLogId(), intentf, record.getCallTime(), cost, companyUserId,
+                StringUtils.isNotBlank(record.getRecordPath()) ? "有录音" : "无录音");
+    }
+
+    /**
+     * EasyCall cc_call_phone 表中的意向(intention 多为数字字符串,intent 多为等级字母)
+     */
+    private String resolveIntentFromCcCallPhone(EasyCallCallPhoneVO callPhoneRes) {
+        if (StringUtils.isNotBlank(callPhoneRes.getIntention())) {
+            return callPhoneRes.getIntention().trim();
+        }
+        if (StringUtils.isNotBlank(callPhoneRes.getIntent())) {
+            return callPhoneRes.getIntent().trim();
+        }
+        return null;
+    }
+
+    /**
+     * 计算意向度
+     *
+     * @param record 记录
+     * @param callPhoneRes 回调数据
+     * @return 意向度标签
+     */
+    private String calculateIntention(CompanyVoiceRoboticCallLogCallphone record, EasyCallCallPhoneVO callPhoneRes) {
+        String intention = null;
+        if (StringUtils.isNotBlank(callPhoneRes.getDialogue())) {
+            try {
+                log.info("【EasyCall回调任务】开始计算意向度,logId={}, dialogue长度={}",
+                        record.getLogId(), callPhoneRes.getDialogue().length());
+
+                intention = crmCustomerAnalyzeService.aiIntentionDegree(
+                        callPhoneRes.getDialogue(),
+                        java.time.LocalTime.now().getLong(java.time.temporal.ChronoField.MILLI_OF_SECOND)
+                );
+
+                log.info("【EasyCall回调任务】意向度计算完成,logId={}, intention={}",
+                        record.getLogId(), intention);
+            } catch (Exception e) {
+                log.error("【EasyCall回调任务】意向度AI解析失败,logId={}, uuid={}",
+                        record.getLogId(), callPhoneRes.getUuid(), e);
+            }
+        } else {
+            log.warn("【EasyCall回调任务】对话内容为空,无法计算意向度,logId={}", record.getLogId());
+        }
+        return intention;
+    }
+
+    /**
+     * 计算通话费用
+     *
+     * @param callTime 通话时长(秒)
+     * @return 费用(元)
+     */
+    private BigDecimal calculateCallCost(Long callTime) {
+        if (callTime == null || callTime == 0) {
+            log.info("【EasyCall回调任务】通话时长为0,费用为0");
+            return BigDecimal.ZERO;
+        }
+
+        // 获取费率配置
+        BigDecimal callCharge = DEFAULT_CALL_CHARGE;
+        try {
+            String json = configService.selectConfigByKey("cid.config");
+            if (StringUtils.isNotBlank(json)) {
+                CidConfigVO cidConfigVO = JSONUtil.toBean(json, CidConfigVO.class);
+                if (cidConfigVO.getCallCharge() != null) {
+                    callCharge = cidConfigVO.getCallCharge();
+                }
+            }
+        } catch (Exception e) {
+            log.warn("【EasyCall回调任务】获取费率配置失败,使用默认费率{}", DEFAULT_CALL_CHARGE, e);
+        }
+
+        // 向上取整分钟数
+        BigDecimal minutes = new BigDecimal(callTime).divide(ONE_MINUTES_SECOND, 0, RoundingMode.CEILING);
+        BigDecimal cost = minutes.multiply(callCharge);
+
+        log.info("【EasyCall回调任务】费用计算完成,通话时长={}s, 分钟数={}, 费率={}, 总费用={}",
+                callTime, minutes, callCharge, cost);
+
+        return cost;
+    }
+
+
+    private Long resolveCompanyUserId(CompanyVoiceRoboticCallLogCallphone record, EasyCallCallPhoneVO callPhoneRes) {
+        if (callPhoneRes.getCompanyUserId() != null) {
+            return callPhoneRes.getCompanyUserId();
+        }
+        return resolveCompanyUserIdFromWxBind(record);
+    }
+
+    private Long resolveCompanyUserIdFromWxBind(CompanyVoiceRoboticCallLogCallphone record) {
+        if (record.getRoboticId() == null || record.getCallerId() == null) {
+            log.warn("【EasyCall回调任务】roboticId或callerId为空,无法解析company_user_id,logId={}", record.getLogId());
+            return null;
+        }
+        try {
+            CompanyVoiceRoboticCallees callees = companyVoiceRoboticCalleesService.selectCompanyVoiceRoboticCalleesById(record.getCallerId());
+            if (callees == null || callees.getUserId() == null) {
+                log.error("【EasyCall回调任务】未找到被叫记录或userId为空,logId={}, callerId={}",
+                        record.getLogId(), record.getCallerId());
+                return null;
+            }
+            CompanyWxClient companyWxClient = companyWxClientService.getOne(
+                    new QueryWrapper<CompanyWxClient>().eq("robotic_id", callees.getRoboticId()).eq("customer_id", callees.getUserId()));
+            if (companyWxClient == null || companyWxClient.getRoboticWxId() == null) {
+                log.error("【EasyCall回调任务】未找到CompanyWxClient或roboticWxId为空,logId={}, roboticId={}, customerId={}",
+                        record.getLogId(), callees.getRoboticId(), callees.getUserId());
+                return null;
+            }
+            CompanyVoiceRoboticWx roboticWx = companyVoiceRoboticWxService.getById(companyWxClient.getRoboticWxId());
+            if (roboticWx == null) {
+                log.error("【EasyCall回调任务】未找到CompanyVoiceRoboticWx,logId={}, roboticWxId={}",
+                        record.getLogId(), companyWxClient.getRoboticWxId());
+                return null;
+            }
+            if (Integer.valueOf(1).equals(companyWxClient.getIsWeCom())) {
+                CompanyWxAccount companyWxAccount = companyWxAccountMapper.selectCompanyWxAccountById(roboticWx.getAccountId());
+                return companyWxAccount != null ? companyWxAccount.getCompanyUserId() : null;
+            }
+            if (Integer.valueOf(2).equals(companyWxClient.getIsWeCom())) {
+                QwUser qwUser = qwUserMapper.selectById(roboticWx.getAccountId());
+                return qwUser != null ? qwUser.getCompanyUserId() : null;
+            }
+            log.error("【EasyCall回调任务】CompanyWxClient.isWeCom非1/2,无法解析company_user_id,logId={}", record.getLogId());
+            return null;
+        } catch (Exception e) {
+            log.error("【EasyCall回调任务】按企微绑定解析company_user_id失败,logId={}", record.getLogId(), e);
+            return null;
+        }
+    }
+
+    /**
+     * 转换意向度值
+     *
+     * @param intention 意向度标签
+     * @return 意向度数值
+     */
+    private String convertIntention(String intention) {
+        if (StringUtils.isBlank(intention)) {
+            return null;
+        }
+
+        List<SysDictData> customerIntentionLevel = sysDictTypeService.selectDictDataByType("customer_intention_level");
+
+        String t = intention.trim();
+        // 已是非负整数(含 0)则直接入库
+        if (t.matches("^\\d+$")) {
+            return t;
+        }
+
+        // 根据标签查找对应的数值
+        Optional<SysDictData> firstDict = customerIntentionLevel.stream()
+                .filter(e -> e.getDictLabel().equals(t))
+                .findFirst();
+
+        if (firstDict.isPresent()) {
+            return firstDict.get().getDictValue();
+        }
+
+        return null;
+    }
+}

+ 22 - 1
fs-qw-task/src/main/java/com/fs/framework/config/DataSourceConfig.java

@@ -34,13 +34,34 @@ public class DataSourceConfig {
         return new DruidDataSource();
     }
 
+    @Bean
+    @ConfigurationProperties(prefix = "spring.datasource.mysql.druid.slave")
+    public DataSource slaveDataSource() {
+        return new DruidDataSource();
+    }
+
+    @Bean
+    @ConfigurationProperties(prefix = "spring.datasource.mysql.druid.wx")
+    public DataSource wxDataSource() {
+        return new DruidDataSource();
+    }
 
+    @Bean
+    @ConfigurationProperties(prefix = "spring.datasource.easycall.druid.master")
+    public DataSource easyCallSource() {
+        return new DruidDataSource();
+    }
 
     @Bean
     @Primary
-    public DynamicDataSource dataSource(@Qualifier("masterDataSource") DataSource masterDataSource, @Qualifier("sopDataSource") DataSource sopDataSource) {
+    public DynamicDataSource dataSource(@Qualifier("masterDataSource") DataSource masterDataSource, @Qualifier("sopDataSource") DataSource sopDataSource,
+                                        @Qualifier("slaveDataSource") DataSource slaveDataSource, @Qualifier("wxDataSource") DataSource wxDataSource, @Qualifier("easyCallSource") DataSource easyCallSource) {
         Map<Object, Object> targetDataSources = new HashMap<>();
+        targetDataSources.put(DataSourceType.MASTER.name(), masterDataSource);
+        targetDataSources.put(DataSourceType.SLAVE.name(), masterDataSource);
         targetDataSources.put(DataSourceType.SOP.name(), sopDataSource);
+        targetDataSources.put(DataSourceType.WX.name(), wxDataSource);
+        targetDataSources.put(DataSourceType.EASYCALL.name(), easyCallSource);
         return new DynamicDataSource(masterDataSource, targetDataSources);
     }
 

+ 4 - 0
fs-service/src/main/java/com/fs/company/domain/CompanyVoiceRoboticCallLogCallphone.java

@@ -106,6 +106,10 @@ public class CompanyVoiceRoboticCallLogCallphone extends BaseEntity{
     @Excel(name = "外呼类型")
     private Integer callType;
 
+    /** 回调重试次数 */
+    @Excel(name = "回调重试次数")
+    private Integer retryCount;
+
     @TableField(exist = false)
     private String companyName;
 

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

@@ -76,7 +76,7 @@ public interface CompanyConfigMapper
     @Select("select config_value from company_config where company_id=#{companyId} and config_key='redPacket:config' ")
     String selectRedPacketConfigByKey(Long companyId);
 
-    @Select("select config_value from company_config where company_id=#{companyId} and config_key='his.AppRedPacket' ")
+    @Select("select config_value from company_config where company_id=#{companyId} and config_key='his:AppRedPacket' ")
     String selectRedPacketConfigByKeyApp(Long companyId);
 
     @Select("select \n" +

+ 10 - 0
fs-service/src/main/java/com/fs/company/mapper/CompanyVoiceRoboticCallLogCallphoneMapper.java

@@ -107,4 +107,14 @@ public interface CompanyVoiceRoboticCallLogCallphoneMapper extends BaseMapper<Co
     List<CalleeRoboticCallOutCountVO> countRoboticCallOutByCalleeIds(@Param("calleeIds") List<Long> calleeIds,
                                                                      @Param("roboticId") Long roboticId,
                                                                      @Param("companyId") Long companyId);
+
+    /**
+     * 查询待回调的记录(status=1且重试次数小于3)
+     */
+    List<CompanyVoiceRoboticCallLogCallphone> selectPendingCallbackRecords();
+
+    /**
+     * 更新重试次数和状态
+     */
+    int updateRetryCountAndStatus(CompanyVoiceRoboticCallLogCallphone record);
 }

+ 3 - 0
fs-service/src/main/java/com/fs/company/mapper/EasyCallMapper.java

@@ -26,4 +26,7 @@ public interface EasyCallMapper {
     @DataSource(DataSourceType.EASYCALL)
     InboundCallInfo selectInboundCallbackInfoByUuid(@Param("uuid") String uuid);
 
+    @DataSource(DataSourceType.EASYCALL)
+    EasyCallCallPhoneVO getCallPhoneInfoByCallBackUuid(@Param("callBackUuid") String callBackUuid);
+
 }

+ 16 - 1
fs-service/src/main/java/com/fs/company/vo/easycall/EasyCallCallPhoneVO.java

@@ -155,10 +155,15 @@ public class EasyCallCallPhoneVO {
     private String emptyNumberDetectionText;
 
     /**
-     * 客户意向
+     * 客户意向(等级字母等,对应 cc_call_phone.intent)
      */
     private String intent;
 
+    /**
+     * 意向度数值(对应 cc_call_phone.intention,多为 0~5;与 intent 并存时优先本字段)
+     */
+    private String intention;
+
     /**
      * asr时长(秒)
      */
@@ -199,6 +204,16 @@ public class EasyCallCallPhoneVO {
      */
     private String callerNumber;
 
+    /**
+     * EasyCall 公司维度(对应 cc_call_phone.company_id)
+     */
+    private Integer companyId;
+
+    /**
+     * 销售员/坐席在用户体系中的 id(对应 cc_call_phone.company_user_id)
+     */
+    private Long companyUserId;
+
     /**
      * customer dtmf input digits
      */

+ 3 - 3
fs-service/src/main/java/com/fs/company/vo/easycall/EasyCallVoiceCodeVO.java

@@ -7,14 +7,14 @@ import lombok.Data;
  */
 @Data
 public class EasyCallVoiceCodeVO {
+    /** 主键id */
+    private Integer id;
     /** 音色编号 */
     private String voiceCode;
     /** 音色名称 */
     private String voiceName;
     /** 声音源:aliyun_tts */
     private String voiceSource;
-    /**
-     * 音色模型
-     */
+    private Integer priority;
     private String ttsModels;
 }

+ 8 - 0
fs-service/src/main/java/com/fs/config/cloud/CloudHostProper.java

@@ -31,6 +31,14 @@ public class CloudHostProper {
     @Value("${cloud_host.volcengineUrl}")
     public String volcengineUrl;
 
+    //评论内容URL替换-源地址
+    @Value("${cloud_host.commentContentReplaceFrom:}")
+    public String commentContentReplaceFrom;
+
+    //评论内容URL替换-目标地址
+    @Value("${cloud_host.commentContentReplaceTo:}")
+    public String commentContentReplaceTo;
+
 //    @Value("${cloud_host.accessKey}")
 //    public String accessKey;
 //

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

@@ -115,6 +115,56 @@ public class CourseConfig implements Serializable {
     // 是否发课不发群  false否,true 是
     private Boolean roomLinkAllow;
 
+    /**
+     * 是否开启评论内容脱敏过滤
+     */
+    private Boolean enableCommentWordFilter;
+
+    /**
+     * 是否开启分类删除时校验关联课程/素材
+     */
+    private Boolean enableCategoryDeleteCheck;
+
+    /**
+     * 是否开启课程列表绑定用户校验
+     */
+    private Boolean enableCourseUserBinding;
+
+    /**
+     * 是否开启看课展示商品
+     */
+    private Boolean enableCourseGoodsShow;
+
+    /**
+     * 是否开启视频删除上架校验
+     */
+    private Boolean enableVideoDeleteOnShelfCheck;
+
+    /**
+     * 是否开启素材删除关联课程校验
+     */
+    private Boolean enableResourceDeleteCourseCheck;
+
+    /**
+     * 是否开启看课视频有效期校验
+     */
+    private Boolean enableVideoExpiredCheck;
+
+    /**
+     * 是否展示看课Tab-评价得积分
+     */
+    private Boolean showTabReviewPoints;
+
+    /**
+     * 是否展示看课Tab-栏目介绍
+     */
+    private Boolean showTabColumnIntro;
+
+    /**
+     * 是否展示看课Tab-精选留言
+     */
+    private Boolean showTabFeaturedComments;
+
 
     @Data
     public static class DisabledTimeVo{

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

@@ -50,4 +50,12 @@ public class FsUserCourseComment extends BaseEntity
 
     private Integer isDel;
 
+    /** 图片URL列表(JSON数组) */
+    @Excel(name = "图片")
+    private String images;
+
+    /** 视频URL列表(JSON数组) */
+    @Excel(name = "视频")
+    private String videos;
+
 }

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

@@ -2787,7 +2787,9 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
             }
         }
 
-        if ("北京卓美".equals(signProjectName)) {
+        String videoCheckJson = configService.selectConfigByKey("course.config");
+        CourseConfig videoCheckConfig = JSONUtil.toBean(videoCheckJson, CourseConfig.class);
+        if (videoCheckConfig != null && Boolean.TRUE.equals(videoCheckConfig.getEnableVideoExpiredCheck())) {
             FsUserCourseVideoH5VO checkVideo = fsUserCourseMapper.selectFsUserCourseVideoH5VOByVideoId(param.getVideoId());
             if (checkVideo == null
                     || (checkVideo.getIsDel() != null && checkVideo.getIsDel() == 1)

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

@@ -1,5 +1,6 @@
 package com.fs.his.param;
 
+import com.fasterxml.jackson.annotation.JsonProperty;
 import lombok.Data;
 
 import javax.validation.constraints.NotNull;
@@ -7,6 +8,7 @@ import javax.validation.constraints.NotNull;
 @Data
 public class FsIntegralOrderDoPayParam {
     @NotNull(message = "订单号不能为空")
+    @JsonProperty("orderId")
     private Long orderId;
 
     private Long userId;

+ 11 - 0
fs-service/src/main/java/com/fs/his/vo/FsIntegralOrderListUVO.java

@@ -1,6 +1,7 @@
 package com.fs.his.vo;
 
 import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
 
 import java.io.Serializable;
@@ -64,5 +65,15 @@ public class FsIntegralOrderListUVO implements Serializable {
     @JsonFormat(pattern = "yyyy-MM-dd")
     private Date deliveryTime;
 
+    @ApiModelProperty("购物车中的商品信息(多个)")
+    private String itemCartJson;
+    @ApiModelProperty("购物车中的商品对应积分(多个)")
+    private String integralByCart;
+    @ApiModelProperty("购物车中对应的商品编码(多个)")
+    private String barCodeCart;
+    @ApiModelProperty("商品对应的数量(多个)")
+    private String quantityCart;
+
+
 
 }

+ 30 - 0
fs-service/src/main/java/com/fs/hisStore/config/StoreConfig.java

@@ -29,4 +29,34 @@ public class StoreConfig implements Serializable {
     private Boolean scanCodeDiscountEnabled;//是否开启扫码立减金
     private BigDecimal scanCodeDiscountAmount;//扫码立减金额
     private Boolean checkStock;//是否检查库存,默认关闭
+
+    /**
+     * 是否开启销售订单模式(非管理员仅看自己数据+标记为销售订单)
+     */
+    private Boolean enableCompanyOrderMode;
+
+    /**
+     * 是否开启订单关联课程视频
+     */
+    private Boolean enableOrderCourseBinding;
+
+    /**
+     * 是否开启订单绑定销售
+     */
+    private Boolean enableOrderSalesBinding;
+
+    /**
+     * 是否开启运费模板校验
+     */
+    private Boolean enablePostageCheck;
+
+    /**
+     * 是否开启运费加入订单金额
+     */
+    private Boolean enablePostageAddToTotal;
+
+    /**
+     * 是否开启积分订单手机号解密
+     */
+    private Boolean enableIntegralOrderPhoneDecrypt;
 }

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

@@ -53,7 +53,7 @@ public class FsStorePaymentScrm extends BaseEntity
     private String openId;
 
     /** 业务类型 1收款 */
-    @Excel(name = "业务类型 1收款")
+    @Excel(name = "业务类型 1收款 8商城订单支付 9直播订单支付")
     private Integer businessType;
 
     /** 关联业务ID */

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

@@ -293,7 +293,7 @@ public interface FsStorePaymentScrmMapper
             "</script>"})
     List<FsStorePaymentVO> selectFsMyStorePaymentListQueryVO(@Param("maps") FsStorePaymentParam fsStorePayment);
 
-    @Select("select * from fs_store_payment_scrm where (business_type=2 or business_type=8) and order_id=#{orderId} and status=1   ")
+    @Select("select * from fs_store_payment_scrm where (business_type=2 or business_type=8 or business_type=9 ) and order_id=#{orderId} and status=1   ")
     List<FsStorePaymentScrm> selectFsStorePaymentByOrderId(Long orderId);
 
 

+ 21 - 11
fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreAfterSalesScrmServiceImpl.java

@@ -899,7 +899,7 @@ public class FsStoreAfterSalesScrmServiceImpl implements IFsStoreAfterSalesScrmS
             auditReasonName.append(":").append(reason2.getReasonName());
         }
         storeAfterSales.setAuditReasonName(auditReasonName.toString());
-        
+
         fsStoreAfterSalesMapper.updateFsStoreAfterSales(storeAfterSales);
 
         FsStoreOrderScrm order = orderService.selectFsStoreOrderByOrderCode(storeAfterSales.getOrderCode());
@@ -950,18 +950,28 @@ public class FsStoreAfterSalesScrmServiceImpl implements IFsStoreAfterSalesScrmS
         // 活动订单:先回滚Redis规格库存(确保Redis和DB库存一致)
         rollbackActivityStockIfNeeded(order, orderItemVOS);
 
-        // 获取售后商品
-        FsStoreAfterSalesItemScrm params = new FsStoreAfterSalesItemScrm();
-        params.setStoreAfterSalesId(storeAfterSales.getId());
-        List<FsStoreAfterSalesItemScrm> fsStoreAfterSalesItems = afterSalesItemService.selectFsStoreAfterSalesItemList(params);
 
-        // 退DB库存
-        for (FsStoreAfterSalesItemScrm item : fsStoreAfterSalesItems) {
-            FsStoreOrderItemVO itemVO = orderItemVOS.stream().filter(i -> i.getProductId().equals(item.getProductId())).findFirst().orElse(null);
-            if(Objects.nonNull(itemVO) && itemVO.getIsAfterSales() == 1 && Objects.nonNull(item.getNum())){
-                productService.incProductStock(item.getNum().longValue(), item.getProductId(), null);
+
+            // 获取售后商品
+            FsStoreAfterSalesItemScrm params = new FsStoreAfterSalesItemScrm();
+            params.setStoreAfterSalesId(storeAfterSales.getId());
+            List<FsStoreAfterSalesItemScrm> fsStoreAfterSalesItems = afterSalesItemService.selectFsStoreAfterSalesItemList(params);
+
+            // 退DB库存
+            for (FsStoreAfterSalesItemScrm item : fsStoreAfterSalesItems) {
+                FsStoreOrderItemVO itemVO = orderItemVOS.stream().filter(i -> i.getProductId().equals(item.getProductId())).findFirst().orElse(null);
+                if(Objects.nonNull(itemVO) && itemVO.getIsAfterSales() == 1 && Objects.nonNull(item.getNum())){
+                    // 1商城订单 2 直播订单
+                    if (order.getOrderType()==1) {
+                        productService.incProductStock(item.getNum().longValue(), item.getProductId(), null);
+                    }else if(order.getOrderType()==2){
+                    // 是个bug,直播订单合并到商城订单,购买的时候  +直播商品库存-直播商品销量,取消的时候又+商品管理库存-商品管理销量(反正销量和库存可以随便改,这里直播库存暂定不做修改了)
+
+                    }
+                }
             }
-        }
+
+
 
         //将钱退还给用户
         if(order.getPayMoney().compareTo(BigDecimal.ZERO)==1){

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

@@ -1069,7 +1069,9 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
                 storeOrder.setCompanyUserId(param.getCompanyUserId());
             }
 
-            if ("北京卓美".equals(companyName) && param.getVideoId()!=null){
+            String hisStoreJson = configService.selectConfigByKey("his.store");
+            StoreConfig hisStoreConfig = JSONUtil.toBean(hisStoreJson, StoreConfig.class);
+            if (hisStoreConfig != null && Boolean.TRUE.equals(hisStoreConfig.getEnableOrderCourseBinding()) && param.getVideoId()!=null){
                 storeOrder.setVideoId(param.getVideoId());
                 storeOrder.setCourseId(param.getCourseId());
                 storeOrder.setPeriodId(param.getPeriodId());
@@ -1079,7 +1081,7 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
             StoreConfig config= JSONUtil.toBean(json, StoreConfig.class);
 
             //卓美商城正常下单
-            if("北京卓美".equals(companyName) && param.getVideoId()!=null && storeOrder.getCompanyId() == null || !"北京卓美".equals(companyName)){
+            if(hisStoreConfig != null && Boolean.TRUE.equals(hisStoreConfig.getEnableOrderSalesBinding())){
                 //绑定销售
                 FsUserScrm fsuser= userService.selectFsUserById(userId);
                 if(ObjectUtil.isEmpty(config.getOrderAttribution())
@@ -1364,7 +1366,7 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
         Integer orderType = param.getOrderType();
         int activityDeductNum = 1; // Redis库存扣减数量(仅秒杀/限时折扣使用,异常时用于回滚)
         Long reservedGroupBuyId = null; // 团购下单阶段预占到的团ID,异常时用于释放名额
-    
+
         //活动参数校验(6=秒杀 7=限时折扣 8=限时团购)
         if (orderType == null || (orderType != 6 && orderType != 7 && orderType != 8)) {
             return R.error("无效的活动类型");
@@ -1449,13 +1451,13 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
                 param.setCompanyId(fsUserCompanyUser.getCompanyId());
                 param.setCompanyUserId(fsUserCompanyUser.getCompanyUserId());
             }
-    
+
             if (!CloudHostUtils.hasCloudHostName("鹤颜堂")){
                 if (ObjectUtil.isEmpty(param.getAddressId())){
                     return R.error("地址不能为空!");
                 }
             }
-    
+
             FsStoreOrderComputedParam computedParam = new FsStoreOrderComputedParam();
             BeanUtils.copyProperties(param, computedParam);
             //计算金额
@@ -1492,7 +1494,9 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
                 if(param.getCompanyUserId()!=null){
                     storeOrder.setCompanyUserId(param.getCompanyUserId());
                 }
-                if ("北京卓美".equals(companyName) && param.getVideoId()!=null){
+                String hisStoreJson = configService.selectConfigByKey("his.store");
+                StoreConfig hisStoreConfig = JSONUtil.toBean(hisStoreJson, StoreConfig.class);
+                if (hisStoreConfig != null && Boolean.TRUE.equals(hisStoreConfig.getEnableOrderCourseBinding()) && param.getVideoId()!=null){
                     storeOrder.setVideoId(param.getVideoId());
                     storeOrder.setCourseId(param.getCourseId());
                     storeOrder.setPeriodId(param.getPeriodId());
@@ -1500,9 +1504,9 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
                 }
                 String json = configService.selectConfigByKey("store.config");
                 StoreConfig config= JSONUtil.toBean(json, StoreConfig.class);
-    
+
                 //绑定销售
-                if("北京卓美".equals(companyName) && param.getVideoId()!=null && storeOrder.getCompanyId() == null || !("北京卓美").equals(companyName)){
+                if(hisStoreConfig != null && Boolean.TRUE.equals(hisStoreConfig.getEnableOrderSalesBinding())){
                     FsUserScrm fsuser= userService.selectFsUserById(userId);
                     if(ObjectUtil.isEmpty(config.getOrderAttribution())
                             ||!config.getOrderAttribution().equals(1)){
@@ -1523,7 +1527,7 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
                         }
                     }
                 }
-    
+
                 CompanyUserUser map=new CompanyUserUser();
                 map.setCompanyUserId(param.getCompanyUserId());
                 map.setUserId(userId);
@@ -1535,7 +1539,7 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
                         companyUserUserMapper.insertCompanyUserUser(map);
                     }
                 }
-    
+
                 storeOrder.setUserId(userId);
                 storeOrder.setOrderCode(orderSn);
                 if (ObjectUtil.isNotEmpty(address)){
@@ -1548,7 +1552,7 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
                 storeOrder.setTotalNum(Long.parseLong(String.valueOf(carts.size())));
                 storeOrder.setTotalPrice(dto.getTotalPrice());
                 storeOrder.setTotalPostage(dto.getPayPostage());
-    
+
                 //优惠券处理
                 if (param.getCouponUserId() != null) {
                     FsStoreCouponUserScrm couponUser = couponUserService.selectFsStoreCouponUserById(param.getCouponUserId());
@@ -1589,11 +1593,11 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
                 storeOrder.setIsChannel(1);
                 storeOrder.setShippingType(1);
                 storeOrder.setCreateTime(new Date());
-    
+
                 if (config.getServiceFee() != null) {
                     storeOrder.setServiceFee(config.getServiceFee());
                 }
-    
+
                 //后台制单处理
                 if (param.getPayPrice() != null && param.getPayPrice().compareTo(BigDecimal.ZERO) > 0) {
                     if (param.getPayPrice().compareTo(dto.getTotalPrice()) > 0) {
@@ -1603,7 +1607,7 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
                 } else {
                     storeOrder.setPayPrice(dto.getPayPrice());
                 }
-    
+
                 //付款方式
                 if (param.getPayType().equals("1") || param.getPayType().equals("99")) {
                     storeOrder.setStatus(0);
@@ -1654,7 +1658,7 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
                     storeOrder.setOrderType(param.getOrderType());
                     storeOrder.setOrderMedium(param.getOrderMedium());
                 }
-    
+
                 //活动商品只扣Redis(已在上文扣减),DB延后扣减
                 // storeOrder.setAssociatedId
                 storeOrder.setAssociatedId(associatedId);
@@ -1666,10 +1670,10 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
                 if (flag == 0) {
                     return R.error("订单创建失败");
                 }
-    
+
                 // 活动商品:订单创建成功后,同步扣减DB中的商品规格库存和销量(与普通下单一致)
                 this.deStockIncSale(carts);
-    
+
                 if (!isPay && storeOrder.getCompanyId() != null) {
                     addOrderAudit(storeOrder);
                 }
@@ -1677,7 +1681,7 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
                 if (dto.getUsedIntegral() > 0) {
                     this.decIntegral(userId, dto.getUsedIntegral(), dto.getDeductionPrice(), storeOrder.getId().toString());
                 }
-    
+
                 //保存OrderItem
                 List<FsStoreOrderItemScrm> listOrderItem = new ArrayList<>();
                 for (FsStoreCartQueryVO vo : carts) {
@@ -1719,17 +1723,17 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
                 }
                 //购物车状态修改
                 cartMapper.updateIsPay(cartIds);
-    
+
                 //删除缓存
                 redisCache.deleteObject("orderKey:" + param.getOrderKey());
                 if (config.getIsBrushOrders() == null || !(config.getIsBrushOrders() && param.getCompanyUserId() != null)) {
                     redisCache.deleteObject("orderCarts:" + param.getOrderKey());
                 }
-    
+
                 //添加记录
                 orderStatusService.create(storeOrder.getId(), OrderLogEnum.CREATE_ORDER.getValue(),
                         OrderLogEnum.CREATE_ORDER.getDesc());
-    
+
                 //加入redis,24小时自动取消
                 String redisKey = String.valueOf(StrUtil.format("{}{}",
                         StoreConstants.REDIS_ORDER_OUTTIME_UNPAY, storeOrder.getId()));
@@ -2371,6 +2375,9 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
         //计算金额
         FsStoreOrderScrm storeOrder = new FsStoreOrderScrm();
         String packageId = redisCache.getCacheObject("orderKey:" + param.getOrderKey());
+        //校验运费
+        String hisStoreJson2 = configService.selectConfigByKey("his.store");
+        StoreConfig hisStoreConfig2 = JSONUtil.toBean(hisStoreJson2, StoreConfig.class);
         if (packageId != null) {
             FsStoreProductPackageScrm storeProductPackage = productPackageService.selectFsStoreProductPackageById(param.getPackageId());
             if (storeProductPackage.getStatus().equals(0)) {
@@ -2432,8 +2439,8 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
                     }
                     cartService.checkProductStock(attrValue.getProductId(), attrValue.getId());
                 }
-                //校验运费
-                if ("北京卓美".equals(companyName)) {
+
+                if (hisStoreConfig2 != null && Boolean.TRUE.equals(hisStoreConfig2.getEnablePostageCheck())) {
                     try {
                         BigDecimal payPostage =getPayPostage(carts,address);
                         storeOrder.setPayPostage(payPostage);
@@ -2490,7 +2497,7 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
             }
 
             //如果运费存在加入运费金额
-            if("北京卓美".equals(companyName)){
+            if(hisStoreConfig2 != null && Boolean.TRUE.equals(hisStoreConfig2.getEnablePostageAddToTotal())){
                 if(storeOrder.getPayPostage().compareTo(BigDecimal.ZERO) == 1){
                     totalMoney = totalMoney.add(storeOrder.getPayPostage());
                 }
@@ -7041,25 +7048,37 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
      * @return
      */
     public FsStoreOrderScrm buildPayment(FsIntegralOrderDoPayParam param){
-        if(param.getType().equals("live")){
-            LiveOrder liveOrder = liveOrderMapper.selectLiveOrderByOrderId(String.valueOf(param.getOrderId()));
-            if (ObjectUtil.isNotEmpty(liveOrder)){
-                FsStoreOrderScrm orderScrm = new FsStoreOrderScrm();
-                BeanUtils.copyProperties(liveOrder,orderScrm);
-                orderScrm.setId(liveOrder.getOrderId());
-                orderScrm.setUserId(Long.valueOf(liveOrder.getUserId()));
-                orderScrm.setIsLive(true);
-                return orderScrm;
-            }
+//        if(param.getType().equals("live")){
+//            LiveOrder liveOrder = liveOrderMapper.selectLiveOrderByOrderId(String.valueOf(param.getOrderId()));
+//            if (ObjectUtil.isNotEmpty(liveOrder)){
+//                FsStoreOrderScrm orderScrm = new FsStoreOrderScrm();
+//                BeanUtils.copyProperties(liveOrder,orderScrm);
+//                orderScrm.setId(liveOrder.getOrderId());
+//                orderScrm.setUserId(Long.valueOf(liveOrder.getUserId()));
+//                orderScrm.setIsLive(true);
+//                return orderScrm;
+//            }
+//        }
+//        if (param.getType().equals("store")){
+//            FsStoreOrderScrm order = fsStoreOrderMapper.selectFsStoreOrderById(param.getOrderId());
+//            if (ObjectUtil.isNotEmpty(order)){
+//                return order;
+//            }
+//        }
+
+        FsStoreOrderScrm order = fsStoreOrderMapper.selectFsStoreOrderById(param.getOrderId());
+
+        if (ObjectUtil.isEmpty(order)){
+            log.error("订单不存在,orderId: {}, type: {}", param.getOrderId(), param.getType());
+            return null;
         }
-        if (param.getType().equals("store")){
-            FsStoreOrderScrm order = fsStoreOrderMapper.selectFsStoreOrderById(param.getOrderId());
-            if (ObjectUtil.isNotEmpty(order)){
-                return order;
-            }
+
+        if ("live".equals(param.getType())){
+            order.setIsLive(true);
+            return order;
         }
 
-        return null;
+        return order;
     }
 
 

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

@@ -1187,12 +1187,12 @@ public class FsStorePaymentScrmServiceImpl implements IFsStorePaymentScrmService
 
         // 创建记录 TODO 根据type创建支付
         FsStorePaymentScrm storePayment = new FsStorePaymentScrm();
-        if (payOrderParam.getBusinessType().getPrefix().equals("live")) {
-            LiveOrderPayment liveOrderPayment = createLiveStorePayment(payConfig, user, payOrderParam,merchantAppConfig.getMerchantId());
-            BeanUtils.copyProperties(liveOrderPayment, storePayment);
-        } else {
+//        if (payOrderParam.getBusinessType().getPrefix().equals("live")) {
+//            LiveOrderPayment liveOrderPayment = createLiveStorePayment(payConfig, user, payOrderParam,merchantAppConfig.getMerchantId());
+//            BeanUtils.copyProperties(liveOrderPayment, storePayment);
+//        } else {
             storePayment = createStorePaymentScrm(payConfig, user, payOrderParam,merchantAppConfig.getMerchantId());
-        }
+//        }
 
         // 根据配置类型创建第三方支付订单
         return createThirdPartyPaymentScrm(payConfig, storePayment, user, payOrderParam);

+ 53 - 18
fs-service/src/main/java/com/fs/live/service/impl/LiveOrderServiceImpl.java

@@ -4038,7 +4038,14 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
         LiveGoodsUploadMqVo vo = LiveGoodsUploadMqVo.builder().goodsId(goods.getGoodsId()).goodsNum(Integer.parseInt(liveOrder.getTotalNum())).build();
         try {
             log.info("订单提交MQ:{}", vo);
-            rocketMQTemplate.syncSend("live-goods-upload", JSON.toJSONString(vo));
+
+            if (("北京卓美").equals(cloudHostProper.getCompanyName())){
+                rocketMQTemplate.syncSend("live-goods-upload", JSON.toJSONString(vo));
+            }else {
+                //其他客户没有mq,暂时直接更新
+                updateStockAndSales(goods.getGoodsId(), purchaseNum);
+            }
+
         }catch (Exception e){
             log.error("更新库存失败!{}", vo, e);
         }
@@ -4134,6 +4141,7 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
 
         // 设置支付金额
         storeOrder.setPayPrice(payPrice.subtract(discountMoney));
+        storeOrder.setPayMoney(payPrice.subtract(discountMoney));
 
         // 设置订单状态
         storeOrder.setStatus(0); // 待支付
@@ -4299,7 +4307,13 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
         LiveGoodsUploadMqVo vo = LiveGoodsUploadMqVo.builder().goodsId(goods.getGoodsId()).goodsNum(Integer.parseInt(liveOrder.getTotalNum())).build();
         try {
             log.info("订单提交MQ:{}", vo);
-            rocketMQTemplate.syncSend("live-goods-upload", JSON.toJSONString(vo));
+           if (("北京卓美").equals(cloudHostProper.getCompanyName())){
+                            rocketMQTemplate.syncSend("live-goods-upload", JSON.toJSONString(vo));
+            }else {
+               //其他客户没有mq,暂时直接更新
+               updateStockAndSales(goods.getGoodsId(), purchaseNum);
+           }
+
         }catch (Exception e){
             log.error("更新库存失败!{}", vo, e);
         }
@@ -4449,6 +4463,25 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
         }
     }
 
+
+    /**
+     * 更新商品库存和销量
+     * @param goodsId 商品ID
+     * @param quantity 购买数量
+     */
+    private void updateStockAndSales(Long goodsId, Integer quantity) {
+        try {
+            int affectedRows = liveGoodsMapper.updateStock(goodsId, quantity);
+            if (affectedRows <= 0) {
+                throw new RuntimeException("库存不足或更新失败,商品ID: " + goodsId);
+            }
+            log.info("库存更新成功,商品ID: {}, 数量: {}", goodsId, quantity);
+        } catch (Exception e) {
+            log.error("库存更新失败,商品ID: {}, 数量: {}", goodsId, quantity, e);
+            throw new RuntimeException("库存更新失败: " + e.getMessage(), e);
+        }
+    }
+
     @Override
     @Transactional(rollbackFor = Throwable.class, propagation = Propagation.REQUIRED)
     public R createLiveOrderTest(LiveOrder liveOrder) {
@@ -4742,25 +4775,27 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
             // 创建订单日志
             liveOrderLogsService.create(order.getOrderId(), OrderLogEnum.CANCEL_ORDER.getValue(),
                     OrderLogEnum.CANCEL_ORDER.getDesc());
-            // 恢复库存
-            FsStoreProductScrm fsStoreProduct = fsStoreProductService.selectFsStoreProductById(liveOrder.getProductId());
-            LiveGoods goods = liveGoodsMapper.selectLiveGoodsByProductId(liveOrder.getLiveId(), liveOrder.getProductId());
-            fsStoreProduct.setStock(fsStoreProduct.getStock() + Integer.parseInt(liveOrder.getTotalNum()));
-            List<LiveOrderItem> liveOrderItems = liveOrderItemMapper.selectCheckedByOrderId(order.getOrderId());
-            List<String> barCodeList = new ArrayList<>();
-            //更新规格库存
-            for (LiveOrderItem item : liveOrderItems) {
-                FsStoreProduct cartDTO = JSONUtil.toBean(item.getJsonInfo(), FsStoreProduct.class);
-                if (StringUtils.isNotEmpty(cartDTO.getBarCode())) {
-                    barCodeList.add(cartDTO.getBarCode());
+            // 恢复库存 (暂时注释了-直播订单下单的时候 又没有减商城商品的库存加销量 这里纯bug,北京卓美的先不变)
+            if (("北京卓美").equals(cloudHostProper.getCompanyName())){
+                FsStoreProductScrm fsStoreProduct = fsStoreProductService.selectFsStoreProductById(liveOrder.getProductId());
+                fsStoreProduct.setStock(fsStoreProduct.getStock() + Integer.parseInt(liveOrder.getTotalNum()));
+                List<LiveOrderItem> liveOrderItems = liveOrderItemMapper.selectCheckedByOrderId(order.getOrderId());
+                List<String> barCodeList = new ArrayList<>();
+                //更新规格库存
+                for (LiveOrderItem item : liveOrderItems) {
+                    FsStoreProduct cartDTO = JSONUtil.toBean(item.getJsonInfo(), FsStoreProduct.class);
+                    if (StringUtils.isNotEmpty(cartDTO.getBarCode())) {
+                        barCodeList.add(cartDTO.getBarCode());
+                    }
                 }
-            }
-            if (!barCodeList.isEmpty()) {
-                attrValueScrmMapper.incStock(fsStoreProduct.getProductId(), barCodeList, liveOrder.getTotalNum());
+                if (!barCodeList.isEmpty()) {
+                    attrValueScrmMapper.incStock(fsStoreProduct.getProductId(), barCodeList, liveOrder.getTotalNum());
+                }
+//             更新商品库存
+                fsStoreProductScrmMapper.incStockDecSales(Long.valueOf(liveOrder.getTotalNum()), fsStoreProduct.getProductId());
             }
 
-            // 更新商品库存
-            fsStoreProductScrmMapper.incStockDecSales(Long.valueOf(liveOrder.getTotalNum()), fsStoreProduct.getProductId());
+            LiveGoods goods = liveGoodsMapper.selectLiveGoodsByProductId(liveOrder.getLiveId(), liveOrder.getProductId());
             goods.setStock(goods.getStock() + Long.parseLong(liveOrder.getTotalNum()));
             goods.setSales(goods.getSales() - Integer.parseInt(liveOrder.getTotalNum()));
             // 更新直播商品库存

+ 2 - 0
fs-service/src/main/resources/application-config-druid-bjzm-test.yml

@@ -92,6 +92,8 @@ cloud_host:
   projectCode: BJZM
   spaceName:
   volcengineUrl: https://bjzmvolcengine.ylrztop.com
+  commentContentReplaceFrom: https://bjzmky-1323137866.cos.ap-chongqing.myqcloud.com
+  commentContentReplaceTo: http://bjzmkytcpv.ylrzcloud.com
 headerImg:
   imgUrl:
   download_poster_url: https://bjzmky-1323137866.cos.ap-chongqing.myqcloud.com/security/2c47e4f105b641b4a49df50a77338e32.png

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

@@ -87,6 +87,8 @@ cloud_host:
   projectCode: BJZM
   spaceName:
   volcengineUrl: https://bjzmvolcengine.ylrztop.com
+  commentContentReplaceFrom: https://bjzmky-1323137866.cos.ap-chongqing.myqcloud.com
+  commentContentReplaceTo: http://bjzmkytcpv.ylrzcloud.com
 headerImg:
   imgUrl:
   download_poster_url: https://bjzmky-1323137866.cos.ap-chongqing.myqcloud.com/security/2c47e4f105b641b4a49df50a77338e32.png

+ 23 - 0
fs-service/src/main/resources/mapper/company/CompanyVoiceRoboticCallLogCallphoneMapper.xml

@@ -25,6 +25,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="companyUserId"    column="company_user_id"    />
         <result property="callTime"    column="call_time"    />
         <result property="cost"    column="cost"    />
+        <result property="retryCount"    column="retry_count"    />
     </resultMap>
 
     <sql id="selectCompanyVoiceRoboticCallLogCallphoneVo">
@@ -83,6 +84,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="companyUserId != null">company_user_id,</if>
             <if test="callTime != null">call_time,</if>
             <if test="cost != null">cost,</if>
+            <if test="retryCount != null">retry_count,</if>
          </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="callbackUuid != null">#{callbackUuid},</if>
@@ -105,6 +107,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="companyUserId != null">#{companyUserId},</if>
             <if test="callTime != null">#{callTime},</if>
             <if test="cost != null">#{cost},</if>
+            <if test="retryCount != null">#{retryCount},</if>
          </trim>
     </insert>
 
@@ -130,6 +133,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="companyUserId != null">company_user_id = #{companyUserId},</if>
             <if test="callTime != null">call_time = #{callTime},</if>
             <if test="cost != null">cost = #{cost},</if>
+            <if test="retryCount != null">retry_count = #{retryCount},</if>
         </trim>
         where log_id = #{logId}
     </update>
@@ -285,5 +289,24 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         GROUP BY caller_id
     </select>
 
+    <select id="selectPendingCallbackRecords" resultType="CompanyVoiceRoboticCallLogCallphone">
+        select * from company_voice_robotic_call_log_callphone
+        where status = 1
+          and callback_uuid is not null
+          and callback_uuid != ''
+          and (retry_count is null or retry_count &lt; 3)
+        order by create_time asc
+            limit 100
+    </select>
+
+    <update id="updateRetryCountAndStatus" parameterType="CompanyVoiceRoboticCallLogCallphone">
+        update company_voice_robotic_call_log_callphone
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="retryCount != null">retry_count = #{retryCount},</if>
+            <if test="status != null">status = #{status},</if>
+        </trim>
+        where log_id = #{logId}
+    </update>
+
 
 </mapper>

+ 4 - 3
fs-service/src/main/resources/mapper/company/EasyCallInboundLlmMapper.xml

@@ -28,9 +28,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     </resultMap>
 
     <resultMap id="VoiceCodeResult" type="com.fs.company.vo.easycall.EasyCallVoiceCodeVO">
+        <id property="id" column="id"/>
         <result property="voiceCode" column="voice_code"/>
         <result property="voiceName" column="voice_name"/>
         <result property="voiceSource" column="voice_source"/>
+        <result property="priority" column="priority"/>
     </resultMap>
 
     <resultMap id="BizGroupResult" type="com.fs.company.vo.easycall.EasyCallBizGroupVO">
@@ -61,7 +63,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 
     <select id="selectInboundLlmList" parameterType="com.fs.company.vo.easycall.EasyCallInboundLlmVO" resultMap="InboundLlmResult">
         <include refid="selectInboundLlmVo"/>
-        <where>
+        where fs_tenant_id is null
             <if test="llmAccountId != null">and llm_account_id = #{llmAccountId}</if>
             <if test="callee != null and callee != ''">and callee = #{callee}</if>
             <if test="inboundAlias != null and inboundAlias != ''">and inbound_alias = #{inboundAlias}</if>
@@ -79,7 +81,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
                     </foreach>
                 </if>
             </if>
-        </where>
         order by id desc
     </select>
 
@@ -205,7 +206,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 
     <!-- 根据音色来源查询音色列表 -->
     <select id="selectVoiceListBySource" parameterType="String" resultMap="VoiceCodeResult">
-        select voice_code, voice_name, voice_source
+        select id,voice_code, voice_name, voice_source,priority
         from cc_tts_aliyun
         where voice_source = #{voiceSource}
         and voice_enabled = 1

+ 4 - 0
fs-service/src/main/resources/mapper/company/EasyCallMapper.xml

@@ -24,4 +24,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         limit 1
     </select>
 
+    <select id="getCallPhoneInfoByCallBackUuid" resultType="com.fs.company.vo.easycall.EasyCallCallPhoneVO">
+        select * from cc_call_phone where biz_json like CONCAT('%', #{callBackUuid}, '%')
+    </select>
+
 </mapper>

+ 8 - 1
fs-service/src/main/resources/mapper/course/FsUserCourseCategoryMapper.xml

@@ -38,7 +38,14 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
          pid 有值:只查该一级下的二级;pid 无值:仅保留在公域课 fs_user_course 中 sub_cate_id 有上架使用 的二级 -->
     <select id="selectFsUserCourseCategoryAppList" parameterType="com.fs.course.param.FsUserCourseCategoryAppQueryParam" resultType="com.fs.course.domain.FsUserCourseCategory">
         SELECT
-         distinct c.cate_id, c.pid, c.cate_name, c.sort, c.is_show, c.create_time, c.update_time, c.is_del, c.cate_type
+         distinct
+        <if test="yxxTag != null and yxxTag == 1 and homePage != null and homePage == 1">
+            d.rec_home_course_top_sort,
+        </if>
+        <if test="yxxTag == null and homePage != null and homePage == 1">
+            d.rec_home_long_video_sort,
+        </if>
+        c.cate_id, c.pid, c.cate_name, c.sort, c.is_show, c.create_time, c.update_time, c.is_del, c.cate_type
         FROM fs_user_course_category c
         LEFT JOIN fs_user_course_category p ON p.cate_id = c.pid
         <if test="homePage != null and homePage == 1">

+ 8 - 9
fs-service/src/main/resources/mapper/course/FsUserCourseMapper.xml

@@ -407,14 +407,13 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         AND c.is_show = 1
         AND c.is_private = 0
 
-        <choose>
-            <when test="q.yxxTag != null and q.yxxTag == 1">
-                AND c.cate_id = (select cate_id from fs_user_course_category WHERE cate_name like '%央广原乡行%' AND cate_type = 1 limit 1)
-            </when>
-            <otherwise>
-                AND c.cate_id != (select cate_id from fs_user_course_category WHERE cate_name like '%央广原乡行%' AND cate_type = 1 limit 1)
-            </otherwise>
-        </choose>
+
+        <if test="q.yxxTag != null and q.yxxTag == 1">
+            AND c.cate_id = (select cate_id from fs_user_course_category WHERE cate_name like '%央广原乡行%' AND cate_type = 1 limit 1)
+        </if>
+        <if test="q.yxxTag == null">
+            AND c.cate_id != (select cate_id from fs_user_course_category WHERE cate_name like '%央广原乡行%' AND cate_type = 1 limit 1)
+        </if>
           AND EXISTS (
                 SELECT 1 FROM fs_user_course_category pc2
                 WHERE pc2.cate_id = c.cate_id AND pc2.cate_type = 1 AND pc2.is_del = 0
@@ -484,7 +483,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     <select id="selectDuplicateRecHomeLongVideoSort" resultType="com.fs.course.domain.FsUserCourse">
         SELECT course_id, course_name FROM fs_user_course
         WHERE is_del = 0
-        AND cate_id = #{cateId}
+
         AND rec_home_long_video_sort = #{recHomeLongVideoSort}
         <if test="excludeCourseId != null">
             AND course_id != #{excludeCourseId}

+ 8 - 1
fs-service/src/main/resources/mapper/his/FsIntegralOrderMapper.xml

@@ -26,10 +26,17 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="remark"    column="remark"    />
         <result property="barCode"    column="bar_code"    />
         <result property="loginAccount"    column="login_account"    />
+        <result property="itemCartJson"    column="item_cart_json"    />
+        <result property="integralByCart"    column="integral_by_cart"    />
+        <result property="barCodeCart"    column="bar_code_cart"    />
+        <result property="quantityCart"    column="quantity_cart"    />
     </resultMap>
 
     <sql id="selectFsIntegralOrderVo">
-        select order_id, order_code, user_id,bar_code, user_name, user_phone, user_address, item_json, integral,pay_money,is_pay,pay_time,pay_type, status, delivery_code, delivery_name, delivery_sn, delivery_time, create_time,qw_user_id,company_user_id,company_id,remark,login_account from fs_integral_order
+        select order_id, order_code, user_id,bar_code, user_name, user_phone, user_address, item_json, integral,pay_money,is_pay,pay_time,pay_type, status,
+               delivery_code, delivery_name, delivery_sn, delivery_time, create_time,qw_user_id,company_user_id,company_id,remark,login_account,
+               item_cart_json,integral_by_cart,bar_code_cart,quantity_cart
+        from fs_integral_order
     </sql>
 
     <select id="selectFsIntegralOrderList" parameterType="FsIntegralOrder" resultMap="FsIntegralOrderResult">

+ 129 - 1
fs-user-app/src/main/java/com/fs/app/controller/CourseCommentController.java

@@ -1,6 +1,7 @@
 package com.fs.app.controller;
 
 import com.fs.app.annotation.Login;
+import com.fs.app.controller.store.CourseCommentScrmController;
 import com.fs.common.annotation.Log;
 import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.domain.AjaxResult;
@@ -24,6 +25,9 @@ import com.fs.course.param.FsUserCourseCommentUParam;
 import com.fs.course.service.IFsUserCourseService;
 import com.fs.config.cloud.CloudHostProper;
 import com.fs.sensitive.ProductionWordFilter;
+import com.fs.course.config.CourseConfig;
+import com.fs.system.service.ISysConfigService;
+import cn.hutool.json.JSONUtil;
 import com.fs.course.vo.FsUserCourseCommentListUVO;
 import com.fs.course.vo.FsUserCourseCommentReplyListUVO;
 import com.fs.course.vo.FsUserCourseCommentVO;
@@ -37,6 +41,8 @@ import lombok.Data;
 import lombok.Synchronized;
 import me.chanjar.weixin.common.util.DataUtils;
 import net.bytebuddy.implementation.bytecode.Throw;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.transaction.annotation.Transactional;
@@ -44,8 +50,15 @@ import org.springframework.util.unit.DataUnit;
 import org.springframework.web.bind.annotation.*;
 
 import javax.servlet.http.HttpServletRequest;
+import com.fs.common.utils.DateUtils;
+import com.fs.system.oss.CloudStorageService;
+import com.fs.system.oss.OSSFactory;
+import org.springframework.web.multipart.MultipartFile;
+
 import java.util.Date;
 import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
 
 /**
  * 课堂评论Controller
@@ -58,6 +71,9 @@ import java.util.List;
 @RequestMapping(value="/app/course/comment")
 public class CourseCommentController extends AppBaseController
 {
+
+    private static final Logger log = LoggerFactory.getLogger(CourseCommentController.class);
+
     @Autowired
     private IFsUserCourseCommentService fsUserCourseCommentService;
 
@@ -72,6 +88,9 @@ public class CourseCommentController extends AppBaseController
 
     @Autowired
     private ProductionWordFilter productionWordFilter;
+
+    @Autowired
+    private ISysConfigService configService;
     /**
      * 查询课堂评论列表
      */
@@ -115,7 +134,10 @@ public class CourseCommentController extends AppBaseController
     public R add(@RequestBody FsUserCourseCommentAddParam param)
     {
         String content = param.getContent();
-        if ("北京卓美".equals(cloudHostProper.getCompanyName())) {
+        // 判断是否开启评论内容脱敏过滤
+        String json = configService.selectConfigByKey("course.config");
+        CourseConfig courseConfig = JSONUtil.toBean(json, CourseConfig.class);
+        if (courseConfig != null && Boolean.TRUE.equals(courseConfig.getEnableCommentWordFilter())) {
             if (content == null) {
                 content = "";
             }
@@ -145,6 +167,112 @@ public class CourseCommentController extends AppBaseController
         return R.ok();
     }
 
+    /**
+     * 新增课堂评论(含图片视频)
+     */
+    @Login
+    @ApiOperation("新增课堂评论(含图片视频)")
+    @Transactional
+    @PostMapping("/addCommentWithMedia")
+    public R addWithMedia(
+            @RequestParam(value = "courseId") Long courseId,
+            @RequestParam(value = "type") Long type,
+            @RequestParam(value = "parentId", required = false) Long parentId,
+            @RequestParam(value = "content", required = false) String content,
+            @RequestParam(value = "toUserId", required = false) Long toUserId,
+            @RequestParam(value = "imageFiles", required = false) MultipartFile[] imageFiles,
+            @RequestParam(value = "videoFiles", required = false) MultipartFile[] videoFiles
+    ) throws Exception {
+        boolean b = redisCache.setIfAbsent("comment_" + courseId + "_" + getUserId(), 1, 10, TimeUnit.MINUTES);
+        if (!b) {
+            return R.error("请勿重复提交");
+        }
+        try {
+            // 判断是否开启评论内容脱敏过滤
+            String json = configService.selectConfigByKey("course.config");
+            CourseConfig courseConfig = JSONUtil.toBean(json, CourseConfig.class);
+            if (courseConfig != null && Boolean.TRUE.equals(courseConfig.getEnableCommentWordFilter())) {
+                if (content == null) {
+                    content = "";
+                }
+                String filtered = productionWordFilter.filter(content).getFilteredText();
+                if (StringUtils.isEmpty(filtered) && (imageFiles == null || imageFiles.length == 0) && (videoFiles == null || videoFiles.length == 0)) {
+                    return R.error("评论内容无效,请重新输入");
+                }
+                content = filtered;
+            }
+
+            // 上传图片,URL拼接到content末尾
+            if (imageFiles != null && imageFiles.length > 0) {
+                long imageMaxSize = 50 * 1024 * 1024L; // 50MB
+                for (MultipartFile imageFile : imageFiles) {
+                    if (imageFile.isEmpty()) {
+                        continue;
+                    }
+                    if (imageFile.getSize() > imageMaxSize) {
+                        return R.error("图片大小不能超过50MB");
+                    }
+                    String fileName = imageFile.getOriginalFilename();
+                    String suffix = fileName.substring(fileName.lastIndexOf("."));
+                    String datePath = DateUtils.dateTime();
+                    String uuid = UUID.randomUUID().toString().replaceAll("-", "");
+                    String path = "comment/image/" + datePath + "/" + uuid + suffix;
+                    CloudStorageService storage = OSSFactory.build();
+                    String url = storage.upload(imageFile.getBytes(), path);
+                    content = (content == null ? "" : content) + "\n" + url;
+                }
+            }
+
+            // 上传视频,URL拼接到content末尾
+            if (videoFiles != null && videoFiles.length > 0) {
+                long videoMaxSize = 100 * 1024 * 1024L; // 100MB
+                for (MultipartFile videoFile : videoFiles) {
+                    if (videoFile.isEmpty()) {
+                        continue;
+                    }
+                    if (videoFile.getSize() > videoMaxSize) {
+                        return R.error("视频大小不能超过100MB");
+                    }
+                    String fileName = videoFile.getOriginalFilename();
+                    String suffix = fileName.substring(fileName.lastIndexOf("."));
+                    String datePath = DateUtils.dateTime();
+                    String uuid = UUID.randomUUID().toString().replaceAll("-", "");
+                    String path = "comment/video/" + datePath + "/" + uuid + suffix;
+                    CloudStorageService storage = OSSFactory.build();
+                    String url = storage.upload(videoFile.getBytes(), path);
+                    content = (content == null ? "" : content) + "\n" + url;
+                }
+            }
+
+            FsUserCourseComment fsUserCourseComment = new FsUserCourseComment();
+            fsUserCourseComment.setUserId(Long.parseLong(getUserId()));
+            fsUserCourseComment.setCourseId(courseId);
+            if (StringUtils.isNotEmpty(cloudHostProper.getCommentContentReplaceFrom())) {
+                content = content.replace(cloudHostProper.getCommentContentReplaceFrom(), cloudHostProper.getCommentContentReplaceTo());
+            }
+            fsUserCourseComment.setContent(content);
+            fsUserCourseComment.setType(type);
+            if (type == 1) {
+                fsUserCourseComment.setParentId(0L);
+            } else {
+                fsUserCourseComment.setParentId(parentId);
+            }
+            fsUserCourseComment.setToUserId(toUserId);
+            fsUserCourseComment.setCreateTime(new Date());
+            fsUserCourseCommentService.insertFsUserCourseComment(fsUserCourseComment);
+            if (type == 2) {
+                fsUserCourseCommentService.addComment(parentId);
+            }
+        } catch (Exception e) {
+            log.error("新增课堂评论失败", e);
+        }finally {
+            redisCache.deleteObject("comment_" + courseId + "_" + getUserId());
+        }
+
+
+        return R.ok();
+    }
+
     /**
      * 修改课堂评论
      */

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

@@ -60,6 +60,7 @@ public class AppController extends AppBaseController {
         if (AppId != null && !AppId.isEmpty()) {
             param.setAppId(AppId);
         }
+        log.info("支付宝支付:{}", param);
         return fsStoreOrderScrmService.payment(param, PaymentMethodEnum.ALIPAY);
     }
 

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

@@ -208,7 +208,7 @@ public class CourseQwController extends AppBaseController {
             if (log.getLogType()==2){
                 isFinish=1;
             }
-            if ("北京卓美".equals(companyName)){
+            if (config != null && Boolean.TRUE.equals(config.getEnableCourseGoodsShow())){
                 getGoodsAndShow(param, course);
             }
 
@@ -246,7 +246,7 @@ public class CourseQwController extends AppBaseController {
             if (log.getLogType()==2){
                 isFinish=1;
             }
-            if ("北京卓美".equals(companyName)){
+            if (config != null && Boolean.TRUE.equals(config.getEnableCourseGoodsShow())){
                 getGoodsAndShow(param, course);
             }
             return R.ok().put("course",course).put("questions",questionVOList).put("config",config).put("playDuration",duration).put("isFinish",isFinish);

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

@@ -340,7 +340,7 @@ public class LiveOrderController extends AppBaseController
         String userId= getUserId();
         log.info("开始创建订单,登录用户id:{}", userId);
         param.setUserId(userId);
-        if(CloudHostUtils.hasCloudHostName("济世百康","蒙牛")){
+        if(CloudHostUtils.hasCloudHostName("济世百康")){
             return  orderService.createLiveOrder(param);
         }else{
             return orderService.createStoreOrder(param);

+ 128 - 1
fs-user-app/src/main/java/com/fs/app/controller/store/CourseCommentScrmController.java

@@ -17,16 +17,28 @@ import com.fs.course.service.IFsUserCourseCommentService;
 import com.fs.course.vo.FsUserCourseCommentListUVO;
 import com.fs.course.vo.FsUserCourseCommentReplyListUVO;
 import com.fs.sensitive.ProductionWordFilter;
+import com.fs.course.config.CourseConfig;
+import com.fs.system.service.ISysConfigService;
+import cn.hutool.json.JSONUtil;
 import com.github.pagehelper.PageHelper;
 import com.github.pagehelper.PageInfo;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import com.fs.common.utils.DateUtils;
+import com.fs.system.oss.CloudStorageService;
+import com.fs.system.oss.OSSFactory;
 
 import java.util.Date;
 import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
 
 /**
  * 课堂评论Controller
@@ -39,6 +51,7 @@ import java.util.List;
 @RequestMapping(value="/store/app/course/comment")
 public class CourseCommentScrmController extends AppBaseController
 {
+    private static final Logger log = LoggerFactory.getLogger(CourseCommentScrmController.class);
     @Autowired
     private IFsUserCourseCommentService fsUserCourseCommentService;
 
@@ -54,6 +67,9 @@ public class CourseCommentScrmController extends AppBaseController
     @Autowired
     private ProductionWordFilter productionWordFilter;
 
+    @Autowired
+    private ISysConfigService configService;
+
     /**
      * 查询课堂评论列表
      */
@@ -97,7 +113,10 @@ public class CourseCommentScrmController extends AppBaseController
     public R add(@RequestBody FsUserCourseCommentAddParam param)
     {
         String content = param.getContent();
-        if ("北京卓美".equals(cloudHostProper.getCompanyName())) {
+        // 判断是否开启评论内容脱敏过滤
+        String json = configService.selectConfigByKey("course.config");
+        CourseConfig courseConfig = JSONUtil.toBean(json, CourseConfig.class);
+        if (courseConfig != null && Boolean.TRUE.equals(courseConfig.getEnableCommentWordFilter())) {
             if (content == null) {
                 content = "";
             }
@@ -127,6 +146,114 @@ public class CourseCommentScrmController extends AppBaseController
         return R.ok();
     }
 
+    /**
+     * 新增课堂评论(含图片视频)
+     */
+    @Login
+    @ApiOperation("新增课堂评论(含图片视频)")
+    @Transactional
+    @PostMapping("/addCommentWithMedia")
+    public R addWithMedia(
+            @RequestParam(value = "courseId") Long courseId,
+            @RequestParam(value = "type") Long type,
+            @RequestParam(value = "parentId", required = false) Long parentId,
+            @RequestParam(value = "content", required = false) String content,
+            @RequestParam(value = "toUserId", required = false) Long toUserId,
+            @RequestParam(value = "imageFiles", required = false) MultipartFile[] imageFiles,
+            @RequestParam(value = "videoFiles", required = false) MultipartFile[] videoFiles
+    ) throws Exception {
+        boolean b = redisCache.setIfAbsent("comment_" + courseId + "_" + getUserId(), 1, 10, TimeUnit.MINUTES);
+        if (!b) {
+            return R.error("请勿重复提交");
+        }
+        try {
+            // 判断是否开启评论内容脱敏过滤
+            String json = configService.selectConfigByKey("course.config");
+            CourseConfig courseConfig = JSONUtil.toBean(json, CourseConfig.class);
+            if (courseConfig != null && Boolean.TRUE.equals(courseConfig.getEnableCommentWordFilter())) {
+                if (content == null) {
+                    content = "";
+                }
+                String filtered = productionWordFilter.filter(content).getFilteredText();
+                if (StringUtils.isEmpty(filtered) && (imageFiles == null || imageFiles.length == 0) && (videoFiles == null || videoFiles.length == 0)) {
+                    return R.error("评论内容无效,请重新输入");
+                }
+                content = filtered;
+            }
+
+            // 上传图片,URL拼接到content末尾
+            if (imageFiles != null && imageFiles.length > 0) {
+                long imageMaxSize = 50 * 1024 * 1024L; // 50MB
+                for (MultipartFile imageFile : imageFiles) {
+                    if (imageFile.isEmpty()) {
+                        continue;
+                    }
+                    if (imageFile.getSize() > imageMaxSize) {
+                        return R.error("图片大小不能超过50MB");
+                    }
+                    String fileName = imageFile.getOriginalFilename();
+                    String suffix = fileName.substring(fileName.lastIndexOf("."));
+                    String datePath = DateUtils.dateTime();
+                    String uuid = UUID.randomUUID().toString().replaceAll("-", "");
+                    String path = "comment/image/" + datePath + "/" + uuid + suffix;
+                    CloudStorageService storage = OSSFactory.build();
+                    String url = storage.upload(imageFile.getBytes(), path);
+                    content = (content == null ? "" : content) + "\n" + url;
+                }
+            }
+
+            // 上传视频,URL拼接到content末尾
+            if (videoFiles != null && videoFiles.length > 0) {
+                long videoMaxSize = 100 * 1024 * 1024L; // 100MB
+                for (MultipartFile videoFile : videoFiles) {
+                    if (videoFile.isEmpty()) {
+                        continue;
+                    }
+                    if (videoFile.getSize() > videoMaxSize) {
+                        return R.error("视频大小不能超过100MB");
+                    }
+                    String fileName = videoFile.getOriginalFilename();
+                    String suffix = fileName.substring(fileName.lastIndexOf("."));
+                    String datePath = DateUtils.dateTime();
+                    String uuid = UUID.randomUUID().toString().replaceAll("-", "");
+                    String path = "comment/video/" + datePath + "/" + uuid + suffix;
+                    CloudStorageService storage = OSSFactory.build();
+                    String url = storage.upload(videoFile.getBytes(), path);
+                    content = (content == null ? "" : content) + "\n" + url;
+                }
+            }
+
+            FsUserCourseComment fsUserCourseComment = new FsUserCourseComment();
+            fsUserCourseComment.setUserId(Long.parseLong(getUserId()));
+            fsUserCourseComment.setCourseId(courseId);
+            if (StringUtils.isNotEmpty(cloudHostProper.getCommentContentReplaceFrom())) {
+                content = content.replace(cloudHostProper.getCommentContentReplaceFrom(), cloudHostProper.getCommentContentReplaceTo());
+            }
+            fsUserCourseComment.setContent(content);
+            fsUserCourseComment.setType(type);
+            if (type == 1) {
+                fsUserCourseComment.setParentId(0L);
+            } else {
+                fsUserCourseComment.setParentId(parentId);
+            }
+            fsUserCourseComment.setToUserId(toUserId);
+            fsUserCourseComment.setCreateTime(new Date());
+
+            fsUserCourseCommentService.insertFsUserCourseComment(fsUserCourseComment);
+            if (type == 2) {
+                fsUserCourseCommentService.addComment(parentId);
+            }
+        } catch (Exception e) {
+            log.error("新增课堂评论失败", e);
+        }finally {
+            redisCache.deleteObject("comment_" + courseId + "_" + getUserId());
+        }
+
+
+
+        return R.ok();
+    }
+
     /**
      * 修改课堂评论
      */

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

@@ -82,7 +82,7 @@ public class PayScrmController {
             switch (order[0]) {
                 case "store":
                 case "live":
-                    if ("live".equals(order[0]) && CloudHostUtils.hasCloudHostName("济世百康","蒙牛")) {
+                    if ("live".equals(order[0]) && CloudHostUtils.hasCloudHostName("济世百康")) {
                         liveOrderService.payConfirm(1,null,order[1], o.getHf_seq_id(),o.getOut_trans_id(),o.getParty_order_id());
                         break;
                     }