Selaa lähdekoodia

cid迁移与改动

lmx 9 tuntia sitten
vanhempi
commit
46003fc024
24 muutettua tiedostoa jossa 878 lisäystä ja 147 poistoa
  1. 70 6
      fs-company/src/main/java/com/fs/company/controller/company/CompanyInboundCallManageController.java
  2. 6 0
      fs-company/src/main/java/com/fs/company/controller/company/CompanyVoiceRoboticController.java
  3. 18 11
      fs-company/src/main/java/com/fs/company/controller/company/CompanyWxAccountController.java
  4. 22 0
      fs-service/src/main/java/com/fs/company/domain/CompanyBindGateway.java
  5. 17 0
      fs-service/src/main/java/com/fs/company/mapper/CompanyBindGatewayMapper.java
  6. 26 0
      fs-service/src/main/java/com/fs/company/param/PauseRoboticActiveParam.java
  7. 61 0
      fs-service/src/main/java/com/fs/company/service/ICompanyInboundBindService.java
  8. 2 0
      fs-service/src/main/java/com/fs/company/service/ICompanyInboundCallManageService.java
  9. 61 0
      fs-service/src/main/java/com/fs/company/service/ICompanySiptaskInfoService.java
  10. 4 0
      fs-service/src/main/java/com/fs/company/service/ICompanyVoiceRoboticService.java
  11. 93 0
      fs-service/src/main/java/com/fs/company/service/impl/CompanyInboundBindServiceImpl.java
  12. 0 11
      fs-service/src/main/java/com/fs/company/service/impl/CompanyInboundCallManageServiceImpl.java
  13. 16 0
      fs-service/src/main/java/com/fs/company/service/impl/CompanyServiceImpl.java
  14. 91 0
      fs-service/src/main/java/com/fs/company/service/impl/CompanySiptaskInfoServiceImpl.java
  15. 31 0
      fs-service/src/main/java/com/fs/company/service/impl/CompanyVoiceRoboticServiceImpl.java
  16. 3 1
      fs-service/src/main/java/com/fs/company/service/impl/call/node/AiAddWxTaskNewNode.java
  17. 5 0
      fs-service/src/main/java/com/fs/company/vo/easycall/EasyCallInboundLlmVO.java
  18. 3 0
      fs-service/src/main/java/com/fs/company/vo/easycall/EasyCallVoiceCodeVO.java
  19. 20 0
      fs-service/src/main/resources/mapper/company/CompanyBindGatewayMapper.xml
  20. 1 0
      fs-service/src/main/resources/mapper/company/CompanyVoiceRoboticCallLogCallphoneMapper.xml
  21. 4 3
      fs-service/src/main/resources/mapper/company/CompanyVoiceRoboticCallLogSendmsgMapper.xml
  22. 8 2
      fs-service/src/main/resources/mapper/company/EasyCallInboundLlmMapper.xml
  23. 203 113
      fs-wx-api/src/main/java/com/fs/app/websocket/service/WebSocketServer.java
  24. 113 0
      fs-wx-api/src/main/java/com/fs/framework/datasource/TenantDataSourceManager.java

+ 70 - 6
fs-company/src/main/java/com/fs/company/controller/company/CompanyInboundCallManageController.java

@@ -1,29 +1,32 @@
 package com.fs.company.controller.company;
 package com.fs.company.controller.company;
 
 
+import com.alibaba.fastjson.JSONObject;
 import com.fs.aicall.service.ICompanyBindAiModelService;
 import com.fs.aicall.service.ICompanyBindAiModelService;
 import com.fs.common.annotation.Log;
 import com.fs.common.annotation.Log;
 import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.enums.BusinessType;
 import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.SecurityUtils;
 import com.fs.common.utils.ServletUtils;
 import com.fs.common.utils.ServletUtils;
 import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.StringUtils;
 import com.fs.company.domain.CompanyInboundBind;
 import com.fs.company.domain.CompanyInboundBind;
 import com.fs.company.domain.EasyCallInboundCdrVO;
 import com.fs.company.domain.EasyCallInboundCdrVO;
 import com.fs.company.mapper.CompanyInboundBindMapper;
 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.mapper.EasyCallInboundLlmMapper;
 import com.fs.company.service.ICompanyInboundCallManageService;
 import com.fs.company.service.ICompanyInboundCallManageService;
 import com.fs.company.vo.easycall.*;
 import com.fs.company.vo.easycall.*;
+import com.fs.framework.datasource.TenantDataSourceManager;
 import com.fs.framework.security.LoginUser;
 import com.fs.framework.security.LoginUser;
 import com.fs.framework.service.TokenService;
 import com.fs.framework.service.TokenService;
+import com.fs.system.service.ISysConfigService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
 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;
 import java.util.stream.Collectors;
 
 
 /**
 /**
@@ -49,6 +52,17 @@ public class CompanyInboundCallManageController extends BaseController {
 
 
     @Autowired
     @Autowired
     private ICompanyBindAiModelService companyBindAiModelService;
     private ICompanyBindAiModelService companyBindAiModelService;
+    @Autowired
+    private CompanyVoiceCloneRefMapper companyVoiceCloneRefMapper;
+
+    @Autowired
+    private ISysConfigService configService;
+
+    @Autowired
+    CompanyMapper companyMapper;
+
+    @Autowired
+    TenantDataSourceManager tenantDataSourceManager;
 
 
     /**
     /**
      * 查询呼入大模型配置列表
      * 查询呼入大模型配置列表
@@ -63,6 +77,7 @@ public class CompanyInboundCallManageController extends BaseController {
         }
         }
         List<Long> ids = companyInboundBinds.stream().map(companyInboundBind -> companyInboundBind.getInboundLlmAccountId()).collect(Collectors.toList());
         List<Long> ids = companyInboundBinds.stream().map(companyInboundBind -> companyInboundBind.getInboundLlmAccountId()).collect(Collectors.toList());
         vo.setVisibleIds(ids);
         vo.setVisibleIds(ids);
+        vo.setFsTenantId(SecurityUtils.getTenantId());
         startPage();
         startPage();
         List<EasyCallInboundLlmVO> list = inboundCallManageService.selectInboundLlmList(vo);
         List<EasyCallInboundLlmVO> list = inboundCallManageService.selectInboundLlmList(vo);
         TableDataInfo rspData = getDataTable(list);
         TableDataInfo rspData = getDataTable(list);
@@ -112,9 +127,24 @@ public class CompanyInboundCallManageController extends BaseController {
     @Log(title = "呼入大模型配置", businessType = BusinessType.INSERT)
     @Log(title = "呼入大模型配置", businessType = BusinessType.INSERT)
     @PostMapping
     @PostMapping
     public AjaxResult add(@RequestBody EasyCallInboundLlmVO vo) {
     public AjaxResult add(@RequestBody EasyCallInboundLlmVO vo) {
+        Boolean b = inboundCallManageService.checkCalleeInboundLlm(vo.getCallee());
+        if(b){
+            throw new RuntimeException("被叫号码已存在,不能重复插入");
+        }
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
         vo.setCompanyId(loginUser.getUser().getCompanyId());
         vo.setCompanyId(loginUser.getUser().getCompanyId());
-        return toAjax(inboundCallManageService.insertInboundLlm(vo));
+        vo.setFsTenantId(SecurityUtils.getTenantId());
+        tenantDataSourceManager.ensureSwitchByTenantId(vo.getFsTenantId());
+        int i = inboundCallManageService.insertInboundLlm(vo);
+        tenantDataSourceManager.ensureSwitchByTenantId(vo.getFsTenantId());
+        if(i >0 && vo.getId()!= null) {
+            CompanyInboundBind bind = new CompanyInboundBind();
+            bind.setInboundLlmAccountId(Long.valueOf(vo.getId()));
+            bind.setCompanyId(vo.getCompanyId());
+            bind.setCreateTime(new Date());
+            companyInboundBindMapper.insertCompanyInboundBind(bind);
+        }
+        return toAjax(true);
     }
     }
 
 
     /**
     /**
@@ -207,8 +237,23 @@ public class CompanyInboundCallManageController extends BaseController {
      */
      */
     @GetMapping("/voiceList")
     @GetMapping("/voiceList")
     public AjaxResult getVoiceList(@RequestParam("voiceSource") String voiceSource) {
     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);
         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);
     }
     }
 
 
     /**
     /**
@@ -225,7 +270,25 @@ public class CompanyInboundCallManageController extends BaseController {
      */
      */
     @GetMapping("/gatewayList")
     @GetMapping("/gatewayList")
     public AjaxResult getGatewayList() {
     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();
         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);
         return AjaxResult.success(list);
     }
     }
 
 
