Преглед на файлове

Merge branch 'refs/heads/master' into feature_260702_积分任务中心

yuhongqi преди 3 седмици
родител
ревизия
8c0e9420ab
променени са 53 файла, в които са добавени 4356 реда и са изтрити 25 реда
  1. 523 0
      fs-company/src/main/java/com/fs/company/controller/aiAddwxSop/AiAddwxSopController.java
  2. 86 9
      fs-ipad-task/src/main/java/com/fs/app/task/SendMsg.java
  3. 9 1
      fs-service/src/main/java/com/fs/common/service/impl/SmsServiceImpl.java
  4. 77 0
      fs-service/src/main/java/com/fs/company/domain/AiAddwxSopExecLog.java
  5. 47 0
      fs-service/src/main/java/com/fs/company/domain/AiAddwxSopTagTemplateBind.java
  6. 48 0
      fs-service/src/main/java/com/fs/company/domain/AiAddwxSopTemplate.java
  7. 65 0
      fs-service/src/main/java/com/fs/company/domain/AiAddwxSopTemplateNode.java
  8. 56 0
      fs-service/src/main/java/com/fs/company/domain/AiAddwxSopTemplateNodeRule.java
  9. 93 0
      fs-service/src/main/java/com/fs/company/domain/AiAddwxSopUserNode.java
  10. 71 0
      fs-service/src/main/java/com/fs/company/domain/AiAddwxSopUserNodeRule.java
  11. 95 0
      fs-service/src/main/java/com/fs/company/domain/AiAddwxSopUserWorkflow.java
  12. 103 0
      fs-service/src/main/java/com/fs/company/enums/AiAddWechatSopNodeEnum.java
  13. 31 0
      fs-service/src/main/java/com/fs/company/mapper/AiAddwxSopExecLogMapper.java
  14. 34 0
      fs-service/src/main/java/com/fs/company/mapper/AiAddwxSopTagTemplateBindMapper.java
  15. 27 0
      fs-service/src/main/java/com/fs/company/mapper/AiAddwxSopTemplateMapper.java
  16. 32 0
      fs-service/src/main/java/com/fs/company/mapper/AiAddwxSopTemplateNodeMapper.java
  17. 32 0
      fs-service/src/main/java/com/fs/company/mapper/AiAddwxSopTemplateNodeRuleMapper.java
  18. 32 0
      fs-service/src/main/java/com/fs/company/mapper/AiAddwxSopUserNodeMapper.java
  19. 32 0
      fs-service/src/main/java/com/fs/company/mapper/AiAddwxSopUserNodeRuleMapper.java
  20. 28 0
      fs-service/src/main/java/com/fs/company/mapper/AiAddwxSopUserWorkflowMapper.java
  21. 14 0
      fs-service/src/main/java/com/fs/company/param/InitAddWxSopTestParam.java
  22. 30 0
      fs-service/src/main/java/com/fs/company/service/IAiAddwxSopExecLogService.java
  23. 30 0
      fs-service/src/main/java/com/fs/company/service/IAiAddwxSopTagTemplateBindService.java
  24. 30 0
      fs-service/src/main/java/com/fs/company/service/IAiAddwxSopTemplateNodeRuleService.java
  25. 30 0
      fs-service/src/main/java/com/fs/company/service/IAiAddwxSopTemplateNodeService.java
  26. 34 0
      fs-service/src/main/java/com/fs/company/service/IAiAddwxSopTemplateService.java
  27. 30 0
      fs-service/src/main/java/com/fs/company/service/IAiAddwxSopUserNodeRuleService.java
  28. 37 0
      fs-service/src/main/java/com/fs/company/service/IAiAddwxSopUserNodeService.java
  29. 39 0
      fs-service/src/main/java/com/fs/company/service/IAiAddwxSopUserWorkflowService.java
  30. 62 0
      fs-service/src/main/java/com/fs/company/service/impl/AiAddwxSopExecLogServiceImpl.java
  31. 62 0
      fs-service/src/main/java/com/fs/company/service/impl/AiAddwxSopTagTemplateBindServiceImpl.java
  32. 62 0
      fs-service/src/main/java/com/fs/company/service/impl/AiAddwxSopTemplateNodeRuleServiceImpl.java
  33. 62 0
      fs-service/src/main/java/com/fs/company/service/impl/AiAddwxSopTemplateNodeServiceImpl.java
  34. 517 0
      fs-service/src/main/java/com/fs/company/service/impl/AiAddwxSopTemplateServiceImpl.java
  35. 62 0
      fs-service/src/main/java/com/fs/company/service/impl/AiAddwxSopUserNodeRuleServiceImpl.java
  36. 322 0
      fs-service/src/main/java/com/fs/company/service/impl/AiAddwxSopUserNodeServiceImpl.java
  37. 226 0
      fs-service/src/main/java/com/fs/company/service/impl/AiAddwxSopUserWorkflowServiceImpl.java
  38. 1 0
      fs-service/src/main/java/com/fs/fastGpt/mapper/FastGptChatSessionMapper.java
  39. 1 0
      fs-service/src/main/java/com/fs/qw/mapper/QwTagMapper.java
  40. 2 0
      fs-service/src/main/java/com/fs/qw/mapper/QwUserMapper.java
  41. 6 1
      fs-service/src/main/java/com/fs/qw/service/impl/QwExternalContactServiceImpl.java
  42. 108 0
      fs-service/src/main/resources/mapper/aiAddwxSop/AiAddwxSopTagTemplateBindMapper.xml
  43. 108 0
      fs-service/src/main/resources/mapper/aiAddwxSop/AiAddwxSopTemplateNodeRuleMapper.xml
  44. 124 0
      fs-service/src/main/resources/mapper/aiAddwxSop/AiAddwxSopUserNodeRuleMapper.xml
  45. 140 0
      fs-service/src/main/resources/mapper/company/AiAddwxSopExecLogMapper.xml
  46. 103 0
      fs-service/src/main/resources/mapper/company/AiAddwxSopTemplateMapper.xml
  47. 137 0
      fs-service/src/main/resources/mapper/company/AiAddwxSopTemplateNodeMapper.xml
  48. 178 0
      fs-service/src/main/resources/mapper/company/AiAddwxSopUserNodeMapper.xml
  49. 136 0
      fs-service/src/main/resources/mapper/company/AiAddwxSopUserWorkflowMapper.xml
  50. 41 0
      fs-service/src/main/resources/mapper/fastGpt/FastGptChatSessionMapper.xml
  51. 6 0
      fs-service/src/main/resources/mapper/qw/QwTagMapper.xml
  52. 50 0
      fs-service/src/main/resources/mapper/qw/QwUserMapper.xml
  53. 45 14
      fs-user-app/src/main/java/com/fs/app/controller/AppLoginController.java

+ 523 - 0
fs-company/src/main/java/com/fs/company/controller/aiAddwxSop/AiAddwxSopController.java

@@ -0,0 +1,523 @@
+package com.fs.company.controller.aiAddwxSop;
+
+import com.fs.common.annotation.Log;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.ServletUtils;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.company.domain.*;
+import com.fs.company.param.InitAddWxSopTestParam;
+import com.fs.company.service.*;
+import com.fs.framework.security.LoginUser;
+import com.fs.framework.service.TokenService;
+import com.fs.qw.domain.QwExternalContact;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * AI添加微信SOP工作流Controller
+ *
+ * @author fs
+ * @date 2026-06-24
+ */
+@RestController
+@RequestMapping("/aiAddwxSop")
+public class AiAddwxSopController extends BaseController {
+
+    @Autowired
+    private IAiAddwxSopTemplateService templateService;
+
+    @Autowired
+    private IAiAddwxSopTemplateNodeService templateNodeService;
+
+    @Autowired
+    private IAiAddwxSopUserWorkflowService userWorkflowService;
+
+    @Autowired
+    private IAiAddwxSopUserNodeService userNodeService;
+
+    @Autowired
+    private IAiAddwxSopExecLogService execLogService;
+
+    @Autowired
+    private IAiAddwxSopTemplateNodeRuleService templateNodeRuleService;
+
+    @Autowired
+    private IAiAddwxSopUserNodeRuleService userNodeRuleService;
+
+    @Autowired
+    private IAiAddwxSopTagTemplateBindService tagTemplateBindService;
+
+    @Autowired
+    private TokenService tokenService;
+
+    // ==================== 模板管理 ====================
+
+    /**
+     * 查询模板列表
+     */
+    @GetMapping("/template/list")
+    public TableDataInfo templateList(AiAddwxSopTemplate template) {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        template.setCompanyId(loginUser.getCompany().getCompanyId());
+        startPage();
+        List<AiAddwxSopTemplate> list = templateService.selectAiAddwxSopTemplateList(template);
+        return getDataTable(list);
+    }
+
+    /**
+     * 获取模板详情(含节点列表)
+     */
+    @GetMapping("/template/{templateId}")
+    public AjaxResult templateDetail(@PathVariable Long templateId) {
+        AiAddwxSopTemplate template = templateService.selectAiAddwxSopTemplateById(templateId);
+        if (template == null) {
+            return AjaxResult.error("模板不存在");
+        }
+        List<AiAddwxSopTemplateNode> nodes = templateNodeService.selectAiAddwxSopTemplateNodeByTemplateId(templateId);
+        AjaxResult result = AjaxResult.success(template);
+        result.put("nodes", nodes);
+        return result;
+    }
+
+    /**
+     * 新增模板
+     */
+    @Log(title = "AI添加微信SOP模板", businessType = BusinessType.INSERT)
+    @PostMapping("/template")
+    public AjaxResult addTemplate(@RequestBody AiAddwxSopTemplate template) {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        template.setCompanyId(loginUser.getCompany().getCompanyId());
+        template.setDelFlag(0);
+        template.setVersion(1);
+        Long templateId = templateService.insertAiAddwxSopTemplate(template);
+        return AjaxResult.success(templateId);
+    }
+
+    /**
+     * 修改模板
+     */
+    @Log(title = "AI添加微信SOP模板", businessType = BusinessType.UPDATE)
+    @PutMapping("/template")
+    public AjaxResult editTemplate(@RequestBody AiAddwxSopTemplate template) {
+        templateService.updateAiAddwxSopTemplate(template);
+        return AjaxResult.success();
+    }
+
+    /**
+     * 删除模板
+     */
+    @Log(title = "AI添加微信SOP模板", businessType = BusinessType.DELETE)
+    @DeleteMapping("/template/{templateIds}")
+    public AjaxResult removeTemplate(@PathVariable Long[] templateIds) {
+        templateService.deleteAiAddwxSopTemplateByIds(templateIds);
+        return AjaxResult.success();
+    }
+
+    /**
+     * 保存模板及节点(含节点增删改)
+     */
+    @Log(title = "AI添加微信SOP模板", businessType = BusinessType.UPDATE)
+    @PostMapping("/template/saveWithNodes")
+    public AjaxResult saveTemplateWithNodes(@RequestBody AiAddwxSopTemplate template) {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        template.setCompanyId(loginUser.getCompany().getCompanyId());
+        // nodes通过请求体中的额外字段传入,此处简化处理
+        Long templateId = templateService.saveTemplateWithNodes(template, null);
+        return AjaxResult.success(templateId);
+    }
+
+    // ==================== 模板节点管理 ====================
+
+    /**
+     * 查询模板节点列表
+     */
+    @GetMapping("/templateNode/list/{templateId}")
+    public AjaxResult templateNodeList(@PathVariable Long templateId) {
+        List<AiAddwxSopTemplateNode> list = templateNodeService.selectAiAddwxSopTemplateNodeByTemplateId(templateId);
+        return AjaxResult.success(list);
+    }
+
+    /**
+     * 获取模板节点详情
+     */
+    @GetMapping("/templateNode/{nodeId}")
+    public AjaxResult templateNodeDetail(@PathVariable Long nodeId) {
+        return AjaxResult.success(templateNodeService.selectAiAddwxSopTemplateNodeById(nodeId));
+    }
+
+    /**
+     * 新增模板节点
+     */
+    @Log(title = "AI添加微信SOP模板节点", businessType = BusinessType.INSERT)
+    @PostMapping("/templateNode")
+    public AjaxResult addTemplateNode(@RequestBody AiAddwxSopTemplateNode node) {
+        templateNodeService.insertAiAddwxSopTemplateNode(node);
+        return AjaxResult.success(node.getNodeId());
+    }
+
+    /**
+     * 修改模板节点
+     */
+    @Log(title = "AI添加微信SOP模板节点", businessType = BusinessType.UPDATE)
+    @PutMapping("/templateNode")
+    public AjaxResult editTemplateNode(@RequestBody AiAddwxSopTemplateNode node) {
+        templateNodeService.updateAiAddwxSopTemplateNode(node);
+        return AjaxResult.success();
+    }
+
+    /**
+     * 删除模板节点
+     */
+    @Log(title = "AI添加微信SOP模板节点", businessType = BusinessType.DELETE)
+    @DeleteMapping("/templateNode/{nodeId}")
+    public AjaxResult removeTemplateNode(@PathVariable Long nodeId) {
+        templateNodeService.deleteAiAddwxSopTemplateNodeById(nodeId);
+        return AjaxResult.success();
+    }
+
+    // ==================== 模板节点规则管理 ====================
+
+    /**
+     * 查询节点规则列表
+     */
+    @GetMapping("/templateNodeRule/list/{nodeId}")
+    public AjaxResult templateNodeRuleList(@PathVariable Long nodeId) {
+        List<AiAddwxSopTemplateNodeRule> list = templateNodeRuleService.selectAiAddwxSopTemplateNodeRuleByNodeId(nodeId);
+        return AjaxResult.success(list);
+    }
+
+    /**
+     * 获取节点规则详情
+     */
+    @GetMapping("/templateNodeRule/{ruleId}")
+    public AjaxResult templateNodeRuleDetail(@PathVariable Long ruleId) {
+        return AjaxResult.success(templateNodeRuleService.selectAiAddwxSopTemplateNodeRuleById(ruleId));
+    }
+
+    /**
+     * 新增节点规则
+     */
+    @Log(title = "AI添加微信SOP模板节点规则", businessType = BusinessType.INSERT)
+    @PostMapping("/templateNodeRule")
+    public AjaxResult addTemplateNodeRule(@RequestBody AiAddwxSopTemplateNodeRule rule) {
+        templateNodeRuleService.insertAiAddwxSopTemplateNodeRule(rule);
+        return AjaxResult.success(rule.getRuleId());
+    }
+
+    /**
+     * 修改节点规则
+     */
+    @Log(title = "AI添加微信SOP模板节点规则", businessType = BusinessType.UPDATE)
+    @PutMapping("/templateNodeRule")
+    public AjaxResult editTemplateNodeRule(@RequestBody AiAddwxSopTemplateNodeRule rule) {
+        templateNodeRuleService.updateAiAddwxSopTemplateNodeRule(rule);
+        return AjaxResult.success();
+    }
+
+    /**
+     * 删除节点规则
+     */
+    @Log(title = "AI添加微信SOP模板节点规则", businessType = BusinessType.DELETE)
+    @DeleteMapping("/templateNodeRule/{ruleId}")
+    public AjaxResult removeTemplateNodeRule(@PathVariable Long ruleId) {
+        templateNodeRuleService.deleteAiAddwxSopTemplateNodeRuleById(ruleId);
+        return AjaxResult.success();
+    }
+
+    // ==================== 个人工作流管理 ====================
+
+    /**
+     * 查询个人工作流列表
+     */
+    @GetMapping("/userWorkflow/list")
+    public TableDataInfo userWorkflowList(AiAddwxSopUserWorkflow userWorkflow) {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        userWorkflow.setCompanyId(loginUser.getCompany().getCompanyId());
+        startPage();
+        List<AiAddwxSopUserWorkflow> list = userWorkflowService.selectAiAddwxSopUserWorkflowList(userWorkflow);
+        return getDataTable(list);
+    }
+
+    /**
+     * 获取个人工作流详情(含节点树)
+     */
+    @GetMapping("/userWorkflow/{userWorkflowId}")
+    public AjaxResult userWorkflowDetail(@PathVariable Long userWorkflowId) {
+        AiAddwxSopUserWorkflow workflow = userWorkflowService.selectAiAddwxSopUserWorkflowById(userWorkflowId);
+        if (workflow == null) {
+            return AjaxResult.error("工作流不存在");
+        }
+        List<AiAddwxSopUserNode> nodes = userWorkflowService.selectUserNodesByWorkflowId(userWorkflowId);
+        AjaxResult result = AjaxResult.success(workflow);
+        result.put("nodes", nodes);
+        return result;
+    }
+
+    /**
+     * 根据模板创建个人工作流
+     */
+    @Log(title = "AI添加微信SOP个人工作流", businessType = BusinessType.INSERT)
+    @PostMapping("/userWorkflow/createFromTemplate")
+    public AjaxResult createFromTemplate(@RequestParam Long templateId
+//            , @RequestParam Long businessKey
+    ) {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        Long companyUserId = loginUser.getUser().getUserId();
+        Long companyId = loginUser.getCompany().getCompanyId();
+        Long userWorkflowId = userWorkflowService.createUserWorkflowFromTemplate(templateId, companyUserId, companyId
+//                , businessKey
+        );
+        return AjaxResult.success(userWorkflowId);
+    }
+
+    /**
+     * 推进工作流到下一节点
+     */
+    @Log(title = "AI添加微信SOP个人工作流", businessType = BusinessType.UPDATE)
+    @PostMapping("/userWorkflow/advance")
+    public AjaxResult advanceWorkflow(@RequestParam Long userWorkflowId, @RequestParam Long currentNodeId) {
+        userWorkflowService.advanceToNextNode(userWorkflowId, currentNodeId);
+        return AjaxResult.success();
+    }
+
+    /**
+     * 修改个人工作流
+     */
+    @Log(title = "AI添加微信SOP个人工作流", businessType = BusinessType.UPDATE)
+    @PutMapping("/userWorkflow")
+    public AjaxResult editUserWorkflow(@RequestBody AiAddwxSopUserWorkflow userWorkflow) {
+        userWorkflowService.updateAiAddwxSopUserWorkflow(userWorkflow);
+        return AjaxResult.success();
+    }
+
+    /**
+     * 删除个人工作流
+     */
+    @Log(title = "AI添加微信SOP个人工作流", businessType = BusinessType.DELETE)
+    @DeleteMapping("/userWorkflow/{userWorkflowIds}")
+    public AjaxResult removeUserWorkflow(@PathVariable Long[] userWorkflowIds) {
+        userWorkflowService.deleteAiAddwxSopUserWorkflowByIds(userWorkflowIds);
+        return AjaxResult.success();
+    }
+
+    // ==================== 个人节点管理 ====================
+
+    /**
+     * 查询个人节点列表
+     */
+    @GetMapping("/userNode/list/{userWorkflowId}")
+    public AjaxResult userNodeList(@PathVariable Long userWorkflowId) {
+        List<AiAddwxSopUserNode> list = userNodeService.selectAiAddwxSopUserNodeByWorkflowId(userWorkflowId);
+        return AjaxResult.success(list);
+    }
+
+    /**
+     * 获取个人节点详情
+     */
+    @GetMapping("/userNode/{userNodeId}")
+    public AjaxResult userNodeDetail(@PathVariable Long userNodeId) {
+        return AjaxResult.success(userNodeService.selectAiAddwxSopUserNodeById(userNodeId));
+    }
+
+    /**
+     * 修改个人节点(含提示词、条件等)
+     */
+    @Log(title = "AI添加微信SOP个人节点", businessType = BusinessType.UPDATE)
+    @PutMapping("/userNode")
+    public AjaxResult editUserNode(@RequestBody AiAddwxSopUserNode userNode) {
+        userNodeService.updateAiAddwxSopUserNode(userNode);
+        return AjaxResult.success();
+    }
+
+    /**
+     * 更新节点状态
+     */
+    @Log(title = "AI添加微信SOP个人节点", businessType = BusinessType.UPDATE)
+    @PutMapping("/userNode/status")
+    public AjaxResult updateNodeStatus(@RequestParam Long userNodeId, @RequestParam Integer status) {
+        userNodeService.updateNodeStatus(userNodeId, status);
+        return AjaxResult.success();
+    }
+
+    // ==================== 个人节点规则管理 ====================
+
+    /**
+     * 查询个人节点规则列表
+     */
+    @GetMapping("/userNodeRule/list/{userNodeId}")
+    public AjaxResult userNodeRuleList(@PathVariable Long userNodeId) {
+        List<AiAddwxSopUserNodeRule> list = userNodeRuleService.selectAiAddwxSopUserNodeRuleByUserNodeId(userNodeId);
+        return AjaxResult.success(list);
+    }
+
+    /**
+     * 获取个人节点规则详情
+     */
+    @GetMapping("/userNodeRule/{userNodeRuleId}")
+    public AjaxResult userNodeRuleDetail(@PathVariable Long userNodeRuleId) {
+        return AjaxResult.success(userNodeRuleService.selectAiAddwxSopUserNodeRuleById(userNodeRuleId));
+    }
+
+    /**
+     * 新增个人节点规则
+     */
+    @Log(title = "AI添加微信SOP个人节点规则", businessType = BusinessType.INSERT)
+    @PostMapping("/userNodeRule")
+    public AjaxResult addUserNodeRule(@RequestBody AiAddwxSopUserNodeRule rule) {
+        userNodeRuleService.insertAiAddwxSopUserNodeRule(rule);
+        return AjaxResult.success(rule.getUserNodeRuleId());
+    }
+
+    /**
+     * 修改个人节点规则
+     */
+    @Log(title = "AI添加微信SOP个人节点规则", businessType = BusinessType.UPDATE)
+    @PutMapping("/userNodeRule")
+    public AjaxResult editUserNodeRule(@RequestBody AiAddwxSopUserNodeRule rule) {
+        userNodeRuleService.updateAiAddwxSopUserNodeRule(rule);
+        return AjaxResult.success();
+    }
+
+    /**
+     * 删除个人节点规则
+     */
+    @Log(title = "AI添加微信SOP个人节点规则", businessType = BusinessType.DELETE)
+    @DeleteMapping("/userNodeRule/{userNodeRuleId}")
+    public AjaxResult removeUserNodeRule(@PathVariable Long userNodeRuleId) {
+        userNodeRuleService.deleteAiAddwxSopUserNodeRuleById(userNodeRuleId);
+        return AjaxResult.success();
+    }
+
+    // ==================== 执行日志管理 ====================
+
+    /**
+     * 查询执行日志列表
+     */
+    @GetMapping("/execLog/list")
+    public TableDataInfo execLogList(AiAddwxSopExecLog execLog) {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        execLog.setCompanyId(loginUser.getCompany().getCompanyId());
+        startPage();
+        List<AiAddwxSopExecLog> list = execLogService.selectAiAddwxSopExecLogList(execLog);
+        return getDataTable(list);
+    }
+
+    /**
+     * 根据工作流ID查询执行日志
+     */
+    @GetMapping("/execLog/workflow/{userWorkflowId}")
+    public AjaxResult execLogByWorkflow(@PathVariable Long userWorkflowId) {
+        List<AiAddwxSopExecLog> list = execLogService.selectAiAddwxSopExecLogByWorkflowId(userWorkflowId);
+        return AjaxResult.success(list);
+    }
+
+    /**
+     * 根据节点ID查询执行日志
+     */
+    @GetMapping("/execLog/node/{userNodeId}")
+    public AjaxResult execLogByNode(@PathVariable Long userNodeId) {
+        List<AiAddwxSopExecLog> list = execLogService.selectAiAddwxSopExecLogByNodeId(userNodeId);
+        return AjaxResult.success(list);
+    }
+
+    /**
+     * 获取执行日志详情
+     */
+    @GetMapping("/execLog/{logId}")
+    public AjaxResult execLogDetail(@PathVariable Long logId) {
+        return AjaxResult.success(execLogService.selectAiAddwxSopExecLogById(logId));
+    }
+
+    /**
+     * 导出执行日志
+     */
+    @Log(title = "AI添加微信SOP执行日志", businessType = BusinessType.EXPORT)
+    @GetMapping("/execLog/export")
+    public AjaxResult exportExecLog(AiAddwxSopExecLog execLog) {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        execLog.setCompanyId(loginUser.getCompany().getCompanyId());
+        List<AiAddwxSopExecLog> list = execLogService.selectAiAddwxSopExecLogList(execLog);
+        ExcelUtil<AiAddwxSopExecLog> util = new ExcelUtil<>(AiAddwxSopExecLog.class);
+        return util.exportExcel(list, "执行日志数据");
+    }
+
+    // ==================== 标签模板绑定管理 ====================
+
+    /**
+     * 查询标签模板绑定列表
+     */
+    @GetMapping("/tagTemplateBind/list")
+    public TableDataInfo tagTemplateBindList(AiAddwxSopTagTemplateBind bind) {
+        startPage();
+        List<AiAddwxSopTagTemplateBind> list = tagTemplateBindService.selectAiAddwxSopTagTemplateBindList(bind);
+        return getDataTable(list);
+    }
+
+    /**
+     * 根据标签ID查询绑定
+     */
+    @GetMapping("/tagTemplateBind/byTag/{qwTagId}")
+    public AjaxResult tagTemplateBindByTag(@PathVariable Long qwTagId) {
+        AiAddwxSopTagTemplateBind bind = tagTemplateBindService.selectAiAddwxSopTagTemplateBindByTagId(qwTagId);
+        return AjaxResult.success(bind);
+    }
+
+    /**
+     * 新增标签模板绑定(一个标签只能绑定一个模板,如已存在则更新)
+     */
+    @Log(title = "标签模板绑定", businessType = BusinessType.INSERT)
+    @PostMapping("/tagTemplateBind")
+    public AjaxResult addTagTemplateBind(@RequestBody AiAddwxSopTagTemplateBind bind) {
+        // 检查该标签是否已绑定
+        AiAddwxSopTagTemplateBind exist = tagTemplateBindService.selectTagBindBytagIdAndTemplateId(bind);
+//        AiAddwxSopTagTemplateBind exist = tagTemplateBindService.selectAiAddwxSopTagTemplateBindByTagId(bind.getQwTagId());
+        if (exist != null) {
+//            // 已存在则更新
+//            bind.setBindId(exist.getBindId());
+//            tagTemplateBindService.updateAiAddwxSopTagTemplateBind(bind);
+            return AjaxResult.error("已存在该标签与该模板绑定关系");
+        }
+        tagTemplateBindService.insertAiAddwxSopTagTemplateBind(bind);
+        return AjaxResult.success(bind.getBindId());
+    }
+
+    /**
+     * 修改标签模板绑定
+     */
+    @Log(title = "标签模板绑定", businessType = BusinessType.UPDATE)
+    @PutMapping("/tagTemplateBind")
+    public AjaxResult editTagTemplateBind(@RequestBody AiAddwxSopTagTemplateBind bind) {
+        tagTemplateBindService.updateAiAddwxSopTagTemplateBind(bind);
+        return AjaxResult.success();
+    }
+
+    /**
+     * 删除标签模板绑定
+     */
+    @Log(title = "标签模板绑定", businessType = BusinessType.DELETE)
+    @DeleteMapping("/tagTemplateBind/{bindIds}")
+    public AjaxResult removeTagTemplateBind(@PathVariable Long[] bindIds) {
+        for (Long bindId : bindIds) {
+            tagTemplateBindService.deleteAiAddwxSopTagTemplateBindById(bindId);
+        }
+        return AjaxResult.success();
+    }
+
+    /**
+     * 测试
+
+     * @return
+     */
+    @PostMapping("/initAddWxSop")
+    public AjaxResult initAddWxSop(@RequestBody InitAddWxSopTestParam params){
+        templateService.initAddWxSop(params.getContacts(),params.getTagIds());
+
+        return AjaxResult.success();
+    }
+}