@@ -261,6 +324,7 @@ public class CompanyInboundCallManageController extends BaseController {
         // 通过llmAccountIds查询对应的callee列表
         // 通过llmAccountIds查询对应的callee列表
         EasyCallInboundLlmVO query = new EasyCallInboundLlmVO();
         EasyCallInboundLlmVO query = new EasyCallInboundLlmVO();
         query.setVisibleIds(llmAccountIds);
         query.setVisibleIds(llmAccountIds);
+        query.setFsTenantId(SecurityUtils.getTenantId());
         List<EasyCallInboundLlmVO> llmConfigs = inboundCallManageService.selectInboundLlmList(query);
         List<EasyCallInboundLlmVO> llmConfigs = inboundCallManageService.selectInboundLlmList(query);
         List<String> calleeList = llmConfigs.stream()
         List<String> calleeList = llmConfigs.stream()
                 .map(EasyCallInboundLlmVO::getCallee)
                 .map(EasyCallInboundLlmVO::getCallee)

+ 6 - 0
fs-company/src/main/java/com/fs/company/controller/company/CompanyVoiceRoboticController.java

@@ -22,6 +22,7 @@ import com.fs.common.utils.poi.ExcelUtil;
 import com.fs.company.domain.CompanyVoiceRobotic;
 import com.fs.company.domain.CompanyVoiceRobotic;
 import com.fs.company.domain.CompanyVoiceRoboticCallees;
 import com.fs.company.domain.CompanyVoiceRoboticCallees;
 import com.fs.company.domain.CompanyVoiceRoboticWx;
 import com.fs.company.domain.CompanyVoiceRoboticWx;
+import com.fs.company.param.PauseRoboticActiveParam;
 import com.fs.company.service.ICompanyVoiceRoboticCallLogCallphoneService;
 import com.fs.company.service.ICompanyVoiceRoboticCallLogCallphoneService;
 import com.fs.company.service.ICompanyVoiceRoboticCalleesService;
 import com.fs.company.service.ICompanyVoiceRoboticCalleesService;
 import com.fs.company.service.ICompanyVoiceRoboticService;
 import com.fs.company.service.ICompanyVoiceRoboticService;
@@ -351,4 +352,9 @@ public class CompanyVoiceRoboticController extends BaseController
             }
             }
         });
         });
     }
     }
+
+    @PostMapping("/pauseRoboticActive")
+    public R pauseRoboticActive(@RequestBody PauseRoboticActiveParam param){
+        return companyVoiceRoboticService.pauseRoboticActive(param);
+    }
 }
 }

+ 18 - 11
fs-company/src/main/java/com/fs/company/controller/company/CompanyWxAccountController.java

@@ -6,10 +6,13 @@ import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.enums.BusinessType;
 import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.ServletUtils;
 import com.fs.common.utils.poi.ExcelUtil;
 import com.fs.common.utils.poi.ExcelUtil;
 import com.fs.company.domain.CompanyUser;
 import com.fs.company.domain.CompanyUser;
 import com.fs.company.domain.CompanyWxAccount;
 import com.fs.company.domain.CompanyWxAccount;
 import com.fs.company.service.ICompanyWxAccountService;
 import com.fs.company.service.ICompanyWxAccountService;
+import com.fs.framework.security.LoginUser;
+import com.fs.framework.service.TokenService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.bind.annotation.*;
@@ -28,11 +31,13 @@ public class CompanyWxAccountController extends BaseController
 {
 {
     @Autowired
     @Autowired
     private ICompanyWxAccountService companyWxAccountService;
     private ICompanyWxAccountService companyWxAccountService;
+    @Autowired
+    private TokenService tokenService;
 
 
     /**
     /**
      * 查询企微账号列表
      * 查询企微账号列表
      */
      */
-    @PreAuthorize("@ss.hasPermi('company:companyAccount:list')")
+    @PreAuthorize("@ss.hasPermi('company:companyWx:list')")
     @GetMapping("/list")
     @GetMapping("/list")
     public TableDataInfo list(CompanyWxAccount companyWxEnterpriseAccount)
     public TableDataInfo list(CompanyWxAccount companyWxEnterpriseAccount)
     {
     {
@@ -43,7 +48,7 @@ public class CompanyWxAccountController extends BaseController
     /**
     /**
      * 查询企微账号列表
      * 查询企微账号列表
      */
      */
-    @PreAuthorize("@ss.hasPermi('company:companyAccount:list')")
+    @PreAuthorize("@ss.hasPermi('company:companyWx:list')")
     @GetMapping("/listAll")
     @GetMapping("/listAll")
     public R listAll(CompanyWxAccount companyWxEnterpriseAccount){
     public R listAll(CompanyWxAccount companyWxEnterpriseAccount){
         List<CompanyWxAccount> list = companyWxAccountService.selectCompanyWxAccountListCompany(companyWxEnterpriseAccount);
         List<CompanyWxAccount> list = companyWxAccountService.selectCompanyWxAccountListCompany(companyWxEnterpriseAccount);
@@ -53,7 +58,7 @@ public class CompanyWxAccountController extends BaseController
     /**
     /**
      * 导出企微账号列表
      * 导出企微账号列表
      */
      */
-    @PreAuthorize("@ss.hasPermi('company:companyAccount:export')")
+    @PreAuthorize("@ss.hasPermi('company:companyWx:export')")
     @Log(title = "企微账号", businessType = BusinessType.EXPORT)
     @Log(title = "企微账号", businessType = BusinessType.EXPORT)
     @GetMapping("/export")
     @GetMapping("/export")
     public AjaxResult export(CompanyWxAccount companyWxEnterpriseAccount)
     public AjaxResult export(CompanyWxAccount companyWxEnterpriseAccount)
@@ -66,7 +71,7 @@ public class CompanyWxAccountController extends BaseController
     /**
     /**
      * 获取企微账号详细信息
      * 获取企微账号详细信息
      */
      */
-    @PreAuthorize("@ss.hasPermi('company:companyAccount:query')")
+    @PreAuthorize("@ss.hasPermi('company:companyWx:query')")
     @GetMapping(value = "/{id}")
     @GetMapping(value = "/{id}")
     public AjaxResult getInfo(@PathVariable("id") Long id)
     public AjaxResult getInfo(@PathVariable("id") Long id)
     {
     {
@@ -76,18 +81,20 @@ public class CompanyWxAccountController extends BaseController
     /**
     /**
      * 新增企微账号
      * 新增企微账号
      */
      */
-    @PreAuthorize("@ss.hasPermi('company:companyAccount:add')")
+    @PreAuthorize("@ss.hasPermi('company:companyWx:add')")
     @Log(title = "企微账号", businessType = BusinessType.INSERT)
     @Log(title = "企微账号", businessType = BusinessType.INSERT)
     @PostMapping
     @PostMapping
-    public AjaxResult add(@RequestBody CompanyWxAccount companyWxEnterpriseAccount)
-    {
-        return toAjax(companyWxAccountService.insertCompanyWxAccount(companyWxEnterpriseAccount));
+    public AjaxResult add(@RequestBody CompanyWxAccount account){
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        account.setCompanyId(loginUser.getCompany().getCompanyId());
+        account.setCompanyUserId(loginUser.getUser().getUserId());
+        return toAjax(companyWxAccountService.insertCompanyWxAccount(account));
     }
     }
 
 
     /**
     /**
      * 修改企微账号
      * 修改企微账号
      */
      */
-    @PreAuthorize("@ss.hasPermi('company:companyAccount:edit')")
+    @PreAuthorize("@ss.hasPermi('company:companyWx:edit')")
     @Log(title = "企微账号", businessType = BusinessType.UPDATE)
     @Log(title = "企微账号", businessType = BusinessType.UPDATE)
     @PutMapping
     @PutMapping
     public AjaxResult edit(@RequestBody CompanyWxAccount companyWxEnterpriseAccount)
     public AjaxResult edit(@RequestBody CompanyWxAccount companyWxEnterpriseAccount)
@@ -98,7 +105,7 @@ public class CompanyWxAccountController extends BaseController
     /**
     /**
      * 删除企微账号
      * 删除企微账号
      */
      */
-    @PreAuthorize("@ss.hasPermi('company:companyAccount:remove')")
+    @PreAuthorize("@ss.hasPermi('company:companyWx:remove')")
     @Log(title = "企微账号", businessType = BusinessType.DELETE)
     @Log(title = "企微账号", businessType = BusinessType.DELETE)
 	@DeleteMapping("/{ids}")
 	@DeleteMapping("/{ids}")
     public AjaxResult remove(@PathVariable Long[] ids)
     public AjaxResult remove(@PathVariable Long[] ids)
@@ -108,7 +115,7 @@ public class CompanyWxAccountController extends BaseController
     /**
     /**
      * 删除企微账号
      * 删除企微账号
      */
      */
-    @PreAuthorize("@ss.hasPermi('company:companyAccount:list')")
+    @PreAuthorize("@ss.hasPermi('company:companyWx:list')")
 	@GetMapping("/companyListAll")
 	@GetMapping("/companyListAll")
     public R companyListAll(){
     public R companyListAll(){
         return R.ok().put("data", companyWxAccountService.companyListAllCompany(new CompanyUser()));
         return R.ok().put("data", companyWxAccountService.companyListAllCompany(new CompanyUser()));

+ 22 - 0
fs-service/src/main/java/com/fs/company/domain/CompanyBindGateway.java

@@ -0,0 +1,22 @@
+package com.fs.company.domain;
+
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * @author MixLiu
+ * @date 2026/4/1 16:22
+ * @description
+ */
+@Data
+public class CompanyBindGateway {
+    private  Integer id;
+    //网关线路id
+    private Long gatewayId;
+    //公司id
+    private Long companyId;
+    //创建时间
+    private Date createTime;
+
+}

+ 17 - 0
fs-service/src/main/java/com/fs/company/mapper/CompanyBindGatewayMapper.java

@@ -0,0 +1,17 @@
+package com.fs.company.mapper;
+
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * @author MixLiu
+ * @date 2026/4/1 16:34
+ * @description
+ */
+public interface CompanyBindGatewayMapper {
+
+    int deleteDataByCompanyId(@Param("companyId") Long companyId);
+
+    int insertData(@Param("companyId") Long companyId, @Param("gatewayIds") List<Long> gatewayIds);
+}

+ 26 - 0
fs-service/src/main/java/com/fs/company/param/PauseRoboticActiveParam.java

@@ -0,0 +1,26 @@
+package com.fs.company.param;
+
+import lombok.Data;
+
+/**
+ * @author MixLiu
+ * @date 2026/4/29 14:33
+ * @description
+ */
+
+@Data
+public class PauseRoboticActiveParam {
+
+    /**
+     * 任务id
+     */
+    private Long taskId;
+
+    /**
+     * 操作类型 1、暂停 2、继续
+     */
+    private Integer activeType;
+
+
+
+}

+ 61 - 0
fs-service/src/main/java/com/fs/company/service/ICompanyInboundBindService.java

@@ -0,0 +1,61 @@
+package com.fs.company.service;
+
+import java.util.List;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.fs.company.domain.CompanyInboundBind;
+
+/**
+ * 呼入线路模型绑定关系Service接口
+ * 
+ * @author fs
+ * @date 2026-04-27
+ */
+public interface ICompanyInboundBindService extends IService<CompanyInboundBind>{
+    /**
+     * 查询呼入线路模型绑定关系
+     * 
+     * @param id 呼入线路模型绑定关系主键
+     * @return 呼入线路模型绑定关系
+     */
+    CompanyInboundBind selectCompanyInboundBindById(Long id);
+
+    /**
+     * 查询呼入线路模型绑定关系列表
+     * 
+     * @param companyInboundBind 呼入线路模型绑定关系
+     * @return 呼入线路模型绑定关系集合
+     */
+    List<CompanyInboundBind> selectCompanyInboundBindList(CompanyInboundBind companyInboundBind);
+
+    /**
+     * 新增呼入线路模型绑定关系
+     * 
+     * @param companyInboundBind 呼入线路模型绑定关系
+     * @return 结果
+     */
+    int insertCompanyInboundBind(CompanyInboundBind companyInboundBind);
+
+    /**
+     * 修改呼入线路模型绑定关系
+     * 
+     * @param companyInboundBind 呼入线路模型绑定关系
+     * @return 结果
+     */
+    int updateCompanyInboundBind(CompanyInboundBind companyInboundBind);
+
+    /**
+     * 批量删除呼入线路模型绑定关系
+     * 
+     * @param ids 需要删除的呼入线路模型绑定关系主键集合
+     * @return 结果
+     */
+    int deleteCompanyInboundBindByIds(Long[] ids);
+
+    /**
+     * 删除呼入线路模型绑定关系信息
+     * 
+     * @param id 呼入线路模型绑定关系主键
+     * @return 结果
+     */
+    int deleteCompanyInboundBindById(Long id);
+}

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

@@ -75,4 +75,6 @@ public interface ICompanyInboundCallManageService {
      * @return 通话记录列表
      * @return 通话记录列表
      */
      */
     List<EasyCallInboundCdrVO> selectInboundCdrList(EasyCallInboundCdrVO vo);
     List<EasyCallInboundCdrVO> selectInboundCdrList(EasyCallInboundCdrVO vo);
+
+    Boolean checkCalleeInboundLlm(String callee);
 }
 }

+ 61 - 0
fs-service/src/main/java/com/fs/company/service/ICompanySiptaskInfoService.java

@@ -0,0 +1,61 @@
+package com.fs.company.service;
+
+import java.util.List;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.fs.company.domain.CompanySiptaskInfo;
+
+/**
+ * 任务与外呼sip任务关联关系Service接口
+ * 
+ * @author fs
+ * @date 2026-04-20
+ */
+public interface ICompanySiptaskInfoService extends IService<CompanySiptaskInfo>{
+    /**
+     * 查询任务与外呼sip任务关联关系
+     * 
+     * @param id 任务与外呼sip任务关联关系主键
+     * @return 任务与外呼sip任务关联关系
+     */
+    CompanySiptaskInfo selectCompanySiptaskInfoById(Long id);
+
+    /**
+     * 查询任务与外呼sip任务关联关系列表
+     * 
+     * @param companySiptaskInfo 任务与外呼sip任务关联关系
+     * @return 任务与外呼sip任务关联关系集合
+     */
+    List<CompanySiptaskInfo> selectCompanySiptaskInfoList(CompanySiptaskInfo companySiptaskInfo);
+
+    /**
+     * 新增任务与外呼sip任务关联关系
+     * 
+     * @param companySiptaskInfo 任务与外呼sip任务关联关系
+     * @return 结果
+     */
+    int insertCompanySiptaskInfo(CompanySiptaskInfo companySiptaskInfo);
+
+    /**
+     * 修改任务与外呼sip任务关联关系
+     * 
+     * @param companySiptaskInfo 任务与外呼sip任务关联关系
+     * @return 结果
+     */
+    int updateCompanySiptaskInfo(CompanySiptaskInfo companySiptaskInfo);
+
+    /**
+     * 批量删除任务与外呼sip任务关联关系
+     * 
+     * @param ids 需要删除的任务与外呼sip任务关联关系主键集合
+     * @return 结果
+     */
+    int deleteCompanySiptaskInfoByIds(Long[] ids);
+
+    /**
+     * 删除任务与外呼sip任务关联关系信息
+     * 
+     * @param id 任务与外呼sip任务关联关系主键
+     * @return 结果
+     */
+    int deleteCompanySiptaskInfoById(Long id);
+}

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

@@ -3,8 +3,10 @@ package com.fs.company.service;
 import com.baomidou.mybatisplus.extension.service.IService;
 import com.baomidou.mybatisplus.extension.service.IService;
 import com.fs.aicall.domain.apiresult.PushIIntentionResult;
 import com.fs.aicall.domain.apiresult.PushIIntentionResult;
 import com.fs.aicall.domain.result.CalltaskcreateaiCustomizeResult;
 import com.fs.aicall.domain.result.CalltaskcreateaiCustomizeResult;
+import com.fs.common.core.domain.R;
 import com.fs.company.domain.CompanyVoiceRobotic;
 import com.fs.company.domain.CompanyVoiceRobotic;
 import com.fs.company.param.ExecutionContext;
 import com.fs.company.param.ExecutionContext;
+import com.fs.company.param.PauseRoboticActiveParam;
 import com.fs.company.vo.*;
 import com.fs.company.vo.*;
 
 
 import java.util.List;
 import java.util.List;
@@ -112,4 +114,6 @@ public interface ICompanyVoiceRoboticService extends IService<CompanyVoiceRoboti
      * @param traceId
      * @param traceId
      */
      */
     void addNewExec4Task(Long taskId,Long crmCustomerId,String traceId);
     void addNewExec4Task(Long taskId,Long crmCustomerId,String traceId);
+
+    R pauseRoboticActive(PauseRoboticActiveParam param);
 }
 }

+ 93 - 0
fs-service/src/main/java/com/fs/company/service/impl/CompanyInboundBindServiceImpl.java

@@ -0,0 +1,93 @@
+package com.fs.company.service.impl;
+
+import java.util.List;
+import com.fs.common.utils.DateUtils;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.fs.company.mapper.CompanyInboundBindMapper;
+import com.fs.company.domain.CompanyInboundBind;
+import com.fs.company.service.ICompanyInboundBindService;
+
+/**
+ * 呼入线路模型绑定关系Service业务层处理
+ * 
+ * @author fs
+ * @date 2026-04-27
+ */
+@Service
+public class CompanyInboundBindServiceImpl extends ServiceImpl<CompanyInboundBindMapper, CompanyInboundBind> implements ICompanyInboundBindService {
+
+    /**
+     * 查询呼入线路模型绑定关系
+     * 
+     * @param id 呼入线路模型绑定关系主键
+     * @return 呼入线路模型绑定关系
+     */
+    @Override
+    public CompanyInboundBind selectCompanyInboundBindById(Long id)
+    {
+        return baseMapper.selectCompanyInboundBindById(id);
+    }
+
+    /**
+     * 查询呼入线路模型绑定关系列表
+     * 
+     * @param companyInboundBind 呼入线路模型绑定关系
+     * @return 呼入线路模型绑定关系
+     */
+    @Override
+    public List<CompanyInboundBind> selectCompanyInboundBindList(CompanyInboundBind companyInboundBind)
+    {
+        return baseMapper.selectCompanyInboundBindList(companyInboundBind);
+    }
+
+    /**
+     * 新增呼入线路模型绑定关系
+     * 
+     * @param companyInboundBind 呼入线路模型绑定关系
+     * @return 结果
+     */
+    @Override
+    public int insertCompanyInboundBind(CompanyInboundBind companyInboundBind)
+    {
+        companyInboundBind.setCreateTime(DateUtils.getNowDate());
+        return baseMapper.insertCompanyInboundBind(companyInboundBind);
+    }
+
+    /**
+     * 修改呼入线路模型绑定关系
+     * 
+     * @param companyInboundBind 呼入线路模型绑定关系
+     * @return 结果
+     */
+    @Override
+    public int updateCompanyInboundBind(CompanyInboundBind companyInboundBind)
+    {
+        return baseMapper.updateCompanyInboundBind(companyInboundBind);
+    }
+
+    /**
+     * 批量删除呼入线路模型绑定关系
+     * 
+     * @param ids 需要删除的呼入线路模型绑定关系主键
+     * @return 结果
+     */
+    @Override
+    public int deleteCompanyInboundBindByIds(Long[] ids)
+    {
+        return baseMapper.deleteCompanyInboundBindByIds(ids);
+    }
+
+    /**
+     * 删除呼入线路模型绑定关系信息
+     * 
+     * @param id 呼入线路模型绑定关系主键
+     * @return 结果
+     */
+    @Override
+    public int deleteCompanyInboundBindById(Long id)
+    {
+        return baseMapper.deleteCompanyInboundBindById(id);
+    }
+}

+ 0 - 11
fs-service/src/main/java/com/fs/company/service/impl/CompanyInboundCallManageServiceImpl.java

@@ -78,10 +78,6 @@ public class CompanyInboundCallManageServiceImpl implements ICompanyInboundCallM
      */
      */
     @Override
     @Override
     public int insertInboundLlm(EasyCallInboundLlmVO vo) {
     public int insertInboundLlm(EasyCallInboundLlmVO vo) {
-        Boolean b = checkCalleeInboundLlm(vo.getCallee());
-        if(b){
-          throw new RuntimeException("被叫号码已存在,不能重复插入");
-        }
         try {
         try {
             //获得总后台配置
             //获得总后台配置
             SysConfig cidConf = sysConfigService.selectConfigByConfigKey("cId.config");
             SysConfig cidConf = sysConfigService.selectConfigByConfigKey("cId.config");
@@ -99,13 +95,6 @@ public class CompanyInboundCallManageServiceImpl implements ICompanyInboundCallM
         }
         }
         
         
         int i = inboundLlmMapper.insertInboundLlm(vo);
         int i = inboundLlmMapper.insertInboundLlm(vo);
-        if(i >0 && vo.getId()!= null) {
-            CompanyInboundBind bind = new CompanyInboundBind();
-            bind.setInboundLlmAccountId(Long.valueOf(vo.getId()));
-            bind.setCompanyId(vo.getCompanyId());
-            bind.setCreateTime(new Date());
-            companyInboundBindMapper.insertCompanyInboundBind( bind);
-        }
         return i;
         return i;
     }
     }
 
 

+ 16 - 0
fs-service/src/main/java/com/fs/company/service/impl/CompanyServiceImpl.java

@@ -140,6 +140,8 @@ public class CompanyServiceImpl implements ICompanyService
 
 
     @Autowired
     @Autowired
     private ICompanyConfigService companyConfigService;
     private ICompanyConfigService companyConfigService;
+    @Autowired
+    CompanyBindGatewayMapper companyBindGatewayMapper;
 
 
 
 
     @Override
     @Override
@@ -534,8 +536,22 @@ public class CompanyServiceImpl implements ICompanyService
         if(company.isUpdateMiniApp()){
         if(company.isUpdateMiniApp()){
             bindMiniApp(company);
             bindMiniApp(company);
         }
         }
+        if(null != company.getShowGatewayIds() && !company.getShowGatewayIds().isEmpty()){
+            handleCompanyBindGateway(company.getCompanyId(),company.getShowGatewayIds());
+        }
         return companyMapper.updateCompany(company);
         return companyMapper.updateCompany(company);
     }
     }
+
+    /**
+     * 处理公司绑定网关路线
+     * @param companyId
+     * @param gatewayIds
+     */
+    public void handleCompanyBindGateway(Long companyId,List<Long> gatewayIds){
+       companyBindGatewayMapper.deleteDataByCompanyId(companyId);
+       companyBindGatewayMapper.insertData(companyId, gatewayIds);
+    }
+
     // 绑定小程序
     // 绑定小程序
     public void bindMiniApp(Company company){
     public void bindMiniApp(Company company){
         companyMiniappService.removeByCompanyId(company.getCompanyId());
         companyMiniappService.removeByCompanyId(company.getCompanyId());

+ 91 - 0
fs-service/src/main/java/com/fs/company/service/impl/CompanySiptaskInfoServiceImpl.java

@@ -0,0 +1,91 @@
+package com.fs.company.service.impl;
+
+import java.util.List;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.fs.company.mapper.CompanySiptaskInfoMapper;
+import com.fs.company.domain.CompanySiptaskInfo;
+import com.fs.company.service.ICompanySiptaskInfoService;
+
+/**
+ * 任务与外呼sip任务关联关系Service业务层处理
+ * 
+ * @author fs
+ * @date 2026-04-20
+ */
+@Service
+public class CompanySiptaskInfoServiceImpl extends ServiceImpl<CompanySiptaskInfoMapper, CompanySiptaskInfo> implements ICompanySiptaskInfoService {
+
+    /**
+     * 查询任务与外呼sip任务关联关系
+     * 
+     * @param id 任务与外呼sip任务关联关系主键
+     * @return 任务与外呼sip任务关联关系
+     */
+    @Override
+    public CompanySiptaskInfo selectCompanySiptaskInfoById(Long id)
+    {
+        return baseMapper.selectCompanySiptaskInfoById(id);
+    }
+
+    /**
+     * 查询任务与外呼sip任务关联关系列表
+     * 
+     * @param companySiptaskInfo 任务与外呼sip任务关联关系
+     * @return 任务与外呼sip任务关联关系
+     */
+    @Override
+    public List<CompanySiptaskInfo> selectCompanySiptaskInfoList(CompanySiptaskInfo companySiptaskInfo)
+    {
+        return baseMapper.selectCompanySiptaskInfoList(companySiptaskInfo);
+    }
+
+    /**
+     * 新增任务与外呼sip任务关联关系
+     * 
+     * @param companySiptaskInfo 任务与外呼sip任务关联关系
+     * @return 结果
+     */
+    @Override
+    public int insertCompanySiptaskInfo(CompanySiptaskInfo companySiptaskInfo)
+    {
+        return baseMapper.insertCompanySiptaskInfo(companySiptaskInfo);
+    }
+
+    /**
+     * 修改任务与外呼sip任务关联关系
+     * 
+     * @param companySiptaskInfo 任务与外呼sip任务关联关系
+     * @return 结果
+     */
+    @Override
+    public int updateCompanySiptaskInfo(CompanySiptaskInfo companySiptaskInfo)
+    {
+        return baseMapper.updateCompanySiptaskInfo(companySiptaskInfo);
+    }
+
+    /**
+     * 批量删除任务与外呼sip任务关联关系
+     * 
+     * @param ids 需要删除的任务与外呼sip任务关联关系主键
+     * @return 结果
+     */
+    @Override
+    public int deleteCompanySiptaskInfoByIds(Long[] ids)
+    {
+        return baseMapper.deleteCompanySiptaskInfoByIds(ids);
+    }
+
+    /**
+     * 删除任务与外呼sip任务关联关系信息
+     * 
+     * @param id 任务与外呼sip任务关联关系主键
+     * @return 结果
+     */
+    @Override
+    public int deleteCompanySiptaskInfoById(Long id)
+    {
+        return baseMapper.deleteCompanySiptaskInfoById(id);
+    }
+}

+ 31 - 0
fs-service/src/main/java/com/fs/company/service/impl/CompanyVoiceRoboticServiceImpl.java

@@ -15,6 +15,7 @@ import com.fs.aicall.service.AiCallService;
 import com.fs.common.annotation.DataScope;
 import com.fs.common.annotation.DataScope;
 import com.fs.common.config.RedisTenantContext;
 import com.fs.common.config.RedisTenantContext;
 import com.fs.common.constant.Constants;
 import com.fs.common.constant.Constants;
+import com.fs.common.core.domain.R;
 import com.fs.common.core.domain.entity.SysDictData;
 import com.fs.common.core.domain.entity.SysDictData;
 import com.fs.common.core.domain.model.TenantPrincipal;
 import com.fs.common.core.domain.model.TenantPrincipal;
 import com.fs.common.core.redis.RedisCache;
 import com.fs.common.core.redis.RedisCache;
@@ -27,6 +28,7 @@ import com.fs.common.utils.spring.SpringUtils;
 import com.fs.company.domain.*;
 import com.fs.company.domain.*;
 import com.fs.company.mapper.*;
 import com.fs.company.mapper.*;
 import com.fs.company.param.ExecutionContext;
 import com.fs.company.param.ExecutionContext;
+import com.fs.company.param.PauseRoboticActiveParam;
 import com.fs.company.service.*;
 import com.fs.company.service.*;
 import com.fs.company.vo.*;
 import com.fs.company.vo.*;
 import com.fs.company.vo.easycall.EasyCallCallPhoneVO;
 import com.fs.company.vo.easycall.EasyCallCallPhoneVO;
@@ -1942,4 +1944,33 @@ public class CompanyVoiceRoboticServiceImpl extends ServiceImpl<CompanyVoiceRobo
             return new HashMap<>();
             return new HashMap<>();
         }
         }
     }
     }
+
+    //暂停
+    private final Integer ACTIVE_TYPE_PAUSE = 1;
+    //继续
+    private final Integer ACTIVE_TYPE_CONTINUE = 2;
+
+    /**
+     * 任务暂停 & 恢复操作
+     *
+     * @param param
+     * @return
+     */
+    @Override
+    public R pauseRoboticActive(PauseRoboticActiveParam param) {
+        //暂停任务
+        if (ACTIVE_TYPE_PAUSE.equals(param.getActiveType())) {
+
+            // 暂停任务更新
+
+            // 暂停任务创建的三方外呼任务
+
+        }
+        //恢复任务继续进入可运行
+        else if (ACTIVE_TYPE_CONTINUE.equals(param.getActiveType())) {
+
+        }
+
+        return R.ok("操作成功");
+    }
 }
 }

+ 3 - 1
fs-service/src/main/java/com/fs/company/service/impl/call/node/AiAddWxTaskNewNode.java

@@ -25,6 +25,7 @@ import com.fs.system.service.ISysConfigService;
 import com.fs.wxcid.domain.WxContact;
 import com.fs.wxcid.domain.WxContact;
 import com.fs.wxcid.mapper.CidIpadServerMapper;
 import com.fs.wxcid.mapper.CidIpadServerMapper;
 import com.fs.wxcid.mapper.WxContactMapper;
 import com.fs.wxcid.mapper.WxContactMapper;
+import com.fs.wxcid.utils.TenantHelper;
 import com.fs.wxwork.service.WxIpadService;
 import com.fs.wxwork.service.WxIpadService;
 import lombok.extern.slf4j.Slf4j;
 import lombok.extern.slf4j.Slf4j;
 
 
@@ -306,7 +307,8 @@ public class AiAddWxTaskNewNode extends AbstractWorkflowNode {
             JSONObject bizJson = new JSONObject()
             JSONObject bizJson = new JSONObject()
                     .fluentPut("instanceId",instanceId)
                     .fluentPut("instanceId",instanceId)
                     .fluentPut("nodeKey",nodeKey)
                     .fluentPut("nodeKey",nodeKey)
-                    .fluentPut("accountId",companyWxAccount.getId());
+                    .fluentPut("accountId",companyWxAccount.getId())
+                    .fluentPut("tenantId", TenantHelper.getTenantId());
             param.setBizJson(bizJson.toJSONString());
             param.setBizJson(bizJson.toJSONString());
             wxService.addWx(param);
             wxService.addWx(param);
         } catch (Exception ex) {
         } catch (Exception ex) {

+ 5 - 0
fs-service/src/main/java/com/fs/company/vo/easycall/EasyCallInboundLlmVO.java

@@ -93,5 +93,10 @@ public class EasyCallInboundLlmVO implements Serializable {
      */
      */
     private Integer fsSceneType;
     private Integer fsSceneType;
 
 
+    /**
+     * saas租户Id
+     */
+    private Long fsTenantId;
+
 
 
 }
 }

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

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

+ 20 - 0
fs-service/src/main/resources/mapper/company/CompanyBindGatewayMapper.xml

@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.fs.company.mapper.CompanyBindGatewayMapper">
+
+    <delete id="deleteDataByCompanyId" parameterType="java.lang.Long">
+        delete from company_bind_gateway where company_id = #{companyId}
+    </delete>
+
+    <insert id="insertData">
+        <if test="gatewayIds != null and gatewayIds.size() > 0">
+            insert into company_bind_gateway(company_id, gateway_id,create_time) values
+            <foreach item="item" collection="gatewayIds" separator=",">
+                (#{companyId},#{item},now())
+            </foreach>
+        </if>
+    </insert>
+
+</mapper>

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

@@ -214,6 +214,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="roboticId != null">and robotic_id = #{roboticId}</if>
             <if test="roboticId != null">and robotic_id = #{roboticId}</if>
         </where>
         </where>
         group by robotic_id
         group by robotic_id
+        order by t1.run_time desc
     </select>
     </select>
     <select id="selectCompanyVoiceRoboticCallPhoneLogCount" resultType="com.fs.company.vo.CompanyVoiceRoboticCallLogCount">
     <select id="selectCompanyVoiceRoboticCallPhoneLogCount" resultType="com.fs.company.vo.CompanyVoiceRoboticCallLogCount">
         select
         select

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

@@ -43,7 +43,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 
 
     <select id="selectCompanyVoiceRoboticCallLogSendmsgGroupList" parameterType="CompanyVoiceRoboticCallLogSendmsg" resultType="com.fs.company.domain.CompanyVoiceRoboticCallLogSendmsg">
     <select id="selectCompanyVoiceRoboticCallLogSendmsgGroupList" parameterType="CompanyVoiceRoboticCallLogSendmsg" resultType="com.fs.company.domain.CompanyVoiceRoboticCallLogSendmsg">
         select
         select
-        robotic_id,
+        msg.robotic_id,
         cvr.name,
         cvr.name,
         count(1) as totalRecordCount,
         count(1) as totalRecordCount,
         sum(case when status = 1 then 1 else 0 end) as runningCount,
         sum(case when status = 1 then 1 else 0 end) as runningCount,
@@ -54,7 +54,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         inner join company_voice_robotic cvr on cvr.id = msg.robotic_id
         inner join company_voice_robotic cvr on cvr.id = msg.robotic_id
         left join company_voice_robotic_callees cc on cc.id = msg.caller_id
         left join company_voice_robotic_callees cc on cc.id = msg.caller_id
         <where>
         <where>
-            <if test="roboticId != null">and robotic_id = #{roboticId}</if>
+            <if test="roboticId != null">and msg.robotic_id = #{roboticId}</if>
             <if test="callerId != null">and caller_id = #{callerId}</if>
             <if test="callerId != null">and caller_id = #{callerId}</if>
             <if test="runTime != null">and run_time = #{runTime}</if>
             <if test="runTime != null">and run_time = #{runTime}</if>
             <if test="runParam != null and runParam != ''">and run_param = #{runParam}</if>
             <if test="runParam != null and runParam != ''">and run_param = #{runParam}</if>
@@ -66,7 +66,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="cost != null">and cost = #{cost}</if>
             <if test="cost != null">and cost = #{cost}</if>
             <if test="contentLen != null">and content_len = #{contentLen}</if>
             <if test="contentLen != null">and content_len = #{contentLen}</if>
         </where>
         </where>
-        group by robotic_id
+        group by msg.robotic_id
+        order by msg.run_time desc
     </select>
     </select>
     
     
     <select id="selectCompanyVoiceRoboticCallLogSendmsgByLogId" parameterType="Long" resultMap="CompanyVoiceRoboticCallLogSendmsgResult">
     <select id="selectCompanyVoiceRoboticCallLogSendmsgByLogId" parameterType="Long" resultMap="CompanyVoiceRoboticCallLogSendmsgResult">

+ 8 - 2
fs-service/src/main/resources/mapper/company/EasyCallInboundLlmMapper.xml

@@ -17,6 +17,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="aiTransferData" column="ai_transfer_data"/>
         <result property="aiTransferData" column="ai_transfer_data"/>
         <result property="ivrId" column="ivr_id"/>
         <result property="ivrId" column="ivr_id"/>
         <result property="satisfSurveyIvrId" column="satisf_survey_ivr_id"/>
         <result property="satisfSurveyIvrId" column="satisf_survey_ivr_id"/>
+        <result property="fsTenantId" column="fs_tenant_id"/>
     </resultMap>
     </resultMap>
 
 
     <resultMap id="LlmAccountResult" type="com.fs.company.vo.easycall.EasyCallLlmAccountVO">
     <resultMap id="LlmAccountResult" type="com.fs.company.vo.easycall.EasyCallLlmAccountVO">
@@ -28,9 +29,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     </resultMap>
     </resultMap>
 
 
     <resultMap id="VoiceCodeResult" type="com.fs.company.vo.easycall.EasyCallVoiceCodeVO">
     <resultMap id="VoiceCodeResult" type="com.fs.company.vo.easycall.EasyCallVoiceCodeVO">
+        <id property="id" column="id"/>
         <result property="voiceCode" column="voice_code"/>
         <result property="voiceCode" column="voice_code"/>
         <result property="voiceName" column="voice_name"/>
         <result property="voiceName" column="voice_name"/>
         <result property="voiceSource" column="voice_source"/>
         <result property="voiceSource" column="voice_source"/>
+        <result property="priority" column="priority"/>
     </resultMap>
     </resultMap>
 
 
     <resultMap id="BizGroupResult" type="com.fs.company.vo.easycall.EasyCallBizGroupVO">
     <resultMap id="BizGroupResult" type="com.fs.company.vo.easycall.EasyCallBizGroupVO">
@@ -56,7 +59,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     </resultMap>
     </resultMap>
 
 
     <sql id="selectInboundLlmVo">
     <sql id="selectInboundLlmVo">
-        select id, llm_account_id, callee, voice_code, voice_source, service_type, asr_provider, ai_transfer_type, ai_transfer_data, ivr_id, satisf_survey_ivr_id, inbound_alias,call_back_url,fs_scene_type from cc_inbound_llm_account
+        select id, llm_account_id, callee, voice_code, voice_source, service_type, asr_provider, ai_transfer_type, ai_transfer_data, ivr_id, satisf_survey_ivr_id, inbound_alias,call_back_url,fs_scene_type,fs_tenant_id from cc_inbound_llm_account
     </sql>
     </sql>
 
 
     <select id="selectInboundLlmList" parameterType="com.fs.company.vo.easycall.EasyCallInboundLlmVO" resultMap="InboundLlmResult">
     <select id="selectInboundLlmList" parameterType="com.fs.company.vo.easycall.EasyCallInboundLlmVO" resultMap="InboundLlmResult">
@@ -79,6 +82,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
                     </foreach>
                     </foreach>
                 </if>
                 </if>
             </if>
             </if>
+            <if test="fsTenantId != null">and fs_tenant_id = #{fsTenantId}</if>
         </where>
         </where>
         order by id desc
         order by id desc
     </select>
     </select>
@@ -111,6 +115,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="companyId != null">fs_company_id,</if>
             <if test="companyId != null">fs_company_id,</if>
             <if test="callBackUrl != null">call_back_url,</if>
             <if test="callBackUrl != null">call_back_url,</if>
             <if test="fsSceneType != null">fs_scene_type,</if>
             <if test="fsSceneType != null">fs_scene_type,</if>
+            <if test="fsTenantId != null">fs_tenant_id,</if>
         </trim>
         </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="id != null">#{id},</if>
             <if test="id != null">#{id},</if>
@@ -128,6 +133,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="companyId != null">#{companyId},</if>
             <if test="companyId != null">#{companyId},</if>
             <if test="callBackUrl != null">#{callBackUrl},</if>
             <if test="callBackUrl != null">#{callBackUrl},</if>
             <if test="fsSceneType != null">#{fsSceneType},</if>
             <if test="fsSceneType != null">#{fsSceneType},</if>
+            <if test="fsTenantId != null">#{fsTenantId},</if>
         </trim>
         </trim>
     </insert>
     </insert>
 
 
@@ -205,7 +211,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 
 
     <!-- 根据音色来源查询音色列表 -->
     <!-- 根据音色来源查询音色列表 -->
     <select id="selectVoiceListBySource" parameterType="String" resultMap="VoiceCodeResult">
     <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
         from cc_tts_aliyun
         where voice_source = #{voiceSource}
         where voice_source = #{voiceSource}
         and voice_enabled = 1
         and voice_enabled = 1

+ 203 - 113
fs-wx-api/src/main/java/com/fs/app/websocket/service/WebSocketServer.java

@@ -6,31 +6,41 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.fs.app.enums.CmdType;
 import com.fs.app.enums.CmdType;
 import com.fs.app.websocket.bean.ResultMsgVo;
 import com.fs.app.websocket.bean.ResultMsgVo;
 import com.fs.app.websocket.bean.SendMsgVo;
 import com.fs.app.websocket.bean.SendMsgVo;
-import com.fs.company.domain.CompanyWxClient;
-import com.fs.company.mapper.CompanyWxClientMapper;
-import com.fs.company.service.CompanyWorkflowEngine;
-import com.fs.company.service.impl.CompanyWxServiceImpl;
-import com.fs.wxcid.domain.CidIpadServer;
-import com.fs.wxcid.mapper.CidIpadServerMapper;
-import com.fs.wxcid.vo.wxvo.*;
+import com.fs.common.config.RedisTenantContext;
+import com.fs.common.core.domain.model.TenantPrincipal;
 import com.fs.common.core.redis.RedisCache;
 import com.fs.common.core.redis.RedisCache;
 import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.spring.SpringUtils;
 import com.fs.common.utils.spring.SpringUtils;
 import com.fs.company.domain.CompanyWxAccount;
 import com.fs.company.domain.CompanyWxAccount;
+import com.fs.company.domain.CompanyWxClient;
 import com.fs.company.mapper.CompanyWxAccountMapper;
 import com.fs.company.mapper.CompanyWxAccountMapper;
+import com.fs.company.mapper.CompanyWxClientMapper;
+import com.fs.company.service.CompanyWorkflowEngine;
+import com.fs.company.service.impl.CompanyWxServiceImpl;
+import com.fs.core.config.TenantConfigContext;
+import com.fs.tenant.domain.TenantInfo;
+import com.fs.tenant.mapper.TenantInfoMapper;
 import com.fs.wxcid.domain.WxContact;
 import com.fs.wxcid.domain.WxContact;
+import com.fs.wxcid.mapper.CidIpadServerMapper;
 import com.fs.wxcid.mapper.WxContactMapper;
 import com.fs.wxcid.mapper.WxContactMapper;
 import com.fs.wxcid.service.IWxMsgLogService;
 import com.fs.wxcid.service.IWxMsgLogService;
+import com.fs.wxcid.utils.TenantHelper;
+import com.fs.wxcid.vo.wxvo.ContactInfoVo;
+import com.fs.wxcid.vo.wxvo.SyncInfoVo;
+import com.fs.wxcid.vo.wxvo.WxSendResultMsgVo;
 import com.hc.openapi.tool.fastjson.JSON;
 import com.hc.openapi.tool.fastjson.JSON;
 import lombok.extern.slf4j.Slf4j;
 import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.stereotype.Component;
 import org.springframework.stereotype.Component;
 
 
 import javax.websocket.*;
 import javax.websocket.*;
 import javax.websocket.server.PathParam;
 import javax.websocket.server.PathParam;
 import javax.websocket.server.ServerEndpoint;
 import javax.websocket.server.ServerEndpoint;
 import java.io.IOException;
 import java.io.IOException;
+import java.lang.reflect.Method;
 import java.time.LocalDateTime;
 import java.time.LocalDateTime;
+import java.util.Collections;
 import java.util.Date;
 import java.util.Date;
 import java.util.List;
 import java.util.List;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentHashMap;
@@ -51,6 +61,7 @@ public class WebSocketServer {
     CompanyWxServiceImpl companyWxService = SpringUtils.getBean(CompanyWxServiceImpl.class);
     CompanyWxServiceImpl companyWxService = SpringUtils.getBean(CompanyWxServiceImpl.class);
     CidIpadServerMapper cidIpadServerMapper = SpringUtils.getBean(CidIpadServerMapper.class);
     CidIpadServerMapper cidIpadServerMapper = SpringUtils.getBean(CidIpadServerMapper.class);
     CompanyWorkflowEngine companyWorkflowEngine = SpringUtils.getBean(CompanyWorkflowEngine.class);
     CompanyWorkflowEngine companyWorkflowEngine = SpringUtils.getBean(CompanyWorkflowEngine.class);
+    TenantInfoMapper tenantInfoMapper = SpringUtils.getBean(TenantInfoMapper.class);
 
 
     //发送消息
     //发送消息
     public <T> void sendMessage(Session session, ResultMsgVo<T> data) {
     public <T> void sendMessage(Session session, ResultMsgVo<T> data) {
@@ -65,133 +76,164 @@ public class WebSocketServer {
             }
             }
         }
         }
     }
     }