+ 86 - 9
fs-ipad-task/src/main/java/com/fs/app/task/SendMsg.java

@@ -3,25 +3,26 @@ package com.fs.app.task;
 
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.fs.app.service.IpadSendServer;
 import com.fs.common.core.redis.RedisCacheT;
 import com.fs.common.utils.CloudHostUtils;
 import com.fs.common.utils.DateUtils;
 import com.fs.common.utils.PubFun;
+import com.fs.company.domain.AiAddwxSopUserNodeRule;
+import com.fs.company.domain.AiAddwxSopUserWorkflow;
+import com.fs.company.enums.AiAddWechatSopNodeEnum;
+import com.fs.company.mapper.AiAddwxSopUserNodeRuleMapper;
+import com.fs.company.mapper.AiAddwxSopUserWorkflowMapper;
+import com.fs.company.service.IAiAddwxSopUserNodeService;
 import com.fs.company.service.ICompanyMiniappService;
 import com.fs.course.config.CourseConfig;
 import com.fs.course.domain.FsCoursePlaySourceConfig;
 import com.fs.course.service.IFsCoursePlaySourceConfigService;
 import com.fs.ipad.vo.BaseVo;
-import com.fs.qw.domain.QwIpadServer;
-import com.fs.qw.domain.QwPushCount;
-import com.fs.qw.domain.QwRestrictionPushRecord;
-import com.fs.qw.domain.QwUser;
-import com.fs.qw.mapper.QwIpadServerMapper;
-import com.fs.qw.mapper.QwPushCountMapper;
-import com.fs.qw.mapper.QwRestrictionPushRecordMapper;
-import com.fs.qw.mapper.QwUserMapper;
+import com.fs.qw.domain.*;
+import com.fs.qw.mapper.*;
 import com.fs.qw.service.impl.AsyncSopTestService;
 import com.fs.qw.vo.QwSopCourseFinishTempSetting;
 import com.fs.qw.vo.QwSopTempSetting;