+
     //建立连接成功调用
     //建立连接成功调用
     @OnOpen
     @OnOpen
-    public void onOpen(Session session, @PathParam(value = "wxId") String wxId) {
-        CompanyWxAccount companyWxAccount = accountMapper.selectOne(new QueryWrapper<CompanyWxAccount>().eq("wx_no", wxId));
-        if(companyWxAccount == null){
-            sendMessage(session, ResultMsgVo.error("未找到对应微信数据"));
-            return;
+    public void onOpen(Session session, @PathParam(value = "wxId") String wxId, @PathParam(value = "tenantCode") String tenantCode) {
+        Boolean switchBool = switchDataBaseByTenantCode(tenantCode);
+        try {
+            CompanyWxAccount companyWxAccount = accountMapper.selectOne(new QueryWrapper<CompanyWxAccount>().eq("wx_no", wxId));
+            if (companyWxAccount == null) {
+                sendMessage(session, ResultMsgVo.error("未找到对应微信数据"));
+                return;
+            }
+            sessionPools.put(wxId, session);
+            companyWxAccount.setLoginStatus(1);
+            companyWxAccount.setLoginTime(LocalDateTime.now());
+            accountMapper.updateById(companyWxAccount);
+            JSONObject jsonObject = new JSONObject();
+            jsonObject.put("remark", companyWxAccount.getRemark());
+            sendMessage(session, ResultMsgVo.<JSONObject>builder().cmd(CmdType.INIT_REMARK).data(jsonObject).build());
+            log.info("{}加入webSocket!当前人数为{}", wxId, sessionPools.size());
+        } catch (Exception e) {
+            log.error("onOpenErr:{}", e.getMessage());
+        } finally {
+            if (switchBool) {
+                finalHandle();
+            }
         }
         }
-        sessionPools.put(wxId, session);
-        companyWxAccount.setLoginStatus(1);
-        companyWxAccount.setLoginTime(LocalDateTime.now());
-        accountMapper.updateById(companyWxAccount);
-        JSONObject jsonObject = new JSONObject();
-        jsonObject.put("remark", companyWxAccount.getRemark());
-        sendMessage(session, ResultMsgVo.<JSONObject>builder().cmd(CmdType.INIT_REMARK).data(jsonObject).build());
-        log.info("{}加入webSocket!当前人数为{}", wxId, sessionPools.size());
+
     }
     }
 
 
     //关闭连接时调用
     //关闭连接时调用
     @OnClose
     @OnClose
-    public void onClose(@PathParam(value = "wxId") String wxId) {
-        sessionPools.remove(wxId);
-        CompanyWxAccount companyWxAccount = accountMapper.selectOne(new QueryWrapper<CompanyWxAccount>().eq("wx_no", wxId));
-        if(companyWxAccount != null){
-            companyWxAccount.setLoginStatus(0);
-            companyWxAccount.setOutTime(LocalDateTime.now());
-            companyWxAccount.setOutRemark("连接断开");
-            accountMapper.updateById(companyWxAccount);
+    public void onClose(@PathParam(value = "wxId") String wxId, @PathParam(value = "tenantCode") String tenantCode) {
+        Boolean switchBool = switchDataBaseByTenantCode(tenantCode);
+        try {
+            sessionPools.remove(wxId);
+            CompanyWxAccount companyWxAccount = accountMapper.selectOne(new QueryWrapper<CompanyWxAccount>().eq("wx_no", wxId));
+            if (companyWxAccount != null) {
+                companyWxAccount.setLoginStatus(0);
+                companyWxAccount.setOutTime(LocalDateTime.now());
+                companyWxAccount.setOutRemark("连接断开");
+                accountMapper.updateById(companyWxAccount);
+            }
+            log.info("{}断开webSocket连接!当前人数为{}", wxId, sessionPools.size());
+        } catch (Exception e) {
+            log.error("onCloseErr:{}", e.getMessage());
+        } finally {
+            if (switchBool) {
+                finalHandle();
+            }
         }
         }
-        log.info("{}断开webSocket连接!当前人数为{}", wxId, sessionPools.size());
     }
     }
 
 
     //收到客户端信息
     //收到客户端信息
     @OnMessage
     @OnMessage