@@ -41,6 +42,7 @@ import org.springframework.util.StringUtils;
 
 import java.text.SimpleDateFormat;
 import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
 import java.util.*;
 import java.util.concurrent.*;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -62,6 +64,10 @@ public class SendMsg {
     private final IFsCoursePlaySourceConfigService fsCoursePlaySourceConfigService;
     private final QwPushCountMapper qwPushCountMapper;
     private final QwRestrictionPushRecordMapper qwRestrictionPushRecordMapper;
+    private final IAiAddwxSopUserNodeService aiAddwxSopUserNodeService;
+    private final AiAddwxSopUserWorkflowMapper aiAddwxSopUserWorkflowMapper;
+    private final AiAddwxSopUserNodeRuleMapper aiAddwxSopUserNodeRuleMapper;
+    private final QwExternalContactMapper qwExternalContactMapper;
 
     @Value("${group-no}")
     private String groupNo;
@@ -95,7 +101,7 @@ public class SendMsg {
     @Qualifier("customThreadPool")
     private ThreadPoolTaskExecutor customThreadPool;
 
-    public SendMsg(QwUserMapper qwUserMapper, QwSopLogsMapper qwSopLogsMapper, IpadSendServer sendServer, SysConfigMapper sysConfigMapper, IQwSopLogsService qwSopLogsService, QwIpadServerMapper qwIpadServerMapper, RedisCacheT<Long> redisCache, AsyncSopTestService asyncSopTestService, ICompanyMiniappService companyMiniappService, IFsCoursePlaySourceConfigService fsCoursePlaySourceConfigService, QwPushCountMapper qwPushCountMapper, QwRestrictionPushRecordMapper qwRestrictionPushRecordMapper) {
+    public SendMsg(QwUserMapper qwUserMapper, QwSopLogsMapper qwSopLogsMapper, IpadSendServer sendServer, SysConfigMapper sysConfigMapper, IQwSopLogsService qwSopLogsService, QwIpadServerMapper qwIpadServerMapper, RedisCacheT<Long> redisCache, AsyncSopTestService asyncSopTestService, ICompanyMiniappService companyMiniappService, IFsCoursePlaySourceConfigService fsCoursePlaySourceConfigService, QwPushCountMapper qwPushCountMapper, QwRestrictionPushRecordMapper qwRestrictionPushRecordMapper, IAiAddwxSopUserNodeService aiAddwxSopUserNodeService, AiAddwxSopUserWorkflowMapper aiAddwxSopUserWorkflowMapper, AiAddwxSopUserNodeRuleMapper aiAddwxSopUserNodeRuleMapper, QwExternalContactMapper qwExternalContactMapper) {
         this.qwUserMapper = qwUserMapper;
         this.qwSopLogsMapper = qwSopLogsMapper;
         this.sendServer = sendServer;
@@ -108,6 +114,10 @@ public class SendMsg {
         this.fsCoursePlaySourceConfigService = fsCoursePlaySourceConfigService;
         this.qwPushCountMapper = qwPushCountMapper;
         this.qwRestrictionPushRecordMapper = qwRestrictionPushRecordMapper;
+        this.aiAddwxSopUserNodeService = aiAddwxSopUserNodeService;
+        this.aiAddwxSopUserWorkflowMapper = aiAddwxSopUserWorkflowMapper;
+        this.aiAddwxSopUserNodeRuleMapper = aiAddwxSopUserNodeRuleMapper;
+        this.qwExternalContactMapper = qwExternalContactMapper;
     }
     private List<QwUser> getQwUserList() {
         if (qwUserList.isEmpty()) {
@@ -477,4 +487,71 @@ public class SendMsg {
         long end3 = System.currentTimeMillis();
         log.info("销售执行完成:{}, 耗时:{}", user.getQwUserName(), end3 - start3);
     }
+    /**
+     * 今正cid加微后定时发消息
+     */
+    @Scheduled(cron = "0 * 9-18 * * ?") // 每分钟
+    public void sendAiAddWxWorkflowMsg(){
+        if(!CloudHostUtils.hasCloudHostName("今正科技")){
+            log.info("非今正科技,不执行cid加微后工作流发企微消息");
+            return;
+        }
+
+        log.info("开始执行cid加微后sop任务,现在时间{}",LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
+        List<AiAddwxSopUserWorkflow> aiAddwxSopUserWorkflows = aiAddwxSopUserWorkflowMapper.selectList(new LambdaQueryWrapper<AiAddwxSopUserWorkflow>()
+                .eq(AiAddwxSopUserWorkflow::getDelFlag, 0).ne(AiAddwxSopUserWorkflow::getCurrentNodeType, AiAddWechatSopNodeEnum.NOT_DEAL.getCode())
+                .ne(AiAddwxSopUserWorkflow::getCurrentNodeType, AiAddWechatSopNodeEnum.DEAL.getCode()));
+        Map<String, List<AiAddwxSopUserWorkflow>> workflowsMap = aiAddwxSopUserWorkflows
+                .stream()
+                .collect(Collectors.groupingBy(AiAddwxSopUserWorkflow::getCurrentNodeType));
+        //        Set<AiAddWechatSopNodeEnum> skipNodes = EnumSet.of(
+//                AiAddWechatSopNodeEnum.DEAL,
+//                AiAddWechatSopNodeEnum.NOT_DEAL
+//        );
+        for (Map.Entry<String, List<AiAddwxSopUserWorkflow>> next : workflowsMap.entrySet()) {
+//                        AiAddWechatSopNodeEnum nodeEnum = AiAddWechatSopNodeEnum.fromCode(next.getKey());
+//            if (skipNodes.contains(nodeEnum)) {
+//                continue;
+//            }
+            aiAddwxSopUserNodeService.nextNode(next);
+        }
+        //执行后再发送user_rule的消息
+        LocalDateTime now = LocalDateTime.now();
+        LocalDateTime minutesAgo = now.minusMinutes(1);
+        List<AiAddwxSopUserNodeRule> sendRules = aiAddwxSopUserNodeRuleMapper.selectList(new LambdaQueryWrapper<AiAddwxSopUserNodeRule>()
+                .eq(AiAddwxSopUserNodeRule::getIsSent, 0).isNotNull(AiAddwxSopUserNodeRule::getRealSendTime)
+                .between(AiAddwxSopUserNodeRule::getRealSendTime, minutesAgo, now));
+        sendRules.forEach( rule ->{
+            try {
+                cidAddwxSopSendQwMsg(rule);
+            }catch (Exception e){
+                log.error("nodeRule{}发送消息错误:{}",rule.getUserNodeRuleId(), e.getMessage());
+            }
+            rule.setIsSent(1);
+            aiAddwxSopUserNodeRuleMapper.updateById( rule);
+        });
+    }
+        private void cidAddwxSopSendQwMsg(AiAddwxSopUserNodeRule rule ){
+        QwUser qwUser = qwUserMapper.selectById(rule.getQwUserId());
+        QwExternalContact externalContact = qwExternalContactMapper.selectQwExternalContactById(rule.getQwExternalId());
+            //只拼一个发消息的逻辑
+        QwSopCourseFinishTempSetting.Setting content = new QwSopCourseFinishTempSetting.Setting();
+        content.setValue(rule.getContentJson());
+        content.setContentType("1");
+
+        QwSopLogs qwSopLogs = new QwSopLogs();
+        qwSopLogs.setId(String.valueOf(rule.getUserNodeRuleId()));
+        qwSopLogs.setSendType(1);
+        qwSopLogs.setExternalUserId(externalContact.getExternalUserId());
+
+        BaseVo vo = new BaseVo();
+        vo.setCorpCode(qwUser.getCorpId());
+        vo.setCorpId(qwUser.getCorpId());
+        // 判断这个企微是否需要发送
+        if (!sendServer.isSend(qwUser, vo)) {
+            log.info(" 当前这个企微不需要发送 数据{}", qwUser);
+        }
+
+        sendServer.send(content, qwUser, qwSopLogs, null, vo);
+    }
 }

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

@@ -640,9 +640,17 @@ public class SmsServiceImpl implements ISmsService
         SysConfig sysConfig = sysConfigMapper.selectConfigByConfigKey("his.sms");
         FsSmsConfig sms = JSON.parseObject(sysConfig.getConfigValue(), FsSmsConfig.class);
         if (sms.getType().equals("rf")) {
+
             try {
                 content = content.replace("${sms.sign}",sms.getRfSign());
-                urls = sms.getRfUrl1() + "sms?action=send&account=" + sms.getRfAccount1() + "&password=" + sms.getRfPassword1() + "&mobile=" + phone + "&content=" + URLEncoder.encode(content, "UTF-8") + "&extno=" + sms.getRfCode1() + "&rt=json";
+                if (temp.getTempType().equals(1)) {
+                    urls = sms.getRfUrl1() + "sms?action=send&account=" + sms.getRfAccount1() + "&password=" + sms.getRfPassword1() + "&mobile=" + phone + "&content=" + URLEncoder.encode(sms.getRfSign() + content, "UTF-8") + "&extno=" + sms.getRfCode1() + "&rt=json";
+                } else if (temp.getTempType().equals(2)) {
+                    urls = sms.getRfUrl2() + "sms?action=send&account=" + sms.getRfAccount2() + "&password=" + sms.getRfPassword2() + "&mobile=" + phone + "&content=" + URLEncoder.encode(sms.getRfSign() + content + "拒收请回复R", "UTF-8") + "&extno=" + sms.getRfCode2() + "&rt=json";
+                }
+                if(StringUtils.isEmpty(urls)){
+                    urls = sms.getRfUrl1() + "sms?action=send&account=" + sms.getRfAccount1() + "&password=" + sms.getRfPassword1() + "&mobile=" + phone + "&content=" + URLEncoder.encode(sms.getRfSign() + content, "UTF-8") + "&extno=" + sms.getRfCode1() + "&rt=json";
+                }
             } catch (UnsupportedEncodingException e) {
                 e.printStackTrace();
             }

+ 77 - 0
fs-service/src/main/java/com/fs/company/domain/AiAddwxSopExecLog.java

@@ -0,0 +1,77 @@
+package com.fs.company.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fs.common.annotation.Excel;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * AI添加微信SOP执行日志对象 ai_addwx_sop_exec_log
+ *
+ * @author fs
+ * @date 2026-06-24
+ */
+@Data
+public class AiAddwxSopExecLog implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    /** 日志ID */
+    @TableId(type = IdType.AUTO)
+    private Long logId;
+
+    /** 个人工作流ID */
+    private Long userWorkflowId;
+
+    /** 个人节点ID */
+    private Long userNodeId;
+
+    /** 节点Key */
+    private String nodeKey;
+
+    /** 节点名称 */
+    private String nodeName;
+
+    /** 节点类型 */
+    private String nodeType;
+
+    /** 输入数据(JSON) */
+    private String inputData;
+
+    /** 输出数据(JSON) */
+    private String outputData;
+
+    /** 执行状态: 0-待执行, 1-执行中, 2-成功, 3-失败, 4-跳过 */
+    @Excel(name = "执行状态", readConverterExp = "0=待执行,1=执行中,2=成功,3=失败,4=跳过")
+    private Integer status;
+
+    /** 开始时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date startTime;
+
+    /** 结束时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date endTime;
+
+    /** 执行时长(毫秒) */
+    private Long duration;
+
+    /** 错误信息 */
+    private String errorMessage;
+
+    /** 重试次数 */
+    private Integer retryCount;
+
+    /** 企业用户ID */
+    private Long companyUserId;
+
+    /** 企业ID */
+    private Long companyId;
+
+    /** 创建时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date createTime;
+}

+ 47 - 0
fs-service/src/main/java/com/fs/company/domain/AiAddwxSopTagTemplateBind.java

@@ -0,0 +1,47 @@
+package com.fs.company.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 企微标签与工作流模板绑定关系对象 ai_addwx_sop_tag_template_bind
+ *
+ * @author fs
+ * @date 2026-06-24
+ */
+@Data
+public class AiAddwxSopTagTemplateBind implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    /** 绑定ID */
+    @TableId(type = IdType.AUTO)
+    private Long bindId;
+
+    /** 企微标签ID */
+    private Long qwTagId;
+
+    /** 企微标签名称 */
+    private String qwTagName;
+
+    /** 模板ID */
+    private Long templateId;
+
+    /** 模板名称 */
+    private String templateName;
+
+    /** 优先级 0-100,评分越高优先级越高 */
+    private Integer priority;
+
+    /** 创建时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date createTime;
+
+    /** 更新时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date updateTime;
+}

+ 48 - 0
fs-service/src/main/java/com/fs/company/domain/AiAddwxSopTemplate.java

@@ -0,0 +1,48 @@
+package com.fs.company.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fs.common.annotation.Excel;
+import com.fs.common.core.domain.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.util.Date;
+
+/**
+ * AI添加微信SOP工作流模板对象 ai_addwx_sop_template
+ *
+ * @author fs
+ * @date 2026-06-24
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class AiAddwxSopTemplate extends BaseEntity {
+    private static final long serialVersionUID = 1L;
+
+    /** 模板ID */
+    @TableId(type = IdType.AUTO)
+    private Long templateId;
+
+    /** 模板名称 */
+    @Excel(name = "模板名称")
+    private String templateName;
+
+    /** 模板描述 */
+    @Excel(name = "模板描述")
+    private String templateDesc;
+
+    /** 模板状态 0禁用 1启用 */
+    @Excel(name = "模板状态", readConverterExp = "0=禁用,1=启用")
+    private Integer status;
+
+    /** 版本号 */
+    private Integer version;
+
+    /** 企业ID */
+    private Long companyId;
+
+    /** 删除标志 0正常 1删除 */
+    private Integer delFlag;
+}

+ 65 - 0
fs-service/src/main/java/com/fs/company/domain/AiAddwxSopTemplateNode.java

@@ -0,0 +1,65 @@
+package com.fs.company.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * AI添加微信SOP模板节点对象 ai_addwx_sop_template_node
+ *
+ * @author fs
+ * @date 2026-06-24
+ */
+@Data
+public class AiAddwxSopTemplateNode implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    /** 节点ID */
+    @TableId(type = IdType.AUTO)
+    private Long nodeId;
+
+    /** 模板ID */
+    private Long templateId;
+
+    /** 节点唯一标识 */
+    private String nodeKey;
+
+    /** 节点名称 */
+    private String nodeName;
+
+    /** 节点类型: receive-收货节点, medication-用药节点, effect_feedback-效果反馈节点, transfer_ai-转AI节点 */
+    private String nodeType;
+
+    /** 上一个节点ID */
+    private Long prevNodeId;
+
+    /** 下一个节点ID */
+    private Long nextNodeId;
+
+    /** 节点图标 */
+    private String nodeIcon;
+
+    /** 节点颜色 */
+    private String nodeColor;
+
+    /** 节点配置JSON(包含提示词、条件等配置) */
+    private String nodeConfig;
+
+    /** 是否有效回复 */
+    private Boolean isValidReply;
+
+    /** 排序 */
+    private Integer sortOrder;
+
+    /** 创建时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date createTime;
+
+    /** 更新时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date updateTime;
+}

+ 56 - 0
fs-service/src/main/java/com/fs/company/domain/AiAddwxSopTemplateNodeRule.java

@@ -0,0 +1,56 @@
+package com.fs.company.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * AI添加微信SOP模板节点规则对象 ai_addwx_sop_template_node_rule
+ *
+ * @author fs
+ * @date 2026-06-24
+ */
+@Data
+public class AiAddwxSopTemplateNodeRule implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    /** 规则ID */
+    @TableId(type = IdType.AUTO)
+    private Long ruleId;
+
+    /** 节点ID */
+    private Long nodeId;
+
+    /** 规则名称(内部可见) */
+    private String ruleName;
+
+    /** 发送时间 HH:mm */
+    private String sendTime;
+
+    /** 第几天 */
+    private Integer sendDay;
+
+    /** 消息类别: 1-普通, 2-课程, 4-AI触达, 5-打标签, 20-直播间 */
+    private Integer msgType;
+
+    /** 内容类别 */
+    private String contentType;
+
+    /** 内容JSON */
+    private String contentJson;
+
+    /** 排序 */
+    private Integer sortOrder;
+
+    /** 创建时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date createTime;
+
+    /** 更新时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date updateTime;
+}

+ 93 - 0
fs-service/src/main/java/com/fs/company/domain/AiAddwxSopUserNode.java

@@ -0,0 +1,93 @@
+package com.fs.company.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * AI添加微信SOP个人节点对象 ai_addwx_sop_user_node
+ *
+ * @author fs
+ * @date 2026-06-24
+ */
+@Data
+public class AiAddwxSopUserNode implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    /** 个人节点ID */
+    @TableId(type = IdType.AUTO)
+    private Long userNodeId;
+
+    /** 个人工作流ID */
+    private Long userWorkflowId;
+
+    /** 企微外部联系人ID */
+    private Long qwExternalId;
+
+    /** 企微用户ID */
+    private Long qwUserId;
+
+    /** 关联模板节点ID */
+    private Long templateNodeId;
+
+    /** 节点唯一标识 */
+    private String nodeKey;
+
+    /** 节点名称 */
+    private String nodeName;
+
+    /** 节点类型: receive-收货节点, medication-用药节点, effect_feedback-效果反馈节点, transfer_ai-转AI节点 */
+    private String nodeType;
+
+    /** 上一个节点ID */
+    private Long prevNodeId;
+
+    /** 下一个节点ID */
+    private Long nextNodeId;
+
+    /** 进入下一节点条件(JSON格式,如 {"type":"timeout","value":86400} 或 {"type":"manual"}) */
+    private String nextCondition;
+
+    /** 子节点ID列表(JSON数组,如 [101,102,103]) */
+    private String childNodes;
+
+    /** 提示词 */
+    private String prompt;
+
+    /** 节点配置JSON(扩展配置) */
+    private String nodeConfig;
+
+    /** 是否有效回复: 0-未有效回复, 1-已有效回复 */
+    private Integer isValidReply;
+
+    /** 节点状态: 0-待执行, 1-执行中, 2-已完成, 3-已跳过, 4-执行失败 */
+    private Integer status;
+
+    /** 排序 */
+    private Integer sortOrder;
+
+    /** 开始时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date startTime;
+
+    /** 结束时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date endTime;
+
+    /** 创建时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date createTime;
+
+    /** 更新时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date updateTime;
+
+    /** 子节点列表(非数据库字段) */
+    @TableField(exist = false)
+    private java.util.List<AiAddwxSopUserNode> childNodeList;
+}

+ 71 - 0
fs-service/src/main/java/com/fs/company/domain/AiAddwxSopUserNodeRule.java

@@ -0,0 +1,71 @@
+package com.fs.company.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * AI添加微信SOP个人节点规则对象 ai_addwx_sop_user_node_rule
+ *
+ * @author fs
+ * @date 2026-06-24
+ */
+@Data
+@Accessors(chain = true)
+public class AiAddwxSopUserNodeRule implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    /** 个人节点规则ID */
+    @TableId(type = IdType.AUTO)
+    private Long userNodeRuleId;
+
+    /** 个人节点ID */
+    private Long userNodeId;
+
+    /** 企微外部联系人ID */
+    private Long qwExternalId;
+
+    /** 企微用户ID */
+    private Long qwUserId;
+
+    /** 规则名称(内部可见) */
+    private String ruleName;
+
+    /** 发送时间 HH:mm */
+    private String sendTime;
+
+    /** 第几天 */
+    private Integer sendDay;
+
+    /** 消息类别: 1-普通, 2-课程, 4-AI触达, 5-打标签, 20-直播间 */
+    private Integer msgType;
+
+    /** 内容类别 */
+    private String contentType;
+
+    /** 内容JSON */
+    private String contentJson;
+
+    /** 排序 */
+    private Integer sortOrder;
+
+    /** 是否发送 0-未发送 1-已发送 */
+    private Integer isSent;
+
+    /** 真实需要发送时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date realSendTime;
+
+    /** 创建时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date createTime;
+
+    /** 更新时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date updateTime;
+}

+ 95 - 0
fs-service/src/main/java/com/fs/company/domain/AiAddwxSopUserWorkflow.java

@@ -0,0 +1,95 @@
+package com.fs.company.domain;
+
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fs.common.annotation.Excel;
+import com.fs.common.core.domain.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+
+import java.util.Date;
+
+/**
+ * AI添加微信SOP个人工作流对象 ai_addwx_sop_user_workflow
+ *
+ * @author fs
+ * @date 2026-06-24
+ */
+@Data
+@Accessors(chain = true)
+public class AiAddwxSopUserWorkflow  {
+    private static final long serialVersionUID = 1L;
+
+    /** 个人工作流ID */
+    @TableId(type = IdType.AUTO)
+    private Long userWorkflowId;
+
+    /** 关联模板ID */
+    private Long templateId;
+
+    /** 企业用户ID(一人一个) */
+    private Long companyUserId;
+
+    /** 企微外部联系人ID */
+    private Long qwExternalId;
+
+    /** 企微用户ID */
+    private Long qwUserId;
+
+    /** 企业ID */
+    private Long companyId;
+
+    /** 工作流状态: 0-未开始, 1-进行中, 2-已完成, 3-已终止 */
+    @Excel(name = "工作流状态", readConverterExp = "0=未开始,1=进行中,2=已完成,3=已终止")
+    private Integer status;
+
+    /** 当前节点ID */
+    private Long currentNodeId;
+
+    /** 当前节点Key */
+    private String currentNodeKey;
+
+    /** 当前节点类型 */
+    private String currentNodeType;
+
+//    /** 关联业务ID(如订单ID、客户ID等) */
+//    private Long businessKey;
+
+    /** 开始时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date startTime;
+
+    /** 结束时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date endTime;
+
+    /** 删除标志 0正常 1删除 */
+    private Integer delFlag;
+
+    /** 创建者 */
+    @TableField(fill = FieldFill.INSERT)
+    private String createBy;
+
+    /** 创建时间 */
+
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @TableField(fill = FieldFill.INSERT)
+    private Date createTime;
+
+    /** 更新者 */
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    private String updateBy;
+
+    /** 更新时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    private Date updateTime;
+
+    /** 备注 */
+    @TableField(exist = false)
+    private String remark;
+}

+ 103 - 0
fs-service/src/main/java/com/fs/company/enums/AiAddWechatSopNodeEnum.java

@@ -0,0 +1,103 @@
+package com.fs.company.enums;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * AI添加微信SOP节点类型枚举
+ *
+ * @author fs
+ * @date 2026-06-24
+ */
+public enum AiAddWechatSopNodeEnum {
+
+    /** 加微 */
+    ADD_WECHAT("1", "用户加微"),
+
+    /** 到货 */
+    ARRIVED("2", "到货"),
+
+    /** 未到货 */
+    NOT_ARRIVED("3", "未到货"),
+
+    /** 使用 */
+    USED("4", "使用"),
+
+    /** 未使用 */
+    NOT_USED("5", "未使用"),
+
+    /** 断药 */
+    DRUG_INTERRUPTION("6", "断药"),
+
+    /** 反馈结果1 */
+    FEEDBACK_1("7", "反馈结果1"),
+
+    /** 反馈结果2 */
+    FEEDBACK_2("8", "反馈结果2"),
+
+    /** 攻单/推荐转人工 */
+    TRANSFER_MANUAL("9", "攻单/推荐转人工"),
+
+    /** 成交 */
+    DEAL("10", "成交"),
+
+    /** 未成交 */
+    NOT_DEAL("11", "未成交");
+
+    private final String code;
+    private final String desc;
+
+    AiAddWechatSopNodeEnum(String code, String desc) {
+        this.code = code;
+        this.desc = desc;
+    }
+
+    public String getCode() {
+        return code;
+    }
+
+    public String getDesc() {
+        return desc;
+    }
+
+    /**
+     * 根据 code 获取枚举
+     */
+    public static AiAddWechatSopNodeEnum fromCode(String code) {
+        if (code == null) {
+            return null;
+        }
+        for (AiAddWechatSopNodeEnum item : values()) {
+            if (item.code.equals(code)) {
+                return item;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * 校验是否为合法节点类型
+     */
+    public static boolean isValid(String code) {
+        return fromCode(code) != null;
+    }
+
+    /**
+     * 获取所有 code 列表
+     */
+    public static List<String> getCodes() {
+        return Arrays.stream(values())
+                .map(AiAddWechatSopNodeEnum::getCode)
+                .collect(Collectors.toList());
+    }
+
+    /**
+     * 判断是否相等
+     */
+    public boolean equalsCode(String code) {
+        return this.code.equals(code);
+    }
+
+
+}

+ 31 - 0
fs-service/src/main/java/com/fs/company/mapper/AiAddwxSopExecLogMapper.java

@@ -0,0 +1,31 @@
+package com.fs.company.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.company.domain.AiAddwxSopExecLog;
+
+import java.util.List;
+
+/**
+ * AI添加微信SOP执行日志Mapper接口
+ *
+ * @author fs
+ * @date 2026-06-24
+ */
+public interface AiAddwxSopExecLogMapper extends BaseMapper<AiAddwxSopExecLog> {
+
+    AiAddwxSopExecLog selectAiAddwxSopExecLogById(Long logId);
+
+    List<AiAddwxSopExecLog> selectAiAddwxSopExecLogList(AiAddwxSopExecLog execLog);
+
+    List<AiAddwxSopExecLog> selectAiAddwxSopExecLogByWorkflowId(Long userWorkflowId);
+
+    List<AiAddwxSopExecLog> selectAiAddwxSopExecLogByNodeId(Long userNodeId);
+
+    int insertAiAddwxSopExecLog(AiAddwxSopExecLog execLog);
+
+    int updateAiAddwxSopExecLog(AiAddwxSopExecLog execLog);
+
+    int deleteAiAddwxSopExecLogById(Long logId);
+
+    int deleteAiAddwxSopExecLogByWorkflowId(Long userWorkflowId);
+}

+ 34 - 0
fs-service/src/main/java/com/fs/company/mapper/AiAddwxSopTagTemplateBindMapper.java

@@ -0,0 +1,34 @@
+package com.fs.company.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.company.domain.AiAddwxSopTagTemplateBind;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * 企微标签与工作流模板绑定关系Mapper接口
+ *
+ * @author fs
+ * @date 2026-06-24
+ */
+public interface AiAddwxSopTagTemplateBindMapper extends BaseMapper<AiAddwxSopTagTemplateBind> {
+
+    AiAddwxSopTagTemplateBind selectAiAddwxSopTagTemplateBindById(Long bindId);
+
+    AiAddwxSopTagTemplateBind selectAiAddwxSopTagTemplateBindByTagId(Long qwTagId);
+
+    List<AiAddwxSopTagTemplateBind> selectAiAddwxSopTagTemplateBindList(AiAddwxSopTagTemplateBind bind);
+
+    int insertAiAddwxSopTagTemplateBind(AiAddwxSopTagTemplateBind bind);
+
+    int updateAiAddwxSopTagTemplateBind(AiAddwxSopTagTemplateBind bind);
+
+    int deleteAiAddwxSopTagTemplateBindById(Long bindId);
+
+    int deleteAiAddwxSopTagTemplateBindByTagId(String qwTagId);
+
+    Long selectOneAiAddwxSopTagTemplateBindByTagIds(@Param("tagIds") List<Long> tagIds);
+
+    AiAddwxSopTagTemplateBind selectTagBindBytagIdAndTemplateId(AiAddwxSopTagTemplateBind bind);
+}

+ 27 - 0
fs-service/src/main/java/com/fs/company/mapper/AiAddwxSopTemplateMapper.java

@@ -0,0 +1,27 @@
+package com.fs.company.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.company.domain.AiAddwxSopTemplate;
+
+import java.util.List;
+
+/**
+ * AI添加微信SOP工作流模板Mapper接口
+ *
+ * @author fs
+ * @date 2026-06-24
+ */
+public interface AiAddwxSopTemplateMapper extends BaseMapper<AiAddwxSopTemplate> {
+
+    AiAddwxSopTemplate selectAiAddwxSopTemplateById(Long templateId);
+
+    List<AiAddwxSopTemplate> selectAiAddwxSopTemplateList(AiAddwxSopTemplate template);
+
+    int insertAiAddwxSopTemplate(AiAddwxSopTemplate template);
+
+    int updateAiAddwxSopTemplate(AiAddwxSopTemplate template);
+
+    int deleteAiAddwxSopTemplateById(Long templateId);
+
+    int deleteAiAddwxSopTemplateByIds(Long[] templateIds);
+}

+ 32 - 0
fs-service/src/main/java/com/fs/company/mapper/AiAddwxSopTemplateNodeMapper.java

@@ -0,0 +1,32 @@
+package com.fs.company.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.company.domain.AiAddwxSopTemplateNode;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * AI添加微信SOP模板节点Mapper接口
+ *
+ * @author fs
+ * @date 2026-06-24
+ */
+public interface AiAddwxSopTemplateNodeMapper extends BaseMapper<AiAddwxSopTemplateNode> {
+
+    AiAddwxSopTemplateNode selectAiAddwxSopTemplateNodeById(Long nodeId);
+
+    List<AiAddwxSopTemplateNode> selectAiAddwxSopTemplateNodeByTemplateId(Long templateId);
+
+    List<AiAddwxSopTemplateNode> selectAiAddwxSopTemplateNodeList(AiAddwxSopTemplateNode node);
+
+    int insertAiAddwxSopTemplateNode(AiAddwxSopTemplateNode node);
+
+    int updateAiAddwxSopTemplateNode(AiAddwxSopTemplateNode node);
+
+    int deleteAiAddwxSopTemplateNodeById(Long nodeId);
+
+    int deleteAiAddwxSopTemplateNodeByTemplateId(Long templateId);
+
+    int batchInsertAiAddwxSopTemplateNode(@Param("list") List<AiAddwxSopTemplateNode> list);
+}

+ 32 - 0
fs-service/src/main/java/com/fs/company/mapper/AiAddwxSopTemplateNodeRuleMapper.java

@@ -0,0 +1,32 @@
+package com.fs.company.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.company.domain.AiAddwxSopTemplateNodeRule;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * AI添加微信SOP模板节点规则Mapper接口
+ *
+ * @author fs
+ * @date 2026-06-24
+ */
+public interface AiAddwxSopTemplateNodeRuleMapper extends BaseMapper<AiAddwxSopTemplateNodeRule> {
+
+    AiAddwxSopTemplateNodeRule selectAiAddwxSopTemplateNodeRuleById(Long ruleId);
+
+    List<AiAddwxSopTemplateNodeRule> selectAiAddwxSopTemplateNodeRuleByNodeId(Long nodeId);
+
+    List<AiAddwxSopTemplateNodeRule> selectAiAddwxSopTemplateNodeRuleList(AiAddwxSopTemplateNodeRule rule);
+
+    int insertAiAddwxSopTemplateNodeRule(AiAddwxSopTemplateNodeRule rule);
+
+    int updateAiAddwxSopTemplateNodeRule(AiAddwxSopTemplateNodeRule rule);
+
+    int deleteAiAddwxSopTemplateNodeRuleById(Long ruleId);
+
+    int deleteAiAddwxSopTemplateNodeRuleByNodeId(Long nodeId);
+
+    int batchInsertAiAddwxSopTemplateNodeRule(@Param("list") List<AiAddwxSopTemplateNodeRule> list);
+}

+ 32 - 0
fs-service/src/main/java/com/fs/company/mapper/AiAddwxSopUserNodeMapper.java

@@ -0,0 +1,32 @@
+package com.fs.company.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.company.domain.AiAddwxSopUserNode;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * AI添加微信SOP个人节点Mapper接口
+ *
+ * @author fs
+ * @date 2026-06-24
+ */
+public interface AiAddwxSopUserNodeMapper extends BaseMapper<AiAddwxSopUserNode> {
+
+    AiAddwxSopUserNode selectAiAddwxSopUserNodeById(Long userNodeId);
+
+    List<AiAddwxSopUserNode> selectAiAddwxSopUserNodeByWorkflowId(Long userWorkflowId);
+
+    List<AiAddwxSopUserNode> selectAiAddwxSopUserNodeList(AiAddwxSopUserNode userNode);
+
+    int insertAiAddwxSopUserNode(AiAddwxSopUserNode userNode);
+
+    int updateAiAddwxSopUserNode(AiAddwxSopUserNode userNode);
+
+    int deleteAiAddwxSopUserNodeById(Long userNodeId);
+
+    int deleteAiAddwxSopUserNodeByWorkflowId(Long userWorkflowId);
+
+    int batchInsertAiAddwxSopUserNode(@Param("list") List<AiAddwxSopUserNode> list);
+}

+ 32 - 0
fs-service/src/main/java/com/fs/company/mapper/AiAddwxSopUserNodeRuleMapper.java

@@ -0,0 +1,32 @@
+package com.fs.company.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.company.domain.AiAddwxSopUserNodeRule;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * AI添加微信SOP个人节点规则Mapper接口
+ *
+ * @author fs
+ * @date 2026-06-24
+ */
+public interface AiAddwxSopUserNodeRuleMapper extends BaseMapper<AiAddwxSopUserNodeRule> {
+
+    AiAddwxSopUserNodeRule selectAiAddwxSopUserNodeRuleById(Long userNodeRuleId);
+
+    List<AiAddwxSopUserNodeRule> selectAiAddwxSopUserNodeRuleByUserNodeId(Long userNodeId);
+
+    List<AiAddwxSopUserNodeRule> selectAiAddwxSopUserNodeRuleList(AiAddwxSopUserNodeRule rule);
+
+    int insertAiAddwxSopUserNodeRule(AiAddwxSopUserNodeRule rule);
+
+    int updateAiAddwxSopUserNodeRule(AiAddwxSopUserNodeRule rule);
+
+    int deleteAiAddwxSopUserNodeRuleById(Long userNodeRuleId);
+
+    int deleteAiAddwxSopUserNodeRuleByUserNodeId(Long userNodeId);
+
+    int batchInsertAiAddwxSopUserNodeRule(@Param("list") List<AiAddwxSopUserNodeRule> list);
+}

+ 28 - 0
fs-service/src/main/java/com/fs/company/mapper/AiAddwxSopUserWorkflowMapper.java

@@ -0,0 +1,28 @@
+package com.fs.company.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.company.domain.AiAddwxSopUserWorkflow;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * AI添加微信SOP个人工作流Mapper接口
+ *
+ * @author fs
+ * @date 2026-06-24
+ */
+public interface AiAddwxSopUserWorkflowMapper extends BaseMapper<AiAddwxSopUserWorkflow> {
+
+    AiAddwxSopUserWorkflow selectAiAddwxSopUserWorkflowById(Long userWorkflowId);
+
+    List<AiAddwxSopUserWorkflow> selectAiAddwxSopUserWorkflowList(AiAddwxSopUserWorkflow userWorkflow);
+
+    int insertAiAddwxSopUserWorkflow(AiAddwxSopUserWorkflow userWorkflow);
+
+    int updateAiAddwxSopUserWorkflow(AiAddwxSopUserWorkflow userWorkflow);
+
+    int deleteAiAddwxSopUserWorkflowById(Long userWorkflowId);
+
+    int deleteAiAddwxSopUserWorkflowByIds(@Param("userWorkflowIds") Long[] userWorkflowIds);
+}

+ 14 - 0
fs-service/src/main/java/com/fs/company/param/InitAddWxSopTestParam.java

@@ -0,0 +1,14 @@
+package com.fs.company.param;
+
+import com.fs.qw.domain.QwExternalContact;
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+import java.util.List;
+
+@Data
+@Accessors
+public class InitAddWxSopTestParam {
+    private List<QwExternalContact> contacts;
+    private List<String> tagIds;
+}

+ 30 - 0
fs-service/src/main/java/com/fs/company/service/IAiAddwxSopExecLogService.java

@@ -0,0 +1,30 @@
+package com.fs.company.service;
+
+import com.fs.company.domain.AiAddwxSopExecLog;
+
+import java.util.List;
+
+/**
+ * AI添加微信SOP执行日志Service接口
+ *
+ * @author fs
+ * @date 2026-06-24
+ */
+public interface IAiAddwxSopExecLogService {
+
+    AiAddwxSopExecLog selectAiAddwxSopExecLogById(Long logId);
+
+    List<AiAddwxSopExecLog> selectAiAddwxSopExecLogList(AiAddwxSopExecLog execLog);
+
+    List<AiAddwxSopExecLog> selectAiAddwxSopExecLogByWorkflowId(Long userWorkflowId);
+
+    List<AiAddwxSopExecLog> selectAiAddwxSopExecLogByNodeId(Long userNodeId);
+
+    int insertAiAddwxSopExecLog(AiAddwxSopExecLog execLog);
+
+    int updateAiAddwxSopExecLog(AiAddwxSopExecLog execLog);
+
+    int deleteAiAddwxSopExecLogById(Long logId);
+
+    int deleteAiAddwxSopExecLogByWorkflowId(Long userWorkflowId);
+}

+ 30 - 0
fs-service/src/main/java/com/fs/company/service/IAiAddwxSopTagTemplateBindService.java

@@ -0,0 +1,30 @@
+package com.fs.company.service;
+
+import com.fs.company.domain.AiAddwxSopTagTemplateBind;
+
+import java.util.List;
+
+/**
+ * 企微标签与工作流模板绑定关系Service接口
+ *
+ * @author fs
+ * @date 2026-06-24
+ */
+public interface IAiAddwxSopTagTemplateBindService {
+
+    AiAddwxSopTagTemplateBind selectAiAddwxSopTagTemplateBindById(Long bindId);
+
+    AiAddwxSopTagTemplateBind selectAiAddwxSopTagTemplateBindByTagId(Long qwTagId);
+
+    List<AiAddwxSopTagTemplateBind> selectAiAddwxSopTagTemplateBindList(AiAddwxSopTagTemplateBind bind);
+
+    int insertAiAddwxSopTagTemplateBind(AiAddwxSopTagTemplateBind bind);
+
+    int updateAiAddwxSopTagTemplateBind(AiAddwxSopTagTemplateBind bind);
+
+    int deleteAiAddwxSopTagTemplateBindById(Long bindId);
+
+    int deleteAiAddwxSopTagTemplateBindByTagId(String qwTagId);
+
+    AiAddwxSopTagTemplateBind selectTagBindBytagIdAndTemplateId(AiAddwxSopTagTemplateBind bind);
+}

+ 30 - 0
fs-service/src/main/java/com/fs/company/service/IAiAddwxSopTemplateNodeRuleService.java

@@ -0,0 +1,30 @@
+package com.fs.company.service;
+
+import com.fs.company.domain.AiAddwxSopTemplateNodeRule;
+
+import java.util.List;
+
+/**
+ * AI添加微信SOP模板节点规则Service接口
+ *
+ * @author fs
+ * @date 2026-06-24
+ */
+public interface IAiAddwxSopTemplateNodeRuleService {
+
+    AiAddwxSopTemplateNodeRule selectAiAddwxSopTemplateNodeRuleById(Long ruleId);
+
+    List<AiAddwxSopTemplateNodeRule> selectAiAddwxSopTemplateNodeRuleByNodeId(Long nodeId);
+
+    List<AiAddwxSopTemplateNodeRule> selectAiAddwxSopTemplateNodeRuleList(AiAddwxSopTemplateNodeRule rule);
+
+    int insertAiAddwxSopTemplateNodeRule(AiAddwxSopTemplateNodeRule rule);
+
+    int updateAiAddwxSopTemplateNodeRule(AiAddwxSopTemplateNodeRule rule);
+
+    int deleteAiAddwxSopTemplateNodeRuleById(Long ruleId);
+
+    int deleteAiAddwxSopTemplateNodeRuleByNodeId(Long nodeId);
+
+    int batchInsertAiAddwxSopTemplateNodeRule(List<AiAddwxSopTemplateNodeRule> list);
+}

+ 30 - 0
fs-service/src/main/java/com/fs/company/service/IAiAddwxSopTemplateNodeService.java

@@ -0,0 +1,30 @@
+package com.fs.company.service;
+
+import com.fs.company.domain.AiAddwxSopTemplateNode;
+
+import java.util.List;
+
+/**
+ * AI添加微信SOP模板节点Service接口
+ *
+ * @author fs
+ * @date 2026-06-24
+ */
+public interface IAiAddwxSopTemplateNodeService {
+
+    AiAddwxSopTemplateNode selectAiAddwxSopTemplateNodeById(Long nodeId);
+
+    List<AiAddwxSopTemplateNode> selectAiAddwxSopTemplateNodeByTemplateId(Long templateId);
+
+    List<AiAddwxSopTemplateNode> selectAiAddwxSopTemplateNodeList(AiAddwxSopTemplateNode node);
+
+    int insertAiAddwxSopTemplateNode(AiAddwxSopTemplateNode node);
+
+    int updateAiAddwxSopTemplateNode(AiAddwxSopTemplateNode node);
+
+    int deleteAiAddwxSopTemplateNodeById(Long nodeId);
+
+    int deleteAiAddwxSopTemplateNodeByTemplateId(Long templateId);
+
+    int batchInsertAiAddwxSopTemplateNode(List<AiAddwxSopTemplateNode> list);
+}

+ 34 - 0
fs-service/src/main/java/com/fs/company/service/IAiAddwxSopTemplateService.java

@@ -0,0 +1,34 @@
+package com.fs.company.service;
+
+import com.fs.company.domain.AiAddwxSopTemplate;
+import com.fs.company.domain.AiAddwxSopTemplateNode;
+import com.fs.qw.domain.QwExternalContact;
+
+import java.util.List;
+
+/**
+ * AI添加微信SOP模板Service接口
+ *
+ * @author fs
+ * @date 2026-06-24
+ */
+public interface IAiAddwxSopTemplateService {
+
+    AiAddwxSopTemplate selectAiAddwxSopTemplateById(Long templateId);
+
+    List<AiAddwxSopTemplate> selectAiAddwxSopTemplateList(AiAddwxSopTemplate template);
+
+    Long insertAiAddwxSopTemplate(AiAddwxSopTemplate template);
+
+    int updateAiAddwxSopTemplate(AiAddwxSopTemplate template);
+
+    int deleteAiAddwxSopTemplateByIds(Long[] templateIds);
+
+    /** 查询模板详情(含节点列表) */
+    AiAddwxSopTemplate selectTemplateDetailById(Long templateId);
+
+    /** 保存模板及节点 */
+    Long saveTemplateWithNodes(AiAddwxSopTemplate template, List<AiAddwxSopTemplateNode> nodes);
+
+    void initAddWxSop(List<QwExternalContact> externalUsers, List<String> tagIds);
+}

+ 30 - 0
fs-service/src/main/java/com/fs/company/service/IAiAddwxSopUserNodeRuleService.java

@@ -0,0 +1,30 @@
+package com.fs.company.service;
+
+import com.fs.company.domain.AiAddwxSopUserNodeRule;
+
+import java.util.List;
+
+/**
+ * AI添加微信SOP个人节点规则Service接口
+ *
+ * @author fs
+ * @date 2026-06-24
+ */
+public interface IAiAddwxSopUserNodeRuleService {
+
+    AiAddwxSopUserNodeRule selectAiAddwxSopUserNodeRuleById(Long userNodeRuleId);
+
+    List<AiAddwxSopUserNodeRule> selectAiAddwxSopUserNodeRuleByUserNodeId(Long userNodeId);
+
+    List<AiAddwxSopUserNodeRule> selectAiAddwxSopUserNodeRuleList(AiAddwxSopUserNodeRule rule);
+
+    int insertAiAddwxSopUserNodeRule(AiAddwxSopUserNodeRule rule);
+
+    int updateAiAddwxSopUserNodeRule(AiAddwxSopUserNodeRule rule);
+
+    int deleteAiAddwxSopUserNodeRuleById(Long userNodeRuleId);
+
+    int deleteAiAddwxSopUserNodeRuleByUserNodeId(Long userNodeId);
+
+    int batchInsertAiAddwxSopUserNodeRule(List<AiAddwxSopUserNodeRule> list);
+}

+ 37 - 0
fs-service/src/main/java/com/fs/company/service/IAiAddwxSopUserNodeService.java

@@ -0,0 +1,37 @@
+package com.fs.company.service;
+
+import com.fs.company.domain.AiAddwxSopUserNode;
+import com.fs.company.domain.AiAddwxSopUserWorkflow;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * AI添加微信SOP个人节点Service接口
+ *
+ * @author fs
+ * @date 2026-06-24
+ */
+public interface IAiAddwxSopUserNodeService {
+
+    AiAddwxSopUserNode selectAiAddwxSopUserNodeById(Long userNodeId);
+
+    List<AiAddwxSopUserNode> selectAiAddwxSopUserNodeByWorkflowId(Long userWorkflowId);
+
+    List<AiAddwxSopUserNode> selectAiAddwxSopUserNodeList(AiAddwxSopUserNode userNode);
+
+    int insertAiAddwxSopUserNode(AiAddwxSopUserNode userNode);
+
+    int updateAiAddwxSopUserNode(AiAddwxSopUserNode userNode);
+
+    int deleteAiAddwxSopUserNodeById(Long userNodeId);
+
+    int deleteAiAddwxSopUserNodeByWorkflowId(Long userWorkflowId);
+
+    int batchInsertAiAddwxSopUserNode(List<AiAddwxSopUserNode> list);
+
+    /** 更新节点状态 */
+    int updateNodeStatus(Long userNodeId, Integer status);
+
+    void nextNode(Map.Entry<String, List<AiAddwxSopUserWorkflow>> next);
+}

+ 39 - 0
fs-service/src/main/java/com/fs/company/service/IAiAddwxSopUserWorkflowService.java

@@ -0,0 +1,39 @@
+package com.fs.company.service;
+
+import com.fs.company.domain.AiAddwxSopUserWorkflow;
+import com.fs.company.domain.AiAddwxSopUserNode;
+
+import java.util.List;
+
+/**
+ * AI添加微信SOP个人工作流Service接口
+ *
+ * @author fs
+ * @date 2026-06-24
+ */
+public interface IAiAddwxSopUserWorkflowService {
+
+    AiAddwxSopUserWorkflow selectAiAddwxSopUserWorkflowById(Long userWorkflowId);
+
+    List<AiAddwxSopUserWorkflow> selectAiAddwxSopUserWorkflowList(AiAddwxSopUserWorkflow userWorkflow);
+
+    Long insertAiAddwxSopUserWorkflow(AiAddwxSopUserWorkflow userWorkflow);
+
+    int updateAiAddwxSopUserWorkflow(AiAddwxSopUserWorkflow userWorkflow);
+
+    int deleteAiAddwxSopUserWorkflowByIds(Long[] userWorkflowIds);
+
+    /** 查询个人工作流详情(含节点列表) */
+    AiAddwxSopUserWorkflow selectUserWorkflowDetailById(Long userWorkflowId);
+
+    /** 根据模板创建个人工作流(含节点) */
+    Long createUserWorkflowFromTemplate(Long templateId, Long companyUserId, Long companyId
+//            , Long businessKey
+    );
+
+    /** 推进工作流到下一节点 */
+    int advanceToNextNode(Long userWorkflowId, Long currentNodeId);
+
+    /** 查询用户的工作流节点列表 */
+    List<AiAddwxSopUserNode> selectUserNodesByWorkflowId(Long userWorkflowId);
+}

+ 62 - 0
fs-service/src/main/java/com/fs/company/service/impl/AiAddwxSopExecLogServiceImpl.java

@@ -0,0 +1,62 @@
+package com.fs.company.service.impl;
+
+import com.fs.company.domain.AiAddwxSopExecLog;
+import com.fs.company.mapper.AiAddwxSopExecLogMapper;
+import com.fs.company.service.IAiAddwxSopExecLogService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * AI添加微信SOP执行日志Service业务层处理
+ *
+ * @author fs
+ * @date 2026-06-24
+ */
+@Service
+public class AiAddwxSopExecLogServiceImpl implements IAiAddwxSopExecLogService {
+
+    @Autowired
+    private AiAddwxSopExecLogMapper execLogMapper;
+
+    @Override
+    public AiAddwxSopExecLog selectAiAddwxSopExecLogById(Long logId) {
+        return execLogMapper.selectAiAddwxSopExecLogById(logId);
+    }
+
+    @Override
+    public List<AiAddwxSopExecLog> selectAiAddwxSopExecLogList(AiAddwxSopExecLog execLog) {
+        return execLogMapper.selectAiAddwxSopExecLogList(execLog);
+    }
+
+    @Override
+    public List<AiAddwxSopExecLog> selectAiAddwxSopExecLogByWorkflowId(Long userWorkflowId) {
+        return execLogMapper.selectAiAddwxSopExecLogByWorkflowId(userWorkflowId);
+    }
+
+    @Override
+    public List<AiAddwxSopExecLog> selectAiAddwxSopExecLogByNodeId(Long userNodeId) {
+        return execLogMapper.selectAiAddwxSopExecLogByNodeId(userNodeId);
+    }
+
+    @Override
+    public int insertAiAddwxSopExecLog(AiAddwxSopExecLog execLog) {
+        return execLogMapper.insertAiAddwxSopExecLog(execLog);
+    }
+
+    @Override
+    public int updateAiAddwxSopExecLog(AiAddwxSopExecLog execLog) {
+        return execLogMapper.updateAiAddwxSopExecLog(execLog);
+    }
+
+    @Override
+    public int deleteAiAddwxSopExecLogById(Long logId) {
+        return execLogMapper.deleteAiAddwxSopExecLogById(logId);
+    }
+
+    @Override
+    public int deleteAiAddwxSopExecLogByWorkflowId(Long userWorkflowId) {
+        return execLogMapper.deleteAiAddwxSopExecLogByWorkflowId(userWorkflowId);
+    }
+}

+ 62 - 0
fs-service/src/main/java/com/fs/company/service/impl/AiAddwxSopTagTemplateBindServiceImpl.java

@@ -0,0 +1,62 @@
+package com.fs.company.service.impl;
+
+import com.fs.company.domain.AiAddwxSopTagTemplateBind;
+import com.fs.company.mapper.AiAddwxSopTagTemplateBindMapper;
+import com.fs.company.service.IAiAddwxSopTagTemplateBindService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * 企微标签与工作流模板绑定关系Service业务层处理
+ *
+ * @author fs
+ * @date 2026-06-24
+ */
+@Service
+public class AiAddwxSopTagTemplateBindServiceImpl implements IAiAddwxSopTagTemplateBindService {
+
+    @Autowired
+    private AiAddwxSopTagTemplateBindMapper tagTemplateBindMapper;
+
+    @Override
+    public AiAddwxSopTagTemplateBind selectAiAddwxSopTagTemplateBindById(Long bindId) {
+        return tagTemplateBindMapper.selectAiAddwxSopTagTemplateBindById(bindId);
+    }
+
+    @Override
+    public AiAddwxSopTagTemplateBind selectAiAddwxSopTagTemplateBindByTagId(Long qwTagId) {
+        return tagTemplateBindMapper.selectAiAddwxSopTagTemplateBindByTagId(qwTagId);
+    }
+
+    @Override
+    public List<AiAddwxSopTagTemplateBind> selectAiAddwxSopTagTemplateBindList(AiAddwxSopTagTemplateBind bind) {
+        return tagTemplateBindMapper.selectAiAddwxSopTagTemplateBindList(bind);
+    }
+
+    @Override
+    public int insertAiAddwxSopTagTemplateBind(AiAddwxSopTagTemplateBind bind) {
+        return tagTemplateBindMapper.insertAiAddwxSopTagTemplateBind(bind);
+    }
+
+    @Override
+    public int updateAiAddwxSopTagTemplateBind(AiAddwxSopTagTemplateBind bind) {
+        return tagTemplateBindMapper.updateAiAddwxSopTagTemplateBind(bind);
+    }
+
+    @Override
+    public int deleteAiAddwxSopTagTemplateBindById(Long bindId) {
+        return tagTemplateBindMapper.deleteAiAddwxSopTagTemplateBindById(bindId);
+    }
+
+    @Override
+    public int deleteAiAddwxSopTagTemplateBindByTagId(String qwTagId) {
+        return tagTemplateBindMapper.deleteAiAddwxSopTagTemplateBindByTagId(qwTagId);
+    }
+
+    @Override
+    public AiAddwxSopTagTemplateBind selectTagBindBytagIdAndTemplateId(AiAddwxSopTagTemplateBind bind) {
+        return tagTemplateBindMapper.selectTagBindBytagIdAndTemplateId(bind);
+    }
+}

+ 62 - 0
fs-service/src/main/java/com/fs/company/service/impl/AiAddwxSopTemplateNodeRuleServiceImpl.java

@@ -0,0 +1,62 @@
+package com.fs.company.service.impl;
+
+import com.fs.company.domain.AiAddwxSopTemplateNodeRule;
+import com.fs.company.mapper.AiAddwxSopTemplateNodeRuleMapper;
+import com.fs.company.service.IAiAddwxSopTemplateNodeRuleService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * AI添加微信SOP模板节点规则Service业务层处理
+ *
+ * @author fs
+ * @date 2026-06-24
+ */
+@Service
+public class AiAddwxSopTemplateNodeRuleServiceImpl implements IAiAddwxSopTemplateNodeRuleService {
+
+    @Autowired
+    private AiAddwxSopTemplateNodeRuleMapper templateNodeRuleMapper;
+
+    @Override
+    public AiAddwxSopTemplateNodeRule selectAiAddwxSopTemplateNodeRuleById(Long ruleId) {
+        return templateNodeRuleMapper.selectAiAddwxSopTemplateNodeRuleById(ruleId);
+    }
+
+    @Override
+    public List<AiAddwxSopTemplateNodeRule> selectAiAddwxSopTemplateNodeRuleByNodeId(Long nodeId) {
+        return templateNodeRuleMapper.selectAiAddwxSopTemplateNodeRuleByNodeId(nodeId);
+    }
+
+    @Override
+    public List<AiAddwxSopTemplateNodeRule> selectAiAddwxSopTemplateNodeRuleList(AiAddwxSopTemplateNodeRule rule) {
+        return templateNodeRuleMapper.selectAiAddwxSopTemplateNodeRuleList(rule);
+    }
+
+    @Override
+    public int insertAiAddwxSopTemplateNodeRule(AiAddwxSopTemplateNodeRule rule) {
+        return templateNodeRuleMapper.insertAiAddwxSopTemplateNodeRule(rule);
+    }
+
+    @Override
+    public int updateAiAddwxSopTemplateNodeRule(AiAddwxSopTemplateNodeRule rule) {
+        return templateNodeRuleMapper.updateAiAddwxSopTemplateNodeRule(rule);
+    }
+
+    @Override
+    public int deleteAiAddwxSopTemplateNodeRuleById(Long ruleId) {
+        return templateNodeRuleMapper.deleteAiAddwxSopTemplateNodeRuleById(ruleId);
+    }
+
+    @Override
+    public int deleteAiAddwxSopTemplateNodeRuleByNodeId(Long nodeId) {
+        return templateNodeRuleMapper.deleteAiAddwxSopTemplateNodeRuleByNodeId(nodeId);
+    }
+
+    @Override
+    public int batchInsertAiAddwxSopTemplateNodeRule(List<AiAddwxSopTemplateNodeRule> list) {
+        return templateNodeRuleMapper.batchInsertAiAddwxSopTemplateNodeRule(list);
+    }
+}

+ 62 - 0
fs-service/src/main/java/com/fs/company/service/impl/AiAddwxSopTemplateNodeServiceImpl.java

@@ -0,0 +1,62 @@
+package com.fs.company.service.impl;
+
+import com.fs.company.domain.AiAddwxSopTemplateNode;
+import com.fs.company.mapper.AiAddwxSopTemplateNodeMapper;
+import com.fs.company.service.IAiAddwxSopTemplateNodeService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * AI添加微信SOP模板节点Service业务层处理
+ *
+ * @author fs
+ * @date 2026-06-24
+ */
+@Service
+public class AiAddwxSopTemplateNodeServiceImpl implements IAiAddwxSopTemplateNodeService {
+
+    @Autowired
+    private AiAddwxSopTemplateNodeMapper templateNodeMapper;
+
+    @Override
+    public AiAddwxSopTemplateNode selectAiAddwxSopTemplateNodeById(Long nodeId) {
+        return templateNodeMapper.selectAiAddwxSopTemplateNodeById(nodeId);
+    }
+
+    @Override
+    public List<AiAddwxSopTemplateNode> selectAiAddwxSopTemplateNodeByTemplateId(Long templateId) {
+        return templateNodeMapper.selectAiAddwxSopTemplateNodeByTemplateId(templateId);
+    }
+
+    @Override
+    public List<AiAddwxSopTemplateNode> selectAiAddwxSopTemplateNodeList(AiAddwxSopTemplateNode node) {
+        return templateNodeMapper.selectAiAddwxSopTemplateNodeList(node);
+    }
+
+    @Override
+    public int insertAiAddwxSopTemplateNode(AiAddwxSopTemplateNode node) {
+        return templateNodeMapper.insertAiAddwxSopTemplateNode(node);
+    }
+
+    @Override
+    public int updateAiAddwxSopTemplateNode(AiAddwxSopTemplateNode node) {
+        return templateNodeMapper.updateAiAddwxSopTemplateNode(node);
+    }
+
+    @Override
+    public int deleteAiAddwxSopTemplateNodeById(Long nodeId) {
+        return templateNodeMapper.deleteAiAddwxSopTemplateNodeById(nodeId);
+    }
+
+    @Override
+    public int deleteAiAddwxSopTemplateNodeByTemplateId(Long templateId) {
+        return templateNodeMapper.deleteAiAddwxSopTemplateNodeByTemplateId(templateId);
+    }
+
+    @Override
+    public int batchInsertAiAddwxSopTemplateNode(List<AiAddwxSopTemplateNode> list) {
+        return templateNodeMapper.batchInsertAiAddwxSopTemplateNode(list);
+    }
+}

+ 517 - 0
fs-service/src/main/java/com/fs/company/service/impl/AiAddwxSopTemplateServiceImpl.java

@@ -0,0 +1,517 @@
+package com.fs.company.service.impl;
+
+import cn.hutool.core.bean.BeanUtil;
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fs.common.core.domain.R;
+import com.fs.common.utils.spring.SpringUtils;
+import com.fs.company.domain.AiAddwxSopTemplate;
+import com.fs.company.domain.AiAddwxSopTemplateNode;
+import com.fs.company.domain.AiAddwxSopTemplateNodeRule;
+import com.fs.company.domain.AiAddwxSopUserNode;
+import com.fs.company.domain.AiAddwxSopUserNodeRule;
+import com.fs.company.domain.AiAddwxSopUserWorkflow;
+import com.fs.company.enums.AiAddWechatSopNodeEnum;
+import com.fs.company.mapper.AiAddwxSopTagTemplateBindMapper;
+import com.fs.company.mapper.AiAddwxSopTemplateMapper;
+import com.fs.company.mapper.AiAddwxSopTemplateNodeMapper;
+import com.fs.company.mapper.AiAddwxSopTemplateNodeRuleMapper;
+import com.fs.company.mapper.AiAddwxSopUserNodeMapper;
+import com.fs.company.mapper.AiAddwxSopUserNodeRuleMapper;
+import com.fs.company.mapper.AiAddwxSopUserWorkflowMapper;
+import com.fs.company.service.IAiAddwxSopTemplateService;
+import com.fs.config.ai.AiHostProper;
+import com.fs.crm.utils.CrmCustomerAiTagUtil;
+import com.fs.fastGpt.domain.FastGptChatMsg;
+import com.fs.fastGpt.domain.FastGptChatSession;
+import com.fs.fastGpt.mapper.FastGptChatMsgMapper;
+import com.fs.fastGpt.mapper.FastGptChatSessionMapper;
+import com.fs.fastGpt.service.IFastGptChatMsgService;
+import com.fs.fastgptApi.param.ChatParam;
+import com.fs.fastgptApi.service.ChatService;
+import com.fs.ipad.vo.BaseVo;
+import com.fs.qw.domain.QwExternalContact;
+import com.fs.qw.domain.QwUser;
+import com.fs.qw.mapper.QwTagMapper;
+import com.fs.qw.mapper.QwUserMapper;
+import com.fs.qw.vo.QwSopCourseFinishTempSetting;
+import com.fs.sop.domain.QwSopLogs;
+import com.vdurmont.emoji.EmojiParser;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.util.*;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+
+/**
+ * AI添加微信SOP模板Service业务层处理
+ *
+ * @author fs
+ * @date 2026-06-24
+ */
+@Service
+public class AiAddwxSopTemplateServiceImpl implements IAiAddwxSopTemplateService {
+
+    private static final Logger log = LoggerFactory.getLogger(AiAddwxSopTemplateServiceImpl.class);
+    @Autowired
+    private AiAddwxSopTemplateMapper templateMapper;
+
+    @Autowired
+    private AiAddwxSopTemplateNodeMapper templateNodeMapper;
+    @Autowired
+    private AiAddwxSopTemplateNodeRuleMapper templateNodeRuleMapper;
+    @Autowired
+    private AiAddwxSopUserWorkflowMapper userWorkflowMapper;
+    @Autowired
+    private AiAddwxSopUserNodeMapper userNodeMapper;
+    @Autowired
+    private AiAddwxSopUserNodeRuleMapper userNodeRuleMapper;
+    @Autowired
+    private AiAddwxSopTagTemplateBindMapper aiAddwxSopTagTemplateBindMapper;
+    @Qualifier("qwUserMapper")
+    @Autowired
+    private QwUserMapper qwUserMapper;
+    @Autowired
+    private QwTagMapper qwTagMapper;
+    @Autowired
+    private FastGptChatSessionMapper fastGptChatSessionMapper;
+    @Autowired
+    private IFastGptChatMsgService fastGptChatMsgService;
+    @Qualifier("fastGptChatMsgMapper")
+    @Autowired
+    private FastGptChatMsgMapper fastGptChatMsgMapper;
+
+    @Value("${cid.addwx.sop.appKey:fastgpt-hPSZNV9pPlX5GlHyjzsGqEPbB2D8tb9mYQcnD1y5lEUIEMgGaO0PFm7BHHjF}")
+    private String CID_ADDWX_SOP_APPKEY;
+    @Autowired
+    private AiAddwxSopUserNodeRuleMapper aiAddwxSopUserNodeRuleMapper;
+
+    @Override
+    public AiAddwxSopTemplate selectAiAddwxSopTemplateById(Long templateId) {
+        return templateMapper.selectAiAddwxSopTemplateById(templateId);
+    }
+
+    @Override
+    public List<AiAddwxSopTemplate> selectAiAddwxSopTemplateList(AiAddwxSopTemplate template) {
+        return templateMapper.selectAiAddwxSopTemplateList(template);
+    }
+
+    @Override
+    public Long insertAiAddwxSopTemplate(AiAddwxSopTemplate template) {
+        templateMapper.insertAiAddwxSopTemplate(template);
+        return template.getTemplateId();
+    }
+
+    @Override
+    public int updateAiAddwxSopTemplate(AiAddwxSopTemplate template) {
+        return templateMapper.updateAiAddwxSopTemplate(template);
+    }
+
+    @Override
+    public int deleteAiAddwxSopTemplateByIds(Long[] templateIds) {
+        return templateMapper.deleteAiAddwxSopTemplateByIds(templateIds);
+    }
+
+    @Override
+    public AiAddwxSopTemplate selectTemplateDetailById(Long templateId) {
+        AiAddwxSopTemplate template = templateMapper.selectAiAddwxSopTemplateById(templateId);
+        if (template != null) {
+            List<AiAddwxSopTemplateNode> nodes = templateNodeMapper.selectAiAddwxSopTemplateNodeByTemplateId(templateId);
+            // 可通过VO扩展返回节点列表,此处简化处理
+        }
+        return template;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Long saveTemplateWithNodes(AiAddwxSopTemplate template, List<AiAddwxSopTemplateNode> nodes) {
+        if (template.getTemplateId() == null) {
+            templateMapper.insertAiAddwxSopTemplate(template);
+        } else {
+            templateMapper.updateAiAddwxSopTemplate(template);
+            // 删除旧节点
+            templateNodeMapper.deleteAiAddwxSopTemplateNodeByTemplateId(template.getTemplateId());
+        }
+        if (nodes != null && !nodes.isEmpty()) {
+            for (AiAddwxSopTemplateNode node : nodes) {
+                node.setTemplateId(template.getTemplateId());
+                node.setCreateTime(new Date());
+                node.setUpdateTime(new Date());
+            }
+            templateNodeMapper.batchInsertAiAddwxSopTemplateNode(nodes);
+        }
+        return template.getTemplateId();
+    }
+
+    @Override
+//    @Async
+    public void initAddWxSop(List<QwExternalContact> externalUsers, List<String> tagIds) {
+        //qw_tag_id获取优先级最高的那条template_id
+        if (tagIds == null || tagIds.isEmpty()) return;
+        if (externalUsers == null || externalUsers.isEmpty()) return;
+        List<Long> tagIdlist = qwTagMapper.selectIdsByTagIds(tagIds);
+        Long aiAddwxTemplateId = aiAddwxSopTagTemplateBindMapper.selectOneAiAddwxSopTagTemplateBindByTagIds(tagIdlist);
+        if (aiAddwxTemplateId == null) {
+            return;
+        }
+        List<Long> collect = externalUsers.stream().map(QwExternalContact::getQwUserId).collect(Collectors.toList());
+        List<QwUser> qwUsers = qwUserMapper.selectQwUserByIdsPrimary(collect);
+        Map<Long, QwUser> qwUserMap = qwUsers.stream().collect(Collectors.toMap(
+                QwUser::getId,
+                user -> user,
+                (k1, k2) -> k1
+        ));
+
+        // 查询模板
+        AiAddwxSopTemplate template = templateMapper.selectAiAddwxSopTemplateById(aiAddwxTemplateId);
+        if (template == null) {
+            return;
+        }
+        // 查询模板节点
+        List<AiAddwxSopTemplateNode> templateNodes = templateNodeMapper.selectAiAddwxSopTemplateNodeByTemplateId(aiAddwxTemplateId);
+        if (templateNodes == null || templateNodes.isEmpty()) {
+            return;
+        }
+
+        Date now = new Date();
+        List<AiAddwxSopUserNode> allUserNodes = new ArrayList<>();
+        List<AiAddwxSopUserNodeRule> allUserNodeRules = new ArrayList<>();
+
+
+        for (QwExternalContact externalUser : externalUsers) {
+            QwUser qwUser = qwUserMap.get(externalUser.getQwUserId());
+            Long qwUserId = qwUser != null ? qwUser.getId() : null;
+
+            // 创建个人工作流
+            AiAddwxSopUserWorkflow userWorkflow = new AiAddwxSopUserWorkflow();
+            userWorkflow.setTemplateId(aiAddwxTemplateId);
+            userWorkflow.setCompanyUserId(externalUser.getCompanyUserId());
+            userWorkflow.setCompanyId(externalUser.getCompanyId());
+            userWorkflow.setQwExternalId(externalUser.getId());
+            userWorkflow.setQwUserId(qwUserId);
+            userWorkflow.setStatus(0);
+            userWorkflow.setDelFlag(0);
+            userWorkflow.setCreateTime(now);
+            userWorkflow.setUpdateTime(now);
+
+            userWorkflowMapper.insertAiAddwxSopUserWorkflow(userWorkflow);
+
+            // 为每个模板节点创建个人节点
+            for (AiAddwxSopTemplateNode templateNode : templateNodes) {
+                AiAddwxSopUserNode userNode = new AiAddwxSopUserNode();
+                userNode.setUserWorkflowId(userWorkflow.getUserWorkflowId());
+                userNode.setQwExternalId(externalUser.getId());
+                userNode.setQwUserId(qwUserId);
+                userNode.setTemplateNodeId(templateNode.getNodeId());
+                userNode.setNodeKey(templateNode.getNodeKey());
+                userNode.setNodeName(templateNode.getNodeName());
+                userNode.setNodeType(templateNode.getNodeType());
+                userNode.setPrevNodeId(templateNode.getPrevNodeId());
+                userNode.setNodeConfig(templateNode.getNodeConfig());
+                userNode.setIsValidReply(templateNode.getIsValidReply() != null && templateNode.getIsValidReply() ? 1 : 0);
+                userNode.setStatus(0);
+                userNode.setSortOrder(templateNode.getSortOrder());
+                userNode.setCreateTime(now);
+                userNode.setUpdateTime(now);
+                userNodeMapper.insertAiAddwxSopUserNode(userNode);
+                //加微节点更新用户工作流
+                if (userNode.getNodeType().equals(AiAddWechatSopNodeEnum.ADD_WECHAT.getCode())){
+                    AiAddwxSopUserWorkflow aiAddwxSopUserWorkflow = new AiAddwxSopUserWorkflow();
+                    aiAddwxSopUserWorkflow.setUserWorkflowId(userWorkflow.getUserWorkflowId()).
+                    setCurrentNodeId(userNode.getUserNodeId())
+                            .setCurrentNodeType(userNode.getNodeType())
+                                    .setCurrentNodeKey(userNode.getNodeKey());
+                    userWorkflowMapper.updateById(aiAddwxSopUserWorkflow);
+                    userWorkflow
+                            .setCurrentNodeId(userNode.getUserNodeId())
+                            .setCurrentNodeType(userNode.getNodeType())
+                            .setCurrentNodeKey(userNode.getNodeKey());
+                }
+                allUserNodes.add(userNode);
+                // 加微节点直接发第一条rule的消息  todo
+                // 查询模板节点规则,生成个人节点规则(不配置send_time和send_day)
+                List<AiAddwxSopTemplateNodeRule> templateRules = templateNodeRuleMapper.selectAiAddwxSopTemplateNodeRuleByNodeId(templateNode.getNodeId());
+                if (templateRules != null && !templateRules.isEmpty()) {
+                    for (AiAddwxSopTemplateNodeRule templateRule : templateRules) {
+
+                        AiAddwxSopUserNodeRule userNodeRule = new AiAddwxSopUserNodeRule();
+                        userNodeRule.setUserNodeId(userNode.getUserNodeId());
+                        userNodeRule.setQwExternalId(externalUser.getId());
+                        userNodeRule.setQwUserId(qwUserId);
+                        userNodeRule.setRuleName(templateRule.getRuleName());
+                        userNodeRule.setMsgType(templateRule.getMsgType());
+                        userNodeRule.setContentType(templateRule.getContentType());
+                        userNodeRule.setContentJson(templateRule.getContentJson());
+                        userNodeRule.setSortOrder(templateRule.getSortOrder());
+                        userNodeRule.setCreateTime(now);
+                        userNodeRule.setUpdateTime(now);
+                        userNodeRule.setSendDay(templateRule.getSendDay());
+                        userNodeRule.setSendTime(templateRule.getSendTime());
+                        if (templateNode.getNodeType().equals(AiAddWechatSopNodeEnum.ADD_WECHAT.getCode())){
+                            if (templateRule.getSortOrder()==1){
+                                // 计算 realSendTime: now + sendDay 天 + sendTime,限制在 9:00-18:00
+                                // 获取当前时间
+                                LocalDateTime nowL = LocalDateTime.now();
+
+                                // 解析发送时间
+                                String[] timeParts = userNodeRule.getSendTime().split(":");
+                                int hour = Integer.parseInt(timeParts[0]);
+                                int minute = Integer.parseInt(timeParts[1]);
+
+                                // 构建目标时间:当前日期 + sendDay 天 + sendTime 时间
+                                LocalDateTime targetTime = nowL.plusDays(userNodeRule.getSendDay())
+                                        .plusHours(hour)
+                                        .plusMinutes(minute).plusMinutes(1)
+                                        .withSecond(0)
+                                        .withNano(0);
+
+                                // 业务规则:调整到 9:00 - 18:00 之间
+                                int targetHour = targetTime.getHour();
+                                if (targetHour < 9) {
+                                    targetTime = targetTime.withHour(9).withMinute(0);
+                                } else if (targetHour >= 18) {
+                                    targetTime = targetTime.plusDays(1).withHour(9).withMinute(0);
+                                }
+
+                                // 转为 Date 类型(如果 userNodeRule.setRealSendTime 需要 Date)
+                                userNodeRule.setRealSendTime(Date.from(targetTime.atZone(ZoneId.systemDefault()).toInstant()));
+
+                            }
+                        }
+                        allUserNodeRules.add(userNodeRule);
+                    }
+                }
+            }
+            // 批量插入个人节点规则
+            if (!allUserNodeRules.isEmpty()) {
+                userNodeRuleMapper.batchInsertAiAddwxSopUserNodeRule(allUserNodeRules);
+            }
+            Map<String, Object> requestParam = new HashMap<>();
+
+            initAiChatParam(requestParam, userWorkflow,null,AiAddWechatSopNodeEnum.ADD_WECHAT.getDesc(),externalUser);
+
+            R r = callAiService(requestParam, ThreadLocalRandom.current().nextLong(100_000_000_000L, 999_999_999_999L + 1)
+                    , CID_ADDWX_SOP_APPKEY);
+
+//            System.out.println(r);
+            JSONObject content = extractContentJson(r);
+//            System.out.println(content);
+            if (content != null) {
+                if (content.containsKey("aiContent")){
+                    String sendWords = (String) content.get("aiContent");
+                    // 新增fastgptchatsession新生成必须发第一条
+                    qwHookNotifyAddMsgNew(qwUser,externalUser,sendWords);
+                    AiAddwxSopUserNodeRule aiAddwxSopUserNodeRule = userNodeRuleMapper.selectOne(new LambdaQueryWrapper<AiAddwxSopUserNodeRule>()
+                            .eq(AiAddwxSopUserNodeRule::getUserNodeId, userWorkflow.getCurrentNodeId())
+                            .orderByAsc(AiAddwxSopUserNodeRule::getSortOrder).last("limit 1"));
+                    AiAddwxSopUserNodeRule firstRUle = new AiAddwxSopUserNodeRule();
+                    if (aiAddwxSopUserNodeRule != null)
+                    firstRUle.setUserNodeRuleId(aiAddwxSopUserNodeRule.getUserNodeRuleId());
+                    firstRUle.setContentJson(sendWords);
+                    userNodeRuleMapper.updateById(firstRUle);
+                }
+            }
+
+            //
+        }
+
+    }
+    private static JSONObject extractContentJson(R result) {
+        try {
+            Object data = result.get("data");
+            if (data == null) {
+                return null;
+            }
+
+            String dataJson = JSON.toJSONString(data);
+            JSONObject dataObj = JSON.parseObject(dataJson);
+
+            JSONArray choices = dataObj.getJSONArray("choices");
+            if (choices == null || choices.isEmpty()) {
+                return null;
+            }
+
+            JSONObject firstChoice = choices.getJSONObject(0);
+            JSONObject message = firstChoice.getJSONObject("message");
+            if (message == null) {
+                return null;
+            }
+
+            String content = message.getString("content");
+            if (content == null || content.trim().isEmpty()) {
+                return null;
+            }
+
+            return JSON.parseObject(content);
+
+        } catch (Exception e) {
+            System.err.println("提取 content JSON 失败: " + e.getMessage());
+            return null;
+        }
+    }
+
+    private static final ObjectMapper mapper = new ObjectMapper();
+    public static R callAiService(Map<String, Object> requestParam, Long logId,String appKey) {
+        try {
+            String requestJson = mapper.writeValueAsString(requestParam);
+
+            ChatParam param = new ChatParam();
+            param.setChatId(logId.toString());
+            param.setStream(false);
+            param.setDetail(true);
+            ChatParam.Message message = new ChatParam.Message();
+            List<ChatParam.Message> messageList = new ArrayList<ChatParam.Message>();
+            message.setContent(requestJson);
+            message.setRole("user");
+            messageList.add(message);
+            param.setMessages(messageList);
+            ChatService chatService = SpringUtils.getBean(ChatService.class);
+            AiHostProper aiHost = SpringUtils.getBean(AiHostProper.class);
+
+            return chatService.initiatingTakeChat(param, aiHost.getAiApiV3(), appKey);
+        } catch (Exception e) {
+            throw new RuntimeException("AI服务调用失败", e);
+        }
+    }
+    private void initAiChatParam(Map<String, Object> requestParam, AiAddwxSopUserWorkflow workflow, FastGptChatSession session, String nodeTypeE, QwExternalContact externalUser) {
+        // 1. 构建 userInfo
+        Map<String, Object> userInfo = new HashMap<>();
+        userInfo.put("姓名", externalUser.getName()!=null?externalUser.getName():"");
+        userInfo.put("微信昵称", externalUser.getRemark() != null ? externalUser.getRemark() : "");
+        userInfo.put("联系方式",  "");
+        userInfo.put("性别", externalUser.getGender()==1?"男":externalUser.getGender()==2?"女":"未知");
+        userInfo.put("收货地址", "");
+        userInfo.put("状态", "");
+        userInfo.put("当前阶段", nodeTypeE);
+
+        // 查询下一阶段(下一节点的类型)
+        StringBuilder nextStages = new StringBuilder();
+        List<AiAddwxSopUserNode> allNodes = userNodeMapper.selectAiAddwxSopUserNodeByWorkflowId(workflow.getUserWorkflowId());
+        AiAddwxSopUserNode currentNode = allNodes.stream().filter(node -> node.getUserNodeId().equals(workflow.getCurrentNodeId())).findFirst().orElse(null);
+        if (allNodes != null && workflow.getCurrentNodeId() != null && currentNode != null) {
+            for (AiAddwxSopUserNode node : allNodes) {
+                if (currentNode.getTemplateNodeId().equals(node.getPrevNodeId())) {
+                    if (nextStages.length() > 0) {
+                        nextStages.append("|");
+                    }
+                    AiAddWechatSopNodeEnum nextType = AiAddWechatSopNodeEnum.fromCode(node.getNodeType());
+                    nextStages.append(nextType != null ? nextType.getDesc() : node.getNodeType());
+                }
+            }
+        }
+        userInfo.put("下一阶段", nextStages.toString());
+        requestParam.put("userInfo", userInfo);
+        // 2. 构建 history
+        List<Map<String, String>> history = new ArrayList<>();
+        String userContent = "";
+        if (session != null) {
+            List<FastGptChatMsg> msgs = fastGptChatMsgMapper.selectFastGptChatMsgByMsgSessionIdAndExtId(session.getSessionId(), String.valueOf(workflow.getQwExternalId()));
+            if (msgs != null) {
+                for (FastGptChatMsg msg : msgs) {
+                    Map<String, String> item = new HashMap<>();
+                    item.put("role", msg.getSendType() != null && msg.getSendType() == 1 ? "user" : "ai");
+                    item.put("content", msg.getContent());
+                    history.add(item);
+                }
+                // 取最后一条用户消息作为 userContent
+                for (FastGptChatMsg msg : msgs) {
+                    if (msg.getSendType() != null && msg.getSendType() == 1) {
+                        userContent = msg.getContent();
+                        break;
+                    }
+                }
+            }
+        }
+        requestParam.put("history", history);
+        requestParam.put("userContent", userContent);
+
+        // 3. knowledgeBase
+        ArrayList<Object> knowledgeBase = new ArrayList<>();
+//        AiAddwxSopUserNodeRule currentRule = aiAddwxSopUserNodeRuleMapper.selectOne(new LambdaQueryWrapper<AiAddwxSopUserNodeRule>()
+//                .eq(AiAddwxSopUserNodeRule::getUserNodeId, workflow.getCurrentNodeId()).eq(AiAddwxSopUserNodeRule::getIsSent, 0).orderByAsc(AiAddwxSopUserNodeRule::getSortOrder).last("limit 1"));
+//        if (currentRule != null){
+//            JSONObject rule = JSON.parseObject(currentRule.getContentJson());
+//            if (rule.get("prompt")!=null){
+//                knowledgeBase.add(rule.get("prompt"));
+//            }
+//        }
+        requestParam.put("knowledgeBase", knowledgeBase);
+    }
+    private void qwHookNotifyAddMsgNew(QwUser sendUser,QwExternalContact qwExternalContacts,String count) {
+        if (sendUser!=null){
+            if (qwExternalContacts==null){
+                return ;
+            }
+            FastGptChatSession fastGptChatSession = fastGptChatSessionMapper.selectFastGptChatSessionByQwExternalContactsAndUserId(qwExternalContacts.getId(), sendUser.getId());
+            if (fastGptChatSession==null){
+                if(qwExternalContacts.getType()!=null&&qwExternalContacts.getType()==1){
+                    if(sendUser.getFastGptRoleId()!=null){
+                        fastGptChatSession = new FastGptChatSession();
+                        String chatId = UUID.randomUUID().toString();
+                        fastGptChatSession.setChatId(chatId);
+                        fastGptChatSession.setKfId(sendUser.getFastGptRoleId().toString());
+                        fastGptChatSession.setStatus(1);
+                        fastGptChatSession.setRemindCount(0);
+                        fastGptChatSession.setRemindStatus(0);
+                        fastGptChatSession.setCreateTime(new Date());
+                        fastGptChatSession.setQwExtId(qwExternalContacts.getId());
+                        fastGptChatSession.setQwUserId(sendUser.getId());
+                        fastGptChatSession.setIsArtificial(0);
+                        fastGptChatSession.setAvatar(qwExternalContacts.getAvatar());
+                        fastGptChatSession.setNickName(qwExternalContacts.getName());
+                        fastGptChatSession.setCompanyId(sendUser.getCompanyId());
+                        fastGptChatSession.setLastTime(new Date());
+                        fastGptChatSession.setIsReply(0);
+                        fastGptChatSessionMapper.insertFastGptChatSession(fastGptChatSession);
+                        saveQwUserMsg(fastGptChatSession,2,count,sendUser);
+                    }
+                }
+            }
+        }
+    }
+    private void saveQwUserMsg(FastGptChatSession fastGptChatSession,Integer sendType,String content,QwUser sendUser) {
+        content = replaceWxEmo(content);
+        if(content.isEmpty()){
+            return;
+        }
+        FastGptChatMsg fastGptChatMsgAi = new FastGptChatMsg();
+        fastGptChatMsgAi.setContent(content);
+        fastGptChatMsgAi.setSessionId(fastGptChatSession.getSessionId());
+        fastGptChatMsgAi.setRoleId(Long.parseLong(fastGptChatSession.getKfId()));
+        fastGptChatMsgAi.setSendType(sendType);
+        fastGptChatMsgAi.setCompanyId(fastGptChatSession.getCompanyId());
+        fastGptChatMsgAi.setCompanyUserId(sendUser.getCompanyUserId());
+        fastGptChatMsgAi.setUserId(fastGptChatSession.getUserId());
+        fastGptChatMsgAi.setUserType(1);
+        fastGptChatMsgAi.setMsgType(1);
+        fastGptChatMsgAi.setStatus(0);
+        fastGptChatMsgAi.setAvatar(fastGptChatSession.getAvatar());
+        fastGptChatMsgAi.setNickName(fastGptChatSession.getNickName());
+        fastGptChatMsgAi.setCreateTime(new Date());
+        fastGptChatMsgAi.setExtId(fastGptChatSession.getQwExtId()+"");
+        fastGptChatMsgService.insertFastGptChatMsg(fastGptChatMsgAi);
+        log.info("新增消息:"+fastGptChatMsgAi);
+    }
+    /** 过滤[] **/
+    private static String replaceWxEmo(String s){
+
+        if(org.springframework.util.StringUtils.isEmpty(s)) return "";
+        // 替换匹配到的内容
+        String text = s.replaceAll("\\[.*?]", "").trim();
+        return EmojiParser.removeAllEmojis(text);
+    }
+}

+ 62 - 0
fs-service/src/main/java/com/fs/company/service/impl/AiAddwxSopUserNodeRuleServiceImpl.java

@@ -0,0 +1,62 @@
+package com.fs.company.service.impl;
+
+import com.fs.company.domain.AiAddwxSopUserNodeRule;
+import com.fs.company.mapper.AiAddwxSopUserNodeRuleMapper;
+import com.fs.company.service.IAiAddwxSopUserNodeRuleService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * AI添加微信SOP个人节点规则Service业务层处理
+ *
+ * @author fs
+ * @date 2026-06-24
+ */
+@Service
+public class AiAddwxSopUserNodeRuleServiceImpl implements IAiAddwxSopUserNodeRuleService {
+
+    @Autowired
+    private AiAddwxSopUserNodeRuleMapper userNodeRuleMapper;
+
+    @Override
+    public AiAddwxSopUserNodeRule selectAiAddwxSopUserNodeRuleById(Long userNodeRuleId) {
+        return userNodeRuleMapper.selectAiAddwxSopUserNodeRuleById(userNodeRuleId);
+    }
+
+    @Override
+    public List<AiAddwxSopUserNodeRule> selectAiAddwxSopUserNodeRuleByUserNodeId(Long userNodeId) {
+        return userNodeRuleMapper.selectAiAddwxSopUserNodeRuleByUserNodeId(userNodeId);
+    }
+
+    @Override
+    public List<AiAddwxSopUserNodeRule> selectAiAddwxSopUserNodeRuleList(AiAddwxSopUserNodeRule rule) {
+        return userNodeRuleMapper.selectAiAddwxSopUserNodeRuleList(rule);
+    }
+
+    @Override
+    public int insertAiAddwxSopUserNodeRule(AiAddwxSopUserNodeRule rule) {
+        return userNodeRuleMapper.insertAiAddwxSopUserNodeRule(rule);
+    }
+
+    @Override
+    public int updateAiAddwxSopUserNodeRule(AiAddwxSopUserNodeRule rule) {
+        return userNodeRuleMapper.updateAiAddwxSopUserNodeRule(rule);
+    }
+
+    @Override
+    public int deleteAiAddwxSopUserNodeRuleById(Long userNodeRuleId) {
+        return userNodeRuleMapper.deleteAiAddwxSopUserNodeRuleById(userNodeRuleId);
+    }
+
+    @Override
+    public int deleteAiAddwxSopUserNodeRuleByUserNodeId(Long userNodeId) {
+        return userNodeRuleMapper.deleteAiAddwxSopUserNodeRuleByUserNodeId(userNodeId);
+    }
+
+    @Override
+    public int batchInsertAiAddwxSopUserNodeRule(List<AiAddwxSopUserNodeRule> list) {
+        return userNodeRuleMapper.batchInsertAiAddwxSopUserNodeRule(list);
+    }
+}

+ 322 - 0
fs-service/src/main/java/com/fs/company/service/impl/AiAddwxSopUserNodeServiceImpl.java

@@ -0,0 +1,322 @@
+package com.fs.company.service.impl;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.fs.common.core.domain.R;
+import com.fs.company.domain.AiAddwxSopUserNode;
+import com.fs.company.domain.AiAddwxSopUserNodeRule;
+import com.fs.company.domain.AiAddwxSopUserWorkflow;
+import com.fs.company.enums.AiAddWechatSopNodeEnum;
+import com.fs.company.mapper.AiAddwxSopUserNodeMapper;
+import com.fs.company.mapper.AiAddwxSopUserNodeRuleMapper;
+import com.fs.company.mapper.AiAddwxSopUserWorkflowMapper;
+import com.fs.company.service.IAiAddwxSopUserNodeService;
+import com.fs.crm.utils.CrmCustomerAiTagUtil;
+import com.fs.fastGpt.domain.FastGptChatMsg;
+import com.fs.fastGpt.domain.FastGptChatSession;
+import com.fs.fastGpt.mapper.FastGptChatMsgMapper;
+import com.fs.fastGpt.mapper.FastGptChatSessionMapper;
+import com.fs.fastGpt.param.FastGptChatSessionParam;
+import com.fs.fastGpt.service.impl.FastGptChatSessionServiceImpl;
+import com.fs.qw.domain.QwExternalContact;
+import com.fs.qw.mapper.QwExternalContactMapper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.util.*;
+
+/**
+ * AI添加微信SOP个人节点Service业务层处理
+ *
+ * @author fs
+ * @date 2026-06-24
+ */
+@Service
+public class AiAddwxSopUserNodeServiceImpl implements IAiAddwxSopUserNodeService {
+
+    private static final Logger log = LoggerFactory.getLogger(AiAddwxSopUserNodeServiceImpl.class);
+    @Autowired
+    private AiAddwxSopUserNodeMapper userNodeMapper;
+    @Autowired
+    private FastGptChatSessionMapper fastGptChatSessionMapper;
+    @Autowired
+    private FastGptChatMsgMapper fastGptChatMsgMapper;
+    @Autowired
+    private AiAddwxSopUserWorkflowMapper aiAddwxSopUserWorkflowMapper;
+    @Autowired
+    private AiAddwxSopTemplateServiceImpl aiAddwxSopTemplateServiceImpl;
+    @Autowired
+    private AiAddwxSopUserNodeRuleMapper aiAddwxSopUserNodeRuleMapper;
+    @Qualifier("qwExternalContactMapper")
+    @Autowired
+    private QwExternalContactMapper qwExternalContactMapper;
+
+
+    @Override
+    public AiAddwxSopUserNode selectAiAddwxSopUserNodeById(Long userNodeId) {
+        return userNodeMapper.selectAiAddwxSopUserNodeById(userNodeId);
+    }
+
+    @Override
+    public List<AiAddwxSopUserNode> selectAiAddwxSopUserNodeByWorkflowId(Long userWorkflowId) {
+        return userNodeMapper.selectAiAddwxSopUserNodeByWorkflowId(userWorkflowId);
+    }
+
+    @Override
+    public List<AiAddwxSopUserNode> selectAiAddwxSopUserNodeList(AiAddwxSopUserNode userNode) {
+        return userNodeMapper.selectAiAddwxSopUserNodeList(userNode);
+    }
+
+    @Override
+    public int insertAiAddwxSopUserNode(AiAddwxSopUserNode userNode) {
+        return userNodeMapper.insertAiAddwxSopUserNode(userNode);
+    }
+
+    @Override
+    public int updateAiAddwxSopUserNode(AiAddwxSopUserNode userNode) {
+        return userNodeMapper.updateAiAddwxSopUserNode(userNode);
+    }
+
+    @Override
+    public int deleteAiAddwxSopUserNodeById(Long userNodeId) {
+        return userNodeMapper.deleteAiAddwxSopUserNodeById(userNodeId);
+    }
+
+    @Override
+    public int deleteAiAddwxSopUserNodeByWorkflowId(Long userWorkflowId) {
+        return userNodeMapper.deleteAiAddwxSopUserNodeByWorkflowId(userWorkflowId);
+    }
+
+    @Override
+    public int batchInsertAiAddwxSopUserNode(List<AiAddwxSopUserNode> list) {
+        return userNodeMapper.batchInsertAiAddwxSopUserNode(list);
+    }
+
+    @Override
+    public int updateNodeStatus(Long userNodeId, Integer status) {
+        AiAddwxSopUserNode node = new AiAddwxSopUserNode();
+        node.setUserNodeId(userNodeId);
+        node.setStatus(status);
+        return userNodeMapper.updateAiAddwxSopUserNode(node);
+    }
+
+    @Value("${ai.add.wx.sop.key:fastgpt-hPSZNV9pPlX5GlHyjzsGqEPbB2D8tb9mYQcnD1y5lEUIEMgGaO0PFm7BHHjF}")
+    private String AI_ADD_WC_SOP_KEY;
+    @Override
+    public void nextNode(Map.Entry<String, List<AiAddwxSopUserWorkflow>> next) {
+        AiAddWechatSopNodeEnum nodeTypeE = AiAddWechatSopNodeEnum.fromCode(next.getKey());
+        for (AiAddwxSopUserWorkflow workflow : next.getValue()){
+            FastGptChatSession session = fastGptChatSessionMapper.selectSessionByTriple(
+            workflow.getQwExternalId(),
+            workflow.getQwUserId(),
+            workflow.getCompanyId());
+            Map<String, Object> requestParam = new HashMap<>();
+
+            List<FastGptChatMsg> msgs = fastGptChatMsgMapper.selectFastGptChatMsgByMsgSessionIdAndExtId(session.getSessionId(), String.valueOf(workflow.getQwExternalId()));
+            //最新的一条是客服发送即未回复,走下一节点
+            if (msgs.get(0).getSendType().equals(2)){
+                List<AiAddwxSopUserNodeRule> aiAddwxSopUserNodeRules = aiAddwxSopUserNodeRuleMapper.selectList(new LambdaQueryWrapper<AiAddwxSopUserNodeRule>()
+                        .eq(AiAddwxSopUserNodeRule::getUserNodeId, workflow.getCurrentNodeId()).eq(AiAddwxSopUserNodeRule::getIsSent, 0).orderByAsc(AiAddwxSopUserNodeRule::getSortOrder));
+                if (!aiAddwxSopUserNodeRules.isEmpty()){
+                    AiAddwxSopUserNodeRule firstRule = aiAddwxSopUserNodeRules.get(0);
+                    if (firstRule.getRealSendTime() != null)continue;
+                    if (firstRule.getSendDay() != null && firstRule.getSendTime() != null && !firstRule.getSendTime().isEmpty()) {
+                        // 计算 realSendTime: now + sendDay 天 + sendTime,限制在 9:00-18:00
+                        // 获取当前时间
+                        LocalDateTime nowL = LocalDateTime.now();
+
+                        // 解析发送时间
+                        String[] timeParts = firstRule.getSendTime().split(":");
+                        int hour = Integer.parseInt(timeParts[0]);
+                        int minute = Integer.parseInt(timeParts[1]);
+
+                        // 构建目标时间:当前日期 + sendDay 天 + sendTime 时间
+                        LocalDateTime targetTime = nowL.plusDays(firstRule.getSendDay())
+                                .plusHours(hour)
+                                .plusMinutes(minute).plusMinutes(1)
+                                .withSecond(0)
+                                .withNano(0);
+
+                        // 业务规则:调整到 9:00 - 18:00 之间
+                        int targetHour = targetTime.getHour();
+                        if (targetHour < 9) {
+                            targetTime = targetTime.withHour(9).withMinute(0);
+                        } else if (targetHour >= 18) {
+                            targetTime = targetTime.plusDays(1).withHour(9).withMinute(0);
+                        }
+                        firstRule.setRealSendTime(Date.from(targetTime.atZone(ZoneId.systemDefault()).toInstant()));
+
+                        aiAddwxSopUserNodeRuleMapper.updateById(firstRule);
+                        continue;
+                    }
+                }
+            }
+            QwExternalContact qwExternalContact = qwExternalContactMapper.selectQwExternalContactById(workflow.getQwExternalId());
+            initAiChatParam(requestParam, workflow,session,nodeTypeE.getDesc(),qwExternalContact);
+
+            R r = aiAddwxSopTemplateServiceImpl.callAiService(requestParam, session.getSessionId(), AI_ADD_WC_SOP_KEY);
+            JSONObject content = extractContentJson(r);
+
+            //根据ai生成的结果进行节点转换
+            if (content!= null){
+                JSONObject userInfo = JSON.parseObject(content.getString("userInfo"));
+                if (userInfo != null){
+                    String currentGeneration = null;
+                    Boolean suc = false;
+                    try {
+                        currentGeneration = userInfo.getString("当前阶段");
+                        suc =true;
+                    }catch (Exception e){
+                        log.error("获取当前阶段失败,无需流转", e);
+                    }
+                    if (suc){
+                        AiAddWechatSopNodeEnum nodeEnum = AiAddWechatSopNodeEnum.fromCode(workflow.getCurrentNodeType());
+                        if (nodeEnum == null){
+                            log.error("当前阶段异常,请检查AI返回的当前阶段");
+                            return;
+                        }
+                        if (!currentGeneration.equals(nodeEnum.getDesc() )){
+                            AiAddwxSopUserWorkflow newWorkflow = new AiAddwxSopUserWorkflow();
+                            AiAddwxSopUserNode currentNode = userNodeMapper.selectById(workflow.getCurrentNodeId());
+                            List<AiAddwxSopUserNode> aiAddwxSopUserNodes = userNodeMapper.selectList(new LambdaQueryWrapper<AiAddwxSopUserNode>()
+                                    .eq(AiAddwxSopUserNode::getPrevNodeId, currentNode.getTemplateNodeId())
+                                    .eq(AiAddwxSopUserNode::getUserWorkflowId,currentNode.getUserWorkflowId()));
+                            AiAddwxSopUserNode nextNode = aiAddwxSopUserNodes.stream().filter(node -> node.getNodeType().equals(nodeEnum.getCode())).findFirst().orElse(null);
+                            if (nextNode != null){
+                                newWorkflow.setUserWorkflowId(workflow.getUserWorkflowId())
+                                        .setCurrentNodeId(nextNode.getUserNodeId())
+                                        .setCurrentNodeKey(nextNode.getNodeKey())
+                                        .setCurrentNodeType(nextNode.getNodeType()); //新节点
+                                aiAddwxSopUserWorkflowMapper.updateById(newWorkflow);
+                            }
+                        }
+                    }else {
+                        //未变更就继续对话
+                        String aiContent = content.getString("aiContent");
+                        List<AiAddwxSopUserNodeRule> aiAddwxSopUserNodeRules = aiAddwxSopUserNodeRuleMapper.selectList(new LambdaQueryWrapper<AiAddwxSopUserNodeRule>()
+                                .eq(AiAddwxSopUserNodeRule::getUserNodeId, workflow.getCurrentNodeId()).eq(AiAddwxSopUserNodeRule::getIsSent, 1).orderByAsc(AiAddwxSopUserNodeRule::getUpdateTime));
+                        if (!aiAddwxSopUserNodeRules.isEmpty()){
+                            AiAddwxSopUserNodeRule finalRule = aiAddwxSopUserNodeRules.get(aiAddwxSopUserNodeRules.size() - 1);
+                            Date date = new Date();
+                            Date datePlus5Seconds = new Date(date.getTime() + 5000);
+                            finalRule.setIsSent(0).setUserNodeRuleId(null).setRealSendTime(datePlus5Seconds).setContentJson(aiContent);
+                            aiAddwxSopUserNodeRuleMapper.updateById(finalRule);
+                        }
+                    }
+
+                }
+            }
+
+        }
+
+    }
+    private static JSONObject extractContentJson(R result) {
+        try {
+            Object data = result.get("data");
+            if (data == null) {
+                return null;
+            }
+
+            String dataJson = JSON.toJSONString(data);
+            JSONObject dataObj = JSON.parseObject(dataJson);
+
+            JSONArray choices = dataObj.getJSONArray("choices");
+            if (choices == null || choices.isEmpty()) {
+                return null;
+            }
+
+            JSONObject firstChoice = choices.getJSONObject(0);
+            JSONObject message = firstChoice.getJSONObject("message");
+            if (message == null) {
+                return null;
+            }
+
+            String content = message.getString("content");
+            if (content == null || content.trim().isEmpty()) {
+                return null;
+            }
+
+            return JSON.parseObject(content);
+
+        } catch (Exception e) {
+            System.err.println("提取 content JSON 失败: " + e.getMessage());
+            return null;
+        }
+    }
+
+    private void initAiChatParam(Map<String, Object> requestParam, AiAddwxSopUserWorkflow workflow, FastGptChatSession session, String nodeTypeE, QwExternalContact externalUser) {
+        // 1. 构建 userInfo
+        Map<String, Object> userInfo = new HashMap<>();
+        userInfo.put("姓名", externalUser.getName()!=null?externalUser.getName():"");
+        userInfo.put("微信昵称", externalUser.getRemark() != null ? externalUser.getRemark() : "");
+        userInfo.put("联系方式",  "");
+        userInfo.put("性别", externalUser.getGender()==1?"男":externalUser.getGender()==2?"女":"未知");
+        userInfo.put("收货地址", "");
+        userInfo.put("状态", "");
+        userInfo.put("当前阶段", nodeTypeE);
+
+        // 查询下一阶段(下一节点的类型)
+        StringBuilder nextStages = new StringBuilder();
+        List<AiAddwxSopUserNode> allNodes = userNodeMapper.selectAiAddwxSopUserNodeByWorkflowId(workflow.getUserWorkflowId());
+        if (allNodes != null && workflow.getCurrentNodeId() != null) {
+            for (AiAddwxSopUserNode node : allNodes) {
+                if (workflow.getCurrentNodeId().equals(node.getPrevNodeId())) {
+                    if (nextStages.length() > 0) {
+                        nextStages.append("|");
+                    }
+                    AiAddWechatSopNodeEnum nextType = AiAddWechatSopNodeEnum.fromCode(node.getNodeType());
+                    nextStages.append(nextType != null ? nextType.getDesc() : node.getNodeType());
+                }
+            }
+        }
+        userInfo.put("下一阶段", nextStages.toString());
+        requestParam.put("userInfo", userInfo);
+
+        // 2. 构建 history
+        List<Map<String, String>> history = new ArrayList<>();
+        String userContent = "";
+        if (session != null) {
+            List<FastGptChatMsg> msgs = fastGptChatMsgMapper.selectFastGptChatMsgByMsgSessionIdAndExtId(session.getSessionId(), String.valueOf(workflow.getQwExternalId()));
+            if (msgs != null) {
+                for (FastGptChatMsg msg : msgs) {
+                    Map<String, String> item = new HashMap<>();
+                    item.put("role", msg.getSendType() != null && msg.getSendType() == 1 ? "user" : "ai");
+                    item.put("content", msg.getContent());
+                    history.add(item);
+                }
+                // 取最后一条用户消息作为 userContent
+                for (FastGptChatMsg msg : msgs) {
+                    if (msg.getSendType() != null && msg.getSendType() == 1) {
+                        userContent = msg.getContent();
+                        break;
+                    }
+                }
+            }
+        }
+        requestParam.put("history", history);
+        requestParam.put("userContent", userContent);
+
+        // 3. knowledgeBase
+        ArrayList<Object> knowledgeBase = new ArrayList<>();
+
+//        AiAddwxSopUserNodeRule currentRule = aiAddwxSopUserNodeRuleMapper.selectOne(new LambdaQueryWrapper<AiAddwxSopUserNodeRule>()
+//                .eq(AiAddwxSopUserNodeRule::getUserNodeId, workflow.getCurrentNodeId()).eq(AiAddwxSopUserNodeRule::getIsSent, 0).orderByAsc(AiAddwxSopUserNodeRule::getSortOrder).last("limit 1"));
+//        if (currentRule != null){
+//            JSONObject rule = JSON.parseObject(currentRule.getContentJson());
+//            if (rule.get("prompt")!=null){
+//                knowledgeBase.add(rule.get("prompt"));
+//            }
+//        }
+        requestParam.put("knowledgeBase", knowledgeBase);
+
+        //待陈迪林提供话术配置
+    }
+}

+ 226 - 0
fs-service/src/main/java/com/fs/company/service/impl/AiAddwxSopUserWorkflowServiceImpl.java

@@ -0,0 +1,226 @@
+package com.fs.company.service.impl;
+
+import com.fs.company.domain.AiAddwxSopTemplateNode;
+import com.fs.company.domain.AiAddwxSopUserNode;
+import com.fs.company.domain.AiAddwxSopUserWorkflow;
+import com.fs.company.mapper.AiAddwxSopTemplateNodeMapper;
+import com.fs.company.mapper.AiAddwxSopUserNodeMapper;
+import com.fs.company.mapper.AiAddwxSopUserWorkflowMapper;
+import com.fs.company.service.IAiAddwxSopUserWorkflowService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.sql.Timestamp;
+import java.util.*;
+
+/**
+ * AI添加微信SOP个人工作流Service业务层处理
+ *
+ * @author fs
+ * @date 2026-06-24
+ */
+@Service
+public class AiAddwxSopUserWorkflowServiceImpl implements IAiAddwxSopUserWorkflowService {
+
+    @Autowired
+    private AiAddwxSopUserWorkflowMapper userWorkflowMapper;
+
+    @Autowired
+    private AiAddwxSopUserNodeMapper userNodeMapper;
+
+    @Autowired
+    private AiAddwxSopTemplateNodeMapper templateNodeMapper;
+
+    @Override
+    public AiAddwxSopUserWorkflow selectAiAddwxSopUserWorkflowById(Long userWorkflowId) {
+        return userWorkflowMapper.selectAiAddwxSopUserWorkflowById(userWorkflowId);
+    }
+
+    @Override
+    public List<AiAddwxSopUserWorkflow> selectAiAddwxSopUserWorkflowList(AiAddwxSopUserWorkflow userWorkflow) {
+        return userWorkflowMapper.selectAiAddwxSopUserWorkflowList(userWorkflow);
+    }
+
+    @Override
+    public Long insertAiAddwxSopUserWorkflow(AiAddwxSopUserWorkflow userWorkflow) {
+        userWorkflowMapper.insertAiAddwxSopUserWorkflow(userWorkflow);
+        return userWorkflow.getUserWorkflowId();
+    }
+
+    @Override
+    public int updateAiAddwxSopUserWorkflow(AiAddwxSopUserWorkflow userWorkflow) {
+        return userWorkflowMapper.updateAiAddwxSopUserWorkflow(userWorkflow);
+    }
+
+    @Override
+    public int deleteAiAddwxSopUserWorkflowByIds(Long[] userWorkflowIds) {
+        return userWorkflowMapper.deleteAiAddwxSopUserWorkflowByIds(userWorkflowIds);
+    }
+
+    @Override
+    public AiAddwxSopUserWorkflow selectUserWorkflowDetailById(Long userWorkflowId) {
+        AiAddwxSopUserWorkflow workflow = userWorkflowMapper.selectAiAddwxSopUserWorkflowById(userWorkflowId);
+        if (workflow != null) {
+            List<AiAddwxSopUserNode> nodes = userNodeMapper.selectAiAddwxSopUserNodeByWorkflowId(userWorkflowId);
+            // 构建节点树结构
+            buildNodeTree(nodes);
+        }
+        return workflow;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Long createUserWorkflowFromTemplate(Long templateId, Long companyUserId, Long companyId
+//            , Long businessKey
+    ) {
+        // 获取模板节点
+        List<AiAddwxSopTemplateNode> templateNodes = templateNodeMapper.selectAiAddwxSopTemplateNodeByTemplateId(templateId);
+        if (templateNodes == null || templateNodes.isEmpty()) {
+            throw new RuntimeException("模板节点为空");
+        }
+
+        // 创建个人工作流
+        AiAddwxSopUserWorkflow userWorkflow = new AiAddwxSopUserWorkflow();
+        userWorkflow.setTemplateId(templateId);
+        userWorkflow.setCompanyUserId(companyUserId);
+        userWorkflow.setCompanyId(companyId);
+//        userWorkflow.setBusinessKey(businessKey);
+        userWorkflow.setStatus(0);
+        userWorkflow.setStartTime(new Date());
+        userWorkflow.setDelFlag(0);
+        userWorkflowMapper.insertAiAddwxSopUserWorkflow(userWorkflow);
+
+        // 按排序号排序模板节点
+        templateNodes.sort(Comparator.comparing(AiAddwxSopTemplateNode::getSortOrder, Comparator.nullsLast(Comparator.naturalOrder())));
+
+        // 创建个人节点,建立前后关系
+        List<AiAddwxSopUserNode> userNodes = new ArrayList<>();
+        Map<String, Long> nodeKeyToIdMap = new HashMap<>();
+
+        for (int i = 0; i < templateNodes.size(); i++) {
+            AiAddwxSopTemplateNode tn = templateNodes.get(i);
+            AiAddwxSopUserNode un = new AiAddwxSopUserNode();
+            un.setUserWorkflowId(userWorkflow.getUserWorkflowId());
+            un.setTemplateNodeId(tn.getNodeId());
+            un.setNodeKey(tn.getNodeKey());
+            un.setNodeName(tn.getNodeName());
+            un.setNodeType(tn.getNodeType());
+            un.setSortOrder(tn.getSortOrder());
+            un.setStatus(0);
+            un.setCreateTime(new Date());
+            un.setUpdateTime(new Date());
+
+            // 设置上一个节点ID
+            if (i > 0) {
+                un.setPrevNodeId(userNodes.get(i - 1).getUserNodeId());
+            }
+            userNodes.add(un);
+        }
+
+        // 先批量插入获取ID
+        userNodeMapper.batchInsertAiAddwxSopUserNode(userNodes);
+
+        // 重新查询获取实际ID后设置nextNodeId
+        List<AiAddwxSopUserNode> savedNodes = userNodeMapper.selectAiAddwxSopUserNodeByWorkflowId(userWorkflow.getUserWorkflowId());
+        savedNodes.sort(Comparator.comparing(AiAddwxSopUserNode::getSortOrder, Comparator.nullsLast(Comparator.naturalOrder())));
+
+        for (int i = 0; i < savedNodes.size(); i++) {
+            AiAddwxSopUserNode node = savedNodes.get(i);
+            if (i < savedNodes.size() - 1) {
+                node.setNextNodeId(savedNodes.get(i + 1).getUserNodeId());
+            }
+            // 从模板节点配置中获取提示词和条件
+            AiAddwxSopTemplateNode tn = templateNodes.get(i);
+            if (tn.getNodeConfig() != null) {
+                node.setNodeConfig(tn.getNodeConfig());
+            }
+            userNodeMapper.updateAiAddwxSopUserNode(node);
+        }
+
+        // 设置工作流当前节点为第一个节点
+        if (!savedNodes.isEmpty()) {
+            AiAddwxSopUserWorkflow updateWf = new AiAddwxSopUserWorkflow();
+            updateWf.setUserWorkflowId(userWorkflow.getUserWorkflowId());
+            updateWf.setCurrentNodeId(savedNodes.get(0).getUserNodeId());
+            updateWf.setCurrentNodeKey(savedNodes.get(0).getNodeKey());
+            userWorkflowMapper.updateAiAddwxSopUserWorkflow(updateWf);
+        }
+
+        return userWorkflow.getUserWorkflowId();
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public int advanceToNextNode(Long userWorkflowId, Long currentNodeId) {
+        AiAddwxSopUserNode currentNode = userNodeMapper.selectAiAddwxSopUserNodeById(currentNodeId);
+        if (currentNode == null) {
+            throw new RuntimeException("当前节点不存在");
+        }
+
+        // 更新当前节点为已完成
+        currentNode.setStatus(2);
+        currentNode.setEndTime(new Date());
+        userNodeMapper.updateAiAddwxSopUserNode(currentNode);
+
+        // 推进到下一节点
+        if (currentNode.getNextNodeId() != null) {
+            AiAddwxSopUserNode nextNode = userNodeMapper.selectAiAddwxSopUserNodeById(currentNode.getNextNodeId());
+            if (nextNode != null) {
+                nextNode.setStatus(1);
+                nextNode.setStartTime(new Date());
+                userNodeMapper.updateAiAddwxSopUserNode(nextNode);
+
+                // 更新工作流当前节点
+                AiAddwxSopUserWorkflow updateWf = new AiAddwxSopUserWorkflow();
+                updateWf.setUserWorkflowId(userWorkflowId);
+                updateWf.setCurrentNodeId(nextNode.getUserNodeId());
+                updateWf.setCurrentNodeKey(nextNode.getNodeKey());
+                updateWf.setStatus(1);
+                userWorkflowMapper.updateAiAddwxSopUserWorkflow(updateWf);
+            }
+        } else {
+            // 没有下一节点,工作流完成
+            AiAddwxSopUserWorkflow updateWf = new AiAddwxSopUserWorkflow();
+            updateWf.setUserWorkflowId(userWorkflowId);
+            updateWf.setStatus(2);
+            updateWf.setEndTime(new Date());
+            userWorkflowMapper.updateAiAddwxSopUserWorkflow(updateWf);
+        }
+        return 1;
+    }
+
+    @Override
+    public List<AiAddwxSopUserNode> selectUserNodesByWorkflowId(Long userWorkflowId) {
+        List<AiAddwxSopUserNode> nodes = userNodeMapper.selectAiAddwxSopUserNodeByWorkflowId(userWorkflowId);
+        buildNodeTree(nodes);
+        return nodes;
+    }
+
+    /**
+     * 构建节点树结构
+     */
+    private void buildNodeTree(List<AiAddwxSopUserNode> nodes) {
+        if (nodes == null || nodes.isEmpty()) {
+            return;
+        }
+        Map<Long, AiAddwxSopUserNode> nodeMap = new HashMap<>();
+        for (AiAddwxSopUserNode node : nodes) {
+            nodeMap.put(node.getUserNodeId(), node);
+        }
+        for (AiAddwxSopUserNode node : nodes) {
+            if (node.getChildNodes() != null && !node.getChildNodes().isEmpty()) {
+                List<AiAddwxSopUserNode> childList = new ArrayList<>();
+                String[] childIds = node.getChildNodes().replace("[", "").replace("]", "").split(",");
+                for (String childId : childIds) {
+                    Long cid = Long.parseLong(childId.trim());
+                    AiAddwxSopUserNode child = nodeMap.get(cid);
+                    if (child != null) {
+                        childList.add(child);
+                    }
+                }
+                node.setChildNodeList(childList);
+            }
+        }
+    }
+}

+ 1 - 0
fs-service/src/main/java/com/fs/fastGpt/mapper/FastGptChatSessionMapper.java

@@ -128,4 +128,5 @@ public interface FastGptChatSessionMapper
     @Update("UPDATE fastgpt_chat_session SET is_artificial = 0 WHERE is_artificial = 1 AND TIMESTAMPDIFF(MINUTE, last_time, NOW()) > #{time}")
     void updateFastGptChatSessionByIsReplyWithTime(@Param("time") Integer time);
 
+    FastGptChatSession selectSessionByTriple(@Param("qwExternalId") Long qwExternalId,@Param("qwUserId") Long qwUserId,@Param("companyId") Long companyId);
 }

+ 1 - 0
fs-service/src/main/java/com/fs/qw/mapper/QwTagMapper.java

@@ -108,4 +108,5 @@ public interface QwTagMapper
             "</script>")
     List<ExternalContactTagVO> selectQwTagListByTagIds(@Param("tagIds") List<String> tagIds);
 
+    List<Long> selectIdsByTagIds(@Param("tagIds")List<String> tagIds);
 }

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

@@ -549,4 +549,6 @@ public interface QwUserMapper extends BaseMapper<QwUser>
 
     @Select("select * from qw_user where company_user_id =#{companyUserId} and is_del = 0 order by id asc limit 1 ")
     QwUser selectQwUserconvertSopVoice(@Param("companyUserId") Long companyUserID);
+
+    List<QwUser> selectQwUserByIdsPrimary(@Param("collect") List<Long> collect);
 }

+ 6 - 1
fs-service/src/main/java/com/fs/qw/service/impl/QwExternalContactServiceImpl.java

@@ -23,6 +23,7 @@ import com.fs.company.domain.CompanyWxAccount;
 import com.fs.company.domain.CompanyWxClient;
 import com.fs.company.mapper.CompanyWxAccountMapper;
 import com.fs.company.mapper.CompanyWxClientMapper;
+import com.fs.company.service.IAiAddwxSopTemplateService;
 import com.fs.company.service.ICompanyConfigService;
 import com.fs.company.service.ICompanyVoiceRoboticCallLogAddwxService;
 import com.fs.company.service.impl.CompanyWxServiceImpl;
@@ -257,6 +258,8 @@ public class QwExternalContactServiceImpl extends ServiceImpl<QwExternalContactM
     Logger logger = LoggerFactory.getLogger(getClass());
     @Autowired
     private CompanyWxAccountMapper companyWxAccountMapper;
+    @Autowired
+    private IAiAddwxSopTemplateService templateService;
 
     @Override
     public void addQwCourseJob() {
@@ -1307,7 +1310,9 @@ public class QwExternalContactServiceImpl extends ServiceImpl<QwExternalContactM
             } catch (Exception e) {
                 return R.error("批量查询用户数据失败:" + e.getMessage());
             }
-
+            if (CloudHostUtils.hasCloudHostName("今正科技")){
+                templateService.initAddWxSop(contacts,param.getTagIds());
+            }
             // 直接遍历contacts而不是userIds
             for (QwExternalContact qwExternalContact : contacts) {
                 futures.add(executor.submit(() -> {

+ 108 - 0
fs-service/src/main/resources/mapper/aiAddwxSop/AiAddwxSopTagTemplateBindMapper.xml

@@ -0,0 +1,108 @@
+<?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.AiAddwxSopTagTemplateBindMapper">
+
+    <resultMap type="com.fs.company.domain.AiAddwxSopTagTemplateBind" id="AiAddwxSopTagTemplateBindResult">
+        <result property="bindId"       column="bind_id"       />
+        <result property="qwTagId"      column="qw_tag_id"     />
+        <result property="qwTagName"    column="qw_tag_name"   />
+        <result property="templateId"   column="template_id"   />
+        <result property="templateName" column="template_name" />
+        <result property="priority"     column="priority"      />
+        <result property="createTime"   column="create_time"   />
+        <result property="updateTime"   column="update_time"   />
+    </resultMap>
+
+    <sql id="selectAiAddwxSopTagTemplateBindVo">
+        select bind_id, qw_tag_id, qw_tag_name, template_id, template_name, priority, create_time, update_time
+        from ai_addwx_sop_tag_template_bind
+    </sql>
+
+    <select id="selectAiAddwxSopTagTemplateBindById" parameterType="Long" resultMap="AiAddwxSopTagTemplateBindResult">
+        <include refid="selectAiAddwxSopTagTemplateBindVo"/>
+        where bind_id = #{bindId}
+    </select>
+
+    <select id="selectAiAddwxSopTagTemplateBindByTagId" parameterType="String" resultMap="AiAddwxSopTagTemplateBindResult">
+        <include refid="selectAiAddwxSopTagTemplateBindVo"/>
+        where qw_tag_id = #{qwTagId}
+    </select>
+
+    <select id="selectAiAddwxSopTagTemplateBindList" parameterType="com.fs.company.domain.AiAddwxSopTagTemplateBind" resultMap="AiAddwxSopTagTemplateBindResult">
+        <include refid="selectAiAddwxSopTagTemplateBindVo"/>
+        <where>
+            <if test="qwTagId != null and qwTagId != ''"> and qw_tag_id = #{qwTagId}</if>
+            <if test="qwTagName != null and qwTagName != ''"> and qw_tag_name like concat('%', #{qwTagName}, '%')</if>
+            <if test="templateId != null"> and template_id = #{templateId}</if>
+            <if test="templateName != null and templateName != ''"> and template_name like concat('%', #{templateName}, '%')</if>
+        </where>
+        order by priority desc, create_time desc
+    </select>
+    <select id="selectOneAiAddwxSopTagTemplateBindByTagIds" resultType="java.lang.Long">
+        select b.template_id
+        from ai_addwx_sop_tag_template_bind b
+                 inner join ai_addwx_sop_template t on t.template_id = b.template_id
+                 inner join qw_tag q on q.id = b.qw_tag_id
+        <where>
+            b.qw_tag_id in
+            <foreach item="tagId" collection="tagIds" separator="," open="(" close=")">
+                #{tagId}
+            </foreach>
+        </where>
+        order by b.priority desc limit 1
+    </select>
+    <select id="selectTagBindBytagIdAndTemplateId"
+            resultType="com.fs.company.domain.AiAddwxSopTagTemplateBind">
+        <include refid="selectAiAddwxSopTagTemplateBindVo"/>
+        where
+        qw_tag_id = #{qwTagId}
+        and
+        template_id = #{templateId}
+    </select>
+
+    <insert id="insertAiAddwxSopTagTemplateBind" parameterType="com.fs.company.domain.AiAddwxSopTagTemplateBind" useGeneratedKeys="true" keyProperty="bindId">
+        insert into ai_addwx_sop_tag_template_bind
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="qwTagId != null">qw_tag_id,</if>
+            <if test="qwTagName != null">qw_tag_name,</if>
+            <if test="templateId != null">template_id,</if>
+            <if test="templateName != null">template_name,</if>
+            <if test="priority != null">priority,</if>
+            <if test="createTime != null">create_time,</if>
+            <if test="updateTime != null">update_time,</if>
+        </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="qwTagId != null">#{qwTagId},</if>
+            <if test="qwTagName != null">#{qwTagName},</if>
+            <if test="templateId != null">#{templateId},</if>
+            <if test="templateName != null">#{templateName},</if>
+            <if test="priority != null">#{priority},</if>
+            <if test="createTime != null">#{createTime},</if>
+            <if test="updateTime != null">#{updateTime},</if>
+        </trim>
+    </insert>
+
+    <update id="updateAiAddwxSopTagTemplateBind" parameterType="com.fs.company.domain.AiAddwxSopTagTemplateBind">
+        update ai_addwx_sop_tag_template_bind
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="qwTagId != null">qw_tag_id = #{qwTagId},</if>
+            <if test="qwTagName != null">qw_tag_name = #{qwTagName},</if>
+            <if test="templateId != null">template_id = #{templateId},</if>
+            <if test="templateName != null">template_name = #{templateName},</if>
+            <if test="priority != null">priority = #{priority},</if>
+            <if test="updateTime != null">update_time = #{updateTime},</if>
+        </trim>
+        where bind_id = #{bindId}
+    </update>
+
+    <delete id="deleteAiAddwxSopTagTemplateBindById" parameterType="Long">
+        delete from ai_addwx_sop_tag_template_bind where bind_id = #{bindId}
+    </delete>
+
+    <delete id="deleteAiAddwxSopTagTemplateBindByTagId" parameterType="String">
+        delete from ai_addwx_sop_tag_template_bind where qw_tag_id = #{qwTagId}
+    </delete>
+
+</mapper>

+ 108 - 0
fs-service/src/main/resources/mapper/aiAddwxSop/AiAddwxSopTemplateNodeRuleMapper.xml

@@ -0,0 +1,108 @@
+<?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.AiAddwxSopTemplateNodeRuleMapper">
+
+    <resultMap type="com.fs.company.domain.AiAddwxSopTemplateNodeRule" id="AiAddwxSopTemplateNodeRuleResult">
+        <result property="ruleId"      column="rule_id"      />
+        <result property="nodeId"      column="node_id"      />
+        <result property="ruleName"    column="rule_name"    />
+        <result property="sendTime"    column="send_time"    />
+        <result property="sendDay"     column="send_day"     />
+        <result property="msgType"     column="msg_type"     />
+        <result property="contentType" column="content_type" />
+        <result property="contentJson" column="content_json" />
+        <result property="sortOrder"   column="sort_order"   />
+        <result property="createTime"  column="create_time"  />
+        <result property="updateTime"  column="update_time"  />
+    </resultMap>
+
+    <sql id="selectAiAddwxSopTemplateNodeRuleVo">
+        select rule_id, node_id, rule_name, send_time, send_day, msg_type, content_type, content_json, sort_order, create_time, update_time
+        from ai_addwx_sop_template_node_rule
+    </sql>
+
+    <select id="selectAiAddwxSopTemplateNodeRuleById" parameterType="Long" resultMap="AiAddwxSopTemplateNodeRuleResult">
+        <include refid="selectAiAddwxSopTemplateNodeRuleVo"/>
+        where rule_id = #{ruleId}
+    </select>
+
+    <select id="selectAiAddwxSopTemplateNodeRuleByNodeId" parameterType="Long" resultMap="AiAddwxSopTemplateNodeRuleResult">
+        <include refid="selectAiAddwxSopTemplateNodeRuleVo"/>
+        where node_id = #{nodeId}
+        order by sort_order asc
+    </select>
+
+    <select id="selectAiAddwxSopTemplateNodeRuleList" parameterType="com.fs.company.domain.AiAddwxSopTemplateNodeRule" resultMap="AiAddwxSopTemplateNodeRuleResult">
+        <include refid="selectAiAddwxSopTemplateNodeRuleVo"/>
+        <where>
+            <if test="nodeId != null"> and node_id = #{nodeId}</if>
+            <if test="ruleName != null and ruleName != ''"> and rule_name like concat('%', #{ruleName}, '%')</if>
+            <if test="msgType != null"> and msg_type = #{msgType}</if>
+        </where>
+        order by sort_order asc
+    </select>
+
+    <insert id="insertAiAddwxSopTemplateNodeRule" parameterType="com.fs.company.domain.AiAddwxSopTemplateNodeRule" useGeneratedKeys="true" keyProperty="ruleId">
+        insert into ai_addwx_sop_template_node_rule
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="nodeId != null">node_id,</if>
+            <if test="ruleName != null">rule_name,</if>
+            <if test="sendTime != null">send_time,</if>
+            <if test="sendDay != null">send_day,</if>
+            <if test="msgType != null">msg_type,</if>
+            <if test="contentType != null">content_type,</if>
+            <if test="contentJson != null">content_json,</if>
+            <if test="sortOrder != null">sort_order,</if>
+            <if test="createTime != null">create_time,</if>
+            <if test="updateTime != null">update_time,</if>
+        </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="nodeId != null">#{nodeId},</if>
+            <if test="ruleName != null">#{ruleName},</if>
+            <if test="sendTime != null">#{sendTime},</if>
+            <if test="sendDay != null">#{sendDay},</if>
+            <if test="msgType != null">#{msgType},</if>
+            <if test="contentType != null">#{contentType},</if>
+            <if test="contentJson != null">#{contentJson},</if>
+            <if test="sortOrder != null">#{sortOrder},</if>
+            <if test="createTime != null">#{createTime},</if>
+            <if test="updateTime != null">#{updateTime},</if>
+        </trim>
+    </insert>
+
+    <update id="updateAiAddwxSopTemplateNodeRule" parameterType="com.fs.company.domain.AiAddwxSopTemplateNodeRule">
+        update ai_addwx_sop_template_node_rule
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="nodeId != null">node_id = #{nodeId},</if>
+            <if test="ruleName != null">rule_name = #{ruleName},</if>
+            <if test="sendTime != null">send_time = #{sendTime},</if>
+            <if test="sendDay != null">send_day = #{sendDay},</if>
+            <if test="msgType != null">msg_type = #{msgType},</if>
+            <if test="contentType != null">content_type = #{contentType},</if>
+            <if test="contentJson != null">content_json = #{contentJson},</if>
+            <if test="sortOrder != null">sort_order = #{sortOrder},</if>
+            <if test="createTime != null">create_time = #{createTime},</if>
+            <if test="updateTime != null">update_time = #{updateTime},</if>
+        </trim>
+        where rule_id = #{ruleId}
+    </update>
+
+    <delete id="deleteAiAddwxSopTemplateNodeRuleById" parameterType="Long">
+        delete from ai_addwx_sop_template_node_rule where rule_id = #{ruleId}
+    </delete>
+
+    <delete id="deleteAiAddwxSopTemplateNodeRuleByNodeId" parameterType="Long">
+        delete from ai_addwx_sop_template_node_rule where node_id = #{nodeId}
+    </delete>
+
+    <insert id="batchInsertAiAddwxSopTemplateNodeRule">
+        insert into ai_addwx_sop_template_node_rule (node_id, rule_name, send_time, send_day, msg_type, content_type, content_json, sort_order, create_time, update_time)
+        values
+        <foreach collection="list" item="item" separator=",">
+            (#{item.nodeId}, #{item.ruleName}, #{item.sendTime}, #{item.sendDay}, #{item.msgType}, #{item.contentType}, #{item.contentJson}, #{item.sortOrder}, #{item.createTime}, #{item.updateTime})
+        </foreach>
+    </insert>
+
+</mapper>

+ 124 - 0
fs-service/src/main/resources/mapper/aiAddwxSop/AiAddwxSopUserNodeRuleMapper.xml

@@ -0,0 +1,124 @@
+<?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.AiAddwxSopUserNodeRuleMapper">
+
+    <resultMap type="com.fs.company.domain.AiAddwxSopUserNodeRule" id="AiAddwxSopUserNodeRuleResult">
+        <result property="userNodeRuleId" column="user_node_rule_id" />
+        <result property="userNodeId"     column="user_node_id"     />
+        <result property="qwExternalId"   column="qw_external_id"   />
+        <result property="qwUserId"       column="qw_user_id"       />
+        <result property="ruleName"       column="rule_name"        />
+        <result property="sendTime"       column="send_time"        />
+        <result property="sendDay"        column="send_day"         />
+        <result property="msgType"        column="msg_type"         />
+        <result property="contentType"    column="content_type"     />
+        <result property="contentJson"    column="content_json"     />
+        <result property="sortOrder"      column="sort_order"       />
+        <result property="isSent"         column="is_sent"          />
+        <result property="realSendTime"   column="real_send_time"   />
+        <result property="createTime"     column="create_time"      />
+        <result property="updateTime"     column="update_time"      />
+    </resultMap>
+
+    <sql id="selectAiAddwxSopUserNodeRuleVo">
+        select user_node_rule_id, user_node_id, qw_external_id, qw_user_id, rule_name, send_time, send_day, msg_type, content_type, content_json, sort_order, is_sent, real_send_time, create_time, update_time
+        from ai_addwx_sop_user_node_rule
+    </sql>
+
+    <select id="selectAiAddwxSopUserNodeRuleById" parameterType="Long" resultMap="AiAddwxSopUserNodeRuleResult">
+        <include refid="selectAiAddwxSopUserNodeRuleVo"/>
+        where user_node_rule_id = #{userNodeRuleId}
+    </select>
+
+    <select id="selectAiAddwxSopUserNodeRuleByUserNodeId" parameterType="Long" resultMap="AiAddwxSopUserNodeRuleResult">
+        <include refid="selectAiAddwxSopUserNodeRuleVo"/>
+        where user_node_id = #{userNodeId}
+        order by sort_order asc
+    </select>
+
+    <select id="selectAiAddwxSopUserNodeRuleList" parameterType="com.fs.company.domain.AiAddwxSopUserNodeRule" resultMap="AiAddwxSopUserNodeRuleResult">
+        <include refid="selectAiAddwxSopUserNodeRuleVo"/>
+        <where>
+            <if test="userNodeId != null"> and user_node_id = #{userNodeId}</if>
+            <if test="ruleName != null and ruleName != ''"> and rule_name like concat('%', #{ruleName}, '%')</if>
+            <if test="msgType != null"> and msg_type = #{msgType}</if>
+        </where>
+        order by sort_order asc
+    </select>
+
+    <insert id="insertAiAddwxSopUserNodeRule" parameterType="com.fs.company.domain.AiAddwxSopUserNodeRule" useGeneratedKeys="true" keyProperty="userNodeRuleId">
+        insert into ai_addwx_sop_user_node_rule
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="userNodeId != null">user_node_id,</if>
+            <if test="qwExternalId != null">qw_external_id,</if>
+            <if test="qwUserId != null">qw_user_id,</if>
+            <if test="ruleName != null">rule_name,</if>
+            <if test="sendTime != null">send_time,</if>
+            <if test="sendDay != null">send_day,</if>
+            <if test="msgType != null">msg_type,</if>
+            <if test="contentType != null">content_type,</if>
+            <if test="contentJson != null">content_json,</if>
+            <if test="sortOrder != null">sort_order,</if>
+            <if test="isSent != null">is_sent,</if>
+            <if test="realSendTime != null">real_send_time,</if>
+            <if test="createTime != null">create_time,</if>
+            <if test="updateTime != null">update_time,</if>
+        </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="userNodeId != null">#{userNodeId},</if>
+            <if test="qwExternalId != null">#{qwExternalId},</if>
+            <if test="qwUserId != null">#{qwUserId},</if>
+            <if test="ruleName != null">#{ruleName},</if>
+            <if test="sendTime != null">#{sendTime},</if>
+            <if test="sendDay != null">#{sendDay},</if>
+            <if test="msgType != null">#{msgType},</if>
+            <if test="contentType != null">#{contentType},</if>
+            <if test="contentJson != null">#{contentJson},</if>
+            <if test="sortOrder != null">#{sortOrder},</if>
+            <if test="isSent != null">#{isSent},</if>
+            <if test="realSendTime != null">#{realSendTime},</if>
+            <if test="createTime != null">#{createTime},</if>
+            <if test="updateTime != null">#{updateTime},</if>
+        </trim>
+    </insert>
+
+    <update id="updateAiAddwxSopUserNodeRule" parameterType="com.fs.company.domain.AiAddwxSopUserNodeRule">
+        update ai_addwx_sop_user_node_rule
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="userNodeId != null">user_node_id = #{userNodeId},</if>
+            <if test="qwExternalId != null">qw_external_id = #{qwExternalId},</if>
+            <if test="qwUserId != null">qw_user_id = #{qwUserId},</if>
+            <if test="ruleName != null">rule_name = #{ruleName},</if>
+            <if test="sendTime != null">send_time = #{sendTime},</if>
+            <if test="sendDay != null">send_day = #{sendDay},</if>
+            <if test="msgType != null">msg_type = #{msgType},</if>
+            <if test="contentType != null">content_type = #{contentType},</if>
+            <if test="contentJson != null">content_json = #{contentJson},</if>
+            <if test="sortOrder != null">sort_order = #{sortOrder},</if>
+            <if test="isSent != null">is_sent = #{isSent},</if>
+            <if test="realSendTime != null">real_send_time = #{realSendTime},</if>
+            <if test="createTime != null">create_time = #{createTime},</if>
+            <if test="updateTime != null">update_time = #{updateTime},</if>
+        </trim>
+        where user_node_rule_id = #{userNodeRuleId}
+    </update>
+
+    <delete id="deleteAiAddwxSopUserNodeRuleById" parameterType="Long">
+        delete from ai_addwx_sop_user_node_rule where user_node_rule_id = #{userNodeRuleId}
+    </delete>
+
+    <delete id="deleteAiAddwxSopUserNodeRuleByUserNodeId" parameterType="Long">
+        delete from ai_addwx_sop_user_node_rule where user_node_id = #{userNodeId}
+    </delete>
+
+    <insert id="batchInsertAiAddwxSopUserNodeRule">
+        insert into ai_addwx_sop_user_node_rule (user_node_rule_id,user_node_id, qw_external_id, qw_user_id, rule_name, send_time, send_day, msg_type, content_type, content_json, sort_order, real_send_time, create_time, update_time)
+        values
+        <foreach collection="list" item="item" separator=",">
+            (null,#{item.userNodeId}, #{item.qwExternalId}, #{item.qwUserId}, #{item.ruleName}, #{item.sendTime}, #{item.sendDay}, #{item.msgType}, #{item.contentType}, #{item.contentJson}, #{item.sortOrder}, #{item.realSendTime}, #{item.createTime}, #{item.updateTime})
+        </foreach>
+    </insert>
+
+</mapper>

+ 140 - 0
fs-service/src/main/resources/mapper/company/AiAddwxSopExecLogMapper.xml

@@ -0,0 +1,140 @@
+<?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.AiAddwxSopExecLogMapper">
+
+    <resultMap type="AiAddwxSopExecLog" id="AiAddwxSopExecLogResult">
+        <result property="logId"            column="log_id"             />
+        <result property="userWorkflowId"   column="user_workflow_id"   />
+        <result property="userNodeId"       column="user_node_id"       />
+        <result property="nodeKey"          column="node_key"           />
+        <result property="nodeName"         column="node_name"          />
+        <result property="nodeType"         column="node_type"          />
+        <result property="inputData"        column="input_data"         />
+        <result property="outputData"       column="output_data"        />
+        <result property="status"           column="status"             />
+        <result property="startTime"        column="start_time"         />
+        <result property="endTime"          column="end_time"           />
+        <result property="duration"         column="duration"           />
+        <result property="errorMessage"     column="error_message"      />
+        <result property="retryCount"       column="retry_count"        />
+        <result property="companyUserId"    column="company_user_id"    />
+        <result property="companyId"        column="company_id"         />
+        <result property="createTime"       column="create_time"        />
+    </resultMap>
+
+    <sql id="selectAiAddwxSopExecLogVo">
+        select log_id, user_workflow_id, user_node_id, node_key, node_name, node_type,
+               input_data, output_data, status, start_time, end_time, duration,
+               error_message, retry_count, company_user_id, company_id, create_time
+        from ai_addwx_sop_exec_log
+    </sql>
+
+    <select id="selectAiAddwxSopExecLogList" parameterType="AiAddwxSopExecLog" resultMap="AiAddwxSopExecLogResult">
+        <include refid="selectAiAddwxSopExecLogVo"/>
+        <where>
+            <if test="userWorkflowId != null"> and user_workflow_id = #{userWorkflowId}</if>
+            <if test="userNodeId != null"> and user_node_id = #{userNodeId}</if>
+            <if test="nodeKey != null and nodeKey != ''"> and node_key = #{nodeKey}</if>
+            <if test="nodeName != null and nodeName != ''"> and node_name like concat('%', #{nodeName}, '%')</if>
+            <if test="nodeType != null and nodeType != ''"> and node_type = #{nodeType}</if>
+            <if test="status != null"> and status = #{status}</if>
+            <if test="companyUserId != null"> and company_user_id = #{companyUserId}</if>
+            <if test="companyId != null"> and company_id = #{companyId}</if>
+            <if test="params.beginTime != null and params.beginTime != '' and params.endTime != null and params.endTime != ''">
+                and create_time between #{params.beginTime} and #{params.endTime}
+            </if>
+        </where>
+        order by create_time desc
+    </select>
+
+    <select id="selectAiAddwxSopExecLogById" parameterType="Long" resultMap="AiAddwxSopExecLogResult">
+        <include refid="selectAiAddwxSopExecLogVo"/>
+        where log_id = #{logId}
+    </select>
+
+    <select id="selectAiAddwxSopExecLogByWorkflowId" parameterType="Long" resultMap="AiAddwxSopExecLogResult">
+        <include refid="selectAiAddwxSopExecLogVo"/>
+        where user_workflow_id = #{userWorkflowId}
+        order by start_time asc
+    </select>
+
+    <select id="selectAiAddwxSopExecLogByNodeId" parameterType="Long" resultMap="AiAddwxSopExecLogResult">
+        <include refid="selectAiAddwxSopExecLogVo"/>
+        where user_node_id = #{userNodeId}
+        order by start_time asc
+    </select>
+
+    <insert id="insertAiAddwxSopExecLog" parameterType="AiAddwxSopExecLog" useGeneratedKeys="true" keyProperty="logId">
+        insert into ai_addwx_sop_exec_log
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="userWorkflowId != null">user_workflow_id,</if>
+            <if test="userNodeId != null">user_node_id,</if>
+            <if test="nodeKey != null and nodeKey != ''">node_key,</if>
+            <if test="nodeName != null and nodeName != ''">node_name,</if>
+            <if test="nodeType != null and nodeType != ''">node_type,</if>
+            <if test="inputData != null">input_data,</if>
+            <if test="outputData != null">output_data,</if>
+            <if test="status != null">status,</if>
+            <if test="startTime != null">start_time,</if>
+            <if test="endTime != null">end_time,</if>
+            <if test="duration != null">duration,</if>
+            <if test="errorMessage != null">error_message,</if>
+            <if test="retryCount != null">retry_count,</if>
+            <if test="companyUserId != null">company_user_id,</if>
+            <if test="companyId != null">company_id,</if>
+            <if test="createTime != null">create_time,</if>
+        </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="userWorkflowId != null">#{userWorkflowId},</if>
+            <if test="userNodeId != null">#{userNodeId},</if>
+            <if test="nodeKey != null and nodeKey != ''">#{nodeKey},</if>
+            <if test="nodeName != null and nodeName != ''">#{nodeName},</if>
+            <if test="nodeType != null and nodeType != ''">#{nodeType},</if>
+            <if test="inputData != null">#{inputData},</if>
+            <if test="outputData != null">#{outputData},</if>
+            <if test="status != null">#{status},</if>
+            <if test="startTime != null">#{startTime},</if>
+            <if test="endTime != null">#{endTime},</if>
+            <if test="duration != null">#{duration},</if>
+            <if test="errorMessage != null">#{errorMessage},</if>
+            <if test="retryCount != null">#{retryCount},</if>
+            <if test="companyUserId != null">#{companyUserId},</if>
+            <if test="companyId != null">#{companyId},</if>
+            <if test="createTime != null">#{createTime},</if>
+        </trim>
+    </insert>
+
+    <update id="updateAiAddwxSopExecLog" parameterType="AiAddwxSopExecLog">
+        update ai_addwx_sop_exec_log
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="userWorkflowId != null">user_workflow_id = #{userWorkflowId},</if>
+            <if test="userNodeId != null">user_node_id = #{userNodeId},</if>
+            <if test="nodeKey != null and nodeKey != ''">node_key = #{nodeKey},</if>
+            <if test="nodeName != null and nodeName != ''">node_name = #{nodeName},</if>
+            <if test="nodeType != null and nodeType != ''">node_type = #{nodeType},</if>
+            <if test="inputData != null">input_data = #{inputData},</if>
+            <if test="outputData != null">output_data = #{outputData},</if>
+            <if test="status != null">status = #{status},</if>
+            <if test="startTime != null">start_time = #{startTime},</if>
+            <if test="endTime != null">end_time = #{endTime},</if>
+            <if test="duration != null">duration = #{duration},</if>
+            <if test="errorMessage != null">error_message = #{errorMessage},</if>
+            <if test="retryCount != null">retry_count = #{retryCount},</if>
+            <if test="companyUserId != null">company_user_id = #{companyUserId},</if>
+            <if test="companyId != null">company_id = #{companyId},</if>
+            <if test="createTime != null">create_time = #{createTime},</if>
+        </trim>
+        where log_id = #{logId}
+    </update>
+
+    <delete id="deleteAiAddwxSopExecLogById" parameterType="Long">
+        delete from ai_addwx_sop_exec_log where log_id = #{logId}
+    </delete>
+
+    <delete id="deleteAiAddwxSopExecLogByWorkflowId" parameterType="Long">
+        delete from ai_addwx_sop_exec_log where user_workflow_id = #{userWorkflowId}
+    </delete>
+
+</mapper>

+ 103 - 0
fs-service/src/main/resources/mapper/company/AiAddwxSopTemplateMapper.xml

@@ -0,0 +1,103 @@
+<?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.AiAddwxSopTemplateMapper">
+
+    <resultMap type="AiAddwxSopTemplate" id="AiAddwxSopTemplateResult">
+        <result property="templateId"      column="template_id"      />
+        <result property="templateName"    column="template_name"    />
+        <result property="templateDesc"    column="template_desc"    />
+        <result property="status"          column="status"           />
+        <result property="version"         column="version"          />
+        <result property="companyId"       column="company_id"       />
+        <result property="delFlag"         column="del_flag"         />
+        <result property="createBy"        column="create_by"        />
+        <result property="createTime"      column="create_time"      />
+        <result property="updateBy"        column="update_by"        />
+        <result property="updateTime"      column="update_time"      />
+        <result property="remark"          column="remark"           />
+    </resultMap>
+
+    <sql id="selectAiAddwxSopTemplateVo">
+        select template_id, template_name, template_desc, status, version, company_id, del_flag,
+               create_by, create_time, update_by, update_time, remark
+        from ai_addwx_sop_template
+    </sql>
+
+    <select id="selectAiAddwxSopTemplateList" parameterType="AiAddwxSopTemplate" resultMap="AiAddwxSopTemplateResult">
+        <include refid="selectAiAddwxSopTemplateVo"/>
+        <where>
+            <if test="templateName != null and templateName != ''"> and template_name like concat('%', #{templateName}, '%')</if>
+            <if test="templateDesc != null and templateDesc != ''"> and template_desc like concat('%', #{templateDesc}, '%')</if>
+            <if test="status != null"> and status = #{status}</if>
+            <if test="version != null"> and version = #{version}</if>
+            <if test="companyId != null"> and company_id = #{companyId}</if>
+            <if test="delFlag != null"> and del_flag = #{delFlag}</if>
+        </where>
+        order by create_time desc
+    </select>
+
+    <select id="selectAiAddwxSopTemplateById" parameterType="Long" resultMap="AiAddwxSopTemplateResult">
+        <include refid="selectAiAddwxSopTemplateVo"/>
+        where template_id = #{templateId}
+    </select>
+
+    <insert id="insertAiAddwxSopTemplate" parameterType="AiAddwxSopTemplate" useGeneratedKeys="true" keyProperty="templateId">
+        insert into ai_addwx_sop_template
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="templateName != null and templateName != ''">template_name,</if>
+            <if test="templateDesc != null">template_desc,</if>
+            <if test="status != null">status,</if>
+            <if test="version != null">version,</if>
+            <if test="companyId != null">company_id,</if>
+            <if test="delFlag != null">del_flag,</if>
+            <if test="createBy != null and createBy != ''">create_by,</if>
+            <if test="createTime != null">create_time,</if>
+            <if test="updateBy != null and updateBy != ''">update_by,</if>
+            <if test="updateTime != null">update_time,</if>
+            <if test="remark != null">remark,</if>
+        </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="templateName != null and templateName != ''">#{templateName},</if>
+            <if test="templateDesc != null">#{templateDesc},</if>
+            <if test="status != null">#{status},</if>
+            <if test="version != null">#{version},</if>
+            <if test="companyId != null">#{companyId},</if>
+            <if test="delFlag != null">#{delFlag},</if>
+            <if test="createBy != null and createBy != ''">#{createBy},</if>
+            <if test="createTime != null">#{createTime},</if>
+            <if test="updateBy != null and updateBy != ''">#{updateBy},</if>
+            <if test="updateTime != null">#{updateTime},</if>
+            <if test="remark != null">#{remark},</if>
+        </trim>
+    </insert>
+
+    <update id="updateAiAddwxSopTemplate" parameterType="AiAddwxSopTemplate">
+        update ai_addwx_sop_template
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="templateName != null and templateName != ''">template_name = #{templateName},</if>
+            <if test="templateDesc != null">template_desc = #{templateDesc},</if>
+            <if test="status != null">status = #{status},</if>
+            <if test="version != null">version = #{version},</if>
+            <if test="companyId != null">company_id = #{companyId},</if>
+            <if test="delFlag != null">del_flag = #{delFlag},</if>
+            <if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if>
+            <if test="updateTime != null">update_time = #{updateTime},</if>
+            <if test="remark != null">remark = #{remark},</if>
+        </trim>
+        where template_id = #{templateId}
+    </update>
+
+    <delete id="deleteAiAddwxSopTemplateById" parameterType="Long">
+        delete from ai_addwx_sop_template where template_id = #{templateId}
+    </delete>
+
+    <delete id="deleteAiAddwxSopTemplateByIds" parameterType="String">
+        delete from ai_addwx_sop_template where template_id in
+        <foreach item="templateId" collection="array" open="(" separator="," close=")">
+            #{templateId}
+        </foreach>
+    </delete>
+
+</mapper>

+ 137 - 0
fs-service/src/main/resources/mapper/company/AiAddwxSopTemplateNodeMapper.xml

@@ -0,0 +1,137 @@
+<?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.AiAddwxSopTemplateNodeMapper">
+
+    <resultMap type="AiAddwxSopTemplateNode" id="AiAddwxSopTemplateNodeResult">
+        <result property="nodeId"       column="node_id"       />
+        <result property="templateId"   column="template_id"   />
+        <result property="nodeKey"      column="node_key"      />
+        <result property="nodeName"     column="node_name"     />
+        <result property="nodeType"     column="node_type"     />
+        <result property="prevNodeId"   column="prev_node_id"  />
+        <result property="nextNodeId"   column="next_node_id"  />
+        <result property="nodeIcon"     column="node_icon"     />
+        <result property="nodeColor"    column="node_color"    />
+        <result property="nodeConfig"   column="node_config"   />
+        <result property="isValidReply" column="is_valid_reply" />
+        <result property="sortOrder"    column="sort_order"    />
+        <result property="createTime"   column="create_time"   />
+        <result property="updateTime"   column="update_time"   />
+    </resultMap>
+
+    <sql id="selectAiAddwxSopTemplateNodeVo">
+        select node_id, template_id, node_key, node_name, node_type, prev_node_id, next_node_id, node_icon, node_color,
+               node_config, is_valid_reply, sort_order, create_time, update_time
+        from ai_addwx_sop_template_node
+    </sql>
+
+    <select id="selectAiAddwxSopTemplateNodeList" parameterType="AiAddwxSopTemplateNode" resultMap="AiAddwxSopTemplateNodeResult">
+        <include refid="selectAiAddwxSopTemplateNodeVo"/>
+        <where>
+            <if test="templateId != null"> and template_id = #{templateId}</if>
+            <if test="nodeKey != null and nodeKey != ''"> and node_key = #{nodeKey}</if>
+            <if test="nodeName != null and nodeName != ''"> and node_name like concat('%', #{nodeName}, '%')</if>
+            <if test="nodeType != null and nodeType != ''"> and node_type = #{nodeType}</if>
+        </where>
+        order by sort_order asc, create_time asc
+    </select>
+
+    <select id="selectAiAddwxSopTemplateNodeById" parameterType="Long" resultMap="AiAddwxSopTemplateNodeResult">
+        <include refid="selectAiAddwxSopTemplateNodeVo"/>
+        where node_id = #{nodeId}
+    </select>
+
+    <select id="selectAiAddwxSopTemplateNodeByTemplateId" parameterType="Long" resultMap="AiAddwxSopTemplateNodeResult">
+        <include refid="selectAiAddwxSopTemplateNodeVo"/>
+        where template_id = #{templateId}
+        order by sort_order asc, create_time asc
+    </select>
+
+    <insert id="insertAiAddwxSopTemplateNode" parameterType="AiAddwxSopTemplateNode" useGeneratedKeys="true" keyProperty="nodeId">
+        insert into ai_addwx_sop_template_node
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="templateId != null">template_id,</if>
+            <if test="nodeKey != null and nodeKey != ''">node_key,</if>
+            <if test="nodeName != null and nodeName != ''">node_name,</if>
+            <if test="nodeType != null and nodeType != ''">node_type,</if>
+            <if test="prevNodeId != null">prev_node_id,</if>
+            <if test="nextNodeId != null">next_node_id,</if>
+            <if test="nodeIcon != null">node_icon,</if>
+            <if test="nodeColor != null">node_color,</if>
+            <if test="nodeConfig != null">node_config,</if>
+            <if test="isValidReply != null">is_valid_reply,</if>
+            <if test="sortOrder != null">sort_order,</if>
+            <if test="createTime != null">create_time,</if>
+            <if test="updateTime != null">update_time,</if>
+        </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="templateId != null">#{templateId},</if>
+            <if test="nodeKey != null and nodeKey != ''">#{nodeKey},</if>
+            <if test="nodeName != null and nodeName != ''">#{nodeName},</if>
+            <if test="nodeType != null and nodeType != ''">#{nodeType},</if>
+            <if test="prevNodeId != null">#{prevNodeId},</if>
+            <if test="nextNodeId != null">#{nextNodeId},</if>
+            <if test="nodeIcon != null">#{nodeIcon},</if>
+            <if test="nodeColor != null">#{nodeColor},</if>
+            <if test="nodeConfig != null">#{nodeConfig},</if>
+            <if test="isValidReply != null">#{isValidReply},</if>
+            <if test="sortOrder != null">#{sortOrder},</if>
+            <if test="createTime != null">#{createTime},</if>
+            <if test="updateTime != null">#{updateTime},</if>
+        </trim>
+    </insert>
+
+    <update id="updateAiAddwxSopTemplateNode" parameterType="AiAddwxSopTemplateNode">
+        update ai_addwx_sop_template_node
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="templateId != null">template_id = #{templateId},</if>
+            <if test="nodeKey != null and nodeKey != ''">node_key = #{nodeKey},</if>
+            <if test="nodeName != null and nodeName != ''">node_name = #{nodeName},</if>
+            <if test="nodeType != null and nodeType != ''">node_type = #{nodeType},</if>
+            <if test="prevNodeId != null">prev_node_id = #{prevNodeId},</if>
+            <if test="nextNodeId != null">next_node_id = #{nextNodeId},</if>
+            <if test="nodeIcon != null">node_icon = #{nodeIcon},</if>
+            <if test="nodeColor != null">node_color = #{nodeColor},</if>
+            <if test="nodeConfig != null">node_config = #{nodeConfig},</if>
+            <if test="isValidReply != null">is_valid_reply = #{isValidReply},</if>
+            <if test="sortOrder != null">sort_order = #{sortOrder},</if>
+            <if test="updateTime != null">update_time = #{updateTime},</if>
+        </trim>
+        where node_id = #{nodeId}
+    </update>
+
+    <delete id="deleteAiAddwxSopTemplateNodeById" parameterType="Long">
+        delete from ai_addwx_sop_template_node where node_id = #{nodeId}
+    </delete>
+
+    <delete id="deleteAiAddwxSopTemplateNodeByTemplateId" parameterType="Long">
+        delete from ai_addwx_sop_template_node where template_id = #{templateId}
+    </delete>
+
+    <insert id="batchInsertAiAddwxSopTemplateNode" parameterType="java.util.List">
+        insert into ai_addwx_sop_template_node (
+            template_id, node_key, node_name, node_type, prev_node_id, next_node_id, node_icon, node_color,
+            node_config, is_valid_reply, sort_order, create_time, update_time
+        ) values
+        <foreach collection="list" item="item" separator=",">
+            (
+                #{item.templateId},
+                #{item.nodeKey},
+                #{item.nodeName},
+                #{item.nodeType},
+                #{item.prevNodeId},
+                #{item.nextNodeId},
+                #{item.nodeIcon},
+                #{item.nodeColor},
+                #{item.nodeConfig},
+                #{item.isValidReply},
+                #{item.sortOrder},
+                #{item.createTime},
+                #{item.updateTime}
+            )
+        </foreach>
+    </insert>
+
+</mapper>

+ 178 - 0
fs-service/src/main/resources/mapper/company/AiAddwxSopUserNodeMapper.xml

@@ -0,0 +1,178 @@
+<?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.AiAddwxSopUserNodeMapper">
+
+    <resultMap type="AiAddwxSopUserNode" id="AiAddwxSopUserNodeResult">
+        <result property="userNodeId"       column="user_node_id"       />
+        <result property="userWorkflowId"   column="user_workflow_id"   />
+        <result property="qwExternalId"    column="qw_external_id"    />
+        <result property="qwUserId"        column="qw_user_id"        />
+        <result property="templateNodeId"   column="template_node_id"   />
+        <result property="nodeKey"          column="node_key"           />
+        <result property="nodeName"         column="node_name"          />
+        <result property="nodeType"         column="node_type"          />
+        <result property="prevNodeId"       column="prev_node_id"       />
+        <result property="nextNodeId"       column="next_node_id"       />
+        <result property="nextCondition"    column="next_condition"     />
+        <result property="childNodes"       column="child_nodes"        />
+        <result property="prompt"           column="prompt"             />
+        <result property="nodeConfig"       column="node_config"        />
+        <result property="isValidReply"    column="is_valid_reply"     />
+        <result property="status"           column="status"             />
+        <result property="sortOrder"        column="sort_order"         />
+        <result property="startTime"        column="start_time"         />
+        <result property="endTime"          column="end_time"           />
+        <result property="createTime"       column="create_time"        />
+        <result property="updateTime"       column="update_time"        />
+    </resultMap>
+
+    <sql id="selectAiAddwxSopUserNodeVo">
+        select user_node_id, user_workflow_id, qw_external_id, qw_user_id, template_node_id, node_key, node_name, node_type,
+               prev_node_id, next_node_id, next_condition, child_nodes, prompt, node_config,
+               is_valid_reply, status, sort_order, start_time, end_time, create_time, update_time
+        from ai_addwx_sop_user_node
+    </sql>
+
+    <select id="selectAiAddwxSopUserNodeList" parameterType="AiAddwxSopUserNode" resultMap="AiAddwxSopUserNodeResult">
+        <include refid="selectAiAddwxSopUserNodeVo"/>
+        <where>
+            <if test="userWorkflowId != null"> and user_workflow_id = #{userWorkflowId}</if>
+            <if test="templateNodeId != null"> and template_node_id = #{templateNodeId}</if>
+            <if test="nodeKey != null and nodeKey != ''"> and node_key = #{nodeKey}</if>
+            <if test="nodeName != null and nodeName != ''"> and node_name like concat('%', #{nodeName}, '%')</if>
+            <if test="nodeType != null and nodeType != ''"> and node_type = #{nodeType}</if>
+            <if test="prevNodeId != null"> and prev_node_id = #{prevNodeId}</if>
+            <if test="nextNodeId != null"> and next_node_id = #{nextNodeId}</if>
+            <if test="status != null"> and status = #{status}</if>
+        </where>
+        order by sort_order asc, create_time asc
+    </select>
+
+    <select id="selectAiAddwxSopUserNodeById" parameterType="Long" resultMap="AiAddwxSopUserNodeResult">
+        <include refid="selectAiAddwxSopUserNodeVo"/>
+        where user_node_id = #{userNodeId}
+    </select>
+
+    <select id="selectAiAddwxSopUserNodeByWorkflowId" parameterType="Long" resultMap="AiAddwxSopUserNodeResult">
+        <include refid="selectAiAddwxSopUserNodeVo"/>
+        where user_workflow_id = #{userWorkflowId}
+        order by sort_order asc, create_time asc
+    </select>
+
+    <insert id="insertAiAddwxSopUserNode" parameterType="AiAddwxSopUserNode" useGeneratedKeys="true" keyProperty="userNodeId">
+        insert into ai_addwx_sop_user_node
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="userWorkflowId != null">user_workflow_id,</if>
+            <if test="qwExternalId != null">qw_external_id,</if>
+            <if test="qwUserId != null">qw_user_id,</if>
+            <if test="templateNodeId != null">template_node_id,</if>
+            <if test="nodeKey != null and nodeKey != ''">node_key,</if>
+            <if test="nodeName != null and nodeName != ''">node_name,</if>
+            <if test="nodeType != null and nodeType != ''">node_type,</if>
+            <if test="prevNodeId != null">prev_node_id,</if>
+            <if test="nextNodeId != null">next_node_id,</if>
+            <if test="nextCondition != null">next_condition,</if>
+            <if test="childNodes != null">child_nodes,</if>
+            <if test="prompt != null">prompt,</if>
+            <if test="nodeConfig != null">node_config,</if>
+            <if test="isValidReply != null">is_valid_reply,</if>
+            <if test="status != null">status,</if>
+            <if test="sortOrder != null">sort_order,</if>
+            <if test="startTime != null">start_time,</if>
+            <if test="endTime != null">end_time,</if>
+            <if test="createTime != null">create_time,</if>
+            <if test="updateTime != null">update_time,</if>
+        </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="userWorkflowId != null">#{userWorkflowId},</if>
+            <if test="qwExternalId != null">#{qwExternalId},</if>
+            <if test="qwUserId != null">#{qwUserId},</if>
+            <if test="templateNodeId != null">#{templateNodeId},</if>
+            <if test="nodeKey != null and nodeKey != ''">#{nodeKey},</if>
+            <if test="nodeName != null and nodeName != ''">#{nodeName},</if>
+            <if test="nodeType != null and nodeType != ''">#{nodeType},</if>
+            <if test="prevNodeId != null">#{prevNodeId},</if>
+            <if test="nextNodeId != null">#{nextNodeId},</if>
+            <if test="nextCondition != null">#{nextCondition},</if>
+            <if test="childNodes != null">#{childNodes},</if>
+            <if test="prompt != null">#{prompt},</if>
+            <if test="nodeConfig != null">#{nodeConfig},</if>
+            <if test="isValidReply != null">#{isValidReply},</if>
+            <if test="status != null">#{status},</if>
+            <if test="sortOrder != null">#{sortOrder},</if>
+            <if test="startTime != null">#{startTime},</if>
+            <if test="endTime != null">#{endTime},</if>
+            <if test="createTime != null">#{createTime},</if>
+            <if test="updateTime != null">#{updateTime},</if>
+        </trim>
+    </insert>
+
+    <update id="updateAiAddwxSopUserNode" parameterType="AiAddwxSopUserNode">
+        update ai_addwx_sop_user_node
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="userWorkflowId != null">user_workflow_id = #{userWorkflowId},</if>
+            <if test="qwExternalId != null">qw_external_id = #{qwExternalId},</if>
+            <if test="qwUserId != null">qw_user_id = #{qwUserId},</if>
+            <if test="templateNodeId != null">template_node_id = #{templateNodeId},</if>
+            <if test="nodeKey != null and nodeKey != ''">node_key = #{nodeKey},</if>
+            <if test="nodeName != null and nodeName != ''">node_name = #{nodeName},</if>
+            <if test="nodeType != null and nodeType != ''">node_type = #{nodeType},</if>
+            <if test="prevNodeId != null">prev_node_id = #{prevNodeId},</if>
+            <if test="nextNodeId != null">next_node_id = #{nextNodeId},</if>
+            <if test="nextCondition != null">next_condition = #{nextCondition},</if>
+            <if test="childNodes != null">child_nodes = #{childNodes},</if>
+            <if test="prompt != null">prompt = #{prompt},</if>
+            <if test="nodeConfig != null">node_config = #{nodeConfig},</if>
+            <if test="isValidReply != null">is_valid_reply = #{isValidReply},</if>
+            <if test="status != null">status = #{status},</if>
+            <if test="sortOrder != null">sort_order = #{sortOrder},</if>
+            <if test="startTime != null">start_time = #{startTime},</if>
+            <if test="endTime != null">end_time = #{endTime},</if>
+            <if test="updateTime != null">update_time = #{updateTime},</if>
+        </trim>
+        where user_node_id = #{userNodeId}
+    </update>
+
+    <delete id="deleteAiAddwxSopUserNodeById" parameterType="Long">
+        delete from ai_addwx_sop_user_node where user_node_id = #{userNodeId}
+    </delete>
+
+    <delete id="deleteAiAddwxSopUserNodeByWorkflowId" parameterType="Long">
+        delete from ai_addwx_sop_user_node where user_workflow_id = #{userWorkflowId}
+    </delete>
+
+    <insert id="batchInsertAiAddwxSopUserNode" parameterType="java.util.List">
+        insert into ai_addwx_sop_user_node (
+            user_workflow_id, qw_external_id, qw_user_id, template_node_id, node_key, node_name, node_type,
+            prev_node_id, next_node_id, next_condition, child_nodes, prompt, node_config,
+            is_valid_reply, status, sort_order, start_time, end_time, create_time, update_time
+        ) values
+        <foreach collection="list" item="item" separator=",">
+            (
+                #{item.userWorkflowId},
+                #{item.qwExternalId},
+                #{item.qwUserId},
+                #{item.templateNodeId},
+                #{item.nodeKey},
+                #{item.nodeName},
+                #{item.nodeType},
+                #{item.prevNodeId},
+                #{item.nextNodeId},
+                #{item.nextCondition},
+                #{item.childNodes},
+                #{item.prompt},
+                #{item.nodeConfig},
+                #{item.isValidReply},
+                #{item.status},
+                #{item.sortOrder},
+                #{item.startTime},
+                #{item.endTime},
+                #{item.createTime},
+                #{item.updateTime}
+            )
+        </foreach>
+    </insert>
+
+</mapper>

+ 136 - 0
fs-service/src/main/resources/mapper/company/AiAddwxSopUserWorkflowMapper.xml

@@ -0,0 +1,136 @@
+<?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.AiAddwxSopUserWorkflowMapper">
+
+    <resultMap type="AiAddwxSopUserWorkflow" id="AiAddwxSopUserWorkflowResult">
+        <result property="userWorkflowId"   column="user_workflow_id"   />
+        <result property="templateId"       column="template_id"        />
+        <result property="companyUserId"    column="company_user_id"    />
+        <result property="qwExternalId"    column="qw_external_id"     />
+        <result property="qwUserId"        column="qw_user_id"         />
+        <result property="companyId"        column="company_id"         />
+        <result property="status"           column="status"             />
+        <result property="currentNodeId"    column="current_node_id"    />
+        <result property="currentNodeKey"   column="current_node_key"   />
+        <result property="currentNodeType" column="current_node_type" />
+<!--        <result property="businessKey"      column="business_key"       />-->
+        <result property="startTime"        column="start_time"         />
+        <result property="endTime"          column="end_time"           />
+        <result property="delFlag"          column="del_flag"           />
+        <result property="createBy"         column="create_by"          />
+        <result property="createTime"       column="create_time"        />
+        <result property="updateBy"         column="update_by"          />
+        <result property="updateTime"       column="update_time"        />
+        <result property="remark"           column="remark"             />
+    </resultMap>
+
+    <sql id="selectAiAddwxSopUserWorkflowVo">
+        select user_workflow_id, template_id, company_user_id, qw_external_id, qw_user_id, company_id, status,
+               current_node_id, current_node_key, current_node_type, business_key, start_time, end_time,
+               del_flag, create_by, create_time, update_by, update_time, remark
+        from ai_addwx_sop_user_workflow
+    </sql>
+
+    <select id="selectAiAddwxSopUserWorkflowList" parameterType="AiAddwxSopUserWorkflow" resultMap="AiAddwxSopUserWorkflowResult">
+        <include refid="selectAiAddwxSopUserWorkflowVo"/>
+        <where>
+            <if test="templateId != null"> and template_id = #{templateId}</if>
+            <if test="companyUserId != null"> and company_user_id = #{companyUserId}</if>
+            <if test="companyId != null"> and company_id = #{companyId}</if>
+            <if test="status != null"> and status = #{status}</if>
+            <if test="currentNodeKey != null and currentNodeKey != ''"> and current_node_key = #{currentNodeKey}</if>
+            <!--           <if test="businessKey != null"> and business_key = #{businessKey}</if>-->
+                       <if test="delFlag != null"> and del_flag = #{delFlag}</if>
+           <!--            <if test="params.beginTime != null and params.beginTime != '' and params.endTime != null and params.endTime != ''">-->
+<!--                and start_time between #{params.beginTime} and #{params.endTime}-->
+<!--            </if>-->
+        </where>
+        order by create_time desc
+    </select>
+
+    <select id="selectAiAddwxSopUserWorkflowById" parameterType="Long" resultMap="AiAddwxSopUserWorkflowResult">
+        <include refid="selectAiAddwxSopUserWorkflowVo"/>
+        where user_workflow_id = #{userWorkflowId}
+    </select>
+
+    <insert id="insertAiAddwxSopUserWorkflow" parameterType="AiAddwxSopUserWorkflow" useGeneratedKeys="true" keyProperty="userWorkflowId">
+        insert into ai_addwx_sop_user_workflow
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="templateId != null">template_id,</if>
+            <if test="companyUserId != null">company_user_id,</if>
+            <if test="qwExternalId != null">qw_external_id,</if>
+            <if test="qwUserId != null">qw_user_id,</if>
+            <if test="companyId != null">company_id,</if>
+            <if test="status != null">status,</if>
+            <if test="currentNodeId != null">current_node_id,</if>
+            <if test="currentNodeKey != null">current_node_key,</if>
+            <if test="currentNodeType != null">current_node_type,</if>
+<!--             <if test="businessKey != null">business_key,</if> -->
+            <if test="startTime != null">start_time,</if>
+            <if test="endTime != null">end_time,</if>
+            <if test="delFlag != null">del_flag,</if>
+            <if test="createBy != null and createBy != ''">create_by,</if>
+            <if test="createTime != null">create_time,</if>
+            <if test="updateBy != null and updateBy != ''">update_by,</if>
+            <if test="updateTime != null">update_time,</if>
+            <if test="remark != null">remark,</if>
+        </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="templateId != null">#{templateId},</if>
+            <if test="companyUserId != null">#{companyUserId},</if>
+            <if test="qwExternalId != null">#{qwExternalId},</if>
+            <if test="qwUserId != null">#{qwUserId},</if>
+            <if test="companyId != null">#{companyId},</if>
+            <if test="status != null">#{status},</if>
+            <if test="currentNodeId != null">#{currentNodeId},</if>
+            <if test="currentNodeKey != null">#{currentNodeKey},</if>
+            <if test="currentNodeType != null">#{currentNodeType},</if>
+<!--             <if test="businessKey != null">#{businessKey},</if> -->
+            <if test="startTime != null">#{startTime},</if>
+            <if test="endTime != null">#{endTime},</if>
+            <if test="delFlag != null">#{delFlag},</if>
+            <if test="createBy != null and createBy != ''">#{createBy},</if>
+            <if test="createTime != null">#{createTime},</if>
+            <if test="updateBy != null and updateBy != ''">#{updateBy},</if>
+            <if test="updateTime != null">#{updateTime},</if>
+            <if test="remark != null">#{remark},</if>
+        </trim>
+    </insert>
+
+    <update id="updateAiAddwxSopUserWorkflow" parameterType="AiAddwxSopUserWorkflow">
+        update ai_addwx_sop_user_workflow
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="templateId != null">template_id = #{templateId},</if>
+            <if test="companyUserId != null">company_user_id = #{companyUserId},</if>
+            <if test="qwExternalId != null">qw_external_id = #{qwExternalId},</if>
+            <if test="qwUserId != null">qw_user_id = #{qwUserId},</if>
+            <if test="companyId != null">company_id = #{companyId},</if>
+            <if test="status != null">status = #{status},</if>
+            <if test="currentNodeId != null">current_node_id = #{currentNodeId},</if>
+            <if test="currentNodeKey != null">current_node_key = #{currentNodeKey},</if>
+            <if test="currentNodeType != null">current_node_type = #{currentNodeType},</if>
+<!--             <if test="businessKey != null">business_key = #{businessKey},</if> -->
+            <if test="startTime != null">start_time = #{startTime},</if>
+            <if test="endTime != null">end_time = #{endTime},</if>
+            <if test="delFlag != null">del_flag = #{delFlag},</if>
+            <if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if>
+            <if test="updateTime != null">update_time = #{updateTime},</if>
+            <if test="remark != null">remark = #{remark},</if>
+        </trim>
+        where user_workflow_id = #{userWorkflowId}
+    </update>
+
+    <delete id="deleteAiAddwxSopUserWorkflowById" parameterType="Long">
+        delete from ai_addwx_sop_user_workflow where user_workflow_id = #{userWorkflowId}
+    </delete>
+
+    <delete id="deleteAiAddwxSopUserWorkflowByIds" parameterType="String">
+        delete from ai_addwx_sop_user_workflow where user_workflow_id in
+        <foreach item="userWorkflowId" collection="userWorkflowIds" open="(" separator="," close=")">
+            #{userWorkflowId}
+        </foreach>
+    </delete>
+
+</mapper>

+ 41 - 0
fs-service/src/main/resources/mapper/fastGpt/FastGptChatSessionMapper.xml

@@ -25,12 +25,37 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="qwUserId"    column="qw_user_id"    />
         <result property="lastTime"    column="last_time"    />
         <result property="isReply"    column="is_reply"    />
+        <result property="userInfo"    column="user_info"    />
     </resultMap>
 
     <sql id="selectFastGptChatSessionVo">
         select session_id,remind_time,is_reply,last_time,remind_status,remind_count,qw_ext_id,qw_user_id,chat_id,is_artificial, user_id, kf_id, create_time, update_time, status, company_id, is_look, user_type, nick_name, avatar from fastgpt_chat_session
     </sql>
 
+    <sql id="Base_Column_List">
+        session_id,
+    chat_id,
+    user_id,
+    qw_ext_id,
+    qw_user_id,
+    kf_id,
+    status,
+    company_id,
+    is_look,
+    user_type,
+    nick_name,
+    avatar,
+    is_artificial,
+    create_time,
+    update_time,
+    remind_time,
+    remind_status,
+    remind_count,
+    last_time,
+    is_reply,
+    user_info
+    </sql>
+
     <select id="selectFastGptChatSessionList" parameterType="FastGptChatSession" resultMap="FastGptChatSessionResult">
         <include refid="selectFastGptChatSessionVo"/>
         <where>
@@ -50,6 +75,22 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <include refid="selectFastGptChatSessionVo"/>
         where session_id = #{sessionId}
     </select>
+    <select id="selectSessionByTriple" resultType="com.fs.fastGpt.domain.FastGptChatSession">
+        SELECT <include refid="Base_Column_List"/>
+        FROM fastgpt_chat_session
+        <where>
+            <if test="companyId != null and companyId != ''">
+                AND company_id = #{companyId}
+            </if>
+            <if test="qwExternalId != null and qwExternalId != ''">
+                AND qw_ext_id = #{qwExternalId}
+            </if>
+            <if test="qwUserId != null and qwUserId != ''">
+                AND qw_user_id = #{qwUserId}
+            </if>
+        </where>
+        ORDER BY create_time DESC
+    </select>
 
     <insert id="insertFastGptChatSession" parameterType="FastGptChatSession" useGeneratedKeys="true" keyProperty="sessionId">
         insert into fastgpt_chat_session

+ 6 - 0
fs-service/src/main/resources/mapper/qw/QwTagMapper.xml

@@ -64,6 +64,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             </foreach>
         </if>
     </select>
+    <select id="selectIdsByTagIds" resultType="java.lang.Long">
+        SELECT id FROM qw_tag WHERE tag_id IN
+        <foreach item="tagId" collection="tagIds" open="(" separator="," close=")">
+            #{tagId}
+        </foreach>
+    </select>
 
 
     <insert id="insertQwTag" parameterType="QwTag">

+ 50 - 0
fs-service/src/main/resources/mapper/qw/QwUserMapper.xml

@@ -386,5 +386,55 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         </where>
         GROUP BY q.id
     </select>
+    <select id="selectQwUserByIdsPrimary" resultType="com.fs.qw.domain.QwUser">
+        SELECT
+        `id`,
+        `qw_user_id`,
+        `qw_user_name`,
+        `avatar`,
+        `department`,
+        `openid`,
+        `company_id`,
+        `company_user_id`,
+        `corp_id`,
+        `status`,
+        `is_del`,
+        `welcome_text`,
+        `welcome_image`,
+        `is_send_msg`,
+        `app_key`,
+        `config_id`,
+        `contact_way`,
+        `qw_hook_id`,
+        `qw_hook_mac`,
+        `fastGpt_role_id`,
+        `login_status`,
+        `tool_status`,
+        `login_code_url`,
+        `fast_gpt_role_id`,
+        `create_time`,
+        `create_by`,
+        `update_by`,
+        `update_time`,
+        `remark`,
+        `version`,
+        `unionid`,
+        `vid`,
+        `uid`,
+        `ipad_status`,
+        `server_id`,
+        `server_status`,
+        `send_msg_type`,
+        `is_auto`,
+        `video_get_status`,
+        `ai_status`,
+        `qw_open_user_id`
+        FROM `qw_user`
+        WHERE `is_del` = 0
+        and id in
+        <foreach collection="collect" open="(" close=")" separator="," item="item">
+            #{item}
+        </foreach>
+    </select>
 
 </mapper>

+ 45 - 14
fs-user-app/src/main/java/com/fs/app/controller/AppLoginController.java

@@ -1178,6 +1178,14 @@ public class AppLoginController extends AppBaseController{
             return captchaError;
         }
 
+        String redisCode = redisCache.getCacheObject("sms:code:" + phone);
+        if (StringUtils.isEmpty(redisCode)) {
+            return R.error("验证码已过期,请重新发送");
+        }
+        if (!redisCode.equals(code)) {
+            return R.error("验证码错误");
+        }
+
         String encryptPhone = encryptPhone(phone);
         List<FsUser> user = userService.selectFsUserListByPhone(encryptPhone);
         if (CollectionUtil.isEmpty(user)) {
@@ -1186,28 +1194,51 @@ public class AppLoginController extends AppBaseController{
         if (CollectionUtil.isEmpty(user)) {
             user = userService.selectFsUserListByPhone(phone);
         }
+
+        FsUser loginUser;
         if (CollectionUtil.isEmpty(user)) {
-            return R.error("此电话号码未绑定用户");
+            loginUser = createUserByPhoneLogin(map, phone);
+            if (loginUser == null) {
+                return R.error("注册失败");
+            }
+        } else {
+            if (user.size() > 1) {
+                user.removeIf(fsUser -> StringUtils.isEmpty(fsUser.getHistoryApp()));
+            }
+            loginUser = user.get(0);
         }
-        if (user.size() > 1) {
-            user.removeIf(fsUser -> StringUtils.isEmpty(fsUser.getHistoryApp()));
+        user.add(loginUser);
+        redisCache.deleteObject(Constants.APP_LOGIN_CAPTCHA_CODE_KEY + captchaUuid);
+        if (ObjectUtil.isNotNull(map.get("jpushId"))) {
+            updateExistingUserJpushId(loginUser, map.get("jpushId"));
         }
-
-        String redisCode = redisCache.getCacheObject("sms:code:" + phone);
-        if (StringUtils.isEmpty(redisCode)) {
-            return R.error("验证码已过期,请重新发送");
+        if (ObjectUtil.isNotNull(map.get("loginDevice")) || ObjectUtil.isNotNull(map.get("source")) ) {
+            updateLoginDevice(loginUser.getUserId(), map.get("loginDevice"), map.get("source"));
         }
-        if (!redisCode.equals(code)) {
-            return R.error("验证码错误");
-        }
-
-        redisCache.deleteObject(Constants.APP_LOGIN_CAPTCHA_CODE_KEY + captchaUuid);
-        updateExistingUserJpushId(user.get(0), map.get("jpushId"));
-        updateLoginDevice(user.get(0).getUserId(), map.get("loginDevice"), map.get("source"));
 
         return generateTokenAndReturn(user.get(0));
     }
 
+    private FsUser createUserByPhoneLogin(Map<String, String> map, String phone) {
+        FsUser user = new FsUser();
+        user.setPhone(phone);
+        user.setJpushId(map.get("jpushId"));
+        user.setSource(map.get("source"));
+        user.setNickName("app用户" + phone.substring(phone.length() - 4));
+        user.setStatus(1);
+        user.setAvatar("https://cos.his.cdwjyyh.com/fs/20240926/420728ee06e54575ba82665dedb4756b.png");
+        user.setCreateTime(new Date());
+        user.setLastIp(IpUtils.getIpAddr(ServletUtils.getRequest()));
+        String defaultPassword = getUserDefaultPassword();
+        if (StringUtils.isNotBlank(defaultPassword)) {
+            user.setPassword(Md5Utils.hash(defaultPassword));
+        }
+        if (userService.insertFsUser(user) > 0) {
+            return user;
+        }
+        return null;
+    }
+
     private R validateAppLoginCaptcha(String captchaCode, String captchaUuid) {
         if (StringUtils.isEmpty(captchaUuid) || StringUtils.isEmpty(captchaCode)) {
             return R.error("请输入图形验证码");