-    public void onMessage(String message, @PathParam(value = "wxId") String wxId) {
-        SendMsgVo msg = JSONObject.parseObject(message, SendMsgVo.class);
-        if(msg.getType() == 0){
-            return;
-        }
-        Session session = sessionPools.get(wxId);
-        if(session == null){
-            log.error("参数异常:{}", wxId);
-            return;
-        }
-        log.info("收到数据:{}", msg.getCmd());
-        CompanyWxAccount companyWxAccount = accountMapper.selectOne(new QueryWrapper<CompanyWxAccount>().eq("wx_no", wxId));
-        if(companyWxAccount == null){
-            log.error("未找到对应账号:{}", wxId);
-            return;
-        }
+    public void onMessage(String message, @PathParam(value = "wxId") String wxId, @PathParam(value = "tenantCode") String tenantCode) {
+        Boolean switchBool = switchDataBaseByTenantCode(tenantCode);
         try {
         try {
-            switch (msg.getCmd()) {
-                case HEARTBEAT:
-                    log.info("接收心跳:{}", wxId);
-                    break;
-                case SYNC_CONTACT_PERSON:
-                    ContactInfoVo contactInfoVo = JSON.parseObject(msg.getDataJson(), ContactInfoVo.class);
-                    if(contactInfoVo == null || StringUtils.isEmpty(contactInfoVo.getRemark())){
-                        log.error("{}同步数据失败,数据缺失:{}", wxId, contactInfoVo);
-                        return;
-                    }
-                    WxContact contact = wxContactMapper.selectOne(new QueryWrapper<WxContact>().eq("remark", contactInfoVo.getRemark()));
-                    if(contact != null){
-                        contact.setNickName(contactInfoVo.getNickName());
-                        contact.setCity(contactInfoVo.getAddress());
-                        contact.setUserName(contactInfoVo.getWxNo());
-                        contact.setUpdateTime(new Date());
-                        wxContactMapper.updateById(contact);
-                    }else{
-                        WxContact contact1 = new WxContact();
-                        contact1.setUserName(contactInfoVo.getWxNo());
-                        contact1.setNickName(contactInfoVo.getNickName());
-                        contact1.setCity(contactInfoVo.getAddress());
-                        contact1.setAccountId(companyWxAccount.getId());
-                        contact1.setCompanyId(companyWxAccount.getCompanyId());
-                        contact1.setCompanyUserId(companyWxAccount.getCompanyUserId());
-                        contact1.setRemark(contactInfoVo.getRemark());
-                        contact1.setCreateTime(new Date());
-                        contact1.setUpdateTime(new Date());
-                        wxContactMapper.insert(contact1);
-                    }
-                    break;
-                case SEND_MSG:
-                    log.info("发送返回:{}", msg);
-                    wxMsgLogService.insertLog(JSON.parseObject(msg.getDataJson(), WxSendResultMsgVo.class), companyWxAccount, 0);
-                    break;
-                case SEND_RESULT:
-                    log.info("接收消息:{}", msg);
-                    wxMsgLogService.insertLog(JSON.parseObject(msg.getDataJson(), WxSendResultMsgVo.class), companyWxAccount, 0);
-                    break;
-                case SYNC_INFO:
-                    SyncInfoVo syncInfoVo = JSON.parseObject(msg.getDataJson(), SyncInfoVo.class);
-                    companyWxAccount.setHeadImgUrl(syncInfoVo.getImg());
-                    companyWxAccount.setPhone(syncInfoVo.getPhone());
-                    accountMapper.updateById(companyWxAccount);
-                    break;
-                case ADD_WX_RESULT:
-                    com.fs.wxcid.vo.wxvo.AddResultWxVo addResultWxVo = JSON.parseObject(msg.getDataJson(), com.fs.wxcid.vo.wxvo.AddResultWxVo.class);
-                    log.info("接收到加好友回调:{}", addResultWxVo);
-                    WxContact wxContact = wxContactMapper.selectOne(new QueryWrapper<WxContact>().eq("remark", addResultWxVo.getRemark()).eq("friends", 0));
-                    log.info("更新联系人:{}", wxContact);
-                    wxContact.setFriends(1);
-                    wxContact.setAlias(addResultWxVo.getWxid());
-                    wxContactMapper.updateById(wxContact);
-                    List<CompanyWxClient> clients = companyWxClientMapper.selectWxV2(companyWxAccount.getId(), wxContact.getPhone());
-                    log.info("更新联系人2:{}", clients);
-                    if(clients != null){
-                        clients.parallelStream().forEach(e -> {
-                            e.setIsAdd(1);
-                            e.setRemark(addResultWxVo.getRemark());
-                            e.setWxName(addResultWxVo.getUserName());
-                            e.setSuccessAddTime(LocalDateTime.now());
-                            companyWxClientMapper.updateById(e);
-                            companyWxService.triggerWorkflowOnAddWxSuccess(e.getId());
-                        });
-                    }
+            SendMsgVo msg = JSONObject.parseObject(message, SendMsgVo.class);
+            if (msg.getType() == 0) {
+                return;
+            }
+            Session session = sessionPools.get(wxId);
+            if (session == null) {
+                log.error("参数异常:{}", wxId);
+                return;
+            }
+            log.info("收到数据:{}", msg.getCmd());
+            CompanyWxAccount companyWxAccount = accountMapper.selectOne(new QueryWrapper<CompanyWxAccount>().eq("wx_no", wxId));
+            if (companyWxAccount == null) {
+                log.error("未找到对应账号:{}", wxId);
+                return;
+            }
+            try {
+                switch (msg.getCmd()) {
+                    case HEARTBEAT:
+                        log.info("接收心跳:{}", wxId);
+                        break;
+                    case SYNC_CONTACT_PERSON:
+                        ContactInfoVo contactInfoVo = JSON.parseObject(msg.getDataJson(), ContactInfoVo.class);
+                        if (contactInfoVo == null || StringUtils.isEmpty(contactInfoVo.getRemark())) {
+                            log.error("{}同步数据失败,数据缺失:{}", wxId, contactInfoVo);
+                            return;
+                        }
+                        WxContact contact = wxContactMapper.selectOne(new QueryWrapper<WxContact>().eq("remark", contactInfoVo.getRemark()));
+                        if (contact != null) {
+                            contact.setNickName(contactInfoVo.getNickName());
+                            contact.setCity(contactInfoVo.getAddress());
+                            contact.setUserName(contactInfoVo.getWxNo());
+                            contact.setUpdateTime(new Date());
+                            wxContactMapper.updateById(contact);
+                        } else {
+                            WxContact contact1 = new WxContact();
+                            contact1.setUserName(contactInfoVo.getWxNo());
+                            contact1.setNickName(contactInfoVo.getNickName());
+                            contact1.setCity(contactInfoVo.getAddress());
+                            contact1.setAccountId(companyWxAccount.getId());
+                            contact1.setCompanyId(companyWxAccount.getCompanyId());
+                            contact1.setCompanyUserId(companyWxAccount.getCompanyUserId());
+                            contact1.setRemark(contactInfoVo.getRemark());
+                            contact1.setCreateTime(new Date());
+                            contact1.setUpdateTime(new Date());
+                            wxContactMapper.insert(contact1);
+                        }
+                        break;
+                    case SEND_MSG:
+                        log.info("发送返回:{}", msg);
+                        wxMsgLogService.insertLog(JSON.parseObject(msg.getDataJson(), WxSendResultMsgVo.class), companyWxAccount, 0);
+                        break;
+                    case SEND_RESULT:
+                        log.info("接收消息:{}", msg);
+                        wxMsgLogService.insertLog(JSON.parseObject(msg.getDataJson(), WxSendResultMsgVo.class), companyWxAccount, 0);
+                        break;
+                    case SYNC_INFO:
+                        SyncInfoVo syncInfoVo = JSON.parseObject(msg.getDataJson(), SyncInfoVo.class);
+                        companyWxAccount.setHeadImgUrl(syncInfoVo.getImg());
+                        companyWxAccount.setPhone(syncInfoVo.getPhone());
+                        accountMapper.updateById(companyWxAccount);
+                        break;
+                    case ADD_WX_RESULT:
+                        com.fs.wxcid.vo.wxvo.AddResultWxVo addResultWxVo = JSON.parseObject(msg.getDataJson(), com.fs.wxcid.vo.wxvo.AddResultWxVo.class);
+                        log.info("接收到加好友回调:{}", addResultWxVo);
+                        WxContact wxContact = wxContactMapper.selectOne(new QueryWrapper<WxContact>().eq("remark", addResultWxVo.getRemark()).eq("friends", 0));
+                        log.info("更新联系人:{}", wxContact);
+                        wxContact.setFriends(1);
+                        wxContact.setAlias(addResultWxVo.getWxid());
+                        wxContactMapper.updateById(wxContact);
+                        List<CompanyWxClient> clients = companyWxClientMapper.selectWxV2(companyWxAccount.getId(), wxContact.getPhone());
+                        log.info("更新联系人2:{}", clients);
+                        if (clients != null) {
+                            clients.parallelStream().forEach(e -> {
+                                e.setIsAdd(1);
+                                e.setRemark(addResultWxVo.getRemark());
+                                e.setWxName(addResultWxVo.getUserName());
+                                e.setSuccessAddTime(LocalDateTime.now());
+                                companyWxClientMapper.updateById(e);
+                                companyWxService.triggerWorkflowOnAddWxSuccess(e.getId());
+                            });
+                        }
 //                    if(null != addResultWxVo && StringUtils.isNotBlank(addResultWxVo.getBizJson())){
 //                    if(null != addResultWxVo && StringUtils.isNotBlank(addResultWxVo.getBizJson())){
 //                        JSONObject jsonObject = JSONObject.parseObject(addResultWxVo.getBizJson());
 //                        JSONObject jsonObject = JSONObject.parseObject(addResultWxVo.getBizJson());
 //                        jsonObject.put("remark",addResultWxVo.getRemark());
 //                        jsonObject.put("remark",addResultWxVo.getRemark());
 //                        companyWorkflowEngine.addWxSuccess(jsonObject);
 //                        companyWorkflowEngine.addWxSuccess(jsonObject);
 //                    }
 //                    }
-                    break;
+                        break;
 
 
+                }
+            } catch (Exception e) {
+                log.error("发生错误;{}", e.getMessage());
             }
             }
         } catch (Exception e) {
         } catch (Exception e) {
-            log.error("发生错误;{}", e.getMessage());
+            log.error("onMessageErr:{}", e.getMessage());
         }
         }
+        finally {
+            if (switchBool) {
+                finalHandle();
+            }
+        }
+
 
 
     }
     }
 
 
@@ -201,4 +243,52 @@ public class WebSocketServer {
         log.error("发生错误;{}", throwable.getMessage());
         log.error("发生错误;{}", throwable.getMessage());
         throwable.printStackTrace();
         throwable.printStackTrace();
     }
     }
+
+    /**
+     * 根据租户编码切换数据源
+     *
+     * @param tenantCode
+     */
+    public Boolean switchDataBaseByTenantCode(String tenantCode) {
+        if (StringUtils.isBlank(tenantCode)) {
+            log.error("未找到对应租户:{}", tenantCode);
+            return Boolean.FALSE;
+        }
+        try {
+            TenantInfo tenantInfo = tenantInfoMapper.getTenByCode(tenantCode);
+            Object manager = SpringUtils.getBean("tenantDataSourceManager");
+            Method method = manager.getClass().getMethod("ensureSwitchByTenantId", Long.class);
+            method.invoke(manager, tenantInfo.getId());
+            // 设置租户到 SecurityContext,供 TenantKeyRedisSerializer 自动为 Redis Key 加 tenantid 前缀
+            SecurityContextHolder.getContext().setAuthentication(
+                    new UsernamePasswordAuthenticationToken(
+                            new TenantPrincipal(TenantHelper.getTenantId()),
+                            null,
+                            Collections.emptyList()
+                    )
+            );
+            // 切换 Redis 租户上下文
+            RedisTenantContext.setTenantId(TenantHelper.getTenantId());
+            return Boolean.TRUE;
+        } catch (Exception e) {
+            log.error("callerResult4EasyCall 切换租户数据源失败: tenantId={}", TenantHelper.getTenantId(), e);
+            return Boolean.FALSE;
+        }
+    }
+
+
+    public void finalHandle() {
+        try {
+            TenantConfigContext.clear();
+            SecurityContextHolder.clearContext();
+            Object manager = SpringUtils.getBean("tenantDataSourceManager");
+            Method method = manager.getClass().getMethod("clear");
+            method.invoke(manager);
+            TenantHelper.clearTenantId();
+            RedisTenantContext.clear();
+        } catch (Exception e) {
+            log.error("SOP异步任务清理租户数据源失败", e);
+        }
+    }
+
 }
 }

+ 113 - 0
fs-wx-api/src/main/java/com/fs/framework/datasource/TenantDataSourceManager.java

@@ -0,0 +1,113 @@
+package com.fs.framework.datasource;
+
+import com.alibaba.druid.pool.DruidDataSource;
+import com.fs.common.enums.DataSourceType;
+import com.fs.tenant.domain.TenantInfo;
+import com.fs.tenant.service.TenantInfoService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import javax.sql.DataSource;
+import java.lang.reflect.Field;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * 租户数据源管理,SaaS 模式下定时任务按租户切库时使用。
+ */
+@Component
+@Slf4j
+public class TenantDataSourceManager {
+
+    @Resource
+    private DynamicDataSource dynamicDataSource;
+
+    @Resource
+    private TenantInfoService tenantInfoService;
+
+    private static final Map<String, DataSource> TENANT_DS_CACHE = new ConcurrentHashMap<>();
+
+    public void switchTenant(TenantInfo tenantInfo) {
+        String tenantKey = buildTenantKey(tenantInfo.getId());
+        if (!TENANT_DS_CACHE.containsKey(tenantKey)) {
+            synchronized (this) {
+                if (!TENANT_DS_CACHE.containsKey(tenantKey)) {
+                    DataSource tenantDs = createTenantDataSource(tenantInfo);
+                    TENANT_DS_CACHE.put(tenantKey, tenantDs);
+                    Map<Object, DataSource> resolvedMap = getResolvedDataSources();
+                    resolvedMap.put(tenantKey, tenantDs);
+                }
+            }
+        }
+        DynamicDataSourceContextHolder.setDataSourceType(tenantKey);
+    }
+
+    private String buildTenantKey(Long tenantId) {
+        return "tenant:" + tenantId;
+    }
+
+    public void clear() {
+        DynamicDataSourceContextHolder.clearDataSourceType();
+    }
+
+    private DataSource createTenantDataSource(TenantInfo tenant) {
+        DruidDataSource ds = new DruidDataSource();
+        ds.setUrl(tenant.getDbUrl());
+        ds.setUsername(tenant.getDbAccount());
+        ds.setPassword(tenant.getDbPwd());
+        ds.setDriverClassName("com.mysql.cj.jdbc.Driver");
+        ds.setInitialSize(5);
+        ds.setMinIdle(10);
+        ds.setMaxActive(20);
+        ds.setMaxWait(60000);
+        return ds;
+    }
+
+    @SuppressWarnings("unchecked")
+    private Map<Object, DataSource> getResolvedDataSources() {
+        try {
+            Field field = org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource.class
+                    .getDeclaredField("resolvedDataSources");
+            field.setAccessible(true);
+            return (Map<Object, DataSource>) field.get(dynamicDataSource);
+        } catch (Exception e) {
+            throw new IllegalStateException("获取 resolvedDataSources 失败", e);
+        }
+    }
+    /**
+     * 根据租户ID确保数据源已注册并切换(用于 Filter/拦截器等非登录场景)
+     * 解决 JVM 重启后 TENANT_DS_CACHE 被清空,导致 resolvedDataSources 中找不到租户数据源的问题
+     *
+     * @param tenantId 租户ID
+     */
+    public void ensureSwitchByTenantId(Long tenantId) {
+        String tenantKey = buildTenantKey(tenantId);
+
+        if (TENANT_DS_CACHE.containsKey(tenantKey)) {
+            DynamicDataSourceContextHolder.setDataSourceType(tenantKey);
+            log.debug("[TenantDS] 数据源已缓存,直接切换: {}", tenantKey);
+            return;
+        }
+
+        DynamicDataSourceContextHolder.setDataSourceType(DataSourceType.MASTER.name());
+        try {
+            TenantInfo tenantInfo = tenantInfoService.getById(tenantId);
+            if (tenantInfo == null) {
+                log.warn("[TenantDS] 租户ID={} 在主库中不存在,回退到主库", tenantId);
+                DynamicDataSourceContextHolder.setDataSourceType(DataSourceType.MASTER.name());
+                return;
+            }
+            if (!tenantInfo.getStatus().equals(1)) {
+                log.warn("[TenantDS] 租户ID={} 已禁用,回退到主库", tenantId);
+                DynamicDataSourceContextHolder.setDataSourceType(DataSourceType.MASTER.name());
+                return;
+            }
+            switchTenant(tenantInfo);
+            log.info("[TenantDS] 动态注册并切换数据源: key={}, url={}", tenantKey, tenantInfo.getDbUrl());
+        } catch (Exception e) {
+            log.error("[TenantDS] 动态注册租户数据源失败, tenantId={}, 回退到主库", tenantId, e);
+            DynamicDataSourceContextHolder.setDataSourceType(DataSourceType.MASTER.name());
+        }
+    }
+}